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实时操作系统的智能小车实验。整个项目代码等后续上传到百度云。

你可能感兴趣的:(FreeRTOS,STM32寄存器开发,stm32,单片机,嵌入式硬件)