I2C (Устаревшая статья)
Статья находится в разработке.
Пример использования I2C MIK32
В данном примере используется две отладочные платы с микроконтроллером MIK32, которые соединены как показано на рисунке 1. Первая плата используется как ведущее устройство (Master), вторая используется как ведомое (Slave).
Ведущий передает ведомому число размером 2 байта. Ведомый принимает это число и увеличивает на единицу. Затем ведущий принимает число от ведомого. Так продолжается до тех пор, пока число, после увеличения ведомым, не станет больше 65530.
Ведущий
Переменные ведущего:
- uint8_t slave_address - адрес ведомого, с которым ведущий обменивается данными;
- uint16_t to_send - число из двух байт;
- uint8_t data[2] - массив с байтами для отправки / приема ведущим.
Массив data заполняется байтами числа to_send.
Для передачи данных используется I2C0, который имеет следующие выводы:
- PORT_0_9 - I2C0_sda;
- PORT_0_10 - I2C0_scl.
Инициализация
Сначала требуется провести инициализацию. Это делается с помощью функции i2c_master_init. Эта функция включает тактирование необходимых блоков для работы GPIO и I2C. Затем следует настройка регистра I2C_TIMINGR, где выставляется делитель частоты и задержки. В конце для включения интерфейса в регистре I2C_CR1 выставляется бит PE. После этого можно отправлять и принимать данные.
Функция i2c_master_init
void i2c_master_init(I2C_TypeDef* i2c) { //Включаем тактирование необходимых блоков и модуля выбора режима GPIO PM->CLK_APB_P_SET |= PM_CLOCK_GPIO_0_M | PM_CLOCK_I2C_0_M; PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M ; for (volatile int i = 0; i < 10; i++); // обнуление регистра управления i2c->CR1 = 0; /* * Инициализация i2c * TIMING - регистр таймингов * * SCLDEL - Задержка между изменением SDA и фронтом SCL в режиме ведущего и ведомого при NOSTRETCH = 0 * * SDADEL - Задержка между спадом SCL и изменением SDA в режиме ведущего и ведомого при NOSTRETCH = 0 * * SCLL - Время удержания SCL в состоянии логического «0» в режиме веедущего * * SCLH - Время удержания SCL в состоянии логической «1» в режиме веедущего * * PRESC - Делитель частоты I2CCLK. Используется для вычесления периода сигнала TPRESC для счетчиков предустановки, * удержания, уровня «0»и «1» * */ i2c->TIMINGR = I2C_TIMINGR_SCLDEL(1) | I2C_TIMINGR_SDADEL(1) | I2C_TIMINGR_SCLL(20) | I2C_TIMINGR_SCLH(20) | I2C_TIMINGR_PRESC(3); //частота 164,7 кГц tsync1 + tsync2 = 10^(-6) /* * * CR1 - Регистр управления * * PE - Управление интерфейсом: 0 – интерфейс выключен; 1 – интерфейс включен * */ i2c->CR1 = I2C_CR1_PE_M; xprintf("\nМастер. Старт\n"); }
Функция принимает аргумент типа I2C_TypeDef* i2c. Это может быть I2C_0 или I2C_1.
Передача
Для передачи данных ведомому, которые были записаны в массив data, используется функция i2c_master_write.
i2c_master_write(I2C_0, slave_address, data, sizeof(data), false);
Функция принимает следующие аргументы:
- I2C_TypeDef* i2c. Выбранный интерфейс. I2C_0 или I2C_1;
- uint8_t slave_adr - адрес ведомого, которому отправляются данные;
- uint8_t data[] - массив с байтами для передачи;
- uint8_t byte_count - количество отправляемых байтов. В данном примере можно отправлять не более 255 байт;
- bool shift - сдвиг адреса на 1. false - если сдвиг не требуется, true - если требуется сдвиг на 1 вправо.
Чтобы интерфейс работал в режиме ведущего, в регистре I2C_CR2 нужно настроить:
- режим адреса (7 или 10 бит) ADD10;
- адрес ведомого SADD;
- направление передачи RD_WRN;
- в случае чтения из ведомого с 10-битным адресом HEAD10R. Обуславливает передачу всего адреса или только заголовка в случае смены направления;
- количество байтов на передачу NBYTES[7:0]. Если количество байтов равно или более 255, в NBYTES[7:0] должно быть сперва записано значение 0xFF.
В примере функция i2c_master_write настраивает регистр I2C_CR2 следующим образом:
- 7 битный режим адреса;
- адрес ведомого SADD записывается в соответствии с переданным в функцию аргументом slave_adr;
- режим записи;
- количество отправляемых байт записывается в соответствии со значением byte_count (в примере 2 байта);
- выставляется бит AUTOEND, который нужен для автоматической отправки сигнала STOP.
После этого в регистре I2C_CR2 выставляется бит START и начинается отправка стартового сигнала и адреса.
Фрагмент кода функции i2c_master_write с настройкой регистра I2C_CR2
// shift - true когда адрес ведомого должен быть сдвинут на 1 бит if(!shift) { slave_adr = slave_adr << 1; } /* * * CR2 - регистр управления 2 * * SADD - адрес ведомого * * RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения * * Количество байт для приема / передачи * * AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл * */ i2c->CR2 = I2C_CR2_SADD(slave_adr) | I2C_CR2_WR_M | I2C_CR2_NBYTES(byte_count) | I2C_CR2_AUTOEND_M; i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных
Флаг TXIS взводится после отправки каждого байта, если получено подтверждение (ACK). При отправке адреса и получения подтверждения устанавливается флаг TXIS, после чего в регистр I2C_TXDR записывается байт, который должен быть отправлен.
В примере передача данных может прекратиться если:
- за 1000000 итераций флаг TXIS не выставился. Возможен разрыв линии связи между ведущим и ведомым;
- во время передачи ведомый прислал нет подтверждения (NACK).
В случае превышения ожидания флага TXIS производится программный сброс с помощью функции i2c_master_restart. В регистре I2C_CR1 бит PE устанавливает в 0, а затем вызывается функция i2c_master_init для повторной инициализации.
Функция i2c_master_restart
void i2c_master_restart(I2C_TypeDef* i2c) { // Программный сброс модуля i2c i2c->CR1 &= ~I2C_CR1_PE_M; for (volatile int i = 0; i < 1000000; i++); // Повторная инициализация i2c_master_init(i2c); }
Фрагмент кода функции i2c_master_write с отправкой данных
for (uint8_t i = 0; i < byte_count; i++) { int counter = 0; // Счетчик для ожидания while(!(i2c->ISR & I2C_ISR_TXIS_M))// TXIS = 1 - предыдущий байт доставлен { counter++; if(counter == 1000000) { xprintf("Разрыв связи\n"); // Ожидание превышено. Возможно механическое повреждение линии связи // Программный сброс модуля i2c и его повторная инициалиизация i2c_master_restart(i2c); return; } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { xprintf("Запись. NACKF = %d\n", (i2c->ISR & I2C_ISR_NACKF_M) >> I2C_ISR_NACKF_S); break; } } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { i2c->ICR |= I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF xprintf("Ошибка при передаче\n"); break; } xprintf("Отправка по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); i2c->TXDR = data[i]; }
Приём
Для приёма данных от ведомого используется функция i2c_master_read.
i2c_master_read(I2C_0, slave_address, data, sizeof(data), false);
Функция принимает следующие аргументы:
- I2C_TypeDef* i2c. Выбранный интерфейс. I2C_0 или I2C_1;
- uint8_t slave_adr - адрес ведомого, от которого принимаются данные;
- uint8_t data[] - массив для приема байтов;
- uint8_t byte_count - количество принимаемых байтов. В данном примере можно отправлять не более 255 байт;
- bool shift - сдвиг адреса на 1. false - если сдвиг не требуется, true - если требуется сдвиг на 1 вправо.
Как и в функции i2c_master_write, в начале нужно настроить регистр I2C_CR2. В примере функция i2c_master_read настраивает регистр следующим образом:
- 7 битный режим адреса;
- адрес ведомого SADD записывается в соответствии с переданным в функцию аргументом slave_adr;
- режим чтения;
- количество отправляемых байт записывается в соответствии со значением byte_count (в примере 2 байта);
- выставляется бит AUTOEND, который нужен для автоматической отправки сигнала STOP.
После этого в регистре I2C_CR2 выставляется бит START и начинается отправка стартового сигнала с адресом.
Фрагмент кода функции i2c_master_read с настройкой регистра I2C_CR2
// shift - true когда адрес ведомого должен быть сдвинут на 1 бит if(!shift) { slave_adr = slave_adr << 1; } xprintf("\nЧтение\n"); /* * * CR2 - регистр управления 2 * * SADD - адрес ведомого * * RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения * * Количество байт для приема / передачи * * AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл * */ i2c->CR2 = I2C_CR2_SADD(slave_adr) | I2C_CR2_RD_M | I2C_CR2_NBYTES(byte_count) | I2C_CR2_AUTOEND_M; i2c->CR2 |= I2C_CR2_START_M;
Флаг RXNE взводится после приема каждого байта. Когда устанавливается флаг RXNE, в соответствующий номеру байта элемент массива data записывается значение регистра I2C_RXDR, в котором хранится принятый байт.
В примере приём данных может прекратиться если:
- за 1000000 итераций флаг RXNE не выставился. Возможен разрыв линии связи между ведущим и ведомым;
- во время приёма ведущий прислал NACK или STOP.
В случае превышения ожидания флага RXNE производится программный сброс с помощью функции i2c_master_restart.
Фрагмент кода функции i2c_master_read с приёмом данных
for (uint8_t i = 0; i < byte_count; i++) { int counter = 0; // Счетчик для ожидания while(!(i2c->ISR & I2C_ISR_TXIS_M))// TXIS = 1 - предыдущий байт доставлен { counter++; if(counter == 1000000) { xprintf("Разрыв связи\n"); // Ожидание превышено. Возможно механическое повреждение линии связи // Программный сброс модуля i2c и его повторная инициалиизация i2c_master_restart(i2c); return; } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { xprintf("Запись. NACKF = %d\n", (i2c->ISR & I2C_ISR_NACKF_M) >> I2C_ISR_NACKF_S); break; } } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { i2c->ICR |= I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF xprintf("Ошибка при передаче\n"); break; } xprintf("Отправка по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); i2c->TXDR = data[i]; }
Полный код для ведущего
main.c для ведущего
#include "common.h" #include "i2c.h" #include "stdbool.h" void i2c_master_init(I2C_TypeDef* i2c) { //Включаем тактирование необходимых блоков и модуля выбора режима GPIO PM->CLK_APB_P_SET |= PM_CLOCK_GPIO_0_M | PM_CLOCK_I2C_0_M; PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M ; for (volatile int i = 0; i < 10; i++); // обнуление регистра управления i2c->CR1 = 0; /* * Инициализация i2c * TIMING - регистр таймингов * * SCLDEL - Задержка между изменением SDA и фронтом SCL в режиме ведущего и ведомого при NOSTRETCH = 0 * * SDADEL - Задержка между спадом SCL и изменением SDA в режиме ведущего и ведомого при NOSTRETCH = 0 * * SCLL - Время удержания SCL в состоянии логического «0» в режиме веедущего * * SCLH - Время удержания SCL в состоянии логической «1» в режиме веедущего * * PRESC - Делитель частоты I2CCLK. Используется для вычесления периода сигнала TPRESC для счетчиков предустановки, * удержания, уровня «0»и «1» * */ i2c->TIMINGR = I2C_TIMINGR_SCLDEL(1) | I2C_TIMINGR_SDADEL(1) | I2C_TIMINGR_SCLL(20) | I2C_TIMINGR_SCLH(20) | I2C_TIMINGR_PRESC(3); //частота 164,7 кГц tsync1 + tsync2 = 10^(-6) /* * * CR1 - Регистр управления * * PE - Управление интерфейсом: 0 – интерфейс выключен; 1 – интерфейс включен * */ i2c->CR1 = I2C_CR1_PE_M; xprintf("\nМастер. Старт\n"); } void i2c_master_restart(I2C_TypeDef* i2c) { // Программный сброс модуля i2c i2c->CR1 &= ~I2C_CR1_PE_M; for (volatile int i = 0; i < 1000000; i++); // Повторная инициализация i2c_master_init(i2c); } void i2c_master_write(I2C_TypeDef* i2c, uint8_t slave_adr, uint8_t data[], uint8_t byte_count, bool shift) { xprintf("\nОтправка %d\n", data[0] << 8 | data[1]); uint8_t slave_adr_print = slave_adr; // переменная используется для вывода адреса // shift - true когда адрес ведомого должен быть сдвинут на 1 бит if(!shift) { slave_adr = slave_adr << 1; } /* * * CR2 - регистр управления 2 * * SADD - адрес ведомого * * RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения * * Количество байт для приема / передачи * * AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл * */ i2c->CR2 = I2C_CR2_SADD(slave_adr) | I2C_CR2_WR_M | I2C_CR2_NBYTES(byte_count) | I2C_CR2_AUTOEND_M; i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных for (uint8_t i = 0; i < byte_count; i++) { int counter = 0; // Счетчик для ожидания while(!(i2c->ISR & I2C_ISR_TXIS_M))// TXIS = 1 - предыдущий байт доставлен { counter++; if(counter == 1000000) { xprintf("Разрыв связи\n"); // Ожидание превышено. Возможно механическое повреждение линии связи // Программный сброс модуля i2c и его повторная инициалиизация i2c_master_restart(i2c); return; } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { xprintf("Запись. NACKF = %d\n", (i2c->ISR & I2C_ISR_NACKF_M) >> I2C_ISR_NACKF_S); break; } } // Ошибка. При записи байта слейв прислал NACK if(i2c->ISR & I2C_ISR_NACKF_M) { i2c->ICR |= I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF xprintf("Ошибка при передаче\n"); break; } xprintf("Отправка по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); i2c->TXDR = data[i]; } } void i2c_master_read(I2C_TypeDef* i2c, uint8_t slave_adr, uint8_t data[], uint8_t byte_count, bool shift) { uint8_t slave_adr_print = slave_adr; // переменная используется для вывода адреса // shift - true когда адрес ведомого должен быть сдвинут на 1 бит if(!shift) { slave_adr = slave_adr << 1; } xprintf("\nЧтение\n"); /* * * CR2 - регистр управления 2 * * SADD - адрес ведомого * * RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения * * Количество байт для приема / передачи * * AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл * */ i2c->CR2 = I2C_CR2_SADD(slave_adr) | I2C_CR2_RD_M | I2C_CR2_NBYTES(byte_count) | I2C_CR2_AUTOEND_M; i2c->CR2 |= I2C_CR2_START_M; for(uint8_t i = 0; i < byte_count; i++) { // Счетчик для ожидания int counter = 0; while(!(i2c->ISR & I2C_ISR_RXNE_M)) // байт принят когда RXNE = 1 { counter++; if(counter == 1000000) { xprintf("Разрыв связи\n"); // Ожидание превышено. Возможно механическое повреждение линии связи // Программный сброс модуля i2c и его повторная инициалиизация i2c_master_restart(i2c); return; } // Ошибка. Во время чтения мастер прислал NACK или STOP if((i2c->ISR & I2C_ISR_STOPF_M) && (i2c->ISR & I2C_ISR_NACKF_M)) { xprintf("Чтение. STOPF = %d, NACKF = %d\n", (i2c->ISR & I2C_ISR_STOPF_M) >> I2C_ISR_STOPF_S, (i2c->ISR & I2C_ISR_NACKF_M) >> I2C_ISR_NACKF_S); break; } } // Ошибка. Во время чтения мастер прислал NACK или STOP if((i2c->ISR & I2C_ISR_STOPF_M) && (i2c->ISR & I2C_ISR_NACKF_M)) { i2c->ICR |= I2C_ICR_STOPCF_M | I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF break; } data[i] = i2c->RXDR; // чтение байта и сброс RXNE xprintf("Чтение по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); } xprintf("Получено %d\n" , data[0] << 8 | data[1]); } int main() { // Адрес ведомого uint8_t slave_address = 0x36; // Число для оптавки uint16_t to_send = 0; // Массив с байтами для отправки / приема uint8_t data[2] = {to_send >> 8, to_send & 0b0000000011111111}; // массив заполняется байтами числа to_send // Инициализация блока i2c в режиме мастер (ведущий) i2c_master_init(I2C_0); while (1) { // Запись данных по адресу slave_address = 0x36 без сдвига адреса i2c_master_write(I2C_0, slave_address, data, sizeof(data), false); for (volatile int i = 0; i < 1000000; i++); // Чтение данных по адресу slave_address = 0x36 без сдвига адреса i2c_master_read(I2C_0, slave_address, data, sizeof(data), false); for (volatile int i = 0; i < 1000000; i++); } }
Ведомый
Переменные ведомого:
- uint8_t slave_address - собственный адрес ведомого;
- uint16_t to_send - число из двух байт;
- uint8_t data[2] - массив с байтами для отправки / приема ведомым.
При отправке данных ведомым массив data заполняется байтами числа to_send. При получении данных ведомым число to_send формируется из байтов массива data.
Для передачи данных используется I2C0, который имеет следующие выводы:
- PORT_0_9 - I2C0_sda;
- PORT_0_10 - I2C0_scl.
Инициализация
Чтобы интерфейс работал в режиме ведомого, нужно задействовать хотя бы один адрес ведомого. Адреса OA1 и OA2 настраиваются в регистрах I2C_OAR1 и I2C_OAR2. Адрес OA1 может быть настроен либо в 7-битном режиме, либо в 10-битном, для чего должен быть установлен бит OA1MODE в регистре I2C_OAR1. Использование адреса OA1 разрешается битом OA1EN. При необходимости можно настроить дополнительный адрес OA2. Маскирование битов OA2 настраивается битами OA2MSK. Использование OA2EN разрешается битом OA2EN. Адреса общего вызова разрешаются установкой бита GCEN.
В примере инициализация ведомого проводится с помощью функции i2c_slave_init. Эта функция включает тактирование необходимых блоков для работы GPIO и I2C. Регистр I2C_OAR1 настраивается следующим образом:
- OA1 соответствует переменной slave_address;
- 7-битный режим;
- использование адреса OA1 разрешено.
Затем следует настройка регистра I2C_TIMINGR, где выставляется делитель частоты и задержки. В конце для включения интерфейса в регистре I2C_CR1 выставляется бит PE. После этого можно отправлять и принимать данные.
Функция i2c_slave_init
void i2c_slave_init(I2C_TypeDef *i2c, uint8_t slave_address) { slave_address = slave_address << 1; // Включаем тактирование необходимых блоков PM->CLK_APB_P_SET |= PM_CLOCK_GPIO_0_M | PM_CLOCK_I2C_0_M; PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M ; // обнуление регистра управления i2c->CR1 = 0; /* * * I2C_OAR1 - регистр собственного адреса * * OA1 - собственный адрес 1 * * OA1MODE - режим 7/10 бит адреса OA1. 0 - 7 бит * * OA1EN - использование собствевнного адреса OA1. 1 - при получении адреса OA1 - ACK * */ i2c->OAR1 = (slave_address << I2C_OAR1_OA1_S) | (1 << I2C_OAR1_OA1EN_S); // собственный адрес /* * Инициализация i2c * TIMING - регистр таймингов * * SCLDEL - Задержка между изменением SDA и фронтом SCL в режиме ведущего и ведомого при NOSTRETCH = 0 * * SDADEL - Задержка между спадом SCL и изменением SDA в режиме ведущего и ведомого при NOSTRETCH = 0 * * SCLL - Время удержания SCL в состоянии логического «0» в режиме веедущего * * SCLH - Время удержания SCL в состоянии логической «1» в режиме веедущего * * PRESC - Делитель частоты I2CCLK. Используется для вычесления периода сигнала TPRESC для счетчиков предустановки, * удержания, уровня «0»и «1» * */ i2c->TIMINGR = I2C_TIMINGR_SCLDEL(1) | I2C_TIMINGR_SDADEL(1) | I2C_TIMINGR_SCLL(20) | I2C_TIMINGR_SCLH(20) | I2C_TIMINGR_PRESC(3); //частота 164,7 кГц tsync1 + tsync2 = 10^(-6) /* * * CR1 - Регистр управления * * PE - Управление интерфейсом: 0 – интерфейс выключен; 1 – интерфейс включен * */ i2c->CR1 |= I2C_CR1_PE_M; xprintf("\nВедомый. Старт\n"); }