在两周四次课的时间里从0学习到能独立做出一个能超声波避障,能蓝牙遥控的小车, 收获真的很大, 同时也领略到偏硬件方向编程的乐趣(C语言不老!)
主要功能点在GPIO的编程, TIM定时器的初步使用, USART串口通讯
编译平台为KEIL, 使用STM32开发板
main.c
用来将各个模块进行初始化,(但是初始化的具体操作放在各自的.c文件中)
while(1)循环中定时探测前方障碍距离
bluetooth_init();
SysTick_Init();//延时初始化
echo_init();
while(1)
{
if(go_flag)//处于前进状态才去发送超声波判断距离
if(get_length()<0.005)
{
turn(1);
Delay_ms(1500);
gogogo(1);
}
}
TIM3(暂时无用)和USART(蓝牙信号指令的接收)的中断函数我也放在了main.c中
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
void USART1_IRQHandler(void)
{
int order ;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
order = USART_ReceiveData(USART1);
switch(order)
{
case 0x31://前进
go_flag=1;
gogogo(1);
break;
case 0x32://倒车
gogogo(-1);
break;
case 0x33://加速
changeSpeed(-1);
break;
case 0x34://减速
changeSpeed(1);
break;
case 0x35://左转
turn(1);
break;
case 0x36://右转
turn(2);
break;
case 0x37://停车
gogogo(0);
go_flag=0;
break;
}
}
}
这里发现了一个KEIL的bug,我通过断点调试了一整天才发现的!!
如果把USART_IRQHandler中的order变量设置为.c的全局变量,则每次进入中断前,order就会被自动设置为0x32,然后跑到case2里执行一遍,然后再中断一次,以正常方式进行,编译器简直有毒--个人怀疑可能是编译器的优化做的太过度了
echo.c
int toofar=0;
int psc = 71; //分频系数 72 000 000 /72 =1us , *1000 0=10ms(1.5米)意味足够远,不予考虑
/* 初始化模块的 GPIO 以及初始化定时器 TIM2*/
void echo_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitStructer.TIM_Period=9999;// 定时周期为 10000
TIM_TimeBaseInitStructer.TIM_Prescaler=psc;
TIM_TimeBaseInitStructer.TIM_ClockDivision=0;
TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructer.TIM_RepetitionCounter = 0 ;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructer);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_InitTypeDef NVIC_InitStructer;
NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructer.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructer.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructer.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructer);
TIM_Cmd(TIM2,DISABLE);// 关闭定时器使能
GPIO_InitTypeDef GPIO_InitStructer;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/*TRIG 信号 */
GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructer.GPIO_Pin=GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructer);
/*ECOH 信号 */
GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructer.GPIO_Pin=GPIO_Pin_9;
GPIO_Init(GPIOB, & GPIO_InitStructer);
}
初始化函数不解释,但是注意一下psc写成全局变量是为了方便修改使用者决定的什么才是"足够短",需要避障的
double get_length(void)
{
double length=0;
long int t;
double time;
GPIO_SetBits(GPIOB,GPIO_Pin_8);
Delay_us(20);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==RESET);
TIM_Cmd(TIM2,ENABLE);// 回响信号到来,开启定时器计数
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET);
TIM_Cmd(TIM2,DISABLE);// 关闭定时器
if(!toofar) //没有完成一次计数周期,即距离不是很远
{
t=TIM_GetCounter(TIM2);// 获取 TIM2 寄存器中的计数值
time = t/1000000; //往返时间,单位s
length = 340*time/2; //单程长度,单位m
TIM2->CNT=0; // 将 TIM2 计数寄存器的计数值清零
}
else
{
toofar = 0;
length =99; //足够远,不用考虑
TIM2->CNT=0; // 将 TIM2 计数寄存器的计数值清零
}
return length;
}
get_length()用来打开超声波模块,最后返回一个距离值
void TIM2_IRQHandler(void) // 中断,当距离足够远时,设置toofar = 1,不必再计算
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);// 清除中断标志
toofar = 1;
}
}
这个中断函数非常重要
比如,如果距离足够长,导致计数器完成了一个计数周期,开始了新的计数周期,那么读出来的getCounter就会比原来小一整个周期,所以如果完成了一个周期的计数,就算他够远,设置toofar = 1
Bluetooth.c
#include "bluetooth.h"
void bluetooth_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef U;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
U.USART_BaudRate = 9600;
U.USART_WordLength = USART_WordLength_8b;
U.USART_StopBits = USART_StopBits_1;
U.USART_Parity = USART_Parity_No;
U.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
U.USART_Mode = USART_Mode_Rx;
USART_Init(USART1,&U);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_Cmd(USART1,ENABLE);
}
这里面只有关于蓝牙引脚的初始化
car.c
int carSpeedPeriod = 0;
void car_init(int i)
{
//和转方向相关的引脚
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_1| GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出?
GPIO_Init(GPIOD, &GPIO_InitStructure);
//和timer相关的引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//抄呼吸灯
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_Init(GPIOB,&GPIO_InitStructure);
TIM_TimeBaseInitTypeDef T;
carSpeedPeriod+= i;
T.TIM_Period = carSpeedPeriod;
T.TIM_Prescaler = 719;
T.TIM_ClockDivision = 0;
T.TIM_CounterMode = TIM_CounterMode_Up;
T.TIM_RepetitionCounter = 0 ;
TIM_TimeBaseInit(TIM3,&T);
TIM_Cmd(TIM3,ENABLE);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_OCInitTypeDef O;
O.TIM_OCMode = TIM_OCMode_PWM2;
O.TIM_Pulse =5;
O.TIM_OutputState = TIM_OutputState_Enable;
O.TIM_OCPolarity=TIM_OCPolarity_Low;
TIM_OC1Init(TIM3,&O);
TIM_OC2Init(TIM3,&O);
TIM_OC3Init(TIM3,&O);
TIM_OC4Init(TIM3,&O);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
控制电机的各个引脚的初始化
为什么有全局变量carSpeedPeriod呢? 因为加减速的时候需要先读出原来的转速值,然后自增/减操作
注意电机的转向并不是完全向前哦! 因为电机是对称的,所以另一侧的电机要反着转
// 控制车子的运作方向
void gogogo(int direction)
{
//前进
if(direction == 1)
{
carSpeedPeriod = 0;
car_init(30);
GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_RESET);
}
//停车
else if(direction == 0)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,DISABLE);
TIM_Cmd(TIM3,DISABLE);
}
//后退
else if(direction == -1)
{
carSpeedPeriod = 0;
car_init(30);
GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_SET);
}
}
//转向,没有考虑回正
void turnx(int direction)
{
//左转
if(direction == 1)
{
GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_SET);
}
//右转
if(direction == 2)
{
GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_RESET);
GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_RESET);
}
}
void turn(int direction)
{
//左转
if(direction == 1)
{
turnx(1);
Delay_ms(1000);
gogogo(1);//回轮
}
//右转
else if(direction == 2)
{
turnx(2);
Delay_ms(1000);
gogogo(1);
}
}
//改变速度,通过改变周期
void changeSpeed(int i)
{
if(carSpeedPeriod > 2)//不能再小了
car_init(i*2);
}
这些操作都是通过重新注册GPIO引脚和TIM定时器的配置完成的,难度不大,但是一定要细心