# STM32 入门教程
# keil5 MDK
是用来给 ARM 系统的单片机编程的
# STM32 的介绍
- STM32(也是一个单片机)是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器
- STM32 常应用在嵌入式领域,如智能车、无人机、机器人、无限通信、物联网、工业控制、娱乐电子产品等
- STM32 功能强大、性能优异、片上资源丰富、功耗低、是一款经典的嵌入式微控制器
STM32 家族
片上资源 / 外设(Peripheral)
| 英文缩写 | 名称 | 英文缩写 | 名称 |
|---|---|---|---|
| NVIC | 嵌套向量中断控制器 | CAN | CAN 通信 |
| SysTick | 系统滴答定时器 | USB | USB 通信 |
| RCC | 复位和时钟控制 | RTC | 实时时钟 |
| GPIO | 通用 IO 口 | CRC | CRC 校验 |
| AFIO | 复用 IO 口 | PWR | 电源控制 |
| EXTI | 外部中断 | BKP | 备份寄存器 |
| TIM | 定时器 | IWDG | 独立看门狗 |
| ADC | 模数转换器 | WWDG | 窗口看门狗 |
| DMA | 直接内存访问 | DAC | 数模转换器 |
| USART | 同步 / 异步串口通信 | SDIO | SD 卡接口 |
| I2C | I2C 通信 | FSMC | 可变静态存储控制器 |
| SPI | SPI 通信 | USN OTG | USN 主机接口 |
# 软件安装
安装:
- 安装 Keil5 MDK
- 安装器件支持包
- 软件注册
- 安装 STLINK 驱动
- 安装 USB 转串口驱动
# 新建工程
STM32 的开发方式(主要有):
-
基于寄存器的方式
就是用程序直接配置寄存器,是最底层、最直接、效率会更高一些
但是由于 STM32 的结构复杂、寄存器太多,所以基于寄存器的方法目前是不推荐的
-
基于标准库的方式(即库函数的方式)
是使用 ST 官方提供的封装好的函数,通过调用这些函数来间接配置这些寄存器
由于 ST 对寄存器封装的比较好,所以这种方式既能满足对寄存器的配置,对开发人员也比较友好,有利于提高开发效率
-
基于 HAL 库的方式
可以用图形话界面快速配置 STM32,比较适合快速上手 STM32
但是这种方法隐藏了底层逻辑
# 过程
-
先建立一个存放工程的文件夹(比如在 D 盘、E 盘)
以后的工程都存放在这个文件夹,方便管理
-
点击进入软件
再点击 Project, 并选择 New uVision Project...
-
选择我们刚才建立的文件夹
-
给你的项目命名,再点保存
-
选择你所要项目的型号,再点 OK
-
弹出的这个界面我们暂时还用不上,先点击右上角的❌
现在项目就建好了,但是这里的工程文件是空空如也,还不能直接用,需要给它添加一些必要的文件
C:\Users\22731\Desktop\STM32\ 固件库 \STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
根据这些路径找到这个界面
这些文件就是 STM32 的执行文件,把文件全部复印下来,然后回到工程模板文件夹里,可以看到这些就是我们刚才新建工程自动生成的文件
如果直接粘贴到模板文件夹里,就会太乱,可以新建一个文件夹,然后将启动文件粘贴到这个新的文件夹里面
并将 STM32F10x 文件夹中的这三个文件也粘贴到模板文件夹中
因为 STM32 是内核和内核外围的设备组成的,而且这个内核的寄存器描述和外围设备的描述文件不是再一起的,所以我们还需要添加一个外围设备的描述文件
这两个文件就是内核寄存器的描述,将这两个文件也粘贴到模板文件夹中
必要文件复制完成
回到 Keil5 软件,把刚才复制的文件添加到工程里,将文件改名为 Start,
接着点右键,选择 Add Files to Group 'Start', 点击进入 Start
将文件类型改为 All files (* . *), 再选择一个启动文件添加(注:只能选一个启动文件),这里我们选择以.md.s 结尾的文件,点击 Add, 然后将剩下所有的.h 和.c 文件添加进来,最后点击 Close
这样,Start 文件夹里面的文件就添加好了(这些带钥匙的文件是只读文件,是不允许我们修改的)
最后,我们还需要在工程选项里添加上这个文件夹的头文件路径,要不然软件是找不到.h 文件的
点击魔术棒按钮,打开工程选项,在 C/C++ 里,点 Include Paths 右边的三个小点,
点击这个小方框,在点击这三个小点
选择 Start 文件夹,再点击 OK
接下来我们再新建一个 main 函数,看看这个工程是不是可行
打开工程文件夹,再新建一个文件夹
在 User 上点击右键,再选择 Add New Item to Group 'User'
选择 C 文件,名字为 main,路径选择到 User, 再点击 Add
这样我们就拥有 main.c 文件了
再 main.c 文件中,右键,选择 Inster '#include file', 再选择 stm32f10x.h (插入头文件)
STM32F1 系列中的型号分类及缩写
| 缩写 | 释义 | Flash 容量 | 型号 |
|---|---|---|---|
| LD_VL | 小容量产品超值系列 | 16~32K | STM32F100 |
| MD_VL | 中容量产品超值系列 | 64~128K | STM32F100 |
| HD_VL | 大容量产品超值系列 | 256~512K | STM32F100 |
| LD | 小容量产品 | 16~32K | STM32F101/102/103 |
| MD | 中容量产品 | 64~128K | STM32F101/102/103 |
| HD | 大容量产品 | 256~512K | STM32F101/102/103 |
| XL | 加大容量产品 | 大于 512K | STM32F101/102/103 |
| CL | 互联型产品 | - | STM32F105/107 |
# GPIO 通用输入输出口
# GPIO 简介
- GPIO(General Purpose Input Output)通用输入输出口
- 可配置为 8 种输入输出模式
- 引脚电平:0V~3.3V,部分引脚可容忍 5V
- 输出模式下可控制端口输出高低电平,用以驱动 LED、控制蜂鸣器、模拟通信协议输出时序等
- 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC 电压采集、模拟通信协议接收数据等
在每个 GPIO 模块内,主要包含了寄存器和驱动器这些东西
寄存器:就是一段特殊的存储器,内核可以通过 APB2 总线对寄存器进行读写
# GPIO 模式
通过配置 GPIO 的端口配置寄存器,端口可以配置成以下 8 种模式
| 模式名称 | 性质 | 特征 |
|---|---|---|
| 浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不确定 |
| 上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 |
| 下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 |
| 模拟输入 | 模拟输入 | GPIO 无效,引脚直接接入内部 ADC |
| 开漏输出 | 数字输出 | 可输出引脚电平,高电平为高阻态,低电平接 VSS |
| 推挽输出 | 数字输出 | 可输出引脚电平,高电平接 VDD,低电平接 VSS |
| 复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平接 VSS |
| 复用推挽输出 | 数字输出 | 由片上外设控制,高电平接 VDD,低电平接 VSS |
注:
- 模拟输入可以说是 ADC 模数转化器的专属配置
- 在这 8 种模式中,除了模拟输入这个模式会关闭数字的输入功能,在其他 7 种模式中,所有的输入都是有效的
# STM32 外部的设备和电路
# LED 和蜂鸣器简介
**LED:** 发光二极管,正向通电点亮,反向通电不亮
注:
如果是引脚没有被剪过的 LED,那其中长脚是正极,短脚是负极
通过 LED 内部也可以看正负极,这里较小的一半是正极,较大的一半是负极
** 有源蜂鸣器:** 内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
** 无源蜂鸣器:** 内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
# LED 闪烁 & LED 流水灯 & 蜂鸣器
首先需要搭建一个面包板,按照图示将相关的硬件安装好,再在 keil5 MDK 中创建一个项目 (具体操作看新建工程)
将默认的组(Groups:Source Group 1)删掉,再点击 New (Insert) 新建三个组,分别为 Start,Library,User
然后对这三个组添加对应的文件
Start:
Library () 全选:
User:
全部添加好后,点击 OK 键
接着点击魔术棒按钮,打开工程选项,选择 C/C++,在 Include Paths 中添加好路径,并在 Define 中填好 USE_STDPERIPH_DRIVER
[
](https://postimg.cc/FYKjHbmn)
然后点击 Debug,调试器选择 ST-Link,再点 seettings
点击 Flash Download,勾选 Reset and Run, 然后确定,OK
这样工程选项就配置好了
接着打开 main.c, 将里面原来的代码全部删掉
# LED 闪烁
操作 STM32 的 GPIO 总共需要 3 个步骤:
- 使用 RCC 开启 GPIO 的时钟
- 使用 GPIO_Init 函数初始化 GPIO
- 使用输入或者输出的函数控制 GPIO 口
** 涉及到了两个外设:**RCC 和 GPIO
灯亮:
灯灭:
LED 闪烁:
为了实现 LED 闪烁,我们需要在主循环里,写上 **‘’点亮 LED,延时一段时间,熄灭 LED,延时一段时间‘’** 的逻辑
准备工作:
点击三个盒子的图标,点击方框,添加新组,再将文件里的文件全部添加进来,最后点击 OK
最后点击魔术棒,选择 C/C++,将 System 的源路径添加进来,就 OK 了
代码:
# 研究推挽输出和开漏输出的驱动问题
首先,将 LED 拔掉,然后把长脚插到 PA0 口,短脚插到负极,这样 LED 就是高电平点亮方式(也能正常闪烁,代码看上图)
说明在推挽模式下,高低电平都是有驱动能力的
改成开漏模式:
这时,我们就可以看到 LED 不亮了,现在 LED 还是高电平点亮的方式,LED 不亮,说明开漏输出的模式高电平是没有驱动能力的
我们再把 LED 该为低电平的方式,可以看到 LED 又亮起来了,说明开漏输出的模式低电平是有驱动能力的
# LED 流水灯
接线图:
需要拿出 8 个 LED 灯,正极都插在正极的供电口,负极依次插到 PA0 到 PA7 的端口
在代码中引用引脚,有两种方式:
为了同时控制 16 个端口,我们可以使用 GPIO_Write 这个函数
这样就是第一个灯点亮,其他灯都熄灭了
代码:
执行之后,可以看到 LED 依次点亮
# 蜂鸣器
接线图:
VCC 正极接到正极供电口,GND 负极接到负极供电口,I/O 控制极随便选择一个 IO 口接上及可(示列选择 PB12)
注意:A15、B3、B4 这三个口不能选,这三个口是 JTAG 的调试端口,如果要用作普通端口的话,还需要进行一些配置
给 PB12 输出低电平,蜂鸣器就会响,输出高电平,蜂鸣器就不响
代码:
还可以换个响的方式
# GPIO 输入
# 按键简介
-
按键:常见的输入设备,按下导通,松手断开
-
按键抖动(在单片机中存在):由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动
这个我们在程序中需要过滤一下(最简单的过滤方法就是加一段延时,把这个抖动时间耗过去)
# 传感器模块简介
传感器模块:传感器元件(光敏电阻 / 热敏电阻 / 红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出
滤波电容:它是为了给中间的电压输出进行滤波的,用来滤除一些干扰,保证输出电压波形的平滑
注意:一般我们在电路中遇到这种一段接在电路中,另一端接地的电容,都可以考虑一下这个是不是滤波电容的作用(如果是则可以在分析电路的时候把这个电容抹掉)
LM393:是一个电压比较器芯片,里面有两个独立的电压比较器电路,有时可以当作是一个运算放大器
# 按键的四种接法:
一般我们的按键都是用下接的方式
下接按键按下时引脚是低电平,松手是高电平,上接则相反
(1)这两种接法要求引脚必须是上拉或下拉输入的模式
(2)这两种接法可以允许引脚是浮空输入的模式,因为已经外设了上拉电阻和下拉电阻
# C 语言数据类型
| 关键字 | 位数 | 表示范围 | stdint 关键字 | ST 关键字 |
|---|---|---|---|---|
| char | 8 | -128 ~ 127 | int8_t | s8 |
| unsigned char | 8 | 0 ~ 255 | uint8_t | u8 |
| short | 16 | -32768 ~ 32767 | int16_t | s16 |
| unsigned short | 16 | 0 ~ 65535 | uint16_t | u16 |
| int | 32 | -2147483648 ~ 2147483647 | int32_t | s32 |
| unsigned int | 32 | 0 ~ 4294967295 | uint32_t | u32 |
| long | 32 | -2147483648 ~ 2147483647 | ||
| unsigned long | 32 | 0 ~ 4294967295 | ||
| long long | 64 | -(2^64)/2 ~ (2^64)/2-1 | int64_t | |
| unsigned long long | 64 | 0 ~ (2^64)-1 | uint64_t | |
| float | 32 | -3.4e38 ~ 3.4e38 | ||
| double | 64 | -1.7e308 ~ 1.7e308 |
# C 语言宏定义
-
关键字:#define
-
** 用途:** 用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改
-
定义宏定义:
#define ABC 12345
-
引用宏定义:
int a = ABC; // 等效于 int a = 12345;
# C 语言 typedef
-
** 关键字:**typedef
-
** 用途:** 将一个比较长的变量类型名换个名字,便于使用
-
定义 typedef:
typedef unsigned char uint8_t;
-
引用 typedef:
uint8_t a; // 等效于 unsigned char a;
typedef 和宏定义的区别:
- 宏定义的新名字在左边,typedef 的新名字在右边
- 宏定义不需要分号,typedef 后面必须加分号
- 宏定义任何名字都可以换,而 typedef 只能专门给变量类型换名字,所以宏定义的改名范围要更宽一些,只不过对变量类型重命名而言,所以 typedef 更加安全
# C 语言结构体
-
** 关键字:**struct
-
** 用途:** 数据打包,不同类型变量的集合
-
定义结构体变量:
struct{char x; int y; float z;} StructName;
因为结构体变量类型较长,所以通常用 typedef 更改变量类型名
-
引用结构体成员:
StructName.x = 'A';
StructName.y = 66;
StructName.z = 1.23;
或 pStructName->x = 'A'; //pStructName 为结构体的地址 pStructName->y = 66;
pStructName->z = 1.23;
# C 语言枚举
-
** 关键字:**enum
-
** 用途:** 定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合
-
定义枚举变量:
enum{FALSE = 0, TRUE = 1} EnumName;
因为枚举变量类型较长,所以通常用 typedef 更改变量类型名
-
引用枚举成员:
EnumName = FALSE;
EnumName = TRUE;
# 按键控制 LED & 光敏传感器控制蜂鸣器
# 按键控制 LED
接线图:
新建一个文件夹,命名为 Hardware (用来存放硬件驱动)
点击三个箱子的按钮,打开工程管理,新建一个组,也叫 Hardware,点击 OK
在点开魔术棒按钮,打开工程选项,选择 C/C++,在源路径中将我们刚刚创建的 Hardware 添加进来,点击 OK, 这样 Hardware 就成功的添加了进来
在 Hardware 点击右键,选择 Add New Item to Group 'Hardware', 选择.c 文件,Name 命名为 LED,路径选择 Hardware,点击 Add
同理,创建一个.h 文件
LED.c 和 LED.h 用来封装 LED 的驱动程序,LED.c 用来存放驱动程序的主体代码,LED.h 用来存放这个驱动程序用来对外提供的函数或变量的说明
在 LED.c 和 LED.h 中写好这样一段
接下来我们开始封装一下 LED 的代码
测试:
运行之后,可以看到两个 LED 都亮起来了,说明我们端口配置和模块化编程是没有问题的
因为 GPIO 配置好了之后默认就是低电平,所以我们还没操作 LED,LED 就亮起来了
我们可以初始化,在其后面加上一段代码,这样初始化后,如果不操作 LED,LED 就是熄灭了的
接下来,我们就需要完善一下 LED 驱动程序模块
除了初始化,还需要点亮和熄灭 LED 的函数
将这四个函数放在 LED.h 中说明一下
这样,LED 的驱动函数模块就封装好了,再写好代码
这时我们可以看到 LED 交替闪烁
接下来我们来写按键的代码(同样也要封装再驱动函数模块里,步骤同上,添加.c 和.h 文件)
最后,我们可以看到,按一下右边的按键 1,LED1 点亮,按一下左边的按键 2,LED2 熄灭
# 实现按键按下,LED 去反
在 LED.cZ 中给 LED1 和 LED2 分别加上翻转功能
最后将这两个函数的第一行放在头文件里申明一下
运用
# 光敏传感器控制蜂鸣器
接线图:
VCC、GND 同样是接电源,DO 数字输出端,接 PB13 号口
和上一小节一样,添加命名为 Buzzer 的.c 和.h 文件 (在 Hardware 中),封装好
编写好函数,蜂鸣器不断鸣响,说明驱动函数没有问题
在封装好光敏传感器的模块(命名为 LightSensor,同样在 Hardware 中)
运行,遮住光敏电阻,蜂鸣器响,拿开,蜂鸣器不响
# 总结
# GPIO 的使用方法:
首先初始化时钟,然后定义结构体,赋值结构体
GPIO_Mode 可以选择那 8 种输入输出模式
GPIO_Pin 选择引脚,可以用按位或的方式同时选中多个引脚
GPIO_Speed 选择输出速度
# 模块编程的方法:
一般我们自己做一个产品的话,外围硬件比较多,所以需要我们把每个硬件的驱动函数单独提取出来,封装在.c 和.h 文件里,这样有利于简化主函数的逻辑,也有利于我们移植程序,还有利于我们进行分工合作
# OLED 调试工具
# OLED 简介
- **OLED(Organic Light Emitting Diode):** 有机发光二极管
- **OLED 显示屏:** 性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点
- **0.96 寸 OLED 模块:** 小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块
- ** 供电:**3~5.5V ** 通信协议:**I2C/SPI ** 分辨率:**128*64
# 调试方式
-
** 串口调试:** 通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
优势:可以借助强大的电脑来调试,电脑端的软件,不仅可以显示单独的参数,而且可以显示曲线、图形、图像等,还可以自己做一个软件,来实现一个强大的用户交互界面
弊端: 调试的时候需要拖这电脑,而且通常的串口助手只能以信息流的方式呈现数据(即只能一行一行的打印)
-
** 显示屏调试:** 直接将显示屏连接到单片机,将调试信息打印在显示屏上
优势:对于不断变化的数据,可以覆盖刷新显示,而且显示屏可以始终接在单片机上,显示方式很直接
弊端:屏幕太小,显示内容有限,没有电脑软件那么强大的功能
-
**Keil 调试模式:** 借助 Keil 软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能
测试程序的基本思想:缩小范围、控制变量、对比测试等
# OLED 驱动函数
| 函数 | 作用 |
|---|---|
| OLED_Init(); | 初始化 |
| OLED_Clear(); | 清屏 |
| OLED_ShowChar(1, 1, 'A'); | 显示一个字符 |
| OLED_ShowString(1, 3, "HelloWorld!"); | 显示字符串 |
| OLED_ShowNum(2, 1, 12345, 5); | 显示十进制数字 |
| OLED_ShowSignedNum(2, 7, -66, 2); | 显示有符号十进制数字 |
| OLED_ShowHexNum(3, 1, 0xAA55, 4); | 显示十六进制数字 |
| OLED_ShowBinNum(4, 1, 0xAA55, 16); | 显示二进制数字 |
# OLED 显示屏
接线图:
将 4 针脚 I2C 版本中的文件复制,粘贴到 Hardware 文件中
接着进入 kile,在 Hardware 点击右键,选择 Add Files to Group 'Hardware', 选中这三个文件,点击 Add 添加
注意:如果长度参数比数字长度长,那它就会在前面补 0;如果长度参数比数字长度小,那它就会给高位的数字删掉
# EXTI 外部中断
中断系统是管理和执行中断的逻辑结构,外部中断是众多能产生中断的外设之一
# 中断系统
-
** 中断:** 在主程序运行过程中,出现了特定的中断触发条件(中断源),使得 CPU 暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
使用中断系统,能够极大的提高程序的效率
-
** 中断优先级:** 当有多个中断源同时申请中断时,CPU 会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
-
** 中断嵌套:** 当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU 再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
** 断点:** 被中断的地方
# STM32 中断
-
68 个可屏蔽中断通道,包含 EXTI、TIM、ADC、USART、SPI、I2C、RTC 等多个外设
-
使用 NVIC 统一管理中断,每个中断通道都拥有 16 个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
NVIC(嵌套中断向量控制器)就是 STM32 用来管理中断、分配优先级的
# NVIC 优先级分组
-
NVIC 的中断优先级由优先级寄存器的 4 位(0~15)决定,这 4 位可以进行切分,分为高 n 位的抢占优先级和低 4-n 位的响应优先级
优先级的数是指值越小,优先级越高,0 就是最高优先级
-
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
| 分组方式 | 抢占优先级 | 响应优先级 |
|---|---|---|
| 分组 0 | 0 位,取值为 0 | 4 位,取值为 0~15 |
| 分组 1 | 1 位,取值为 0~1 | 3 位,取值为 0~7 |
| 分组 2 | 2 位,取值为 0~3 | 2 位,取值为 0~3 |
| 分组 3 | 3 位,取值为 0~7 | 1 位,取值为 0~1 |
| 分组 4 | 4 位,取值为 0~15 | 0 位,取值为 0 |
# EXTI 简介
-
EXTI(Extern Interrupt)外部中断
-
EXTI 可以监测指定 GPIO 口的电平信号,当其指定的 GPIO 口产生电平变化时,EXTI 将立即向 NVIC 发出中断申请,经过 NVIC 裁决后即可中断 CPU 主程序,使 CPU 执行 EXTI 对应的中断程序
简单来说,就是引脚电平变化,申请中断
-
** 支持的触发方式:** 上升沿 / 下降沿 / 双边沿 / 软件触发
软件触发:即引脚什么事也没有,程序里执行一段代码,就能触发中断
-
** 支持的 GPIO 口:** 所有 GPIO 口,但相同的 Pin 不能同时触发中断(PA0 和 PB0 不能同时用,或者 PA1、PB1、PC1 这样的,端口 GPIO_Pin 一样的,只能选一个作为中断引脚)
-
** 通道数:**16 个 GPIO_Pin,外加 PVD 输出、RTC 闹钟、USB 唤醒、以太网唤醒
-
** 触发响应方式:** 中断响应 / 事件响应
注意:外部中断的 95 会触发同一个中断函数,1510 也会触发同一个中断函数,在编程的时候,我们在这两个中断函数里,需要再根据标志位来区分到底是那个中断进来的
# AFIO 复用 IO 口
- AFIO 主要用于引脚复用功能的选择和重定义
- 在 STM32 中,AFIO 主要完成 ** 两个任务:** 复用功能引脚重映射、中断引脚选择
AFIO 选择中断引脚的结构图:
图中梯形的符号,是数据选择器,有多个输入,一个输出,在侧面有选择控制端,根据控制端的数据,从输入选择一个接到输出
# EXTI 框图
图片里面像月亮的符号是或门,它可以有多个输入,但只能有一个输出,执行的是或的逻辑,在输入端,只要有一个是高电平 1,输出就是高电平 1,只有全部输入低电平 0,输出才为 0
图中带有直边的符号是于门,它可以有多个输入,但只能有一个输出,执行的是与的逻辑,在输入端,只要有一个是低电平 0,输出就是低电平 0,只有全部输入高电平 1,输出才为 1
# 旋转编码器简介
- ** 旋转编码器:** 用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
- ** 类型:** 机械触点式 / 霍尔传感器式 / 光栅式
# 硬件电路
模块的电路图:
# 对射式红外传感器计次 & 旋转编码器计次
# 对射式红外传感器计次
接线图:
我们还是把传感器的功能封装到一个模块里(具体操作看以前的示例,命名为 CountSensor, 也是放在 Hardware 文件里)
用来复位 AFIO 外设,调用一下这个函数,AFIO 外设的外置就会全部清除(350)
用来锁定 GPIO 配置,调用这个函数,参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改,这个也是 GPIO 外设的函数(361)
这两个函数是用来配置 AFIO 的事件输出功能的(362、363)
可以用来进行引脚重映射,第一个参数可以选择你要重映射的方式,第二个参数是新的状态(364)
调用这个函数,就可以配置 AFIO 的数据选择器,来选择我们想要的中断引脚(365)
和以太网有关(366)
调用它,就可以把 EXTI 的配置都清除,恢复成上电默认的状态(158)
调用这个函数,就可以这个结构体里的参数配置 EXIT 外设,初始化 EXTI 主要用的就是这个函数(159)
调用这个函数,可以把参数传递的结构体变量赋一个默认值(160)
用来软件触发外部中断,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断(161)
库函数的模板函数,因为在外设运行的过程中,会产生一些状态标志位(162、163、164、165)
中断分组,参数是中断分组的方式(196)
根据结构体里面指定的参数初始化 NVIC(197)
设置中断向量表(198)
系统低功耗配置(199)
在中断函数里,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数 EXTI10 到 EXTI15 都能进来,所以要先判断一下是不是我们想要的
最后,中断程序结束后,一定要再调用一下清除中断标志位的函数,因为只有中断标志 1 了,程序就会跳转到中断函数,如果不清除中断标志位,那它就会一直申请中断
运行后,遮挡的时候数字加 1
# 旋转编码器计次
接线图:
再 Hardware 中同上面几个步骤一样,添加名为 Encoder 的.c 和.h 文件
运行,向右转,数字增加,向左转,数字减小
中断编程的建议:
- 在这个中断函数里,最好不要执行耗时过长的代码,中断函数要简短快速,因为中断是处理突发的事情
- 最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件,尤其是硬件相关的函数
# TIM 定时中断
** 计时器:**STM32 中功能最强大、结构最复杂的一个外设
# TIM 简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断(定时器的基本功能)
- 16 位计数器、预分频器、自动重装寄存器的时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
# 定时器类型
| 类型 | 编号 | 总线 | 功能 |
|---|---|---|---|
| 高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
| 通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
| 基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发 DAC 的功能 |
这三种定时器是由高级到低级向下兼容的
STM32F103C8T6 定时器资源:TIM1、TIM2、TIM3、TIM4
基本定时器:
自动重装载寄存器、PSC 预分频器和 CNT 计数器这一块电路叫做时基单元
图中向上的折线箭头,就代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,我们一般把它叫做 “更新中断”
图中向下的折线箭头,代表的是会产生一个事件,这里对应的事件就叫做 “更新事件”,更新事件不会触发中断,但可以触发电路内部其他电路的工作
** 主模式触发 DAC 的功能 :** 它能让内部的硬件在不受程序的控制下实现自动运行
通用定时器:
高级定时器:
# 定时中断基本结构
# 预分频器时序
**CK_PSC:** 预分频器的输入时钟,选内部时钟的话一般是 72MHz
**CNT_EN:** 计数器使能,高电平计数器正常运行,低电平计算器停止
**CK_CNT:** 计数器时钟
** 计数器计数频率:**CK_CNT = CK_PSC / (PSC + 1)
# 计数器时序
** 计数器溢出频率:**CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
# 定时器定时中断 & 定时器外部时钟
# 定时器定时中断
接线图:
我们任然需要建立.c 和.h 文件(在 System 文件中,命名为 Timer)
恢复缺省配置 (1054)
时基单元初始化,第一个参数,选择某个定时器;第二个参数,结构体,里面包含了配置时基单元的一些参数 (1055)
这个函数可以把结构体变量赋一个默认值
用来使能计数器,它有两个参数,第一个 TIMx 选择定时器,第二个 NewState 新的状态,也就是使能还是失能,使能,计数器就可以运行,失能,计数器就不运行
这个是用来使能中断输出信号的,第一个 TIMx 选择定时器,第二个 TIM_IT,选择要配置那个中断输出,第三个 NewState 新的状态,使能还是失能
时钟源选择:
选择内部时钟 (1073)
选择 ITRx 其他定时器的时钟,参数 InputTriggerSource,选择要接入那个其他的定时器 (1074)
选择 TIx 捕获通道的时钟 (1075),参数 TIxExternalCLKSource,选择 TIx 具体的某个引脚,ICPolarity 和 ICFilter,输入的极性和滤波器
选择 ETR 通过外部时钟模式 1 输入的时钟,参数 ExtTRGPrescaler,外部触发预分频器,这里可以对 ETR 的外部时钟再提前做一个分频 (1077)
选择 ETR 通过外部时钟模式 2 输入的时钟 (1079)
单独用来配置 ETR 引脚的预分频器、极性、滤波器这些参数的 (1081)
用来单独写预分频值,参数,Prescaler,就是要写入的预分频值,PSCReloadMode,写入的模式(预分频器有一个缓冲器,写入的值是在更新事件发生后才有效的),可以选择是听从安排,在更新事件生效,或者是,在写入后,手动产生一个更新事件,让这个值立即生效 (1083)
用来改变计数器的计数模式,参数,CounterMode,选择新的计数模式 (1084)
自动重装器预装功能配置
给计数器写入一个值 (1125)
给自动重装器写入一个值 (1126)
获取当前计数器的值,返回值就是当前计数器的值 (1140)
获取当前的预分频器的值 (1141)
获取标志位和清除标志位 (1142~1145)
跨越不同文件的变量的解决方法:
-
如果你想跨文件使用变量,可以在使用变量那个文件的上面,用 extern 申明一下要用的变量
extern 申明变量,就是告诉编译器,我现在有 Num 这个变量,它在别的文件里定义了,编译器就会自己找到其所在的位置,找到之后,它就会把这个 extern 申明的变量,当作被找到的文件的一个引用
注意:这个过程并没有定义新的变量
-
直接把这个中断函数复制一下,放在主函数后面
运行之后,Num 的值就正在执行每秒 + 1 的操作
# 定时器外部时钟
接线图:
运行之后,当当光片挡一下,CNT 加 1,加到 9 后,自动清 0,同时申请中断,Num++
# TIM 输出比较
** 输出比较功能:** 主要是用来输出 PWM 波形的,PWM 波形又是驱动电机的必要条件
# 输出比较简介
- OC(Output Compare)输出比较
- 输出比较可以通过比较 CNT 与 CCR 寄存器值的关系,来对输出电平进行置 1、置 0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形
- 每个高级定时器和通用定时器都拥有 4 个输出比较通道
- 高级定时器的前 3 个通道额外拥有死区生成和互补输出的功能
# PWM 简介
-
PWM(Pulse Width Modulation)脉冲宽度调制
PWM 波形是一个数字输出信号,意思由高低电平组成的
-
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
-
PWM 参数:
频率 = 1 / Ts 占空比 = Ton / Ts 分辨率 = 占空比变化步距
Ton 在这里是高电平的时间 Ts 是一个周期的时间
占空比决定了 PWM 等效出来的模拟电压的大小,占空比越大,等效的模拟电压就越趋近于高电平,占空比越小,等效的模拟电压就越趋近于低电平,这个等效关系一般来说是线型的
使用 PWM 波形,就可以在数字系统等效输出模拟量,就能实现 LED 控制亮度、电机控速等功能
输出比较通道 (通用)
输出比较通道 (高级)
图中 REF 信号(参考信号)实际上就是指这里信号的高低电平
到图中三角形部分,就是极性选择,就是选择是不是要把高低电平反转一下
# 输出比较模式
| 模式 | 描述 |
|---|---|
| 冻结 | CNT=CCR 时,REF 保持为原状态 (即 CNT 和 CCR 无效) |
| 匹配时置有效电平 | CNT=CCR 时,REF 置有效电平 |
| 匹配时置无效电平 | CNT=CCR 时,REF 置无效电平 |
| 匹配时电平翻转 | CNT=CCR 时,REF 电平翻转 |
| 强制为无效电平 | CNT 与 CCR 无效,REF 强制为无效电平 |
| 强制为有效电平 | CNT 与 CCR 无效,REF 强制为有效电平 |
| PWM 模式 1 | 向上计数:CNT<CCR 时,REF 置有效电平,CNT≥CCR 时,REF 置无效电平 向下计数:CNT>CCR 时,REF 置无效电平,CNT≤CCR 时,REF 置有效电平 |
| PWM 模式 2 | 向上计数:CNT<CCR 时,REF 置无效电平,CNT≥CCR 时,REF 置有效电平 向下计数:CNT>CCR 时,REF 置有效电平,CNT≤CCR 时,REF 置无效电平 |
# 参数计算
- PWM 频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
- PWM 占空比: Duty = CCR / (ARR + 1)
- PWM 分辨率: Reso = 1 / (ARR + 1)
# 舵机简介
- 舵机是一种根据输入 PWM 信号占空比来控制输出角度的装置
- ** 输入 PWM 信号要求:** 周期为 20ms(即 50Hz),高电平宽度为 0.5ms~2.5ms
# 直流电机及驱动简介
-
直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
-
直流电机属于大功率器件,GPIO 口无法直接驱动,需要配合电机驱动电路来操作
电机这类器件基本都属于大功率设备,必须要加驱动电路才能控制
-
TB6612 是一款双路 H 桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向
# PWM 驱动 LED 呼吸灯 & PWM 驱动舵机 & PWM 驱动直流电机
# PWM 驱动 LED 呼吸灯
接线图:
同上几个步骤一样,建立.c 和.h 文件(在 Hardware 文件中,命名为 PWM)
接下来,打通模块,就可以输出 PWM
具体步骤为:
- PCC 开启时钟,把我们要用的 TIM 外设和 GPIO 外设的时钟打开
- 配置时基单元
- 配置输出比较单元
- 配置 GPIO,把 PWM 对应的 GPIO 口,初始化为复用推挽输出的配置
- 运行控制
配置输出比较模块
给输出比较结构体赋一个默认值
配置强制输出模式(如果你在运行中想要暂停波形并且强制输出高或低电平,可以用)
用来配置 CCR 寄存器的预装功能(即影子寄存器)(1096~1099)
用来快速配置使能(1100~1103)
外部事件时清除 REF 信号(1104~1107)
用来单独设置输出比较的极性,这里带 N 的就是高级定时器里互补通道的配置,OC4 没有互补通道(1108~1114)
一般来说,结构体里的参数,都会有一个单独的函数可以进行更改
用来单独修改使能参数(1115、1116)
选择输出比较模式,用来单独更改输出比较模式的函数
用来单独更改 CCR 寄存器值的函数
仅高级定时器使用,在使用高级定时器输出 PWM 时,需要调用这个函数,使能主输出,否则 PWM 将不能正常输出
运行之后,LED 点亮,并以 1kHz 的频率闪烁
实现呼吸灯效果
运行之后,LED 不断变亮变暗,呈现了呼吸灯的效果
重映射:更改复用的引脚
# PWM 驱动舵机
接线图:
编写代码
运行之后,舵机就移动到 0 度的位置了(如果我们稍微扳一下输出轴,可以感觉有一股力在维持它现在的位置)
在 Hardware 中添加.c 和.h 文件,命名为 Servo
运行之后,可以看到舵机的角度,每按一下,加 30 度
# PWM 驱动直流电机
接线图:
在 Hardware 中添加.c 和.h 文件,命名为 Motor
运行,我们可以看到电机已经转起来了
同时,现在可以发现一个问题,就是这个电机会发出蜂鸣器的响声(是正常现象),如果想避免,可以加大 PWM 频率
运行,按下按键,OLED 上速度变量变化,同时电机也转起来了
# TIM 输入捕获
# 输入捕获简介
- IC(Input Capture)输入捕获
- 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前 CNT 的值将被锁存到 CCR 中,可用于测量 PWM 波形的频率、占空比、脉冲间隔、电平持续时间等参数
- 每个高级定时器和通用定时器都拥有 4 个输入捕获通道
- 可配置为 PWMI 模式,同时测量频率和占空比
- 可配合主从触发模式,实现硬件全自动测量
# 频率测量
** 测频法(测高频):** 在闸门时间 T 内,对上升沿计次,得到 N,则频率
f_x=N / T
T 通常设置为一秒
** 测周法(测低频):** 两个上升沿内,以标准频率 fc 计次,得到 N ,则频率
f_x=f_c / N
** 中界频率:** 测频法与测周法误差相等的频率点
f_m=√(f_c / T)
# 输入捕获通道
# 主从触发模式
就是主模式、从模式和触发源选择这三个功能的选择
** 主模式:** 可以将定时器内部的信号,映射到 TRGO 引脚,用于触发别的外设
** 从模式:** 就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也接收被别的信号控制
复位模式(是从模式的一部分):选中的触发输入(TRGI)的上升沿重新初始化计数器,并且产生一个更新寄存器的信号
** 触发源选择:** 接收选择从模式的触发信号源的,可以认为它是从模式的一部分,选择一个指定的信号,得到 TRGI,TRGI 去触发从模式
# 输入捕获模式测频率 & PWMI 模式测频率占空比
# 输入捕获模式测频率
接线图:
在 Hardware 中添加.c 和.h 文件,命名为 IC
步骤:
- RCC 开启时钟,把 GPIO 和 TIM 的时钟打开
- GPIO 初始化,把 GPIO 配置成输入模式,一般选择上拉输入或者浮空输入模式
- 配置时基单元,让 CNT 计数器在内部时钟的驱动下自增运行
- 配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道、分频器这些参数,用一个结构体就可以统一进行配置了
- 选择从模式的触发源
- 选择触发之后执行的操作,执行 Reset 操作
- 最后,调用 TIM_Cmd 函数,开启定时器
** 注意:** 滤波器和分频器的区别,滤波器记次,并不会改变信号的原有频率,一般滤波器的采样频率都会高于信号频率,所以它只会滤除高频噪声,使信号更平滑;分频器就是对信号本身进行记次,会改变频率
运行,可以测量到频率
# PWMI 模式测频率占空比
接线图:
目前测量结果,频率 1000Hz, 占空比 50%
# TIM 编码器接口
# 编码器接口简介
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制 CNT 自增或自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有 1 个编码器接口
- 两个输入引脚借用了输入捕获的通道 1 和通道 2
# 正交编码器
正转:
| 边沿 | 另一相状态 |
|---|---|
| A 相↑ | B 相低电平 |
| A 相↓ | B 相高电平 |
| B 相↑ | A 相高电平 |
| B 相↓ | A 相低电平 |
反转:
| 边沿 | 另一相状态 |
|---|---|
| A 相↑ | B 相高电平 |
| A 相↓ | B 相低电平 |
| B 相↑ | A 相低电平 |
| B 相↓ | A 相高电平 |
# 编码器接口基本结构
# 编码器接口测速
接线图:
在 Hardware 中添加.c 和.h 文件,命名为 Encoder
运行,目前显示 CNT 是 0,向右转一下编码器,可以看到值变化(增加),往左转,CNT 自减
在.c 文件后面加上
运行,目前 CNT 的值就代表速度,单位是脉冲个数 /s, 向右慢速转,速度是正数,比较小,快速转,正数比较大,向左慢速转,速度负数。比较小,快速转,负数比较大
如果主程序中没有其他东西的话,可以使用 Delay 函数,但是如果有其他东西的话,最好就不要在主函数加入过长的 Delay, 这样会阻塞主循环的执行,可以用定时中断
运行,向右旋转,正的速度;向左旋转,负的速度;停止速度为 0
# ADC 模数转换器
# ADC 简介
-
ADC(Analog-Digital Converter)模拟 - 数字转换器
-
ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
** 注:** 数字到模拟的桥梁 ----DAC,数字模拟转换器,使用 DAC 就可以将数字变量转换为模拟电压,同时,PWM 也可以实现数字到模电的转变,但是 PWM 只有完全导通和完全断开两种状态,在这两种状态下都没有功率损耗,所以在直流电机调速这种大功率的应用场景,使用 PWM 来等效模拟量,是比 DAC 更好的选择,并且 PWM 电路更加简单,更加常用
-
12 位逐次逼近型 ADC,1us 转换时间
-
** 输入电压范围:**03.3V,** 转换结果范围:**04095
-
18 个输入通道,可测量 16 个外部和 2 个内部信号源
-
规则组和注入组两个转换单元
-
模拟看门狗自动监测输入电压范围
-
STM32F103C8T6 **ADC 资源:**ADC1、ADC2,10 个外部输入通道
# 逐次逼近型 ADC
# STM32ADC 框图
# ADC 基本结构
# 输入通道
| 通道 | ADC1 | ADC2 | ADC3 |
|---|---|---|---|
| 通道 0 | PA0 | PA0 | PA0 |
| 通道 1 | PA1 | PA1 | PA1 |
| 通道 2 | PA2 | PA2 | PA2 |
| 通道 3 | PA3 | PA3 | PA3 |
| 通道 4 | PA4 | PA4 | PF6 |
| 通道 5 | PA5 | PA5 | PF7 |
| 通道 6 | PA6 | PA6 | PF8 |
| 通道 7 | PA7 | PA7 | PF9 |
| 通道 8 | PB0 | PB0 | PF10 |
| 通道 9 | PB1 | PB1 | |
| 通道 10 | PC0 | PC0 | PC0 |
| 通道 11 | PC1 | PC1 | PC1 |
| 通道 12 | PC2 | PC2 | PC2 |
| 通道 13 | PC3 | PC3 | PC3 |
| 通道 14 | PC4 | PC4 | |
| 通道 15 | PC5 | PC5 | |
| 通道 16 | 温度传感器 | ||
| 通道 17 | 内部参考电压 |
# 转换时间
如果不需要高速的转换频率,那么转换时间就可以忽略
-
**AD 转换的步骤:** 采样,保持,量化,编码
-
STM32 ADC 的总转换时间为:
TCONV = 采样时间 + 12.5 个 ADC 周期
-
** 例如:** 当 ADCCLK=14MHz,采样时间为 1.5 个 ADC 周期
TCONV = 1.5 + 12.5 = 14 个 ADC 周期 = 1μs
# 校准
- ADC 有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码 (数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
- 建议在每次上电后执行一次校准
- 启动校准前, ADC 必须处于关电状态超过至少两个 ADC 时钟周期
# 硬件电路
第一个是定位器产生一个可调的电压,当滑动端往上滑时,电压增大,往下滑时,电压减小,一般至少要接千欧姆(** 注:** 电阻的阻值不能给太小,因为电阻两端是直接跨接在电压正负极的,如果阻值太小,那这个电阻就会比较费电,再小就有可能发热冒烟)
中间是传感器输出电压的电路,传感器阻值变小时,下拉作用变强,输出端电压就下降,一般来说,像光敏电阻、热敏电阻、红外接收管、麦克风等,都可以等效为一个可变电阻,由于电阻阻值无法直接测量,所以就可以通过和一个固定电阻串联分压,来得到反应电阻值电压的电路
一个简单的电压转换电路,但是高电压不建议使用此电路,高电压采集最好使用一些专用的电压芯片,比如隔离放大器
# AD 单通道 & AD 多通道
# AD 单通道
接线图:
在 Hardware 中添加.c 和.h 文件,命名为 AD
# 介绍
这个函数(在 RCC 库函数里面)是用来配置 ADCCLK 分频器的,它可以对 APB2 的 72MHz 时钟选择 2、4、6、8 分频,输入到 ADCCLK
用于给 ADC 上电(431)
用于开启 DMA 输出信号(432)
中断输出控制,用于控制某个中断,能不能通往 NVIC(433)
复位校准、获取复位校准状态、开始校准、获取开始校准状态【** 注:** 这四个就是控制校准的函数】(434、435、436、437)
ADC_软件开始转换控制,用于软件触发(438)
ADC_获取软件开始转换状态(439)
获取标志位状态,参数给 EOC 的标志位,判断 EOC 标志位是不是置 1 了,如果转化结束,EOC 标志位置 1,然后调用这个函数,判断标志位
440 和 441 用来配置间断模式的
每隔几个通道间断一次(440)
是不是启用间断模式(441)
ADC_规则组通道配置,给序列的每个位置填写指定的通道,第一个参数是 ADCx, 第二个 ADC_Channel,就是你想指定的通道,第三个 Rank,就是序列几的位置,第四个 ADC_SampleTime,就是指定通道的采样时间(442)
ADC_外部触发转换控制,就是是否允许外部触发转换(443)
ADC_获取转换值,就是获取 AD 转换的数据寄存器(444)
ADC_获取双模式转换值,这个是双 ADC 模式读取转换结果的函数(445)
这三个函数就是对是对模拟看门狗进行配置的
是否启动模拟看门狗(456)
配置高低阈值(457)
配置看门的通道(458)
ADC_温度传感器、内部参考电压控制,这个是用来开启内部的两个通道的(459)
获取标志位状态、清除标志位、获取中断状态、清除中断挂起位(460~463)
在 AIN 模式下,GPIO 口是无效的,断开 GPIO,防止 GPIO 口的输入输出对模拟电压造成干扰,所以 AIN 模式就是 ADC 的专属模式
独立模式,就是 ADC1 和 ADC2 各转换各的
剩下的就全是双 ADC 模式
第一个,模拟看门狗标志位
第二个,规则组转换完成标志位
第三个,注入组转换完成标志位
第四个,注入组开始转换标志位
第五个,规则组开始转换标志位
# 代码
可以看到,OLED 上已经有数据了,电位器往左拧,数据减小,最小值是 0,往右拧,数据增大,最大是 4095
如果你想显示实际的电压值,只需要对这个数据进行一个线性变换就行了
这样电压值就已经转换出来了,拧动电位器,AD 值在变化,电压值也对应变化
# AD 多通道
接线图:
AD0 代表电位器,AD1 代表光敏,AD2 代表热敏,AD3 代表对射传感器
# DMA 直接存储器存取
# DMA 简介
- DMA(Direct Memory Access)直接存储器存取
- DMA 可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须 CPU 干预,节省了 CPU 的资源
- 12 个独立可配置的通道: DMA1(7 个通道), DMA2(5 个通道)
- 每个通道都支持软件触发和特定的硬件触发
- STM32F103C8T6 **DMA 资源:**DMA1(7 个通道)
# 存储器映像
| 类型 | 起始地址 | 存储器 | 用途 |
|---|---|---|---|
| ROM | 0x0800 0000 | 程序存储器 Flash(主闪存) | 存储 C 语言编译后的程序代码 |
| 0x1FFF F000 | 系统存储器 | 存储 BootLoader,用于串口下载 | |
| 0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | |
| RAM | 0x2000 0000 | 运行内存 SRAM | 存储运行过程中的临时变量 |
| 0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 | |
| 0xE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 |
**ROM:** 只读存储器,是一种非易失性、掉电不丢失的存储器
**RAM:** 随机存储器,是一种易失性、掉电丢失的存储器
# DMA 框图
总线矩阵的左端,是主动单元,也就是拥有存储器的访问权;右边,是被动单元,它们的存储器只能被左边的主动单元读写
# DMA 基本结构
** 起始地址:** 这个参数决定了数据是从那来,到那去的
** 数据宽度:** 指定一次转运要按多大的数据宽度来进行,它可以选择字节 Byte、半字 Halfword 和字 word (字节就是 8 位,也就是一次转运一个 uint_8;半字就是 16 位,也就是一次转运一个 uint_16;节就是 32 位,也就是一次转运一个 uint_32)
** 地址是否自增:** 指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去(相当于指针)
** 传输计数器:** 用来指定,总共需要转运几次,这是一个自减计数器,转运过程中,每转运一次,计数器的数就会减 1,当传输器计数器减到 0 之后,DMA 就不会再进行数据转运了。另外,它减到 0 之后,之前自增的地址,也会恢复到起始地址的位置,以方便之后 DMA 开启新一轮的转运
** 自动重装器:** 传输计数器减到 0 之后,是否要自动恢复到最初的值,决定了转运的模式
DMA 转运的条件:
- 开关控制,DMA_Cmd 必须使能
- 传输计数器必须大于 0
- 触发源,必须要触发信号
# 数据宽度与对齐
如果你把小的数据转到大的里面去,高位就会补 0;如果把大的数据转到小的里面,高位就会舍弃掉;如果数据宽度一样,那就没事了
# DMA 数据转运 & DMA+AD 多通道
# DMA 数据转运
接线图:
** 需要定义常量的情况:** 当我们程序中出现了一大批数据,并且不需要更改时,就可以把它定义成常量,这样能节省 SRAM 的空间
在 System 中添加.c 和.h 文件,命名为 MyDMA
DMA_设置当前数据寄存器,即给传输计数器写数据(415)
DMA_获取当前数据寄存器,即返回传输计数器的值,可以看还剩多少数据没有转运(416)
DMA 是 AHB 总线的设备,使用要用 AHB 开启时钟的函数
运行,在屏幕上,前两行是转运前的 DataA 和 DataB,可以看到 DataA 是 1、2、3、4,DataB 是 0、0、0、0,之后调用 MyDMA_Init,初始化 DMA,并且初始化之后就立刻开始了运转,转运之后,可以看到,DataA 和 DataB 都变成了 1、2、3、4,这说明 DataA 的数据成功运转到了 DataB,在整个过程中,源端数据 DataA 是不会变化的
运行,实现 DMA 数据转运
如果想把 Flash 的数据转运到 SRAM 里面的话,可以:
# DMA+AD 多通道
接线图:
完成
# USART 串口协议
# 通信接口
- ** 通信的目的:** 将一个设备的数据传送到另一个设备,扩展硬件系统
- ** 通信协议:** 制定通信的规则,通信双方按照协议规则进行数据收发
| 名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
|---|---|---|---|---|---|
| USART | TX(数据发送脚)、RX(数据接收脚) | 全双工 | 异步 | 单端 | 点对点 |
| I2C | SCL(时钟)、SDA(数据) | 半双工 | 同步 | 单端 | 多设备 |
| SPI | SCLK(时钟)、MOSI(主机输出数据脚)、MISO(主机输入数据脚)、CS(片选,用于指定通信对象) | 全双工 | 同步 | 单端 | 多设备 |
| CAN | CAN_H、CAN_L(这两个是差分数据脚,用两个引脚表示一个差分数据) | 半双工 | 异步 | 差分 | 多设备 |
| USB | DP、DM(一对差分数据脚) | 半双工 | 异步 | 差分 | 点对点 |
** 全双工:** 指通信双方能同时进行双向通信,一般来说,全双工的通信都有两根通信线
** 单工(扩展):** 指在数据只能从一个设备到另一个设备,而不能反着来
# 串口通信
- 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
- 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
# 硬件电路
- 简单双向串口通信有两根通信线(发送端 TX 和接收端 RX)
- TX 与 RX 要交叉连接
- 当只需单向的数据传输时,可以只接一根通信线
- 当电平标准不一致时,需要加电平转换芯片
# 电平标准
电平标准是数据 1 和数据 0 的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
- TTL 电平:+3.3V 或 + 5V 表示 1,0V 表示 0
- RS232 电平:-3-15V 表示 1,+3+15V 表示 0
- **RS485 电平:** 两线压差 + 2+6V 表示 1,-2-6V 表示 0(差分信号)
差分信号抗干扰能力非常强,使用 RS485 电平标准,通信距离可达上千米,而上面的两种电平,最远只能达到几十米
# 串口参数及时序
-
** 波特率:** 串口通信的速率
-
** 起始位:** 标志一个数据帧的开始,固定为低电平
-
** 数据位:** 数据帧的有效载荷,1 为高电平,0 为低电平,低位先行
数据位有两种表示方法:一种是把校验位作为数据位的一部分;另一种就是把数据位和校验位独立开,数据位就是有效载荷,校验位就是独立的 1 位
-
** 校验位:** 用于数据验证,根据数据位计算得来
-
** 停止位:** 用于数据帧间隔,固定为高电平
** 总结:**TX 引脚输出定时翻转的高低电平,RX 引脚定时读取引脚的高低电平,每个引脚的数据加上起始位、停止位、可选的校验位,打包为数据帧,依次输出在 TX 引脚,另一端 RX 依次接收,这样就完成了字节数据的传递,这就是串口通信
# USART 串口外设
# USART 简介
-
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步 / 异步收发器
**UART:** 异步收发器
-
USART 是 STM32 内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从 TX 引脚发送出去,也可自动接收 RX 引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
-
自带波特率发生器,最高达 4.5Mbits/s
** 波特率发生器:** 用来配置波特率,其实就是一个分频器
-
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
-
可选校验位(无校验 / 奇校验 / 偶校验)
-
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
** 硬件流控制:** 防止因为 B 处理过慢而导致的数据丢失的问题
如果有大量的数据进行收发,可以使用 DMA 转运数据,减轻 CPU 的负担
-
STM32F103C8T6 USART 资源: USART1、 USART2、 USART3
# USART 框图
有了 TDR 和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲,提高了工作效率
** 唤醒单元:** 实现串口挂载多设备(串口一般是点对点的通信,点对点,只支持两个设备互相通信,想发数据直接发就行;而多设备,在一条总线上,可以接多个从设备,每个设备分配一个地址)
# USART 基本结构
** 波特率发生器:** 用于产生约定的通信速率
# 数据帧
# 起始位侦测
当输入电路侦测到一个数据帧的起始位后,就会以波特率的频率,连续采样一帧数据,同时,从起始位开始,采样位置就要对齐到位的正中间
# 数据采样
数据采样这一段中,在没有噪声的理想情况下,这 3 次肯定全为 1 或者全为 0(全为 1,就认为收到了 1;全为 0,就认为收到了 0);如果有噪声,导致 3 次采样不是全为 1 或者全为 0,那它就按照 2:1 的规则来(两次为 1,就认为收到了 1;两次为 0,就认为收到了 0),在这种情况下,噪声标志位 NE 也会置 1
# 波特率发生器
- 发送器和接收器的波特率由波特率寄存器 BRR 里的 DIV 确定
- ** 计算公式:** 波特率 = fPCLK2/1 / (16 * DIV)
DIV 分为整数部分和小数部分,可以实现更细腻的分频
# 数据模式
- **HEX 模式 / 十六进制模式 / 二进制模式:** 以原始数据的形式显示
- ** 文本模式 / 字符模式:** 以原始数据编码后的形式显示
# 串口发送 & 串口发送 + 接收
# 串口发送
接线图:
在 Hardware 中添加.c 和.h 文件,命名为 Serial
这两个函数是用来配置同步时钟输出的,包括时钟是不是要输出,时钟的极性相位等函数
可以开启 USART 到 DMA 的触发通道(372)
发送数据(378)
接收数据(379)
运行
# 串口发送 + 接收
接线图:
同时开启发送和接收
我们先演示一下查询
查询的流程是,在主函数里不断判断 RXNE 标志位,如果置 1 了,就说明收到数据了,那再调用 ReceiveData,读取 DR 寄存器,这样就可以了
**RXNE 标志位:** 当 RDR 移位寄存器中的数据被转移到 USART_DR 寄存器中,该位被硬件置位,如果 USART_CR1 寄存器中的 RXNEIE 为 1,则产生中断,对 USART_DR 的读操作可以将该为清 0
添加:
运行,此时,我们需要在发送区里写入数据(发送模式可以选择 HEX 模式或文本模式,HEX 就是原始数据,文本模式首先要过一遍字符编码),我们选择 HEX 模式,写 41,再点发送
再面包板上可以看到,就收到了数据 41
接下来,我们演示一下中断的程序
运行,发送 41,屏幕显示 41,串口接收回传的数据 41
# USART 串口数据包
# HEX 数据包
在 HEX 数据包里,数据都是以原始的字节数据本身呈现的
** 数据包的作用:** 把一个个单独的数据打包起来,方便我们进行多字节的数据通信,就是把同一批的数据进行去打包和分割
** 串口数据包:** 通常使用的是额外添加包头包尾的这种方式
数据包会出现的问题:
-
包头包尾和数据载荷重复
如果数据和包头包尾重复,可能会引起误判
解决方法:
(1) 限制载荷数据的范围
(2) 如果无法避免载荷数据和包头包尾重复,那我们就尽量使用固定长度的数据包,这样由于载荷数据是固定的,只要我们通过包头包尾对齐了数据,我们就可以知道,那个数据应该是包头包尾,那个数据应该是载荷数据
(3) 增加包头包尾的数量,并且尽量让它呈现出载荷数据出现不了的状态
-
包头包尾并不是全部都需要
-
固定包长和可变包长的选择
-
各种数据转化为字节流
只需要用一个 uint_8 的指针指向它,把它们当作一个字节数组发送就行了
# 文本数据包
在文本数据包里,每个字节就经过了一层编码和译码,实际上,每个文本字符背后,其实都还是一个字节的 HEX 数据
由于数据译码成了字符形式,这就会存在大量的字符可以作为包头包尾,可以有效避免载荷和包头包尾重复的问题
HEX 数据包和文本数据包的优缺点:
HEX 数据包的优点是,传输最直接,解析数据非常简单,比较适合模块发送原始的数据,缺点就是灵活性不足、载荷容易和包头包尾重复
文本数据包的优点是,数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,缺点就是解析效率低
# HEX 数据包接收
** 状态机:** 在程序中,我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移
我们可以定义三个状态,第一个状态是等待包头,第二个状态是接收数据,第三个状态是等待包尾,每个状态需要用一个变量来标志一下
# 文本数据包接收
# 串口收发 HEX 数据包 & 串口收发文本数据包
# 串口收发 HEX 数据包
接线图:
代码:
运行,打开串口,发送模式喝和接收模式都选择 HEX 模式,然后按一下复位,可以看到,数据包就送过来了
接下来,我们就写一下接收这个数据包的代码
在接收中断函数里,我们需要用状态机来执行接收逻辑
在原有的代码上,添加以下代码
运行,在发送区,按照数据包格式发送数据
** 注意:**RxPacket 数组,它是一个同时被写入又同时被读出的数组,即在中断函数里,我们会依次写入它,在主函数里,我们又会依次读出它,这回造成数据包之间可能回混在一起
** 解决方法:** 可以在接收部分加入判断,就是在每个数据包读取处理完毕后,再接收下一个数据包(当然很多情况下其实还可以不进行,像 HEX 数据包,多是用于传输各种传感器,它们相邻数据包的数据,具有连续性,这样即使相邻数据包混在一起了,也没关系)
接下来,完善最终的程序现象
运行,首先是发送数据包,按一下按键,变换一下数据,发送一个数据包,OLED 显示发送数据,串口助手收到数据
然后是接收数据包,发送指定格式的数据包,OLED 显示接收到的数据包
# 串口收发文本数据包
接线图和串口收发 HEX 数据包的图是一样的
编写代码
运行,发送 LED_ON,开灯,发送 LED_OFF,关灯
# FlyMcu 串口下载 & C Utility
# FlyMcu
**FlyMcu:** 可以通过串口给 STM32 下载程序,如果你没有 STLINK,就可以用这个软件通过串口下载程序
**FlyMcu 程序烧录软件:** 是绿色软件,打开就能直接运行,不需要安装
演示串口下载的流程:
打开 FlyMcu 程序烧录软件
** 硬件:** 我们需要连接一个串口的电路,这个电路要能保证 USART1 和电脑进行串口通信
** 软件:** 随便打开一个实例代码,为了实现串口下载,我们须要配置工程,生成一个 HEX 文件
点击工程选项,在 Output 选项卡里,把创建 HEX 文件的勾,勾上,点击 OK
这时再编译一下,下面信息就多了一条,创建 HEX 文件的信息
编程没有问题,我们就可以打开工程目录,在 Objects 文件夹下,找到我们刚才生成的 HEX 文件,这就是我们串口下载我们所需要的程序文件
接下来我们就可以用 FlyMcu 下载程序了
首先先点搜索串口,然后 Port 这里我们选择串口通信的 COM 号,bps,波特率,可以保持默认的 115200,接着选择程序文件,点击三个点的按钮,找到我们刚才生成的 HEX 文件,下面其他的配置,我们暂时先保持默认
在开始编程之前,我们还需要配置 BOOT 引脚,让 STM32 执行 BootLoader 程序
否则,点击开始编程,它回一直停在这个位置
# 如何进入 BootLoader?
第一步,找到这里的跳线帽,这两个跳线帽是用来配置 VOOT 引脚的,把配置 BOOT0 引脚的跳线帽拔下来,然后插在右边两个针脚,配置 BOOT0 为 1
第二步,按一下复位键,让程序重新开始运行(切换 BOOT 引脚后,一定要再按一下复位键,因为 STM32 只有再刚复位时才回读取 BOOT 引脚,程序运行之后,切换 BOOT 引脚是无效的),这样,芯片就进入 BootLoader 程序了,之后,STM32 执行的程序就是不断接收 USART1 的数据,刷新到主闪存
接着,回到 FlyMcu 程序,点击开始编程
最后显示下载成功
回到 STM32,目前 LED 还没有亮起来,这是因为目前 STM32 还在执行 BootLoader 的刷机程序,我们还需要把 BOOT 引脚换回来,拔掉 BOOT0 的跳线帽,换到左边两个引脚,然后按一下复位,可以看到 LED 闪烁,程序运行正常
# BOOT 引脚是做什么的?为什么要这样配置?BootLoader 是做什么的?串口下载的原理是什么?
**BOOT 引脚:** 在芯片复位时,根据这些引脚的电平状态(高电平或低电平),决定 CPU 将程序计数器(Program Counter, PC)指向哪个内存地址,从而选择不同的启动模式
| BOOT0 引脚状态 | BOOT1 引脚状态 | 启动模式 | 描述 |
|---|---|---|---|
| 0 (低电平) | X (任意) | 从主闪存存储器启动 | 这是正常运行模式。MCU 上电后直接执行用户烧录到 Flash 中的应用程序。绝大多数时候,设备都是运行在这个模式下。 |
| 1 (高电平) | 0 (低电平) | 从系统存储器启动 | MCU 会执行其内部的 ROM Bootloader 程序。通过这个 Bootloader,你可以使用串口工具(如 STM32CubeProgrammer、FlyMcu 等)将新的固件下载到芯片的 Flash 中。这是进行固件烧录和恢复的关键模式。 |
| 1 (高电平) | 1 (高电平) | 从嵌入式 SRAM 启动 | MCU 会从内部 SRAM 的起始地址开始执行。这通常用于高级开发、调试或运行一些临时性、不需要写入 Flash 的小程序。例如,有些自定义的二级 Bootloader 可能会先加载到 SRAM 运行。 |
**BootLoader:**Bootloader 就像是嵌入式系统的 “开机管理员” 和 “维修工具箱”。它确保设备能够正确启动,提供一种便捷的方式来更新或恢复设备固件,极大地简化了产品的开发、生产、维护和升级过程。
** 串口下载的原理:** 在目标设备上运行一个预先烧录的、小型且固定的程序(称为 Bootloader,引导加载程序),该 Bootloader 负责接收主机(PC 或编程器)通过串口发送的数据,并将其写入到目标设备的指定存储区域
# 每次下载程序,都要拔两边跳线帽,太麻烦了,有没有什么解决方法?
想要程序自我更新,就必须有一个切换小机器人的过程,BOOT0 引脚和 RST 复位引脚必须得有高低电平变化
也可以接两个线,当点击下载时,自动帮我设置 BOOT0 和 RST 的电平
FlyMcu 下面有一个下拉框,就是用来配置 DTR 和 RTS 的,这些配置需要根据一键下载电路来选(一般最常用的配置是 DTR 的低电平复位,RST 高电平进 BootLoader)
# 读 Flash
点击读 Flash,放在桌面,起个名字 led, 保存
这样就可以将芯片里的程序读出来,桌面上也出现了程序文件
这个文件里面记录了 STM32 从 0800 开始存储的程序数据
.bin 格式是没有地址信息的原始数据文件(之前生成的.hex 文件,是有地址信息的,不过作为记录程序代码的文件,这两种格式的作用其实都是一样的)
点击清除芯片,可以把主程序区域全部擦除,擦除之后,所有的数据都是 FF
之后读器件信息,这是会把芯片的序列号什么的信息读出来
点击设置选项字节的按钮,选择 STM32F1 的这一项
这个界面展示的就是选择字节里面的参数
第一块,就是读保护(如果你做产品,不开启读保护,别人就很容易把你的程序偷走了),为了保护程序的安全,选项字节里就有一个参数可以配置读保护
** 注意:** 如果你阻止读出,再回到 Keil 下载程序时就回失败,如果因为读保护下载失败,那需要再到这个地方来,取消读保护,另外,在取消读保护时,会同时清空芯片的程序,这样程序就不会被偷走了
选项字节里面参数的好处:
- 选择字节的数据相当于是世外桃源,无论程序怎么更新,选项字节里面的数据都可以不变,我们可以用这些字节来存储不随程序变化而变化的参数
- 可以用上位机很方便的修改,在上位机里,可以直接修改选项字节的内容
** 写保护字节:** 可以对 Flash 的每几个页单独进行写保护,设置写保护之后,就无法再写了,如果想再次写入的话,解除写保护就行了
** 注意:** 设置写保护之后,再下载,如果需要写入保护区的话,就会出错
配置好之后,就可以点击采样这个设置
然后把编程到 Flash 时写选择字节的勾勾上,再执行正常的下载流程,就能更新选择字节的配置了
# STLINK Utility
**STLINK Utility:** 这个软件是配合 STLINK 使用的一个工具,可以通过 STLINK 给 STM32 下载程序
安装成功之后出现的图标:
软件主页面:
** 硬件部分:** 只需要把 STLINK 链接好,串口可以不连,跳键帽两个都在最左边,复位
之后,点击这个按钮进行连接,连接好之后,这里就会出现一些器件的信息
下面的大框框,展示的是 STM32 里面 0800 开始的程序数据
如果点击保存,就可以把这个程序给存起来,同时可以选择保存为.hex 或者.bin 文件
如果要下载程序,先点击第一个按钮,打开文件,这里文件支持.hex 和.bin,打开之后,点击编程按钮,点击 Start 开始下载
下载之后,我们就可以看到代码所对应的现象
# 选项字节的配置
可以点击 Target,选择 Option Bytes...
这样就可以打开选项字节的配置的页面了
第一块,读保护,可以使能或失能
第二块,硬件参数,灰色选项的表示的是我们现在使用的芯片上没有这些功能
第三块,用户参数
第四块,写保护
配置好之后,直接点击 Apply, 就能直接单独更改字节的参数
# 固件更新功能
点击 ST-LINK,选择 Firmware update, 打开 ST-LINK Upgrade, 这个界面就是给 STLINK 更新固件的
然后点击 Connect,连接,会提示重启一下(即重新连接一下硬件电路)
再重新点击 Connect,就可以看到目前 STLINK 的固件和最新的固件,如果要升级的话,点 Yes,就能给 STLINK 升级固件了
# I2C 通信
我们最基本的任务是,实现单片机读写外挂模块寄存器的功能,其中最少要实现,在指定的位置写寄存器和在指定的位置读寄存器这两个功能,实现了读写寄存器,就实现了对这个外挂模块的完全控制
# I2C 通信
- I2C(Inter IC Bus)是由 Philips 公司开发的一种通用数据总线
- ** 两根通信线:**SCL(Serial Clock)、SDA(Serial Data)
- 同步,半双工
- 带数据应答
- 支持总线挂载多设备(一主多从、多主多从)
# 硬件电路
- ** 接线要求:** 所有 I2C 设备的 SCL 连在一起,SDA 连在一起
- 设备的 SCL 和 SDA 均要配置成开漏输出模式
- SCL 和 SDA 各添加一个上拉电阻,阻值一般为 4.7KΩ 左右
一主多从模型:
# 忽略这两个电阻,如何规定每个设备 SCL 和 SDA 输入输出模式呢?
**SCL:** 因为现在是一主多从的模式,主机拥有 SCL 的完全控制权,所以主机的 SCL 可以配置成推挽输出,所有从机的 SCL 都配置成浮空输出或者上拉输出,数据流向是,主机发送,所有从机接收
**SDA:** 半双工的协议,所以主机的 SDA 在发送的时候是输出,在接收的时候是输入,同样,从机的 SDA 也会在输入和输出之间反复切换,为了避免总线没协调好导致电源短路,I2C 的设计是,禁止所有设备进行强上拉的高电平,采用弱上拉电阻加开漏输出的电路结构
** 主机的权力:** 任何时候,都是主机完全掌控 SCL 线,另外在空闲状态下,主机可以主动发起对 SDA 的控制,只有在从机发送数据和从机应答的时候,主机才会转交 SDA 的控制权给从机
从机的权力比较小,对于 SCL 时钟线,在任何时候都只能被动的读取,从机不允许控制 SCL 线
对于 SDA 数据线,从机不允许主动发起对 SDA 的控制,只有在主机读取从机的命令后,或者从机应答的时候,从机才能短暂的取得 SDA 的控制权
所有的设备,包括 CPU 和被控 IC,它引脚的内部结构都是和上图一样的
# I2C 时序基本单元
-
** 起始条件:**SCL 高电平期间,SDA 从高电平切换到低电平
-
** 终止条件:** 高电平期间,SDA 从低电平切换到高电平
这个起始条件和终止条件就类似串口时序里的起始位和停止位,一个完整的数据帧,总是以起始条件开始、终止条件结束
起始和终止都是由主机产生的,从机不允许产生起始和终止,所以在主机空闲状态时,从机必须始终双手放开,不允许主动跳出来,去碰总线(如果允许的话,那就是多主机模型)
-
** 发送一个字节:**SCL 低电平期间,主机将数据位依次放到 SDA 线上(高位先行),然后释放 SCL,从机将在 SCL 高电平期间读取数据位,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可发送一个字节
-
** 接收一个字节:**SCL 低电平期间,从机将数据位依次放到 SDA 线上(高位先行),然后释放 SCL,主机将在 SCL 高电平期间读取数据位,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可接收一个字节(主机在接收之前,需要释放 SDA)
-
** 发送应答:** 主机在接收完一个字节之后,在下一个时钟发送一位数据,数据 0 表示应答,数据 1 表示非应答
-
** 接收应答:** 主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据 0 表示应答,数据 1 表示非应答(主机在接收之前,需要释放 SDA)
# I2C 时序
-
指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
-
当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
-
指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
# MPU6050 简介
# MPU6050 简介
-
MPU6050 是一个 6 轴姿态传感器,可以测量芯片自身 X、Y、Z 轴的加速度、角速度参数,通过数据融合(常见的数据融合算法,一般由互补滤波、卡尔曼滤波等),可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
-
**3 轴加速度计(Accelerometer):** 测量 X、Y、Z 轴的加速度
-
**3 轴陀螺仪传感器(Gyroscope):** 测量 X、Y、Z 轴的角速度
由图可以想象到,如果我们把这个东西放在手上,来回晃,中间这个小滑块就会左右移动,去压缩或拉伸两边的弹簧,当滑块移动时,就会带动上面的电位器(这个电位器其实就是一个分压电阻)滑动,然后我们测量电位器输出的电压,就能得到小滑块所受的加速度值了
由此可知,这个加速度计,实际上就是一个弹簧测力计
# MPU6050 参数
-
16 位 ADC 采集传感器的模拟信号,量化范围:-32768~32767
-
加速度计满量程选择:±2、±4、±8、±16(g)
如果你所测量的物体运动非常激烈,就可以把满量程选择大一些,防止你的加速度和角速度超出了量程;如果你所测量的物体运动比较平缓,就可以选择比较小的量程,这样测量的分辨率就会更大
因为 AD 值的范围是一定的,所以满量程选的越小,测量就会越细腻
另外,AD 值和加速度是线性关系、一一对应的,由 AD 值求加速度,就是乘一个系数就可以了
-
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
满量程选的越大,测量范围就越广;满量程选的越小,测量分辨率越高
-
可配置的数字低通滤波器
如果你觉得输出数据抖动太厉害,就可以加一点低通滤波,这样输出数据就会平缓一些
-
可配置的时钟源
-
可配置的采样分频
-
I2C 从机地址:
1101000(AD0=0)
1101001(AD0=1)
# 硬件电路
MPU6050 模块的原理图:
| 引脚 | 功能 |
|---|---|
| VCC、GND | 电源 |
| SCL、SDA | I2C 通信引脚 |
| XCL、XDA | 主机 I2C 通信引脚 |
| AD0 | 从机地址最低位 |
| INT | 中断信号输出 |
# MPU6050 框图
灰色的部分,就是芯片内部的传感器,其中包括 XYZ 轴的加速度计,XYZ 轴的陀螺仪
每个传感器都有一个自测单元,这部分是用来验证芯片好坏的,当启动自测后,芯片内部会模拟一个外力施加在传感器上,这个外力导致传感器数据会比平时大一些
那我们要如何自测呢?
我们可以先使能自测,读取数据,再失能自测,读取数据,两个数据一相减,得到的数据叫自测响应(有范围),如果自测响应在这个范围内,就说明芯片没有问题;如果不在,就说明芯片可能坏了,使用的话就要小心点
** 电荷泵(Charge Pump):** 也可以叫充电泵,是一种升压电路,
CPUOUT 引脚需要外接一个电容
** 中断状态寄存器(Interrupt Status Register):** 可以控制内部的那些事件到中断引脚的输出
**FIFO:** 先入先出寄存器,可以对数据流进行缓存
** 配置寄存器(Config Registers):** 可以对内部的各个电路进行配置
** 传感器寄存器(Sensor Registers):** 也就是数据寄存器,存储了各个传感器的数据
** 工厂校准(Factory Calibrtaion):** 就是内部的传感器进行了校准
** 数字运动传感器(DMP):** 是芯片内部自带的一个姿态解算的硬件算法,配合官方的 DMP 库,可以进行姿态解算
**FSYNC:** 帧同步
MPU-6000 和 MPU-6050 的区别:
| 特性 | MPU-6000 | MPU-6050 |
|---|---|---|
| 核心功能 | 3 轴陀螺仪 + 3 轴加速度计 | 3 轴陀螺仪 + 3 轴加速度计 |
| 通信接口 | SPI (主要) 和 I2C | I2C (唯一) |
| 封装 | QFN (通常是 24-pin) | QFN (通常是 24-pin) |
| 工作电压 | 2.375V - 3.46V (核心电压) | 2.375V - 3.46V (核心电压) |
| 数字运动处理器 (DMP) | 内置 | 内置 |
| FIFO 缓存 | 内置 | 内置 |
| 寄存器映射 | 几乎完全相同 (便于代码移植) | 几乎完全相同 (便于代码移植) |
| 典型应用 | 需要高速数据传输的专业应用、无人机、机器人、工业控制、汽车电子 (SPI 速度更快,抗干扰能力更强) | 消费电子、智能手机、可穿戴设备、航模、DIY 项目、Arduino / 树莓派等 (I2C 布线简单,适用于低速多设备通信) |
| 引脚数量 | 通常比 MPU-6050 多 (因为要支持 SPI) | 通常比 MPU-6000 少 (只需 I2C 引脚) |
| 常见性 | 在专业或工业产品中更常见 | 在消费级产品、模块和教学 / DIY 社区中非常常见 |
MPU-6050 有一个独立的逻辑电源引脚 VLOGIC,可以支持供电和 IO 口不一样的电平等级,MPU-6000 没有
# MPU-6050 的时钟系统
- 内部晶振,可以作为系统时钟
- XYZ 轴的陀螺仪,它们也都会有个晶振,因为陀螺仪内部需要高精度时钟的支持,所以陀螺仪内部也有独立的时钟,这三个时钟也可以输出,作为系统时钟
- 可以通过外部的 CLKIN 引脚,输入 32.678kHz 的方波,或者 19.2MHz 的方波,作为系统时钟(不过,外部时钟还需要额外的电路,比较麻烦)
# 寄存器
-
采样频率分频器
里面的 8 位为一个整体,作为分频值,可以配置采样频率的分频系数,简单来说就是,分频越小,内部的 AD 转换就越快,数据寄存器刷新就越快,反之就越慢
采样频率 = 陀螺仪输出时钟频率 / (1 + 分频值)
** 注意:** 不使用低通滤波器时,陀螺仪时钟为 8kHz,使用滤波器了,时钟就是 1kHz
-
配置寄存器
内部有两部分,外部同步设置和低通滤波器配置
低通滤波器可以让输出更加平滑,配置滤波器参数越大,输出数据抖动就越小,0 是不使用低通滤波器,陀螺仪时钟为 8kHz,之后使用了滤波器,陀螺仪时钟就是 1kHz
-
陀螺仪配置寄存器
高三位是 XYZ 轴的自测使能位,中间两位是满量程选择位
自测响应 = 自测使能时的数据 - 自测失能时的数据
-
加速度计配置寄存器
高三位是 XYZ 轴的自测使能位,中间两位是满量程选择位,后面三位是配置高通滤波器的
# 软件 I2C 读写 MPU-6050
# 软件 I2C 读写 MPU6050
接线图:
软件 I2C 相对于硬件 I2C 的优势:端口不受限,可以指定任意
目前这里 STM32 是主机,MPU6050 是从机
连接好电路后,插上 STLINK,这个小模块上的电源指示灯亮起,如果插上电之后,电源指示灯不亮,那赶紧断电检查一下是不是 VCC 和 GND 接反了,或者供电引脚没电,或者模块坏了,再排查一下问题
在 Hardware 中添加.c 和.h 文件,命名为 MyI2C


























































































































































































































































































































