STM32
直播中

bigbangboom

8年用户 1310经验值
擅长:电源/新能源
私信 关注
[问答]

如何去制作一种基于STM32F103C8T6的循迹避障小车呢

如何使用pwm让舵机旋转到相应的角度呢?
如何配置定时器使用超声波模块测距呢?

回帖(1)

李颜

2021-11-18 10:55:16
  上篇主要是讲一些基础的东西,中篇讲了如何制作循迹,本篇讲一下制作避障小车。
  七,如何用使用pwm让舵机旋转到相应的角度
  我对他其中的一些关键信息再说明一下:
  1,接线
  橙色信号线
  红色正极
  棕褐色负极
  2,舵机控制对pwm的要求
  舵机的控制需要一个20ms的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分:
  t = 0.5ms——————-舵机会转动 0 °
  t = 1.0ms——————-舵机会转动 45°
  t = 1.5ms——————-舵机会转动 90°
  t = 2.0ms——————-舵机会转动 135°
  t = 2.5ms——————-舵机会转动180°
  我设置的是在转90度的时候为舵机的正前方,这样就能让舵机左右转了。
  好了,经过上面的分析我们可以开始写程序了,思路如下:
  利用定时器输出一个占空比可调的pwm,且这个pwm的周期为20ms。
  下面看一下我的sg90.c文件:
  #include “sg90.h”
  void SG90_pwm_init(void)
  {
  GPIO_InitTypeDef GPIO_InitStructure;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure;
  /* 开启时钟 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  /* 配置GPIO的模式和IO口 */
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;// PA1
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
  GPIO_Init(GPIOA,&GPIO_InitStructure);
  //TIM3定时器初始化
  TIM_TimeBaseInitStructure.TIM_Period = 199; //PWM 频率=72000/(199+1)=36Khz//设置自动重装载寄存器周期的值
  TIM_TimeBaseInitStructure.TIM_Prescaler = 7199;//设置用来做为TIMx时钟频率预分频值
  TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;//设置时钟分割:TDTS = Tck_tim
  TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
  TIM_TimeBaseInit(TIM2, & TIM_TimeBaseInitStructure);
  //PWM初始化 //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
  TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
  TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//PWM输出使能
  TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;
  TIM_OC2Init(TIM2,&TIM_OCInitStructure);
  //注意此处初始化时TIM_OC1Init而不是TIM_OCInit,不然会出错。由于固件库的版本不同。
  TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能或者失能TIMx在CCR1上的预装载寄存器
  TIM_Cmd(TIM2,ENABLE);//使能或者失能TIMx外设
  }
  下面是sg90.h文件内容:
  #ifndef __SG90_H
  #define __SG90_H
  #include “stm32f10x.h”
  #include “delay.h”
  #define SG90_Right_90 TIM_SetCompare2(TIM2, 195) //右转90度
  #define SG90_Right_45 TIM_SetCompare2(TIM2, 190)
  #define SG90_Front TIM_SetCompare2(TIM2, 185) //舵机摆正
  #define SG90_Left_45 TIM_SetCompare2(TIM2, 180) //左转45度
  #define SG90_Left_90 TIM_SetCompare2(TIM2, 175)
  void SG90_pwm_init(void); //舵机pwm初始化
  #endif
  我来解释一下舵机代码比较关键的几个参数设置:
  1,引脚怎么接?
  PA1接舵机橙色线。
  2,设置周期为20ms
  TIM_TimeBaseInitStructure.TIM_Period = 199;
  TIM_TimeBaseInitStructure.TIM_Prescaler = 7199;
  通过这两句话,根据周期计算公式:
  PWM周期为 (7200*200)/72000000=0.02=20ms
  3,设置pwm模式和初始极性
  TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
  TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//初始极性为低
  PWM1模式的意思如下:
  当计时器值小于比较器设定值时则TIMX输出脚此时输出有效低电位。
  当计时器值大于或等于比较器设定值时则TIMX输出脚此时输出高电位。
  4,通过TIM_SetCompare2设置占空比
  前面已经说了,我所设置的正前方是舵机转90度的时候,下面解释一下怎么通过设置占空比来设置方向吧。比如下面这句函数:
  #define SG90_Front TIM_SetCompare2(TIM2, 185) //舵机摆正
  由于高电平t = 1.5ms舵机会转动 90°,计数值TIM_Period设置的为200,比较值为185,所以高电平占比为(15/200)x20ms=1.5ms。
  类似的要设置其它的高电平占比也是这样设置了。
  八,如何配置定时器使用超声波模块测距
  1,先来看看舵机和云台是怎么接在一起的?
  
  类似这种,把超声波模块插在云台上,云台的作用其实就是把超声波模块固定在舵机上,再把模块的线引出来而已,舵机的线单独接或者接在云台上都是可以的。
  2,超声波模块怎么用?
  HC-SR04基本工作原理:
  (1)采用IO口TRIG触发测距,给最少10us的高电平信呈。
  (2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
  (3)有信号返回, 通过IO口ECHO输出一个高电平, 高电平持续的时间就是超声波从发射到返回的时间。 测试距离=(高电平时间*声速(340M/S))/2。
  3,程序怎么写?
  如果要写测距的话要观察测的是否准确,因为我手里正好有一块OLED屏,我就讲测得的距离放到了屏幕上,就可以直接观察测的距离了,你如果手里没有屏幕也可以用个串口程序讲数值传到电脑上去,只是这个我还没尝试过,这里就不介绍了。
  下面贴一下测距文件cs.c里的内容:
  #include “cs.h”
  #include “stm32f10x.h”
  #include “delay.h”
  #include “usart.h”
  /*记录定时器溢出次数*/
  uint overcount=0;
  /*设置中断优先级*/
  void NVIC_Config(void)
  {
  NVIC_InitTypeDef NVIC_InitStructer;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority=0;
  NVIC_InitStructer.NVIC_IRQChannelSubPriority=0;
  NVIC_InitStructer.NVIC_IRQChannel=TIM4_IRQn;
  NVIC_InitStructer.NVIC_IRQChannelCmd=ENABLE;
  NVIC_Init(&NVIC_InitStructer);
  }
  /*初始化模块的GPIO以及初始化定时器TIM2*/
  void CH_SR04_Init(void)
  {
  GPIO_InitTypeDef GPIO_InitStructer;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
  /*TRIG触发信号*/
  GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
  GPIO_InitStructer.GPIO_Pin=GPIO_Pin_8;
  GPIO_Init(GPIOB, &GPIO_InitStructer);
  /*ECOH回响信号*/
  GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
  GPIO_InitStructer.GPIO_Pin=GPIO_Pin_9;
  GPIO_Init(GPIOB, & GPIO_InitStructer);
  /*定时器TIM2初始化*/
  TIM_DeInit(TIM4);
  TIM_TimeBaseInitStructer.TIM_Period=999;//定时周期为1000
  TIM_TimeBaseInitStructer.TIM_Prescaler=71; //分频系数72
  TIM_TimeBaseInitStructer.TIM_ClockDivision=TIM_CKD_DIV1;//不分频
  TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructer);
  TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//开启更新中断
  NVIC_Config();
  TIM_Cmd(TIM4,DISABLE);//关闭定时器使能
  }
  float Senor_Using(void)
  {
  float length=0,sum=0;
  u16 tim;
  uint i=0;
  /*测5次数据计算一次平均值*/
  while(i!=5)
  {
  PBout(8)=1; //拉高信号,做为触发信号
  delay_us(20); //高电平信号超过10us
  PBout(8)=0;
  /*等待回响信号*/
  while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==RESET);
  TIM_Cmd(TIM4,ENABLE);//回响信号到来,开启定时器计数
  i+=1; //每收到一次回响信号+1,收到5次就计算均值
  while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET);//回响信号消失
  TIM_Cmd(TIM4,DISABLE);//关闭定时器
  tim=TIM_GetCounter(TIM4);//获取计TIM4数寄存器中的计数值,一边计算回响信号时间
  length=(tim+overcount*1000)/58.0;//经过回响信号计算距离
  sum=length+sum;
  TIM4-》CNT=0; //将TIM4计数寄存器的计数值清零
  overcount=0; //中断溢出次数清零
  delay_ms(10);
  }
  length=sum/5;
  return length;//距离做为函数返回值
  }
  void TIM4_IRQHandler(void) //中断,当回响信号很长是,计数值溢出后重复计数,用中断来保存溢出次数
  {
  if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
  {
  TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除中断标志
  overcount++;
  }
  }
  然后下面是cs.h文件:
  #ifndef __CS_H
  #define __CS_H
  #include “stm32f10x.h”
  #include “delay.h”
  #include “sys.h”
  #define uint unsigned int
  #define TRIG_Send PBout(8)
  #define ECHO_Reci PBin(9)
  void CH_SR04_Init(void); //超声波模块相关配置初始化
  float Senor_Using(void); //测距函数,返回值即为距离
  void NVIC_Config(void); //中断配置
  #endif
  下面简单介绍怎么使用:
  (1):引脚怎么接?
  PB8接Trig口,PB9接Echo口。
  (2):怎么用?
  主函数中调用CH_SR04_Init()这一个函数即可初始化测距的相关配置,
  测距直接用调用Senor_Using()这个函数,函数的返回值即为距离。
  其它关于代码的设置我就不讲了,注释里已经很详细了
  (3):使用例子
  我用了oled屏幕显示,这段程序在我上传的代码里没有,仅仅用来测试的而已,但是我上传的代码已经将oled初始化的代码在主函数里调用了,所以在主函数也可以直接用oled屏幕。
  下面是我的测试程序:
  //头文件
  #include “stm32f10x.h”
  #include “led.h”
  #include “moter.h”
  #include “xunji.h”
  #include “sg90.h”
  #include “delay.h”
  #include “sys.h”
  #include “oled.h”
  #include “bmp.h”
  #include “cs.h”
  int main(void)
  {
  char str[20]; //用来存放浮点数字符
  float length_res[5]; //用来存放测距结果
  SystemInit(); // 配置系统时钟为72M
  delay_init(); //延时初始化
  xunji_config(); //循迹初始化
  TIM3_PWM_Init(); //电机pwm TIM3
  SG90_pwm_init(); //舵机pwm TIM2
  CH_SR04_Init(); //超声波定时器 TIM4
  OLED_Init(); //oled显示初始化
  while(1)
  {
  SG90_Front; //舵机摆正
  length_res[0] =Senor_Using(); //测前方距离放在数组里
  num2char(str,length_res[0],3,3); //将浮点数转为字符
  OLED_ShowString(44,24,str,16);
  OLED_ShowString(0,2,“distance is”,16);
  OLED_Refresh();
  }
  }
  效果是这样的
  好了,测距已经准备就绪了,下面就可以开始写避障函数啦。
  九,完成避障小车的制作
  这里的任务主要就是编写主函数里的循环了。
  设计思路如下:
  1,舵机向前摆正,测量正前方的距离,如果距离小于30cm就停下来。
  2,停下后,舵机检测左边45度和右边45度的距离,比较这两个距离。
  3,假如左边的距离比右边大,就用一个do-while循环,使舵机摆正不断测量前方距离,同时小车缓慢左转,一直转到前方距离大于30cm,小车继续向前,循环继续。
  下面是主函数:
  int main(void)
  {
  char str[20]; //用来存放浮点数字符
  float length_res[5]; //用来存放测距结果
  SystemInit(); // 配置系统时钟为72M
  delay_init(); //延时初始化
  xunji_config(); //循迹初始化
  TIM3_PWM_Init(); //电机pwm TIM3
  SG90_pwm_init(); //舵机pwm TIM2
  CH_SR04_Init(); //超声波定时器 TIM4
  OLED_Init(); //oled显示初始化
  while(1)
  {
  SG90_Front; //舵机摆正
  delay_ms(100);
  length_res[0] =Senor_Using(); //测前方距离放在数组里
  delay_ms(100);
  if(length_res[0]》30.00) //若是前方距离大于30cm 前进
  {
  CarGo();
  }
  if(length_res[0]《30.00) //若是前方距离小于30厘米 停车测左右距离
  {
  CarStop();
  SG90_Left_45; //舵机左转45度测距
  delay_ms(700);
  length_res[1] =Senor_Using(); //把测量结果放进数组
  SG90_Right_45; //舵机右转45度测距
  delay_ms(700);
  length_res[4] =Senor_Using(); //把测量结果放进数组
  SG90_Front; //舵机摆正
  delay_ms(100);
  if(length_res[1]》length_res[4]) //若是左边的距离大于右边的距离
  {
  do //舵机摆正
  {
  SG90_Front;
  delay_ms(10);
  length_res[0] =Senor_Using(); //重复测前方的距离同时左转
  delay_ms(10);
  CarLeft();
  }
  while(length_res[0]《30.00); //一直转到前方距离大于30cm
  }
  if(length_res[1]《length_res[4]) //若是右边的距离大于左边的距离
  {
  do
  {
  SG90_Front;
  delay_ms(10);
  length_res[0] =Senor_Using(); //重复测前方的距离同时右转
  delay_ms(10);
  CarRight();
  }
  while(length_res[0]《30.00); //一直转到前方距离大于30cm
  }
  }
  }
  }
  好了,到此为止,循迹避障小车已经设计完成了。
  下面总结一下如何接线的:
  电机驱动:
  A6----IN1
  A7----IN2
  B0----IN3
  B1----IN4
  循迹模块:(从左到右为1234)
  B4----第1个循迹模块的D0
  B5----第2个循迹模块的D0
  B6----第3个循迹模块的D0
  B7----第4个循迹模块的D0
  舵机:
  A1----舵机橙色线
  超声波模块:
  PB8----Trig
  PB9----Echo
  oled(iic协议):
  A8----SCL
  A9----SDA
  当然循迹和避障的策略都是我自己为了完成任务写的,比较简单,能够实现循迹避障功能,到后面你已经会操作各个模块后,自己写个更好的循迹避障策略是完全没有问题的,或者是用我的工程,里面的各模块函数也都写好了,直接调用就行。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分