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

298 lines
9.2 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 "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();
/* RS485 喂料秤控制模块 */
feedScaleInit();
/* LEDLED2 心跳同时兼任外部看门狗 TPL5010 喂狗) */
ledInit();
/* ADC 电量检测 */
adcInit();
update_batter();
/* HLW8032 电能计量UART5 PD2只累计电量 */
hlw8032_init();
/* 网络模块初始化:按 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);
}
}
/*==== 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();
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 */
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);
if (hlw8032_online()) {
/* 每分钟随心跳上报一次累计电量(标识符 energy日志同步显示 */
float kwh = hlw8032_get_energy_kwh();
NET_publish_energy(kwh);
log_info(FMT_UPTIME_EN,
(unsigned)(mins / 1440),
(unsigned)((mins % 1440) / 60),
(unsigned)(mins % 60), (double)kwh,
(double)hlw8032_get_voltage_v(),
(double)hlw8032_get_current_a(),
(double)hlw8032_get_power_w());
} else {
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", 256, NULL, 1, NULL); /* 栈 1KB */
vTaskStartScheduler();
/* 不会走到这里(堆不足创建失败才会) */
log_error("> RTOS: 调度器启动失败");
for (;;) { }
}