OTA终端系统设计(Modbus+MQTT+CANOpen)——version_1
基于FreeRTOS的多协议融合开发,整合了ESP8266 MQTT通信、Modbus RTU、CAN总线、传感器数据采集(温湿度、电压、电流)等核心功能。
先说明一下:本文基于STM32(以STM32F103为例,其他系列可快速适配),使用HAL库开发,整合了多个常用外设和通信协议,适合用于工业控制、智能监测等场景。
一、项目功能概述
本项目实现了一个多功能、多协议的嵌入式监测与控制终端,核心功能如下:
- 传感器采集:通过AHT20采集温湿度、INA226采集总线电压/电流、ADC采集电位器电压和CPU温度;
- 显示功能:通过OLED实时显示采集到的温湿度、电压、电流数据;
- 通信协议:支持Modbus RTU(从机)、CAN总线(接收数据+SDO监测)、ESP8266 MQTT(上传传感器数据、接收控制指令);
- 任务管理:基于FreeRTOS实现多任务调度(MQTT任务、CAN任务、Modbus任务),任务优先级合理分配;
- 控制功能:通过MQTT指令控制LED和蜂鸣器,支持从机地址动态配置、参数掉电保存(AT24C02)。
二、硬件选型(关键外设)
项目硬件搭配简洁,性价比高,适合入门和项目落地,核心外设如下:
|
外设名称 |
型号 |
用途 |
通信方式 |
|
主控芯片 |
STM32F103C8T6 |
核心控制,任务调度、协议处理 |
无(主控) |
|
温湿度传感器 |
AHT20 |
采集环境温湿度(精度高) |
IIC |
|
电压电流传感器 |
INA226 |
采集总线电压、电流 |
IIC |
|
WiFi模块 |
ESP8266(ESP-01) |
MQTT通信,上传数据、接收指令 |
UART(USART3) |
|
显示模块 |
0.96寸OLED(128*64) |
实时显示传感器数据 |
IIC |
|
存储模块 |
AT24C02 |
掉电保存从机地址等参数 |
IIC |
|
通信总线 |
CAN收发器(TJA1050) |
CAN总线数据收发、SDO监测 |
CAN |
|
其他 |
LED、蜂鸣器、电位器 |
指示、报警、模拟量采集 |
GPIO、ADC |
备注:硬件接线需对应代码中的引脚配置(如ESP8266接USART3、AHT20/INA226接IIC引脚),具体可参考代码中HAL库的外设初始化配置。
三、核心代码解析(重点模块)
项目代码分为3个核心文件:main.c(主函数、系统初始化)、app_task.c(任务实现、传感器采集、协议处理)、freertos.c(FreeRTOS任务创建),以下重点解析关键模块,完整代码附在文末。
3.1 系统初始化(main.c)
主函数核心逻辑:初始化MCU外设 → 初始化应用任务 → 启动FreeRTOS调度器,代码简洁,重点关注外设初始化顺序和任务启动时机。
int main(void)
{
/* 1. 基础初始化:HAL库、系统时钟、外设 */
HAL_Init();
SystemClock_Config(); // 系统时钟配置(72MHz)
MX_GPIO_Init(); // GPIO初始化(LED、蜂鸣器)
MX_CAN_Init(); // CAN初始化
MX_USART1_UART_Init();// USART1(调试/透传)
MX_USART3_UART_Init();// USART3(ESP8266通信)
MX_ADC1_Init(); // ADC初始化(电位器、CPU温度)
MX_TIM6_Init(); // 定时器初始化
MX_TIM7_Init();
MX_NVIC_Init(); // 中断优先级配置
/* 2. 应用任务初始化:传感器、OLED、MQTT、Modbus等 */
Task_AppInit();
/* 3. 启动FreeRTOS调度器,接管任务管理 */
osKernelInitialize();
MX_FREERTOS_Init(); // 创建3个核心任务
osKernelStart();
/* 调度器启动后,不会执行到这里 */
while (1)
{
}
}
关键说明:SystemClock_Config函数配置HSE时钟,PLL倍频到72MHz,适配STM32F103的常规配置;Task_AppInit是应用层核心初始化函数,后续重点解析。
3.2 应用任务初始化(Task_AppInit)
该函数在app_task.c中实现,负责初始化所有传感器、OLED、通信协议,配置中断和任务参数,是项目的“启动入口”,核心代码解析如下:
|
c |
关键说明:
- AT24C02用于保存从机地址,避免掉电后地址丢失,初始化时读取,异常时设为默认值1;
- UART采用中断接收+FIFO缓存(128字节),避免数据丢失,适配ESP8266的MQTT指令和调试信息;
- CanFestival用于CAN总线的SDO监测,可实时获取OD对象字典中的参数(如温度、蜂鸣器状态);
- Modbus RTU初始化后,可通过Modbus主机读取传感器数据、控制LED/蜂鸣器。
3.3 FreeRTOS任务调度(freertos.c)
项目创建3个核心任务,基于FreeRTOS的抢占式调度,合理分配优先级,避免任务阻塞,核心代码如下:
void Task_AppInit(void)
{
// 1. OLED初始化,显示启动信息
OLED_Init();
OLED_ShowString8x16(0, 0, "ESP8266");
OLED_ShowString8x16(0, 1, "READY");
OLED_ShowString8x16(0, 2, "waitting...");
// 2. 传感器初始化
INA226_Init(&ina226); // 电压电流传感器
AHT20_Init(&aht20); // 温湿度传感器
HAL_ADCEx_Calibration_Start(&hadc1); // ADC校准
// 3. 存储模块初始化,读取从机地址(掉电保存)
at24cxx_init();
at24cxx_read(0, &g_addr, 1);
if ((g_addr < 1U) || (g_addr > 127U))
{
g_addr = 1U; // 地址异常时默认设为1
}
usRegHoldingBuf[REG_SLAVE_ADDR] = (USHORT)g_addr;
deviceInfo_SlaveAddress = g_addr;
// 4. UART中断接收初始化(USART1/USART3)
HAL_UART_Receive_IT(&huart1, &rx1_byte, 1);
HAL_UART_Receive_IT(&huart3, &rx3_byte, 1);
// 5. MQTT初始化(ESP8266连接WiFi、MQTT服务器)
mqtt_start();
printf("%d\r\n", g_addr); // 打印从机地址(调试用)
// 6. CAN总线、CanFestival初始化(SDO监测)
HAL_TIM_Base_Start_IT(&htim7);
CanFestival_Can_Init();
setNodeId(&TestSlave_Data, g_addr);
setState(&TestSlave_Data, Initialisation);
setState(&TestSlave_Data, Operational);
// 7. 注册OD回调函数(蜂鸣器、从机地址更新)
(void)RegisterSetODentryCallBack(&TestSlave_Data, 0x2000, 0x09, &OnBuzzerUpdate);
Buzzer_ApplyFromOD();
(void)RegisterSetODentryCallBack(&TestSlave_Data, 0x2000, 0x0A, &OnNodeIdUpdate);
NodeId_ApplyFromOD();
// 8. Modbus RTU初始化(从机,波特率115200,无校验)
eMBInit(MB_RTU, g_addr, 0, 115200, MB_PAR_NONE);
eMBEnable();
modbus_active = 1;
}
任务优先级说明:
- MQTT任务设为中等优先级:因为MQTT涉及WiFi通信,需要及时处理接收的指令和上传数据,避免通信超时;
- CAN任务和Modbus任务设为低优先级:两者均为周期性任务(1秒执行一次),优先级低于MQTT,避免抢占MQTT的CPU资源;
- 每个任务通过osDelay(1)释放CPU,确保其他任务有机会执行,避免单个任务独占CPU。
3.4 核心功能模块实现
3.4.1 传感器数据采集(ADC_VR_Test + IIC_VR_Test)
通过ADC采集电位器电压和CPU温度,通过IIC采集AHT20温湿度、INA226电压电流,数据处理后更新到OLED和Modbus寄存器,核心代码片段:
// ADC采集(电位器、CPU温度)
static void ADC_VR_Test(void)
{
uint32_t adc_pa1 = 0;
uint32_t adc_temp = 0;
ADC_ChannelConfTypeDef sConfig = {0};
// 采集电位器(PA1)
sConfig.Channel = ADC_CHANNEL_1;
(void)HAL_ADC_ConfigChannel(&hadc1, &sConfig);
for (uint8_t i = 0; i < 10; i++) // 10次采样平均,提高精度
{
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
{
adc_pa1 += HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Stop(&hadc1);
}
adc_pa1 /= 10U;
val = (adc_pa1 * 3.3f / 4095.0f); // 转换为电压值(0-3.3V)
// 采集CPU温度
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
(void)HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 同样10次采样平均...
// 温度计算(参考STM32手册公式)
int64_t vsense_uv = ((int64_t)adc_temp * 3300000LL) / 4095LL;
int32_t cpu_temp_deci_c = (int32_t)((((vsense_uv - 1430000LL) * 10LL) / 4300LL) + 250LL);
cpu_temp_abs = (cpu_temp_deci_c < 0) ? -cpu_temp_deci_c : cpu_temp_deci_c;
}
// IIC传感器采集 + OLED显示
static void IIC_VR_Test(void)
{
// 读取AHT20温湿度、INA226电压电流
AHT20_Read(&aht20, &temp_deci_c, &humi_deci_pct);
INA226_ReadBusVoltage_mV(&ina226, &bus_mV);
INA226_ReadCurrent_mA(&ina226, &cur_mA);
// 格式化数据,显示到OLED
sprintf(line, "T:%c%ld.%ldC", (temp_deci_c < 0) ? '-' : '+',
(long)(abs(temp_deci_c)/10), (long)(abs(temp_deci_c)%10));
OLED_ShowLineDiff8x16(0, line);
sprintf(line, "V:%lu.%02luV", bus_mV/1000U, (bus_mV%1000U)/10U);
OLED_ShowLineDiff8x16(1, line);
// 湿度、电流显示...
// 更新设备信息结构体(供Modbus、CAN使用)
deviceInfo_TP = temp_deci_c;
deviceInfo_RH = humi_deci_pct;
deviceInfo_VL = bus_mV;
deviceInfo_CU = cur_mA;
}
3.4.2 MQTT通信(ESP8266)
通过ESP8266连接WiFi和MQTT服务器(使用emqx公共服务器,无需自己搭建),实现“接收控制指令”和“定时上传传感器数据”,核心逻辑:
- mqtt_start():初始化ESP8266,连接WiFi(需修改WiFi名称和密码),连接MQTT服务器,订阅控制主题;
- mqtt_senddata():每2秒上传一次传感器数据(温湿度、电压、电流)到MQTT发布主题;
- Process_ESP8266_Command():解析ESP8266接收的MQTT指令(如LED_ON、BEEP_OFF、temp查询),执行对应操作。
关键注意:代码中WiFi名称(MIFI-070180-2.4G)和密码(12345678)需替换为自己的WiFi信息,MQTT主题可根据需求修改。
3.4.3 Modbus RTU通信
项目作为Modbus从机,寄存器地址定义在app_task.h中,支持主机读取传感器数据、控制LED/蜂鸣器,核心逻辑:
- read_value():将传感器采集到的数据更新到Modbus保持寄存器(usRegHoldingBuf);
- eMBPoll():Modbus从机轮询函数,在Modbus任务中周期性执行,处理主机的读写请求;
- 寄存器地址映射:REG_TEMPERATURE(0x0003)对应温度、REG_VOLTAGE(0x0005)对应电压,可直接通过Modbus主机访问。
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter <wolti@sil.at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* File: $Id$
*/
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "mbutils.h"
#include "app_task.h"
/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent; /* 事件队列中待处理的事件类型 */
static BOOL xEventInQueue; /* 事件队列非空标志: TRUE-有待处理事件, FALSE-无事件 */
/* ----------------------- Register buffers ---------------------------------*/
#define HOLDING_REG_SIZE 0x0100 /* 保持寄存器数量: 256个 (地址范围 0x0000-0x00FF) */
#define DISCRETE_SIZE 0x0010 /* 离散量数量: 16个 (地址范围 0x0000-0x000F) */
/* 保持寄存器缓冲区 - 用于Modbus功能码03(读)/06/16(写) */
USHORT usRegHoldingBuf[HOLDING_REG_SIZE];
/* 线圈缓冲区 - 用于Modbus功能码01(读)/05/15(写),按位存储节省空间 */
static UCHAR ucRegCoilsBuf[(DISCRETE_SIZE + 7) / 8];
/* 离散输入缓冲区 - 用于Modbus功能码02(读),按位存储节省空间 */
static UCHAR ucRegDiscreteBuf[(DISCRETE_SIZE + 7) / 8];
extern uint8_t g_addr; /* 从站设备地址,支持运行时修改 */
/* ----------------------- 事件管理接口 --------------------------------------*/
/**
* @brief 初始化Modbus事件队列
* @return TRUE-初始化成功, FALSE-初始化失败
* @note 在eMBInit()或eMBTCPInit()之前调用
*/
BOOL xMBPortEventInit(void)
{
xEventInQueue = FALSE;
return TRUE;
}
/**
* @brief 向事件队列投递一个Modbus事件
* @param eEvent - 待投递的事件类型
* @return TRUE-投递成功, FALSE-投递失败
* @note 此函数通常在串口接收中断或定时器中断中调用
*/
BOOL xMBPortEventPost(eMBEventType eEvent)
{
xEventInQueue = TRUE;
eQueuedEvent = eEvent;
return TRUE;
}
/**
* @brief 从事件队列获取一个Modbus事件
* @param eEvent - 输出参数,返回获取到的事件类型
* @return TRUE-成功获取事件, FALSE-队列为空
* @note 此函数在主循环中调用,用于轮询处理Modbus事件
*/
BOOL xMBPortEventGet(eMBEventType *eEvent)
{
BOOL xEventHappened = FALSE;
if (xEventInQueue)
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
/* ----------------------- 保持寄存器回调函数 ---------------------------------*/
/**
* @brief 保持寄存器访问回调函数
* @param pucRegBuffer - 数据缓冲区指针
* - 读操作: 输出参数,填充要返回的数据
* - 写操作: 输入参数,包含要写入的数据
* @param usAddress - 寄存器起始地址(Modbus协议地址,从1开始)
* @param usNRegs - 寄存器数量
* @param eMode - 操作模式: MB_REG_READ(读取) 或 MB_REG_WRITE(写入)
* @return MB_ENOERR - 操作成功
* MB_ENOREG - 寄存器地址超出范围
* @note 支持的Modbus功能码:
* - 03: 读保持寄存器
* - 06: 写单个保持寄存器
* - 16: 写多个保持寄存器
* @par 特殊寄存器功能映射:
* - REG_LED0: 控制LED0 (PB5,低电平点亮)
* - REG_LED1: 控制LED1 (PE5,低电平点亮)
* - REG_BEEP: 控制蜂鸣器 (PB8,高电平鸣叫)
* - REG_SLAVE_ADDR: 从站地址 (写入后自动重启Modbus协议栈)
*/
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress,
USHORT usNRegs, eMBRegisterMode eMode)
{
eMBErrorCode eStatus = MB_ENOERR;
USHORT iRegIndex;
/* 地址转换: FreeModbus传入的usAddress从1开始,转换为从0开始便于数组索引 */
if (usAddress >= 1)
{
usAddress--;
}
else
{
return MB_ENOREG; /* 无效地址: Modbus地址最小为1 */
}
/* 地址边界检查: 确保访问范围不超出保持寄存器缓冲区(0-255) */
if ((usAddress + usNRegs) <= HOLDING_REG_SIZE)
{
if (eMode == MB_REG_WRITE) /* 写操作: 处理功能码06(写单个)和16(写多个) */
{
for (iRegIndex = 0; iRegIndex < usNRegs; iRegIndex++)
{
/* Modbus协议采用大端格式: 高字节在前,低字节在后 */
USHORT usRegValue = (pucRegBuffer[iRegIndex * 2] << 8) |
pucRegBuffer[iRegIndex * 2 + 1];
/* 写入保持寄存器缓冲区 */
usRegHoldingBuf[usAddress + iRegIndex] = usRegValue;
/* 特殊寄存器映射 - 控制硬件外设 */
if ((usAddress + iRegIndex) == REG_LED0)
{
/* LED0控制: 最低位为1时关闭(高电平),为0时点亮(低电平) */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,
(usRegValue & 0x01) ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
else if ((usAddress + iRegIndex) == REG_LED1)
{
/* LED1控制: 最低位为1时关闭(高电平),为0时点亮(低电平) */
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5,
(usRegValue & 0x01) ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
else if ((usAddress + iRegIndex) == REG_BEEP)
{
/* 蜂鸣器控制: 最低位为1时鸣叫(高电平有效),为0时关闭 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8,
(usRegValue & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
else if ((usAddress + iRegIndex) == REG_SLAVE_ADDR)
{
/* 从站地址修改: 支持地址范围1-247 (Modbus标准有效地址) */
if (usRegValue >= 1 && usRegValue <= 247)
{
g_addr = usRegValue;
/* 保存地址到EEPROM,确保掉电不丢失 */
at24cxx_write(0, &g_addr, 1);
/* 重启Modbus协议栈以应用新地址 */
eMBDisable(); /* 禁用当前协议栈 */
eMBInit(MB_RTU, g_addr, 0, 115200, MB_PAR_NONE); /* 重新初始化 */
eMBEnable(); /* 使能新协议栈 */
}
}
}
}
else /* 读操作: 处理功能码03(读保持寄存器) */
{
for (iRegIndex = 0; iRegIndex < usNRegs; iRegIndex++)
{
USHORT usRegValue = usRegHoldingBuf[usAddress + iRegIndex];
/* 以大端格式填充响应缓冲区: 高字节在前,低字节在后 */
pucRegBuffer[iRegIndex * 2] = (UCHAR)(usRegValue >> 8); /* 高字节 */
pucRegBuffer[iRegIndex * 2 + 1] = (UCHAR)(usRegValue & 0xFF); /* 低字节 */
}
}
}
else
{
eStatus = MB_ENOREG; /* 地址越界: 访问了不存在的寄存器区域 */
}
return eStatus;
}
/* ----------------------- 输入寄存器回调函数 ---------------------------------*/
/**
* @brief 输入寄存器读回调函数
* @param pucRegBuffer - 输出参数,返回要填充的数据缓冲区
* @param usAddress - 寄存器起始地址(Modbus协议地址,从1开始)
* @param usNRegs - 寄存器数量
* @return MB_ENOERR - 操作成功
* MB_ENOREG - 寄存器地址超出范围
* @note 支持的Modbus功能码: 04(读输入寄存器)
* @warning 当前为示例实现,全部返回0,实际应用需根据硬件修改
* 典型应用: ADC采集值、传感器数据、设备状态等只读数据
*/
eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
eMBErrorCode eStatus = MB_ENOERR;
/* 地址转换: Modbus地址从1开始 -> 数组索引从0开始 */
if (usAddress >= 1)
{
usAddress--;
}
else
{
return MB_ENOREG;
}
/* 地址边界检查: 输入寄存器范围 0x0000-0x000F (共16个寄存器) */
if ((usAddress + usNRegs) <= DISCRETE_SIZE)
{
for (USHORT i = 0; i < usNRegs; i++)
{
/* TODO: 替换为实际数据源,例如:
* - HAL_ADC_GetValue(&hadc)
* - 传感器采集的温湿度值
* - 设备运行状态 */
pucRegBuffer[i * 2] = 0; /* 高字节 */
pucRegBuffer[i * 2 + 1] = 0; /* 低字节 */
}
}
else
{
eStatus = MB_ENOREG; /* 地址越界 */
}
return eStatus;
}
/* ----------------------- 线圈回调函数 --------------------------------------*/
/**
* @brief 线圈访问回调函数
* @param pucRegBuffer - 数据缓冲区指针
* - 读操作: 输出参数,填充要返回的线圈状态
* - 写操作: 输入参数,包含要设置的线圈状态
* @param usAddress - 线圈起始地址(Modbus协议地址,从1开始)
* @param usNCoils - 线圈数量
* @param eMode - 操作模式: MB_REG_READ(读取) 或 MB_REG_WRITE(写入)
* @return MB_ENOERR - 操作成功
* MB_ENOREG - 地址超出范围
* @note 支持的Modbus功能码:
* - 01: 读线圈
* - 05: 写单个线圈
* - 15: 写多个线圈
* @attention 线圈采用位压缩存储,每个字节存储8个线圈状态以提高内存效率
*/
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress,
USHORT usNCoils, eMBRegisterMode eMode)
{
eMBErrorCode eStatus = MB_ENOERR;
/* 地址转换: Modbus地址从1开始 -> 数组索引从0开始 */
if (usAddress >= 1)
{
usAddress--;
}
else
{
return MB_ENOREG;
}
/* 地址边界检查: 线圈范围 0x0000-0x000F (共16个线圈) */
if ((usAddress + usNCoils) <= DISCRETE_SIZE)
{
if (eMode == MB_REG_WRITE) /* 写操作: 处理功能码05(写单个)和15(写多个) */
{
USHORT iBit = 0;
while (iBit < usNCoils)
{
/* 每次处理最多8个线圈(1字节),提高处理效率 */
UCHAR ucNBits = (UCHAR)((usNCoils - iBit) > 8 ? 8 : (usNCoils - iBit));
/* 将缓冲区的位数据写入线圈缓冲区 */
xMBUtilSetBits(ucRegCoilsBuf, (USHORT)(usAddress + iBit),
ucNBits, pucRegBuffer[iBit / 8]);
iBit = (USHORT)(iBit + ucNBits);
}
}
else /* 读操作: 处理功能码01(读线圈) */
{
USHORT iBit = 0;
while (iBit < usNCoils)
{
/* 每次处理最多8个线圈(1字节) */
UCHAR ucNBits = (UCHAR)((usNCoils - iBit) > 8 ? 8 : (usNCoils - iBit));
/* 从线圈缓冲区读取位数据并填充到响应缓冲区 */
pucRegBuffer[iBit / 8] = xMBUtilGetBits(ucRegCoilsBuf,
(USHORT)(usAddress + iBit), ucNBits);
iBit = (USHORT)(iBit + ucNBits);
}
}
}
else
{
eStatus = MB_ENOREG; /* 地址越界 */
}
return eStatus;
}
/* ----------------------- 离散输入回调函数 -----------------------------------*/
/**
* @brief 离散输入读回调函数
* @param pucRegBuffer - 输出参数,返回要填充的离散输入状态
* @param usAddress - 离散输入起始地址(Modbus协议地址,从1开始)
* @param usNDiscrete - 离散输入数量
* @return MB_ENOERR - 操作成功
* MB_ENOREG - 地址超出范围
* @note 支持的Modbus功能码: 02(读离散输入)
* @warning 当前为示例实现,全部返回0,实际应用需根据硬件修改
* 典型应用: 限位开关状态、按钮状态、传感器触发信号等
*/
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
eMBErrorCode eStatus = MB_ENOERR;
/* 地址转换: Modbus地址从1开始 -> 数组索引从0开始 */
if (usAddress >= 1)
{
usAddress--;
}
else
{
return MB_ENOREG;
}
/* 地址边界检查: 离散输入范围 0x0000-0x000F (共16个离散输入) */
if ((usAddress + usNDiscrete) <= DISCRETE_SIZE)
{
USHORT iBit = 0;
while (iBit < usNDiscrete)
{
/* 每次处理最多8个离散输入(1字节) */
UCHAR ucNBits = (UCHAR)((usNDiscrete - iBit) > 8 ? 8 : (usNDiscrete - iBit));
/* TODO: 替换为实际数据源,例如:
* - HAL_GPIO_ReadPin(限位开关)
* - 按钮状态
* - 传感器触发信号
*
* 当前示例从ucRegDiscreteBuf读取(该缓冲区当前全为0) */
pucRegBuffer[iBit / 8] = xMBUtilGetBits(ucRegDiscreteBuf,
(USHORT)(usAddress + iBit), ucNBits);
iBit = (USHORT)(iBit + ucNBits);
}
}
else
{
eStatus = MB_ENOREG; /* 地址越界 */
}
return eStatus;
}
四、调试要点(避坑指南)
在多协议融合开发中容易遇到通信失败、数据异常等问题,这里总结几个关键调试点:
- 外设接线:确保IIC引脚(SDA、SCL)接对,ESP8266的TX/RX与STM32的USART3 RX/TX交叉连接(ESP_TX → STM32 RX),CAN收发器接线正确(CAN_H、CAN_L);
- 中断优先级:UART、CAN中断优先级需高于FreeRTOS的任务优先级,避免中断被任务阻塞,导致数据丢失;
- ESP8266调试:可通过USART1串口打印ESP8266的响应信息,排查WiFi连接、MQTT连接失败的问题(如密码错误、服务器地址错误);
- 传感器校准:ADC采集需进行校准,INA226需根据实际电路配置分流电阻参数(代码中默认1000Ω,可修改);
- 任务栈大小:如果任务执行过程中出现死机,可能是栈大小不足,可适当增大对应任务的栈大小(如MQTT任务栈改为128*5)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)