如何使用STM32测量接入模拟信号的电压周期及平均值
STM32 PC0模拟信号采集与参数计算实现方案
基础配置说明
- 本实现基于STM32 HAL库,适配STM32F1/F4主流系列,其他系列仅需修改对应外设寄存器参数即可使用
- 硬件连接:待测模拟信号直接接入PC0引脚(若信号幅值超过3.3V需先做分压,负电压信号需先做电平抬升)
- 核心实现逻辑:
- ADC采样获取PC0引脚的模拟量数值,换算为实际电压
- 边沿检测法捕获信号跳变沿,结合定时器计数计算信号周期
- 滑动窗口滤波法计算连续采样的电压平均值,降低噪声干扰
核心代码实现
1. 宏定义与全局变量声明
// 基础参数配置 #define ADC_VREF 3.3f // 参考电压,按实际硬件连接调整 #define ADC_RESOLUTION 4095.0f // 12位ADC量程 #define WINDOW_SIZE 20 // 平均值滑动窗口大小 #define EDGE_THRESHOLD 1.65f // 边沿检测阈值,默认取参考电压1/2,按需调整 // 全局变量 uint16_t adc_sample_buf[WINDOW_SIZE] = {0}; uint8_t buf_index = 0; uint32_t last_edge_tick = 0; float signal_period = 0; uint8_t edge_flag = 0; ADC_HandleTypeDef hadc1; TIM_HandleTypeDef htim2;
2. 外设初始化代码
需要先初始化GPIO、ADC、基础定时器三个外设,核心配置逻辑如下:
void MX_Peripherals_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 开时钟:GPIOC、ADC1、TIM2 __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_TIM2_CLK_ENABLE(); // PC0配置为模拟输入模式 GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // ADC1配置:12位分辨率,单通道非扫描模式 hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); // ADC通道10(对应PC0)配置,采样时间28.5个周期 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_10; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // TIM2配置:计数频率1MHz,用于周期计时 htim2.Instance = TIM2; htim2.Init.Prescaler = (HAL_RCC_GetPCLK1Freq() / 1000000) - 1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); HAL_TIM_Base_Start(&htim2); // 启动ADC转换 HAL_ADC_Start(&hadc1); }
3. 电压采集与平均值计算函数
// 获取ADC原始采样值 uint16_t get_adc_raw(void) { HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); return HAL_ADC_GetValue(&hadc1); } // 换算为实际电压并更新滑动窗口,返回平均值 float get_voltage_avg(void) { uint16_t raw_val = get_adc_raw(); // 更新滑动窗口 adc_sample_buf[buf_index] = raw_val; buf_index = (buf_index + 1) % WINDOW_SIZE; // 计算平均值 uint32_t sum = 0; for(uint8_t i=0; i<WINDOW_SIZE; i++) { sum += adc_sample_buf[i]; } return (sum * ADC_VREF) / (WINDOW_SIZE * ADC_RESOLUTION); }
4. 信号周期计算函数
float get_signal_period(void) { float current_voltage = get_adc_raw() * ADC_VREF / ADC_RESOLUTION; // 上升沿检测:当前电压超过阈值,上一次采样低于阈值 if(current_voltage > EDGE_THRESHOLD && edge_flag == 0) { edge_flag = 1; uint32_t current_tick = TIM2->CNT; if(last_edge_tick != 0) { // 定时器计数频率1MHz,计数值差即为us数,换算为s signal_period = (current_tick - last_edge_tick) / 1000000.0f; } last_edge_tick = current_tick; } else if(current_voltage < EDGE_THRESHOLD) { edge_flag = 0; } return signal_period; }
调用示例
int main(void) { HAL_Init(); SystemClock_Config(); // 按实际芯片配置系统时钟 MX_Peripherals_Init(); float voltage_avg = 0; float period = 0; while(1) { voltage_avg = get_voltage_avg(); period = get_signal_period(); // 此处可添加串口打印逻辑输出参数 HAL_Delay(10); } }
注意事项
- 若输入信号噪声较大,可在PC0引脚对地接100nF滤波电容,或在软件中增加多次采样取均值的逻辑
- 边沿检测阈值
EDGE_THRESHOLD需要根据实际输入信号的幅值调整,建议设置为信号峰峰值的中间值 - 采样频率需高于待测信号最高频率的5倍以上,若测量高频信号可适当缩短ADC采样时间,提高采样率
- 测量低于100Hz的低频信号时,可替换定时器为SysTick计时,无需额外占用定时器资源
内容的提问来源于stack exchange,提问作者Alperen Eroğlu
相关产品推荐
相关产品推荐

