【智能小车扩展板】资料
智能小车资料下载链接
- 百度网盘链接: https://pan.baidu.com/s/1jQtQC8FXaXFwqxJ9SEr0Fg?pwd=tu25 提取码: tu25
- 开源工程:https://oshwhub.com/li-chuang-kai-fa-ban/zhi-nen-xiao-che-kuo-zhan-ban
- 视频教程:智能小车扩展板
1、项目介绍
相信大家学习完立创·梁山派核心板配套基础教程后,已经掌握了GD32的基础开发技能,大家是不是早也已经想“大显身手”自己做一个有趣的项目来验证自己的学习成果呢?所以基于大家的这种迫切学习项目的想法,我们设计了一款基于立创·梁山派的智能小车,供大家学习参考。
1.1 小车功能介绍
- 梁山派智能小车前方搭载了两个LED照明灯,左右各一个,可用于模拟行车过车中的车灯状态;
- 梁山派智能小车有2个独立按键,分别是KEYS与KEYM,可用于启动和运动模式切换;
- 梁山派智能小车搭载了一个蜂鸣器,可用于遇到障碍物时发出警报,也可使用定时器改变其输出频率让其播放音乐;
- 梁山派智能小车配置了四个电机驱动和四个N20电机,可实现PWM输出与调速功能;
- 梁山派智能小车搭载了五路红外循迹,可用于循黑线行驶,学习比较器电路的使用,实现循迹功能;
- 梁山派智能小车配备了HCSR04超声波模块接口电路,可通过学习模块原理以及底层驱动代码,实现超声波避障功能;
- 梁山派智能小车利用开发板上的ADC功能检测智能小车电量,方便我们及时充电;
- 梁山派智能小车提供了HC-05蓝牙模块接口电路,可配合手机蓝牙APP实现无线遥控小车的功能;
- 梁山派智能小车提供了一个摄像头模块接口电路,可用于学习摄像头识别相关知识;
- 梁山派智能小车提供了一个2.4G无线模块接口电路,可配合遥控器实现智能小车的控制;
1.2 小车应用场景
- 该教程适用于电子爱好者以及新手小白入门基础项目的学习;
- 该教程适用于嵌入式课程教学,以智能小车结合立创·梁山派开发板贯穿嵌入式课程教学;
- 该教程适用于高校课程设计以及毕设参考案例,让学生结合立创·梁山派核心板根据课程设计要求完成特定功能的智能小车项目;
- 该教程适用于学生以及相关人员入门学习相关的智能车比赛;
2、总体设计方案
梁山派智能小车总体设计方案系统框架图如图2-1所示,供电电路采用两节锂电池7.4V给本系统供电,通过降压芯片降到5V给单片机系统供电,立创·梁山派核心板与智能小车扩展板上的LED车灯、按键电路、避障电路、循迹电路、ADC电压采集电路、蓝牙遥控电路(无线遥控功能)、蜂鸣器、以及电机驱动电路进行连接。
3、电路设计
3.1 电源输入电路
3.1.1 电源设计需求
我们对智能小车系统的供电电路进行分析,通过查阅芯片手册以及立创·梁山派核心板的原理图我们得知,电机驱动芯片RZ7899的最大供电电压不超过25V,立创·梁山派核心板供电是3.3V(兼容5V),超声波模块是5V供电,其余外设模块均是3.3V。根据这个需求我们就可以开始对电源进行选型了,这里我选用了一款7.4V可充电锂电池,大家可以不必跟我一样,根据需求选用即可。
3.1.2 智能小车电源原理图
3.1.3 电源电路设计分析
电源的输入接口P3是一个电池座,通过电池座接入2节3.7V的锂电池后给小车供电。电源进来后经过一个HE6250MPR降压芯片,该降压芯片的最大输入电压是15V,可固定输出5V电压给单片机和外围器件提供供电,电机驱动芯片则直接由7.4V电池供电。其中,D1是一个肖特基二极管,型号是SS43,作用是防止电源输入端正负极接反,起到一个保护作用。LED1是电源工作指示灯,当电源开关SW1打开时,系统开启供电,LED1会被点亮。C1和C2是10uF的电容,主要作用是上电瞬间可能电流会比较大,使用电容可以起到一个缓冲作用。
3.2 单片机最小系统电路
毋庸置疑,我们使用的是立创·梁山派核心板作为我们的最小系统板,立创·梁山派核心板采用的主控芯片是GD32F470,具备了超高的计算性能,处理器最高主频可达200MHz,并提供了完整的DSP指令集、并行计算能力和专用浮点运算单元(FPU) 来满足高级计算需求。具体关于立创·梁山派核心板的相关资料和内容这里不作过多的介绍,大家可以去立创开发板官网进行了解。
3.3 电机驱动电路
3.3.1 电机驱动电路原理图
3.3.2 电机驱动电路设计分析
首先我们都知道单片机本身的IO口电流是非常有限的,如果直接将电机接在单片机IO口,会导致无法正常驱动电机转动,所以我们必须使用电机驱动芯片来驱动我们的电机组,这里我选用RZ7899电机驱动芯片。RZ7899是一款DC双向马达驱动电路,适用于自动阀门电机驱动、电磁门锁驱动等,该电路具有良好的抗干扰性,微小的待机电流、低的输出内阻,同 时它还具有内置二极管能释放感性负载的反向冲击电流。
它通过两个逻辑输入端子BI和FI来控制电机的前进、后退及制动。我们将BI和FI与单片机IO进行连接,通过改变单片机I/O口电平从而改变芯片控制端输出引脚的电平,就可以对电机进行正反转,停止以及制动控制,控制原理非常简单,运用起来非常方便,亦能满足直流电机的大电流要求。
在进行代码调试时我们可以参照以下的引脚功能表和输入输出真值表进行调试。我们只要将BI和FI引脚连接到单片机的定时器通道引脚,就可以结合单片机的定时器PWM输出功能控制电机转速,从而改变我们智能小车行驶的速度。
芯片特点
- 微小的待机电流,小于 2uA;工作电压范围宽 3.0V~25V。
- 有紧急停止功能 ; 有过热保护功能 。
- 有过流嵌流及短路保护功能 ; 封装外形为: SOP8。
3.4 循迹电路
3.4.1 循迹电路原理图
3.4.2 循迹电路设计分析
循迹电路的设计是利用了红外光遇到不同颜色地面反射程度不同的原理。循迹电路采用LM393电压比较器与ITR9909红外对管进行设计,其中ITR9909的内部集成了红外发射管和接红外收管。循迹的工作原理是将电压比较器的1引脚连接到单片机引脚上,IO配置为输入模式,当电压比较器的1引脚输出高电平时,表示红外光被吸收,检测到黑线,LED6指示灯亮起,单片机IO读取到高电平。
红外循迹的原理:利用红外光在不同颜色的反射情况进行识别
- 检测黑线的原理是红外发射管发射光线到地面,当红外光遇到白色地面则被反射,红外接收管接收到反射光,经过电压比较器后输出低电平。
- 检测黑线的原理是红外发射管发射光线到地面,当红外光遇到黑色地面则被吸收,红外接收管未接收到反射光,经过电压比较器后输出高电平。
3.5 避障电路
3.5.1 避障电路原理图
避障电路使用的是超声波HC-SR04模块,该模块的类型可自行选取购买,以下是实物图。
下面是智能小车扩展板上的超声波模块接口。
3.5.2 避障电路设计分析
超声波测距原理是在超声波发射装置发出超声波,在发射超声波的同时开始计时,超声波在空气中传播,在传播的时刻碰到障碍物,就会返回一个信号给超声波接收器,超声波接收器接收到信号后立即停止计时,这时候会有一个时间t,而超声波在空气中传播的速度为340m/s,通过公式s=340 x t / 200,即可计算出待测距离是多少。
HC-SR04超声波测距模块工作原理
- 工作电压:DC 5V;
- 采用I/O口触发方式测距,通过单片机发出至少10us的高电平信号到超声波模块的Trig引脚,用于触发超声波模块工作;
- 模块的发射探头会自动发送8个40KHz的方波信号,自动检测是否有信号返回;
- 如果有信号返回,通过Echo引脚连接单片机的I/O口输出高电平,高电平持续时间就是超声波从发射到返回的时间;
- 根据声音在空气中的传播速度为340米/秒,即可计算出所测的距离;
3.6 其他电路
3.6.1 LED车灯与按键电路
(1)LED灯的一端连接到单片机IO口,通过单片机IO的高低电平控制LED灯,这里设计了2种驱动的方式,LED车灯的电路是低电平点亮,按键指示灯的电路是高电平点亮,其中电阻的大小可以自己结合灯所需的亮度以及欧姆定律计算。比如这里按键指示灯不需要太亮则电阻为1KΩ大一点,LED车灯需要亮一些则电阻为100Ω小一点。
(2)这里独立按键的电路设计原理是将按键一端连接到单片机IO口,另一端接地,当按键按下时,读取到低电平;按键松开时,读取到高电平。注意这里IO口配置时需要将其配置为上拉输入。当然你也可以加一个上拉电阻将IO口的电平拉高,这样就不需要配置为上拉输入了。
3.6.2 蜂鸣器与ADC电压采集电路
(1)蜂鸣器电路:这里采用的蜂鸣器是有源蜂鸣器,有源蜂鸣器内部自带震荡源,只要一通电就会发出鸣叫。这里设计的电路原理是采用了一个PNP型的三极管充当开关的作用,通过调整单片机IO口的高低电平从而达到控制蜂鸣器的效果。之所以采用三极管是因为我们都知道单片机的电流驱动能力是比较小的,直接驱动器件无法正常工作,所以采用三极管的发射极引导电流进入集电极,而不是将单片机IO口直接加到蜂鸣器上。
- 当IO口输出高电平时,发射极电压没有远大于基极电压,三极管没有导通,处于截止状态,蜂鸣器没有发出声音;
- 当IO口输出低电平时,发射极电压远大于基极电压,三极管导通,电流从发射极流入集电极,蜂鸣器发出鸣叫声。
(2)ADC电压采集电路:首先我们在使用ADC之前,需要知道它的位数即精度。通过数据手册可以查询到我们的ADC是12位的,通过计算我们知道精度为2^12-1等于4095。代表ADC可以表示的精度范围为0~4095。
- 那么如何通过ADC测量的值转换成电压值,判断当前电量呢?要想明白这个原理,我们需要知道这里12位我们计算出的4095的值是代表什么意思。假设最高电压为7.4V,那么ADC测量的值就是最大值4095,当接入电压为GND即0V时,ADC测量的值就为0了。所以我们可以通过计算算出该函数的斜率,从而就可以求出ADC采样值的函数,进而就知道了任意ADC测量值对应的电压值。
- ADC的电路原理图如下,这里通过电阻分压的形式测量ADC,因为IO口最大可以兼容5V,超过5V就会把IO口烧坏,这里上面R30和R18两个电阻采用串联的形式是为了可以减少一个物料,也可以直接采用一个20K的电阻,电阻的大小可以根据IO口电平计算,分压后不要超过IO口容忍的电压的即可,选取一个合适的值。接下来通过计算给大家实际演示如何将ADC值与电压值进行转换。
- 前提:假设ADC是12位的,电池供电最高8.4V,最低6.6V(低于就开始报警提醒充电);
-------知道电压求ADC值-------
(1)ADC端占的电压比例:10K/(10K+10K+10k)≈0.3333;
(2)电池电量最高时,ADC端分压得到的电压为:0.33338.4V≈2.7997V 电池电量最低时,ADC端分压得到的电压为:0.33336.6V≈2.1998V
如果参照3V,即满电状态下为3V,没电是0V
电池电量最高时的ADC值为:(2.7997/3V)*4095=3821
电池电量最低时的ADC值为:(2.1998/3V)*4095=3002
-------知道ADC值求电压-------
如果参照3V,即满电状态下为3V,没电是0V;
通过上式子的逆运算可以知道,任意ADC值对应采集的电压值为:V_value=(ADC*3)/4095 单位:V
4、原理图以及PCB设计
4.1 原理图设计
4.1.1 原理图绘制
(1)创建工程
- 打开 嘉立创EDA 官方网站,进入官网后找到右上角头像,找到头像底下的工作区,进入工作区创建工程,如图4-1所示
- 点击新建工程开始我们的工程创建,这里我们选择新建专业版工程,工程所属可以默认存放在个人工程下,工程名称可以命名为:【立创·梁山派】智能小车扩展板,工程描述可以适当填写或忽略,最后点击创建即可,具体步骤按照下图4-2所示
- 创建完成后,我们就可以看到以下图4-3界面,点击编辑器打开按钮即可打开我们创建的工程,至此我们的工程创建完毕
(2)放置元器件
- 创建完工程后我们就可以开始进行原理图的绘制了,我们双击打开原理图界面,如图4-4所示
- 按Shift+F快速打开元器件搜索界面,输入我们的器件名称后,进行筛选,找到目标器件后需要注意选择库存较多的进行放置,可以避免后期下单时无库存后又要重新更换型号。如果你对元器件的类型不是很熟悉,也可以直接通过复制物料中的商品编号进行搜索(每一个元器件在立创商城都有唯一的商品编号),智能小车所使用到的元器件商品编号我已经整理成一个表格放置在下面的对应内容中。
(3)智能小车物料清单
(4)整理原理图
- 原理图绘制完毕后,我们很多初学者都急着开始绘制PCB了,但是这是非常不好的,在进行PCB绘制之前我们还有一件事要完成,那就是整理我们的原理图,方便我们后续的阅读和查看。下面是整理前和整理后的对比,可以看到整理后我们的原理图变得更加整齐便于阅读,没有整理的原理图显得十分凌乱。
4.1.2原理图设计注意事项
(1)在选择器件时优先选择基础库以及库存较多的器件; (2)不同器件的发货仓库尽量保持一致; (3)不同封装的器件其大小不同,建议新手或者焊接技术较为薄弱的选择直插式封装或者0805封装,当然你也可以直接采用SMT贴片的形式; (4)器件的类型尽可能减少,比如800Ω的电阻和1000Ω的电阻在可以相互替代不影响电路的情况下,合并选择其中一种即可; (5)原理图设计完要对原理图进行整理,每一个模块都使用一个方格框起来,这样会使原理图的整体结构比较清晰,方便阅读;
4.2 PCB设计
4.2.1 PCB布局
(1)进入PCB设计
- 完成以上的原理图步骤后,就可以进行我们的下一步工作——PCB的设计。首先我们先对原理图进行DRC检查,无错误无警告后就可以进行PCB设计了。
- 顶部菜单栏找到设计,选择更新到PCB,等待加载完成就会自动跳转到PCB设计界面。
(2)小车PCB外形设计
- 我们在开始PCB布局走线前,需要根据元器件数量所占空间确定PCB的形状及边框大小,如果你没有特殊的外壳要求,一般设计成矩形、圆形以及正方形都行。在设计PCB外形时,我们遵循着大小合适,美观大方的原则。
- 我们在顶部工具菜单栏下的放置矩形板框,实际板框大小会随着布局布线中进行调整,如果太小可适当放大,太大也可相应缩小边框。尽量控制在10cm*10cm之内,这样就可以到嘉立创免费打样啦。在顶部工具栏将线条45°修改成线条自由角度,放置线条折线(快捷键:Alt + L)。
- 下面是我设计的一款小车的车型,供大家参考。
- 我们也可以修改小车底板颜色,首先点击顶部菜单栏的设置,找到PCB/封装,勾选彩色丝印工艺,最后点击应用。
- 点击右侧属性,找到彩色丝印设置,就可以选择自己喜欢的丝印以及板子颜色了。设置完成后点击3D预览,将丝印工艺改为彩色丝印就可以看到彩色丝印的3D预览图了
- 彩色丝印3D预览图如下:
(3)PCB布局
- 点击顶部折线图标开始布线,快捷键W,左侧网络图标可以看到飞线数量及名称
- 合理的PCB布局是十分重要的,不仅影响PCB的美观,还影响我们后期走线的难易程度以及工整性,以下是我的整体布局,供大家参考
这里给大家提供一些PCB布局的建议:
(1)优先放置关键元器件以及大块元器件,比如主控板,电机,电池座等。
(2)调试接口以及供电口要放置在容易插拔的位置,方便我们后期使用。
(3)对于一些容易发热的元器件,应该给其安装散热片。
(4)高频元器件引脚导线应尽量短些,这样可以减少对其他元件电路的影响。
(5)滑动变阻器尽量电位器等元件应布放在便于调试的地方。
(6)布局尽量美观,左右对称,这样会给人一种舒服的视觉冲击感,也显得你很专业。
(4)PCB走线
在PCB走线时提供以下几点参考建议:
(1)电源线设置为40mil,信号线设置为10mil宽度。
(2)走线时选择其中一层为主层,比如以顶层走线为主,当走不通的可以放置过孔切换到底层进行连接。
(3)走线过程中尽量走直线,在需要拐弯的地方用钝角或圆弧拐弯过度。
(4)绘制完成后加上泪滴,添加丝印标记该PCB板的尺寸以及一些接口的图标。
(5)最后完成DRC检查,无错误后就可以进行PCB下单了。
4.2.2 PCB设计注意事项
(1)电源及信号走线按照信号电流流向,严格按照原理图设计图进行布局设计,即使它们都连接上去了,没有报错,但也要考虑先后顺序,先经过A再到B,最后到C,不能直接从A到C到B,这点在初学的时候尤其重要。
(2)布线时要避免走直角,一般取 45 度走向或圆弧形。在高频电路中,拐弯时不能取直角或锐角,以防止高频信号在导线拐弯时发生信号反射现象。
(3)电源线的线宽要比信号线宽,GND一般采用铺铜的方式。
(4)通常情况下,导线的宽度一般不低于 10mil单位,一般电源线设置为25mil以上宽度。
5、软件设计
5.0 代码规范
你是否遇到自己写的代码间隔很久之后再来阅读时感觉有点读不太懂甚至是忘记当时的编程逻辑?当你的代码发给其他人阅读时经常被吐槽?变量命名直接a,b,c,再来看时直接蒙圈?
相信大家多多少少都会犯以上的一些错误,由此可见,代码规范是软件设计的一个重要环节。当你养成一个良好的代码编程习惯时你就可以避免以上的错误,写出像诗句一般的代码,让人阅读起来非常舒服移易懂。下面我将讲述一些代码规范的事项,大家可参照学习养成良好的代码规范。
- 模块化编程:将各个模块分开成一个个独立的.c和.h文件,这样可以快速移植代码。
- 适当添加注释:在一些头文件以及函数,变量,控制逻辑处的代码进行必要的一些注释说明。
- 变量以及函数命名规范:变量和函数的命名尽量做到见名知意。
- 代码排版格式尽量规范:在需要缩进的地方要缩进,在需要括号的地方尽量添加括号。
5.1 LED车灯
5.1.1LED灯原理图
- 由原理图可以知道,LED_M连接单片机PG13,LED_S连接单片机PB5,LED_L连接单片机PA12,LED_R连接单片机PG7,所以我们只要通过代码配置对应引脚的就可以对LED灯进行控制了
5.1.2代码
- bsp_led.h
- 1)bsp_led.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
#ifndef _BSP_LED_H
#define _BSP_LED_H
#include "gd32f4xx.h"
#include "systick.h"
#define LED_L RCU_GPIOA // GPIOA的时钟
#define PORT_LED_L GPIOA // GPIOA的端口
#define LED_L_PIN GPIO_PIN_12 // GPIOA的引脚
#define LED_R RCU_GPIOG // GPIOG的时钟
#define PORT_LED_R GPIOG // GPIOG的端口
#define LED_R_PIN GPIO_PIN_7 // GPIOG的引脚
void led_gpio_config(void); // led gpio引脚配置
void led_l_on(void);
void led_l_off(void);
void led_r_on(void);
void led_r_off(void);
#endif /* BSP_LED_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- bsp_led.c
- 1)
void led_gpio_config(void)
函数调用rcu_periph_clock_enable(LED_L)
;函数使能开启LED对应的时钟, - 2)调用
gpio_mode_set(PORT_LED_L,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,LED_L_PIN)
;对LED对应的IO进行模式设置,其中第一个参数PORT_LED_L是LED灯的对应IO端口,第二个参数GPIO_MODE_OUTPUT表示将IO口配置为输出模式,第三个参数GPIO_PUPD_NONE表示IO不设置上下拉即浮空模式,因为我们的LED电路已经配置了上下拉电阻。第四个参数LED_L_PIN就是我们的LED对应引脚。
#include "bsp_led.h"
void led_gpio_config(void)
{
/* 使能时钟 /
rcu_periph_clock_enable(LED_L);
rcu_periph_clock_enable(LED_R);
/ 配置为输出模式 浮空模式 /
gpio_mode_set(PORT_LED_L,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,LED_L_PIN);
gpio_mode_set(PORT_LED_R,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,LED_R_PIN);
/ 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_LED_L,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,LED_L_PIN);
gpio_output_options_set(PORT_LED_R,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,LED_R_PIN);
}
void led_l_on(void)
{
gpio_bit_write(PORT_LED_L,LED_L_PIN,RESET);
}
void led_l_off(void)
{
gpio_bit_write(PORT_LED_L,LED_L_PIN,SET);
}
void led_r_on(void)
{
gpio_bit_write(PORT_LED_R,LED_R_PIN,RESET);
}
void led_r_off(void)
{
gpio_bit_write(PORT_LED_R,LED_R_PIN,SET);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_led.h"
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
uint8_t i;
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
led_l_off();
led_r_off();
while(1){
//亮灭
led_l_on();
led_r_on();
delay_1ms(1000);
led_l_off();
led_r_off();
delay_1ms(1000);
//快闪
for(i=0;i<10;i++){
led_l_on();
led_r_off();
delay_1ms(200);
led_r_on();
led_l_off();
delay_1ms(200);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
👉文件下载
通过百度网盘分享的文件:01_LED车灯 链接:https://pan.baidu.com/s/1f5jCnCyzcPWjB42DKx9abg?pwd=LCKF 提取码:LCKF
5.1.3 代码解析
- 其中bsp_led.h和bsp_led.c文件是操作LED车灯的底层驱动函数,主函数main.c通过调用LED底层驱动函数控制LED车灯完成对应功能的实现;
- bsp_led.c里面编写了LED灯初始化函数,以及LED灯控制的函数封装;bsp_led.h主要存放的是bsp_led.c一些函数的声明;
- 主函数main.c实现了LED车灯的亮灭操作以及快速闪烁的功能效果。在开头包含我们需要使用到的头文件,由于需要使用到延时函数,所以想先初始化滴答定时器,然后调用led初始化函数,进入while(1)后就可以开始执行我们的逻辑代码了,这里实现的是先同时亮灭,然后快速闪烁10次,反复重复执行。
5.2 按键
5.2.1 按键原理图
5.2.2 代码
- bsp_key.h
- 1)bsp_key.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
#define KEYM_Press_Flag 1
是按键KEYM按下标志宏定义。 - 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
。 - 3)
extern uint8_t uckey
;这里使用了extern关键字表示变量uckey是在外部定义的。
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "gd32f4xx.h"
#include "systick.h"
#define BSP_KEYM_RCU RCU_GPIOB // 按键端口时钟
#define BSP_KEYM_PORT GPIOB // 按键端口
#define BSP_KEYM_PIN GPIO_PIN_5 // 按键引脚
#define BSP_KEYS_RCU RCU_GPIOE // 按键端口时钟
#define BSP_KEYS_PORT GPIOE // 按键端口
#define BSP_KEYS_PIN GPIO_PIN_5 // 按键引脚
#define KEYM_Press_Flag 1
#define KEYS_Press_Flag 2
extern uint8_t uckey;
void key_gpio_config(void); // key gpio引脚配置
uint8_t KEY_Read(void);
void KEY_Proc(void);
#endif /* BSP_KEY_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- bsp_key.c
#include "bsp_key.h"
#include "sys.h"
#include "bsp_led.h"
#include "stdio.h"
uint8_t uckey=0;
/*******
函数名称 : key_gpio_config
功 能 : 独立按键gpio引脚配置
参 数 : 无
返 回 值 : 无
*******/
void key_gpio_config(void)
{
/* 开启时钟 /
rcu_periph_clock_enable(BSP_KEYM_RCU);
rcu_periph_clock_enable(BSP_KEYS_RCU);
/ 配置为输入模式 上拉模式 */
gpio_mode_set(BSP_KEYM_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,BSP_KEYM_PIN); // 按键默认状态是高电平,配置为上拉
gpio_mode_set(BSP_KEYS_PORT,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,BSP_KEYS_PIN); // 按键默认状态是高电平,配置为上拉
}
/*******
函数名称 : KEY_Read
功 能 : 按键读取函数
参 数 : 无
返 回 值 : 对应按键标志位
*******/
uint8_t KEY_Read(void)
{
uint8_t key_val=0;
/* 先读取按键引脚的电平 如果低电平,按键按下 /
if(gpio_input_bit_get(BSP_KEYM_PORT,BSP_KEYM_PIN) == RESET) // 按键按下
{
delay_1ms(20); // 延迟消抖
if(gpio_input_bit_get(BSP_KEYM_PORT,BSP_KEYM_PIN) == RESET) // 再次检测按键是否按下
key_val=KEYM_Press_Flag;
}
/ 先读取按键引脚的电平 如果低电平,按键按下 */
if(gpio_input_bit_get(BSP_KEYS_PORT,BSP_KEYS_PIN) == RESET) // 按键按下
{
delay_1ms(20); // 延迟消抖
if(gpio_input_bit_get(BSP_KEYS_PORT,BSP_KEYS_PIN) == RESET) // 再次检测按键是否按下
key_val=KEYS_Press_Flag;
}
return key_val;
}
/*******
函数名称 : KEY_Proc
功 能 : 按键执行程序
参 数 : 无
返 回 值 : 无
********/
void KEY_Proc(void)
{
uint8_t key_val=0;
key_val = KEY_Read();
if(key_val != uckey)
uckey=key_val;
else
key_val = 0;
switch(key_val)
{
case KEYM_Press_Flag:
LEDM^=1;
break;
case KEYS_Press_Flag:
LEDS^=1;
break;
default:
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_key.h"
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
led_l_off();
led_r_off();
LEDS=0;
LEDM=0;
while(1){
KEY_Proc();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
:point*right:文件下载
通过百度网盘分享的文件:02*按键 链接:https://pan.baidu.com/s/1r-gvlNV-WdBmdBhmz_vpFw?pwd=LCKF 提取码:LCKF
5.2.3 代码解析
- bsp_key.c中存放了按键的IO口初始化函数·key_gpio_config(void)·,按键读取函数·uint8_t KEY_Read(void)·的作用是返回对应按键按下时的键值,按键执行程序·KEY_Proc(void)·是用来处理每一个按键按下时需要执行的任务;
- 主函数main.c调用了按键执行程序,实现的效果是对应按键按下时对应指示灯的状态进行切换;
5.3 蜂鸣器
5.3.1 蜂鸣器原理图
5.3.2 代码
- bsp_beep.h
- 1)bsp_beep.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:·#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束·。
#ifndef _BSP_BEEP_H
#define _BSP_BEEP_H
#include "gd32f4xx.h"
#include "systick.h"
#define BEEP_RCU RCU_GPIOF // GPIOF的时钟
#define PORT_BEEP GPIOF // GPIOF的端口
#define BEEP_PIN GPIO_PIN_8 // GPIOF的引脚
void beep_gpio_config(void); // beep gpio引脚配置
void beep_on(void);
void beep_off(void);
#endif /* BSP_BEEP_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- bsp_beep.c
#include "bsp_beep.h"
/*******
函数名称 : beep_gpio_config
功 能 : 蜂鸣器gpio引脚配置
参 数 : 无
返 回 值 : 无
********/
void beep_gpio_config(void)
{
/* 使能时钟 /
rcu_periph_clock_enable(BEEP_RCU);
/ 配置为输出模式 浮空模式 /
gpio_mode_set(PORT_BEEP,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,BEEP_PIN);
/ 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_BEEP,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BEEP_PIN);
}
void beep_on(void)
{
gpio_bit_write(PORT_BEEP,BEEP_PIN,RESET);
}
void beep_off(void)
{
gpio_bit_write(PORT_BEEP,BEEP_PIN,SET);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_led.h"
#include "bsp_beep.h"
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
beep_gpio_config(); // beep初始化
led_l_off();
led_r_off();
while(1){
beep_on();
led_l_on();
led_r_on();
delay_1ms(1000);
beep_off();
led_l_off();
led_r_off();
delay_1ms(1000);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
:point*right:文件下载
通过百度网盘分享的文件:03*蜂鸣器 链接:https://pan.baidu.com/s/1abx9HKxxNviCgARIf7ASow?pwd=LCKF 提取码:LCKF
5.3.3 代码解析
- 蜂鸣器的代码和LED车灯的代码原理类似,这里不过多解释;
- 这里我们也可以将蜂鸣器的控制引脚重映射到定时器通道,从而让蜂鸣器发出频率不同的声音,达到类似音乐播放的效果;
5.3.4数据手册
5.4 ADC电压检测
5.4.1 ADC电压检测原理图
5.4.2 代码
- adc.h
- 1)adc.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
。 - 3)
extern uint16_t adcValue
;这里使用了extern关键字表示变量adcValue是在外部定义的。
#ifndef _ADC_H
#define _ADC_H
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_usart.h"
/* PC5 ADC0_1__IN15*/
#define ADC_RCU RCU_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_PIN_5
extern uint16_t adcValue;
void adc_config(void);
uint16_t adc_channel_sample(uint8_t channel);
void adc_get_val(void);
#endif /* ADC_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- adc.c
#include "adc.h"
#include "stdio.h"
#define DEBUG // 打开这个将会使能 adc_get_val下面的打印信息
uint16_t adcValue;
/*******
函数名称 : adc_gpio_init
功 能 : adc_gpio引脚配置,ADC的附加功能,优先于普通GPIO
参 数 : 无
返 回 值 : 无
********/
static void adc_gpio_init(void)
{
/* enable the clock */
rcu_periph_clock_enable(ADC_RCU);
/* configure GPIO port 附加功能需要配置为 GPIO_MODE_ANALOG */
gpio_mode_set(ADC_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE,ADC_PIN);
}
void adc_config(void)
{
/* enable ADC0 clock */
rcu_periph_clock_enable(RCU_ADC0);
/* config ADC clock */
adc_clock_config(ADC_ADCCK_PCLK2_DIV8);
/* reset ADC */
adc_deinit();
/* configure the ADC mode */
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); // 所有ADC都工作在独立模式
/* ADC contineous function disable */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE); // 关闭连续模式
/* ADC scan mode disable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE); // 关闭扫描模式
/* ADC data alignment config */
adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT); // LSB对齐,低位对齐
/* ADC channel length config */
adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,1U); // ADC规则通道 长度为1
/* enable ADC interface */
adc_enable(ADC0);
/* wait for ADC stability */
delay_1ms(1);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0); // ADC校准
/* wait for ADC stability */
delay_1ms(1);
/* adc 引脚初始化 */
adc_gpio_init();
}
/*!
\brief ADC channel sample ADC通道采样
\param[] none
\param[] none
\retval none
*/
uint16_t adc_channel_sample(uint8_t channel)
{
/* ADC regular channel config */
adc_regular_channel_config(ADC0, 0U, channel, ADC_SAMPLETIME_15); // 15个采样周期
/* ADC software trigger enable */
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); // ADC软件触发使能
/* wait the end of conversion flag */
while(!adc_flag_get(ADC0, ADC_FLAG_EOC));
/* clear the end of conversion flag */
adc_flag_clear(ADC0, ADC_FLAG_EOC);
/* return regular channel sample value */
return (adc_regular_data_read(ADC0));
}
void adc_get_val(void)
{
adcValue = adc_channel_sample(ADC_CHANNEL_15); // 采样
#ifdef DEBUG
printf("adcValue is :%d\r\n ",adcValue);
#endif
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_usart.h"
#include "adc.h"
extern uint16_t adcValue;
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
usart_gpio_config(9600U); // 串口0初始化
adc_config(); // adc初始化
led_l_off();
led_r_off();
while(1){
adc_get_val();
delay_1ms(200);
if(adcValue <1000)
{
led_l_off();
led_r_off();
}
else
{
led_l_on();
led_r_on();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
👉文件下载
通过百度网盘分享的文件:04_ADC电压检测 链接:https://pan.baidu.com/s/1MEvQPCiBDwxpFqwogBayDg?pwd=LCKF 提取码:LCKF
5.4.3 代码解析
- adc.c中完成了光敏电阻ADC通道的配置,以及封装了ADC采样值读取函数
adc_get_val(void)
,可以发现实际是调用了adc_channel_sample(ADC_CHANNEL_15)
函数; - 主函数main.c主要实现的功能效果是通过串口不断打印出ADC检测到的值。
- ADC检测电压的原理:
前提:假设ADC是12位的,电池供电最高8.4V,最低6.6V(低于就开始报警提醒充电);
-------知道电压求ADC值-------
(1)ADC端占的电压比例:10K/(10K+10K+10k)≈0.3333;
(2)电池电量最高时,ADC端分压得到的电压为:0.33338.4V≈2.7997V
电池电量最低时,ADC端分压得到的电压为:0.33336.6V≈2.1998V
如果参照3V,即满电状态下为3V,没电是0V
电池电量最高时的ADC值为:(2.7997/3V)*4095=3821
电池电量最低时的ADC值为:(2.1998/3V)*4095=3002
-------知道ADC值求电压-------
如果参照3V,即满电状态下为3V,没电是0V;
通过上式子的逆运算可以知道,任意ADC值对应采集的电压值为: V_value=(ADC*3)/4095 单位:V
5.5 电机
5.5.1 电机驱动原理图
5.5.2 代码
(1)控制电机的第一种方法——直接使用IO口高低电平
- 控制电机的第一种方法——直接使用IO口高低电平:电机的IO口引脚直接配置为通用推挽输出,只要将电机的两个IO口输出不同的电平就可以让电机转动起来,将引脚的高低电平交换就可以改变电机转动的方向;
#include "main.h"
#include "stdlib.h"
#include "string.h"
static void MOTOR_gpio_config(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
/* 配置GPIO的模式 */
gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_0);
gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_1);
gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_2);
gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_3);
gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_7);
gpio_mode_set(GPIOB,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_0);
gpio_mode_set(GPIOB,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_1);
gpio_mode_set(GPIOB,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_4);
/* 配置GPIO的输出 */
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_0);
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_1);
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_2);
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_3);
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_7);
gpio_output_options_set(GPIOB,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_0);
gpio_output_options_set(GPIOB,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_1);
gpio_output_options_set(GPIOB,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_4);
}
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
MOTOR_gpio_config();
PAout(0)=1;
PAout(1)=1;
PAout(2)=1;
PAout(3)=1;
PBout(4)=1;
PAout(7)=1;
PBout(0)=1;
PBout(1)=1;
while(1){
PAout(0)=1;
PAout(1)=0;
PAout(2)=1;
PAout(3)=0;
PBout(4)=1;
PAout(7)=0;
PBout(0)=1;
PBout(1)=0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
文件下载
通过百度网盘分享的文件:06*电机_IO口高低电平 链接:https://pan.baidu.com/s/12ETPSB-U7F3JYwq8Bd0VRg?pwd=LCKF 提取码:LCKF
(2)控制电机的第二种方法——使用定时器PWM功能
- 以上控制电机的方法比较简单易懂,然而如果你想改变电机转动的速度,以上的方法就行不通了,就需要引入定时器的PWM功能了;
- 控制电机的第二种方法——使用定时器PWM功能:通过IO口复用功能,将引脚复用为定时器PWM功能,通过改变PWM的占空比改变电机的转速;
void pwm0_config(uint16_t pre,uint16_t per)//200 1000
{
timer_parameter_struct timere_initpara; // 定义定时器结构体
timer_oc_parameter_struct timer_ocintpara; // 定时器比较输出结构体
rcu_periph_clock_enable(BSP_PWM0_TIMER_RCU); // 开启定时器时钟
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
/* 配置定时器参数 */
timer_deinit(BSP_PWM0_TIMER); // 复位定时器
timere_initpara.prescaler = pre-1; // 时钟预分频值 PSC_CLK= 200MHZ / 200 = 1MHZ
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = per-1; // 周期 T = 10000 1MHZ = 10ms f = 100HZ
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_PWM0_TIMER,&timere_initpara); // 初始化定时器
/* 配置输出结构体 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 有效电平的极性
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 配置比较输出模式状态 也就是使能PWM输出到端口
/* 配置定时器输出功能 */
timer_channel_output_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_0,&timer_ocintpara); //使能的定时器通道0
timer_channel_output_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_2,&timer_ocintpara); //使能的定时器通道2
/* 配置占空比 */
timer_channel_output_pulse_value_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_0,0); // 配置定时器通道输出脉冲值
timer_channel_output_mode_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_0,TIMER_OC_MODE_PWM0); // 配置定时器通道输出比较模式
timer_channel_output_shadow_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_0,TIMER_OC_SHADOW_DISABLE); // 配置定时器通道输出影子寄存器
timer_channel_output_pulse_value_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_2,0); // 配置定时器通道输出脉冲值
timer_channel_output_mode_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_2,TIMER_OC_MODE_PWM0); // 配置定时器通道输出比较模式
timer_channel_output_shadow_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_2,TIMER_OC_SHADOW_DISABLE); // 配置定时器通道输出影子寄存器
/* 只有高级定时器使用 */
timer_primary_output_config(TIMER0,ENABLE);//只有高级定时器0/7使用
timer_auto_reload_shadow_enable(BSP_PWM0_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM0_TIMER);
}
void motor_gpio_config(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(LQ_FI_RCU);
/* 配置为输出模式 复用模式 */
gpio_mode_set(PORT_LQ_FI,GPIO_MODE_AF,GPIO_PUPD_NONE,LQ_FI_PIN);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_LQ_FI,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,LQ_FI_PIN);
gpio_af_set(PORT_LQ_FI,TIMER0_CH0,LQ_FI_PIN);//配置GPIO的复用
}
/*******
函数名称 : motor_LQ_front
功 能 : 左前轮正转
参 数 : speed
返 回 值 : 无
*******/
void motor_LQ_front(uint8_t speed)
{
gpio_mode_set(PORT_LQ_BI,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,LQ_BI_PIN);//通用推挽输出
LQ_BI=0;
gpio_mode_set(PORT_LQ_FI,GPIO_MODE_AF,GPIO_PUPD_NONE,LQ_FI_PIN); //复用推挽输出
timer_channel_output_pulse_value_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_0,speed); // 配置定时器通道输出脉冲值
}
/*******
函数名称 : motor_LQ_back
功 能 : 左前轮反转
参 数 : speed
返 回 值 : 无
*******/
void motor_LQ_back(uint8_t speed)
{
gpio_mode_set(PORT_LQ_FI,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,LQ_FI_PIN);//通用推挽输出
LQ_FI=0;
gpio_mode_set(PORT_LQ_BI,GPIO_MODE_AF,GPIO_PUPD_NONE,LQ_BI_PIN); //复用推挽输出
timer_channel_output_pulse_value_config(BSP_PWM0_TIMER,BSP_PWM0_CHANNEL_2,speed); // 配置定时器通道输出脉冲值
}
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
motor_gpio_config(); // 电机驱动引脚初始化
pwm1_config(200,100); // 定时器1 PWM配置 PWM输出频率10000HZ
pwm2_config(200,100); // 定时器2 PWM配置 PWM输出频率10000HZ
while(1){
car_front(50); //前进
delay_1ms(2000);
car_back(50); //后退
delay_1ms(2000);
car_left(50); //左转
delay_1ms(2000);
car_front(50); //前进
delay_1ms(2000);
car_right(50); //右转
delay_1ms(2000);
car_front(50); //前进
delay_1ms(2000);
car_stop(Brake_Stop);//刹车停止
delay_1ms(2000);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
👉文件下载
通过百度网盘分享的文件:07_电机_PWM 链接:https://pan.baidu.com/s/1eVS25HFwC3kUR0xrTTTqzg?pwd=LCKF 提取码:LCKF
5.5.3 代码解析
- 先将电机使用到的定时器PWM通道进行配置,让PWM输出频率为10000HZ,不同的电机驱动频率有所差异,大家根据自己的电机型号配置。
- 再调用我们编写好的前进后退左右转的函数,实现小车的基本运动功能。
- 如何实现小车的转向:这里我们采用的方式是使用差速转向。当我们需要左转时,左边两个轮子反转,右边两个轮子正转即可实现左转;当我们需要右转时,右边两个轮子反转,左边两个轮子正转即可实现右转;
5.5.4 数据手册
5.6 循迹
5.6.1 循迹原理图
5.6.2 代码
- track.h
- 1)track.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
。
#ifndef _TRACK_H
#define _TRACK_H
#include "gd32f4xx.h"
#include "systick.h"
#define XJ01_RCU RCU_GPIOA // GPIOA的时钟
#define PORT_XJ01 GPIOA // GPIOA的端口
#define XJ01_PIN GPIO_PIN_15 // GPIOA的引脚
#define XJ02_RCU RCU_GPIOC // GPIOC的时钟
#define PORT_XJ02 GPIOC // GPIOC的端口
#define XJ02_PIN GPIO_PIN_10 // GPIOC的引脚
#define XJ03_RCU RCU_GPIOC // GPIOC的时钟
#define PORT_XJ03 GPIOC // GPIOC的端口
#define XJ03_PIN GPIO_PIN_12 // GPIOC的引脚
#define XJ04_RCU RCU_GPIOB // GPIOB的时钟
#define PORT_XJ04 GPIOB // GPIOB的端口
#define XJ04_PIN GPIO_PIN_13 // GPIOB的引脚
#define XJ05_RCU RCU_GPIOB // GPIOB的时钟
#define PORT_XJ05 GPIOB // GPIOB的端口
#define XJ05_PIN GPIO_PIN_15 // GPIOB的引脚
#define No_Black_Line_Found 0 //没有检查到黑线
#define Black_Line_Found 1 //检查到黑线
void track_gpio_config(void); // 循迹gpio引脚配置
void Black_Line_Detection(void); // 黑线检测函数
#endif /* TRACK_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- track.c
#include "track.h"
FlagStatus XJ01 = RESET;
FlagStatus XJ02 = RESET;
FlagStatus XJ03 = RESET;
FlagStatus XJ04 = RESET;
FlagStatus XJ05 = RESET;
/*******
函数名称 : track_gpio_config
功 能 : 循迹gpio引脚配置
参 数 : 无
返 回 值 : 无
*******/
void track_gpio_config(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(XJ01_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(PORT_XJ01,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,XJ01_PIN);
/* 使能时钟 */
rcu_periph_clock_enable(XJ02_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(PORT_XJ02,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,XJ02_PIN);
/* 使能时钟 */
rcu_periph_clock_enable(XJ03_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(PORT_XJ03,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,XJ03_PIN);
/* 使能时钟 */
rcu_periph_clock_enable(XJ04_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(PORT_XJ04,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,XJ04_PIN);
/* 使能时钟 */
rcu_periph_clock_enable(XJ05_RCU);
/* 配置为输入模式 上拉模式 */
gpio_mode_set(PORT_XJ05,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,XJ05_PIN);
}
/*******
函数名称 : Black_Line_Detection
功 能 : 黑线检测函数
参 数 : 无
返 回 值 : 无
*******/
void Black_Line_Detection(void)
{
XJ01 = gpio_input_bit_get(PORT_XJ01,XJ01_PIN);
XJ02 = gpio_input_bit_get(PORT_XJ02,XJ02_PIN);
XJ03 = gpio_input_bit_get(PORT_XJ03,XJ03_PIN);
XJ04 = gpio_input_bit_get(PORT_XJ04,XJ04_PIN);
XJ05 = gpio_input_bit_get(PORT_XJ05,XJ05_PIN);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_pwm.h"
#include "motor.h"
#include "track.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_beep.h"
uint16_t No_Black_Line_TimeCount = 0 ; // 没有发现黑线时间
uint8_t LongTime_No_Black_Line_Flag = 0 ; // 长时间没发现黑线标志位
extern FlagStatus XJ01;
extern FlagStatus XJ02;
extern FlagStatus XJ03;
extern FlagStatus XJ04;
extern FlagStatus XJ05;
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
pwm1_config(200,100); // 定时器1 PWM配置 PWM输出频率10KHZ
pwm2_config(200,100); // 定时器2 PWM配置 PWM输出频率10KHZ 速度调节0-100
motor_gpio_config(); // 电机驱动引脚初始化
track_gpio_config(); // 循迹gpio引脚配置
beep_gpio_config();
led_gpio_config();
key_gpio_config();
//熄火停止
car_stop(Flameout_Stop);
//关闭车灯和蜂鸣器
beep_off();
led_l_off();
led_r_off();
LEDS=0;
LEDM=0;
while(1){
Black_Line_Detection();//黑线检测函数
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//3中间检测到黑线00100
{
car_front(87); //前进
}
if(XJ01==No_Black_Line_Found && XJ02==Black_Line_Found && XJ03==Black_Line_Found && XJ04==Black_Line_Found && XJ05==No_Black_Line_Found)//2 3 4中间检测到黑线00100
{
car_front(87); //前进
}
if(XJ01==No_Black_Line_Found && XJ02==Black_Line_Found && XJ03==Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//2 3检测到黑线(右偏小)01100
{
motor_LQ_front(40);
motor_LH_front(40);
motor_RQ_front(87);
motor_RH_front(87);
}
if(XJ01==No_Black_Line_Found && XJ02==Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//2检测到黑线(右偏大)01000
{
motor_LQ_front(30);
motor_LH_front(30);
motor_RQ_front(87);
motor_RH_front(87);
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==Black_Line_Found && XJ04==Black_Line_Found && XJ05==No_Black_Line_Found)//4 3检测到黑线(左偏小)00110
{
motor_LQ_front(87);
motor_LH_front(87);
motor_RQ_front(40);
motor_RH_front(40);
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==Black_Line_Found && XJ05==No_Black_Line_Found)//4检测到黑线(左偏大)
{
motor_LQ_front(87);
motor_LH_front(87);
motor_RQ_front(30);
motor_RH_front(30);
}
if(XJ01==Black_Line_Found && XJ02==Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//1 2检测到黑线(左转)
{
motor_LQ_back(55);
motor_LH_back(55);
motor_RQ_front(87);
motor_RH_front(87);
}
if(XJ01==Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//1 检测到黑线(左转)
{
motor_LQ_back(45);
motor_LH_back(45);
motor_RQ_front(87);
motor_RH_front(87);
}
if(XJ01==Black_Line_Found && XJ02==Black_Line_Found && XJ03==Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//1 2 3检测到黑线(左转)
{
motor_LQ_back(35);
motor_LH_back(35);
motor_RQ_front(87);
motor_RH_front(87);
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==Black_Line_Found && XJ05==Black_Line_Found)//4 5检测到黑线(右转)
{
motor_LQ_front(87);
motor_LH_front(87);
motor_RQ_back(55);
motor_RH_back(55);
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==Black_Line_Found)//5 检测到黑线(右转)
{
motor_LQ_front(87);
motor_LH_front(87);
motor_RQ_back(45);
motor_RH_back(45);
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==Black_Line_Found && XJ04==Black_Line_Found && XJ05==Black_Line_Found)//3 4 5检测到黑线(右转)
{
motor_LQ_front(87);
motor_LH_front(87);
motor_RQ_back(35);
motor_RH_back(35);
}
if(XJ01==Black_Line_Found && XJ02==Black_Line_Found && XJ03==Black_Line_Found && XJ04==Black_Line_Found && XJ05==Black_Line_Found)
{
car_stop(Brake_Stop);//刹车停止
delay_1ms(4000);
car_front(87); //前进
delay_1ms(200);
}
if(XJ01==Black_Line_Found || XJ02==Black_Line_Found || XJ03==Black_Line_Found || XJ04==Black_Line_Found || XJ05==Black_Line_Found)
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
}
if(XJ01==No_Black_Line_Found && XJ02==No_Black_Line_Found && XJ03==No_Black_Line_Found && XJ04==No_Black_Line_Found && XJ05==No_Black_Line_Found)//未检测到黑线(停止)
{
if(LongTime_No_Black_Line_Flag == 1)//长时间
car_stop(Brake_Stop);//刹车停止
else
car_front(87); //前进
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
👉文件下载
通过百度网盘分享的文件:09_循迹 链接:https://pan.baidu.com/s/1H4jO3zOhi8uqFKNj3AJRjA?pwd=LCKF 提取码:LCKF
5.6.3 代码解析
循迹的原理是检测黑线:红外发射管发射光线到地面,当红外光遇到白色地面则被反射,红外接收管接收到反射光,经过施密特触发器后输出低电平。所以可以通过读取左右循迹的IO口电平判断是否在黑线上行驶;
- 两路循迹逻辑:两路都检测到黑线则前进,左循迹未检测到黑线就右转,右循迹未检测到黑线就左转;
- 主函数main.c主要实现的功能效果是让智能小车沿着黑线赛道上行驶;
5.6.4数据手册
5.7 避障
5.7.1 避障原理图
5.7.2 代码
- bsp_hcsr.h
- 1)bsp_hcsr.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
。 - 3)#define Trig PBout(12) 将PBout(12)宏定义为Trig,表示调用Tirg时就是调用PBout(12)。
#ifndef _BSP_HCSR_H
#define _BSP_HCSR_H
#include "gd32f4xx.h"
#include "systick.h"
#include "sys.h"
#define HCSR04_RCU RCU_GPIOB // GPIOB的时钟
#define PORT_HCSR04 GPIOB // GPIOB的端口
#define HCSR04_Trig_PIN GPIO_PIN_12 // GPIOB的引脚
#define HCSR04_Echo_PIN GPIO_PIN_10 // GPIOB的引脚
#define Trig PBout(12)
#define Echo PBin(10)
void hcsr04_gpio_config(void);
int32_t HCSR04_Get_Distance(void);
#endif /* BSP_HCSR_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- bsp_hcsr.c
- 1)
int32_t HCSR04_Get_Distance(void)
函数作用是读取HCSR04模块测量的距离(取声音在空气传播的速度为340m/s),根据模块的工作时序图编写。 - 2)
void hcsr04_gpio_config(void)
函数作用是超声波模块gpio引脚初始化配置。
#include "bsp_hcsr.h"
/*******
函数名称 : hcsr04_gpio_config
功 能 : 超声波模块gpio引脚配置
参 数 : 无
返 回 值 : 无
*******/
void hcsr04_gpio_config(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(HCSR04_RCU);
/* 配置为输出模式 浮空模式 */
gpio_mode_set(PORT_HCSR04,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,HCSR04_Trig_PIN);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_HCSR04,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,HCSR04_Trig_PIN);
/* 配置为输入模式 下拉模式 */
gpio_mode_set(PORT_HCSR04,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,HCSR04_Echo_PIN);
gpio_bit_write(PORT_HCSR04,HCSR04_Trig_PIN,RESET);//Trig初始状态为低电平,看时序图
}
/*******
函数名称 : HCSR04_Get_Distance(这是延时等待方式,后续修改为定时器中断方式触发)
功 能 : 读取HCSR04模块测量的距离(取声音在空气传播的速度为340m/s)
参 数 : 无
返 回 值 : int32_t类型的距离d(单位:mm)
*******/
int32_t HCSR04_Get_Distance(void)
{
uint32_t t=0;//Echo高电平时间
int32_t d=0;
Trig=1;
delay_1us(20);
Trig=0;
t=0;
while(Echo==0)
{
delay_1us(1);
t++;
if(t >= 1000000)
return -1;//设备响应超时处理
}
t=0;
while(Echo)
{
delay_1us(9);
t++;
if(t >= 10000)//超出测量范围处理
return -2;
}
d = t*3/2;
return d;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_pwm.h"
#include "motor.h"
#include "bsp_hcsr.h"
#include "bsp_led.h"
#include "bsp_beep.h"
int32_t distance=0;//测量距离
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
motor_gpio_config(); // 电机驱动引脚初始化
pwm0_config(200,100); // 定时器0 PWM配置 PWM输出频率10000HZ
pwm1_config(200,100); // 定时器1 PWM配置 PWM输出频率10000HZ
pwm2_config(200,100); // 定时器2 PWM配置 PWM输出频率10000HZ
pwm8_config(200,100); // 定时器8 PWM配置 PWM输出频率10000HZ
hcsr04_gpio_config(); // 超声波模块初始化
//关闭车灯和蜂鸣器
beep_off();
led_l_off();
led_r_off();
LEDS=0;
LEDM=0;
while(1){
distance=HCSR04_Get_Distance();
if(distance <= 80)//前方8cm有障碍
{
beep_on();//蜂鸣器鸣叫
car_back(45);//后退
delay_1ms(1000);
car_left(80); //左转
delay_1ms(300);
}
else if(distance > 80 && distance <= 3000)//前方无障碍
{
beep_off();//蜂鸣器关闭
car_front(80); //前进
}
else
{
beep_off();//蜂鸣器关闭
car_stop(Brake_Stop);//刹车停止
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
👉文件下载
通过百度网盘分享的文件:08_避障 链接:https://pan.baidu.com/s/1sTVLgcNJ6PsiJaWxgbnS7g?pwd=LCKF 提取码:LCKF
5.7.3 代码解析
- bsp_hcsr.c配置了超声波模块的IO引脚初始化,以及读取HCSR04模块测量的距离函数
int32_t HCSR04_Get_Distance(void)
,该函数是根据模块时序图编写的,返回值是int32_t
类型的距离d(单位:mm); - 主函数main.c调用了
HCSR04_Get_Distance()
函数,当前方8cm有障碍物时,蜂鸣器鸣叫,小车后退左转;当前方无障碍物时,蜂鸣器关闭,小车前进;其他情况现车刹车停止,蜂鸣器关闭;
5.8 蓝牙遥控
5.8.1 蓝牙原理图
5.8.2 代码
- bsp_usart.h
- 1)bsp_usart.h头文件主要存放一些需要引用的头文件,以及宏定义,函数声明。
- 2)为了避免头文件重复定义,使用了条件编译的方式:
#ifndef xx:如果没有定义xx;#define xx:则定义xx;#endif:结束
。 - 3)
#define USART_RECEIVE_LENGTH 4096
宏定义了串口缓冲区的数据长度为4096。
#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "gd32f4xx.h"
#include "systick.h"
//蓝牙连接接口
#define BSP_UART6_TX_RCU RCU_GPIOF // 串口6TX的端口时钟
#define BSP_UART6_RX_RCU RCU_GPIOF // 串口6RX的端口时钟
#define BSP_UART6_RCU RCU_UART6 // 串口6的时钟
#define BSP_UART6_TX_PORT GPIOF // 串口TX的端口
#define BSP_UART6_RX_PORT GPIOF // 串口RX的端口
#define BSP_UART6_AF GPIO_AF_8 // 串口6的复用功能
#define BSP_UART6_TX_PIN GPIO_PIN_7 // 串口6TX的引脚
#define BSP_UART6_RX_PIN GPIO_PIN_6 // 串口6RX的引脚
#define BSP_UART6 UART6 // 串口6
#define BSP_UART6_IRQ UART6_IRQn // 串口6中断
#define BSP_UART6_IRQHandler UART6_IRQHandler // 串口6中断服务函数
/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 4096
extern uint8_t g_recv_buff[]; // 接收缓冲区
extern uint16_t g_recv_length; // 接收数据长度
extern uint8_t g_recv_complete_flag; // 接收完成标志位
void uart6_gpio_config(uint32_t band_rate); // 配置串口6
#endif /* BSP_USART_H */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- bsp_usart.c
#include "bsp_usart.h"
#include "stdio.h"
uint8_t g_recv_buff[]; // 接收缓冲区
uint16_t g_recv_length = 0; // 接收数据长度
uint8_t g_recv_complete_flag = 0; // 接收数据完成标志位
void uart6_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_UART6_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_UART6_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_UART6_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_UART6_TX_PORT,BSP_UART6_AF,BSP_UART6_TX_PIN);
gpio_af_set(BSP_UART6_RX_PORT,BSP_UART6_AF,BSP_UART6_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_UART6_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_UART6_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_UART6_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_UART6_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_UART6_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_UART6_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_UART6_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_UART6_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_UART6); // 复位串口
usart_baudrate_set(BSP_UART6,band_rate); // 设置波特率
usart_parity_config(BSP_UART6,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_UART6,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_UART6,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_UART6); // 使能串口
usart_transmit_config(BSP_UART6,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_UART6,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_UART6_IRQ, 2, 2); // 配置中断优先级
usart_interrupt_enable(BSP_UART6,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_UART6,USART_INT_IDLE); // 空闲检测中断
}
/*******
函数名称 : BSP_UART6_IRQHandler
功 能 : 串口6接收中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
********/
void BSP_UART6_IRQHandler(void)
{
if(usart_interrupt_flag_get(BSP_UART6,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
g_recv_buff[] = usart_data_receive(BSP_UART6); // 把接收到的数据放到缓冲区中
}
if(usart_interrupt_flag_get(BSP_UART6,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{
usart_data_receive(BSP_UART6); // 必须要读,读出来的值不能要
g_recv_buff[] = '\0'; // 数据接收完毕,数组结束标志
g_recv_complete_flag = 1; // 接收完成
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_usart.h"
#include "bluetooth.h"
extern uint8_t g_recv_buff[]; // 接收缓冲区
extern uint16_t g_recv_length; // 接收数据长度
extern uint8_t g_recv_complete_flag; // 接收数据完成标志位
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
beep_gpio_config(); // beep初始化
usart_gpio_config(9600U); // 串口0初始化
uart6_gpio_config(9600U); //蓝牙接口,串口6初始化,波特率请尝试使用为9600bps/38400bps进行测试
beep_off();
led_l_off();
led_r_off();
#if 0
printf("-------This is bluetooth AT test-------\r\n");
HC05_Bluetooth2_0_config();//直接使用串口调试助手发送AT指令也行,需要借助USB转TTL模块
#endif
#if 1
printf("-------This is bluetooth data transmission test-------\r\n");
printf("使用手机蓝牙控制开发板,实现灯的亮灭,要求如下:\r\n");
printf("接收到数据‘0’,则LEDL点亮;接收到数据‘a’,则LEDL熄灭!\r\n");
printf("接收到数据‘1’,则LEDR点亮;接收到数据‘b’,则LEDR熄灭!\r\n");
printf("接收到数据‘2’,则BEEP鸣叫;接收到数据‘c’,则BEEP不叫!\r\n");
printf("你收到来自手机的指令为:\r\n");
#endif
while(1){
/* 等待数据传输完成 */
if(g_recv_complete_flag) // 数据接收完成
{
g_recv_complete_flag = 0; // 等待下次接收
printf("g_recv_length:%d ",g_recv_length); // 打印接收的数据长度
printf("g_recv_buff:%s\r\n",g_recv_buff); // 打印接收的数据
switch(g_recv_buff[]){
case '0':
led_l_on();
break;
case '1':
led_r_on();
break;
case '2':
beep_on();
break;
case 'a':
led_l_off();
break;
case 'b':
led_r_off();
break;
case 'c':
beep_off();
break;
default:
printf("指令错误!!!\r\n");
break;
}
memset(g_recv_buff,0,g_recv_length); // 清空数组
g_recv_length = 0; // 清空长度
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
👉文件下载
通过百度网盘分享的文件:10_蓝牙遥控 链接:https://pan.baidu.com/s/1yXtiwLC19209QH85Bp5HaQ?pwd=LCKF 提取码:LCKF
5.8.3 代码解析
- 通过查看原理图我们发现蓝牙APP的串行接口是连接在UART6上的,所以在bsp_usart.c中完成串口6的基本配置,以及串口6的中断服务函数的编写;
- 主函数main.c实现的效果是通过蓝牙APP发送数据给梁山派智能小车,梁山派智能小车接收到字符数据后进行解析执行相应的操作如开灯关灯等;
5.9 手机蓝牙APP
5.9.1 蓝牙APP界面绘制
- 以下是我制作的一个蓝牙APP的界面,具体如何制作的,我将在视频教程中给大家讲解。
5.9.2 蓝牙APP控制逻辑
- 以下是我制作的一个蓝牙APP的控制逻辑,具体是什么意思,我也将在视频教程中给大家讲解。
5.10 综合功能
5.10.1 代码
- gd32f4xx_it.c
#include "gd32f4xx_it.h"
#include "main.h"
#include "systick.h"
extern uint16_t No_Black_Line_TimeCount; // 没有发现黑线时间
extern uint8_t LongTime_No_Black_Line_Flag; // 长时间没发现黑线标志位
void SysTick_Handler(void)
{
delay_decrement();
No_Black_Line_TimeCount++;
if(No_Black_Line_TimeCount >=2000)//2s未发现黑线
{
No_Black_Line_TimeCount = 0;
LongTime_No_Black_Line_Flag = 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- main.c
#include "main.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_beep.h"
#include "adc_Light.h"
#include "motor.h"
#include "bsp_pwm.h"
#include "bsp_basic_timer.h"
#include "track.h"
#include "bsp_hcsr.h"
#include "bluetooth.h"
#include "bsp_usart.h"
uint16_t No_Black_Line_TimeCount = 0 ; // 没有发现黑线时间
uint8_t LongTime_No_Black_Line_Flag = 0 ; // 长时间没发现黑线标志位
extern FlagStatus Left_XJ; // 左循迹标志位
extern FlagStatus Right_XJ; // 右循迹标志位
extern uint8_t Motion_Mode; // 运动模式
extern uint8_t Start_Flag; // 小车启动按键标志位
extern uint16_t adcValue; // 光明电阻ADC采样值
int32_t distance=0;//测量距离
/*******
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
********/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
usart_gpio_config(9600U); // 串口0初始化
uart6_gpio_config(9600U); // 蓝牙接口,串口6初始化,波特率请尝试使用为9600bps/38400bps进行测试
motor_gpio_config(); // 电机驱动引脚初始化
pwm0_config(200,100); // 定时器0 PWM配置 PWM输出频率10000HZ
pwm1_config(200,100); // 定时器1 PWM配置 PWM输出频率10000HZ
pwm2_config(200,100); // 定时器2 PWM配置 PWM输出频率10000HZ
pwm8_config(200,100); // 定时器8 PWM配置 PWM输出频率10000HZ
track_gpio_config(); // 循迹gpio引脚配置
led_gpio_config(); // led初始化
key_gpio_config(); // 按键初始化
beep_gpio_config(); // beep初始化
adc_config(); // adc光敏电阻初始化
hcsr04_gpio_config(); // 超声波模块初始化
//关闭车灯和蜂鸣器
beep_off();
led_l_off();
led_r_off();
LEDS=0;
LEDM=0;
while(1){
KEY_Proc();
adc_get_val();
if(adcValue > 100)//环境光线太暗,自动开灯,具体值再调整
{
led_l_on();
led_r_on();
}
else//环境光线正常,自动关灯,具体值再调整
{
led_l_off();
led_r_off();
}
if(Motion_Mode)//避障模式LEDM点亮
{
LEDM=1;
if(Start_Flag)//小车启动LEDS点亮,小车停止LEDS熄灭
{
LEDS=1;
distance=HCSR04_Get_Distance();
if(distance <= 100)//前方10cm有障碍
{
beep_on();//蜂鸣器鸣叫
car_left(40); //左转
delay_1ms(1000);
}
else if(distance > 100 && distance <= 1000)//前方无障碍
{
beep_off();//蜂鸣器关闭
car_front(80); //前进
}
else
{
beep_off();//蜂鸣器关闭
car_stop(Brake_Stop);//刹车停止
Start_Flag=0;
LEDS=0;
}
}
else
{
car_stop(Brake_Stop);//刹车停止
LEDS=0;
}
}
else//循迹模式模式LEDM熄灭
{
LEDM=0;
if(Start_Flag)//小车启动LEDS点亮
{
LEDS=1;
Black_Line_Detection();//黑线检测函数
if(Left_XJ==Black_Line_Found && Right_XJ==Black_Line_Found)//左右两侧都有黑线
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
car_front(80); //前进
}
else if(Left_XJ==No_Black_Line_Found && Right_XJ==Black_Line_Found)//左侧没黑线,右侧有黑线
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
car_right(40); //右转
}
else if(Left_XJ==Black_Line_Found && Right_XJ==No_Black_Line_Found)//左侧有黑线,右侧没黑线
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
car_left(40); //左转
}
else if(Left_XJ==No_Black_Line_Found && Right_XJ==No_Black_Line_Found)//左右两侧都没有黑线
{
if(LongTime_No_Black_Line_Flag == 1)//长时间没有发现黑线
{
car_stop(Brake_Stop);//刹车停止
Start_Flag=0;
LEDS=0;
}
else//短时间没有发现黑线
car_front(40); //前进
}
else //其它
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
car_stop(Brake_Stop);//刹车停止
Start_Flag=0;
LEDS=0;
}
}
else//小车停止LEDS熄灭
{
No_Black_Line_TimeCount=0;
LongTime_No_Black_Line_Flag=0;
car_stop(Brake_Stop);//刹车停止
LEDS=0;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
👉文件下载
通过百度网盘分享的文件:11_综合测试 链接:https://pan.baidu.com/s/1URxELtXeAq9i40LduMKIuA?pwd=LCKF 提取码:LCKF
5.10.2 代码解析
- 综合了上述全部模块的功能代码;
- 具体实现的功能如下所示:
1.通过按键切换运动模式:循迹模式和避障模式;
2.循迹模式:可沿着黑线行走;
3.避障模式:前方障碍物距离小于8cm,小车停止运动,后退左转(右转);
4.按键切换说明:
(1)KEYM用于切换运动模式,避障模式LEDM点亮,循迹模式模式LEDM熄灭;
(2)KEYS用于启动小车,小车启动LEDS点亮,小车停止LEDS熄灭;
6、项目总结
通过对本项目的学习,你已经掌握了基本硬件电路设计能力,原理图以及PCB绘制能力,软件代码功能调试能力,以及解决项目开发过程中遇到的各种问题的解决能力,当然,这些也只是项目开发的基本必备能力。学无止境,天道酬勤,只要通过不断勤奋的学习,我们就可以完成从一个技术小白到技术大佬的蜕变,立创开发板将会陪伴大家一步步完成各个阶段的蜕变。
当前阶段你已经基本掌握了独立开发一个项目的能力,期待优秀的你后续可以设计出有趣的个人项目,如果你愿意的话,也欢迎将你的个人项目开源到 硬件开源平台,供广大网友参考学习。
最后,如果你在学习本项目的过程中有任何疑问,可以在 立创开发板官网 进行询问,我们会有专门的技术人员为你解答问题和提供帮助。