使用STM32进行FreeRTOS项目练习的智能小车

文章目录

  • 前言
  • 一、FreeRTOS移植到stm32f103c8t6
  • 二、0.97寸OLED屏幕显示(I^2^C通信)
  • 1.引脚连接:
  • 2.写引脚代码:
  • 3.引脚初始化:
  • 4.I^2^C
  • 1.起始:
  • 2.停止:
  • 3.发送一个字节:
  • 5.OLED
  • 1.写命令:
  • 2.写数据:
  • 3.设置光标位置:
  • 4.清屏:
  • 6.OLED显示函数:
  • 7.OLED初始化
  • 三、超声波测距HCSR-04:
  • 1.引脚连接:
  • 2. HCSR-04初始化:
  • 3.获取距离函数:
  • 四、马达驱动(L298N双路驱动)
  • 1.接线
  • 2.定时器2输出PWM波控制移动初始化:
  • 3.小车移动
  • 4. 移动缓冲,防止突然启动或者转向切换时齿轮间的相互作用力过大而损坏器件。
  • 五、蓝牙控制模块(USART1传输):
  • 1.HC-05的蓝牙驱动代码:
  • 六、定时器初始化:
  • 1.TIM2输出PWM波控制小车速度
  • 2.TIM3记数初始化
  • 七、系统时钟初始化函数
  • 八、应用FreeRTOS:
  • 1. 任务间看似是同步运行,互不干扰,其实是操作系统分时运行,只是运行的速度非常快,近似同时运行,这也是FreeRTOS最主要的特点之一。
  • 2. 任务句柄:
  • 3. 任务入口:
  • 4. main函数:
  • 七、总结

  • 前言

    看完了FreeRTOS的教程,想找个项目练手,淘宝或者网上少有基于FreeRTOS的项目,于是我就想用FreeRTOS做个智能小车,可能FreeRTOS不是做小车的最优解,但主要目的是用来练手。
    文中的代码有自己写的也有引用别人的代码,基于寄存器的一些操作是自己对照手册写的,其他代码引用的有江科大、正点原子、野火和一些网上搜罗来的
    完整项目代码:链接:链接:https://pan.baidu.com/s/1zkWxjnJZLNv9Jg_zTl0yhw?pwd=0f68
    提取码:0f68


    一、FreeRTOS移植到stm32f103c8t6

    移植的细节不多做介绍,我是直接复制我之前学习FreeRTOS的动态创建任务的模板,只有一个任务和空闲任务,实现了PC13口的测试LED闪烁。 下面是LED闪烁的任务入口,本案例大多数都是基于寄存器。

    /*测试程序:LED闪烁的任务入口*/
    void Task_LED_Entry(void *p_arg)
    {
        while(1)
        {
           GPIOC->ODR &= ~(1 << 13);//关灯
           vTaskDelay(1000);
           GPIOC->ODR |= 1 << 13;//开灯
           vTaskDelay(1000);
        }
    }
    

    二、0.97寸OLED屏幕显示(I2C通信)

    1.引脚连接:

    PB8 ——-> SCL
    PB9 ——-> SDA

    2.写引脚代码:

    /*写SCL*/
    void OLED_W_SCL(uint8_t Bit)
    {
        if(Bit)
        {
            GPIOB->ODR |= 1 << 8;//pin8写1
            
        }else
        {
            GPIOB->ODR &= (uint16_t)0xfeff;//pin8写0
        }
    }
    /*写SDA*/
    void OLED_W_SDA(uint8_t Bit)
    {
        if(Bit)
        {
             GPIOB->ODR |= 1 << 9;
        }else
        {
            GPIOB->ODR &= (uint16_t)0xfdff;//pin9写0
        }
    }
    

    3.引脚初始化:

    /*引脚初始化*/
    void OLED_I2C_Init(void)
    {
        RCC->APB2ENR |= (1 << 3); // 使能APB2上的GPIOB
        GPIOB->CRH &= 0xffffff00;
        GPIOB->CRH |= 0x00000077;//PB8和PB9设置为50MHz开漏输出模式
        
        OLED_W_SCL(1);//起始PB8和PB9置高电平
        OLED_W_SDA(1);
    }
    

    4.I2C

    1.起始:

    void OLED_I2C_Start(void)
    {
        OLED_W_SDA(1);
        OLED_W_SCL(1);
    
        OLED_W_SDA(0);
        OLED_W_SCL(0);
    }
    

    2.停止:

    void OLED_I2C_Stop(void)
    {
        OLED_W_SDA(0);
        OLED_W_SCL(1);
        OLED_W_SDA(1);
    }
    

    3.发送一个字节:

    void OLED_I2C_SendByte(uint8_t Byte)
    {
        unsigned char i;
        //高位先行
        for(i=0;i<8;i++)
        {
            OLED_W_SDA(Byte&(0x80>>i));
            OLED_W_SCL(1);
            OLED_W_SCL(0);
        }
        OLED_W_SCL(1);//额外的一个时钟,不处理应答信号
        OLED_W_SCL(0);
    }
    

    5.OLED

    1.写命令:

    void OLED_WriteCommand(uint8_t Command)//Command 要写入的命令
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78); //从机地址
    	OLED_I2C_SendByte(0x00); //写命令
    	OLED_I2C_SendByte(Command);
    	OLED_I2C_Stop();
    }
    

    2.写数据:

    void OLED_WriteData(uint8_t Data)//Data 要写入的数据
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78); //从机地址
    	OLED_I2C_SendByte(0x40); //写数据
    	OLED_I2C_SendByte(Data);
    	OLED_I2C_Stop();
    }
    

    3.设置光标位置:

    void OLED_SetCursor(uint8_t Y, uint8_t X)
    {
    	OLED_WriteCommand(0xB0 | Y);				 //设置Y位置
    	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
    	OLED_WriteCommand(0x00 | (X & 0x0F));		 //设置X位置低4位
    }
    

    4.清屏:

    void OLED_Clear(void)
    {
    	uint8_t i, j;
    	for (j = 0; j < 8; j++)
    	{
    		OLED_SetCursor(j, 0);
    		for (i = 0; i < 128; i++)
    		{
    			OLED_WriteData(0x00);
    		}
    	}
    }
    

    6.OLED显示函数:

     /*OLED显示一个字符*/
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
    {
    	uint8_t i;
    	//Line 行位置,范围:1~4,Column 列位置,范围:1~16
    	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
    	}
    	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
    	}
    }
    
     /*OLED显示字符串*/
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
    {
    	uint8_t i;
    	for (i = 0; String[i] != '\0'; i++)
    	{
    		OLED_ShowChar(Line, Column + i, String[i]);
    	}
    }
    
     /*OLED次方函数*/
    uint32_t OLED_Pow(uint32_t X, uint32_t Y)
    {
    	uint32_t Result = 1;
    	while (Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
     /*OLED显示数字(十进制,正数)
     //Number 要显示的数字,范围:0~4294967295*/
     // Length 要显示数字的长度,范围:1~10
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
     /*OLED显示数字(十进制,带符号数)*/
     //要显示的数字,范围:-2147483648~2147483647
     //Length 要显示数字的长度,范围:1~10
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	uint32_t Number1;
    	if (Number >= 0)
    	{
    		OLED_ShowChar(Line, Column, '+');
    		Number1 = Number;
    	}
    	else
    	{
    		OLED_ShowChar(Line, Column, '-');
    		Number1 = -Number;
    	}
    	for (i = 0; i < Length; i++)
    	{
    		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
     /*OLED显示数字(十六进制,正数)*/
     // Number 要显示的数字,范围:0~0xFFFFFFFF
     //Length 要显示数字的长度,范围:1~8
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i, SingleNumber;
    	for (i = 0; i < Length; i++)
    	{
    		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
    		if (SingleNumber < 10)
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
    		}
    		else
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
    		}
    	}
    }
    
     /*OLED显示数字(二进制,正数)*/
     //Number 要显示的数字,范围:0~1111 1111 1111 1111
     //Number 要显示的数字,范围:0~1111 1111 1111 1111
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
    	}
    }
    

    7.OLED初始化

    void OLED_Init(void)
    {
        uint32_t i, j;
    
        for (i = 0; i < 1000; i++) //上电延时
        {
            for (j = 0; j < 1000; j++)
                ;
        }
    
        OLED_I2C_Init(); //端口初始化
        OLED_WriteCommand(0xAE); //关闭显示
        OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
        OLED_WriteCommand(0x80);
        OLED_WriteCommand(0xA8); //设置多路复用率
        OLED_WriteCommand(0x3F);
        OLED_WriteCommand(0xD3); //设置显示偏移
        OLED_WriteCommand(0x00);
        OLED_WriteCommand(0x40); //设置显示开始行
        OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
        OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
        OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
        OLED_WriteCommand(0x12);
        OLED_WriteCommand(0x81); //设置对比度控制
        OLED_WriteCommand(0xCF);
        OLED_WriteCommand(0xD9); //设置预充电周期
        OLED_WriteCommand(0xF1);
        OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
        OLED_WriteCommand(0x30);
        OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
        OLED_WriteCommand(0xA6); //设置正常/倒转显示
        OLED_WriteCommand(0x8D); //设置充电泵
        OLED_WriteCommand(0x14);
        OLED_WriteCommand(0xAF); //开启显示
        OLED_Clear(); // OLED清屏
    }
    

    三、超声波测距HCSR-04:

    1.引脚连接:

    ECHO ——> PB10
    TRIG ——–> PB11

    2. HCSR-04初始化:

    void HCSR04_Init()
    {
        RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;//使能PB口时钟
        GPIOB->CRH &= 0xffff00ff;
        GPIOB->CRH |= 0x00003800;     //配置PB10为上下拉输入,PB11配置为推挽输出
        GPIOB->ODR &= ~GPIO_ODR_ODR10;  //通过往ODR寄存器写0配置PB10为下拉输入
        GPIOB -> ODR &= ~GPIO_ODR_ODR11;
    }
    

    3.获取距离函数:

    uint16_t Get_Distance()
    {
        uint16_t echo_time=0,distance=0;
        GPIOB -> ODR |= (uint16_t)0x0800;
        delay_us(20);//触发超声波模块测距
        GPIOB -> ODR &= (uint16_t)~0x0800;  
        TIM3->CNT = 0;//清零TIM3计数寄存器,一个数1us
        while((GPIOB -> IDR & (uint16_t)0x0400)==0);//等待超声波模块返回高电平
        TIM3->CR1 |= ((uint16_t)0x0001);             // 使能定时器,开始计数
        while((GPIOB -> IDR & (uint16_t)0x0400));//超声波发送信息完毕
        TIM3->CR1 &= (~TIM_CR1_CEN);             // 失能定时器
        echo_time = TIM3->CNT;//获取超声波模块返回的时间
        distance=echo_time*340/2/10/1000;//计算距离,单位厘米
        return distance;
    }
    
    

    四、马达驱动(L298N双路驱动)

    1.接线

    IN1 ——-> PA3
    IN2 ——-> PA2
    IN3 ——-> PA1
    IN4 ——-> PA0

    2.定时器2输出PWM波控制移动初始化:

    void TIM2_PWM_Init()
    {
        RCC->APB1ENR|=1<<0; //TIM2 时钟使能 
        RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
        GPIOA->CRL&=0xFFFF0000; //PA1 到 PA3 输出
        GPIOA->CRL|=0x0000BBBB; //复用功能输出
        RCC->APB2ENR|=1<<0; //开启辅助时钟 
        AFIO->MAPR&=0xFFFFFCFF; //清除 MAPR 的[9:8]
        TIM2->ARR=100; //设定计数器自动重装值
        TIM2->PSC=7201-1; //预分频器7200分频,降低频率,减小电路噪音
        //前进组
        TIM2->CCMR1|=6<<12; //CH2 PWM1 模式
        TIM2->CCMR2|=6<<12; //CH4 PWM1 模式
        //后退组
        TIM2->CCMR1|=6<<4; //CH1 PWM1 模式
        TIM2->CCMR2|=6<<4; //CH3 PWM1 模式
        TIM2->CR1=0x0080; //ARPE 使能
        TIM2->CR1|=0x01; //使能定时器 2
    }
    

    3.小车移动

    #define STOP    (uint8_t)0
    #define ADVANCE (uint8_t)1
    #define BACK    (uint8_t)2
    #define LEFT    (uint8_t)3
    #define RIGHT   (uint8_t)4
    #define SPEED_UP  (uint8_t)5
    #define SPEED_DOWN  (uint8_t)6
    
    #define SPEED_ADD    (uint8_t)1
    #define SPEED_SUB    (uint8_t)0
    
    void Car_Move(uint8_t Direction)
    {
        switch(Direction)
        {
            case ADVANCE:
                OLED_ShowString(1,12,"ADV  ");
                TIM2->CCER&=0xfefe;  //OC1 OC3输出失能  
                TIM2->CCER|=1<<4;    //OC2 输出使能 
                TIM2->CCER|=1<<12;   //OC4 输出使能
                break;
            case BACK:
                OLED_ShowString(1,12,"BACK ");
                TIM2->CCER&=0xefef;  //OC2 OC4输出失能 
                TIM2->CCER|=1<<0;    //OC1 输出使能 
                TIM2->CCER|=1<<8;    //OC3 输出使能 
                break;
            case LEFT:
                OLED_ShowString(1,12,"LEFT ");
                TIM2->CCER&=0xeffe;  //OC1 OC4输出失能      
                TIM2->CCER|=1<<4;    //OC2 输出使能 
                TIM2->CCER|=1<<8;    //OC3 输出使能 
                break;
            case RIGHT:
                OLED_ShowString(1,12,"RIGHT");
                TIM2->CCER&=0xfeef;  //OC1 OC4输出失能
                TIM2->CCER|=1<<0;    //OC1 输出使能
                TIM2->CCER|=1<<12;   //OC4 输出使能
                break;
            case STOP:
                OLED_ShowString(1,12,"STOP ");
                TIM2->CCER &=0xeeee;
                break;
        } 
    }
    
    

    4. 移动缓冲,防止突然启动或者转向切换时齿轮间的相互作用力过大而损坏器件。

    void Car_Move_Buffer(uint8_t Speed,uint8_t dir)//加速减速缓冲程序
    { 
        if(dir)
        {
            uint8_t i=0;
            for(i=20;i<=Speed;i++)
            {
                delay_ms(10);
                TIM2 -> CCR2 = i;
                TIM2 -> CCR4 = i;
                TIM2 -> CCR1 = i;
                TIM2 -> CCR3 = i;
            } 
        }
        else{
            while(Speed--){
                delay_ms(5);
                TIM2 -> CCR2 = Speed;
                TIM2 -> CCR4 = Speed;
                TIM2 -> CCR1 = Speed;
                TIM2 -> CCR3 = Speed;
            }
        }
    }
    

    五、蓝牙控制模块(USART1传输):

    1.HC-05的蓝牙驱动代码:

    #include "stm32f10x.h"                  // Device header
    #include "OLED.h"
    uint8_t Serial_RxData;
    extern unsigned char direction;
    extern uint8_t flag_carmove;
    
    void Serial_Init(void)
    {
        RCC->APB2ENR|=1<<14; //使能串口时钟
        RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
    	GPIOA->CRH&=0xFFFFF0FF; //PA10 输入
        GPIOA->CRH|=0x00000800; //上下拉输入
        GPIOA->ODR|=1<<10;//上拉输入
    	USART1->BRR=0x00001d4c;//设置波特率为9600
        /*清除STOP[13:12]位,00:一个停止位,Clock, CPOL, CPHA and LastBit用默认的0 */
    	USART1->CR2&=0xcfff;
        /* 不启用硬件控制流:0x0000 */
    	USART1->CR3|=0x0000;
        /* 字长8:0x0000 奇偶校验无:0x0000 接收模式:0x0004 */
    	/*0x0000|0x0000|0x0004=0x0004*/
    	USART1->CR1|=0x0004;
        USART1->CR1|=1<<5; //接收缓冲区非空中断使能
        //NVIC->ICER[1]|=1<<5;//关闭中断通道
        NVIC->ISER[1]|=1<<5;//打开中断通道
        /*开启USART1*/
    	USART1->CR1|=0x2000;
    }
    
    
    //串口中断服务程序
    void USART1_IRQHandler(void)
    {
        if(USART1->SR & 1 << 5)//如果接收了数据
        {
            Serial_RxData = (uint16_t)(USART1->DR & (uint16_t)0x01FF);
            USART1->SR = (uint16_t)~(1<<5);
            if(Serial_RxData != direction && Serial_RxData != 5 && Serial_RxData != 6 && Serial_RxData != 0)
            {
                flag_carmove = 1;
                direction=Serial_RxData;
            }else
            {
                switch(Serial_RxData){
                    case 5:
                        flag_carmove=2;
                        break;
                    case 6:
                        flag_carmove=3;
                        break;
                    case 0 :
                        flag_carmove=4;
                        break;
                }
            }
    	}
    }
    

    六、定时器初始化:

    1.TIM2输出PWM波控制小车速度

    void TIM2_PWM_Init()
    {
        RCC->APB1ENR|=1<<0; //TIM2 时钟使能 
        RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
        
        GPIOA->CRL&=0xFFFF0000; //PA1 - PA3 输出
        GPIOA->CRL|=0x0000BBBB; //复用功能输出
        
        RCC->APB2ENR|=1<<0; //开启辅助时钟 
        AFIO->MAPR&=0xFFFFFCFF; //清除 MAPR 的[9:8]
        
        //AFIO->MAPR|=1<<11; //部分重映像,TIM3_CH2->PB5
        TIM2->ARR=100; //设定计数器自动重装值
        TIM2->PSC=7201-1; //预分频器72分频,一次计数1us
        //前进组
        TIM2->CCMR1|=6<<12; //CH2 PWM1 模式
        TIM2->CCMR2|=6<<12; //CH4 PWM1 模式
        //后退组
        TIM2->CCMR1|=6<<4; //CH1 PWM1 模式
        TIM2->CCMR2|=6<<4; //CH3 PWM1 模式
      
        TIM2->CR1=0x0080; //ARPE 使能
        
        TIM2->CR1|=0x01; //使能定时器 2
    }
    
    

    2.TIM3记数初始化

    void TIM3_Count_Init()
    {
    	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // TIM3时钟使能
    	TIM3->SMCR &=  (uint16_t)(~((uint16_t)TIM_SMCR_SMS));//关闭从模式
        TIM3->CR1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
        TIM3->CR1 |= (uint32_t)((uint16_t)0x0000);
        TIM3->ARR = 65535 - 1;                    // 重装载值
        TIM3->PSC = 72 - 1;                    // 预分频数
       	TIM3->EGR = 0x0001; //无事件发生
        TIM3->CR1 &=  ((uint16_t)~0x0001) ;//关闭时钟
    }
    
    

    七、系统时钟初始化函数

    /*时钟源为外部晶振,9倍频,SYSCLK为72MHz,APB2不分频,APB1二分频*/
    void SYSCLK_Init()
    {
      /*打开HSE*/
      RCC->CR |= RCC_CR_HSEON;
      /* 等待HSE等待完毕 */
      while ((RCC->CR & RCC_CR_HSERDY) == 0)
      {
      }
      /*时钟源选择为 HSE,进行9倍频 SYSCLK = HSE*9 = 8MHz*9 = 72MHz */
      RCC->CFGR |= RCC_CFGR_PLLMULL9;
      RCC->CFGR |= RCC_CFGR_PLLSRC;
    
      FLASH->ACR |= FLASH_ACR_LATENCY_2; // FLASH缓冲
    
      /* 使能PLL倍频器 */
      RCC->CR |= RCC_CR_PLLON;
      /* Wait till PLL is ready */
      while ((RCC->CR & RCC_CR_PLLRDY) == 0) // 等待PLL倍频器就绪
      {
      }
      /* SYSCLK预分频系数为1,即不分频 */
      RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
      /* APB2预分频系数为1,即不分频 =SYSCLK */
      RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
      //
      //    /* APB1预分频系数为2,即 APB2 = SYSCLK/2 */
      RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
    
      /* 复位时钟源选择,并将时钟源选择为PLL */
      RCC->CFGR &= (~(RCC_CFGR_SW));
      RCC->CFGR |= RCC_CFGR_SW_PLL;
      //
      /* 等待时钟源选择完成置位 */
      while ((RCC->CFGR & RCC_CFGR_SWS) != 0x08)
      {
      }
      /*SysTick使用外部晶振,并关闭systick定时器*/
      //SysTick->CTRL |= 0x00000004;
    }
    

    八、应用FreeRTOS:

    1. 任务间看似是同步运行,互不干扰,其实是操作系统分时运行,只是运行的速度非常快,近似同时运行,这也是FreeRTOS最主要的特点之一。

    2. 任务句柄:

    static TaskHandle_t Task_CarMove_handle = NULL;
    static TaskHandle_t Task_Get_Distance_handle = NULL;
    static TaskHandle_t Task_HG04_handle = NULL;
    
    

    3. 任务入口:

    void Task_CarMove_Entry(void *p_arg)
    {
        uint8_t temp=0,temp_dir=0;
        while(1)
        {
            switch(flag_carmove){
                case 1://转向
                    flag_carmove=0;
                    taskENTER_CRITICAL();//进入临界段保护完成转向
                    Car_Move_Buffer(speed,SPEED_SUB);
                    Car_Move(direction);
                    Car_Move_Buffer(speed,SPEED_ADD);
                    taskEXIT_CRITICAL();//退出临界段
                    break;
                case 2://加速
                    flag_carmove=0;
                    if(speed>=100)
                    {
                        speed=20;
                    }
                    speed += 10;
                    TIM2->CCR2 =speed;
                    TIM2->CCR4 =speed;
                    TIM2->CCR1 =speed;
                    TIM2->CCR3 =speed;
                    break;
                case 3://减速
                    flag_carmove=0;
                    if(speed<=30)
                    {
                        speed=110;
                    }
                    speed -= 10;
                    TIM2->CCR2 =speed;
                    TIM2->CCR4 =speed;
                    TIM2->CCR1 =speed;
                    TIM2->CCR3 =speed;
                    break;
                case 4://STOP
                    flag_carmove=0;
                    direction= STOP;
                    Car_Move_Buffer(speed,SPEED_SUB);
                    Car_Move(direction);
                    break;
            } 
        }
    }
    
    void Task_Get_Distance_Entry(void *p_arg)
    {
        uint16_t distance=0;
        while(1){
            vTaskDelay(100);//延迟100ms
            OLED_ShowNum(2,12,speed,3);//显示速度
            taskENTER_CRITICAL();//进入临界段保护,完成准确测距
            distance=Get_Distance();//获取距离
            taskEXIT_CRITICAL();//退出临界段保护
            if(distance <= 30)//如果距离小于30,避障
            {
                Car_Move_Buffer(speed,SPEED_SUB);//减速缓冲,保护马达齿轮
                Car_Move(BACK);//倒退一段时间
                Car_Move_Buffer(speed,SPEED_ADD);//加速缓冲,保护马达齿轮
                vTaskDelay(500);
                Car_Move(STOP);//停止
                vTaskResume(Task_HG04_handle);//恢复舵机转向任务,舵机转向任务优先级最高,恢复立即执行
            }
        }
    }
    
    void Task_HG04_Entry(void *p_arg)//舵机转向任务
    {
        uint16_t left_distance=0,right_distance=0;
        uint8_t flag_hg04=0;
        while(1)
        {
            /*舵机转向*/
            delay_ms(800);//给足够多的时间使舵机完成转向
            GPIOC->ODR |= (1<<13);
            delay_us(650);//根据高电平的时间,舵机完成特定角度转向
            GPIOC->ODR &=~(1<<13);
            
            delay_ms(800);
            if(flag_hg04)//初始化使不需要测距
            {
                taskENTER_CRITICAL();//进入临界段保护,使其准确测距
                left_distance=Get_Distance();//测距:右方
                taskEXIT_CRITICAL();//完成测距,退出临界段保护
            }
    
            GPIOC->ODR |= (1<<13);
           
            delay_us(1310);//舵机特定角度转向
            GPIOC->ODR &=~(1<<13);
           
            delay_ms(800);
            if(flag_hg04)
            {
                taskENTER_CRITICAL();
                right_distance=Get_Distance();//测距:左方
                taskEXIT_CRITICAL();
            }
    
            GPIOC->ODR |= (1<<13);
            delay_us(980);
            GPIOC->ODR &=~(1<<13);
            OLED_ShowNum(3,1,left_distance,8);//左方距离显示,这两句主要用于测试
            OLED_ShowNum(4,1,right_distance,8);//右方距离显示
            if(flag_hg04)
            {
                if(left_distance>=right_distance)//如果左方距离大于右方,左转
                {
                    Car_Move(LEFT);
                    Car_Move_Buffer(speed,SPEED_ADD);
                }else//如果左方距离小于右方,右转
                {
                    Car_Move(RIGHT);
                    Car_Move_Buffer(speed,SPEED_ADD);
                }
                vTaskDelay(500);
                Car_Move_Buffer(speed,SPEED_SUB);
                
                Car_Move(ADVANCE);
                Car_Move_Buffer(speed,SPEED_ADD);
            }
           flag_hg04=1;
           vTaskSuspend(Task_HG04_handle);//挂起舵机转头任务,只有当距离小于30cm时唤醒舵机转头任务,此任务优先级最高,所以一唤醒就执行
        }
    }
    
    

    4. main函数:

    int main(void)
    {
        /*系统时钟初始化*/
        SYSCLK_Init();
        /*OLED初始化*/
        OLED_Init();
        /*串口初始化*/
        Serial_Init();
        /*舵机控制初始化*/
        HG04_Init();
        /*TIM2输出PWM 初始化*/
        TIM2_PWM_Init();
        /*超声波测距时间模块初始化*/
        TIM3_Count_Init();
        /*超声波测距模块初始化*/
        HCSR04_Init();
        /*创建任务*/
        xTaskCreate(Task_CarMove_Entry, "CarMove", 128, NULL, 30, &Task_CarMove_handle);
        xTaskCreate(Task_Get_Distance_Entry, "HCSR04", 128, NULL, 30, &Task_Get_Distance_handle);
        xTaskCreate(Task_HG04_Entry, "HG04", 128, NULL, 31, &Task_HG04_handle);
        //关闭中断号小于15的中断,在FreeRTOSConfig.h中配置
        portDISABLE_INTERRUPTS();
        // 开启调度
        vTaskStartScheduler();
        while(1)
        {
            OLED_ShowNum(3,1,88,8);//正常情况下不会执行这一句
        }
    }
    

    七、总结

    经过大概两个星期的时间,终于完成了基于FreeRTOS实时操作系统的智能小车实验。整个项目代码等后续上传到百度云。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32进行FreeRTOS项目练习的智能小车

    发表评论