shuichan_new/STM32F103_App1
helei 7cfb659699 联网选择页第3行去掉上次前缀:模式名从最左显示+秒数紧随,修复长模式名时秒数超宽绕回左边 2026-09-18 15:35:05 +08:00
..
.vscode V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00
Drivers V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00
HardWare 联网选择页第3行去掉上次前缀:模式名从最左显示+秒数紧随,修复长模式名时秒数超宽绕回左边 2026-09-18 15:35:05 +08:00
Inc V1.7.1: 485扩展继电器板支持(4路带反馈,Modbus RTU从机0x10) 2026-09-04 21:08:56 +08:00
MDK-ARM 指令应答可靠化:新增NET_publish_response_wait(等OK+重试2次),继电器/扩展板反馈应答换用,修复动作了但平台响应超时 2026-09-14 10:23:05 +08:00
Middlewares/FreeRTOS/Source V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00
Src V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00
m_app 移植4G MQTT主动健康检查(每60s查MQTTSTATU,连续3次不在线复位,OTA期间暂停;RSN_MQTT_LOST=18),根治断网假死 2026-09-18 14:56:01 +08:00
README.md V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00
keilkill.bat V1.7.0: 新硬件板型引脚重排(不兼容老板,迁至shuichan_new) 2026-09-04 20:09:04 +08:00

README.md

智能水产投料机 V1.6.0FreeRTOS · 串口日志中文版)

本版本基于 FreeRTOS 实时操作系统V10.0.1net/app/sys 三任务架构), 串口日志为中文输出(GBK 编码,串口工具/蓝牙 APP 字符集请选 GBK

工程概述

本工程基于 STM32F103RCT6(兼容 GD32F103RCT6+ STM32Cube HAL + FreeRTOS V10.0.1 + Keil MDK,实现设备的控制、联网与投喂。

  • BootLoaderSTM32F103_BootLoader/,负责 OTA 固件烧录、试运行计数与失败回滚、主电源门禁
  • 应用固件STM32F103_App1/,当前运行版本 V1.6.0FreeRTOS 版,全中文串口日志)
  • 双网络通道4G(Air780E/EG) 优先WiFi(ESP-01S) 兜底,见下文"双通道网络"
  • 蓝牙调试HLK-B40 BLE 透传模块USART1上电自动配置名称/GATT UUID见下文"蓝牙模块配置"

注意Keil 工程必须带编译选项 --no_multibyte_charsApp1 与 BootLoader 均已配置), 使 ARMCC 不解读多字节字符中文字符串GBK/UTF-8 均可)字节原样透传输出。 missing closing quote 编译错误。

FreeRTOS 任务架构

任务 优先级 职责
net_task 3 3KB 网络状态机 NET_process4G/WiFi 建链与切换、MQTT 收发、GPS 轮询、OTA 下载
app_task 2 2KB 业务:继电器命令/反馈状态机、断电记忆恢复、投喂秤轮询与保护、状态上报、OTA 确认与健康检查
sys_task 1 1KB 系统LED、电量1s、蓝牙调试命令、Uptime 打印(联网后每 60s
idle 钩子 - - 喂 IWDG任务死循环占 CPU → idle 饿死 → 复位,正是期望行为)

关键设计:

  • HAL 时基 = TIM21ms 中断,Src/stm32f1xx_hal_timebase_tim.c,注意 HAL_TIM_PeriodElapsedCallback 里必须 HAL_IncTick()SysTick 归 FreeRTOS 当 RTOS tickit.c 不再定义 SVC/PendSV/SysTick 三个 Handlerport.c 提供,经 FreeRTOSConfig.h 宏映射)
  • bg_delay 改造:调度器运行中 = vTaskDelay 让出;调度器未启动(开机初始化)= HAL_Delay + 喂狗
  • 互斥锁三把(调度器启动前全部为空操作直通):
    • s_net_at_mutexnetwork.cnet 任务长 AT 会话(建链/重连/OTA持锁其他任务 NET_publish_* 尝试锁、拿不到丢弃,防 AT 应答交织
    • s_storage_mutexapp_loop.c storage_lockEEPROM(软件I2C) 与 W25Q64(软件SPI) 跨任务互斥,递归锁
    • s_log_mutexlog.c日志输出不交织
  • 内存预算48KB RAM裸机存量 ZI ~36.3KB + FreeRTOS 堆 10KBconfigTOTAL_HEAP_SIZE≈ 46.3KBFlash ~67KB/224KB 分区。中断优先级维持原值UART/DMA 0~3 级,不调 RTOS API低于 5 级 syscall 阈值无冲突)

产品型号配置

同一套板子适配两种产品,编译前在 Inc/main.h 顶部通过一个宏二选一:

#define PRODUCT_WITH_FEED_SCALE   0
取值 产品 投喂行为
1 智能水产投料机(接 RS485 磅秤) feedingonceWt 定量自动投喂,带重量反增/超时保护;sw1 指令被忽略
0 基础四路继电器(不接磅秤) feeding / sw1 直接开关投喂继电器 1由平台侧自行计时

硬件接口

接口 用途 引脚 参数
USART1 蓝牙串口调试 / 日志输出 PA9/PA10 115200-8N1
USART2 RS485 喂料秤 PA2/PA3 9600-8N1
USART3 4G Air780E/EG MQTT+GNSS PB10/PB11 921600-8N1见下文波特率自适应
UART4 WiFi ESP-01S PC10/PC11 115200→921600-8N1见下文 WiFi 波特率同步)
UART5 HLW8032 电能计量 PD2(RX) 4800-8E1只收芯片每 50ms 主动上报
ESP_RST WiFi 模块复位 PA5 低电平 500ms 复位
CAT_PWR 4G 模块开机 PC4 开漏输出,拉低=模块开机(上电即拉低)
ESP_EN WiFi 模块使能 PA6 推挽输出,高电平=使能;默认关闭4G 运行时拉低断电省电,切 WiFi 才拉高
BT_MODE 蓝牙 AT/透传切换 PB0 开漏输出,拉低 1s 进 AT 模式ble_at.c 控制)
BT_RST 蓝牙模块复位 PA7 推挽输出,低电平=工作(默认),高电平=复位
OLED 屏幕 128x64 + GB2312 字库 IC CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7 FSO=PC6 CS2=PB15 GPIO 模拟 SPI显示电压/电流/功率/电能/继电器/网络
Relay1 投喂电机控制 PB7 高电平吸合
Relay2~4 备用控制 PB8/PC0/PC2 高电平吸合
I2C软件 AT24C02 配置存储 PA11/PA12 页写入
SPI软件 W25Q64 外部 Flash PB3/PB4/PA15/PB5 OTA 固件暂存
ADC1 电池电压检测 PA0 低电量上报 pow:0
PA8 手动模式检测 PA8 高电平=手动模式
PA1 NET_LED 联网指示 PA1 低电平点亮,见下表
PC13 LED2 心跳 / TPL5010 喂狗 PC13 每 1s 翻转(见下文看门狗)

指示灯含义

状态 含义
NET_LEDPA1 100ms 快闪 SmartConfig 智能配网中,请用 ESP-Touch APP 广播密码
NET_LEDPA1 500ms 慢闪 联网中4G/WiFi 建链、重连)
NET_LEDPA1 常亮 已联网MQTT 就绪)
LED2PC13 1s 心跳闪烁 系统正常运行(兼任 TPL5010 外置看门狗喂狗)

看门狗(双狗架构)

看门狗 超时 喂狗点 作用
IWDG片内独立看门狗 ~26sLSI 漂移下最短 ~17s APP 由 FreeRTOS idle 钩子喂养(vApplicationIdleHookBL 的擦除/拷贝循环 固件卡死 → 复位BL 启动OTA 试运行期计一次试错
TPL5010外置硬件狗 7 分钟 PC13(LED2) 电平翻转APP activeEvents() 每秒翻转BL 进入时及长循环中翻转 MCU 完全失控(含 IWDG 失效)时的最后兜底

注意LED2 同时承担运行指示和外部狗喂狗,修改 LED2 闪烁逻辑时必须保持周期性翻转,否则 7 分钟后整板复位。

重启记录与断电保护

重启记录reset_log.c

  • 开机时("固件版本"日志后)打印最近 10 条重启记录:重启原因 + 上次运行时长
  • 存储W25Q64 0x100000 一个 4KB 扇区10 条 32B 记录环形覆盖(序号+原因码+运行秒数+CRC16写满一圈擦一次断电不丢
  • 原因来源RCC->CSR 由 BootLoader 读入后放共享 RAMBOOT_SHARE,地址 0x2000BF00,热复位保持)传给 APP软件复位的细分原因由各复位点 reset_log_mark() 预写(被平台踢下线/心跳超时/4G无服务/无SIM/OTA升级/OTA健康超时/4G初始化失败/串口RESET/OTA/RECOVERY命令/4G模组自重启等
  • 运行时长:每秒写入共享 RAM热复位不丢看门狗/软件复位后精确到秒

断电保护(法拉电容 + PA0 检测)

  • APPPA0 电量检测改为 50ms 快轮询(原 1s<10%=外部 12V 断开)立即 50ms 快速复核 2 次(检测+确认全程 ~150ms先发 pow:0 后打日志 → 写电能/断电记录 → 留 500ms 让模组把报文发完 → 复位。发送通道忙(如 GPS 轮询占锁)时不锁存、下个周期重试直到发出或电容耗尽。电量百分比做了 0~100% 限幅(电容放电时 3.3V 基准下跌会失真)
  • BootLoader跳转 APP 前读 PA0寄存器直读 ADC主电源未恢复<约 11V则每 500ms 检测原地等待,不跳 APP——防止单片机靠电容低压运行 APP 而外设没电的乱态12V 恢复后正常放行
  • 断电记录只被下次成功启动消费一次(boot_seen 位翻转标记),一次掉电 = 一条记录BL 等待期间不产生记录
  • BL 找不到有效 APP如只刷了 BL打印提示并每 3s 主动复位重试,刷入 APP 即恢复

电能计量HLW8032

只计量累计消耗电量,上报平台标识符 energy(单位 kWh两位小数

  • 硬件HLW80325V 供电)经 B0505S 隔离电源 + 光耦隔离,芯片 TXD 接 PD2UART5_RX电压采样 ZMPT107-12mA:2mA初级 220V 串 100K次级并 49.9Ω),电流采样 CT 100A/100mA1000:1输出并 2Ω三个模拟脚各对地 33nF
  • 通信4800bps 8E1F1 上按 9 位帧长 + 偶校验配置),芯片每 50ms 主动上报 24 字节帧,只收不发;帧同步 = 首字节 0x55/0xAA/0xFx + 第二字节 0x5A + 校验和([2]~[22] 相加低 8 位)
  • 电量算法(手册公式):kWh = 总脉冲 × 功率参数寄存器 × 电压系数 × 电流系数 / (10^9×3600)PF 寄存器 16 位溢出由数据更新寄存器 bit7 翻转计数(总脉冲 = k×65536 + PF50ms 一帧不可能漏翻转;芯片断电重启(计数值倒退)自动重新基准
  • 系数宏main.hHLW_U_COEF=100K/49.9/1000=2.004)、HLW_I_COEF=1/(0.002×1000)=0.5)——改电阻/互感器只动这两个宏
  • 防噪声假同步:要求连续 3 帧且间隔 20~150ms 才认定在线PD2 悬空时电磁干扰偶发也能凑出校验和正确的假帧,但不可能连续卡在 50ms 节奏上)
  • 滤波与零漂:电压/电流/功率经 EMA 滑动平均(系数 1/8约 0.4s 响应)压空载跳变;电流再减零点偏移 HLW_I_ZERO_A(0.02A) 钳位到 0功率死区 HLW_P_DEAD_W(2W) 以下显示 0、超出再减 HLW_P_ZERO_W(0.5W)——宏都在 main.h
  • 持久化累计脉冲数uint64存 EEPROM 偏移 164~171每攒约 0.1kWh 写一次(限制擦写),所有计划内复位(reset_log_mark 覆盖的复位点)复位前写一次,断电预警(法拉电容续航内)再写一次——除突然死机外基本不丢电量
  • 上报策略:无论功率多少,每 3 分钟上报一次 {"energy":%.2f,"power":%.1f};继电器开关切换随状态大报文携带 energy。串口电能日志固定 10s 一条。间隔宏 ENERGY_REPORT_INTERVAL_MS 在 app_loop.c
  • 调试日志:芯片在线时每 10s 打印一条(电能: x.xxkWh 电压: xxx.xV 电流: x.xxA 功率: xxxx.xW),与上报节奏无关

OLED 屏幕显示128x64 + GB2312 字库 IC

  • 移植自《485模拟磅秤数据带CRC》的 oledisp 驱动GPIO 模拟 SPI屏幕 CS1 + 字库 CS2 两路片选,字库数据从 FSO 脚读回)
  • 字符串直接写中文(工程源文件即 GBK 编码,字库 IC 按 GB2312 地址换算取字模)
  • 已联网界面sys_task 每 1s 刷新):第 0 行 网络 + 运行时间(WiFi 00:02:35,天:时:分),第 1 行 电压/电流,第 2 行 功率/电能(计量芯片离线时显示"计量芯片离线"),第 3 行 继电器状态中文(开关:开开关关
  • 未联网界面:只显示联网进度,不显示电能/开关——4G 模式显示"正在等待4G联网"+ 最近完成阶段(模块复位成功/AT指令成功/SIM卡检测成功/附着网络成功/连接服务器中/无SIM卡状态由 g_net_ui_phase 从 air780e.c 各阶段更新WiFi 固定热点显示"WiFi固定模式 正在搜索热点"+倒计时SmartConfig 显示"智能配网 请打开APP广播"+倒计时(倒计时由 esp_wait_countdown 写入 g_net_ui_countdown
  • 开机欢迎页按产品模式显示标题(带秤=智能水产投料机,无秤=继电器控制终端+ V1.5.0 RTOS4G 阻塞建链期间由 bg_delay 钩子每 500ms 刷进度页(调度器启动前 sys_task 未运行,曾一直卡在欢迎页)

双通道网络4G 优先WiFi 兜底)

网络层 HardWare/NETWORK/network.c 对业务层屏蔽底层模组差异,业务只调 NET_* 接口4G 后端(CAR/air780e.c,模组内置 MQTT与 WiFi 后端(NETWORK/net_wifi.cTCP 透传 + 软件 MQTT同构运行时互斥切换。

通道切换状态机

EEPROM偏移 130记忆上次成功联网方式4G / WiFi固定热点 / WiFi配网热点),仅方式变化时写一次;开机优先跑上次成功的方式:

上次 4G 成功4G无 SIM/无信号每 10s 重试3 次失败)→ WiFi → 回 4G…循环
上次 WiFi 成功WiFi 一轮建链失败 → 4G同上重试→ 回 WiFi…循环

WiFi 建链子顺序net_wifi_init阻塞式等待期间 vTaskDelay 让出 CPU
  ① 上次是配网热点AT+CIPSTATUS 查已存 AP 自动连接10sSTATUS:2=已拿到IP
  ② AT+CWJAP 连代码里固定的 SSID/PASSesp8266.h 的 WIFI_SSID/WIFI_PASS60s 倒计时)
  ③ ESP-Touch SmartConfig 智能配网60s 倒计时,配到的热点由模块保存)
  全部失败 → 回 4G
WiFi 运行30s MQTT PINGREQ 心跳,连续 2 帧无应答(约 60s判死TCP 断开(CLOSED)/心跳超时 → 打印原因并直接整机复位重连
  CLOSED=服务器主动断开/被平台踢下线ping 超时=服务器无响应/假在线,最坏检测延迟 ~30s

注:已存 AP 的判断用 AT+CIPSTATUS 主动查询而非被动等 WIFI GOT IP 打印—— 后者在波特率同步阶段就会被清缓冲擦掉,永远等不到。

WiFi 通道实现要点

  • TCP 透传 + 软件 MQTTESP-01S 用 AT+CIPMODE=1 + AT+CIPSEND 进透传MQTT 3.1.1 协议栈在 MCU 软件实现(HardWare/WIFI/mqtt_client.c),不用 AT+MQTT
  • WiFi 波特率同步ESP-01S 出厂/RESTORE 后 115200初始化时探测并用 AT+UART_DEF=921600 固化掉电保存MCU 侧 UART4_SetBaudRate() 同步;失败自动回滚维持 115200
  • 接收缓冲共享WiFi 与 4G 互斥运行,共用 Rx_Buf/U2_CopyBuffstm32f1xx_it.c 两路 IDLE 均带激活后端守卫RAM 增量 <2KB
  • AT 应答严格匹配:发送后才清接收缓冲(防上一条应答迟到尾巴造成"假成功")、二进制安全匹配、收到 ERROR/FAIL 立即失败;进透传前先 AT+CIPCLOSE 清残留连接、CIPMUX=0 先于 CIPMODE=1
  • ORE 自愈921600 高波特率下 UART 溢出会让 HAL 中止 DMA 接收,HAL_UART_ErrorCallback 里按当前激活通道立即重新武装DMA 填满(连续流无 IDLE 间隙)时在 HAL_UART_RxCpltCallback 里搬运并重启,防接收停摆
  • WiFi 通道 OTA:支持,透传 TCP 上软件实现 HTTP Range 分块下载,见下文"OTA 升级"章节

4G 波特率自适应115200 → 921600

  • Air780E 出厂/自适应模式只覆盖 9600~115200921600 必须用 AT+IPR 固定。
  • APP 联网时永远先按 115200 探测(避免向自适应侦测窗口发送其他波特率的乱码):
    • 通 → 发 AT+IPR=921600 + AT&W 固化,切换到 921600日志出现 baud -> 921600
    • 不通(模块已固化 921600→ 直接切 921600 连接。
  • 切换由 USART3_SetBaudRate() 运行中重写 BRR 完成,不影响 GPIO/DMA/NVIC。

4G 信号检测AT+CSQ

  • 网络附着CGATT成功后查询一次 AT+CSQrssi(0~31) 转百分比串口输出:> 4G: signal 77% (rssi=24)
  • 无信号rssi=99 或 <2g_no_signal_detected,与无 SIM 走同一套重试/回退 WiFi 逻辑
  • 信号弱(<20%):告警但继续联网

GPS 定位上报(仅 4G 通道Air780EG

开关运行时可切与产品模式同机制——EEPROM(173) 存 0/1空白时用 Inc/main.hPRODUCT_WITH_GPS 宏做默认值;蓝牙命令 GPS 查询、GPS0 关 / GPS1 开,切换后自动重启生效。关闭后不进行任何 GPS 相关 AT 交互(纯 Air780E 用)。

参考工程《air780EG-V1.1加入北斗GPS数据》移植实现air780e.cair780e_gps_poll())。

工作流程

  1. GNSS 初始化4G 拨号(AT+SAPBR=1,1)成功后执行 AT+CGNSPWR=1(开 GNSS 电源)+ AT+CGNSAID=31,1,1,1A-GPS 辅助定位)。非 EG 版本模组回 ERROR 时仅告警并关闭轮询(s_gnss_present=0),不影响联网
  2. 周期查询:联网 60 秒后首次查询(GPS_FIRST_DELAY_MS,留搜星时间),之后每 5 分钟一次(GPS_REPORT_INTERVAL_MSAT+CGNSINF
  3. 解析+CGNSINF: run,fix,UTC,lat,lon,...,手工按逗号切分(不依赖 sscanf scanset定位无效fix≠1跳过本次上报(室内常见),不发空串
  4. 坐标转换WGS-84 → GCJ-02见下节
  5. 上报:模组内置 MQTT AT+MPUBtopic /iot/data/up/<ClientID>

上报格式

云平台字段标识符 GPS经度在前、纬度在后(高德/GeoJSON 习惯):

{"header":"iot.prop.post","body":{"GPS":"115.924092,28.679470"}}

坐标系转换WGS-84 → GCJ-02

GPS 模块输出 WGS-84 原始坐标,直接输入高德地图会有 100~600m 加密偏移(国内地图强制使用 GCJ-02"火星坐标系")。固件在上报前用公开标准算法完成转换(wgs84_to_gcj02(),三角函数近似,中国境外自动跳过),上报的坐标可直接输入高德地图/坐标拾取器。

注意:若改用百度地图,需要再转一层 GCJ-02 → BD-09。

保护机制

  • OTA 下载期间(g_net_tx_busy)禁止 GPS 查询插队,避免污染 HTTP 数据流
  • 查询等待期间有平台下行命令(+MSUB:)插队时主动让行,不吞继电器指令

使用注意

  • GPS 天线需能看到天空:室内基本无法定位(> GPS: no fix, skip 属正常),窗边/阳台通常可定,室外最佳;冷启动首次定位约 1~3 分钟
  • 验证:串口日志 > GPS: 115.924092,28.679470,或直接看平台 GPS 字段

OTA 升级

流程

平台下发 iot.ota.upgrade.post(url, crc16, ver)
  → APP: 模块硬复位 → HTTP HEAD 拿大小 → 擦 W25Q64 槽 B
  → 3072 字节/块分段下载AT+HTTPPARA BREAK/BREAKEND + HTTPACTION + HTTPREAD
  → 每块写 W25Q64 并回读校验,全程累计 CRC16
  → 整包 CRC + 栈顶/复位向量校验 → 可靠上报 result.post等 OK + 重试)
  → 写 OTA 信息区(状态 DOWNLOADED) → 复位进 BootLoader
BootLoader:
  → 擦除备份槽 → 备份当前 APP1 到槽 A失败则中止更新留在旧版本
  → 擦 APP1 → 写入新固件 → CRC + 向量校验 → 状态 INSTALLED → 跳转
新 APP 试运行:
  → MQTT 就绪 + 首次发布成功 + 3s → 上报 confirm.post
  → 确认标志写 W25Q64回读验证 + 3 次重试)→ 版本号存 EEPROM
  → 下次启动 BL 清理 OTA 标志,升级完成

Flash 分区

内部 Flash256KB

区域 地址 大小
BootLoader 0x08000000 32KB
APP1 运行区 0x08008000 224KB

W25Q64 外部 Flash8MB

区域 地址 用途
槽 A 0x000000 (256KB) 当前固件备份(回滚镜像)
槽 B 0x040000 (256KB) 新固件下载暂存
信息区 A/B 0x080000 / 0x081000 (各 4KB) OTA 状态双副本magic + 序号 + checksum

OTA 确认通知可靠性

  • 确认通知(iot.ota.confirm.post)延后到首次发布成功后 9 秒发送避开开机上报风暴pow/ICCID/状态/电能挤占模组 MQTT 发送窗口)
  • 本地确认标志和版本号先于通知落盘——通知发送失败不会导致 BL 误判回滚
  • 通知未送达时每 30s 自动重试直到成功(仅补平台通知);开机 pow/ICCID 上报间隔拉开到 300ms

健康检查与回退

新固件"健康" = 4G 附着 → MQTT 连接 → 首次发布成功。回退判据(3 次真实故障才回退):

  • 看门狗复位IWDG/WWDG固件真的卡死→ BL 计一次试错(读 RCC->CSR 判别);
  • 健康超时MQTT 就绪 1 分钟仍无首次发布)→ APP 复位前自行计一次;
  • 上电/按键/普通软件复位不计次——现场断电、断网不会误触发回退;
  • 试运行期间完全断网 → 不计次、不回退,持续等待网络恢复。

掉电/断网安全性

故障点 结果
下载中断网/掉电 信息区未更新,旧固件照常运行,平台可重推
BL 备份阶段掉电 backup_valid=0,重启后重新备份
BL 擦写 APP1 中途掉电 状态 COPYING 视为 DOWNLOADED,重启后完整重做(备份不会被坏镜像覆盖)
备份创建失败 中止更新,留在旧版本,下次重启重试
回退过程掉电 重启后重新执行回退拷贝

其他要点

  • 双通道均支持 OTA4G 用模组内置 HTTPAT+HTTP* 分块WiFi 通道由 MCU 在透传 TCP 上软件实现 HTTP Range 分块下载NETWORK/net_wifi_ota.c
    • 流程:收 OTA 指令 → 停投喂 → +++ 退透传MQTT 连接被顶替,单连接限制)→ 连固件服务器 → 每 3072B 一个 GETRange: bytes=off-endMCU 自定节奏,防止服务器一次灌爆接收缓冲)→ 每块写 W25Q64 槽 B + 回读校验 + 累计 CRC16 → 整包 CRC + 固件头校验 → 重连 MQTT 上报 result → 写信息区复位进 BL
    • 下载期间 MQTT 断开属正常,进度只在开始/结束上报;失败自动重连 MQTT 恢复业务
  • OTA 触发时先强制停止投喂(断开继电器 1
  • 下载期间其余任务(秤轮询/LED/电量/蓝牙命令)由 RTOS 调度照常并发运行,业务状态上报暂时屏蔽
  • 分段接收按 +HTTPREAD: <len> 声明长度收齐即处理无静默等待40KB 固件全程约 38 秒(含模块复位重连 ~15s

主要功能

  1. 双通道 MQTT 联网4G(Air780E/EG) 优先 + WiFi(ESP-01S) 兜底自动切换,见上文"双通道网络"
  2. 远程控制:平台下发开关 sw1/sw2/sw3/sw4feeding 指令(投料机模式下 sw1 被忽略,投喂只能走 feeding
  3. 自动投喂PRODUCT_WITH_FEED_SCALE = 1
    • 平台设置单次投喂量 onceWt
    • 平台下发 feeding=true 启动投喂
    • 继电器 1 吸合RS485 秤实时读取重量
    • 当重量减少 ≥ onceWt 时自动停止,带重量反增与 10 分钟超时保护
  4. 去皮/标定:平台设置 zero 空载重量,设置后按最新原始重量重新计算剩余料重并立即上报
  5. GPS 定位上报:见上文专题(仅 4G 通道)
  6. 4G 信号检测:见上文专题
  7. OTA 升级见上文专题4G/WiFi 双通道均支持)
  8. 蓝牙调试USART1 支持 RESET/INFO/STATUS/R1ON~R4OFF/ALLON/ALLOFF/OTA/RECOVERY/HELP 等命令
  9. 掉电安全(断电记忆)联网成功MQTT 首次就绪)后按 EEPROM 恢复继电器上次状态,随后全量上报让平台与电路一致
    • 无秤模式(PRODUCT_WITH_FEED_SCALE = 0四路全部恢复(含 SW1
    • 投料机模式(PRODUCT_WITH_FEED_SCALE = 1继电器 1 不恢复且上电强制断开(防止误投料浪费饲料),继电器 2~4 恢复

EEPROM 布局AT24C02256 字节)

上电由 eepromReadInfo() 读取并按 Magic 校验;配置损坏/未初始化时回退到 main.h 的编译期默认值并重新写入。

偏移 大小 内容
0~19 20B ClientID MQTT 客户端 ID
20~39 20B Username
40~59 20B Passward
60~79 20B ServerIP
80~99 20B Topic(下行主题前缀 /iot/data/down/
100~101 2B ServerPort
102~108 7B Relay_State[7][1..4] 四路继电器掉电记忆状态,[0] 保留,[5][6] 未用
112~115 4B Magic0xDEADBEEF检测 EEPROM 是否已初始化109~111 为对齐填充)
116~117 2B Ver OTA 固件版本号(确认后落盘,断电保持)
120~121 2B 秤参数:去皮/空载重量 zero0.1KG 单位,仅称重模式)
122~123 2B 秤参数:单次投喂量 onceWt0.1KG 单位,仅称重模式)
124 1B 秤参数初始化标志
130 1B 上次成功联网方式0=4G1=WiFi固定热点2=WiFi配网热点仅变化时写
132~157 26B 蓝牙名称配置缓存(命中则开机跳过蓝牙 AT 设置流程)
160 1B 日志等级0~5TRACE/DEBUG/INFO/WARN/ERROR/FATAL
164~171 8B HLW8032 累计脉冲数uint64每攒 0.1kWh + 断电前写入)
172 1B 产品模式0=四路继电器(无秤)1=称重投料(带秤),空白时用 main.h 宏默认值(蓝牙 SCALE0/SCALE1 切换)
173 1B GPS 定位开关0=关1=开,空白时用 main.h 宏默认值(蓝牙 GPS0/GPS1 切换,重启生效)
175~194 20B OTA 平台任务 id下载开始时写入重启确认补发 result.post 成功后清 0xFF
250 1B 探测字节(上电写 0xA5 回读,验证 I2C 通信)

注意:改 MqttInfo_Str 结构体会改变上述偏移秤参数120+和联网方式130feed_scale.c / network.c 里是独立宏定义,需同步避让。

平台数据字段

字段 含义 说明
ver 固件版本号 每次状态上报携带,平台可据此识别设备是否发生回退
weight 剩余料重(净重) 与平台物模型一致
zero 空载重量(去皮) 与平台物模型一致
onceWt 单次投喂量 每次投喂目标重量;无秤模式不上报、下发设置被拒绝
sw1 / feeding 第一路继电器状态 1=投喂中0=停止;无秤模式下两字段同时上报,平台两个开关同步刷新
sw2~sw4 备用继电器状态 0/1
pow 电量状态 1=正常0=低电量
energy 累计消耗电量 HLW8032 计量,单位 kWh两位小数>10W 每 30s否则首次+开关切换)
power 实时功率 HLW8032 计量,单位 W一位小数与 energy 同一条报文上报(策略同 energy
iccid SIM 卡号 仅 4G 通道MQTT 连上后随 pow 上报一次,用于物联网卡套餐查询缴费
fwver 代码版本字符串 格式 FIRMWARE_VERSION_STR+:+EEPROM 版本序号(如 "1.5.0:15"),字符串类型,每次重启联网后上报一次(与状态大报文中的整数 ver OTA 版本号区分开)
GPS 定位坐标 仅 4G 通道(Air780EG)GCJ-02 高德坐标系,格式 "经度,纬度",每 5 分钟上报

注:zero(剩余料重)与 onceWt 为称重模式专用字段——PRODUCT_WITH_FEED_SCALE=0 时状态上报不携带这两个字段,平台下发设置会收到 no scale on this device 拒绝应答。

下行报文由 m_app/proto.c 按键名定位解析("sw2" 不会误中 "sw21",其他字段里的 true/false 不会造成误判)。

产品模式(带秤/无秤,运行时可切)

PRODUCT_WITH_FEED_SCALE 从纯编译宏改为运行时可切换:模式存 EEPROM(172),空白时用 main.h 宏做默认值。蓝牙命令:SCALE 查询,SCALE0=四路继电器(无秤)SCALE1=称重投料(带秤)切换后自动重启生效(初始化路径不同,必须重启)。影响范围:状态上报字段(zero/onceWt)、sw1/feeding 指令语义、继电器 1 断电记忆策略、秤轮询开关。

串口调试命令

通过 USART1蓝牙串口发送

RESET    - 系统复位
INFO     - 打印 MQTT 配置
MQTTINFO - 查询 MQTT 配置详情(明文显示)
SETMQTT ID 用户名 密码 - 灌入设备身份(写 EEPROM保存校验通过后自动重启生效空格分隔
SETIP ip port - 修改服务器地址/端口(写 EEPROM保存后自动重启生效空格分隔
SETWIFI 名称 密码 - 配置 WiFi 固定热点(写 EEPROM 200~244保存后自动重启生效空格分隔
WIFIINFO - 查询 WiFi 热点配置(明文显示)
WIFICLEAR - 清除 SETWIFI 配置的热点,回到编译默认(自动重启生效)
FEEDSTART 投料量KG - 带秤模式开始投喂0.1~500.0,如 FEEDSTART 1.5
FEEDSTOP - 停止投喂FEED - 查询投喂状态
FACTORY  - 恢复出厂:清空 EEPROM 全部配置(身份/服务器/继电器记忆/模式/电能累计等),
         自动重启进入待配置模式。不清 ESP 模块里 SmartConfig 保存的热点(要清用 WIFIRESET
STATUS   - 打印继电器状态
R1ON     - 打开继电器 1
R1OFF    - 关闭继电器 1
R2ON/R2OFF ~ R4ON/R4OFF - 控制继电器 2~4
ALLON    - 打开所有继电器
ALLOFF   - 关闭所有继电器
OTA      - 复位进入 BootLoader 检查升级
WIFIRESET - WiFi 模块 AT+RESTORE 恢复出厂(忘掉已存热点)+ 整机复位,用于测试 SmartConfig
BLESET   - 强制重新设置蓝牙名称/GATT UUID清 EEPROM 缓存后重跑配置流程)
LOG      - 查询当前日志等级
LOGTRACE/LOGDEBUG/LOGINFO/LOGWARN/LOGERROR/LOGFATAL
         - 设置日志等级(写入 EEPROM断电不丢
HISTORY  - 打印最近 10 条重启记录(原因+运行时长;开机不再自动打印)
HELP     - 显示支持命令

SmartConfig 配网方式推荐:优先用微信小程序"安信可配网"AirKiss 协议,免装 APP ESP-Touch APP 需授予定位权限(否则 SSID 读不到、广播内容为空导致失败),且部分路由器拦组播。 安信可 AT 固件 AT+CWSTARTSMART 同时监听 SmartConfig 和 AirKiss 两种协议,两种工具都能配。 配网成功以设备日志为准模块回报手机这步可能被路由器拦截APP 显示"连接中/失败"不代表真失败)。

SmartConfig 测试流程:关掉固定热点 → 发 WIFIRESET → 设备复位走完 4G 重试进入 WiFi → 固定热点(60s) 失败后自动进 SmartConfig60s 倒计时)→ 用 ESP-Touch APP 广播密码;配网成功后下次开机按记忆优先 AT+CIPSTATUS 秒连已存热点(无需再配)。

设备身份配置V1.6.0 起:固件不含身份,蓝牙灌入)

  • MQTT 身份只存 EEPROMClientID/Username/Password 不再是编译宏,固件 bin 全设备通用, OTA 批量推送不会"串号";服务器地址/端口/主题保留编译默认值(main.h),可用 SETIP 修改
  • 待配置模式:新板/EEPROM 无身份时,开机跳过 4G/WiFi 初始化OLED 显示"未配置身份/SETMQTT" 串口提示用法;蓝牙保持可用,灌入后 RESET 重启即正常联网
  • 老设备 OTA 平滑升级:老固件当年已把身份写入 EEPROM升级后新固件直接读 EEPROM 身份上线, 平台和设备无感知EEPROM 结构体布局与 Magic 未变)
  • 灌入流程:平台上建好设备拿到三元组 → 蓝牙 APP 发 SETMQTT ID 用户名 密码(空格分隔,仅字母数字, 各不超过 19 位)→ 校验通过保存后自动重启 → 设备以新身份上线;MQTTINFO 可随时查询(密码明文显示)
  • WiFi 热点也可蓝牙配置SETWIFI 名称 密码 写入 EEPROM200~244 区),优先于编译默认热点(esp8266.hWIFI_SSID/WIFI_PASSWIFIINFO 查询(密码明文显示);恢复出厂 FACTORY 会一并清掉,回到编译默认

蓝牙模块配置HLK-B40

  • 接线:模块 UART0_TXD/RXD 接 USART1PA10/PA9PC5功能键PB0(开漏输出)
  • 上电自动配置ble_at.c):拉低 PC5 1s 进 AT 模式 → 设 AT+NAME=<BLE_NAME> → 设 GATT UUID默认 FFE0 服务 + FFE1 收发共用特征JDY-31/HM-10 同款,通用 BLE 串口 APP 可连)→ AT+REBOOT 生效
  • 发射功率可调main.hBLE_TX_POWERAT+RFPOWER1~18 级,出厂默认 8——多台设备同场所时调小如 1~3缩短搜索距离避免认错缓存同时记录功率值改宏后下次开机自动重设一次
  • 零窗口设计:配置成功后将名称缓存进 EEPROM偏移 132之后每次开机命中缓存就完全跳过 AT 流程——蓝牙 APP 不再断联,开机最早的重启原因/重启记录日志都能实时看到;只有代码里改了 BLE_NAMEmain.h才会自动重设一次
  • 手动重设:蓝牙命令 BLESET(清缓存强制重跑)
  • 日志等级LOG 查询,LOGTRACE~LOGFATAL 六档切换,写 EEPROM偏移 160断电保持。档位即"输出阈值"——只打印该级及以上:
命令 级别 输出内容 适用场景
LOGTRACE 0 最细 全部当前≈DEBUG 底层逐字节排查
LOGDEBUG 1 调试(默认) AT 指令交互、连接细节、正常运行信息 现场排查
LOGINFO 2 信息 联网成功、继电器动作、重启记录、警告/错误 日常观察
LOGWARN 3 警告 仅警告/错误AT 超时、无 SIM、掉线复位 稳定运行免打扰
LOGERROR 4 错误 仅错误踢下线、心跳超时、CRC 失败) 只看故障
LOGFATAL 5 致命 全部静音 极端安静
  • USART1 接收中断带 ORE 自愈(HAL_UART_ErrorCallback 自动恢复接收,蓝牙模块重启乱码不会把命令通道打死)

代码分层APP

main.c 只保留初始化和调度器启动,逻辑全部下沉到任务与模块:

int main(void)
{
    SCB->VTOR = FLASH_BASE | Application_1_Addr;
    Board_Init();        // 基础硬件HAL/时钟/GPIO/DMA/UART/看门狗SYSINIT/system.c
    app_init();          // 业务模块初始化SYSINIT/app_loop.c
    app_rtos_start();    // 创建 net/app/sys 任务 + 启动调度器,不再返回
}

目录结构

STM32F103_App1/
├── Src/
│   ├── main.c              # 仅 VTOR + Board_Init/app_init/app_rtos_start
│   └── stm32f1xx_hal_timebase_tim.c  # TIM2 作 HAL 时基SysTick 归 FreeRTOS
├── Inc/
│   ├── main.h              # 产品型号宏 PRODUCT_WITH_FEED_SCALE、MQTT 默认配置
│   └── FreeRTOSConfig.h    # RTOS 配置堆10KB/钩子/中断阈值,含内存预算注释)
├── Middlewares/
│   └── FreeRTOS/Source/    # FreeRTOS V10.0.1 内核tasks/queue/list + ARM_CM3 port + heap_4
├── m_app/
│   ├── proto.c/h           # 平台 JSON 报文取值工具(按键名定位)
│   ├── ota_app.h           # Flash 分区定义BootLoader 32K + APP1 224K
│   └── boot_share.h        # BL↔APP 共享 RAM 结构(复位原因/运行时长/原因码)
├── HardWare/
│   ├── USER_CMD/           # 蓝牙串口命令处理
│   ├── FEED_SCALE/         # RS485 喂料秤与定量投喂状态机
│   ├── RELAY/              # 继电器控制、反馈检测、平台指令执行、断电记忆恢复
│   ├── NETWORK/            # 网络层network.c 通道切换/统一分发 + net_wifi.c WiFi 后端 + net_wifi_ota.c WiFi OTA
│   ├── WIFI/               # esp8266.c ESP-01S AT驱动/透传/SmartConfig + mqtt_client.c 软件MQTT
│   ├── CAR/                # Air780E/EG AT 驱动、波特率自适应、CSQ 信号检测、GNSS 定位、OTA 下载
│   ├── UART/               # USART1/2/3+UART4/5 初始化、运行中切波特率、中断/DMA 接收、ORE 自愈
│   ├── HLW8032/            # 电能计量UART5 帧解析、PF 脉冲累计、kWh 换算、EEPROM 持久化)
│   ├── 24C02/              # EEPROM 读写(页写入,带跨任务互斥锁)
│   ├── IIC/                # 软件 I2C带总线恢复SDA 被拉死时补 9 时钟释放)
│   ├── ADC/                # 电池电压检测0~100% 限幅、掉电去抖判定)
│   ├── LED/                # 指示灯LED2 兼任 TPL5010 喂狗)
│   ├── LOG/                # 分级日志USART1 输出带互斥锁log_cn.c 中文格式串)
│   ├── SYSINIT/            # Board_Init/IWDG、app_init、RTOS 任务、reset_log 重启记录
│   └── W25Q64/             # 外部 Flash 驱动与 OTA 双槽信息区(带跨任务互斥锁)
└── MDK-ARM/                # Keil 工程文件

STM32F103_BootLoader/
├── Src/main.c              # 硬件初始化 + IWDG + 入口
├── m_app/ota_boot.c        # OTA 状态机:备份/烧录/回退 + 复位原因透传 + 主电源门禁 + 无APP重试
├── m_app/boot_share.h      # 与 APP 共享的 RAM 结构定义(同一份拷贝)
├── HardWare/W25Q64/        # 外部 Flash 驱动(与 APP 共用设计)
├── HardWare/LOG/           # 分级日志(与 APP 相同)
└── MDK-ARM/                # Keil 工程文件

编译烧录

Keil GUI

  1. 打开 MDK-ARM/STM32F103rb_App1.uvprojxBootLoader 工程同理)
  2. 按产品形态设置 Inc/main.h 中的 PRODUCT_WITH_FEED_SCALE
  3. 编译下载(先烧 BootLoader 到 0x08000000APP1 起始地址 0x08008000
  4. 首次启动会自动把默认 MQTT 配置写入 AT24C02

命令行CI/脚本可用):

# 编译Keil 安装目录的 UV4-r 全量 -b 增量,-o 输出日志)
/c/Keil_v5/UV4/UV4.exe -r -j0 STM32F103rb_App1.uvprojx -o build.txt

# 烧录J-Link 命令脚本,工程 MDK-ARM 目录下有现成的 jlink_flash.jlink
JLink.exe -CommanderScript jlink_flash.jlink

版本

  • Version: 1.5.0
  • Author: Helei