Продолжая цикл публикаций по микроконтроллерам на ядре Cortex-M0 компании Megawin (см. предыдущие статьи 1, 2, 3, 4, 5 и 6), сегодня рассмотрим модуль интерфейса I2C.
Функциональные возможности модуля I2C
Микроконтроллеры серии MG32F02 включают один (MG32F02A032) или два (остальные МК) модуля интерфейса I2C. Модули имеют следующие функциональные особенности:
-
работа в режиме ведущего (master) или ведомого (slave);
-
частота шина до 1 МГц;
-
детальная настройка параметров сигнала SCL в режиме ведущего устройства;
-
удержание сигнала SCL в состоянии низкого уровня в режиме ведомого устройства;
-
схема активной подтяжки для линий SCL и SDA;
-
поддержка до двух адресов в режиме ведомого, а также детектирования адреса по маске;
-
поддержка адреса общего вызова;
-
поддержка режима нескольких ведущих (multi-master);
-
детектирование «нештатных» ситуаций на шине (некорректный NACK, переполнение буфера, потеря приоритета);
-
низкоуровневый (Byte mode) и высокоуровневый (Buffer mode) режимы работы;
-
буфер приема и передачи размером 4 байта;
-
поддержка DMA;
-
встроенный таймер таймаута, определения таймаута шины SMBus;
-
пробуждение модуля из состояния STOP.
Функциональная схема модулей I2C0 и I2C1 приведена на рисунке.
Каждый модуль I2Cx включает следующие основные узлы:
-
схему тактирования,
-
схему детектирования состояния пробуждения,
-
блок контроля линии SCL,
-
блок контроля линии SDA,
-
блок управления буфером Buffer Control,
-
блок управления режимами Master/Slave,
-
блок детектирования ошибок Error Detector,
-
таймер таймаута,
-
схему управления флагами событий и формирования сигнала прерывания INT_I2Cx.
Тактирование
Модуль I2Cx тактируется сигналом CK_I2Cx, источник которого выбирается в поле I2Cx_CLK.I2Cx_CK_SEL
из числа следующих:
-
сигнал CK_I2Cx_PR с выхода подсистемы тактирования МК, который, в свою очередь, определяется битом
CSC_CKS1.CSC_I2Cx_CKS
из сигналов CK_APB (0) или CK_AHB (1); -
выходной сигнал таймера TM00_TRGO.
Сигнал CK_I2Cx поступает на предделитель частоты Prescaler, на выходе которого формируется сигнал CK_I2Cx_PSC. Коэффициент предделителя из непрерывного диапазона 2-16 определяется в 4-битном поле I2Cx_CLK.I2Cx_CK_PSC
значениями от 1 до 15 соответственно. Далее сигнал поступает на делитель частоты DIV, коэффициент которого задается в поле I2Cx_CLK.I2Cx_CK_DIV
из ряда 2, 4, 8, …, 128. На выходе делителя формируется основной тактовый сигнал модуля CK_I2Cx_INT. С другого выхода делителя с фиксированным коэффициентом 64 формируется сигнал CK_I2Cx_DIV64. Тактовый сигнал таймера таймаута CK_I2Cx_TMO в зависимости от значения поля I2Cx_CLK.I2Cx_TMO_CKS
может формироваться из сигнала CK_I2Cx_DIV64 или общесистемного сигнала CK_UT.
Частота внутреннего сигнала тактирования модуля CK_I2Cx_INT определяется выражением
FINT = F(CK_I2Cx_INT) = F(CK_I2Cx) / [ (PSC + 1)·DIV ],
где F(CK_I2Cx) частота сигнала CK_I2Cx, PSC — значение поля I2Cx_CLK.I2Cx_CK_PSC
, DIV — значение поля I2Cx_CLK.I2Cx_CK_DIV
.
На следующем рисунке показана временная диаграмма сигналов SCL и SDA.
В модуле имеется возможность настраивать интервалы tHIGH и tLOW через поля I2Cx_HT
и I2Cx_LT
регистра I2Cx_CR1
соответственно. Длительности состояния высокого уровня tHIGH и состояния низкого уровня tLOW сигнала SCL определяются выражениями
tHIGH = (1 + HT)·TINT,
tLOW = (1 + LT)·TINT,
где HT и LT — значения полей I2Cx_HT
и I2Cx_LT
соответственно, а TINT = 1 / FINT — период сигнала CK_I2Cx_INT.
В режиме мастера интервал tVD:DAT между задним фронтом сигнала SCL и моментом изменения состояния сигнала SDA составляет 2·TINT. Далее интервал до переднего фронта сигнала SCL составляет (LT-1)·TINT. Период сигнала SCL, формируемого мастером, будет определяться выражением
TSCL = tHIGH + tLOW = (2 + HT + LT)·TINT.
Итоговая номинальная частота шины I2C будет определяться выражением
FSCL = 1 / TSCL = FINT / (2 + HT + LT) = F(CK_I2Cx) / [ (PSC + 1)·DIV·(2 + HT + LT) ].
Параметры HT и LT имеют значения по-умолчанию 5 и 4 соответственно. Минимальными «рабочими» значениями параметров являются 2 и 2.
Режимы работы
Общие сведения
Модуль I2Cx включается установкой бита I2Cx_CR0.I2Cx_EN
. С точки зрения логики взаимодействия аппаратуры и программы в модулях I2Cx реализованы три основных режима работы:
-
I2C Byte mode — низкоуровневый (программно-аппаратный) режим с необходимостью реализации программного контроля приема и передачи на основе конечного автомата,
-
I2C Buffer mode — высокоуровневый (аппаратный режим),
-
Monitor (Buffer mode) — режим мониторинга (сниффер шины).
В первых двух режимах (I2C) модуль может играть роль ведущего (Master) или ведомого (Slave) устройства на шине I2C. Режим работы определяется битами I2Cx_MDS
и I2Cx_BUF_EN
регистра I2Cx_CR0
согласно следующей таблице:
|
|
Режим работы |
---|---|---|
0 (I2C) |
0 (Disable) |
I2C Byte mode (по-умолчанию) |
0 (I2C) |
1 (Enable) |
I2C Buffer mode |
1 (Monitor) |
0 (Disable) |
— |
1 (Monitor) |
1 (Enable) |
Monitor |
При дальнейшем рассмотрении работы модуля будем исходить из того, что читатель знаком с общим принципом функционирования шины I2C. Для изучения этого вопроса можно обратиться к официальной спецификации и ее переводу на русский язык:
Далее будем использовать следующие обозначения:
Обозначение |
Описание |
---|---|
START |
Состояние шины «Старт» (Start) |
STOP |
Состояние шины «Стоп» (Stop) |
RSTART |
Состояние шины «Повторный старт» (Repeated Start) |
SLA |
Адресный кадр, с которого начинается обмен данными (Slave Address) |
SLA+W |
Адресный кадр, в котором указывается, что ведущий далее будет передавать данные (SLA + Write) |
SLA+R |
Адресный кадр, в котором указывается, что ведущий далее будет принимать данные (SLA + Read) |
ACK |
Состояние шины «Подтверждено» (Acknowledgment) |
NACK |
Состояние шины «Не подтверждено» (No Acknowledgment) |
Адресация
С точки зрения роли устройства на шине I2C в модулях I2Cx реализованы режимы «ведущее устройство» (Master) и «ведомое устройство» (Slave). По-умолчанию включен режим ведущего устройства. Режим ведомого включается при разрешении детектирования адреса.
В режиме ведомого устройства могут быть заданы один или два собственных адреса, на которые будет отвечать модуль. Основной адрес ведомого задается в регистре I2Cx_SADR
в битах 1-7, т.е. если записывать байт, то бит 0 можно оставить равным 0. Дополнительный адрес задается в битах 9-15 аналогично. Детектирование основного адреса разрешается установкой бита I2Cx_SADR_EN
, а дополнительного — установкой бита I2Cx_SADR2_EN
в регистре I2Cx_CR0
. Установка хотя бы одного из этих битов переводит модуль в режим ведомого устройства. В режиме пониженного энергопотребления STOP, если один из этих битов установлен, при детектировании на шине кадра обращения к устройству модуль активирует флаг пробуждения WUPF.
Для основного адреса также можно задействовать маску I2Cx_MASK.I2Cx_SA_MSK
(биты 1-7 регистра), расширяющую диапазон адресов, на которые будет отвечать модуль: в адресе будут проверяться только те разряды, которые установлены в 1 в маске. По-умолчанию, в маске установлено значение 0x7F, т.е. строгое соответствие заданному в поле I2Cx_SADR
адресу. Кроме того, модуль будет отвечать на нулевой адрес (общий вызов), если установлен бит I2Cx_CR0.I2Cx_GC_EN
(по-умолчанию сброшен).
Буфер данных
В модуле I2Cx программно доступен 8-разрядный сдвиговый регистр I2Cx_SBUF
, выполняющий функцию буфера данных нижнего уровня. Программа же обычно взаимодействует с 32-разрядным регистром I2Cx_DAT
, выполняющим функцию буфера верхнего уровня с возможностью 8-, 16- и 32-битного доступа. Буфер тесно связан с полем I2Cx_CR2.I2Cx_BUF_CNT
, в котором в режиме Buffer mode задается фактическое число (1-4) принимаемых или передаваемых байт (в режиме Master) или сохраняется число уже принятых байт (в режиме Slave). Последовательность байт при приеме или передаче — от младшего к старшему.
В режиме Buffer mode при передаче данных разрядность операции записи в регистр I2Cx_DAT
должна соответствовать объему передаваемых данных: для записи одного байта нужно использовать байтовую пересылку (STRB
) в младший байт регистра, для двух байт — двухбайтовую (STRH
), для 3-4 байт — пересылку слова (STR
).
Команды управления состоянием
Для управления состоянием шины I2C в регистре I2Cx_CR2
имеются биты, установка которых приводит к генерации нового состояния непосредственно в момент их установки или по завершению текущей операции приема или передачи данных. Биты команд приведены в следующей таблице.
Разряд |
Название |
Описание |
---|---|---|
0 |
|
Генерация состояния START |
1 |
|
Генерация состояния STOP |
2 |
|
Генерация состояния ACK |
3 |
|
Разрешение генерации состояний при установке битов PSTA, PSTO, PAA |
4 |
|
Разблокировка записи в разряды |
5 |
|
Разблокировка записи в разряды |
6 |
|
Разблокировка записи в разряды |
24 |
|
Генерация состояния START после завершения текущей операции |
25 |
|
Генерация состояния STOP после завершения текущей операции |
26 |
|
Генерация состояния ACK после завершения текущей операции |
Команда STA (START) формируется при установке бита I2Cx_STA
вместе с битом разблокировки I2Cx_STA_LCK
и приводит в режиме Master к генерации состояния START, если шина была свободна. Если шина была занята, ожидается состояние STOP на шине, после чего генерируется состояние START. Если модуль уже находится в режиме ведущего после предыдущей команды STA в процессе приема или передачи, генерируется состояние RSTART.
Команда STO (STOP) формируется при установке бита I2Cx_STO
вместе с битом разблокировки I2Cx_STO_LCK
и приводит в режиме Master к генерации состояния STOP. Если одновременно с командой STO дается команда STA, то после генерации состояния STOP генерируется состояние START. В режиме Slave команда STO может использоваться для выхода из состояния какой-либо ошибки на шине.
Команда AA (ACK) формируется при установке бита I2Cx_AA
вместе с битом разблокировки I2Cx_AA_LCK
и приводит к генерации состояния ACK (низкий уровень сигнала SDA в течение передачи 9-го импульса сигнала SCL в текущем кадре) в следующих случаях:
-
подтверждение адреса при совпадении в кадре SLA в режиме ведомого устройства,
-
подтверждение принятия байта в режиме приема ведущим устройством,
-
подтверждение принятия байта в режиме приема ведомым устройством.
Если устанавливается бит I2Cx_AA_LCK
при сброшенном бите I2Cx_AA
, генерируется состояние NACK (остается высокий уровень сигнала SDA в течение передачи 9-го импульса сигнала SCL в текущем кадре) в следующих случаях:
-
принят байт в режиме приема ведущим устройством,
-
принят байт в режиме приема ведомым устройством.
Команды STO и AA предназначены для применения, в первую очередь, в программно-аппаратном режиме (Byte mode). В аппаратном режиме (Buffer mode) большинство действий выполняется автоматически и в программе лишь требуется заранее установить поведение модуля с помощью команд PSTA, PSTO и PAA. Для их выполнения также должен быть установлен бит I2Cx_CMD_TC
.
Команда PSTA формируется при установке бита I2Cx_PSTA
вместе с битом I2Cx_STA_LCK
и приводит в режиме Master к генерации состояния RSTART после завершения запланированной операции приема или передачи данных.
Команда PSTO формируется при установке бита I2Cx_PSTO
вместе с битом I2Cx_STO_LCK
и приводит в режиме Master к генерации состояния STOP после завершения запланированной операции приема или передачи данных.
Команда PAA формируется при установке бита I2Cx_PAA
вместе с битом I2Cx_STO_LCK
и приводит к формирования состояния подтверждения после завершения запланированной операции приема или передачи данных. Если бит I2Cx_PAA
сброшен, а бит I2Cx_STO_LCK
установлен, после завершения операции подтверждение генерироваться не будет.
Программно-аппаратный режим (Byte mode)
В программно-аппаратном режиме Byte mode передача и прием каждого кадра шины I2C разбивается на несколько этапов, характеризующихся тем или иным состоянием шины и модуля. Завершение каждого этапа и переход в новое состояние представляет собой событие. Программа должна реагировать на каждое новое событие: формировать команды, считывать или записывать данные. Все возможные состояния пронумерованы и представлены в таблице. Отметим, что разработчики МК серии MG32F02 взяли схему кодирования состояний модуля I2C, применяемую в 8-разрядных МК на ядре C8051 (например, NXP, Silicon Labs) и МК серии ATmega.
Код |
Описание |
MT |
MR |
SR |
ST |
---|---|---|---|---|---|
0x00 |
Ошибка шины |
v |
v |
v |
v |
0x08 |
Сформировано состояние START |
v |
v |
|
|
0x10 |
Сформировано состояние RSTART |
v |
v |
|
|
0x18 |
Передан кадр SLA+W и получено подтверждение (ACK) |
v |
|
|
|
0x20 |
Передан кадр SLA+W, подтверждение не получено (NACK) |
v |
|
|
|
0x28 |
Передан байт с данными и получено подтверждение (ACK) |
v |
|
|
|
0x30 |
Передан байт с данными, подтверждение не получено (NACK) |
v |
|
|
|
0x38 |
Потеря приоритета при передаче кадра SLA+R/W или данных |
v |
v |
|
|
0x40 |
Передан кадр SLA+R и получено подтверждение (ACK) |
|
v |
|
|
0x48 |
Передан кадр SLA+R, подтверждение не получено (NACK) |
|
v |
|
|
0x50 |
Принят байт с данными и сформировано состояние ACK |
|
v |
|
|
0x58 |
Принят байт с данными и сформировано состояние NACK |
|
v |
|
|
0x60 |
Принят кадр SLA+W с собственным адресом и сформировано состояние ACK |
|
|
v |
|
0x68 |
Принят кадр SLA+W с собственным адресом, потерян приоритет, сформировано состояние ACK |
|
|
v |
|
0x70 |
Принят общий вызов и сформировано состояние ACK |
|
|
v |
|
0x78 |
Принят общий вызов, потерян приоритет, сформировано состояние ACK |
|
|
v |
|
0x80 |
Устройство уже адресовано, принят байт с данными и сформировано состояние ACK |
|
|
v |
|
0x88 |
Устройство уже адресовано, принят байт с данными и сформировано состояние NACK |
|
|
v |
|
0x90 |
Общий вызов, принят байт с данными и сформировано состояние ACK |
|
|
v |
|
0x98 |
Общий вызов, принят байт с данными и сформировано состояние NACK |
|
|
v |
|
0xA0 |
Устройство адресовано, обнаружено состояние START или RSTART |
|
|
v |
|
0xA8 |
Принят кадр SLA+R с собственным адресом и сформировано состояние ACK |
|
|
|
v |
0xB0 |
Принят кадр SLA+R с собственным адресом, потерян приоритет, сформировано состояние ACK |
|
|
|
v |
0xB8 |
Передан байт с данными и получено подтверждение (ACK) |
|
|
|
v |
0xC0 |
Передан байт с данными, подтверждение не получено (NACK) |
|
|
|
v |
0xC8 |
Передан последний байт с данными и получено подтверждение (ACK) |
|
|
|
v |
0xF8 |
Состояние шины STOP или шина свободна |
v |
v |
v |
v |
В колонках MT, MR, SR и ST указана применимость состояния в режимах «ведущий передает», «ведущий принимает», «ведомый принимает» и «ведомый передает» соответственно. Код последнего события (состояния) всегда отображается в поле I2Cx_STA2.I2Cx_EVENT
, а появление нового события приводит к установке флага EVENTF и возможному прерыванию. В приеме и передаче данных в этом режиме используется только однобайтный буфер I2Cx_SBUF
. Для формирования состояний на шине применяются вышеописанные команды STA, STO и AA.
В документации User Guide приводятся подробные блок-схемы алгоритмов работы модуля и таблицы переходов между состояниями для режимов: «ведущий передает», «ведущий принимает», «ведомый передает», «ведомый принимает» и «ведомый принимает общий вызов». Оптимальной является реализация рассматриваемых алгоритмов на основе прерываний и конечного автомата.
Аппаратный режим (Buffer mode)
Общие сведения
Аппаратный режим (Buffer mode) позволяет максимально автоматизировать работу с модулем I2C и свести к минимуму программный код. Регистр I2Cx_DAT
выполняет в этом режиме функцию 4-байтного буфера, а синхронизация действий с программой достигается с помощью основных флагов RXF и TXF. В данном режиме при приеме и передаче данных используется также 32-разрядный промежуточный буфер (Shadow Buffer). С буфером связан счетчик I2Cx_ACNT
, который увеличивается на 1 в следующих случаях:
-
в режиме приема данных — при передаче каждого байта из сдвигового регистра Shift Buffer в промежуточный буфер,
-
в режиме передачи данных — при передаче каждого байта из промежуточного буфера в сдвиговый регистр.
Ведущее устройство (Master)
На следующем рисунке показан алгоритм работы модуля в данном режиме.
Ведущее устройство начинает работу с формирования на шине адресного кадра SLA. Для этого дается команда STA с одновременным указанием в поле I2Cx_CR2.I2Cx_BUF_CNT
числа передаваемых байт N от 1 до 4. Далее в регистр I2Cx_DAT
записываются передаваемые данные. Первым по порядку байтом должен быть адрес ведомого устройства, к которому происходит обращение, сдвинутый на 1 разряд влево. Если младший бит байта сброшен, формируется кадр SLA+W, если установлен — кадр SLA+R. Если было указано N>1, то в случае ответа ведомого состоянием ACK в кадре SLA+W ведущий продолжает передачу данных в этом же кадре. В любом случае данные передаются только если было получено подтверждение на предыдущий байт. По завершению передачи устанавливается флаг TXF.
При указании в поле I2Cx_BUF_CNT
числа передаваемых байт нужно одновременно сформировать команду PSTO или PSTA, чтобы по завершению передачи модуль сгенерировал состояние STOP или RSTART соответственно.
Если был сформирован кадр SLA+R, после подтверждения адреса со стороны ведомого устройства модуль переходит в режим приема данных. В поле I2Cx_BUF_CNT
нужно указать число принимаемых байт N от 1 до 4. После передачи N байт из промежуточного буфера в регистр I2Cx_DAT
активируется флаг RXF.
В завершающей операции приема данных следует также сформировать команду PSTO или PSTA. В этом случае после получения последнего байта модуль генерирует состояние NACK, сигнализируя тем самым ведомому устройству о завершении операции. Прием всех предыдущих байтов модулем подтверждается автоматически. Если требуется подтвердить и последний байт, вместе с командой PSTO (или PSTA) необходимо также дать команду PAA.
Если в программе перед приемом данных требуется сбросить флаг RXF, то необходимо выполнить «фиктивное» чтение регистра I2Cx_DAT
, а не сбрасывать флаг явно записью 1 в регистр I2Cx_STA
, что может привести к приему лишних данных.
Для ведущего устройства можно рекомендовать следующий алгоритм отправки данных:
-
Дать команду
STA
с одновременной записью 1 в полеI2Cx_CR2.I2Cx_BUF_CNT
. -
В регистр
I2Cx_DAT
записать адрес ведомого устройства, сдвинутый на 1 разряд влево. Младший бит адреса должен быть нулевым. Таким образом будет сформировано состояние SLA+W. -
Ожидать установку флага TXF, т.е. готовность буфера принять новые данные.
-
В поле
I2Cx_CR2.I2Cx_BUF_CNT
указать число отправляемых байт (1-4), а также опционально установить битI2Cx_PSTO
илиI2Cx_PSTA
(вместе с битомI2Cx_CMD_TC
), если после отправки требуется автоматически сгенерировать состояние STOP или RSTART соответственно. -
Записать новые данные в регистр
I2Cx_DAT
(после чего начинается фактическая отправка). -
Если требуется дальнейшая передача данных (более 4-х байт), повторить пункты 3-5.
-
При «ручном» методе генерации состояния STOP ожидать активацию флага TSCF (фактическое завершение передачи) и только после этого дать команду
STO
. -
При автоматической генерации состояния STOP перед отправкой следующего кадра (очередной команды
STA
) необходимо ожидать активацию флага STOPF.
Кроме того, возможно потребуется программный сброс флага TXF перед пунктом 4 в случае отправки нескольких кадров с данными.
Для ведущего устройства можно рекомендовать следующий алгоритм приема данных:
-
Дать команду
STA
с одновременной записью 1 в полеI2Cx_CR2.I2Cx_BUF_CNT
. -
В регистр
I2Cx_DAT
записать адрес ведомого устройства, сдвинутый на 1 разряд влево. Младший бит адреса должен быть установлен. Таким образом будет сформировано состояние SLA+R. -
Ожидать установку флага SADRF, т.е. завершение формирования состояния SLA+R.
-
В поле
I2Cx_CR2.I2Cx_BUF_CNT
указать число ожидаемых байт (1-4), а также опционально установить битI2Cx_PAA
и(или)I2Cx_PSTO
(вместе с битомI2Cx_CMD_TC
), если после приема последнего байта требуется автоматически сгенерировать подтверждение и(или) состояние STOP соответственно. -
Ожидать установку флага RXF, т.е. готовность данных в буфере.
-
Прочитать данные из регистра
I2Cx_DAT
. -
Если требуется дальнейший прием данных (более 4-х байт), повторить пункты 4-6.
-
При «ручном» методе генерации состояния STOP дать команду
STO
. -
При автоматической генерации состояния STOP перед отправкой следующего кадра (очередной команды
STA
) необходимо ожидать активацию флага STOPF.
Ведомое устройство (Slave)
Модуль переходит в режим ведомого устройства (slave) после установки одного из битов включения механизма детектирования адреса I2Cx_SADR_EN
или I2Cx_SADR2_EN
. Алгоритм работы модуля показан на следующем рисунке.
При получении кадра SLA+R с подходящим адресом модуль подтверждает кадр и активирует флаг SADRF. Далее происходит передача данных из буфера I2Cx_DAT
(режим Slave Transmitter) в промежуточный буфер. Счетчик в поле I2Cx_CR2.I2Cx_ACNT
отображает число фактически отправленных байт из промежуточного буфера в сдвиговый регистр.
Если число N, указанное в поле I2Cx_CR2.I2Cx_BUF_CNT
, было в интервале от 1 до 4, после передачи N байт в промежуточный буфер на отправку активируется флаг TXF. В этот момент в программе в регистр I2Cx_DAT
нужно записать новые данные. После записи в регистр I2Cx_DAT
счетчик I2Cx_ACNT
обнуляется. Если новые данные не были записаны, будут отправляться существующие данные последовательно с нулевого по (N-1)-й байт. Данные в принципе будут передаваться на шину пока ведущее устройство генерирует тактовые импульсы сигнала SCL не зависимо от значения I2Cx_BUF_CNT
до тех пор, пока на шине не будет cгенерировано состояние STOP или RSTART. Если значения I2Cx_BUF_CNT
равно 0, флаг TXF сработает после отправки 8-го байта, при этом соответствия отправляемых данных каким-либо байтам буфера не гарантируется.
После получения и подтверждения кадра SLA+W также активируется флаг SADRF и затем начинается процесс приема данных. Каждый принятый байт из сдвигового регистра помещается в промежуточный буфер. Четырехбайтный промежуточный буфер заполняется начиная с младшего байта. Количество байт, записанных в этот буфер, доступно в поле I2Cx_CR2.I2Cx_ACNT
. После записи 4-го байта или при определении на шине состояния STOP или RSTART выполняются следующие действия:
-
принятые данные копируются в регистр
I2Cx_DAT
, -
число принятых байт записывается в поле
I2Cx_CR2.I2Cx_BUF_CNT
, -
значение поля
I2Cx_CR2.I2Cx_ACNT
обнуляется, -
активируется флаг RXF, после чего в программе можно прочитать и проанализировать данные.
Для ведомого устройства можно рекомендовать следующий алгоритм работы:
-
Установить адрес или адреса (при необходимости, и маску) ведомого устройства и переключиться в режим slave.
-
Опционально: ожидать срабатывание флага SADRF (очевидно, после получения кадра SLA+W с командой) для детектирования факта обращения к устройству и подготовки к выполнению запроса.
-
Ожидать срабатывание флага RXF, после чего прочитать данные из регистра
I2Cx_DAT
, количество принятых байт взять из поляI2Cx_CR2.I2Cx_BUF_CNT
. -
Проанализировать данные. Если требуется дальнейший прием данных, перейти на предыдущий пункт.
-
Если согласно полученному запросу требуется отправить данные в ответ, количество байт указать в поле
I2Cx_CR2.I2Cx_BUF_CNT
, затем поместить их в регистрI2Cx_DAT
. Это нужно сделать не дожидаясь активации флага SADRF в следующем кадре SLA+R. -
Если требуется дальнейшая отправка данных, ожидать срабатывание флага TXF и перейти на предыдущий пункт.
-
Опционально: ожидать срабатывание флагов STOPF или RSTRF.
Дополнительные функции
Управление линией SCL для Slave
В режиме Buffer mode в процессе обработки запросов ведомому может потребоваться дополнительное время на формирование ответа, например, на получение данных, которые затребовал ведущий. В этом случае согласно спецификации интерфейса I2C ведомое устройство может задержать тактирование от ведущего путем удержания линии SCL в состоянии низкого уровня. Функция удержания линии SCL по-умолчанию включена и работает в следующих случая:
-
в режиме приема буфер уже заполнен, но программа еще не прочитала из него данные;
-
в режиме передачи буфер уже опустошен, но программа еще не записала в него новые данные.
Для выключения данной функции нужно установить бит I2Cx_CR0.I2Cx_SCLS_DIS
.
Таймер таймаута
В состав модулей I2Cx входит 8-разрядный таймер таймаута TMO. Таймер тактируется сигналом CK_I2Cx_TMO (см. п. Тактирование). На рисунке показана функциональная схема таймера.
В зависимости от значения поля I2Cx_TMOUT.I2Cx_TMO_MDS
таймер работает в следующих режимах:
-
0 — ожидание низкого уровня на линии SCL (по-умолчанию),
-
1 — ожидание высокого уровня на линиях SCL и SDA,
-
2 — таймер общего назначения.
Для включения таймера необходимо установить бит I2Cx_TMOUT.I2Cx_TMO_EN
. Для применения таймера в режиме общего назначения необязательно включать модуль I2Cx. Период счета таймера задается в поле I2Cx_TMOUT.I2Cx_TMO_CNT
. После завершения полного периода (переполнения) активируется флаг TMOUTF. Если установлен бит I2Cx_TMOUT.I2Cx_TMO_CTL
, то при этом происходит сброс всего модуля I2Cx. После срабатывания таймера TMO с настройкой на сброс перед началом каких-либо следующих действий необходимо сбросить флаг TMOUTF, иначе модуль не сможет корректно работать. Автоматический сброс модуля I2Cx при установленном бите I2Cx_TMOUT.I2Cx_TMO_CTL
не приводит к сбросу этого флага.
События и прерывания
Схема формирования общих (вторичных) флагов событий BUFF, STPSTRF и ERRF, генерирующих прерывание модуля INT_I2Cx, приведена на следующем рисунке.
Флаги событий собраны в регистре I2Cx_STA
, а биты разрешения прерываний — в регистре I2Cx_INT
. Перечень событий и соответствующих флагов прерываний приведен в следующей таблице.
Разряд |
Флаг события |
Бит прерывания |
Название события |
Описание |
---|---|---|---|---|
0 |
BUSYF |
— |
I2C control busy |
Модуль занят выполнением операции |
1 |
EVENTF |
EVENT_IE |
Event code change |
Изменилось состояние модуля |
2 |
BUFF |
BUF_IE |
Buffer mode event |
Общий флаг событий в буфере |
3 |
ERRF |
ERR_IE |
Error detect |
Общий флаг ошибок |
4 |
TMOUTF |
TMOUT_IE |
Timeout detect |
Сработал таймер таймаута |
5 |
WUPF |
WUP_IE |
STOP mode wakeup by I2C event |
Событие пробуждения в режиме питания STOP при получении адресного кадра в режиме Slave |
6 |
RXF |
BUF_IE |
Receive data register not empty |
Принятые данные готовы к чтению (Buffer mode) |
7 |
TXF |
BUF_IE |
Transmit data register empty |
Буфер передачи готов к записи новых данных (Buffer mode) |
8 |
RSTRF |
BUF_IE |
Repeat Start asserted |
Обнаружено состояние шины RSTART |
9 |
STOPF |
BUF_IE |
Stop detection |
Обнаружено состояние шины STOP |
10 |
CNTF |
— |
BUF_CNT register empty |
Счетчик BUF_CNT в значении 0 |
11 |
ERRCF |
— |
I2C error |
Не принято подтверждение в режиме Master в кадре SLA или при передаче данных |
12 |
SADRF |
BUF_IE |
Slave address asserted or match detect |
В режиме Slave — получен кадр SLA с подходящим адресом, в режиме Master — ведомый подтвердил адрес в кадре SLA+R |
13 |
SLAF |
— |
Slave mode detect |
Включен режим ведомого |
14 |
MSTF |
— |
Master mode detection |
Включен режим ведущего |
15 |
RWF |
— |
Read or write transfer direction |
Состояние 8-го бита в кадре SLA: 0 — кадр SLA+W, 1 — кадр SLA+R |
16 |
TSCF |
— |
Shadow Buffer Transfer complete |
В режиме передачи байт из промежуточного буфера скопирован в сдвиговый регистр, в режиме приема — из сдвигового регистра в промежуточный буфер |
17 |
STPSTRF |
STPSTR_IE |
STOP or START detect |
Обнаружено состояние STOP или START |
18 |
TXRF |
— |
Slave mode transmit data register remained status |
Флаг активен, если при отправке из-за ошибки в буфере остались непереданные данные |
19 |
ROVRF |
ERR_IE |
Data buffer RX overrun |
Переполнение буфера приема (Buffer mode, задержка SCL отключена) |
20 |
TOVRF |
ERR_IE |
Data buffer TX Overrun |
Буфер передачи не содержит данных (Buffer mode, задержка SCL отключена) |
21 |
NACKF |
ERR_IE |
Invalid NoACK received Error |
Обнаружено состояние шины NACK |
22 |
ALOSF |
ERR_IE |
Arbitration lost error |
Потеря приоритета |
23 |
BERRF |
ERR_IE |
Bus error |
Ошибка шины |
В режиме Byte mode для работы ПО необходимо включить прерывание EVENT_IE по флагу EVENTF. В режиме Buffer mode для работы ПО достаточно включить прерывание BUF_IE по флагу BUFF. Если требуется анализ ошибок, необходимо также включить прерывание ERR_IE по общему флагу ERRF. Прерывание TMOUT_IE генерируется только при включении в работу модуля таймера таймаута TMO.
В процессе тестирования обнаружена недокументированная функция: в старшем байте регистра I2Cx_STA
отображается код состояния модуля I2Cx_EVENT
, в том числе, в режиме Buffer mode.
Для включения генерации прерывания необходимо:
-
Выбрать событие (или события) в регистре
I2Cx_INT
. -
Разрешить прерывание самого модуля установкой бита
I2Cx_INT.I2Cx_IEA
. -
Разрешить прерывание IRQ от модуля в контроллере прерываний NVIC в регистре
CPU_ISER
.
Тестирование
Аппаратная часть
Целью тестирования является проверка работы МК в роли ведущего и ведомого устройства шины I2C. В роли ведущего будет применяться МК MG32F02A064AD48, в роли ведомого — (1) «эталонный» Slave — часы реального времени DS3231 и (2) МК MG32F02A032AT20. Схема подключения микроконтроллеров показана на следующем рисунке.
Все устройства запитываются от стабилизатора напряжением 3.3 В программатора J-Link/ST-Link. Для удобства переключения линий SWD-интерфейса между МК установлен переключатель S1. Для линий интерфейса I2C SCL и SDA установлены внешние резисторы подтяжки R3 и R4. При подключении модуля DS3231 МК U2 отключается от шины I2C перемычками (на схеме не показаны), а при работе U2 в качестве ведомого — отключается модуль DS3231, поскольку МК эмулирует его работу. Каждый МК также подключается к ПК через интерфейс UART (в обоих случаях используется модуль МК URT0).
Проектные файлы
В тестировании принимают участие одновременно два «подопытных» МК, поэтому в файлах конфигурации проекта есть изменения. Действия по настройке путей к библиотечным файлам от вендора перенесены в отдельную функцию setup_paths()
в файле premake5.lua
, поскольку пути зависят от типа МК. Для МК MG32F02A032 аргументом функции нужно указать строку "MG32F02A032"
, для всех остальных МК семейства — строку "MG32F02A128"
. В файле определены следующие цели сборки:
-
svr32
— базовая часть (supervisor) для МК MG32F02A032, -
svr64
— базовая часть для МК MG32F02A064, -
app
— прикладная часть (application) для ведущего устройства (MG32F02A064), -
slave
— прикладная часть для ведомого устройства (MG32F02A032), -
clock
— прикладная часть для создания часов на базе LED-дисплея на м/с TM1637 (MG32F02A064), в статье не рассматривается.
В каталог проекта также добавлены соответствующие скрипты shell для сборки, запуска и просмотра листинга дизассемблера. Библиотечные файлы драйверов внешних микросхем, подключаемых далее к МК, выделены в отдельный подкаталог ic
.
Параметры и действия, связанные с конфигурацией выводов МК, вынесены в отдельный заголовочный файл hwcf.h
. На данный момент заданы два набора параметров. В качестве примера приводим набор для MG32F02A032:
#ifdef HWCF_A032 #define HW_CLK_AHB 12000000 // MHz #define HW_LED1_CRH0 PB_CR2_h0 // control register #define HW_LED1_SCH0 PB_SC_h0 // set-clear register #define HW_LED1_MASK (1 << 2) // bit mask #define HW_LED2_CRH0 PB_CR3_h0 // control register #define HW_LED2_SCH0 PB_SC_h0 // set-clear register #define HW_LED2_MASK (1 << 3) // bit mask // Настройка выводов URT0: #define HW_URT0_SETTX RH(PC_CR0_h0) = (0xA << 12) | 2 #define HW_URT0_SETRX RH(PC_CR1_h0) = (0xA << 12) | (1 << 5) | 3 // Настройка выводов I2C0: #define HW_I2C0_SETSCL RH(PB_CR10_h0) = (2 << 12) | (1 << 5) | 1 #define HW_I2C0_SETSDA RH(PB_CR11_h0) = (2 << 12) | (1 << 5) | 1 #endif // HWCF_A032
Настройки выводов светодиодов сделаны через константы. Настройки выводов интерфейсов UART и I2C заданы в виде полной операции конфигурации портов.
Напомню, что весь исходный код и сопутствующие файлы доступны в репозитории GitHub.
Библиотека для работы с модулем I2C
Общие функции
Для удобства тестирования разработана библиотека модуля I2C с минимальным набором функций (файлы src/i2c.h
и src/i2c.c
). Библиотека поддерживает только аппаратный режим Buffer mode. Каждая функция получает первым аргументом идентификатор конкретного модуля I2Cx в виде базового адреса его регистров:
#define I2C0_id I2C0_Base #define I2C1_id I2C1_Base
Такой подход оказался очень эффективным, поскольку «заставил» компилятор gcc применять инструкции обращения к памяти с указанием константного смещения вида str r1, [r0, #16]
(здесь r0
— первый аргумент, который содержит базовый адрес), вместо того, чтобы для каждого обращения к регистру выделять в секции кода еще 4 байта для его адреса.
Работа с модулем начинается с функции инициализации i2c_init()
:
/// Инициализация модуля I2C (включение тактирования) void i2c_init(uint32_t id) { RH(CSC_KEY_h0) = 0xA217; // unlock access to CSC regs #ifdef MG32F02A032 RB(CSC_APB0_b1) |= CSC_APB0_I2C0_EN_enable_b1; #endif #ifdef MG32F02A128 RB(CSC_APB0_b1) |= (id & 0x00010000) ? CSC_APB0_I2C1_EN_enable_b1 : CSC_APB0_I2C0_EN_enable_b1; #endif RH(CSC_KEY_h0) = 0; // lock access to CSC regs // Настройка тактирования RH(id+( I2C0_CLK_h0 -I2C0_Base)) = I2C_CLK_TMO_CKS_div64_h0 | // CK_TMO: F(CK_PSC)/64 = 37500 Hz ((5 -1) << I2C_CLK_CK_PSC_shift_h0) | // CK_PSC: 12 MHz /5 = 2400 kHz I2C_CLK_CK_DIV_div4_h0 | // CK_I2Cx_INT: 600 kHz => F(SCL) = 100 kHz I2C_CLK_CK_SEL_proc_h0; // I2Cx_CK_SEL: APB, 12 MHz // Тайминг режима master RH(I2C0_CR1_h0) = 0x0202; // (2+HT+LT) = 6 }
Здесь в зависимости от типа МК включается общее тактирование единственного модуля I2C0 или одного из двух доступных I2C0 или I2C1 в зависимости от id
. Далее производится настройка тактирования внутри модуля. В данном примере показаны настройки на частоту шины 100 кГц. Расчет следующий: суммарный коэффициент деления k = F(CK_I2Cx) / FSCL = 12 МГц / 100 кГц = 120. При HT=2 и LT=2 получаем
(PSC + 1)·DIV = k/ (2 + HT + LT) = 20.
Чтобы частота сигнала CK_I2Cx_TMO была как можно выше, выбираем PSC=4 (коэффициент предделителя 4+1=5) и DIV=4. Таким образом, получаем частоту сигнала I2Cx_CK_PSC 2400 кГц, сигнала I2Cx_CK_INT — 600 кГц, сигнала I2Cx_CK_TMO — 37.5 кГц.
Режим работы модуля устанавливается с помощью функции i2c_setup_mode()
:
/// Установка режима работы модуля I2C по формату регистра CR0 (устанавливает I2Cx_EN) inline void i2c_setup_mode(uint32_t id, uint32_t mode) { RW(id+( I2C0_CR0_w -I2C0_Base)) = mode | I2C_CR0_EN_enable_w; // включаем модуль }
Для настройки прерывания имеется функция i2c_setup_int()
:
/// Включение прерывания INT_I2Cx по флагам, указанным в flags согласно формату I2Cx_INT void i2c_setup_int(uint32_t id, uint32_t flags) { RW(id+( I2C0_INT_w -I2C0_Base)) = flags | I2C_INT_IEA_enable_w; // включаем прерывания в модуле // включаем прерывание в модуле NVIC: RW(CPU_ISER_w) = 1 << ((id & 0x00010000) ? 29 : 28); // SETENA }
В функциях блокирующего опроса состояния модуля используется таймер таймаута, запуск которого с установкой интервала времени ~ 1 мс осуществляется функцией i2c_setup_tmout()
:
/// Настройка таймера таймаута, режим работы mode определяется по формату младшего байта регистра I2Cx_TMOUT. inline void i2c_setup_tmout(uint32_t id, uint8_t mode) { RH(id+( I2C0_TMOUT_h0 -I2C0_Base)) = (38 << I2C_TMOUT_TMO_CNT_shift_h0) | // период счета ~1 мс для F(CK_TMO)=37.5 кГц mode | // I2C_TMOUT_TMO_MDS_scl_low_h0 | // I2C_TMOUT_TMO_MDS_scl_sda_high_h0 | I2C_TMOUT_TMO_CTL_enable_h0 | I2C_TMOUT_TMO_EN_enable_h0; }
Проверка срабатывания таймера осуществляется функцией i2c_get_tmout()
:
/// Возвращает 1, если таймаут, иначе 0. inline uint32_t i2c_get_tmout(uint32_t id) { return (RB(id+( I2C0_STA_b0 -I2C0_Base)) & I2C_STA_TMOUTF_happened_b0) != 0; }
Все биты регистра статуса возвращает функция i2c_get_status()
:
/// Возвращает I2Cx_STA inline uint32_t i2c_get_status(uint32_t id) { return RW(id+( I2C0_STA_w -I2C0_Base)); }
Следующие две функции i2c_wait_start()
и i2c_wait_stop()
предназначены для ожидания состояний RSTART и STOP:
/// Ожидает состояние REPEAT START void i2c_wait_start(uint32_t id) { i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_RSTRF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; } /// Ожидает состояние STOP void i2c_wait_stop(uint32_t id) { i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_STOPF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; }
Функции для ведущего устройства (Master)
В предлагаемых примерах работа ведущего устройства реализована на основе блокирующих функций с заданным временем ожидания без применения прерывания. Функция i2c_master_startw()
генерирует кадр SLA+W и ожидает готовность модуля (перед отправкой данных или завершения кадра):
/// Генерирует состояние START + WRITE с указанным адресом (младший бит должен быть 0). void i2c_master_startw(uint32_t id, uint8_t addr) { RW(id+( I2C0_CR2_w -I2C0_Base)) = (1 << 8) | // BUF_CNT I2C_CR2_STA_LCK_un_locked_w | I2C_CR2_STA_mask_w; // STA RB(id+( I2C0_DAT_b0 -I2C0_Base)) = addr; i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_TXF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; }
Функция i2c_master_startr()
генерирует кадр SLA+R и ожидает его завершение:
/// Генерирует состояние START + READ с указанным адресом (младший бит должен быть 0). void i2c_master_startr(uint32_t id, uint8_t addr) { RW(id+( I2C0_CR2_w -I2C0_Base)) = (1 << 8) | // BUF_CNT I2C_CR2_STA_LCK_un_locked_w | I2C_CR2_STA_mask_w; // STA RB(id+( I2C0_DAT_b0 -I2C0_Base)) = addr | 0x01; i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_SADRF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; }
Функция i2c_master_send()
помещает в буфер передачи данные data
длиной len
от 1 до 4 байт и ожидает их передачу в промежуточный буфер:
/// Режим master: передача в режиме Buffer len байт (1-4) из data. /// Генерирует состояние STOP, если указана опция I2C_STOP. /// Блокирующая функция с таймаутом: ожидает флаг TXF. void i2c_master_send(uint32_t id, uint32_t opts, uint8_t len, uint32_t data) { RW(id+( I2C0_STA_w -I2C0_Base)) |= I2C_STA_TXF_mask_w; // сбрасываем TXF, иначе финальная проверка в конце функции может сработать сразу после старта RW(id+( I2C0_CR2_w -I2C0_Base)) = opts | (len << 8); // BUF_CNT RW(id+( I2C0_DAT_w -I2C0_Base)) = data; i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_TXF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; }
Функция i2c_master_recv()
ожидает прием данных в количестве len
байт и возвращает содержимое 32-битного буфера:
/// Режим master: прием в режиме Buffer len байт (1-4). /// Генерирует ACK, если указана опция I2C_ACK. /// Генерирует состояние STOP, если указана опция I2C_STOP. /// Блокирующая функция с таймаутом: ожидает флаг RXF. uint32_t i2c_master_recv(uint32_t id, uint32_t opts, uint8_t len) { RW(id+( I2C0_CR2_w -I2C0_Base)) = opts | (len << 8); // BUF_CNT i2c_setup_tmout(id,0); while (! (RW(id+( I2C0_STA_w -I2C0_Base)) & (I2C_STA_RXF_happened_w | I2C_STA_TMOUTF_happened_w) )) ; return RW(id+( I2C0_DAT_w -I2C0_Base)); }
Указанные две функции принимают вторым аргументом opts
опции в виде двоичных флагов в соответствии с форматом регистра I2Cx_CR2
. Используемые опции включают команды и собраны в перечислении:
/// Опции по формату регистра I2Cx_CR2 enum I2C_Options { I2C_NOOPTS = 0, I2C_NACK = I2C_CR2_CMD_TC_enable_w | I2C_CR2_AA_LCK_un_locked_w, I2C_ACK = I2C_CR2_CMD_TC_enable_w | I2C_CR2_AA_LCK_un_locked_w | I2C_CR2_PAA_mask_w, I2C_STOP = I2C_CR2_CMD_TC_enable_w | I2C_CR2_STO_LCK_un_locked_w | I2C_CR2_PSTO_mask_w, I2C_START = I2C_CR2_CMD_TC_enable_w | I2C_CR2_STA_LCK_un_locked_w | I2C_CR2_PSTA_mask_w };
Функции для ведомого устройства (Slave)
Работа ведомого устройства реализована на основе неблокирующих функций с применением простейшего конечного автомата и прерывания модуля I2Cx.
Функция i2c_write()
записывает данные от 1 до 4 байт в буфер для отправки:
/// Запись данных в буфер отправки (1-4 байта) inline void i2c_write(uint32_t id, uint32_t data, uint8_t len) { RB(id+( I2C0_CR2_b1 -I2C0_Base)) = (len & I2C_CR2_BUF_CNT_mask_b1); // BUF_CNT RW(id+( I2C0_DAT_w -I2C0_Base)) = data; }
Функция i2c_read()
считывает данные (4 байта) из буфера после приема:
/// Чтение буфера приема (4 байта) inline uint32_t i2c_read(uint32_t id) { return RW(id+( I2C0_DAT_w -I2C0_Base)); }
Для передачи данных объемом более 4 байт можно использовать функцию i2c_writebuf()
:
/// Запись данных из буфера программы в буфер отправки. /// На входе: *p - текущее положение в буфере (указывает на следующий байт после отправленного). /// На выходе: *p - новое значение, увеличенное на число отправленных байт. /// Возвращает оставшееся число байт. uint32_t i2c_writebuf(uint32_t id, const void* buf, uint32_t* p, uint32_t len) { uint8_t m; int32_t n; n = len - *p; if (n > 0) { m = n < 4 ? n : 4; RB(id+( I2C0_CR2_b1 -I2C0_Base)) = (m & I2C_CR2_BUF_CNT_mask_b1); // BUF_CNT switch (m) { case 1: RB(id+( I2C0_DAT_b0 -I2C0_Base)) = *((uint8_t*)buf + *p); break; case 2: RH(id+( I2C0_DAT_h0 -I2C0_Base)) = *(uint16_t*)((uint8_t*)buf + *p); break; default: RW(id+( I2C0_DAT_w -I2C0_Base)) = *(uint32_t*)((uint8_t*)buf + *p); break; } *p += m; } return len-*p; }
Функция за один вызов может отправить не более 4 байт и предназначена для работы в подпрограмме обработки прерывания. Функция вызывается несколько раз до тех пор, пока весь буфер длиной len
байт не будет передан на отправку. На входе также указывается адрес буфера buf
, из которого нужно передать данные и адрес переменной p
, хранящей текущую позицию в буфере. Содержимое буфера и значение len
не должны изменяться пока все данные не будут отправлены. Перед первым вызовом по адресу p
должен быть записан 0. Функция возвращается число оставшихся неотправленных байт.
Аналогично для приема данных объемом более 4 байт можно использовать функцию i2c_readbuf()
:
/// Чтение принятых данных и их запись в указанный буфер. /// На входе: *p - текущее положение в буфере (указывает на следующий байт после записанного). /// На выходе: *p - новое значение, увеличенное на число считанных байт. /// Возвращает фактическое число считанных байт. uint8_t i2c_readbuf(uint32_t id, void* buf, uint32_t* p) { uint8_t n = RB(id+( I2C0_CR2_b1 -I2C0_Base)) & I2C_CR2_BUF_CNT_mask_b1; // BUF_CNT *(uint32_t*)((uint8_t*)buf + *p) = RW(id+( I2C0_DAT_w -I2C0_Base)); *p += n; return n; }
Функция за один вызов может принять не более 4 байт. Функция вызывается каждый раз после срабатывания флага RXF до тех пор, пока не будет принят весь объем данных. На входе также указывается адрес буфера buf
, в который нужно записывать данные и адрес переменной p
, хранящей текущую позицию в буфере. Перед первым вызовом по адресу p
должен быть записан 0. Функция возвращается число байт, принятых за один вызов.
Библиотека для работы с часами DS3231
Функции для работы с м/с DS3231 помещены в файл ic/ds3231.c
. В файле ic/ds3231.h
объявлены идентификатор модуля DS3231_PORT
и адрес ведомого DS3231_ADDR
:
#define DS3231_PORT I2C0_id // I2C module base address #define DS3231_ADDR 0xD0 // 0b1101000X, X - direction: 0 - write, 1 - read.
Для удобства проверки и обработки таймаута созданы макросы:
#define TMOUT_CHECK if (i2c_get_tmout(DS3231_PORT)) return; #define TMOUT_CHECK2 if (i2c_get_tmout(DS3231_PORT)) goto failure;
В файле также объявлено перечисление DS3231_Registers
с адресами регистров DS3231 и прототипы функций для работы с часами. Функция ds3231_read()
выполняет запрос значения одного регистра reg
(все регистры 8-разрядные):
uint8_t ds3231_read(uint8_t reg) { uint32_t d; i2c_master_startw(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK2 i2c_master_send(DS3231_PORT, I2C_START, 1, reg); TMOUT_CHECK2 i2c_wait_start(DS3231_PORT); TMOUT_CHECK2 i2c_master_startr(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK2 d=i2c_master_recv(DS3231_PORT, I2C_STOP, 1); TMOUT_CHECK2 i2c_wait_stop(DS3231_PORT); TMOUT_CHECK2 return d; failure: return 0;
Вначале формируется кадр SLA+W с одним байтом-адресом, затем отдельно передается байт с номером запрашиваемого регистра и автоматическим формированием RSTART по окончанию передачи. Перед дальнейшими действиями ожидается переход шины в это состояние. Далее формируется кадр SLA+R, в котором запрашивается 1 байт данных, после чего формируется состояние STOP и ожидается его фактическая генерация. На каждом шаге выполняется проверка таймаута.
Альтернативным методом чтения регистров DS3231 является мультибайтовый запрос (функция ds3231_read_multi()
), в котором ведущий указывает номер первого регистра, а затем выполняет чтение данных первого и последующих по порядку регистров до тех пор, пока не сформирует состояние NACK:
uint32_t ds3231_read_multi(uint8_t first_reg, uint8_t len) { uint32_t d; i2c_master_startw(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK2 i2c_master_send(DS3231_PORT, I2C_START, 1, first_reg); TMOUT_CHECK2 i2c_wait_start(DS3231_PORT); TMOUT_CHECK2 i2c_master_startr(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK2 d=i2c_master_recv(DS3231_PORT, I2C_STOP, len & 0x07); TMOUT_CHECK2 i2c_wait_stop(DS3231_PORT); TMOUT_CHECK2 return d; failure: return 0; }
В данной функции максимальное число регистров модуля DS3231, которые можно прочитать, ограничено числом 4 по размеру буфера приема (4 байта). Состояние NACK модуль I2C сгенерирует автоматически после приема указанного числа байт len
перед формированием состояния STOP. Аналогично реализованы два вида функций записи данных в регистры DS3231:
void ds3231_write(uint8_t reg, uint8_t val) { i2c_master_startw(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK i2c_master_send(DS3231_PORT, I2C_STOP, 2, ((uint32_t)val << 8) | reg ); TMOUT_CHECK i2c_wait_stop(DS3231_PORT); TMOUT_CHECK } void ds3231_write_multi(uint8_t first_reg, uint8_t len, uint32_t vals) { i2c_master_startw(DS3231_PORT, DS3231_ADDR); TMOUT_CHECK i2c_master_send(DS3231_PORT, I2C_NOOPTS, 1, first_reg); TMOUT_CHECK i2c_master_send(DS3231_PORT, I2C_STOP, len & 0x07, vals); TMOUT_CHECK i2c_wait_stop(DS3231_PORT); TMOUT_CHECK }
Запись реализуется проще, поскольку все данные можно передать одним пакетом после кадра SLA+W. Также реализованы две функции «верхнего» уровня для установки и запроса времени в двоично-десятичном формате (BCD) 0xHHMMSS:
/// Установка времени в формате BCD: 0xHHMMSS void clock_set_bcd(uint32_t t) { ds3231_write(REG_SEC, t); ds3231_write(REG_MIN, t >> 8); ds3231_write(REG_HOUR,t >> 16); } /// Считывание времени в формате BCD: 0xHHMMSS uint32_t clock_get_bcd() { uint8_t d[4]; d[0]=ds3231_read(REG_SEC); d[1]=ds3231_read(REG_MIN); d[2]=ds3231_read(REG_HOUR); d[3]=0; return *(uint32_t*)d; }
Здесь для наглядности приведены версии функций с побайтовыми запросами, которые компилируются при определении макроса DS3231_ONEBYTE_MODE
. Если макрос не определен, компилируются мультибайтовые версии этих двух функций.
Реализация ведущего устройства
Перейдем к реализации ведущего устройства в аппаратном режиме (Buffer mode). В качестве ведомого будем использовать модуль часов на основе м/с DS3231. Тестовые функции ведущего помещены в файл src/i2c_test.c
, а вызываются они из прикладной части в функции app()
.
Перед началом работы вызывается функция настройки модуля i2c_test_master_setup()
:
void i2c_test_master_setup() { i2c_init(DS3231_PORT); HW_I2C0_SETSCL; HW_I2C0_SETSDA; // Настройка режима работы i2c_setup_mode(DS3231_PORT, I2C_CR0_PDRV_SEL_1t_w | I2C_CR0_BUF_EN_enable_w | // Режим работы через буфер I2C_CR0_MDS_i2c_w // I2C : Single/Multi-Master/ Slave mode ); }
В ней происходит настройка тактирования, установка выводов SCL и SDA, а затем задается режим работы модуля I2C. Далее вызывается одна из трех функций, выполняющих запросы:
-
i2c_test_master_w1r_ds3231()
— запрос времени с DS3231; -
i2c_test_master_wN()
— установка произвольного регистра в ведомом; -
i2c_test_master_w1r()
— запрос произвольного количества байт в ведомом.
Рассмотрим первую функцию:
void i2c_test_master_w1r_ds3231() { uint32_t d; while (1) { d=clock_get_bcd(); if (i2c_get_tmout(I2C_PORT)) { d=i2c_get_status(I2C_PORT); debug32hex('S',d); i2c_print_status(d); led2_flash(); i2c_clr_status(I2C_PORT, I2C_STA_TMOUTF_mask_w); } else { debug32hex('T',d); } delay_ms(1000); } }
В цикле примерно 1 раз в секунду опрашиваются регистры, содержащие секунды, минуты и часы. Если не происходит срабатывание таймера таймаута, полученное 24-битное число в формате 0xHHMMSS выводится в терминал. Если таймер срабатывает, в терминал выводится статус модуля I2C со всеми флагами в многострочном текстовом формате (по 8 флагов в строку). Для этого используется отладочная функция разработанной библиотеки i2c_print_status()
, которая компилируется только при установленном макросе I2C_DEBUG
. После срабатывания таймера флаг TMOUTF в программе сбрасывается. Для контроля также включается светодиод D2.
Подключаем часы DS3231 и смотрим результат работы в терминале. Через несколько секунд разрываем соединение с ведомым, затем вновь его восстанавливаем:
Hello T 00180353 T 00180354 T 00180355 T 00180356 S F8000010 ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- TMOUT ----- ----- ----- ----- S F8000010 ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- TMOUT ----- ----- ----- ----- T 00180359 T 00180400 T 00180401
Видно, что ведущий работает и получает корректное значение всех трех байт времени. Также работает таймер TMO при нарушении связи: видно, что в слове статуса устанавливается флаг таймаута (бит 4), а старший байт отображает состояние модуля 0xF8 (недокументированная функция). С помощью цифрового анализатора Saleae Logic посмотрим временную диаграмму работы шины (канал 0 (белый) — сигнал SDA, канал 1 (коричневый) — сигнал SCL):
Запрос начинается с кадра SLA+W, адрес подтверждается (ACK), затем отправляется номер регистра секунд (0x00). Далее, после состояния RSTART (зеленый кружок) формируется кадр SLA+R, адрес подтверждается (ACK) и далее происходит считывание трех байт с подтверждением первых двух со стороны ведущего. Последний байт не подтверждается и ведущий генерирует состояние STOP (красный кружок).
Теперь посмотрим, что происходит в момент обрыва соединения с ведомым:
В результате обрыва ведущий не получил ACK в кадре SLA+W, прервал передачу данных и перешел в режим ожидания. Уменьшим масштаб и посмотрим длительность состояния низкого уровня на линии SCL:
Время ожидания составило установленную 1 мс, после чего модуль «сбросился» и перешел в начальное состояние. Тест пройден успешно.
Реализация ведомого устройства
Общая функция
Перейдем к реализации ведомого устройства на МК MG32F02A032. Модуль I2C также будет работать в аппаратном режиме. Для ведомого устройства удобно использовать прерывания, поскольку события на шине I2C для него всегда будут асинхронными. В проекте сконфигурирована цель slave
с отдельным главным файлом прикладной части app_slave.c
, в котором собраны все функции тестирования и обработчики прерывания. В файле определены идентификатор порта I2C_PORT
и глобальные переменные, отвечающие за буфер:
#define I2C_PORT I2C0_id #define BUFLEN 16 /// Буфер данных слэйва uint8_t buf[BUFLEN]; ///< данные буфера uint32_t bufp; ///< указатель буфера uint32_t bufn; ///< размер данных на отправку
Указатель bufp
содержит текущую позицию в буфере buf
для операций чтения или записи и должен быть обнулен перед началом работы. Рассмотрим основную функцию i2c_test_slave()
:
/// Общая настройка режима слэйва void i2c_test_slave() { uint32_t i; for (i=0; i< BUFLEN; i++) buf[i]=((i+1)<< 4) | (i+1); // инициализация буфера // Настройка тактирования: i2c_init(I2C_PORT); // Настройка выводов I2C: HW_I2C0_SETSCL; HW_I2C0_SETSDA; // Настройка режима работы i2c_setup_mode(I2C_PORT, I2C_CR0_PDRV_SEL_1t_w | I2C_CR0_BUF_EN_enable_w | // Режим работы через буфер I2C_CR0_MDS_i2c_w | // I2C : Single/Multi-Master/ Slave mode I2C_CR0_SADR_EN_enable_b0 // Включаем детектор адреса слэйва ); // Адрес слэйва: RB(I2C0_SADR_b0) = DS3231_ADDR; // установка адреса RB(I2C0_MASK_b0) = 0xFE; // настройка маски // Отключаем задержку сигнала SCL от слэйва RB(I2C0_CR0_b1) |= I2C_CR0_SCLS_DIS_disable_b1; // Устанавливаем обработчик прерываний: SVC2(SVC_HANDLER_SET,28,i2c_hdl_w1rN); // Включаем прерывания в модуле: RW(I2C0_INT_w) = I2C_INT_BUF_IE_enable_w | // flags: RXF, TXF, RSTRF, STOPF, SADRF I2C_INT_IEA_enable_w; // Включаем прерывание в модуле NVIC: RW(CPU_ISER_w) = (1 << 28); // SETENA 28 while (1) { // Проверка ACNT: if (RB(I2C0_CR2_b2) & 0x07) led2_on(); else led2_off(); } }
В начале функции инициализируется буфер значениями 0x11, 0x22, и т.д. Далее настраивается тактирование, назначаются выводы модуля и устанавливается собственный адрес устройства, такой же, как и у часов DS3231. Затем устанавливается обработчик прерывания и настраивается прерывание по флагу BUFF, включающее все события (кроме ошибок), которые могут произойти в режиме Buffer mode. С этого момента функция ведомого устройства будет целиком определяться обработчиком прерывания.
Передача данных по запросу ведущего
Начнем с обработчика i2c_hdl_w1rN()
, реализующего отправку произвольного числа байт из своего буфера. Протокол обмена следующий: ведущий отправляет пакет SLA+W с одним байтом, в котором указывает число запрашиваемых байт. Ведомый после получения кадра SLA+R начинает отправку запрошенных данных. Ведущий подтверждает прием каждого байта, кроме последнего, после чего формирует состояние STOP. Рассмотрим код функции:
/// Обработчик прерывания I2C0 void i2c_hdl_w1rN() { uint32_t d; // флаги uint32_t n; led1_on(); d=i2c_get_status(I2C_PORT); if (d & I2C_STA_SADRF_mask_w) { if (d & I2C_STA_RWF_read_w) { // Мастер читает } else { // Мастер пишет } } if (d & I2C_STA_TXF_mask_w) { //if (bufp==bufn) led2_on(); if (bufn > bufp) {i2c_writebuf(I2C_PORT,buf,&bufp,bufn); led1_on();} } if (d & I2C_STA_RXF_mask_w) { n=i2c_read(I2C_PORT) & 0xFF; bufn = (n <= BUFLEN) ? n : BUFLEN; bufp=0; i2c_writebuf(I2C_PORT,buf,&bufp,bufn); // Подготавливаем данные для отправки (копируем в буфер максимум байт (4)) } if (d & I2C_STA_STOPF_mask_w) { } if (d & I2C_STA_RSTRF_mask_w) { } led1_off(); i2c_clr_status(I2C_PORT, 0x00ffffff); }
Светодиодные выводы МК будем использовать для отладки и подключим к цифровому анализатору вместе с сигналами SCL и SDA. Сигнал со светодиода D1 будет показывать момент возникновения прерывания, а сигнал с D2 — срабатывание некоторых флагов. В зависимости от установленных флагов в функции выполняются те или иные действия. Одновременно могут быть установлены несколько флагов.
При установленном флаге SADRF далее определяется тип кадра SLA (чтение или запись) и выполняются какие-либо прикладные действия (показано в качестве шаблона). При установленном флаге RXF считывается младший байт буфера приема в переменную n
— запрашиваемое число байт, которое затем проходит формальную проверку на непревышение BUFLEN
. Далее в буфер на отправку передается первая порция данных через функцию i2c_writebuf()
. При установленном флаге TXF вновь вызывается функция i2c_writebuf()
для передачи оставшейся порции данных. В качестве шаблона также показан код для определения установки флагов STOPF и RSTRF. В конце функции сигнал D1 возвращается в 0 и сбрасываются все флаги.
На стороне ведущего устройства в файле i2c_test.c
для данного теста будем использовать функцию i2c_test_master_w1r()
, в которой раз в секунду запрашивается заданное число байт (9) с помощью вспомогательной функции i2c_test_master_req()
. Полученные данные помещаются в глобальный буфер (аналогично ведомому) и после приема последнего байта выдаются в терминал через отладочную функцию debugbuf()
. Состояние таймаута проверяется аналогично функции i2c_test_master_w1r_ds3231()
. Код этих двух функций читатель может посмотреть в репозитории.
Подключаем вместо DS3231 МК MG32F02A032 и смотрим результат в терминале ведущего:
11 22 33 44 55 66 77 88 99 11 22 33 44 55 66 77 88 99 11 22 33 44 55 66 77 88 99
Теперь посмотрим временные диаграммы (канал 2 (красный) — сигнал D1, канал 3 (желтый) — сигнал D2):
В функции i2c_test_slave()
после разрешения прерываний мы оставили циклический опрос счетчика I2Cx_ACNT
: когда его значение становится отлично от нуля, сигнал D2 переходит в состояние высокого уровня. Проанализируем диаграммы. Прерывание сработало всего три раза: первый раз по флагу RXF после фиксирования состояния RSTART, что показывает та же диаграмма в большем масштабе:
В этот момент мы подготовили данные для отправки, не дожидаясь следующего прерывания. В противном случае ведущий получил бы некорректные данные (читатель может это проверить самостоятельно). Сразу после получения первого байта со значением 0x09 и подтверждением ACK значение ACNT стало равно 1, но после прочтения вернулось в 0.
Второй раз прерывание сработало по флагу TXF после кадра SLA+R и подтверждения ACK в момент начала передачи первого байта от ведомого:
В этот момент первые 4 байта уже были переданы в промежуточный буфер для отправки и основной буфер I2Cx_DAT
освободился для новых данных, что привело к установке флага. Теперь мы записали в этот буфер следующие 4 байта. После фактической передачи одного байта (0x11) в сдвиговый регистр значение счетчика ACNT изменилось на 1 и дальше увеличивалось с отправкой каждого последующего байта.
Третий раз прерывание сработало также по флагу TXF после передачи очередных 4 байт в промежуточный буфер. Значение счетчика сбросилось в 0. Мы записали в буфер последний, 9-й байт. Следующие 4 байта 0x55-0x88 были отправлены из промежуточного буфера сразу. После этого, когда счетчик ACNT вновь сбросился, последний байт 0x99 был также скопирован в промежуточный буфер и отправлен.
Прием данных от ведущего
Теперь рассмотрим обработчик i2c_hdl_wN()
со следующим алгоритмом: slave-устройство принимает один пакет с данными и подтверждает каждый байт до тех пор, пока ведомый их передает, после чего дамп буфера выводится в терминал:
/// Обработчик прерывания I2C0 void i2c_hdl_wN() { uint32_t d; led1_on(); d=i2c_get_status(I2C_PORT); if (d & I2C_STA_SADRF_mask_w) { if (d & I2C_STA_RWF_read_w) { // Master reads } else { // Master writes bufp=0; } } if (d & I2C_STA_RXF_mask_w) { i2c_readbuf(I2C_PORT,buf,&bufp); } if (d & I2C_STA_STOPF_mask_w) { debugbuf(buf,bufp); } led1_off(); i2c_clr_status(I2C_PORT, 0x00ffffff); }
На стороне ведущего устройства в файле i2c_test.c
для данного теста будем использовать функцию i2c_test_master_wN()
, в которой раз в секунду ведущий передает сначала 1 байт с 0x01, затем последовательность из 4 байт 0xA1, 0xB2, 0xC3, 0xD4.
Смотрим результат в терминале ведомого:
01 A1 B2 C3 D4 01 A1 B2 C3 D4 01 A1 B2 C3 D4
Теперь посмотрим временные диаграммы:
Первый раз прерывание сработало по флагу SADRF после получения кадра SLA+W до генерации подтверждения ACK. После приема первого байта из сдвигового регистра счетчик ACNT принял значение 1. Второй раз прерывание сработало по флагу RXF после приема первых 4 байт, данные из буфера были прочитаны, после чего счетчик ACNT сбросился. После приема последнего байта счетчик ACNT вновь принял значение 1. Третий раз прерывание сработало по двум флагам STOPF и RXF. Мы прочитали из буфера последний байт и стали выполнять длительную процедуру по выводу буфера в терминал, на что потратили примерно 1,4 мс (диаграмма с таким масштабом не показана). Поэтому сигналы D1 и D2 сразу не вернулись в состояние 0.
Заключение
Мы экспериментально проверили основные сценарии взаимодействия ведущего и ведомого устройств в максимально автоматизированном аппаратном режиме работы модулей I2C. Для более специфических задач можно использовать низкоуровневый режим Byte mode. Модуль показал полную работоспособность и высокую функциональность одновременно с относительной простой использования. Предложенные библиотеки могут быть взяты за основу для разработки собственных прошивок МК. За рамками статьи остались следующие вопросы, которые читатель может изучить по User Guide:
-
управление активной подтяжкой для линий SCL и SDA (16.12.1),
-
пробуждение модуля из состояния STOP (16.12.3),
-
обработка ошибок (16.13),
-
применение DMA (16.14),
-
режим Monitor.
ссылка на оригинал статьи https://habr.com/ru/post/695670/
Добавить комментарий