深圳市航顺芯片技术研发有限公司
直播中

陈超

7年用户 958经验值
私信 关注
[问答]

怎样去设计一种基于STM32F103c8t6的智能垃圾桶呢

基于STM32F103c8t6的智能垃圾桶是由哪些部分组成的?
怎样去设计一种基于STM32F103c8t6的智能垃圾桶呢?

回帖(1)

黄乃康

2021-11-3 10:19:41
  软件配置
  本文主要用的编译软件为keil5,软件配置的部分可参考以下文章(文章也包括了烧录的部分)。
  硬件部分
  以STM32F103c8t6的最小系统板为核心,配合HC-SR04超声波模块、SG90舵机以及一个0.96寸的IIC接口的OLED显示屏。当然还有若干的杜邦线及一个垃圾桶模型,模块固定部分可自行发挥。
  1. 主控芯片
  以STM32F103c8t6芯片为核心的最小系统板,实物图就如下所示,来源是淘宝。
  关于STM32F103c8t6芯片本文就不加以介绍,可参考以下文章。
  2. HC-SR04超声波模块
  模块简介
  HC-SR04超声波模块常用于机器人避障、物体测距、液位检测、公共安防、停车场检测等场所。HC-SR04超声波模块主要是由两个通用的压电陶瓷超声传感器,并加外围信号处理威廉希尔官方网站 构成的。如图:
  模块参数
  1) 模块主要参数
  ① 使用电压:DC—5V
  ② 静态电流:小于2mA
  ③ 电平输出:高5V
  ④ 电平输出:底0V
  ⑤ 感应角度:不大于15度
  ⑥ 探测距离:2cm-450cm
  ⑦ 高精度 可达0.2cm
  2) 模块引脚
  超声波模块有4个引脚,分别为Vcc(电源+)、GND(接地)、 Trig(控制端)、 Echo(接收端);其中Vcc接上5V电源,GND接地, Trig(控制端)控制发出的超声波信号,Echo(接收端)接收反射回来的超声波信号。
  工作原理
  a.单片机引脚触发Trig测距,给至少 10us 的高电平信号;
  b.模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
  c.有信号返回,通过 IO 输出一高电平,并单片机定时器计算高电平持续的时间;
  d.超声波从发射到返回的时间.
  计算公式:测试距离=(高电平时间*声速(340M/S))/2;
  
  简单来说,就是超声波模块发出声波信号,同时定时计数器器开始计时,声波信号在遇到障碍物后返回。采集到返回的声波信号后,模块输出一个高电平信号,相应配合的定时计数器停止计时,最后通过超声波在空气中的传递规律公式计算出障碍物的距离。
  3. SG90舵机
  模块简介
  舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。目前,在高档遥控玩具,如飞机、潜艇模型,遥控机器人中已经得到了普遍应用。如图:
  模块参数
  1) 模块主要参数
  ① 死区设定:7us (7MHZ)
  ② 工作电压:4.8V-6V
  ③ 位置等级:1024级
  ④ 脉冲控制精度为2us
  ⑤ 尺寸:21.5mmX11.8mmX22.7mm
  ⑥ 重量:9克 (1kg=1公斤=2斤)
  ⑦ 无负载速度:0.12秒/60度(4.8V) 0.002s/度
  ⑧ 堵转扭矩:1.2-1.4公斤/厘米(4.8V)
  ⑨ 使用温度:-30~~+60摄氏度
  2) 模块引脚
  SG90模块有三个引脚,信号引脚(橙色)、Vcc(红色)及GND(棕色);在使用过程中,信号引脚与单片机输出PWM信号的引脚相连接,Vcc与电源+5V相连接,GND接地。
  工作原理
  SG90的工作原理是采用PWM控制的方式来进行舵机的操纵,主要是让板子产生一个20ms的脉冲信号,以0.5ms到2.5ms的高电平来控制舵机的角度。以180度角度伺服为例,那么对应的控制关系如下:
  0.5ms-------------0度; 2.5% 对应函数中占空比为250
  1.0ms------------45度; 5.0% 对应函数中占空比为500
  1.5ms------------90度; 7.5% 对应函数中占空比为750
  2.0ms-----------135度; 10.0% 对应函数中占空比为1000
  2.5ms-----------180度; 12.5% 对应函数中占空比为1250
  详细的舵机使用原理可参考《舵机使用基础(SG90模拟舵机和MG90S数字舵机为例)》
  GPIO配置
  3. OLED显示屏
  模块简介
  OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
  模块参数
  1) 模块主要参数
  ① 尺寸:0.96 (inch)
  ② 工作电压:3.3V/5V
  ③ 高分辨率: 128x64
  ④ 有效显示区域: 21.744x10.864(mm)
  ⑤ 模块尺寸: 27.3x27.8(mm)
  ⑥ 视角:》160°
  ⑦ 重量:15克
  ⑧ OLED驱动芯片:SSD1306
  ⑨ 功耗: 全亮约为25mA,全灭约为1.5mA
  ⑩ 使用温度:-20~~+70摄氏度
  数据来源LCD wiki
  2) 模块引脚
  0.96寸OLED显示屏(IIC)有四个引脚,GND (接地)、 Vcc (电源+)、SCL OLED 的 D0 脚、SDA OLED 的 D1 脚。在使用过程中,Vcc接电源+3.3V(或+5V),GND接地, D0引脚,在 IIC 通信中为时钟管脚,D1引脚,在 IIC 通信中为数据管脚,接相应的GPIO口。
  工作原理
  本屏所用的驱动 IC 为 SSD1306;其具有内部升压功能;当然了本屏也可以选用外部升压,具体的请详查数据手册。SSD1306 控制器用1bit控制一个像素点显示。所以每个像素点只能显示黑白两色。其显示的RAM总共分为8页,每页有8行,每行128个像素点。设置像素点数据时,需要指定页地址,再分别指定列低地址跟列高地址,所以每次同时设置垂直方向的8个像素点。为了能够灵活控制任意任意位置的像素点,软件上先设置一个设置一个和RAM一样大小的全局一维数组,先将像素点数据设置到全局数组中,此过程采用或、与操作保证之前写入全局数组的数据不受破坏,然后将全局数组的数据写入到显示RAM中,这样就可以通过OLED显示出来了。
  GPIO配置
  #include “hc-sr04.h”
  uint count = 0;
  // Set NVIC
  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_IRQChannelCmd = ENABLE;
  NVIC_InitStructer.NVIC_IRQChannel = TIM2_IRQn;
  NVIC_Init(&NVIC_InitStructer);
  }
  /*HC-SR04 GPIO TIM3*/
  void HC_SR04_init(void){
  GPIO_InitTypeDef GPIO_InitStructer;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  /*TRIG PB8*/
  GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructer.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
  GPIO_InitStructer.GPIO_Pin = HCSR04_TRIG;
  GPIO_Init(HCSR04_PORT, &GPIO_InitStructer);
  /*ECOH PB7*/
  GPIO_InitStructer.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_InitStructer.GPIO_Pin = HCSR04_ECHO;
  GPIO_Init(HCSR04_PORT, &GPIO_InitStructer);
  /*定时器TIM3初始化*/
  TIM_DeInit(TIM3);
  TIM_TimeBaseInitStructer.TIM_Period = 999; //计数到1000为1ms
  TIM_TimeBaseInitStructer.TIM_Prescaler = 71; //1M的计数频率 1us计数
  TIM_TimeBaseInitStructer.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
  TIM_TimeBaseInitStructer.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructer);
  TIM_ClearFlag(TIM3, TIM_FLAG_Update); // 清除中断更新
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 打开定时器更新中断
  NVIC_Config();
  TIM_Cmd(TIM3, DISABLE);
  }
  /*测距部分*/
  float Count_Dist(void)
  {
  float distance = 0, sum = 0;
  uint16_t time;
  uint i = 0;
  while(i 《 5)
  {
  GPIO_SetBits(HCSR04_PORT, HCSR04_TRIG);
  delay_us(20);
  GPIO_ResetBits(HCSR04_PORT, HCSR04_TRIG);
  while(GPIO_ReadInputDataBit(HCSR04_PORT, HCSR04_ECHO) == 0);//回响信号出现
  TIM_Cmd(TIM3, ENABLE); //打开定时器
  i += 1;
  while(GPIO_ReadInputDataBit(HCSR04_PORT, HCSR04_ECHO) == 1); //回响信号消失
  TIM_Cmd(TIM3,DISABLE); //关闭定时器
  time = TIM_GetCounter(TIM3); //获取TIM3计数值
  distance = (time + count*1000)/58.0; //通过回响计算距离
  sum = distance + sum; //五次结果相加
  TIM3-》CNT = 0; //TIM3清零
  count = 0; //中断溢出次数清零
  delay_ms(10);
  }
  distance = sum/5; //取5次的平均值
  return distance;
  }
  void TIM3_IRQHandler(void)
  {
  if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET)
  {
  TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //??????
  count++;
  }
  }
  SG90舵机代码
  #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 选择GPIOA_1 */
  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);
  //TIM2定时初始化
  TIM_TimeBaseInitStructure.TIM_Period = 199; //PWM 频率=72000/(199+1)=36Khz
  TIM_TimeBaseInitStructure.TIM_Prescaler = 7199;//设置TIM2预分频
  TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;//时钟分割:TDTS = Tck_tim
  TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM2向上计数
  TIM_TimeBaseInit(TIM2, & TIM_TimeBaseInitStructure);
  //PWM初始化
  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_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能TIM2在CCR1上的预装载寄存器
  TIM_Cmd(TIM2,ENABLE);//使能TIM2外设
  }
  显示屏代码
  由于显示屏的代码过于冗长,可参考中景园电子的0.96寸显示屏代码,这里对本项目修改的部分进行演示。首先讲解汉字取模的过程:
  ① 打开取模软件,然后模式选择字符模式,然后点击选项
  
  ② 汉字取模设置如下,点击确定
  
  ③ 在红框内输入要取模的汉字,然后点击生成字模
  
  讲完怎么取模后,针对本项目需要的字进行取模。
  显示屏主要显示垃圾桶的状态及障碍物的距离,因此需要显示的文字为“垃圾桶:开关距离为:”。
  {0x20,0x20,0x20,0xFF,0x20,0x20,0x10,0x90,0x11,0x16,0x10,0x10,0xD0,0x10,0x00,0x00},
  {0x10,0x30,0x10,0x0F,0x08,0x48,0x40,0x41,0x5E,0x40,0x70,0x4E,0x41,0x40,0x40,0x00},/*“垃”,0*/
  /* (16 X 16 , ?? )*/
  {0x20,0x20,0x20,0xFF,0x20,0x22,0x02,0xFE,0x02,0x02,0x62,0x5A,0x46,0xC0,0x00,0x00},
  {0x08,0x18,0x08,0x07,0x44,0x34,0x8E,0x81,0x46,0x28,0x10,0x28,0x46,0x81,0x80,0x00},/*“圾”,1*/
  /* (16 X 16 , ?? )*/
  {0x10,0x90,0xFF,0x90,0x10,0xE2,0x22,0x2A,0x2A,0xF2,0x2A,0x26,0x22,0xE0,0x00,0x00},
  {0x06,0x01,0xFF,0x00,0x01,0xFF,0x09,0x09,0x09,0x7F,0x09,0x49,0x89,0x7F,0x00,0x00},/*“桶”,2*/
  /* (16 X 16 , ?? )*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
  {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*“:”,3*/
  /* (16 X 16 , ?? )*/
  {0x80,0x82,0x82,0x82,0xFE,0x82,0x82,0x82,0x82,0x82,0xFE,0x82,0x82,0x82,0x80,0x00},
  {0x00,0x80,0x40,0x30,0x0F,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00},/*“开”,4*/
  /* (16 X 16 , ?? )*/
  {0x00,0x00,0x10,0x11,0x16,0x10,0x10,0xF0,0x10,0x10,0x14,0x13,0x10,0x00,0x00,0x00},
  {0x81,0x81,0x41,0x41,0x21,0x11,0x0D,0x03,0x0D,0x11,0x21,0x41,0x41,0x81,0x81,0x00},/*“关”,5*/
  /* (16 X 16 , ?? )*/
  {0x00,0x3E,0x22,0xE2,0x22,0x3E,0x00,0xFE,0x22,0x22,0x22,0x22,0x22,0xE2,0x02,0x00},
  {0x20,0x3F,0x20,0x1F,0x11,0x11,0x00,0x7F,0x44,0x44,0x44,0x44,0x44,0x47,0x40,0x00},/*“距”,6*/
  /* (16 X 16 , ?? )*/
  {0x04,0x04,0x04,0xF4,0x84,0xD4,0xA5,0xA6,0xA4,0xD4,0x84,0xF4,0x04,0x04,0x04,0x00},
  {0x00,0xFE,0x02,0x02,0x12,0x3A,0x16,0x13,0x12,0x1A,0x32,0x42,0x82,0x7E,0x00,0x00},/*“离”,7*/
  /* (16 X 16 , ?? )*/
  {0x00,0x20,0x22,0x2C,0x20,0x20,0xE0,0x3F,0x20,0x20,0x20,0x20,0xE0,0x00,0x00,0x00},
  {0x80,0x40,0x20,0x10,0x08,0x06,0x01,0x00,0x01,0x46,0x80,0x40,0x3F,0x00,0x00,0x00},/*“为”,8*/
  /* (16 X 16 , ?? )*/
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
  {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*“:”,9*/
  /* (16 X 16 , ?? )*/
  根据需要的文字,写入部分显示屏的代码:
  void LEDSHOW1(void)
  {
  OLED_ShowChinese(18,8,0,16);//垃
  OLED_ShowChinese(36,8,1,16);//圾
  OLED_ShowChinese(54,8,2,16);//桶
  OLED_ShowChinese(72,8,3,16);//:
  OLED_ShowChinese(18,40,6,16);//距
  OLED_ShowChinese(36,40,7,16);//离
  OLED_ShowChinese(54,40,8,16);//为
  OLED_ShowChinese(72,40,3,16);//:
  OLED_Refresh();//更新显存到OLED
  delay_ms(500);
  }
  这里讲解一下中文显示函数
  void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1)
  x,y —— 起点对应坐标
  num —— 汉字对应的序号
  size1 —— 汉字字体的大小
  OLED显示屏的分辨率是128*64,用一个方块来表示一个汉字,坐标(x,y)对应汉字左上角相对显示屏左上角的坐标,size1(以16×16为例)则相当于方块的大小。如图:
  
  以下是完整的汉字OLED显示函数:
  void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1)
  {
  u8 i,m,n=0,temp,chr1;
  u8 x0=x,y0=y;
  u8 size3=size1/8;
  while(size3--)
  {
  chr1=num*size1/8+n;
  n++;
  for(i=0;i《size1;i++)
  {
  if(size1==16)
  {temp=Hzk1[chr1][i];}//调用16*16字体
  else if(size1==24)
  {temp=Hzk2[chr1][i];}//调用24*24字体
  else if(size1==32)
  {temp=Hzk3[chr1][i];}//调用32*32字体
  else if(size1==64)
  {temp=Hzk4[chr1][i];}//调用64*64字体
  else return;
  for(m=0;m《8;m++)
  {
  if(temp&0x01)OLED_DrawPoint(x,y);
  else OLED_ClearPoint(x,y);
  temp》》=1;
  y++;
  }
  x++;
  if((x-x0)==size1)
  {x=x0;y0=y0+8;}
  y=y0;
  }
  }
  }
  其余代码
  为了方便判断,将距离判断的代码直接写在HC-SR04部分的代码中。
  》float Count_Dist(void)
  》{
  》……(取得distance平均值后)
  》 OLED_ShowNum(90,40,distance,2,16);//显示距离
  OLED_Refresh();
  delay_ms(500);
  》//判断
  if(distance 》 7)
  {
  SG90_Right_90;//关闭垃圾桶
  //delay_ms(1000);
  OLED_ShowChinese(90,8,5,16);//关
  OLED_Refresh();
  delay_ms(500);
  //a = 1;
  }
  else if(distance 《= 7)
  {
  SG90_Front;//打开垃圾桶
  //delay_ms(1000);
  OLED_ShowChinese(90,8,4,16);//开
  OLED_Refresh();
  delay_ms(500);
  //a = 0;
  }
  》……
  》}
  主函数
  #include “main.h”
  int main(void)
  {
  delay_init();
  NVIC_Configuration();
  OLED_Init();//显示屏初始化
  SG90_pwm_init();//舵机初始化
  HC_SR04_init();//超声波初始化
  OLED_ColorTurn(0);//0正常显示,1反色显示
  OLED_DisplayTurn(0);//0正常显示,1屏幕翻转显示
  OLED_Refresh();
  while(1)
  {
  LEDSHOW1();//显示OLED屏固定文字
  Count_Dist();//超声波测距并显示距离,判断后对舵机进行操作
  }
  }
  过程中遇到的问题
  SG90及HC-SR04在编写代码的过程中,选择了同一个通用定时器,虽然程序没有报错,但是在实际使用的过程中发现垃圾桶并没有打开,即舵机没有转动,修改后问题得到解决。
  HC-SR04在使用本文的代码时,会出现由于障碍物距离过远或没有障碍物的情况会导致检测到的距离出错,容易导致垃圾桶的非正常运作。在实际使用过程中,若手部靠近垃圾桶时,垃圾桶能正常打开,但若手突然消失(即手在垃圾桶应该打开的距离内离开超声波检测模块的检测距离)时,容易出现距离检测失败,垃圾桶依旧保持打开状态,但大部分情况下正常。此问题尚未解决。
举报

更多回帖

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