Кто-то любит пирожки, а кто-то - нет.
Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки.
Естественно, с полной поддержкой всего этого дела.
Работа с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате:
Я> Шли START.
S> Ок, послал.
Я> Круто, шли адрес теперь. Вот такой: 0xXX.
S> Ок, послал. Мне сказали, что ACK. Давай дальше.
Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли.
S> Послал, получил ACK.
Я> Шли ему теперь данные, вот тебе байт: 0xZZ.
S> Послал, он согласен на большее: ACK.
Я> Фиг ему, а не ещё. Шли STOP.
S> Okay.
И всё примерно в таком духе.
В данном контроллере выводы i2c раскиданы по портам таким образом:
PB6: I2C1_SCL
PB7: I2C1_SDA
PB8: I2C1_SCL
PB9: I2C1_SDA
PB10: I2C2_SCL
PB11: I2C2_SDA
PA8: I2C3_SCL
PC9: I2C3_SDA
Вообще, распиновку периферии удобно смотреть в на 59 странице.
Что удивительно, но для работы с i2c нужны все его регистры, благо их немного:
I2C_CR1
- команды модулю для отправки команд/состояний и выбор режимов работы;
I2C_CR2
- настройка DMA и указание рабочей частоты модуля (2-42 МГц);
I2C_OAR1
- настройка адреса устройства (для slave), размер адреса (7 или 10 бит);
I2C_OAR2
- настройка адреса устройства (если адресов два);
I2C_DR
- регистр данных;
I2C_SR1
- регистр состояния модуля;
I2C_SR2
- регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1);
I2C_CCR
- настройка скорости интерфейса;
I2C_TRISE
- настройка таймингов фронтов.
Впрочем, половина из них типа «записать и забыть».
На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22 , аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.
Порядок настройки примерно таков:
1
. Разрешить тактирование портов и самого модуля.
Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся.
И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно.
2
. Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR).
По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер.
3
. Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола.
Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц).
Максимально разрешённая частота тактирования: 42 МГц.
4
. Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый).
Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано).
5
. Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один:
если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9.
6
. По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных);
7
. Модуль включается: флаг PE в регистре CR1 переводится в 1.
Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра:
1
. Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1.
Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе.
2
. Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм.
Если устройство ответит на адрес ACK"ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure).
Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных.
Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей.
но мы рассматриваем запись, потому примем, что там был ноль.
3
. Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена).
4
. Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся.
5
. По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.
При чтении всё то же самое. Меняется только после записи адреса регистра.
Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения).
Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK).
Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.
Вот то же самое в виде кода:
// Инициализация модуля
void i2c_Init(void)
{
uint32_t Clock = 16000000UL; // Частота тактирования модуля (system_stm32f4xx.c не используется)
uint32_t Speed = 100000UL; // 100 кГц
// Включить тактирование порта GPIOB
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// Настроим выводы PB6, PB9
// Open drain!
GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9;
// Подтяжка внешняя, потому тут не настраивается!
// если надо, см. регистр GPIOB->PUPDR
// Номер альтернативной функции
GPIOB->AFR &= ~(0x0FUL << (6 * 4)); // 6 очистим
GPIOB->AFR |= (0x04UL << (6 * 4)); // В 6 запишем 4
GPIOB->AFR &= ~(0x0FUL << ((9 - 8) * 4)); // 9 очистим
GPIOB->AFR |= (0x04UL << ((9 - 8) * 4)); // В 9 запишем 4
// Режим: альтернативная функция
GPIOB->MODER &= ~((0x03UL << (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим
GPIOB->MODER |= ((0x02UL << (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2
// Включить тактирование модуля I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// На данный момент I2C должен быть выключен
// Сбросим всё (SWRST == 1, сброс)
I2C1->CR1 = I2C_CR1_SWRST;
// PE == 0, это главное
I2C1->CR1 = 0;
// Считаем, что запущены от RC (16 МГц)
// Предделителей в системе тактирования нет (все 1)
// По-хорошему, надо бы вычислять это вс из
// реальной частоты тактирования модуля
I2C1->CR2 = Clock / 1000000UL; // 16 МГц
// Настраиваем частоту
{
// Tclk = (1 / Fperiph);
// Thigh = Tclk * CCR;
// Tlow = Thigh;
// Fi2c = 1 / CCR * 2;
// CCR = Fperiph / (Fi2c * 2);
uint16_t Value = (uint16_t)(Clock / (Speed * 2));
// Минимальное значение: 4
if(Value < 4) Value = 4;
I2C1->CCR = Value;
}
// Задаём предельное время фронта
// В стандартном режиме это время 1000 нс
// Просто прибавляем к частоте, выраженной в МГц единицу (см. RM стр. 604).
I2C1->TRISE = (Clock / 1000000UL) + 1;
// Включим модуль
I2C1->CR1 |= (I2C_CR1_PE);
// Теперь можно что-нибудь делать
}
// Отправить байт
bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Данные
if(!i2c_SendData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
// Получить байт
bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Повторный старт
if(!i2c_SendStart()) return false;
// Адрес микросхемы (чтение)
if(!i2c_SendAddress(Address | 1)) return i2c_SendStop();
// Получим байт
if(!i2c_ReceiveData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
Использование:
{
uint8_t ID = 0;
i2c_Init();
// Считаем, что PD4 выставлен в высокий уровень и ЦАП работает (это надо сделать как-нибудь)
// Отправка байта в устройство с адресом 0x94, в регистр 0x00 со значением 0x00.
i2c_SendByte(0x94, 0x00, 0x00);
// Приём байта из устройства с адресом 0x94 из регистра 0x01 (ID) в переменную buffer
i2c_ReceiveByte(0x94, 0x01, &ID);
}
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.
Кто-то любит пирожки, а кто-то - нет.
Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки.
Естественно, с полной поддержкой всего этого дела.
Работа с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате:
Я> Шли START.
S> Ок, послал.
Я> Круто, шли адрес теперь. Вот такой: 0xXX.
S> Ок, послал. Мне сказали, что ACK. Давай дальше.
Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли.
S> Послал, получил ACK.
Я> Шли ему теперь данные, вот тебе байт: 0xZZ.
S> Послал, он согласен на большее: ACK.
Я> Фиг ему, а не ещё. Шли STOP.
S> Okay.
И всё примерно в таком духе.
В данном контроллере выводы i2c раскиданы по портам таким образом:
PB6: I2C1_SCL
PB7: I2C1_SDA
PB8: I2C1_SCL
PB9: I2C1_SDA
PB10: I2C2_SCL
PB11: I2C2_SDA
PA8: I2C3_SCL
PC9: I2C3_SDA
Вообще, распиновку периферии удобно смотреть в на 59 странице.
Что удивительно, но для работы с i2c нужны все его регистры, благо их немного:
I2C_CR1
- команды модулю для отправки команд/состояний и выбор режимов работы;
I2C_CR2
- настройка DMA и указание рабочей частоты модуля (2-42 МГц);
I2C_OAR1
- настройка адреса устройства (для slave), размер адреса (7 или 10 бит);
I2C_OAR2
- настройка адреса устройства (если адресов два);
I2C_DR
- регистр данных;
I2C_SR1
- регистр состояния модуля;
I2C_SR2
- регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1);
I2C_CCR
- настройка скорости интерфейса;
I2C_TRISE
- настройка таймингов фронтов.
Впрочем, половина из них типа «записать и забыть».
На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22 , аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.
Порядок настройки примерно таков:
1
. Разрешить тактирование портов и самого модуля.
Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся.
И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно.
2
. Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR).
По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер.
3
. Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола.
Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц).
Максимально разрешённая частота тактирования: 42 МГц.
4
. Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый).
Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано).
5
. Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один:
если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9.
6
. По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных);
7
. Модуль включается: флаг PE в регистре CR1 переводится в 1.
Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра:
1
. Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1.
Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе.
2
. Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм.
Если устройство ответит на адрес ACK"ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure).
Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных.
Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей.
но мы рассматриваем запись, потому примем, что там был ноль.
3
. Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена).
4
. Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся.
5
. По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.
При чтении всё то же самое. Меняется только после записи адреса регистра.
Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения).
Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK).
Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.
Вот то же самое в виде кода:
// Инициализация модуля
void i2c_Init(void)
{
uint32_t Clock = 16000000UL; // Частота тактирования модуля (system_stm32f4xx.c не используется)
uint32_t Speed = 100000UL; // 100 кГц
// Включить тактирование порта GPIOB
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// Настроим выводы PB6, PB9
// Open drain!
GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9;
// Подтяжка внешняя, потому тут не настраивается!
// если надо, см. регистр GPIOB->PUPDR
// Номер альтернативной функции
GPIOB->AFR &= ~(0x0FUL << (6 * 4)); // 6 очистим
GPIOB->AFR |= (0x04UL << (6 * 4)); // В 6 запишем 4
GPIOB->AFR &= ~(0x0FUL << ((9 - 8) * 4)); // 9 очистим
GPIOB->AFR |= (0x04UL << ((9 - 8) * 4)); // В 9 запишем 4
// Режим: альтернативная функция
GPIOB->MODER &= ~((0x03UL << (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим
GPIOB->MODER |= ((0x02UL << (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2
// Включить тактирование модуля I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// На данный момент I2C должен быть выключен
// Сбросим всё (SWRST == 1, сброс)
I2C1->CR1 = I2C_CR1_SWRST;
// PE == 0, это главное
I2C1->CR1 = 0;
// Считаем, что запущены от RC (16 МГц)
// Предделителей в системе тактирования нет (все 1)
// По-хорошему, надо бы вычислять это вс из
// реальной частоты тактирования модуля
I2C1->CR2 = Clock / 1000000UL; // 16 МГц
// Настраиваем частоту
{
// Tclk = (1 / Fperiph);
// Thigh = Tclk * CCR;
// Tlow = Thigh;
// Fi2c = 1 / CCR * 2;
// CCR = Fperiph / (Fi2c * 2);
uint16_t Value = (uint16_t)(Clock / (Speed * 2));
// Минимальное значение: 4
if(Value < 4) Value = 4;
I2C1->CCR = Value;
}
// Задаём предельное время фронта
// В стандартном режиме это время 1000 нс
// Просто прибавляем к частоте, выраженной в МГц единицу (см. RM стр. 604).
I2C1->TRISE = (Clock / 1000000UL) + 1;
// Включим модуль
I2C1->CR1 |= (I2C_CR1_PE);
// Теперь можно что-нибудь делать
}
// Отправить байт
bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Данные
if(!i2c_SendData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
// Получить байт
bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Повторный старт
if(!i2c_SendStart()) return false;
// Адрес микросхемы (чтение)
if(!i2c_SendAddress(Address | 1)) return i2c_SendStop();
// Получим байт
if(!i2c_ReceiveData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
Использование:
{
uint8_t ID = 0;
i2c_Init();
// Считаем, что PD4 выставлен в высокий уровень и ЦАП работает (это надо сделать как-нибудь)
// Отправка байта в устройство с адресом 0x94, в регистр 0x00 со значением 0x00.
i2c_SendByte(0x94, 0x00, 0x00);
// Приём байта из устройства с адресом 0x94 из регистра 0x01 (ID) в переменную buffer
i2c_ReceiveByte(0x94, 0x01, &ID);
}
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.
В одном из своих проектов использую микроконтроллеры STM32F030. Недавно возникла необходимость подключения внешней EEPROM памяти по шине I 2 C. Сначала я хотел взять готовый пример с инета, но в итоге пришлось изобретать свой велосипед писать свой код. В статье рассказываю о типичных граблях при работе с шиной I 2 C STM32F030, и предлагаю свой велосипед вариант работы с шиной.
Итак, для критики возьму один из примеров кода, взятый с интернета:
/** Описание Записывает байт данных в I2C EEPROM. * Параметр data: переменная для записи в EEPROM. * Параметр WriteAddr: Внутренний адрес EEPROM для записи. * Возвращаемое значение нет */ uint32_t EEPROM_I2C_Write(uint8_t data, uint16_t WriteAddr) { //uint32_t DataNum = 0; Address = Address + (WriteAddr / 256); /* Конфигурирование адреса ведомого; количество байтов, которые будут запрограммированы (переданы); перезагрузки и генерировать старт */ I2C_TransferHandling(I2C1, Address, 1, I2C_Reload_Mode, I2C_Generate_Start_Write); /* Подождите, пока TXIS флаг не будет установлен */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET); /* Отправить адрес памяти */ I2C_SendData(I2C1, (uint8_t)WriteAddr); /* Подождите, пока TCR флаг не будет установлен */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TCR) == RESET); /* Обновить CR2: установить Адрес ведомого, установить запрос на запись, генерировать Пуск и заданного конечного режим */ I2C_TransferHandling(I2C1, Address, 1, I2C_AutoEnd_Mode, I2C_No_StartStop); /* Подождите, пока TXIS флаг не будет установлен */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET); /* Запись данных в TXDR */ I2C_SendData(I2C1, data); /* Подождите, пока STOPF флаг не будет установлен */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_STOPF) == RESET); /* Очистить флаг STOPF */ I2C_ClearFlag(I2C1, I2C_ICR_STOPCF); } |
Вставляю этот код в свой проект, проверяю, шина работает. Дальше начинаю проверять код на наличие граблей. Для начала отключаю микросхему памяти, чтобы при обращении на шине отсутствовал ACK. Проверяю работу кода, и тут же натыкаюсь на грабли. Давайте разбираться где подвох.
На шину выдан старт, отправлен адрес устройства. Так как мы отключили микросхему памяти, установился флаг NACKF (Not Acknowledge Flag). Смотрим код дальше.
А вот здесь микроконтроллер зависает, так как ждёт запрос на передачу байта (установку флага TXIS, Transmit Interrupt Status). Запрос никогда не поступит, так как ведомое устройство на шине не отвечает. Это первые грабли. Соответственно, пока никаких сбоев на шине нет - наше устройство работает нормально. Как только произошёл малейший сбой - микроконтроллер наглухо виснет. Смотрю код дальше.
Здесь тоже имеется ошибка. Если микросхема не отвечает, устанавливается флаг NACKF, а флаг TCR (Transfer Complete Reload) никогда не будет возведён. Микроконтроллер зависнет.
Последняя строчка ожидает возведения флага STOPF (Stop detection Flag), но мы замкнули ножки и заблокировали обмен данными. Шина замечает подвох, и взлетает флаг ARLO (Arbitration Lost). Флаг STOPF не устанавливается, микроконтроллер зависает. Более того, появляются ещё одни грабли.
Так как возведён флаг ARLO, обмен данными по шине невозможен, микроконтроллер не будет выдавать даже старт на шину.
Таким образом, применив вышеописанный код, мы закладываем в проект пачку хаотично появляющихся граблей. В общем я решил изобрести велосипед написать свой вариант кода. Весь код расписывать не буду (можно скачать в конце статьи), лишь поясню ключевые моменты.
Отправка данных.
/* Выполняет транзакцию записи Size байт в регистр Register по адресу Adress. Параметры: Adress - адрес ведомого устройства Register - регистр, в который хотим передать данные Data - указывает откуда брать данные для передачи Size - сколько байт хотим передать (от 1 до 254) Возвращает: 1 - если данные успешно переданы 0 - если произошла ошибка */ u8 I2C_Write_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size) { u8 Count=0; // Счётчик успешно переданных байт // Старт I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1+Size); // Сейчас либо I2C запросит первый байт для отправки, // Либо взлетит NACK-флаг, говорящий о том, что микросхема не отвечает. // Если взлетит NACK-флаг, отправку прекращаем. while ((((I2C_BUS->ISR & I2C_ISR_TXIS)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) {}; if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=Register; // Отправляю адрес регистра // Отправляем байты до тех пор, пока не взлетит TC-флаг. // Если взлетит NACK-флаг, отправку прекращаем. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) { if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=*(Data+Count++); // Отправляю данные } I2C_Stop(); if (Count == Size) return 1; return 0; } |
После старта шины и отправки адреса микросхемы, есть 3 варианта исхода событий:
Следует обратить внимание на циклы while - они реализованы с учётом всех вышеописанных вариантов. Идём дальше.
Приём данных.
/* Выполняет транзакцию чтения Size байт из регистра Register по адресу Adress. Параметры: Adress - адрес ведомого устройства Register - регистр, из которого хотим принять данные Data - указывает куда складывать принятые данные Size - сколько байт хотим принять (от 1 до 255) Возвращает: 1 - если данные успешно приняты 0 - если произошла ошибка */ u8 I2C_Read_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size) { u8 Count=0; // Счётчик успешно принятых байт // Старт I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1); // Сейчас либо I2C запросит первый байт для отправки, // Либо взлетит NACK-флаг, говорящий о том, что микросхема не отвечает. // Если взлетит NACK-флаг, отправку прекращаем. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) { if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR = Register; // Отправляю адрес регистра } // Повторный старт I2C_Start_Direction_Adress_Size (I2C_Receiver, Adress, Size); // Принимаем байты до тех пор, пока не взлетит TC-флаг. // Если взлетит NACK-флаг, приём прекращаем. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) { if (I2C_BUS->ISR & I2C_ISR_RXNE) *(Data+Count++) = I2C_BUS->RXDR; // Принимаю данные } I2C_Stop(); if (Count == Size) return 1; return 0; } |
Здесь всё почти так же, как и при передаче. Подробно описывать не буду, идём дальше.
В статье приведено описание последовательного интерфейса I2С 32-разрядных ARM-микроконтроллеров серии STM32 от компании STMicroelectronics. Рассмотрены архитектура, состав и назначение регистров конфигурирования интерфейса, а также приведены примеры программ его использования.
Введение
Интерфейс I2С, или IIC получил свою аббревиатуру от английских слов Inter-Integrated Circuit и представляет собой последовательную шину, состоящую из двух двунаправленных линий связи с названием SDA и SCL, как сокращение от слов Serial Data Address и Serial Clock. Он обеспечивает обмен данными между микроконтроллером и различными периферийными устройствами, такими как АЦП, ЦАП, микросхемы памяти, другие микроконтроллеры и микросхемы. Схема подключения устройств по интерфейсу I2C показана на рисунке 1.
Рис. 1. Схема подключения устройств по интерфейсу I 2 C
Стандарт на интерфейс I2С был разработан фирмой Philips в начале 1980-х годов. Согласно этому стандарту, интерфейс имел 7-разрядный адрес. Он позволял обращаться к 127 устройствам на скорости до 100 кбит/с. В дальнейшем интерфейс получил своё развитие и стал 10-разрядным, позволяющим обращаться к 1023 устройствам на скорости до 400 кбит/с. Максимальное
допустимое количество микросхем, подсоединённых к одной шине, ограничивается максимальной ёмкостью шины в 400 пФ. Версия стандарта 2.0, выпущенная в 1998 году, представила
высокоскоростной режим работы со скоростью до 3,4 Мбит/с с пониженным энергопотреблением. Версия 2.1 2001 года включает в себя лишь незначительные доработки.
Описание интерфейса I 2 C
Микроконтроллер STM32 включает в свой состав интерфейс I2С, который отличается своей развитостью. Он допускает несколько ведущих устройств на шине и поддерживает высокоскоростной режим. Кроме того, в микроконтроллере STM32 интерфейс I2C можно использовать для широкого спектра приложений, включая генерацию и верификацию контрольной суммы. С ним также можно работать по протоколам SMBus (System Management Bus) и PMBus (Power Management Bus). Большинство моделей STM32 включают в свой состав два интерфейса I2С с именами I2С1 и I2С2. Интерфейс может работать в одном из следующих четырёх режимов:
По умолчанию интерфейс работает в режиме «Ведомый» и автоматически переключается на «Ведущий» после генерирования старт-условия. Переключение с «Ведущего» на «Ведомый» происходит при потере арбитража или после генерирования стоп-условия, что позволяет работать нескольким «Ведущим» микроконтроллерам в одной системе поочередно. В режиме «Ведущий» I2C инициирует обмен данными и генерирует тактовый сигнал. Передаче последовательных данных всегда предшествует старт-условие, а завершается обмен всегда стоп-условием. Оба этих условия генерируются в режиме «Ведущий» программно. В режиме «Ведомый» I2C способен распознать свой собственный адрес (7 или 10 бит) и адрес общего вызова. Определение наличия адреса общего вызова можно включить или отключить программно. Адрес и данные передаются 8-битными посылками, старшим битом вперёд. Первый байт, следующий за стартусловием, содержит адрес (один байт в 7-битном режиме и два байта в 10-битном режиме). Адрес всегда передаётся в режиме «Ведущий».
За 8 тактами передачи байта данных следует 9-й такт, в течение которого приёмник должен послать бит уведомления ACK, получивший своё название от слова ACKnowledge. На рисунке 2 приведена временна′я диаграмма одной посылки интерфейса I2C. Наличие уведомления в ответе можно программно включить или отключить. Размерность адреса интерфейса I2C (7 бит или 10 бит и адрес
общего вызова) можно выбрать программно.
Рис. 2. Временная диаграмма одной посылки интерфейса I
Архитектура блока интерфейса I 2 С
Функциональная схема блока интерфейса I2C для микроконтроллера STM32 приведена на рисунке 3.
Рис. 3. Функциональная схема блока интерфейса I 2 C
Регистр сдвига на этой схеме представляет собой основной регистр, через который передаются и принимаются данные. Передаваемые данные предварительно записываются в регистр данных, после чего через регистр сдвига последовательно транслируются в линию связи SDA. Принимаемые по этой же линии связи данные накапливаются в регистре сдвига, а затем перемещаются в регистр данных. Таким образом, интерфейс может передавать и принимать данные только поочередно. Кроме того, регистр сдвига аппаратно подключён к компаратору, который позволяет сравнивать принятый адрес
с адресными регистрами и, таким образом, определять, для кого предназначен очередной блок данных. Узел управления частотой позволяет формировать сигнал синхронизации SCL в роли ведущего и синхронизироваться от этого сигнала в качестве ведомого устройства. Регистр CCR обеспечивает программную настройку данного узла. Блок интерфейса подключён к выходу PCLK1 шины APB1 через
два предварительных делителя. Микроконтроллер поддерживает два режима обмена: стандартный (Standard Speed) – до 100 кГц, и быстрый (Fast Speed) – до 400 кГц. В зависимости от режима обмена частота тактирования модуля должна быть не менее 2 МГц в стандартном режиме и не менее 4 МГц в быстром режиме. Блок вычисления позволяет аппаратно вычислять контрольную сумму блока данных и сохранять её в регистре PEC. Управление блоком интерфейса I2C, а также формирование флагов событий и прерываний выполняется узлом логики управления. Он же позволяет обслуживать запросы ПДП и формировать сигнал ACK. Связь этого блока с микроконтроллером осуществляется программно с помощью регистров управления CR1, CR2 и регистров состояния SR1, SR2.
Прерывания от I 2 C
Интерфейс I2C имеет аппаратную организацию, способную формировать запросы на прерывание в зависимости от режима работы и текущих событий. В таблице 1 приведены запросы на прерывание от интерфейса I2C.
Таблица 1. Запросы на прерывание от интерфейса I 2 C
Описание регистров
Для работы с интерфейсом I2C в микроконтроллере STM32 имеются специальные регистры. Карта этих регистров с названием входящих в них разрядов представлена в таблице 2. Рассмотрим регистры, необходимые для работы интерфейса I2С. К ним относятся:
Некоторые разряды этих регистров используются для работы в режиме SMBus. Регистр I2C_CR1 является первым управляющим регистром интерфейса I2C. Он имеет следующие управляющие разряды:
Регистр I2C_CR2 является вторым управляющим регистром интерфейса I2C и имеет следующие управляющие разряды:
Регистр I2C_OAR1 – первый регистр собственного адреса, включает в себя следующие разряды:
Регистр I2C_OAR2 – второй регистр собственного адреса, включает в себя следующие разряды:
Регистр данных I2C_DR имеет 8 разрядов DR для приёма и передачи данных на шину I2C. В этот регистр данные записываются для передачи и читаются из него при приёме. Разряды 15…9 – зарезервированы. Регистр I2C_SR1 – первый статусный регистр, и включает в себя следующие разряды:
Регистр I2C_SR2 – второй статусный регистр, включает в себя следующие разряды:
Регистр I2C_CCR – регистр управления тактовым сигналом, который включает в себя разряды:
Регистр I2C_TRISE – регистр параметра TRISE, который включает в себя:
Более подробное описание назначения всех регистров I2C и их разрядов можно найти на сайте www.st.com .
П рограммирование интерфейса I 2 С
Рассмотрим практическую реализацию по использованию интерфейса I2С. Для этого можно воспользоваться стандартной библиотекой периферии микроконтроллера STM32. Для интерфейса I2C настройки режима, скорости и всего остального находятся в заголовочном файле и объявлены в виде структуры:
I2C_InitTypeDef:typedef struct{ uint32_t I2C_ClockSpeed; uint16_t I2C_Mode; uint16_t I2C_DutyCycle; uint16_t I2C_OwnAddress1; uint16_t I2C_Ack; uint16_t I2C_ AcknowledgedAddress; }I2C_InitTypeDef;
В этой структуре её элементы имеют следующее назначение:
Рассмотрим процедуры инициализации и работы с интерфейсом I2C. Для настройки интерфейса I2C в качестве ведущего устройства и передачи данных через него необходимо выполнить следующие действия:
Для облегчения процесса программирования желательно создать набор основных функций для работы с I2C. В листинге 1 приведена функция инициализации интерфейса I2C в соответствии с описанным выше алгоритмом.
Листинг 1 GPIO_InitTypeDef gpio; // Создание структуры для портов ввода-вывода I2C_InitTypeDef i2c; // Создание структуры для интерфейса I2C void init_I2C1(void) { // Включить тактирование RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // Инициализировать I2C i2c.I2C_ClockSpeed = 100000; i2c.I2C_Mode = I2C_Mode_I2C; i2c.I2C_DutyCycle = I2C_DutyCycle_2; // Задать адрес=0x12 i2c.I2C_OwnAddress1 = 0x12; i2c.I2C_Ack = I2C_Ack_Disable; i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &i2c); // Назначить выводы интерфейса gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
Теперь рассмотрим функцию для общения по I2C. Для расширения возможностей эта функция имеет три параметра: номер используемого блока I2C, направление передачи данных и адрес подчинённого устройства. Код данной функции приведён в листинге 2.
Листинг 2 void I2C_StartTransmission(I2C_TypeDef* I2Cx, uint8_t transmissionDirection, uint8_t slaveAddress) { // Ждать освобождения шины while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)); // Сформировать старт-условие I2C_GenerateSTART(I2Cx, ENABLE); // Ждать установки бита while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); // Отправить адрес ведомому устройству I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); // Если передача данных if(transmissionDirection== I2C_Direction_Transmitter) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));} // Если прием данных if(transmissionDirection== I2C_Direction_Receiver) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));} }
Приведённая функция использует простые функции передачи и приёма данных, приведённые в листинге 3.
Листинг 3 // Функция передачи данных void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data) { // Вызвать библиотечную функцию передачи данных I2C_SendData(I2Cx, data); // Ждать окончания передачи данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } // Функция приема данных uint8_t I2C_ReadData(I2C_TypeDef* I2Cx) { // Ждать поступления данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2Cx); // Считать данные из регистра return data; // Возвратить данные в вызывающую функцию
Закончив обмен данными по I2C, необходимо вызвать функцию формирования стоп-условия
I2C_Generate STOP(I2Cx, ENABLE).
На основе приведённых функций можно создавать программы для работы с множеством разнообразных периферийных устройств.
Заключение
Неоспоримым преимуществом интерфейса I2C является простота подключения устройств с помощью всего лишь двух линий связи и общего провода, благодаря чему данный интерфейс надёжно закрепился в технике и по-прежнему широко применяется в современной аппаратуре.
(Руководство разработчика по микроконтроллерам семейства HCS08)
Для управления модулем I2C используются 6 регистров специальных функций:
В МК серии QE присутствуют 2 модуля I2C и, соответственно, по два регистра управления каждого типа. Например, первый регистр состояния IIC1S и второй регистр состояния IIC2S.
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICC | Чтение | IICEN | IICIE | MST | TX | TXAK | 0 | 0 | 0 |
Запись | RSTA | — | — | ||||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Имя бита | Описание | Символ в языке С |
IICEN | Бит разрешения работы модуля I2C: 0 — контроллер I2C выключен; 1 — контроллер I2C включен. |
bIICEN |
IICIE | Бит разрешения прерывания модуля от I2C: 0 — прерывания по запросу I2C запрещены; 1 — прерывания по запросу I2C разрешены. |
bHCIE |
MST | Бит выбора режима работы контроллера I2C: 0 — контроллер I2C работает в режиме ведомого (Slave); 1 — контроллер I2C работает в режиме ведущего (Master). Когда этот бит изменяется с 0 на 1, генерируется состояние Старт. Наоборот, когда бит изменяется с 1 на 0, генерируется состояние Стоп. |
bMST |
TX | Бит выбора направления передачи по линии данных SDA: 0 — линия работает на ввод; 1 — линия работает на вывод. |
bTX |
TXAK | Бит подтверждения в режиме приема: 0 — генерируется бит подтверждения после приема байта; 1 — генерируется бит неподтверждения приема байта. Этот бит управляет генерацией бита подтверждения после приема байта данных, независимо от того, ведомый это или ведущий. |
bTXAK |
RSTA | Если модуль I2C работает в режиме ведущего, то запись 1 в этот бит вызывает повторную генерацию состояния Старт — «Повторный старт». | bRSTA |
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICS | Чтение | TCF | IAAS | BUSY | ARBL | 0 | SRW | IICIF | RXAK |
Запись | — | — | — | — | — | ||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Имя бита | Описание | Символ в языке С |
TCF | Бит завершения обмена. Устанавливается после завершения обмена одним байтом: 0 — обмен не завершен; 1 — обмен завершен. Флаг TCF сбрасывается в 0, когда читают регистр данных IICD (в режиме приема) или когда записывают в регистр данных IICD (в режиме передачи). |
bTCF |
IAAS | Флаг адреса ведомого. Устанавливается, если устройство работает в режиме ведомого и переданный в сообщении ведущего адрес равен адресу ведомого, который хранится в регистре адреса IICA. Флаг сбрасывается при записи в регистр IICC. |
bIAAS |
BUSY | Флаг занятой линии. Этот флаг устанавливается, если модуль I2C распознал старт-бит на линии. Флаг сбрасывается, когда модуль распознает стоп-бит на линии. 0 — шина I2C свободна; 1 — шина I2C занята. |
bBUSY |
ARBL | Флаг потери арбитража: 0 — нет нарушений в работе шины I2C; 1 — есть потеря арбитража. Модуль I2C должен некоторое время подождать, а потом начать операцию передачи снова. |
bARBL |
SRW | Бит направления передачи ведомого. Этот бит показывает состояние бита R/W в поле адреса: 0 — ведомый принимает. Ведущий передает ведомому; 1 — ведомый передает. Ведущий принимает от ведомого. |
bSRW |
IICIF | Флаг необслуженных запросов на прерывание модуля I2C.Устанавливается в 1, если установлен один из флагов: TCF, IAAS или ARBL. 0 — нет необслуженных прерываний; 1 — есть необслуженные прерывания. Флаг сбрасывается посредством записи в него 1. |
bIICIF |
RXAK | Бит подтверждения приема ведущего: 0 — ведомый подтвердил прием данных; 1 — ведомый не подтвердил прием данных. Этот бит отражает состояние поля ASK на временной диаграмме обмена. |
bRXAK |
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICA | Чтение | ADDR | |||||||
Запись | |||||||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
В этом регистре хранится 7-битный адрес ведомого устройства, который разработчик присвоил данному устройству при разработке системы. Этот адрес автоматически сравнивается с кодом адреса, который ведомый получил в поле адреса по шине I2C. Если адреса совпали, то устанавливается бит IAAS в регистре состояния IICS.
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICF | Чтение | MULT | ICR | ||||||
Запись | |||||||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Значения коэффициентов SCL_DIV и SDA_HV
ISR | SCL_DIV | SDA_HV | ISR | SCL_DIV | SDA_HV | |
0x00 | 20 | 7 | 0x20 | 160 | 17 | |
0x01 | 22 | 7 | 0x21 | 192 | 17 | |
0x02 | 24 | 8 | 0x22 | 224 | 33 | |
0x03 | 26 | 8 | 0x23 | 256 | 33 | |
0x04 | 28 | 9 | 0x24 | 288 | 49 | |
0x05 | 30 | 9 | 0x25 | 320 | 49 | |
0x06 | 34 | 10 | 0x26 | 384 | 65 | |
0x07 | 40 | 10 | 0x27 | 480 | 65 | |
0x08 | 28 | 7 | 0x28 | 320 | 33 | |
0x09 | 32 | 7 | 0x29 | 384 | 33 | |
0x0A | 36 | 9 | 0x2A | 448 | 65 | |
0x0B | 40 | 9 | 0x2B | 512 | 65 | |
0x0C | 44 | 11 | 0x2C | 576 | 97 | |
0x0D | 48 | 11 | 0x2D | 640 | 97 | |
0x0E | 56 | 13 | 0x2E | 768 | 129 | |
0x0F | 68 | 13 | 0x2F | 960 | 129 | |
0x10 | 48 | 9 | 0x30 | 640 | 65 | |
0x11 | 56 | 9 | 0x31 | 768 | 65 | |
0x12 | 64 | 13 | 0x32 | 896 | 129 | |
0x13 | 72 | 13 | 0x33 | 1024 | 129 | |
0x14 | 80 | 17 | 0x34 | 1152 | 193 | |
0x15 | 88 | 17 | 0x35 | 1280 | 193 | |
0x16 | 104 | 21 | 0x36 | 1536 | 257 | |
0x17 | 128 | 21 | 0x37 | 1920 | 257 | |
0x18 | 80 | 9 | 0x38 | 1280 | 129 | |
0x19 | 96 | 9 | 0x39 | 1536 | 129 | |
0x1A | 112 | 17 | 0x3A | 1792 | 257 | |
0x1B | 128 | 17 | 0x3B | 2048 | 257 | |
0x1C | 144 | 25 | 0x3C | 2304 | 385 | |
0x1D | 160 | 25 | 0x3D | 2560 | 385 | |
0x1E | 192 | 33 | 0x3E | 3072 | 513 | |
0x1F | 240 | 33 | 0x3F | 3840 | 513 |
В этом регистре хранятся два битовых поля, которые определяют скорость и параметры временной диаграммы обмена по I2C. Частота сигналов синхронизации определяется по формуле:
Время установления данных SDA_hold_time на шине I2C — это временной интервал между моментом установления в 0 сигнала SCL и изменением данных на линии SDA. Назначается параметром SDA_HV (SDA_Hold_Value) из таблицы для фактора ICR регистра скорости обмена:
.
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICD | Чтение | I2C DATA | |||||||
Запись | |||||||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Если модуль I2C работает в режиме ведущего, то операция записи в этот регистр инициирует обмен по I2C (но только если бит направления обмена в регистре управления IICC выставлен правильно, т.е. TX = 1). Первый байт после состояния Старт, который программа записывает в регистр данных, интерпретируется ведомыми как адрес устройства. Поэтому программа должна правильно сформировать содержимое первого байта. Операция чтения регистра возвращает последний принятый байт по I2C. Операция чтения регистра также инициирует начало приема следующего байта, но только если бит направления обмена в регистре управления IICC выставлен правильно, т.е. при TX = 0! При TX = 1 операция чтения регистра не вызовет прием нового байта по I2C от ведомого.
Если модуль I2C работает в режиме ведомого, то записанные в этот регистр данные будут переданы на линию SDA шины I2C тогда, когда ведущее устройство будет выполнять цикл приема от этого ведомого. Операция чтения регистра возвращает последний принятый байт от ведущего.
Регистр | Режим | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
IICC | Чтение | GCAEN | ADEXT | 0 | 0 | 0 | AD10 | AD9 | AD8 |
Запись | — | — | — | ||||||
Сброс | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |