nongye/STM32F103_App1/HardWare/SYSINIT/app_loop.c

417 lines
14 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* app_loop.c - FreeRTOS 任务调度封装
*
* 任务划分(裸机主循环轮询项拆分而来):
* net_task(优先级3, 栈3KB) - 网络状态机 NET_process4G/WiFi 建链、收发、OTA 下载
* app_task(优先级2, 栈2KB) - 业务磅秤重量采集上报、OTA 确认与健康检查
* sys_task(优先级1, 栈1KB) - 系统LED、蓝牙调试命令、运行时长打印
* idle 钩子 - 喂 IWDG只要调度器正常看门狗就不会断粮
* 有任务死循环占 CPU 时 idle 饿死 → 复位,正是想要的行为)
*
* 共享资源互斥:
* g_net_at_mutex - NET_publish_* 与长 AT 会话(建链/重连)互斥,拿锁失败直接丢弃
* s_storage_mutex - EEPROM(软件I2C) / W25Q64(软件SPI) 跨任务访问互斥storage_lock
* log 输出 - log.c 内部互斥,多任务日志不交织
*/
#include "app_loop.h"
#include "app_common.h"
#include "user_cmd.h"
#include "network.h"
#include "led.h"
#include "sensor.h"
#include "adc.h"
#include "iic.h"
#include "24c02.h"
#include "uart.h"
#include "log_cn.h"
#include "ble_at.h"
#include "oled.h"
#include "../W25Q64/w25q64.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/*==== 存储总线互斥锁EEPROM/W25Q64 共用一把递归锁)====*/
static SemaphoreHandle_t s_storage_mutex = NULL;
void storage_lock_init(void)
{
s_storage_mutex = xSemaphoreCreateRecursiveMutex();
}
void storage_lock(void)
{
if (s_storage_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
xSemaphoreTakeRecursive(s_storage_mutex, portMAX_DELAY);
}
}
void storage_unlock(void)
{
if (s_storage_mutex && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
xSemaphoreGiveRecursive(s_storage_mutex);
}
}
/*==== 业务模块初始化(调度器启动前调用,与裸机版一致)====*/
void app_init(void)
{
log_info("");
log_info("* * * * * * * * * * * * * * * * * *");
log_info("* 版本: %s (RTOS) *", FIRMWARE_VERSION_STR);
log_info("* * * * * * * By Helei * * * *");
/* EEPROM 中的 MQTT/版本配置 */
iicInit();
eepromReadInfo();
log_load_level(); /* 恢复保存的日志等级 */
log_info("> 服务器: %s:%d", MqttInfoStr.ServerIP, MqttInfoStr.ServerPort);
log_info("> 客户端ID: %s", MqttInfoStr.ClientID);
log_info("> 用户名: %s", MqttInfoStr.Username);
log_info("> 版本序号: %u", MqttInfoStr.Ver);
/* W25Q64 外部 Flash用于 OTA 固件暂存 */
W25Q64_Init();
/* 采集终端:无断电记录/继电器/电量计量,相关初始化全部移除 */
gps_load_mode(); /* GPS 定位开关EEPROM 173蓝牙 GPS0/1 切换) */
/* 电池电量检测PA0 ADC */
adcInit();
/* RS485 传感器采集 */
sensor_init();
/* LEDLED2 心跳同时兼任外部看门狗 TPL5010 喂狗) */
ledInit();
/* OLED 屏幕128x64 + GB2312 字库 IC */
oled_init();
oled_locate(1, 1); oled_print("数据采集终端");
oled_locate(2, 2); oled_print("V" FIRMWARE_VERSION_STR " RTOS");
log_info("> OLED: 初始化完成");
/* 网络模块初始化:按 EEPROM 记忆的上次成功方式优先4G 或 WiFi */
NET_init();
/* 启动各路串口接收 */
USART2_StartRx(); /* RS485 传感器总线 */
USART3_StartRx(); /* 4G MQTT 消息DMA+IDLE */
USART1_StartRx(); /* 蓝牙调试命令 */
/* 上电把 HLK-B40 蓝牙名称设置为 MQTT ClientID多台设备可区分 */
ble_at_init();
ble_setup_name();
}
/*==== net_task网络状态机4G/WiFi 建链、收发、OTA====*/
static void net_task(void *arg)
{
(void)arg;
for (;;) {
NET_process();
vTaskDelay(5);
}
}
/*==== app_task业务逻辑秤/上报/OTA 确认)====*/
static void app_task(void *arg)
{
(void)arg;
/* 主循环状态变量 */
static uint8_t ota_confirmed = 0;
static uint8_t ota_simulate_fail = 0; /* =1测试模拟 OTA 确认失败(验证回退) */
static uint8_t ota_fail_logged = 0;
static uint32_t ota_confirm_tick = 0;
static uint8_t ota_report_pending = 0;
static uint32_t ota_report_tick = 0;
static uint32_t mqtt_ready_tick = 0;
static uint8_t mqtt_ready_recorded = 0;
static uint8_t mqtt_first_status_reported = 0;
for (;;) {
/* 记录 MQTT 首次就绪时刻 */
if (!mqtt_ready_recorded && g_net_mqtt_ready) {
mqtt_ready_recorded = 1;
mqtt_ready_tick = HAL_GetTick();
}
/* MQTT 首次就绪后上报一次固件版本,让平台刷新版本序号 */
if (g_net_mqtt_ready && !mqtt_first_status_reported) {
mqtt_first_status_reported = 1;
{ /* 每次重启联网后上报一次代码版本:"1.5.0:15"(字符串版本 + EEPROM 版本序号) */
char verbuf[24];
snprintf(verbuf, sizeof(verbuf), "%s:%u",
FIRMWARE_VERSION_STR, (unsigned)MqttInfoStr.Ver);
NET_publish_version(verbuf);
}
log_info("> Main: MQTT Ready 后首次状态上报");
ota_confirm_tick = HAL_GetTick();
}
/* 首次发布成功后延迟 9s 再 OTA 确认避开开机上报风暴pow/ICCID/状态/电能) */
if (!ota_confirmed && g_net_mqtt_ready && g_net_first_publish_ok &&
(HAL_GetTick() - ota_confirm_tick) > 9000) {
if (ota_simulate_fail) {
if (!ota_fail_logged) {
ota_fail_logged = 1;
log_info("> OTA 确认跳过(模拟失败,用于回退测试)");
}
/* 不置位 ota_confirmed让 1 分钟健康超时触发复位 */
} else {
ota_confirmed = 1;
if (NET_ota_confirm() < 0) { /* <0 才是未送达1=无挂起 OTA 属正常 */
ota_report_pending = 1; /* 通知未送达30s 后重试 */
ota_report_tick = HAL_GetTick();
log_warn("> OTA 确认通知未送达,稍后重试");
} else {
log_info("> OTA 已确认MQTT Ready + 首次发布成功 + 延迟9s");
sensor_notify_ota_confirmed(); /* 锚定传感器上传计划 */
}
}
}
/* OTA 确认通知未送达:每 30s 重试直到成功(本地确认标志已落盘,仅补平台通知) */
if (ota_report_pending && g_net_mqtt_ready &&
(HAL_GetTick() - ota_report_tick) > 30000) {
ota_report_tick = HAL_GetTick();
if (NET_ota_confirm() >= 0) {
ota_report_pending = 0;
log_info("> OTA 确认通知重试成功");
}
}
/* MQTT 就绪后 1 分钟仍未首次 MPUB主动复位 */
if (!ota_confirmed && mqtt_ready_recorded &&
(HAL_GetTick() - mqtt_ready_tick) > OTA_HEALTH_TIMEOUT_MS) {
log_warn("> OTA 健康超时: MQTT Ready 但 1 分钟内无 MPUB复位");
W25Q64_OTA_IncTrial(); /* 软件复位 BL 不计次,这里自行消耗一次试错 */
HAL_Delay(100);
NVIC_SystemReset();
}
/* 传感器轮询与上报 */
sensor_process();
vTaskDelay(10);
}
}
/*==== OLED 界面刷新1s 一次)
* 已联网:电压电流/功率电能/开关状态/网络+运行时间
* 未联网:按 g_net_ui_phase 显示 4G/WiFi 联网进度WiFi 带倒计时)
* 防烧屏:每 10s 内容整体右移 0/2/4px 轮换 ====*/
static void oled_home_refresh(uint32_t up_ms);
/* 调度器启动前的阻塞建链阶段app_init 里的 4G 初始化),
* sys_task 还没跑,由 bg_delay 每 500ms 调一次刷新联网进度页 */
void app_oled_presched_tick(void)
{
static uint32_t last = 0;
if (HAL_GetTick() - last < 500) return;
last = HAL_GetTick();
oled_home_refresh(0);
}
/* 写一整行(带防烧屏偏移):先写空格清掉左移腾出的列,再从偏移处写内容 */
static void oled_row(uint8_t row, uint8_t xoff, const char *s)
{
/* 按内容宽度钳位:内容宽就少移,保证不超出右边界 */
uint8_t w = oled_text_w(s);
if (xoff > 0 && (uint16_t)xoff + w > 128) xoff = 128 - w;
if (xoff) {
oled_locate(row, 0);
for (uint8_t c = 0; c < xoff; c += 8) oled_print(" "); /* 清空左侧腾出的列,防残留 */
oled_locate(row, 0);
}
oled_locate(row, 0);
oled_xoff(xoff);
oled_print_line(s);
}
static void oled_home_refresh(uint32_t up_ms)
{
char buf[24];
/* 防烧屏:每 10s 换一档左右往返0→24→0按内容宽度自动钳位 */
uint8_t phase = (uint8_t)((HAL_GetTick() / 10000u) % 8u);
uint8_t xoff = (phase <= 4u) ? phase * 6u : (8u - phase) * 6u;
if (g_net_mqtt_ready) {
/*---- 已联网界面(四行布局) ----*/
/* R0: 左=网络类型,右=电池电量10s 刷一次 ADC */
{
static uint32_t bat_tick = 0;
static int bat_val = 0;
if (HAL_GetTick() - bat_tick >= 10000u) {
bat_tick = HAL_GetTick();
bat_val = battery_percent();
}
char left[8], right[8];
char r0[40];
const char *nt = (NET_GetActiveBackend() == NET_BACKEND_WIFI) ? "WiFi" : "4G";
snprintf(left, sizeof(left), "%s", nt);
snprintf(right, sizeof(right), "%d%%", bat_val);
/* 左网络右电量(空格 8px/个,允许少量过冲)。
* 整行在防烧屏偏移基础上再右移 4px贴边 x=0 的字符会被屏幕物理裁边 */
int wl = oled_text_w(left), wr = oled_text_w(right);
int pos;
strcpy(r0, left); pos = wl;
int right_pos = 104 - wr; /* 右边界内收:内容顶满 128 时防烧屏钳位会把 xoff 锁死 */
while (pos < right_pos) { strcat(r0, " "); pos += 8; }
strcat(r0, right);
oled_row(0, (uint8_t)(xoff + 4), r0);
}
/* R1: 运行时间 天:时:分 */
{
uint32_t mins = up_ms / 60000u;
snprintf(buf, sizeof(buf), "%02u:%02u:%02u",
(unsigned)(mins / 1440u),
(unsigned)((mins % 1440u) / 60u),
(unsigned)(mins % 60u));
oled_row(1, xoff, buf);
}
/* R2/R3: 在线传感器翻页显示一页一路5s 翻页) */
{
char l1[24], l2[24];
if (sensor_oled_summary(l1, l2, sizeof(l1))) {
oled_row(2, xoff, l1);
oled_row(3, xoff, l2);
} else {
oled_row(2, xoff, "无传感器应答");
oled_row(3, xoff, "");
}
}
} else {
/*---- 未联网界面:联网进度 ----*/
net_ui_phase_t ph = g_net_ui_phase;
oled_row(0, xoff, "断网");
if (ph == NET_UI_WIFI_FIXED || ph == NET_UI_WIFI_SMART ||
NET_GetActiveBackend() == NET_BACKEND_WIFI) {
if (ph == NET_UI_WIFI_FIXED) {
oled_row(1, xoff, "WiFi固定模式");
oled_row(2, xoff, "正在搜索热点");
} else if (ph == NET_UI_WIFI_SMART) {
oled_row(1, xoff, "智能配网");
oled_row(2, xoff, "请打开APP广播");
} else {
oled_row(1, xoff, "WiFi模式");
oled_row(2, xoff, "初始化中...");
}
snprintf(buf, sizeof(buf), "剩余 %ds", g_net_ui_countdown);
oled_row(3, xoff, g_net_ui_countdown > 0 ? buf : "");
} else {
/* 4G 进度:显示最近完成的阶段 */
const char *step = "初始化中";
switch (ph) {
case NET_UI_4G_RESET: step = "模块复位成功"; break;
case NET_UI_4G_AT: step = "AT指令成功"; break;
case NET_UI_4G_SIM: step = "SIM卡检测成功"; break;
case NET_UI_4G_ATTACH: step = "附着网络成功"; break;
case NET_UI_4G_MQTT: step = "连接服务器中"; break;
case NET_UI_4G_NOSIM: step = "无SIM卡!"; break;
default: break;
}
oled_row(1, xoff, "正在等待4G联网");
oled_row(2, xoff, step);
oled_row(3, xoff, "");
}
}
}
/*==== sys_task系统辅助LED/电量/蓝牙命令/运行时长)====*/
static void sys_task(void *arg)
{
(void)arg;
uint32_t up_last = HAL_GetTick();
uint32_t up_print = HAL_GetTick();
uint32_t disp_tick = HAL_GetTick();
uint64_t up_ms = 0;
for (;;) {
/* 蓝牙调试命令 */
process_usart1_command();
/* 联网指示连接中闪烁连上常亮led.c activeEvents 据此控制 PA1 */
g_net_led_connected = g_net_mqtt_ready ? 1 : 0;
/* LED 心跳/联网指示 */
activeEvents();
/* OLED 界面 1s 刷新 */
if (HAL_GetTick() - disp_tick >= 1000) {
disp_tick = HAL_GetTick();
oled_home_refresh(up_ms);
}
/* 运行时长心跳:联网后每 60s 串口输出一次(天/时/分);
* 未联网(配网/重连中)不打印,避免干扰配网倒计时日志。
* 64 位累计HAL_GetTick 约 49 天回绕也不受影响 */
{
uint32_t now = HAL_GetTick();
up_ms += (uint32_t)(now - up_last); /* 32位差值天然容忍回绕 */
up_last = now;
if (now - up_print >= 60000) {
up_print = now;
if (g_net_mqtt_ready) {
uint32_t mins = (uint32_t)(up_ms / 60000);
log_info(FMT_UPTIME,
(unsigned)(mins / 1440),
(unsigned)((mins % 1440) / 60),
(unsigned)(mins % 60));
}
}
}
vTaskDelay(20);
}
}
/*==== FreeRTOS 钩子 ====*/
/* idle 钩子:喂独立看门狗。有任务死循环占 CPU 时 idle 饿死 → IWDG 复位 */
void vApplicationIdleHook(void)
{
IWDG_Feed();
}
void vApplicationMallocFailedHook(void)
{
log_error("> RTOS: malloc 失败(堆耗尽)");
taskDISABLE_INTERRUPTS();
for (;;) { }
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)xTask;
log_error("> RTOS: 任务 %s 栈溢出", pcTaskName);
taskDISABLE_INTERRUPTS();
for (;;) { }
}
/*==== 任务创建与调度器启动main 调用,不再返回)====*/
void app_rtos_start(void)
{
/* 互斥锁先于任务创建 */
log_lock_init();
storage_lock_init();
NET_LockInit();
xTaskCreate(net_task, "net", 768, NULL, 3, NULL); /* 栈 3KBAT/OTA 有大的 sprintf 缓冲 */
xTaskCreate(app_task, "app", 512, NULL, 2, NULL); /* 栈 2KB */
xTaskCreate(sys_task, "sys", 512, NULL, 1, NULL); /* 栈 2KB电能日志一行 4 个浮点格式化很吃栈 */
vTaskStartScheduler();
/* 不会走到这里(堆不足创建失败才会) */
log_error("> RTOS: 调度器启动失败");
for (;;) { }
}