一、项目简介
通过利用STM32F103C8、直流电机、按键、us015超声波测距模块、MPU6050、蜂鸣器、TFLCD、霍尔传感器等硬件设计一个车载电控单元,实现了手动加档、实时显示车速、超声波避障预警、车身倾斜预警以及更新固件功能,以保证行车安全。
二、项目框架
三、硬件选型以及实现思路
1.电机驱动
这里我采用的是360°的SG90舵机来模拟车轮电机。其工作原理是靠PWM驱动实现变速。该舵机实际上由一个直流电机和驱动电路组成,它不像180°舵机那样可以实现控制角度,它只能实现控制方向、速度和停止。其对照关系图如下:
因此,根据上述工作原理,我采用了两个按键来分别实现正向的加速过程和反向的加速过程,如果到达最高速度,此时继续按下,会到最低速度。
2.测速驱动
这是通过A3144霍尔传感器模块来实现测量电机转速的。A3144只对单磁极有效,其磁场阈值为7mT,这个模块由霍尔传感器和电压比较器组成,其电路图如下所示:
那么要想了解上图的工作原理,首先我们要知道什么是霍尔效应。霍尔效应是指在一个导体两端,接上电流,此时给这个导体施加垂直与电流的磁场,由于洛伦兹力,电子会往导体一侧偏转,造成导体两侧有电压差,这个电压叫做霍尔电压。因此,只要我们用一个磁铁靠近霍尔传感器,这个传感器就能将磁场的变化转变为电压的变化,而我们的电机里边是有永磁铁的,因此我们就能通过霍尔传感器来检测电机转动时候的磁场变化。而通过官方手册,我们发现A3144霍尔传感器在磁场强度高于本身的磁场阈值时,会输出一个低电平信号。也就是从上图的U2处输出一个低电平,此时经过一个比较器,当这个低电平电压小于2.5v时,会输出0,此时会点亮D3二极管,代表检测了一次磁场。因此,我们只要将这个霍尔传感器靠近电机的最高点,当转动一圈后电机的永磁铁S极经过这个电机最高点的时候,就可以检测到此时电机的磁场变化,并将其转为为低电平,那么我们就可以利用下降沿触发中断,在外部中断里边进行计数,同时通过定时器中断1s去看此时1s有多少个计数值,然后再乘以60,即为一分钟的转速。
3.us015驱动
在此处我们选用us015作为避障预警模块,其工作原理是trigger引脚发出一个10us以上的电平。然后此时系统就会发出超声波脉冲,然后系统开始检测回波信号,当检测到了回波信号以后通过echo引脚输出。而根据echo引脚输出的高电平持续时间可计算出距离。其公式为(高电平时间*340)/2。因此,我们从中就可以知道,需要创建一个us015任务来不停发出10us高电平信号。然后通过定时器捕获功能来获得返送回来的高电平脉冲宽度。其中,需要注意的是,对于启动任务,我们至少在该任务中添加1s的延时,这是因为,如果启动信号的频率过快,会导致返回的高电平频率过快,从而导致进入捕获中断的频率过快,会使得CPU资源耗尽导致程序跑飞。
4.MPU6050驱动
对于利用MPU6050模块来测量车身倾斜度,我们需要得到欧拉角数据就够了。因此实际上我们只需要移植mpu6050的初始化文件以及DMP的初始化文件就够了,但是,移植过程中需要注意的是,接口问题。由于我们用的是软件来模拟iic,因此我们将输出模式配置成推挽输出就行。其次,有一个坑,就是下载程序的时候记得在DEBUG里边用use stlink模式,而不是用仿真器模式,否则会出现iic时序错误,导致mpu6050初始化失败问题。
5.IAP的bootloader程序
最后说一下IAP任务,该任务主要实现三个功能,接收、拷贝、跳转。在此处的接收步骤,我们采用DMA来接收串口数据,从而节省CPU资源。在此处我给出大概的程序框架,因为c8t6的资源受限,已经无法添加IAP的更新任务了。首先是串口以及DMA的初始化。其中,需要注意的是根据手册,要想开启串口的DMA传输,需要使能串口CR3寄存器的DMAR位才能开启DMA接收,因此需要在串口初始化程序中加入以下代码:USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1的DMA接收
将DMA初始化以后,创建IAP任务,并且在任务里边加入等待信号量,开启串口中断,在串口中断函数里边只写入释放信号量。如此,当检测到串口接收到数据时,进入中断,然后切换到IAP任务,此时在IAP任务里边开启一次DMA传输,然后去检测定义的内存变量是否有值,如果有值,代表已经接收到APP程序,此时调用写flash函数将APP程序拷贝到flash区,然后调用跳转函数。
其他的驱动比较简单,略过。
四、主程序代码
UCOSII任务设置///
//START 任务
//设置任务优先级
#define START_TASK_PRIO 10 //开始任务的优先级设置为最低
//设置任务堆栈大小
#define START_STK_SIZE 64
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata);
//电机正向转动任务
//设置任务优先级
#define PWM_TASK_PRIO 7
//设置任务堆栈大小
#define PWM_STK_SIZE 64
//任务堆栈
OS_STK PWM_TASK_STK[PWM_STK_SIZE];
//任务函数
void pwm_task(void *pdata);
//电机反向转动
#define Re_TASK_PRIO 6
#define Re_STK_SIZE 64
OS_STK Re_TASK_STK[Re_STK_SIZE];
void reverse_task(void *pdata);
//电机转速显示任务
#define Motoshow_task_prio 4
#define Motoshow_task_size 64
OS_STK Motoshow_task_stk[Motoshow_task_size];
void Motoshow_task(void *pdata);
//us105测量距离任务
#define us105_task_prio 5
#define us105_task_size 128
OS_STK us105_task_stk[us105_task_size];
void us105_task(void *pdata);
//us_015启动任务
#define st_task_prio 9
#define st_task_size 32
OS_STK st_task_stk[st_task_size];
void st_task(void *pdata);
//MPU6050测量倾斜角度任务
#define mpu6050_task_prio 8
#define mpu6050_task_size 512
OS_STK mpu6050_task_stk[mpu6050_task_size];
void mpu6050_task(void *pdata);
//定义需要用到的全局变量
//OS_EVENT *Bsem;
//OS_EVENT *email;
OS_EVENT *sig1;
OS_EVENT *sig2;
OS_EVENT *sig3;
OS_EVENT *sig4;
led_d led1;
led_d bep;
led_d us;
volatile u8 flag=8;
volatile u8 mark=8;
volatile u32 rate=0;
volatile u32 buff;
volatile u8 STA=0;
volatile u16 PluseWidth;
u32 num;
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
MPU_Init();
mpu_dmp_init();
LED_Init(&led1,GPIOC,GPIO_Pin_13); //初始化与LED连接的硬件接口
Beep_Init(&bep,GPIOB,GPIO_Pin_15);
us105Init(&us,GPIOA,GPIO_Pin_12);
TIM3_PWM_Init(99,14399);
TIM2_Int_Init(4999,14399);
TIM1_Cap_Init(84,71999);//计数频率也不能太高,否则程序也会跑飞,因为会频繁触发中断占用cpu资源
EXTIX_Init();
LCD_Init();
uart_init(115200);
OSInit();
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
OSStart();
}
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
//Bsem=OSSemCreate(0);
sig1=OSSemCreate(0);
sig2=OSSemCreate(0);
sig3=OSSemCreate(0);
sig4=OSSemCreate(0);
//email=OSMboxCreate((void *)0);
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
OSTaskCreate(pwm_task,(void *)0,(OS_STK*)&PWM_TASK_STK[PWM_STK_SIZE-1],PWM_TASK_PRIO); //而R0寄存器是堆栈指针PSP,如果PSP=0,代表任务第一次进行任务切换,所以
OSTaskCreate(reverse_task,(void *)0,(OS_STK*)&Re_TASK_STK[Re_STK_SIZE-1],Re_TASK_PRIO);
OSTaskCreate(Motoshow_task,(void *)0,(OS_STK*)&Motoshow_task_stk[Motoshow_task_size-1],Motoshow_task_prio);
OSTaskCreate(us105_task,(void *)0,(OS_STK*)&us105_task_stk[us105_task_size-1],us105_task_prio);
OSTaskCreate(st_task,(void *)0,(OS_STK*)&st_task_stk[st_task_size-1],st_task_prio);
OSTaskCreate(mpu6050_task,(void *)0,(OS_STK*)&mpu6050_task_stk[mpu6050_task_size-1],mpu6050_task_prio);
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//电机正向转动任务
void pwm_task(void *pdata)
{ u8 err;
while(1)
{
OSSemPend(sig1,0,&err);
TIM_SetCompare3(TIM3,flag);
}
}
//电机反转任务
void reverse_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sig2,0,&err);
TIM_SetCompare3(TIM3,mark);
}
}
//电机转速显示任务
void Motoshow_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sig3,0,&err);
buff=rate;
Show_Str(0,80,BLUE,WHITE,"Speed:00r/min",16,0);
LCD_ShowNum(0+6*8,80,60*buff/2,2,16);
rate=0;
}
}
//us105测量距离任务
void us105_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sig4,0,&err);
if(STA&0x80)
{
num=0.005*340*PluseWidth;
Show_Str(0,60,BLUE,WHITE,"juli:00cm",16,0);
LCD_ShowNum(0+5*8,60,num,2,16);
if(num<=5)
{
Beep_on(&bep);
led_on(&led1);
TIM_SetCompare3(TIM3,8);
}
else
{
led_off(&led1);
Beep_off(&bep);
}
STA=0;
}
}
}
//us-015启动任务
void st_task(void *pdata)
{
while(1)
{
us105_Start(&us);
delay_ms(1000);//这个延时一定要加,因为这是us015的触发信号,但是触发信号的频率越高,输出的脉冲频率也越高,导致进入中断的频率也会越高,会让中断过于频繁,让cpu资源耗尽
//导致程序跑飞,当我延时了1s时,此时显示距离都很正常了。
}
}
//mpu6050测量倾斜角度任务
void mpu6050_task(void *pdata)
{
float pitch,roll,yaw;
int tmp;
while(1)
{
if(mpu_dmp_get_data(&pitch,&roll,&yaw)!=0)//防止FIFO队列溢出,队列溢出是指你处理的速度过慢,导致你处理的跟不上人家发送过来的速度,就会堆积在缓冲区,造成缓冲区堵塞
{//因此建议把延时弄低一些,加快处理速度。
tmp = roll * 10;
if (tmp < 0)
{
tmp = -tmp;
Show_Str(0,40,BLUE,WHITE,"angle:-00.00d",16,0);
LCD_ShowNum(0+7*8,40,tmp/10,2,16);
LCD_ShowNum(0+10*8,40,tmp%10,2,16);
}
else
{
Show_Str(0,40,BLUE,WHITE,"angle:00.00d",16,0);
LCD_ShowNum(0+6*8,40,tmp/10,2,16);
LCD_ShowNum(0+9*8,40,tmp%10,2,16);
}
if(tmp>45)
{
TIM_SetCompare3(TIM3,8);
Show_Str(0,20,BLUE,WHITE,"Cuation!",16,0);
}
else LCD_Fill(0,0,128,35,WHITE);
delay_ms(300);//延时尽量短,保证读取数据的频率足够高,否则时间长了会看不到数据变化和FIFO溢出,最高延时不能超过300ms
}
}
}
void EXTI1_IRQHandler(void)//外部中断1,对应蜂鸣器按键,按下加速
{
OSIntEnter();
delay_ms(10);
if(KEY0==0)
{
flag-=1;
if(flag<3)
{
flag=8;
}
Show_Str(0,100,BLUE,WHITE,"Gear:",16,0);
LCD_ShowNum(0+5*8,100,flag,2,16);
OSSemPost(sig1);
}
EXTI_ClearITPendingBit(EXTI_Line1);
OSIntExit();
}
void EXTI15_10_IRQHandler(void)//外部中断PB12,PC14用于控制电机反转/电机转速计数
{
OSIntEnter();
delay_ms(10);
if(EXTI_GetITStatus(EXTI_Line12)!=RESET)
{
if(KEY1==0)
{
mark+=1;
if(mark>13)
{
mark=8;
}
Show_Str(0,120,BLUE,WHITE,"Gear:",16,0);
LCD_ShowNum(0+5*8,120,mark,2,16);
OSSemPost(sig2);
}
EXTI_ClearITPendingBit(EXTI_Line12);
}
if(EXTI_GetITStatus(EXTI_Line14)!=RESET)
{
if(KEY2==0)
{
rate++;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
OSIntExit();
}
void TIM2_IRQHandler(void)//定时器中断函数,定时1s计算此时的转速,需要注意的是中断函数里边不能写太复杂的东西,不然就会被卡住
{
OSIntEnter();
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查 TIM2 更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); //清除 TIM2 更新中断标志
OSSemPost(sig3);
}
OSIntExit();
}
//以下是us105测距模块,捕获传回来的高电平脉冲,捕获中断
void TIM1_CC_IRQHandler(void)
{
OSIntEnter();
if((STA&0X80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
if(STA&0X40) //捕获到一个下降沿
{
STA|=0X80; //标记成功捕获到一次高电平脉宽
PluseWidth=TIM_GetCapture1(TIM1);
TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
STA=0; //清空
PluseWidth=0;
TIM_SetCounter(TIM1,0);
STA|=0X40; //因此程序实际上是等到上升沿中断的时候,在此处主动把这个比寄存器的值变成不是0的
TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
}
OSSemPost(sig4);
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); //清除中断标志位
OSIntExit();
}
五、调试记录
- 转速显示功能不能放在定时器中断函数里执行,因为太过复杂,会导致程序卡住。
- 针对转速实时显示,在进入定时器中断函数以后,此时电机持续转动,但是由于在定时器中断函数里边加入了清零计数器操作,会导致转速数据丢失,因此采用双缓冲技术,此时引入另外一个缓冲变量buffer,用于保存当前计数值,再将当前计数值清零,显示buffer。并且需要注意,buffer和计数器都应该用volatile关键字声明,这是因为计数器属于中断函数中的非自动变量,需要重新取值。再加入双缓冲技术以后,还是会出现变成0的情况,因此我在硬件上再加入一块磁铁,计算的时候除以2,这样既保证了实时更新,也变得稳定了许多,但是目前还是会出现速度会跳变的情况,原因估计是抖动问题,我已经加了消抖功能,但是好像不能完全消抖。
- 针对us105模块的使用,不存在定时器溢出的情况,因为根据其手册,最长距离也就80ms的高电平脉冲,因此不用在捕获中断函数中添加定时器中断溢出的情况。
- 在us015中,使用的是TIM1高级定时器来进行输入捕获,但是当我开启TIM1的中断时,输入TIM1_IRQn却出现了报错,这是由于TIM1是高级定时器,因此不像普通定时器那样只有一个普通中断,高级定时器分为四个中断:TIM1_BRK_IRQn = 24, /*!< TIM1 Break Interrupt */
TIM1_UP_IRQn = 25, /*!< TIM1 Update Interrupt */
TIM1_TRG_COM_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */此处我们只用到捕获中断,因此只需要开启捕获中断即可。
- 首先是us015没有工作,然后在线调试利用逻辑分析仪发现triger引脚没有重复输出10us以上的电平,随后新创建一个任务重复输出高低电平,接下来发现程序能正常运行,但是程序很容易跑飞,需要复位才能正常运行。查了一下结果发现,是中断触发太平繁,计数频率太高,与霍尔传感器的计数频率一致,导致cpu资源耗尽,而且lcd屏幕显示数字过快,此时我降低输入捕获计数频率为1000hz,此时依然会让程序跑飞,捕获中断触发的还是过于频繁,在lcd屏幕上显示的距离数字跳动的都看不清也能说明这一点,最后我发现是由于us015触发任务的问题,原因就是我在这个任务里没有添加延时,导致触发信号的发送频率过高,由此会引起输出回冲波的频率过快,从而导致捕获回冲波频率过快,由此引起中断进入频繁,最后导致CPU资源耗尽,程序跑飞。因此,我们只需要在us015触发任务里加入1s的延时,降低触发信号的输入频率,即可解决上述问题。
- 关于MPU6050倾斜测量功能,当我将mpu6050焊接好了以后,移植iic以及dmp的代码进去以后,发现没有iic信号,mpu初始化失败。首先检查接口的问题,我用的是PB11,PB10接口,这是用软件iic来进行模拟,因此,只需配置成推挽输出模式即可,其他方式不能出iic信号。其次是头文件,因为SDA线,在读和写情况下,io口模式不同,因此这都是在头文件中对其寄存器进行更改的,我们可以看到io口的寄存器由于是8-15接口,因此寄存器是CRH,注意一个io口为4位,因此配置成GPIOB->CRH&=0Xffff0FFF,GPIOB->CRH|=3<<12就是将其配置成推挽输出模式,具体可以看手册。其次,再弄好接口以后,烧录进去还是初始化失败,这个问题卡了一两天,才发现是调试的问题,也就是说如果在keil中的Debug菜单下选择的是use simulator的方式,此时会导致初始化失败,如果你选择的是use stlink的方式。此时程序会正常运行,这里一定要注意这个坑。查了一下,这是因为软件模拟没法完成的模拟出iic时序,但是我们遇到问题的时候又得用软件模拟得方式去看逻辑分析仪的iic波形,因此导致了这样的结果。因此当我们用iic接口的时候,一定要注意debug下用stlink而不是simulator!最后,在所有初始化成功以后,dmp还是没有检测到欧拉角,数字一直为0,去百度了一下,发现是FIFO溢出导致的,也就是说你处理数据的速度赶不上发送速度,会导致FIFO缓冲区堵塞,接收不到新数据。因此需要将延时缩短,增加处理数据的频率,并且如果你延时大于300ms,此时无论你怎么倾斜,也看不到角度的变化。
六、运行效果
<iframe allowfullscreen="true" data-mediaembed="csdn" frameborder="0" id="Hyznyw3O-1719755450417" src="https://live.csdn.net/v/embed/404944"></iframe>ECU
标签:ucosii,task,中断,void,车载,STK,TIM1,电控,OS From: https://blog.csdn.net/weixin_52247452/article/details/140084582