feat: add STM8 mesh relay firmware

This commit is contained in:
十七
2026-11-02 20:30:08 +08:00
commit d275a3523b
27 changed files with 2985 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
/**
* @file adc_current.c
* @brief ACS712 电流 ADC 采样和毫安换算实现。
*/
#include <stdint.h>
#include "adc_current.h"
#include "stm8s003_registers.h"
/** 最近一次完成换算的实际电流,单位 mA。 */
static uint16_t current_milliamperes;
/** 零点校准和当前电流数据是否已经准备完成。 */
static uint8_t current_ready;
/** 启动校准得到的 ADC 零点码值。 */
static uint16_t zero_code;
/** 启动校准期间用于累加 ADC 码值的 32 位累加器。 */
static uint32_t zero_accumulator;
/** 已经完成的零点校准样本数。 */
static uint8_t zero_count;
/** 正常运行期间用于累加偏差样本的累加器。 */
static uint32_t signal_accumulator;
/** 当前平均窗口已经积累的偏差样本数。 */
static uint8_t signal_count;
/**
* STM8S003F3P6 官方引脚复用定义:PC4 = AIN2,因此 ADC1 通道选择值为 2。
* 该对应关系来自 ST 官方 STM8S003F3 数据手册 DS7147 Rev 10 的表 6,
* PC4 项明确标注为“Analog input 2”。
*/
#define ACS712_ADC_CHANNEL ADC1_CHANNEL_PC4
/** ADC 转换结束标志。 */
#define ADC1_EOC_MASK 0x80u
/**
* 读取一次 ADC 原始值。
*
* ADC 采用 10 位右对齐结果,转换结束后先读 DRL 再读 DRH,避免在硬件
* 更新期间读取到不一致的高低字节。若本次转换尚未完成,返回 0xFFFF。
*/
static uint16_t Current_ReadAdc(void)
{
uint16_t result;
REG8(ADC1_CSR_ADDR) = (uint8_t)(ACS712_ADC_CHANNEL & 0x0Fu);
REG8(ADC1_CR1_ADDR) |= 0x01u;
if ((REG8(ADC1_CSR_ADDR) & ADC1_EOC_MASK) == 0u)
{
return 0xFFFFu;
}
result = REG8(ADC1_DRL_ADDR);
result |= (uint16_t)REG8(ADC1_DRH_ADDR) << 8u;
return (uint16_t)(result & 0x03FFu);
}
/** 初始化 PC4 ADC 服务状态;真实 ADC 寄存器配置在后续阶段接入。 */
void Current_Init(void)
{
current_milliamperes = 0u;
current_ready = 0u;
zero_code = 512u;
zero_accumulator = 0ul;
zero_count = 0u;
signal_accumulator = 0ul;
signal_count = 0u;
/* ADC1 单次转换,右对齐,选择 PC4 对应的 ADC 通道由后续封装核对。 */
REG8(ADC1_CR1_ADDR) = 0x01u;
REG8(ADC1_CR2_ADDR) = 0x08u;
REG8(ADC1_CR3_ADDR) = 0x00u;
}
/**
* 执行一次后台采样任务。
*
* ACS712 静态换算:PC4 前端为 1:1 分压,ADC 参考按 3.3V,ACS712-30A
* 灵敏度按典型 66mV/A 计算,因此理论比例约为 97.7mA/ADC count。
* 上电前 64 个样本用于零点校准;实际产品仍建议用实测零点和校准系数
* 替换默认值。当前接口输出的是整流后的平均电流,后续可再扩展为 AC RMS。
*/
void Current_Task(void)
{
uint16_t adc_code = Current_ReadAdc();
uint16_t difference;
uint32_t milliamperes;
if (adc_code == 0xFFFFu)
{
return;
}
if (zero_count < 64u)
{
zero_accumulator += adc_code;
++zero_count;
if (zero_count == 64u)
{
zero_code = (uint16_t)(zero_accumulator / 64ul);
current_ready = 1u;
}
return;
}
difference = (adc_code >= zero_code) ?
(uint16_t)(adc_code - zero_code) :
(uint16_t)(zero_code - adc_code);
signal_accumulator += difference;
++signal_count;
if (signal_count >= 16u)
{
difference = (uint16_t)(signal_accumulator / 16ul);
milliamperes = ((uint32_t)difference * 977ul + 5ul) / 10ul;
current_milliamperes = (milliamperes > 65535ul) ? 65535u :
(uint16_t)milliamperes;
signal_accumulator = 0ul;
signal_count = 0u;
}
}
/** 返回最近一次计算的实际电流,单位为 mA。 */
uint16_t Current_GetMilliAmpere(void)
{
return current_milliamperes;
}
/** 返回 RMS 和零点校准是否准备完成。 */
uint8_t Current_IsReady(void)
{
return current_ready;
}
+99
View File
@@ -0,0 +1,99 @@
/**
* @file board.c
* @brief 板级硬件抽象的第一阶段占位实现。
*
* 真实 STM8 GPIO 寄存器地址和封装引脚确认后,在本文件中补充寄存器操作,
* 业务层不应因此改变接口。当前实现只用于验证工程可以被 SDCC 编译。
*/
#include <stdint.h>
#include "board.h"
#include "stm8s003_registers.h"
/** 当前 PA1 继电器控制输出的逻辑状态。 */
static uint8_t relay_state;
/** 当前 PD3 LED3 输出的逻辑状态。 */
static uint8_t led3_state;
/** 初始化板级资源,并将继电器置于安全断开状态。 */
void Board_Init(void)
{
/* PA1:推挽输出,默认低电平,确保上电继电器断开。 */
REG8(GPIOA_DDR_ADDR) |= PA1_MASK;
REG8(GPIOA_CR1_ADDR) |= PA1_MASK;
REG8(GPIOA_CR2_ADDR) &= (uint8_t)~PA1_MASK;
REG8(GPIOA_ODR_ADDR) &= (uint8_t)~PA1_MASK;
/* PC3:浮空输入,读取 JDY-28M STAT;PC4:模拟输入,禁止数字输出。 */
REG8(GPIOC_DDR_ADDR) &= (uint8_t)~(PC3_MASK | PC4_MASK);
REG8(GPIOC_CR1_ADDR) &= (uint8_t)~(PC3_MASK | PC4_MASK);
REG8(GPIOC_CR2_ADDR) &= (uint8_t)~(PC3_MASK | PC4_MASK);
/* PC5/PWRC 和 PC6/RESET 上电默认高电平,避免模块误进入 AT 或复位。 */
REG8(GPIOC_ODR_ADDR) |= (uint8_t)(PC5_MASK | PC6_MASK);
REG8(GPIOC_DDR_ADDR) |= (uint8_t)(PC5_MASK | PC6_MASK);
REG8(GPIOC_CR1_ADDR) |= (uint8_t)(PC5_MASK | PC6_MASK);
REG8(GPIOC_CR2_ADDR) &= (uint8_t)~(PC5_MASK | PC6_MASK);
/* PD3:推挽输出,默认熄灭 LED3。 */
REG8(GPIOD_DDR_ADDR) |= PD3_MASK;
REG8(GPIOD_CR1_ADDR) |= PD3_MASK;
REG8(GPIOD_CR2_ADDR) &= (uint8_t)~PD3_MASK;
REG8(GPIOD_ODR_ADDR) &= (uint8_t)~PD3_MASK;
relay_state = 0u;
led3_state = 0u;
}
/** 设置继电器状态,后续接入 PA1 寄存器时保持高电平吸合约定。 */
void Board_SetRelay(uint8_t on)
{
relay_state = (on != 0u) ? 1u : 0u;
if (relay_state != 0u)
{
REG8(GPIOA_ODR_ADDR) |= PA1_MASK;
}
else
{
REG8(GPIOA_ODR_ADDR) &= (uint8_t)~PA1_MASK;
}
}
/** 返回当前 PA1 继电器控制输出的逻辑状态。 */
uint8_t Board_GetRelay(void)
{
return relay_state;
}
/** 读取 JDY-28M STAT 状态;当前阶段返回安全的未组网占位值。 */
uint8_t Board_ReadMeshJoined(void)
{
return ((REG8(GPIOC_IDR_ADDR) & PC3_MASK) != 0u) ? 1u : 0u;
}
/** 设置 LED3 状态,后续接入 PD3 寄存器时保持高电平点亮约定。 */
void Board_SetLed3(uint8_t on)
{
led3_state = (on != 0u) ? 1u : 0u;
if (led3_state != 0u)
{
REG8(GPIOD_ODR_ADDR) |= PD3_MASK;
}
else
{
REG8(GPIOD_ODR_ADDR) &= (uint8_t)~PD3_MASK;
}
}
/** 控制 PWRC;低电平使能 AT 指令窗口。 */
void Board_SetBluetoothAtWindow(uint8_t enabled)
{
if (enabled != 0u) REG8(GPIOC_ODR_ADDR) &= (uint8_t)~PC5_MASK;
else REG8(GPIOC_ODR_ADDR) |= PC5_MASK;
}
/** 控制 RESET;低电平复位,高电平正常工作。 */
void Board_SetBluetoothReset(uint8_t reset_active)
{
if (reset_active != 0u) REG8(GPIOC_ODR_ADDR) &= (uint8_t)~PC6_MASK;
else REG8(GPIOC_ODR_ADDR) |= PC6_MASK;
}
+96
View File
@@ -0,0 +1,96 @@
/**
* @file nonvolatile_config.c
* @brief STM8S003 Data EEPROM 阈值保存实现。
*
* EEPROM 布局:
* 0x4000:标志字节 0xA5;
* 0x4001:电流阈值低字节;
* 0x4002:电流阈值高字节;
* 0x4003:阈值低字节异或 0xFF;
* 0x4004:阈值高字节异或 0xFF。
*
* 每次写入后立即回读并校验,避免掉电或 EEPROM 写入失败时把损坏配置
* 当作有效阈值使用。实际量产前仍应依据芯片数据手册确认写周期和擦写寿命。
*/
#include <stdint.h>
#include "nonvolatile_config.h"
#include "stm8s003_registers.h"
#define EEPROM_BASE_ADDR 0x4000u
#define EEPROM_MAGIC_ADDR (EEPROM_BASE_ADDR + 0u)
#define EEPROM_THRESHOLD_LOW_ADDR (EEPROM_BASE_ADDR + 1u)
#define EEPROM_THRESHOLD_HIGH_ADDR (EEPROM_BASE_ADDR + 2u)
#define EEPROM_CHECK_LOW_ADDR (EEPROM_BASE_ADDR + 3u)
#define EEPROM_CHECK_HIGH_ADDR (EEPROM_BASE_ADDR + 4u)
#define EEPROM_MAGIC 0xA5u
#define FLASH_DUKR_ADDR 0x5064u
#define FLASH_IAPSR_ADDR 0x505Fu
#define FLASH_DUL_MASK 0x08u
/** 初始化接口;当前 EEPROM 无需额外运行时初始化。 */
void NonvolatileConfig_Init(void)
{
}
/** 等待 EEPROM 解锁状态,超时后返回失败。 */
static uint8_t NonvolatileConfig_WaitUnlocked(void)
{
uint16_t timeout = 60000u;
while (((REG8(FLASH_IAPSR_ADDR) & FLASH_DUL_MASK) == 0u) && (timeout != 0u))
{
--timeout;
}
return (timeout == 0u) ? 0u : 1u;
}
/** 解锁 Data EEPROM 编程权限。 */
static uint8_t NonvolatileConfig_Unlock(void)
{
REG8(FLASH_DUKR_ADDR) = 0xAEu;
REG8(FLASH_DUKR_ADDR) = 0x56u;
return NonvolatileConfig_WaitUnlocked();
}
/** 向指定 EEPROM 地址写入一个字节。 */
static uint8_t NonvolatileConfig_WriteByte(uint16_t address, uint8_t value)
{
REG8(address) = value;
return (REG8(address) == value) ? 1u : 0u;
}
/** 读取并校验 EEPROM 中的电流阈值。 */
uint16_t NonvolatileConfig_ReadThreshold(void)
{
uint8_t low;
uint8_t high;
if ((REG8(EEPROM_MAGIC_ADDR) != EEPROM_MAGIC) ||
(REG8(EEPROM_CHECK_LOW_ADDR) != (uint8_t)(REG8(EEPROM_THRESHOLD_LOW_ADDR) ^ 0xFFu)) ||
(REG8(EEPROM_CHECK_HIGH_ADDR) != (uint8_t)(REG8(EEPROM_THRESHOLD_HIGH_ADDR) ^ 0xFFu)))
{
return 0u;
}
low = REG8(EEPROM_THRESHOLD_LOW_ADDR);
high = REG8(EEPROM_THRESHOLD_HIGH_ADDR);
if ((low == 0u) && (high == 0u)) return 0u;
return (uint16_t)low | ((uint16_t)high << 8u);
}
/** 写入阈值、校验字节和有效标志,并回读确认。 */
uint8_t NonvolatileConfig_WriteThreshold(uint16_t threshold_ma)
{
uint8_t low;
uint8_t high;
if (threshold_ma == 0u) return 0u;
if (NonvolatileConfig_Unlock() == 0u) return 0u;
low = (uint8_t)threshold_ma;
high = (uint8_t)(threshold_ma >> 8u);
if ((NonvolatileConfig_WriteByte(EEPROM_THRESHOLD_LOW_ADDR, low) == 0u) ||
(NonvolatileConfig_WriteByte(EEPROM_THRESHOLD_HIGH_ADDR, high) == 0u) ||
(NonvolatileConfig_WriteByte(EEPROM_CHECK_LOW_ADDR, (uint8_t)(low ^ 0xFFu)) == 0u) ||
(NonvolatileConfig_WriteByte(EEPROM_CHECK_HIGH_ADDR, (uint8_t)(high ^ 0xFFu)) == 0u) ||
(NonvolatileConfig_WriteByte(EEPROM_MAGIC_ADDR, EEPROM_MAGIC) == 0u))
{
return 0u;
}
return (NonvolatileConfig_ReadThreshold() == threshold_ma) ? 1u : 0u;
}
+58
View File
@@ -0,0 +1,58 @@
/**
* @file system_tick.c
* @brief 使用 TIM4 产生 1ms 系统节拍。
*/
#include <stdint.h>
#include "system_tick.h"
#include "stm8s003_registers.h"
/** TIM4 中断递增的全局毫秒计数,溢出后按无符号差值继续工作。 */
static volatile uint32_t system_tick_ms;
/**
* 初始化 TIM4 产生 1ms 更新中断。
*
* 假设系统主频为 16MHz:TIM4 预分频设置为 128 后计数频率为 125kHz,
* 自动重装值设为 124,计数 125 个周期正好得到约 1ms。若工程后续修改
* 主时钟频率,必须同步重新计算 TIM4_PSCR 和 TIM4_ARR,否则所有 10 秒
* 故障计时和 LED 闪烁周期都会产生比例误差。
*/
void SystemTick_Init(void)
{
system_tick_ms = 0ul;
REG8(TIM4_CR1_ADDR) = 0x00u;
REG8(TIM4_IER_ADDR) = 0x01u;
REG8(TIM4_PSCR_ADDR) = 0x07u;
REG8(TIM4_ARR_ADDR) = 124u;
REG8(TIM4_CNTR_ADDR) = 0u;
REG8(TIM4_EGR_ADDR) = 0x01u;
REG8(TIM4_SR1_ADDR) = 0x00u;
REG8(TIM4_CR1_ADDR) = 0x01u;
}
/** 返回当前系统毫秒计数。 */
uint32_t SystemTick_NowMs(void)
{
return system_tick_ms;
}
/** 由真实 1 ms 定时器中断调用,递增系统时间基准。 */
void SystemTick_InterruptHandler(void)
{
++system_tick_ms;
}
/**
* TIM4 更新中断服务函数。
*
* STM8S003 的 TIM4 更新中断向量号为 23。本函数只做清除中断标志和递增
* 软件毫秒计数,禁止在中断中执行 UART、EEPROM 或 Mesh 发送等耗时操作。
*/
void SystemTick_TIM4_ISR(void) __interrupt(23)
{
if ((REG8(TIM4_SR1_ADDR) & 0x01u) != 0u)
{
REG8(TIM4_SR1_ADDR) = 0x00u;
SystemTick_InterruptHandler();
}
}
+91
View File
@@ -0,0 +1,91 @@
/**
* @file uart1.c
* @brief UART1 第一阶段占位实现。
*/
#include <stdint.h>
#include "uart1.h"
#include "stm8s003_registers.h"
/** UART1 接收环形缓冲区容量,必须使用 2 的幂便于快速回绕。 */
#define UART1_RX_BUFFER_SIZE 64u
/** UART1 接收环形缓存,保存尚未交给协议层处理的原始字节。 */
static volatile uint8_t rx_buffer[UART1_RX_BUFFER_SIZE];
/** 环形缓存写入位置,由 UART 接收任务推进。 */
static volatile uint8_t rx_write_index;
/** 环形缓存读取位置,由上层协议任务推进。 */
static volatile uint8_t rx_read_index;
/** 初始化 UART1;后续接入 JDY-28M 的 9600 baud 配置。 */
void Uart1_Init(void)
{
/* 先关闭 UART,再配置 9600 baud@16 MHz:BRR=0x0683。 */
REG8(UART1_CR2_ADDR) = 0x00u;
REG8(UART1_BRR2_ADDR) = 0x03u;
REG8(UART1_BRR1_ADDR) = 0x68u;
REG8(UART1_CR1_ADDR) = 0x00u;
REG8(UART1_CR3_ADDR) = 0x00u;
/* RE=1、TE=1、接收中断暂不打开,环形缓冲区接入后再开启。 */
REG8(UART1_CR2_ADDR) = 0x0Cu;
rx_write_index = 0u;
rx_read_index = 0u;
}
/**
* 轮询 UART1 接收状态并保存字节。
*
* 本阶段先采用轮询方式,避免在尚未建立中断向量表前引入中断链接问题。
* 后续如果业务帧速率提高,可将同一环形缓冲区写入逻辑迁移到 UART RX 中断。
*/
void Uart1_Task(void)
{
uint8_t next_index;
while ((REG8(UART1_SR_ADDR) & 0x20u) != 0u)
{
next_index = (uint8_t)((rx_write_index + 1u) & (UART1_RX_BUFFER_SIZE - 1u));
/* 缓冲区满时丢弃当前字节,避免覆盖尚未处理的完整帧。 */
if (next_index == rx_read_index)
{
(void)REG8(UART1_DR_ADDR);
continue;
}
rx_buffer[rx_write_index] = REG8(UART1_DR_ADDR);
rx_write_index = next_index;
}
}
/** 发送一个字节;当前阶段不连接真实 UART 寄存器。 */
void Uart1_WriteByte(uint8_t value)
{
while ((REG8(UART1_SR_ADDR) & 0x80u) == 0u)
{
}
REG8(UART1_DR_ADDR) = value;
}
/** 从接收环形缓冲区读取一个字节;当前阶段始终表示无数据。 */
uint8_t Uart1_ReadByte(uint8_t *value)
{
if ((value == (uint8_t *)0) || (rx_read_index == rx_write_index))
{
return 0u;
}
*value = rx_buffer[rx_read_index];
rx_read_index = (uint8_t)((rx_read_index + 1u) & (UART1_RX_BUFFER_SIZE - 1u));
return 1u;
}
/** 返回接收缓冲区当前字节数,供协议层判断是否需要继续读取。 */
uint8_t Uart1_Available(void)
{
return (uint8_t)((rx_write_index - rx_read_index) & (UART1_RX_BUFFER_SIZE - 1u));
}
/** UART1 接收中断入口占位。 */
void Uart1_InterruptHandler(void)
{
}
+24
View File
@@ -0,0 +1,24 @@
/**
* @file watchdog.c
* @brief STM8S003 独立看门狗实现。
*
* 看门狗只在主循环末尾刷新。任何任务死循环或长期阻塞超过约 5 秒,
* 都会触发 MCU 复位;复位后 main() 从头执行,继电器首先被置为断开。
*/
#include "watchdog.h"
#include "stm8s003_registers.h"
/** 启动看门狗并设置约 5 秒量级的预分频和重装值。 */
void Watchdog_Init(void)
{
REG8(IWDG_KR_ADDR) = 0xCCu;
REG8(IWDG_PR_ADDR) = 0x06u;
REG8(IWDG_RLR_ADDR) = 0xFFu;
REG8(IWDG_KR_ADDR) = 0xAAu;
}
/** 刷新看门狗,防止正常运行时复位。 */
void Watchdog_Refresh(void)
{
REG8(IWDG_KR_ADDR) = 0xAAu;
}
+63
View File
@@ -0,0 +1,63 @@
/**
* @file main.c
* @brief STM8S003F3P6TR 固件第一阶段主入口。
*
* 当前版本用于验证工程结构和各模块接口。后续实现真实硬件驱动后,
* 主循环仍应保持非阻塞,通过 1 ms 系统节拍驱动蓝牙初始化、LED 状态、
* ACS712 采样、继电器故障确认和业务协议处理。
*/
#include <stdint.h>
#include "board.h"
#include "system_tick.h"
#include "uart1.h"
#include "adc_current.h"
#include "business_protocol.h"
#include "mesh_frame.h"
#include "fault_manager.h"
#include "nonvolatile_config.h"
#include "bluetooth_manager.h"
#include "watchdog.h"
/**
* @brief 固件主函数。
*
* 上电先完成板级安全初始化,继电器默认断开,然后初始化通信、采样和
* 协议模块。主循环不使用长时间阻塞延时,保证后续可以同时处理 UART、
* 电流采样、Mesh 状态和故障恢复计时。
*
* @return 理论上不会返回。
*/
int main(void)
{
Board_Init();
SystemTick_Init();
__asm
rim
__endasm;
Uart1_Init();
Current_Init();
BusinessProtocol_Init();
MeshFrame_Init();
FaultManager_Init();
BluetoothManager_Init();
Watchdog_Init();
while (1)
{
Uart1_Task();
Current_Task();
if (BluetoothManager_IsReady() == 0u)
{
BluetoothManager_Task();
}
else
{
MeshFrame_Task();
BluetoothManager_Task();
}
FaultManager_Task();
Watchdog_Refresh();
}
}
+293
View File
@@ -0,0 +1,293 @@
/**
* @file bluetooth_manager.c
* @brief JDY-28M 上电初始化状态机。
*
* 启动顺序:
* 1. 查询 ROLE;
* 2. 若不是 ROLE2,发送 ROLE2 和 RESET;
* 3. 再次确认 ROLE;
* 4. 查询 MADDR,保存 2 字节短地址到 RAM;
* 5. 初始化完成后由 PC3 决定 LED3 常亮或 1 秒闪烁。
*
* 初始化过程不使用阻塞延时。UART 返回内容只识别关键字符和超时,具体
* AT 返回格式仍应在首次联调时用串口工具记录并校准。
*/
#include <stdint.h>
#include "app_config.h"
#include "bluetooth_manager.h"
#include "board.h"
#include "system_tick.h"
#include "uart1.h"
typedef enum
{
/** 启动流程:准备查询当前 ROLE。 */
BT_QUERY_ROLE = 0,
/** 等待 AT+ROLE 查询响应。 */
BT_WAIT_ROLE,
/** 已确认需要切换到 ROLE2,准备发送 AT+ROLE2。 */
BT_SET_ROLE,
/** 已发送 AT+RESET,等待模块软件复位完成。 */
BT_WAIT_RESET,
/** 软件复位重试失败后,保持 PC6 低电平执行硬件复位。 */
BT_HARD_RESET_LOW,
/** 已释放 PC6,等待模块硬件复位后的启动时间。 */
BT_HARD_RESET_WAIT,
/** 已进入 ROLE2,准备查询 Mesh 短地址。 */
BT_QUERY_ADDRESS,
/** 等待 AT+MADDR 返回完整短地址。 */
BT_WAIT_ADDRESS,
/** ROLE2、短地址和启动检查均成功,允许进入业务通信。 */
BT_READY,
/** 蓝牙初始化失败,只保留故障 LED 指示。 */
BT_FAULT
} BluetoothState;
/** AT 指令等待响应的最大时间,单位为毫秒。 */
#define BT_TIMEOUT_MS 1000ul
/** AT+RESET 后等待模块重新启动的时间,单位为毫秒。 */
#define BT_RESET_WAIT_MS 1500ul
/** 蓝牙初始化状态机的当前状态。 */
static BluetoothState bluetooth_state;
/** 当前状态开始时的系统毫秒计数,用于实现非阻塞超时。 */
static uint32_t state_start_ms;
/** AT+MADDR 查询得到的设备短地址,仅在本次上电期间保存。 */
static uint16_t short_address;
/** ROLE 响应中是否识别到 ROLE2 的标志,1 表示已识别。 */
static uint8_t response_has_role2;
/** 已解析出的短地址十六进制字符数量,完整地址应为 4 位。 */
static uint8_t response_address_digits;
/** 正在解析的短地址临时数值。 */
static uint16_t response_address_value;
/** 软件 AT+RESET 已尝试次数,达到 3 次后切换硬件复位。 */
static uint8_t software_reset_attempts;
/** LED3 当前闪烁节拍的起始时间。 */
static uint32_t led_start_ms;
/** LED3 当前逻辑状态,1 表示点亮,0 表示熄灭。 */
static uint8_t led_state;
/** 最近一次采样到的 PC3 电平,1 表示模块报告已组网。 */
static uint8_t mesh_candidate_state;
/** 已经连续确认的 PC3 组网状态。 */
static uint8_t mesh_confirmed_state;
/** 候选状态开始保持的时间。 */
static uint32_t mesh_candidate_start_ms;
/**
* 拉低 PWRC 后等待规定时间。
*
* 该等待只发生在启动阶段的 AT 指令发送前,时间为 10ms,不会出现在
* 正常 Mesh 数据收发路径中。系统节拍中断仍然运行,因此不会影响超时计时。
*/
static void BluetoothManager_WaitAtWindow(void)
{
uint32_t start = SystemTick_NowMs();
while ((SystemTick_NowMs() - start) < JDY28M_AT_ENTRY_WAIT_MS)
{
/* 等待 PWRC 生效期间不执行其他耗时业务。 */
}
}
/** 发送一条 AT 命令,统一补充 CRLF,避免不同调用点格式不一致。 */
static void BluetoothManager_SendCommand(const char *command)
{
Board_SetBluetoothAtWindow(1u);
BluetoothManager_WaitAtWindow();
while (*command != '\0') Uart1_WriteByte((uint8_t)*command++);
Uart1_WriteByte('\r');
Uart1_WriteByte('\n');
}
/** 进入新的初始化状态并记录状态开始时间。 */
static void BluetoothManager_Enter(BluetoothState state)
{
bluetooth_state = state;
state_start_ms = SystemTick_NowMs();
}
/** 初始化蓝牙状态机和 RAM 中的短地址。 */
void BluetoothManager_Init(void)
{
bluetooth_state = BT_QUERY_ROLE;
state_start_ms = 0ul;
short_address = 0u;
response_has_role2 = 0u;
response_address_digits = 0u;
response_address_value = 0u;
software_reset_attempts = 0u;
led_start_ms = 0ul;
led_state = 0u;
mesh_candidate_state = 0u;
mesh_confirmed_state = 0u;
mesh_candidate_start_ms = 0ul;
Board_SetLed3(0u);
}
/** 从 UART 返回流中提取 ROLE2 和 4 位十六进制短地址。 */
static void BluetoothManager_ConsumeResponse(void)
{
uint8_t value;
while (Uart1_ReadByte(&value) != 0u)
{
if ((value == '2') && (bluetooth_state == BT_WAIT_ROLE))
{
response_has_role2 = 1u;
}
if ((bluetooth_state == BT_WAIT_ADDRESS) && (response_address_digits < 4u))
{
uint8_t digit = 0xFFu;
if ((value >= '0') && (value <= '9')) digit = (uint8_t)(value - '0');
else if ((value >= 'A') && (value <= 'F')) digit = (uint8_t)(value - 'A' + 10u);
else if ((value >= 'a') && (value <= 'f')) digit = (uint8_t)(value - 'a' + 10u);
if (digit != 0xFFu)
{
response_address_value = (uint16_t)((response_address_value << 4u) | digit);
++response_address_digits;
}
}
}
}
/** 根据初始化故障、组网状态和计时驱动 LED3。 */
static void BluetoothManager_UpdateLed(void)
{
uint32_t now = SystemTick_NowMs();
uint32_t period;
uint8_t physical_state = Board_ReadMeshJoined();
/* PC3 瞬态变化不能直接反映到 LED。只有同一个电平连续保持至少
* MESH_STATUS_CONFIRM_MS,才更新确认状态,避免无线组网瞬态导致 LED
* 快速抖动。 */
if (physical_state != mesh_candidate_state)
{
mesh_candidate_state = physical_state;
mesh_candidate_start_ms = now;
}
else if ((mesh_confirmed_state != mesh_candidate_state) &&
((now - mesh_candidate_start_ms) >= MESH_STATUS_CONFIRM_MS))
{
mesh_confirmed_state = mesh_candidate_state;
}
if (bluetooth_state == BT_FAULT)
{
period = LED_INIT_FAULT_PERIOD_MS;
}
else if (bluetooth_state != BT_READY)
{
Board_SetLed3(0u);
return;
}
else if (mesh_confirmed_state == 0u)
{
period = LED_NOT_JOINED_PERIOD_MS;
}
else
{
Board_SetLed3(1u);
return;
}
if ((now - led_start_ms) >= period)
{
led_start_ms = now;
led_state = (uint8_t)!led_state;
Board_SetLed3(led_state);
}
}
/** 执行蓝牙初始化状态机、地址查询和 LED 状态显示。 */
void BluetoothManager_Task(void)
{
uint32_t now = SystemTick_NowMs();
BluetoothManager_ConsumeResponse();
switch (bluetooth_state)
{
case BT_QUERY_ROLE:
BluetoothManager_SendCommand("AT+ROLE");
response_has_role2 = 0u;
BluetoothManager_Enter(BT_WAIT_ROLE);
break;
case BT_WAIT_ROLE:
if (response_has_role2 != 0u)
{
Board_SetBluetoothAtWindow(0u);
BluetoothManager_SendCommand("AT+MADDR");
response_address_digits = 0u;
response_address_value = 0u;
BluetoothManager_Enter(BT_WAIT_ADDRESS);
}
else if ((now - state_start_ms) >= BT_TIMEOUT_MS)
{
Board_SetBluetoothAtWindow(0u);
if (software_reset_attempts >= 3u)
{
Board_SetBluetoothReset(1u);
BluetoothManager_Enter(BT_HARD_RESET_LOW);
}
else
{
BluetoothManager_SendCommand("AT+ROLE2");
BluetoothManager_Enter(BT_SET_ROLE);
}
}
break;
case BT_SET_ROLE:
if ((now - state_start_ms) >= BT_TIMEOUT_MS)
{
Board_SetBluetoothAtWindow(0u);
BluetoothManager_SendCommand("AT+RESET");
++software_reset_attempts;
BluetoothManager_Enter(BT_WAIT_RESET);
}
break;
case BT_WAIT_RESET:
if ((now - state_start_ms) >= BT_RESET_WAIT_MS)
{
Board_SetBluetoothAtWindow(0u);
BluetoothManager_Enter(BT_QUERY_ROLE);
}
break;
case BT_HARD_RESET_LOW:
if ((now - state_start_ms) >= JDY28M_RESET_PULSE_MS)
{
Board_SetBluetoothReset(0u);
BluetoothManager_Enter(BT_HARD_RESET_WAIT);
}
break;
case BT_HARD_RESET_WAIT:
if ((now - state_start_ms) >= JDY28M_RESET_STARTUP_WAIT_MS)
{
software_reset_attempts = 0u;
BluetoothManager_Enter(BT_QUERY_ROLE);
}
break;
case BT_WAIT_ADDRESS:
if (response_address_digits >= 4u)
{
short_address = response_address_value;
BluetoothManager_Enter(BT_READY);
}
else if ((now - state_start_ms) >= BT_TIMEOUT_MS)
{
Board_SetBluetoothAtWindow(0u);
BluetoothManager_Enter(BT_FAULT);
}
break;
case BT_READY:
break;
case BT_FAULT:
break;
default:
BluetoothManager_Enter(BT_FAULT);
break;
}
BluetoothManager_UpdateLed();
}
/** 返回蓝牙初始化完成状态。 */
uint8_t BluetoothManager_IsReady(void) { return (bluetooth_state == BT_READY) ? 1u : 0u; }
/** 返回蓝牙初始化故障状态。 */
uint8_t BluetoothManager_IsFault(void) { return (bluetooth_state == BT_FAULT) ? 1u : 0u; }
/** 返回本次上电从模块查询到的短地址。 */
uint16_t BluetoothManager_GetShortAddress(void) { return short_address; }
+348
View File
@@ -0,0 +1,348 @@
/**
* @file business_protocol.c
* @brief 业务协议第一阶段实现。
*/
#include <stdint.h>
#include "app_config.h"
#include "business_protocol.h"
#include "uart1.h"
#include "board.h"
#include "adc_current.h"
#include "mesh_frame.h"
#include "fault_manager.h"
#include "bluetooth_manager.h"
/** 业务帧解析状态。 */
typedef enum
{
/** 等待业务帧头第一个字节 0x55。 */
PARSER_WAIT_HEAD_0 = 0,
/** 已收到 0x55,等待帧头第二个字节 0xAA。 */
PARSER_WAIT_HEAD_1,
/** 等待命令字节。 */
PARSER_WAIT_CMD,
/** 等待 DATA 长度字节。 */
PARSER_WAIT_LENGTH,
/** 按 LEN 接收 DATA 区。 */
PARSER_WAIT_DATA,
/** 等待 CRC 低字节。 */
PARSER_WAIT_CRC_LOW,
/** 等待 CRC 高字节并完成校验。 */
PARSER_WAIT_CRC_HIGH
} BusinessParserState;
/** 解析器当前状态。 */
static BusinessParserState parser_state;
/** 当前正在接收的命令。 */
static uint8_t parser_command;
/** 当前正在接收的数据长度。 */
static uint8_t parser_length;
/** 当前已经接收的数据字节数。 */
static uint8_t parser_data_index;
/** 当前正在接收的数据区。 */
static uint8_t parser_data[BUSINESS_MAX_DATA_LENGTH];
/** 当前正在接收的 CRC。 */
static uint16_t parser_received_crc;
/** CRC 计算临时区,依次保存 CMD、LEN 和 DATA。 */
static uint8_t parser_crc_input[2u + BUSINESS_MAX_DATA_LENGTH];
/** 最近一帧合法业务帧的内容。 */
static uint8_t last_command;
/** 最近一帧合法业务帧的 DATA 长度。 */
static uint8_t last_length;
/** 最近一帧合法业务帧的 DATA 内容。 */
static uint8_t last_data[BUSINESS_MAX_DATA_LENGTH];
/** 发送一个只有 1 字节数据的业务应答。 */
static void BusinessProtocol_SendByteResponse(uint16_t address,
uint8_t command,
uint8_t value)
{
(void)MeshFrame_SendBusiness(address, command, &value, 1u);
}
/** 初始化业务协议状态机;完整解析器在第二阶段实现。 */
void BusinessProtocol_Init(void)
{
parser_state = PARSER_WAIT_HEAD_0;
parser_command = 0u;
parser_length = 0u;
parser_data_index = 0u;
parser_received_crc = 0u;
last_command = 0u;
last_length = 0u;
}
/** 处理 UART 数据和待发送帧;当前阶段仅保留任务入口。 */
void BusinessProtocol_Task(void)
{
uint8_t value;
while (Uart1_ReadByte(&value) != 0u)
{
/* 当前阶段仅消费 UART 字节,正式接入时由 Mesh 外层解析器调用
* BusinessProtocol_InputByte(),避免把外层地址和帧尾误当业务数据。 */
(void)BusinessProtocol_InputByte(value);
}
}
/**
* 向业务帧状态机输入一个字节。
*
* 帧格式为 `55 AA CMD LEN DATA CRC16`。CRC 计算范围为 CMD、LEN 和
* DATA;线上 CRC 低字节在前。CRC 错误会恢复到等待帧头状态,不执行
* 任何控制动作。
*/
uint8_t BusinessProtocol_InputByte(uint8_t value)
{
uint16_t expected_crc;
uint8_t index;
switch (parser_state)
{
case PARSER_WAIT_HEAD_0:
if (value == BUSINESS_FRAME_HEAD_0)
{
parser_state = PARSER_WAIT_HEAD_1;
}
break;
case PARSER_WAIT_HEAD_1:
parser_state = (value == BUSINESS_FRAME_HEAD_1) ?
PARSER_WAIT_CMD : PARSER_WAIT_HEAD_0;
break;
case PARSER_WAIT_CMD:
parser_command = value;
parser_state = PARSER_WAIT_LENGTH;
break;
case PARSER_WAIT_LENGTH:
parser_length = value;
parser_data_index = 0u;
if (parser_length > BUSINESS_MAX_DATA_LENGTH)
{
parser_state = PARSER_WAIT_HEAD_0;
}
else if (parser_length == 0u)
{
parser_state = PARSER_WAIT_CRC_LOW;
}
else
{
parser_state = PARSER_WAIT_DATA;
}
break;
case PARSER_WAIT_DATA:
parser_data[parser_data_index++] = value;
if (parser_data_index >= parser_length)
{
parser_state = PARSER_WAIT_CRC_LOW;
}
break;
case PARSER_WAIT_CRC_LOW:
parser_received_crc = value;
parser_state = PARSER_WAIT_CRC_HIGH;
break;
case PARSER_WAIT_CRC_HIGH:
parser_received_crc |= (uint16_t)value << 8u;
parser_crc_input[0] = parser_command;
parser_crc_input[1] = parser_length;
for (index = 0u; index < parser_length; ++index)
{
parser_crc_input[2u + index] = parser_data[index];
}
expected_crc = BusinessProtocol_Crc16Modbus(parser_crc_input,
(uint16_t)(2u + parser_length));
parser_state = PARSER_WAIT_HEAD_0;
if (expected_crc != parser_received_crc)
{
return 0u;
}
last_command = parser_command;
last_length = parser_length;
for (index = 0u; index < last_length; ++index)
{
last_data[index] = parser_data[index];
}
return 1u;
default:
parser_state = PARSER_WAIT_HEAD_0;
break;
}
return 0u;
}
/** 读取最近一帧合法业务帧的命令字。 */
uint8_t BusinessProtocol_GetCommand(void)
{
return last_command;
}
/** 读取最近一帧合法业务帧的数据长度。 */
uint8_t BusinessProtocol_GetDataLength(void)
{
return last_length;
}
/** 读取最近一帧合法业务帧的数据区字节。 */
uint8_t BusinessProtocol_GetDataByte(uint8_t index, uint8_t *value)
{
if ((value == (uint8_t *)0) || (index >= last_length))
{
return 0u;
}
*value = last_data[index];
return 1u;
}
/**
* 执行当前合法业务帧。
*
* 当前先接入三项无副作用或低风险基础命令:继电器控制、继电器状态查询
* 和实际电流查询。控制命令只有 DATA=00/01 才会改变 PA1;其他参数只返
* 回错误应答,不会误动作。应答目标使用接收帧中的源短地址。
*/
void BusinessProtocol_HandleFrame(uint16_t source_address)
{
uint8_t value;
uint8_t current_data[2u];
uint16_t current;
uint16_t threshold;
switch (last_command)
{
case CMD_RELAY_CONTROL:
if ((last_length != 1u) || ((last_data[0] != 0u) && (last_data[0] != 1u)))
{
value = 0x03u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
Board_SetRelay(last_data[0]);
BusinessProtocol_SendByteResponse(source_address,
CMD_RELAY_CONTROL | CMD_RESPONSE_FLAG,
Board_GetRelay());
break;
case CMD_RELAY_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
BusinessProtocol_SendByteResponse(source_address,
CMD_RELAY_QUERY | CMD_RESPONSE_FLAG,
Board_GetRelay());
break;
case CMD_CURRENT_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
current = Current_GetMilliAmpere();
current_data[0] = (uint8_t)current;
current_data[1] = (uint8_t)(current >> 8u);
(void)MeshFrame_SendBusiness(source_address,
CMD_CURRENT_QUERY | CMD_RESPONSE_FLAG,
current_data, 2u);
break;
case (CMD_FAULT_REPORT | CMD_RESPONSE_FLAG):
if (last_length == 1u)
{
FaultManager_OnReportAck(last_data[0]);
}
break;
case CMD_THRESHOLD_CONFIG:
if (last_length != 2u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
threshold = (uint16_t)last_data[0] | ((uint16_t)last_data[1] << 8u);
if ((threshold == 0u) || (threshold > 30000u))
{
value = 0x03u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
if (FaultManager_SetThreshold(threshold) == 0u)
{
value = 0x05u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
(void)MeshFrame_SendBusiness(source_address,
CMD_THRESHOLD_CONFIG | CMD_RESPONSE_FLAG,
last_data, 2u);
break;
case CMD_ADDRESS_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
current_data[0] = (uint8_t)(BluetoothManager_GetShortAddress() >> 8u);
current_data[1] = (uint8_t)BluetoothManager_GetShortAddress();
(void)MeshFrame_SendBusiness(source_address,
CMD_ADDRESS_QUERY | CMD_RESPONSE_FLAG,
current_data, 2u);
break;
default:
value = 0x06u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
}
/**
* 计算 CRC-16/MODBUS。
*
* @param data 待计算数据指针,不能为 NULL。
* @param length 数据长度。
* @return CRC 结果;发送时低字节在前。
*/
uint16_t BusinessProtocol_Crc16Modbus(const uint8_t *data, uint16_t length)
{
uint16_t crc = 0xFFFFu;
uint16_t index;
uint8_t bit;
if (data == (const uint8_t *)0)
{
return 0u;
}
for (index = 0u; index < length; ++index)
{
crc ^= data[index];
for (bit = 0u; bit < 8u; ++bit)
{
if ((crc & 0x0001u) != 0u)
{
crc = (uint16_t)((crc >> 1u) ^ 0xA001u);
}
else
{
crc >>= 1u;
}
}
}
return crc;
}
+166
View File
@@ -0,0 +1,166 @@
/**
* @file fault_manager.c
* @brief 继电器电流一致性故障检测实现。
*
* 故障码约定:
* 0x00:继电器指令为吸合,但连续确认时间内没有有效电流;
* 0x01:继电器指令为断开,但连续确认时间内仍有有效电流;
* 0x04:电流故障恢复;
* 0x06:ACS712 采样故障恢复(采样异常检测尚待硬件量程和异常边界确认)。
*
* 本实现使用 SystemTick_NowMs() 做非阻塞计时,不在故障判断中使用 delay,
* 这样 UART 接收、Mesh 解析和 ADC 采样仍然能够持续运行。
*/
#include <stdint.h>
#include "app_config.h"
#include "board.h"
#include "adc_current.h"
#include "business_protocol.h"
#include "fault_manager.h"
#include "mesh_frame.h"
#include "nonvolatile_config.h"
#include "system_tick.h"
#define FAULT_RELAY_ON_NO_CURRENT 0x00u
#define FAULT_RELAY_OFF_HAS_CURRENT 0x01u
#define FAULT_CURRENT_RECOVERED 0x04u
/** 故障上报采用广播,使主机能够接收设备主动上报。 */
#define FAULT_REPORT_TARGET 0xFFFFu
/** 当前有效电流阈值,默认 100mA,后续由 ROM 配置覆盖。 */
static uint16_t fault_threshold_ma;
/** 当前正在连续确认的异常类型,0 表示没有异常确认。 */
static uint8_t pending_fault_code;
/** 连续异常确认开始时间。 */
static uint32_t pending_fault_start_ms;
/** 当前已经确认并处于活动状态的电流故障码。 */
static uint8_t active_fault_code;
/** 上一次故障/恢复报告发送时间。 */
static uint32_t last_report_ms;
/** 是否存在等待主机 ACK 的故障报告。 */
static uint8_t report_waiting_ack;
/** 初始化所有故障状态,避免上电瞬间使用未初始化的计时数据。 */
void FaultManager_Init(void)
{
fault_threshold_ma = NonvolatileConfig_ReadThreshold();
if (fault_threshold_ma == 0u)
{
fault_threshold_ma = DEFAULT_CURRENT_THRESHOLD_MA;
}
pending_fault_code = 0xFFu;
pending_fault_start_ms = 0ul;
active_fault_code = 0xFFu;
last_report_ms = 0ul;
report_waiting_ack = 0u;
}
/** 发送一字节故障码的主动上报帧。 */
static void FaultManager_SendReport(uint8_t code)
{
(void)MeshFrame_SendBusiness(FAULT_REPORT_TARGET, CMD_FAULT_REPORT, &code, 1u);
last_report_ms = SystemTick_NowMs();
report_waiting_ack = 1u;
}
/**
* 周期性处理故障状态。
*
* 只有当电流状态连续保持异常满 10 秒,才把待确认异常升级为活动故障。
* 一旦电流恢复并连续保持正常满 10 秒,则发送 0x04 恢复报告。报告未获
* 得匹配的 0x84 ACK 时,严格每 10 秒重发,不重复改变继电器输出。
*/
void FaultManager_Task(void)
{
uint32_t now = SystemTick_NowMs();
uint8_t relay_on = Board_GetRelay();
uint8_t abnormal = 0u;
uint8_t expected_fault = 0xFFu;
uint16_t current_ma = Current_GetMilliAmpere();
if ((relay_on != 0u) && (current_ma < fault_threshold_ma))
{
abnormal = 1u;
expected_fault = FAULT_RELAY_ON_NO_CURRENT;
}
else if ((relay_on == 0u) && (current_ma >= fault_threshold_ma))
{
abnormal = 1u;
expected_fault = FAULT_RELAY_OFF_HAS_CURRENT;
}
if (abnormal != 0u)
{
if (pending_fault_code != expected_fault)
{
pending_fault_code = expected_fault;
pending_fault_start_ms = now;
}
else if ((active_fault_code != expected_fault) &&
((now - pending_fault_start_ms) >= CURRENT_FAULT_CONFIRM_MS))
{
active_fault_code = expected_fault;
FaultManager_SendReport(active_fault_code);
}
}
else if (active_fault_code != 0xFFu)
{
if (pending_fault_code != 0xFEu)
{
pending_fault_code = 0xFEu;
pending_fault_start_ms = now;
}
else if ((now - pending_fault_start_ms) >= CURRENT_FAULT_CONFIRM_MS)
{
active_fault_code = 0xFFu;
FaultManager_SendReport(FAULT_CURRENT_RECOVERED);
}
}
else
{
pending_fault_code = 0xFFu;
}
if ((report_waiting_ack != 0u) &&
((now - last_report_ms) >= CURRENT_FAULT_CONFIRM_MS))
{
if (active_fault_code != 0xFFu)
{
FaultManager_SendReport(active_fault_code);
}
else
{
FaultManager_SendReport(FAULT_CURRENT_RECOVERED);
}
}
}
/** 处理匹配故障码 ACK,停止当前报告的 10 秒重发。 */
void FaultManager_OnReportAck(uint8_t fault_code)
{
if ((report_waiting_ack != 0u) &&
((fault_code == active_fault_code) ||
((fault_code == FAULT_CURRENT_RECOVERED) && (active_fault_code == 0xFFu))))
{
report_waiting_ack = 0u;
}
}
/** 设置电流阈值;参数为 0 时拒绝修改,避免关闭故障检测。 */
uint8_t FaultManager_SetThreshold(uint16_t threshold_ma)
{
if ((threshold_ma != 0u) &&
(NonvolatileConfig_WriteThreshold(threshold_ma) != 0u))
{
fault_threshold_ma = threshold_ma;
return 1u;
}
return 0u;
}
/** 返回当前电流阈值。 */
uint16_t FaultManager_GetThreshold(void)
{
return fault_threshold_ma;
}
+219
View File
@@ -0,0 +1,219 @@
/**
* @file mesh_frame.c
* @brief JDY-28M Mesh 外层帧实现。
*
* 发送格式:41 54 2B AA A1 | 目标地址高低字节 | A0 | 业务帧 | 0D 0A。
* 接收格式:F1 DD 09 00 0A | 源地址高低字节 | 业务帧 | 0D 0A。
*/
#include <stdint.h>
#include "business_protocol.h"
#include "mesh_frame.h"
#include "uart1.h"
#define RX_PREFIX_0 0xF1u
#define RX_PREFIX_1 0xDDu
#define RX_PREFIX_2 0x09u
#define RX_PREFIX_3 0x00u
#define RX_PREFIX_4 0x0Au
#define TX_PREFIX_0 0x41u
#define TX_PREFIX_1 0x54u
#define TX_PREFIX_2 0x2Bu
#define TX_PREFIX_3 0xAAu
#define TX_PREFIX_4 0xA1u
#define MESH_DATA_FLAG 0xA0u
#define MESH_TAIL_0 0x0Du
#define MESH_TAIL_1 0x0Au
/** 外层解析状态。 */
typedef enum
{
/** 等待接收头 F1。 */
MESH_WAIT_PREFIX_0 = 0,
/** 已收到 F1,等待 DD。 */
MESH_WAIT_PREFIX_1,
/** 等待固定头第三字节 09。 */
MESH_WAIT_PREFIX_2,
/** 等待固定头第四字节 00。 */
MESH_WAIT_PREFIX_3,
/** 等待固定头第五字节 0A。 */
MESH_WAIT_PREFIX_4,
/** 接收源地址高字节。 */
MESH_READ_SOURCE_HIGH,
/** 接收源地址低字节。 */
MESH_READ_SOURCE_LOW,
/** 接收内层业务帧。 */
MESH_READ_BUSINESS,
/** 等待外层帧尾 0D。 */
MESH_WAIT_TAIL_0,
/** 等待外层帧尾 0A。 */
MESH_WAIT_TAIL_1
} MeshParserState;
/** JDY-28M 外层接收状态机当前状态。 */
static MeshParserState mesh_state;
/** 最近一帧合法外层数据携带的源短地址。 */
static uint16_t last_source_address;
/** 当前正在接收的业务帧缓冲区。 */
static uint8_t business_buffer[BUSINESS_MAX_DATA_LENGTH + 6u];
/** 当前已经接收的业务帧字节数。 */
static uint8_t business_length;
/** 根据业务 LEN 计算出的完整业务帧长度。 */
static uint8_t business_expected_length;
/** 将解析器恢复到等待 F1 的状态。 */
static void MeshFrame_ResetParser(void)
{
mesh_state = MESH_WAIT_PREFIX_0;
business_length = 0u;
business_expected_length = 0u;
}
/** 初始化外层帧解析器。 */
void MeshFrame_Init(void)
{
last_source_address = 0u;
MeshFrame_ResetParser();
}
/**
* 处理一个外层 UART 字节。
*
* 业务帧长度由 LEN 字段确定,因此收齐业务帧后只接受紧随其后的 0D 0A;
* 只有内层 CRC 通过时,才把数据提交给业务协议层。
*/
static void MeshFrame_InputByte(uint8_t value)
{
uint8_t expected_length;
switch (mesh_state)
{
case MESH_WAIT_PREFIX_0:
if (value == RX_PREFIX_0) mesh_state = MESH_WAIT_PREFIX_1;
break;
case MESH_WAIT_PREFIX_1:
mesh_state = (value == RX_PREFIX_1) ? MESH_WAIT_PREFIX_2 : MESH_WAIT_PREFIX_0;
break;
case MESH_WAIT_PREFIX_2:
mesh_state = (value == RX_PREFIX_2) ? MESH_WAIT_PREFIX_3 : MESH_WAIT_PREFIX_0;
break;
case MESH_WAIT_PREFIX_3:
mesh_state = (value == RX_PREFIX_3) ? MESH_WAIT_PREFIX_4 : MESH_WAIT_PREFIX_0;
break;
case MESH_WAIT_PREFIX_4:
mesh_state = (value == RX_PREFIX_4) ? MESH_READ_SOURCE_HIGH : MESH_WAIT_PREFIX_0;
break;
case MESH_READ_SOURCE_HIGH:
last_source_address = (uint16_t)value << 8u;
mesh_state = MESH_READ_SOURCE_LOW;
break;
case MESH_READ_SOURCE_LOW:
last_source_address |= value;
business_length = 0u;
mesh_state = MESH_READ_BUSINESS;
break;
case MESH_READ_BUSINESS:
if (business_length >= (uint8_t)sizeof(business_buffer))
{
MeshFrame_ResetParser();
break;
}
business_buffer[business_length++] = value;
if (business_length >= 4u)
{
if ((business_buffer[0] != 0x55u) || (business_buffer[1] != 0xAAu))
{
MeshFrame_ResetParser();
break;
}
expected_length = (uint8_t)(business_buffer[3] + 6u);
business_expected_length = expected_length;
if ((business_buffer[3] > BUSINESS_MAX_DATA_LENGTH) ||
(business_length > expected_length))
{
MeshFrame_ResetParser();
break;
}
if (business_length == expected_length)
{
/* 先确认外层 0D 0A,再提交内层业务帧。 */
mesh_state = MESH_WAIT_TAIL_0;
}
}
break;
case MESH_WAIT_TAIL_0:
mesh_state = (value == MESH_TAIL_0) ? MESH_WAIT_TAIL_1 : MESH_WAIT_PREFIX_0;
break;
case MESH_WAIT_TAIL_1:
if (value == MESH_TAIL_1)
{
uint8_t index;
uint8_t frame_ready = 0u;
for (index = 0u; index < business_expected_length; ++index)
{
if (BusinessProtocol_InputByte(business_buffer[index]) != 0u)
{
frame_ready = 1u;
}
}
if (frame_ready != 0u)
{
BusinessProtocol_HandleFrame(last_source_address);
}
}
MeshFrame_ResetParser();
break;
default:
MeshFrame_ResetParser();
break;
}
}
/** 从 UART 接收并解析 JDY-28M 外层帧。 */
void MeshFrame_Task(void)
{
uint8_t value;
while (Uart1_ReadByte(&value) != 0u)
{
MeshFrame_InputByte(value);
}
}
/** 返回最近一帧合法外层数据的源地址。 */
uint16_t MeshFrame_GetLastSourceAddress(void)
{
return last_source_address;
}
/** 封装并发送一帧业务数据。 */
uint8_t MeshFrame_SendBusiness(uint16_t target_address,
uint8_t command,
const uint8_t *data,
uint8_t length)
{
uint8_t crc_input[2u + BUSINESS_MAX_DATA_LENGTH];
uint16_t crc;
uint8_t index;
if ((length > BUSINESS_MAX_DATA_LENGTH) ||
((data == (const uint8_t *)0) && (length != 0u)))
{
return 0u;
}
crc_input[0] = command;
crc_input[1] = length;
for (index = 0u; index < length; ++index) crc_input[2u + index] = data[index];
crc = BusinessProtocol_Crc16Modbus(crc_input, (uint16_t)(2u + length));
Uart1_WriteByte(TX_PREFIX_0); Uart1_WriteByte(TX_PREFIX_1);
Uart1_WriteByte(TX_PREFIX_2); Uart1_WriteByte(TX_PREFIX_3);
Uart1_WriteByte(TX_PREFIX_4);
Uart1_WriteByte((uint8_t)(target_address >> 8u));
Uart1_WriteByte((uint8_t)target_address);
Uart1_WriteByte(MESH_DATA_FLAG);
Uart1_WriteByte(0x55u); Uart1_WriteByte(0xAAu);
Uart1_WriteByte(command); Uart1_WriteByte(length);
for (index = 0u; index < length; ++index) Uart1_WriteByte(data[index]);
Uart1_WriteByte((uint8_t)crc); Uart1_WriteByte((uint8_t)(crc >> 8u));
Uart1_WriteByte(MESH_TAIL_0); Uart1_WriteByte(MESH_TAIL_1);
return 1u;
}