常用HAL库代码移植(常用函数+各模块常用函数功能函数编写)

注意:在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)
{
    
}
  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sillyfoxzero

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值