程序框架
本小节对程序框架和运行流程进行说明。
打开 MiaowLabs两轮自平衡小车「小霸王Lite」光盘\02、程序源代码\Mwbalanced-stm32-小霸王Lite-firmware-互补滤波-none V3.33\BasicBalance.uvprojx
工程文件,展开工程分组如下图所示:
如图所示,可以看到工程分有 6 个分组,其中 FWlib 分组主要是 HAL 库的文件代码,CMSIS 分组主要是 Cortex-M3 核心的文件代码,Startup 分组是 stm32f10x 的启动文件代码,BSP 分组主要是外设硬件的驱动程序代码,比如 MPU6050、oled 等,SYS 分组主要是滴答定时器和调试相关的配置代码,USER 分组是用户代码。
下面我们先讲解一下 USER 分组的 main.c。main.c 包含了所有硬件初始化和参数初始化代码。初始化代码如下:
int main(void)
{
BspInit(); //初始化BSP
PIDInit(); //初始化PID
CarUpstandInit();//初始化系统参数
SysTick_Init(); //初始化定时器
if(IsInfrareOK())//检测是否悬挂红外模块
g_iGravity_Offset = 1; //如果检测到悬挂红外模块,则更改偏移值。
ShowHomePageInit();//初始化OLED显示屏主页
while (1)//进入主循环
{
SecTask();//秒级任务,主要是记录小车运行时间、读取电池电压等非实时任务
if(SoftTimer[1] == 0)//系统软件定时器1,分辨率为1ms,递减计数
{// 每隔20ms 执行一次
SoftTimer[1] = 20;
ResponseIMU();//上报姿态数据到APP
DebugService(); //上位机调试数据发送
Parse(Uart3Buffer); //APP数据解析函数
}
if(SoftTimer[2] == 0)//系统软件定时器2,分辨率为1ms,递减计数
{// 每隔20ms 执行一次
SoftTimer[2] = 20;
ShowHomePage();//刷新OLED页面
Read_Distane();//读取超声波测距距离
if(g_CarRunningMode == ULTRA_FOLLOW_MODE){
if(IsUltraOK())UltraControl(0); //设置超声波跟随模式
}
if(g_CarRunningMode == ULTRA_AVOID_MODE){
if(IsUltraOK())UltraControl(1); //设置超声波避障模式
}
else if(g_CarRunningMode == INFRARED_TRACE_MODE){
TailingControl();//设置红外循迹模式
}
}
}
}
初始化代码主要是硬件底层驱动和系统参数的初始化。下面我们来讲解一下功能函数的具体作用。
BspInit 主要是硬件底层驱动的初始化,包括 STM32 外设的初始化、外围模块比如 MPU-6050 的初始化。具体见下面代码:
void BspInit(void)
{
SWDConfig(); //SWD 调试接口配置,使能 SWD,失能 JTAG
ADCInit(); //ADC 初始化
USART1Init(); //串口 1 初始化-底板预留下载及调试用
USART3Init(0); //串口 3 初始化-用于蓝牙
TIM1_Cap_Init(); //TIM1初始化-用于超声波跟随功能
TIM3_PWM_Init(); //PWM初始化
TIM2_Encoder_Init(); //TIM2 正交解码初始化-用于测速
TIM4_Encoder_Init(); //TIM4 正交解码初始化-用于测速
i2cInit(); //I2C 初始化
InfraredIOInit(); //红外 IO 口初始化
OLED_Init(); //OLED 初始化
MPU6050_Init(); //MPU6050 初始化
LEDInit(); //指示灯初始化
UltraSelfCheck(); //超声模块开机自检
InfrareSelfCheck(); //红外模块开机自检
delay_ms(500); //延时 0.5s,等待蓝牙模块启动
Uart3SendStr("\r\nAT+BAUD8\r\n"); //配置蓝牙串口波特率为 115200 ( 原波特率9600 )
USART3Init(1); //更改 UART3 波特率为 115200
delay_ms(20); //延时 20ms,等待波特率稳定
SetBlueToothName(); //配置蓝牙模块名称
}
PIDInit 主要是小车系统的 PID 参数初始化。
CarUpstandInit 主要是小车系统的各种参数初始化。
SysTick_Init 是系统滴答定时器 SysTick 的初始化,在这里设置为 1ms 中断一次。
if(IsInfrareOK()) g_iGravity_Offset = 1; 检测是否悬挂红外循迹模块,如果有则需要修改重心偏移值。因为在一侧悬挂红外后,小车重心会偏移。
ShowHomePageInit 主要是在 OLED 显示 logo。
while(1) 主循环中主要是执行一些非实时任务(早些迟些执行都无所谓的任务),人为定义一个秒级任务,轮询执行这些任务。这些非实时任务有上报数据、调试数据、解析协议、刷新 OLED 数据、读取距离等。
执行完初始化,代码会由于滴答定时器 SysTick 进入中断而转跳到 SysTick 定时中断服务函数中执行。在 stm32f10x_it.c 中可以找到 SysTick_Handler 定时中断服务函数。代码具体内容如下:
void SysTick_Handler(void)
{
SoftTimerCountDown(); //软定时器
g_u8MainEventCount++; //主事件计数变量
g_u8SpeedControlPeriod++; //速度环控制周期计数变量
SpeedControlOutput(); //速度环控制输出函数,每1ms执行一次
if(g_u8MainEventCount>=5) //5ms进入一次
{
g_u8MainEventCount=0;
GetMotorPulse(); //捕获电机脉冲(速度)函数,每5ms执行一次
}
else if(g_u8MainEventCount==1)
{
MPU6050_Pose(); //读取MPU6050数据函数,每5ms执行一次
AngleCalculate(); //角度环计算函数,每5ms执行一次
}
else if(g_u8MainEventCount==2)
{
AngleControl(); //角度环控制函数,每5ms执行一次
}
else if(g_u8MainEventCount==3)
{
g_u8SpeedControlCount++;
if(g_u8SpeedControlCount >= 5)//25ms
{
SpeedControl(); //车模速度控制函数,每25ms调用一次
g_u8SpeedControlCount=0;
g_u8SpeedControlPeriod=0;
}
}
else if(g_u8MainEventCount==4)
{
MotorManage(); //电机使能/失能控制函数,每5ms执行一次
MotorOutput(); //电机输出函数,每5ms执行一次
}
}
SoftTimerCountDown 是软定时器在 SysTick 中断中得到精确的递减。
g_u8MainEventCount 是主事件计数变量。我们需要在中断中运行的代码,在 1ms 内运行不完,所以需要主事件计数变量将代码合理分段,放到 5 个 1ms 内运行。
g_u8SpeedControlPeriod 是速度环的控制周期计数变量。
在 5ms 内执行 GetMotorPulse、MPU6050_Pose、AngleCalculate、AngleControl、MotorManage、MotorOutput,而速度环是 25ms 运行一次。