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:
@@ -0,0 +1,33 @@
|
||||
# STM8S003F3P6 汇编主程序实验工程。
|
||||
# 主程序固定从0x8500开始链接,与汇编Bootloader的新地址布局匹配。
|
||||
|
||||
PROJECT := stm8_app_asm
|
||||
BUILD_DIR := build
|
||||
AS := sdasstm8
|
||||
LD := sdldstm8
|
||||
PACKIHX := packihx
|
||||
|
||||
ASMFLAGS := -plosgff
|
||||
LINKFLAGS := -mjwxu -b HOME=0x8500 -b CODE=0x8568
|
||||
OBJECT := $(BUILD_DIR)/app_vectors.rel
|
||||
IHX := $(BUILD_DIR)/$(PROJECT).ihx
|
||||
HEX := $(BUILD_DIR)/$(PROJECT).hex
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(HEX)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(OBJECT): src/app_vectors.asm | $(BUILD_DIR)
|
||||
$(AS) $(ASMFLAGS) -o $@ $<
|
||||
|
||||
$(IHX): $(OBJECT)
|
||||
$(LD) $(LINKFLAGS) -i $@ $^
|
||||
|
||||
$(HEX): $(IHX)
|
||||
$(PACKIHX) $< > $@
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
@@ -0,0 +1,20 @@
|
||||
# STM8 汇编主程序实验工程
|
||||
|
||||
本工程是正式 C 主程序的并行汇编实现,当前只验证最小启动链路:
|
||||
|
||||
1. 应用向量表从 `0x8500` 开始;
|
||||
2. Bootloader 能够跳转到应用复位入口;
|
||||
3. TIM4 中断能够从 Bootloader 的固定向量转发到应用程序;
|
||||
4. PB5 运行灯每约500ms反转一次。
|
||||
|
||||
应用启动后将 `CLK_CKDIVR` 配置为 HSI/1,主频为16MHz;TIM4和UART1参数均按16MHz计算。
|
||||
|
||||
当前不包含蓝牙、UART、ADC、继电器和OTA功能。后续每增加一个模块,都应先独立编译并与Bootloader合并后再进行实物验证。
|
||||
|
||||
## 构建
|
||||
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
输出文件:`build/stm8_app_asm.hex`。
|
||||
@@ -0,0 +1,448 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; STM8S003F3P6 汇编主程序最小启动工程
|
||||
;
|
||||
; 应用程序从0x8500开始链接。STM8硬件中断仍然使用固定向量地址,实际产品
|
||||
; 由Bootloader在0x8064等固定向量中转发到应用程序对应向量槽。
|
||||
;-------------------------------------------------------------------------------
|
||||
|
||||
.module app_vectors
|
||||
.optsdcc -mstm8
|
||||
|
||||
.area HOME (ABS)
|
||||
|
||||
; 每个中断向量项占4字节:0x82 + 3字节远跳转地址。
|
||||
int _App_Reset
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
|
||||
; IRQ18:UART1接收,进入汇编接收中断入口。
|
||||
int _App_UART1Vector
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
int _App_DefaultInterrupt
|
||||
; IRQ23:TIM4更新中断,Bootloader固定向量会转发到这里。
|
||||
int _App_TIM4Vector
|
||||
|
||||
.area CODE
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 应用复位入口。
|
||||
; 初始化堆栈、PB5和TIM4,然后进入主循环。Bootloader跳转前不依赖应用RAM状态,
|
||||
; 因此这里主动设置堆栈,保证汇编主程序可以独立运行。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_Reset:
|
||||
sim
|
||||
ldw x, #0x03FF
|
||||
ldw sp, x
|
||||
; CLK_CKDIVR:HSI默认16MHz经/8得到约2MHz;写0后改为HSI/1=16MHz。
|
||||
; 后续TIM4和UART1参数均按16MHz配置。
|
||||
mov 0x50C6, #0x00
|
||||
|
||||
; PB5:推挽输出,初始输出低电平。当前硬件运行灯按低电平点亮验证。
|
||||
bres 0x5005, #5
|
||||
bset 0x5007, #5
|
||||
bset 0x5008, #5
|
||||
bres 0x5009, #5
|
||||
|
||||
; TIM4:16MHz/128/125,目标约1ms更新一次。
|
||||
mov 0x5340, #0x00
|
||||
mov 0x5343, #0x01
|
||||
mov 0x5347, #0x07
|
||||
mov 0x5348, #0x7C
|
||||
mov 0x5346, #0x00
|
||||
mov 0x5345, #0x01
|
||||
mov 0x5344, #0x00
|
||||
mov 0x5340, #0x01
|
||||
|
||||
; UART1:当前系统时钟16MHz,9600bps使用BRR1=0x68、BRR2=0x03。
|
||||
; PD5为TX推挽输出,PD6为RX输入并开启内部上拉。
|
||||
; 内部上拉用于保证串口线在适配器未驱动或连接接触不稳定时保持空闲高电平,
|
||||
; 不改变正常UART接收的逻辑电平。
|
||||
; UART1引脚配置:接收中断只保存一个字节,主循环负责发送回显。
|
||||
bset 0x5011, #5
|
||||
bset 0x5012, #5
|
||||
bres 0x5011, #6
|
||||
bset 0x5012, #6
|
||||
mov 0x5235, #0x00
|
||||
mov 0x5233, #0x03
|
||||
mov 0x5232, #0x68
|
||||
mov 0x5234, #0x00
|
||||
mov 0x5236, #0x00
|
||||
; 清理Bootloader或上电阶段可能遗留的UART状态:
|
||||
; 先读SR,再读DR。这样可以清除RXNE及可能的帧错误、噪声错误、溢出错误,
|
||||
; 防止刚开启接收中断时立即进入一次“伪接收”或把残留字节当作有效数据。
|
||||
ld a, 0x5230
|
||||
ld a, 0x5231
|
||||
; UART接收环形缓冲区使用RAM 0x0020~0x002F,共16字节。
|
||||
; 0x0030为写索引,0x0031为读索引,0x0032为溢出计数。
|
||||
mov 0x0030, #0x00
|
||||
mov 0x0031, #0x00
|
||||
mov 0x0032, #0x00
|
||||
; Mesh外层解析状态:0等待F1,1等待DD,2等待09,3跳过4字节地址,
|
||||
; 4接收业务区直到0A。0x0035记录业务区长度,0x0036表示最近一帧有效。
|
||||
mov 0x0034, #0x00
|
||||
mov 0x0035, #0x00
|
||||
mov 0x0036, #0x00
|
||||
mov 0x5235, #0x2C
|
||||
rim
|
||||
; 发送一次固定的0x55,仅用于现场确认TX链路、波特率和串口接线。
|
||||
; 该字节不是业务协议数据,正式联调确认TX正常后可删除这三行。
|
||||
ld a, #0x55
|
||||
ld 0x0020, a
|
||||
_App_StartupTxWait:
|
||||
ld a, 0x5230
|
||||
and a, #0x80
|
||||
jreq _App_StartupTxWait
|
||||
ld a, 0x0020
|
||||
ld 0x5231, a
|
||||
|
||||
_App_MainLoop:
|
||||
; 主循环从环形缓冲区取出字节。UART中断只负责接收和入队,
|
||||
; 当前阶段不再对任意字节原样回显;所有字节统一交给Mesh外层状态机。
|
||||
ld a, 0x0030
|
||||
cp a, 0x0031
|
||||
jreq _App_MainLoop
|
||||
ld a, 0x0031
|
||||
add a, #0x20
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, (x)
|
||||
ld 0x0020, a
|
||||
ld a, 0x0031
|
||||
inc a
|
||||
and a, #0x0F
|
||||
ld 0x0031, a
|
||||
; 将取出的字节交给Mesh外层帧状态机。当前仅识别帧边界,
|
||||
; 不执行继电器等业务动作。
|
||||
call _App_MeshParseByte
|
||||
ld a, 0x0036
|
||||
jreq _App_MainLoop
|
||||
call _App_ProcessBusiness
|
||||
mov 0x0036, #0x00
|
||||
jra _App_MainLoop
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; JDY-28M Mesh外层帧状态机。
|
||||
;
|
||||
; 接收格式:F1 DD 09 | SRC_H SRC_L | DST_H DST_L | BUSINESS | 0A
|
||||
; 当前阶段只确认固定头、跳过源/目标地址,并以0A作为外层帧结束符。
|
||||
; 业务数据最大按20字节外层限制约束为12字节,超长帧直接重新寻找F1。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_MeshParseByte:
|
||||
ld a, 0x0020
|
||||
ld xl, a
|
||||
ld a, 0x0034
|
||||
cp a, #0x00
|
||||
jreq _App_ParseWaitF1
|
||||
cp a, #0x01
|
||||
jreq _App_ParseWaitDD
|
||||
cp a, #0x02
|
||||
jreq _App_ParseWait09
|
||||
cp a, #0x03
|
||||
jreq _App_ParseAddress
|
||||
; 状态4:业务数据区,0A表示完整外层帧结束。
|
||||
cp a, #0x04
|
||||
jreq _App_ParseBusiness
|
||||
mov 0x0034, #0x00
|
||||
ret
|
||||
|
||||
_App_ParseWaitF1:
|
||||
ld a, 0x0020
|
||||
cp a, #0xF1
|
||||
jrne _App_ParseReturn
|
||||
mov 0x0034, #0x01
|
||||
ret
|
||||
|
||||
_App_ParseWaitDD:
|
||||
ld a, 0x0020
|
||||
cp a, #0xDD
|
||||
jrne _App_ParseReset
|
||||
mov 0x0034, #0x02
|
||||
ret
|
||||
|
||||
_App_ParseWait09:
|
||||
ld a, 0x0020
|
||||
cp a, #0x09
|
||||
jrne _App_ParseReset
|
||||
mov 0x0034, #0x03
|
||||
mov 0x0035, #0x00
|
||||
ret
|
||||
|
||||
_App_ParseAddress:
|
||||
; 保存外层源地址和目标地址:前2字节为源地址,后2字节为目标地址。
|
||||
ld a, 0x0035
|
||||
add a, #0x38
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, 0x0020
|
||||
ld (x), a
|
||||
inc 0x0035
|
||||
ld a, 0x0035
|
||||
cp a, #0x04
|
||||
jrne _App_ParseReturn
|
||||
mov 0x0034, #0x04
|
||||
mov 0x0035, #0x00
|
||||
ret
|
||||
|
||||
_App_ParseBusiness:
|
||||
ld a, 0x0020
|
||||
cp a, #0x0A
|
||||
jreq _App_ParseFrameDone
|
||||
; JDY-28M实测可能在Mesh业务数据后携带0D 0A,
|
||||
; 0D属于外层结束符的一部分,不应计入业务长度和CRC数据。
|
||||
cp a, #0x0D
|
||||
jreq _App_ParseReturn
|
||||
; 保存业务区字节,后续可从0x0040开始读取CMD、LEN、DATA和CRC16。
|
||||
ld a, 0x0035
|
||||
add a, #0x40
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, 0x0020
|
||||
ld (x), a
|
||||
inc 0x0035
|
||||
ld a, 0x0035
|
||||
cp a, #0x0C
|
||||
jrult _App_ParseReturn
|
||||
; 业务区超过外层长度限制,放弃当前帧。
|
||||
mov 0x0034, #0x00
|
||||
ret
|
||||
|
||||
_App_ParseFrameDone:
|
||||
; 业务区至少包含CMD、LEN和CRC16,共4字节。
|
||||
ld a, 0x0035
|
||||
cp a, #0x04
|
||||
jrult _App_ParseReset
|
||||
; LEN必须等于业务区长度减去CMD、LEN和CRC16四个字节。
|
||||
ld a, 0x0035
|
||||
sub a, #0x04
|
||||
ld 0x0037, a
|
||||
ld a, 0x0041
|
||||
cp a, 0x0037
|
||||
jrne _App_ParseReset
|
||||
; 记录一帧外层和业务长度均合法的Mesh数据。
|
||||
mov 0x0036, #0x01
|
||||
mov 0x0034, #0x00
|
||||
ret
|
||||
|
||||
_App_ParseReset:
|
||||
mov 0x0034, #0x00
|
||||
_App_ParseReturn:
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 处理当前已通过外层和长度检查的业务帧。
|
||||
; 当前仅实现CMD=01继电器控制,CRC计算范围为业务区的CMD、LEN、DATA。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_ProcessBusiness:
|
||||
; 先计算CMD、LEN、DATA的CRC,并与业务帧末尾两个字节比较;
|
||||
; CRC错误时静默丢弃,绝不执行继电器动作。
|
||||
call _App_CrcBusiness3
|
||||
ld a, 0x003D
|
||||
cp a, 0x0043
|
||||
jreq _App_CrcHighOK
|
||||
jpf _App_BusinessReturn
|
||||
_App_CrcHighOK:
|
||||
ld a, 0x003C
|
||||
cp a, 0x0044
|
||||
jreq _App_CrcLowOK
|
||||
jpf _App_BusinessReturn
|
||||
_App_CrcLowOK:
|
||||
ld a, 0x0040
|
||||
cp a, #0x01
|
||||
jreq _App_CmdOK
|
||||
jpf _App_BusinessReturn
|
||||
_App_CmdOK:
|
||||
ld a, 0x0041
|
||||
cp a, #0x01
|
||||
jreq _App_LenOK
|
||||
jpf _App_BusinessReturn
|
||||
_App_LenOK:
|
||||
ld a, 0x0042
|
||||
cp a, #0x01
|
||||
jreq _App_RelayOn
|
||||
cp a, #0x00
|
||||
jreq _App_RelayOff
|
||||
jpf _App_BusinessReturn
|
||||
; 只有DATA=00或01允许控制继电器。
|
||||
_App_RelayOff:
|
||||
mov 0x5000, #0x00
|
||||
jra _App_RelayRespond
|
||||
_App_RelayOn:
|
||||
mov 0x5000, #0x02
|
||||
|
||||
_App_RelayRespond:
|
||||
; 业务区改写为CMD=81、LEN=1、DATA=当前控制状态。
|
||||
mov 0x0040, #0x81
|
||||
mov 0x0041, #0x01
|
||||
ld a, 0x5000
|
||||
and a, #0x02
|
||||
jreq _App_ResponseOff
|
||||
mov 0x0042, #0x01
|
||||
jra _App_ResponseCrc
|
||||
_App_ResponseOff:
|
||||
mov 0x0042, #0x00
|
||||
_App_ResponseCrc:
|
||||
call _App_CrcBusiness3
|
||||
ld a, 0x003D
|
||||
ld 0x0043, a
|
||||
ld a, 0x003C
|
||||
ld 0x0044, a
|
||||
; JDY-28M发送格式:41 54 2B AA A1 | 目标地址 | A0 | 业务区 | 0D 0A。
|
||||
mov 0x003E, #0x41
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x54
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x2B
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0xAA
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0xA1
|
||||
call _App_SendByte
|
||||
ld a, 0x0038
|
||||
ld 0x003E, a
|
||||
call _App_SendByte
|
||||
ld a, 0x0039
|
||||
ld 0x003E, a
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0xA0
|
||||
call _App_SendByte
|
||||
ld a, #0x00
|
||||
ld 0x003F, a
|
||||
_App_SendResponseBusiness:
|
||||
ld a, 0x003F
|
||||
add a, #0x40
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, (x)
|
||||
ld 0x003E, a
|
||||
call _App_SendByte
|
||||
inc 0x003F
|
||||
ld a, 0x003F
|
||||
cp a, #0x05
|
||||
jrne _App_SendResponseBusiness
|
||||
; 按JDY-28M Mesh发送协议保留完整帧尾0D 0A。
|
||||
mov 0x003E, #0x0D
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0A
|
||||
call _App_SendByte
|
||||
_App_BusinessReturn:
|
||||
ret
|
||||
|
||||
; CRC-16/MODBUS,计算业务区0x0040~0x0042,结果低字节在0x003C、高字节在0x003D。
|
||||
_App_CrcBusiness3:
|
||||
mov 0x003C, #0xFF
|
||||
mov 0x003D, #0xFF
|
||||
mov 0x003F, #0x00
|
||||
_App_CrcByte:
|
||||
ld a, 0x003F
|
||||
add a, #0x40
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, (x)
|
||||
xor a, 0x003C
|
||||
ld 0x003C, a
|
||||
mov 0x003E, #0x08
|
||||
_App_CrcBit:
|
||||
srl 0x003D
|
||||
rrc 0x003C
|
||||
jrnc _App_CrcNoPoly
|
||||
ld a, 0x003C
|
||||
xor a, #0x01
|
||||
ld 0x003C, a
|
||||
ld a, 0x003D
|
||||
xor a, #0xA0
|
||||
ld 0x003D, a
|
||||
_App_CrcNoPoly:
|
||||
dec 0x003E
|
||||
jrne _App_CrcBit
|
||||
inc 0x003F
|
||||
ld a, 0x003F
|
||||
cp a, #0x03
|
||||
jrne _App_CrcByte
|
||||
ret
|
||||
|
||||
_App_SendByte:
|
||||
ld a, 0x5230
|
||||
and a, #0x80
|
||||
jreq _App_SendByte
|
||||
ld a, 0x003E
|
||||
ld 0x5231, a
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; TIM4中断服务函数。
|
||||
; 每500次约1ms中断翻转一次PB5,因此目标周期约500ms。计数变量放在RAM中,
|
||||
; 不使用栈上局部变量,便于后续继续控制RAM占用。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_TIM4Vector:
|
||||
push a
|
||||
pushw x
|
||||
ld a, 0x5344
|
||||
and a, #0x01
|
||||
jreq _App_TIM4Exit
|
||||
mov 0x5344, #0x00
|
||||
ldw x, 0x0000
|
||||
incw x
|
||||
cpw x, #500
|
||||
jrne _App_TIM4Save
|
||||
ldw x, #0
|
||||
bcpl 0x5005, #5
|
||||
_App_TIM4Save:
|
||||
ldw 0x0000, x
|
||||
_App_TIM4Exit:
|
||||
popw x
|
||||
pop a
|
||||
iret
|
||||
|
||||
; 未使用中断统一停机,防止错误向量执行未知代码。
|
||||
_App_DefaultInterrupt:
|
||||
sim
|
||||
_App_DefaultLoop:
|
||||
jra _App_DefaultLoop
|
||||
|
||||
; UART1接收中断:读取DR清除RXNE,将字节保存到单字节测试缓冲区。
|
||||
_App_UART1Vector:
|
||||
push a
|
||||
pushw x
|
||||
ld a, 0x5230
|
||||
and a, #0x20
|
||||
jreq _App_UART1Exit
|
||||
ld a, 0x5231
|
||||
ld 0x0033, a
|
||||
ld a, 0x0030
|
||||
inc a
|
||||
and a, #0x0F
|
||||
cp a, 0x0031
|
||||
jreq _App_UART1Exit
|
||||
ld a, 0x0030
|
||||
add a, #0x20
|
||||
ldw x, #0x0000
|
||||
ld xl, a
|
||||
ld a, 0x0033
|
||||
ld (x), a
|
||||
ld a, 0x0030
|
||||
inc a
|
||||
and a, #0x0F
|
||||
ld 0x0030, a
|
||||
_App_UART1Exit:
|
||||
popw x
|
||||
pop a
|
||||
iret
|
||||
Reference in New Issue
Block a user