I2C: различия между версиями
Андрей (обсуждение | вклад) |
Андрей (обсуждение | вклад) |
||
| Строка 1: | Строка 1: | ||
Статья находится в разработке. | Статья находится в разработке. | ||
| − | Пример использования I2C MIK32 в режиме | + | == '''Пример использования I2C MIK32''' == |
| + | [[Файл:Рисунок 1 - Схема подключения.png|мини|469x469пкс|Рисунок 1 - Схема подключения]] | ||
| + | |||
| + | В данном примере используется две отладочные платы с микроконтроллером 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<syntaxhighlight lang="c++" line="1"> | ||
| + | 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"); | ||
| + | } | ||
| + | </syntaxhighlight>Функция принимает аргумент типа I2C_TypeDef* i2c. Это может быть I2C_0 или I2C_1. | ||
| + | |||
| + | ==== '''Передача''' ==== | ||
| + | Для передачи данных ведомому, которые были записаны в массив data, используется функция i2c_master_write.<syntaxhighlight lang="c++" line="1"> | ||
| + | i2c_master_write(I2C_0, slave_address, data, sizeof(data), false); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | Функция принимает следующие аргументы: | ||
| + | |||
| + | - I2C_TypeDef* i2c. Выбранный интерфейс. I2C_0 или I2C_1; | ||
| + | |||
| + | - uint8_t slave_adr - адрес ведомого, которому отправляются данные; | ||
| + | |||
| + | - uint8_t data[] - массив с байтами для передачи; | ||
| + | |||
| + | - uint8_t byte_count - количество отправляемых байтов. В данном примере можно отправлять не более 255 байт; | ||
| + | |||
| + | - bool shift - сдвиг адреса на 1. false - если сдвиг не требуется, true - если требуется сдвиг | ||
| + | |||
| + | i2c_master_write(I2C_0, slave_address, data, sizeof(data), false); | ||
| + | |||
| + | Чтобы интерфейс работал в режиме ведущего, в регистре I2C_CR2 нужно настроить: | ||
| + | |||
| + | - режим адреса (7 или 10 бит) ADD10; | ||
| + | |||
| + | - адрес ведомого SADD[9:0]; | ||
| + | |||
| + | - направление передачи RD_WRN; | ||
| + | |||
| + | - в случае чтения из ведомого с 10-битным адресом HEAD10R. Обуславливает передачу всего адреса или только заголовка в случае смены направления; | ||
| + | |||
| + | - количество байтов на передачу NBYTES[7:0]. Если количество байтов равно или более 255, в NBYTES[7:0] должно быть сперва записано значение 0xFF. | ||
| + | |||
| + | После этого пользователь устанавливает бит START в регистре I2C_CR2. После этого изменение вышеописанных битов не допускается. | ||
| + | |||
| + | |||
| + | В примере | ||
| + | |||
| + | |||
| + | Полный код main.c для ведущего<syntaxhighlight lang="c++" line="1"> | ||
#include "common.h" | #include "common.h" | ||
| − | #include " | + | #include "i2c.h" |
#include "stdbool.h" | #include "stdbool.h" | ||
| − | |||
| − | void | + | |
| + | 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_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 ; | PM->CLK_APB_M_SET |= PM_CLOCK_PAD_CONFIG_M | PM_CLOCK_WU_M | PM_CLOCK_PM_M ; | ||
| Строка 42: | Строка 151: | ||
* | * | ||
* PE - Управление интерфейсом: 0 – интерфейс выключен; 1 – интерфейс включен | * PE - Управление интерфейсом: 0 – интерфейс выключен; 1 – интерфейс включен | ||
| − | |||
| − | |||
* | * | ||
*/ | */ | ||
i2c->CR1 = I2C_CR1_PE_M; | i2c->CR1 = I2C_CR1_PE_M; | ||
xprintf("\nМастер. Старт\n"); | 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) | void i2c_master_write(I2C_TypeDef* i2c, uint8_t slave_adr, uint8_t data[], uint8_t byte_count, bool shift) | ||
{ | { | ||
| − | xprintf("\nОтправка\n"); | + | xprintf("\nОтправка %d\n", data[0] << 8 | data[1]); |
| − | uint8_t slave_adr_print = slave_adr; | + | uint8_t slave_adr_print = slave_adr; // переменная используется для вывода адреса |
| + | |||
// shift - true когда адрес ведомого должен быть сдвинут на 1 бит | // shift - true когда адрес ведомого должен быть сдвинут на 1 бит | ||
if(!shift) | if(!shift) | ||
| Строка 67: | Строка 185: | ||
* | * | ||
* RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения | * RD_WRN - Направление передачи: 0 – ведущий в режиме записи; 1 – ведущий в режиме чтения | ||
| + | * | ||
| + | * Количество байт для приема / передачи | ||
* | * | ||
* AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл | * AUTOEND - Управление режимом автоматического окончания: 0 – автоматическое окончание выкл; 1 – автоматическе окончание вкл | ||
| Строка 74: | Строка 194: | ||
i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных | i2c->CR2 |= I2C_CR2_START_M; // старт отправки адреса, а затем данных | ||
| − | + | ||
for (uint8_t i = 0; i < byte_count; i++) | for (uint8_t i = 0; i < byte_count; i++) | ||
{ | { | ||
| + | int counter = 0; // Счетчик для ожидания | ||
while(!(i2c->ISR & I2C_ISR_TXIS_M))// TXIS = 1 - предыдущий байт доставлен | while(!(i2c->ISR & I2C_ISR_TXIS_M))// TXIS = 1 - предыдущий байт доставлен | ||
{ | { | ||
| − | if(i2c | + | 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); | xprintf("Запись. NACKF = %d\n", (i2c->ISR & I2C_ISR_NACKF_M) >> I2C_ISR_NACKF_S); | ||
| Строка 86: | Строка 218: | ||
} | } | ||
| − | if(i2c->ISR & I2C_ISR_NACKF_M) | + | // Ошибка. При записи байта слейв прислал NACK |
| + | if(i2c->ISR & I2C_ISR_NACKF_M) | ||
{ | { | ||
i2c->ICR |= I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF | i2c->ICR |= I2C_ICR_NACKCF_M; // сброс флага STOPF и сброс флага NACKF | ||
| + | xprintf("Ошибка при передаче\n"); | ||
break; | break; | ||
} | } | ||
| + | |||
xprintf("Отправка по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); | xprintf("Отправка по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); | ||
i2c->TXDR = data[i]; | i2c->TXDR = data[i]; | ||
| Строка 96: | Строка 231: | ||
} | } | ||
| − | |||
| − | |||
void i2c_master_read(I2C_TypeDef* i2c, uint8_t slave_adr, uint8_t data[], uint8_t byte_count, bool shift) | 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; | + | uint8_t slave_adr_print = slave_adr; // переменная используется для вывода адреса |
| + | |||
// shift - true когда адрес ведомого должен быть сдвинут на 1 бит | // shift - true когда адрес ведомого должен быть сдвинут на 1 бит | ||
if(!shift) | if(!shift) | ||
| Строка 107: | Строка 241: | ||
slave_adr = slave_adr << 1; | slave_adr = slave_adr << 1; | ||
} | } | ||
| + | |||
xprintf("\nЧтение\n"); | 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_SADD(slave_adr) | I2C_CR2_RD_M | I2C_CR2_NBYTES(byte_count) | I2C_CR2_AUTOEND_M; | ||
i2c->CR2 |= I2C_CR2_START_M; | i2c->CR2 |= I2C_CR2_START_M; | ||
| Строка 113: | Строка 263: | ||
for(uint8_t i = 0; i < byte_count; i++) | for(uint8_t i = 0; i < byte_count; i++) | ||
{ | { | ||
| − | + | // Счетчик для ожидания | |
| + | int counter = 0; | ||
| + | |||
while(!(i2c->ISR & I2C_ISR_RXNE_M)) // байт принят когда RXNE = 1 | 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)) | if((i2c->ISR & I2C_ISR_STOPF_M) && (i2c->ISR & I2C_ISR_NACKF_M)) | ||
{ | { | ||
| Строка 125: | Строка 287: | ||
} | } | ||
| + | // Ошибка. Во время чтения мастер прислал NACK или STOP | ||
if((i2c->ISR & I2C_ISR_STOPF_M) && (i2c->ISR & I2C_ISR_NACKF_M)) | if((i2c->ISR & I2C_ISR_STOPF_M) && (i2c->ISR & I2C_ISR_NACKF_M)) | ||
{ | { | ||
| Строка 130: | Строка 293: | ||
break; | break; | ||
} | } | ||
| + | |||
| + | |||
data[i] = i2c->RXDR; // чтение байта и сброс RXNE | data[i] = i2c->RXDR; // чтение байта и сброс RXNE | ||
xprintf("Чтение по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); | xprintf("Чтение по адресу 0x%02x байта 0x%02x\n", slave_adr_print, data[i]); | ||
} | } | ||
| + | |||
| + | xprintf("Получено %d\n" , data[0] << 8 | data[1]); | ||
} | } | ||
| − | |||
| − | |||
int main() | int main() | ||
| − | { | + | { |
| − | + | // Адрес ведомого | |
| − | + | uint8_t slave_address = 0x36; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | // Число для оптавки | |
| − | uint16_t to_send = | + | uint16_t to_send = 0; |
| − | |||
| − | //uint8_t data[2] = {to_send >> 8, to_send & 0b0000000011111111}; // массив | + | // Массив с байтами для отправки / приема |
| + | uint8_t data[2] = {to_send >> 8, to_send & 0b0000000011111111}; // массив заполняется байтами числа to_send | ||
| − | + | // Инициализация блока i2c в режиме мастер (ведущий) | |
| − | + | i2c_master_init(I2C_0); | |
| Строка 162: | Строка 322: | ||
{ | { | ||
| − | // | + | // Запись данных по адресу 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, | + | i2c_master_read(I2C_0, slave_address, data, sizeof(data), false); |
| + | for (volatile int i = 0; i < 1000000; i++); | ||
| − | |||
} | } | ||
Версия 18:09, 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 - если требуется сдвиг
i2c_master_write(I2C_0, slave_address, data, sizeof(data), false);
Чтобы интерфейс работал в режиме ведущего, в регистре I2C_CR2 нужно настроить:
- режим адреса (7 или 10 бит) ADD10;
- адрес ведомого SADD[9:0];
- направление передачи RD_WRN;
- в случае чтения из ведомого с 10-битным адресом HEAD10R. Обуславливает передачу всего адреса или только заголовка в случае смены направления;
- количество байтов на передачу NBYTES[7:0]. Если количество байтов равно или более 255, в NBYTES[7:0] должно быть сперва записано значение 0xFF.
После этого пользователь устанавливает бит START в регистре I2C_CR2. После этого изменение вышеописанных битов не допускается.
В примере
Полный код 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++);
}
}