LCM — CH32V003 Light Control Module
Light Control Module, I2C slave
Loading...
Searching...
No Matches
lcm_relay.c
Go to the documentation of this file.
1/********************************** (C) COPYRIGHT *******************************
2 * File Name : lcm_relay.c
3 * Description : Драйвер 8 реле LCM (GPIO push-pull, активный высокий уровень).
4 *
5 * Соответствие реле выводам контроллера (по ТЗ):
6 * | Реле | Вывод |
7 * |------|-------|
8 * | 0 | PC3 |
9 * | 1 | PC4 |
10 * | 2 | PC5 |
11 * | 3 | PC6 |
12 * | 4 | PC7 |
13 * | 5 | PD0 |
14 * | 6 | PD2 |
15 * | 7 | PD3 |
16 *********************************************************************************/
17
18#include "lcm_relay.h"
19#include "lcm_module.h"
20#include "ch32v00x_conf.h"
21
22/* Связка «порт + маска пина» для прямого управления реле через GPIO. */
23typedef struct {
24 GPIO_TypeDef *port;
25 uint16_t pin;
27
28/*
29 * Таблица GPIO для 8 реле. Индекс массива = номер реле (0…7), совпадает
30 * с номером бита в маске s_relay_state (lcm_module.c) и с кодом команды
31 * LCM_LAMP_N_ON/OFF (lcm_module.h). Реле физически разнесены по двум
32 * портам (5 на GPIOC, 3 на GPIOD) — таблица скрывает эту неоднородность
33 * от остального кода: снаружи реле — просто индекс 0…7.
34 * SetBits = реле включено (контакты замкнуты), ResetBits = выключено.
35 */
37 { GPIOC, GPIO_Pin_3 }, /* реле 0 — LCM_RELAY_1 */
38 { GPIOC, GPIO_Pin_4 }, /* реле 1 — LCM_RELAY_2 */
39 { GPIOC, GPIO_Pin_5 }, /* реле 2 — LCM_RELAY_3 */
40 { GPIOC, GPIO_Pin_6 }, /* реле 3 — LCM_RELAY_4 */
41 { GPIOC, GPIO_Pin_7 }, /* реле 4 — LCM_RELAY_5 */
42 { GPIOD, GPIO_Pin_0 }, /* реле 5 — LCM_RELAY_6 */
43 { GPIOD, GPIO_Pin_2 }, /* реле 6 — LCM_RELAY_7 */
44 { GPIOD, GPIO_Pin_3 } /* реле 7 — LCM_RELAY_8 */
45};
46
47/*********************************************************************
48 * @fn LCM_Relay_Init
49 *
50 * @brief Настройка выводов реле как цифровых выходов push-pull.
51 *
52 * @details Выполняется в два приёма — отдельно для GPIOC (5 пинов) и
53 * GPIOD (3 пина), т.к. это разные регистровые блоки периферии
54 * и разные биты RCC_APB2PeriphClockCmd. Перед конфигурированием
55 * каждого порта явно включается его тактирование (APB2) —
56 * без этого запись в регистры GPIO_Init не будет иметь эффекта.
57 *
58 * GPIO_ResetBits() сразу после GPIO_Init() гарантирует, что реле
59 * не «дёрнутся» в непредсказуемое состояние между конфигурацией
60 * выхода и первой командой от RC (push-pull выход при сбросе
61 * порта по умолчанию может быть в высокоимпедансном/входном
62 * режиме до явной настройки Mode_Out_PP).
63 *
64 * Проверочный цикл читает фактический уровень каждого пина через
65 * GPIO_ReadOutputDataBit() — это readback самого регистра вывода
66 * (ODR), а не физического состояния линии, но позволяет отловить
67 * программную ошибку (например, неверно собранную маску пинов)
68 * сразу при старте, до того как модуль начнёт отвечать на I2C.
69 *
70 * @return LCM_ERR_NONE — все 8 выходов в 0;
71 * LCM_ERR_GPIO_GET — ODR вернул значение, не являющееся Bit_SET/RESET
72 * (аппаратно невозможно, но проверяется из защитных соображений);
73 * LCM_ERR_GPIO_INIT — хотя бы один пин после сброса не в 0.
74 */
75uint8_t LCM_Relay_Init(void)
76{
77 GPIO_InitTypeDef gpio = {0};
78 uint8_t i;
79 uint8_t level;
80
81 /* --- Порт C: реле 0…4 (PC3…PC7) --- */
82 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
83
84 gpio.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
85 gpio.GPIO_Mode = GPIO_Mode_Out_PP;
86 gpio.GPIO_Speed = GPIO_Speed_30MHz;
87 GPIO_Init(GPIOC, &gpio);
88 /* Явный сброс сразу после Init — все реле выключены до первой команды */
89 GPIO_ResetBits(GPIOC, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
90
91 /* --- Порт D: реле 5…7 (PD0, PD2, PD3) --- */
92 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
93 gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_3;
94 gpio.GPIO_Mode = GPIO_Mode_Out_PP;
95 gpio.GPIO_Speed = GPIO_Speed_30MHz;
96 GPIO_Init(GPIOD, &gpio);
97 GPIO_ResetBits(GPIOD, GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_3);
98
99 /* Постфактум-проверка: убедиться, что каждый выход реально в 0.
100 Порядок обхода — по таблице s_relay_gpio, т.е. по логическому
101 номеру реле, а не по физическому порту/пину. */
102 for (i = 0U; i < LCM_RELAY_COUNT; i++) {
103 level = GPIO_ReadOutputDataBit(s_relay_gpio[i].port, s_relay_gpio[i].pin);
104 if (level != Bit_SET && level != Bit_RESET) {
105 return LCM_ERR_GPIO_GET;
106 }
107 if (level != Bit_RESET) {
108 return LCM_ERR_GPIO_INIT;
109 }
110 }
111
112 return LCM_ERR_NONE;
113}
114
115/*********************************************************************
116 * @fn LCM_Relay_Apply
117 *
118 * @brief Применить битовую маску реле к физическим выводам GPIO.
119 *
120 * @param relay_state — маска реле (LSB = реле 0, бит = 1 → включено).
121 *
122 * @details Для каждого из 8 реле по таблице s_relay_gpio выставляется
123 * нужный уровень (SetBits/ResetBits), затем СРАЗУ читается ODR
124 * того же пина — это единственный доступный на CH32V003 способ
125 * подтвердить, что запись в регистр действительно прошла
126 * (например, отличить настоящий сбой периферии от опечатки
127 * в номере пина на этапе разработки).
128 *
129 * Специальные коды ошибок LCM_ERR_GPIO_SET_ALL / CLEAR_ALL
130 * используются только тогда, когда операция затрагивала ВСЕ
131 * 8 реле одновременно (relay_state == 0xFF при включении или
132 * 0x00 при выключении, т.е. команды LCM_LAMP_ALL_ON/OFF) — это
133 * позволяет в журнале событий отличить «не включилось одно
134 * конкретное реле» от «не включились все реле сразу» (что чаще
135 * говорит об общей проблеме питания/GPIO, а не о конкретном
136 * канале). Функция прерывает обход при первой обнаруженной
137 * ошибке — оставшиеся реле в маске не проверяются и не трогаются
138 * в этом вызове.
139 *
140 * @return LCM_ERR_NONE — маска применена ко всем 8 реле;
141 * LCM_ERR_GPIO_GET/SET/SET_ALL/CLEAR/CLEAR_ALL — см. выше.
142 */
143uint8_t LCM_Relay_Apply(uint8_t relay_state)
144{
145 uint8_t i;
146 uint8_t bit;
147 uint8_t level;
148
149 for (i = 0U; i < LCM_RELAY_COUNT; i++) {
150 bit = (uint8_t)(1U << i);
151 if (relay_state & bit) {
152 /* Требуемое состояние реле i — включено */
153 GPIO_SetBits(s_relay_gpio[i].port, s_relay_gpio[i].pin);
154 level = GPIO_ReadOutputDataBit(s_relay_gpio[i].port, s_relay_gpio[i].pin);
155 if (level != Bit_SET && level != Bit_RESET) {
156 return LCM_ERR_GPIO_GET;
157 }
158 if (level != Bit_SET) {
159 /* SetBits не подействовал — различаем «все реле» и «одно реле» */
160 if (relay_state == 0xFFU) {
162 }
163 return LCM_ERR_GPIO_SET;
164 }
165 } else {
166 /* Требуемое состояние реле i — выключено */
167 GPIO_ResetBits(s_relay_gpio[i].port, s_relay_gpio[i].pin);
168 level = GPIO_ReadOutputDataBit(s_relay_gpio[i].port, s_relay_gpio[i].pin);
169 if (level != Bit_SET && level != Bit_RESET) {
170 return LCM_ERR_GPIO_GET;
171 }
172 if (level != Bit_RESET) {
173 if (relay_state == 0U) {
175 }
176 return LCM_ERR_GPIO_CLEAR;
177 }
178 }
179 }
180
181 return LCM_ERR_NONE;
182}
#define LCM_RELAY_COUNT
Definition lcm_module.h:66
#define LCM_ERR_GPIO_INIT
Definition lcm_module.h:191
#define LCM_ERR_GPIO_CLEAR_ALL
Definition lcm_module.h:196
#define LCM_ERR_NONE
Definition lcm_module.h:174
#define LCM_ERR_GPIO_SET
Definition lcm_module.h:192
#define LCM_ERR_GPIO_CLEAR
Definition lcm_module.h:194
#define LCM_ERR_GPIO_SET_ALL
Definition lcm_module.h:195
#define LCM_ERR_GPIO_GET
Definition lcm_module.h:193
static const LCM_RelayGpio s_relay_gpio[LCM_RELAY_COUNT]
Definition lcm_relay.c:36
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
uint16_t pin
Definition lcm_relay.c:25
GPIO_TypeDef * port
Definition lcm_relay.c:24