stm32 DMA+IDLE modbus_rtu
2025-11-05 来源:bilibili
没有做3.5T延时判断,直接用IDLE中断,所以时间间距会更小。
此程序只使用了两个功能码:03查询数据,06修改从机地址。
需要发送的数据直接放在data[]数组里面,协议直接从此数组中取数据然后发送。
00为广播地址,当忘记从机地址时使用此地址发送命令修改从机地址。


modbus_slave.c
#include "stm32f10x.h"
#include "modbus_slave.h"
#include "bsp_usart_dma.h"
#include "bsp_user_lib.h"
static void Modbus_SendWithCRC(uint8_t *_pBuf, uint8_t Len);
static void Modbus_SendAckErr(uint8_t ErrCode);
static void Modbus_AnalyzeApp(void);
static void Modbus_03H(void);
static void Modbus_06H(void);
u8 RspCode;
u8 data[DATA_SIZE];
static void Modbus_SendWithCRC(uint8_t *_pBuf, uint8_t Len)
{
uint16_t crc;
crc = CRC16_Modbus(_pBuf, Len);
TxBuffer[Len++] = crc >> 8;
TxBuffer[Len++] = crc;
uart_dma_send_enable(Len);
}
static void Modbus_SendAckErr(uint8_t ErrCode)
{
TxBuffer[0] = RxBuffer[0]; //从机地址
TxBuffer[1] = RxBuffer[1] | 0x80; //异常的功能码
TxBuffer[2] = ErrCode; // 错误代码
Modbus_SendWithCRC(TxBuffer, 3);
}
void Modbus_Poll(void)
{
uint16_t crc16;
// Modbus命令至少需要包括从机号、命令号和CRC字段,否则是无效帧
if (RxBufferSize < 4)
{
goto end;
}
// 从机地址检查 0是广播地址
if ((RxBuffer[0] != (FLASH_R(FLASH_START_ADDR) & 0xff)) && (RxBuffer[0] != 0))
{
goto end;
}
// 帧校验检查
if (CRC16_Modbus(RxBuffer, RxBufferSize) != 0)
{
Modbus_SendAckErr(MODBUS_CRC_ERROR);
goto end;
}
switch (RxBuffer[1]) // 功能码
{
case 0x03: //读取数据
Modbus_03H();
break;
case 0x06: //写入从机地址
Modbus_06H();
break;
default:
Modbus_SendAckErr(MODBUS_ILLEGAL_CMD); //返回功能码错误
break;
}
end:;
}
static void Modbus_03H(void)
{
uint16_t u16Addr;
uint16_t u16Num;
uint16_t i;
RspCode = MODBUS_OK;
if (RxBufferSize != 8) /* 03H命令必须是8个字节 */
{
RspCode = MODBUS_ILLEGAL_DATA; /* 数据值域错误 */
goto ret;
}
u16Addr = BEBufToUint16(&RxBuffer[2]); /* 寄存器号 */
u16Num = BEBufToUint16(&RxBuffer[4]); /* 寄存器个数 */
if ((u16Addr < 0) || (u16Addr > (DATA_SIZE - 5))) //1位从机地址+1位功能码+1位寄存器数+2位crc校验码
{
RspCode = MODBUS_ILLEGAL_ADDR; /* 寄存器地址错误 */
goto ret;
}
if ((u16Num > ((DATA_SIZE - 5) / 2)) || (u16Num == 0))
{
RspCode = MODBUS_ILLEGAL_DATA; /* 数据值域错误 */
goto ret;
}
ret:
if (RspCode == MODBUS_OK) /* 正确应答 */
{
uint16_t TxCount = 0;
uint16_t j = u16Addr;
TxBuffer[TxCount++] = FLASH_R(FLASH_START_ADDR) & 0xff; //从机地址
TxBuffer[TxCount++] = RxBuffer[1]; //功能码
TxBuffer[TxCount++] = u16Num * 2; // 返回字节数
// TxBuffer[TxCount++] = 0x00;
// TxBuffer[TxCount++] = 0x01; // 传感器类型
// for (i = 0; i < (u16Num - 1); i++) //(u16Num-1) 1个字节固定用于传感器类型
for (i = 0; i < u16Num; i++)
{
TxBuffer[TxCount++] = data[j++];
TxBuffer[TxCount++] = data[j++];
}
Modbus_SendWithCRC(TxBuffer, TxCount); /* 发送正确应答 */
}
else
{
Modbus_SendAckErr(RspCode); /* 发送错误应答 */
}
}
static void Modbus_06H(void)
{
uint16_t u16Addr;
uint16_t u16Num;
uint16_t i;
RspCode = MODBUS_OK;
if (RxBufferSize != 8) /* 03H命令必须是8个字节 */
{
RspCode = MODBUS_ILLEGAL_DATA; /* 数据值域错误 */
goto ret;
}
u16Addr = BEBufToUint16(&RxBuffer[2]); /* 寄存器号 */
u16Num = BEBufToUint16(&RxBuffer[4]); /* 寄存器个数 */
if ((u16Addr < 0) || (u16Addr > (DATA_SIZE - 5))) //1位从机地址+1位功能码+1位寄存器数+2位crc校验码
{
RspCode = MODBUS_ILLEGAL_ADDR; /* 寄存器地址错误 */
goto ret;
}
if ((u16Num > ((DATA_SIZE - 5) / 2)) || (u16Num == 0))
{
RspCode = MODBUS_ILLEGAL_DATA; /* 数据值域错误 */
goto ret;
}
ret:
if (RspCode == MODBUS_OK) /* 正确应答 */
{
uint16_t TxCount = 0;
uint16_t j = 0;
FLASH_W(FLASH_START_ADDR, ((uint16_t)(RxBuffer[4] << 8) | RxBuffer[5])); //写从机地址
TxBuffer[TxCount++] = FLASH_R(FLASH_START_ADDR) & 0xff; //从机地址,上电初始化为0,即广播地址
TxBuffer[TxCount++] = RxBuffer[1]; //功能码
//此程序中06号功能码只用于写从机地址,所以寄存器地址可以为任意数
TxBuffer[TxCount++] = RxBuffer[2]; //寄存器地址高8位
TxBuffer[TxCount++] = RxBuffer[3]; //寄存器地址低8位
TxBuffer[TxCount++] = RxBuffer[4]; //寄存器数据高8位
TxBuffer[TxCount++] = RxBuffer[5]; //寄存器数据低8位
Modbus_SendWithCRC(TxBuffer, TxCount); /* 发送正确应答 */
}
else
{
Modbus_SendAckErr(RspCode); /* 发送错误应答 */
}
}
modbus_slave.h
#ifndef __MODBUY_SLAVE_H
#define __MODBUY_SLAVE_H
#include "stm32f10x.h"
#define MODBUS_OK 0 // 其它未定义的错误
#define MODBUS_ILLEGAL_CMD 1 // 不支持的功能码
#define MODBUS_ILLEGAL_ADDR 2 // 非法数据地址,主站试图读取非法的寄存器地址
#define MODBUS_ILLEGAL_DATA 3 // 非法数据值,数据不完整或字节数错
#define MODBUS_CRC_ERROR 4 // CRC错误
#define MODBUS_ADDR_NUM 2
#define DATA_SIZE (DMA_SIZE - 5)
extern u8 data[];
void Modbus_Poll(void);
#endif
- 意法半导体中国本地造STM32微控制器启动规模量产
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 基于机智云与STM32的智能拐杖安全监测系统在养老物联网中的应用
- 内置全栈安全,一站式满足CRA法案与IEC 62443标准——米尔STM32MP257核心板
- 如何用 STM32 FLASH 实现等效 100 万次擦写的 EEPROM 功能?
- 实战解析:通过一个小项目掌握STM32所有外设
- STM32学了两年半,却还是不会做项目
- 意法半导体推出最新STM32MP21微处理器,兼具高性价比、低功耗、高灵活性
- 基于STM32的矿井作业环境监测系统设计与实现
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 蓝牙信道探测技术原理与开发套件实践
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策




