STM32------智能小车
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
其余接线参考51单片机小车项目。
1.让小车动起来
motor.c
c
#include "motor.h"
void goForward(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goBack(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
void goLeft(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goRight(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void stop(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
motor.h
c
#ifndef __MOTOR_H__
#define __MOTOR_H__
#include "main.h"
void goForward(void);
void goBack(void);
void goLeft(void);
void goRight(void);
void stop(void);
#endif
main.c
C
#include "motor.h"
//main函数的while循环部分:
while (1)
{
/* USER CODE END WHILE */
goForward();
HAL_Delay(1000);
goBack();
HAL_Delay(1000);
goLeft();
HAL_Delay(1000);
goRight();
HAL_Delay(1000);
stop();
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
2.串口控制小车
uart.c
c
#include "string.h"
#include "stdio.h"
#include "motor.h"
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
#define SIZE 12
char buffer[SIZE];
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
// 灯控指令
if(!strcmp(UART1_RX_Buffer, "M1"))
goForward();
else if(!strcmp(UART1_RX_Buffer, "M2"))
goBack();
else if(!strcmp(UART1_RX_Buffer, "M3"))
goLeft();
else if(!strcmp(UART1_RX_Buffer, "M4"))
goRight();
else
stop();
memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
UART1_RX_STA = 0;
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
main.c
c
#include "motor.h"
extern uint8_t buf;
//main函数
HAL_UART_Receive_IT(&huart1, &buf, 1);
3.点动控制小车
uart.c
c
if (!strcmp(UART1_RX_Buffer, "M1"))
{
goForward();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M2"))
{
goBack();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M3"))
{
goLeft();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M4"))
{
goRight();
HAL_Delay(10);
}
else
stop();
mian.c
c
// main函数里
HAL_NVIC_SetPriority(SysTick_IRQn,0,0); //或者通过cubeMX配置
while(1)
{
stop();
}
4.硬件PWM调速
硬件接线
B-1A -- PA0
B-1B -- PB1
A-1A -- PA1
A-1B -- PB10
其余接线参考上官一号小车项目。
main.c
c
// main函数里
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
while (1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15);
HAL_Delay(1000);
}
5.左右轮各自调速
main.c
c
// main函数里
while (1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
HAL_Delay(1000);
}
6.循迹小车
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
循迹模块(左) -- PB3
循迹模块(右) -- PB4
c
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
// main函数里
while (1)
{
if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
goForward();
if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
goLeft();
if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
goRight();
if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
stop();
}
7.循迹小车解决转弯平滑问题
硬件接线
B-1A -- PA0
B-1B -- PB1
A-1A -- PA1
A-1B -- PB10
循迹模块(左) -- PB3
循迹模块(右) -- PB4
c
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
// main函数里
while (1)
{
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19);
}
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
}
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
}
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0);
}
}
8.跟随小车
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
跟随模块(左) -- PB5
跟随模块(右) -- PB6
c
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)
// main函数里
while (1)
{
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
goRight();
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
goLeft();
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
stop();
}
9.摇头避障小车
硬件接线
sg90 -- PB9
sg90.c
c
#include "sg90.h"
#include "gpio.h"
#include "tim.h"
void initSG90(void)
{
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgMiddle(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgRight(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
}
void sgLeft(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度
}
SG90.h
c
#ifndef __SG90_H__
#define __SG90_H__
void initSG90(void);
void sgMiddle(void);
void sgRight(void);
void sgLeft(void);
#endif
main.c
c
initSG90();
HAL_Delay(1000);
while (1)
{
sgLeft();
HAL_Delay(1000);
sgMiddle();
HAL_Delay(1000);
sgRight();
HAL_Delay(1000);
sgMiddle();
HAL_Delay(1000);
}
封装超声波传感器
超声波模块:
Trig -- PB7
Echo -- PB8
c
#include "sr04.h"
#include "gpio.h"
#include "tim.h"
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
double get_distance(void)
{
int cnt=0;
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);//拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);//拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
return (cnt*340/2*0.000001*100); //单位:cm
}
sr04.h
c
#ifndef __SR04_H__
#define __SR04_H__
double get_distance(void);
#endif
main.c
C
while (1)
{
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(300);
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
}
else
{
//停止
//测左边距离
sgLeft();
HAL_Delay(300);
disLeft = get_distance();
sgMiddle();
HAL_Delay(300);
sgRight();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
}
}
封装电机驱动
硬件接线
与 "让小车动起来" 完全一样
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
C
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(300);
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
goForward();
}else if(disMiddle < 10){
goBack();
}else
{
//停止
stop();
//测左边距离
sgLeft();
HAL_Delay(300);
disLeft = get_distance();
sgMiddle();
HAL_Delay(300);
sgRight();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
if(disLeft < disRight){
goRight();
HAL_Delay(150);
stop();
}
if(disRight < disLeft){
goLeft();
HAL_Delay(150);
stop();
}
}
HAL_Delay(50);
}
10.小车测速
硬件接线
测速模块:
VCC -- 3.3V 不能接5V,否则遮挡一次会触发3次中断
OUT -- PB14
c
unsigned int speedCnt;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_14)
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
speedCnt++;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
printf("speed: %d\r\n", speedCnt);
speedCnt = 0;
}
main函数里:
HAL_TIM_Base_Start_IT(&htim2);
11.串口控制小车并使用Oled显示速度
硬件接线
SCL -- PB6
SDA -- PB7
封装Oled模块
12.Wi-Fi测速小车并本地Oled显示
硬件接线
把esp8266插进串口1
13.语音控制小车
硬件接线
循迹小车:
循迹模块(左) -- PB3
循迹模块(右) -- PB4
跟随小车:
跟随模块(左) -- PA8
跟随模块(右) -- PA9
避障小车:
sg90:PB9
Trig:PA10
Echo:PA11
OLED****模块:
SCL -- PB6SDA -- PB7
语音模块:
A25 -- PA15 (跟随)
A26 -- PA13 (避障)
A27 -- PA14 (循迹)
C
上官二号-STM32F1单片机教程_2022版(良许)
此教程以动手为主,解决大伙学了半天单片机不知道干什么的问题。
上官二号(小朋友)涉及的内容和知识以小项目为基本单元(暂规划如下)
小项目做啥就先讲啥,有目标有趣地来一起学习单片机
喜欢不?
课程要求:C语言熟练,如果C语言不好,可以学习上官老师录制的C语言课程。另外,最好提前学完
C51 课程。
课程特点:不会很正经,不会很学术,不会很理论,不喜勿入!
一、开发环境的安装
编程语言:C语言
需要安装的软件有两个:Keil5 和 STM32CubeMX
/* 01. 电动车报警器 ====》 IO控制入门 */
/* 02. 感应开关盖垃圾桶 ====》 定时器,PWM开发,超声波 */
/* 03. 基于wifi的智能控制插座 =====》 串口开发,ESP8266模块AT控制指令学习,中断学习*/
/* 04. 基于蓝牙HC-05的智能控制插座 =====》 串口开发,蓝牙穿透*/
/* 05. 基于4G的智能控制插座 =====》 串口开发,蓝牙穿透*/
/* 06. 温湿度检测系统 ======》 DS18B20单线协议,如何看时序图,IIC协议液晶屏显示,SPI协议液晶显示
*/
/* 07. 语音控制开关灯 ======》 语音模块二次开发 */
/* 08. 智能小车_远程控制/壁障/寻迹/数据采集等 ======》 综合性项目 */
Keil5 的安装
使用 Keil4 写 STM32 代码其实也是可以,但需要很复杂的配置,不建议新手操作。
比较推荐 Keil5 编写 STM32 ,只需要一些简单的设置就可以上手,对新手友好。
安装
安装包(不需要太新,本课程以 MDK324 为例,最新的 MDK327 有问题)
安装过程一路下一步即可(建议不要安装在 C 盘)
安装路径一定不要有中文或空格!!(重要)
Keil5 安装完之后,记得安装 F1 固件包
破姐
使用
编程与编译过程与 Keil4 完全一样
STM32F1 模板工程
如何下载程序到上官二号
烧录工具有很多种,比如:串口、J-Link、ST-Link、U-Link 等等,本教程使用 ST-Link。
安装驱动
官网下载(慢)https://www.st.com/en/development-tools/stsw-link009.html
资料包
接线
配置
STM32CubeMX 的安装
作用
通过界面的方式,快速生成工程文件。
下载
官网(慢)https://www.st.com/zh/development-tools/stm32cubemx.html#overview
资料包
安装
一路下一步,建议不要安装在C盘
配置
更新固件包位置(比较大,默认在C盘,可以更改到其它盘)
help ---> update settings --> Firmware Repository
使用STM32CubeMX生成工程文件
1. 点击「ACCESS TO MCU SELECTOR」;
2. 左上角搜索对应的芯片,并在右侧双击对应的芯片;
3. 点击芯片对应的引脚,并进行配置;
4. 配置工程名称及位置:
1. 按下图配置 Coder Generator :
6. 点击右上角 generate code :
7. 点击 Open Project 即可调用 Keil5 打开自动生成的工程文件。
二、初识STM32单片机
什么是单片机?
单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器
CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包
括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个
小而完善的微型计算机系统,在工业控制领域广泛应用。
STM系列单片机命名规则
ST -- 意法半导体
M -- Microelectronics 微电子
32 -- 总线宽度
项目 介绍
内核 Cortex-M3
Flash 64K x 8bit
SRAM 20K x 8bit
GPIO 37个GPIO,分别为PA0-PA15、PBO-PB15、PC13-PC15、PDO-PD1
ADC
2个12bit ADC合计12路通道,外部通道: PAO到PA7+PBO到PB1内部通道: 温度传感器通道
ADC Channel 16和内部参考电压通道ADC Channel 17
定时器/
计数器
4个16bit定时器/计数器,分别为TIM1、TIM2、TIM3、TIM4TM1带死区插入,常用于产生
PWM控制电机
看门狗
定时器 2个看门狗定时器 (独立看门狗IWDG、窗口看门狗WWDG)
滴答定
时器
1个24bit向下计数的滴答定时器systick
工作电
压、温
度
2V~3.6V、-40°C~85°C
通信串
口
2 * IIC,2 * SPI,3 * USART,1 * CAN
STM32F103C8T6单片机简介
项目 介绍
系统时
钟
内部8MHz时钟HSI最高可倍频到64MHZ,外部8MHZ时钟HSE最高可倍频到72MHZ
标准库与HAL库区别
1. 寄存器
寄存器众多,需要经常翻阅芯片手册,费时费力;
更大灵活性,可以随心所欲达到自己的目的;
深入理解单片机的运行原理,知其然更知其所以然。
2. 标准库
将寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用
每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx...之类的;
配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能;
大大降低单片机开发难度,但是在不同芯片间不方便移植。
3. HAL库
ST公司目前主力推的开发方式,新的芯片已经不再提供标准库;
为了实现在不同芯片之间移植代码;
为了兼容所有芯片,导致代码量庞大,执行效率低下。
三、通用输入输出端口GPIO
什么是GPIO?
定义
GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚STM32芯片的GPIO引脚与外部设备连
接起来,从而实现与外部通讯、控制以及数据采集的功能。
简单来说我们可以控制GPIO引脚的电平变化,达到我们的各种目的。
命名规则
组编号+引脚编号
组编号:GPIOA, GPIOB, GPIOC, GPIOD .. GPIOG
引脚编号:0,1,2,3,4...15
组合起来:
PA0, PA1, PA2 .. PA15
PB0, PB1, PB2 .. PB15
PC0, PC1, PC2 .. PC15
...
有一些特殊功能的引脚是不能用作IO的。
内部框架图
下图来源于官方参考手册,了解即可。
推挽输出与开漏输出
内部结构图
推挽输出: 可以真正能真正的输出高电平和低电平
开漏输出: 开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动
如何点亮一颗LED灯
标号一样的导线在物理上是连接在一起的。
将PB8或PB9拉低,就可以实现将对应的LED灯点亮。
编程实现点灯
常用的GPIO HAL库函数:
结构体 GPIO_InitTypeDef 定义:
按键点亮LED灯(轮询法)
输入(按键):
KEY1:PA0
KEY2:PA1
输出(LED灯):
LED1:PB8
LED2:PB9
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState
PinState);
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
typedef struct
{
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
} GPIO_InitTypeDef;
#define KEY_ON 0
#define KEY_OFF 1
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
if( HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET)
{
/* 按键按下 */
return KEY_ON;
}
else
{
/* 按键松开 */
while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET);
return KEY_OFF;
}
}
四、复位和时钟控制(RCC)
复位
系统复位
当发生以下任一事件时,产生一个系统复位:
1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位
电源复位
当以下事件中之一发生时,产生电源复位:
1. 上电/掉电复位(POR/PDR复位)
2. 从待机模式中返回
备份区复位
备份区域拥有两个专门的复位,它们只影响备份区域。
当以下事件中之一发生时,产生备份区域复位。
1. 软件复位,备份区域复位可由设置备份域控制寄存器 (RCC_BDCR)(见6.3.9节)中的BDRST位
产生。
2. 在VDD和VBAT两者掉电的前提下,VDD或VBAT上电将引发备份区域复位。
时钟控制
什么是时钟?
时钟打开,对应的设备才会工作。
时钟来源
三种不同的时钟源可被用来驱动系统时钟(SYSCLK)
HSI振荡器时钟(高速内部时钟)
HSE振荡器时钟(高速外部时钟)
while (1)
{
/* USER CODE END WHILE */
if(Key_Scan(GPIOA,GPIO_PIN_0) == KEY_ON)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
if(Key_Scan(GPIOA,GPIO_PIN_1) == KEY_ON)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
/* USER CODE BEGIN 3 */
}
PLL时钟(锁相环倍频时钟)
二级时钟源:
40kHz低速内部RC(LSIRC)振荡器
32.768kHz低速外部晶体(LSE晶体)
如何使用CubeMX配置时钟
五、中断和事件
中断概述
什么是中断?
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入
处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
什么是EXTI?
外部中断/事件控制器(EXTI)管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一个边沿检测
器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可以实现对每个中断/事件线进行单独配置,可
以单独配置为中断或者事件,以及触发事件的属性。
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而
产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。
EXTI初始化结构体:
typedef struct
{
//中断/事件线
uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled or
disabled.
This parameter can be any combination value
of @ref EXTI_Lines */
//EXTI 模式
EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.
This parameter can be a value of @ref
EXTIMode_TypeDef */
//触发类型
EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge
for the EXTI lines.
This parameter can be a value of @ref
EXTITrigger_TypeDef */
//EXTI 控制
FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected
EXTI lines.
This parameter can be set either to ENABLE
or DISABLE */
}EXTI_InitTypeDef;
中断/事件线:
#define EXTI_Line0 ((uint32_t)0x00001) /*!< External interrupt line 0 */
#define EXTI_Line1 ((uint32_t)0x00002) /*!< External interrupt line 1 */
#define EXTI_Line2 ((uint32_t)0x00004) /*!< External interrupt line 2 */
#define EXTI_Line3 ((uint32_t)0x00008) /*!< External interrupt line 3 */
#define EXTI_Line4 ((uint32_t)0x00010) /*!< External interrupt line 4 */
#define EXTI_Line5 ((uint32_t)0x00020) /*!< External interrupt line 5 */
#define EXTI_Line6 ((uint32_t)0x00040) /*!< External interrupt line 6 */
#define EXTI_Line7 ((uint32_t)0x00080) /*!< External interrupt line 7 */
#define EXTI_Line8 ((uint32_t)0x00100) /*!< External interrupt line 8 */
#define EXTI_Line9 ((uint32_t)0x00200) /*!< External interrupt line 9 */
#define EXTI_Line10 ((uint32_t)0x00400) /*!< External interrupt line 10 */
#define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11 */
#define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12 */
#define EXTI_Line13 ((uint32_t)0x02000) /*!< External interrupt line 13 */
#define EXTI_Line14 ((uint32_t)0x04000) /*!< External interrupt line 14 */
#define EXTI_Line15 ((uint32_t)0x08000) /*!< External interrupt line 15 */
#define EXTI_Line16 ((uint32_t)0x10000) /*!< External interrupt line 16
Connected to the PVD Output */
#define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17
Connected to the RTC Alarm event */
#define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18
Connected to the USB OTG FS Wakeup from suspend event */
#define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19
Connected to the Ethernet Wakeup event */
#define EXTI_Line20 ((uint32_t)0x00100000) /*!< External interrupt line 20
Connected to the USB OTG HS (configured in FS) Wakeup event */
#define EXTI_Line21 ((uint32_t)0x00200000) /*!< External interrupt line 21
Connected to the RTC Tamper and Time Stamp events */
#define EXTI_Line22 ((uint32_t)0x00400000) /*!< External interrupt line 22
Connected to the RTC Wakeup event */
EXTI模式:
typedef enum
{
EXTI_Mode_Interrupt = 0x00, //产生中断
EXTI_Mode_Event = 0x04 //产生事件
}EXTIMode_TypeDef;
触发类型:
typedef enum
{
EXTI_Trigger_Rising = 0x08, //上升沿
EXTI_Trigger_Falling = 0x0C, //下降沿
EXTI_Trigger_Rising_Falling = 0x10 //上升沿和下降沿都触发
}EXTITrigger_TypeDef;
EXTI控制:
使能 EXTI ,一般都是使能, ENABLE
什么是优先级?
抢占优先级和响应优先级的区别:
高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
什么是优先级分组?
Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把指定中断优
先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第0组:所有4位用于指定响应优先级
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级
第4组:所有4位用于指定抢占式优先级
什么是NVIC?
STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断的管理 。NVIC是属于
Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK不是由NVIC控制的。
什么是中断向量表?
每个中断源都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中
断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程序入口的跳转指令构成
一张表,称为中断向量跳转表。
按键点亮LED灯(中断法)
1. 配置时钟
2. 配置GPIO口
3. 使能中断
4. 配置工程
项目一:电动车报警器
项目需求
点击遥控器 A 按键,系统进入警戒模式,一旦检测到震动(小偷偷车),则喇叭发出声响报警,吓退小偷。
点击遥控器 B 按键,系统退出警戒模式,再怎么摇晃系统都不会报警,否则系统一直发出尖叫,让车主尴
尬。
项目框图
typedef struct
{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority; //抢断优先级
uint8_t NVIC_IRQChannelSubPriority; //响应优先级
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
硬件清单
振动传感器
继电器
高功率喇叭
433M无线接收发射模块
杜邦线
a. 振动传感器介绍及实战
振动传感器介绍
单片机供电VCC GND接单片机
产品不震动,输出高电平,模块上的DO口
产品震动,输出低电平,绿色指示灯亮
AO口不用
编程实现
需求:当振动传感器接收到振动信号时,使用中断方式点亮LED1。
如果直接在中断服务函数里调用 HAL_Delay 函数,则会造成系统卡死。
原因:程序初始化时默认把滴答定时器的中断优先级设为最低,其它中断源很容易打断它导致卡死。
解决:在 main 函数里使用以下函数提高滴答定时器的中断优先级(提升至0):
//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//一根中断线上接有多个中断源,判断中断请求是否来自PA4
if(GPIO_Pin == GPIO_PIN_4)
{
//如果检测到PA4被拉低
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET)
{
//则点亮LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
//延时1秒
HAL_Delay(1000);
//关闭LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}
else
{
//未检测到PA4被拉低,则关闭LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}
}
}
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
并且将 EXTI4 的中断优先级设置比滴答定时器的中断优先级高,比如 2 。
b. 继电器介绍及实战
继电器工作原理
单片机供电VCC GND接单片机,VCC需要接3.3V,5V不行!
最大负载电路交流250V/10A,直流30V/10A
引脚 IN 接收到低电平时,开关闭合。
编程实现
c. 433M无线发射接收模块介绍及实战
433M无线发射接收模块介绍
单片机供电VCC GND接单片机
接收到信号,接收模块对应针脚输出高电平
有D0 D1 D2 D3,对应遥控器的ABCD
编程实现
需求:按下遥控器A按键,LED1亮1秒;按下遥控器B按键,LED2亮1秒。
D0 -- PA5
D1 -- PA6
//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
// 如果检测到PA5被拉高(按键A被按下)
项目设计及实现
项目设计
//如果检测到PA4被拉低(小偷偷车),并且警报模式打开
//则将PB7拉低,继电器通电,喇叭一直响
// 如果检测到PA5被拉高(按键A按下),设定为开启警报模式
// 则将PB7拉低(喇叭响),2秒后恢复电平(喇叭不响),表示进入警报模式
// 同时将标志位设置为ON
// 如果检测到PA6被拉高(按键B按下),设定为关闭警报模式
// 则将PB7拉低(喇叭响),1秒后恢复电平(喇叭不响),表示关闭警报模式
// 同时将标志位设置为OFF
case GPIO_PIN_5:
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET)
{
//则点亮LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}
else
{
//如果未检测到PA5,则关闭LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
}
break;
// 如果检测到PA6被拉高(按键B按下)
case GPIO_PIN_6:
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET)
{
//则点亮LED2
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
}
else
{
//如果未检测到PA4,则关闭LED1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
}
break;
}
}
编程实现
#define J_ON 1
#define J_OFF 0
//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static int mark = J_OFF;
switch(GPIO_Pin)
{
case GPIO_PIN_4:
//如果检测到PA4被拉低(小偷偷车),并且警报模式打开
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET && mark ==
J_ON)
{
//则将PB7拉低,继电器通电,喇叭一直响
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
}
break;
// 如果检测到PA5被拉高(按键A按下),设定为开启警报模式
case GPIO_PIN_5:
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET)
{
六、定时器Timer
定时器介绍
软件定时
缺点:不精确、占用CPU资源
定时器工作原理:
使用精准的时基,通过硬件的方式,实现定时功能。定时器核心就是计数器。
// 则将PB7拉低(喇叭响),2秒后恢复电平(喇叭不响),表示进入警报模式
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
// 同时将标志位设置为ON
mark = J_ON;
}
break;
// 如果检测到PA6被拉高(按键B按下),设定为关闭警报模式
case GPIO_PIN_6:
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET)
{
// 则将PB7拉低(喇叭响),1秒后恢复电平(喇叭不响),表示关闭警报模式
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
// 同时将标志位设置为OFF
mark = J_OFF;
}
break;
}
}
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
定时器分类:
基本定时器(TIM6~TIM7)
通用定时器(TIM2~TIM5)
高级定时器(TIM1和TIM8)
STM32F103C8T6定时器资源:
通用定时器介绍:
1) 16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
2) 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数
值。
3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C.PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电
路。
5)如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
定时器计数模式:
定时器时钟源:
定时器溢出时间计算公式:
例如,要定时500ms,则:PSC=7199,ARR=4999,Tclk=72M
定时器中断实验
需求:使用定时器中断方法,每500ms翻转一次LED1灯状态。
1. RCC配置
2. LED1灯配置
3. 时钟数配置
4. TIM2配置
5. 工程配置
6. 重写更新中断回调函数
7. 启动定时器
在main.c中,在定时器初始化命令之后加入以下代码:
PWM介绍
STM32F103C8T6 PWM资源:
高级定时器(TIM1):7路
通用定时器(TIM2~TIM4):各4路
PWM输出模式:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
}
HAL_TIM_Base_Start_IT(&htim2);
PWM模式1:在向上计数时,一旦 CNT < CCRx 时输出为有效电平,否则为无效电平; 在向下计数
时,一旦 CNT > CCRx 时输出为无效电平,否则为有效电平。
PWM模式2:在向上计数时,一旦 CNT < CCRx 时输出为无效电平,否则为有效电平; 在向下计数
时,一旦 CNT > CCRx 时输出为有效电平,否则为无效电平。
PWM周期与频率:
PWM占空比:
由TIMx_CCRx寄存器决定。
PWM实验
需求:使用PWM点亮LED1实现呼吸灯效果。
LED灯为什么可以越来越亮,越来越暗?
这是由不同的占空比决定的。
如何计算周期/频率?
假如频率为 2kHz ,则:PSC=71,ARR=499
LED1连接到哪个定时器的哪一路?
学会看产品手册:
开始实战!
1. 设置时钟
2. 设置定时器
记得把极性设置为Low,因为LED灯是低电平才亮。
3. 配置工程
4. 业务代码
项目二:感应开关盖垃圾桶
项目需求
检测靠近时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
发生震动时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
按下按键时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
项目框图
// 定义变量
uint16_t pwmVal=0; //调整PWM占空比
uint8_t dir=1; //设置改变方向。1:占空比越来越大;0:占空比越来越小
// 使能 Timer4 第3通道 PWM 输出
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
// while循环实现呼吸灯效果
while (1)
{
HAL_Delay(1);
if (dir)
pwmVal++;
else
pwmVal--;
if (pwmVal > 500)
dir = 0;
if (pwmVal == 0)
dir =1;
//修改比较值,修改占空比
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
}
硬件清单
SG90舵机,超声波模块,震动传感器,蜂鸣器
a. sg90舵机介绍及实战
sg90舵机介绍
PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右。
确定周期/频率
如果周期为20ms,则 PSC=7199,ARR=199
角度控制
0.5ms-------------0度; 2.5% 对应函数中CCRx为5
1.0ms------------45度; 5.0% 对应函数中CCRx为10
1.5ms------------90度; 7.5% 对应函数中CCRx为15
2.0ms-----------135度; 10.0% 对应函数中CCRx为20
2.5ms-----------180度; 12.5% 对应函数中CCRx为25
编程实现
需求:
每隔1s,转动一个角度:0度 --> 45度 --> 90度 --> 135度 --> 180度 --> 0度
接线:
代码:
b. 超声波传感器介绍及实战
超声波传感器介绍
怎么让它发送波
Trig ,给Trig端口至少10us的高电平
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
while (1)
{
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 5);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 10);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 15);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 20);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 25);
}
怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离
距离 = 速度 (340m/s)* 时间/2
编程实战
需求:
使用超声波测距,当手离传感器距离小于5cm时,LED1点亮,否则保持不亮状态。
接线:
Trig --- PB6
Echo --- PB7
LED1 --- PB8
定时器配置:
使用 TIM2 ,只用作计数功能,不用作定时。
将 PSC 配置为71,则计数 1 次代表 1us 。
编写微秒级函数:
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
主函数:
//1. Trig ,给Trig端口至少10us的高电平
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
//3. 由高电平跳转回低电平,表示波回来了
//波回来的那一下,我们开始停止定时器
//4. 计算出中间经过多少时间
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
//每500毫秒测试一次距离
int cnt;
float distance;
while (1)
{
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);//拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
distance = cnt*340/2*0.000001*100; //单位:cm
if(distance < 5)
项目设计及实现
项目设计
超声波模块:
Trig -- PB6
Echo -- PB7
sg90舵机:
PWM -- PB9
按键:
KEY1 -- PA0
LED灯:
LED1 -- PB8
震动传感器:
D0 -- PB5
VCC -- 5V
蜂鸣器:
IO -- PB4
VCC -- 3V3
项目实现
七、串口
串口介绍
参见以下视频:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
//每500毫秒测试一次距离
HAL_Delay(500);
}
常用函数介绍
串口发送/接收函数:
HAL_UART_Transmit(); 串口发送数据,使用超时管理机制
HAL_UART_Receive(); 串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT(); 串口中断模式发送
HAL_UART_Receive_IT(); 串口中断模式接收
作用:以阻塞的方式发送指定字节的数据
形参 1 :UART_HandleTypeDef 结构体类型指针变量
形参 2:指向要发送的数据地址
形参 3:要发送的数据大小,以字节为单位
形参 4:设置的超时时间,以ms单位
作用:以中断的方式接收指定字节的数据
形参 1 是 UART_HandleTypeDef 结构体类型指针变量
形参 2 是指向接收数据缓冲区
形参 3 是要接收的数据大小,以字节为单位
此函数执行完后将清除中断,需要再次调用以重新开启中断。
串口中断回调函数:
HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //发送中断回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收中断回调函数
状态标记变量:
USART_RX_STA
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size)
从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK时候(回车和换行符号来的时
候),那么 USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main函数里面可以处理数据
了。
串口接收中断流程
串口实验(非中断)
需求:
接受串口工具发送的字符串,并将其发送回串口工具。
硬件接线:
TX -- A10
RX -- A9
一定要记得交叉接线!!
串口配置:
1. 选定串口
2. 选择模式
异步通讯
3. 串口配置
4. 使用MicroLIB库
从魔术棒打开,这个勾勾一定要打上,否则 printf 无法重映射!
编程实现:
串口实验(中断)
需求:
通过中断的方法接受串口工具发送的字符串,并将其发送回串口工具。
#include <stdio.h>
#include <string.h>
unsigned char ch[20] = {0};
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
main函数里:
unsigned char ch[20] = {0};
HAL_UART_Transmit(&huart1, "hello world\n", strlen("hello world\n"), 100);
while(1)
{
HAL_UART_Receive(&huart1, ch, 19, 100);
//HAL_UART_Transmit(&huart1, ch, strlen(ch), 100);
printf(ch);
memset(ch, 0, strlen(ch));
}
硬件接线:
同上
串口配置:
前4步同上
5. 打开中断
编程实现:
#include <stdio.h>
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
main函数部分
HAL_UART_Receive_IT(&huart1, &buf, 1);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//判断判断串口是否接收完成
if(UART1_RX_STA & 0x8000)
{
printf("收到数据:");
// 将收到的数据发送到串口
HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);
// 等待发送完成
项目三:蓝牙插座风扇灯
项目需求
通过蓝牙模块,实现手机控制蓝牙插座/风扇/灯。
本质:
1. 采用蓝牙的透传功能;
2. 控制 IO 口的输出。
项目框图
硬件清单
HC01蓝牙模块
CH340
杜邦线
项目设计及实现
while(huart1.gState != HAL_UART_STATE_READY);
printf("\r\n");
// 重新开始下一次接收
UART1_RX_STA = 0;
}
printf("hello liangxu\r\n");
HAL_Delay(1000);
}
项目设计
HC01_TX -- RX1
HC01_RX -- TX1
项目实现
1. 串口非中断法
2. 串口中断法
项目四:Wi-Fi插座风扇灯
项目需求
通过ESP8266模块,实现手机控制wifi插座/风扇/灯。
项目框图
HAL_UART_Receive(&huart1, ch, 19, 100);
//HAL_UART_Transmit(&huart1, ch, strlen(ch), 100);
//printf((char *)ch);
printf("%s", ch);
if (!strcmp((const char *)ch, "open")) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)
printf("LED1已打开\n");
}else if(!strcmp((const char *)ch, "close")) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)
printf("LED1已关闭\n");
} else {
if(ch[0] != '\0')
printf("指令发送错误:%s", ch);
}
printf("收到数据:");
if (!strcmp((const char *)UART1_RX_Buffer, "open")) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)
printf("LED1已打开\n");
}else if(!strcmp((const char *)UART1_RX_Buffer, "close")) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)
printf("LED1已关闭\n");
} else {
if(UART1_RX_Buffer[0] != '\0')
printf("指令发送错误:%s", UART1_RX_Buffer);
}
硬件清单
ESP8266模块
CH340
杜邦线
项目设计及实现
项目设计
串口1用于与ESP8266通讯,串口2连接PC,用于打印log,查看系统状态。
项目实现
注意:
1. 工作中一般不直接在中断服务函数里处理数据,而是在收到数据后直接丢给队列,再处理数据;
2. 在中断服务函数里尽量减少使用延时函数及打印函数。
AP模式:
#define SIZE 12
char buffer[SIZE];
char LJWL[] = "AT+CWJAP=\"TP-LINK_3E30\",\"18650711783\"\r\n"; //入网指令
char LJFWQ[] = "AT+CIPSTART=\"TCP\",\"192.168.0.130\",8880\r\n"; //连接服务器指令
char TCMS[] = "AT+CIPMODE=1\r\n"; //透传指令
char SJCS[] = "AT+CIPSEND\r\n"; //数据传输开始指令
char CQMK[] = "AT+RST\r\n"; //重启模块指令
char AT_OK_Flag = 0; //OK返回值的标志位
char AT_Connect_Net_Flag = 0; //WIFI GOT IP返回值的标志位
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
// 查看是否收到 WIFI GOT IP
if(!strcmp((uint8_t *)UART1_RX_Buffer, "WIFI GOT IP"))
AT_Connect_Net_Flag = 1;
// 查看是否收到 OK
if(!strcmp((uint8_t *)UART1_RX_Buffer, "OK"))
AT_OK_Flag = 1;
// 查看是否收到 FAIL
if(!strcmp((uint8_t *)UART1_RX_Buffer, "FAIL"))
{
int i = 0;
for(i = 0; i < 5; i++)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(1000);
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
printf(CQMK);
}
// 灯控指令
if(!strcmp((uint8_t *)UART1_RX_Buffer, "L-1"))
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
if(!strcmp((uint8_t *)UART1_RX_Buffer, "L-0"))
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
UART1_RX_STA = 0;
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
// 开启接收中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
HAL_UART_Transmit(&huart2, "let's go!!\r\n", strlen("let's go!!\r\n"), 100);
//发送联网AT指令并等待成功
printf(LJWL);
//while(!AT_Connect_Net_Flag);
while(!AT_OK_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
//发送连服务器指令并等待成功
printf(LJFWQ);
while(!AT_OK_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
//发送透传模式指令并等待成功
printf(TCMS);
while(!AT_OK_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
//发送数据传输指令并等待成功
printf(SJCS);
while(!AT_OK_Flag) HAL_Delay(50);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("liangxu shuai\r\n");
HAL_UART_Transmit(&huart2, "hello liangxu\r\n", strlen("hello liangxu\r\n"),
100);
HAL_Delay(3000);
}
/* USER CODE END 3 */
}
STA模式:
#include <stdio.h>
#include <string.h>
char buffer[SIZE];
//1 工作在路由模式
char LYMO[] = "AT+CWMODE=2\r\n";
//2 使能多链接
char DLJ[] = "AT+CIPMUX=1\r\n";
//3 建立TCPServer
char JLFW[] = "AT+CIPSERVER=1\r\n"; // default port = 333
//发送数据
char FSSJ[] = "AT+CIPSEND=0,5\r\n";
char AT_OK_Flag = 0; //OK返回值的标志位
char AT_Connect_Net_Flag = 0; //WIFI GOT IP返回值的标志位
char Client_Connect_Flag = 0;
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
// 查看是否收到 WIFI GOT IP
if(!strcmp(UART1_RX_Buffer, "WIFI GOT IP"))
AT_Connect_Net_Flag = 1;
// 查看是否收到 OK
if(!strcmp(UART1_RX_Buffer, "OK"))
AT_OK_Flag = 1;
// 查看是否收到 FAIL
if(!strcmp(UART1_RX_Buffer, "0,CONNECT"))
Client_Connect_Flag = 1;
// 灯控指令
if(!strcmp(UART1_RX_Buffer, "L-1"))
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
if(!strcmp(UART1_RX_Buffer, "L-0"))
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
UART1_RX_STA = 0;
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
项目五:4G遥控插座风扇灯
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
// 开启接收中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
HAL_UART_Transmit(&huart2, "let's go\r\n", strlen("let's go\r\n"), 100);
printf(LYMO);
while(!AT_OK_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
printf(DLJ);
while(!AT_OK_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
printf(JLFW);
while(!Client_Connect_Flag) HAL_Delay(50);
AT_OK_Flag = 0;
if(Client_Connect_Flag){
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//4 发送数据
printf(FSSJ);
HAL_Delay(2000);
printf("Hello");
HAL_Delay(2000);
}
/* USER CODE END 3 */
}
项目需求
通过4G模块,实现电脑控制插座/风扇/灯。
项目框图
注意:
由于硬件的限制,上官二号无法直接带动 4G 模块,可以将 4G 模块的 VCC 和 GND 插到 CH340 的 5V 和
GND 里。
硬件清单
4G模块
CH340
杜邦线
项目设计及实现
项目设计
1. 服务器搭建
参照C51课程;
2. 代码修改
其实可以直接复用上节课的代码,把不相关的代码删除即可
项目实现
八、独立看门狗 IWDG
// 按视频删除不相关代码即可
独立看门狗介绍
什么是看门狗?
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑
飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停
滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监
测单片机程序运行状态的模块或者芯片,俗称"看门狗"(watchdog) 。
独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由 VDD 电压供
电, 在停止模式和待机模式下仍能工作。
独立看门狗本质
本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即
IWDG_RESET 。
如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的
喂狗。
独立看门狗框图
独立看门狗时钟
独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。启用IWDG后,
LSI时钟会自动开启。LSI时钟频率并不精确,F1用40kHz。
LSI经过一个8位的预分频器得到计数器时钟。
分频系数算法:
prer是IWDG_PR 的值。
重装载寄存器
重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值的大小决
定着独立看门狗的溢出时间。
键寄存器
键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器写入下面三
个不同的值有不同的效果。
溢出时间计算公式
独立看门狗实验
需求:
开启独立看门狗,溢出时间为1秒,使用按键1进行喂狗。
硬件接线:
KEY1 -- PA0
UART1 -- PA9/PA10
溢出时间计算:
PSC=64,RLR=625
编程实现:
九、窗口看门狗 WWDG
窗口看门狗介绍
什么是窗口看门狗?
窗口看门狗用于监测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时
间的场合。
窗口看门狗的本质是一个能产生系统复位信号和提前唤醒中断的6位计数器。
产生复位条件:
当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0)
计数器的值大于 W[6:0] 值时喂狗会复位。
产生中断条件:
当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。
在窗口期内重装载计数器的值,防止复位,也就是所谓的喂狗。
#include <string.h>
main函数:
HAL_UART_Transmit(&huart1, "程序启动。。\n", strlen("程序启动。。\n"), 100);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(50);
}
窗口看门狗工作原理
WWDG框图
控制寄存器
配置寄存器
状态寄存器
超时时间计算
Tout是WWDG超时时间(没喂狗)
Fwwdg是WWDG的时钟源频率(最大36M)
4096是WWDG固定的预分频系数
2^WDGTB是WWDG_CFR寄存器设置的预分频系数值
T[5:0]是WWDG计数器低6位,最多63
窗口看门狗实验
需求:
开启窗口看门狗,计数器值设置为 0X7F ,窗口值设置为 0X5F ,预分频系数为 8 。程序启动时点亮 LED1 ,
300ms 后熄灭。在提前唤醒中断服务函数进行喂狗,同时翻转 LED2 状态。
硬件接线:
LED1 -- PB8
LED2 -- PB9
WWDG配置:
编程实现:
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
HAL_WWDG_Refresh(hwwdg);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
main函数
MX_GPIO_Init();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_Delay(300);
MX_WWDG_Init();
while (1)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
HAL_Delay(40);
对比点 独立看门狗 窗口看门狗
时钟源 独立时钟,LSI (40KHz) ,不精确 PCLK1或PCLK3,精确
复位条件 递减计数到0 窗口期外喂狗或减到0x3F
中断 没有中断 计数值减到0x40可产生中断
递减计数器位数 12位(最大计数范围:4096~0) 7位(最大计数范围:127~63)
应用场合 防止程序跑飞,死循环,死机 检测程序时效,防止软件异常
独立看门狗和窗口看门狗的异同点
十、DMA
DMA介绍
什么是DMA?
令人头秃的描述:
DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之
间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,
CPU对于内存的工作来说就无法使用。
简单描述:
就是一个数据搬运工!!
DMA的意义
代替 CPU 搬运数据,为 CPU 减负。
1. 数据搬运的工作比较耗时间;
2. 数据搬运工作时效要求高(有数据来就要搬走);
3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
}
搬运什么数据?
存储器、外设
这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括
自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。
三种搬运方式:
存储器→存储器(例如:复制某特别大的数据buf)
存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
存储器→存储器
存储器→外设
外设→存储器
DMA 控制器
STM32F103有2个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。
一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。
DMA1有7个通道:
DMA2有5个通道
DMA及通道的优先级
优先级管理采用软件+硬件:
软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级
最高级>高级>中级>低级
硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。
比如:如果软件优先级相同,通道2优先于通道4
DMA传输方式
DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输
模式
指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址
将是前一个地址加上增量值。
实验一、内存到内存搬运
实验要求
使用DMA的方式将数组A的内容复制到数组B中,搬运完之后将数组B的内容打印到屏幕。
CubeMX配置
DMA 配置:
重定向 printf 的话记得将下面这个勾打开:
用到的库函数
1. HAL_DMA_Start
参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
参数二:uint32_t SrcAddress,源内存地址
参数三:uint32_t DstAddress,目标内存地址
参数四:uint32_t DataLength,传输数据长度。注意:需要乘以sizeof(uint32_t)
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,
uint32_t DstAddress, uint32_t DataLength)
2. __HAL_DMA_GET_FLAG
参数一:HANDLE,DMA通道句柄
参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
返回值:FLAG的值(SET/RESET)
代码实现
1. 开启数据传输
2. 等待数据传输完成
3. 打印数组内容
实验二、内存到外设搬运
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
#define BUF_SIZE 16
// 源数组
uint32_t srcBuf[BUF_SIZE] = {
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
// 目标数组
uint32_t desBuf[BUF_SIZE];
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
main函数里:
// 开启数据传输
HAL_DMA_Start(&hdma_memtomem_dma1_channel1,
(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
// 等待数据传输完成
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);
// 打印数组内容
for (i = 0; i < BUF_SIZE; i++)
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
实验要求
使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED1。
CubeMX配置
DMA配置
用到的库函数
HAL_UART_Transmit_DMA
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,待发送数据首地址
参数三:uint16_t Size,待发送数据长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size)
代码实现
1. 准备数据
2. 将数据通过串口DMA发送
实验三、外设到内存搬运
实验要求
使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁LED1。
CubeMX配置
DMA配置:
#define BUF_SIZE 1000
// 待发送的数据
unsigned char sendBuf[BUF_SIZE];
main函数里
// 准备数据
for (i = 0; i < BUF_SIZE; i++)
sendBuf[i] = 'A';
// 将数据通过串口DMA发送
HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(100);
}
串口中断配置
用到的库函数
1. __HAL_UART_ENABLE
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((((__INTERRUPT__) >> 28U)
== UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) &
UART_IT_MASK)): \
(((__INTERRUPT__) >> 28U)
== UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) &
UART_IT_MASK)): \
((__HANDLE__)->Instance-
>CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))
参数一:HANDLE,串口句柄
参数二:INTERRUPT,需要使能的中断
返回值:无
2. HAL_UART_Receive_DMA
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size)
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,接收缓存首地址
参数三:uint16_t Size,接收缓存长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
3. __HAL_UART_GET_FLAG
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR &
(__FLAG__)) == (__FLAG__))
参数一:HANDLE,串口句柄
参数二:FLAG,需要查看的FLAG
返回值:FLAG的值
4. __HAL_UART_CLEAR_IDLEFLAG
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
参数一:HANDLE,串口句柄
返回值:无
5. HAL_UART_DMAStop
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数一:UART_HandleTypeDef *huart,串口句柄
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
6. __HAL_DMA_GET_COUNTER
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
参数一:HANDLE,串口句柄
返回值:未传输数据大小
代码实现
如何判断串口接收是否完成?如何知道串口收到数据的长度?
使用串口空闲中断(IDLE)!
串口空闲时,触发空闲中断;
空闲中断标志位由硬件置1,软件清零
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
1. 使能IDLE空闲中断;
2. 使能DMA接收中断;
3. 收到串口接收中断,DMA不断传输数据到缓冲区;
4. 一帧数据接收完毕,串口暂时空闲,触发串口空闲中断;
5. 在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰);
6. 计算刚才收到了多少个字节的数据。
7. 处理缓冲区数据,开启DMA传输,开始下一帧接收。
有三个文件需要修改:
main.c
main.h
stm32f1xx_it.c
uint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组
uint8_t rcvLen = 0; // 接收一帧数据的长度
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(300);
}
#define BUF_SIZE 100
extern uint8_t rcvBuf[BUF_SIZE];
extern uint8_t rcvLen;
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
十一、ADC
ADC介绍
ADC是什么?
全称:Analog-to-Digital Converter,指模拟/数字转换器
ADC的性能指标
量程:能测量的电压范围
分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示,比如:8、10、12、16位等;位
数越多,分辨率越高,一般来说分辨率越高,转化时间越长
转化时间:从转换开始到获得稳定的数字量输出所需要的时间称为转换时间
ADC特性
12位精度下转换速度可高达1MHZ
供电电压:V SSA :0V,V DDA :2.4V~3.6V
ADC输入范围:VREF- ≤ VIN ≤ VREF+
采样时间可配置,采样时间越长, 转换结果相对越准确, 但是转换速度就越慢
ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rcvLen = BUF_SIZE - temp; //计算数据长度
HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
}
/* USER CODE END USART1_IRQn 1 */
}
ADC通道
总共2个ADC(ADC1,ADC2),每个ADC有18个转换通道: 16个外部通道、 2个内部通道(温度传感器、内
部参考电压)。
外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。
规则组:正常排队的人;
注入组:有特权的人(军人、孕妇)
ADC转换顺序
每个ADC只有一个数据寄存器,16个通道一起共用这个寄存器,所以需要指定规则转换通道的转换顺序。
规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制
着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器
来控制,控制关系如下:
注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换顺序才会按
照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。
ADC触发方式
1. 通过向控制寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换。
2. 也可以通过外部事件(如定时器)进行转换。
ADC转化时间
ADC是挂载在APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高 14 MHz。
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,
采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
转换时间=采样时间+12.5个周期
ADC转化模式
扫描模式
关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道
打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道
单次转换/连续转换
单次转换:只转换一次
连续转换:转换一次之后,立马进行下一次转换
实验:使用ADC读取烟雾传感器的值
CubeMX配置
代码实现
十二、IIC
IIC介绍
笔记参照:上官一号笔记第5章节;
视频参照:上官一号92~103节
函数封装
用到的库函数:
参数一:I2C_HandleTypeDef *hi2c,I2C设备句柄
参数二:uint16_t DevAddress,目标器件的地址,七位地址必须左对齐
参数三:uint16_t MemAddress,目标器件的目标寄存器地址
参数四:uint16_t MemAddSize,目标器件内部寄存器地址数据长度
while (1)
{
HAL_ADC_Start(&hadc1); //启动ADC单次转换
HAL_ADC_PollForConversion(&hadc1, 50); //等待ADC转换完成
smoke_value = HAL_ADC_GetValue(&hadc1); //读取ADC转换数据
printf("smoke_value = %f\r\n", 3.3/4096 * smoke_value);
//printf("smoke_value = %d \r\n", smoke_value);
HAL_Delay(500);
}
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
参数五:uint8_t *pData,待写的数据首地址
参数六:uint16_t Size,待写的数据长度
参数七:uint32_t Timeout,超时时间
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
向OLED写命令的封装:
向OLED写数据的封装:
重做上官一号的IIC实验
接线:
SCL -- PB6
SDA -- PB7
void Oled_Write_Cmd(uint8_t dataCmd)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
&dataCmd, 1, 0xff);
}
void Oled_Write_Data(uint8_t dataData)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
&dataData, 1, 0xff);
}
void Oled_Write_Cmd(uint8_t dataCmd)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
&dataCmd, 1, 0xff);
}
void Oled_Write_Data(uint8_t dataData)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
&dataData, 1, 0xff);
}
void Oled_Init(void){
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
void Oled_Screen_Clear(void){
int i,n;
Oled_Write_Cmd (0x20); //set memory addressing mode
Oled_Write_Cmd (0x02); //page addressing mode
for(i=0;i<8;i++){
Oled_Write_Cmd(0xb0+i); //éè??ò3μ??·£¨0~7£?
Oled_Write_Cmd(0x00); //éè????ê??????aáDμíμ??·
Oled_Write_Cmd(0x10); //éè????ê??????aáD??μ??·
for(n=0;n<128;n++)Oled_Write_Data(0x00);
}
}
unsigned char bmpImager[] = {
/*-- 调入了一幅图像:D:\无标题.bmp --*/
/*-- 宽度x高度=128x64 --128x8x8*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0xF0,0x08,0x0C,0x04,0x06,0x06,0x0C,0x04,0x0C,0xFC,0x1C,0x74,0xFC,0xF8,
0xF0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x01,0x07,0x04,0x88,0xF8,0x08,0x08,0x0C,0x06,0x01,0x00,0x00,0x01,0x1F,
0x7F,0xFF,0xDC,0xF8,0xE0,0xC0,0x40,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x10,0x18,0x08,0x0C,
0x04,0x04,0x06,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x60,0xC0,0x80,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xE0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
0x03,0x06,0x1C,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0xFC,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x88,0xE8,0x38,0x0E,0x09,0x08,
0x08,0x88,0xE8,0x18,0x08,0x08,0x08,0x00,0x00,0xFF,0x89,0x89,0x89,0xFF,0x00,0xFF,
0x89,0x89,0x89,0x89,0xFF,0x00,0x00,0x04,0x04,0x84,0x74,0x6F,0xA4,0x24,0x24,0x24,
0x24,0xA4,0x64,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x80,0xF0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x7F,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0x08,0x09,0x09,0x06,0x06,
0x06,0x05,0x08,0x08,0x10,0x10,0x00,0x00,0x0C,0x03,0x10,0x10,0x10,0x1F,0x18,0x07,
0x00,0x00,0x10,0x10,0x1F,0x00,0x10,0x08,0x06,0x11,0x10,0x08,0x09,0x0A,0x06,0x06,
0x0B,0x08,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,
0x1E,0x03,0x00,0x00,0xC0,0x60,0x30,0x0C,0x04,0x06,0x02,0x01,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x1E,0x60,0x78,0x0F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
void Oled_Show_Image(unsigned char *image)
{
unsigned char i;
unsigned int j;
for(i=0;i<8;i++){
Oled_Write_Cmd(0xB0 + i);//page0--page7
//每个page从0列
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//0到127列,依次写入0,每写入数据,列地址自动偏移
for(j = 128 * i; j<(128 * (i+1));j++){
Oled_Write_Data(image[j]);
}
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
//1. OLED初始化
十三、SPI
SPI 介绍
SPI 是什么?
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且
在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这
种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。
以上介绍来自360百科
SPI 物理架构
Oled_Init();
//2. 选择一个位置
//2.1 确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
Oled_Screen_Clear();
Oled_Show_Image(bmpImager);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如下 :
(1) MISO -- Master Input Slave Output,主设备数据输入,从设备数据输出
(2) MOSI -- Master Output Slave Input,主设备数据输出,从设备数据输入
(3) SCK -- Serial Clock,时钟信号,由主设备产生
(4) CS -- Chip Select,片选信号,由主设备控制
SPI 工作原理
SPI 工作模式
时钟极性(CPOL):
没有数据传输时时钟线的空闲状态电平
0:SCK在空闲状态保持低电平
1:SCK在空闲状态保持高电平
时钟相位(CPHA):
时钟线在第几个时钟边沿采样数据
0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时钟边沿被锁存
1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存
模式 0 和模式 3 最常用。
模式 0 时序图:
模式 3 时序图:
W25Q128 介绍
什么是 W25Q128 ?
W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字
节。
Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按"扇区/块"擦除、掉电后数据可
继续保存的特性。
Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。
指令(HEX) 名称 作用
0X06 写使能 写入数据/擦除之前,必须先发送该指令
0X05 读SR1 判定FLASH是否处于空闲状态,擦除用
0X03 读数据 用于读取NOR FLASH数据
0X02 页写 用于写入NOR FLASH数据,最多写256字节
0X20 扇区擦除 扇区擦除指令,最小擦除单位(4096字节)
W25Q128 存储架构
一般按扇区(4k)进行擦除。
可以按 章 -- 节 -- 页 -- 字 进行理解。
W25Q128 常用指令
W25Q128 全部指令非常多,但常用的如下几个指令:
写使能 (06H)
执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。
拉低CS片选 → 发送06H → 拉高CS片选
读状态寄存器(05H)
拉低CS片选 → 发送05H→ 返回SR1的值 → 拉高CS片选
读时序(03H)
拉低CS片选 → 发送03H→ 发送24位地址 → 读取数据(1~n) → 拉高CS片选
页写时序 (02H)
页写命令最多可以向FLASH传输256个字节的数据。
拉低CS片选 → 发送02H→ 发送24位地址 → 发送数据(1~n) → 拉高CS片选
扇区擦除时序(20H)
写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。
拉低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选
W25Q128 状态寄存器
W25Q128 一共有 3 个状态寄存器,它们的作用是跟踪芯片的状态。
其中,状态寄存器 1 较为常用。
BUSY:指示当前的状态,0 表示空闲,1 表示忙碌
WEL:写使能锁定,为 1 时,可以操作页/扇区/块。为 0 时,写禁止。
W25Q128 常见操作流程
以下流程省略了拉低/拉高片选信号CS。
读操作:
擦除扇区:
写操作
实验:使用 SPI 通讯读写 W25Q128 模块
硬件接线
VCC -- 3.3V
CS -- PA4
CLK -- PA5
DO -- PA6
DI -- PA7
cubeMX配置
w25q128_write_nocheck流程图
项目六、温湿度LCD显示并上传服务器
项目需求
使用温湿度传感器模块(DHT11)获取温度及湿度,并将值显示在LCD1602上,同时通过蓝牙模块透传到手
机。
项目框图
硬件清单
DHT11
LCD1602
HC-08
继电器
杜邦线
a. LCD1602介绍及实战
硬件接线
D0~D7 -- A0~A7
RS -- B1
RW -- B2
EN -- B10
V0 -- GND(正视看不到显示结果,需要侧着看。否则需要接可调电阻)
引脚封装
RS、RW、EN三根信号线经常需要进行拉高/拉低操作,可以进行封装
如何将一个字节的数据按位一次性发送到GPIOA的8个管脚?
#define RS_GPIO_Port GPIOB
#define RW_GPIO_Port GPIOB
#define EN_GPIO_Port GPIOB
#define RS_Pin GPIO_PIN_1
#define RW_Pin GPIO_PIN_2
#define EN_Pin GPIO_PIN_10
#define RS_HIGH HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_SET)
#define RS_LOW HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_RESET)
#define RW_HIGH HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_SET)
#define RW_LOW HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_RESET)
#define EN_HIGH HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET)
#define EN_LOW HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_RESET)
GPIOA->ODR = cmd;
代码实现
#define RS_GPIO_Port GPIOB
#define RW_GPIO_Port GPIOB
#define EN_GPIO_Port GPIOB
#define RS_Pin GPIO_PIN_1
#define RW_Pin GPIO_PIN_2
#define EN_Pin GPIO_PIN_10
#define RS_HIGH HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_SET)
#define RS_LOW HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_RESET)
#define RW_HIGH HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_SET)
#define RW_LOW HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_RESET)
#define EN_HIGH HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET)
#define EN_LOW HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_RESET)
void Write_Cmd_Func(uint8_t cmd)
{
RS_LOW;
RW_LOW;
EN_LOW;
GPIOA->ODR = cmd;
HAL_Delay(5);
EN_HIGH;
HAL_Delay(5);
EN_LOW;
}
void Write_Data_Func(uint8_t dataShow)
{
RS_HIGH;
RW_LOW;
EN_LOW;
GPIOA->ODR = dataShow;
HAL_Delay(5);
EN_HIGH;
HAL_Delay(5);
EN_LOW;
}
void LCD1602_INIT(void)
{
//(1)延时 15ms
HAL_Delay(15);
//(2)写指令 38H(不检测忙信号)
Write_Cmd_Func(0x38);
//(3)延时 5ms
HAL_Delay(5);
//(4)以后每次写指令,读/写数据操作均需要检测忙信号
//(5)写指令 38H:显示模式设置
Write_Cmd_Func(0x38);
b. DHT11介绍及实战
//(6)写指令 08H:显示关闭
Write_Cmd_Func(0x08);
//(7)写指令 01H:显示清屏
Write_Cmd_Func(0x01);
//(8)写指令 06H:显示光标移动设置
Write_Cmd_Func(0x06);
//(9)写指令 0CH:显示开及光标设置}
Write_Cmd_Func(0x0c);
}
void LCD1602_showLine(char row, char col, char *string)
{
switch(row){
case 1:
Write_Cmd_Func(0x80+col);
while(*string){
Write_Data_Func(*string);
string++;
}
break;
case 2:
Write_Cmd_Func(0x80+0x40+col);
while(*string){
Write_Data_Func(*string);
string++;
}
break;
}
}
main函数里:
//char position = 0x80 + 0x05;
//char dataShow = 'C';
LCD1602_INIT();
//Write_Cmd_Func(position);//选择要显示的地址
//Write_Data_Func(dataShow);//发送要显示的字符
LCD1602_showLine(1,5,"NO.2");
LCD1602_showLine(2,0,"LX handsome");
硬件接线
DAT -- PB7
注意:PB7既作为输入,也作为输出,则不能直接在CubeMX里配置,需要自己写代码
引脚封装
代码实现
#define DHT_HIGHT HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET)
#define DHT_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)
#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
#define DHT_HIGHT HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET)
#define DHT_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)
#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
uint8_t datas[5];
void delay_us(uint16_t cnt)
{
uint8_t i;
while(cnt)
{
for (i = 0; i < 10; i++)
{
}
cnt--;
}
}
void DHT_GPIO_Init(uint32_t Mode)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = Mode;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void DHT11_Start(void)
{
DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);
DHT_HIGHT;
DHT_LOW;
HAL_Delay(30);
DHT_HIGHT;
DHT_GPIO_Init(GPIO_MODE_INPUT);
while(DHT_VALUE);
while(!DHT_VALUE);
while(DHT_VALUE);
}
void Read_Data_From_DHT()
{
int i;//轮
int j;//每一轮读多少次
char tmp;
char flag;
DHT11_Start();
DHT_GPIO_Init(GPIO_MODE_INPUT);
for(i= 0;i < 5;i++){
for(j=0;j<8;j++){
while(!DHT_VALUE);//等待卡g点
delay_us(40);
if(DHT_VALUE == 1){
flag = 1;
while(DHT_VALUE);
}else{
flag = 0;
}
tmp = tmp << 1;
tmp |= flag;
}
datas[i] = tmp;
}
}
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
项目设计及实现
项目设计
继电器数据线插在PB6上,DHT11及LCD1602接线与上述相同。
项目实现
注意点:
1. 不要忘记将Use MicroLIB的勾打上;
2. 不要忘记在main函数把串口中断打开;
3. 使用蓝牙模块时,记得将波特率设置为9600.
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("hello world\r\n");
HAL_Delay(2000);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//DHT_Get_Temp_Humi_Data(datas);
Read_Data_From_DHT();
printf("Temp: %d.%d ", datas[2], datas[3]);
printf("Humi: %d.%d\r\n", datas[0], datas[1]);
HAL_Delay(2000);
}
/* USER CODE END 3 */
}
项目七:智能小车
1. 让小车动起来
对应源代码:smartCar_project1
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
其余接线参考上官一号小车项目。
代码实现
motor.c
char message[16];
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
Read_Data_From_DHT();
if (datas[2] >= 25)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
memset(message, 0, sizeof(message));
sprintf(message, "Temp: %d.%d", datas[2], datas[3]);
LCD1602_showLine(1,0,message);
memset(message, 0, sizeof(message));
sprintf(message, "Humi: %d.%d", datas[0], datas[1]);
LCD1602_showLine(2,0,message);
HAL_Delay(1000);
}
#include "motor.h"
void goForward(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goBack(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
void goLeft(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void goRight(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
void stop(void)
{
// 左轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
// 右轮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
motor.h
main.c
2. 串口控制小车
对应源代码:smartCar_project2
代码实现
usart.c
#ifndef __MOTOR_H__
#define __MOTOR_H__
#include "main.h"
void goForward(void);
void goBack(void);
void goLeft(void);
void goRight(void);
void stop(void);
#endif
#include "motor.h"
//main函数的while循环部分:
while (1)
{
/* USER CODE END WHILE */
goForward();
HAL_Delay(1000);
goBack();
HAL_Delay(1000);
goLeft();
HAL_Delay(1000);
goRight();
HAL_Delay(1000);
stop();
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
#include "string.h"
#include "stdio.h"
#include "motor.h"
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
#define SIZE 12
char buffer[SIZE];
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
// 灯控指令
if(!strcmp(UART1_RX_Buffer, "M1"))
goForward();
else if(!strcmp(UART1_RX_Buffer, "M2"))
goBack();
else if(!strcmp(UART1_RX_Buffer, "M3"))
goLeft();
else if(!strcmp(UART1_RX_Buffer, "M4"))
goRight();
else
stop();
memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
UART1_RX_STA = 0;
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
main.c
3. 点动控制小车
对应源代码:smartCar_project3
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
#include "motor.h"
extern uint8_t buf;
//main函数
HAL_UART_Receive_IT(&huart1, &buf, 1);
代码实现
usart.c
main.c
4. 硬件PWM调速
对应源代码:smartCar_project4
硬件接线
B-1A -- PA0
B-1B -- PB1
A-1A -- PA1
A-1B -- PB10
其余接线参考上官一号小车项目。
if (!strcmp(UART1_RX_Buffer, "M1"))
{
goForward();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M2"))
{
goBack();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M3"))
{
goLeft();
HAL_Delay(10);
}
else if (!strcmp(UART1_RX_Buffer, "M4"))
{
goRight();
HAL_Delay(10);
}
else
stop();
// main函数里
HAL_NVIC_SetPriority(SysTick_IRQn,0,0); //或者通过cubeMX配置
while(1)
{
stop();
}
cubeMX配置
TIM2配置如下图。
这两节里配置有误,正确的应该是PSC=7199,ARR=199,大家注意修正!!
设置 PSC=71 ,ARR=19,PWM 周期则为 20ms 。
将控制车轮的4个 GPIO 口配置修改如下,否则小车动不起来。
原因:L9110每个控制口需要一高一低才可以动起来,如果PWM有效电平为高电平,则另一个GPIO口则需
要输出低电平才可以驱动轮子。
代码实现
main.c
5. 左右轮各自调速
对应源代码:smartCar_project5
代码实现
main.c
6. 循迹小车
对应源代码:smartCar_project6
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
// main函数里
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
while (1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15);
HAL_Delay(1000);
}
// main函数里
while (1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
HAL_Delay(1000);
}
A-1B -- PB10
循迹模块(左) -- PB3
循迹模块(右) -- PB4
其余接线参考上官一号小车项目。
代码实现
7. 循迹小车解决转弯平滑问题
对应源代码:smartCar_project7
硬件接线
B-1A -- PA0
B-1B -- PB1
A-1A -- PA1
A-1B -- PB10
循迹模块(左) -- PB3
循迹模块(右) -- PB4
其余接线参考上官一号小车项目。
代码实现
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
// main函数里
while (1)
{
if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
goForward();
if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
goLeft();
if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
goRight();
if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
stop();
}
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
// main函数里
while (1)
8. 跟随小车
对应源代码:smartCar_project8
硬件接线
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
跟随模块(左) -- PB5
跟随模块(右) -- PB6
其余接线参考上官一号小车项目。
代码实现
{
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19);
}
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);
}
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);
}
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0);
}
}
#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)
// main函数里
while (1)
9. 摇头避障小车
对应源代码:smartCar_project9
9.1 封装摇头功能
对应源代码:smartCar_project9_1
硬件接线
sg90 -- PB9
cubeMX配置
{
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)
goRight();
if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)
goLeft();
if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)
stop();
}
代码实现
sg90.c
#include "sg90.h"
#include "gpio.h"
#include "tim.h"
void initSG90(void)
{
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgMiddle(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度
}
void sgRight(void)
sg90.h
main.c
9.2 封装超声波传感器
对应源代码:smartCar_project9_2
硬件接线
请注意,超声波模块的接线与垃圾桶项目有所不同!
超声波模块:
Trig -- PB7
Echo -- PB8
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
}
void sgLeft(void)
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度
}
#ifndef __SG90_H__
#define __SG90_H__
void initSG90(void);
void sgMiddle(void);
void sgRight(void);
void sgLeft(void);
#endif
initSG90();
HAL_Delay(1000);
while (1)
{
sgLeft();
HAL_Delay(1000);
sgMiddle();
HAL_Delay(1000);
sgRight();
HAL_Delay(1000);
sgMiddle();
HAL_Delay(1000);
}
cubeMX配置
代码实现
sr04.c
#include "sr04.h"
#include "gpio.h"
#include "tim.h"
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
double get_distance(void)
{
int cnt=0;
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);//拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);//拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
return (cnt*340/2*0.000001*100); //单位:cm
}
sr04.h
#ifndef __SR04_H__
#define __SR04_H__
double get_distance(void);
#endif
main.c
while (1)
{
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(300);
9.3 封装电机驱动
对应源代码:smartCar_project9_3
硬件接线
与 "让小车动起来" 完全一样
B-1A -- PB0
B-1B -- PB1
A-1A -- PB2
A-1B -- PB10
代码实现
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
}
else
{
//停止
//测左边距离
sgLeft();
HAL_Delay(300);
disLeft = get_distance();
sgMiddle();
HAL_Delay(300);
sgRight();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
}
}
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(300);
10. 小车测速
对应源代码:smartCar_project10
硬件接线
测速模块:
VCC -- 3.3V 不能接5V,否则遮挡一次会触发3次中断
OUT -- PB14
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
goForward();
}else if(disMiddle < 10){
goBack();
}else
{
//停止
stop();
//测左边距离
sgLeft();
HAL_Delay(300);
disLeft = get_distance();
sgMiddle();
HAL_Delay(300);
sgRight();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
if(disLeft < disRight){
goRight();
HAL_Delay(150);
stop();
}
if(disRight < disLeft){
goLeft();
HAL_Delay(150);
stop();
}
}
HAL_Delay(50);
}
cubeMX配置
代码实现
11. 串口控制小车并使用Oled显示速度
对应源代码:smartCar_project11
unsigned int speedCnt;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_14)
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
speedCnt++;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
printf("speed: %d\r\n", speedCnt);
speedCnt = 0;
}
main函数里:
HAL_TIM_Base_Start_IT(&htim2);
硬件接线
SCL -- PB6
SDA -- PB7
封装Oled模块
对应源代码:smartCar_project11_1
实现测速并使用Oled显示速度
对应源代码:smartCar_project11_2
代码实现
太长了,大家直接去看源代码吧。。
12. Wi-Fi测速小车并本地Oled显示
对应源代码:smartCar_project12
硬件接线
把esp8266插进串口1
13. 语音控制小车
对应源代码:smartCar_project13
硬件接线
循迹小车:
循迹模块(左) -- PB3
循迹模块(右) -- PB4
跟随小车:
跟随模块(左) -- PA8
跟随模块(右) -- PA9
避障小车:
sg90:PB9
Trig:PA10
Echo:PA11
OLED模块:
SCL -- PB6
SDA -- PB7
语音模块:
A25 -- PA15 (跟随)
A26 -- PA13 (避障)
A27 -- PA14 (循迹)
cubeMX配置
SU-03T配置
配置与上官一号一样样~
注意事项
1. 语音模块电源需要接到上官二号,否则小车跟随功能异常;
2. 上官二号烧录代码时,需要将语音模块断电,否则烧录失败。
代码实现
#include "sg90.h"
#include "sr04.h"
#include "motor.h"
#include "oled.h"
#include "string.h"
#define MIDDLE 0
#define LEFT 1
#define RIGHT 2
#define BZ 1
#define XJ 2
#define GS 3
#define LeftWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)
#define RightWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
#define LeftWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8)
#define RightWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)
#define XJ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14)
#define GS_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)
#define BZ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13)
char dir;
void xunjiMode()
{
if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ ==
GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_RESET)
goLeft();
if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_SET)
goRight();
if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_SET)
stop();
}
void gensuiMode()
{
if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS ==
GPIO_PIN_RESET)
goForward();
if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_RESET)
goRight();
if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_SET)
goLeft();
if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_SET)
stop();
}
void bizhangMode()
{
double disMiddle;
double disLeft;
double disRight;
if(dir != MIDDLE){
sgMiddle();
dir = MIDDLE;
HAL_Delay(300);
}
disMiddle = get_distance();
if(disMiddle > 35){
//前进
goForward();
}else if(disMiddle < 10){
goBack();
}else
{
//停止
stop();
//测左边距离
sgLeft();
HAL_Delay(300);
disLeft = get_distance();
sgMiddle();
HAL_Delay(300);
sgRight();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
if(disLeft < disRight){
goRight();
HAL_Delay(150);
stop();
}
if(disRight < disLeft){
goLeft();
HAL_Delay(150);
stop();
}
}
HAL_Delay(50);
}
int main(void)
{
int mark = 0;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM4_Init();
MX_TIM2_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
initSG90();
HAL_Delay(1000);
dir = MIDDLE;
Oled_Init();
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----Ready----");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//满足循迹模式的条件
if(XJ_VALUE == GPIO_PIN_RESET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE ==
GPIO_PIN_SET)
{
if(mark != XJ)
{
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----XunJi----");
}
mark = XJ;
xunjiMode();
}
//满足跟随模式的条件
if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_RESET && BZ_VALUE ==
GPIO_PIN_SET)
{
if(mark != GS)
{
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----GenSui----");
}
mark = GS;
gensuiMode();
}
//满足避障模式的条件
if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE ==
GPIO_PIN_RESET)
{
if(mark != BZ)
{
Oled_Screen_Clear();
Oled_Show_Str(2,2,"-----BiZhang----");
}
mark = BZ;
bizhangMode();
}
HAL_Delay(50);
}
/* USER CODE END 3 */
}