在智能小车开发中,精确的速度控制往往是决定项目成败的关键因素之一。相比简单的开关控制,基于模拟量的调速系统能带来更平滑的运动体验和更精准的定位能力。本文将带你深入STM32F407的ADC模块应用,构建一个从电位器输入到PWM电机输出的完整调速闭环。
我们需要准备的硬件包括:
信号链路按照以下方式连接:
code复制电位器中间引脚 → PA1(ADC1_IN1)
电位器两端分别接3.3V和GND
L298N的ENA → PA8(TIM1_CH1)
电机输出端接L298N的OUT1/OUT2
注意:电机驱动电源应与MCU电源共地,但电压需根据电机规格单独提供(通常7-12V)
STM32F407的ADC主要特性参数:
| 参数项 | 规格说明 |
|---|---|
| 分辨率 | 12位(0-4095) |
| 采样时钟 | 最大36MHz(APB2时钟分频) |
| 转换时间 | 3周期(采样)+12周期(转换) |
| 输入阻抗 | 约50kΩ |
| 参考电压 | VDDA=3.3V,VSSA=0V |
对于调速应用,推荐配置:
首先配置ADC的GPIO和基本参数:
c复制// adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f4xx.h"
void ADC1_Init(void);
u16 Get_ADC_Value(u8 ch, u8 times);
#endif
c复制// adc.c
#include "adc.h"
#include "delay.h"
void ADC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// PA1 as analog input
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
}
对应电机控制的PWM生成代码:
c复制// pwm.h
#ifndef __PWM_H
#define __PWM_H
#include "sys.h"
void TIM1_PWM_Init(u16 arr, u16 psc);
#endif
c复制// pwm.c
#include "pwm.h"
#include "stm32f4xx_tim.h"
void TIM1_PWM_Init(u16 arr, u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
// PA8 as TIM1_CH1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputState_Disable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
获取ADC值并进行软件滤波:
c复制u16 Get_ADC_Value(u8 ch, u8 times)
{
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_84Cycles);
u32 temp_val = 0;
for(u8 i=0; i<times; i++)
{
ADC_SoftwareStartConv(ADC1);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
temp_val += ADC_GetConversionValue(ADC1);
delay_ms(5);
}
return temp_val/times;
}
将ADC值转换为PWM占空比:
c复制// main.c
#include "stm32f4xx.h"
#include "adc.h"
#include "pwm.h"
#include "usart.h"
#define PWM_PERIOD 999 // ARR值
#define PWM_PRESCALER 83 // 84MHz/(83+1) = 1MHz
void Speed_Control(void)
{
u16 adc_val = Get_ADC_Value(ADC_Channel_1, 5);
u16 pwm_duty = (u32)adc_val * PWM_PERIOD / 4095;
// 死区处理
if(pwm_duty < 50) pwm_duty = 0;
else if(pwm_duty > PWM_PERIOD-50) pwm_duty = PWM_PERIOD;
TIM_SetCompare1(TIM1, pwm_duty);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART1_Init(115200);
ADC1_Init();
TIM1_PWM_Init(PWM_PERIOD, PWM_PRESCALER);
while(1)
{
Speed_Control();
delay_ms(100);
}
}
改善系统响应速度的几种方法:
c复制#define FILTER_LEN 5
u16 adc_filter_buf[FILTER_LEN];
u16 Moving_Average_Filter(u16 new_val)
{
static u8 index = 0;
u32 sum = 0;
adc_filter_buf[index++] = new_val;
if(index >= FILTER_LEN) index = 0;
for(u8 i=0; i<FILTER_LEN; i++)
sum += adc_filter_buf[i];
return sum/FILTER_LEN;
}
c复制u16 Nonlinear_Map(u16 adc_val)
{
// 低速区放大,高速区压缩
if(adc_val < 1000) return adc_val * 1.5;
else if(adc_val < 3000) return adc_val;
else return adc_val * 0.8;
}
调试过程中可能遇到的问题及解决方案:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| ADC读数不稳定 | 电源噪声大 | 增加滤波电容(0.1uF+10uF) |
| 采样时间不足 | 增大ADC_SampleTime参数 | |
| PWM无输出 | 定时器未使能PWM输出 | 检查TIM_CtrlPWMOutputs调用 |
| GPIO未配置为复用功能 | 确认GPIO_Mode_AF设置 | |
| 电机启动不顺畅 | 死区设置不合理 | 调整Speed_Control中的死区值 |
| PWM频率不合适 | 尝试10kHz-20kHz频率范围 |
完成基础调速后,可以考虑:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
} PID_Controller;
float PID_Update(PID_Controller* pid, float setpoint, float measurement)
{
float error = setpoint - measurement;
pid->integral += error;
float derivative = error - pid->prev_error;
pid->prev_error = error;
return pid->Kp*error + pid->Ki*pid->integral + pid->Kd*derivative;
}
python复制# 简易串口绘图示例
import serial
import matplotlib.pyplot as plt
ser = serial.Serial('COM3', 115200)
plt.ion()
fig = plt.figure()
while True:
data = ser.readline().decode().strip()
if data.startswith('SPEED:'):
speed = float(data.split(':')[1])
plt.scatter(time.time(), speed)
plt.pause(0.01)