I2C (Устаревшая статья): различия между версиями
Андрей (обсуждение | вклад) Нет описания правки |
Андрей (обсуждение | вклад) Нет описания правки |
||
Строка 142: | Строка 142: | ||
i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных | i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Флаг TXIS взводится после отправки каждого байта, если получено подтверждение (ACK). При отправке адреса и получения подтверждения устанавливается флаг TXIS, после чего в регистр I2C_TXDR записывается байт, который должен быть отправлен. | Флаг TXIS взводится после отправки каждого байта, если получено подтверждение (ACK). При отправке адреса и получения подтверждения устанавливается флаг TXIS, после чего в регистр I2C_TXDR записывается байт, который должен быть отправлен. | ||
Строка 167: | Строка 163: | ||
i2c_master_init(i2c); | i2c_master_init(i2c); | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Фрагмент кода функции i2c_master_write с отправкой данных <syntaxhighlight lang="c++" line="1"> | Фрагмент кода функции i2c_master_write с отправкой данных <syntaxhighlight lang="c++" line="1"> | ||
Строка 313: | Строка 309: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Полный код main.c для ведущего<syntaxhighlight lang="c++" line="1"> | |||
Полный код main.c для ведущего <syntaxhighlight lang="c++" line="1"> | |||
#include "common.h" | #include "common.h" | ||
#include "i2c.h" | #include "i2c.h" |
Версия от 17:27, 23 июня 2022
Статья находится в разработке.
Пример использования 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_adr = 0x36 без сдвига адреса i2c_master_write(I2C_0, slave_address, data, sizeof(data), false); for (volatile int i = 0; i < 1000000; i++); // Чтение данных по адресу slave_adr = 0x36 без сдвига адреса i2c_master_read(I2C_0, slave_address, data, sizeof(data), false); for (volatile int i = 0; i < 1000000; i++); } }