Понадобилось мне для проекта по приколу сделать i2c slave (ведомого устройства), но не просто эмуляцию одного устройства (например eeprom), а сразу эдакого эмулятора с, если можно это так назвать, API, к которому можно уже привязывать эмуляции конкретных реализаций устройств на произвольные адреса.
На тему реализации ведомых устройств на STM32 с использованием LL я как-то не особо много инфы нарыл, в итоге накостылил, как сам понял 😀 Тест отвёрткой проходит, хотя первые реализации иногда прям залипали намертво.
В статье я не буду глубоко описывать регистры, саму работу шины, прочее, этого добра навалом. Просто окунёмся в дип дарк фэнтези реализацию эмулятора простейшего тача cst816s.
Для тестирования нам понадобится примерно подобный стенд:
Примечание: Описания мастер-платы здесь не будет, если очень надо, то могу в отдельной статье описать драйвер LCD с примером использования в LVGL.
I2C Slave
Начнём с базы. Для начала реализовываем фундамент — это i2c_slave файлы, которые содержат в себе самый основной код по обработке прерываний и зарегистрированные реализации ведомых устройств. Этот фундамент умеет работать и отвечать мастеру самостоятельно, даже без единой привязки эмуляторов, щедро вываливая всё происходящее в лог (Segger RTT).
Если представить это в виде блок-схемы, то получится следующее:
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 -
если нет реализации эмулятора, а мастер запрашивает байт, то 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); } } }
Проверка
Бахаем теперь на столе стенд по блок-схеме:
Попервой работало как-то так (выше привёл уже исправленные исходники):
Но спустя небольшое время получаем:
Вот так это выглядит в консоли:
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/
Добавить комментарий