AT91SAM7X: работа с портом SPI в режиме master |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
В этой статье рассмотрены практические примеры работы с портом SPI (в режиме Master) микроконтроллеров ARM7 компании Atmel (AT91SAM7X128, AT91SAM7X256, AT91SAM7X512) на языке C (в среде программирования IAR Embedded Workbench). Интерфейс SPI широко используется для высокоскоростной связи между микроконтроллерами, работы с картами памяти SD (или SDHC, или MMC), управления внешней периферией и других целей. Например, LCD в некоторых телефонах подключены через SPI, интерфейс программирования ISP для микроконтроллеров AVR и для многих других - все тот же SPI. [Порт SPI микроконтроллеров ARM7] В микроконтроллер ARM7 встроены 2 порта SPI: SPI0 и SPI1, которые имеют одинаковые возможности и программируются одинаково. Порт SPI может передавать данные (слово от 8 до 16 бит длиной) как под программным управлением, так и в режиме DMA. Все сигналы интерфейса SPI (выборка внешнего чипа CS, такты SPCK, выходные данные MOSI, входные данные MISO) генерируются и обрабатываются аппаратно. Данные выдвигаются побитно, при этом возможны одновременно как прием, так и передача данных (полный дуплекс). На рисунке приведена упрощенная блок-схема SPI. Пояснения к упрощенной блок-схеме: серым цветом помечены направления и сигналы, относящиеся к режиму slave SPI (в данной статье не рассматривается). APB - шина Advanced Peripheral Bus, подключенная к MCU (внутренняя мина микроконтроллера). Таблица 10-2. Мультиплексирование ножек кристалла ARM7 для PIO Controller A, относящееся к SPI.
Таблица 10-3. Мультиплексирование ножек кристалла ARM7 для PIO Controller B, относящееся к SPI.
Пояснения к таблицам: Pin № - номера ножек корпуса LQFP100 для микроконтроллеров AT91SAM7X128, AT91SAM7X256, AT91SAM7X512. Организация выборки внешних чипов, подключенных по SPI (slave устройств шины SPI) позволяет делать аппаратную выборку до 4 чипов без применения дешифратора, и до 15 чипов с применением внешнего дешифратора 4x16. Сигнал выборки CS может также генерироваться программно. Интерфейс SPI хорошо описан в даташите, и имеет довольно простой интерфейс программирования (набор регистров SPI). Можно задать набор прерываний по событиям SPI (опустошение регистра передачи, окончание приема слова, ошибка режима и другие события). Для начала работы с SPI нужно запрограммировать ножки чипа на использование периферии SPI, запрограммировать PMC на тактирование периферии SPI, задать скорость, настроить систему выборки внешних чипов и другие параметры. [Управление выборкой внешних чипов (slave-устройства SPI)] Чип ARM7 имеет 4 вывода, которые могут быть запрограммированы как аппаратные выходы для управления периферийными slave-устройствами на шине SPI (NPCS0/NSS, NPCS1, NPCS2, NPCS3). Выборка slave-устройств SPI (аппаратное управление сигналами CS) настраивается через регистры SPI_MR (битовые поля PS, PCSDEC, PCS, DLYBCS) и SPI_CSRx (поля CSAAT, DLYBS, DLYBCT). Дополнительное поведение сигнала выборки настраивается также через регистры SPI_CR (бит LASTXFER) и SPI_TDR (бит LASTXFER, поле PCS). В организации выборки возможны следующие варианты: 1. Выбор фиксированной периферии, когда SPI_MR.PS=0, SPI_MR.PCSDEC=0. В этом случае на ножках NPCS0, NPCS1, NPCS2, NPCS3 устанавливается логический сигнал в соответствии с полем PCS регистра SPI_MR. Для поля SPI_MR.PCS допустимы значения 14 (0xE, 1110b, активна выборка NPCS0), 13 (0xD, 1101b, активна выборка NPCS1), 11 (0xB, 1011b, активна выборка NPCS2), 7 (0x7, 0111b, активна выборка NPCS3). Поле PCS регистра передачи SPI_TDR при этом не используется. Вариант фиксированной периферии применяется, когда нужно вести передачу в режиме DMA только для одного чипа (SPI_TDR.PCS при этом использовать нельзя). Для того, чтобы запустить передачу DMA для другого чипа, нужно переустановить поле SPI_MR.PCS. 2. Выбор переменной периферии, когда SPI_MR.PS=1, SPI_MR.PCSDEC=0. Этот режим позволяет работать без DMA одновременно с несколькими slave-устройствами на шине SPI. Поле SPI_MR.PCS теперь не используется для активизации сигналов выборок, а применяется поле PCS регистра передачи, SPI_TDR.PCS. Для поля SPI_TDR.PCS допустимы значения 14 (0xE, 1110b, активна выборка NPCS0), 13 (0xD, 1101b, активна выборка NPCS1), 11 (0xB, 1011b, активна выборка NPCS2), 7 (0x7, 0111b, активна выборка NPCS3). 3. Выбор фиксированной периферии с внешним дешифратором, подключенным к ножкам NPCS0 .. NPCS3, когда SPI_MR.PS=0, SPI_MR.PCSDEC=1. Этот режим позволяет выбрать любое из 15 slave-устройств, подключенных параллельно к шине SPI. Для выборки используется значение из поля SPI_MR.PCS, допустимы значения от 0 до 14. Поле PCS регистра передачи SPI_TDR при этом не используется. Этот вариант также, как и вариант 1, может использоваться для передачи по шине SPI через DMA. 4. Выбор переменной периферии с внешним дешифратором, подключенным к ножкам NPCS0 .. NPCS3, когда SPI_MR.PS=1, SPI_MR.PCSDEC=1. Этот режим позволяет выбрать любое из 15 slave-устройств, подключенных параллельно к шине SPI. Для выборки используется значение из поля SPI_TDR.PCS, допустимы значения от 0 до 14. Поле PCS регистра передачи SPI_MR при этом не используется. Этот вариант также, как и вариант 2, может использоваться для передачи по шине SPI без использования DMA. На длительность сигнала выборки влияют следующие регистры и их поля: SPI_MR.DLYBCS это поле влияет на задержку передачи для всех режимов формирования выборок (режимы 1..4). Имеет смысл использовать при наличии нескольких slave-устройств на шине SPI. Программирование этого поля позволяет задать время задержки между отказом от выбора одного slave-устройства в пользу выбора другого, что гарантирует отсутствие конфликтов на шине SPI. Т. е. задержка вставляется только при переходе с одного slave-устройства на другое. Если поле DLYBCS меньше или равно 6, то время задержки будет установлено в значение 6 периодов MCK (или 6*N периодов MCK, если установлено поле SPI_MR.FDIV). В противном случае задержка вычисляется по формуле DLYBCS/MCK (если FDIV=0) или (DLYBCS*N)/MCK (если FDIV=1). SPI_CSRx.DLYBS это поле программируется отдельно для каждой ножки NPCSx (NPCS0 .. NPCS3), и влияет на задержку между спадом на ножке NPCSx (активизация выборки) и появлением тактов SPCK. Если поле DLYBS=0, то время задержки составляет полпериода частоты SPCK. В противном случае время задержки вычисляется по формуле DLYBS/MCK (если FDIV=0) или (32 * DLYBCS)/MCK (если FDIV=1). SPI_CSRx.DLYBCT это поле программируется отдельно для каждой ножки NPCSx (NPCS0 .. NPCS3), и влияет на задержку между между двумя отдельными передачами, приходящимися на одно и то же slave-устройство. Когда поле DLYBCT=0, то никакой задержки нет, и синхросигнал SPCK сохраняет свой рабочий цикл, который был во время передачи. Иначе время задержки определяется уравнением (32 * DLYBCT)/MCK + SCBR/(2 * MCK) (если FDIV=0), или уравнением (32 * 32 * DLYBCT)/MCK + (32 * SCBR)/(2 * MCK) (если FDIV=1). Внимание: при перенастройке выборки не забывайте о регистрах SPI_CSRx - соответствующий выборке регистр также должен быть корректно настроен, иначе передача и прием будут невозможны (не будут корректно устанавливаться флаги SPIO_SR.TDRE, SPIO_SR.TXBUFE, SPIO_SR.RDRF). [Настройка скорости передачи SPI] Скорость передачи по шине SPI определяется тактовой частотой, выводимой на ножку синхросигнала SPCK. Этот сигнал генерирует master шины SPI, в нашем случае это микроконтроллер ARM7. Частота SPCK влияет на скорость передачи, и определяется полем SPI_MR.FDIV и полем SPI_CSRx.SCBR. В поле SCBR может быть записано значение от 1 до 255, значение 0 запрещено (если записать 0, то поведение программы станет непредсказуемым). Скорость определяется уравнением MCK/SCBR (если FDIV=0), MCK/(32 * SCBR) (если FDIV=1). [Формирование сигнала тактов SPCK] Формат передачи зависит от фазы синхросигнала SPCK по отношению к сигналу данных (MOSI, MISO). Это настраивается полями NCPHA и CPOL регистров SPI_CSRx. Поле SPI_CSRx.CPOL определяет полярность синхросигнала: если SPI_CSRx.CPOL=0, то неактивное состояние SPCK лог. 0, а если SPI_CSRx.CPOL=1, то неактивное состояние SPCK лог. 1. Поле SPI_CSRx.NCPHA определяет фазу синхросигнала. Вместе поля поля CPOL и NCPHA определяют 4 варианта формирования сигнала SPCK. 1. NCPHA=0, CPOL=0. Данные фиксируются по спаду SPCK, меняются по фронту SPCK, начальное состояние SPCK лог. 0. [Передача SPI с использованием DMA / без использования DMA] Когда не используется DMA, то может быть выбран любой вариант настройки периферии (любые значения SPI_MR.PS, SPI_MR.PCSDEC), в зависимости от применяемой схемы подключения slave-устройств, их количества и предпочтений программиста. Когда DMA используется, то возможности по настройке выборки slave-устройств несколько сужаются, если используется несколько slave-устройств на шине SPI. Причина в том, что при передаче одного блока данных через DMA нельзя перенастроить регистр SPI_MR. Так что можно задать режим фиксированной периферии (SPI_MR.PS=0), с декодером или без (SPI_MR.PCSDEC=0 или SPI_MR.PCSDEC=1), тогда буфер PDC можно выбрать 8-битный или 16-битный (в зависимости от выбранного формата передачи). В пределах передачи одного блока через DMA можно вести обмен только с одним slave-устройством, но расход памяти на буферы DMA получается экономичным. Если же выбрать для DMA режим переменной периферии (SPI_MR.PS=1), с декодером или без (SPI_MR.PCSDEC=0 или SPI_MR.PCSDEC=1), то тогда необходим 32-битный буфер DMA, в данных которого заполнены поля выбора slave-устройства и генерации выборки. Это позволяет в пределах одной передачи DMA вести обмен с несколькими slave-устройствами, однако увеличивает накладные расходы по памяти под буферы DMA. Для запуска передачи в режиме DMA также необходима настройка регистров PDC. [Интерфейс пользователя SPI] Под интерфейсом пользователя понимается карта памяти регистров SPI. Через эти регистры осуществляется как обмен данными, так и настройка, управление SPI, получение его состояния и работа с SPI через DMA. У микроконтроллеров AT91SAM7X два интерфейса SPI, и каждому соответствует 2 блока адресного пространства. Для SPI0 это адресное пространство 0xFFFE0000..0xFFFE0124, для SPI1 0xFFFE4000..0xFFFE4124. В таблице ниже перечислены все регистры, краткое описание их назначения, а также смещение в адресном пространстве относительно базового адреса (базовый адрес для SPI0 равен 0xFFFE0000, а для SPI1 0xFFFE4000).
За основу примера взят рабочий код из библиотеки EFSL (работа с картами SD/SDHC/MMC по SPI в однобитном режиме). Используется порт SPI1, данные передаются по 8 бит, выборка формируется в режиме фиксированной периферии, только для одного slave-устройства (стандартная карта памяти SD/SDHC/MMC [1]), DMA и прерывания не используются. Сигнал выборки формируется на каждый передаваемый / принимаемый байт. Рассматривается только низкоуровневый код, который относится к настройке SPI и приему/передаче байта (нет кода настройки режима карты и поддержки протокола). Пример физического подключения карты памяти см. в [1]. Подключение карты памяти SD/SDHC/MMC макетной платы Olimex SAM7-EX256-REV-B. Настройка Перед использованием SPI необходимо настроить, и настройка заключается в программировании контроллера PMC, настройке формирования выборки и настройке скорости SPI. Пример настройки приведен в подпрограмме if_spiInit, которая принимает в качестве параметра базовый адрес интерфейса SPI. Например, для SPI0 базовый адрес равен AT91C_BASE_SPI0 (0xFFFE0000), а для SPI1 базовый адрес равен AT91C_BASE_SPI1 (0xFFFE4000). #define SPI_CSR_NUM 0 //номер выборки для карты SD/SDHC/MMC, сигнал CS void if_spiInit(hwInterface *iface) Подпрограмма настройки скорости if_spiSetSpeed заполняет поле SCBR регистра SPI_CSR0 с проверкой на корректность входного параметра SCBRval. void if_spiSetSpeed (u8 SCBRval) Передача и прием Передача и прием происходят одновременно, поскольку SPI дуплексный (у него отдельные линии для приема и передачи, MISO и MOSI). Передача и прием осуществляются вызовом функции if_spiSend. На входе у неё базовый адрес интерфейса SPI (iface), передаваемый байт outgoing, а на выходе функция возвращает принятый байт. u8 if_spiSend(hwInterface *iface, u8 outgoing) В Примере 1 выборка slave-устройства генерируется аппаратно на каждый посылаемый байт (передается блок данных из 4 байт 00 FF 0F 0F): Иногда необходимо, чтобы выборка slave-устройства (NPCSx) происходила не на каждый передаваемый байт, а становилась активной в течение всего передаваемого блока данных (массива байт). Для этого есть несколько возможностей. В этом примере показано, как управлять выборкой программно. Для этого нужно перезадать определение ножки PIN_SPI1_CS из предыдущего примера на обычный порт ввода вывода, а не на подключение к периферии (PIO_PERIPH_A надо заменить на PIO_OUTPUT_1): //#define PIN_SPI0_CS {1 << 12, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} При передаче блока данных нужно теперь из кода программировать логическое состояние ножки выборки. В примере кода ниже показано, как программно управлять выборкой (SPI1_NPCS0). В Gameduino передается блок данных из буфера buffer (определения портов, код инициализации SPI остаются без изменений). //выборку опускаем в 0 перед отправкой блока байт PIO_Clear(&pinsGAMEDUINO[3]); for (int idx=0; idx<sizeof(buffer); idx++) { //ожидание завершения передачи while( !( pSPI->SPI_SR & AT91C_SPI_TDRE ) ); //запуск передачи байта pSPI->SPI_TDR = buffer[idx]; //ожидание завершения приема while( !( pSPI->SPI_SR & AT91C_SPI_RDRF ) ); //считывание полученного байта incoming = (u8)( pSPI->SPI_RDR ); } //возвращаем выборку в 1 по окончании передачи блока байт PIO_Set(&pinsGAMEDUINO[3]); В результате получится такая осциллограмма (передается блок данных из 4 байт 00 FF 0F 0F, сравните с осциллограммой из Примера 1): В этом примере 8-битные посылки данных передаются единым блоком с использованием DMA. Преимущество использования DMA в следующем: можно написать программу так, что во время передачи микроконтроллер сможет заниматься другой работой, что значительно ускоряет обработку данных. В этом примере для упрощения используется просто ожидание окончания передачи путем опроса бита статуса SPI_SR.TXBUFE, хотя в реальной задаче лучше использовать прерывание для сигнализации об окончании передачи блока данных. Выборка slave-устройства может генерироваться как программно, так и аппаратно (по флажку useCSHW). //набор ножек SPI для аппаратного управления выборкой
#define PINS_GAMEDUINO_CSHW PIN_SPI0_MISO, PIN_SPI0_MOSI, PIN_SPI0_SPCK, PIN_SPI0_CSHW
const Pin pinsGAMEDUINO_CSHW[] = {PINS_GAMEDUINO_CSHW}; //набор ножек SPI для программного управления выборкой
#define PINS_GAMEDUINO_CSSW PIN_SPI0_MISO, PIN_SPI0_MOSI, PIN_SPI0_SPCK, PIN_SPI0_CSSW
const Pin pinsGAMEDUINO_CSSW[] = {PINS_GAMEDUINO_CSSW}; //буфер DMA для передачи u32 TXSPIDMAbuf[(4+ZXSPECTRUM_SCREEN_SIZE)]; void init_spi_Gameduino (void) { if (useCSHW) PIO_Configure(pinsGAMEDUINO_CSHW, PIO_LISTSIZE(pinsGAMEDUINO_CSHW)); else PIO_Configure(pinsGAMEDUINO_CSSW, PIO_LISTSIZE(pinsGAMEDUINO_CSSW)); AT91C_BASE_PMC->PMC_PCER = ( 1 << GAMEDUINO_ID_SPI ); // Запрет SPI GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIDIS; // Инициализация контроллера PDC (DMA) для SPI: // запрет PDC TX и RX GAMEDUINO_BASE_SPI->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS; // инициализация счетчиков и указателей на буфер в 0 // "следующий" буфер TX GAMEDUINO_BASE_SPI->SPI_TNPR = 0; GAMEDUINO_BASE_SPI->SPI_TNCR = 0; // "следующий" буфер RX GAMEDUINO_BASE_SPI->SPI_RNPR = 0; GAMEDUINO_BASE_SPI->SPI_RNCR = 0; // буфер TX GAMEDUINO_BASE_SPI->SPI_TPR = 0; GAMEDUINO_BASE_SPI->SPI_TCR = 0; // буфер RX GAMEDUINO_BASE_SPI->SPI_RPR = 0; GAMEDUINO_BASE_SPI->SPI_RCR = 0; // Разрешение SPI и его сброс // "Кажется, что у машины состояний для revB версии должно быть // 2 программных сброса SPI, сброс прошел успешно." GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SWRST; GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SWRST; GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIEN; // Режим SPI: master, FDIV=0, запрет детектирования ошибки конфигурации. GAMEDUINO_BASE_SPI->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS; // Установка регистра выборки чипа: // 8 бит на передачу, CPOL=1, ClockPhase=0, DLYBCT = 0 GAMEDUINO_BASE_SPI->SPI_CSR[SPI_CSR_NUM] = AT91C_SPI_CPOL | AT91C_SPI_BITS_8; // Разрешение работы SPI GAMEDUINO_BASE_SPI->SPI_CR = AT91C_SPI_SPIEN; } void send_block_to_Gameduino (void *buf, int bytes) { int idx; init_spi_Gameduino(); LED(1); if (!useCSHW) PIO_Clear(&pinsGAMEDUINO_CSSW[3]); //передача if (GAMEDUINO_BASE_SPI->SPI_MR & AT91C_SPI_PS) { //Подготовка буфера DMA, если SPI_MR.PS=1 (режим переменной периферии) for (idx=0; idx<(bytes-1); idx++) { TXSPIDMAbuf[idx] = ((u8*)buf)[idx]; } TXSPIDMAbuf[idx] = ((u8*)buf)[idx] | AT91C_SPI_LASTXFER; while (0 == SPI_WriteBuffer(GAMEDUINO_BASE_SPI, TXSPIDMAbuf, bytes)); } else while (0 == SPI_WriteBuffer(GAMEDUINO_BASE_SPI, buf, bytes)); //ожидание окончания передачи while (0 == (GAMEDUINO_BASE_SPI->SPI_SR & AT91C_SPI_TXBUFE)); if (!useCSHW) PIO_Set(&pinsGAMEDUINO_CSSW[3]); LED(0); close_spi_Gameduino(); } //------------------------------------------------------------------------------
/// Посылает содержимое буфера buffer через периферийное устройство SPI,
/// используя PDC для обслуживания транзакции.
/// \param spi указатель на экземляр AT91S_SPI.
/// \param buffer указатель на отправляемый буфер.
/// \param length длина данных в буфере.
//------------------------------------------------------------------------------
unsigned char SPI_WriteBuffer(AT91S_SPI *spi, void *buffer, unsigned int length) { // Проверка, пуст ли первый банк: if (spi->SPI_TCR == 0) { spi->SPI_TPR = (unsigned int) buffer; spi->SPI_TCR = length; spi->SPI_PTCR = AT91C_PDC_TXTEN; return 1; } // Проверка, пуст ли второй банк: else if (spi->SPI_TNCR == 0) { spi->SPI_TNPR = (unsigned int) buffer; spi->SPI_TNCR = length; return 1; } // Нет свободных банков: return 0; }
Обратите внимание, что в случае выбора переменной периферии SPI_MR.PS=1 для DMA используется специально подготовленный 32-битный буфер. В последнюю ячейку буфера записывается признак SPI_TDR.LASTXFER, который нужен для аппаратного возврата сигнала выборки в неактивное состояние. На первой осциллограмме DMA работает с программно формируемой выборкой, используется 8-битный буфер DMA (передается блок данных из 4 байт 00 FF 0F 0F): На второй осциллограмме DMA работает вместе с аппаратно формируемой выборкой, для этого используется 32-битный буфер DMA (передается блок данных из 4 байт 00 FF 0F 0F): [Словарик] CS сигнал выборки устройства на шине SPI. Если это вход, то для микроконтроллера ARM7 он называется также NSS (в этой статье не рассматривается), а если выход, то NPCSx (NPCS0, NPCS1, NPCS2, NPCS3). DMA аббревиатура от Direct Memory Access, что означает "прямой доступ к памяти". Имеется в виду, что периферия (в нашем случае это интерфейс SPI) получает доступ к памяти без участия вычислительного ядра ARM, что разгружает ядро для других задач и ускоряет работу firmware. См. также PDC. MMC MultiMedia Card - типоразмер стандартных карт памяти (обычно емкостью до 512 мегабайт). Это уже устаревший тип карт, который не выпускается. Он физически совместим с форматом SD снизу вверх, т. е. карту MMC можно вставить в слот для карт SD, но не наоборот (карта MMC тоньше, имеет толщину 1.45 мм, а карта SD имеет толщину 2.1 мм). См. также SD, SDHC. MISO Master Input Slave Output провод шины SPI, вход главного устройства (master), выход подчиненного (slave). MOSI Master Output Slave Input провод шины SPI, выход главного устройства (master), вход подчиненного (slave). PMC Power Management Controller - контроллер управления питанием, который находится внутри чипа ARM7. Название немного неочевидное, поскольку на самом деле PMC отвечает за включение/выключение тактирования периферии (в нашем случае PMC необходимо настроить, чтобы заработало тактирование периферии SPI). PDC Peripheral DMA Controller, контроллер прямого доступа к памяти. См. также DMA. SD карты Security Digital (иногда их называют SDC, Security Digital Card) - типоразмер стандартных карт памяти размером до 2 гигабайт включительно. Карты SD и SDHC имеют одинаковый типоразмер. См. также MMC, SDHC. SDHC карты Secure Digital High Capacity - типоразмер стандартных карт памяти повышенной емкости (до 32 гигабайт включительно). Карты SD и SDHC имеют одинаковый типоразмер. См. также MMC, SD. SPI Serial Peripheral Interface, популярная последовательная шина для обмена данными. Шина имеет архитектуру точка-точка, т. е. на шине в любой момент времени может присутствовать только 2 активных участника обмена данными - главное устройство (SPI master) и подчиненное устройство (SPI slave). Между master и slave возможен дуплексный обмен данными. SPI_CR SPI Control Register - регистр управления SPI. SPI_MR SPI Mode Register - регистр режима SPI. SPI_CSRx SPI Chip Select Register - регистр выбора чипа SPI. Имеется 4 регистра SPI_CSRx: SPI_CSR0, SPI_CSR1, SPI_CSR2, SPI_CSR3. SPCK тактовый сигнал шины SPI, генерируется устройством master (в нашем случае это микроконтроллер ARM7). [Ссылки] 1. Как использовать карты памяти MMC/SDC. |