I2C: различия между версиями

Материал из me-robotics wiki
Строка 1: Строка 1:
 
Статья находится в разработке.
 
Статья находится в разработке.
  
Пример использования I2C MIK32  в режиме мастер.<syntaxhighlight lang="c++" line="1">
+
== '''Пример использования 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 "i2c_loop_common.h"
+
#include "i2c.h"
 
#include "stdbool.h"
 
#include "stdbool.h"
#include "wakeup.h"
 
  
void i2c_init(I2C_TypeDef* i2c)
+
 
 +
void i2c_master_init(I2C_TypeDef* i2c)
 
{
 
{
     //Включаем тактирование необходимых блоков - GPIO_0, GPIO_1, GPIO_2 и модуля выбора режима GPIO  
+
     //Включаем тактирование необходимых блоков и модуля выбора режима 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 – интерфейс включен
    *
 
    * STOPIE - Разрешение прерывания детектировании STOP: 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->ISR & I2C_ISR_NACKF_M)//при записи байта слейв прислал NACK
+
            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
+
        // Ошибка. При записи байта слейв прислал 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()
{
+
{  
     PM->AHB_CLK_MUX = 0; // выбор источника тактирования. 0 - внешний кварц
+
     // Адрес ведомого
    PM->CPU_RTC_CLK_MUX = 0;// выбор часового кварца как внешний
+
     uint8_t slave_address = 0x36;  
    for (volatile int i = 0; i < 100; i++);
 
    WU->CLOCKS_SYS = 1 << 1; // выключение внутреннего системного кварца
 
     WU->CLOCKS_BU = 1 << 1; // выключение внутреннего часовго кварца
 
    for (volatile int i = 0; i < 100; i++) ;
 
   
 
  
     uint8_t slave_adr = 0x36; // адрес ведомого
+
     // Число для оптавки
     uint16_t to_send = 300; // данные для оптавки
+
     uint16_t to_send = 0;  
    uint8_t to_send_byte = 1; // данные для оптавки
 
 
      
 
      
     //uint8_t data[2] = {to_send >> 8, to_send & 0b0000000011111111}; // массив байтов на отправку
+
     // Массив с байтами для отправки / приема
 +
    uint8_t data[2] = {to_send >> 8, to_send & 0b0000000011111111}; // массив заполняется байтами числа to_send
  
     uint8_t data[1] = {to_send_byte}; // массив байтов на отправку
+
     // Инициализация блока i2c в режиме мастер (ведущий)
     i2c_init(I2C_0); // инициализация блока i2c
+
     i2c_master_init(I2C_0);  
 
      
 
      
  
Строка 162: Строка 322:
 
     {
 
     {
 
          
 
          
         //i2c_master_write(I2C_0, slave_adr, data, 2, false); // запись данных по адресу slave_adr = 0x36 без сдвига адреса
+
         // Запись данных по адресу slave_adr = 0x36 без сдвига адреса
         //i2c_master_write(I2C_0, slave_adr, data, 1, false); // запись данных по адресу slave_adr = 0x36 без сдвига адреса
+
         i2c_master_write(I2C_0, slave_address, data, sizeof(data), false);  
 +
        for (volatile int i = 0; i < 1000000; i++);
  
         //i2c_master_read(I2C_0, slave_adr, data, 2, false); // чтение данных по адресу slave_adr = 0x36 без сдвига адреса
+
         // Чтение данных по адресу slave_adr = 0x36 без сдвига адреса
         i2c_master_read(I2C_0, slave_adr, data, 1, false); // чтение данных по адресу slave_adr = 0x36 без сдвига адреса
+
         i2c_master_read(I2C_0, slave_address, data, sizeof(data), false);  
 +
        for (volatile int i = 0; i < 1000000; i++);
  
        for (volatile int i = 0; i < 1000000; i++);
 
 
     }
 
     }
 
      
 
      

Версия 18:09, 23 июня 2022

Статья находится в разработке.

Пример использования I2C MIK32

Ошибка создания миниатюры: Файл не найден
Рисунок 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

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++); 

    }
    
    
}