赞
踩
看完了FreeRTOS的教程,想找个项目练手,淘宝或者网上少有基于FreeRTOS的项目,于是我就想用FreeRTOS做个智能小车,可能FreeRTOS不是做小车的最优解,但主要目的是用来练手。
文中的代码有自己写的也有引用别人的代码,基于寄存器的一些操作是自己对照手册写的,其他代码引用的有江科大、正点原子、野火和一些网上搜罗来的
完整项目代码:链接:https://pan.baidu.com/s/1kG849Op9goCgRZb0h7sC9g?pwd=6r7e
提取码:6r7e
移植的细节不多做介绍,我是直接复制我之前学习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);
}
}
PB8 -------> SCL
PB9 -------> SDA
/*写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 } }
/*引脚初始化*/
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);
}
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
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);
}
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();
}
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();
}
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位
}
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);
}
}
}
/*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'); } }
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清屏 }
ECHO ------> PB10
TRIG --------> PB11
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;
}
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; }
IN1 -------> PA3
IN2 -------> PA2
IN3 -------> PA1
IN4 -------> PA0
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 }
#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; } }
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; } } }
#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; } } } }
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 }
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; }
static TaskHandle_t Task_CarMove_handle = NULL;
static TaskHandle_t Task_Get_Distance_handle = NULL;
static TaskHandle_t Task_HG04_handle = NULL;
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时唤醒舵机转头任务,此任务优先级最高,所以一唤醒就执行 } }
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实时操作系统的智能小车实验。整个项目代码等后续上传到百度云。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。