——工业级抗干扰、FreeRTOS多任务、Modbus RTU实战
STM32引脚 | 外设 | DMA通道 | 说明 |
---|---|---|---|
PA9(TX) | UART_TX | DMA1_CH4 | 发送通道 |
PA10(RX) | UART_RX | DMA1_CH5 | 接收通道 |
void UART_DMA_Init(void) {
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置接收DMA(USART1_RX)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = RX_BUF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 环形缓冲区
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);
// 使能UART接收DMA
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
}
模式 | CPU占用 | 最大带宽 | 适用场景 |
---|---|---|---|
轮询 | 100% | 低(≤100KB/s) | 简单调试 |
中断 | <1% | 中(≤500KB/s) | 中等数据量 |
DMA | 0% | 高(≤2MB/s) | 高速连续数据(传感器阵列) |
USART_IT_IDLE
)void UART_Idle_Init(void) {
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 使能空闲中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
// 中断服务函数
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除中断标志
uint16_t len = RX_BUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
if (len > 0) {
xQueueSend(uart_queue, &rx_buf[RX_BUF_SIZE - len], 0); // 发送到FreeRTOS队列
}
}
}
#define RX_BUF_SIZE 256
uint8_t rx_buf[RX_BUF_SIZE]; // 环形缓冲区
__IO uint16_t rx_write = 0, rx_read = 0; // 读写指针
// 写入数据(DMA中断)
void rx_buf_write(uint8_t data) {
rx_buf[rx_write++] = data;
rx_write %= RX_BUF_SIZE;
if (rx_write == rx_read) rx_read++; // 溢出处理
}
// 读取数据(应用层)
uint16_t rx_buf_read(uint8_t* buf, uint16_t len) {
uint16_t i, count = 0;
while (rx_read != rx_write && count < len) {
buf[count++] = rx_buf[rx_read++];
rx_read %= RX_BUF_SIZE;
}
return count;
}
#define RS485_RE_DE PB1 // 收发控制引脚
void RS485_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_1); // 默认接收模式(RE/DE低电平)
}
// 发送模式(DE=1, RE=1)
#define RS485_TX_EN() GPIO_SetBits(GPIOB, GPIO_Pin_1)
// 接收模式(DE=0, RE=0)
#define RS485_RX_EN() GPIO_ResetBits(GPIOB, GPIO_Pin_1)
[从机地址:01] [功能码:03] [起始地址:0000] [数据长度:0002] [CRC:840A]
# 自动识别Modbus协议(Saleae Lua脚本)
function decode_modbus(sample)
if sample[0] == 0x01 and sample[1] == 0x03 then
return "Read Holding Registers: " .. sample[2]..":"..sample[3]
end
end
+-------------------+ +-----------------+
| UART接收任务 | | Modbus解析任务 |
| (空闲中断+DMA) ────Queue───> (协议解析+响应)|
+-------------------+ +-----------------+
// 创建队列
QueueHandle_t uart_queue;
#define UART_QUEUE_SIZE 16
#define UART_ITEM_SIZE 256
void uart_task(void* pvParameters) {
uint8_t rx_frame[UART_ITEM_SIZE];
while (1) {
if (xQueueReceive(uart_queue, &rx_frame, portMAX_DELAY)) {
xQueueSend(modbus_queue, &rx_frame, 0); // 转发到Modbus队列
}
}
}
void modbus_task(void* pvParameters) {
uint8_t frame[UART_ITEM_SIZE];
while (1) {
if (xQueueReceive(modbus_queue, &frame, portMAX_DELAY)) {
Modbus_ProcessFrame(frame); // 解析Modbus帧
}
}
}
// 初始化(main.c)
int main(void) {
UART_DMA_Init();
RS485_Init();
uart_queue = xQueueCreate(UART_QUEUE_SIZE, UART_ITEM_SIZE);
xTaskCreate(uart_task, "UART Task", 256, NULL, 2, NULL);
xTaskCreate(modbus_task, "Modbus Task", 512, NULL, 1, NULL);
vTaskStartScheduler();
}
要素 | 内容 |
---|---|
语法 | RTU帧:从机地址(1B)+功能码(1B)+数据(NB)+CRC(2B) |
语义 | 功能码:0x03(读寄存器)、0x06(写单个寄存器) |
时序 | 字符间超时≤1.5字符时间,帧间超时≤3.5字符时间(115200bps:13μs/字符) |
void Modbus_ProcessFrame(uint8_t* frame) {
uint8_t slave_addr = frame[0];
if (slave_addr != MY_SLAVE_ADDR) return; // 过滤非本机地址
uint8_t func_code = frame[1];
switch (func_code) {
case 0x03: // 读保持寄存器
{
uint16_t start_addr = (frame[2] << 8) | frame[3];
uint16_t quantity = (frame[4] << 8) | frame[5];
uint8_t* data = Modbus_ReadRegisters(start_addr, quantity);
uint8_t response[8 + quantity*2];
response[0] = slave_addr;
response[1] = func_code;
response[2] = quantity * 2;
memcpy(&response[3], data, quantity*2);
uint16_t crc = CRC16_Calculate(response, 3 + quantity*2);
response[3 + quantity*2] = crc >> 8;
response[4 + quantity*2] = crc & 0xFF;
RS485_TX_EN();
UART_DMA_Send(response, sizeof(response)); // DMA发送
RS485_RX_EN();
}
break;
// 其他功能码...
}
}
// 公式:BRR = f_PCLK2 / (16 * BaudRate)
#define BAUDRATE 115200
uint32_t BRR = SystemCoreClock / (16 * BAUDRATE); // 72000000 / 1843200 = 39.0625
USART_InitStructure.USART_BaudRate = BAUDRATE;
USART_Init(USART1, &USART_InitStructure);
// 实际波特率误差:
// (72000000 / (16*39.0625)) - 115200 = 0 → 0%误差
USART_SetPrescaler()
动态调整DMA_ISR
寄存器(TCIF
传输完成标志)USART_GetFlagStatus(USART_FLAG_RXNE)
调试接收超时Q:DMA传输时CPU能否休眠?
A:可以!DMA独立于CPU,配合__WFI()
实现低功耗(功耗<1mA)
Q:Modbus帧间超时如何实现?
A:使用定时器测量最后一个字符接收时间,超时后触发帧解析
工业级通信心法:
“抗干扰是工业通信的生命线,DMA是高速传输的引擎,FreeRTOS是复杂逻辑的调度器。三者结合,让STM32在恶劣环境中实现稳定、高效的通信。”
本章适配STM32F103ZET6特色:
✅ 专属配置:DMA1通道5(USART1_RX)、FreeRTOS任务优先级(抢占优先级1/2)
✅ 标准库深度:USART_DMACmd()
与DMA_Mode_Circular
的工业级应用
✅ 行业标准:严格遵循Modbus RTU规范(TIA/EIA-485-A)
技术指标: