shuichan_old/V1.5/STM32F103_App1/HardWare/SYSINIT/app_loop.c

404 lines
14 KiB
C
Raw 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 "relay.h"
#include "network.h"
#include "led.h"
#include "adc.h"
#include "feed_scale.h"
#include "iic.h"
#include "24c02.h"
#include "uart.h"
#include "log_cn.h"
#include "ble_at.h"
#include "hlw8032.h"
#include "oled.h"
#include "reset_log.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();
/* 生成本次重启记录(原因+上轮运行时长)并打印最近 10 条 */
reset_log_boot();
/* 继电器 GPIO控制输出 + 反馈输入) */
relayInit();
/* 产品模式(带秤/无秤EEPROM 可切,默认跟编译宏) */
feed_scale_load_mode();
/* RS485 喂料秤控制模块 */
feedScaleInit();
/* LEDLED2 心跳同时兼任外部看门狗 TPL5010 喂狗) */
ledInit();
/* ADC 电量检测 */
adcInit();
update_batter();
/* HLW8032 电能计量UART5 PD2只累计电量 */
hlw8032_init();
/* OLED 屏幕128x64 + GB2312 字库 IC */
oled_init();
if (g_feed_scale_on) { oled_locate(1, 1); oled_print("智能水产投料机"); }
else { 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 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 首次就绪后:先按 EEPROM 恢复继电器上次状态(断电记忆),
* 再上报一次完整状态,让平台刷新为真实状态 */
if (g_net_mqtt_ready && !mqtt_first_status_reported) {
mqtt_first_status_reported = 1;
relayRestoreState();
relayStatueUpdata();
log_info("> Main: MQTT Ready 后首次状态上报");
ota_confirm_tick = HAL_GetTick();
}
/* 首次发布成功后延迟 3s 再 OTA 确认 */
if (!ota_confirmed && g_net_mqtt_ready && g_net_first_publish_ok &&
(HAL_GetTick() - ota_confirm_tick) > 3000) {
if (ota_simulate_fail) {
if (!ota_fail_logged) {
ota_fail_logged = 1;
log_info("> OTA 确认跳过(模拟失败,用于回退测试)");
}
/* 不置位 ota_confirmed让 1 分钟健康超时触发复位 */
} else {
ota_confirmed = 1;
NET_ota_confirm();
log_info("> OTA 已确认MQTT Ready + 首次发布成功 + 延迟3s");
}
}
/* 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复位");
reset_log_mark(RSN_OTA_HEALTH);
W25Q64_OTA_IncTrial(); /* 软件复位 BL 不计次,这里自行消耗一次试错 */
HAL_Delay(100);
NVIC_SystemReset();
}
/* 继电器命令响应状态机 */
relayCmdProcess();
/* 延迟状态上报 */
if (relay_report_pending && (HAL_GetTick() - relay_report_tick) > 100) {
relay_report_pending = 0;
relayStatueUpdata();
}
/* 喂料秤轮询与投喂保护 */
feedScaleProcess();
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_home_refresh(uint32_t up_ms)
{
char buf[24];
/* 防烧屏:每 10s 换一档水平偏移0/2/4px */
uint8_t xoff = (uint8_t)((HAL_GetTick() / 10000u) % 3u) * 2u;
if (g_net_mqtt_ready) {
/*---- 已联网界面 ----*/
/* R0: 网络 + 运行时间(天:时:分) */
uint32_t mins = up_ms / 60000u;
const char *nt = (NET_GetActiveBackend() == NET_BACKEND_WIFI) ? "WiFi" : "4G";
snprintf(buf, sizeof(buf), "%s %02u:%02u:%02u", nt,
(unsigned)(mins / 1440u),
(unsigned)((mins % 1440u) / 60u),
(unsigned)(mins % 60u));
oled_locate(0, 0); oled_xoff(xoff); oled_print_line(buf);
/* R1/R2: 电能参数 */
if (hlw8032_online()) {
snprintf(buf, sizeof(buf), "%.1fV %.2fA",
(double)hlw8032_get_voltage_v(), (double)hlw8032_get_current_a());
oled_locate(1, 0); oled_xoff(xoff); oled_print_line(buf);
snprintf(buf, sizeof(buf), "%.1fW %.2fkWh",
(double)hlw8032_get_power_w(), (double)hlw8032_get_energy_kwh());
oled_locate(2, 0); oled_xoff(xoff); oled_print_line(buf);
} else {
oled_locate(1, 0); oled_xoff(xoff); oled_print_line("计量芯片离线");
oled_locate(2, 0); oled_xoff(xoff); oled_print_line("");
}
/* R3: 开关状态 */
snprintf(buf, sizeof(buf), "开关:%s%s%s%s",
MqttInfoStr.Relay_State[1] ? "" : "",
MqttInfoStr.Relay_State[2] ? "" : "",
MqttInfoStr.Relay_State[3] ? "" : "",
MqttInfoStr.Relay_State[4] ? "" : "");
oled_locate(3, 0); oled_xoff(xoff); oled_print_line(buf);
} else {
/*---- 未联网界面:联网进度 ----*/
net_ui_phase_t ph = g_net_ui_phase;
oled_locate(0, 0); oled_xoff(xoff); oled_print_line("断网");
if (ph == NET_UI_WIFI_FIXED || ph == NET_UI_WIFI_SMART ||
NET_GetActiveBackend() == NET_BACKEND_WIFI) {
if (ph == NET_UI_WIFI_FIXED) {
oled_locate(1, 0); oled_xoff(xoff); oled_print_line("WiFi固定模式");
oled_locate(2, 0); oled_xoff(xoff); oled_print_line("正在搜索热点");
} else if (ph == NET_UI_WIFI_SMART) {
oled_locate(1, 0); oled_xoff(xoff); oled_print_line("智能配网");
oled_locate(2, 0); oled_xoff(xoff); oled_print_line("请打开APP广播");
} else {
oled_locate(1, 0); oled_xoff(xoff); oled_print_line("WiFi模式");
oled_locate(2, 0); oled_xoff(xoff); oled_print_line("初始化中...");
}
snprintf(buf, sizeof(buf), "剩余 %ds", g_net_ui_countdown);
oled_locate(3, 0); oled_xoff(xoff); oled_print_line(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_locate(1, 0); oled_xoff(xoff); oled_print_line("正在等待4G联网");
oled_locate(2, 0); oled_xoff(xoff); oled_print_line(step);
oled_locate(3, 0); oled_xoff(xoff); oled_print_line("");
}
}
}
/* 电能日志/上报间隔:想改频率就改这个宏(单位 ms */
#define ENERGY_REPORT_INTERVAL_MS 10000u
/*==== sys_task系统辅助LED/电量/蓝牙命令/运行时长)====*/
static void sys_task(void *arg)
{
(void)arg;
uint32_t batt_tick = HAL_GetTick();
uint32_t up_last = HAL_GetTick();
uint32_t up_print = HAL_GetTick();
uint32_t en_print = 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();
/* 电量 1s 节流更新 */
if (HAL_GetTick() - batt_tick >= 1000) {
batt_tick = HAL_GetTick();
update_batter();
hlw8032_tick(); /* 攒够约 0.1kWh 写一次 EEPROM */
oled_home_refresh(up_ms); /* OLED 界面 1s 刷新 */
reset_log_tick((uint32_t)(up_ms / 1000)); /* 共享 RAM 更新运行秒数,热复位不丢 */
}
/* 运行时长心跳:联网后每 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));
}
}
}
/* 电能日志+上报:联网且计量芯片在线时每 ENERGY_REPORT_INTERVAL_MS 一条 */
if (g_net_mqtt_ready && hlw8032_online() &&
(HAL_GetTick() - en_print) >= ENERGY_REPORT_INTERVAL_MS) {
en_print = HAL_GetTick();
float kwh = hlw8032_get_energy_kwh();
NET_publish_energy(kwh);
log_info(FMT_ENERGY, (double)kwh,
(double)hlw8032_get_voltage_v(),
(double)hlw8032_get_current_a(),
(double)hlw8032_get_power_w());
}
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 (;;) { }
}