Search
Write a publication
Pull to refresh

I2C Slave на STM32G4

Reading time19 min
Views1.7K

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

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

В статье я не буду глубоко описывать регистры, саму работу шины, прочее, этого добра навалом. Просто окунёмся в дип дарк фэнтези реализацию эмулятора простейшего тача 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

Tags:
Hubs:
+15
Comments2

Articles