一、STM32F103开发
- STM32F103C8T6(入门款):共有48个引脚,Flash有64KB,SRAM有20KB
- STM32F103RCT6(进阶款):共有64个引脚,Flash有256KB,SRAM有48KB
- STM32F103ZET6(旗舰款):共有144个引脚,Flash有512KB,SRAM有64KB
STM32开发方式:
- 寄存器开发:直接操作寄存器对硬件控制,抽象层级低,开发效率低
- 标准库开发:对硬件寄存器操作进行封装,提供面向函数的API
- HAL库开发:更高级的硬件抽象,可图形化界面配置寄存器,屏蔽掉底层对硬件寄存器操作
二、定时器
1、定时器类型
| 定时器种类 | 捕获/比较通道 | 编码器接口 | 应用场景 |
|---|---|---|---|
| 高级定时器(TIM1、TIM8) | 4 | 1 | 拥有通用定时器全部功能,并具有重复计数器、互补输出、刹车输入等功能 |
| 通用定时器(TIM2~TIM5) | 4 | 1 | 16位向上/向下计数,拥有基本定时器全部功能,并具有内外时钟源选择、输入捕获、输出比较、编码器接口等功能 |
| 基本定时器(TIM6、TIM7) | 0 | 0 | 仅16位向上计数 |
2、输入捕获
IC(Input Capture):输入捕获。在输入捕获模式下,当通道引脚出现指定电平跳变时,锁存当前定时器计数值到捕获寄存器;
可测量PWM 频率、占空比、脉冲时长,典型应用:超声波测距。
常用函数:
cpp
HAL_TIM_Base_Start_IT(&htim1);//打开定时器使能
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);//打开捕获使能
HAL_TIM_Base_Start_IT(&htim2);
u16 TIM2CH2_CAPTURE_STA;//回响电平信号是否捕获到
u16 TIM2CH2_CAPTURE_VAL;//低14位记录总时长
//清除原来设置,配置定时器捕获上升沿
TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2);
TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2,TIM_ICPOLARITY_FALLING);
//检测到上升沿后捕获下降沿,获得之间的捕获值
TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING);
3、定时器编码器
1).工作原理
采用霍尔磁式编码器:磁环随电机转动,霍尔元件感应 N/S 极输出高低电平;
- 脉冲个数表示位移的大小,正负表示位移的方向。
旋转速度:波形频率越高,旋转越快
旋转方向:正转,A比B提前90°;反转,A比B滞后90°。
2).编码器的配置:
- 配置定时器4,选择Encoder Mode:变压器模式,该模式会占用通道1、通道2。

cpp
//变压器模式使能
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1);
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2);
//读取计数器的值,转速越大值越大,根据该值的正负判断方向
CNT = (short)TIM4->CNT;
4、定时器输出PWM
OC(Output Compare):输出比较,输出比较可以通过CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
- PWM:脉冲宽度调制。可通过对脉冲宽度的调制来获取所需要的模拟信号,通常应用于电机控制等领域。
- 频率 = 1 / TS
- 占空比 = TH / TS
驱动芯片:TB6612

1).电机驱动原理:
电磁感应,通过调节电机线的直流电压大小,即可实现直流电机调速,改变电机线的直流电压的极性即可实现点击转向。
- PA11控制电机A的PWM,PB13低电平,PB12高电平,AO1输出负极,AO2输出正极
- PA8控制电机B的PWM,PB14控制BO2,PB15控制BO1
2).配置
- pulse:时钟脉冲。
- CH Polarity:通道极性。配置初始占空比

cpp
//定时器1PWM使能
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
//定时器2使能,通过中断调整PWM
HAL_TIM_Base_Start_IT(&htim2);
//编码器使能
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1);
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2);
cpp
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static int Flag=0;
if(htim == &htim2){
if(++COUNT >= 10){ //每10ms自增一次并判断
switch(Flag){
case0:if(++PWM>=70) PWM=70,Flag=1;break; //加速
case1:if(--PWM<=30) PWM=30,Flag=0;break; //减速
default;break;
}
Count=0;
}
}
}
三、MPU6050
1、基础功能
集成3 轴陀螺仪 (测三轴角速度)和 3 轴加速度(测三轴加速度),I2C 通信最高 400KHz
支持 DMP 硬件解算,直接输出旋转矩阵、四元数、欧拉角
- MPU6050_Get_Accelscope:读取加速度原始值
- MPU6050_Get_Gyroscope:读取陀螺仪原始值。
2、DMP姿态解算
- 横滚角Roll:左右倾斜角度
- 俯仰角Pitch:前后倾斜角度
- 偏航角Yaw:水平旋转
cpp
float Roll,Pitch,Yaw;
void Read_DMP(void)
{
unsigned long sensor_timestamp;
unsigned char more;
long quat[4];
dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);//读取DMP数据
if(sensore&INV_WXYZ_QUAT)
{
q0=quat[0]/q30;
q1=quat[1]/q30;
q2=quat[2]/q30;
q3=quat[3]/q30;
Roll = asin(-2*q1*q3+2*q0*q2)*57.3;//算出横滚角
Pitch = atan2(2*q2*q3+2*q0*q1,-2*q1*q1-2*q2*q2+1)*57.3;//算出俯仰角
Yaw = atan2(2*(q1*q2+q0*q3),q0*q0+q1*q1-q2*q2-q3*q3)*57.3;//算出偏航角
}
}
四、PID控制算法
1、基础概念
- 目标值:目标值:期望达到的设定值(平衡车俯仰目标 0°)
- 测量值:传感器实时采集的值;
- 偏差=目标值-测量值。
2、参数说明

- e(k):本次偏差
- e(k-1):上一次偏差
- Σe(k):偏差的累积和
比例项Kp:快速纠正偏差,让系统尽快接近目标值;只有Kp,可能会留下稳态误差。
**积分项Ki:**消除稳态误差,让系统最终精确到目标值;积分太大,系统反应慢,容易"超调"和"震荡"。
**微分项Kd:**抑制快速变化(过冲、震荡),提高系统稳定性;Kd过大易导致抖动。
3、串级PID
特性:串级PID是两个PID普通环套起来,外环(速度环)控制响应较慢的变量,内环(直立环)控制需要快速响应的变量,外环的输出作为内环的输入。


1).直立环:
cpp
//直立环PD系数
float Balance_Kp,Balance_Kd;
//直立环控制
//参数:Angle---测量值,Gyro---角速度表示两次偏差的差值
int Blance(float Angle,float Gyro)
{
float Angle_bias;
int balance;
Angle_bias = 0-Angle;//偏差
//加上角速度的负值,抑制快速变化
balance = Balance_Kp*Angle_bias + Balance_Kd*(-Gyro);
return balance;
}
参数调节(前提,满占空比为7200):做直立环的参数范围确认,直立环是一个负反馈,将直立环涉及的参数Pitch、gyro输出,轻微晃动观察两个值的范围。一般估算Kp值是百位数,Kd是个位数。
2).速度环:
cpp
float Velocity_Kp,Velocity_Ki;
//速度环:让小车静止
//参数:encoder_left---左轮编码器读数,encoder_right---右轮编码器读数
int Velocity(int encoder_left,int encoder_right)
{
static float velocity,Encoder_Least,Encoder_bias,Encoder_Integral;
Encoder_Least = 0-(encoder_left+encoder_right);//偏差:目标速度-左右轮转速和
// 一阶低通滤波
Encoder_bias = Encoder_bias * 0.86 + Encoder_Least * 0.14;
Encoder_Integral += Encoder_bias;
//电机限幅
velocity = Velocity_Kp*Encoder_bias + Velocity_Ki*Encoder_Interal;//速度环
if(Flag_Stop==1) Encoder_Interal=0;
return velocity;
}
参数调节:正反馈,根据编码器最大偏差估算 Kp、Ki。
3).最终输出:
cpp
int LeftPWM = Balance_PWM + Velocity_PWM;
int RightPWM = Balance_PWM + Velocity_PWM;
set_pwm(LeftPWM, RightPWM);
void set_pwm(int left,int right)
{
if(left>7200) left=7200;//电机驱动限制
if(left<-7200) left=-7200;
if(right>7200) right=7200;
if(right<-7200) right=-7200;
if(left>0){
//电机A正转,置对应电机线电平}
else{//电机A反转}
_HAL_TIM_SetCompare(&htim1,通道,abs(left));//设置PWM
if(right>0){
//电机B正转,置对应电机线电平}
else{//电机B反转}
_HAL_TIM_SetCompare(&htim1,通道,abs(right));//设置PWM
}
