Compare commits
4
Commits
aedde75d65
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c64d50d83 | ||
|
|
12f6bb62f2 | ||
|
|
72393b6ef7 | ||
|
|
7d3997700c |
@@ -97,23 +97,23 @@ JDY-28M 的 STAT 依据模块手册第 7 页:MESH 模式下高电平表示入
|
||||
### 6.2 模块转发给 STM8
|
||||
|
||||
```text
|
||||
F1 DD 09 00 0A | 源短地址 2B | 业务数据 | 0D 0A
|
||||
F1 DD 08/09/0A | 源短地址 2B | 目标短地址 2B | 业务数据 | 0A
|
||||
```
|
||||
|
||||
- `F1 DD 09 00 0A`:固定接收帧头;
|
||||
- `F1 DD 08/09/0A`:根据当前实测,第三字节可能随模块固件或转发路径变化;
|
||||
- 源短地址:2 字节;
|
||||
- 目标短地址:2 字节;
|
||||
- 业务数据:STM8 自定义业务帧;
|
||||
- `0D 0A`:蓝牙帧结束符。
|
||||
- `0A`:实测帧尾,部分串口工具可能同时显示前置`0D`。
|
||||
|
||||
广播和单播格式相同,仅目标地址不同。整个 JDY-28M 外层数据包最大 20 字节。
|
||||
|
||||
## 7. STM8 业务帧
|
||||
|
||||
```text
|
||||
0x55 0xAA | CMD | LEN | DATA | CRC16
|
||||
CMD | LEN | DATA | CRC16_H | CRC16_L
|
||||
```
|
||||
|
||||
- `0x55 0xAA`:业务帧头;
|
||||
- `CMD`:命令码;
|
||||
- `LEN`:DATA 长度;
|
||||
- `DATA`:命令数据;
|
||||
@@ -124,8 +124,8 @@ CRC-16/MODBUS 固定参数:
|
||||
- 初始值:`0xFFFF`;
|
||||
- 右移实现多项式:`0xA001`;
|
||||
- 输入、输出反转;
|
||||
- CRC 低字节在前;
|
||||
- 计算范围:从 `CMD` 到 DATA,不包含 `0x55 0xAA`;
|
||||
- CRC 高字节在前,低字节在后;
|
||||
- 计算范围:从 `CMD` 到 DATA;
|
||||
- 不计算 JDY-28M 外层帧字段。
|
||||
|
||||
测试向量:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# W25Q80BV SPI 独立测试工程。
|
||||
# 本工程直接从0x8000启动,不依赖Bootloader和正式主程序。
|
||||
|
||||
PROJECT := stm8_spi_test
|
||||
BUILD_DIR := build
|
||||
AS := sdasstm8
|
||||
LD := sdldstm8
|
||||
PACKIHX := packihx
|
||||
|
||||
ASMFLAGS := -plosgff
|
||||
LINKFLAGS := -mjwxu -b HOME=0x8000 -b CODE=0x8068
|
||||
OBJECT := $(BUILD_DIR)/spi_test.rel
|
||||
IHX := $(BUILD_DIR)/$(PROJECT).ihx
|
||||
HEX := $(BUILD_DIR)/$(PROJECT).hex
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(HEX)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(OBJECT): src/spi_test.asm | $(BUILD_DIR)
|
||||
$(AS) $(ASMFLAGS) -o $@ $<
|
||||
|
||||
$(IHX): $(OBJECT)
|
||||
$(LD) $(LINKFLAGS) -i $@ $^
|
||||
|
||||
$(HEX): $(IHX)
|
||||
$(PACKIHX) $< > $@
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
@@ -0,0 +1,675 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; STM8S003F3P6TR - W25Q80BV SPI独立读写测试固件
|
||||
;
|
||||
; 测试目的:
|
||||
; 1. 读取W25Q80BV JEDEC ID;
|
||||
; 2. 擦除一个专用测试扇区;
|
||||
; 3. 写入固定测试数据;
|
||||
; 4. 读回并逐字节比较。
|
||||
;
|
||||
; 硬件连接:
|
||||
; PC5 -> W25Q80BV CLK
|
||||
; PC6 -> W25Q80BV DI/MOSI
|
||||
; PC7 <- W25Q80BV DO/MISO
|
||||
; PD4 -> W25Q80BV CS#
|
||||
; PB5 -> 测试LED
|
||||
;
|
||||
; 测试区域:外部Flash 0x01F000所在的4KB扇区。
|
||||
; 该区域位于预留空间,不会覆盖出厂固件、出厂元数据、OTA固件和OTA元数据。
|
||||
;
|
||||
; LED结果指示:
|
||||
; 测试成功:PB5约每500ms翻转一次;
|
||||
; 测试失败:PB5约每100ms翻转一次。
|
||||
;
|
||||
; 注意:本固件会擦除并写入外部Flash 0x01F000所在扇区,测试前请确认该扇区
|
||||
; 没有保存需要保留的数据。
|
||||
;-------------------------------------------------------------------------------
|
||||
|
||||
.module spi_test
|
||||
.optsdcc -mstm8
|
||||
|
||||
.area HOME (ABS)
|
||||
int _Reset
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
int _DefaultInterrupt
|
||||
|
||||
.area CODE
|
||||
|
||||
_Reset:
|
||||
sim
|
||||
ldw x, #0x03FF
|
||||
ldw sp, x
|
||||
; HSI/1,系统主频16MHz。
|
||||
mov 0x50C6, #0x00
|
||||
; PB5推挽输出,初始低电平。
|
||||
bres 0x5005, #5
|
||||
bset 0x5007, #5
|
||||
bset 0x5008, #5
|
||||
bres 0x5009, #5
|
||||
call _UART_Init
|
||||
ldw x, #_TextStart
|
||||
call _UART_SendText
|
||||
call _SPI_Init
|
||||
mov 0x0003, #0x01
|
||||
call _SPI_CheckJedec
|
||||
tnz a
|
||||
jrne _TestFail
|
||||
ldw x, #_TextJedecOK
|
||||
call _UART_SendText
|
||||
mov 0x0003, #0x05
|
||||
call _ReadFactoryImageInfo
|
||||
tnz a
|
||||
jrne _TestFail
|
||||
ldw x, #_TextFactoryPass
|
||||
call _UART_SendText
|
||||
|
||||
_TestPass:
|
||||
; A=0表示通过,使用长周期指示。
|
||||
ld a, #0x00
|
||||
ld 0x0000, a
|
||||
ldw x, #500
|
||||
jra _BlinkLoop
|
||||
|
||||
_TestFail:
|
||||
ld a, 0x0003
|
||||
cp a, #0x01
|
||||
jreq _ReportJedecFail
|
||||
cp a, #0x02
|
||||
jreq _ReportEraseFail
|
||||
cp a, #0x03
|
||||
jreq _ReportWriteFail
|
||||
cp a, #0x05
|
||||
jreq _ReportFactoryFail
|
||||
ldw x, #_TextVerifyFail
|
||||
call _UART_SendText
|
||||
jra _TestFailLed
|
||||
_ReportJedecFail:
|
||||
ldw x, #_TextJedecFail
|
||||
call _UART_SendText
|
||||
jra _TestFailLed
|
||||
_ReportEraseFail:
|
||||
ldw x, #_TextEraseFail
|
||||
call _UART_SendText
|
||||
jra _TestFailLed
|
||||
_ReportWriteFail:
|
||||
ldw x, #_TextWriteFail
|
||||
call _UART_SendText
|
||||
jra _TestFailLed
|
||||
_ReportFactoryFail:
|
||||
ldw x, #_TextFactoryFail
|
||||
call _UART_SendText
|
||||
_TestFailLed:
|
||||
; 0x0003保存失败阶段,进入分组闪烁指示,避免只能知道“失败”而不知道
|
||||
; 失败发生在JEDEC、擦除、写入还是回读校验阶段。
|
||||
ld a, 0x0003
|
||||
ld 0x0004, a
|
||||
ldw x, #100
|
||||
call _DelayMsApprox
|
||||
; 每组开始前先熄灭LED约1秒,作为组间分隔。
|
||||
bset 0x5005, #5
|
||||
ldw x, #1000
|
||||
call _DelayMsApprox
|
||||
_ErrorGroup:
|
||||
; 每次点亮约100ms,再熄灭约100ms。
|
||||
bres 0x5005, #5
|
||||
ldw x, #100
|
||||
call _DelayMsApprox
|
||||
bset 0x5005, #5
|
||||
ldw x, #100
|
||||
call _DelayMsApprox
|
||||
dec 0x0004
|
||||
jrne _ErrorGroup
|
||||
; 一组错误码结束后暂停约1秒,然后重复指示。
|
||||
ldw x, #1000
|
||||
call _DelayMsApprox
|
||||
ld a, 0x0003
|
||||
ld 0x0004, a
|
||||
jra _ErrorGroup
|
||||
|
||||
_BlinkLoop:
|
||||
call _DelayMsApprox
|
||||
bcpl 0x5005, #5
|
||||
ldw x, #500
|
||||
ld a, 0x0000
|
||||
tnz a
|
||||
jrne _FailDelaySelect
|
||||
jra _BlinkLoop
|
||||
_FailDelaySelect:
|
||||
ldw x, #100
|
||||
jra _BlinkLoop
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; SPI1初始化:PC5/PC6推挽输出,PC7输入;PD4为软件片选。
|
||||
; SPI模式0,主机模式,约500kHz,MSB first。
|
||||
;-------------------------------------------------------------------------------
|
||||
_SPI_Init:
|
||||
mov 0x500C, #0x60
|
||||
mov 0x500D, #0x60
|
||||
mov 0x500E, #0x00
|
||||
mov 0x500F, #0x10
|
||||
mov 0x5011, #0x10
|
||||
mov 0x5012, #0x10
|
||||
mov 0x5013, #0x00
|
||||
bset 0x500F, #4
|
||||
mov 0x5200, #0x4C
|
||||
mov 0x5201, #0x03
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; UART1初始化,PD5为TX,PD6为RX,16MHz系统时钟下配置为9600bps。
|
||||
; 测试日志只使用TX发送,RX保持输入状态。
|
||||
;-------------------------------------------------------------------------------
|
||||
_UART_Init:
|
||||
bset 0x5011, #5
|
||||
bset 0x5012, #5
|
||||
bres 0x5011, #6
|
||||
bset 0x5012, #6
|
||||
; CR2:同时打开UART发送器和接收器,TXE轮询发送才能继续执行。
|
||||
mov 0x5235, #0x0C
|
||||
mov 0x5233, #0x03
|
||||
mov 0x5232, #0x68
|
||||
mov 0x5234, #0x00
|
||||
mov 0x5236, #0x00
|
||||
ret
|
||||
|
||||
; 发送A寄存器中的一个UART字节。
|
||||
_UART_SendByte:
|
||||
push a
|
||||
_UART_WaitTXE:
|
||||
ld a, 0x5230
|
||||
and a, #0x80
|
||||
jreq _UART_WaitTXE
|
||||
pop a
|
||||
ld 0x5231, a
|
||||
ret
|
||||
|
||||
; 发送以0结尾的ASCII文本,X指向字符串首地址。
|
||||
_UART_SendText:
|
||||
ld a, (x)
|
||||
tnz a
|
||||
jreq _UART_SendTextDone
|
||||
call _UART_SendByte
|
||||
incw x
|
||||
jra _UART_SendText
|
||||
_UART_SendTextDone:
|
||||
ret
|
||||
|
||||
; A寄存器发送一个SPI字节,同时返回收到的字节。
|
||||
_SPI_Transfer:
|
||||
ld 0x5204, a
|
||||
_SPI_WaitTx:
|
||||
btjf 0x5203, #1, _SPI_WaitTx
|
||||
_SPI_WaitRx:
|
||||
btjf 0x5203, #0, _SPI_WaitRx
|
||||
ld a, 0x5204
|
||||
ret
|
||||
|
||||
; 读取JEDEC ID,W25Q80BV期望返回EF 40 14。
|
||||
_SPI_CheckJedec:
|
||||
bres 0x500F, #4
|
||||
ld a, #0x9F
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
cp a, #0xEF
|
||||
jrne _SPI_Fail
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
cp a, #0x40
|
||||
jrne _SPI_Fail
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
cp a, #0x14
|
||||
jrne _SPI_Fail
|
||||
bset 0x500F, #4
|
||||
ld a, #0x00
|
||||
ret
|
||||
_SPI_Fail:
|
||||
bset 0x500F, #4
|
||||
ld a, #0x01
|
||||
ret
|
||||
|
||||
; 等待W25Q80BV非忙状态,超时返回失败。
|
||||
_SPI_WaitReady:
|
||||
ldw x, #0xFFFF
|
||||
_SPI_ReadyLoop:
|
||||
bres 0x500F, #4
|
||||
ld a, #0x05
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
push a
|
||||
bset 0x500F, #4
|
||||
pop a
|
||||
and a, #0x01
|
||||
jreq _SPI_ReadyOK
|
||||
decw x
|
||||
jrne _SPI_ReadyLoop
|
||||
ld a, #0x01
|
||||
ret
|
||||
_SPI_ReadyOK:
|
||||
ld a, #0x00
|
||||
ret
|
||||
|
||||
; 发送写使能命令WREN。
|
||||
_SPI_WriteEnable:
|
||||
bres 0x500F, #4
|
||||
ld a, #0x06
|
||||
call _SPI_Transfer
|
||||
bset 0x500F, #4
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 从W25Q80BV读取一个字节。
|
||||
; 输入:X为16位地址;当前出厂固件和元数据地址均低于0x10000。
|
||||
; 返回:A为读取到的数据。
|
||||
;-------------------------------------------------------------------------------
|
||||
_SPI_ReadByteX:
|
||||
bres 0x500F, #4
|
||||
ld a, #0x03
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
ld a, xh
|
||||
call _SPI_Transfer
|
||||
ld a, xl
|
||||
call _SPI_Transfer
|
||||
ld a, #0xFF
|
||||
call _SPI_Transfer
|
||||
push a
|
||||
bset 0x500F, #4
|
||||
pop a
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 读取出厂固件和元数据,并由STM8重新计算出厂固件CRC16/MODBUS。
|
||||
; 返回:A=0表示元数据和固件CRC均正确,A=1表示检查失败。
|
||||
; 串口输出实际读取到的关键字段,便于确认CH341A烧录内容。
|
||||
;-------------------------------------------------------------------------------
|
||||
_ReadFactoryImageInfo:
|
||||
; 读取并输出魔数0x2000~0x2003。
|
||||
ldw x, #0x2000
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0005, a
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0006, a
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0007, a
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0008, a
|
||||
ldw x, #_TextMagic
|
||||
call _UART_SendText
|
||||
ld a, 0x0005
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x0006
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x0007
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x0008
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
ld a, 0x0005
|
||||
cp a, #0x31
|
||||
jreq _FactoryMagic1OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryMagic1OK:
|
||||
ld a, 0x0006
|
||||
cp a, #0x43
|
||||
jreq _FactoryMagic2OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryMagic2OK:
|
||||
ld a, 0x0007
|
||||
cp a, #0x41
|
||||
jreq _FactoryMagic3OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryMagic3OK:
|
||||
ld a, 0x0008
|
||||
cp a, #0x46
|
||||
jreq _FactoryMagic4OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryMagic4OK:
|
||||
|
||||
; 读取4字节固件长度,保存低16位供CRC循环使用。
|
||||
ldw x, #0x2004
|
||||
call _SPI_ReadByteX
|
||||
; 32位长度的高字节必须为0。
|
||||
tnz a
|
||||
jreq _FactoryLengthByte0OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryLengthByte0OK:
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
; 32位长度的次高字节也必须为0。
|
||||
tnz a
|
||||
jreq _FactoryLengthByte1OK
|
||||
jpf _FactoryInfoFail
|
||||
_FactoryLengthByte1OK:
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0000, a
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0001, a
|
||||
ldw x, #_TextLength
|
||||
call _UART_SendText
|
||||
ld a, 0x0000
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x0001
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
|
||||
; 输出版本号。
|
||||
ldw x, #_TextVersion
|
||||
call _UART_SendText
|
||||
ldw x, #0x2008
|
||||
call _SPI_ReadByteX
|
||||
call _UART_SendHexByte
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
|
||||
; 读取并输出元数据保存的CRC16,高字节在0x0002,低字节在0x0003。
|
||||
ldw x, #0x200A
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0002, a
|
||||
incw x
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0003, a
|
||||
ldw x, #_TextStoredCRC
|
||||
call _UART_SendText
|
||||
ld a, 0x0002
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x0003
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
|
||||
; 输出并检查镜像有效标志。
|
||||
ldw x, #0x200E
|
||||
call _SPI_ReadByteX
|
||||
ld 0x0009, a
|
||||
ldw x, #_TextValid
|
||||
call _UART_SendText
|
||||
ld a, 0x0009
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
ld a, 0x0009
|
||||
cp a, #0xA5
|
||||
jrne _FactoryInfoFail
|
||||
|
||||
; 从外部Flash 0x000000开始逐字节计算完整固件CRC。
|
||||
ld a, #0xFF
|
||||
ld 0x000A, a
|
||||
ld 0x000B, a
|
||||
ldw x, #0x0000
|
||||
_FactoryCrcLoop:
|
||||
ld a, 0x0000
|
||||
or a, 0x0001
|
||||
jreq _FactoryCrcDone
|
||||
call _SPI_ReadByteX
|
||||
call _CRC_UpdateByte
|
||||
incw x
|
||||
call _DecLength
|
||||
jra _FactoryCrcLoop
|
||||
_FactoryCrcDone:
|
||||
ldw x, #_TextCalcCRC
|
||||
call _UART_SendText
|
||||
ld a, 0x000B
|
||||
call _UART_SendHexByte
|
||||
ld a, 0x000A
|
||||
call _UART_SendHexByte
|
||||
ldw x, #_TextCRLF
|
||||
call _UART_SendText
|
||||
ld a, 0x000B
|
||||
cp a, 0x0002
|
||||
jrne _FactoryInfoFail
|
||||
ld a, 0x000A
|
||||
cp a, 0x0003
|
||||
jrne _FactoryInfoFail
|
||||
ld a, #0x00
|
||||
ret
|
||||
_FactoryInfoFail:
|
||||
ld a, #0x01
|
||||
ret
|
||||
|
||||
; CRC-16/MODBUS逐字节更新,输入A,累加值低字节0x000A、高字节0x000B。
|
||||
_CRC_UpdateByte:
|
||||
xor a, 0x000A
|
||||
ld 0x000A, a
|
||||
mov 0x000C, #0x08
|
||||
_CRC_BitLoop:
|
||||
srl 0x000B
|
||||
rrc 0x000A
|
||||
jrc _CRC_Poly
|
||||
jra _CRC_Next
|
||||
_CRC_Poly:
|
||||
ld a, 0x000A
|
||||
xor a, #0x01
|
||||
ld 0x000A, a
|
||||
ld a, 0x000B
|
||||
xor a, #0xA0
|
||||
ld 0x000B, a
|
||||
_CRC_Next:
|
||||
dec 0x000C
|
||||
jrne _CRC_BitLoop
|
||||
ret
|
||||
|
||||
; 递减0x0000/0x0001保存的16位剩余长度。
|
||||
_DecLength:
|
||||
ld a, 0x0001
|
||||
tnz a
|
||||
jrne _DecLengthLow
|
||||
dec 0x0000
|
||||
_DecLengthLow:
|
||||
dec 0x0001
|
||||
ret
|
||||
|
||||
; 输出A的两个十六进制字符。
|
||||
_UART_SendHexByte:
|
||||
push a
|
||||
swap a
|
||||
and a, #0x0F
|
||||
call _UART_SendHexNibble
|
||||
pop a
|
||||
and a, #0x0F
|
||||
call _UART_SendHexNibble
|
||||
ret
|
||||
_UART_SendHexNibble:
|
||||
cp a, #0x0A
|
||||
jrult _UART_SendDigit
|
||||
add a, #0x37
|
||||
jra _UART_SendHexOut
|
||||
_UART_SendDigit:
|
||||
add a, #0x30
|
||||
_UART_SendHexOut:
|
||||
call _UART_SendByte
|
||||
ret
|
||||
|
||||
; 擦除0x01F000所在4KB扇区。
|
||||
_SPI_EraseSector:
|
||||
call _SPI_WriteEnable
|
||||
bres 0x500F, #4
|
||||
ld a, #0x20
|
||||
call _SPI_Transfer
|
||||
ld a, #0x01
|
||||
call _SPI_Transfer
|
||||
ld a, #0xF0
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
bset 0x500F, #4
|
||||
call _SPI_WaitReady
|
||||
ret
|
||||
|
||||
; 向0x01F000写入8字节测试数据:00 55 AA FF 12 34 56 78。
|
||||
_SPI_WriteTestData:
|
||||
call _SPI_WriteEnable
|
||||
bres 0x500F, #4
|
||||
ld a, #0x02
|
||||
call _SPI_Transfer
|
||||
ld a, #0x01
|
||||
call _SPI_Transfer
|
||||
ld a, #0xF0
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
ld a, #0x55
|
||||
call _SPI_Transfer
|
||||
ld a, #0xAA
|
||||
call _SPI_Transfer
|
||||
ld a, #0xFF
|
||||
call _SPI_Transfer
|
||||
ld a, #0x12
|
||||
call _SPI_Transfer
|
||||
ld a, #0x34
|
||||
call _SPI_Transfer
|
||||
ld a, #0x56
|
||||
call _SPI_Transfer
|
||||
ld a, #0x78
|
||||
call _SPI_Transfer
|
||||
bset 0x500F, #4
|
||||
call _SPI_WaitReady
|
||||
ret
|
||||
|
||||
; 读取8字节并逐字节比较测试数据。
|
||||
_SPI_VerifyTestData:
|
||||
bres 0x500F, #4
|
||||
ld a, #0x03
|
||||
call _SPI_Transfer
|
||||
ld a, #0x01
|
||||
call _SPI_Transfer
|
||||
ld a, #0xF0
|
||||
call _SPI_Transfer
|
||||
ld a, #0x00
|
||||
call _SPI_Transfer
|
||||
ldw x, #_TestData
|
||||
mov 0x0001, #0x08
|
||||
_VerifyLoop:
|
||||
ld a, #0xFF
|
||||
call _SPI_Transfer
|
||||
ld 0x0002, a
|
||||
ld a, (x)
|
||||
cp a, 0x0002
|
||||
jrne _VerifyFail
|
||||
incw x
|
||||
dec 0x0001
|
||||
jrne _VerifyLoop
|
||||
bset 0x500F, #4
|
||||
ld a, #0x00
|
||||
ret
|
||||
_VerifyFail:
|
||||
bset 0x500F, #4
|
||||
ld a, #0x01
|
||||
ret
|
||||
|
||||
; 固定测试数据表。
|
||||
_TestData:
|
||||
.db 0x00, 0x55, 0xAA, 0xFF, 0x12, 0x34, 0x56, 0x78
|
||||
|
||||
; 粗略毫秒延时,X中的次数由调用方设置。
|
||||
_DelayMsApprox:
|
||||
pushw x
|
||||
_DelayOuter:
|
||||
ldw y, #1600
|
||||
_DelayInner:
|
||||
decw y
|
||||
jrne _DelayInner
|
||||
popw x
|
||||
decw x
|
||||
jrne _DelayMsApprox
|
||||
ret
|
||||
|
||||
_DefaultInterrupt:
|
||||
sim
|
||||
_DefaultLoop:
|
||||
jra _DefaultLoop
|
||||
|
||||
; UART测试日志文本,均以回车换行结尾并以0结束。
|
||||
_TextStart:
|
||||
.ascii "SPI TEST START\r\n"
|
||||
.db 0
|
||||
_TextJedecOK:
|
||||
.ascii "JEDEC OK\r\n"
|
||||
.db 0
|
||||
_TextEraseOK:
|
||||
.ascii "ERASE OK\r\n"
|
||||
.db 0
|
||||
_TextWriteOK:
|
||||
.ascii "WRITE OK\r\n"
|
||||
.db 0
|
||||
_TextVerifyOK:
|
||||
.ascii "VERIFY OK\r\n"
|
||||
.db 0
|
||||
_TextPass:
|
||||
.ascii "SPI TEST PASS\r\n"
|
||||
.db 0
|
||||
_TextJedecFail:
|
||||
.ascii "JEDEC FAIL\r\n"
|
||||
.db 0
|
||||
_TextEraseFail:
|
||||
.ascii "ERASE FAIL\r\n"
|
||||
.db 0
|
||||
_TextWriteFail:
|
||||
.ascii "WRITE FAIL\r\n"
|
||||
.db 0
|
||||
_TextVerifyFail:
|
||||
.ascii "VERIFY FAIL\r\n"
|
||||
.db 0
|
||||
_TextFactoryFail:
|
||||
.ascii "FACTORY IMAGE FAIL\r\n"
|
||||
.db 0
|
||||
_TextFactoryPass:
|
||||
.ascii "FACTORY IMAGE PASS\r\n"
|
||||
.db 0
|
||||
_TextMagic:
|
||||
.ascii "MAGIC: "
|
||||
.db 0
|
||||
_TextLength:
|
||||
.ascii "LENGTH: "
|
||||
.db 0
|
||||
_TextVersion:
|
||||
.ascii "VERSION: "
|
||||
.db 0
|
||||
_TextStoredCRC:
|
||||
.ascii "STORED CRC: "
|
||||
.db 0
|
||||
_TextValid:
|
||||
.ascii "VALID: "
|
||||
.db 0
|
||||
_TextCalcCRC:
|
||||
.ascii "CALC CRC: "
|
||||
.db 0
|
||||
_TextCRLF:
|
||||
.ascii "\r\n"
|
||||
.db 0
|
||||
@@ -6,16 +6,18 @@ BUILD_DIR := build
|
||||
AS := sdasstm8
|
||||
LD := sdldstm8
|
||||
PACKIHX := packihx
|
||||
MAKEBIN := makebin
|
||||
|
||||
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
|
||||
BIN := $(BUILD_DIR)/$(PROJECT).bin
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(HEX)
|
||||
all: $(HEX) $(BIN)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
@@ -29,5 +31,10 @@ $(IHX): $(OBJECT)
|
||||
$(HEX): $(IHX)
|
||||
$(PACKIHX) $< > $@
|
||||
|
||||
# 生成用于写入W25Q80BV出厂区的主程序裸BIN。
|
||||
# -s提供完整STM8地址空间,-o跳过0x0000~0x84FF,仅保留0x8500之后的应用数据。
|
||||
$(BIN): $(IHX)
|
||||
$(MAKEBIN) -p -s 0x10000 -o 0x8500 $< $@
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
@@ -61,6 +61,11 @@ _App_Reset:
|
||||
bset 0x5007, #5
|
||||
bset 0x5008, #5
|
||||
bres 0x5009, #5
|
||||
; PB4:蓝牙初始化故障指示灯,低电平点亮;正常流程中输出高电平熄灭。
|
||||
bset 0x5005, #4
|
||||
bset 0x5007, #4
|
||||
bset 0x5008, #4
|
||||
bres 0x5009, #4
|
||||
; PA2/PWRC:低电平进入JDY-28M AT指令窗口,高电平恢复Mesh通信。
|
||||
bres 0x5000, #2
|
||||
bset 0x5002, #2
|
||||
@@ -100,6 +105,24 @@ _App_Reset:
|
||||
mov 0x5235, #0x0C
|
||||
; PWRC拉低后等待约10ms,给JDY-28M预留AT指令窗口建立时间。
|
||||
call _App_Delay10ms
|
||||
; 上电先确认模块是否处于MESH指令模式ROLE2。
|
||||
call _App_QueryRole2
|
||||
ld a, 0x004C
|
||||
jrne _App_RoleReady
|
||||
; 非ROLE2时写入ROLE2,并重新查询;最多再尝试两轮。
|
||||
call _App_SetRole2
|
||||
call _App_Delay10ms
|
||||
call _App_QueryRole2
|
||||
ld a, 0x004C
|
||||
jrne _App_RoleReady
|
||||
call _App_SetRole2
|
||||
call _App_Delay10ms
|
||||
call _App_QueryRole2
|
||||
ld a, 0x004C
|
||||
jrne _App_RoleReady
|
||||
jpf _App_BluetoothInitFault
|
||||
|
||||
_App_RoleReady:
|
||||
call _App_QueryModuleAddress
|
||||
; 查询结束后释放PWRC,模块恢复正常Mesh通信。
|
||||
bset 0x5000, #2
|
||||
@@ -114,17 +137,9 @@ _App_Reset:
|
||||
mov 0x0035, #0x00
|
||||
mov 0x0036, #0x00
|
||||
mov 0x5235, #0x2C
|
||||
; 蓝牙模式、短地址和串口业务状态均初始化完成后,广播一次上线通知。
|
||||
call _App_SendOnlineBroadcast
|
||||
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中断只负责接收和入队,
|
||||
@@ -151,6 +166,203 @@ _App_MainLoop:
|
||||
mov 0x0036, #0x00
|
||||
jra _App_MainLoop
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 蓝牙初始化故障停机点。
|
||||
; 模块未确认ROLE2时点亮PB4,释放PWRC后停留在此处,不进入Mesh业务。
|
||||
; PB5仍由TIM4驱动,便于区分蓝牙故障和MCU停机。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_BluetoothInitFault:
|
||||
bset 0x5000, #2
|
||||
bres 0x5005, #4
|
||||
rim
|
||||
_App_BluetoothFaultLoop:
|
||||
jra _App_BluetoothFaultLoop
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 查询JDY-28M当前模式,只有收到+ROLE=2才将0x004C置1。
|
||||
; 该函数在启动阶段调用,此时UART接收中断尚未开启,因此直接轮询UART。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_QueryRole2:
|
||||
mov 0x004B, #0x00
|
||||
mov 0x004C, #0x00
|
||||
; 发送AT+ROLE\r\n。
|
||||
mov 0x003E, #0x41
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x54
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x2B
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x52
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x4F
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x4C
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x45
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0D
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0A
|
||||
call _App_SendByte
|
||||
ldw x, #0xFFFF
|
||||
_App_RoleWait:
|
||||
ld a, 0x5230
|
||||
and a, #0x20
|
||||
jrne _App_RoleRead
|
||||
decw x
|
||||
jrne _App_RoleWait
|
||||
ret
|
||||
_App_RoleRead:
|
||||
ld a, 0x5231
|
||||
ld 0x003E, a
|
||||
call _App_RoleParseByte
|
||||
tnz 0x004C
|
||||
jrne _App_RoleDone
|
||||
decw x
|
||||
jrne _App_RoleWait
|
||||
_App_RoleDone:
|
||||
ret
|
||||
|
||||
; 逐字节匹配字符串“+ROLE=2”。收到完整字符串后确认ROLE2。
|
||||
_App_RoleParseByte:
|
||||
ld a, 0x003E
|
||||
ld 0x003F, a
|
||||
ld a, 0x004B
|
||||
cp a, #0x00
|
||||
jreq _App_RolePlus
|
||||
cp a, #0x01
|
||||
jreq _App_RoleR
|
||||
cp a, #0x02
|
||||
jreq _App_RoleO
|
||||
cp a, #0x03
|
||||
jreq _App_RoleL
|
||||
cp a, #0x04
|
||||
jreq _App_RoleE
|
||||
cp a, #0x05
|
||||
jreq _App_RoleEqual
|
||||
ld a, 0x003F
|
||||
cp a, #0x32
|
||||
jrne _App_RoleReset
|
||||
mov 0x004C, #0x01
|
||||
ret
|
||||
_App_RolePlus:
|
||||
ld a, 0x003F
|
||||
cp a, #0x2B
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleR:
|
||||
ld a, 0x003F
|
||||
cp a, #0x52
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleO:
|
||||
ld a, 0x003F
|
||||
cp a, #0x4F
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleL:
|
||||
ld a, 0x003F
|
||||
cp a, #0x4C
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleE:
|
||||
ld a, 0x003F
|
||||
cp a, #0x45
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleEqual:
|
||||
ld a, 0x003F
|
||||
cp a, #0x3D
|
||||
jreq _App_RoleNext
|
||||
jra _App_RoleReset
|
||||
_App_RoleNext:
|
||||
inc 0x004B
|
||||
ret
|
||||
_App_RoleReset:
|
||||
mov 0x004B, #0x00
|
||||
ret
|
||||
|
||||
; 发送AT+ROLE2\r\n,将模块切换到MESH指令模式。
|
||||
_App_SetRole2:
|
||||
mov 0x003E, #0x41
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x54
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x2B
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x52
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x4F
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x4C
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x45
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x32
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0D
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0A
|
||||
call _App_SendByte
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 发送设备上线广播。
|
||||
;
|
||||
; 业务格式:CMD=07、LEN=00、CRC16_H、CRC16_L。
|
||||
; 外层目标地址固定使用FFFF,表示广播到Mesh网络内的所有设备。
|
||||
; 该函数只在本次上电初始化全部成功后调用一次,用于通知其他设备或主机:
|
||||
; 本设备已经完成ROLE2确认、短地址查询,并进入可通信状态。
|
||||
;
|
||||
; 发送帧格式:
|
||||
; 41 54 2B AA A1 FF FF A0 07 00 CRC_H CRC_L 0D 0A
|
||||
;
|
||||
; 注意:函数不依赖外层接收帧中的源地址,也不把本机地址重复放入业务数据;
|
||||
; 其他设备需要使用JDY-28M接收外层提供的发送方短地址识别本设备。
|
||||
;-------------------------------------------------------------------------------
|
||||
_App_SendOnlineBroadcast:
|
||||
; 准备业务区并计算CMD、LEN的CRC16/MODBUS。
|
||||
mov 0x0040, #0x07
|
||||
mov 0x0041, #0x00
|
||||
call _App_CrcBusiness3
|
||||
|
||||
; JDY-28M Mesh发送固定帧头。
|
||||
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
|
||||
; FFFF表示广播目标地址。
|
||||
mov 0x003E, #0xFF
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0xFF
|
||||
call _App_SendByte
|
||||
; A0表示Mesh数据通信类型。
|
||||
mov 0x003E, #0xA0
|
||||
call _App_SendByte
|
||||
; 发送业务区:07 00 CRC高字节 CRC低字节。
|
||||
mov 0x003E, #0x07
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x00
|
||||
call _App_SendByte
|
||||
ld a, 0x003D
|
||||
ld 0x003E, a
|
||||
call _App_SendByte
|
||||
ld a, 0x003C
|
||||
ld 0x003E, a
|
||||
call _App_SendByte
|
||||
; 保留JDY-28M发送帧尾0D 0A。
|
||||
mov 0x003E, #0x0D
|
||||
call _App_SendByte
|
||||
mov 0x003E, #0x0A
|
||||
call _App_SendByte
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 查询JDY-28M本机Mesh短地址。
|
||||
; 发送AT+MADDR\r\n,解析+MADDR=XXXX。地址保存到0x0046/0x0047,
|
||||
|
||||
@@ -50,11 +50,18 @@ int _Asm_TIM4Vector
|
||||
; 因此从0x8501开始读取三字节入口地址,避免写死主程序启动地址。
|
||||
_Asm_Reset:
|
||||
sim
|
||||
; STM8复位后的默认HSI时钟为16MHz/8,约2MHz。
|
||||
; Bootloader和应用程序均使用16MHz系统时钟。
|
||||
ldw x, #0x03FF
|
||||
ldw sp, x
|
||||
mov 0x50C6, #0x00
|
||||
call _Asm_SPI_Init
|
||||
call _Asm_SPI_CheckJedec
|
||||
; 外部Flash故障时不阻止已有主程序运行。
|
||||
tnz a
|
||||
jrne _Asm_JumpApplication
|
||||
jreq _Asm_SpiOK
|
||||
jpf _Asm_JumpApplication
|
||||
_Asm_SpiOK:
|
||||
; PA3配置为带上拉输入,按键按下时读取为低电平。
|
||||
mov 0x5002, #0x00
|
||||
mov 0x5003, #0x08
|
||||
@@ -84,7 +91,9 @@ _Asm_WaitOneMs:
|
||||
jrne _Asm_KeyWaitLoop
|
||||
; 长按已超过5秒,优先执行出厂固件恢复。
|
||||
call _Asm_CheckFactoryMetadata
|
||||
jrne _Asm_NormalBoot
|
||||
jreq _Asm_FactoryKeyMetaOK
|
||||
jpf _Asm_NormalBoot
|
||||
_Asm_FactoryKeyMetaOK:
|
||||
jpf _Asm_FactoryRecovery
|
||||
|
||||
_Asm_NormalBoot:
|
||||
@@ -223,21 +232,21 @@ _Asm_CheckFactoryMetadata:
|
||||
; 读取4字节固件长度,只接受不超过应用区的长度。
|
||||
ldw x, #0x2004
|
||||
call _Asm_SPI_ReadByteX
|
||||
; 长度为高字节在前:前两个字节必须为0。
|
||||
tnz a
|
||||
jrne _Asm_FactoryMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
tnz a
|
||||
jrne _Asm_FactoryMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
ld 0x0000, a
|
||||
ld 0x000E, a
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
ld 0x0001, a
|
||||
ld 0x000F, a
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
; 当前应用区小于64KB,高16位必须为0。
|
||||
tnz a
|
||||
jrne _Asm_FactoryMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
tnz a
|
||||
jrne _Asm_FactoryMetadataInvalid
|
||||
; 长度不能为0,也不能超过0x1800字节的应用空间。
|
||||
ld a, 0x0000
|
||||
or a, 0x0001
|
||||
@@ -287,20 +296,21 @@ _Asm_CheckOtaMetadata:
|
||||
jrne _Asm_OtaMetadataInvalid
|
||||
ldw x, #0x5004
|
||||
call _Asm_SPI_ReadByteX
|
||||
; OTA长度同样为4字节高字节在前,前两个字节必须为0。
|
||||
tnz a
|
||||
jrne _Asm_OtaMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
tnz a
|
||||
jrne _Asm_OtaMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
ld 0x0000, a
|
||||
ld 0x000E, a
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
ld 0x0001, a
|
||||
ld 0x000F, a
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
tnz a
|
||||
jrne _Asm_OtaMetadataInvalid
|
||||
incw x
|
||||
call _Asm_SPI_ReadByteX
|
||||
tnz a
|
||||
jrne _Asm_OtaMetadataInvalid
|
||||
ld a, 0x0000
|
||||
or a, 0x0001
|
||||
jreq _Asm_OtaMetadataInvalid
|
||||
@@ -460,7 +470,9 @@ _Asm_CrcBitLoop:
|
||||
srl 0x000B
|
||||
rrc 0x000A
|
||||
ld a, 0x000A
|
||||
xor a, #0xA1
|
||||
; CRC-16/MODBUS反射算法多项式为0xA001,拆成寄存器操作时:
|
||||
; 高字节异或0xA0,低字节异或0x01。
|
||||
xor a, #0x01
|
||||
ld 0x000A, a
|
||||
ld a, 0x000B
|
||||
xor a, #0xA0
|
||||
@@ -493,7 +505,8 @@ _Asm_ValidateFactoryImage:
|
||||
ld a, #0xFF
|
||||
ld 0x000A, a
|
||||
ld 0x000B, a
|
||||
ldw x, 0x0004
|
||||
; 出厂固件镜像从外部Flash 0x000000开始,不能跳过前4字节向量表。
|
||||
ldw x, #0x0000
|
||||
ldw y, 0x0000
|
||||
_Asm_ValidateLoop:
|
||||
ld a, 0x0000
|
||||
@@ -518,26 +531,32 @@ _Asm_ValidateFail:
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; 擦除内部应用区0x8500~0x9FFF,每次擦除一个64字节块。
|
||||
; 擦除内部应用区0x8500~0x9FFF。
|
||||
; 这里采用STM8官方支持的字节擦除方式:逐地址写入0x00并等待EOP。
|
||||
; 不使用块擦除模式,避免块擦除关键步骤必须从RAM执行的问题。
|
||||
;-------------------------------------------------------------------------------
|
||||
_Asm_EraseApplication:
|
||||
ld a, #0x56
|
||||
ld 0x5062, a
|
||||
ld a, #0xAE
|
||||
ld 0x5062, a
|
||||
ldw x, #0x8500
|
||||
_Asm_EraseBlock:
|
||||
ld a, #0x20
|
||||
; 退出块擦除/块编程模式,恢复普通字节擦除/编程方式。
|
||||
ld a, #0x00
|
||||
ld 0x505B, a
|
||||
ld a, #0xDF
|
||||
ld a, #0xFF
|
||||
ld 0x505C, a
|
||||
ldw x, #0x8500
|
||||
_Asm_EraseByte:
|
||||
; 字节擦除由向目标地址写入0x00触发;写指令后CPU会等待本次操作完成。
|
||||
ld a, #0x00
|
||||
ld (x), a
|
||||
_Asm_EraseWait:
|
||||
; 本项目STM8S003寄存器定义中,FLASH_IAPSR.EOP为bit2(掩码0x04)。
|
||||
; 置1表示当前擦除操作已完成。
|
||||
btjf 0x505F, #2, _Asm_EraseWait
|
||||
addw x, #64
|
||||
incw x
|
||||
cpw x, #0xA000
|
||||
jrne _Asm_EraseBlock
|
||||
jrne _Asm_EraseByte
|
||||
ld a, #0x00
|
||||
ret
|
||||
|
||||
@@ -549,7 +568,15 @@ _Asm_CopyFactoryImage:
|
||||
ld 0x0000, a
|
||||
ld a, 0x000F
|
||||
ld 0x0001, a
|
||||
ldw x, 0x0004
|
||||
; 搬运阶段采用STM8的单字节编程方式:
|
||||
; 只向目标地址执行一次写操作,不设置块编程CR2/NCR2模式。
|
||||
; 这样每个字节写入后都可以单独等待EOP完成。
|
||||
ld a, #0x00
|
||||
ld 0x505B, a
|
||||
ld a, #0xFF
|
||||
ld 0x505C, a
|
||||
; 搬运必须从出厂镜像第0字节开始,保持应用向量表与0x8500对齐。
|
||||
ldw x, #0x0000
|
||||
ldw y, #0x8500
|
||||
_Asm_CopyLoop:
|
||||
ld a, 0x0000
|
||||
@@ -557,13 +584,11 @@ _Asm_CopyLoop:
|
||||
jreq _Asm_CopyOK
|
||||
call _Asm_SPI_ReadByteX
|
||||
ld 0x000C, a
|
||||
ld a, #0x01
|
||||
ld 0x505B, a
|
||||
ld a, #0xFE
|
||||
ld 0x505C, a
|
||||
ld a, 0x000C
|
||||
ld (y), a
|
||||
_Asm_CopyWait:
|
||||
; 本项目STM8S003寄存器定义中,FLASH_IAPSR.EOP为bit2(掩码0x04)。
|
||||
; 置1表示当前字节编程已完成。
|
||||
btjf 0x505F, #2, _Asm_CopyWait
|
||||
incw x
|
||||
incw y
|
||||
@@ -582,7 +607,8 @@ _Asm_VerifyApplication:
|
||||
ld 0x0000, a
|
||||
ld a, 0x000F
|
||||
ld 0x0001, a
|
||||
ldw x, 0x0004
|
||||
; 回读校验与写入过程使用同一个外部Flash起始地址0x000000。
|
||||
ldw x, #0x0000
|
||||
ldw y, #0x8500
|
||||
_Asm_VerifyLoop:
|
||||
ld a, 0x0000
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# 汇编 Bootloader 出厂固件恢复验证记录
|
||||
|
||||
## 1. 验证目的
|
||||
|
||||
验证当 STM8S003F3P6TR 内部应用 Flash 没有主程序时,汇编 Bootloader 能否从外部 W25Q80BV 读取出厂固件,完成内部应用区恢复,并跳转到主程序执行。
|
||||
|
||||
## 2. 当前地址规划
|
||||
|
||||
| 区域 | 地址范围 | 说明 |
|
||||
|---|---:|---|
|
||||
| Bootloader | `0x8000~0x84FF` | 固定汇编 Bootloader 区域 |
|
||||
| 主程序 | `0x8500~0x9FFF` | 汇编主程序运行区域 |
|
||||
| 出厂固件 | W25Q80BV `0x000000~0x001FFF` | 最大 8KiB |
|
||||
| 出厂元数据 | W25Q80BV `0x002000~0x002FFF` | 4KiB 区域 |
|
||||
|
||||
## 3. 外部 Flash 内容验证
|
||||
|
||||
独立 SPI 测试固件已经读取并校验出厂镜像,串口输出如下:
|
||||
|
||||
```text
|
||||
SPI TEST START
|
||||
JEDEC OK
|
||||
MAGIC: 31434146
|
||||
LENGTH: 06AE
|
||||
VERSION: 0100
|
||||
STORED CRC: DF89
|
||||
VALID: A5
|
||||
CALC CRC: DF89
|
||||
FACTORY IMAGE PASS
|
||||
```
|
||||
|
||||
因此确认:
|
||||
|
||||
- W25Q80BV JEDEC ID 读取正常;
|
||||
- 出厂固件魔数正确;
|
||||
- 出厂固件长度为 `0x06AE`;
|
||||
- 出厂固件版本为 `0x0100`;
|
||||
- 元数据保存 CRC 为 `0xDF89`;
|
||||
- Bootloader 有效标志为 `0xA5`;
|
||||
- 单片机重新计算 CRC 后仍为 `0xDF89`。
|
||||
|
||||
## 4. Bootloader 恢复流程
|
||||
|
||||
当前汇编 Bootloader 的出厂恢复流程为:
|
||||
|
||||
1. 设置系统时钟为 16MHz;
|
||||
2. 初始化 SPI1;
|
||||
3. 读取并检查 W25Q80BV JEDEC ID;
|
||||
4. 检查出厂固件元数据;
|
||||
5. 按 `CRC-16/MODBUS` 逐字节校验出厂固件;
|
||||
6. 解锁 STM8 程序 Flash;
|
||||
7. 对 `0x8500~0x9FFF` 应用区逐字节擦除;
|
||||
8. 从 W25Q80BV `0x000000` 开始逐字节搬运到内部 Flash `0x8500`;
|
||||
9. 回读内部 Flash,并与外部出厂镜像逐字节比较;
|
||||
10. 跳转到主程序复位向量执行。
|
||||
|
||||
## 5. 现场验证结果
|
||||
|
||||
验证前先清除内部 Flash 的主程序区域,使 `0x8500` 之后没有有效主程序。重新烧录 Bootloader 并上电后,设备能够自动完成出厂固件搬运,随后主程序正常启动,并输出蓝牙初始化相关信息:
|
||||
|
||||
```text
|
||||
AT+ROLE
|
||||
AT+ROLE2
|
||||
AT+ROLE
|
||||
AT+ROLE2
|
||||
AT+ROLE
|
||||
```
|
||||
|
||||
验证结论:
|
||||
|
||||
**内部 Flash 无主程序时,Bootloader 自动从 W25Q80BV 搬运出厂固件并成功启动主程序,功能验证通过。**
|
||||
|
||||
## 6. 调试代码清理
|
||||
|
||||
为便于现场确认,曾临时加入 Bootloader UART1 阶段输出。恢复流程验证完成后,已经删除:
|
||||
|
||||
- Bootloader UART1 初始化代码;
|
||||
- 临时阶段字符输出;
|
||||
- 元数据诊断输出;
|
||||
- CRC、擦除、搬运、回读和跳转诊断输出。
|
||||
|
||||
正式 Bootloader 不再占用 UART1 发送调试功能。
|
||||
|
||||
## 7. 本次修正的关键问题
|
||||
|
||||
### 7.1 元数据长度解析
|
||||
|
||||
固件长度字段是 4 字节高字节在前:
|
||||
|
||||
```text
|
||||
00 00 06 AE
|
||||
```
|
||||
|
||||
前两个字节必须为 `0x00`,后两个字节才是实际长度 `0x06AE`。
|
||||
|
||||
### 7.2 CRC 多项式
|
||||
|
||||
`CRC-16/MODBUS` 反射算法使用多项式 `0xA001`。在当前两个字节寄存器实现中:
|
||||
|
||||
```text
|
||||
高字节异或:0xA0
|
||||
低字节异或:0x01
|
||||
```
|
||||
|
||||
### 7.3 STM8 Flash 擦除方式
|
||||
|
||||
块擦除流程要求连续装载 4 个字节,并且关键步骤需要从 RAM 执行。当前 Bootloader 采用逐字节擦除方式,向应用区每个地址写入 `0x00`,每次等待 EOP 完成,避免块擦除流程在 Flash 中执行时卡住。
|
||||
|
||||
### 7.4 EOP 标志
|
||||
|
||||
当前项目使用的 STM8S003 寄存器定义中,`FLASH_IAPSR.EOP` 掩码为 `0x04`,即 bit2。Bootloader 按 bit2 检测擦除和写入完成状态。
|
||||
|
||||
## 8. 当前验证状态
|
||||
|
||||
### 8.1 已完成并通过
|
||||
|
||||
- 外部 W25Q80BV JEDEC ID 读取;
|
||||
- 外部 Flash 出厂元数据读取;
|
||||
- 出厂固件 CRC16 校验;
|
||||
- 内部 Flash 无主程序时自动恢复出厂固件;
|
||||
- 主程序复位向量 `0x8500` 损坏时自动恢复;
|
||||
- 内部 Flash 应用区擦除;
|
||||
- 出厂固件逐字节搬运;
|
||||
- 内部 Flash 回读校验;
|
||||
- Bootloader 成功跳转主程序;
|
||||
- PA3 上电长按超过 5 秒恢复出厂固件;
|
||||
- 恢复流程完成后主程序正常启动。
|
||||
|
||||
### 8.2 后续待验证
|
||||
|
||||
以下项目属于主程序 OTA 和整机联调范围,尚未作为当前 Bootloader 的最终验收依据:
|
||||
|
||||
- OTA 固件元数据处理;
|
||||
- OTA 固件搬运和升级结果写回;
|
||||
- OTA 分包丢失、CRC错误和断电恢复;
|
||||
- 主程序向主机上报升级结果;
|
||||
- ACS712 实际电流采样和电流故障判断;
|
||||
- 蓝牙 Mesh 多设备完整联调;
|
||||
- 继电器带实际负载的长期运行测试。
|
||||
|
||||
在上述主程序功能开始前,不应修改已经通过验证的 Bootloader 恢复流程。
|
||||
@@ -36,13 +36,14 @@ DST_L = 0C
|
||||
### 1.2 接收端串口收到的 Mesh 数据格式
|
||||
|
||||
```text
|
||||
F1 DD 09 | SRC_H SRC_L | DST_H DST_L | BUSINESS | 0A
|
||||
F1 DD 08/09/0A | SRC_H SRC_L | DST_H DST_L | BUSINESS | 0A
|
||||
```
|
||||
|
||||
这是接收方 UART 侧看到的接收格式,不是设备或主机发送时使用的格式。
|
||||
其中前两字节是发送方短地址,后两字节是接收方短地址;业务帧中不再重复
|
||||
嵌入发送方或接收方地址。部分模块/串口工具可能显示为`0D 0A`,设备解析器
|
||||
兼容`0A`和`0D 0A`两种接收表现。
|
||||
嵌入发送方或接收方地址。根据当前实测,第三字节可能显示为`08`、`09`或`0A`,
|
||||
设备和上位机解析器应兼容这三种表现。帧尾通常为`0A`,部分模块或串口工具可能
|
||||
显示为`0D 0A`;其中`0D`属于外层结束符,不能计入业务数据。
|
||||
|
||||
无论发送方是主机还是设备,发送时都统一使用第1.1节的格式:
|
||||
|
||||
@@ -202,7 +203,7 @@ DATA[0] = 当前继电器控制状态
|
||||
设备接收侧看到:
|
||||
|
||||
```text
|
||||
F1 DD 09 34 FA 35 FA 01 01 01 50 E0 0A
|
||||
F1 DD 0A 34 FA 35 FA 01 01 01 50 E0 0A
|
||||
```
|
||||
|
||||
设备向主机地址`34FA`回复:
|
||||
@@ -217,8 +218,8 @@ F1 DD 09 34 FA 35 FA 01 01 01 50 E0 0A
|
||||
41 54 2B AA A1 FF FF A0 01 01 01 50 E0 0D 0A
|
||||
```
|
||||
|
||||
设备收到广播后执行控制,并向发送方地址回复`0x81`。正式多设备版本应对广播
|
||||
应答增加随机错峰延时,避免多个设备同时发送。
|
||||
设备收到广播后执行控制,并向发送方地址回复`0x81`。广播应答增加随机错峰
|
||||
延时,避免多个设备同时发送造成网络拥塞。
|
||||
|
||||
### 4.2 `0x02` 查询继电器状态
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ CRC正确
|
||||
业务数据帧格式:
|
||||
|
||||
```text
|
||||
SRC_H | SRC_L | CMD | LEN | DATA | CRC16_L | CRC16_H
|
||||
CMD | LEN | DATA | CRC16_H | CRC16_L
|
||||
```
|
||||
|
||||
说明:
|
||||
@@ -224,6 +224,7 @@ SRC_H | SRC_L | CMD | LEN | DATA | CRC16_L | CRC16_H
|
||||
- `LEN`:DATA长度;
|
||||
- `DATA`:命令数据;
|
||||
- `CRC16`:CRC-16/MODBUS,计算范围为 CMD、LEN、DATA,高字节在前;
|
||||
- 发送方和接收方 Mesh 短地址由 JDY-28M 外层帧携带,业务帧不重复添加地址;
|
||||
- CRC错误时静默丢弃,不执行继电器动作,不返回错误帧。
|
||||
|
||||
JDY-28M的Mesh外层帧负责目标地址、源地址、Mesh头和帧尾,业务帧本身不重复添加地址。
|
||||
@@ -238,6 +239,11 @@ JDY-28M的Mesh外层帧负责目标地址、源地址、Mesh头和帧尾,业
|
||||
| `0x04` | 故障/恢复上报 | 设备→主机 | DATA为故障码或恢复码 |
|
||||
| `0x05` | 查询设备短地址 | 主机→设备 | 返回2字节短地址 |
|
||||
| `0x06` | 配置电流阈值 | 主机→设备 | 2字节,单位mA,低字节在前,保存到EEPROM |
|
||||
|
||||
当前汇编主程序已经完成并通过实物验证的命令为:`0x01`、`0x02`和`0x05`。
|
||||
`0x03`、`0x04`、`0x06`、`0x07`以及`0x08~0x0E`属于已确定的协议设计,
|
||||
在当前汇编主程序中尚未全部完成或尚未完成实物验证,上位机不应据此判断当前
|
||||
固件已经支持这些命令。
|
||||
| `0x0E` | 查询当前固件版本 | 主机→设备 | 无DATA |
|
||||
| `0x0D` | OTA升级结果上报 | 设备→主机 | DATA为升级结果码 |
|
||||
|
||||
|
||||
Binary file not shown.
@@ -13,8 +13,9 @@ from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
# 主程序Makefile生成的裸二进制文件,已经去除了Bootloader地址空间。
|
||||
FIRMWARE_PATH = Path("build/mesh_relay.bin")
|
||||
# 当前汇编主程序Makefile生成的裸二进制文件,已经去除了Bootloader地址空间。
|
||||
# 该文件从应用链接地址0x8500对应的第一个字节开始,不包含Bootloader。
|
||||
FIRMWARE_PATH = Path("STM8_app_asm/build/stm8_app_asm.bin")
|
||||
|
||||
# 主程序版本定义文件,脚本会自动读取其中的FIRMWARE_VERSION宏。
|
||||
VERSION_HEADER_PATH = Path("include/app_config.h")
|
||||
@@ -95,7 +96,7 @@ def main() -> None:
|
||||
if not FIRMWARE_PATH.is_file():
|
||||
raise SystemExit(
|
||||
f"找不到固件文件:{FIRMWARE_PATH}\n"
|
||||
"请先在项目根目录执行 make。"
|
||||
"请先执行 make -C STM8_app_asm。"
|
||||
)
|
||||
|
||||
firmware = FIRMWARE_PATH.read_bytes()
|
||||
|
||||
@@ -385,14 +385,12 @@ def main() -> None:
|
||||
parser.add_argument("--port", help="直接指定电脑串口;不指定时启动后扫描并选择")
|
||||
parser.add_argument("--baudrate", type=int, default=9600, help="串口波特率,默认9600")
|
||||
parser.add_argument("--timeout", type=float, default=0.2, help="串口读取超时,默认0.2秒")
|
||||
parser.add_argument("--response-timeout", type=float, default=3.0, help="设备应答等待时间,默认10秒")
|
||||
parser.add_argument("--response-timeout", type=float, default=10.0, help="设备应答等待时间,默认10秒")
|
||||
args = parser.parse_args()
|
||||
|
||||
selected_port = args.port or select_serial_port()
|
||||
with serial.Serial(selected_port, args.baudrate, timeout=args.timeout) as port:
|
||||
print(f"已打开串口:{selected_port},波特率:{args.baudrate}")
|
||||
run_menu(port, args.response_timeout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user