LCM — CH32V003 Light Control Module
Light Control Module, I2C slave
Loading...
Searching...
No Matches
lcm_module.c
Go to the documentation of this file.
1/********************************** (C) COPYRIGHT *******************************
2 * File Name : lcm_module.c
3 * Description : Ядро I2C-ведомого модуля освещения (LCM): регистр
4 * ошибки, конфигурация NVM, диспетчер команд,
5 * жизненный цикл модуля (Init/Poll).
6 *
7 * Архитектура:
8 * — lcm_relay.c — GPIO-драйвер 8 реле (push-pull, активный высокий уровень).
9 * — lcm_i2c.c — I2C1 slave: FSM приёма/передачи, ISR (ADDR/RXNE/STOPF).
10 * — lcm_module.c — команды, ошибки, NVM (user_data), Init/Poll.
11 *
12 * 4 группы освещения: каждая группа — битовая маска из 8 реле.
13 * Запись во flash (NVM) и перезагрузка MCU — в LCM_Module_Poll() из main().
14 *********************************************************************************/
15
16#include "lcm_module.h"
17#include "lcm_relay.h"
18#include "lcm_i2c.h"
19#include "lcm_led.h"
20#include "user_data.h"
21#include "event_log.h"
22#include "ch32v00x_conf.h"
23
24/* --- Состояние приложения --- */
25static uint8_t s_i2c_addr; /* текущий 7-битный адрес slave */
26static uint8_t s_relay_state; /* битовая маска реле (LSB = реле 0) */
27static uint8_t s_group_mask[LCM_GROUP_COUNT]; /* маски групп 0…3 */
28
29/* Регистр, отдаваемый по LCM_GET_ERROR */
30static volatile uint8_t s_error_code;
31
32/* Флаги отложенных действий (обрабатываются в LCM_Module_Poll) */
33static volatile uint8_t s_nvm_dirty; /* 1: нужен UserData_Commit() */
34static volatile uint8_t s_reset_requested; /* 1: после commit — NVIC_SystemReset */
35static volatile uint8_t s_factory_reset; /* 1: LCM_RESET — сброс NVM к заводскому */
36
37/*********************************************************************
38 * @fn LCM_SetSuccess
39 *
40 * @brief Отметить успешное выполнение последней команды: код ошибки
41 * = LCM_ERR_NONE.
42 */
43static void LCM_SetSuccess(void)
44{
46}
47
48/*********************************************************************
49 * @fn LCM_SetError
50 *
51 * @brief Зафиксировать ошибку обработки команды.
52 *
53 * @param err — код LCM_ERR_* для чтения через LCM_GET_ERROR.
54 */
55static void LCM_SetError(uint8_t err)
56{
57 s_error_code = err;
58 if (err != LCM_ERR_NONE) {
61 }
62}
63
64void LCM_Module_SetError(uint8_t err)
65{
66 LCM_SetError(err);
67}
68
69/*********************************************************************
70 * @fn LCM_UserDataErrorCode
71 *
72 * @brief Преобразовать UserData_Status в код LCM_ERR_USER_DATA_*.
73 */
75{
76 switch (st) {
85 default:
87 }
88}
89
90/*********************************************************************
91 * @fn LCM_SetUserDataError
92 *
93 * @brief Зафиксировать ошибку API user_data (кроме UserData_Commit).
94 */
96{
97 if (st != USER_DATA_OK) {
98 EventLog_PushNvm(1U, (uint8_t)st);
99 }
101}
102
103/*********************************************************************
104 * @fn LCM_SetUserDataCommitError
105 *
106 * @brief Зафиксировать ошибку UserData_Commit().
107 */
109{
110 EventLog_PushNvm(2U, (uint8_t)st);
111 if (st == USER_DATA_ERR_FLASH) {
113 } else {
115 }
116}
117
118/*********************************************************************
119 * @fn LCM_ApplyFactoryDefaults
120 *
121 * @brief Заводские значения конфигурации LCM в RAM.
122 */
124{
125 uint8_t i;
126
128 for (i = 0U; i < LCM_GROUP_COUNT; i++) {
129 s_group_mask[i] = 0U;
130 }
131}
132
133/*********************************************************************
134 * @fn LCM_UpdateRelayGpio
135 *
136 * @brief Применить маску реле к GPIO и зафиксировать ошибку при сбое.
137 *
138 * @return 1 — GPIO обновлён успешно; 0 — ошибка (код в s_error_code).
139 */
140static uint8_t LCM_UpdateRelayGpio(void)
141{
142 uint8_t err = LCM_Relay_Apply(s_relay_state);
143
144 if (err != LCM_ERR_NONE) {
145 LCM_SetError(err);
146 return 0U;
147 }
148 return 1U;
149}
150
151/*********************************************************************
152 * @fn LCM_NvmWriteConfig
153 *
154 * @brief Записать текущую конфигурацию в RAM-кэш user_data.
155 *
156 * @details Формирует блок LCM_NVM_SIZE байт (magic, адрес, 4 маски групп)
157 * и вызывает UserData_Write(). Устанавливает s_nvm_dirty — фактическая
158 * запись во flash произойдёт в LCM_Module_Poll() через UserData_Commit().
159 * Не вызывать Commit из прерывания I2C.
160 */
161static uint8_t LCM_NvmWriteConfig(void)
162{
163 uint8_t buf[LCM_NVM_SIZE];
164 uint8_t i;
166
169 for (i = 0U; i < LCM_GROUP_COUNT; i++) {
171 }
172 st = UserData_Write(0U, buf, LCM_NVM_SIZE);
173 if (st == USER_DATA_OK) {
174 s_nvm_dirty = 1U;
175 return 1U;
176 }
177
179 return 0U;
180}
181
182/*********************************************************************
183 * @fn LCM_LoadConfig
184 *
185 * @brief Загрузить конфигурацию LCM из NVM (RAM-кэш user_data).
186 *
187 * @details Если magic != LCM_NVM_MAGIC (первый запуск или повреждённые данные),
188 * используются заводские значения: адрес 0x7F, все группы = 0x00.
189 * Вызывается один раз при LCM_Module_Init() после UserData_Init().
190 */
191static void LCM_LoadConfig(void)
192{
193 uint8_t magic;
194 uint8_t i;
196
198 if (st != USER_DATA_OK) {
201 return;
202 }
203 if (magic != LCM_NVM_MAGIC) {
205 return;
206 }
207
209 if (st != USER_DATA_OK) {
212 return;
213 }
214
215 for (i = 0U; i < LCM_GROUP_COUNT; i++) {
217 if (st != USER_DATA_OK) {
220 return;
221 }
222 }
223}
224
225/*********************************************************************
226 * @fn LCM_ApplyGroupOn
227 *
228 * @brief Включить реле, входящие в выбранные группы.
229 *
230 * @param group_sel — битовая маска групп (бит g → группа g).
231 *
232 * @details Для каждой установленной бита group_sel объединяет (OR) маску
233 * s_group_mask[g] в общую маску реле, затем включает эти реле
234 * в s_relay_state. Реле вне выбранных групп не изменяются.
235 */
236static void LCM_ApplyGroupOn(uint8_t group_sel)
237{
238 uint8_t g;
239 uint8_t mask = 0U;
240
241 for (g = 0U; g < LCM_GROUP_COUNT; g++) {
242 if (group_sel & (uint8_t)(1U << g)) {
243 mask |= s_group_mask[g];
244 }
245 }
246 s_relay_state |= mask;
247 if (LCM_UpdateRelayGpio()) {
249 }
250}
251
252/*********************************************************************
253 * @fn LCM_ApplyGroupOff
254 *
255 * @brief Выключить реле, входящие в выбранные группы.
256 *
257 * @param group_sel — битовая маска групп.
258 *
259 * @details Аналогично LCM_ApplyGroupOn, но сбрасывает соответствующие биты
260 * в s_relay_state (AND с инверсией маски).
261 */
262static void LCM_ApplyGroupOff(uint8_t group_sel)
263{
264 uint8_t g;
265 uint8_t mask = 0U;
266
267 for (g = 0U; g < LCM_GROUP_COUNT; g++) {
268 if (group_sel & (uint8_t)(1U << g)) {
269 mask |= s_group_mask[g];
270 }
271 }
272 s_relay_state &= (uint8_t)~mask;
273 if (LCM_UpdateRelayGpio()) {
275 }
276}
277
278/*********************************************************************
279 * @fn LCM_Module_ExecuteSingleByte
280 *
281 * @brief Выполнить однобайтовую команду и вооружить ответ (READ-фазу).
282 *
283 * @param cmd — код команды 0x00…0x1D (либо неизвестный код).
284 *
285 * @details Вызывается из lcm_i2c.c при приёме первого байта транзакции.
286 * Диапазоны:
287 * 0x00–0x07 — включить реле (номер = cmd);
288 * 0x08–0x0F — выключить реле (номер = cmd − 0x08);
289 * 0x10/0x11 — все вкл/выкл;
290 * 0x12–0x19 — группы вкл/выкл;
291 * 0x1A — сброс (флаги factory_reset + reset_requested);
292 * 0x1C/0x1D — явное чтение регистра ошибки / ID.
293 * Неизвестные коды фиксируют ошибку `LCM_ERR_UNKNOWN_CMD`.
294 *
295 * Протокол: КАЖДАЯ команда (в т.ч. неизвестная) вооружает READ-фазу
296 * кодом текущей ошибки (LCM_I2C_ArmRead(s_error_code)), поэтому
297 * мастер получает результат сразу же — RepSTART + Addr+R следом за
298 * записью команды, без отдельной команды GET_ERROR. Исключения:
299 * GET_ERROR/GET_ID вооружают READ-фазу своим собственным байтом
300 * (код ошибки / ID) и выходят из функции сразу после этого.
301 */
303{
304 if (cmd <= LCM_LAMP_7_ON) {
305 s_relay_state |= (uint8_t)(1U << cmd);
306 if (LCM_UpdateRelayGpio()) {
308 }
309 } else if (cmd >= LCM_LAMP_0_OFF && cmd <= LCM_LAMP_7_OFF) {
310 s_relay_state &= (uint8_t)~(uint8_t)(1U << (cmd - LCM_LAMP_0_OFF));
311 if (LCM_UpdateRelayGpio()) {
313 }
314 } else {
315 switch (cmd) {
316 case LCM_LAMP_ALL_ON:
317 s_relay_state = 0xFFU;
318 if (LCM_UpdateRelayGpio()) {
320 }
321 break;
322
323 case LCM_LAMP_ALL_OFF:
324 s_relay_state = 0U;
325 if (LCM_UpdateRelayGpio()) {
327 }
328 break;
329
330 case LCM_GROUP_0_ON:
331 case LCM_GROUP_1_ON:
332 case LCM_GROUP_2_ON:
333 case LCM_GROUP_3_ON:
334 LCM_ApplyGroupOn((uint8_t)(1U << (cmd - LCM_GROUP_0_ON)));
335 break;
336
337 case LCM_GROUP_0_OFF:
338 case LCM_GROUP_1_OFF:
339 case LCM_GROUP_2_OFF:
340 case LCM_GROUP_3_OFF:
341 LCM_ApplyGroupOff((uint8_t)(1U << (cmd - LCM_GROUP_0_OFF)));
342 break;
343
344 case LCM_RESET:
345 /* Сброс NVM и reboot выполняются в Poll — здесь только флаги */
346 s_factory_reset = 1U;
349 break;
350
351 case LCM_GET_ERROR:
353 /* Ошибка считана мастером — гасим красный индикатор */
355 return;
356
357 case LCM_GET_ID:
358 LCM_I2C_ArmRead((uint8_t)LCM_ID);
359 return;
360
361 default:
362 /* Нераспознанная команда — фиксируем ошибку, код вернётся ниже */
364 break;
365 }
366 }
367
368 /* Единая точка выхода: код ошибки сразу доступен по RepSTART + Addr+R.
369 Явного GET_ERROR теперь может и не быть — красный гасим здесь же,
370 чтобы он не оставался гореть навсегда (ErrorOff учитывает минимальное
371 время показа LCM_LED_RED_MIN_MS, заданное в LCM_SetError/ErrorOn). */
374}
375
376/*********************************************************************
377 * @fn LCM_Module_ExecuteTwoByte
378 *
379 * @brief Выполнить двухбайтовую команду и вооружить ответ (READ-фазу).
380 *
381 * @param cmd — 0x20…0x28.
382 * @param param — маска реле, маска групп, маска состава группы или I2C-адрес.
383 *
384 * @details Вызывается из lcm_i2c.c после приёма второго байта транзакции.
385 * Как и для однобайтовых команд, по завершении всегда вооружается
386 * READ-фаза кодом текущей ошибки — мастер читает результат сразу
387 * через RepSTART + Addr+R, без отдельной команды GET_ERROR.
388 */
389void LCM_Module_ExecuteTwoByte(uint8_t cmd, uint8_t param)
390{
391 switch (cmd) {
392 case LCM_LAMP_ON:
393 s_relay_state |= param;
394 if (LCM_UpdateRelayGpio()) {
396 }
397 break;
398
399 case LCM_LAMP_OFF:
400 s_relay_state &= (uint8_t)~param;
401 if (LCM_UpdateRelayGpio()) {
403 }
404 break;
405
406 case LCM_GROUP_ON:
407 LCM_ApplyGroupOn(param);
408 break;
409
410 case LCM_GROUP_OFF:
411 LCM_ApplyGroupOff(param);
412 break;
413
414 case LCM_GROUP_0_SET:
415 case LCM_GROUP_1_SET:
416 case LCM_GROUP_2_SET:
417 case LCM_GROUP_3_SET:
418 s_group_mask[cmd - LCM_GROUP_0_SET] = param;
419 if (LCM_NvmWriteConfig()) {
421 }
422 break;
423
424 case LCM_SET_ADDR:
425 if (param > LCM_I2C_ADDR_FACTORY) {
427 break;
428 }
429 s_i2c_addr = param;
430 if (LCM_NvmWriteConfig()) {
431 s_reset_requested = 1U; /* новый адрес I2C — после Commit */
433 }
434 break;
435
436 default:
437 break;
438 }
439
440 /* Единая точка выхода: код ошибки сразу доступен по RepSTART + Addr+R.
441 Красный гасим здесь же (см. пояснение в ExecuteSingleByte) — иначе
442 без явного GET_ERROR индикатор ошибки остаётся гореть навсегда. */
445}
446
447/*********************************************************************
448 * @fn LCM_Module_ReportUserDataStatus
449 *
450 * @brief Публичная фиксация ошибки user_data (для main и др.).
451 */
453{
454 if (st != USER_DATA_OK) {
456 }
457}
458
460{
461 return s_i2c_addr;
462}
463
465{
466 return s_relay_state;
467}
468
469const uint8_t *LCM_Module_GetGroupMasks(void)
470{
471 return s_group_mask;
472}
473
474/*********************************************************************
475 * @fn LCM_Module_Init
476 *
477 * @brief Точка входа инициализации LCM (вызывается из main).
478 *
479 * @details Порядок: сброс переменных → загрузка NVM → GPIO (все реле выкл) →
480 * I2C slave. К моменту появления мастера на шине выводы реле
481 * уже в безопасном состоянии (логический 0).
482 */
484{
485 uint8_t err;
486
487 s_relay_state = 0U;
489 s_nvm_dirty = 0U;
491 s_factory_reset = 0U;
492
493 LCM_Led_Init();
495
496 err = LCM_Relay_Init();
497 if (err != LCM_ERR_NONE) {
498 LCM_SetError(err);
499 }
501 if (err != LCM_ERR_NONE) {
502 LCM_SetError(err);
503 }
505}
506
507/*********************************************************************
508 * @fn LCM_Module_Poll
509 *
510 * @brief Фоновая обработка: NVM commit, заводской сброс, перезагрузка.
511 *
512 * @details Вызывается в каждой итерации while(1) в main. Не блокирует ISR
513 * надолго, кроме периода UserData_Commit() (стирание/программирование
514 * option bytes, несколько миллисекунд).
515 *
516 * Порядок обработки:
517 * 1) factory_reset — записать заводские значения в кэш NVM;
518 * 2) nvm_dirty — UserData_Commit(); при ошибке — LCM_ERR_USER_DATA_*;
519 * 3) reset_requested — NVIC_SystemReset() (новый I2C-адрес и т.д.).
520 */
522{
524 uint8_t buf[LCM_NVM_SIZE];
525 uint8_t i;
526
528 LCM_Led_Poll();
529
530 if (s_factory_reset) {
533 for (i = 0U; i < LCM_GROUP_COUNT; i++) {
534 buf[LCM_NVM_OFF_GROUP_BASE + i] = 0U;
535 s_group_mask[i] = 0U;
536 }
538 st = UserData_Write(0U, buf, LCM_NVM_SIZE);
539 if (st == USER_DATA_OK) {
540 s_nvm_dirty = 1U;
541 } else {
542 EventLog_PushNvm(3U, (uint8_t)st);
544 s_nvm_dirty = 0U;
545 }
546 s_factory_reset = 0U;
547 }
548
549 if (s_nvm_dirty) {
550 st = UserData_Commit();
551 if (st != USER_DATA_OK) {
553 s_nvm_dirty = 0U;
554 return;
555 }
557 s_nvm_dirty = 0U;
558 }
559
560 if (s_reset_requested) {
564 NVIC_SystemReset();
565 }
566}
void EventLog_Process(void)
Definition event_log.c:356
void EventLog_PushNvm(uint8_t tag, uint8_t status)
Definition event_log.c:440
void EventLog_PushSystem(uint8_t tag)
Definition event_log.c:445
void EventLog_PushLcmError(uint8_t lcm_err_code)
Definition event_log.c:435
void LCM_I2C_Init(uint8_t addr)
Definition lcm_i2c.c:136
void LCM_I2C_ArmRead(uint8_t byte)
Definition lcm_i2c.c:129
void LCM_Led_Init(void)
Definition lcm_led.c:83
void LCM_Led_ErrorOff(void)
Definition lcm_led.c:100
void LCM_Led_ErrorOn(void)
Definition lcm_led.c:93
void LCM_Led_Poll(void)
Definition lcm_led.c:116
void LCM_Module_Poll(void)
Definition lcm_module.c:521
void LCM_Module_ExecuteSingleByte(uint8_t cmd)
Definition lcm_module.c:302
static void LCM_ApplyFactoryDefaults(void)
Definition lcm_module.c:123
static uint8_t s_group_mask[LCM_GROUP_COUNT]
Definition lcm_module.c:27
static uint8_t s_i2c_addr
Definition lcm_module.c:25
static uint8_t LCM_NvmWriteConfig(void)
Definition lcm_module.c:161
static void LCM_ApplyGroupOn(uint8_t group_sel)
Definition lcm_module.c:236
static void LCM_LoadConfig(void)
Definition lcm_module.c:191
static uint8_t LCM_UserDataErrorCode(UserData_Status st)
Definition lcm_module.c:74
static void LCM_SetUserDataCommitError(UserData_Status st)
Definition lcm_module.c:108
static void LCM_ApplyGroupOff(uint8_t group_sel)
Definition lcm_module.c:262
static volatile uint8_t s_factory_reset
Definition lcm_module.c:35
void LCM_Module_ReportUserDataStatus(UserData_Status st)
Definition lcm_module.c:452
static void LCM_SetUserDataError(UserData_Status st)
Definition lcm_module.c:95
static uint8_t LCM_UpdateRelayGpio(void)
Definition lcm_module.c:140
static volatile uint8_t s_nvm_dirty
Definition lcm_module.c:33
void LCM_Module_SetError(uint8_t err)
Definition lcm_module.c:64
uint8_t LCM_Module_GetI2cAddr(void)
Definition lcm_module.c:459
static void LCM_SetSuccess(void)
Definition lcm_module.c:43
uint8_t LCM_Module_GetRelayState(void)
Definition lcm_module.c:464
static uint8_t s_relay_state
Definition lcm_module.c:26
const uint8_t * LCM_Module_GetGroupMasks(void)
Definition lcm_module.c:469
void LCM_Module_ExecuteTwoByte(uint8_t cmd, uint8_t param)
Definition lcm_module.c:389
static void LCM_SetError(uint8_t err)
Definition lcm_module.c:55
static volatile uint8_t s_reset_requested
Definition lcm_module.c:34
void LCM_Module_Init(void)
Definition lcm_module.c:483
static volatile uint8_t s_error_code
Definition lcm_module.c:30
#define LCM_ERR_USER_DATA_RANGE
Definition lcm_module.h:186
#define LCM_LAMP_ON
Definition lcm_module.h:156
#define LCM_GROUP_3_ON
Definition lcm_module.h:130
#define LCM_GET_ID
Definition lcm_module.h:144
#define LCM_LAMP_7_ON
Definition lcm_module.h:111
#define LCM_ERR_BAD_PARAM
Definition lcm_module.h:176
#define LCM_GROUP_OFF
Definition lcm_module.h:159
#define LCM_GROUP_2_SET
Definition lcm_module.h:163
#define LCM_GROUP_1_ON
Definition lcm_module.h:128
#define LCM_GROUP_COUNT
Definition lcm_module.h:67
#define LCM_SET_ADDR
Definition lcm_module.h:166
#define LCM_LAMP_ALL_OFF
Definition lcm_module.h:124
#define LCM_GROUP_0_SET
Definition lcm_module.h:161
#define LCM_LAMP_7_OFF
Definition lcm_module.h:121
#define LCM_ERR_USER_DATA_BUSY
Definition lcm_module.h:188
#define LCM_ID
Definition lcm_module.h:65
#define LCM_GROUP_1_OFF
Definition lcm_module.h:134
#define LCM_GROUP_2_OFF
Definition lcm_module.h:135
#define LCM_GROUP_0_ON
Definition lcm_module.h:127
#define LCM_ERR_NONE
Definition lcm_module.h:174
#define LCM_NVM_SIZE
Definition lcm_module.h:89
#define LCM_GROUP_0_OFF
Definition lcm_module.h:133
#define LCM_LAMP_OFF
Definition lcm_module.h:157
#define LCM_ERR_USER_DATA_PARAM
Definition lcm_module.h:185
#define LCM_LAMP_ALL_ON
Definition lcm_module.h:123
#define LCM_ERR_UNKNOWN_CMD
Definition lcm_module.h:175
#define LCM_ERR_USER_DATA_COMMIT
Definition lcm_module.h:184
#define LCM_NVM_MAGIC
Definition lcm_module.h:90
#define LCM_LAMP_0_OFF
Definition lcm_module.h:114
#define LCM_GROUP_3_OFF
Definition lcm_module.h:136
#define LCM_ERR_USER_DATA_FLASH
Definition lcm_module.h:187
#define LCM_GROUP_3_SET
Definition lcm_module.h:164
#define LCM_NVM_OFF_GROUP_BASE
Definition lcm_module.h:88
#define LCM_GET_ERROR
Definition lcm_module.h:143
#define LCM_NVM_OFF_MAGIC
Definition lcm_module.h:86
#define LCM_GROUP_1_SET
Definition lcm_module.h:162
#define LCM_RESET
Definition lcm_module.h:138
#define LCM_GROUP_ON
Definition lcm_module.h:158
#define LCM_I2C_ADDR_FACTORY
Definition lcm_module.h:57
#define LCM_NVM_OFF_I2C_ADDR
Definition lcm_module.h:87
#define LCM_GROUP_2_ON
Definition lcm_module.h:129
uint8_t LCM_Relay_Init(void)
Definition lcm_relay.c:75
uint8_t LCM_Relay_Apply(uint8_t relay_state)
Definition lcm_relay.c:143
UserData_Status UserData_Write(uint8_t offset, const uint8_t *buf, uint8_t len)
Definition user_data.c:509
UserData_Status UserData_Commit(void)
Definition user_data.c:355
UserData_Status UserData_ReadByte(uint8_t offset, uint8_t *value)
Definition user_data.c:531
UserData_Status
Definition user_data.h:44
@ USER_DATA_ERR_PARAM
Definition user_data.h:46
@ USER_DATA_OK
Definition user_data.h:45
@ USER_DATA_ERR_BUSY
Definition user_data.h:49
@ USER_DATA_ERR_RANGE
Definition user_data.h:47
@ USER_DATA_ERR_FLASH
Definition user_data.h:48