I2C Slave на STM32G4

от автора

Понадобилось мне для проекта по приколу сделать i2c slave (ведомого устройства), но не просто эмуляцию одного устройства (например eeprom), а сразу эдакого эмулятора с, если можно это так назвать, API, к которому можно уже привязывать эмуляции конкретных реализаций устройств на произвольные адреса.

На тему реализации ведомых устройств на STM32 с использованием LL я как-то не особо много инфы нарыл, в итоге накостылил, как сам понял 😀 Тест отвёрткой проходит, хотя первые реализации иногда прям залипали намертво.

В статье я не буду глубоко описывать регистры, саму работу шины, прочее, этого добра навалом. Просто окунёмся в дип дарк фэнтези реализацию эмулятора простейшего тача cst816s.

Для тестирования нам понадобится примерно подобный стенд:

Блок-схема собранного на столе макета

Блок-схема собранного на столе макета

Примечание: Описания мастер-платы здесь не будет, если очень надо, то могу в отдельной статье описать драйвер LCD с примером использования в LVGL.

I2C Slave

Начнём с базы. Для начала реализовываем фундамент — это i2c_slave файлы, которые содержат в себе самый основной код по обработке прерываний и зарегистрированные реализации ведомых устройств. Этот фундамент умеет работать и отвечать мастеру самостоятельно, даже без единой привязки эмуляторов, щедро вываливая всё происходящее в лог (Segger RTT).

Если представить это в виде блок-схемы, то получится следующее:

wtf

wtf

i2c_slave.h

В хедере довольно минимальный набор необходимого. Дефайны и структуры:

  • I2C_SLAVE_SLAVES_MAX — сколько максимум поддерживается ссылок на эмуляции устройств

  • I2C_SLAVE_HW_DEBUG_LOG — куча дебаг инфы в RTT

  • slave_instance_t — «ссылка» на реализацию эмулятора конкретного устройства

  • i2c_slave_t — конфиг самого i2c_slave (например, можно сделать несколько, под каждый хардварный i2c)

Вызываемые из основной программы функции:

  • i2cSlave_Init — базовая инициализация

  • i2cSlave_Enable \ i2cSlave_Disable — оперативное отключение i2c_slave (при включении специально не активируется, т.к. бывают ситуации, что нужно сначала быть на шине мастером, а потом переключиться в режим ведомого)

  • i2cSlave_Register — регистрация (добавление ссылки) нового эмулятора

Колбеки, которые уже вызываются из прерывания периферии (в моей реализации i2c на LL в режима мастера прерывания вообще не используются):

  • i2cSlave_IrqAddrSet — новый запрос от мастера с адресом ведомого устройства

  • i2cSlave_IrqRecive — принят байт

  • i2cSlave_IrqTransmit — запрошен байт

  • i2cSlave_IrqNack — получен NACK

  • i2cSlave_IrqError — ошибка на шине

Последние функции вызываются из простого обработчика прерываний, пример которого будет ниже.

Скрытый текст
#ifndef _I2C_SLAVE_H_ #define _I2C_SLAVE_H_  /* ----------------------------------------------------------------------------------------------------------------- // v0.10 - 2025.07.20: - Первоначальная версия  v0.11 - 2025.07.21: - Мелкие правки, вроде работает  // ----------------------------------------------------------------------------------------------------------------- */  #include "main.h"  #ifndef I2C_SLAVE_SLAVES_MAX #define I2C_SLAVE_SLAVES_MAX          20 #endif  #define I2C_SLAVE_LOG_PREFIX          "[i2c_slv] "  #ifndef I2C_SLAVE_HW_DEBUG_LOG #define I2C_SLAVE_HW_DEBUG_LOG        0 #endif  typedef struct __attribute__((__packed__)) {    uint8_t addr;    void *conf;    void (*freg)(void *, uint8_t);    void (*fwrite)(void *, uint8_t);    void (*fread)(void *, uint8_t *);    void (*fdone)(void *); } slave_instance_t;  typedef struct __attribute__((__packed__)) {    uint8_t inited;    uint8_t addr;    uint8_t reg;    uint8_t slave_num;    uint8_t slave_id;    void *i2c;    slave_instance_t slave[I2C_SLAVE_SLAVES_MAX]; } i2c_slave_t;  uint32_t i2cSlave_Init(i2c_slave_t *conf, void *hi2c); void i2cSlave_Enable(i2c_slave_t *conf); void i2cSlave_Disable(i2c_slave_t *conf); uint32_t i2cSlave_Register(i2c_slave_t *conf, uint8_t slave_addr, void *slave_conf, void *freg, void *fwrite, void *fread, void *fdone);  // Call in Interrupt uint32_t i2cSlave_IrqAddrSet(i2c_slave_t *conf, uint8_t addr); void i2cSlave_IrqRecive(i2c_slave_t *conf); void i2cSlave_IrqTransmit(i2c_slave_t *conf); void i2cSlave_IrqComplete(i2c_slave_t *conf); void i2cSlave_IrqNack(i2c_slave_t *conf); void i2cSlave_IrqError(i2c_slave_t *conf);  #endif // _I2C_SLAVE_H_

i2c_slave.c

В реализации же особо ничего специфичного, но есть-таки пара моментов:

  • i2c_slave отвечает на все адреса, разрешённые маской (а это значит, что реализация поиска устройств у мастера может сбоить и «видеть» кучу устройств на шине), и выглядит это так:

    Мастер сканит шину и видит иллюзию изобилия ведомых устройств, а реальный только 0x15

    Мастер сканит шину и видит иллюзию изобилия ведомых устройств, а реальный только 0x15
  • если нет реализации эмулятора, а мастер запрашивает байт, то i2c_slave отправляет ему 0xFF всегда

Скрытый текст
#include "i2c_slave.h" #include <string.h>  uint32_t i2cSlave_Init(i2c_slave_t *conf, void *hi2c) {    conf->i2c = hi2c;    conf->inited = 1;    conf->slave_num = 0;    conf->slave_id = 0xff;    conf->reg = 0;     for (uint8_t i = 0; i < I2C_SLAVE_SLAVES_MAX; i++)    {       conf->slave[i].addr = 0xff;    }     // TODO: кривое определение, но для большинства задач пойдёт    SysView_LogSuccess("\r\n" I2C_SLAVE_LOG_PREFIX "Inited at I2C%u\r\n", (hi2c == I2C1) ? 1 : 2);    return 0x00; }  void i2cSlave_Enable(i2c_slave_t *conf) {    if (conf->inited)    {       LL_I2C_SetOwnAddress1(conf->i2c, (0x01 << 1), LL_I2C_OWNADDRESS1_7BIT);       LL_I2C_SetOwnAddress2(conf->i2c, (0x00 << 1), LL_I2C_OWNADDRESS2_MASK07); // 7 bit only compare       LL_I2C_EnableOwnAddress1(conf->i2c);       LL_I2C_EnableOwnAddress2(conf->i2c);        LL_I2C_EnableIT_RX(conf->i2c);       LL_I2C_EnableIT_TX(conf->i2c);       LL_I2C_EnableIT_ADDR(conf->i2c);       LL_I2C_EnableIT_NACK(conf->i2c);       LL_I2C_EnableIT_ERR(conf->i2c);       LL_I2C_EnableIT_STOP(conf->i2c);        SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Enabled\r\n");    } }  void i2cSlave_Disable(i2c_slave_t *conf) {    if (conf->inited)    {       LL_I2C_DisableOwnAddress1(conf->i2c);       LL_I2C_DisableOwnAddress2(conf->i2c);        LL_I2C_DisableIT_RX(conf->i2c);       LL_I2C_DisableIT_TX(conf->i2c);       LL_I2C_DisableIT_ADDR(conf->i2c);       LL_I2C_DisableIT_NACK(conf->i2c);       LL_I2C_DisableIT_ERR(conf->i2c);       LL_I2C_DisableIT_STOP(conf->i2c);        conf->slave_id = 0xff;       conf->reg = 0;       SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Disabled\r\n");    } }  uint32_t i2cSlave_Register(i2c_slave_t *conf, uint8_t slave_addr, void *slave_conf, void *freg, void *fwrite, void *fread, void *fdone) {    if (conf->inited)    {       if (conf->slave_num < (I2C_SLAVE_SLAVES_MAX - 1))       {          for (uint8_t i = 0; i < conf->slave_num; i++)          {             if (conf->slave[i].addr == slave_addr)             {                SysView_LogWarning(I2C_SLAVE_LOG_PREFIX "0x%x already registered at id = %u! Skip\r\n", slave_addr, i);                return 0x01;             }          }           SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Registered: addr = 0x%x, id = %u\r\n", slave_addr, conf->slave_num);          conf->slave[conf->slave_num].addr = slave_addr;          conf->slave[conf->slave_num].conf = slave_conf;          conf->slave[conf->slave_num].freg = freg;          conf->slave[conf->slave_num].fwrite = fwrite;          conf->slave[conf->slave_num].fread = fread;          conf->slave[conf->slave_num].fdone = fdone;          conf->slave_num++;          return 0x00;       }    }     return 0x01; }  uint32_t i2cSlave_IrqAddrSet(i2c_slave_t *conf, uint8_t addr) {    if (conf->inited)    { #if (I2C_SLAVE_HW_DEBUG_LOG)       SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Addr 0x%x: ", addr); #endif       conf->addr = addr;       conf->reg = 0;       conf->slave_id = 0xff;       for (uint8_t i = 0; i < conf->slave_num; i++)       {          if (conf->slave[i].addr == conf->addr)          {             conf->slave_id = i; #if (I2C_SLAVE_HW_DEBUG_LOG)             SysView_LogInfo("id = 0x%x\r\n", conf->slave_id); #endif             return 0x00;          }       }        if (conf->slave_id == 0xff)       { #if (I2C_SLAVE_HW_DEBUG_LOG)          SysView_LogWarning("not found\r\n"); #endif       }    }     return 0x01; }  void i2cSlave_IrqRecive(i2c_slave_t *conf) {    if (conf->inited)    {       uint8_t data = LL_I2C_ReceiveData8(conf->i2c);       if (conf->slave_id != 0xff)       {          if (conf->reg == 0)          { #if (I2C_SLAVE_HW_DEBUG_LOG)             SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Reg = 0x%x\r\n", data); #endif             conf->reg = 1;             conf->slave[conf->slave_id].freg(conf->slave[conf->slave_id].conf, data);          }          else          { #if (I2C_SLAVE_HW_DEBUG_LOG)             SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Write 0x%x\r\n", data); #endif             conf->slave[conf->slave_id].fwrite(conf->slave[conf->slave_id].conf, data);          }       }       else       {          SysView_LogWarning(I2C_SLAVE_LOG_PREFIX "Write skip 0x%x\r\n", data);       }    } }  void i2cSlave_IrqTransmit(i2c_slave_t *conf) {    if (conf->inited)    {       if (conf->slave_id != 0xff)       {          static uint8_t data;          data = 0x00;          conf->slave[conf->slave_id].fread(conf->slave[conf->slave_id].conf, &data); #if (I2C_SLAVE_HW_DEBUG_LOG)          SysView_LogInfo(I2C_SLAVE_LOG_PREFIX "Read 0x%x\r\n", data); #endif          LL_I2C_TransmitData8(conf->i2c, data);          return ;       }  #if (I2C_SLAVE_HW_DEBUG_LOG)       SysView_LogWarning(I2C_SLAVE_LOG_PREFIX "Read skip\r\n"); #endif       LL_I2C_TransmitData8(conf->i2c, 0xff);    } }  void i2cSlave_IrqComplete(i2c_slave_t *conf) {    if (conf->inited)    { #if (I2C_SLAVE_HW_DEBUG_LOG)       SysView_LogSuccess(I2C_SLAVE_LOG_PREFIX "Complete\r\n"); #endif       conf->addr = 0xff;       conf->reg = 0;       if (conf->slave_id != 0xff)       {          conf->slave[conf->slave_id].fdone(conf->slave[conf->slave_id].conf);       }        conf->slave_id = 0xff;    } }  void i2cSlave_IrqNack(i2c_slave_t *conf) {    if (conf->inited)    { #if (I2C_SLAVE_HW_DEBUG_LOG)       SysView_LogWarning(I2C_SLAVE_LOG_PREFIX "NACK\r\n"); #endif       conf->addr = 0xff;       conf->reg = 0;       if (conf->slave_id != 0xff)       {          conf->slave[conf->slave_id].fdone(conf->slave[conf->slave_id].conf);       }        conf->slave_id = 0xff;    } }  void i2cSlave_IrqError(i2c_slave_t *conf) {    if (conf->inited)    {       LL_I2C_Disable(conf->i2c);       LL_I2C_Enable(conf->i2c); #if (I2C_SLAVE_HW_DEBUG_LOG)       SysView_LogError(I2C_SLAVE_LOG_PREFIX "Error\r\n"); #endif       conf->addr = 0xff;       conf->reg = 0;       if (conf->slave_id != 0xff)       {          conf->slave[conf->slave_id].fdone(conf->slave[conf->slave_id].conf);       }        conf->slave_id = 0xff;    } }

cst816s

Тут особо и описывать нечего, просто переписанная под себя версия, взятая с примера WaveShare.

cst816s.h

Скрытый текст
#ifndef _CST816S_H_ #define _CST816S_H_  /* ----------------------------------------------------------------------------------------------------------------- // v0.10 - 2025.03.23: - Первоначальная версия  v0.11 - 2025.05.20: - Правки возвращаемых статусов - Переход на target_common.h  // ----------------------------------------------------------------------------------------------------------------- */  #include "main.h"  #ifndef CST816S_TIMEOUT_MS #define CST816S_TIMEOUT_MS                         500 #endif  #ifndef CST816S_DEFAULT_ADDRESS #define CST816S_DEFAULT_ADDRESS                    0x15 #endif  #ifndef CST816S_RESET_PIN_ENABLE #define CST816S_RESET_PIN_ENABLE                   0 #endif  #ifndef CST816S_DEBUG_SHOW_RAW_EVENTS #define CST816S_DEBUG_SHOW_RAW_EVENTS              0 #endif  #define CST816S_LOG_PREFIX                         "[cst816s] "  typedef enum {    CST816S_GESTURE_NONE = 0x00,    CST816S_GESTURE_SWIPE_UP = 0x01,    CST816S_GESTURE_SWIPE_DOWN = 0x02,    CST816S_GESTURE_SWIPE_LEFT = 0x03,    CST816S_GESTURE_SWIPE_RIGHT = 0x04,    CST816S_GESTURE_SINGLE_CLICK = 0x05,    CST816S_GESTURE_DOUBLE_CLICK = 0x0B,    CST816S_GESTURE_LONG_PRESS = 0x0C } cst816s_gesture_t;  typedef enum {    CST816S_EVENT_DOWN = 0x00,    CST816S_EVENT_UP,    CST816S_EVENT_CONTACT, } cst816s_event_type_t;  typedef struct __attribute__((__packed__)) {    uint16_t x;    uint16_t y; } cst816s_event_t;  typedef struct __attribute__((__packed__)) {    uint8_t address;    uint8_t inited;    uint8_t gesture; // cst816s_gesture_t    uint8_t event_type; // cst816s_event_type_t    cst816s_event_t event; #if (CST816S_RESET_PIN_ENABLE)    void *reset_port;    uint16_t reset_pin; #endif    void *i2c; } cst816s_touch_t;  uint8_t cst816s_Init(cst816s_touch_t *conf, void *hi2c, uint8_t address); uint8_t cst816s_SetAutoSleep(cst816s_touch_t *conf, uint8_t enable, uint8_t sec); uint8_t cst816s_ReadVersion(cst816s_touch_t *conf); uint8_t cst816s_ReadEvent(cst816s_touch_t *conf);  #endif // _CST816S_H_ 

cst816s.c

Скрытый текст
#include "cst816s.h" #include "target_delay.h" #include "target_gpio.h" #include "target_i2c.h"  __inline uint32_t cst816s_RegWrite(cst816s_touch_t *conf, uint8_t reg, uint8_t data) {    return target_I2C_WriteByte(conf->i2c, conf->address, reg, TARGET_I2C_REG_LEN_BYTE, data, CST816S_TIMEOUT_MS); }  __inline uint32_t cst816s_RegRead(cst816s_touch_t *conf, uint8_t reg, uint8_t *data) {    return target_I2C_ReadByte(conf->i2c, conf->address, reg, TARGET_I2C_REG_LEN_BYTE, data, CST816S_TIMEOUT_MS); }  uint8_t cst816s_Init(cst816s_touch_t *conf, void *hi2c, uint8_t address) {    conf->i2c = hi2c;    conf->address = (address << 1);    conf->inited = 0;  #if (CST816S_RESET_PIN_ENABLE)    target_ResetPin(conf->reset_port, conf->reset_pin);    target_Delay_ms(1);    target_SetPin(conf->reset_port, conf->reset_pin);    target_Delay_ms(10); #endif     if (target_I2C_IsDeviceReady(conf->i2c, conf->address, CST816S_TIMEOUT_MS) == 0x00)    {       conf->inited = 1;       SysView_LogSuccess("\r\n"CST816S_LOG_PREFIX "Inited (addr 0x%x)\r\n", address);       cst816s_ReadVersion(conf);       cst816s_RegWrite(conf, 0xEC, 1); // Enable double-tap       cst816s_SetAutoSleep(conf, 0, 255); // Disable auto sleep       return STATUS_OK;    }     SysView_LogWarning("\r\n"CST816S_LOG_PREFIX "Init failed (addr 0x%x)\r\n", address);    return STATUS_BUSY; }  uint8_t cst816s_SetAutoSleep(cst816s_touch_t *conf, uint8_t enable, uint8_t sec) {    if (conf->inited)    {       uint32_t status = STATUS_OK;       if (enable)       {          SysView_LogInfo(CST816S_LOG_PREFIX "Auto sleep enabled, timeout %u sec\r\n", sec);          status  = cst816s_RegWrite(conf, 0xFE, 0x00);          status |= cst816s_RegWrite(conf, 0xF9, sec);       }       else       {          SysView_LogInfo(CST816S_LOG_PREFIX "Auto sleep disabled\r\n");          status = cst816s_RegWrite(conf, 0xFE, 0xFE);       }        return status;    }     return STATUS_ERROR; }  uint8_t cst816s_ReadVersion(cst816s_touch_t *conf) {    if (conf->inited)    {       uint8_t data[3] = { 0 };       uint32_t status = STATUS_OK;        status  = cst816s_RegRead(conf, 0xA7, &data[0]); // ChipID       status |= cst816s_RegRead(conf, 0xA8, &data[1]); // ProjID       status |= cst816s_RegRead(conf, 0xA9, &data[2]); // Firmware        SysView_LogInfo(CST816S_LOG_PREFIX "Chip ID: 0x%x\r\n", data[0]);       SysView_LogInfo(CST816S_LOG_PREFIX "Project ID: 0x%x\r\n", data[1]);       SysView_LogInfo(CST816S_LOG_PREFIX "Firmware: 0x%x\r\n", data[2]);        return status;    }     return STATUS_ERROR; }  uint8_t cst816s_ReadEvent(cst816s_touch_t *conf) {    if (conf->inited)    {       uint8_t data[6];       uint32_t status = target_I2C_ReadMem(conf->i2c, conf->address, 0x01, TARGET_I2C_REG_LEN_BYTE, &data[0], 6, CST816S_TIMEOUT_MS);        conf->gesture = data[0];       //points = data[1];       conf->event_type = data[2] >> 6;       conf->event.x = ((data[2] & 0xF) << 8) + data[3];       conf->event.y = ((data[4] & 0xF) << 8) + data[5];  #if (CST816S_DEBUG_SHOW_RAW_EVENTS)       SysView_LogInfo(CST816S_LOG_PREFIX "Event: X=%03u Y=%03u\r\n", conf->event.x, conf->event.y);       if (conf->gesture)       {          SysView_LogInfo(CST816S_LOG_PREFIX "Gesture: 0x%x\r\n", conf->gesture);       } #endif       return status;    }     return STATUS_ERROR; } 

cst816s_emu

А тут уже интереснее, скопировал драйвер выше, переименовал и стал дописывать. Чего поменялось\появилось:

  • Вначале мастер опрашивает тач и читает с него Chip ID, Proj ID и Firmware Version, так вот, первые два я оставил как в чипе, а Firmware поменял на 0x69 (чтобы отличать эмулятор от хардварного чипа)

  • Добавляем функции I2C Slave API, которые потом будем привязывать

  • cst816s_emu_NewEvent — вызываем и таким образом «генерим» событие нажатия (формируется прерывание на int пине, и при запросе от мастера выдаём ему x,y, которые переданы в эту функцию)

cst816s_emu.h

Скрытый текст
#ifndef _CST816S_EMU_H_ #define _CST816S_EMU_H_  /* ----------------------------------------------------------------------------------------------------------------- // v0.10 - 2025.07.20: - Первоначальная версия на основе cst816s.h из LLSDK  // ----------------------------------------------------------------------------------------------------------------- */  #include "main.h" #include "cst816s.h" #include "i2c_slave.h"  #ifndef CST816S_EMU_TIMEOUT_MS #define CST816S_EMU_TIMEOUT_MS                  100 #endif  #ifndef CST816S_EMU_DEFAULT_ADDRESS #define CST816S_EMU_DEFAULT_ADDRESS             0x15 #endif  #ifndef CST816S_EMU_DEBUG_SHOW_RAW_EVENTS #define CST816S_EMU_DEBUG_SHOW_RAW_EVENTS       0 #endif  #define CST816S_EMU_CHIP_ID                     0xb6 // 0xA7 #define CST816S_EMU_PROJECT_ID                  0x02 // 0xA8 #define CST816S_EMU_FIRMWARE_VER                0x69 // 0xA9  #define CST816S_EMU_LOG_PREFIX                  "[cst816s_emu] "  typedef struct __attribute__((__packed__)) {    uint8_t address;    uint8_t inited;    uint8_t reg_addr;    uint8_t event_new;    uint32_t event_time;    cst816s_event_t event;    void *int_port;    uint16_t int_pin;    void *i2c; } cst816s_emu_touch_t;  uint32_t cst816s_emu_Init(cst816s_emu_touch_t *conf, void *hi2c, uint8_t address); uint32_t cst816s_emu_NewEvent(cst816s_emu_touch_t *conf, uint16_t x, uint16_t y); void cst816s_emu_Routine(cst816s_emu_touch_t *conf);  // I2C Slave API void cst816s_emu_Reg(cst816s_emu_touch_t *conf, uint8_t data); void cst816s_emu_Write(cst816s_emu_touch_t *conf, uint8_t data); void cst816s_emu_Read(cst816s_emu_touch_t *conf, uint8_t *data); void cst816s_emu_Done(cst816s_emu_touch_t *conf);  #endif // _CST816S_EMU_H_ 

cst816s_emu.c

Из интересного здесь, наверное, только реализация cst816s_emu_Read, в которой мы, считай, и должны следить за текущим адресом (и смещением, откуда мастер ожидает прочитать или записать данные), а также обрабатывать возможные нештатные ситуации (когда мастер пытается запросить данные или записать вне адресов симулированных регистров).

Скрытый текст
#include "cst816s_emu.h" #include "target_gpio.h" #include "target_delay.h"  uint32_t cst816s_emu_Init(cst816s_emu_touch_t *conf, void *hi2c, uint8_t address) {    conf->i2c = hi2c;    conf->address = (address << 1);    conf->inited = 1;    conf->event_new = 0;    target_SetPin(conf->int_port, conf->int_pin);    SysView_LogSuccess("\r\n"CST816S_EMU_LOG_PREFIX "Inited (addr 0x%x)\r\n", address);    return STATUS_OK; }  uint32_t cst816s_emu_NewEvent(cst816s_emu_touch_t *conf, uint16_t x, uint16_t y) {    if (conf->inited)    {       if (conf->event_new == 0)       {          SysView_LogInfo(CST816S_EMU_LOG_PREFIX "Event: X=%03u Y=%03u\r\n", x, y);          conf->event.x = x;          conf->event.y = y;          conf->event_time = SysTick_GetCurrentTick();          conf->event_new = 1;          return STATUS_OK;       }        return STATUS_BUSY;    }     return STATUS_ERROR; }  void cst816s_emu_Routine(cst816s_emu_touch_t *conf) {    if (conf->inited)    {       if (conf->event_new)       {          target_ResetPin(conf->int_port, conf->int_pin);          if (SysTick_GetCurrentTick() - conf->event_time) // 1ms          {             target_SetPin(conf->int_port, conf->int_pin);             conf->event_new = 0;          }       }    } }  void cst816s_emu_Reg(cst816s_emu_touch_t *conf, uint8_t data) {    if (conf->inited)    {       conf->reg_addr = data;    } }  void cst816s_emu_Write(cst816s_emu_touch_t *conf, uint8_t data) {    if (conf->inited)    {       // TODO: skip all    } }  void cst816s_emu_Read(cst816s_emu_touch_t *conf, uint8_t *data) {    if (conf->inited)    {       switch (conf->reg_addr)       {          case 0xa7:             *data = CST816S_EMU_CHIP_ID;          break;           case 0xa8:             *data = CST816S_EMU_PROJECT_ID;          break;           case 0xa9:             *data = CST816S_EMU_FIRMWARE_VER;          break;           case 0x01:             // gesture             *data = CST816S_GESTURE_NONE;          break;           case 0x02:             // points             *data = 0x01;          break;           case 0x03:             // event type [7:6] + msb 4bit x             *data = 0 | ((conf->event.x >> 8) & 0xF);          break;           case 0x04:             // lsb x             *data = (conf->event.x & 0xFF);          break;           case 0x05:             // msb 4bit y             *data = ((conf->event.y >> 8) & 0xF);          break;           case 0x06:             // lsb y             *data = (conf->event.y & 0xFF);          break;           default:             *data = 0xFF;          break;       }        conf->reg_addr++;    } }  void cst816s_emu_Done(cst816s_emu_touch_t *conf) {    if (conf->inited)    {       conf->reg_addr = 0x00;    } } 

stm32g4xx_init

Пример инициализации I2C периферии на LL, всё остальное не столь важно:

Скрытый текст
void SysInit_I2C(void) {    // I2C1: SCL (PA15), SDA (PB7) - Slave    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_15, LL_GPIO_MODE_ALTERNATE);    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_15, LL_GPIO_AF_4);    LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_15, LL_GPIO_OUTPUT_OPENDRAIN);    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_15, LL_GPIO_SPEED_FREQ_HIGH);     LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_7, LL_GPIO_MODE_ALTERNATE);    LL_GPIO_SetAFPin_0_7(GPIOB, LL_GPIO_PIN_7, LL_GPIO_AF_4);    LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_7, LL_GPIO_OUTPUT_OPENDRAIN);    LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_7, LL_GPIO_SPEED_FREQ_HIGH);     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);     LL_I2C_InitTypeDef I2C_InitStruct;    I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;    I2C_InitStruct.Timing = 0x00E057FD; // 400kHz    //I2C_InitStruct.Timing = 0x20B0D9FF; // 100kHz    I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;    I2C_InitStruct.DigitalFilter = 2;    I2C_InitStruct.OwnAddress1 = 0;    I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;    I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;    LL_I2C_Init(I2C1, &I2C_InitStruct);     LL_I2C_EnableAutoEndMode(I2C1);    LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK);    LL_I2C_DisableOwnAddress2(I2C1); // TODO: ...    LL_I2C_DisableGeneralCall(I2C1);    LL_I2C_EnableClockStretching(I2C1);     NVIC_SetPriority(I2C1_EV_IRQn, 0);    NVIC_EnableIRQ(I2C1_EV_IRQn);    NVIC_SetPriority(I2C1_ER_IRQn, 0);    NVIC_EnableIRQ(I2C1_ER_IRQn);     // I2C2: SCL (PA9), SDA (PA8) - Master    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_8, LL_GPIO_MODE_ALTERNATE);    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_8, LL_GPIO_AF_4);    LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_8, LL_GPIO_OUTPUT_OPENDRAIN);    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_8, LL_GPIO_SPEED_FREQ_HIGH);     LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_9, LL_GPIO_MODE_ALTERNATE);    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_9, LL_GPIO_AF_4);    LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_9, LL_GPIO_OUTPUT_OPENDRAIN);    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_9, LL_GPIO_SPEED_FREQ_HIGH);     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C2);     I2C_InitStruct.OwnAddress1 = 0;    LL_I2C_Init(I2C2, &I2C_InitStruct);     LL_I2C_EnableAutoEndMode(I2C2);    LL_I2C_SetOwnAddress2(I2C2, 0, LL_I2C_OWNADDRESS2_NOMASK);    LL_I2C_DisableOwnAddress2(I2C2);    LL_I2C_DisableGeneralCall(I2C2);    LL_I2C_EnableClockStretching(I2C2); }

Из важных моментов здесь:

  • Оба I2C изначально инициализируются как мастеры

  • LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK) — отключаем маску для I2C1, чтобы i2c_slave мог отвечать на любой адрес, но в реальном проекте стоит пересмотреть эту строку

  • I2C1 после базовой инициализации переключается дальше в режим slave, и там же включаются прерывания от него (это прекрасно работает, когда, например, нужно сначала инициализировать как мастер FPD микросхему, активируя проброс I2C поверх FPD, а потом уже по этому же I2C отвечать как ведомое устройство)

  • I2C2 работает вообще без прерываний (самописный драйвер)

stm32g4xx_it

Пример базовой реализации вызова колбеков для I2C1:

#include "i2c_slave.h" extern i2c_slave_t i2cs;  void I2C1_EV_IRQHandler(void) {    if (LL_I2C_IsActiveFlag_ADDR(i2cs.i2c))    {       LL_I2C_ClearFlag_ADDR(i2cs.i2c);       i2cSlave_IrqAddrSet(&i2cs, (LL_I2C_GetAddressMatchCode(i2cs.i2c) >> 1));    }    else if (LL_I2C_IsActiveFlag_NACK(i2cs.i2c))    {       LL_I2C_ClearFlag_NACK(i2cs.i2c);       i2cSlave_IrqNack(&i2cs);    }    else if (LL_I2C_IsActiveFlag_TXIS(i2cs.i2c))    {       i2cSlave_IrqTransmit(&i2cs);    }    else if (LL_I2C_IsActiveFlag_RXNE(i2cs.i2c))    {       i2cSlave_IrqRecive(&i2cs);    }    else if (LL_I2C_IsActiveFlag_STOP(i2cs.i2c))    {       LL_I2C_ClearFlag_STOP(i2cs.i2c);       if (!LL_I2C_IsActiveFlag_TXE(i2cs.i2c))       {          LL_I2C_ClearFlag_TXE(i2cs.i2c);       }        i2cSlave_IrqComplete(&i2cs);    } }  void I2C1_ER_IRQHandler(void) {    i2cSlave_IrqError(&i2cs); }

Главное, не забываем флаги сбрасывать. 🙂

main

Ну и, собственно, вот так вызываем это всё добро:

Скрытый текст
#include "main.h" #include "stm32g4xx_init.h"  #include "cst816s.h" cst816s_touch_t touch; volatile uint8_t touch_int = 0;  #include "i2c_slave.h" i2c_slave_t i2cs;  #include "cst816s_emu.h" cst816s_emu_touch_t touch_emu;  int main(void) {    SysInit_Clock();    SysTick_Config(SystemCoreClock / 1000U); // 1ms // No OS    SysInit_GPIO();    SysInit_I2C();     SysView_Init();    SysView_PrintInfo();     cst816s_Init(&touch, I2C2, CST816S_DEFAULT_ADDRESS);     i2cSlave_Init(&i2cs, I2C1);    i2cSlave_Enable(&i2cs);     touch_emu.int_port = GPIOB;    touch_emu.int_pin = LL_GPIO_PIN_5;    cst816s_emu_Init(&touch_emu, I2C1, CST816S_EMU_DEFAULT_ADDRESS);    i2cSlave_Register(&i2cs, CST816S_EMU_DEFAULT_ADDRESS, &touch_emu,                      &cst816s_emu_Reg,                      &cst816s_emu_Write,                      &cst816s_emu_Read,                      &cst816s_emu_Done);     for (;;)    {       cst816s_emu_Routine(&touch_emu);       if (touch_int)       {          touch_int = 0;          cst816s_ReadEvent(&touch);          cst816s_emu_NewEvent(&touch_emu, touch.event.x, touch.event.y);       }    } }

Проверка

Бахаем теперь на столе стенд по блок-схеме:

Ещё и запитываем от J-Link всё.

Ещё и запитываем от J-Link всё.

Попервой работало как-то так (выше привёл уже исправленные исходники):

Когда мастер-плата пробует работать с эмулятором

Когда мастер-плата пробует работать с эмулятором

Но спустя небольшое время получаем:

Вот мы и получили троллейбус из буханки... работает!

Вот мы и получили троллейбус из буханки… работает!

Вот так это выглядит в консоли:

RTT

RTT

sysview_init (бонус)

Чисто допом, вдруг кому пригодится — реализация небольшой надстройки над Segger RTT, чтобы удобно инитить и выводить цветные буковки в RTT, как на записи экрана выше:

sysview_init.h

Скрытый текст
#ifndef _SYSTEMVIEW_INIT_H_ #define _SYSTEMVIEW_INIT_H_  #include "stm32g4xx.h" #include <stdio.h> #include "main.h"  #define SYSVIEW_APP_NAME                     "LLSDK App" #define SYSVIEW_DEVICE_NAME                  "STM32G431CBU6" #define SEGGER_SYSVIEW_EXCLUDE_PRINTF        1 #define SEGGER_RTT_MODE_DEFAULT              SEGGER_RTT_MODE_NO_BLOCK_SKIP #define SYSVIEW_NUM_TASKS                    16 // No OS  #ifndef USE_CYCCNT_TIMESTAMP #define USE_CYCCNT_TIMESTAMP                 1 #endif  #ifndef ENABLE_DWT_CYCCNT #define ENABLE_DWT_CYCCNT                    (USE_CYCCNT_TIMESTAMP & SEGGER_SYSVIEW_POST_MORTEM_MODE) #endif  #define DWT_CTRL                             (*(volatile unsigned long*) (0xE0001000uL)) #define NOCYCCNT_BIT                         (1uL << 25) #define CYCCNTENA_BIT                        (1uL << 0)  #if(LIB_COMMON_SYSVIEW)  #include "SEGGER_SYSVIEW.h" #include "SEGGER_SYSVIEW_Conf.h" #include "SEGGER_RTT.h" #include "SEGGER_RTT_Conf.h"  #define SysView_ExecuteTask(t)               SEGGER_SYSVIEW_OnTaskStartExec((U32)t); \                                              t(); \                                              SEGGER_SYSVIEW_OnTaskStopReady((U32)t, 0);  #define SysView_LogInfo                      SEGGER_SYSVIEW_PrintfTarget #define SysView_LogWarning                   SEGGER_SYSVIEW_WarnfTarget #define SysView_LogError                     SEGGER_SYSVIEW_ErrorfTarget #define SysView_LogSuccess                   SEGGER_SYSVIEW_PrintfTarget #define SysView_EnterISR                     SEGGER_SYSVIEW_RecordEnterISR #define SysView_ExitISR                      SEGGER_SYSVIEW_RecordExitISR  #elif(LIB_COMMON_RTT)  #include "SEGGER_RTT.h" #include "SEGGER_RTT_Conf.h"  #define SysView_ExecuteTask(t)               t(); #define SysView_LogInfo(FORMAT, ...)         SEGGER_RTT_printf(0, FORMAT, ##__VA_ARGS__) #define SysView_LogWarning(FORMAT, ...)      SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_YELLOW FORMAT RTT_CTRL_RESET, ##__VA_ARGS__) #define SysView_LogError(FORMAT, ...)        SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_RED FORMAT RTT_CTRL_RESET, ##__VA_ARGS__) #define SysView_LogSuccess(FORMAT, ...)      SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_GREEN FORMAT RTT_CTRL_RESET, ##__VA_ARGS__) #define SysView_EnterISR() #define SysView_ExitISR()  #else  #define SysView_ExecuteTask(t)               t(); #define SysView_LogInfo(...) #define SysView_LogWarning(...) #define SysView_LogError(...) #define SysView_LogSuccess(...) #define SysView_EnterISR() #define SysView_ExitISR()  #endif  void SysView_Init(void); void SysView_PrintInfo(void); void SysView_AddTask(void* pTask, const char* sName, uint32_t Prio); char SysView_WaitForInputChar(void);  #endif // _SYSTEMVIEW_INIT_H_

sysview_init.c

Скрытый текст
#include "sysview_init.h" #include "build_info.h"  #if(LIB_COMMON_SYSVIEW) static void _cb_SendTaskList(void); static void _cb_SendSystemDesc(void);  static const SEGGER_SYSVIEW_OS_API SysView_API = { NULL, _cb_SendTaskList }; static SEGGER_SYSVIEW_TASKINFO SysView_TasksInfo[SYSVIEW_NUM_TASKS]; static uint32_t SysView_TasksNum = 0;  static void _cb_SendSystemDesc(void) {    SEGGER_SYSVIEW_SendSysDesc("N="SYSVIEW_APP_NAME",D="SYSVIEW_DEVICE_NAME);    SEGGER_SYSVIEW_SendSysDesc(    //",I#15=SysTick"    ); }  static void _cb_SendTaskList(void) {    for (uint32_t n = 0; n < SysView_TasksNum; n++)       SEGGER_SYSVIEW_SendTaskInfo(&SysView_TasksInfo[n]); }  void SysView_AddTask(void* pTask, const char* sName, uint32_t Prio) {    uint32_t n;    SEGGER_SYSVIEW_OnTaskCreate((U32)pTask);     if (SysView_TasksNum > SYSVIEW_NUM_TASKS)       return;     n = SysView_TasksNum;    SysView_TasksNum++;     SysView_TasksInfo[n].TaskID      = (U32)pTask;    SysView_TasksInfo[n].sName       = sName;    SysView_TasksInfo[n].Prio        = Prio;    SysView_TasksInfo[n].StackBase   = 0;    SysView_TasksInfo[n].StackSize   = 0; } #else void SysView_AddTask(void* pTask, const char* sName, uint32_t Prio) { } #endif // LIB_COMMON_SYSVIEW  void SysView_Init(void) { #if(LIB_COMMON_SYSVIEW) #if(SEGGER_SYSVIEW_CORE == SEGGER_SYSVIEW_CORE_CM3)    if (((DWT_CTRL & NOCYCCNT_BIT) == 0) && ((DWT_CTRL & CYCCNTENA_BIT) == 0))    {       DWT_CTRL |= CYCCNTENA_BIT;    } #endif // SEGGER_SYSVIEW_CORE     SEGGER_SYSVIEW_Init(SystemCoreClock, SystemCoreClock, &SysView_API, _cb_SendSystemDesc);    SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_BASE_RAM_ADDR); #elif(LIB_COMMON_RTT)    SEGGER_RTT_Init(); #endif // LIB_COMMON_SYSVIEW }  #if(LIB_COMMON_SYSVIEW || LIB_COMMON_RTT) char SysView_WaitForInputChar(void) {    while(SEGGER_RTT_HasKey() == 0) ;    return SEGGER_RTT_GetKey(); } #else char SysView_WaitForInputChar(void) {    return 0x00; } #endif  void SysView_PrintInfo(void) {    SysView_LogInfo(RTT_CTRL_CLEAR);    SysView_LogInfo("Compiled:    %s\r\n", COMPILE_DATE);    SysView_LogInfo("Compiler:    %s\r\n", COMPILER_VERSION); #ifdef xPortSysTickHandler // FreeRTOS    SysView_LogInfo("FreeRTOS:    %s\r\n", tskKERNEL_VERSION_NUMBER); #endif    SysView_LogInfo("Device:      %s\r\n", SYSVIEW_DEVICE_NAME);    SysView_LogInfo("App Name:    %s\r\n", SYSVIEW_APP_NAME);    SysView_LogInfo("Core Clock:  %u Hz (%u MHz)\r\n\r\n", SystemCoreClock, SystemCoreClock / 1000000); }

Подписывайтесь на канал (ссылки нет), ставьте классы, обсирайте в комментах. :3


ссылка на оригинал статьи https://habr.com/ru/articles/933604/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *