Продолжая цикл публикаций по микроконтроллерам на ядре Cortex-M0 компании Megawin (см. предыдущие статьи 1, 2, 3, 4, 5, 6 и 7), сегодня рассмотрим модуль интерфейса SPI в роли ведущего и ведомого, а также: Ethernet-контроллер ENC28J60 в качестве внешнего slave-устройства, контроллер DMA для высокоскоростного обмена данными по шине SPI, контроллер прерываний EXIC для генерации прерывания по сигналу с внешнего устройства, модуль GPL для аппаратного вычисления CRC32 кадров Ethernet. Отметим, что ранее рассмотренные модули URT0, URT1 и URT2 также могут работать в качестве портов SPI в роли ведущего и ведомого, но управляются они иначе. Микроконтроллеры серии MG32F02 включают один модуль интерфейса SPI0, имеющий следующие функциональные особенности: работа в режиме ведущего (master) или ведомого (slave); режимы передачи данных: полный дуплекс, полудуплекс, симплекс; программное или аппаратное управление сигналом NSS (Chip Select); работа без сигнала NSS; поддержка режима multi-master; частота шины до 24 МГц в режиме ведущего, до 16 МГц в режиме ведомого; передача данных по обоим фронтам сигнала SCK (удвоенная скорость передачи); выбор полярности и фазы сигнала SCK; выбор порядка бит в кадре: MSB или LSB; настраиваемый размер одного кадра от 4 до 32 бит; буферы приема и передачи размером 4 байта; конфигурация линий передачи: стандартный дуплексный режим SPI, двунаправленный одно-, двух-, четырех- и восьмиразрядный режим; поддержка восьми типов микросхем flash-памяти с интерфейсом SPI; поддержка DMA; работа в режимах питания ON и SLEEP. Восьмиразрядный режим передачи не реализован в МК MG32F02A032. Функциональная схема модуля SPI0 приведена на рисунке. Модуль включает следующие основные узлы: схему тактирования, схему управления выводами «IO Control», блок формирования сигнала SCK шины SPI «Clock Control», блок управления буферами передачи и приема «TX/RX Control», блок формирования сигнала выбора кристалла «NSS Timing Control», блок управления режимами «Master/Slave Control», регистр для приема данных «Receive Data Register», регистр для отправки данных «Transmit Data Register», блок детектирования ошибок «Error Detector», блок формирования сигнала прерывания «Interrupt Control». Общее тактирование модуля осуществляется сигналом CK_SPI0_PR (Process Clock) от подсистемы тактирования МК. Источник сигнала определяется битом CSC_CKS1.CSC_SPI0_CKS из сигналов CK_APB (0) или CK_AHB (1). Для формирования основного синхронизирующего сигнала шины SPI используется внутренний сигнал CK_SPI0_INT, формируемый из сигнала CK_SPI0. Источником сигнала CK_SPI0 в зависимости от значения поля SPI0_CLK.SPI0_CK_SEL может быть сигнал CK_SPI0_PR или выходной сигнал таймера TM00_TRGO. Сигнал CK_SPI0 поступает на первый делитель DIV с коэффициентом 1, 2, 4 или 8, выбираемым в поле SPI0_CLK.SPI0_CK_PDIV. Далее сигнал поступает на предделитель с выбираемым в трехразрядном поле SPI0_CLK.SPI0_CK_PSC коэффициентом деления из диапазона 1-8. Итоговый сигнал CK_SPI0_INT получается на выходе второго делителя DIV с коэффициентом 2, 4, 8 или 16, выбираемым в поле SPI0_CLK.SPI0_CK_DIV. Частота FINT сигнала CK_SPI0_INT определяется выражением FINT = F(CK_SPI0) / [ PDIV·(PSC + 1)·DIV ], где F(CK_SPI0) — частота сигнала CK_SPI0, а PDIV, PSC и DIV — значения соответствующих полей регистра SPI0_CLK. Частота FSCK синхронизирующего сигнала SCK шины SPI соответствует частоте FINT и не должна превышать половины F(CK_SPI0) в режиме ведущего и 1/4 F(CK_SPI0) в режиме ведомого. Максимальное значение FSCK в режиме ведущего составляет 24 МГц для МК MG32F02A032 и 22 МГц для остальных МК. Максимальное значение FSCK в режиме ведомого составляет 16 МГц для всех актуальных МК серии. Основные параметры конфигурации модуля SPI0 собраны в регистре SPI0_CR0. Модуль SPI0 включается установкой бита SPI0_EN. Режим синхронизации по сигналу SCK настраивается следующими разрядами: SPI0_CPOL — исходное состояние сигнала SCK: 0 - низкий уровень, 1 - высокий уровень; SPI0_CPHA — фаза тактового сигнала SCK: 0 - передний фронт (нарастающий при CPOL=0, спадающий при CPOL=1), 1 - задний фронт (нарастающий при CPOL=1, спадающий при CPOL=0). По-умолчанию, данные в кадре передаются начиная со старшего бита (MSB). При установке бита SPI0_LSB_EN данные передаются начиная с младшего бита (LSB). Общий режим работы в роли ведущего или ведомого определяется полем SPI0_MDS: 0 — модуль работает в роли ведомого устройства (Slave), 1 — модуль работает в роли ведущего устройства (Master). Дополнительные параметры конфигурации собраны в регистре SPI0_CR2, рассмотрим некоторые из них. При установке бита SPI0_DTR_EN в режиме ведущего включается режим удвоенной скорости передачи по обоим фронтам синхросигнала SCK. При установке бита SPI0_BDIR_OE включается двунаправленный режим обмена данными по одной линии интерфейса. Поле SPI0_DAT_LINE определяет число линий передачи данных и их режим работы: 0 — стандартный дуплексный режим SPI с линиями MOSI и MISO; 1 — полудуплексный режим с одной двунаправленной линией MOSI; 2 — полудуплексный режим с двумя двунаправленными линиями D0(MOSI) и D1(MISO); 3 — полудуплексный режим с четырьмя двунаправленными линиями D0-D3; 4 — полудуплексный режим с четырьмя двунаправленными линиями D0-D3, дублированными на линии D4-D7 (режим отсутствует в МК MG32F02A032); 5 — полудуплексный режим с восемью двунаправленными линиями D0-D7 (режим отсутствует в МК MG32F02A032). В 5-битовом поле SPI0_DSIZE задается размер одного кадра для передачи в битах. Поле целиком располагается в 2-м байте регистра SPI0_CR2, что удобно для быстрого изменения в процессе передачи. Бит SPI0_TXUPD_EN разрешает прямое обновление промежуточного буфера по данным регистра SPI0_TDAT (или SPI0_TDAT3) в режиме ведомого. На следующем рисунке показан общий алгоритм работы буферов приема и передачи данных модуля SPI. Принимаемые данные из входного регистра сдвига (RX Shift Buffer) передаются в промежуточный 32-разрядный буфер приема (RX Shadow Buffer). Число полученных байт обновляется в поле SPI0_STA.SPI0_RX_LVL и всегда доступно по чтению. В режиме ведущего после заполнения промежуточного буфера данные копируются в 32-разрядный регистр SPI0_RDAT, после чего активируется флаг RXF, сигнализируя о их готовности к считыванию. В режиме ведомого в поле SPI0_CR2.SPI0_RX_TH задается пороговое число байт для приема. При накоплении указанного числа байт в промежуточном буфере они копируются в регистр SPI0_RDAT, после чего активируется флаг RXF. Число скопированных байт отображается в поле SPI0_STA.SPI0_RNUM. После считывания данных из регистра SPI0_RDAT флаг RXF автоматически сбрасывается. Если данные из регистра SPI0_RDAT не были востребованы программой к моменту следующего копирования из промежуточного буфера, генерируется событие переполнения буфера приема Receive overrun и активируется флаг ROVRF. Данные для передачи записываются программой в регистр SPI0_TDAT. Разрядность операции записи в регистр должна соответствовать объему передаваемых данных: для записи одного байта нужно использовать байтовую пересылку (STRB) в младший байт регистра, для двух байт — двухбайтовую (STRH), для 4 байт — пересылку слова (STR). Для передачи 3 байт следует использовать отдельный регистр SPI0_TDAT3. После записи данных программой они копируются в промежуточный 32-битный буфер передачи (TX Shadow Buffer). После этого активируется флаг TXF, сигнализируя о готовности принять следующие данные для отправки через регистр SPI0_TDAT или SPI0_TDAT3. При очередной записи в эти регистры флаг TXF автоматически сбрасывается. Каждый байт из промежуточного буфера копируется в выходной регистр сдвига (TX Shift Buffer) и под действием синхроимпульсов сигнала SCK передается в линию интерфейса. Число байт, которые еще не переданы, отображается в поле SPI0_STA.SPI0_TX_LVL. После фактической отправки всех данных из промежуточного буфера генерируется событие Transmission complete и активируется флаг TCF. Это событие можно использовать, например, при полудуплексном обмене. При установке бита SPI0_CR1.SPI0_RDAT_CLR происходит "сброс" буфера приема: сбрасывается промежуточный буфер, устанавливается в 0 значение SPI0_RX_LVL и сбрасывается флаг RXF. При установке бита  SPI0_CR1.SPI0_TDAT_CLR происходит "сброс" буфера передачи: сбрасывается промежуточный буфер, устанавливается в 0 значение SPI0_TX_LVL и сбрасывается флаг TXF. В режиме ведомого возможно принудительное копирование текущих данных из промежуточного буфера в регистр SPI0_CR2.SPI0_RDAT при установке бита SPI0_CR1.SPI0_RSB_TRG. Бит сбрасывается аппаратно после копирования. В обычном дуплексном режиме SPI вывод МК для сигнала MOSI может быть настроен как выход с открытым стоком (поле PxCRn.IOMn=1) или как двухтактный выход (push-pull) (поле PxCRn.IOMn=2). Однако, режим работы вывода будет также определяться значением поля SPI0_CR0.SPI0_DOUT_MDS: 0 (Disabled) — в исходном состоянии и при передаче логической "1" вывод будет находиться в высокоомном состоянии независимо от значения PxCRn.IOMn, для работы потребуется резистор подтяжки; 1 (Enabled) — модуль будет формировать полноценный уровень логической "1", но только при PxCRn.IOMn=2 (push-pull). Настройка вывода МК для сигнала SCK (значение поля PxCRn.IOMn 1 или 2) применяется как обычно. Модуль поддерживает аппаратное (автоматическое) или программное управление сигналом выбора кристалла NSSO. Для использования сигнала необходимо установить бит SPI0_CR0.SPI0_NSSO_EN. В режиме аппаратного управления (SPI0_CR0.SPI0_NSS_PEN=1) сигнал NSSO активируется на всё время передачи одного кадра указанного размера (см. поле SPI0_CR2.SPI0_DSIZE). В режиме программного управления (SPI0_CR0.SPI0_NSS_PEN=0, SPI0_CR0.SPI0_NSS_SWEN=1) сигнал управляется изменением бита SPI0_CR2.SPI0_NSS_SWO. Бит SPI0_CR0.SPI0_NSSO_INV отвечает за включение инверсии сигнала NSSO. Длительность неактивного состояния сигнала NSSO определяется в поле SPI0_CR1.SPI0_NSS_IDT: 1 такт (0) или 2 такта (1). В режиме ведущего устройства передача данных по линии MOSI начинается сразу после того, как программа записала данные в регистр SPI0_TDAT или SPI0_TDAT3, при этом начинают формироваться импульсы сигнала SCK согласно настройкам SPI0_CPOL и SPI0_CPHA. Передачу данных необходимо производить и для приема данных, который происходит по линии MISO одновременно с передачей под действием тех же импульсов сигнала SCK. Таким образом, чтобы прочитать кадр заданной длины (например, 8 бит) ведущему нужно сначала отправить кадр этой длины и после этого по активации флага RXF прочитать данные из регистра SPI0_RDAT. Этот же флаг можно использовать наравне с TCF для определения завершения фактической передачи кадра. В режиме DMA синхроимпульсы формируются автоматически при приеме заданного объема данных. В обычном дуплексном режиме SPI для ведомого устройства вывод MISO может быть настроен как выход с открытым стоком (поле PxCRn.IOMn=1) или как двухтактный выход (push-pull) (поле PxCRn.IOMn=2) в зависимости от наличия резистора подтяжки. Остальные выводы MOSI, SCK и NSS (NSSI) должны быть настроены как цифровые входы. Модуль поддерживает аппаратное управление с помощью сигнала выбора кристалла SPI0_NSS_IN, в качестве источника которого можно выбрать вывод NSS (SPI0_NSSI_SEL=0) или NSSI (SPI0_NSSI_SEL=1). Для использования сигнала управления необходимо установить бит SPI0_CR0.SPI0_NSSI_EN. Бит SPI0_CR0.SPI0_NSSI_INV отвечает за включение инверсии сигналов с выводов NSS и NSSI. В модуле возможно и программное управление при установке бита SPI0_CR0.SPI0_NSSI_SWEN. В этом случае сигнал SPI0_NSS_IN управляется изменением бита SPI0_CR2.SPI0_NSS_SWI. Если программное управление отключено (SPI0_NSSI_SWEN=0), этот бит можно использовать в качестве бита статуса внешнего сигнала SPI0_NSS_IN. В режиме ведомого программе необходимо реагировать на флаг RXF и без задержки считывать данные из регистра SPI0_RDAT. Если после приема требуется отправка данных ведущему, их нужно как можно раньше поместить в буфер передачи. Однако первый байт данных копируется в промежуточный буфер на отправку еще в момент приема последнего бита (такта сигнала SCK) предыдущего кадра, т.е. фактически это может произойти еще до получения команды, на которую ведомому нужно ответить и поместить необходимые данные на отправку. Решает эту проблему включение режима прямого обновления буфера передачи путем установки бита SPI0_CR2.SPI0_TXUPD_EN. В этом режиме при записи в регистр SPI0_TDAT или SPI0_TDAT3 данные также копируются в промежуточный буфер с начальной позиции. Если к моменту отправки очередных данных они еще не были доступны в промежуточном буфере, т.е. не были своевременно записаны в регистр SPI0_TDAT или SPI0_TDAT3, генерируется событие Slave mode transmit underrun и активируется флаг TUDRF. При этом на линии MISO формируется высокий уровень и под действием синхроимпульсов SCK ведущий начинает считывать единицы и получать байты 0xFF (если был настроен 8-битный режим). Флаги событий модуля SPI0 собраны в регистре SPI0_STA, а биты разрешения прерываний — в регистре SPI0_INT. Перечень событий и соответствующих флагов прерываний приведен в следующей таблице. Разряд SPI0_STA Флаг события Бит прерывания Название события Описание 0 BUSYF - Data Transfer Busy Flag Модуль занят передачей данных 3 IDLF IDL_IE Slave mode NSS idle detect Состояние Idle в режиме Slave 4 TCF TC_IE Transmission complete Передача данных из регистра сдвига завершена 5 RXDF - Received data byte number is different Число принятых байт в регистре SPI0_RDAT отличается от предыдущего приема 6 RXF RX_IE Receive data register not empty Принятые данные готовы к чтению 7 TXF TX_IE Transmit data register empty Буфер передачи готов к записи новых данных 8 MODF MODF_IE SPI mode detect fault Активация сигнала NSS со стороны внешнего устройства в режиме Master 9 WEF WE_IE Slave mode write error В режиме Slave ведущий прекратил чтение деактивацией сигнала NSS, не завершив операцию 10 ROVRF ROVR_IE Receive overrun Переполнение буфера приема 11 TUDRF TUDR_IE Slave mode transmit underrun Буфер передачи не содержит данных в режиме Slave 31 IDL_STA - Idle state detect status for Slave with NSS mode Обнаружено состояние Idle в режиме Slave с включенным управлением от сигнала NSS Кроме флагов событий, регистр SPI0_STA содержит три 3-разрядных поля: SPI0_RX_LVL — число несчитанных байт из буфера приема (только чтение); SPI0_TX_LVL — число байт в буфере, ожидающих отправки (только чтение); SPI0_RNUM — число принятых байт, переданных из промежуточного буфера в регистр SPI0_RDAT (чтение и запись). Схема формирования сигнала прерывание INT_SPI0 модуля SPI0 приведена на следующем рисунке. Для включения генерации прерывания необходимо: Выбрать событие (или события) в регистре SPI0_INT. Разрешить прерывание самого модуля установкой бита SPI0_INT.SPI0_IEA. Разрешить прерывание IRQ#24 от модуля SPI0 в контроллере прерываний NVIC в регистре CPU_ISER. Контроллер DMA имеется во всех МК серии, но включает разное число каналов: в МК MG32F02A032 — один канал, в остальных МК — пять каналов. Модуль DMA включает следующие общие регистры: Регистр Назначение DMA_STA Флаги состояния всех каналов, по 4 флага на канал, расположены начиная с 0-го разряда DMA_INT Регистр включает единственный используемый бит DMA_IEA для разрешения общего прерывания INT_DMA DMA_CR0 Регистр включает общие поля настройки, а также дублирует биты включения каналов из регистров DMA_CHnA Каждый канал включает следующие восемь регистров (n — номер канала 0-4): Регистр Назначение DMA_CHnA Основные поля управления каналом, биты разрешения прерывания, флаги состояния DMA_CHnB Дополнительные поля настройки канала, включая коды источника и приемника данных DMA_CHnNUM Объем передаваемых данных, начальное значение (байт) (биты 0-16) DMA_CHnCNT Объем оставшихся для передачи данных, текущее значение (байт) (биты 0-16) DMA_CHnSSA Адреса начала области памяти источника DMA_CHnSCA Текущий адрес передачи в источнике DMA_CHnDSA Адреса начала области памяти приемника DMA_CHnDCA Текущий адрес передачи в приемнике В следующей таблице представлены возможные источники и приемники данных: Код для полей CHn_SRC и CHn_DET Источник Приемник  MG32F02A032 MG32F02x064 MG32F02x128 MG32F02A032 MG32F02x064 MG32F02x128 0 MEM_Read MEM_Read MEM_Write MEM_Write 1 ADC0_IN ADC0_IN - DAC_OUT 2 I2C0_RX I2C0_RX I2C0_TX I2C0_TX 3 - I2C1_RX - I2C1_TX 4 URT0_RX URT0_RX URT0_TX URT0_TX 5 URT1_RX URT1_RX URT1_TX URT1_TX 6 - URT2_RX - URT2_TX 7 - - - - 8 SPI0_RX SPI0_RX SPI0_TX SPI0_TX 9 - - - - 10 - USB_RX - USB_TX 11 - - GPL_Write GPL_Write 12 - - TM36_CC0B TM36_CC0B 13 - - TM36_CC1B TM36_CC1B 14 - - TM36_CC2B TM36_CC2B 15 TM36_IC3 TM36_IC3 - - При настройке заданного канала n нужно выполнить по крайней мере следующее: включить канал установкой бита DMA_CHnA.DMA_CHn_EN; установить код источника данных в поле DMA_CHnB.DMA_CHn_SRC; если источник — память (MEM_Read), в регистре DMA_CHnSSA установить адрес начала области памяти источника; установить код приемника данных в поле DMA_CHnB.DMA_CHn_DET; если приемник — память (MEM_Write), в регистре DMA_CHnDSA установить адрес начала области памяти приемника; установить объем пересылаемых данных (байт) в регистре DMA_CHnNUM; установить размер единицы передаваемой информации (1, 2 или 4 байта) в поле DMA_CHnA.DMA_CH0_BSIZE. Запуск пересылки данных по каналу DMA начинается сразу после установки бита DMA_CHnA.DMA_CHn_REQ. В модуле DMA поддерживаются следующие события и флаги (см. регистр DMA_CHnA): Флаг события Бит прерывания Название события Описание GIF - Global interrupt flag Установлен один из нижеперечисленных флагов TCF DMA_CHn_CIE Transfer complete flag Пересылка данных завершена THF DMA_CHn_HIE Transfer half flag Передана половина данных ERRF DMA_CHn_EIE Transfer error flag Ошибка пересылки данных Для включения генерации прерывания необходимо: Выбрать событие (или события) в байте 2 регистра DMA_CHnA (n — номер канала). Разрешить прерывание самого модуля установкой бита DMA_INT.DMA_IEA. Разрешить прерывание IRQ#8 от модуля DMA в контроллере прерываний NVIC в регистре CPU_ISER. При использовании DMA совместно с модулем SPI0 необходимо: Выполнить настройку модуля SPI0. Выполнить настройку заданного канала DMA. Если требуется пересылка из модуля SPI0, установить бит SPI0_CR0.SPI0_DMA_RXEN непосредственно перед стартом DMA. Если требуется пересылка в модуль SPI0, установить бит SPI0_CR0.SPI0_DMA_TXEN непосредственно перед стартом DMA. Ожидать завершение пересылки (срабатывания флага TCF) на выбранном канале. После запуска операции пересылки по каналу DMA флаги событий модуля SPI TCF, RXF и TXF маскируются, так что применение блокирующих функций с опросом этих флагов (например, из библиотеки enc28j60.c) становится невозможным до завершения операции DMA. Экспериментально установлено, что после завершения операции DMA по отправке данных в SPI работа флагов может не восстановиться. Проблему помогает решить сброс всего модуля SPI0 (настройки модуля при этом сохраняются): Для своевременного реагирования на события внешних устройств вместе с линиями интерфейса SPI, как правило, требуется отдельная линия для сигнала внешнего прерывания от устройства. Рассмотрим схему формирования сигнала внешнего прерывания модуля EXIC (общая структура контроллера EXIC рассмотрена в первой статье): Каждый вывод с номером n каждого порта x подключен к триггерной схеме управления флагом прерывания, функция которой определяется в поле EXIC_Pxn_TRGS регистра EXIC_Px_TRGS: 0 — вывод не приводит к генерации прерывания, 1 — срабатывание по уровню, 2 — срабатывание по фронту, 3 — срабатывание по обоим фронтам. При выборе функции 1 если бит инверсии Px_CRn.Px_INVn в настройке вывода сброшен, активный уровень сигнала будет низкий, если бит инверсии установлен — высокий. При выборе функции 2 если инверсия выключена, активным будет спадающий фронт, если инверсия включена — нарастающий. Все выходы триггерных схем данного порта могут объединяться по схеме «И» (AND) или по схеме «ИЛИ» (OR) для формирования итогового сигнала прерывания INT_Px порта. Подключение выходов к данным схемам определяется масками в регистре EXIC_Px_MSK. Модуль EXIC включает следующие общие регистры: Регистр Назначение EXIC_STA Флаги прерываний каждого порта Px отдельно для схемы «И» EXIC_Px_AF и схемы «ИЛИ» EXIC_Px_OF EXIC_INT Биты разрешения прерывания EXIC_Px_IEA для каждого порта Px отдельно EXIC_CR0 Дополнительные поля настройки, включая поля управления NMI и разрешения инверсии сигналов прерывания Кроме того, имеются регистры EXIC_SRC0-EXIC_SRC7, позволяющие определить, какое из внешних прерываний IRQ сработало. Каждый порт включает следующие три регистра (x — имя порта A-E, n — номер вывода 0-15): Регистр Назначение EXIC_Px_PF Флаги прерывания EXIC_Pxn_PF для каждого вывода порта Px EXIC_Px_TRGS Настройки триггера EXIC_Pxn_TRGS для каждого вывода порта Px (по 2 бита на вывод) EXIC_Px_MSK Маски включения каждого вывода порта Px в схему «И» EXIC_Pxn_AM и схему «ИЛИ» EXIC_Pxn_OM Для настройки внешнего прерывания от вывода n порта Px необходимо: Настроить вывод порта через регистр Px_CRn на нужный режим работы. Настроить режим работы триггера в регистре EXIC_Px_TRGS. Включить вывод в схему «И» или схему «ИЛИ» в регистре EXIC_Px_MSK. Разрешить внешнее прерывание от порта в регистре EXIC_INT. В зависимости от выбранного порта разрешить прерывание IRQ в контроллере прерываний NVIC в регистре CPU_ISER согласно таблице: Порт Обозначение прерывания Номер прерывания IRQ PA EXINT0 3 PB EXINT1 4 PC EXINT2 5 PD, PE EXINT3/EXINT4 6 Все МК серии MG32F02 имеют модуль GPL (General Purpose Logic), с помощью которого можно вычислять контрольные суммы CRC8, CRC16 и CRC32 с наиболее распространенными полиномами. В предлагаемом тестировании модуль используется для вычисления CRC32 кадров Ethernet с целью контроля приема данных по шине SPI. Модуль GPL включает 5 регистров, связанных с функцией вычисления CRC, приведенных в таблице (BE - Big Endian). Регистр Назначение Описание полей (биты) GPL_CR0 Настройка параметров GPL_IN_INV — инверсия входных данных, GPL_BEND16_EN — режим BE для 16-битных данных, GPL_BREV_MDS — перестановка битов в 8-, 16- или 32-разрядных данных, GPL_BEND_EN — режим BE для 32-битных данных GPL_CR1 Включение и настройка параметров GPL_CRC_BREV — перестановка битов в 8-, 16- или 32-разрядных выходных данных, GPL_CRC_DSIZE — размер входных данных (1, 2 или 4 байта), GPL_CRC_MDS — тип контрольной суммы, GPL_CRC_EN — бит включения/сброса функции CRC GPL_DIN Входные данные 1, 2 или 4 байта исходных данных (в зависимости от поля GPL_CR1.GPL_CRC_DSIZE) GPL_DOUT Результат вычисления Контрольная сумма, полученная в результате накопления данных GPL_DIN GPL_INIT Начальное значение Начальное значение суммы (Initial value) Модуль вычисляет следующие типы контрольных сумм (определяется значением поля GPL_CRC_MDS): 0 — CCITT16 (полином 0x1021), 1 — CRC8 (полином 0x07), 2 — CRC16 (полином 0x8005), 3 — CRC32 (полином 0x04C11DB7). Модуль также содержит регистр статуса GPL_STA, включающий биты результата проверки на четность/нечетность. В МК MG32F02x064 и MG32F02x128 имеются дополнительные регистры для выполнения аппаратного деления. Данные регистры в вычислении CRC не участвуют. В стандарте IEEE 802.3 Ethernet каждый кадр снабжается 32-битным полем контрольной суммы CRC32, вычисляемой на основе полинома 0xEDB88320. Примеров реализации алгоритма в сети довольно много. Например, можно встретить такой код: В документации к МК указан полином 0x04C11DB7, который получается перестановкой всех битов (0-й с 31-м, 1-й с 30-м и т.д.). Соответственно для того, чтобы можно было аппаратно вычислить сумму по нужному алгоритму, необходимо: переставить биты в исходных данных (8-, 16- или 32-разрядных), переставить биты в 32-битной сумме, инвертировать результат. Для указанной задачи разработана функция crc32_block8() (файлы ulib.h и ulib.c), выполняющая аппаратное вычисление CRC32 кадров Ethernet: Перед каждым последующим вычислением CRC32 на основе массива данных модуль необходимо сбросить записью 0 в разряд GPL_CR1.GPL_CRC_EN, после чего записать в этот разряд 1. Если этого не сделать, аккумулирование суммы будет продолжаться с текущего значения. Запись в регистр GPL_CRCINIT начального значения не приводит к инициализации алгоритма накопления. Перед использованием модуля GPL необходимо включить его тактирование (функция gpl_init(), файлы init.h init.c): В качестве внешнего SPI устройства в тестировании будем использовать популярный Ethernet-контроллер ENC28J60 компании Microchip, работающий в стандарте 10Base-T. Основными достоинствами микросхемы являются: хорошая функциональность, сравнительно большой встроенный буфер (8 кбайт), простота применения, а также доступность. Из недостатков стоит отметить большое число ошибок, неисправленных вплоть до последних ревизий кристалла, а также значительный потребляемый ток (до 150 мА от источника 3.3 В). Контроллер очень подробно описан в первом приведенном источнике. Рассмотренная в нем библиотека с небольшими доработками под МК серии MG32F02 используется и в наших тестах. Обмен данными по шине SPI ведётся в режиме 0 (CPOL=0, CPHA=0), а также применяется сигнал внешнего прерывания. Микросхема ENC28J60 поддерживает скорость передачи данных по SPI до 10 Мбит/с. В наших тестах мы ограничимся скоростью 2 Мбит/с. Подключение микроконтроллера к локальной сети: работаем с ENC28J60 ENC28J60 Stand-Alone Ethernet Controller (Datasheet) ENC28J60 Rev. B7 Silicon Errata Целью тестирования является проверка работы МК в роли ведущего и ведомого устройства SPI. В качестве ведущего будет применяться МК MG32F02A064AD48, в качестве ведомого — Ethernet-контроллер ENC28J60 и МК MG32F02A032AT20. Схема подключения микроконтроллеров показана на следующем рисунке. Для питания схемы в связи с большим током потребления м/с ENC28J60 (до 150 мА) необходимо использовать отдельный источник питания напряжением 3.3 В. К интерфейсу SPI (J4) ведущего МК U1 подключаются либо сетевой модуль на ENC28J60, либо МК U2 (условно показан как разъем J5). Каждый МК, как и в предыдущих тестах, подключается также к отладочному ПК через интерфейс UART (в обоих случаях используется модуль МК URT0). В сегодняшних тестах будет задействован контроллер DMA. Эксперимент показал, что контроллер DMA не может работать адекватно, если программа выполняется в ОЗУ. Видимо это связано с тем, что для DMA просто не остается свободных временных интервалов, когда можно обращаться в память. Поэтому, оставляя метод разделения "прошивки" на базовую и прикладную части, последнюю мы вынуждены перенести в область flash-памяти. В связи с этим в файле premake5.lua появились следующие цели сборки: svr64_ram — базовая часть для МК MG32F02A064, выполняющая переход в прикладную часть в ОЗУ; svr64_rom — базовая часть для МК MG32F02A064, выполняющая переход в прикладную часть в области flash-памяти; spim — прикладная часть для ведущего устройства SPI (MG32F02A064), spis — прикладная часть для ведомого устройства SPI (MG32F02A032). Соответственно добавлены ld-скрипты mg32f02a064_app_ram.ld и mg32f02a064_app_rom.ld для сборки прикладных частей под ОЗУ и под flash-память. Также добавлены скрипты shell для сборки и запуска (загрузки в ОЗУ или "прошивки" в flash): build_svr64_ram, build_svr64_rom, build_spim, build_spis и run_svr64_ram, run_svr64_rom, run_spim, run_spis. Для операций DMA согласно рекомендациям User Guide выделены последние 2 килобайта области ОЗУ. Для прикладной части во flash устанавливается стартовый адрес 0x00001000. В файле premake5.lua для каждой цели сборки необходимо указать начало области прикладной части в переменной APP_ORIGIN, например: Для взаимодействия с Ethernet-контроллером ENC28J60 в проект добавлена библиотека нижнего уровня ic/enc28j60.c. Высокоуровневые операции вынесены в библиотеку src/ethernet.c. Некоторые действия с модулем SPI0 оформлены в виде функций в файлах src/spi.h и src/spi.c, а с модулем DMA — в файле src/dma.c. Весь исходный код и сопутствующие файлы доступны в репозитории GitHub. Рассмотрим функции из файлов src/spi.h и src/spi.c. Функция spi_init() выполняет инициализацию модуля и настройку тактирования. Выбирается тактовая частота сигнала SCK 2 МГц. Функция spi_setup_mode() устанавливает режим работы модуля согласно формату регистра SPI0_CR0. Для удобства применения функции создано перечисление SPI_Config, включающее некоторые возможные опции (битовые маски). Функция spi_setup_int() разрешает прерывание модуля INT_SPI0 по флагам, указанным в аргументе согласно формату регистра SPI0_INT. Также разрешается прерывание в модуле NVIC. Функция spi_nss() применяется для управления сигналом выбора кристалла NSSO: при значении аргумента "1" сигнал активируется (низкий уровень), при значении "0" — деактивируется (высокий уровень). Функция применяется в ведущем устройстве. Функции spi_flush_rx() и spi_flush_tx() используются для сброса буфера приема и передачи соответственно. Функции spi_tx() и spi_rx() используются для передачи и приема данных по одному байту в ведущем устройстве. Функции spi_slave_tx() и spi_slave_rx() используются для передачи и приема данных по одному байту в ведомом устройстве. Начнем с простого примера приема кадров Ethernet на основе обработчика внешнего прерывания от сетевого модуля ENC28J60 по приему кадра. Составим основную функцию теста spi_test_master() в файле test/spi_test.c: После настройки выводов интерфейса согласно информации из файла hwcf.h инициализируется модуль SPI0. Далее выполняется настройка режима работы через функцию spi_setup_mode(). Для генерации сигнала выбора кристалла (Chip Select) будем использовать "штатный" сигнал NSSO с низким активным уровнем (поэтому включаем инверсию). Управлять сигналом NSSO будем программно (SPI_NSS_SWEN). Модуль SPI0 включаем в режиме ведущего с указанием нужно режима CPHA и CPOL. В завершении настройки устанавливаем однобайтовый режим приема (SPI0_RX_TH=1 и SPI_CR2_DSIZE=8) и сбрасываем буфер приема на всякий случай (spi_flush_rx()). Далее переходим к инициализации сетевого модуля с указанием локального MAC-адреса, заданного чуть выше в этом же файле: Запрашиваем значения регистров EREVID (номер ревизии м/с) и ERXFCON (правила фильтрации кадров), выводим их значения вместе с MAC-адресов в терминал. Далее выполняем настройку внешнего прерывания МК в функции exint_setup() и разрешаем генерацию прерывания в ENC28J60 (eth_setup_int()). Рассмотрим функцию exint_setup(): Для сигнала прерывания используем вывод PA12 в режиме цифрового входа с подтяжкой. В контроллере EXIC для данного вывода устанавливаем срабатывание по фронту и включаем вывод в схему формирования прерывания "ИЛИ". Далее включаем прерывание EXINT0, устанавливаем обработчик и разрешаем прерывание IRQ#3 в модуле NVIC. Рассмотрим обработчик прерывания exint0_hdl_nodma(): В начале функции в состояние лог. "1" переходит сигнал светодиода D1, который будет использоваться в цифровом анализаторе. Далее запрещается генерация внешнего прерывания от сетевого модуля и вызывается функция приема кадра eth_recvpkt() в глобальный буфер eth_frame размером ETH_FRAME_MAXSIZE байт. В случае успешного получения кадра в терминал с меткой L выводится его длина, а также hex-дамп первых 14 байт. После этого прерывания сетевого модуля разрешаются, сбрасывается флаг прерывания EXINT0 и выключается светодиод. Результат работы наблюдаем в терминале: Принимаются только широковещательные кадры, поскольку адрес сетевого модуля пока нигде неизвестен. С помощью цифрового анализатора Saleae Logic посмотрим на временные диаграммы приема кадра. На следующем рисунке процесс показан крупным планом: В момент времени A1 (спадающий фронт сигнала ETH_INT) срабатывает прерывание EXINT0 по приему кадра от ENC28J60. Далее в обработчике прерывания exint0_hdl_nodma() начинают выполняться команды по запросу сетевого модуля. С момента B1 до B2 (422 мкс) происходит считывание буфера, содержащего данные принятого кадра Ethernet. В течение этого времени сигнал NSS (SPI-ENABLE) остается в активном состоянии согласно работе программы. После этого выполняются завершающие действия функции eth_recvpkt() до момента A2. Интервал A1-A2 составил 668 мкс. Далее выполняется сравнительно медленный вывод информации в UART, после чего разрешается прерывание в сетевом модуле по команде установки бита EIE_INTIE (0x9B, 0x80), выключается светодиод и обработчик прерывания завершает работу (на данной диаграмме не показано, см. ниже). Общее время приема кадра длиной 60 байт в данном примере составило 5729 мкс. На следующем рисунке начало взаимодействия МК и сетевого модуля показано более подробно. Поскольку прием данных выполняется программой по одному байту, между байтами имеется существенный интервал около 2.5 мкс, что показано на следующих диаграммах: Завершающая стадия показана на следующем рисунке: Создадим функцию для настройки режима работы каналов DMA, которая будет вызываться из spi_test_master(): Вначале происходит инициализация модуля DMA (включение тактирования и самого модуля). Далее канал 0 DMA настраивается для приема данных из модуля SPI0 и их отправки в область ОЗУ по адресу eth_frame. Канал 1 настраивается для чтения данных из ОЗУ с этого же адреса и их отправки в модуль SPI0 для последующей передачи. Затем разрешаются прерывания по завершению передачи для обоих каналов и прерывание модуля DMA. Далее устанавливается обработчик прерывания и IRQ#8 разрешается в модуле NVIC. Процесс приема кадра Ethernet можно разделить на 3 этапа: выполнение обработчика внешнего прерывания exint0_hdl_dma() после получения нового кадра модулем ENC28J60, в котором выполняются подготовительные действия для получения данных самого кадра по каналу DMA; прием данных кадра Ethernet из модуля SPI0 в ОЗУ по каналу DMA; выполнение обработчика прерывания DMA dma_hdl() по завершению приема данных из модуля SPI0. Рассмотрим обработчик exint0_hdl_dma(): В отличие от предыдущего варианта обработчика без DMA, в данной функции выполняется часть кода из функции eth_recvpkt(). Вначале считывается заголовок кадра Ethernet и статус операции. Если операция получения кадра завершена успешно, в глобальной переменной eth_frame_len устанавливается общая длина массива данных, которые нужно принять, включая данные кадра и 4 байта поля CRC32. После этого активируется сигнал NSSO (выбор кристалла) и запускается процедура приема данных по каналу DMA через функцию spi_dma_start_rx(): В функции выполняются следующие действия: в модуле SPI разрешается управление приемом данных со стороны контроллера DMA, устанавливается объем пересылаемых данных (длина кадра Ethernet), запускается пересылка по каналу 0 DMA. С этого момента до завершения операции пересылки данных по каналу DMA отключается работа флагов модуля SPI, поэтому вызов каких-либо функций из библиотеки enc28j60.c недопустим. В функции exint_setup() заменим обработчик прерывания на вариант с DMA: Теперь рассмотрим обработчик dma_hdl(): Вначале по флагам определяется, по какому каналу DMA завершилась операция пересылки данных. В данном тесте используется канал 0, обработка события на канале 1 будет применяться для следующего теста при отправке кадров. По завершению приема данных по SPI деактивируется сигнал NSSO, после чего выполняются завершающие операции в ENC28J60 и сбрасывается флаг прерывания DMA. Далее разрешается генерация сигнала прерывания в ENC28J60 и устанавливается прикладной флаг ST_PKTRECV для главного цикла программы. Опционально запрещаются прерывания МК до завершения обработки события в главном цикле. После настройки DMA в функции spi_test_master() начинается основной цикл: При активации флага ST_PKTRECV в терминал выводятся: сообщение с меткой R о приеме кадра с указанием его длины (без учета поля с CRC32); дамп первых 14 байт кадра, включая адреса отправителя и получателя и тип кадра (или длину); сообщение с меткой C с полученной из кадра контрольной суммой; сообщение с меткой S с контрольной суммой, вычисленной с помощью ранее рассмотренной функции crc32_block8(). В терминале получаем результат работы (в течение нескольких секунд): Видно, что контрольные суммы совпадают, а значит кадры принимаются правильно и сумма CRC32 вычисляется корректно. Наш сетевой адаптер настроен на прием "своих" и широковещательных кадров. Последние чаще всего представляют собой ARP-запросы, которые, к тому же легко генерировать на ПК через команду ping на несуществующий или уже удаленный из ARP-таблицы адрес. Для разбора ARP-запросов создадим простейшую функцию arp_get_ipaddr(), которая будет возвращать IP адрес из принятого кадра: В основном цикле программы будем выводить в терминал: 1) сообщение с меткой R о приеме кадра с указанием его длины без поля с CRC32, 2) дамп всего кадра, 3) IP адрес с меткой A, если кадр является ARP-запросом, а вывод контрольной суммы уберем: На ПК в программе Wireshark установим фильтр eth.type==ARP и будем наблюдать активность в сети, в которую также подключен модуль на ENC28J60: В терминале получаем такой результат (дамп кадров перенесен на вторую строку): Видно, что в сети проходили ARP-запросы на адреса 192.168.0.160 и 192.168.0.161. Дамп соответствующих кадров в терминале и в Wireshark совпадают, функция приема данных на основе DMA работает корректно. Теперь перейдем к отправке кадров Ethernet. Реализуем на МК имитацию узла сети IPv4 и простейшую функцию arp_gen_reply() генерации пакетов с ARP-ответом: Локальный адрес узла на МК зададим в глобальной переменной: В основном цикле программы при активации флага ST_PKTRECV добавим проверку адреса в ARP-запросе и в случае его равенства установленному в переменной ipaddr: вывод сообщения "> ARP_REPLY", генерацию кадра с ARP-ответом в массиве eth_frame (длина кадра 42 байта), запуск процедуры отправки кадра на ENC28J60 через функцию spi_dma_start_tx(). В этом же цикле при активации флага ST_PKTSENT при завершении передачи данных в буфер ENC28J60 по шине SPI добавим: вывод сообщения "T PKTSENT", сброс модуля SPI0 для восстановления работы флагов, установку границ кадра на отправку в буфере ENC28J60 и отправку команды на передачу кадра в сеть, выключение светодиода для наблюдения завершения процедуры отправки в цифровом анализаторе. Код основного цикла получается следующий: Рассмотрим функцию запуска процедуры отправки данных spi_dma_start_tx(): В начале функции включается светодиод для отметки момента времени в цифровом анализаторе. Далее в цикле while происходит обработка бага ENC28J60 (см. функцию eth_sendpkt()). После этого выполняются действия: записывается служебный заголовок в буфер ENC28J60 с нулевым управляющим байтом, активируется сигнал NSSO, в модуле SPI разрешается управление передачей данных со стороны контроллера DMA, устанавливается объем пересылаемых данных n, запускается пересылка по каналу 1 DMA. На ПК инициируем отправку ARP-запроса с помощью команды ping на узел 192.168.0.177, MAC-адрес которого еще не был известен: В Wireshark наблюдаем ARP-запрос с ПК и ответ с МК: В терминале получаем такой результат: После этого на ПК в текущей ARP-таблице наблюдаем новую запись с нашим узлом с корректными значениями IP и MAC адресов: Если требуется повторить эксперимент, удалить запись о нашем узле можно с помощью команды: Посмотрим на временные диаграммы процесса отправки кадра. На следующем рисунке процесс показан крупным планом: В момент времени A1 начинается отправка команд на ENC28J60 в функции spi_dma_start_tx() для подготовки буфера передачи сетевого модуля, после чего запускается процедура отправки данных по каналу DMA. По завершению отправки активируется прикладной флаг ST_PKTSENT и выводится сообщение в терминал, на что тратится существенное время порядка 880 мкс. После этого в сетевой модуль подаются команды на фактическую отправку кадра в сеть и взаимодействие МК с модулем ENC28J60 завершается в момент A2. На следующем рисунке начало отправки данных показано более подробно. Начиная примерно с отметки времени +80 мкс запускается процедура передачи данных по каналу DMA после программной активации сигнала NSSO и выдачи команды "Write Buffer Memory" (0x7A): Передача данных в модуле SPI, принимаемых по каналу DMA, происходит байт за байтом без каких-либо межсимвольных интервалов на заданной тактовой частоте сигнала SCK. По завершению передачи активируется флаг TCF модуля DMA и генерируется прерывание. В обработчике прерывания деактивируется сигнал NSSO. Далее в сетевой модуль отправляется последовательность команд для установки границ буфера. Завершается процесс отправки кадра передачей команды "Transmit Request to Send" установкой бита ECON1_TXRTS в модуле ENC28J60 (0x9F, 0x08): Рассмотрим ведомое SPI-устройства на МК MG32F02A032, реализующее отправку произвольного числа байт из своего буфера. Протокол обмена следующий: ведущий отправляет один байт, в котором указывает число запрашиваемых байт. Со следующего кадра шины ведомый начинает отправку запрошенных данных. Для ведущего создадим функцию spi_test_master2(): В этом тесте какие-либо прерывания или DMA не используются, процесс отправки и получения данных происходит в главном цикле. В отличие от предыдущих примеров в данной реализации "мастера" будем использовать автоматическую активацию сигнала NSSO. Для этого в функции spi_setup_mode() добавим флаг SPI_NSS_PEN. В переменной n указывается число запрашиваемых байт, в массиве s полученные данные сохраняются, после чего строка выводится в терминал и выдерживается пауза около 250 мс. Перед приемом данных выдерживается пауза (delay(20)) для того, чтобы ведомый успел поместить запрашиваемые данные в свой буфер. Из "слейва" будем ожидать последовательность ASCII-символов. Перейдем к реализации ведомого устройства, для чего создадим отдельный файл app_spis.c, в котором реализуем весь основной код. Прием данных будет обрабатываться в прерывании. Контроллер DMA использоваться не будет, поэтому прикладную часть можно поместить в ОЗУ. Рассмотрим основную функцию spi_test_slave(): Вначале происходит настройка выводов и модуля SPI0 аналогично "мастеру", но в функции spi_setup_mode() указываются флаг SPI_NSSI_EN для включения автоматического принятия данных при активации входного сигнала NSSI и флаг SPI_SLAVE для выбора основного режима работы. Особенностью реализации ведомого является необходимость быстрой записи данных в буфер SPI0_TDAT на отправку после получения команды. По-умолчанию, первый байт из буфера SPI0_TDAT копируется в промежуточный буфер еще в момент приема последнего бита предыдущего кадра, т.е. фактически до активации флага RXF. Чтобы "слейв" мог подготовить данные на отправку после приема командного байта, необходимо включить опцию прямого обновления буфера передачи SPI0_TXUPD_EN в регистре SPI0_CR2 и передавать данные в буфер SPI0_TDAT как можно быстрее, чтобы успеть к началу следующего кадра. Далее устанавливается обработчик прерывания по приему данных spi_hdl() и разрешается прерывание в модулях SPI0 и NVIC. Рассмотрим обработчик прерывания: В начале считывается один байт в переменную n, входящий буфер очищается (на всякий случай) и, если n меньше размера буфера TXBUF_SIZE, на передачу отправляются n байт. Фактически, по первому байту, удовлетворяющему этому условию, происходит синхронизация работы ведомого с потоком байт от ведущего. В функции spi_slave_tx() после передачи в буфер SPI0_TDAT каждого байта ожидается активация флага TXF. Для отладки в цифровом анализаторе указанные действия отмечаются активацией и деактивацией отладочного сигнала MARK (вывод PB8). Поскольку при отправке данных одновременно происходит и прием, входной буфер может переполняться, после чего флаг RXF перестает активироваться и перестает срабатывать прерывание. Чтобы это не происходило, в обработчике также проверяется флаг ROVRF и в случае его активности сбрасывается он и сам буфер. Данное событие будет отображаться на временной диаграмме сигнала MARK коротким положительным импульсом. В обработчике используются функции mark_*() для управления отладочным сигналом MARK и глобальный буфер данных: Запускаем программы на обоих МК и смотрим терминал "мастера" при запросе 5 байт: Периодичность не нарушается, значит программа работает правильно. Посмотрим на временную диаграмму обмена данными: Видно, что прерывание сработало спустя около 5 мкс после фактического получения первого байта и дополнительная задержка в "мастере" перед приемом данных оказалась кстати. Функция spi_slave_tx() завершилась после фактической отправки третьего байта. После этого (короткий импульс MARK) было обнаружено переполнения буфера (поскольку установлен порог SPI0_RX_TH на 1 байт), флаг ROVRF и буфер приема были сброшены. Следующие срабатывания прерывания пришлись на моменты после отправки 4-го и 5-го байтов. В обоих случаях был принят байт 0xFF, который был проигнорирован. Переполнения не происходило. Если в обработчике прерывания отключить сброс флага ROVRF, ведомый перестанет работать правильно. Посмотрим, что произойдет, если сброс флага оставить, но не сбрасывать буфер приема (spi_flush_rx()): Видно, что переполнение было зафиксировано дважды: второй раз при втором срабатывании прерывания. Оба варианта программы "слэйва" работают правильно. Сегодня мы протестировали модуль SPI0 в режиме ведущего и ведомого, применили контроллер DMA для пересылки данных между модулем SPI0 и ОЗУ, использовали внешнее прерывание от Ethernet-контроллера ENC28J60 и имитировали узел локальной сети. За рамками статьи осталось применение модуля SPI0 в режимах Dual, Quad и Octal, а также работа на удвоенной скорости обмена. Все эти вопросы читатель может изучить по User Guide микроконтроллеров серии MG32F02.