基于STM32F407的数据采集系统设计
基于STM32F407的数据采集系统的软件设计。STM32F407凭借其高性能Cortex-M4内核、丰富的外设(特别是双ADC模块)和DMA控制器,非常适合于高效能数据采集任务。
基于STM32F407的数据采集系统设计
摘要
本设计方案围绕STM32F407单片机,构建一个高效、可靠的数据采集系统。方案采用分层架构,涵盖外设驱动层、应用程序层及数据处理与通信层。核心实现基于ADC(模数转换器) 与 DMA(直接存储器访问) 的无阻塞数据搬运,确保实时性。同时,方案集成了定时器触发、环形缓冲区、数据处理算法(如校准、滤波)以及多种通信接口(如UART、SPI、USB、以太网)用于数据传输,并提供详细的代码示例以供参考。
1. 系统架构与软件层次
一个健壮的数据采集系统软件通常采用分层设计,便于维护和扩展:
+-----------------------------------------------+
| 应用层 (Application Layer) | <-> 主循环、状态机、业务逻辑
+-----------------------------------------------+
| 数据处理层 (Data Processing Layer) | <-> 滤波、校准、变换、协议封装
+-----------------------------------------------+
| 硬件抽象层 (HAL) / 外设驱动层 (Driver Layer) | <-> STM32Cube HAL库或LL库/寄存器操作
+-----------------------------------------------+
| 硬件 (Hardware) | <-> STM32F407 MCU、传感器、外设
+-----------------------------------------------+
2. 核心模块设计与实现
2.1 ADC模块配置 (以双ADC交替采样为例)
这是数据采集的核心。STM32F407拥有多达3个ADC模块,支持极其强大的采样模式。
目标:利用TIM定时器触发ADC,配合DMA实现高速、自动、不间断的数据采集。
配置步骤:
- 时钟配置:确保ADC、DMA、TIM时钟已使能。
- GPIO配置:将模拟输入引脚(如
PA0,PA1)配置为模拟模式。 - ADC通用配置:
- 分辨率:12位
- 扫描模式:
Enable - 连续转换模式:
Enable - 外部触发转换源:
Timer 2/3/4... Trigger Out event
- ADC规则通道配置:
- 为每个ADC配置要转换的通道序列和采样时间。
- DMA配置:
- 模式:
Circular(循环模式,实现环形缓冲区) - 数据长度:
Word(若ADC为12位,也可用HalfWord) - 内存地址自增:
Enable - 外设地址不自增:
Disable
- 模式:
- 定时器配置:
- 配置TIM为PWM输出模式或更新事件,以产生固定频率的触发信号。
- 采样频率
Fs = Timer_Clock / ((PSC + 1) * (ARR + 1))
代码 (使用STM32CubeMX HAL库):
// main.c
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
TIM_HandleTypeDef htim3;
// 定义ADC DMA目标缓冲区(双缓冲之一)
#define ADC_BUFFER_SIZE 1024
volatile uint32_t adc_buffer[ADC_BUFFER_SIZE];
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_TIM3_Init();
// 启动DMA,将ADC转换结果直接搬运到内存
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);
// 启动定时器,开始产生触发脉冲
HAL_TIM_Base_Start(&htim3);
while (1) {
// 主循环处理数据
// ...
}
}
// ADC1初始化函数(由CubeMX生成,需检查关键配置)
static void MX_ADC1_Init(void) {
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; // 使用TIM3触发
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2; // 2个通道
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&hadc1);
// 配置通道转换序列
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0; // PA0
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1; // PA1
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
2.2 数据缓冲区管理
DMA在后台持续搬运数据,而主程序需要读取和处理这些数据,二者存在速度差异。
解决方案:实现双缓冲(乒乓缓冲) 或环形缓冲区机制。
- 双缓冲:DMA填满缓冲区A时,产生中断,程序处理A,同时DMA开始填充缓冲区B。
- 环形缓冲区:DMA在循环模式下工作,指向一个大的环形内存。程序通过维护
head(DMA当前位置)和tail(已处理位置)指针来安全地读取数据。
代码(环形缓冲区思路):
#define BUFFER_SIZE 4096
volatile uint32_t adc_ring_buffer[BUFFER_SIZE];
volatile uint32_t dma_last_index = 0; // 记录DMA上一次的位置
void ProcessADCData() {
// 1. 获取DMA当前写入位置
uint32_t current_index = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_adc1);
// 2. 计算自上次处理以来,新增加了多少数据
uint32_t data_to_process = (current_index - dma_last_index + BUFFER_SIZE) % BUFFER_SIZE;
if(data_to_process > 0) {
// 3. 处理从 dma_last_index 到 current_index 之间的数据
for(uint32_t i = 0; i < data_to_process; i++) {
uint32_t index = (dma_last_index + i) % BUFFER_SIZE;
uint16_t adc_value = adc_ring_buffer[index] & 0xFFFF; // 获取12位ADC值
// 进行数据处理(校准、滤波等)
ProcessSample(adc_value);
}
// 4. 更新最后一次处理的位置
dma_last_index = current_index;
}
}
// 在主循环中调用 ProcessADCData()
2.3 数据处理算法
在MCU能力允许的范围内,对原始ADC数据进行处理。
-
校准:
- 偏移校准:
Value_calibrated = Value_raw - Offset - 增益校准:
Value_calibrated = (Value_raw - Offset) * Gain
- 偏移校准:
-
数字滤波(在MCU上实现):
-
移动平均滤波:简单有效,适合消除随机噪声。
#define FILTER_WINDOW_SIZE 10 uint16_t moving_avg_filter(uint16_t new_sample) { static uint16_t buffer[FILTER_WINDOW_SIZE] = {0}; static uint32_t sum = 0; static uint8_t index = 0; sum = sum - buffer[index] + new_sample; buffer[index] = new_sample; index = (index + 1) % FILTER_WINDOW_SIZE; return (sum + FILTER_WINDOW_SIZE / 2) / FILTER_WINDOW_SIZE; // 四舍五入 } -
IIR低通滤波:计算量小,实时性好。
// 一阶IIR低通: Yn = α * Xn + (1-α) * Yn-1 float alpha = 0.1; // 平滑系数,越小越平滑,滞后越大 float filtered_value = 0; float iir_lowpass_filter(float new_sample) { filtered_value = alpha * new_sample + (1 - alpha) * filtered_value; return filtered_value; }
-
2.4 数据传输接口
代码 (通过USB Virtual COM Port传输):
// 集成USB CDC库后,可以使用printf重定向
#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE {
// 通过USB CDC发送一个字符
CDC_Transmit_FS((uint8_t *)&ch, 1);
return ch;
}
// 在主程序中,可以方便地发送数据
void SendDataToPC(uint16_t *data, uint32_t length) {
for(uint32_t i = 0; i < length; i++) {
// 发送原始二进制数据(更高效)
// CDC_Transmit_FS((uint8_t*)&data[i], 2);
// 或者发送格式化的字符串(更易读)
printf("%d,%.3f\n", i, (data[i] / 4095.0) * 3.3); // 假设Vref=3.3V
}
}
3. 程序流程设计
3.1 初始化流程
- 初始化系统时钟(配置PLL,最大化系统性能)。
- 初始化GPIO(LED、按键、ADC输入引脚等)。
- 初始化调试用UART(可选)。
- 初始化DMA。
- 初始化ADC并配置通道。
- 初始化触发用的定时器。
- 初始化其他通信外设(USB、SPI等)。
- 启用中断(如果需要)。
- 启动ADC DMA。
- 启动定时器。
3.2 主循环流程
int main(void) {
// 硬件初始化
System_Init();
while (1) {
// 1. 检查并处理ADC数据(非阻塞方式)
ProcessADCData();
// 2. 检查是否有命令从通信接口(如UART)传来
HandleUARTCommand();
// 3. 处理其他任务(如按键扫描、状态指示灯控制)
Key_Scan();
LED_Blink();
// 4. 必要时进入低功耗模式(如果采样间隔很长)
// __WFI();
}
}
参考代码 基于STM32F407单片机的数据采集系统的软件设计 www.youwenfan.com/contentcne/111862.html
总结
本设计方案为STM32F407数据采集系统提供了一个从底层驱动到上层应用的完整软件框架。核心在于利用 DMA+Timer+ADC 实现高效、自动的数据采集,通过环形缓冲区解决数据生产者与消费者的速度矛盾,并集成了必要的数据处理和数据传输模块。
实际开发中,强烈建议使用STM32CubeMX工具进行图形化引脚和外设配置,生成初始化代码,从而大大提高开发效率和减少配置错误。开发者可以在此基础上,根据具体的传感器类型、采样速率和通信需求进行进一步的定制和优化。

浙公网安备 33010602011771号