LCM — CH32V003 Light Control Module
Light Control Module, I2C slave
Loading...
Searching...
No Matches
lcm_i2c.c
Go to the documentation of this file.
1/********************************** (C) COPYRIGHT *******************************
2 * File Name : lcm_i2c.c
3 * Description : I2C1 slave: приём/передача протокола LCM (FSM + ISR).
4 *
5 * Обработчики прерываний:
6 * I2C1_EV_IRQHandler — события шины (ADDR, RXNE, STOP, передача ответа).
7 * I2C1_ER_IRQHandler — ошибки (AF, BERR, OVR, ARLO).
8 *
9 * Разбор и выполнение команд делегируется в lcm_module.c через
10 * LCM_Module_ExecuteSingleByte() / LCM_Module_ExecuteTwoByte().
11 *********************************************************************************/
12
13#include "lcm_i2c.h"
14#include "lcm_module.h"
15#include "lcm_led.h"
16#include "event_log.h"
17#include "ch32v00x_conf.h"
18
19/*
20 * ---------------------------------------------------------------------------
21 * Состояния конечного автомата приёма I2C (только фаза WRITE от мастера)
22 * ---------------------------------------------------------------------------
23 * WAIT_CMD — ожидаем первый байт транзакции (код команды).
24 * WAIT_PARAM — принята двухбайтовая команда (0x20…0x28), ждём параметр.
25 * ARMED_READ — принята команда чтения (0x1C, 0x1D); ответ будет отправлен
26 * при следующем ADDR в режиме slave-transmitter (RepSTART+R).
27 * ---------------------------------------------------------------------------
28 */
34
35/* --- Переменные FSM I2C (volatile: изменяются в ISR) --- */
37static volatile uint8_t s_i2c_cmd; /* код команды текущей транзакции */
38static volatile uint8_t s_i2c_tx_byte; /* байт для передачи мастеру (READ) */
39static volatile uint8_t s_i2c_tx_pending; /* 1: есть данные для фазы READ */
40
41/*********************************************************************
42 * @fn LCM_IsTwoByteCmd
43 *
44 * @brief Проверка: команда требует второго байта (параметра).
45 *
46 * @param cmd — код команды из первого байта WRITE-транзакции.
47 *
48 * @return 1 — диапазон 0x20…0x28; 0 — однобайтовая команда.
49 */
50static uint8_t LCM_IsTwoByteCmd(uint8_t cmd)
51{
52 return (cmd >= LCM_LAMP_ON && cmd <= LCM_SET_ADDR) ? 1U : 0U;
53}
54
55/*********************************************************************
56 * @fn LCM_IsReadCmd
57 *
58 * @brief Проверка: команда явного чтения регистра (для выбора текста лога).
59 *
60 * @param cmd — код команды.
61 *
62 * @return 1 для LCM_GET_ERROR / LCM_GET_ID, иначе 0.
63 *
64 * @details Влияет только на формулировку в журнале событий (EventLog).
65 * На выполнение не влияет: READ-фаза вооружается для ЛЮБОЙ
66 * команды (см. LCM_Module_ExecuteSingleByte/ExecuteTwoByte) —
67 * мастер может прочитать код ошибки после любой команды.
68 */
69static uint8_t LCM_IsReadCmd(uint8_t cmd)
70{
71 return (cmd == LCM_GET_ERROR || cmd == LCM_GET_ID) ? 1U : 0U;
72}
73
74/*********************************************************************
75 * @fn LCM_I2C_ResetRx
76 *
77 * @brief Сброс машины состояний приёма I2C в начальное состояние.
78 *
79 * @details Вызывается при STOP и при новой WRITE-транзакции (ADDR+W).
80 * После ExecuteSingleByte()/ExecuteTwoByte() состояние специально
81 * не сбрасывается сразу — см. LCM_I2C_OnRxData().
82 */
83static void LCM_I2C_ResetRx(void)
84{
86 s_i2c_cmd = 0U;
88}
89
90/*********************************************************************
91 * @fn LCM_I2C_OnRxData
92 *
93 * @brief Обработка принятого байта данных (slave-receiver).
94 *
95 * @details Вызывается из RXNE и перед STOP, если в DR остался байт.
96 *
97 * Важно: после ExecuteSingleByte()/ExecuteTwoByte() состояние FSM
98 * уже переведено в ARMED_READ (см. LCM_I2C_ArmRead в lcm_module.c —
99 * вооружает ответ кодом ошибки для ЛЮБОЙ команды). Здесь это
100 * состояние специально не сбрасывается: если мастер продолжит
101 * транзакцию RepSTART + Addr+R, придёт код ошибки; если сразу
102 * STOP — состояние сбросит сам обработчик STOPF ниже.
103 */
104static void LCM_I2C_OnRxData(uint8_t byte)
105{
107 s_i2c_cmd = byte;
108 if (LCM_IsTwoByteCmd(byte)) {
110 return;
111 }
112
114 if (LCM_IsReadCmd(byte)) {
116 } else {
117 /* Включая коды вне известных диапазонов (0x1E/0x1F, 0x29…0xFF) —
118 диспетчер сам зафиксирует LCM_ERR_UNKNOWN_CMD. */
119 EventLog_PushI2cCmd(byte, 0U, 0U);
120 }
122 } else if (s_i2c_rx_state == LCM_I2C_WAIT_PARAM) {
126 }
127}
128
129void LCM_I2C_ArmRead(uint8_t byte)
130{
131 s_i2c_tx_byte = byte;
132 s_i2c_tx_pending = 1U;
134}
135
136void LCM_I2C_Init(uint8_t addr)
137{
138 GPIO_InitTypeDef gpio = {0};
139 I2C_InitTypeDef i2c = {0};
140 NVIC_InitTypeDef nvic = {0};
141
142 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
143 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
144
145 gpio.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
146 gpio.GPIO_Mode = GPIO_Mode_AF_OD;
147 gpio.GPIO_Speed = GPIO_Speed_30MHz;
148 GPIO_Init(GPIOC, &gpio);
149
150 I2C_DeInit(I2C1);
151
152 i2c.I2C_ClockSpeed = LCM_I2C_SPEED_HZ;
153 i2c.I2C_Mode = I2C_Mode_I2C;
154 i2c.I2C_DutyCycle = I2C_DutyCycle_2;
155 /* OADDR1: 7-битный адрес сдвинут влево; бит R/W дополняет аппаратура */
156 i2c.I2C_OwnAddress1 = (uint16_t)((uint16_t)addr << 1);
157 i2c.I2C_Ack = I2C_Ack_Enable;
158 i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
159 I2C_Init(I2C1, &i2c);
160
161 I2C_AcknowledgeConfig(I2C1, ENABLE);
162
163 nvic.NVIC_IRQChannel = I2C1_EV_IRQn;
164 nvic.NVIC_IRQChannelPreemptionPriority = 1;
165 nvic.NVIC_IRQChannelSubPriority = 0;
166 nvic.NVIC_IRQChannelCmd = ENABLE;
167 NVIC_Init(&nvic);
168
169 nvic.NVIC_IRQChannel = I2C1_ER_IRQn;
170 nvic.NVIC_IRQChannelPreemptionPriority = 1;
171 nvic.NVIC_IRQChannelSubPriority = 1;
172 nvic.NVIC_IRQChannelCmd = ENABLE;
173 NVIC_Init(&nvic);
174
175 I2C_ITConfig(I2C1, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR, ENABLE);
176 I2C_Cmd(I2C1, ENABLE);
177
179}
180
181/*********************************************************************
182 * @fn I2C1_EV_IRQHandler
183 *
184 * @brief Обработчик событий I2C1 (режим slave).
185 *
186 * @details Протокол: КАЖДАЯ команда сразу вооружает READ-фазу кодом
187 * текущей ошибки (0x00 — нет ошибки), поэтому мастер может либо
188 * завершить транзакцию STOP-ом (результат не читается), либо
189 * продолжить RepSTART + Addr+R и получить код ошибки одной
190 * транзакцией — без отдельной команды GET_ERROR.
191 *
192 * Типичные сценарии:
193 *
194 * WRITE + READ (однобайтовая команда):
195 * START → Addr+W → cmd → RepSTART → Addr+R → [код ошибки] → STOP
196 *
197 * WRITE + READ (двухбайтовая):
198 * START → Addr+W → cmd → param → RepSTART → Addr+R → [код ошибки] → STOP
199 *
200 * WRITE + READ (явное чтение ошибки/ID):
201 * START → Addr+W → 0x1C/0x1D → RepSTART → Addr+R → [байт] → STOP
202 *
203 * Если мастеру не нужен результат — RepSTART можно не делать,
204 * сразу STOP; вооружённая READ-фаза просто отбрасывается.
205 *
206 * Обработка идёт в цикле, пока подняты флаги ADDR/RXNE/STOPF —
207 * это гарантирует, что байт, принятый непосредственно перед STOP,
208 * не будет потерян (RXNE может остаться необработанным к моменту STOPF).
209 *
210 * При ADDR+W (приём) FSM всегда сбрасывается — новая WRITE-фаза
211 * начинается с чистого состояния.
212 *
213 * @return none
214 */
215void I2C1_EV_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
216void I2C1_EV_IRQHandler(void)
217{
218 uint8_t work;
219
220 do {
221 work = 0U;
222
223 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR) != RESET) {
224 work = 1U;
225 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_TRA) != RESET) {
226 /* Slave-transmitter: мастер читает (фаза READ после RepSTART) */
227 (void)I2C1->STAR2;
228 if (s_i2c_tx_pending) {
229 I2C_SendData(I2C1, s_i2c_tx_byte);
230 s_i2c_tx_pending = 0U;
231 }
232 } else {
233 /* Slave-receiver: новая WRITE-фаза — всегда сброс FSM */
234 (void)I2C1->STAR2;
236 }
237 }
238
239 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) != RESET) {
240 work = 1U;
241 LCM_I2C_OnRxData((uint8_t)I2C_ReceiveData(I2C1));
242 }
243
244 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) != RESET) {
245 work = 1U;
246 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) != RESET) {
247 LCM_I2C_OnRxData((uint8_t)I2C_ReceiveData(I2C1));
248 }
249 /* STOP: очистка STOPF — чтение STAR1 + повторное включение PE */
250 (void)I2C1->STAR1;
251 I2C_Cmd(I2C1, ENABLE);
253 }
254 } while (work != 0U);
255}
256
257/*********************************************************************
258 * @fn I2C1_ER_IRQHandler
259 *
260 * @brief Обработчик ошибок I2C1.
261 *
262 * @details В режиме slave типичны:
263 * — AF: NACK от мастера (например, конец чтения одного байта);
264 * — BERR: нарушение протокола;
265 * — OVR: не успели прочитать DATAR до следующего байта;
266 * — ARLO: потеря арбитража (для slave редко).
267 * Без сброса флагов периферия I2C может перестать отвечать на шине.
268 *
269 * @return none
270 */
271void I2C1_ER_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
272void I2C1_ER_IRQHandler(void)
273{
274 /* AF (NACK) часто случается штатно при завершении чтения 1 байта мастером,
275 поэтому не перезаписываем последнюю ошибку на AF. */
276 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) != RESET) {
277 I2C_ClearFlag(I2C1, I2C_FLAG_AF);
278 }
279
280 /* Если одновременно подняты несколько флагов, считаем приоритет:
281 BERR > OVR > ARLO. */
282 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_BERR) != RESET) {
283 (void)I2C1->STAR1;
284 I2C_ClearFlag(I2C1, I2C_FLAG_BERR);
287 return;
288 }
289
290 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_OVR) != RESET) {
291 (void)I2C1->STAR1;
292 I2C_ClearFlag(I2C1, I2C_FLAG_OVR);
295 return;
296 }
297
298 if (I2C_GetFlagStatus(I2C1, I2C_FLAG_ARLO) != RESET) {
299 (void)I2C1->STAR1;
300 I2C_ClearFlag(I2C1, I2C_FLAG_ARLO);
303 return;
304 }
305}
void EventLog_PushI2cReadArm(uint8_t cmd)
Definition event_log.c:430
void EventLog_PushI2cCmd(uint8_t cmd, uint8_t param, uint8_t has_param)
Definition event_log.c:425
static void LCM_I2C_OnRxData(uint8_t byte)
Definition lcm_i2c.c:104
void I2C1_EV_IRQHandler(void)
Definition lcm_i2c.c:215
void LCM_I2C_Init(uint8_t addr)
Definition lcm_i2c.c:136
void LCM_I2C_ArmRead(uint8_t byte)
Definition lcm_i2c.c:129
static volatile LCM_I2C_RxState s_i2c_rx_state
Definition lcm_i2c.c:36
static uint8_t LCM_IsTwoByteCmd(uint8_t cmd)
Definition lcm_i2c.c:50
static uint8_t LCM_IsReadCmd(uint8_t cmd)
Definition lcm_i2c.c:69
static void LCM_I2C_ResetRx(void)
Definition lcm_i2c.c:83
static volatile uint8_t s_i2c_cmd
Definition lcm_i2c.c:37
LCM_I2C_RxState
Definition lcm_i2c.c:29
@ LCM_I2C_WAIT_PARAM
Definition lcm_i2c.c:31
@ LCM_I2C_ARMED_READ
Definition lcm_i2c.c:32
@ LCM_I2C_WAIT_CMD
Definition lcm_i2c.c:30
void I2C1_ER_IRQHandler(void)
Definition lcm_i2c.c:271
static volatile uint8_t s_i2c_tx_pending
Definition lcm_i2c.c:39
static volatile uint8_t s_i2c_tx_byte
Definition lcm_i2c.c:38
void LCM_Led_ActivityPulse(void)
Definition lcm_led.c:110
void LCM_Module_ExecuteSingleByte(uint8_t cmd)
Definition lcm_module.c:302
void LCM_Module_SetError(uint8_t err)
Definition lcm_module.c:64
void LCM_Module_ExecuteTwoByte(uint8_t cmd, uint8_t param)
Definition lcm_module.c:389
#define LCM_LAMP_ON
Definition lcm_module.h:156
#define LCM_GET_ID
Definition lcm_module.h:144
#define LCM_I2C_SPEED_HZ
Definition lcm_module.h:58
#define LCM_SET_ADDR
Definition lcm_module.h:166
#define LCM_ERR_I2C_OVR
Definition lcm_module.h:180
#define LCM_ERR_I2C_ARLO
Definition lcm_module.h:181
#define LCM_GET_ERROR
Definition lcm_module.h:143
#define LCM_ERR_I2C_BERR
Definition lcm_module.h:179