[硬件]
元件选型
照片上传出问题了,改天补上,着急的可以看视频 视频链接在这里
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
要结合购买的元件模块设计原理图
比如
查看数据手册与参考手册确定引脚功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
根据元件特点布局
核心板的排母间距要注意!!!
比如:这种元件就要放到PCB边上
下单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文件
- 删除原来的:startup_stm32f10x_hd.s
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BbjxCZc-1646313963961)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113183940843.png)]
- 将startup_stm32f10x_md.s复制到工程文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdU25Kou-1646313963962)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113184742797.png)]
- 工程中添加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烧录 时候出现:
方法:
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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
}
LED.h 部分宏定义
#define LED PCout(13)
第二步
编译下载(如果没有运行,需要按复位 运行)
以上我们就完成基本测试,下面让我们学习一下,如何从零设计小车!!!<( ̄︶ ̄)↗[GO!]
小车设计
总体设计方案
总体的设计方案对完成项目非常重要,下面是小车的设计方案,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJgjG0yf-1646313963964)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113215641227.png)]
对小车的模组进行了,简单分类。这里简单介绍一下:
一辆’自动化’小车,要能够像人一样,有观察事物的眼睛,有处理事情的大脑,有可以跑动的腿,这里:
输入信号模块就像人类的眼睛,可以讲一些外界信息测量并送至’大脑’,比如超声波把距离信息发送给单片机。
执行模块就像人类的腿,可以根据’大脑’控制指令进行’运动’,比如舵机根据单片机指令旋转。
单片机就像人类的大脑,可以根据输入信号模块完成对执行模块的控制。
电源负责给整个系统供电。
OLED模块显示一些系统信息。
[硬件]系统硬件设计
主控:STM32单片机
使用:STM32f103c8t6最小系统板
选择原因:STM32F103C8T6价格较低,资源丰富可以满足项目要求,可以在其数据手册阅读资源介绍。
注意:
系统需要5V供电,可输出3.3V
OLED模块
使用:OLED显示屏模块 0.96寸 IIC/SPI
选择原因:价格较低、使用方便
注意:
这里使用 四管脚 顺序为 GND VCC SCL SDA,绘制PCB要注意顺序
供电为3.3V
陀螺仪
使用:MPU-6050模块 三轴加速度陀螺仪6DOF GY-521
原因:满足项目需要,使用方便
注意:
供电3V-5V
超声波测距模块
使用:HC-SR04 超声波测距模块
注意:
绘制PCB注意四个引脚顺序 Vcc Trig Echo Gnd
供电3.3V-5V
测距原理
不同模式
GPIO模式
红外循迹模块
使用:寻迹传感器 TCRT5000红外反射传感器
注意:
供电3.3V-5V
引脚顺序为: VCC GDN DO AO (DO表示数字输出,AO表示模拟输出)
来自TB的介绍
不完全总结就是:红外对管前面是黑色的时候,DO引脚为高电平,二极管熄灭状态。前面是红色的时候为低电平,二极管点亮。
蓝牙模块
使用:HC-05 主从机一体蓝牙串口透传模块
注意:
供电3.6V-6V
引脚顺序 VCC GND TXD RXD
按键
使用:这里按键使用PCB 元件
电机驱动
使用:TB6612FNG电机驱动模块
注意:
供电 比较复杂
来自淘宝的介绍
电机
使用:电机马达 DC3V-6V直流减速电机
注意:
供电3V-6V
电机要能够安装在小车车架上(这里使用的电机是小车车架套餐配套的)
舵机
使用:SG90 9g舵机 固定翼航模遥控飞机 180度舵机
注意:
供电4.8V-6V
需要控制角度,故购买180度 舵机
电源
使用:12v锂电池组18650充电带保护板大容量电瓶通用移动电源便携蓄电池
注意:
使用电池输出为12V
接口为DC5.5-2.1公母头
系统软件设计
点亮小灯
查看原理图
查阅原理图,小灯接在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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
}
LED.h 部分宏定义
#define LED PCout(13)
测试
编译下载(如果没有运行,需要按复位 运行)
电机驱动
由TB6612介绍得,通过控制AO和AO2高低电平可以控制AIN1和AIN2输出。
GPIO 高低电平控制AIN和BIN
- 查阅原理图AIN1、AIN2、BIN1、BIN2依次接在单片机的PB13、PB12、PB1、PB0
-
原理同GPIO输出高低电平见第二节 TB6612 GPIO驱动函数代码
void TB6612_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1);
}
相关宏定义 #define AIN1 PBout(13)
#define AIN2 PBout(12)
#define BIN1 PBout(1)
#define BIN2 PBout(0)
PWM控制PWMA和PWMB
将 PWM输出实验 的 timer 文件移植到我们前面点灯的工程中,更改驱动文件
-
查看原理图 PWMA 和PWMB依次连接PA11和PA8 -
查看 参考手册 关于定时器复用功能重映射的介绍(中文参考手册第119页) -
初始化外设
//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);
TIM_SetCompare1(TIM1,100);
TIM_SetCompare4(TIM1,1900);
通过软件仿真 逻辑分析仪观察波形输出、显示PWM波形 设置好仿真环境 打开逻辑分析仪 添加要观察的引脚 跳到设置对应程序位置,打开仿真 打开实时更新选项 调节观察分析仪 产生的如图方波就是一种PWM波 那么在程序哪里设置的这些参数那 时钟预分频数 决定了PWM 频率和周期 TIM1_PWM_Init(1999,359);
那么谁调节占空比那?
-
非常好理解、定时器的计数器向上计数就是越来越大。 -
PWM 模式我们可以看手册 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);
TIM_SetCompare1(TIM1,1500);
让小车跑一跑吧
小车电机线正确接法
错误接法
小车直行
void Forward(void)
{
AIN1 = 1;
AIN2 = 0;
BIN1 = 1;
BIN2 = 0;
TIM_SetCompare4(TIM1,1500);
TIM_SetCompare1(TIM1,1500);
}
小车后退
void Backward(void)
{
AIN1 = 0;
AIN2 = 1;
BIN1 = 0;
BIN2 = 1;
TIM_SetCompare4(TIM1,1500);
TIM_SetCompare1(TIM1,1500);
}
小车左转
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
}
舵机控制
查看原理图
芯片手册
使用上节移植的定时器三例程
不需要开启部分重映射,
初始化函数为
//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);
TIM_SetCompare1(TIM3,32);
delay_ms(900);
TIM_SetCompare1(TIM3,80);
delay_ms(900);
TIM_SetCompare1(TIM3,130);
delay_ms(900);
然后
按键与红外对管
按键外部中断实验
让我们先实现按键控制灯的亮灭
查看原理图
这里发现翻车,呜呜呜
由于C8T6小板子的PA12接了上拉电阻,所以使用PA12的时候要注意。而且如果我们用Mrico USB供电可能会影响PA11。
现在我们的原理图是这样的 KEY1-PA7 KEY2-PA12
配置按键端口模式
通过原理图知:KEY1(PA7)应该配置成下拉输入、上升沿触发。
KEY2(PA12)应该配置成上拉输入、下降沿触发。
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
配置中断线和配置外部通道
void KEY_EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7);
EXTI_InitStructure.EXTI_Line=EXTI_Line7;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
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);
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
相关宏定义 读取按键状态
#define KEY_1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)
#define KEY_2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)
编写响应中断函数
void EXTI9_5_IRQHandler(void)
{
delay_ms(10);
if(KEY_1 == 1)
{
LED =! LED;
EXTI_ClearITPendingBit(EXTI_Line7);
}
}
void EXTI15_10_IRQHandler(void)
{
delay_ms(10);
if(KEY_2 == 0)
{
LED =! LED;
EXTI_ClearITPendingBit(EXTI_Line12);
}
}
调用初始化函数
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
KEY_EXTIX_Init();
烧录调试
观察现象
红外对管硬件使用方法
详见:系统硬件设计->红外循迹模块
可以把红外对管看成’按键’,当前面有黑色时候为高电平,前面白色低电平。
红外对管的驱动
红外对管这里使用查询的方式,通过GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)函数获得对应端口的电平
查看原理图 红外对管依次连接 PB5 、PB4 、PB3 、PA15
红外管GPIO初始化
注意:这里我们需要使用的PB3、PB4、PA15是单片机的’特殊引脚
我们打开数据手册:STM32F103x8B_DS_CH_V10,在引脚定义章节,说明了复位后的主功能和默认复用功能以及重定义功能。
在参考手册:STM32中文参考手册_V10, 在8.3.5 JTAG/SWD复用功能重映射中,说明了引脚使用
所以我们需要关闭JTAG-DP 启用SW-DP ,我们重映射配置应写为GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
void TCRT5000_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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);
}
一些宏定义,利用函数读取电平
#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
u8 USART_RX_BUF[USART_REC_LEN];
u16 USART_RX_STA=0;
void uart_init(u32 bound){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
在main中定义标志位
int g_USART1_FLAG1 = 0; //串口控制标志位
在usart.h中声明变量
extern int g_USART1_FLAG1 ;
在中断服务函数添加处理
void USART1_IRQHandler(void)
{
u8 Res;
#if SYSTEM_SUPPORT_OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
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)
{
if(Res!=0x0a)USART_RX_STA=0;
else USART_RX_STA|=0x8000;
}
else
{
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
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与单片机连接
TTL插入电脑,使用串口助手->选择端口->更改波特率115200->发送数据
现象 发送A 或B 可以使小灯反转、发送其他命令无现象。
配置蓝牙
更改蓝牙波特率
见硬件蓝牙介绍
我们在AT模式下设置发送AT指令:AT+UART=115200,0,0
测试蓝牙
断电重启蓝牙,更改软件波特率为115200,打开手机蓝牙与HC-05配对 (密码:1234)
使用蓝牙调试器(应用商店下载即可),发送aa 观察电脑串口软件
手机APP-蓝牙调试器的设置方法
调试成功 :蓝牙软件和串口软件能够通讯
练一练–蓝牙控制小灯
连接如图
通过发送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 ;
在中断服务函数添加处理
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
跳线帽改至蓝牙
手机连接蓝牙 使用蓝牙调试器发送 A 或者 B
现象:发送A 小车直行、发送B小车停止。
练一练–蓝牙控制小车运动
USART中断服务函数
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);
}
}
手机中蓝牙调试助手的设计
练一练–把数据发送给电脑串口助手和手机APP
前面我们介绍了,如何通过电脑或者蓝牙APP,向单片机发送数据,下面我们介绍如何:单片机如何向电脑和蓝牙APP发送数据。
库函数提供了相关串口函数,但是每次只能发送一个字节
USART_SendData(USART1,'X');
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
在正点原子例程中完成了对printf的重映射,所以我们可以轻松的通过printf ()函数向串口1 发送不定长数据,这是正点原子的例程
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);
USART1->DR = (u8) ch;
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显示数据
OLED显示
找资料
链接:
0.96寸(4管脚)资料下载链接:
https://pan.baidu.com/s/1J57Izsv-PKmbwVrA2ynDzg 提取码:vktz
测试例程-现象正常-更改引脚-现象正常-移植到自己的工程
拷贝移植文件
一般移植传感器的xxx.c 和xxx.h
复制相关文件到工程
KEIL中添加文件
添加头文件路径
对比程序移植
根据原理图更改初始化函数
SCL–PC14
SDA–PC15
修改OLED_Init() 函数
void OLED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
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;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_15|GPIO_Pin_14);
delay_ms(800);
OLED_WR_Byte(0xAE,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x10,OLED_CMD);
OLED_WR_Byte(0x40,OLED_CMD);
OLED_WR_Byte(0xB0,OLED_CMD);
OLED_WR_Byte(0x81,OLED_CMD);
OLED_WR_Byte(0xFF,OLED_CMD);
OLED_WR_Byte(0xA1,OLED_CMD);
OLED_WR_Byte(0xA6,OLED_CMD);
OLED_WR_Byte(0xA8,OLED_CMD);
OLED_WR_Byte(0x3F,OLED_CMD);
OLED_WR_Byte(0xC8,OLED_CMD);
OLED_WR_Byte(0xD3,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xD5,OLED_CMD);
OLED_WR_Byte(0x80,OLED_CMD);
OLED_WR_Byte(0xD8,OLED_CMD);
OLED_WR_Byte(0x05,OLED_CMD);
OLED_WR_Byte(0xD9,OLED_CMD);
OLED_WR_Byte(0xF1,OLED_CMD);
OLED_WR_Byte(0xDA,OLED_CMD);
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);
OLED_WR_Byte(0x30,OLED_CMD);
OLED_WR_Byte(0x8D,OLED_CMD);
OLED_WR_Byte(0x14,OLED_CMD);
OLED_WR_Byte(0xAF,OLED_CMD);
}
修改oled.h中的宏
#define OLED_SCLK_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_14)
#define OLED_SCLK_Set() GPIO_SetBits(GPIOC,GPIO_Pin_14)
#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_15)
#define OLED_SDIN_Set() GPIO_SetBits(GPIOC,GPIO_Pin_15)
#define OLED_CMD 0
#define OLED_DATA 1
调用初始化函数
OLED_Init();
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是嘛
百度百科介绍:
我们知道万用表 电压表可以测量电池,或者电路电压。那么我们是否可以通过单片机获得电压,方便我们监控电池状态
如何测量我们的锂电池电压那?锂电池电压12V左右,单片机ADC最大测量电压3.3V,这里我们需要分压电路分压。
通过测量ADC点的电压就可以计算VBAT_IN的电压。
移植程序
拷贝文件
在adc.c的程序
#include "adc.h"
#include "delay.h"
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
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_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
u16 Get_Adc(u8 ch)
{
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
return ADC_GetConversionValue(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);
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输入捕获
查看数据手册
初始化定时器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);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);
TIM_Cmd(TIM2,ENABLE );
}
u8 TIM5CH1_CAPTURE_STA=0;
u16 TIM5CH1_CAPTURE_VAL;
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)
{
if(TIM5CH1_CAPTURE_STA&0X40)
{
TIM5CH1_CAPTURE_STA|=0X80;
TIM5CH1_CAPTURE_VAL=TIM_GetCapture2(TIM2);
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising);
}else
{
TIM5CH1_CAPTURE_STA=0;
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0);
TIM5CH1_CAPTURE_STA|=0X40;
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling);
}
}
}
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);
完成测距的函数
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;
}
使用串口助手查看结果
练一练–编写定距离跟随功能
功能:根据根据超声波测量距离跟随前方物体
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);
整体逻辑
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);
}
}
}
} }
综合一下-缝合上面练一练的功能
功能:
- 小车具有红外对管循迹、蓝牙遥控、定距离跟随、避障运动模式
- 可以通过小车按键和APP进行切换小车的运动模式。
- APP与OLED显示小车所处模式和超声波测量值、电池电压。
实现切换功能必须
main中的循环
while(1)
{
sprintf((char *)string,"Distance:%d ",TCRT5000_Dist());
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;
}
}
|