文章目录
注意:在CubeMX生成的代码中,需要将代码添加在BEFIN和END中,否则下次生成时将被覆盖
一.输出GPIO的高低电平输出与反转电平
HAL_GPIO_TogglePin(Port,Pin); //翻转电平
HAL_GPIO_WritePin(GPIO_Port,Pin,GPIO_PIN_RESET);//设置低电平
HAL_GPIO_WritePin(GPIO_Port,Pin,GPIO_PIN_SET); //设置为高电平
/******************LED0**********************/
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); //翻转电平
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);//设置低电平
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET); //设置为高电平
/******************LED1**********************/
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin); //翻转电平
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);//设置低电平
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); //设置为高电平
/******************Beep**********************/
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin); //翻转电平
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);//设置低电平
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); //设置为高电平
二.自带滴答定时器延时
HAL_Delay(500); //单位ms
三.外部中断编写
外部中断要写在gpio.c中
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY1_Pin) //判断是哪个按键按下?
{
//这里编写触发中断后要执行的程序
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//示例
}
if(GPIO_Pin == KEY2_Pin)
{
//这里编写触发中断后要执行的程序
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//示例
}
}
四.模块初始化函数整理:
初始化函数在main中while语句前进行
//1.OLED初始化
OLED_Init();
OLEDClear();
//2.PWM初始化
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);//开启定时器1 通道1 PWM输出
//3.编码器初始化
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);//开启相关定时器
HAL_TIM_Base_Start_IT(&htim2); //开启相关定时器中断
//4.普通计时定时器中断
HAL_TIM_Base_Start_IT(&htim1); //开启定时器1 中断
//5.串口中断开启
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //开启串口1接收中断
//6.相关编码器初始化(需要先开启定时器,在开启定时器中断)
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);//开启定时器2
HAL_TIM_Encoder_Start(&htim4,TIM_CHANNEL_ALL);//开启定时器4
HAL_TIM_Base_Start_IT(&htim2); //开启定时器2 中断
HAL_TIM_Base_Start_IT(&htim4); //开启定时器4 中断
五.模块的使用代码
1.OLED
- 初始化(主函数中进行)
OLED_Init();
OLED_Clear();
- 关于显示
/***************************************字库显示***************************************/
//参数:x坐标 y坐标 字库数字编号
OLED_ShowCHinese(0,0,0);//中
OLED_ShowCHinese(18,0,1);//景
OLED_ShowCHinese(36,0,2);//园
OLED_ShowCHinese(54,0,3);//电
OLED_ShowCHinese(72,0,4);//子
OLED_ShowCHinese(90,0,5);//科
OLED_ShowCHinese(108,0,6);//技
/************************************OLED字符串以及参数显示*******************/
uint8_t OledString[20];
sprintf((char *)OledString,"V1:%.2fV2:%.2f", Motor1Speed,Motor2Speed);
//x坐标,y坐标,驱动参数,字体大小
OLED_ShowString(0,0,OledString,12);
sprintf((char *)OledString,"Mileage:%.2f ",Mileage);//显示里程数
OLED_ShowString(0,1,OledString,12);//这个是oled驱动里面的,是显示位置的一个函
2.串口
F1和F4串口的时钟不需要注意,他们都是挂在一个总线上
- 1.开启串口接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //开启串口1接收中断
- 2.串口打印方法一
uint8_t Data[] = " " //定义一个数组保存要打印的内容
HAL_UART_Transmit(&huart1,Data,sizeof(c_Data),0xFFFF); //串口输出
- 串口打印方法二
使用重定向函数,在usart.c中编写。重定向后可以使用printf,但注意头文件要包含“stdio.h",且勾选魔术棒中的设置。否则程序容易异常。若使用串口,则每次重新生成代码时要设置魔术棒。
//重定向前要写这一句
typedef struct __FILE FILE;
/* USER CODE BEGIN 1 */
int fputc(int ch,FILE *stream) //重定向函数
{
HAL_UART_Transmit(&huart1,( uint8_t *)&ch,1,0xFFFF); //重定向串口选择
return ch;
}
- 判断串口是否接收完一帧数据(usart.c)
uint8_t Usart_WaitReasFinish(void)
{
static uint16_t Usart_LastReadCount = 0;//记录上次的计数值
if(Usart1_ReadCount == 0)
{
Usart_LastReadCount = 0;
return 1;//表示没有在接收数据
}
if(Usart1_ReadCount == Usart_LastReadCount)//如果这次计数值等于上次计数值
{
Usart1_ReadCount = 0;
Usart_LastReadCount = 0;
return 0;//已经接收完成了
}
Usart_LastReadCount = Usart1_ReadCount;
return 2;//表示正在接受中
}
(1) 串口单个字符的发送,该方法也可以发送字符串
//定义一个数组,保存要发送的字符
uint8_t UART1_TX_BUF[1]; //串口1要发送的数据
//直接发送
HAL_UART_Transmit(&huart1,UART1_TX_BUF,1,0xffff);
(2) 发送字符串函数
//发送字符串函数 参数:字符串
//使用该函数时,最好字符串前加(uint8_t *)进行类型转换
void Usart_SendString(uint8_t *str)
{
unsigned int k = 0;
do //循环发送字符
{
HAL_UART_Transmit(&huart1,( uint8_t *)(str + k),1,1000);
k++;
}while(*(str + k)!='\0'); //直到该字符结束
}
(3)串口关于scanf和getchar重定向
//串口的重定向2:可使用scanf和getchar
int fgetc(FILE *f)
{
int ch;
HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,1000);
return ch;
}
(4)中断函数
//查看在串口中是否有中断接收 以下函数在stm32f1xx.h中
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t ch = 0;
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET)//当进入到这个中断时,证明串口有数据
{
ch=(uint16_t)READ_REG(huart1.Instance->DR); //将数据保存在DR寄存器
WRITE_REG(huart1.Instance->DR,ch); //将DR的内容发送出去
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
3.PWM的使用与值修改
- 1.主函数中开启定时器
//参数:定时器句柄,通道
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
- 2.重新装载占空比
//参数:定时器选择,定时器通道选择,修改的PWM值
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 40);
4.编码器的计数与测速
电机的两个重要参数:编码器线数(13),减速比:1:20
车轮旋转一周,编码器计数为20* 13 *4
- 1.在主函数中进行编码器初始化
//参数:定时器句柄
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
- 2.编码器中断开启
//参数:定时器句柄
HAL_TIM_Base_Start_IT(&htim2);
(1) 编码器的计数
//1.设置两个变量,记录左右电机的编码值
short EncoderLCount = 0;//编码器计数器值
short EncoderRCount = 0;
//2.保存计数值(从定时器捕获中读取)
EncoderLCount =(short)__HAL_TIM_GET_COUNTER(&htim2); //捕获的定时器选择
EncoderRCount =(short)__HAL_TIM_GET_COUNTER(&htim4); //捕获的定时器选择
//3.捕获之后,需要清空定时器
__HAL_TIM_SET_COUNTER(&htim4,0);
__HAL_TIM_SET_COUNTER(&htim2,0);
(2)电机速度的计算
//1.设置两个变量,保存速度,速度通常是小数,因此设置float类型
float MotorLSpeed = 0.00;
float MotorRSpeed = 0.00;
//2.速度计算公式:速度 = 编码器计数值 * 100 / 减速比 /线数 / 4 这里为10ms计算一次
Motor1Speed = (float)Encode1Count*100/20/13/4;
Motor2Speed = (float)Encode2Count*100/20/13/4;
5.普通定时器的定时功能
- 主函数中开启该定时器
HAL_TIM_Base_Start_IT(&htim6);
在stm32f1xx_it.c中编写
uint16_t TimerCount=0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1)//定时器选择 500HZ(72M / psc /arr) 2ms 中断一次
{
TimerCount++;
if(TimerCount %5 == 0)//每10ms执行一次
{
TimerCount=0;
}
}
}
6.cJSON
-
魔术棒记得勾选
-
打开串口及其NVIC和中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //开启串口1接收中断
- 在stm32f1xx.h中编写中断回调函数
uint8_t Usart1_ReadBuf[256]; //串口1 缓冲数组
uint8_t Usart1_ReadCount = 0; //串口1 接收字节计数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!= RESET)//判断huart1 是否读到字节
{
if(Usart1_ReadCount >= 255) Usart1_ReadCount = 0; //如过溢出,则清空
HAL_UART_Receive(&huart1,&Usart1_ReadBuf[Usart1_ReadCount++],1,1000);
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);//翻转灯,表明已经接收到数据
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
- 在usart中编写串口是否发送完一帧数据
//判断否接收完一帧数据
uint8_t Usart_WaitReasFinish(void)
{
static uint16_t Usart_LastReadCount = 0;//记录上次的计数值
if(Usart1_ReadCount == 0)
{
Usart_LastReadCount = 0;
return 1;//表示没有在接收数据
}
if(Usart1_ReadCount == Usart_LastReadCount)//如果这次计数值等于上次计数值
{
Usart1_ReadCount = 0;
Usart_LastReadCount = 0;
return 0;//已经接收完成了
}
Usart_LastReadCount = Usart1_ReadCount;
return 2;//表示正在接受中
}
- 主函数中写cJSON收发函数
#include "cJSON.h"
#include <string.h>
//变量设置
cJSON *cJsonData ,*cJsonVlaue;
extern uint8_t Usart1_ReadBuf[255]; //串口1 缓冲数组
if(Usart_WaitReasFinish() == 0)//是否接收完毕
{
cJsonData = cJSON_Parse((const char *)Usart1_ReadBuf);
if(cJSON_GetObjectItem(cJsonData,"p") !=NULL)
{
cJsonVlaue = cJSON_GetObjectItem(cJsonData,"p");
p = cJsonVlaue->valuedouble;
pidMotor1Speed.Kp = p;
}
if(cJSON_GetObjectItem(cJsonData,"i") !=NULL)
{
cJsonVlaue = cJSON_GetObjectItem(cJsonData,"i");
i = cJsonVlaue->valuedouble;
pidMotor1Speed.Ki = i;
}
if(cJSON_GetObjectItem(cJsonData,"d") !=NULL)
{
cJsonVlaue = cJSON_GetObjectItem(cJsonData,"d");
d = cJsonVlaue->valuedouble;
pidMotor1Speed.Kd = d;
}
if(cJSON_GetObjectItem(cJsonData,"a") !=NULL)
{
cJsonVlaue = cJSON_GetObjectItem(cJsonData,"a");
a = cJsonVlaue->valuedouble;
pidMotor1Speed.target_val =a;
}
if(cJsonData != NULL){
cJSON_Delete(cJsonData);//释放空间、但是不能删除cJsonVlaue不然会 出现异常错误
}
memset(Usart1_ReadBuf,0,255);//清空接收buf,注意这里不能使用strlen
}
printf("P:%.3f I:%.3f D:%.3f A:%.3f\r\n",p,i,d,a);
(1) 串口1 CJSON调参代码
(2) 串口2 CJSON调参代码
7.蓝牙的使用
//1.在main中定义全局变量
uint8_t g_ucUsart3ReceiveData; //保存串口三接收的数据
//2.开启该串口的中断
HAL_UART_Receive_IT(&huart3,&g_ucUsart3ReceiveData,1); //串口三接收数据
//3.在中断回显里面使用串口(stm32f1xx.h)自己调用编写
//串口接收回调函数(只有接收时才会产生中断)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if( huart == &huart3)//判断中断源
{
if(g_ucUsart3ReceiveData == 'A') motorPidSetSpeed(1,1);//前运动
if(g_ucUsart3ReceiveData == 'B') motorPidSetSpeed(-1,-1);//后运动
if(g_ucUsart3ReceiveData == 'C') motorPidSetSpeed(0,0);//停止
if(g_ucUsart3ReceiveData == 'D') motorPidSetSpeed(1,2);//右边运动
if(g_ucUsart3ReceiveData == 'E') motorPidSetSpeed(2,1);//左边运动
if(g_ucUsart3ReceiveData == 'F') motorPidSpeedUp();//加速
if(g_ucUsart3ReceiveData == 'G') motorPidSpeedCut();//减速
HAL_UART_Receive_IT( &huart3, &g_ucUsart3ReceiveData, 1);//继续进行中断接收
}
}
//4.记得声明外部变量
extern uint8_t g_ucUsart3ReceiveData; //保存串口三接收的数据
/*************************串口发送字符串********************************/
uint8_t Usart3String[20];
sprintf((char *)Usart3String,"V1:%.2fV2:%.2f\r\n",Motor1Speed,Motor2Speed);//显示两个电机转速 单位:转/秒
HAL_UART_Transmit(&huart3,( uint8_t *)Usart3String,strlen(( const char *)Usart3String),50);//阻塞式发送通过串口三输出字符 strlen:计算字符串大小
8.超声波的使用
(1)不用定时器的方法:
该方法注意延时要2ms以上(5ms一次)
//1.HC_SRO4.c中写一个us级延时的函数
void HC_SR04_Delayus(uint32_t usdelay)
{
__IO uint32_t Delay = usdelay * (SystemCoreClock / 8U / 1000U/1000);//SystemCoreClock:系统频率
do
{
__NOP();
}
while (Delay --);
}
//距离读取
/*******************
* @brief HC_SR04读取超声波距离
* @param 无
* @return 障碍物距离单位:cm (静止表面平整精度更高)
*注意:两个HC_SR04_Read()函数调用的时间间隔要2ms及以上,测量范围更大 精度更高
*******************/
float HC_SR04_Read(void)
{
uint32_t i = 0;
float Distance;
HAL_GPIO_WritePin(HC_SR04_Trig_GPIO_Port,HC_SR04_Trig_Pin,GPIO_PIN_SET);//输出15us高电平
HC_SR04_Delayus(15);
HAL_GPIO_WritePin(HC_SR04_Trig_GPIO_Port,HC_SR04_Trig_Pin,GPIO_PIN_RESET);//高电平输出结束,设置为低电平
while(HAL_GPIO_ReadPin(HC_SR04_Echo_GPIO_Port,HC_SR04_Echo_Pin) == GPIO_PIN_RESET)//等待回响高电平
{
i++;
HC_SR04_Delayus(1);
if(i>100000) return -1;//超时退出循环、防止程序卡死这里
}
i = 0;
while(HAL_GPIO_ReadPin(HC_SR04_Echo_GPIO_Port,HC_SR04_Echo_Pin) == GPIO_PIN_SET)//下面的循环是2us
{
i = i+1;
HC_SR04_Delayus(1);//1us 延时,但是整个循环大概2us左右
if(i >100000) return -2;//超时退出循环
}
Distance = i*2*0.033/2;//这里乘2的原因是上面是2微妙
return Distance ;
}
1.超声波避障
//**************避障功能********************//
//避障逻辑
if(HC_SR04_Read() > 25)//前方无障碍物
{
motorPidSetSpeed(1,1);//前运动
HAL_Delay(100);
}
else{ //前方有障碍物
motorPidSetSpeed(-1,1);//右边运动 原地
HAL_Delay(500);
if(HC_SR04_Read() > 25)//右边无障碍物
{
motorPidSetSpeed(1,1);//前运动
HAL_Delay(100);
}
else{//右边有障碍物
motorPidSetSpeed(1,-1);//左边运动 原地
HAL_Delay(1000);
if(HC_SR04_Read() >25)//左边无障碍物
{
motorPidSetSpeed(1,1);//前运动
HAL_Delay(100);
}
else{
motorPidSetSpeed(-1,-1);//后运动
HAL_Delay(1000);
motorPidSetSpeed(-1,1);//右边运动
HAL_Delay(50);
}
}
}
2.超声波跟随
//超声波跟随
if(HC_SR04_Read() > 25)
{
motorForward();//前进
HAL_Delay(100);
}
if(HC_SR04_Read() < 20)
{
motorBackward();//后退
HAL_Delay(100);
}
(2)使用定时器进行捕获
-
可用捕获定时器:TIM1 TIM5
-
1.定时器配置(72M时钟,定时器配置为1us)
- 2.开启定时器捕获中断
-
3.引脚配置,同不使用定时器的配置相同
-
4.在tim.c中编写毫秒级延时函数(使用配置的定时器)
/* USER CODE BEGIN 1 */
//使用TIM1来做us级延时函数,此函数为1us
void TIM1_Delay_us(uint16_t n_us)
{
/* 使能定时器1计数 */
__HAL_TIM_ENABLE(&htim1);
__HAL_TIM_SetCounter(&htim1, 0);//htim1
while(__HAL_TIM_GetCounter(&htim1) < ((1 * n_us)-1) );
/* 失能定时器1计数 */
__HAL_TIM_DISABLE(&htim1);
}
/* USER CODE END 1 */
- 5.超声波开始函数
void Start()
{
HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_SET);//拉高
TIM1_Delay_us(20);
HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_RESET);//拉低
}
- 距离计算
Start();//开启超声波模块
HAL_TIM_Base_Start(&htim1);//开启定时器
//对超声波输入端口操作
while( HAL_GPIO_ReadPin (Echo_GPIO_Port ,Echo_Pin) == GPIO_PIN_RESET);//等待输入电平拉高
__HAL_TIM_SetCounter(&htim1,0); //清空捕获计数器
//对超声波输入端口操作
while( HAL_GPIO_ReadPin (Echo_GPIO_Port ,Echo_Pin) == GPIO_PIN_SET);//等待输入电平变低
Cnt = __HAL_TIM_GetCounter(&htim1); //获取定时器的计数值
HAL_TIM_Base_Stop(&htim1); //停止计时定时器1
Distance = Cnt*340/2*0.000001*100 ; //距离的计算
HAL_Delay(500); //延时5ms
9.MPU6050
- 主函数中头文件包含
#include "IIC.h"
#include "mpu6050.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
- 主函数中相关参数的设置
float pitch,roll,yaw; //欧拉角
short aacx,aacy,aacz; //加速度传感器原始数据
short gyrox,gyroy,gyroz; //陀螺仪原始数据
//float temp; //温度
- 主函数中MPU6050的初始化(while前)
MPU_Init(); //MPU6050初始化
mpu_dmp_init(); //dmp初始化
printf("Initial OK\r\n");
- while循环中数据的读取
HAL_Delay(100);
while(mpu_dmp_get_data(&pitch, &roll, &yaw)); //必须要用while等待,才能读取成功
MPU_Get_Accelerometer(&aacx,&aacy, &aacz); //得到加速度传感器数据
MPU_Get_Gyroscope(&gyrox, &gyroy, &gyroz); //得到陀螺仪数据
// temp=MPU_Get_Temperature(); //得到温度信息
// printf("X:%.1f Y:%.1f Z:%.1f temp:%.2f\r\n",roll,pitch,yaw,temp/100);//串口1输出采集信息
printf("X:%.1f Y:%.1f Z:%.1f \r\n",roll,pitch,yaw);//串口1输出采集信息
- 右转为负数,正转为正数
(1) MPU6050+PID控制转向使用
MPU6050_PID = PID_realize(&PID_MPU6050,yaw); //MPU6050 PID的计算(右偏移为负数,PID计算结果为正数;左偏移为正数,PID计算结果为负数)
printf("MUP6050_PID:%f \r\n",MPU6050_PID); //打印一下结果查看
PWML = 300 - (int)MPU6050_PID;
PWMR = 295 + (int)MPU6050_PID;
Car_Set_PWM(PWML,PWMR);
六.两轮电机的驱动
- 1.在头文件中定义单个单机的正转,反转,停止
#define RightMotor_Back() {HAL_GPIO_WritePin(BIN1_GPIO_Port,BIN1_Pin,GPIO_PIN_RESET);HAL_GPIO_WritePin(BIN2_GPIO_Port,BIN2_Pin,GPIO_PIN_SET);}
#define RightMotor_Go() {HAL_GPIO_WritePin(BIN1_GPIO_Port,BIN1_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(BIN2_GPIO_Port,BIN2_Pin,GPIO_PIN_RESET);}
#define RightMotor_Stop() {HAL_GPIO_WritePin(BIN1_GPIO_Port,BIN1_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(BIN2_GPIO_Port,BIN2_Pin,GPIO_PIN_RESET);}
#define LeftMotor_Back() {HAL_GPIO_WritePin(AIN1_GPIO_Port,AIN1_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(AIN2_GPIO_Port,AIN2_Pin,GPIO_PIN_SET);}
#define LeftMotor_Go() {HAL_GPIO_WritePin(AIN1_GPIO_Port,AIN1_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(AIN2_GPIO_Port,AIN2_Pin,GPIO_PIN_RESET);}
#define LeftMotor_Stop() {HAL_GPIO_WritePin(AIN1_GPIO_Port,AIN1_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(AIN2_GPIO_Port,AIN2_Pin,GPIO_PIN_RESET);}
- 2.在电机头文件中定义STBY的高低,用来控制两个电机是否转动
#define Car_Star() {HAL_GPIO_WritePin(STBY_GPIO_Port,STBY_Pin,GPIO_PIN_SET);} //STBY置高电平,小车启动
#define Car_Stop() {HAL_GPIO_WritePin(STBY_GPIO_Port,STBY_Pin,GPIO_PIN_RESET);} //STBY置低电平,小车停止
- 3.在头文件中定义PWM的最大值,达到限幅的目的
#define PWM_MAX 1000 //PWM最大值限幅,百分之80
- 4.头文件的函数声明
void Car_direction(int L,int R); //小车方向设置
void Car_Set_PWM(int PWML,int PWMR); //用PWM设置小车的速度
- C文件函数————小车两个电机方向控制
//参数:左轮方向,右轮方向
//0:电机反转 1:电机正转
void Car_direction(int L,int R)
{
if(L>0) //设置左电机方向
{
LeftMotor_Go();
}
else
{
LeftMotor_Back();
}
if(R>0) //设置右电机方向
{
RightMotor_Go();
}
else
{
RightMotor_Back();
}
}
- C文件函数————小车速度设置(PWM设置)
void Car_Set_PWM(int PWML,int PWMR)
{
//进行PWM大小的限幅
if(PWML > PWM_MAX)
{PWML = PWM_MAX;}
else if(PWML < 0)
{
PWML = 0;
}
if(PWMR > PWM_MAX)
{PWMR = PWM_MAX;}
else if(PWMR < 0)
{
PWMR = 0;
}
// printf("PWML:%d PWMR:%d\r\n",PWML,PWMR);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, PWML); //设置左电机的PWM
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, PWMR); //设置右电机PWM
}
七.PID的使用
- 头文件中创建一个PID结构体
typedef struct
{
float target_val;//目标值
float actual_val;//实际输出值
float err;//当前偏差
float err_last;//上次偏差
float err_pre; //上上次偏差
float err_sum;//误差累计值
float Kp,Ki,Kd;//比例,积分,微分系数
float MAX; //最大限幅值
float MIN; //最小限幅值
}tPid;
- 在PID的C文件中创建结构体变量
//定义一个结构体类型变量
tPid PID_Link; //定义一个循迹结构体
tPid PID_HC_SR04; //超声波加减速结构体
- PID初始化函数
//给结构体类型变量赋初值 结构体名,设定值,Kp,Ki,Kd,最大限幅值,最小限幅值
void PID_init(tPid *pid,float target_val,float Kp,float Ki,float Kd,float MAX,float MIN)
{
pid->target_val = target_val; //目标值设定
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->MAX = MAX;
pid->MIN = MIN;
pid->err = 0; //误差清零
pid->err_last = 0;
pid->err_pre = 0;
pid->err_sum = 0;
pid->actual_val = 0;//实际输出值
}
//主函数中进行初始化
PID_init(&PID_Link,0,55,0,200,500,-300); //循迹PID初始化
PID_init(&PID_HC_SR04,30,30,0,0,300,-50); //超声波PID初始化,目标设定为20 最大减速为0,最大加速为50
- 位置式PID计算函数
// 位置式PID控制函数
float PID_realize(tPid * pid,float actual_val)
{
pid->actual_val = actual_val;//传递真实值
pid->err = pid->target_val - pid->actual_val;当前误差=目标值-真实值
pid->err_sum += pid->err;//误差累计值 = 当前误差累计和
//使用PID控制 输出 = Kp*当前误差 + Ki*误差累计值 + Kd*(当前误差-上次误差)
pid->actual_val = pid->Kp*pid->err + pid->Ki*pid->err_sum + pid->Kd*(pid->err - pid->err_last);
//保存上次误差: 这次误差赋值给上次误差
pid->err_last = pid->err;
//PID限幅
if(pid->actual_val > pid->MAX)
{
pid->actual_val = pid -> MAX;
}
else if(pid->actual_val < pid->MIN)
{
pid->actual_val = pid -> MIN;
}
// printf("%f\r\n",actual_val);
return pid->actual_val;
}
八.循迹+PID
- 1.在头文件中定义循迹模块的引脚
#define L1 HAL_GPIO_ReadPin(L1_GPIO_Port,L1_Pin) //读取红外对管连接的GPIO电平
#define L2 HAL_GPIO_ReadPin(L2_GPIO_Port,L2_Pin)
#define L3 HALGPIO_ReadPin(L3_GPIO_Port,L3_Pin)
#define L4 HAL_GPIO_ReadPin(L4_GPIO_Port,L4_Pin)
#define R1 HAL_GPIO_ReadPin(R1_GPIO_Port,R1_Pin) //读取红外对管连接的GPIO电平
#define R2 HAL_GPIO_ReadPin(R2_GPIO_Port,R2_Pin)
#define R3 HAL_GPIO_ReadPin(R3_GPIO_Port,R3_Pin)
#define R4 HAL_GPIO_ReadPin(R4_GPIO_Port,R4_Pin)
- 2.在头文件中定义读取到黑线时的有效电平
#define Value 0 //读取黑线时为低电平
- 3.读取八路循迹函数
void track_Read8(void) //8路循迹的读取
{
int8_t j = 0; //循环计数变量(局部)
ValidSensorNum = 0; //读取前清空循迹模块计数标识
if(L1 == Value){Get_Read8[0] = 1;}else{Get_Read8[0] = 0;}
if(L2 == Value){Get_Read8[1] = 1;}else{Get_Read8[1] = 0;}
if(L3 == Value){Get_Read8[2] = 1;}else{Get_Read8[2] = 0;}
if(L4 == Value){Get_Read8[3] = 1;}else{Get_Read8[3] = 0;}
if(R1 == Value){Get_Read8[4] = 1;}else{Get_Read8[4] = 0;}
if(R2 == Value){Get_Read8[5] = 1;}else{Get_Read8[5] = 0;}
if(R3 == Value){Get_Read8[6] = 1;}else{Get_Read8[6] = 0;}
if(R4 == Value){Get_Read8[7] = 1;}else{Get_Read8[7] = 0;}
for(j=0;j<8;j++) //记录有多少个循迹模块在黑线上
{
ValidSensorNum = ValidSensorNum + Get_Read8[j];
}
// printf("%d\r\n",ValidSensorNum);
if(ValidSensorNum >= 5) //当小车正在行驶且检测到停止标识时
{
Beep_On(); //蜂鸣器响
StarTimerFlag = 1; //开始计时
}
else
{
Beep_off(); //蜂鸣器灭
}
}
1.四路循迹算法
//4路循迹,走外圈
float track_Error4(void)
{
track_Read8(); //读取8路循迹结果
if(Get_Read8[2] == 0 && Get_Read8[3] == 0 && Get_Read8[4] == 0 && Get_Read8[5] == 0 ) //全部未检测到,保持上一个状态
{
//保持上一个循迹状态
// memcpy(Last_Read,Get_Read,sizeof(Get_Read)); //保存为上一次得到状态
}
else
{
memcpy(Last_Read8,Get_Read8,sizeof(Get_Read8)); //保存为上一次得到状态
}
//开始逻辑判断
if(Last_Read8[2] == 0 && Last_Read8[3] == 0 && Last_Read8[4] == 0 && Last_Read8[5] == 1) //最外面一个检测到
{
error4 = 3;
}
else if(Last_Read8[2] == 0 && Last_Read8[3] == 0 && Last_Read8[4] == 1 && Last_Read8[5] == 1)
{
error4 = 2;
}
else if(Last_Read8[2] == 0 && Last_Read8[3] == 0 && Last_Read8[4] == 1 && Last_Read8[5] == 0)
{
error4 = 1;
}
else if(Last_Read8[2] == 0 && Last_Read8[3] == 1 && Last_Read8[4] == 1 && Last_Read8[5] == 0) //直行
{
error4 = 0;
}
else if(Last_Read8[2] == 0 && Last_Read8[3] == 1 && Last_Read8[4] == 0 && Last_Read8[5] == 0)
{
error4 = -1;
}
else if(Last_Read8[2] == 1 && Last_Read8[3] == 1 && Last_Read8[4] == 0 && Last_Read8[5] == 0)
{
error4 = -2;
}
else if(Last_Read8[2] == 1 && Last_Read8[3] == 0 && Last_Read8[4] == 0 && Last_Read8[5] == 0)
{
error4 = -3;
}
return error4;
}
2.八路循迹算法
//8路循迹,走内圈
float track_Error8(void)
{
track_Read8(); //读取8路循迹结果
if(Get_Read8[0]==0&&Get_Read8[1]==0&&Get_Read8[2]==0&&Get_Read8[3]==0&&Get_Read8[4]==0&&Get_Read8[5]==0&&Get_Read8[6]==0&&Get_Read8[7]==0) //全未检测,保持
{
//保持上一个循迹状态
}
else
{
memcpy(Last_Read8,Get_Read8,sizeof(Get_Read8)); //保存为上一次得到状态
}
//开始逻辑判断
if(Last_Read8[0]==1&&Last_Read8[1]==0)
{
error8 = -8;
// error8 = -25;
}
else if(Last_Read8[7] == 1) //右边最外侧一个检测到
{
error8 = -7;
}
else if(Last_Read8[0]==1&&Last_Read8[1]==1)
{
error8 = -6;
// error8 = -12;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==1&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = -5;
// error8 = -10;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==1&&Last_Read8[2]==1&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = -4;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==1&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = -3;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==1&&Last_Read8[3]==1&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = -2;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==1&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = -1;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==1&&Last_Read8[4]==1&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = 0;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==1&&Last_Read8[5]==0&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = 1;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==1&&Last_Read8[5]==1&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = 2;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==1&&Last_Read8[6]==0&&Last_Read8[7]==0)
{
error8 = 3;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==1&&Last_Read8[6]==1&&Last_Read8[7]==0)
{
error8 = 4;
}
else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==1&&Last_Read8[7]==0)
{
error8 = 5;
}
// else if(Last_Read8[0]==0&&Last_Read8[1]==0&&Last_Read8[2]==0&&Last_Read8[3]==0&&Last_Read8[4]==0&&Last_Read8[5]==0&&Last_Read8[6]==1&&Last_Read8[7]==1)
// {
// error8 = 6;
// }
if(ValidSensorNum>=5)
{
}
else
{
error8 = error8 + Last_Read8[0]*(-20) + Last_Read8[7]*(-20);
}
return error8;
}
3.循迹PID使用
PID_init(&PID_Link,0,55,0,200,500,-300); //PID初始化
track_err = track_Error4(); //获取四路循迹的偏差
track_err = track_Error8(); //获取四路循迹的偏差
deviation = (int32_t)PID_realize(&PID_Link,track_err); //获取PID的计算结果
//速度赋值
PWML = PWML_Base - deviation
PWMR = PWMR_Base + deviation
九.超声波+PID
1.超声波专用延时函数
void HC_SR04_Delayus(uint32_t usdelay)
{
__IO uint32_t Delay = usdelay * (SystemCoreClock / 8U / 1000U/1000);//SystemCoreClock:系统频率 = 72M
do
{
__NOP();
}
while (Delay --);
}
2.超声波距离读取函数(不用定时器)
注意:至少要2ms以上读取,精度更高。实际躲避距离比超声波读取的距离要短
float HC_SR04_Read(void)
{
uint32_t i = 0; //局部变量,用来循环计数
float Distance; //测量的距离保存结果
HAL_GPIO_WritePin(HC_SR04_Tring_GPIO_Port,HC_SR04_Tring_Pin,GPIO_PIN_SET); //给Tring脚输出15us高电平
HC_SR04_Delayus(15); //延时15us,给T脚输出15us高电平
HAL_GPIO_WritePin(HC_SR04_Tring_GPIO_Port,HC_SR04_Tring_Pin,GPIO_PIN_RESET); //高电平输出结束,设置为低电平
while(HAL_GPIO_ReadPin(HC_SR04_Echo_GPIO_Port,HC_SR04_Echo_Pin) == GPIO_PIN_RESET)//等待回响高电平
{
i++;
HC_SR04_Delayus(1);
if(i>100000) return -1;//超时退出循环、防止程序卡死这里
}
i = 0;
while(HAL_GPIO_ReadPin(HC_SR04_Echo_GPIO_Port,HC_SR04_Echo_Pin) == GPIO_PIN_SET)//下面的循环是2us
{
i = i+1;
HC_SR04_Delayus(1); //1us 延时,但是整个循环大概2us左右
if(i >100000) return -2;//超时退出循环
}
Distance = i*2*0.033/2;//这里乘2的原因是上面是2微妙
//小于20PID算出的结果为正值,大于20PID算出的结果为负值
if(Distance > 40) {Distance = 30;} //如果超声波距离大于45,则前方无障碍物,超声波不使用
// else if(Distance < 20) {Distance = 0;} //如果距离小于20cm,则让小车停止
return Distance ;
}
3.超声波PID的使用
PID_init(&PID_HC_SR04,30,30,0,0,300,-50);
distance = HC_SR04_Read(); //超声波距离的获取
HC_SR04_PID = PID_realize(&PID_HC_SR04,distance); //超声波距离PID的计算
PWML = PWML_Base - deviation - (int)HC_SR04_PID;
PWMR = PWMR_Base + deviation - (int)HC_SR04_PID;
十.按键中断
gpio.c
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == WKUP_Pin)
{
stopflag = 0;
Car_Star(); //小车开启
}
if(GPIO_Pin == KEY0_Pin) //判断是哪个按键按下?
{
//这里编写触发中断后要执行的程序
// HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
// stopflag = 0; //小车启动
// Car_Star();
stopflag = 1; //小车停止,设置目标值
Car_Stop();
}
if(GPIO_Pin == KEY1_Pin)
{
//这里编写触发中断后要执行的程序
// HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
stopflag = 1;
Car_Stop(); //小车停止
}
}
十一.舵机
- PWM开启
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); //开启定时器3,通道1PWM输出
- 舵机的使用
for(int16_t CCR = 500;CCR <= 2500;CCR+=1) //让舵机慢慢转动查看数据
{
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, CCR);
sprintf((char *)OledString,"CCR:%.2d ", CCR);
//x坐标,y坐标,驱动参数,字体大小
OLED_ShowString(0,3,OledString,12);
}
// }
t = 0.5ms——————-舵机会转动 0 °
t = 1.0ms——————-舵机会转动 45°
t = 1.5ms——————-舵机会转动 90°
t = 2.0ms——————-舵机会转动 135°
t = 2.5ms——————-舵机会转动180
十二.头文件的撰写模板
/*
**********************************************************************************************************************************
*
*
*
*
**********************************************************************************************************************************
*/
/* Define to prevent recursive inclusion------------------------------------------------------------------------------------------*/
#ifndef __LED_H
#define __LED_H
/* Includes-----------------------------------------------------------------------------------------------------------------------*/
#include "main.h"
/* 定义-----------------------------------------------------------------------------------------------------------------------*/
#define LED_RED PB
/* 提供给其他C文件调用的函数--------------------------------------------------------------------------------------------------------*/
void LED_GPIO_Config(void);
#endif /* __LED_H */
/*********************************************END OF FILE*************************************************************************/
十三.c文件的攥写模板
/*
**********************************************************************************************************************************
*
*
*
*
**********************************************************************************************************************************
*/
/* Includes-----------------------------------------------------------------------------------------------------------------------*/
#include "main.h"
/* 定义-----------------------------------------------------------------------------------------------------------------------*/
#define LED_RED PB
/* 函数-----------------------------------------------------------------------------------------------------------------------*/
/**
* 函数名:
* 描述:
* 输入:
* 输出:
**/
void LED_Config(void)
{
}