feat: complete assembly mesh relay control path

- synchronize STM8 application and external flash layout updates\n- add assembly bootloader and assembly application test projects\n- add UART1 interrupt receive ring buffer\n- parse JDY-28M Mesh outer frames and source addresses\n- add CRC-16/MODBUS validation\n- implement relay control command 0x01 and response 0x81\n- support unicast and broadcast relay control frames\n- update host protocol, OTA documents, and serial tools
This commit is contained in:
十七
2026-11-05 16:12:51 +08:00
parent df1aad3d16
commit ebef6a8694
40 changed files with 2133 additions and 307 deletions
+19 -19
View File
@@ -12,13 +12,13 @@
/** 当前 PA1 继电器控制输出的逻辑状态。 */
static uint8_t relay_state;
/** 当前 PB5 蓝牙组网/故障指示灯的逻辑状态。 */
/** 当前 PB4 蓝牙组网/故障指示灯的逻辑状态。 */
static uint8_t led3_state;
/** 当前 PD3 继电器状态灯的逻辑状态。 */
static uint8_t relay_led_state;
/** PB4程序状态灯当前逻辑状态,1表示逻辑点亮。 */
/** PB5程序状态灯当前逻辑状态,1表示逻辑点亮;硬件为高电平点亮。 */
static uint8_t status_led_state;
/** 上次翻转PB4状态灯的系统时间。 */
/** 上次翻转PB5状态灯的系统时间。 */
static uint32_t status_led_last_ms;
/** 初始化板级资源,并将继电器置于安全断开状态。 */
@@ -45,11 +45,11 @@ void Board_Init(void)
REG8(GPIOD_CR1_ADDR) |= PD2_MASK;
REG8(GPIOD_CR2_ADDR) &= (uint8_t)~PD2_MASK;
/* PB5:低电平点亮,默认熄灭蓝牙组网/故障指示灯。 */
REG8(GPIOB_DDR_ADDR) |= PB5_MASK;
REG8(GPIOB_CR1_ADDR) |= PB5_MASK;
REG8(GPIOB_CR2_ADDR) &= (uint8_t)~PB5_MASK;
REG8(GPIOB_ODR_ADDR) |= PB5_MASK;
/* PB4:低电平点亮,默认熄灭蓝牙组网/故障指示灯。 */
REG8(GPIOB_DDR_ADDR) |= PB4_MASK;
REG8(GPIOB_CR1_ADDR) |= PB4_MASK;
REG8(GPIOB_CR2_ADDR) &= (uint8_t)~PB4_MASK;
REG8(GPIOB_ODR_ADDR) |= PB4_MASK;
/* PD3:高电平点亮,默认熄灭继电器状态灯。 */
REG8(GPIOD_DDR_ADDR) |= PD3_MASK;
@@ -63,11 +63,11 @@ void Board_Init(void)
status_led_state = 0u;
status_led_last_ms = 0ul;
/* PB4:低电平点亮,默认熄灭,用于程序运行/OTA状态指示。 */
REG8(GPIOB_DDR_ADDR) |= PB4_MASK;
REG8(GPIOB_CR1_ADDR) |= PB4_MASK;
REG8(GPIOB_CR2_ADDR) &= (uint8_t)~PB4_MASK;
REG8(GPIOB_ODR_ADDR) |= PB4_MASK;
/* PB5:高电平点亮。初始化为低电平,避免上电瞬间误点亮。 */
REG8(GPIOB_DDR_ADDR) |= PB5_MASK;
REG8(GPIOB_CR1_ADDR) |= PB5_MASK;
REG8(GPIOB_CR2_ADDR) &= (uint8_t)~PB5_MASK;
REG8(GPIOB_ODR_ADDR) &= (uint8_t)~PB5_MASK;
}
/** 设置继电器状态,后续接入 PA1 寄存器时保持高电平吸合约定。 */
@@ -96,17 +96,17 @@ uint8_t Board_ReadMeshJoined(void)
return ((REG8(GPIOC_IDR_ADDR) & PC3_MASK) != 0u) ? 1u : 0u;
}
/** 设置PB5蓝牙组网/故障指示灯;由于低电平点亮,逻辑值需要反相输出。 */
/** 设置PB4蓝牙组网/故障指示灯;由于低电平点亮,逻辑值需要反相输出。 */
void Board_SetLed3(uint8_t on)
{
led3_state = (on != 0u) ? 1u : 0u;
if (led3_state != 0u)
{
REG8(GPIOB_ODR_ADDR) &= (uint8_t)~PB5_MASK;
REG8(GPIOB_ODR_ADDR) &= (uint8_t)~PB4_MASK;
}
else
{
REG8(GPIOB_ODR_ADDR) |= PB5_MASK;
REG8(GPIOB_ODR_ADDR) |= PB4_MASK;
}
}
@@ -125,7 +125,7 @@ void Board_SetRelayLed(uint8_t on)
}
/**
* @brief 周期驱动PB4程序状态灯。
* @brief 周期驱动PB5程序状态灯。
* @param ota_active 1表示OTA接收/校验中,0表示正常运行。
*/
void Board_StatusLedTask(uint8_t ota_active)
@@ -137,9 +137,9 @@ void Board_StatusLedTask(uint8_t ota_active)
status_led_last_ms = now;
status_led_state = (status_led_state == 0u) ? 1u : 0u;
if (status_led_state != 0u)
REG8(GPIOB_ODR_ADDR) &= (uint8_t)~PB4_MASK;
REG8(GPIOB_ODR_ADDR) |= PB5_MASK;
else
REG8(GPIOB_ODR_ADDR) |= PB4_MASK;
REG8(GPIOB_ODR_ADDR) &= (uint8_t)~PB5_MASK;
}
}
-4
View File
@@ -25,7 +25,3 @@ void Interrupts_Enable(void)
* STM8 `sim` 指令会屏蔽可屏蔽中断。临界区应尽可能短,不能在关闭中断后
* 执行 UART 等待、EEPROM 写入或长时间循环,否则可能导致系统节拍丢失。
*/
void Interrupts_Disable(void)
{
__asm__("sim");
}
+12 -7
View File
@@ -20,13 +20,20 @@ static volatile uint32_t system_tick_ms;
void SystemTick_Init(void)
{
system_tick_ms = 0ul;
/* 显式选择HSI不分频,确保TIM4获得稳定的16MHz主时钟。 */
REG8(CLK_CKDIVR_ADDR) = 0x00u;
/* 先停止定时器并关闭更新中断,避免配置过程中产生伪中断。 */
REG8(TIM4_CR1_ADDR) = 0x00u;
REG8(TIM4_IER_ADDR) = 0x01u;
REG8(TIM4_IER_ADDR) = 0x00u;
REG8(TIM4_PSCR_ADDR) = 0x07u;
REG8(TIM4_ARR_ADDR) = 124u;
REG8(TIM4_CNTR_ADDR) = 0u;
/* 产生一次更新事件,使预分频器和自动重装值立即装载。 */
REG8(TIM4_EGR_ADDR) = 0x01u;
/* 更新事件可能置位 UIF,打开中断前必须先清除。 */
REG8(TIM4_SR1_ADDR) = 0x00u;
/* 所有配置和标志清理完成后,最后才打开更新中断。 */
REG8(TIM4_IER_ADDR) = 0x01u;
REG8(TIM4_CR1_ADDR) = 0x01u;
}
@@ -48,11 +55,9 @@ void SystemTick_InterruptHandler(void)
* STM8S003 的 TIM4 更新中断向量号为 23。本函数只做清除中断标志和递增
* 软件毫秒计数,禁止在中断中执行 UART、EEPROM 或 Mesh 发送等耗时操作。
*/
void SystemTick_TIM4_ISR(void) __interrupt(23)
void SystemTick_TIM4_Handler(void)
{
if ((REG8(TIM4_SR1_ADDR) & 0x01u) != 0u)
{
REG8(TIM4_SR1_ADDR) = 0x00u;
SystemTick_InterruptHandler();
}
/* 进入TIM4更新中断后先清除标志,防止同一中断请求反复进入。 */
REG8(TIM4_SR1_ADDR) = 0x00u;
SystemTick_InterruptHandler();
}
-11
View File
@@ -54,11 +54,6 @@ void Uart1_Init(void)
* 接收字节已经由 UART1 接收中断写入环形缓冲区,因此主循环不再轮询UART
* 硬件寄存器。保留该接口是为了维持主循环结构和后续扩展接口兼容性。
*/
void Uart1_Task(void)
{
/* UART硬件接收由Uart1_InterruptHandler()完成,此处无需执行任何操作。 */
}
/** 发送一个字节;当前阶段不连接真实 UART 寄存器。 */
void Uart1_WriteByte(uint8_t value)
{
@@ -110,9 +105,3 @@ void Uart1_InterruptHandler(void)
Uart1_RxPush(value);
}
}
/** UART1接收寄存器非空中断向量,IRQ号19。 */
void Uart1_RX_IRQHandler(void) __interrupt(19)
{
Uart1_InterruptHandler();
}
+13 -4
View File
@@ -4,12 +4,14 @@
#define W25_CS_MASK PD4_MASK
#define W25_CMD_READ 0x03u
#define W25_CMD_JEDEC_ID 0x9Fu
#define W25_CMD_PROGRAM 0x02u
#define W25_CMD_WREN 0x06u
#define W25_CMD_RDSR 0x05u
#define W25_CMD_ERASE_4K 0x20u
#define W25_BUSY 0x01u
#define W25_RESULT_ADDR 0x005010ul
static uint8_t w25_ready;
static uint8_t Xfer(uint8_t value)
{
@@ -30,7 +32,7 @@ static uint8_t Ready(void)
}
static void WriteEnable(void){Select();Xfer(W25_CMD_WREN);Deselect();}
void W25Q64App_Init(void)
uint8_t W25Q64App_Init(void)
{
REG8(GPIOC_DDR_ADDR)|=(uint8_t)(PC5_MASK|PC6_MASK);
REG8(GPIOC_CR1_ADDR)|=(uint8_t)(PC5_MASK|PC6_MASK);
@@ -41,10 +43,15 @@ void W25Q64App_Init(void)
Deselect();
SPI1_CR1=(uint8_t)(SPI_CR1_MSTR|SPI_CR1_BR2|SPI_CR1_BR1|SPI_CR1_BR0|SPI_CR1_SPE);
SPI1_CR2=(uint8_t)(SPI_CR2_SSM|SPI_CR2_SSI);
Select(); Xfer(W25_CMD_JEDEC_ID);
w25_ready = (Xfer(0xFFu) == 0xEFu && Xfer(0xFFu) == 0x40u &&
Xfer(0xFFu) == 0x14u) ? 1u : 0u;
Deselect();
return w25_ready;
}
uint8_t W25Q64App_ReadByte(uint32_t address,uint8_t *value)
{
if(value==(uint8_t *)0)return 0u;
if(!w25_ready || value==(uint8_t *)0)return 0u;
Select(); Xfer(W25_CMD_READ); Xfer((uint8_t)(address>>16));
Xfer((uint8_t)(address>>8)); Xfer((uint8_t)address); *value=Xfer(0xFFu); Deselect();
return 1u;
@@ -52,7 +59,7 @@ uint8_t W25Q64App_ReadByte(uint32_t address,uint8_t *value)
uint8_t W25Q64App_Read(uint32_t address,uint8_t *buffer,uint16_t length)
{
uint16_t index;
if(buffer==(uint8_t *)0)return 0u;
if(!w25_ready || buffer==(uint8_t *)0)return 0u;
Select(); Xfer(W25_CMD_READ); Xfer((uint8_t)(address>>16));
Xfer((uint8_t)(address>>8)); Xfer((uint8_t)address);
for(index=0u;index<length;index++)buffer[index]=Xfer(0xFFu);
@@ -61,6 +68,7 @@ uint8_t W25Q64App_Read(uint32_t address,uint8_t *buffer,uint16_t length)
}
uint8_t W25Q64App_EraseSector(uint32_t address)
{
if(!w25_ready)return 0u;
WriteEnable(); Select(); Xfer(W25_CMD_ERASE_4K);
Xfer((uint8_t)(address>>16)); Xfer((uint8_t)(address>>8));
Xfer((uint8_t)address); Deselect();
@@ -70,7 +78,7 @@ uint8_t W25Q64App_PageProgram(uint32_t address,const uint8_t *data,
uint16_t length)
{
uint16_t index;
if(data==(const uint8_t *)0 || length==0u || length>256u ||
if(!w25_ready || data==(const uint8_t *)0 || length==0u || length>256u ||
(((uint16_t)address&0xFFu)+length)>256u)return 0u;
WriteEnable(); Select(); Xfer(W25_CMD_PROGRAM);
Xfer((uint8_t)(address>>16)); Xfer((uint8_t)(address>>8));
@@ -82,6 +90,7 @@ uint8_t W25Q64App_PageProgram(uint32_t address,const uint8_t *data,
uint8_t W25Q64App_ClearUpgradeResult(void)
{
uint8_t zero=0u;
if(!w25_ready)return 0u;
WriteEnable(); Select(); Xfer(W25_CMD_PROGRAM);
Xfer((uint8_t)(W25_RESULT_ADDR >> 16));
Xfer((uint8_t)(W25_RESULT_ADDR >> 8));
+82 -20
View File
@@ -19,7 +19,39 @@
#include "bluetooth_manager.h"
#include "watchdog.h"
#include "interrupt_control.h"
#include "ota_manager.h"
#include "stm8s003_registers.h"
/**
* @brief 主程序固定的TIM4中断向量入口。
*
* STM8硬件中断向量表相对于应用程序起始地址固定布局。TIM4 IRQ23在主
* 程序向量表中的位置为0x8500 + 0x64 = 0x8564,
* 由SDCC根据__interrupt(23)自动生成入口。真正的计时处理集中在驱动层。
*/
/** TIM4诊断中断计数,累计500次后翻转一次PB5。 */
static volatile uint16_t tim4_test_count;
void SystemTick_TIM4_ISR(void) __interrupt(23)
{
REG8(TIM4_SR1_ADDR) = 0x00u;
++tim4_test_count;
if (tim4_test_count >= 500u)
{
tim4_test_count = 0u;
REG8(GPIOB_ODR_ADDR) ^= PB5_MASK;
}
}
/**
* @brief 主程序固定的UART1接收中断向量入口。
*
* STM8S003F3官方中断表中,UART1接收中断为IRQ18,对应主程序向量槽
* 0x8550。实际接收处理仍集中在uart1.c,避免中断向量入口混入业务逻辑。
*/
void Uart1_RX_IRQHandler(void) __interrupt(18)
{
Uart1_InterruptHandler();
}
/** 标记本次上电是否已经完成设备上线广播,避免主循环重复发送。 */
static uint8_t startup_broadcast_sent;
@@ -36,22 +68,45 @@ static uint8_t startup_broadcast_sent;
int main(void)
{
Board_Init();
/* 初始化TIM4并打开其更新中断,验证裸汇编跳板能够正常返回主程序。 */
SystemTick_Init();
Interrupts_Enable();
Uart1_Init();
Current_Init();
BusinessProtocol_Init();
MeshFrame_Init();
FaultManager_Init();
BluetoothManager_Init();
OtaManager_Init();
Watchdog_Init();
tim4_test_count = 0u;
/*
* 当前为最小主循环诊断版本。暂时不初始化 UART、ADC、业务协议、
* Mesh、故障管理和蓝牙模块,先确认 MCU 能否从主程序入口进入循环,
* 并由系统节拍驱动 PB5 状态灯。
*/
/* 当前最小入口诊断不初始化UART和ADC,避免外设状态干扰入口验证。 */
/* BusinessProtocol_Init(); */
/* MeshFrame_Init(); */
/* 当前最小入口诊断不初始化故障管理和蓝牙状态机。 */
/*
* 临时屏蔽 OTA 初始化,用于定位正式主程序是否卡在外部 W25Q80BV
* 的 SPI/JEDEC 初始化阶段。当前测试版本不访问外部 Flash,其他
* 蓝牙、继电器、电流检测和故障处理初始化保持不变。
*
* 确认 PB5 可以正常闪烁后,再恢复 OtaManager_Init(),并继续检查
* W25Q80BV 的供电、CS(PD4)、SPI 引脚和 JEDEC ID 读取过程。
*/
/* OtaManager_Init(); */
/* 当前最小入口诊断暂不启动看门狗,避免复位现象干扰入口判断。 */
startup_broadcast_sent = 0u;
while (1)
{
Uart1_Task();
Current_Task();
/* 最小诊断版本暂不执行任何通信、采样和业务任务。 */
/* UART接收由中断写入环形缓冲区,任务入口保持周期调用。 */
/* ADC采用轮询采样,不使用新的中断向量。 */
/* 当前先屏蔽 ADC 后台任务,确认它不会影响主循环和状态灯节拍。 */
/* Current_Task(); */
/*
* 临时屏蔽蓝牙初始化状态机和Mesh任务,用于确认正式程序是否能够
* 正常进入主循环。BluetoothManager_Task() 内部会等待 UART 发送
* 状态,如果 UART 初始化或引脚复用存在问题,可能在这里阻塞,
* 从而导致后面的 PB5 状态灯任务永远没有机会执行。
*/
#if 0
if (BluetoothManager_IsReady() == 0u)
{
BluetoothManager_Task();
@@ -61,7 +116,7 @@ int main(void)
MeshFrame_Task();
BluetoothManager_Task();
/* 所有基础模块已完成初始化且蓝牙进入可通信状态后,向Mesh
* 广播一次上线通知。业务帧SRC字段会自动携带本机短地址,
* 广播一次上线通知。JDY-28M外层会自动携带本机短地址,
* 其他设备无需回复,避免广播上线时形成应答风暴。 */
if ((startup_broadcast_sent == 0u) &&
(BluetoothManager_IsReady() != 0u))
@@ -75,15 +130,22 @@ int main(void)
}
}
}
FaultManager_Task();
BusinessProtocol_Task();
#endif
/* 周期执行电流持续时间和继电器状态一致性判断。 */
/* 当前先屏蔽故障管理任务,避免在排查主循环时引入其他调用链。 */
/* FaultManager_Task(); */
/* BusinessProtocol_Task(); */
/* 蓝牙完成初始化后再上报Bootloader升级结果,避免结果帧丢在AT阶段。 */
if (BluetoothManager_IsReady() != 0u)
{
OtaManager_Task();
}
Board_StatusLedTask(OtaManager_IsActive());
Watchdog_Refresh();
/* 临时屏蔽 OTA 任务,与上面的初始化屏蔽保持一致。 */
/*
* if (BluetoothManager_IsReady() != 0u)
* {
* OtaManager_Task();
* }
*/
/* 使用TIM4毫秒节拍驱动状态灯,验证中断跳板和ISR返回链路。 */
Board_StatusLedTask(0u);
/* 最小入口诊断不使用看门狗。 */
}
}
+11 -5
View File
@@ -7,7 +7,7 @@
* 2. 若不是 ROLE2,发送 ROLE2 和 RESET;
* 3. 再次确认 ROLE;
* 4. 查询 MADDR,保存 2 字节短地址到 RAM;
* 5. 初始化完成后由 PC3 决定 PB5 蓝牙状态灯常亮或 1 秒闪烁。
* 5. 初始化完成后由 PC3 决定 PB4 蓝牙状态灯常亮或 1 秒闪烁。
*
* 初始化过程不使用阻塞延时。UART 返回内容只识别关键字符和超时,具体
* AT 返回格式仍应在首次联调时用串口工具记录并校准。
@@ -105,6 +105,15 @@ static void BluetoothManager_Enter(BluetoothState state)
state_start_ms = SystemTick_NowMs();
}
/** 将一个ASCII十六进制字符转换为数值;非十六进制字符返回0xFF。 */
static uint8_t BluetoothManager_HexDigit(uint8_t value)
{
if ((value >= '0') && (value <= '9')) return (uint8_t)(value - '0');
if ((value >= 'A') && (value <= 'F')) return (uint8_t)(value - 'A' + 10u);
if ((value >= 'a') && (value <= 'f')) return (uint8_t)(value - 'a' + 10u);
return 0xFFu;
}
/** 初始化蓝牙状态机和 RAM 中的短地址。 */
void BluetoothManager_Init(void)
{
@@ -135,10 +144,7 @@ static void BluetoothManager_ConsumeResponse(void)
}
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);
uint8_t digit = BluetoothManager_HexDigit(value);
if (digit != 0xFFu)
{
response_address_value = (uint16_t)((response_address_value << 4u) | digit);
+46 -76
View File
@@ -11,7 +11,6 @@
#include "mesh_frame.h"
#include "fault_manager.h"
#include "bluetooth_manager.h"
#include "ota_manager.h"
#include "system_tick.h"
/** 广播应答的最小随机延时,避免多个设备同时发送。 */
@@ -69,12 +68,8 @@ void BusinessProtocol_QueueDelayedResponse(uint16_t address,
/** 业务帧解析状态。 */
typedef enum
{
/** 等待发送方短地址高字节。 */
PARSER_WAIT_SOURCE_HIGH = 0,
/** 等待发送方短地址低字节。 */
PARSER_WAIT_SOURCE_LOW,
/** 等待命令字节。 */
PARSER_WAIT_CMD,
PARSER_WAIT_CMD = 0,
/** 等待 DATA 长度字节。 */
PARSER_WAIT_LENGTH,
/** 按 LEN 接收 DATA 区。 */
@@ -89,7 +84,7 @@ typedef enum
static BusinessParserState parser_state;
/** 当前正在接收的命令。 */
static uint8_t parser_command;
/** 当前正在接收的发送方 Mesh 短地址。 */
/** 当前外层接收帧携带的发送方 Mesh 短地址。 */
static uint16_t parser_source_address;
/** 当前正在接收的数据长度。 */
static uint8_t parser_length;
@@ -99,8 +94,8 @@ static uint8_t parser_data_index;
static uint8_t parser_data[BUSINESS_MAX_DATA_LENGTH];
/** 当前正在接收的 CRC。 */
static uint16_t parser_received_crc;
/** CRC 计算临时区,依次保存 SRC、CMD、LEN 和 DATA。 */
static uint8_t parser_crc_input[4u + BUSINESS_MAX_DATA_LENGTH];
/** CRC 计算临时区,依次保存 CMD、LEN 和 DATA。 */
static uint8_t parser_crc_input[2u + BUSINESS_MAX_DATA_LENGTH];
/** 最近一帧合法业务帧的内容。 */
static uint8_t last_command;
/** 最近一帧合法业务帧的发送方短地址。 */
@@ -118,6 +113,12 @@ static void BusinessProtocol_SendByteResponse(uint16_t address,
BusinessProtocol_SendResponse(address, command, &value, 1u);
}
/** 发送统一格式的业务错误应答,集中减少各命令分支的重复封装代码。 */
static void BusinessProtocol_SendError(uint16_t address, uint8_t error)
{
BusinessProtocol_SendByteResponse(address, CMD_ERROR_RESPONSE, error);
}
/**
* 生成一个8位线性反馈移位寄存器伪随机数。
*
@@ -159,7 +160,7 @@ static void BusinessProtocol_SendResponse(uint16_t address,
/** 初始化业务协议状态机;完整解析器在第二阶段实现。 */
void BusinessProtocol_Init(void)
{
parser_state = PARSER_WAIT_SOURCE_HIGH;
parser_state = PARSER_WAIT_CMD;
parser_command = 0u;
parser_length = 0u;
parser_data_index = 0u;
@@ -170,6 +171,12 @@ void BusinessProtocol_Init(void)
response_random_state = 0xA5u;
}
/** 保存JDY-28M外层提供的发送方地址,供当前业务帧校验和应答使用。 */
void BusinessProtocol_SetSourceAddress(uint16_t address)
{
parser_source_address = address;
}
/** 处理 UART 数据和待发送帧;当前阶段仅保留任务入口。 */
void BusinessProtocol_Task(void)
{
@@ -187,8 +194,8 @@ void BusinessProtocol_Task(void)
/**
* 向业务帧状态机输入一个字节。
*
* 帧格式为 `SRC_H SRC_L CMD LEN DATA CRC16`。CRC 计算范围为 SRC、CMD、
* LEN 和 DATA;线上 CRC 高字节在前。CRC 错误会恢复到等待地址状态,
* 帧格式为 `CMD LEN DATA CRC16`。CRC 计算范围为 CMD、LEN 和 DATA;
* 线上 CRC 高字节在前。CRC 错误会恢复到等待命令状态,
* 不执行任何控制动作。
*/
uint8_t BusinessProtocol_InputByte(uint8_t value)
@@ -198,16 +205,6 @@ uint8_t BusinessProtocol_InputByte(uint8_t value)
switch (parser_state)
{
case PARSER_WAIT_SOURCE_HIGH:
parser_source_address = (uint16_t)value << 8u;
parser_state = PARSER_WAIT_SOURCE_LOW;
break;
case PARSER_WAIT_SOURCE_LOW:
parser_source_address |= value;
parser_state = PARSER_WAIT_CMD;
break;
case PARSER_WAIT_CMD:
parser_command = value;
parser_state = PARSER_WAIT_LENGTH;
@@ -218,7 +215,7 @@ uint8_t BusinessProtocol_InputByte(uint8_t value)
parser_data_index = 0u;
if (parser_length > BUSINESS_MAX_DATA_LENGTH)
{
parser_state = PARSER_WAIT_SOURCE_HIGH;
parser_state = PARSER_WAIT_CMD;
}
else if (parser_length == 0u)
{
@@ -245,17 +242,15 @@ uint8_t BusinessProtocol_InputByte(uint8_t value)
case PARSER_WAIT_CRC_LOW:
parser_received_crc |= value;
parser_crc_input[0] = (uint8_t)(parser_source_address >> 8u);
parser_crc_input[1] = (uint8_t)parser_source_address;
parser_crc_input[2] = parser_command;
parser_crc_input[3] = parser_length;
parser_crc_input[0] = parser_command;
parser_crc_input[1] = parser_length;
for (index = 0u; index < parser_length; ++index)
{
parser_crc_input[4u + index] = parser_data[index];
parser_crc_input[2u + index] = parser_data[index];
}
expected_crc = BusinessProtocol_Crc16Modbus(parser_crc_input,
(uint16_t)(4u + parser_length));
parser_state = PARSER_WAIT_SOURCE_HIGH;
(uint16_t)(2u + parser_length));
parser_state = PARSER_WAIT_CMD;
if (expected_crc != parser_received_crc)
{
return 0u;
@@ -271,31 +266,13 @@ uint8_t BusinessProtocol_InputByte(uint8_t value)
return 1u;
default:
parser_state = PARSER_WAIT_SOURCE_HIGH;
parser_state = PARSER_WAIT_CMD;
break;
}
return 0u;
}
/** 读取最近一帧合法业务帧的命令字。 */
uint8_t BusinessProtocol_GetCommand(void)
{
return last_command;
}
/** 读取最近一帧合法业务帧的数据长度。 */
uint8_t BusinessProtocol_GetDataLength(void)
{
return last_length;
}
/** 读取最近一帧合法业务帧携带的发送方 Mesh 短地址。 */
uint16_t BusinessProtocol_GetSourceAddress(void)
{
return last_source_address;
}
/**
* 执行当前合法业务帧。
*
@@ -316,8 +293,7 @@ void BusinessProtocol_HandleFrame(void)
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);
BusinessProtocol_SendError(source_address, 0x03u);
break;
}
Board_SetRelay(last_data[0]);
@@ -330,8 +306,7 @@ void BusinessProtocol_HandleFrame(void)
case CMD_RELAY_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
break;
}
BusinessProtocol_SendByteResponse(source_address,
@@ -342,8 +317,7 @@ void BusinessProtocol_HandleFrame(void)
case CMD_CURRENT_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
break;
}
current = Current_GetMilliAmpere();
@@ -357,9 +331,7 @@ void BusinessProtocol_HandleFrame(void)
case CMD_FIRMWARE_VERSION_QUERY:
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address,
CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
break;
}
current_data[0] = (uint8_t)(FIRMWARE_VERSION >> 8u);
@@ -376,6 +348,8 @@ void BusinessProtocol_HandleFrame(void)
}
break;
#if 0
/* OTA命令暂时屏蔽。保留完整实现,待主程序空间和硬件验证完成后恢复。 */
case CMD_OTA_RESULT_ACK:
if (last_length == 1u)
{
@@ -415,25 +389,23 @@ void BusinessProtocol_HandleFrame(void)
* 返回“成功”而实际校验尚未完成。 */
break;
}
#endif
case CMD_THRESHOLD_CONFIG:
if (last_length != 2u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
break;
}
threshold = ((uint16_t)last_data[0] << 8u) | last_data[1];
if ((threshold == 0u) || (threshold > 30000u))
{
value = 0x03u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x03u);
break;
}
if (FaultManager_SetThreshold(threshold) == 0u)
{
value = 0x05u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x05u);
break;
}
BusinessProtocol_SendResponse(source_address,
@@ -442,14 +414,12 @@ void BusinessProtocol_HandleFrame(void)
break;
case CMD_DEVICE_ONLINE:
/* 上线通知是设备间广播事件。发送方短地址已经位于业务帧SRC
* 字段中,因此不需要DATA,也不返回应答,避免多个设备同时
/* 上线通知是设备间广播事件。发送方短地址已经由JDY-28M外层
* 提供,因此不需要DATA,也不返回应答,避免多个设备同时
* 上线时产生广播应答风暴。 */
if (last_length != 0u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address,
CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
}
break;
@@ -458,9 +428,7 @@ void BusinessProtocol_HandleFrame(void)
* 在新版本高于当前版本时回复,避免全网无意义应答。 */
if (last_length != 4u)
{
value = 0x02u;
BusinessProtocol_SendByteResponse(source_address,
CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x02u);
break;
}
/* 这里直接复用最近一帧的DATA缓冲区生成应答,避免额外申请
@@ -488,16 +456,18 @@ void BusinessProtocol_HandleFrame(void)
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
break;
}
current_data[0] = (uint8_t)(BluetoothManager_GetShortAddress() >> 8u);
current_data[1] = (uint8_t)BluetoothManager_GetShortAddress();
/* 只读取一次短地址:除去重复函数调用,也避免地址在两次读取之间
* 被异步状态更新时出现高、低字节不属于同一地址的情况。 */
threshold = BluetoothManager_GetShortAddress();
current_data[0] = (uint8_t)(threshold >> 8u);
current_data[1] = (uint8_t)threshold;
BusinessProtocol_SendResponse(source_address,
CMD_ADDRESS_QUERY | CMD_RESPONSE_FLAG,
current_data, 2u);
break;
default:
value = 0x06u;
BusinessProtocol_SendByteResponse(source_address, CMD_ERROR_RESPONSE, value);
BusinessProtocol_SendError(source_address, 0x06u);
break;
}
}
+33 -41
View File
@@ -3,8 +3,8 @@
* @brief JDY-28M Mesh 外层帧实现。
*
* 发送格式(所有发送方统一):41 54 2B AA A1 | 目标地址高低字节 | A0 | 业务帧 | 0D 0A。
* 接收格式(接收方 UART 侧):F1 DD 09 00 0A | 地址高低字节 | 业务帧 | 0D 0A。
* 发送方地址由业务帧 SRC_H/SRC_L 携带;JDY-28M 外层接收地址不是发送方地址。
* 接收格式(接收方 UART 侧):F1 DD 09 | 发送方地址 | 接收方地址 | 业务帧 | 0A。
* 业务帧不再重复携带地址,发送方和接收方地址均由JDY-28M外层提供。
*/
#include <stdint.h>
#include "business_protocol.h"
@@ -15,16 +15,15 @@
#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
#define MESH_RX_TAIL 0x0Au
#define MESH_TX_TAIL_0 0x0Du
#define MESH_TX_TAIL_1 0x0Au
/** 外层解析状态。 */
typedef enum
@@ -35,26 +34,26 @@ typedef enum
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_TARGET_HIGH,
/** 接收外层目标地址低字节。 */
/** 接收外层接收方地址低字节。 */
MESH_READ_TARGET_LOW,
/** 接收内层业务帧。 */
MESH_READ_BUSINESS,
/** 等待外层帧尾 0D。 */
MESH_WAIT_TAIL_0,
/** 等待外层帧尾 0A。 */
MESH_WAIT_TAIL_1
/** 等待外层单字节帧尾 0A。 */
MESH_WAIT_TAIL
} MeshParserState;
/** JDY-28M 外层接收状态机当前状态。 */
static MeshParserState mesh_state;
/** 最近一帧合法外层数据携带的目标短地址,仅用于诊断。 */
static uint16_t last_target_address;
/** 当前正在接收帧携带的发送方短地址。 */
static uint16_t current_source_address;
/** 当前正在接收的业务帧缓冲区。 */
static uint8_t business_buffer[BUSINESS_MAX_DATA_LENGTH + 6u];
/** 当前已经接收的业务帧字节数。 */
@@ -96,13 +95,15 @@ static void MeshFrame_InputByte(uint8_t value)
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;
mesh_state = (value == RX_PREFIX_2) ? MESH_READ_SOURCE_HIGH : MESH_WAIT_PREFIX_0;
break;
case MESH_WAIT_PREFIX_3:
mesh_state = (value == RX_PREFIX_3) ? MESH_WAIT_PREFIX_4 : MESH_WAIT_PREFIX_0;
case MESH_READ_SOURCE_HIGH:
current_source_address = (uint16_t)value << 8u;
mesh_state = MESH_READ_SOURCE_LOW;
break;
case MESH_WAIT_PREFIX_4:
mesh_state = (value == RX_PREFIX_4) ? MESH_READ_TARGET_HIGH : MESH_WAIT_PREFIX_0;
case MESH_READ_SOURCE_LOW:
current_source_address |= value;
mesh_state = MESH_READ_TARGET_HIGH;
break;
case MESH_READ_TARGET_HIGH:
last_target_address = (uint16_t)value << 8u;
@@ -122,7 +123,7 @@ static void MeshFrame_InputByte(uint8_t value)
business_buffer[business_length++] = value;
if (business_length >= 4u)
{
expected_length = (uint8_t)(business_buffer[3] + 6u);
expected_length = (uint8_t)(business_buffer[1] + 4u);
business_expected_length = expected_length;
if ((business_buffer[3] > BUSINESS_MAX_DATA_LENGTH) ||
(business_length > expected_length))
@@ -132,19 +133,16 @@ static void MeshFrame_InputByte(uint8_t value)
}
if (business_length == expected_length)
{
/* 先确认外层 0D 0A,再提交内层业务帧。 */
mesh_state = MESH_WAIT_TAIL_0;
mesh_state = MESH_WAIT_TAIL;
}
}
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)
case MESH_WAIT_TAIL:
if (value == MESH_RX_TAIL)
{
uint8_t index;
uint8_t frame_ready = 0u;
BusinessProtocol_SetSourceAddress(current_source_address);
for (index = 0u; index < business_expected_length; ++index)
{
if (BusinessProtocol_InputByte(business_buffer[index]) != 0u)
@@ -187,8 +185,7 @@ uint8_t MeshFrame_SendBusiness(uint16_t target_address,
const uint8_t *data,
uint8_t length)
{
uint8_t crc_input[4u + BUSINESS_MAX_DATA_LENGTH];
uint16_t source_address;
uint8_t crc_input[2u + BUSINESS_MAX_DATA_LENGTH];
uint16_t crc;
uint8_t index;
@@ -197,13 +194,10 @@ uint8_t MeshFrame_SendBusiness(uint16_t target_address,
{
return 0u;
}
source_address = BluetoothManager_GetShortAddress();
crc_input[0] = (uint8_t)(source_address >> 8u);
crc_input[1] = (uint8_t)source_address;
crc_input[2] = command;
crc_input[3] = length;
for (index = 0u; index < length; ++index) crc_input[4u + index] = data[index];
crc = BusinessProtocol_Crc16Modbus(crc_input, (uint16_t)(4u + length));
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);
@@ -211,11 +205,9 @@ uint8_t MeshFrame_SendBusiness(uint16_t target_address,
Uart1_WriteByte((uint8_t)(target_address >> 8u));
Uart1_WriteByte((uint8_t)target_address);
Uart1_WriteByte(MESH_DATA_FLAG);
Uart1_WriteByte((uint8_t)(source_address >> 8u));
Uart1_WriteByte((uint8_t)source_address);
Uart1_WriteByte(command); Uart1_WriteByte(length);
for (index = 0u; index < length; ++index) Uart1_WriteByte(data[index]);
Uart1_WriteByte((uint8_t)(crc >> 8u)); Uart1_WriteByte((uint8_t)crc);
Uart1_WriteByte(MESH_TAIL_0); Uart1_WriteByte(MESH_TAIL_1);
Uart1_WriteByte(MESH_TX_TAIL_0); Uart1_WriteByte(MESH_TX_TAIL_1);
return 1u;
}
+37 -42
View File
@@ -58,6 +58,31 @@ static uint8_t ota_verify_response_pending;
/** CMD=0x0B请求是否来自广播,用于决定最终0x8B是否随机延迟。 */
static uint8_t ota_verify_broadcast_request;
/**
* 发送后台OTA校验结果。
*
* 校验读取失败和整包校验完成原来分别实现了一套广播/单播发送分支,
* 这里统一封装,确保两条路径使用完全相同的应答规则。
*/
static void OtaManager_SendVerifyResult(uint8_t result)
{
if (ota_verify_response_pending == 0u)
{
return;
}
if (ota_verify_broadcast_request != 0u)
{
BusinessProtocol_QueueDelayedResponse(ota_verify_response_target,
0x8Bu, &result, 1u);
}
else
{
(void)MeshFrame_SendBusiness(ota_verify_response_target,
0x8Bu, &result, 1u);
}
ota_verify_response_pending = 0u;
}
/**
* @brief 初始化 OTA 状态机。
*/
@@ -75,7 +100,12 @@ void OtaManager_Init(void)
ota_verify_response_target = 0u;
ota_verify_response_pending = 0u;
ota_verify_broadcast_request = 0u;
W25Q64App_Init();
if (W25Q64App_Init() == 0u)
{
/* 外部Flash故障只关闭OTA和升级结果读取,不能影响继电器、
* 电流检测、蓝牙通信等主程序基础功能。 */
return;
}
if (W25Q64App_ReadByte(OTA_METADATA_ADDRESS + 0x10ul,
&ota_bootloader_result) != 0u &&
ota_bootloader_result != 0u)
@@ -115,20 +145,7 @@ void OtaManager_Task(void)
* 不能只退出状态机,否则主机将一直等待0x8B应答。 */
result = 0x01u;
ota_state = OTA_STATE_IDLE;
if (ota_verify_response_pending != 0u)
{
if (ota_verify_broadcast_request != 0u)
{
BusinessProtocol_QueueDelayedResponse(ota_verify_response_target,
0x8Bu, &result, 1u);
}
else
{
(void)MeshFrame_SendBusiness(ota_verify_response_target,
0x8Bu, &result, 1u);
}
ota_verify_response_pending = 0u;
}
OtaManager_SendVerifyResult(result);
}
else
{
@@ -175,28 +192,12 @@ void OtaManager_Task(void)
}
}
ota_state = OTA_STATE_IDLE;
if (ota_verify_response_pending != 0u)
/* 只有整包CRC正确且OTA元数据已经成功写入时,才在0x8B
* 应答发送完成后复位。失败分支必须继续留在当前程序。 */
OtaManager_SendVerifyResult(result);
if ((result == 0x00u) && (ota_verify_broadcast_request == 0u))
{
if (ota_verify_broadcast_request != 0u)
{
/* 广播校验请求仍使用随机延时应答,但广播场景不立即
* 复位,避免延时队列中的0x8B尚未发出就被复位丢失。 */
BusinessProtocol_QueueDelayedResponse(ota_verify_response_target,
0x8Bu, &result, 1u);
}
else
{
(void)MeshFrame_SendBusiness(ota_verify_response_target,
0x8Bu, &result, 1u);
}
ota_verify_response_pending = 0u;
/* 只有整包CRC正确且OTA元数据已经成功写入时,才在0x8B
* 应答发送完成后复位。失败分支必须继续留在当前程序。 */
if ((result == 0x00u) && (ota_verify_broadcast_request == 0u))
{
Watchdog_ResetNow();
}
Watchdog_ResetNow();
}
}
}
@@ -322,12 +323,6 @@ uint8_t OtaManager_GetExpectedOffset(uint16_t *offset)
*offset=ota_expected_offset; return 1u;
}
uint8_t OtaManager_GetBootloaderResult(void)
{
/* W25Q80BV读取接口接入后,在此处加载result字段并置pending。 */
return (ota_bootloader_result_pending != 0u) ? ota_bootloader_result : 0u;
}
void OtaManager_AcknowledgeBootloaderResult(void)
{
if (ota_bootloader_result_pending != 0u)