基于STM32F103C8T6通过串口中断+DMA获取传感器数据的实现方案
方案严格遵循手册中Modbus协议规范和数据格式要求
一、硬件配置说明
- 串口选择:使用USART1(PA9发送、PA10接收),波特率115200(默认),8位数据位,无校验位,1位停止位(8N1)。
- RS485接口:通过MAX485芯片实现电平转换,控制引脚(如PA8)用于切换收发模式。
- DMA配置:使用DMA1通道5(对应USART1接收),循环模式,数据宽度8位。
二、软件核心代码
1. 数据结构定义
#include "stm32f10x.h"
// 传感器数据结构体
typedef struct {
float roll; // 横滚角 (°)
float pitch; // 俯仰角 (°)
float yaw; // 方位角 (°)
float x_plane; // X轴面角 (°)
float y_plane; // Y轴面角 (°)
float z_plane; // Z轴面角 (°)
} SensorData;
SensorData sensor_data = {0};
uint8_t rx_buf[16] = {0}; // DMA接收缓冲区(Modbus响应最大长度)
uint8_t tx_buf[8] = {0}; // 发送缓冲区(Modbus命令)
2. 初始化配置
void USART1_DMA_Init(void) {
// 1. 串口初始化
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 引脚配置:PA9推挽输出(TX),PA10浮空输入(RX)
GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9 |
GPIO_CRH_MODE10 | GPIO_CRH_CNF10);
GPIOA->CRH |= GPIO_CRH_MODE9_1 | GPIO_CRH_CNF9_1; // TX: 50MHz推挽
GPIOA->CRH |= GPIO_CRH_CNF10_0; // RX: 浮空输入
// 波特率配置:115200(PCLK2=72MHz,USARTDIV=72000000/(16*115200)=39.0625)
USART1->BRR = 0x2710; // 0x2710 = 39.0625 * 16
USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 使能收发
USART1->CR3 |= USART_CR3_DMAR; // 使能DMA接收
// 2. DMA配置(接收)
DMA1_Channel5->CPAR = (uint32_t)&USART1->DR; // 外设地址(串口数据寄存器)
DMA1_Channel5->CMAR = (uint32_t)rx_buf; // 内存地址(接收缓冲区)
DMA1_Channel5->CNDTR = 16; // 接收最大长度
DMA1_Channel5->CCR |= DMA_CCR5_MINC | DMA_CCR5_CIRC | DMA_CCR5_EN; // 内存递增+循环模式
// 3. 中断配置(接收完成后处理)
USART1->CR1 |= USART_CR1_IDLEIE; // 使能空闲中断
NVIC_EnableIRQ(USART1_IRQn);
}
3. Modbus命令发送(读取输入寄存器)
// 发送Modbus命令:读取0000H-0008H寄存器(共9个,包含所需6个角度)
void SendReadCommand(void) {
// 命令格式:从站地址(05) + 功能码(04) + 起始地址(0000) + 寄存器数量(0009) + CRC校验
tx_buf[0] = 0x05; // 从站地址(默认5)
tx_buf[1] = 0x04; // 功能码(读取输入寄存器)
tx_buf[2] = 0x00; // 起始地址高字节(0000H)
tx_buf[3] = 0x00; // 起始地址低字节
tx_buf[4] = 0x00; // 数量高字节(9个寄存器)
tx_buf[5] = 0x09; // 数量低字节
uint16_t crc = CRC16(tx_buf, 6); // 计算CRC校验
tx_buf[6] = crc & 0xFF; // CRC低字节
tx_buf[7] = crc >> 8; // CRC高字节
// 切换为发送模式(控制MAX485方向引脚)
GPIOA->BSRR = GPIO_BSRR_BS8; // PA8置高(发送)
for (int i = 0; i < 8; i++) {
while (!(USART1->SR & USART_SR_TXE)); // 等待发送缓冲区空
USART1->DR = tx_buf[i];
}
while (!(USART1->SR & USART_SR_TC)); // 等待发送完成
GPIOA->BSRR = GPIO_BSRR_BR8; // PA8置低(接收)
}
// CRC16校验计算(按手册算法实现)
uint16_t CRC16(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x01) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
4. 中断处理与数据解析
// 串口空闲中断(接收完成后触发)
void USART1_IRQHandler(void) {
if (USART1->SR & USART_SR_IDLE) {
USART1->DR; // 清除空闲中断标志
DMA1_Channel5->CCR &= ~DMA_CCR5_EN; // 关闭DMA
// 解析响应帧(格式:地址+功能码+字节数+数据+CRC)
if (rx_buf[0] == 0x05 && rx_buf[1] == 0x04 && rx_buf[2] == 0x12) {
// 提取Roll(0000H)、Pitch(0001H)、Yaw(0002H)
int16_t roll_raw = (rx_buf[3] << 8) | rx_buf[4];
int16_t pitch_raw = (rx_buf[5] << 8) | rx_buf[6];
int16_t yaw_raw = (rx_buf[7] << 8) | rx_buf[8];
// 提取X轴面角(0006H)、Y轴面角(0007H)、Z轴面角(0008H)
int16_t x_plane_raw = (rx_buf[15] << 8) | rx_buf[16];
int16_t y_plane_raw = (rx_buf[17] << 8) | rx_buf[18];
int16_t z_plane_raw = (rx_buf[19] << 8) | rx_buf[20];
// 转换为实际角度(乘以0.01缩放系数)
sensor_data.roll = roll_raw * 0.01f;
sensor_data.pitch = pitch_raw * 0.01f;
sensor_data.yaw = yaw_raw * 0.01f;
sensor_data.x_plane = x_plane_raw * 0.01f;
sensor_data.y_plane = y_plane_raw * 0.01f;
sensor_data.z_plane = z_plane_raw * 0.01f;
}
// 重置DMA
DMA1_Channel5->CNDTR = 16;
DMA1_Channel5->CCR |= DMA_CCR5_EN;
}
}
三、关键说明
寄存器地址映射:
- Roll(0000H)、Pitch(0001H)、Yaw(0002H)
- X轴面角(0006H)、Y轴面角(0007H)、Z轴面角(0008H)
数据格式:
- 16位有符号整数(Int16),高字节在前(大端模式)
- 实际角度 = 原始值 × 0.01°(缩放系数)
通讯时序:
- 发送命令后需等待传感器响应(约10-50ms)
- 响应帧长度为:1(地址)+1(功能码)+1(字节数)+18(数据,9×2字节)+2(CRC)=23字节
四、使用流程
- 初始化串口DMA和GPIO
- 调用
SendReadCommand()发送读取命令 - 传感器响应后,中断自动解析数据到
sensor_data结构体 - 主程序中直接使用解析后的角度值
此方案严格遵循手册中的Modbus协议和数据格式要求,可稳定获取所需角度数据,适用于STM32F103C8T6平台。