前言:
这是使用stm32系列单片机使用hal库开发的一个红外循迹小车,用于理解stm32一些基础外设的使用如PGIO,定时器中断,pwm波生成等。使用的外设模块有“两块tb6612直流电机驱动模块”“两块tcrt5000红外循迹模块”
tb6612直流电机驱动模块
tb6612直流电机驱动模块的功能
双驱动,也就是可以驱动两个电机。
STBY:接单片机的IO口,清零,电机全部停止。
置1,通过AIN1 AIN2,BIN1,BIN2来控制正反转
VM 接12V电源
VCC 接5V电源
GND 接地
PWMA 接单片机的PWM口
TB6612FNG每通道输出最高1A的连续驱动电流,启动峰值电流达2A/3A(连续脉冲/单脉冲);4种电机控制模式:正转/反转/制动/停止;PWM支持频率高达100 kHz;待机状态;片内低压检测电路与热停机保护电路;工作温度:-20~85℃;SSOP24小型贴片封装。
tb6612直流电机驱动模块的引脚配置及原理图
原理图
真值表
在程序里我们使用io口输出不同的pwm波控制车速用来达到转向的效果 AIN1 AIN2 BIN2 BIN2输出对应电平保持执行即可,这也是程序设计思路的一部分。
tcrt5000红外循迹模块
tcrt5000红外循迹模块的功能
TCRT5000 传感器的红外发射二极管不断发射红外线,当发射出的红外线没有被反射回来或被反射回来但强度不够大时,红外接收管一直处于关断态,此时模块的输出端为高电平,指示二极管一直处于熄灭状态;被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时模块的输出端为低电平,指示二极管被点亮。
tcrt5000红外循迹模块的原理图
这里我们主要看他的输出指示灯如图led灯阴极与vcc连接,被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时模块的输出端为低电平,指示二极管被点亮。
当左边的指示灯亮起时说明左边循迹模块触碰边线这时让左轮停止右轮加速实现转弯的效果 这是整体的一个程序设计思路
使用cubeMX生成项目的主要配置
时钟配置
选择外部时钟输入(因为我的开发板上外置一颗24Mhz的晶振)
使用pll倍频到80Mhz这样我们的工作频率就来到了80mhz
pwm输出
我将pa6,pa7设置了pwm波输出用来控制车速 选择了定时器16和17都为通道1
在Timers里配置定时器16使能并选择通道一生成pwm波定时器17同样的操作
在参数设置可以设置pwm波的频率80000000/4000/100=200hz
输出GPIO配置
这里我把pa11,pa12设置成GPIO_INPUT用来读取两个红外循迹模块的返回结果根据结果判断小车的运动状态
定时器中断配制
这里我用到的是定时器tim4 时钟源选择internal clock
在参数设置里分频系数设置为80这样我们的工作频率就变成了80000000/80=1000000(应为我的系统时钟频率为80MHZ) 计数器周期(counter period)设置为10000这样计数器周期就变成了1000000/10000=100hz 也就是10ms执行一次中断
在NVIC settings里将中断使能
生成代码
具体代码
新建.c和.h文件模块化编程
在interruput.c里编写中断服务函数这里有用的其实只有读取两个模块状态的两行代码其他是我写的按键检测的代码
#include "headfile.h"
#include "interrupt.h"
struct keys key[4]={0,0,0};
bool carleft,carr,car3;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance==TIM4) //是我们设置的TIM4执行的中断任务
{
carleft=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12); //读取模块1的状态(右边)
carr = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11); //读取模块2的状态(左边)
key[0].key_sta =HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].key_sta =HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].key_sta =HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].key_sta =HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0); //读取初始电平状态
for(int i=0;i<4;i++)
{
switch (key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta==0) key[i].judge_sta=1;
} // 第一次判断按键是否按下
break;
case 1:
{
if(key[i].key_sta==0) {
key[i].judge_sta=2;
key[i].single_flag=1;
}
else key[i].judge_sta=0;
} // 10ms(因为定时器终端周期设置为10ms)后判断按键是否真的按下
break;
case 2:
{
if(key[i].key_sta==1)
{
key[i].judge_sta=0;
}
} // 回到初始化
break;
}
}
}
}
主逻辑我写在了主函数里将函数写在循环里就可以用了car_run 要初始化的变量有car_speed,pa6
pa7.
/*
所有需要初始化的变量
*/
unsigned char car_speed =10; //车的初速度
unsigned char PA6=10;
unsigned char PA7=10;
unsigned char view=0;
extern unsigned int frq1,frq2;
extern bool carleft,carr,car3;
extern float duty1, duty2;
extern struct keys key[];
void key_proc(void);
void disp_proc(void);
void car_run(void);
void car_run(void)
{
if(carleft==1&&carr==1) //拿在手上停止
{
PA7 = 0;
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,PA7);
PA6 = 0;
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,PA6);
}
if(carleft==0&&carr==0) // 两边都没触碰到边线直行
{
PA7 = car_speed;
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,PA7);
PA6 = car_speed;
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,PA6);
}
if(carleft==1&&carr==0) // 右边触碰边线左调
{
PA6=car_speed+40;
PA7=0;
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,PA7);
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,PA6);
}
if(carr==1&&carleft==0) // 左边触碰边线右调
{
PA7=car_speed+40;
PA6 =0 ;
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,PA6);
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,PA7);
}
}
效果展示
红外循迹