基于ucosii的车载电控单元

一、项目简介

     通过利用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();
}

五、调试记录

  1. 转速显示功能不能放在定时器中断函数里执行,因为太过复杂,会导致程序卡住。
  2. 针对转速实时显示,在进入定时器中断函数以后,此时电机持续转动,但是由于在定时器中断函数里边加入了清零计数器操作,会导致转速数据丢失,因此采用双缓冲技术,此时引入另外一个缓冲变量buffer,用于保存当前计数值,再将当前计数值清零,显示buffer。并且需要注意,buffer和计数器都应该用volatile关键字声明,这是因为计数器属于中断函数中的非自动变量,需要重新取值。再加入双缓冲技术以后,还是会出现变成0的情况,因此我在硬件上再加入一块磁铁,计算的时候除以2,这样既保证了实时更新,也变得稳定了许多,但是目前还是会出现速度会跳变的情况,原因估计是抖动问题,我已经加了消抖功能,但是好像不能完全消抖。
  3. 针对us105模块的使用,不存在定时器溢出的情况,因为根据其手册,最长距离也就80ms的高电平脉冲,因此不用在捕获中断函数中添加定时器中断溢出的情况。
  4. 在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                       */此处我们只用到捕获中断,因此只需要开启捕获中断即可。

  1. 首先是us015没有工作,然后在线调试利用逻辑分析仪发现triger引脚没有重复输出10us以上的电平,随后新创建一个任务重复输出高低电平,接下来发现程序能正常运行,但是程序很容易跑飞,需要复位才能正常运行。查了一下结果发现,是中断触发太平繁,计数频率太高,与霍尔传感器的计数频率一致,导致cpu资源耗尽,而且lcd屏幕显示数字过快,此时我降低输入捕获计数频率为1000hz,此时依然会让程序跑飞,捕获中断触发的还是过于频繁,在lcd屏幕上显示的距离数字跳动的都看不清也能说明这一点,最后我发现是由于us015触发任务的问题,原因就是我在这个任务里没有添加延时,导致触发信号的发送频率过高,由此会引起输出回冲波的频率过快,从而导致捕获回冲波频率过快,由此引起中断进入频繁,最后导致CPU资源耗尽,程序跑飞。因此,我们只需要在us015触发任务里加入1s的延时,降低触发信号的输入频率,即可解决上述问题。
  2. 关于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,此时无论你怎么倾斜,也看不到角度的变化。

六、运行效果

ECU

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值