IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> stm32项目_stm32f103c8t6项目_循迹避障小车完整制作过程_智能小车设计_STM32智能小车教程-循迹-避障-蓝牙遥控-跟随 -> 正文阅读

[嵌入式]stm32项目_stm32f103c8t6项目_循迹避障小车完整制作过程_智能小车设计_STM32智能小车教程-循迹-避障-蓝牙遥控-跟随

[硬件]

元件选型

照片上传出问题了,改天补上,着急的可以看视频
视频链接在这里

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKjJGMXv-1646313963955)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303131457462.png)]

原理图绘制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Y1vYGWJ-1646313963957)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303131554546.png)]

要结合购买的元件模块设计原理图

比如

image-20220208193635089

查看数据手册参考手册确定引脚功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDAUGJ9n-1646313963957)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220208194421919.png)]

PCB布局与走线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnDQ4kBN-1646313963958)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303132333282.png)]

电源线走线粗一点

可以把电源线走在底层,信号线在顶层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ch7UqHRV-1646313963959)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220208210628764.png)]

根据元件特点布局

核心板的排母间距要注意!!!

image-20220208211025675

比如:这种元件就要放到PCB边上

image-20220208210121549

下单PCB打样

检查DRC没有问题就可以打样了

焊接PCB

焊接比较简单

如果大家有问题,留言我抽空补上视频

安装组装

安装比较简单

如果大家有问题,留言我抽空补上视频

[软件]编程开发中如何获得资料

模块资料

我们通过淘宝获得

STM32F103C8T6最小系统板模块

通过淘宝下载同一型号资料即可

其他模块资料

可以通过淘宝简介得到

STM32外设驱动资料

我们通过正点原子下载获取:

小车原理图

通过EDA软件导出

程序移植-STM32F103ZET6移植到STM32F103C8T6

第一步

打开魔术棒,点击Device,更改芯片类型为C8T6

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3ALbwms-1646313963959)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173242935.png)]

第二步

点击Target,晶振频率改为8Mhz

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJJFUsmS-1646313963960)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173436660.png)]

第三步

点击C/C++,将define中的STM32F10X_HD,USE_STDPERIPH_DRIVER改成STM32F10X_MD,USE_STDPERIPH_DRIVER

STM32F10X_MD,USE_STDPERIPH_DRIVER

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMLZGuGq-1646313963960)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173653028.png)]

第四步

点击Utilities,点开settings,在Flash Download栏下,将STM32F103ZET6中512k的移除,并改为128k,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2jzyDeIb-1646313963961)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113174002136.png)]

第五步

将该工程文件中CORE中的startup_stm32f10x_hd.s文件换为startup_stm32f10x_md.s文件

  1. 删除原来的:startup_stm32f10x_hd.s

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BbjxCZc-1646313963961)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113183940843.png)]

  1. 将startup_stm32f10x_md.s复制到工程文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdU25Kou-1646313963962)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113184742797.png)]

  1. 工程中添加startup_stm32f10x_md.s

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DILgDGz8-1646313963962)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113192246006.png)]

第六步

编译一下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oRbc6YlL-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113192519150.png)]

那么我们就完成了把ZET6的工程移植成为C8T6的工作,下面让我们点灯测试一下啊.

使用STlink烧录 时候出现:

image-20220129221014488

方法:

image-20220129221117759

GPIO输出实验点亮C8T6板载小灯

第一步

查阅原理图,小灯接在PC13上下面驱动PC13

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jeFGlgSG-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195307685.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUvVvhyX-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195226345.png)]

思考题:如果同时驱动PC13与PC14,应该如何编写?(答案:应该增加下图代码)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9NIgtkw-1646313963964)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195611097.png)]

LED_Init()函数的代码

void LED_Init(void)
{
 GPIO_InitTypeDef  GPIO_InitStructure;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PC端口时钟
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				 //PC13
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOC, &GPIO_InitStructure);					 //根据设定参数初始化GPIOC.13
 GPIO_SetBits(GPIOC,GPIO_Pin_13);						 //PC.13输出高		
}

LED.h 部分宏定义

#define LED PCout(13)// PC13

第二步

编译下载(如果没有运行,需要按复位 运行)

以上我们就完成基本测试,下面让我们学习一下,如何从零设计小车!!!<( ̄︶ ̄)↗[GO!]

小车设计

总体设计方案

总体的设计方案对完成项目非常重要,下面是小车的设计方案,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJgjG0yf-1646313963964)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113215641227.png)]

对小车的模组进行了,简单分类。这里简单介绍一下:

一辆’自动化’小车,要能够像人一样,有观察事物的眼睛,有处理事情的大脑,有可以跑动的腿,这里:

输入信号模块就像人类的眼睛,可以讲一些外界信息测量并送至’大脑’,比如超声波把距离信息发送给单片机。

执行模块就像人类的腿,可以根据’大脑’控制指令进行’运动’,比如舵机根据单片机指令旋转。

单片机就像人类的大脑,可以根据输入信号模块完成对执行模块的控制。

电源负责给整个系统供电。

OLED模块显示一些系统信息。

[硬件]系统硬件设计

主控:STM32单片机

使用:STM32f103c8t6最小系统板

选择原因:STM32F103C8T6价格较低,资源丰富可以满足项目要求,可以在其数据手册阅读资源介绍。

注意:

系统需要5V供电,可输出3.3V

image-20220113213127136

OLED模块

使用:OLED显示屏模块 0.96寸 IIC/SPI

选择原因:价格较低、使用方便

注意:

这里使用 四管脚 顺序为 GND VCC SCL SDA,绘制PCB要注意顺序

供电为3.3V

image-20220113214310617

陀螺仪

使用:MPU-6050模块 三轴加速度陀螺仪6DOF GY-521

原因:满足项目需要,使用方便

注意:

供电3V-5V

image-20220113215037252

超声波测距模块

使用:HC-SR04 超声波测距模块

注意:

绘制PCB注意四个引脚顺序 Vcc Trig Echo Gnd

供电3.3V-5V

image-20220113220339326

测距原理

image-20220123153322483

不同模式

image-20220123150130120

GPIO模式

image-20220123150318603 image-20220123150441905

红外循迹模块

使用:寻迹传感器 TCRT5000红外反射传感器

注意:

供电3.3V-5V

引脚顺序为: VCC GDN DO AO (DO表示数字输出,AO表示模拟输出)

image-20220113221830368

来自TB的介绍

不完全总结就是:红外对管前面是黑色的时候,DO引脚为高电平,二极管熄灭状态。前面是红色的时候为低电平,二极管点亮。

image-20220114234620688

蓝牙模块

使用:HC-05 主从机一体蓝牙串口透传模块

注意:

供电3.6V-6V

引脚顺序 VCC GND TXD RXD

image-20220113222743332 image-20220115011720155 image-20220119134502802 image-20220119134528752

按键

使用:这里按键使用PCB 元件

电机驱动

使用:TB6612FNG电机驱动模块

注意:

供电 比较复杂

image-20220113223326895

来自淘宝的介绍

image-20220113233943367 image-20220113234009739 image-20220113234034447

电机

使用:电机马达 DC3V-6V直流减速电机

注意:

供电3V-6V

电机要能够安装在小车车架上(这里使用的电机是小车车架套餐配套的)

image-20220113223741490

舵机

使用:SG90 9g舵机 固定翼航模遥控飞机 180度舵机

注意:

供电4.8V-6V

需要控制角度,故购买180度 舵机

image-20220113224135083 image-20220201143528698

电源

使用:12v锂电池组18650充电带保护板大容量电瓶通用移动电源便携蓄电池

注意:

使用电池输出为12V

接口为DC5.5-2.1公母头

image-20220113232436671

系统软件设计

点亮小灯

查看原理图

查阅原理图,小灯接在PC13上下面驱动PC13

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNU6Z0Mb-1646313963965)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195307685.png)]

编写驱动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60KaVxby-1646313963966)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195226345.png)]

思考题:如果同时驱动PC13与PC14,应该如何编写?(答案:应该增加下图代码)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRdMp3nk-1646313963966)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195611097.png)]

LED_Init()函数的代码

void LED_Init(void)
{
 GPIO_InitTypeDef  GPIO_InitStructure;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PC端口时钟
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				 //PC13
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOC, &GPIO_InitStructure);					 //根据设定参数初始化GPIOC.13
 GPIO_SetBits(GPIOC,GPIO_Pin_13);						 //PC.13输出高		
}

LED.h 部分宏定义

#define LED PCout(13)// PC13

测试

编译下载(如果没有运行,需要按复位 运行)

电机驱动

由TB6612介绍得,通过控制AO和AO2高低电平可以控制AIN1和AIN2输出。

GPIO 高低电平控制AIN和BIN

  1. 查阅原理图AIN1、AIN2、BIN1、BIN2依次接在单片机的PB13、PB12、PB1、PB0
image-20220114121958125
  1. 原理同GPIO输出高低电平见第二节

    TB6612 GPIO驱动函数代码

    //驱动6612 的AIN1 AIN2 BIN1 BIN2
    // AIN1 PB13
    // AIN2 PB12
    // BIN1 PB1
    // BIN2 PB0
    void TB6612_GPIO_Init(void)
    {
     GPIO_InitTypeDef  GPIO_InitStructure; 	
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟	
     GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1;				 //PB0 OB1 PB12 PB13端口配置
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
     GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化
     GPIO_SetBits(GPIOB,GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1);						 //PB0 OB1 PB12 PB1 输出高
    			
    }
    

    相关宏定义

    #define AIN1 PBout(13)// PB13
    #define AIN2 PBout(12)// PB12
    #define BIN1 PBout(1)// PB1
    #define BIN2 PBout(0)// PB0
    

PWM控制PWMA和PWMB

将 PWM输出实验 的 timer 文件移植到我们前面点灯的工程中,更改驱动文件

  1. 查看原理图 PWMA 和PWMB依次连接PA11和PA8

  2. 查看 参考手册 关于定时器复用功能重映射的介绍(中文参考手册第119页)

    image-20220114130807260
  3. 初始化外设

    • 配置对应引脚功能

    • 初始化TIM1

    • 初始化TIM1 相应通道的 PWM模式

    • 使能

    • 注意输出使能 高级定时器必须使用:TIM_CtrlPWMOutputs(TIM_TypeDef TIMx, FunctionalState NewState);*

    //TIM1 PWM部分初始化 
    //PWM输出初始化
    //arr:自动重装值
    //psc:时钟预分频数
    void TIM1_PWM_Init(u16 arr,u16 psc)
    {  
    	GPIO_InitTypeDef GPIO_InitStructure;
    	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    	TIM_OCInitTypeDef  TIM_OCInitStructure;
    	
      //使能对应定时器
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);	//使能定时器1时钟
     	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
    	
    	//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM1, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
    		//配置对应引脚功能
       //设置该引脚为复用输出功能,输出TIM1 CH1 和CH4
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 TIM_CH4 
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
    	
       //初始化TIM1
    	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
    	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    	
    	//初始化TIM1 Channel1 PWM模式	 
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
     	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
    	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2
    	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
     
     //初始化TIM1 Channel4 PWM模式	 
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
     	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
    	TIM_OC4Init(TIM1, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2
    	TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
     
    	TIM_Cmd(TIM1, ENABLE);  //使能TIM1
    	
    	TIM_CtrlPWMOutputs(TIM1,ENABLE);        //MOE 主输出使能,高级定时器必须开启这个
    	
    }
    
    

    调用初始化函数、改变占空比。

        TIM1_PWM_Init(1999,359); 	  
    	//TIM1挂在APB2为72M ,故计算 72 000 000 /(359+1)/(1999+1) = 100 Hz,
    	 //故设置了频率为100 Hz、自动重装载值 1999
    	 
    	 TIM_SetCompare1(TIM1,100);	 //设置 TIM1 通道1 捕获/比较寄存器值 为 1000 可以计算出占空比
    	 //PA8  PWMB
    	 TIM_SetCompare4(TIM1,1900);	 //设置 
    	  //PA11 PWMA
    

    通过软件仿真

    逻辑分析仪观察波形输出、显示PWM波形

    设置好仿真环境

    image-20220131214214231

    打开逻辑分析仪

    image-20220131214444301

    添加要观察的引脚

    image-20220201010501430

    跳到设置对应程序位置,打开仿真

    image-20220201004845126

    打开实时更新选项

    image-20220203232243652

    调节观察分析仪

    image-20220201005303707

    产生的如图方波就是一种PWM波

    image-20220201115143730 image-20220201115844664

    那么在程序哪里设置的这些参数那

    时钟预分频数 决定了PWM 频率和周期

        TIM1_PWM_Init(1999,359); 	  
    	 //TIM1挂在APB2为72M ,故计算 72 000 000 /(359+1)/(1999+1) = 100 Hz,
    

    那么谁调节占空比那?

    image-20220201122604014
    1. 非常好理解、定时器的计数器向上计数就是越来越大。

    2. PWM 模式我们可以看手册

      image-20220201123616734

      3.这里的TIM_OCPolarity_High 就是把有效电平设置为高

      举个栗子:如果我们设置上面的示例参数,工作过程应该是怎么的呐?

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EVjjm9v6-1646313963967)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220201141825559.png)]

    电机控制通过AIN1、AIN2、BIN1、BIN2控制电机正反转,通过PWMA、PWMB控制电机转速。

    	AIN1 = 1;
    	AIN2 = 0;
    	BIN1 = 1;
    	BIN2 = 0;
    	TIM_SetCompare4(TIM1,1500);	 //设置 A
    	TIM_SetCompare1(TIM1,1500);	//设置B	
    

让小车跑一跑吧

小车电机线正确接法

image-20220131014001001

错误接法

image-20220131014212160 image-20220130033350828

小车直行

void Forward(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 1;
	BIN2 = 0;
	TIM_SetCompare4(TIM1,1500);	 //设置 A
	TIM_SetCompare1(TIM1,1500);	//设置B	
}

小车后退

void Backward(void)
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 0;
	BIN2 = 1;
	TIM_SetCompare4(TIM1,1500);	 //设置 A
	TIM_SetCompare1(TIM1,1500);	//设置B	
}

小车左转

void Leftward(void)
{
	
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 1;
	BIN2 = 0;
	TIM_SetCompare4(TIM1,1500);	 //设置 A
	TIM_SetCompare1(TIM1,1500);	//设置B	
	
}

小车右转

void Rightward(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 1;
	TIM_SetCompare4(TIM1,1500);	 //设置 A
	TIM_SetCompare1(TIM1,1500);	//设置B

}

舵机控制

查看原理图

image-20220201143839098

芯片手册

image-20220201144021615 image-20220201144350267

使用上节移植的定时器三例程

image-20220201144553613

不需要开启部分重映射,

//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5   

初始化函数为

//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
  //使能对应时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
	
   //设置该引脚为复用输出功能,输出TIM3 CH1的PWM脉冲波形	GPIOA.6
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM1
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel1 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC1

	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
	

}

让舵机摇摇头

调用初始化函数和改变占空比

		TIM3_PWM_Init(999,1439); //时钟源为72MHZ 故72 000 000 /(1439+1)/(999+1)=50HZ
					TIM_SetCompare1(TIM3,32);	 //舵机向右
					delay_ms(900);
					TIM_SetCompare1(TIM3,80);	 //舵机向前
					delay_ms(900);
					TIM_SetCompare1(TIM3,130); //舵机向左 
					delay_ms(900);

然后

按键与红外对管

按键外部中断实验

让我们先实现按键控制灯的亮灭

查看原理图

这里发现翻车,呜呜呜

image-20220114193719838

由于C8T6小板子的PA12接了上拉电阻,所以使用PA12的时候要注意。而且如果我们用Mrico USB供电可能会影响PA11。

现在我们的原理图是这样的 KEY1-PA7 KEY2-PA12

image-20220118210952587

配置按键端口模式

通过原理图知:KEY1(PA7)应该配置成下拉输入、上升沿触发。

KEY2(PA12)应该配置成上拉输入、下降沿触发。

//按键初始化函数
void KEY_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA时钟

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA7设置成输入,默认下拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.7

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.12
	
}

配置中断线和配置外部通道

//KEY外部中断服务程序
void KEY_EXTIX_Init(void)
{
 
   	EXTI_InitTypeDef EXTI_InitStructure;
 	  NVIC_InitTypeDef NVIC_InitStructure;

    KEY_Init();	 //	按键端口初始化

  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//使能复用功能时钟

   //GPIOA.7	  中断线以及中断初始化配置 上升沿触发 PA7  KEY_1
 	  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7); //选择GPIO引脚作为中断线
  	EXTI_InitStructure.EXTI_Line=EXTI_Line7;               //线路选择 
		EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	   //事件选择 
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //触发模式 上升沿触发
  	EXTI_Init(&EXTI_InitStructure);		//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器

   //GPIOA.5  中断线以及中断初始化配置 下降沿触发PA12 KEY_2
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource12);
  	EXTI_InitStructure.EXTI_Line=EXTI_Line12;
  	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  //下降沿触发
  	EXTI_Init(&EXTI_InitStructure);	  	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器


  	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;			//使能按键KEY1所在的外部中断通道
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2 
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;					//子优先级1 
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
  	NVIC_Init(&NVIC_InitStructure);  	  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

  	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;			//使能按键KEY0所在的外部中断通道
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2 
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;					//子优先级0 
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
  	NVIC_Init(&NVIC_InitStructure);  	  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

相关宏定义 读取按键状态

#define KEY_1  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)//读取按键KEY_1
#define KEY_2   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)//读取按键KEY_2 

编写响应中断函数


void EXTI9_5_IRQHandler(void)//按键KEY_1 和KEY_2的中断服务函数
{
			delay_ms(10);//消抖
			if(KEY_1 == 1) //判断按键KEY_1 是否被按下
			{
				LED =! LED;
				EXTI_ClearITPendingBit(EXTI_Line7);  //清除LINE7上的中断标志位  
			}
		
}
void EXTI15_10_IRQHandler(void)//按键KEY_SW1 和KEY_SW2的中断服务函数
{
			delay_ms(10);//消抖
			if(KEY_2  == 0)  //判断按键KEY_2 是否被按下
			{
				LED =! LED;
				EXTI_ClearITPendingBit(EXTI_Line12);  //清除LINE7上的中断标志位 
			}
}

调用初始化函数

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
//如果没有设置中断优先级分组要先设置
KEY_EXTIX_Init();         	//初始化外部中断输入 

烧录调试

观察现象

红外对管硬件使用方法

详见:系统硬件设计->红外循迹模块

可以把红外对管看成’按键’,当前面有黑色时候为高电平,前面白色低电平。

红外对管的驱动

红外对管这里使用查询的方式,通过GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)函数获得对应端口的电平

查看原理图 红外对管依次连接 PB5 、PB4 、PB3 、PA15

image-20220118213549666

红外管GPIO初始化

注意:这里我们需要使用的PB3、PB4、PA15是单片机的’特殊引脚

我们打开数据手册:STM32F103x8B_DS_CH_V10,在引脚定义章节,说明了复位后的主功能和默认复用功能以及重定义功能。

image-20220118221626299 image-20220118221700151

在参考手册:STM32中文参考手册_V10, 在8.3.5 JTAG/SWD复用功能重映射中,说明了引脚使用

image-20220118221751500

所以我们需要关闭JTAG-DP 启用SW-DP ,我们重映射配置应写为GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

//红外循迹TCRT5000初始化函数
void TCRT5000_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);//使能PORTA,PORTB时钟
	 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	//重映射配置关闭JTAG-DP 启用SW-DP从而可以使用PA15 PB3 PB4
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA15  设置成下拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.15
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5|GPIO_Pin_4|GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //  设置成下拉输入
 	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB 5 4 3
}

一些宏定义,利用函数读取电平

#define HW_1  GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)//读取 PB5 电平
#define HW_2   GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_4)//读取 PB4
#define HW_3   GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_3)//读取 PB3
#define HW_4   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)//读取 PA15

调用初始化函数

TCRT5000_Init();

红外对管控制小灯

    while(1){

				if(HW_1 == 1 && HW_2==0 && HW_3 == 1 && HW_4 == 0 )//当第一个和第三个前面是黑色时候板子小灯亮,其他情况板子小灯灭
					{					
					 LED =0;
				  }
			else{			
					LED =1;
				   }
		}

练一练–红外对管循迹


				while(1)
		{
					if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
			{
				Forward();
				delay_ms(50);
			}
			if(HW_1 == 0 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
			{
				Rightward();
				delay_ms(150);
			}
						if(HW_1 == 1 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
			{
				Rightward();
				delay_ms(250);
			}
			if(HW_1 == 1 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
			{
				Rightward();
				delay_ms(300);
			}
			if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 0)
			{
				Leftward();
				delay_ms(150);
			}
			if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 1)
			{
				Leftward();
				delay_ms(250);
			}
			if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 1)
			{
				Leftward();
				delay_ms(300);
			}
			
			
			
		}

串口接收发送

STM32串口初始化

这里先初始化使用串口1

//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

在main中定义标志位

int g_USART1_FLAG1 = 0; //串口控制标志位

在usart.h中声明变量

extern int g_USART1_FLAG1 ;

在中断服务函数添加处理


void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
			if(Res == 'A') g_USART1_FLAG1 = 1 ; //根据接受的数据 置为标志位
			if(Res == 'B')g_USART1_FLAG1 = 2 ;
			
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 

调用初始化函数

uart_init(115200); //串口初始化为115200

在main.c 的逻辑

		while(1)
		{			
						//串口			
						if(g_USART1_FLAG1 == 1){							
									LED =! LED; 
						}
						if(g_USART3_FLAG1 == 2) {
									LED =! LED;			
						}			
			}

测试单片机串口

TTL与单片机连接

image-20220119165439364

TTL插入电脑,使用串口助手->选择端口->更改波特率115200->发送数据

image-20220119133723709

现象 发送A 或B 可以使小灯反转、发送其他命令无现象。

配置蓝牙

更改蓝牙波特率

见硬件蓝牙介绍

我们在AT模式下设置发送AT指令:AT+UART=115200,0,0

image-20220119164735086

测试蓝牙

断电重启蓝牙,更改软件波特率为115200,打开手机蓝牙与HC-05配对 (密码:1234)

使用蓝牙调试器(应用商店下载即可),发送aa 观察电脑串口软件

image-20220119170703045

手机APP-蓝牙调试器的设置方法

调试成功 :蓝牙软件和串口软件能够通讯

image-20220119172138269

练一练–蓝牙控制小灯

连接如图

image-20220119172906367

通过发送A或者B 控制单片机小灯反转

那么上面我们就完成了蓝牙的基本控制

然后我们就可以蓝牙反转灯的时候控制小车前行停止

							//串口			
							if(g_USART1_FLAG1 == 1){
								
									g_USART1_FLAG1 = 0;
									//左电机慢速正转		
									AIN1=0;
									AIN2=1;
									TIM_SetCompare4(TIM1,1700);	 //设置
									//右边电机慢速执行
									BIN1 =1;
									BIN2 =0;
									TIM_SetCompare1(TIM1,1700);	
									LED =! LED; 
							}
							if(g_USART1_FLAG1 == 2) {
									g_USART1_FLAG1 = 0;			
									//双电机停止
									BIN1 = 0;
									BIN2 = 0;
									AIN1 = 0;
									AIN2 =0;
									LED =! LED;			
							}
									

上面是通过串口一(PA9 PA10)

蓝牙硬件是串口三(PB10 PB11)下面我们通过串口三实现

初始化使用串口3

//初始化串口3
void uart_init_3(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);	//使能USART3
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);		//,GPIOB时钟
	//USART3_TX   GPIOB.10
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.10
   
  //USART3_RX	  GPIOB.11初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.11 

  //Usart3 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

   //USART 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART3, &USART_InitStructure); //初始化串口3
  USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART3, ENABLE);                    //使能串口3 

}

在main中定义标志位

int g_USART3_FLAG1 = 0; //串口3控制标志位

在usart.h中声明变量

extern int g_USART3_FLAG1 ;

在中断服务函数添加处理

//串口3 中断处理函数
void USART3_IRQHandler (void)
{
	u8 Res;	
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  
	{
			Res =USART_ReceiveData(USART3);	//读取接收到的数据
		  if(Res == 'A') g_USART3_FLAG1 = 1 ; //根据接受的数据 置为标志位
			if(Res == 'B')g_USART3_FLAG1 = 2 ;
	}
}

调用初始化函数

uart_init_3(115200); //初始化串口3

在main.c 编写逻辑

									
			while(1)
			{			
							//串口			
							if(g_USART3_FLAG1 == 1){							
										g_USART3_FLAG1 = 0;
										//左电机慢速正转		
										AIN1=0;
										AIN2=1;
										TIM_SetCompare4(TIM1,1700);	 //设置
										//右边电机慢速执行
										BIN1 =1;
										BIN2 =0;
										TIM_SetCompare1(TIM1,1700);	
										LED =! LED; 
							}
							if(g_USART3_FLAG1 == 2) {
								
										g_USART3_FLAG1 = 0;			
										//双电机停止
										BIN1 = 0;
										BIN2 = 0;
										AIN1 = 0;
										AIN2 =0;
										LED =! LED;			
							}			
				}
						

把蓝牙安装顺序连接到STM32

跳线帽改至蓝牙

image-20220121191953524

手机连接蓝牙 使用蓝牙调试器发送 A 或者 B

现象:发送A 小车直行、发送B小车停止。

练一练–蓝牙控制小车运动

USART中断服务函数

//串口3 中断处理函数
void USART3_IRQHandler (void)
{
	u8 Res;	
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  
	{
			Res =USART_ReceiveData(USART3);	//读取接收到的数据
		  if(Res == 'A') g_USART3_FLAG1 = 1 ; //根据接受的数据 置为标志位
			if(Res == 'B')g_USART3_FLAG1 = 2 ;
		  if(Res == 'C') g_USART3_FLAG1 = 3 ; //根据接受的数据 置为标志位
			if(Res == 'D')g_USART3_FLAG1 = 4 ;
		  if(Res == 'E')g_USART3_FLAG1 = 5; 
	}
		
}

main 中的逻辑

		while(1)
		{
				if(g_USART3_FLAG1 == 1)  //前进
				{
					g_USART3_FLAG1=0;
					Forward();
					delay_ms(500);
				}
				if(g_USART3_FLAG1 == 2)  //向右
				{
					g_USART3_FLAG1=0;
					Rightward();
					delay_ms(500);
				}
				if(g_USART3_FLAG1 ==3)  //向左
				{
					g_USART3_FLAG1=0;
					Leftward();
					delay_ms(500);
				}
				if(g_USART3_FLAG1 ==4)  //向后
				{
					g_USART3_FLAG1=0;
					Backward();
					delay_ms(500);
				}
				if(g_USART3_FLAG1 ==5)  //停止
				{
					g_USART3_FLAG1=0;
					AIN1=0;
					AIN2=0;
					BIN1=0;
					BIN2=0;
					delay_ms(500);
				}
		}	

手机中蓝牙调试助手的设计

image-20220201224631835

练一练–把数据发送给电脑串口助手和手机APP

前面我们介绍了,如何通过电脑或者蓝牙APP,向单片机发送数据,下面我们介绍如何:单片机如何向电脑和蓝牙APP发送数据。

库函数提供了相关串口函数,但是每次只能发送一个字节

USART_SendData(USART1,'X');//通过库函数发送字节数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//判断发送标志位,是否发送结束

在正点原子例程中完成了对printf的重映射,所以我们可以轻松的通过printf ()函数向串口1 发送不定长数据,这是正点原子的例程

struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕  通过SR寄存器判断是否发送完成 
    USART1->DR = (u8) ch;      //通过DR寄存器发送数据
	return ch;
}

那么我们如何实现任意串口都可以任性发送那?

这里我们使用vsprintf 格式化字符串来完成

需要包含的头文件

#include "stdarg.h"
void UsartPrintf(USART_TypeDef * USARTx,char * fmt ,...)
{
	unsigned char UsartPrintfBuf[256]; //定义一个字符串数组
	va_list ap;//初始化指向参数列表的指针
	unsigned char *pStr = UsartPrintfBuf; //指针指向数组首地址
	
	va_start(ap,fmt);//将第一个可变参数的地址付给ap,即ap 指向可变参数列表的开始
	vsprintf((char *)UsartPrintfBuf, fmt,ap);
	//将参数fmt、ap 指向的可变参数一起转化成格式化字符串,放string数组中,作用同sprintf(),只是参数类型不同 

	va_end(ap); //清除指针
	while(*pStr != 0) //判断是否发送完字符串
	{
		//while(USART_GetFlagStatus(USART3,USART_FLAG_TC == RESET));//判断发送标志位,是否发送结束
		USART_SendData(USARTx,*pStr++);//通过库函数发送字符串
		//pStr ++;
		while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);//判断发送标志位,是否发送结束
	}
}

参考资料:

在main 中调用函数

UsartPrintf(USART3,"Distance:%dMode:%d",TCRT5000_Dist(),Mode);

在手机APP显示数据

image-20220204164650629

OLED显示

找资料

image-20220121202528005

链接:

0.96寸(4管脚)资料下载链接:
https://pan.baidu.com/s/1J57Izsv-PKmbwVrA2ynDzg                       提取码:vktz

测试例程-现象正常-更改引脚-现象正常-移植到自己的工程

拷贝移植文件

一般移植传感器的xxx.c 和xxx.h

复制相关文件到工程

image-20220121203522428

KEIL中添加文件

image-20220121203826773

添加头文件路径

image-20220121204122098

对比程序移植

image-20220121204419643

根据原理图更改初始化函数

SCL–PC14

SDA–PC15

image-20220123142944473

修改OLED_Init() 函数


//初始化SSD1306					    
void OLED_Init(void)
{ 	 
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	 //使能A端口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_14;	 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOC, &GPIO_InitStructure);	  //初始化GPIOC14,15
 	GPIO_SetBits(GPIOC,GPIO_Pin_15|GPIO_Pin_14);	
	
  delay_ms(800);
  OLED_WR_Byte(0xAE,OLED_CMD);//--display off
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  
	OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
	OLED_WR_Byte(0x81,OLED_CMD); // contract control
	OLED_WR_Byte(0xFF,OLED_CMD);//--128   
	OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap 
	OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
	OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
	OLED_WR_Byte(0x00,OLED_CMD);//
	
	OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
	OLED_WR_Byte(0x80,OLED_CMD);//
	
	OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
	OLED_WR_Byte(0x05,OLED_CMD);//
	
	OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
	OLED_WR_Byte(0xF1,OLED_CMD);//
	
	OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
	OLED_WR_Byte(0x12,OLED_CMD);//
	
	OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
	OLED_WR_Byte(0x30,OLED_CMD);//
	
	OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
	OLED_WR_Byte(0x14,OLED_CMD);//
	
	OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}  

修改oled.h中的宏

//-----------------OLED IIC端口定义----------------  					   

#define OLED_SCLK_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_14)//SCL
#define OLED_SCLK_Set() GPIO_SetBits(GPIOC,GPIO_Pin_14)

#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_15)//SDA
#define OLED_SDIN_Set() GPIO_SetBits(GPIOC,GPIO_Pin_15)

 		     
#define OLED_CMD  0	//写命令

#define OLED_DATA 1	//写数据

调用初始化函数

	 	OLED_Init();			//初始化OLED  
		OLED_Clear(); 

在main使用显示函数

显示字符串

		OLED_ShowString(6,3,"ABCDSDKJF",16);//显示一个字符号串
		OLED_ShowString(0,6,"GFGFGF:",16);  
	  	OLED_ShowString(63,6,"FGFGFG:",16); 

显示数据的一种方法

		   u8 string[10] = {0};	//定义在前面
...
		  sprintf((char *)string,"Pitch:%.2f",pitch);
		  OLED_ShowString(6,3,string,16);

ADC测量电池电压

ADC是嘛

百度百科介绍:

image-20220206175309594

我们知道万用表 电压表可以测量电池,或者电路电压。那么我们是否可以通过单片机获得电压,方便我们监控电池状态

image-20220206180847034

如何测量我们的锂电池电压那?锂电池电压12V左右,单片机ADC最大测量电压3.3V,这里我们需要分压电路分压。

image-20220206194906974

通过测量ADC点的电压就可以计算VBAT_IN的电压。

移植程序

拷贝文件

image-20220206173803688 image-20220206174015635 image-20220206174134150

在adc.c的程序

 #include "adc.h"
 #include "delay.h"
   
//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3																	   
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟
 

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}				  
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)   
{
  	//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  			    
  
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 


初始化函数

Adc_Init(); //ADC初始化

在main中测量并显示

		while(1)
		{
			adcx=Get_Adc_Average(ADC_Channel_4,10);
			//LCD_ShowxNum(156,130,adcx,4,16,0);//显示ADC的值
			temp=(float)adcx*(3.3/4096);
			adcx=temp;
			sprintf((char *)string,"temp:%.2f",temp);
		  OLED_ShowString(6,3,string,16);	
		}

超声波测距

通过超声波的硬件介绍我们知道

MCU给Trig脚一个大于10us的高电平脉冲;然后读取Echo脚的高电平信号时间,通过公式:距离 = T*声速/2 就可以算出来距离。

软件方面:10us高电平脉冲通过GPIO输出实现,高电平信号时间我们通过定时器的输入捕获来计算的。

初始化脉冲引脚PA0

在led.c中的SR04初始化函数

void SR04_GPIO_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;	    		
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
 GPIO_Init(GPIOA, &GPIO_InitStructure);	  				
 GPIO_SetBits(GPIOA,GPIO_Pin_0); 						 
}

led.h有关宏定义和声明

#define SR04 PAout(0)    // PA0
void SR04_GPIO_Init(void);

初始化PA1输入捕获

查看数据手册

image-20220123172235233

初始化定时器2 通道2 输入捕获相关功能

//定时器2通道2输入捕获配置

TIM_ICInitTypeDef  TIM2_ICInitStructure;

void TIM2_Cap_Init(u16 arr,u16 psc)
{	 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
   	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	//使能TIM2时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_1;  //PA1 清除之前设置  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA1 输入  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOA,GPIO_Pin_1);						 //PA1 下拉
	
	//初始化定时器5 TIM5	 
	TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; 	//预分频器   
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  
	//初始化TIM5输入捕获参数
	TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2; //	选择输入端 IC2映射到TI2上
  	TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//上升沿捕获
  	TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI2上
  	TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 //配置输入分频,不分频 
  	TIM2_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
  	TIM_ICInit(TIM2, &TIM2_ICInitStructure);
	
	//中断分组初始化
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	
	TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC2IE捕获中断	
	
   	TIM_Cmd(TIM2,ENABLE ); 	//使能定时器2
   


}
u8  TIM5CH1_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值
 
//定时器2中断服务程序	 
void TIM2_IRQHandler(void)
{ 

 	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{	  
		if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
		 
		{	    
			if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0XFFFF;
				}else TIM5CH1_CAPTURE_STA++;
			}	 
		}
	if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//捕获2发生捕获事件
		{	
			if(TIM5CH1_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{	  			
				TIM5CH1_CAPTURE_STA|=0X80;		//标记成功捕获到一次上升沿
				TIM5CH1_CAPTURE_VAL=TIM_GetCapture2(TIM2);
		   		TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising); //CC2P=0 设置为上升沿捕获
			}else  								//还未开始,第一次捕获上升沿
			{
				TIM5CH1_CAPTURE_STA=0;			//清空
				TIM5CH1_CAPTURE_VAL=0;
	 			TIM_SetCounter(TIM2,0);
				TIM5CH1_CAPTURE_STA|=0X40;		//标记捕获到了上升沿
		   		TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling);		//CC2P=1 设置为下降沿捕获
			}		    
		}			     	    					   
 	}
 
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
 
}


在time 中声明初始化函数

void TIM2_Cap_Init(u16 arr,u16 psc);

计算输出距离

在main.c声明变量

extern u8  TIM5CH1_CAPTURE_STA;		//输入捕获状态		    				
extern u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值

定义变量

int Distance =0;
int time=0;

调用初始化函数

SR04_GPIO_Init();
TIM2_Cap_Init(0XFFFF,72-1);	//以1Mhz的频率计数 

完成测距的函数

			delay_ms(500);//加入延时
			HC_SR04 =0;
			delay_us(10);
			HC_SR04 = 1;	
 		if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
		{
			time=TIM5CH1_CAPTURE_STA&0X3F;
			time*=65536;//溢出时间总和
			time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
			printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间
			Distance = time*0.033/2;
		  printf("cm:%d\r\n",Distance);
			TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
					
		}

封装一下方便调用

int TCRT5000_Dist(void)
{
	HC_SR04 = 1;
	delay_us(13);
	HC_SR04=0;
	 		if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
		{
			time=TIM5CH1_CAPTURE_STA&0X3F;
			time*=65536;//溢出时间总和
			time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
			printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间
			Distance = time*0.033/2;
		    printf("cm:%d\r\n",Distance);
			TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
					
		}
	return Distance;
	
}

使用串口助手查看结果

image-20220123173231127

练一练–编写定距离跟随功能

功能:根据根据超声波测量距离跟随前方物体

				while(1){

									HC_SR04 = 1;
									delay_us(13);
									HC_SR04=0;
									if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
								{
									time=TIM5CH1_CAPTURE_STA&0X3F;
									time*=65536;//溢出时间总和
									time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
									printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间
									Distance = time*0.033/2;
									printf("cm:%d\r\n",Distance);
									TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
											
								}
								if(Distance>20)
								{
									Forward();
									delay_ms(50);
								}
									if(Distance<15)
								{
									Backward();
									delay_ms(50);
								}

							AIN1 =0;
							AIN2 = 0;
							BIN1 = 0;
							BIN2 =0;

					
				}

练一练–结合舵机完成避障功能

功能:通过舵机旋转不同角度,超声波测量左右是否存在障碍物,控制小车运动。

测试舵机的转角,不同占空比小车舵机的角度

TIM_SetCompare1(TIM3,80);	
TIM_SetCompare1(TIM3,50); 
TIM_SetCompare1(TIM3,110); 
image-20220207013334311

整体逻辑

				if(Mode == 3)
				{				
						//超声波避障
					TIM_SetCompare1(TIM3,80);	 //舵机向前 使超声波朝前方
					delay_ms(200);
					if(TCRT5000_Dist()>25)// 前方无障碍物
					{
							Forward();
							delay_ms(500);
					}
					
					if(TCRT5000_Dist()<25)  //向前有障碍物
					{
								TIM_SetCompare1(TIM3,50);	 //舵机向右边转大约30度
								delay_ms(200);
								if(TCRT5000_Dist()>25)//右侧无障碍物判断
								{
										Rightward();
										delay_ms(700);
								}
								else {   //右边有障碍物
										TIM_SetCompare1(TIM3,100); //舵机向左边转大约30度
										delay_ms(200);
										if(TCRT5000_Dist()>25)//左侧无障碍物
										{
													Leftward();
													delay_ms(700);	
										}
										else{
													Backward();//后退
													delay_ms(700);
													Rightward(); //右转
													delay_ms(700);
										}											
							 }
					}					
				 }                                    }

综合一下-缝合上面练一练的功能

功能:

  • 小车具有红外对管循迹、蓝牙遥控、定距离跟随、避障运动模式
  • 可以通过小车按键和APP进行切换小车的运动模式。
  • APP与OLED显示小车所处模式和超声波测量值、电池电压。

实现切换功能必须

main中的循环

				while(1)
			{	
        sprintf((char *)string,"Distance:%d    ",TCRT5000_Dist());// 显示距离信息 这里的 %d 需要几个空格
		    OLED_ShowString(6,3,string,16);		
				sprintf((char *)string,"Mode:%d",Mode);//显示小车模式
				OLED_ShowString(6,6,string,16);		
				if(Mode == 1)
				{
					//定距离跟随
					TIM_SetCompare1(TIM3,80);	 //超声波舵机向前
					if(TCRT5000_Dist()>25)// 距离太远
					{
							Forward();
							delay_ms(200);
					}
					if(TCRT5000_Dist() <20)//距离太近
					{
							Backward();
							delay_ms(200);	
					}
					AIN1 =0;//车辆暂定 如果不加 小车就会一直往前或者一直往后
					AIN2 =0;
					BIN1 =0;
					BIN2 =0;				
				}
				
	      if(Mode == 2)
		    {
				
				  //蓝牙控制小车
					TIM_SetCompare1(TIM3,80);	 //超声波舵机向前
					if(g_USART3_FLAG1 == 1)  //前进
					{
						g_USART3_FLAG1=0;
						Forward();
						delay_ms(500);
					}
					if(g_USART3_FLAG1 == 2)  //向右
					{
						g_USART3_FLAG1=0;
						Rightward();
						delay_ms(500);
					}
					if(g_USART3_FLAG1 ==3)  //向左
					{
						g_USART3_FLAG1=0;
						Leftward();
						delay_ms(500);
					}
					if(g_USART3_FLAG1 ==4)  //向后
					{
						g_USART3_FLAG1=0;
						Backward();
						delay_ms(500);
					}
					if(g_USART3_FLAG1 ==5)  //停止
					{
						g_USART3_FLAG1=0;
						AIN1=0;
						AIN2=0;
						BIN1=0;
						BIN2=0;
						delay_ms(500);
					}
		    }			
				if(Mode == 3)
				{				
						//超声波避障
					TIM_SetCompare1(TIM3,80);	 //舵机向前
					delay_ms(200);
					if(TCRT5000_Dist()>25)// 前方无障碍物
					{
							Forward();
							delay_ms(500);
					}
					
					if(TCRT5000_Dist()<25)  //向前有障碍物
					{
								TIM_SetCompare1(TIM3,50);	 //舵机右转
								delay_ms(200);
								if(TCRT5000_Dist()>25)//右侧无障碍物判断
								{
										Rightward();
										delay_ms(700);
								}
								else {
										TIM_SetCompare1(TIM3,100); //舵机向左 
										delay_ms(200);
										if(TCRT5000_Dist()>25)//左侧无障碍物
										{
													Leftward();
													delay_ms(700);	
										}
										else{
													Backward();
													delay_ms(700);//后退
													Rightward();
													delay_ms(700);
										}											
							 }
					}					
				 }
	       if(Mode == 4)
				 {
						 //红外循迹
					 TIM_SetCompare1(TIM3,80);	 //超声波舵机向前
							if(HW_1 == 0&&HW_2 == 0&&HW_3 == 0&&HW_4 == 0)//应该前进		
						{
							Forward();
							delay_ms(20);
						}
						if(HW_1 == 0&&HW_2 == 1&&HW_3 == 0&&HW_4 == 0)//应该右边
						{
							Rightward();
							delay_ms(150);			
						}
						if(HW_1 == 1&&HW_2 == 0&&HW_3 == 0&&HW_4 == 0)//应该右边
						{
							Rightward();
							delay_ms(270);
						}
						if(HW_1 == 1&&HW_2 == 1&&HW_3 == 0&&HW_4 == 0)//应该右边
						{
							Rightward();
							delay_ms(370);
						}
						if(HW_1 == 0&&HW_2 == 0&&HW_3 == 1&&HW_4 == 0)//应该左边
						{
							Leftward();
							delay_ms(150);
						}
						if(HW_1 == 0&&HW_2 == 0&&HW_3 == 0&&HW_4 == 1)//应该左边
						{	
							Leftward();
							delay_ms(270);	
						}
						if(HW_1 == 0&&HW_2 == 0&&HW_3 == 1&&HW_4 == 1)//应该左边
						{
							Leftward();
							delay_ms(370);	
						}

				 }	
					if(Mode ==0)
					{
						delay_ms(1);
						AIN1=0;
						AIN2=0;
						BIN1=0;
						BIN2=0;
					}
			}
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 13:17:11  更:2022-03-06 13:18:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:35:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码