嵌入式单片机STM32应用技术(课本)

时间:2024-01-17 23:23:26

目录
SAIU R20 1 6 第1页
第1 章. 初识STM32...................................................................................................................... 1
1.1. 课前预习........................................................................................................................................... 1
1.2. 概述.................................................................................................................................................... 1
1.3. 什么是STM32....................................................................................................................................1
1.4. STM32 能做什么............................................................................................................................... 2
1.5. STM32 怎么选型.............................................................................................................................. 3
1.5.1. STM32 分类................................................................................................................................. 3
1.5.2. STM32 命名方法........................................................................................................................ 4
1.5.3. 选择合适的MCU...................................................................................................................... 4
1.5.4. PCB 哪里打样..............................................................................................................................6
1.6. 总结.................................................................................................................................................... 7
1.7. 课后练习........................................................................................................................................... 7
第2 章. STM32 的结构和组成...................................................................................................... 8
2.1. 课前预习........................................................................................................................................... 8
2.2. 概述.................................................................................................................................................... 8
2.3. 什么是寄存器.................................................................................................................................. 8
2.4. STM 32 长啥样..................................................................................................................................8
2.5. 芯片里面有什么............................................................................................................................10
2.5.1. ICode 总线.................................................................................................................................10
2.5.2. 驱动单元....................................................................................................................................10
2.5.3. 被动单元....................................................................................................................................11
2.6. 存储器映射.....................................................................................................................................13
2.7. 寄存器映射.....................................................................................................................................14
2.7.1. STM32 的外设地址映射.......................................................................................................... 15
2.7.2. 总线基地址................................................................................................................................15
2.7.3. 外设基地址................................................................................................................................15
2.7.4. 外设寄存器................................................................................................................................16
2.8. C 语言对寄存器的封装................................................................................................................16
2.8.1. 封装总线和外设基地址........................................................................................................... 16
2.8.2. 封装寄存器列表....................................................................................................................... 17
2.9. 课后练习......................................................................................................................................... 20
第3 章. 初识STM32 标准库..................................................................................................... 21
3.1. 课前预习......................................................................................................................................... 21
3.2. 概述.................................................................................................................................................. 21
3.3. 库目录、文件简介....................................................................................................................... 21
3.4. STM32F10x_StdPeriph_Driver 文件夹..................................................................................24
3.5. 库各文件间的关系....................................................................................................................... 26
3.6. 初识库函数.....................................................................................................................................28
目录
第2 页SAIUR201 6
陈德金老师编著
3.7. 课后练习......................................................................................................................................... 29
第4 章. GPIO 的使用.................................................................................................................. 30
4.1. 课前预习......................................................................................................................................... 30
4.2. 概述.................................................................................................................................................. 30
4.3. GPIO 简介.......................................................................................................................................30
4.4. GPIO 框图剖析.............................................................................................................................. 31
4.4.1. 保护二极管及上、下拉电阻................................................................................................... 31
4.4.2. P-MOS 管和N-MOS 管......................................................................................................... 31
4.4.3. 输出数据寄存器....................................................................................................................... 33
4.4.4. 复用功能输出........................................................................................................................... 34
4.4.5. 输入数据寄存器....................................................................................................................... 34
4.4.6. 复用功能输入........................................................................................................................... 34
4.4.7. 模拟输入输出........................................................................................................................... 34
4.5. GPIO 工作模式.............................................................................................................................. 35
4.5.1. 输入模式(模拟/浮空/上拉/下拉)............................................................................................. 35
4.5.2. 输出模式(推挽/开漏)................................................................................................................35
4.5.3. 复用功能(推挽/开漏)................................................................................................................35
4.6. 点亮LED-硬件设计...................................................................................................................... 37
4.7. 点亮LED-编程要点...................................................................................................................... 38
4.8. 点亮LED-代码分析...................................................................................................................... 38
4.8.1. LED 灯引脚宏定义...................................................................................................................38
4.8.2. 控制LED 灯亮灭状态的宏定义........................................................................................... 39
4.8.3. LED GPIO 初始化函数............................................................................................................ 40
4.8.4. 主函数........................................................................................................................................41
4.9. 点亮LED-下载验证...................................................................................................................... 42
4.10. 课后练习.......................................................................................................................................42
第5 章. STM32 RCC 时钟系统.................................................................................................... 43
5.1. 课前预习......................................................................................................................................... 43
5.2. 概述.................................................................................................................................................. 43
5.3. RCC 主要作用—时钟部分.......................................................................................................... 43
5.4. RCC 框图剖析—时钟部分.......................................................................................................... 43
5.5. 系统时钟......................................................................................................................................... 44
5.5.1. HSE 高速外部时钟信号...........................................................................................................44
5.5.2. PLL 时钟源............................................................................................................................... 45
5.5.3. PLL 时钟PLLCLK.................................................................................................................. 45
5.5.4. 系统时钟SYSCLK.................................................................................................................. 45
5.5.5. AHB 总线时钟HCLK............................................................................................................. 45
5.5.6. APB2 总线时钟HCLK2..........................................................................................................45
目录
SAIU R20 1 6 第3页
5.5.7. 总线时钟HCLK1.....................................................................................................................46
5.6. 设置系统时钟库函数................................................................................................................... 46
5.7. 其他时钟......................................................................................................................................... 47
5.7.1. USB 时钟...................................................................................................................................47
5.7.2. Cortex 系统时钟........................................................................................................................47
5.7.3. ADC 时钟.................................................................................................................................. 48
5.7.4. RTC 时钟、独立看门狗时钟.................................................................................................. 48
5.7.5. MCO 时钟输出......................................................................................................................... 48
5.8. 配置系统时钟实验....................................................................................................................... 48
5.8.1. 使用HSE..................................................................................................................................48
5.8.2. 使用HSI................................................................................................................................... 48
5.8.3. 硬件设计....................................................................................................................................49
5.8.4. 软件设计....................................................................................................................................49
5.8.5. 编程要点....................................................................................................................................49
5.8.6. 代码分析....................................................................................................................................49
5.8.7. 下载验证....................................................................................................................................54
第6 章. STM32 中断应用概览................................................................................................... 55
6.1. 课前预习......................................................................................................................................... 55
6.2. 概述.................................................................................................................................................. 55
6.3. 异常类型......................................................................................................................................... 55
6.4. NVIC 简介.......................................................................................................................................56
6.5. NVIC 寄存器简介..........................................................................................................................56
6.6. NVIC 中断配置固件库.................................................................................................................57
6.7. 优先级的定义................................................................................................................................ 58
6.7.1. 优先级定义................................................................................................................................58
6.7.2. 优先级分组................................................................................................................................58
6.8. 中断编程......................................................................................................................................... 59
6.9. 课后练习......................................................................................................................................... 60
第7 章. EXTI—外部中断/事件控制器..................................................................................... 61
7.1. 课前预习......................................................................................................................................... 61
7.2. 概述.................................................................................................................................................. 61
7.3. EXTI 简介.......................................................................................................................................61
7.4. EXTI 功能框图.............................................................................................................................. 61
7.5. 中断/事件线.................................................................................................................................. 63
7.6. EXTI 初始化结构体详解............................................................................................................ 64
7.7. 外部中断控制实验....................................................................................................................... 65
7.7.1. 硬件设计....................................................................................................................................65
7.7.2. 软件设计....................................................................................................................................65
目录
第4 页SAIUR201 6
陈德金老师编著
7.7.3. 编程要点....................................................................................................................................65
7.7.4. 代码分析....................................................................................................................................65
7.7.5. 下载验证....................................................................................................................................69
7.8. 课后练习......................................................................................................................................... 69
第8 章. SysTick 系统定时器.................................................................................................... 70
8.1. 课前预习......................................................................................................................................... 70
8.2. 概述.................................................................................................................................................. 70
8.3. SysTick 简介................................................................................................................................ 70
8.4. SysTick 寄存器介绍................................................................................................................... 70
8.5. SysTick 定时实验.......................................................................................................................... 72
8.5.1. 硬件设计....................................................................................................................................72
8.5.2. 软件设计....................................................................................................................................72
8.5.3. 编程要点....................................................................................................................................72
8.5.4. 代码分析....................................................................................................................................73
8.6. 课后练习......................................................................................................................................... 79
第9 章. USART—串口通讯......................................................................................................... 80
9.1. 课前预习......................................................................................................................................... 80
9.2. 概述.................................................................................................................................................. 80
9.3. 串口通讯协议简介....................................................................................................................... 80
9.3.1. 物理层........................................................................................................................................80
9.3.2. 协议层........................................................................................................................................84
9.4. STM32 的USART 简介................................................................................................................ 85
9.5. USART 功能框图............................................................................................................................85
9.6. USART 初始化结构体详解.......................................................................................................... 90
9.7. USART1 接发通信实验.................................................................................................................91
9.7.1. 硬件设计....................................................................................................................................92
9.7.2. 软件设计....................................................................................................................................92
9.7.3. 编程要点....................................................................................................................................92
9.7.4. 代码分析....................................................................................................................................93
9.7.5. 下载验证....................................................................................................................................97
9.8. 课后练习......................................................................................................................................... 97
第10 章. DMA 直接存储区访问.................................................................................................. 98
10.1. 课前预习.......................................................................................................................................98
10.2. 概述................................................................................................................................................98
10.3. DMA 简介.......................................................................................................................................98
10.4. DMA 功能框图.............................................................................................................................. 98
10.5. DMA 数据配置............................................................................................................................100
10.6. DMA 初始化结构体详解.......................................................................................................... 101
目录
SAIU R20 1 6 第5页
10.7. DMA 存储器到存储器模式实验.............................................................................................103
10.7.1. 硬件设计................................................................................................................................103
10.7.2. 软件设计................................................................................................................................103
10.7.3. 编程要点................................................................................................................................103
10.7.4. 代码分析................................................................................................................................104
10.7.5. 下载验证................................................................................................................................107
10.8. 课后练习.....................................................................................................................................107
第11 章. TIM 基本定时器........................................................................................................ 108
11.1. 课前预习.....................................................................................................................................108
11.2. 概述..............................................................................................................................................108
11.3. 定时器分类................................................................................................................................ 108
11.4. 基本定时器功能框图讲解......................................................................................................109
11.5. 定时器初始化结构体详解......................................................................................................110
11.6. 基本定时器定时实验...............................................................................................................111
11.6.1. 硬件设计................................................................................................................................111
11.6.2. 软件设计................................................................................................................................111
11.6.3. 编程要点................................................................................................................................111
11.6.4. 软件分析................................................................................................................................111
11.6.5. 下载验证................................................................................................................................114
11.7. 课后练习.....................................................................................................................................114
第12 章. TIM 高级定时器........................................................................................................ 115
12.1. 课前预习.....................................................................................................................................115
12.2. 概述..............................................................................................................................................115
12.3. 高级控制定时器....................................................................................................................... 115
12.4. 高级控制定时器功能框图........................................................................................................116
12.4.1. 时钟源....................................................................................................................................117
12.4.2. 外部时钟模式1................................................................................................................... 117
12.4.3. 外部时钟模式2................................................................................................................... 118
12.4.4. 内部触发输入....................................................................................................................... 119
12.4.5. 输入捕获................................................................................................................................121
12.4.6. 输出比较................................................................................................................................122
12.4.7. 断路功能................................................................................................................................125
12.5. 输入捕获应用............................................................................................................................125
12.5.1. 测量频率................................................................................................................................126
12.5.2. 测量脉宽................................................................................................................................126
12.6. PWM 输入模式............................................................................................................................126
12.7. 输出比较应用............................................................................................................................128
12.7.1. PWM 输出模式.....................................................................................................................128
目录
第6 页SAIUR201 6
陈德金老师编著
12.7.2. PWM 边沿对齐模式.............................................................................................................128
12.7.3. PWM 中心对齐模式.............................................................................................................129
12.8. 定时器初始化结构体详解......................................................................................................129
12.8.1. TIM_TimeBaseInitTypeDef...................................................................................................130
12.8.2. TIM_OCInitTypeDef..............................................................................................................130
12.8.3. TIM_ICInitTypeDef............................................................................................................... 131
12.8.4. TIM_BDTRInitTypeDef.........................................................................................................132
12.9. PWM 互补输出实验................................................................................................................... 133
12.9.1. 硬件设计................................................................................................................................133
12.9.2. 软件设计................................................................................................................................133
12.9.3. 编程要点................................................................................................................................133
12.9.4. 软件分析................................................................................................................................134
12.9.5. 下载验证................................................................................................................................136
第13 章. I2C 通讯.................................................................................................................... 138
13.1. 课前预习.....................................................................................................................................138
13.2. 概述..............................................................................................................................................138
13.3. I2C 协议简介............................................................................................................................138
13.3.1. I2C 物理层............................................................................................................................. 139
13.3.2. 协议层....................................................................................................................................140
13.3.3. 通讯的起始和停止信号....................................................................................................... 141
13.4. STM32 的I2C 特性及架构................................................................................................... 144
13.4.1. STM32 的I2C 外设简介.................................................................................................... 144
13.4.2. STM32 的I2C 架构剖析.................................................................................................... 145
13.4.3. 通讯过程................................................................................................................................147
13.5. I2C 初始化结构体详解.......................................................................................................... 149
13.6. I2C—读写EEPROM 实验........................................................................................................150
13.6.1. 硬件设计................................................................................................................................150
13.6.2. 软件设计................................................................................................................................151
13.6.3. 编程要点................................................................................................................................151
13.6.4. 代码分析................................................................................................................................152
13.6.5. 下载验证................................................................................................................................167
13.7. 课后练习.....................................................................................................................................168
第14 章. SPI 通讯.................................................................................................................... 169
14.1. 课前预习.....................................................................................................................................169
14.2. 概述..............................................................................................................................................169
14.3. SPI 协议简介............................................................................................................................169
14.3.1. SPI 物理层.............................................................................................................................169
14.3.2. 协议层....................................................................................................................................171
目录
SAIU R20 1 6 第7页
14.4. STM32 的SPI 特性及架构................................................................................................... 173
14.4.1. STM32 的SPI 外设简介.................................................................................................... 173
14.4.2. TM32 的SPI 架构剖析...................................................................................................... 174
14.4.3. 通讯过程................................................................................................................................175
14.5. SPI 初始化结构体详解.......................................................................................................... 177
14.6. SPI—读写串行FLASH 实验................................................................................................. 178
14.6.1. 硬件设计................................................................................................................................179
14.6.2. 软件设计................................................................................................................................179
14.6.3. 编程要点................................................................................................................................180
14.6.4. 代码分析................................................................................................................................180
14.6.5. 下载验证................................................................................................................................198
14.7. 课后练习.....................................................................................................................................198
第15 章. 陀螺仪姿态检测....................................................................................................... 199
15.1. 课前预习.....................................................................................................................................199
15.2. 概述..............................................................................................................................................199
15.3. 姿态检测.....................................................................................................................................199
15.3.1. 基本认识................................................................................................................................199
15.3.2. 坐标系....................................................................................................................................200
15.4. 利用陀螺仪检测角度...............................................................................................................201
15.5. 利用加速度计检测角度............................................................................................................ 202
15.6. 利用磁场检测角度................................................................................................................... 203
15.7. 利用GPS 检测角度................................................................................................................ 204
15.8. 姿态融合与四元数..................................................................................................................... 204
15.9. MPU6050 模块简介...................................................................................................................... 204
15.9.1. MPU6050 模块功能及外观..................................................................................................204
15.9.2. MPU6050 模块的引脚功能说明......................................................................................... 205
15.9.3. MPU6050 模块的硬件原理图..............................................................................................205
15.10. MPU6050 模块的特性参数.......................................................................................................206
15.11. MPU6050—获取原始数据实验............................................................................................. 207
15.11.1. 硬件设计............................................................................................................................. 207
15.11.2. 配套程序简介..................................................................................................................... 208
15.11.3. 软件设计............................................................................................................................. 209
15.11.4. 程序设计要点..................................................................................................................... 209
15.11.5. 代码分析............................................................................................................................. 209
15.11.6. 下载验证............................................................................................................................. 215
15.12. MPU6050—利用DMP 进行姿态解算..................................................................................216
15.12.1. 硬件设计............................................................................................................................. 216
15.12.2. 软件设计............................................................................................................................. 216
15.12.3. 程序设计要点..................................................................................................................... 216
目录
第8 页SAIUR201 6
陈德金老师编著
15.12.4. 代码分析............................................................................................................................. 216
15.12.5. 下载验证............................................................................................................................. 226
15.13. MPU6050—使用第三方上位机............................................................................................. 227
15.13.1. 硬件设计............................................................................................................................. 227
15.13.2. 软件设计............................................................................................................................. 227
15.13.3. 程序设计要点..................................................................................................................... 227
15.13.4. 代码分析............................................................................................................................. 227
15.13.5. 下载验证............................................................................................................................. 231
第1 章.初识STM32
SAIU R20 1 6 第1页
第1 章. 初识STM32
1.1. 课前预习
在书上找到答案。
1. 什么是STM32。
2. STM32 能做什么什么?
1.2. 概述
本章所讲内容:
(1)STM32 简介
(2)STM32 功能介绍
1.3. 什么是STM32
STM32,从字面上来理解,ST 是意法半导体,M 是Microelectronics 的缩写,32 表示32 位,
合起来理解,STM32 就是指ST 公司开发的32 位微控制器。在如今的32 位控制器当中,STM32 可
以说是最璀璨的新星,它受宠若娇,大受工程师和市场的青睐,无芯能出其右。
51 是嵌入式学习中一款入门级的精典MCU,因其结构简单,易于教学,且可以通过串口编程而不
需要额外的仿真器,所以在教学时被大量采用,至今很多大学在嵌入式教学中用的还是51。51 诞生
于70 年代,属于传统的8 位单片机,如今,久经岁月的洗礼,既有其辉煌又有其不足。现在的市场
产品竞争越来越激烈,对成本极其敏感,相应地对MCU 的性能要求也更苛刻:更多功能,更低功耗,
易用界面和多任务。面对这些要求,51 现有的资源就显得得抓襟见肘。所以无论是高校教学还是市
场需求,都急需一款新的MCU 来为这个领域注入新的活力。基于这样的市场需求, ARM 公司推出了其
全新的基于ARMv7 架构的32 位Cortex-M3 微控制器内核。紧随其后,ST(意法半导体)公司就推出
了基于Cortex-M3 内核的MCU— STM32。STM32 凭借其产品线的多样化、极高的性价比、简单易用的
库开发方式,迅速在众多Cortex-M3 MCU 中脱颖而出,成为最闪亮的一颗新星。STM32 一上市就迅
速占领了中低端MCU 市场,受到了市场和工程师的无比青睐,颇有星火燎原之势。作为一名合格的嵌
入式工程师,面对新出现的技术,我们不是充耳不闻,而是要尽快吻合市场的需要,跟上技术的潮流。
第1 章.初识STM32
第2 页SAIUR201 6
图1-2 STM32F103 搭建的系统界面
如今STM32 的出现就是一种趋势,一种潮流,我们要做的就是搭上这趟快车,让自己的技术更有竞争
力。
1.4. STM32 能做什么
STM32 属于一个微控制器,自带了各种常用通信接口,比如USART、I2C、SPI 等,可接非常多的传感
器,可以控制很多的设备。现实生活中,我们接触到的很多电器产品都有STM32 的身影,比如智能手环,
微型四轴飞行器,平衡车、移动POST 机,智能电饭锅,3D 打印机等等。下面我们以最近最为火爆的两个
产品来讲解下,一个是手环,一个是飞行器。
图1-1 三星GearFit 智能手环
红圈:STM32F439ZIY6S 处理器,2048KB FLASH ,256KB RAM ,WLCSP143 封装。橙圈:Macronix
MX69V28F64 16 MB 闪存,基于MCP 封装的存储器,是一种包含了NOR 和SRAM 的闪存,这在手环手
机这种移动设备中经常使用,优点是体积小,可以减小PCB 的尺寸。这个闪存用的439 的FSMC 接口
驱动。黄圈:InvenSense MPU-6500 陀螺仪/加速度计,用
439 的I2C 接口驱动。绿圈:博通BCM4334WKUBG 芯片,
支持802.11n,蓝牙4.0+HS 以及FM 接收芯片,用439 的
SDIO 或者SPI 接口驱动。显示:1.84"可弯曲屏幕(Super
AMOLED),432 x 128 像素。触摸部分用439 的I2C 接口
驱动,OLED 显示部分用LTDC 接口驱动。
除了这几个重要资源的对比,我们的霸道开发板上还集
成了以太网,音频,CAN, 485 , 232 , USB 转串口, 蜂
鸣器, LED , 电容按键等外设资源, 可以充分的学习
STM32F103ZET6 这个芯片。在板子上面,还可以跑系统
ucosiii,学习图形界面emwin。如果功夫所至,学完之后,
自己都可以做一个类似Gear Fit 这样的手环。可很多人又
会说,Gear Fit 涉及硬件和软件,整个系统这么复杂,并不
是一个人可以完成的。说的没错,我们可以做不了,但是我们
第1 章.初识STM32
SAIU R20 1 6 第3页
的能力可以无限接近,多学点,技多不压身嘛。现在无人机非常火热,高端的无人机用STM32 做不来,
但是小型的四轴飞行器用STM32 还是绰绰有余的。如图1-3 所示飞行器的基本都可以用STM32 搞定。
图1-3 四轴飞行器
1.5. STM32 怎么选型
1.5.1. STM32 分类
STM32 有很多系列,可以满足市场的各种需求,从内核上分有Cortex-M0、M3、M4 和M7 这几
种,每个内核又大概分为主流、高性能和低功耗。具体的见表格1-1。
表格1-2 STM8 和STM32 分类
单纯从学习的角度出发,可以选择F1 和F4,F1 代表了基础型,基于Cortex-M3 内核,主频为
72MHZ,F4 代表了高性能,基于Cortex-M4 内核,主频180M。之于F1,F4(429 系列以上)除了
内核不同和主频的提升外,升级的明显特色就是带了LCD 控制器和摄像头接口,支持SDRAM,这
个区别在项目选型上会被优先考虑。但是从大学教学和用户初学来说,还是首选F1 系列,目前在市
场上资料最多,产品占有量最多的就是F1 系列的STM32。
第1 章.初识STM32
第4 页SAIUR201 6
1.5.2. STM32 命名方法
这里我们以STM32F103ZET6 来讲解下STM32 的命名方法。
表1--3 STM32F103ZET6 命名解释
有关更详细的命名方法见图1-4。
图1-4 STM8 和STM32 命名方法
1.5.3. 选择合适的MCU
了解了STM32 的分类和命名方法之后,就可以根据项目的具体需求先大概选择哪类内核的
MCU,普通应用,不需要接大屏幕的一般选择Cortex-M3 内核的F1 系列,如果要追求高性能,需要
大量的数据运算,且需要外接RGB 大屏幕的则选择Cortex-M4 内核的F429 系列。明确了大方向之后,
接下来就是细分选型,先确定引脚,引脚多的功能就多,价格也贵,具体得根据实际项目中需要使用到
什么功能,够用就好。确定好了引脚数目之后再选择FLASH 大小,相同引脚数的MCU 会有不同的
FLASH 大小可供选择,这个也是根据实际需要选择,程序大的就选择大点的FLASH,要是产品一量
产,这些省下来的都是钱啊。有些月出货量以KK(百万数量级)为单位的产品,不仅是MCU,连电
第1 章.初识STM32
SAIU R20 1 6 第5页
阻电容能少用就少用,更甚者连PCB 的过孔的多少都有讲究。项目中的元器件的选型的水深的很,很
多学问。
如何分配原理图IO
在画原理图之前,一般的做法是先把引脚分类好,然后才开始画原理图,引脚分类具体见表格1-5。
表格1-5 画原理图时的引脚分类
要想根据功能来分配IO,那就得先知道每个IO 的功能说明,这个我们可以从官方的数据手册里
面找到。在学习的时候,有两个官方资料我们会经常用到,一个是参考手册(英文叫Reference manual),
另外一个是数据手册(英文叫Data Sheet)。两者的具体区别见表格1-6。
表格1-6 参考手册和数据手册的内容区别
一句话概括:数据手册主要用于芯片选型和设计原理图时参考,参考手册主要用于在编程的时
候查阅。官方的这两个文档可以从官方网址里面下载:
http://www.stmcu.org/document/list/index/category-150
在数据手册中,有关引脚定义的部分在Pinouts and pin description 这个小节中,具体定义见表格
1-7。
第1 章.初识STM32
第6 页SAIUR201 6
表格1-7 数据手册中对引脚定义
表格1-8 对引脚定义的解读
开始分配原理图IO
比如MCU 型号是STM32F103ZET6,封装为LQFP144,我们在数据手册中找到这个封装的引脚
定义,然后根据引脚序号,一个一个复制出来,整理成excel 表。具体整理方法按照表格5-4 画原理
图时的引脚分类即可。分配好之后就开始画原理图。
1.5.4. PCB 哪里打样
设计好原理图,画好PCB 之后,需要把板子做出来,进行软硬件联调。首先得PCB 打样,可以
在网上找到一些专业的公司帮忙做,如果你足够懒,不想自己焊接电阻电容二三极管什么的,还可以帮
你把PCB 样板上的阻容贴好给你,打样贴片一条龙。
第1 章.初识STM32
SAIU R20 1 6 第7页
1.6. 总结
本章介绍了STM32 的基本概念、应用领域和芯片的选型方法。主要对STM32 芯片有一个比较清
晰的认识,后续章节将会详细介绍这个芯片的各个模块和功能。
1.7. 课后练习
1. 查看实训室的无人机和机器人使用的芯片是哪个?哪些使用的是STM32?
第2 章.STM32 的结构和组成
第8 页SAIUR201 6
陈德金老师编著
第2 章. STM32 的结构和组成
2.1. 课前预习
在书上找到答案。
1. STM32 里面有什么?
2. STM32 的开发方式和51 单片机有什么区别?
2.2. 概述
本章所讲内容:
(1)STM32 芯片结构
(2)寄存器映射
2.3. 什么是寄存器
我们经常说寄存器,那么什么是寄存器?这是我们本章需要讲解的内容,在学习的过程中,大家
带着这个疑问好好思考下,到最后看看大家能否用一句话给寄存器下一个定义。
2.4. STM 32 长啥样
STM32 的基本结构我们开发板中使用的芯片是144pin 的STM32F103ZET6,具体见图6-1。这个就
是我们接下来要学习的STM32,它将带领我们进入嵌入式的殿堂。芯片正面是丝印,ARM 应该是表示该
芯片使用的是ARM 的内核,STM32F103ZET6 是芯片型号,后面的字应该是跟生产批次相关,最上面的
是ST 的LOGO。芯片四周是引脚,左下角的小圆点表示1 脚,然后从1 脚起按照逆时针的顺序排列
(所有芯片的引脚顺序都是逆时针排列的)。开发板中把芯片的引脚引出来,连接到各种传感器上,
然后在STM32 上编程(实际就是通过程序控制这些引脚输出高电平或者低电平)来控制各种传感器工
作,通过做实验的方式来学习STM32 芯片的各个资源。开发板是一种评估板,板载资源非常丰富,引
脚复用比较多,力求在一个板子上验证芯片的全部功能。图2-1 STM32F103ZET6 实物图(红色框中部
分)
第2 章.STM32 的结构和组成
SAIU R20 1 6 第9页
图2-1 STM32F103ZET6 实物图
图2-2 STM32F103ZET6 正面引脚图
第2 章.STM32 的结构和组成
第10 页SAIUR2016
陈德金老师编著
2.5. 芯片里面有什么
我们看到的STM32 芯片是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内
核与外设就如同电脑上的CPU 与主板、内存、显卡、硬盘的关系。STM32F103 采用的是Cortex-M3 内
核,内核即CPU,由ARM 公司设计。ARM 公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂
商(SOC)如ST、TI、Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称
为核外外设或片上外设。如GPIO、USART(串口)、I2C、SPI 等都叫做片上外设。具体见图2-3。
图2-3 STM32 芯片架构简图
芯片(这里指内核,或者叫CPU)和外设之间通过各种总线连接,其中驱动单元有4 个,被动单
元也有4 个,具体见图2-4。为了方便理解,我们都可以把驱动单元理解成是CPU 部分,被动单元
都理解成外设。下面我们简单介绍下驱动单元和被动单元的各个部件。
2.5.1. ICode 总线
ICode 中的I 表示Instruction,即指令。我们写好的程序编译之后都是一条条指令,存放在
FLASH 中,内核要读取这些指令来执行程序就必须通过ICode 总线,它几乎每时每刻都需要被使用,
它是专门用来取指的。
2.5.2. 驱动单元
DCode 总线
第2 章.STM32 的结构和组成
SAIUR201 6 第11页
DCode 中的D 表示Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据
有常量和变量两种,常量就是固定不变的,用C 语言中的const 关键字修饰,是放到内部的FLASH 当
中的,变量是可变的,不管是全局变量还是局部变量都放在内部的SRAM。因为数据可以被Dcode 总
线和DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪
个总线在取数。
系统总线
系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统
总线来完成的。
DMA 总线
DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在SRAM,可
以在内部的FLASH。因为数据可以被Dcode 总线和DMA 总线访问,所以为了避免访问冲突,在取
数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。
2.5.3. 被动单元
内部的闪存存储器
内部的闪存存储器即FLASH,我们编写好的程序就放在这个地方。内核通过ICode 总线来取里
面的指令。
内部的SRAM
内部的SRAM,即我们通常说的RAM,程序的变量,堆栈等的开销都是基于内部的SRAM。内
核通过DCode 总线来访问它。
FSMC
FSMC 的英文全称是Flexible static memory controller,叫灵活的静态的存储器控制器, 是
STM32F10xx 中一个很有特色的外设,通过FSMC,我们可以扩展内存,如外部的SRAM,NANDFLASH
和NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的S:static,
不能是动态的内存,比如SDRAM 就不能扩展。
第2 章.STM32 的结构和组成
第12 页SAIUR2016
陈德金老师编著
AHB 到APB 的桥
从AHB 总线延伸出来的两条APB2 和APB1 总线,上面挂载着STM32 各种各样的特色外
设。我们经常说的GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习STM32
的重点,就是要学会编程这些外设去驱动外部的各种设备。
图2-4 STM32F10xx 系统框图(不包括互联型)
第2 章.STM32 的结构和组成
SAIUR201 6 第13页
2.6. 存储器映射
在图2-4 中,被控单元的FLASH,RAM,FSMC 和AHB 到APB 的桥(即片上外设),这些功能部
件共同排列在一个4GB 的地址空间内。我们在编程的时候,可以通过他们的地址找到他们,然后来操
作他们(通过C 语言对它们进行数据的读和写)。存储器本身不具有地址信息,它的地址是由芯片厂
商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见图2-5。如果给存储器再分配一
个地址就叫存储器重映射。
图2-5 存储器映射
第2 章.STM32 的结构和组成
第14 页SAIUR2016
陈德金老师编著
在这4GB 的地址空间中,ARM 已经粗线条的平均分成了8 个块,每块512MB,每个块也都规
定了用途,具体分类见表格6-1。每个块的大小都有512MB,显然这是非常大的,芯片厂商在每个块
的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而已。
表格2-1 存储器功能分类
在这8 个Block 里面,有3 个块非常重要,也是我们最关心的三个块。Block0 用来设计成内
部FLASH,Block1 用来设计成内部RAM,Block2 用来设计成片上的外设,下面我们简单的介绍下
这三个Block 里面的具体区域的功能划分。存储器Block0 内部区域功能划分Block0 主要用于设计片
内的FLASH , 我们使用的STM32F103ZET6 的FLASH 是512KB,属于大容量。要在芯片
内部集成更大的FLASH 或者SRAM 都意味着芯片成本的增加,往往片内集成的FLASH 都不会太
大,ST 能在追求性价比的同时做到512KB,实乃良心之举。
2.7. 寄存器映射
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?
寄存器到底是什么?在存储器Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共
32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个
单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的
方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个
内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内
存单元取别名的过程就叫寄存器映射。比如,我们找到GPIOB 端口的输出数据寄存器ODR 的地址
是0x4001 0C0C(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解),ODR 寄存器是
32bit,低16bit 有效,对应着16 个外部IO,写0/1 对应的的IO 则输出低/高电平。现在我们通过C
语言指针的操作方式,让GPIOB 的16 个IO 都输出高电平,具体见代码2-1。
第2 章.STM32 的结构和组成
SAIUR201 6 第15页
代码2-1 通过绝对地址访问内存单元
0x4001 0C0C 在我们看来是GPIOB 端口ODR 的地址,但是在编译器看来,这只是一个普通的
变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即
(unsigned int *)0x4001 0C0C,然后再对这个指针进行* 操作。刚刚我们说了,通过绝对地址访问内存
单元不好记忆且容易出错,我们可以通过寄存器的方式来操作,具体见代码2-2。
代码2-2 通过寄存器别名方式访问内存单元
为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面,具体见代码2-3。
代码2-3 通过寄存器别名访问内存单元
2.7.1. STM32 的外设地址映射
片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1 挂载低速外
设,APB2 和AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂
载在该总线上的首个外设的地址。其中APB1 总线的地址最低,片外设从这里开始,也叫外设基地址。
2.7.2. 总线基地址
表格2-2 总线基地址
表格2-2 的“相对外设基地址偏移”即该总线地址与“片上外设”基地址0x4000 0000 的差值。关于
地址的偏移我们后面还会讲到。
2.7.3. 外设基地址
总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX 外设基地
址”,也叫XX 外设的边界地址。具体有关STM32F10xx 外设的边界地址请参考《STM32F10xx 参考
手册》的2.3 小节的存储器映射的表1:STM32F10xx 寄存器边界地址。这里面我们以GPIO 这个外
设来讲解外设的基地址,GPIO 属于高速的外设,挂载到APB2 总线上,具体见表格2-3。
第2 章.STM32 的结构和组成
第16 页SAIUR2016
陈德金老师编著
表格2-3 外设GPIO 基地址
2.7.4. 外设寄存器
在XX 外设的地址范围内,分布着的就是该外设的寄存器。以GPIO 外设为例,GPIO 是通用输
入输出端口的简称,简单来说就是STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电
平。最简单的应用就是把GPIO 的引脚连接到LED 灯的阴极,LED 灯的阳极接电源,然后通过
STM32 控制该引脚的电平,从而实现控制LED 灯的亮灭。GPIO 有很多个寄存器,每一个都有特定
的功能。每个寄存器为32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相
对该外设基地址的偏移地址来描述。
2.8. C 语言对寄存器的封装
以上所有的关于存储器映射的内容,最终都是为大家更好地理解如何用C 语言控制读写外设寄存
器做准备,此处是本章的重点内容。
2.8.1. 封装总线和外设基地址
在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起来,总线或者
外设都以他们的名字作为宏名,具体见代码2-4。
第2 章.STM32 的结构和组成
SAIUR201 6 第17页
代码2-4 总线和外设基址宏定义
代码2-4 首先定义了“片上外设”基地址PERIPH_BASE,接着在PERIPH_BASE 上加入各个总
线的地址偏移, 得到APB1 、APB2 总线的地址APB1PERIPH_BASE 、APB2PERIPH_BASE,在
其之上加入外设地址的偏移,得到GPIOA-G 的外设地址,最后在外设地址上加入各寄存器的地址偏
移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针读写,具体见代码2-5。
代码2-5 使用指针控制BSRR 寄存器
2.8.2. 封装寄存器列表
用上面的方法去定义地址,还是稍显繁琐,例如GPIOA-GPIOE 都各有一组功能相同的寄存器,
如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,它们只是地址不一样,但却要为每个寄存器都定义
它的地址。为了更方便地访问寄存器,我们引入C 语言中的结构体语法对寄存器进行封装,具体见代
码2-6。
第2 章.STM32 的结构和组成
第18 页SAIUR2016
陈德金老师编著
代码2-6 使用结构体对GPIO 寄存器组的封装
这段代码用typedef 关键字声明了名为GPIO_TypeDef 的结构体类型,结构体内有7 个成员变
量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中32
位的变量占用4 个字节,16 位的变量占用2 个字节,具体见图2-6。
图2-6 GPIO_TypeDef 结构体成员的地址偏移
也就是说,我们定义的这个GPIO_TypeDef ,假如这个结构体的首地址为0x4001 0C00(这也是
第一个成员变量CRL 的地址), 那么结构体中第二个成员变量CRH 的地址即为0x4001 0C00
+0x04 ,加上的这个0x04 ,正是代表CRL 所占用的4 个字节地址的偏移量,其它成员变量相对于
结构体首地址的偏移,在上述代码右侧注释已给。这样的地址偏移与STM32 GPIO 外设定义的寄存器
地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结
构体的形式访问寄存器,具体见代码2-7。
第2 章.STM32 的结构和组成
SAIUR201 6 第19页
代码2-7 通过结构体指针访问寄存器
这段代码先用GPIO_TypeDef 类型定义一个结构体指针GPIOx , 并让指针指向地址
GPIOB_BASE(0x4001 0C00),使用地址确定下来,然后根据C 语言访问结构体的语法,用GPIOx->ODR
及GPIOx->IDR 等方式读写寄存器。最后,我们更进一步,直接使用宏定义好GPIO_TypeDef 类型的
指针,而且指针指向各个GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可,具体代码2-8。
代码2-8 定义好GPIO 端口首地址址针
这里我们仅是以GPIO 这个外设为例,给大家讲解了C 语言对寄存器的封装。以此类推,其他外
设也同样可以用这种方法来封装。好消息是,这部分工作都由固件库帮我们完成了,这里我们只是分析
了下这个封装的过程,让大家知其然,也只其所以然。
第2 章.STM32 的结构和组成
第20 页SAIUR2016
陈德金老师编著
2.9. 课后练习
1、什么是存储器映射?什么是存储器重映射?
2、什么是寄存器?
第3 章.初识STM32 标准库
SAIUR201 6 第21页
第3 章. 初识STM32 标准库
3.1. 课前预习
在书上找到答案。
1. 什么是STM32 标准库。
2. STM32 使用标准库的开发方式和以前学的寄存器方式有什么不同
3.2. 概述
本章所讲内容:
(1)STM32 标准库的简介
(2)STM32 标准库的文件夹和功能介绍
在上一章中,我们构建了几个控制GPIO 外设的函数,算是实现了函数库的雏形,但GPIO 还有
很多功能函数我们没有实现,而且STM32 芯片不仅仅只有GPIO 这一个外设。如果我们想要亲自完
成这个函数库,工作量非常巨大。ST 公司提供的标准软件库,包含了STM32 芯片所有寄存器的控制
操作,我们直接学习如何使用ST 标准库,会极大地方便控制STM32 芯片。
3.3. 库目录、文件简介
STM32 标准库可以从官网获得,也可以直接从本书的配套资料得到。本书讲解的例程全部采用
3.5.0 库文件。以下内容请大家打开STM32 标准库文件配合阅读。解压库文件后进入其目录:
“STM32F10x_StdPeriph_Lib_V3.5.0\”软件库各文件夹的内容说明见图3-1。
第3 章.初识STM32 标准库
第22 页SAIUR2016
陈德金老师编著
图3-1 ST 标准库目录:STM32F10x_StdPeriph_Lib_V3.5.0\
1. Libraries:文件夹下是驱动库的源代码及启动文件,这个非常重要,我们要使用的固件库就在这
个文件夹里面。
2. Project :文件夹下是用驱动库写的例子和工程模板,其中那些为每个外设写好的例程对我们非常
有用,我们在学习的时候就可以参考这里面的例程,非常全面,简直就是穷尽了外设的所有功能。
3. Utilities:包含了基于ST 官方实验板的例程,不需要用到,略过即可。
4. stm32f10x_stdperiph_lib_um.chm: 库帮助文档,这个很有用,不喜欢直接看源码的可以在合理查
询每个外设的函数说明,非常详细。这是一个已经编译好的HTML 文件, 主要讲述如何使用驱
动库来编写自己的应用程序。说得形象一点,这个HTML 就是告诉我们:ST 公司已经为你写好
了每个外设的驱动了,想知道如何运用这些例子就来向我求救吧。不幸的是,这个帮助文档是英
文的,这对很多英文不好的朋友来说是一个很大的障碍。但这里要告诉大家,英文仅仅是一种工
具,绝对不能让它成为我们学习的障碍。其实这些英文还是很简单的,我们需要的是拿下它的勇
气。在使用库开发时,我们需要把libraries 目录下的库函数文件添加到工程中,并查阅库帮助文
档来了解ST 提供的库函数,这个文档说明了每一个库函数的使用方法。进入Libraries 文件夹
看到, 关于内核与外设的库文件分别存放在CMSIS 和STM32F10x_StdPeriph_Driver 文件夹
中。
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\文件夹展开内容见图3-2。
第3 章.初识STM32 标准库
SAIUR201 6 第23页
图3-2 CMSIS 文件夹内容目录:Libraries\CMSIS\
其中黄色框框住的是我们需要用到的内容,下面我们一一讲解下这几个文件的作用。内核相关文件
在CoreSupport 文件夹中有core_cm3.c 和core_cm3.h 两个文件。Core_cm3.h 头文件里面实现了内核
的寄存器映射,对应外设头文件stm32f10x.h,区别就是一个针对内核的外设,一个针对片上(内核之
外)的外设。core_cm3.c 文件实现了一下操作内核外设寄存器的函数,用的比较少。我们还需要了解
的是core_cm3.h 头文件中包含了“stdint.h” 这个头文件,这是一个ANSI C 文件,是独立于处理器之
外的,就像我们熟知的C 语言头文件“stdio.h” 文件一样。位于RVMDK 这个软件的安装目录下,主
要作用是提供一些类型定义。见代码清单3-1。
代码清单3-1:stdint.h 文件中的类型定义
这些新类型定义屏蔽了在不同芯片平台时,出现的诸如int 的大小是16 位,还是32 位的差异。
所以在我们以后的程序中,都将使用新类型如uint8_t 、uint16_t 等。在稍旧版的程序中还经常会出现
如u8、u16、u32 这样的类型,分别表示的无符号的8 位、16 位、32 位整型。初学者碰到这样的旧
类型感觉一头雾水,它们定义的位置在STM32f10x.h 文件中。建议在以后的新程序中尽量使用
第3 章.初识STM32 标准库
第24 页SAIUR2016
陈德金老师编著
uint8_t 、uint16_t 类型的定义。启动文件启动文件放在startup/arm 这个文件夹下面,这里面启动文件
有很多个,不同型号的单片机用的启动文件不一样,有关每个启动文件的详细说明见表
我们开发板中用的STM32F103VET6 或者STM32F103ZET6 的FLASH 都是512K,属于基本型
的大容量产品,启动文件统一选择startup_stm32f10x_hd.s。
Stm32f10x.h
这个头文件实现了片上外设的所以寄存器的映射,是一个非常重要的头文件,在内核中与之想对应
的头文件是core_cm3.h。
system_stm32f10x.c
system_stm32f10x.c 文件实现了STM32 的时钟配置,操作的是片上的RCC 这个外设。系统在
上电之后,首选会执行由汇编编写的启动文件,启动文件中的复位函数中调用的SystemInit 函数就在
这个文件里面定义。调用完之后,系统的时钟就被初始化成72M。如果后面我们需要重新配置系统时
钟,我们就可以参考这个函数重写。为了维持库的完整性, 我们不会直接在这个文件里面修改时钟配
置函数。
3.4. STM32F10x_StdPeriph_Driver 文件夹
文件目录: Libraries\STM32F10x_StdPeriph_Driver 进入libraries 目录下的
STM32F10x_StdPeriph_Driver 文件夹,见图3-3。
图3-3 外设驱动
第3 章.初识STM32 标准库
SAIUR201 6 第25页
STM32F10x_StdPeriph_Driver 文件夹下有inc(include 的缩写)跟src(source 的简写) 这两
个文件夹,这里的文件属于CMSIS 之外的的、芯片片上外设部分。src 里面是每个设备外设的驱动
源程序,inc 则是相对应的外设头文件。src 及inc 文件夹是ST 标准库的主要内容,甚至不少人直接
认为ST 标准库就是指这些文件,可见其重要性。在src 和inc 文件夹里的就是ST 公司针对每个
STM32 外设而编写的库函数文件,每个外设对应一个.c 和.h 后缀的文件。我们把这类外设文件统称
为: stm32f10x_ppp.c 或stm32f10x_ppp.h 文件,PPP 表示外设名称。如在上一章中我们自建的
stm32f10x_gpio.c 及tm32f10x_gpio.h 文件,就属于这一类。如针对模数转换(ADC)外设,在src 文件
夹下有一个stm32f10x_adc.c 源文件,在inc 文件夹下有一个stm32f10x_adc.h 头文件,若我们开发的
工程中用到了STM32 内部的ADC,则至少要把这两个文件包含到工程里。见图3-4。
图3-4 驱动的源文件及头文件
这两个文件夹中,还有一个很特别的misc.c 文件,这个文件提供了外设对内核中的NVIC(中断向
量控制器) 的访问函数, 在配置中断时, 我们必须把这个文件添加到工程中。stm32f10x_it.c 、
stm32f10x_conf.h 和system_stm32f10x.c 文件
stm32f10x_it.c:这个文件是专门用来编写中断服务函数的,在我们修改前,这个文件
已经定义了一些系统异常(特殊中断)的接口,其它普通中断服务函数由我们自己添加。但是我们怎么知
道这些中断服务函数的接口如何写?是不是可以自定义呢?答案当然不是,
这些都可以在汇编启动文件中找到,在学习中断和启动文件的时候我们会详细介绍
system_stm32f10x.c:这个文件包含了STM32 芯片上电后初始化系统时钟、扩展外部存储器用的
第3 章.初识STM32 标准库
第26 页SAIUR2016
陈德金老师编著
函数,例如我们前两章提到供启动文件调用的“SystemInit”函数,用于上电后初始化时钟,该函数的定
义就存储在system_stm32f10x.c 文件。STM32F103 系列的芯片,调用库的这个SystemInit 函数后,
系统时钟被初始化为72MHz,如有需要可以修改这个文件的内容,设置成自己所需的时钟频率,但鉴
于保持库的完整性,我们在做系统时钟配置的时候会另外重写时钟配置函数。
stm32f10x_conf.h:这个文件被包含进stm32f10x.h 文件。当我们使用固件库编程的时候,如果需
要某个外设的驱动库,就需要包含该外设的头文件:stm32f10x_ppp.h,包含一个还好,如果是用了多
外设,就需要包含多个头文件,这不仅影响代码美观也不好管理,现我们用一个头文件stm32f10x_conf.h
把这些外设的头文件都包含在里面,让这个配置头文件统一管理这些外设的头文件,我们在应用程序中
只需要包含这个配置头文件即可,我们又知道这个头文件在stm32f10x.h 的最后被包含,所以最终我
们只需要包含stm32f10x.h 这个头文件即可,非常方便。Stm32f10x_conf.h 见代码清单3-2。默认情况
下是所以头文件都被包含,没有被注释掉。我们也可以把不要的都注释掉,只留下需要使用的即可。
代码清单3-2 stm32f10x_conf.h 文件配置软件库
3.5. 库各文件间的关系
前面向大家简单介绍了各个库文件的作用,库文件是直接包含进工程即可,丝毫不用修改,而有的
文件就要我们在使用的时候根据具体的需要进行配置。接下来从整体上把握一下各个文件在库工程中的
层次或关系,这些文件对应到CMSIS 标准架构上。见图3-5。
第3 章.初识STM32 标准库
SAIUR201 6 第27页
图3-5 库各文件关系
图3-5 描述了STM32 库各文件之间的调用关系,在实际的使用库开发工程的过程中,我们把位
于CMSIS 层的文件包含进工程,除了特殊系统时钟需要修改system_stm32f10x.c,其它文件丝毫不用
修改,也不建议修改。对于位于用户层的几个文件,就是我们在使用库的时候,针对不同的应用对库文
件进行增删(用条件编译的方法增删)和改动的文件。
第3 章.初识STM32 标准库
第28 页SAIUR2016
陈德金老师编著
3.6. 初识库函数
所谓库函数,就是STM32 的库文件中为我们编写好驱动外设的函数接口,我们只要调用这些库
函数,就可以对STM32 进行配置,达到控制目的。我们可以不知道库函数是如何实现的,但我们调
用函数必须要知道函数的功能、可传入的参数及其意义、和函数的返回值。于是,有读者就问那么多函
数我怎么记呀?我的回答是:会查就行了,哪个人记得了那么多。所以我们学会查阅库帮助文档是很
有必要的。打开库帮助文档《stm32f10x_stdperiph_lib_um.chm》见图3-6
图3-6 库帮助文档
层层打开文档的目录标签:标签目录:Modules\STM32F10x_StdPeriph_Driver\可看到STM32F10x
_StdPeriph_Driver 标签下有很多外设驱动文件的名字MISC、ADC、BKP、CAN 等标签。我们试着查
看GPIO 的“ 位设置函数GPIO_SetBits” 看看, 打开标签: 标签目录:
Modules\STM32F10x_StdPeriph_Driver\GPIO\Functions\GPIO_SetBits 见图3-7。
第3 章.初识STM32 标准库
SAIUR201 6 第29页
图3-7 库帮助文档的函数说明
利用这个文档,我们即使没有去看它的具体源代码,也知道要怎么利用它了。如GPIO_SetBits,
函数的原型为void GPIO_SetBits(GPIO_TypeDef * GPIOx , uint16_tGPIO_Pin)。它的功能是:输入一
个类型为GPIO_TypeDef 的指针GPIOx 参数,选定要控制的GPIO 端口;输入GPIO_Pin_x 宏,其
中x 指端口的引脚号,指定要控制的引脚。其中输入的参数GPIOx 为ST 标准库中定义的自定义数
据类型,这两个传入参数均为结构体指针。初学时,我们并不知道如GPIO_TypeDef 这样的类型是什
么意思,可以点击函数原型中带下划线的GPIO_TypeDef 就可以查看这个类型的声明了。就这样初步
了解了一下库函数,读者就可以发现STM32 的库是写得很优美的。每个函数和数据类型都符合见名
知义的原则,当然,这样的名称写起来特别长,而且对于我们来说要输入这么长的英文,很容易出错,
所以在开发软件的时候,在用到库函数的地方,直接把库帮助文档中的函数名称复制粘贴到工程文件就
可以了。而且,配合MDK 软件的代码自动补全功能,可以减少输入量。有的用户觉得使用库文档麻
烦,也可以直接查阅STM32 标准库的源码,库帮助文档的说明都是根据源码生成的,所以直接看源
码也可以了解函数功能。
3.7. 课后练习
打开ST 标准库,查看它的各个文件,对比一下ST 标准库与我们上一章中工程里的同名文件,
查看有何差异。
第4 章.GPIO 的使用
第30 页SAIUR2016
第4 章. GPIO 的使用
4.1. 课前预习
在书上找到答案。
1. STM32 f103zet6 的GPIO 有多少个?
2. STM32 f103zet6 的GPIO 工作模式有哪些?
4.2. 概述
本章所讲内容:
(1)STM32 的GPIO 的结构和功能
(2)使用GPIO 点亮LED 灯
利用库建立好的工程模板,就可以方便地使用STM32 标准库编写应用程序,可以说从这一章我
们真正开始迈入STM32 固件库开发的大门。
4.3. GPIO 简介
GPIO 是通用输入输出端口的简称,简单来说就是STM32 可控制的引脚,STM32 芯片的GPIO
引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的GPIO 被
分成很多组,每组有16 个引脚,如型号为STM32F103ZET6 型号的芯片有GPIOA、GPIOB、GPIOC
至GPIOG 共7 组GPIO,芯片一共144 个引脚,其中GPIO 就占了一大部分,所有的GPIO 引脚
都有基本的输入输出功能。最基本的输出功能是由STM32 控制引脚输出高、低电平,实现开关控制,
如把GPIO 引脚接入到LED 灯,那就可以控制LED 灯的亮灭,引脚接入到继电器或三极管,那就可
以通过继电器或三极管控制外部大功率电路的通断。最基本的输入功能是检测外部输入电平,如把
GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。
第4 章.GPIO 的使用
SAIUR201 6 第31页
4.4. GPIO 框图剖析
图4-1 GPIO 结构框图
通过GPIO 硬件结构框图,就可以从整体上深入了解GPIO 外设及它的各种应用模式。该图从最
右端看起,最右端就是代表STM32 芯片引出的GPIO 引脚,其余部件都位于芯片内部。
4.4.1. 保护二极管及上、下拉电阻
引脚的两个保护二级管可以防止引脚外部过高或过低的电压输入,当引脚电压高于VDD 时,上方
的二极管导通,当引脚电压低于VSS 时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧
毁。尽管有这样的保护,并不意味着STM32 的引脚能直接外接大功率驱动器件,如直接驱动电机,
强制驱动要么电机不转,要么导致芯片烧坏,必须要加大功率及隔离电路驱动。
4.4.2. P-MOS 管和N-MOS 管
GPIO 引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。
先看输出模式部分,线路经过一个由P-MOS 和N-MOS 管组成的单元电路。这个结构使GPIO 具有
了“推挽输出”和“开漏输出”两种模式。所谓的推挽输出模式,是根据这两个MOS 管的工作方式来命名
的。在该结构中输入高电平时,经过反向后,上方的P-MOS 导通,下方的N-MOS 关闭,对外输出
高电平;而在该结构中输入低电平时,经过反向后,N-MOS 管导通,P-MOS 关闭,对外输出低电平。
当引脚高低电平切换时,两个管子轮流导通,P 管负责灌电流,N 管负责拉电流,使其负载能力和开
关速度都比普通的方式有很大的提高。推挽输出的低电平为0 伏,高电平为3.3 伏,具体参考图4-2,
它是推挽输出模式时的等效电路。
第4 章.GPIO 的使用
第32 页SAIUR2016
图4-2 推挽等效电路
而在开漏输出模式时,上方的P-MOS 管完全不工作。如果我们控制输出为0,低电平,则P-MOS
管关闭,N-MOS 管导通,使输出接地,若控制输出为1 (它无法直接输出高电平)时,则P-MOS 管和
N-MOS 管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。为正常使用时必须外部
接上拉电阻,参考图8-3 中等效电路。它具有“线与”特性, 也就是说,若有很多个开漏模式引脚连接
到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部上拉电
阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电
平,0 伏。推挽输出模式一般应用在输出电平为0 和3.3 伏而且需要高速切换开关状态的场合。在
STM32 的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。开漏输出一般应用在
I2C、SMBUS 通讯等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输
出5 伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5 伏,并且把GPIO 设置为开漏模式,
当输出高阻态时,由上拉电阻和电源向外输出5 伏的电平,具体见图4-3。
第4 章.GPIO 的使用
SAIUR201 6 第33页
图4-3 STM32 IO 对外输出5V 电平
4.4.3. 输出数据寄存器
前面提到的双MOS 管结构电路的输入信号,是由GPIO“输出数据寄存器GPIOx_ODR”提供的,
因此我们通过修改输出数据寄存器的值就可以修改GPIO 引脚的输出电平。而“置位/复位寄存器
GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。
第4 章.GPIO 的使用
第34 页SAIUR2016
4.4.4. 复用功能输出
“复用功能输出”中的“复用”是指STM32 的其它片上外设对GPIO 引脚进行控制,此时GPIO 引
脚用作该外设功能的一部分,算是第二用途。从其它外设引出来的“复用功能输出信号”与GPIO 本身
的数据据寄存器都连接到双MOS 管结构的输入中,通过图中的梯形结构作为开关切换选择。例如我
们使用USART 串口通讯时,需要用到某个GPIO 引脚作为通讯发送引脚,这个时候就可以把该
GPIO 引脚配置成USART 串口复用功能,由串口外设控制该引脚,发送数据。
4.4.5. 输入数据寄存器
GPIO 结构框图的上半部分,GPIO 引脚经过内部的上、下拉电阻,可以配置成上/下拉输入,然
后再连接到施密特触发器,信号经过触发器后,模拟信号转化为0、1 的数字信号,然后存储在“输入
数据寄存器GPIOx_IDR”中,通过读取该寄存器就可以了解GPIO 引脚的电平状态。
4.4.6. 复用功能输入
与“复用功能输出”模式类似,在“复用功能输入模式”时,GPIO 引脚的信号传输到STM32 其它片
上外设,由该外设读取引脚状态。同样,如我们使用USART 串口通讯时,需要用到某个GPIO 引脚
作为通讯接收引脚,这个时候就可以把该GPIO 引脚配置成USART 串口复用功能,使USART 可以
通过该通讯引脚的接收远端数据。
4.4.7. 模拟输入输出
当GPIO 引脚用于ADC 采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施密
特触发器的,因为经过施密特触发器后信号只有0、1 两种状态,所以ADC 外设要采集到原始的模
拟信号,信号源输入必须在施密特触发器之前。类似地,当GPIO 引脚用于DAC 作为模拟电压输出
通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双MOS 管结构,模拟信号直接
输出到引脚。
第4 章.GPIO 的使用
SAIUR201 6 第35页
4.5. GPIO 工作模式
总结一下,由GPIO 的结构决定了GPIO 可以配置成以下模式:
在固件库中,GPIO 总共有8 种细分的工作模式,稍加整理可以大致归类为以下三类:
4.5.1. 输入模式(模拟/浮空/上拉/下拉)
在输入模式时,施密特触发器打开,输出被禁止,可通过输入数据寄存器GPIOx_IDR 读取I/O 状
态。其中输入模式,可设置为上拉、下拉、浮空和模拟输入四种。上拉和下拉输入很好理解,默认的电
平由上拉或者下拉决定。浮空输入的电平是不确定的,完全由外部的输入决定,一般接按键的时候用的
是这个模式。模拟输入则用于ADC 采集。
4.5.2. 输出模式(推挽/开漏)
在输出模式中,推挽模式时双MOS 管以轮流方式工作,输出数据寄存器GPIOx_ODR 可控制I/O
输出高低电平。开漏模式时,只有N-MOS 管工作,输出数据寄存器可控制I/O 输出高阻态或低电平。
输出速度可配置,有2MHz\10MHz\50MHz 的选项。此处的输出速度即I/O 支持的高低电平状态最
高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可。在输出模式
时施密特触发器是打开的,即输入可用,通过输入数据寄存器GPIOx_IDR 可读取I/O 的实际状态。
4.5.3. 复用功能(推挽/开漏)
复用功能模式中,输出使能,输出速度可配置,可工作在开漏及推挽模式,但是输出信号源于其
它外设,输出数据寄存器GPIOx_ODR 无效;输入可用,通过输入数据寄存器可获取I/O 实际状态,
但一般直接用外设的寄存器来获取该数据信号。通过对GPIO 寄存器写入不同的参数,就可以改变
GPIO 的工作模式,在GPIO 外设中,控制端口高低控制寄存器CRH 和CRL 可以配置每个GPIO 的
工作模式和工作的速度,每4 个位控制一个IO,CRH 控制端口的高八位,CRL 控制端口的低8 位,
具体的看CRH 和CRL 的寄存器描述。
第4 章.GPIO 的使用
第36 页SAIUR2016
图4-4 GPIO 端口配置低寄存器
第4 章.GPIO 的使用
SAIUR201 6 第37页
图4-5 GPIO 端口配置高寄存器
4.6. 点亮LED-硬件设计
在本教程中STM32 芯片与LED 灯的连接见图12-1,这是一个RGB 灯,里面由红蓝绿三个小灯构
成, 使用PWM 控制时可以混合成256*256*256 种不同的颜色。
图4-6 LED 硬件原理图
第4 章.GPIO 的使用
第38 页SAIUR2016
这些LED 灯的阴极都是连接到STM32 的GPIO 引脚,只要我们控制GPIO 引脚的电平输出
状态,即可控制LED 灯的亮灭。若您使用的实验板LED 灯的连接方式或引脚不一样, 只需根据我
们的工程修改引脚即可,程序的控制原理相同。点亮LED-软件设计这里只讲解核心部分的代码,有些
变量的设置,头文件的包含等可能不会涉及到,完整的代码请参考本章配套的工程。为了使工程更加有
条理,我们把LED 灯控制相关的代码独立分开存储,方便以后移植。在“工程模板”之上新建
“ bsp_led.c”及“bsp_led.h ”文件,其中的“bsp ”即Board Support Packet 的缩写(板级支持包),这些文件也
可根据您的喜好命名,这些文件不属于STM32 标准库的内容,是由我们自己根据应用需要编写的。
4.7. 点亮LED-编程要点
1. 使能GPIO 端口时钟;
2. 初始化GPIO 目标引脚为推挽输出模式;
3. 编写简单测试程序,控制GPIO 引脚输出高、低电平。
4.8. 点亮LED-代码分析
4.8.1. LED 灯引脚宏定义
在编写应用程序的过程中,要考虑更改硬件环境的情况,例如LED 灯的控制引脚与当前的不一样,
我们希望程序只需要做最小的修改即可在新的环境正常运行。这个时候一般把硬件相关的部分使用宏来
封装,若更改了硬件环境,只修改这些硬件相关的宏即可,这些定义一般存储在头文件,即本例子中的
“bsp_led.h”文件中,见代码清单4-1。
代码清单4-1 LED 控制引脚相关的宏
以上代码分别把控制LED 灯的GPIO 端口、GPIO 引脚号以及GPIO 端口时钟封装起来了。在
实际控制的时候我们就直接用这些宏,以达到应用代码硬件无关的效果。其中的GPIO 时钟宏
“RCC_APB2Periph_GPIOB” 是STM32 标准库定义的GPIO 端口时钟相关的宏, 它的作用与
“GPIO_Pin_x”这类宏类似,是用于指示寄存器位的,方便库函数使用,下面初始化GPIO 时钟的时候
可以看到它的用法。
第4 章.GPIO 的使用
SAIUR201 6 第39页
4.8.2. 控制LED 灯亮灭状态的宏定义
为了方便控制LED 灯,我们把LED 灯常用的亮、灭及状态反转的控制也直接定义成宏,见代码清单4-2。
代码清单4-2 控制LED 亮灭的宏
第4 章.GPIO 的使用
第40 页SAIUR2016
这部分宏控制LED 亮灭的操作是直接向BSRR、BRR 和ODR 这三个寄存器写入控制指令来实
现的,对BSRR 写1 输出高电平,对BRR 写1 输出低电平,对ODR 寄存器某位进行异或操作可
反转位的状态。RGB 彩灯可以实现混色,如最后一段代码我们控制红灯和绿灯亮而蓝灯灭,可混出黄
色效果。代码中的“\”是C 语言中的续行符语法,表示续行符的下一行与续行符所在的代码是同一行。
代码中因为宏定义关键字“#define”只是对当前行有效,所以我们使用续行符来连接起来,以下的代码是
等效的:
#define LED_YELLOW LED1_ON; LED2_ON; LED3_OFF
应用续行符的时候要注意,在“\”后面不能有任何字符(包括注释、空格),只能直接回车。
4.8.3. LED GPIO 初始化函数
利用上面的宏,编写LED 灯的初始化函数,见代码清单4-3。
代码清单4-3 LED GPIO 初始化函数
第4 章.GPIO 的使用
SAIUR201 6 第41页
整个函数与“构建库函数雏形”章节中的类似,主要区别是硬件相关的部分使用宏来代替,初始化
GPIO 端口时钟时也采用了STM32 库函数,函数执行流程如下:
1. 使用GPIO_InitTypeDef 定义GPIO 初始化结构体变量,以便下面用于存储GPIO 配置。
2. 调用库函数RCC_APB2PeriphClockCmd 来使能LED 灯的GPIO 端口时钟,在前面的章节中我
们是直接向RCC 寄存器赋值来使能时钟的,不如这样直观。该函数有两个输入参数,第一个参
数用于指示要配置的时钟,如本例中的“RCC_ APB2Periph_GPIOB”, 应用时我们使用“|”操作同时
配置3 个LED 灯的时钟;函数的第二个参数用于设置状态,可输入“Disable”关闭或“Enable”使
能时钟。
3. 向GPIO 初始化结构体赋值, 把引脚初始化成推挽输出模式, 其中的GPIO_Pin 使用宏
“LEDx_GPIO_PIN”来赋值,使函数的实现方便移植。
4. 使用以上初始化结构体的配置,调用GPIO_Init 函数向寄存器写入参数,完成GPIO 的初始化,
这里的GPIO 端口使用“LEDx_GPIO_PORT”宏来赋值,也是为了程序移植方便。
5. 使用同样的初始化结构体,只修改控制的引脚和端口,初始化其它LED 灯使用的GPIO 引脚。
6. 使用宏控制RGB 灯默认关闭。
4.8.4. 主函数
编写完LED 灯的控制函数后,就可以在main 函数中测试了,见代码清单4-4。
代码清单4-4 控制LED 灯,main 文件
第4 章.GPIO 的使用
第42 页SAIUR2016
代码清单4-4 控制LED 灯,main 文件main 函数中,调用我们前面定义的LED_GPIO_Config
初始化好LED 的控制引脚,然后直接调用各种控制LED 灯亮灭的宏来实现LED 灯的控制。以上,
就是一个使用STM32 标准软件库开发应用的流程。
4.9. 点亮LED-下载验证
把编译好的程序下载到开发板并复位,可看到RGB 彩灯轮流显示不同的颜色。
4.10. 课后练习
使用GPIO 让LED 灯显示不同的颜色。
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第43页
第5 章. STM32 RCC 时钟系统
5.1. 课前预习
在书上找到答案。
1. APB2 时钟最大为多少?
2. 使用标准库怎么配置GPIO 的时钟?
5.2. 概述
本章所讲内容:
(1)时钟系统的结构和使用分析
(2)使用标准库配置系统时钟
RCC :reset clock control 复位和时钟控制器。本章我们主要讲解时钟部分,特别是要着重理解时
钟树,理解了时钟树,STM32 的一切时钟的来龙去脉都会了如指掌。
5.3. RCC 主要作用—时钟部分
设置系统时钟SYSCLK、设置AHB 分频因子(决定HCLK 等于多少)、设置APB2 分频因子
(决定PCLK2 等于多少)、设置APB1 分频因子(决定PCLK1 等于多少)、设置各个外设的分频
因子;控制AHB、APB2 和APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。对于
SYSCLK、HCLK、PCLK2、PCLK1 这四个时钟的配置一般是:PCLK2 = HCLK = SYSCLK=PLLCLK =
72M,PCLK1=HCLK/2 = 36M。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。
5.4. RCC 框图剖析—时钟部分
时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容
易, 而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数: SetSysClockTo72(); 以这个函数的
编写流程来讲解时钟树,这个函数也是我们用库的时候默认的系统时钟设置函数。该函数的功能是利
第5 章.STM32 RCC 时钟系统
第44 页SAIUR2016
用HSE 把时钟设置为:PCLK2 = HCLK = SYSCLK = 72M,PCLK1=HCLK/2 = 36M。下面我们就以这
个代码的流程为主线,来分析时钟树,对应的是图中的黄色部分,代码流程在时钟树中以数字的大小顺
序标识。
图5-1 STM32 时钟树
5.5. 系统时钟
5.5.1. HSE 高速外部时钟信号
HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从4-16MHZ 不等。当使
用有源晶振时,时钟从OSC_IN 引脚进入,OSC_OUT 引脚悬空,当选用无源晶振时,时钟从OSC_IN
和OSC_OUT 进入,并且要配谐振电容。HSE 最常使用的就是8M 的无源晶振。当确定PLL 时钟来
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第45页
源的时候,HSE 可以不分频或者2 分频,这个由时钟配置寄存器CFGR 的位17:PLLXTPRE 设置,
我们设置为HSE 不分频。
5.5.2. PLL 时钟源
PLL 时钟来源可以有两个,一个来自HSE,另外一个是HSI/2,具体用哪个由时钟配置寄存器
CFGR 的位16:PLLSRC 设置。HSI 是内部高速的时钟信号,频率为8M,根据温度和环境的情况频
率会有漂移,一般不作为PLL 的时钟来源。这里我们选HSE 作为PLL 的时钟来源。
5.5.3. PLL 时钟PLLCLK
通过设置PLL 的倍频因子, 可以对PLL 的时钟来源进行倍频, 倍频因子可以
是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],具体设置成多少,由时钟配置寄存器CFGR 的位21-18:
PLLMUL[3:0]设置。我们这里设置为9 倍频,因为上一步我们设置PLL 的时钟来源为HSE=8M,
所以经过PLL 倍频之后的PLL 时钟:PLLCLK = 8M *9 = 72M。72M 是ST 官方推荐的稳定运行时
钟,如果你想超频的话,增大倍频因子即可,最高为128M。我们这里设置PLL 时钟:PLLCLK = 8M
*9 = 72M。
5.5.4. 系统时钟SYSCLK
系统时钟来源可以是:HSI、PLLCLK、HSE,具体的时钟配置寄存器CFGR 的位1-0:SW[1:0]
设置。我们这里设置系统时钟:SYSCLK = PLLCLK = 72M。
5.5.5. AHB 总线时钟HCLK
系统时钟SYSCLK 经过AHB 预分频器分频之后得到时钟叫APB 总线时钟,即HCLK,分频因
子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器CFGR 的位7-4 :HPRE[3:0]
设置。片上大部分外设的时钟都是经过HCLK 分频得到,至于AHB 总线上的外设的时钟设置为多少,
得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好APB 的时钟即可。我们这里设
置为1 分频,即HCLK=SYSCLK=72M。
5.5.6. APB2 总线时钟HCLK2
APB1 APB2 总线时钟PCLK2 由HCLK 经过高速APB2 预分频器得到,分频因子可以
是:[1,2,4,8,16],具体由时钟配置寄存器CFGR 的位13-11:PPRE2[2:0]决定。HCLK2 属于高速的
总线时钟,片上高速的外设就挂载到这条总线上,比如全部的GPIO、USART1、SPI1 等。至于APB2
总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好
APB2 的时钟即可。我们这里设置为1 分频,即PCLK2 = HCLK =72M。
第5 章.STM32 RCC 时钟系统
第46 页SAIUR2016
5.5.7. 总线时钟HCLK1
APB1 总线时钟PCLK1 由HCLK 经过低速APB 预分频器得到,分频因子可以是:[1,2,4,8,16],
具体的由时钟配置寄存器CFGR 的位10-8:PRRE1[2:0]决定。HCLK1 属于低速的总线时钟,最高为
36M,片上低速的外设就挂载到这条总线上,比如USART2/3/4/5、SPI2/3,I2C1/2 等。至于APB1 总
线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好
APB1 的时钟即可。我们这里设置为2 分频,即PCLK1 = HCLK/2 = 36M。
5.6. 设置系统时钟库函数
上面的7 个步骤对应的设置系统时钟库函数如下, 该函数截取自固件库文件
system_stm32f10x.c。为了方便阅读,我已把互联型相关的代码删掉,把英文注释翻译成了中文,并把
代码标上了序号,总共七个步骤。该函数是直接操作寄存器的,有关寄存器部分请参考数据手册的RCC
的寄存器描述部分。
代码5-1 设置系统时钟库函数
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第47页
5.7. 其他时钟
通过对系统时钟设置的讲解,整个时钟树我们已经把握的有六七成,剩下的时钟部分我们讲解几个
重要的。
5.7.1. USB 时钟
USB 时钟是由PLLCLK 经过USB 预分频器得到,分频因子可以是:[1,1.5],具体的由时钟配置
寄存器CFGR 的位22:USBPRE 配置。USB 的时钟最高是48M,根据分频因子反推过来算,PLLCLK
只能是48M 或者是72M 。一般我们设置PLLCLK=72M , USBCLK=48M。USB 对时钟要求比较
高,所以PLLCLK 只能是由HSE 倍频得到,不能使用HSI 倍频。
5.7.2. Cortex 系统时钟
Cortex 系统时钟由HCLK 8 分频得到,等于9M,Cortex 系统时钟用来驱动内核的系统定时器
SysTick,SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。
第5 章.STM32 RCC 时钟系统
第48 页SAIUR2016
5.7.3. ADC 时钟
ADC 时钟由PCLK2 经过ADC 预分频器得到,分频因子可以是[2,4,6,8],具体的由时钟配置寄
存器CFGR 的位15-14:ADCPRE[1:0]决定。很奇怪的是怎么没有1 分频。ADC 时钟最高只能是
14M,如果采样周期设置成最短的1.5 个周期的话,ADC 的转换时间可以达到最短的1us。如果真要
达到最短的转换时间1us 的话,那ADC 的时钟就得是14M,反推PCLK2 的时钟只能是:28M、56M、
84M、112M,鉴于PCLK2 最高是72M,所以只能取28M 和56M。
5.7.4. RTC 时钟、独立看门狗时钟
RTC 时钟可由HSE/128 分频得到,也可由低速外部时钟信号LSE 提供,频率为32.768KHZ,也
可由低速内部时钟信号HSI 提供,具体选用哪个时钟由备份域控制寄存器BDCR 的位9-8:
RTCSEL[1:0]配置。独立看门狗的时钟由LSI 提供,且只能是由LSI 提供,LSI 是低速的内部时钟信
号,频率为30~60KHZ 直接不等,一般取40KHZ。
5.7.5. MCO 时钟输出
MCO 是microcontroller clock output 的缩写,是微控制器时钟输出引脚,在STM32 F1 系列中由
PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO 的时钟来源可以是:
PLLCLK/2 、HSI、HSE 、SYSCLK ,具体选哪个由时钟配置寄存器CFGR 的位26-24:MCO[2:0]
决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控MCO 引脚的时钟输出来验证我
们的系统时钟配置是否正确。
5.8. 配置系统时钟实验
5.8.1. 使用HSE
一般情况下,我们都是使用HSE,然后HSE 经过PLL 倍频之后作为系统时钟。通常的配置是:
HSE=8M,PLL 的倍频因子为:9,系统时钟就设置成:SYSCLK = 8M * 9 = 72M。使用HSE,系统时钟
SYSCLK 最高是128M。我们使用的库函数就是这么干的, 当程序来到main 函数之前,启动文件:
statup_stm32f10x_hd.s 已经调用SystemInit()函数把系统时钟初始化成72MHZ,SystemInit()在库文件:
system_stm32f10x.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,
但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。
5.8.2. 使用HSI
HSE 故障的时候,如果PLL 的时钟来源是HSE,那么当HSE 故障的时候,不仅HSE 不能使
用,连PLL 也会被关闭,这个时候系统会自动切换HSI 作为系统时钟,此时SYSCLK=HSI=8M,如
果没有开启CSS 和CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第49页
如果开启了CSS 功能的话,那么可以当HSE 故障时,在CSS 中断里面采取补救措施,使用HSI,
并把系统时钟设置为更高的频率,最高是64M, 64M 的频率足够一般的外设使用,如:ADC 、SPI、
I2C 等。但是这里就又有一个问题了, 原来SYSCLK=72M,现在因为故障改成64M,那么那些外设
的时钟肯定被改变了,那么外设工作就会被打乱,那我们是不是在设置HSI 时钟的时候,也重新调
整外设总线的分频因子,即AHB,APB2 和APB1 的分频因子,使外设的时钟达到跟HSE 没有故
障之前一样。但是这个也不是最保障的办法,毕竟不能一直使用HSI,所以当HSE 故障时还是要采
取报警措施。还有一种情况是,有些用户不想用HSE,想用HSI,但是又不知道怎么用HSI 来设置
系统时钟,因为调用库函数都是使用HSE,下面我们给出个使用HSI 配置系统时钟例子, 起个抛砖
引玉的作用。
5.8.3. 硬件设计
1、RCC
2、LED 一个
RCC 是单片机内部资源,不需要外部电路。通过LED 闪烁的频率来直观的判断不同系统时钟频
率对软件延时的效果。
5.8.4. 软件设计
我们编写两个RCC 驱动文件,bsp_clkconfig.h 和bsp_clkconfig.c,用来存放RCC 系统时钟配置
函数。
5.8.5. 编程要点
编程要点对应着时钟树图中的序号。
1、开启HSE/HSI ,并等待HSE/HSI 稳定
2、设置AHB、APB2、APB1 的预分频因子
3、设置PLL 的时钟来源,和PLL 的倍频因子,设置各种频率主要就是在这里设置
4、开启PLL,并等待PLL 稳定
5、把PLLCK 切换为系统时钟SYSCLK
6、读取时钟切换状态位,确保PLLCLK 被选为系统时钟
5.8.6. 代码分析
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考
本章配套的工程。使用HSE 配置系统时钟
第5 章.STM32 RCC 时钟系统
第50 页SAIUR2016
代码5-2 HSE 作为系统时钟来源
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第51页
这个函数采用库函数编写, 函数有个形参pllmul,pllmul 用来设置PLL 的倍频因子,在调用的
时候形参可以是:RCC_PLLMul_x , x:[2,3,...16],这些宏来源于库函数的定义,宏展开是一些32 位的
十六进制数,具体功能是配置了时钟配置寄存器CFGR 的位21-18 PLLMUL[3:0],预先定义好倍频因
子,方便调用。函数调用举例:HSE_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:8MHZ * 9 =
72MHZ。HSE_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ 超频慎用。
代码5-3 实用HSI 配置系统时钟
第5 章.STM32 RCC 时钟系统
第52 页SAIUR2016
HSI 设置
系统时钟函数跟HSE 设置系统时钟函数在原理上是一样的,有一个区别的地方就是,HSI 必须2 分
频之后才能作为PLL 的时钟来源,所以使用HSI 时,最大的系统时钟SYSCLK 只能是
HSI/2*16=4*16=64MHZ。函数调用举例:HSI_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:4MHZ
* 9 =使用HSI 配置系统时钟36MHZ。软件延时
软件延时函数,使用不同的系统时钟,延时时间不一样,可以通过LED 闪烁的频率来判断。
MCO 输出
在STM32F103 系列中,PA8 可以复用为MCO 引脚,对外提供时钟输出,我们也可以用示波器
监控该引脚的输出来判断我们的系统时钟是否设置正确。
第5 章.STM32 RCC 时钟系统
SAIUR201 6 第53页
代码5-4 MCO GPIO 初始化
代码5-5 MCO 输出时钟选择
我们初始化MCO 引脚之后,可以直接调用库函数RCC_MCOConfig()来选择MCO 时钟来源。主
函数如代码5-5
第5 章.STM32 RCC 时钟系统
第54 页SAIUR2016
代码5-5 主函数
在主函数中,可以调用HSE_SetSysClock()或者HSI_SetSysClock()这两个函数把系统时钟设置成
各种常用的时钟,然后通过MCO 引脚监控,或者通过LED 闪烁的快慢体验不同的系统时钟对同一
个软件延时函数的影响。
5.8.7. 下载验证
把编译好的程序下载到开发板,可以看到设置不同的系统时钟时,LED 闪烁的快慢不一样。更精
确的数据我们可以用示波器监控MCO 引脚看到。
图5-2 MCO=SYSCLK=72M 图5-3 MCO=HSI=8M
第6 章.STM32 中断应用概述
SAIUR201 6 第55页
第6 章. STM32 中断应用概览
6.1. 课前预习
在书上找到答案。
1. 什么是中断控制器?
2. 怎么配置NVIC 寄存器
6.2. 概述
本章所讲内容:
(1)NVIC 的介绍
(2)NVIC 的配置方法
STM32 中断非常强大,每个外设都可以产生中断,所以中断的讲解放在哪一个外设里面去讲都
不合适,这里单独抽出一章来做一个总结性的介绍,这样在其他章节涉及到中断部分的知识我们就不
用费很大的篇幅去讲解,只要示意性带过即可。本章如无特别说明,异常就是中断,中断就是异常,请
不要刻意钻牛角尖较劲。
6.3. 异常类型
STM32F103 在内核水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断。其
中系统异常有8 个(如果把Reset 和HardFault 也算上的话就是10 个),外部中断有60 个。除了
个别异常的优先级被定死外,其它异常的优先级都是可编程的。有关具体的系统异常和外部中断可在标
准库文件stm32f10x.h 这个头文件查询到,在IRQn_Type 这个结构体里面包含了F103 系列全部的异
常声明。
表格6-1 F103 系统异常清单
第6 章.STM32 中断应用概述
第56 页SAIUR2016
表格6-2 F103 外部中断清单
6.4. NVIC 简介
在讲如何配置中断优先级之前,我们需要先了解下NVIC。NVIC 是嵌套向量中断控制器,控制着
整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片
的时候会对Cortex-M3 内核里面的NVIC 进行裁剪,把不需要的部分去掉,所以说STM32 的NVIC
是Cortex-M3 的NVIC 的一个子集。
6.5. NVIC 寄存器简介
在固件库中,NVIC 的结构体定义可谓是颇有远虑,给每个寄存器都预留了很多位,恐怕为的是日
后扩展功能。不过STM32F103 可用不了这么多,只是用了部分而已,具体使用了多少可参考
第6 章.STM32 中断应用概述
SAIUR201 6 第57页
《Cortex-M3 内核编程手册》-4.3.11:NVIC 寄存器映射。
代码6-1 NVIC 结构体定义,来自固件库头文件:core_cm3.h
在配置中断的时候我们一般只用ISER、ICER 和IP 这三个寄存器,ISER 用来使能中断,ICER 用
来失能中断,IP 用来设置中断优先级。
6.6. NVIC 中断配置固件库
固件库文件core_cm3.h 的最后,还提供了NVIC 的一些函数,这些函数遵循CMSIS 规则,只
要是Cortex-M3 的处理器都可以使用,具体如下:
表格6-3 符合CMSIS 标准的NVIC 库函数
这些库函数我们在编程的时候用的都比较少,甚至基本都不用。在配置中断的时候我们还有更简洁
的方法,请看中断编程小节。
第6 章.STM32 中断应用概述
第58 页SAIUR2016
6.7. 优先级的定义
6.7.1. 优先级定义
在NVIC 有一个专门的寄存器:中断优先级寄存器NVIC_IPRx,用来配置外部中断的优先级,IPR
宽度为8bit,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。但是绝大多数
CM3 芯片都会精简设计,以致实际上支持的优先级数减少,在F103 中,只使用了高4bit,如下所示:
表格6-4 F103 使用4bit 表达优先级
用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占
优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢
占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
6.7.2. 优先级分组
优先级的分组由内核外设SCB 的应用程序中断及复位控制寄存器AIRCR 的
PRIGROUP[10:8]位决定,F103 分为了5 组,具体如下:主优先级=抢占优先级
表格6-5 优先级表
设置优先级分组可调用库函数NVIC_PriorityGroupConfig()实现,有关NVIC 中断相关的库函数
都在库文件misc.c 和misc.h 中。
表格6-7 优先级分组真值表
第6 章.STM32 中断应用概述
SAIUR201 6 第59页
代码6-2 中断优先级分组库函数NVIC_PriorityGroupConfig()
6.8.中断编程
在配置每个中断的时候一般有3 个编程要点:
1. 使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接
收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
2. 初始化NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中
断请求。NVIC_InitTypeDef 结构体在固件库头文件misc.h 中定义。
代码6-3 NVIC 初始化结构体
有关NVIC 初始化结构体的成员我们一一解释下:
NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序也不会
报错,只会导致不响应中断。具体的成员配置可参考stm32f10x.h 头文件里面的IRQn_Type 结构体定
义,这个结构体包含了所有的中断源。
代码6-4 IRQn_Type 中断源结构体
第6 章.STM32 中断应用概述
第60 页SAIUR2016
NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定.。
NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定,具体参考表格17-5 优
先级分组真值表。
NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是NVIC_ISER 和
NVIC_ICER 这两个寄存器。
3. 编写中断服务函数
在启动文件startup_stm32f10x_hd.s 中我们预先为每个中断都写了一个中断服务函数, 只是这些
中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写,为了方
便管理我们把中断服务函数统一写在stm32f10x_it.c 这个库文件中。关于中断服务函数的函数名必须
跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,直
接跳转到启动文件里面预先写好的空函数, 并且在里面无限循环,实现不了中断。
6.9. 课后练习
1、库文件core_cm3.h 主要实现了什么?
2、库文件mics.c 和mics.h 主要实现了什么?
3、如果实现一次软件系统复位,具体是操作哪个寄存器的哪个位实现?
第7 章.EXTI-外部中断/事件控制器
SAIUR201 6 第61页
第7 章. EXTI—外部中断/事件控制器
7.1. 课前预习
在书上找到答案。
1. EXTI 有多少种?
2. 怎么使用标准库配置EXTI
7.2. 概述
本章所讲内容:
(1)EXTI 的介绍
(2)EXTI 的配置方法
上一章节我们已经详细介绍了NVIC,对STM32F10x 系列中断管理系统有个全局的了解,我们
这章的内容是NVIC 的实例应用,也是STM32F10x 控制器非常重要的一个资源。学习本章时,配
合《STM32F10X-中文参考手册》中断和事件章节一起阅读,效果会更佳,特别是涉及到寄存器说明的
部分。
7.3. EXTI 简介
EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的20 个中断/事
件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。
EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属
性。
7.4. EXTI 功能框图
EXTI 的功能框图包含了EXTI 最核心内容,掌握了功能框图,对EXTI 就有一个整体的把握,
在编程时思路就非常清晰。EXTI 功能框图见图7-1。在图18-1 可以看到很多在信号线上打一个斜杠
并标注“20”字样,这个表示在控制器内部类似的信号线路有20 个,这与EXTI 总共有20 个中断/事
陈德金老师编著
第7 章.EXTI-外部中断/事件控制器
第62 页SAIUR2016
件线是吻合的。所以我们只要明白其中一个的原理,那其他19 个线路原理也就知道了。
图7-1 EXTI 功能框图
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不
同。首先我们来看图18-1 中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到
NVIC 控制器内。编号1 是输入线,EXTI 控制器有19 个中断/事件输入线,这些输入线可以通过寄
存器设置为任意一个GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输入线一
般是存在电平变化的信号。编号2 是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)
和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号
输入端,如果检测到有边沿跳变就输出有效信号1 给编号3 电路,否则输出无效信号0。而
EXTI_RTSR 和EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上
升沿触发、只有下降沿触发或者上升沿和下降沿都触发。编号3 电路实际就是一个或门电路,它一个
输入来自编号2 电路,另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我
们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有1 就为
1,所以这两个输入随便一个有有效信号1 就可以输出1 给编号4 和编号6 电路。编号4 电路是一
个与门电路,它一个输入是编号3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要
求输入都为1 才输出1,导致的结果是如果EXTI_IMR 设置为0 时,那不管编号3 电路的输出信
号是1 还是0,最终编号4 电路输出的信号都为0;如果EXTI_IMR 设置为1 时,最终编号4 电
路输出的信号才由编号3 电路的输出信号决定,这样我们可以简单的控制EXTI_IMR 来实现是否产
生中断的目的。编号4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号4 电路输
出为1 就会把EXTI_PR 对应位置1。编号5 是将EXTI_PR 寄存器内容输出到NVIC 内,从而实
现系统中断事件控制。接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终
第7 章.EXTI-外部中断/事件控制器
SAIUR201 6 第63页
输出一个脉冲信号。产生事件线路是在编号3 电路之后与中断线路有所不同,之前电路都是共用的。
编号6 电路是一个与门,它一个输入来自编号3 电路, 另外一个输入来自事件屏蔽寄存器
(EXTI_EMR)。如果EXTI_EMR 设置为0 时,那不管编号3 电路的输出信号是1 还是0,最终编
号6 电路输出的信号都为0;如果EXTI_EMR 设置为1 时,最终编号6 电路输出的信号才由编号
3 电路的输出信号决定,这样我们可以简单的控制EXTI_EMR 来实现是否产生事件的目的。编号7 是
一个脉冲发生器电路,当它的输入端,即编号6 电路的输出端,是一个有效信号1 时就会产生一个
脉冲;如果输入端是无效信号就不会输出脉冲。编号8 是一个脉冲信号,就是产生事件的线路最终的
产物,这个脉冲信号可以给其他外设电路使用,比如定时器TIM、模拟数字转换器ADC 等等,这样
的脉冲信号一般用来触发TIM 或者ADC 开始转换。产生中断线路目的是把输入信号输入到NVIC,
进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号
给其他外设使用,并且是电路级别的信号传输,属于硬件级的。另外,EXTI 是在APB2 总线上的,
在编程时候需要注意到这点。
7.5. 中断/事件线
EXTI 有20 个中断/事件线,每个GPIO 都可以被设置为输入线,占用EXTI0 至EXTI15,还有另外
七根用于特定的外设事件,见表7-1。4 根特定外设中断/事件线由外设触发。
表7-1 EXTI 中断/事件线
EXTI0 至EXTI15 用于GPIO,通过编程控制可以实现任意一个GPIO 作为EXTI 的输入源。
陈德金老师编著
第7 章.EXTI-外部中断/事件控制器
第64 页SAIUR2016
由表7-1 可知,EXTI0 可以通过AFIO 的外部中断配置寄存器1(AFIO_EXTICR1)的EXTI0[3:0]位选
择配置为PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或者PI0,见图7-2。其他EXTI 线(EXTI 中
断/事件线)使用配置都是类似的。
图7-2 EXTI0 输入源选择
7.6. EXTI 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如EXTI_InitTypeDef,结构体成员用于设
置外设工作参数,并由外设初始化配置函数,比如EXTI_Init()调用,这些设定参数将会设置外设相应
的寄存器,达到配置外设工作环境的目的。初始化结构体和初始化库函数配合使用是标准库精髓所在,
理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在
stm32f10x_exti.h 文件中,初始化库函数定义在stm32f10x_exti.c 文件中,编程时我们可以结合这两个
文件内注释使用。
代码清单7-1 EXTI 初始化结构体
EXTI_Line:EXTI 中断/事件线选择,可选EXTI0 至EXTI19,可参考表7-1 选择。
EXTI_Mode : EXTI 模式选择, 可选为产生中断(EXTI_Mode_Interrupt) 或者产生事件
(EXTI_Mode_Event)。
EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降沿触发
( EXTI_Trigger_Falling) 或者上升沿和下降沿都触发( EXTI_Trigger_Rising_Falling)。
EXTI_LineCmd:控制是否使能EXTI 线,可选使能EXTI 线(ENABLE)或禁用(DISABLE)。
第7 章.EXTI-外部中断/事件控制器
SAIUR201 6 第65页
7.7. 外部中断控制实验
中断在嵌入式应用中占有非常重要的地位,几乎每个控制器都有中断功能。中断对保证紧急事件
得到第一时间处理是非常重要的。我们设计使用外接的按键来作为触发源,使得控制器产生中断,并在
中断服务函数中实现控制RGB 彩灯的任务。
7.7.1. 硬件设计
轻触按键在按下时会使得引脚接通,通过电路设计可以使得按下时产生电平变化,见图7-3。
图7-3 按键电路设计
7.7.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参
考本章配套的工程。我们创建了两个文件:bsp_exti.c 和bsp_exti.h 文件用来存放EXTI 驱动程序及相
关宏定义,中断服务函数放在stm32f10x_it.h 文件中。
7.7.3. 编程要点
1.初始化用来产生中断的GPIO;
2.初始化EXTI;
3.配置NVIC;
4.编写中断服务函数;
7.7.4. 代码分析
使用宏定义方法指定与硬件电路设计相关配置,这对于程序移植或升级非常有用的。在上面的宏定
义中,我们除了开GPIO 的端口时钟外,我们还打开了AFIO 的时钟,这是因为等下配置EXTI 信号
源的时候需要用到AFIO 的外部中断控制寄存器AFIO_EXTICRx,具体见《STM32F10X-中文参考手
册》8.4 章节AFIO 寄存器描述。嵌套向量中断控制器NVIC 配置有关NVIC 配置问题可参考《STM32
中断应用概览》章节内容,这里不做过多解释。这里我们配置两个的中断软件优先级一样,如果出现了
两个按键同时按下的情况,那怎么办,到底该执行哪一个中断?当两个中断的软件优先级一样的时候,
陈德金老师编著
第7 章.EXTI-外部中断/事件控制器
第66 页SAIUR2016
中断来临时,具体先执行哪个中断服务函数由硬件的中断编号决定,编号越小,优先级越高。有关外设
的硬件编号可查询《STM32F10X-中文参考手册》的中断和事件章节中的向量表,表中的位置编号即
是每个外设的硬件中断优先级。当然,我们也可以把抢占优先级设置成一样,子优先级设置成不一样,
这样就可以区别两个按键同时按下的情况,而不用硬件去对比硬件编号。
代码清单7-2 按键和EXTI 宏定义
代码清单7-3 NVIC 配置
第7 章.EXTI-外部中断/事件控制器
SAIUR201 6 第67页
EXTI 中断配置
代码清单7-4 EXTI 中断配置
首先,使用GPIO_InitTypeDef 和EXTI_InitTypeDef 结构体定义两个用于GPIO 和EXTI 初始化
配置的变量,关于这两个结构体前面都已经做了详细的讲解。使用GPIO 之前必须开启GPIO 端口的
时钟;用到EXTI 必须开启AFIO 时钟。调用NVIC_Configuration 函数完成对按键1、按键2 优
先级配置并使能中断通道。作为中断/事件输入线时需把GPIO 配置为输入模式,具体为浮空输入,由
外部电路完全决定引脚的状态。GPIO_EXTILineConfig 函数用来指定中断/事件线的输入源,它实际是
陈德金老师编著
第7 章.EXTI-外部中断/事件控制器
第68 页SAIUR2016
设定外部中断配置寄存器的AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定GPIO 端口
源,第二个参数为选择对应GPIO 引脚源编号。我们的目的是产生中断,执行中断服务函数,EXTI 选
择中断模式,按键1 使用上升沿触发方式,并使能EXTI 线。按键2 基本上采用与按键1 相关参数
配置,只是改为下降沿触发方式。两个按键的电路是一样的,可代码中我们设置按键1 是上升沿中断,
按键2 是下降沿中断,有人就会问这是不是设置错了?实际上可以这么理解,按键1 检测的是按键按
下的状态,按键2 检测的是按键弹开的状态,那这样就解释的通了。
代码清单7-5 EXTI 中断服务函数
当中断发生时,对应的中断服务函数就会被执行,我们可以在中断服务函数实现一些控制。一般为
确保中断确实发生,我们会在中断服务函数中调用中断标志位状态读取函数读取外设中断标志位并判断
标志位状态。EXTI_GetITStatus 函数用来获取EXTI 的中断标志位状态,如果EXTI 线有中断发生函
数返回“ SET ” 否则返回“ RESET ” 。实际上, EXTI_GetITStatus 函数是通过读取EXTI_PR
寄存器值来判断EXTI 线状态的。按键1 的中断服务函数我们让LED1 翻转其状态,按键2 的中断
服务函数我们让LED2 翻转其状态。执行任务后需要调用EXTI_ClearITPendingBit 函数清除EXTI 线
的中断标志位。主函数
代码清单7-6 主函数
主函数非常简单,只有两个任务函数。LED_GPIO_Config 函数定义在bsp_led.c 文件内,完成RGB
第7 章.EXTI-外部中断/事件控制器
SAIUR201 6 第69页
彩灯的GPIO 初始化配置。EXTI_Key_Config 函数完成两个按键的GPIO 和EXTI 配置。
7.7.5. 下载验证
保证开发板相关硬件连接正确,把编译好的程序下载到开发板。此时RGB 彩色灯是暗的,如果
我们按下开发板上的按键1,RGB 彩灯变亮,再按下按键1,RGB 彩灯又变暗;如果我们按下开发
板上的按键2 并弹开,RGB 彩灯变亮,再按下开发板上的KEY2 并弹开,RGB 彩灯又变暗。按键
按下表示上升沿,按键弹开表示下降沿,这跟我们软件设置是一样的。
7.8. 课后练习
1、是否可以同时使用PA0 和PB0 中断?如果不可以,有什么解决方法。
2、从硬件角度结合程序分析,为什么按下按键1RGB 彩灯就马上变化,而按键2 却需要按下
按键再弹开之后RGB 彩灯才变化?
第8 章.SysTick 系统定时器
第70 页SAIUR2016
第8 章. SysTick 系统定时器
8.1. 课前预习
在书上找到答案。
1. SysTIck 和系统时钟有什么区别?
8.2. 概述
本章所讲内容:
(1)SysTick 的说明和使用
8.3. SysTick 简介
SysTick—系统定时器是属于CM3 内核中的一个外设,内嵌在NVIC 中。系统定时器是一个24bit
的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK 等于
72M。当重装载数值寄存器的值递减到0 的时候,系统定时器就产生一次中断,以此循环往复。因为
SysTick 是属于CM3 内核的外设,所以所有基于CM3 内核的单片机都具有这个系统定时器,使得
软件在CM3 单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作
系统的心跳。
8.4. SysTick 寄存器介绍
SysTick—系统定时器有4 个寄存器,简要介绍如下。在使用SysTick 产生定时的时候,需要配置
前三个寄存器,最后一个校准寄存器不需要使用。
表8-1 SysTick 寄存器汇总
第8 章.SysTick 系统定时器
SAIUR201 6 第71页
表8-2 SysTick 控制及状态寄存器
表8-3 SysTick 重装载数值寄存器
表8-4 SysTick 当前数值寄存器
第8 章.SysTick 系统定时器
第72 页SAIUR2016
表8-5 SysTick 当前数值寄存器
系统定时器的校准数值寄存器在定时实验中不需要用到。有关各个位的描述这里引用手册里面的英
文版本,比较晦涩难懂,暂时不知道这个寄存器用来干什么。有研究过的朋友可以交流,起个抛砖引玉
的作用。
8.5. SysTick 定时实验
利用SysTick 产生1s 的时基,LED 以1s 的频率闪烁。
8.5.1. 硬件设计
SysTick 属于单片机内部的外设,不需要额外的硬件电路,剩下的只需一个LED 灯即可。
8.5.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参
考本章配套的工程。我们创建了两个文件:bsp_SysTick.c 和bsp_ SysTick.h 文件用来存放SysTick 驱
动程序及相关宏定义,中断服务函数放在stm32f10x_it.h 文件中。
8.5.3. 编程要点
1、设置重装载寄存器的值
2、清除当前数值寄存器的值
3、配置控制与状态寄存器
第8 章.SysTick 系统定时器
SAIUR201 6 第73页
8.5.4. 代码分析
SysTick 属于内核的外设, 有关的寄存器定义和库函数都在内核相关的库文件core_cm3.h 中。
SysTick 配置库函数
代码8-1 SysTick 配置库函数
用固件库编程的时候我们只需要调用库函数SysTick_Config()即可,形参ticks 用来设置重装载寄
存器的值,最大不能超过重装载寄存器的值224,当重装载寄存器的值递减到0 的时候产生中断,然
后重装载寄存器的值又重新装载往下递减计数,以此循环往复。紧随其后设置好中断优先级,最后配置
系统定时器的时钟等于AHBCLK=72M,使能定时器和定时器中断,这样系统定时器就配置好了,一个
库函数搞定。SysTick_Config()库函数主要配置了SysTick 中的三个寄存器:LOAD、VAL 和CTRL,
有关具体的部分看代码注释即可。
配置SysTick 中断优先级
在SysTick_Config()库函数还调用了固件库函数NVIC_SetPriority()来配置系统定时器的中断优先
级,该库函数也在core_m3.h 中定义,原型如下:
函数首先先判断形参IRQn 的大小,如果是小于0,则表示这个是系统异常,系统异常的优先级
由内核外设SCB 的寄存器SHPRx 控制,如果大于0 则是外部中断,外部中断的优先级由内核外设
NVIC 中的IPx 寄存器控制。因为SysTick 属于内核外设,跟普通外设的中断优先级有些区别,并没
有抢占优先级和子优先级的说法。在STM32F103 中,内核外设的中断优先级由内核SCB 这个外设的
第8 章.SysTick 系统定时器
第74 页SAIUR2016
寄存器:SHPRx(x=1.2.3)来配置。有关SHPRx 寄存器的详细描述可参考《Cortex-M3 内核编程手册》
4.4.8 章节。下面我们简单介绍下这个寄存器。SPRH1-SPRH3 是一个32 位的寄存器,但是只能通过
字节访问,每8 个字段控制着一个内核外设的中断优先级的配置。在STM32F103 中,只有位7:3 这
高四位有效,低四位没有用到,所以内核外设的中断优先级可编程为:0~15,只有16 个可编程优先
级,数值越小,优先级越高。如果软件优先级配置相同,那就根据他们在中断向量表里面的位置编号来
决定优先级大小,编号越小,优先级越高。
表8-6 系统异常优先级字段
如果要修改内核外设的优先级,只需要修改下面三个寄存器对应的某个字段即可。
图8-1 SHPR1 寄存器
图8-2 SHPR2 寄存器
第8 章.SysTick 系统定时器
SAIUR201 6 第75页
图8-3 SHPR3 寄存器
系统定时器中, 配置优先级为(1UL << __NVIC_PRIO_BITS) - 1UL) , 其中宏
__NVIC_PRIO_BITS 为4,那计算结果就等于15,可以看出系统定时器此时设置的优先级在内核外设
中是最低的,如果要修改优先级则修改这个值即可,范围为:0~15。
systick 和片上外设呢?而且片上外设也刚好需要使用中断,那systick 的中断优先级跟外设的中
断优先级怎么设置?会不会因为systick 是内核里面的外设,所以它的中断优先级就一定比内核之外的
外设的优先级高?
从《STM32 中断应用概览》这章我们知道,外设在设置中断优先级的时候,首先要分组,然后
设置抢占优先级和子优先级。而systick 这类内核的外设在配置的时候,只需要配置一个寄存器即可,
取值范围为0~15。既然配置方法不同,那如何区分两者的优先级?下面举例说明。比如配置一个外设
的中断优先级分组为2,抢占优先级为1,子优先级也为1,systick 的优先级为固件库默认配置的15。
当我们比较内核外设和片上外设的中断优先级的时候,我们只需要抓住NVIC 的中断优先级分组不仅
对片上外设有效,同样对内核的外设也有效。我们把systick 的优先级15 转换成二进制值就是
1111(0b),又因为NVIC 的优先级分组2,那么前两位的11(0b)就是3,后两位的11(0b)也是3。无
论从抢占还是子优先级都比我们设定的外设的优先级低。如果当两个的软件优先级都配置成一样,那么
就比较他们在中断向量表中的硬件编号,编号越小,优先级越高。
第8 章.SysTick 系统定时器
第76 页SAIUR2016
SysTick 初始化函数
代码8-2 SysTick 初始化函数
SysTick 初始化函数由用户编写,里面调用了SysTick_Config()这个固件库函数,通过设置该固件
库函数的形参,就决定了系统定时器经过多少时间就产生一次中断。
SysTick 中断时间的计算
SysTick 定时器的计数器是向下递减计数的,计数一次的时间TDEC=1/CLKAHB,当重装载寄存
器中的值VALUELOAD 减到0 的时候, 产生中断, 可知中断一次的时间TINT=VALUELOAD *
TDEC= VALUELOAD/CLKAHB , 其中CLKAHB =72MHZ 。如果设置VALUELOAD 为72,
那中断一次的时间TINT=72/72M=1us。不过1us 的中断没啥意义,整个程序的重心都花在进出中断上
了,根本没有时间处理其他的任务。
SysTick_Config()的形我们配置为SystemCoreClock / 100000=72M/100000=720,从刚刚分析我
们知道这个形参的值最终是写到重装载寄存器LOAD 中的,从而可知我们现在把SysTick 定时器中断
一次的时间TINT=720/72M=10us。
SysTick 定时时间的计算
当设置好中断时间TINT 后,我们可以设置一个变量t,用来记录进入中断的次数,那么变量t 乘
以中断的时间TINT 就可以计算出需要定时的时间。
第8 章.SysTick 系统定时器
SAIUR201 6 第77页
SysTick 定时函数
现在我们定义一个微秒级别的延时函数,形参为nTime,当用这个形参乘以中断时间TINT 就得
出我们需要的延时时间,其中TINT 我们已经设置好为10us。关于这个函数的具体调用看注释即可。
函数Delay_us()中我们等待TimingDelay 为0,当TimingDelay 为0 的时候表示延时时间到。变
量TimingDelay 在中断函数中递减,即SysTick 每进一次中断即10us 的时间TimingDelay 递减一次。
SysTick 中断服务函数
中断复位函数调用了另外一个函数TimingDelay_Decrement(),原型如下:
TimingDelay 的值等于延时函数中传进去的nTime 的值,比如nTime=100000,则延时的时间等于
100000*10us=1s。
第8 章.SysTick 系统定时器
第78 页SAIUR2016
主函数中初始化了LED 和SysTick,然后在一个while 循环中以1s 的频率让LED 闪烁。另
外一种更简洁的定时编程上面的实验,我们是使用了中断,而且经过多个函数的调用,还使用了全局变
量,理解起来挺费劲的,其实还有另外一种更简洁的写法。我们知道,systick 的counter 从reload 值
往下递减到0 的时候,CTRL 寄存器的位16:countflag 会置1,且读取该位的值可清0,所有我们可
以使用软件查询的方法来实现延时。具体代码见代码8-3 和代码8-4,我敢肯定这样的写法,初学者
肯定会更喜欢,因为它直接,套路浅。
代码8-3 systick 微秒级延时
在这两个微秒和毫秒级别的延时函数中,我们还是调用了SysTick_Config 这个固件库函数, 有关
这个函数的说明具体见代码19-5 。配套代码注释理解即可。其中SystemCoreClock 是一个宏,大
小为72000000,如果不想使用这个宏,也可以直接改成数字。
第8 章.SysTick 系统定时器
SAIUR201 6 第79页
8.6. 课后练习
1、如果修改SysTick 的中断优先级?
2、如何计算SysTick 进入一次中断的时间?
3、如何利用SysTick 实现一个1ms 的延时?
第9 章.USART-串口通讯
第80 页SAIUR2016
第9 章. USART—串口通讯
9.1. 课前预习
在书上找到答案。
1. 同步和异步的区别?
2. USART 有多少个引脚?
9.2. 概述
本章所讲内容:
(1)USART 的介绍
(2)使用串口发送和接收数据
9.3. 串口通讯协议简介
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此
大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32
标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本
的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数
据在物理媒体的传输。协议层主要规定通讯逻辑, 统一收发双方的数据打包、解包标准。简单来说物
理层规定我们用嘴巴还是用肢体来交流, 协议层则规定我们用中文还是英文来交流。下面我们分别对
串口通讯协议的物理层及协议层进行讲解。
9.3.1. 物理层
串口通讯的物理层有很多标准及变种,我们主要讲解RS-232 标准,RS-232 标准主要规定了信
号的用途、通讯接口以及信号的电平标准。使用RS-232 标准的串口设备间常见的通讯结构见图9-1。
第9 章.USART-串口通讯
SAIUR201 6 第81页
图9-1 串口通讯结构图
在上面的通讯方式中,两个通讯设备的“DB9 接口”之间通过串口信号线建立起连接, 串口信号线
中使用“RS-232 标准”传输数据信号。由于RS-232 电平标准的信号不能直接被控制器直接识别,所以
这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL 标准”的电平信号,才能实现通讯。
电平标准
根据通讯使用的电平标准不同,串口通讯可分为TTL 标准及RS-232 标准,见表9-1。
表9-1 TTL 电平标准与RS232 电平标准
我们知道常见的电子电路中常使用TTL 的电平标准,理想状态下,使用5V 表示二进制逻辑1,
使用0V 表示逻辑0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V 表示逻辑1,+15V
表示逻辑0。使用RS232 与TTL 电平校准表示同一个信号时的对比见图8-2。
图9-2 RS-232 与TTL 电平标准下表示同一个信号
因为控制器一般使用TTL 电平标准,所以常常会使用MA3232 芯片对TTL 及RS-232 电平的信
第9 章.USART-串口通讯
第82 页SAIUR2016
号进行互相转换。
RS-232 信号线
在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫”)之间的
通讯,在这种通讯系统中,设备被分为数据终端设备DTE(计算机、路由)和数据通讯设备DCE(调制
调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。在旧式的台式计算机
中一般会有RS-232 标准的COM 口(也称DB9 接口),见图9-3。
图9-3 电脑主板上的COM 口及串口线
其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出
公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起
来。通讯时,串口线中传输的信号就是使用前面讲解的RS-232 标准调制的。在这种应用场合下,DB9
接口中的公头及母头的各个引脚的标准信号线接法见图9-4 及表9-2。
第9 章.USART-串口通讯
SAIUR201 6 第83页
图9-4 DB9 标准的公头及母头接法
表9-2 DB9 信号线说明(公头,为方便理解,可把DTE 理解为计算机,DCE 理解为调制调解
器)
上表中的是计算机端的DB9 公头标准接法,由于两个通讯设备之间的收发信号(RXD 与TXD)应
交叉相连,所以调制调解器端的DB9 母头的收发信号接法一般与公头的相反,两个设备之间连接时,
只要使用“直通型”的串口线连接起来即可,见图9-5。
第9 章.USART-串口通讯
第84 页SAIUR2016
图9-5 计算机与调制调解器的信号线连接
串口线中的RTS、CTS、DSR、DTR 及DCD 信号,使用逻辑1 表示信号有效,逻辑0 表示信
号无效。例如,当计算机端控制DTR 信号线表示为逻辑1 时,它是为了告知远端的调制调解器,本
机已准备好接收数据,0 则表示还没准备就绪。在目前的其它工业控制使用的串口通讯中,一般只使用
RXD、TXD 以及GND 三条信号线,直接传输数据信号,而RTS、CTS、DSR、DTR 及DCD 信号
都被裁剪掉了。
9.3.2. 协议层
串口通讯的数据包由发送设备通过自身的TXD 接口传输到接收设备的RXD 接口。在串口通讯
的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据
包格式要约定一致才能正常收发数据,其组成见图9-6。
图9-6 串口数据包的基本组成
波特率
本章中主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号(如前面讲解的DB9 接口中是没
有时钟信号的),所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码,
图21-6 中用虚线分开的每一格就是代表一个码元。常见的波特率为4800、9600、115200 等。
通讯的起始和停止信号
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0 的
数据位表示,而数据包的停止信号可由0.5、1、1.5 或2 个逻辑1 的数据位表示,只要双方约定一致
即可。
有效数据
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常
被约定为5、6、7 或8 位长。
第9 章.USART-串口通讯
SAIUR201 6 第85页
数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据
出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0 校
验(space)、1 校验(mark)以及无校验(noparity)。奇校验要求有效数据和校验位中“1”的个数为奇数,比如
一个8 位长的有效数据为:01101001,此时总共有4 个“1”,为达到奇校验效果,校验位为“1”,最后
传输的数据将是8 位的有效数据加上1 位的校验位总共9 位。偶校验与奇校验要求刚好相反,要求
帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为4 个,所以偶
校验位为“0”。0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
9.4. STM32 的USART 简介
通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter)是一个串行通信
设备,可以灵活地与外部设备进行全双工数据交换。有别于USART 还有一个UART(Universal
Asynchronous Receiver and Transmitter),它是在USART 基础上裁剪掉了同步通信功能,只有异步通信。
简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是
UART。串行通信一般是以帧格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、数据信息、
停止信息,可能还有校验信息。USART 就是对这些传输参数有具体规定,当然也不是只有唯一一个
参数值,很多参数值都可以自定义设置,只是增强它的兼容性。USART 满足外部设备对工业标准NRZ
异步串行数据格式的要求,并且使用了小数波特率发生器,可以提供多种波特率,使得它的应用更加
广泛。USART 支持同步单向通信和双工单线通信;还支持局域互连网络LIN、智能卡(SmartCard)协议
与lrDA(红外线数据协会) SIR ENDEC 规范。USART 支持使用DMA,可实现高速数据通信,有关
DMA 具体应用将在DMA 章节作具体讲解。USART 在STM32 应用最多莫过于“打印”程序信息,一
般在硬件设计时都会预留一个USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息
“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。
9.5. USART 功能框图
USART 的功能框图包含了USART 最核心内容,掌握了功能框图,对USART 就有一个整体的
把握,在编程时就思路就非常清晰。USART 功能框图见图9-7。
第9 章.USART-串口通讯
第86 页SAIUR2016
图9-7 USART 功能框图
功能引脚
TX:发送数据输出引脚。
RX:接收数据输入引脚。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能RTS 流控制,当
USART 接收器准备好接收新数据时就会将nRTS 变成低电平;当接收寄存器已满时,nRTS 将被
设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能CTS 流控制,发送器在发送下
一帧数据之前会检测nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前
数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
USART 引脚在STM32F103ZET6 芯片具体分布见表21-3。
表9-3 STM32F103VET6 芯片的USART 引脚
第9 章.USART-串口通讯
SAIUR201 6 第87页
STM32F103VET6 系统控制器有三个USART 和两个UART,其中USART1 和时钟来源于
APB2 总线时钟,其最大频率为72MHz,其他四个的时钟来源于APB1 总线时钟,其最大频率为
36MHz。UART 只是异步传输功能,所以没有SCLK、nCTS 和nRTS 功能引脚。
数据寄存器
USART 数据寄存器(USART_DR)只有低9 位有效,并且第9 位数据是否有效要取决于USART
控制寄存器1(USART_CR1)的M 位设置,当M 位为0 时表示8 位数据字长,当M 位为1 表示9
位数据字长,我们一般使用8 位数据字长。USART_DR 包含了已发送的数据或者接收到的数据。
USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写TDR,一个专门用于接收的可读
RDR。当进行发送操作时, 往USART_DR 写入数据会自动存储在TDR 内;当进行读取操作时,向
USART_DR 读取数据会自动提取RDR 数据。TDR 和RDR 都是介于系统总线和移位寄存器之间。
串行通信是一个位一个位传输的, 发送时把TDR 内容转移到发送移位寄存器,然后把移位寄存器数
据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到RDR。
USART 支持DMA 传输,可以实现高速数据传输,具体DMA 使用将在DMA 章节讲解。
控制器
USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。
使用USART 之前需要向USART_CR1 寄存器的UE 位置1 使能USART,UE 位用来开启供
给给串口的时钟。发送或者接收数据字长可选8 位或9 位,由USART_CR1 的M 位控制。发送器
当USART_CR1 寄存器的发送使能位TE 置1 时,启动数据发送,发送移位寄存器的数据会在TX
引脚输出,低位在前,高位在后。如果是同步模式SCLK 也输出时钟信号。一个字符帧发送需要三个
部分:起始位+数据帧+停止位。起始位是一个位周期的低电平,位周期就是每一位占用的时间;数据
帧就是我们要发送的8 位或9 位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电
平。停止位时间长短是可以通过USART 控制寄存器2(USART_CR2)的STOP[1:0]位控制,可选0.5
个、1 个、1.5 个和2 个停止位。默认使用1 个停止位。2 个停止位适用于正常USART 模式、单线
模式和调制解调器模式。0.5 个和1.5 个停止位用于智能卡模式。当选择8 位字长,使用1 个停止位
时,具体发送字符时序图见图9-8。
第9 章.USART-串口通讯
第88 页SAIUR2016
图9-8 字符发送时序图
当发送使能位TE 置1 之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),接下
来就可以往USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待USART 状
态寄存器(USART_SR) 的TC 位为1 , 表示数据传输完成, 如果USART_CR1 寄存器的TCIE
位置1,将产生中断。在发送数据时,编程的时候有几个比较重要的标志位我们来总结下。
接收器
如果将USART_CR1 寄存器的RE 位置1,使能USART 接收,使得接收器在RX 线开始搜
索起始位。在确定到起始位后就根据RX 线电平状态把数据存放在接收移位寄存器内。接收完成后
就把接收移位寄存器数据移到RDR 内,并把USART_SR 寄存器的RXNE 位置1,同时如果
USART_CR2 寄存器的RXNEIE 置1 的话可以产生中断。在接收数据时,编程的时候有几个比较
重要的标志位我们来总结下。
小数波特率生成
波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示, 单位为波
特。比特率指单位时间内传输的比特数,单位bit/s(bps)。对于USART 波特率与比特率相等,以后不
区分这两个概念。波特率越大,传输速率越快。USART 的发送器和接收器使用相同的波特率。计算公
式如下:
公式9-1 波特率计算
其中,fPLCK 为USART 时钟, USARTDIV 是一个存放在波特率寄存器(USART_BRR)的一个无
符号定点数。其中DIV_Mantissa[11:0] 位定义USARTDIV 的整数部分,DIV_Fraction[3:0]位定义
第9 章.USART-串口通讯
SAIUR201 6 第89页
USARTDIV 的小数部分。例如: DIV_Mantissa=24(0x18) , DIV_Fraction=10(0x0A) , 此时
USART_BRR 值为0x18A;那么USARTDIV 的小数位10/16=0.625;整数位24,最终USARTDIV 的
值为24.625。如果知道USARTDIV 值为27.68,那么DIV_Fraction=16*0.68=10.88,最接近的正整数
为11,所以DIV_Fraction[3:0]为0xB;DIV_Mantissa=整数(27.68)=27,即为0x1B。波特率的常用值
有2400、9600、19200、115200。下面以实例讲解如何设定寄存器值得到波特率的值。我们知道USART1
使用APB2 总线时钟,最高可达72MHz,其他USART 的最高频率为36MHz。我们选取USART1 作
为实例讲解,即fPLCK=72MHz。为得到115200bps 的波特率,此时:
解得USARTDIV=39.0625,可算得DIV_Fraction=0.0625*16=1=0x01,DIV_Mantissa=39=0x17,即
应该设置USART_BRR 的值为0x171。
校验控制
STM32F103 系列控制器USART 支持奇偶校验。当使用校验位时,串口传输的长度将是8 位的
数据帧加上1 位的校验位总共9 位,此时USART_CR1 寄存器的M 位需要设置为1,即9 数据位。
将USART_CR1 寄存器的PCE 位置1 就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动
了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时
如果出现奇偶校验位验证失败,会见USART_SR 寄存器的PE 位置1,并可以产生奇偶校验中断。
使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。
中断控制
USART 有多个中断请求事件,具体见表9-4。
表9-4 USART 中断请求
第9 章.USART-串口通讯
第90 页SAIUR2016
9.6. USART 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如USART_InitTypeDef,结构体成员用于
设置外设工作参数,并由外设初始化配置函数,比如USART_Init()调用,这些设定参数将会设置外设
相应的寄存器,达到配置外设工作环境的目的。初始化结构体和初始化库函数配合使用是标准库精髓所
在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在
stm32f10x_usart.h 文件中,初始化库函数定义在stm32f10x_usart.c 文件中,编程时我们可以结合这两
个文件内注释使用。
USART_BaudRate:波特率设置。一般设置为2400、9600、19200、115200。标准库函数会根据
设定值计算得到USARTDIV 值,从而设置USART_BRR 寄存器值。
USART_WordLength:数据帧字长,可选8 位或9 位。它设定USART_CR1 寄存器的M 位的
值。如果没有使能奇偶校验控制,一般使用8 数据位;如果使能了奇偶校验则一般设置为9 数据位。
USART_StopBits:停止位设置,可选0.5 个、1 个、1.5 个和2 个停止位,它设定USART_CR2
寄存器的STOP[1:0]位的值,一般我们选择1 个停止位。
USART_Parity : 奇偶校验控制选择, 可选USART_Parity_No( 无校验) 、
USART_Parity_Even( 偶校验) 以及USART_Parity_Odd( 奇校验) , 它设定USART_CR1
寄存器的PCE 位和PS 位的值。
USART_Mode:USART 模式选择,有USART_Mode_Rx 和USART_Mode_Tx,允许使用逻辑或
运算选择两个,它设定USART_CR1 寄存器的RE 位和TE 位。
USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效, 可选有⑴使能
RTS、⑵使能CTS、⑶同时使能RTS 和CTS、⑷不使能硬件流。当使用同步模式时需要配置SCLK 引
脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef 来设置,该结构体内
容也只有在同步模式才需要设置。
第9 章.USART-串口通讯
SAIUR201 6 第91页
USART_Clock : 同步模式下SCLK 引脚上时钟输出使能控制, 可选禁止时钟输出
(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要
开启时钟。它设定USART_CR2 寄存器的CLKEN 位的值。
USART_CPOL:同步模式下SCLK 引脚上输出时钟极性设置,可设置在空闲时SCLK 引脚为低
电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定USART_CR2 寄存器的CPOL 位的
值。
USART_CPHA:同步模式下SCLK 引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数
据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2 寄存器的CPHA 位
的值。USART_CPHA 与USART_CPOL 配合使用可以获得多种模式时钟关系。
USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在SCLK 引脚输出, 可以
是不输出脉冲(USART_LastBit_Disable) 、输出脉冲(USART_LastBit_Enable)。它设定
USART_CR2 寄存器的LBCL 位的值。
9.7. USART1 接发通信实验
USART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留USART 接口来
实现与其他模块或者控制器进行数据传输,比如GSM 模块,WIFI 模块、蓝牙模块等等。在硬件设
计时,注意还需要一根“共地线”。我们经常使用USART 来实现控制器与电脑之间的数据传输。这使
得我们调试程序非常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过
USART 发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再把
这些调试信息去除即可。我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发
送数据给控制器,控制器程序根据接收到的数据进行下一步工作。首先,我们来编写一个程序实现开
发板与电脑通信,在开发板上电时通过USART 发送一串字符串给电脑,然后开发板进入中断接收等
待状态,如果电脑有发送数据过来,开发板就会产生中断,我们在中断服务函数接收数据,并马上把
数据返回发送给电脑。
第9 章.USART-串口通讯
第92 页SAIUR2016
9.7.1. 硬件设计
为利用USART 实现开发板与电脑通信,需要用到一个USB 转USART 的IC,我们选择
CH340G 芯片来实现这个功能,CH340G 是一个USB 总线的转接芯片,实现USB 转USART、USB
转lrDA 红外或者USB 转打印机接口,我们使用其USB 转USART 功能。具体电路设计见图9-9。
我们将CH340G 的TXD 引脚与USART1 的RX 引脚连接,CH340G 的RXD 引脚与USART1 的
TX 引脚连接。CH340G 芯片集成在开发板上,其地线(GND)已与控制器的GND 连通。
图9-9 USB 转串口硬件设计
9.7.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参
考本章配套的工程。我们创建了两个文件:bsp_usart.c 和bsp _usart.h 文件用来存放USART 驱动程
序及相关宏定义。
9.7.3. 编程要点
1.使能RX 和TX 引脚GPIO 时钟和USART 时钟;
2.初始化GPIO,并将GPIO 复用到USART 上;
3.配置USART 参数;
4.配置中断控制器并使能USART 接收中断;
5.使能USART;
6.在USART 接收中断服务函数实现数据接收和发送。
第9 章.USART-串口通讯
SAIUR201 6 第93页
9.7.4. 代码分析
代码清单9-1 GPIO 和USART 宏定义
使用宏定义方便程序移植和升级。开发板中的CH340G 的收发引脚默认通过跳帽连接到
USART1,如果想使用其他串口,可以把CH340G 跟USART1 直接的连接跳帽拔掉,然后再把其他
串口的IO 用杜邦线接到CH340G 的收发引脚即可。这里我们使用USART1,设定波特率为115200,
选定USART 的GPIO 为PA9 和PA10。嵌套向量中断控制器NVIC 配置。
代码清单9-2 中断控制器NVIC 配置
第9 章.USART-串口通讯
第94 页SAIUR2016
代码清单9-3 USART 初始化配置
第9 章.USART-串口通讯
SAIUR201 6 第95页
使用GPIO_InitTypeDef 和USART_InitTypeDef 结构体定义一个GPIO 初始化变量以及一个
USART 初始化变量,这两个结构体内容我们之前已经有详细讲解。调用RCC_APB2PeriphClockCmd
函数开启GPIO 端口时钟, 使用GPIO 之前必须开启对应端口的时钟。使用
RCC_APB2PeriphClockCmd 函数开启USART 时钟。使用GPIO 之前都需要初始化配置它,并且还要
添加特殊设置,因为我们使用它作为外设的引脚,一般都有特殊功能。我们在初始化时需要把它的模式
设置为复用功能。这里把串口的Tx 引脚配置为复用推挽输出,Rx 引脚为浮空输入,数据完全由外部
输入决定。接下来,我们配置USART1 通信参数为:波特率115200,字长为8,1 个停止位,没有
校验位,不使用硬件流控制,收发一体工作模式,然后调用USART 初始化函数完成配置。程序用到
USART 接收中断,需要配置NVIC,这里调用NVIC_Configuration 函数完成配置。配置完NVIC 之
后调用USART_ITConfig 函数使能USART 接收中断。
最后调用USART_Cmd 函数使能USART,这个函数最终配置的是USART_CR1 的UE 位,具体
的作用是开启USART 的工作时钟,没有时钟那USART 这个外设自然就工作不了。
代码清单9-4 字符发送函数
Usart_SendByte 函数用来在指定USART 发送一个ASCLL 码值字符,它有两个形参,第一个为
USART,第二个为待发送的字符。它是通过调用库函数USART_SendData 来实现的,并且增加了等待
发送完成功能。通过使用USART_GetFlagStatus 函数来获取USART 事件标志来实现发送完成功能等
待,它接收两个参数,一个是USART,一个是事件标志。这里我们循环检测发送数据寄存器为空这个
标志,当跳出while 循环时说明发送数据寄存器为空这个事实。Usart_SendString 函数用来发送一个字
符串,它实际是调用Usart_SendByte 函数发送每个字符,直到遇到空字符才停止发送。最后使用循环
检测发送完成的事件标志TC 来实现保证数据发送完成后才退出函数。
第9 章.USART-串口通讯
第96 页SAIUR2016
USART 中断服务函数
代码清单21-5 USART 中断服务函数
这段代码是存放在stm32f10x_it.c 文件中的,该文件用来集中存放外设中断服务函数。当我们使
能了中断并且中断发生时就会执行这里的中断服务函数。我们在代码清单21-3 使能了USART 接收
中断,当USART 有接收到数据就会执行USART_IRQHandler 函数。USART_GetITStatus 函数与
USART_GetFlagStatus 函数类似用来获取标志位状态,但USART_GetITStatus 函数是专门用来获取中
断事件标志的,并返回该标志位状态。使用if 语句来判断是否是真的产生USART 数据接收这个中断
事件,如果是真的就使用USART 数据读取函数USART_ReceiveData 读取数据到指定存储区。然后
再调用USART 数据发送函数USART_SendData 把数据又发送给源设备,即PC 端的串口调试助手。
主函数
代码清单9-6 主函数
首先我们需要调用USART_Config 函数完成USART 初始化配置,包括GPIO 配置,USART 配
置,接收中断使能等等信息。接下来就可以调用字符发送函数把数据发送给串口调试助手了。最后主函
数什么都不做,只是静静地等待USART 接收中断的产生,并在中断服务函数把数据回传。
第9 章.USART-串口通讯
SAIUR201 6 第97页
9.7.5. 下载验证
保证开发板相关硬件连接正确,用USB 线连接开发板的USB 转串口跟电脑,在电脑端打开串口
调试助手并配置好相关参数:115200 8-N-1,把编译好的程序下载到开发板,此时串口调试助手即可收
到开发板发过来的数据。我们在串口调试助手发送区域输入任意字符,点击发送按钮,马上在串口调试
助手接收区即可看到相同的字符。
图9-10 实验现象
9.8. 课后练习
1.使用Usart 控制RGB 彩灯
第10 章.DMA 直接存储区访问
第98 页SAIUR2016
第10 章.DMA 直接存储区访问
10.1.课前预习
在书上找到答案。
1. 什么是DMA
10.2.概述
本章所讲内容:
(1)DMA 功能和配置
(2)使用DMA 读写存储器数据
10.3. DMA 简介
DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数
据,但是不需要占用CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。数据
传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM 或者是FLASH。DMA 控
制器包含了DMA1 和DMA2,其中DMA1 有7 个通道,DMA2 有5 个通道,这里的通道可以理
解为传输数据的一种管道。要注意的是DMA2 只存在于大容量的单片机中。
10.4. DMA 功能框图
DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌
握功能框图中的三部分内容即可,具体见图10-1:DMA 控制器的框图。
第10 章.DMA 直接存储区访问
SAIUR201 6 第99页
图10-1 DMA 框图
DMA 请求
如果外设要想通过DMA 来传输数据,必须先给DMA 控制器发送DMA 请求,DMA 收到请求
信号之后,控制器会给外设一个应答信号,当外设应答后且DMA 控制器收到应答信号之后,就会启
动DMA 的传输,直到传输完毕。DMA 有DMA1 和DMA2 两个控制器,DMA1 有7 个通道,DMA2
有5 个通道,不同的DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么
设置,具体见DMA 请求映像表。
图10-2 DMA1 各个通道的请求映像
第10 章.DMA 直接存储区访问
第100 页SAIUR2016
图10-3 DMA2 各个通道的请求映像
其中ADC3、SDIO 和TIM8 的DMA 请求只在大容量产品中存在,这个在具体项目时要注意。
通道
DMA 具有12 个独立可编程的通道,其中DMA1 有7 个通道,DMA2 有5 个通道,每个通道
对应不同的外设的DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,
不能同时接收多个。
仲裁器
当发生多个DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。
仲裁器管理DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设
置,有4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的DMA
通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0 高于
通道1。在大容量产品和互联型产品中,DMA1 控制器拥有高于DMA2 控制器的优先级。
10.5. DMA 数据配置
使用DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位
是什么,要传多少数据,是一次传输还是循环传输等等。
从哪里来到哪里去
我们知道DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。
具体的方向DMA_CCR 位4 DIR 配置:0 表示从外设到存储器,1 表示从存储器到外设。这里面涉
及到的外设地址由DMA_CPAR 配置,存储器地址由DMA_CMAR 配置。外设到存储器当我们使用
从外设到存储器传输时,以ADC 采集为例。DMA 外设寄存器的地址对应的就是ADC 数据寄存器的
地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储AD 采集的数据)的地址。方向我
第10 章.DMA 直接存储区访问
SAIUR201 6 第101页
们设置外设为源地址。存储器到外设当我们使用从存储器到外设传输时,以串口向电脑端发送数据为
例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定
义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目
标地址。存储器到存储器当我们使用从存储器到存储器传输时,以内部FLASH 向内部SRAM 复制数
据为例。DMA 外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH 当作一个外设来
看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH
的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要
把DMA_CCR 位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M 模式。
要传多少,单位是什么
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的
单位是什么。以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由
DMA_CNDTR 配置,这是一个32 位的寄存器,一次最多只能传输65535 个数据。要想数据传输正
确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8 位的,所以我们定义的要发送的
数据也必须是8 位。外设的数据宽度由DMA_CCR 的PSIZE[1:0]配置,可以是8/16/32 位,存储器
的数据宽度由DMA_CCR 的MSIZE[1:0]配置, 可以是8/16/32 位。
在DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置
两边数据指针的增量模式。外设的地址指针由DMA_CCRx 的PINC 配置,存储器的地址指针由
MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指
针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量
模式由实际情况决定。
什么时候传输完成
数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA 通道
在DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会
产生中断。有关各个标志位的详细描述请参考DMA 中断状态寄存器DMA_ISR 的详细描述。传输完
成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传
输的话,必须关断DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复
第一次传输时的配置循环传输,不断的重复。具体的DMA_CCR 寄存器的CIRC 循环模式位控制。
10.6. DMA 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx 为外设名称),结构体成
员用于设置外设工作参数,并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,
达到配置外设工作环境的目的。结构体xxx_InitTypeDef 和库函数xxx_Init 配合使用是标准库精髓所
第10 章.DMA 直接存储区访问
第102 页SAIUR2016
在, 理解了结构体xxx_InitTypeDef 每个成员意义基本上就可以对该外设运用自如。结构体
xxx_InitTypeDef 定义在stm32f10x_xxx.h(后面xxx 为外设名称)文件中,库函数xxx_Init 定义在
stm32f10x_xxx.c 文件中,编程时我们可以结合这两个文件内注释使用。
DMA_PeripheralBaseAddr:外设地址,设定DMA_CPAR 寄存器的值;一般设置为外设的数据
寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
DMA_Memory0BaseAddr:存储器地址,设定DMA_CMAR 寄存器值;一般设置为我们自定义
存储区的首地址。
DMA_DIR : 传输方向选择, 可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器
的DIR[1:0]位的值。这里并没有存储器到存储器的方向选择, 当使用存储器到存储器时,只需要把其
中一个存储器当作外设使用即可。
DMA_BufferSize:设定待传输数据数目,初始化设定DMA_CNDTR 寄存器的值。
DMA_PeripheralInc:如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它
设定DMA_CCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该
位。
DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设
定DMA_CCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能
存储器地址自动递增功能。
DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位)和字(32 位), 它设定
DMA_CCR 寄存器的PSIZE[1:0]位的值。
DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32 位),它设定
DMA_CCR 寄存器的MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置
为一致大小。
DMA_Mode : DMA 传输模式选择, 可选一次传输或者循环传输, 它设定DMA_CCR 寄存
第10 章.DMA 直接存储区访问
SAIUR201 6 第103页
器的CIRC 位的值。例程我们的ADC 采集是持续循环进行的,所以使用循环传输模式。
DMA_Priority:软件设置通道的优先级,有4 个可选优先级分别为非常高、高、中和低,它设
定DMA_CCR 寄存器的PL[1:0]位的值。DMA 通道优先级只有在多个DMA 通道同时使用时才有意
义,如果是单个通道,优先级可以随便设置。
DMA_M2M : 存储器到存储器模式, 使用存储器到存储器时用到, 设定DMA_CCR 的位14
MEN2MEN 即可启动存储器到存储器模式。
10.7. DMA 存储器到存储器模式实验
本章只讲解存储器到存储器和存储器到外设这两种模式,其他功能模式在其他章节使用到的时候再
讲。存储器到存储器模式可以实现数据在两个内存的快速拷贝。我们先定义一个静态的源数据,存放在
内部FLASH,然后使用DMA 传输把源数据拷贝到目标地址上(内部SRAM),最后对比源数据和
目标地址的数据,看看是否传输准确。
10.7.1. 硬件设计
DMA 存储器到存储器实验不需要其他硬件要求,只用到RGB 彩色灯用于指示程序状态。
10.7.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参
考本章配套的工程。这个实验代码比较简单,主要程序代码都在main.c 文件中。
10.7.3. 编程要点
1.使能DMA 时钟;
2.配置DMA 数据参数;
3.使能DMA,进行传输;
4.等待传输完成,并对源数据和目标地址数据进行比较。
第10 章.DMA 直接存储区访问
第104 页SAIUR2016
10.7.4. 代码分析
DMA 宏定义及相关变量定义
代码清单10-1 DMA 数据流和相关变量定义
使用宏定义设置外设配置方便程序修改和升级。存储器到存储器传输通道没有硬性规定,可以随意
选择。aSRC_Const_Buffer[BUFFER_SIZE]定义用来存放源数据,并且使用了const 关键字修饰,即常
量类型,使得变量是存储在内部flash 空间上。
第10 章.DMA 直接存储区访问
SAIUR201 6 第105页
DMA 数据配置
代码清单10-2 DMA 传输参数配置
使用DMA_InitTypeDef 结构体定义一个DMA 初始化变量,这个结构体内容我们之前已经有详细
讲解。调用RCC_AHBPeriphClockCmd 函数开启DMA 时钟,使用DMA 控制器之前必须开启对应
的时钟。源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏BUFFER_SIZE 决定,源
和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个DMA 通道,优先级随便
设置,最后调用DMA_Init 函数完成DMA 的初始化配置。
DMA_ClearFlag 函数用于清除DMA 标志位,代码用到传输完成标志位,使用之前先清除传输完
成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要1 个形参,即事件标志位,可选有传输完成
标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择传输完成标
志位,由宏DMA_FLAG_TC 定义。
DMA_Cmd 函数用于启动或者停止DMA 数据传输,它接收两个参数,第一个是DMA 通道,另
外一个是开启ENABLE 或者停止DISABLE。存储器数据对比
第10 章.DMA 直接存储区访问
第106 页SAIUR2016
代码清单10-3 源数据与目标地址数据对比
判断指定长度的两个数据源是否完全相等,如果完全相等返回1;只要其中一对数据不相等返回0。
它需要三个形参,前两个是两个数据源的地址,第三个是要比较数据长度。
主函数
代码清单10-4 存储器到存储器模式主函数
第10 章.DMA 直接存储区访问
SAIUR201 6 第107页
首先定义一个变量用来保存存储器数据比较结果。RGB 彩色灯用来指示程序进程,使用之前需要
初始化它,LED_GPIO_Config 定义在bsp_led.c 文件中。开始设置RGB 彩色灯为紫色,LED_PURPLE
是定义在bsp_led.h 文件的一个宏定义。Delay 函数只是一个简单的延时函数。调用DMA_Config 函
数完成DMA 数据流配置并启动DMA 数据传输。DMA_GetFlagStatus 函数获取DMA 事件标志位的
当前状态,这里获取DMA 数据传输完成这个标志位,使用循环持续等待直到该标志位被置位,即
DMA 传输完成这个事件发生,然后退出循环,运行之后程序。确定DMA 传输完成之后就可以调用
Buffercmp 函数比较源数据与DMA 传输后目标地址的数据是否一一对应。TransferStatus 保存比较结
果,如果为1 表示两个数据源一一对应相等说明DMA 传输成功;相反,如果为0 表示两个数据源
数据存在不等情况,说明DMA 传输出错。如果DMA 传输成功设置RGB 彩色灯为蓝色,如果DMA
传输出错设置RGB 彩色灯为红色。
10.7.5. 下载验证
确保开发板供电正常,编译程序并下载。观察RGB 彩色灯变化情况。正常情况下RGB 彩色灯先
为紫色,然后变成蓝色。如果DMA 传输出错才会为红色。
10.8.课后练习
1. 根据存储器至外设模式实验编写一个外设到存储器程序,实现USART 的DMA 接收请求功能,
在接收数据的同时LED 不断的亮灭,相当于两个任务在执行。
第11 章.TIM 基本定时器
第108 页SAIUR2016
第11 章. TIM 基本定时器
11.1.课前预习
在书上找到答案。
1. 什么是TIM 基本定时器?
11.2.概述
本章所讲内容:
(1)定时器的分类
(2)定时器功能框图
(3)使用基本定时器编写程序
11.3.定时器分类
STM32F1 系列中,除了互联型的产品,共有8 个定时器,分为基本定时器,通用定时器和高级
定时器。基本定时器TIM6 和TIM7 是一个16 位的只能向上计数的定时器,只能定时,没有外部
IO。通用定时器TIM2/3/4/5 是一个16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可
以输入捕捉,每个定时器有四个外部IO。高级定时器TIM1/8 是一个16 位的可以向上/下计数的定时
器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有8 个
外部IO。更加具体的分类详情见图11-1。
第11 章.TIM 基本定时器
SAIUR201 6 第109页
图11-1 定时器分类
11.4.基本定时器功能框图讲解
基本定时器的核心是时基,不仅基本定时器有,通用定时器和高级定时器也有。学习定时器时,我
们先从简单的基本定时器学起,到了后面的通用和高级定时器的学习中,我们直接跳过时基部分的讲解
即可。基本定时器的功能框图见图11-2。
图11-2 基本定时器功能框图
时钟源
定时器时钟TIMxCLK,即内部时钟CK_INT,经APB1 预分频器后分频提供,如果APB1 预分
频系数等于1,则频率不变,否则频率乘以2,库函数中APB1 预分频的系数是2,即PCLK1=36M,
所以定时器时钟TIMxCLK=36*2=72M。
计数器时钟
定时器时钟经过PSC 预分频器之后,即CK_CNT,用来驱动计数器计数。PSC 是一个16 位的
预分频器,可以对定时器时钟TIMxCLK 进行1~65536 之间的任何一个数进行分频。具体计算方式
第11 章.TIM 基本定时器
第110 页SAIUR2016
为:CK_CNT=TIMxCLK/(PSC+1)。
计数器
计数器CNT 是一个16 位的计数器,只能往上计数,最大计数值为65535。当计数达到自动重
装载寄存器的时候产生更新事件,并清零从头开始计数。
自动重装载寄存器
自动重装载寄存器ARR 是一个16 位的寄存器,这里面装着计数器能计数的最大数值。当计数
到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。
定时时间的计算
定时器的定时时间等于计数器的中断周期乘以中断的次数。计数器在CK_CNT 的驱动下,计一
个数的时间则是CK_CLK 的倒数,等于:1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于:1/
(CK_CLK * ARR)。如果在中断服务程序里面设置一个变量time,用来记录中断的次数, 那么就
可以计算出我们需要的定时时间等于: 1/CK_CLK * (ARR+1)*time。
11.5.定时器初始化结构体详解
在标准库函数头文件stm32f10x_tim.h 中对定时器外设建立了四个初始化结构体,基本定时器只用
到其中一个即TIM_TimeBaseInitTypeDef,具体的见代码清单11-1,其他三个我们在高级定时器章节讲
解。
代码清单11-1 定时器基本初始化结构体
TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定TIMx_PSC 寄
存器的值。可设置范围为0 至65535,实现1 至65536 分频。
TIM_CounterMode:定时器计数方式,可是在为向上计数、向下计数以及三种中心对齐模式。基
本定时器只能是向上计数,即TIMx_CNT 只能从0 开始递增,并且无需初始化。TIM_Period:定时
器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为0 至
65535。
TIM_ClockDivision:时钟分频,设置定时器时钟CK_INT 频率与数字滤波器采样时钟频率分频
第11 章.TIM 基本定时器
SAIUR201 6 第111页
比,基本定时器没有此功能,不用设置。
TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易
控制输出PWM 的个数。这里不用设置。虽然定时器基本初始化结构体有5 个成员,但对于基本定时
器只需设置其中两个就可以,想想使用基本定时器就是简单。
11.6.基本定时器定时实验
11.6.1. 硬件设计
本实验利用基本定时器TIM6/7 定时1s,1s 时间到LED 翻转一次。基本定时器是单片机内部
的资源,没有外部IO,不需要接外部电路,现只需要一个LED 即可。
11.6.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到, 完整的代码请参
考本章配套的工程。我们编写两个定时器驱动文件,bsp_TiMbase.h 和bsp_TiMbase.h,用来配置定时
器中断优先级和和初始化定时器。
11.6.3. 编程要点
1.开定时器时钟TIMx_CLK, x[6,7];
2.初始化时基初始化结构体;
3.使能TIMx, x[6,7] update 中断;
4.打开定时器;
5.编写中断服务程序
通用定时器和高级定时器的定时编程要点跟基本定时器差不多,只是还要再选择下计数器的计数模
式,是向上还是向下。因为基本定时器只能向上计数,且没有配置计数模式的寄存器,默认是向上。
11.6.4. 软件分析
基本定时器宏
第11 章.TIM 基本定时器
第112 页SAIUR2016
代码清单11-2 宏定义
基本定时器有TIM6 和TIM7,我们可以有选择的使用,为了提高代码的可移植性,我们把当需
要修改定时器时需要修改的代码定义成宏,默认使用的是定时器6,如果想修改成定时器7,只需要把
宏BASIC_TIM6 注释掉即可。
基本定时器配置
代码清单11-3 基本定时器模式配置
第11 章.TIM 基本定时器
SAIUR201 6 第113页
我们把定时器设置自动重装载寄存器ARR 的值为1000,设置时钟预分频器为71,则驱动计数器
的时钟:CK_CNT = CK_INT / (71+1)=1M,则计数器计数一次的时间等于:1/CK_CNT=1us,当计数器
计数到ARR 的值1000 时,产生一次中断,则中断一次的时间为:1/CK_CNT*ARR=1ms。在初始化
定时器的时候, 我们定义了一个结构体: TIM_TimeBaseInitTypeDef , TIM_TimeBaseInitTypeDef 结
构体里面有5 个成员,TIM6 和TIM7 的寄存器里面只有TIM_Prescaler 和TIM_Period,另外三个成
员基本定时器是没有的,所以使用TIM6 和TIM7 的时候只需初始化这两个成员即可, 另外三个成员
是通用定时器和高级定时器才有,具体说明如下:
其中TIM15/16/17 只存在与互联型产品中,在F1 大/中/小容量型号中没有。
定时器中断优先级配置
我们设置中断分组为0,主优先级为0,抢占优先级为3。
定时器中断服务程序
定时器中断一次的时间是1ms,我们定义一个全局变量time,每当进一次中断的时候,让time 来
记录进入中断的次数。如果我们想实现一个1s 的定时,我们只需要判断time 是否等于1000 即可,
1000 个1ms 就是1s。然后把time 清0,重新计数,以此循环往复。在中断服务程序的最后,要把
相应的中断标志位清除掉,切记。
第11 章.TIM 基本定时器
第114 页SAIUR2016
主函数
主函数做一些必须的初始化,然后在一个死循环中不断的判断time 的值,time 的值在定时器中
断改变,每加一次表示定时器过了1ms,当time 等于1000 时,1s 时间到,LED1 翻转一次,并把time
清0。
11.6.5. 下载验证
把编写好的程序下载到开发板,可以看到LED1 以1s 的频率闪烁一次。
11.7.课后练习
1.计算基本定时器一次最长定时时间,如果需要使用基本定时器产生100s 周期事件有什么办法实
现?
2.修改实验程序,在保使其每0.5s 翻转一次LED1 的同时在每10s 翻转LED2。
第12 章.TIM 高级定时器
SAIUR201 6 第115页
第12 章.TIM 高级定时器
12.1.课前预习
在书上找到答案。
1. 什么是TIM 高级定时器?
12.2.概述
本章所讲内容:
(1)高级定时器功能框图
(2)使用高级定时器编写程序
12.3.高级控制定时器
高级控制定时器(TIM1 和TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以实现
输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带
刹车(断路)功能,这些功能都是针对工业电机控制方面。这几个功能在本书不做详细的介绍,主要介绍
常用的输入捕获和输出比较功能。高级控制定时器时基单元包含一个16 位自动重装载寄存器ARR,
一个16 位的计数器CNT,可向上/下计数,一个16 位可编程预分频器PSC,预分频器时钟源有多种
可选,有内部的时钟、外部时钟。还有一个8 位的重复计数器RCR,这样最高可实现40 位的可编程
定时。STM32F103ZET6 的高级/通用定时器的IO 分配具体见表12-1。配套开发板因为IO 资源紧缺,
定时器的IO 很多已经复用它途,故下表中的IO 只有部分可用于定时器的实验。
第12 章.TIM 高级定时器
第116 页SAIUR2016
表12-1 高级控制和通用定时器通道引脚分布
12.4. 高级控制定时器功能框图
高级控制定时器功能框图包含了高级控制定时器最核心内容,掌握了功能框图,对高级控制定时器
就有一个整体的把握,在编程时思路就非常清晰,见,图中有些寄存器是带影子的,表示其有影子寄存
器。
图12-1 高级控制定时器功能框图
第12 章.TIM 高级定时器
SAIUR201 6 第117页
12.4.1. 时钟源
高级控制定时器有四个时钟源可选:
1.内部时钟源CK_INT
2.外部时钟模式1:外部输入引脚TIx(x=1,2,3,4)
3.外部时钟模式2:外部触发输入ETR
4.内部触发输入(ITRx)
5.内部时钟源(CK_INT)
内部时钟CK_INT 即来自于芯片内部,等于72M,一般情况下,我们都是使用内部时钟。当从模
式控制寄存器TIMx_SMCR 的SMS 位等于000 时,则使用内部时钟。
12.4.2. 外部时钟模式1
图12-2 外部时钟模式1 框图
①:时钟信号输入引脚
当使用外部时钟模式1 的时候,时钟信号来自于定时器的输入通道,总共有4 个,分别为
TI1/2/3/4,即TIMx_CH1/2/3/4。具体使用哪一路信号,由TIM_CCMRx 的位CCxS[1:0]配置,其中
CCMR1 控制TI1/2,CCMR2 控制TI3/4。
②:滤波器
如果来自外部的时钟信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对信号
重新采样,来达到降频或者去除高频干扰的目的,具体的由TIMx_CCMRx 的位ICxF[3:0]配置。
③:边沿检测
边沿检测的信号来自于滤波器的输出,在成为触发信号之前,需要进行边沿检测,决定是上升沿有
效还是下降沿有效,具体的由TIMx_CCER 的位CCxP 和CCxNP 配置。
第12 章.TIM 高级定时器
第118 页SAIUR2016
④:触发选择
当使用外部时钟模式1 时,触发源有两个,一个是滤波后的定时器输入1(TI1FP1)和滤波后的
定时器输入2(TI2FP2),具体的由TIMxSMCR 的位TS[2:0]配置。
⑤:从模式选择
选定了触发源信号后,最后我们需把信号连接到TRGI 引脚,让触发信号成为外部时钟模式1 的
输入,最终等于CK_PSC,然后驱动计数器CNT 计数。具体的配置TIMx_SMCR 的位SMS[2:0]为000
即可选择外部时钟模式1。
⑥:使能计数器
经过上面的5 个步骤之后,最后我们只需使能计数器开始计数,外部时钟模式1 的配置就算完成。
使能计数器由TIMx_CR1 的位CEN 配置。
12.4.3. 外部时钟模式2
图12-3 外部时钟模式2 框图
①:时钟信号输入引脚
当使用外部时钟模式2 的时候,时钟信号来自于定时器的特定输入通道TIMx_ETR,只有1 个。
②:外部触发极性
来自ETR 引脚输入的信号可以选择为上升沿或者下降沿有效,具体的由TIMx_SMCR 的位ETP
配置。
③:外部触发预分频器
由于ETRP 的信号的频率不能超过TIMx_CLK(72M)的1/4,当触发信号的频率很高的情况下,
就必须使用分频器来降频,具体的由TIMx_SMCR 的位ETPS[1:0]配置。
④:滤波器
如果ETRP 的信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对ETRP
信号重新采样,来达到降频或者去除高频干扰的目的。具体的由TIMx_SMCR 的位ETF[3:0]配置,其
中的fDTS 是由内部时钟CK_INT 分频得到,具体的由TIMx_CR1 的位CKD[1:0]配置。
第12 章.TIM 高级定时器
SAIUR201 6 第119页
⑤:从模式选择
经过滤波器滤波的信号连接到ETRF 引脚后,触发信号成为外部时钟模式2 的输入, 最终等于
CK_PSC,然后驱动计数器CNT 计数。具体的配置TIMx_SMCR 的位ECE 为1 即可选择外部时钟
模式2。
⑥:使能计数器
经过上面的5 个步骤之后,最后我们只需使能计数器开始计数,外部时钟模式2 的配置就算完成。
使能计数器由TIMx_CR1 的位CEN 配置。
12.4.4. 内部触发输入
内部触发输入是使用一个定时器作为另一个定时器的预分频器。硬件上高级控制定时器和通用定
时器在内部连接在一起,可以实现定时器同步或级联。主模式的定时器可以对从模式定时器执行复位、
启动、停止或提供时钟。
控制器
高级控制定时器控制器部分包括触发控制器、从模式控制器以及编码器接口。触发控制器用来针对
片内外设输出触发信号,比如为其它定时器提供时钟和触发DAC/ADC 转换。编码器接口专门针对编
码器计数而设计。从模式控制器可以控制计数器复位、启动、递增/递减、计数。有关控制器部分只需
熟练阅读寄存器描述即可。
时基单元
图12-4 高级定时器时基单元
高级控制定时器时基单元功能包括四个寄存器,分别是计数器寄存器(CNT)、预分频器寄存器
(PSC)、自动重载寄存器(ARR)和重复计数器寄存器(RCR)。其中重复计数器RCR 是高级定时器独有,
通用和基本定时器没有。前面三个寄存器都是16 位有效,TIMx_RCR 寄存器是8 位有效。
预分频器PSC
预分频器PSC ,有一个输入时钟CK_PSC 和一个输出时钟CK_CNT 。输入时钟CK_PSC 就是
上面时钟源的输出,输出CK_CNT 则用来驱动计数器CNT 计数。通过设置预分频器PSC 的值可以
第12 章.TIM 高级定时器
第120 页SAIUR2016
得到不同的CK_CNT,实际计算为:fCK_CNT 等于fCK_PSC/(PSC[15:0]+1),可以实现1 至65536 分
频。
计数器CNT
高级控制定时器的计数器有三种计数模式,分别为递增计数模式、递减计数模式和递增/递减(中心
对齐)计数模式。
1. 递增计数模式下,计数器从0 开始计数,每来一个CK_CNT 脉冲计数器就增加1,直到计数器
的值与自动重载寄存器ARR 值相等,然后计数器又从0 开始计数并生成计数器上溢事件,计
数器总是如此循环计数。如果禁用重复计数器,在计数器生成上溢事件就马上生成更新事件
(UEV);如果使能重复计数器,每生成一次上溢事件重复计数器内容就减1,直到重复计数器内
容为0 时才会生成更新事件。
2. 递减计数模式下,计数器从自动重载寄存器ARR 值开始计数,每来一个CK_CNT 脉冲计数器
就减1,直到计数器值为0,然后计数器又从自动重载寄存器ARR 值开始递减计数并生成计数
器下溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成下溢事件就马上生
成更新事件;如果使能重复计数器,每生成一次下溢事件重复计数器内容就减1,直到重复计数
器内容为0 时才会生成更新事件。
3. 中心对齐模式下,计数器从0 开始递增计数,直到计数值等于(ARR-1)值生成计数器上溢事件,
然后从ARR 值开始递减计数直到1 生成计数器下溢事件。然后又从0 开始计数,如此循环。
每次发生计数器上溢和下溢事件都会生成更新事件。
自动重载寄存器ARR
自动重载寄存器ARR 用来存放与计数器CNT 比较的值,如果两个值相等就递减重计数器。可以
通过TIMx_CR1 寄存器的ARPE 位控制自动重载影子寄存器功能,如果ARPE 位置1,自动重载影
子寄存器有效,只有在事件更新时才把TIMx_ARR 值赋给影子寄存器。如果ARPE 位为0,则修改
TIMx_ARR 值马上有效。
重复计数器RCR
在基本/通用定时器发生上/下溢事件时直接就生成更新事件,但对于高级控制定时器却不是这样,
高级控制定时器在硬件结构上多出了重复计数器,在定时器发生上溢或下溢事件是递减重复计数器的
值,只有当重复计数器为0 时才会生成更新事件。在发生N+1 个上溢或下溢事件(N 为RCR 的值)
时产生更新事件。
第12 章.TIM 高级定时器
SAIUR201 6 第121页
12.4.5. 输入捕获
图12-5 输入捕获功能框图
输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽
和测量PWM 输入信号的频率和占空比这两种。输入捕获的大概的原理就是,当捕获到信号的跳变沿
的时候,把计数器CNT 的值锁存到捕获寄存器CCR 中,把前后两次捕获到的CCR 寄存器中的值相
减,就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,
这个我们需要做额外的处理。
①输入通道
需要被测量的信号从定时器的外部引脚TIMx_CH1/2/3/4 进入,通常叫TI1/2/3/4,在后面的捕获
讲解中对于要被测量的信号我们都以TIx 为标准叫法。
②输入滤波器和边沿检测器
当输入的信号存在高频干扰的时候,我们需要对输入信号进行滤波,即进行重新采样, 根据采样
定律,采样的频率必须大于等于两倍的输入信号。比如输入的信号为1M,又存在高频的信号干扰,
那么此时就很有必要进行滤波,我们可以设置采样频率为2M,这样可以在保证采样到有效信号的基
础上把高于2M 的高频干扰信号过滤掉。滤波器的配置由CR1 寄存器的位CKD[1:0]和CCMR1/2 的
位ICxF[3:0]控制。从ICxF 位的描述可知,采样频率fSAMPLE 可以由fCK_INT 和fDTS 分频后的
时钟提供,其中是fCK_INT 内部时钟,fDTS 是fCK_INT 经过分频后得到的频率,分频因子由
CKD[1:0]决定,可以是不分频,2 分频或者是4 分频。边沿检测器用来设置信号在捕获的时候是什
么边沿有效,可以是上升沿,下降沿,或者是双边沿,具体的由CCER 寄存器的位CCxP 和CCxNP
决定。
③捕获通道
第12 章.TIM 高级定时器
第122 页SAIUR2016
捕获通道就是图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器CCR1/2/3/4,当发生捕获
的时候,计数器CNT 的值就会被锁存到捕获寄存器中。这里我们要搞清楚输入通道和捕获通道的区
别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道,一个输入通道的信号可以同时
输入给两个捕获通道。比如输入通道TI1 的信号经过滤波边沿检测器之后的TI1FP1 和TI1FP2 可以
进入到捕获通道IC1 和IC2,其实这就是我们后面要讲的PWM 输入捕获,只有一路输入信号(TI1)
却占用了两个捕获通道(IC1 和IC2)。当只需要测量输入信号的脉宽时候,用一个捕获通道即可。输
入通道和捕获通道的映射关系具体由寄存器CCMRx 的位CCxS[1:0]配置。
④预分频器
ICx 的输出信号会经过一个预分频器,用于决定发生多少个事件时进行一次捕获。具体的由寄存
器CCMRx 的位ICxPSC 配置,如果希望捕获信号的每一个边沿,则不分频。
⑤捕获寄存器
经过预分频器的信号ICxPS 是最终被捕获的信号,当发生捕获时(第一次),计数器CNT 的值
会被锁存到捕获寄存器CCR 中,还会产生CCxI 中断,相应的中断位CCxIF(在SR 寄存器中)会
被置位,通过软件或者读取CCR 中的值可以将CCxIF 清0。如果发生第二次捕获(即重复捕获:CCR
寄存器中已捕获到计数器值且CCxIF 标志已置1),则捕获溢出标志位CCxOF(在SR 寄存器中)
会被置位,CCxOF 只能通过软件清零。
12.4.6. 输出比较
图12-6 输出比较功能框图
第12 章.TIM 高级定时器
SAIUR201 6 第123页
输出比较就是通过定时器的外部引脚对外输出控制信号, 有冻结、将通道X(x=1,2,3,4)设置
为匹配时输出有效电平、将通道X 设置为匹配时输出无效电平、翻转、强制变为无效电平、强制变为
有效电平、PWM1 和PWM2 这八种模式,具体使用哪种模式由寄存器CCMRx 的位OCxM[2:0]配置。
其中PWM 模式是输出比较中的特例,使用的也最多。
①比较寄存器
当计数器CNT 的值跟比较寄存器CCR 的值相等的时候,输出参考信号OCxREF 的信号的极
性就会改变,其中OCxREF=1(高电平)称之为有效电平,OCxREF=0(低电平) 称之为无效电平,
并且会产生比较中断CCxI,相应的标志位CCxIF(SR 寄存器中)会置位。然后OCxREF 再经过一
系列的控制之后就成为真正的输出信号OCx/OCxN。
②死区发生器
在生成的参考波形OCxREF 的基础上,可以插入死区时间,用于生成两路互补的输出信号OCx
和OCxN,死区时间的大小具体由BDTR 寄存器的位DTG[7:0]配置。死区时间的大小必须根据与输
出信号相连接的器件及其特性来调整。下面我们简单举例说明下带死区的PWM 信号的应用,我们以
一个板桥驱动电路为例。
图12-7 半桥驱动电路
在这个半桥驱动电路中,Q1 导通,Q2 截止,此时我想让Q1 截止Q2 导通,肯定是要先让Q1 截
止一段时间之后,再等一段时间才让Q2 导通,那么这段等待的时间就称为死区时间,因为Q1 关闭
需要时间(由MOS 管的工艺决定)。如果Q1 关闭之后,马上打开Q2,那么此时一段时间内相当
于Q1 和Q2 都导通了,这样电路会短路。图12-8 是针对上面的半桥驱动电路而画的带死区插入的
PWM 信号,图中的死区时间要根据MOS 管的工艺来调节。
第12 章.TIM 高级定时器
第124 页SAIUR2016
图12-8 带死区插入的互补输出
图12-9 输出比较(通道1~3)的输出控制框图
③输出控制
在输出比较的输出控制中,参考信号OCxREF 在经过死区发生器之后会产生两路带死区的互补信
号OCx_DT 和OCxN_DT(通道1~3 才有互补信号,通道4 没有,其余跟通道1~3 一样),这两路
带死区的互补信号然后就进入输出控制电路,如果没有加入死区控制, 那么进入输出控制电路的信号
就直接是OCxREF。进入输出控制电路的信号会被分成两路,一路是原始信号,一路是被反向的信号,
具体的由寄存器CCER 的位CCxP 和CCxNP 控制。经过极性选择的信号是否由OCx 引脚输出到外
部引脚CHx/CHxN 则由寄存器CCER 的位CxE/CxNE 配置。如果加入了断路(刹车)功能,则断路
和死区寄存器BDTR 的MOE、OSSI 和OSSR 这三个位会共同影响输出的信号。
④输出引脚
输出比较的输出信号最终是通过定时器的外部IO 来输出的,分别为CH1/2/3/4,其中前面三个
通道还有互补的输出通道CH1/2/3N。更加详细的IO 说明还请查阅相关的数据手册。
第12 章.TIM 高级定时器
SAIUR201 6 第125页
12.4.7. 断路功能
断路功能就是电机控制的刹车功能,使能断路功能时,根据相关控制位状态修改输出信号电平。
在任何情况下,OCx 和OCxN 输出都不能同时为有效电平,这关系到电机控制常用的H 桥电路结构
原因。断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统(CSS)生成,也可以是外
部断路输入IO,两者是或运算关系。系统复位启动都默认关闭断路功能,将断路和死区寄存器
(TIMx_BDTR)的BKE 为置1,使能断路功能。可通过TIMx_BDTR 寄存器的BKP 位设置设置断路
输入引脚的有效电平, 设置为1 时输入BRK 为高电平有效,否则低电平有效。
发送断路时,将产生以下效果:
1. TIMx_BDTR 寄存器中主输出模式使能(MOE)位被清零,输出处于无效、空闲或复位状态;
2. 根据相关控制位状态控制输出通道引脚电平;当使能通道互补输出时,会根据情况自动控制输出
通道电平;
3. 将TIMx_SR 寄存器中的BIF 位置1,并可产生中断和DMA 传输请求。
4. 如果TIMx_BDTR 寄存器中的自动输出使能(AOE)位置1,则MOE 位会在发生下一个UEV
事件时自动再次置1。
12.5.输入捕获应用
输入捕获一般应用在两个方面,一个方面是脉冲跳变沿时间测量,另一方面是PWM 输入测量。
图12-10 脉宽/频率测量示意图
第12 章.TIM 高级定时器
第126 页SAIUR2016
12.5.1. 测量频率
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获寄存器
CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),
并把捕获寄存器中的值读取到value1 中。当出现第二次上升沿时,发生第二次捕获,计数器CNT 的
值会再次被锁存到捕获寄存器CCR 中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取
到value3 中,并清除捕获记录标志。利用value3 和value1 的差值我们就可以算出信号的周期(频率)。
12.5.2. 测量脉宽
当捕获通道TIx 上出现上升沿时,发生第一次捕获,计数器CNT 的值会被锁存到捕获寄存器
CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),
并把捕获寄存器中的值读取到value1 中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降
沿。当下降沿到来的时候,发生第二次捕获,计数器CNT 的值会再次被锁存到捕获寄存器CCR 中,
并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3 中,并清除捕获记录标志。然
后把捕获边沿设置为上升沿捕获。
在测量脉宽过程中需要来回的切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生
溢出,溢出的时候会产生更新中断,我们可以在中断里面对溢出进行记录处理。
12.6. PWM 输入模式
测量脉宽和频率还有一个更简便的方法就是使用PWM 输入模式,该模式是输入捕获的特例,只
能使用通道1 和通道2,通道3 和通道4 使用不了。与上面那种只使用一个捕获寄存器测量脉宽和
频率的方法相比,PWM 输入模式需要占用两个捕获寄存器。
图12-11 输入通道和捕获通道的关系映射图
第12 章.TIM 高级定时器
SAIUR201 6 第127页
当使用PWM 输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时
器在使用PWM 输入的时候最多只能使用两个输入通道(TIx)。我们以输入通道TI1 工作在PWM 输
入模式为例来讲解下具体的工作原理,其他通道以此类推即可。
PWM 信号由输入通道TI1 进入,因为是PWM 输入模式的缘故,信号会被分为两路, 一路是
TI1FP1,另外一路是TI2FP2。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占
空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的哪一路信号对应的就是周期,另一
路就是对应占空比。作为触发输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设
置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。一句话概括就
是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是PWM 输入的缘故,另一
路信号则由硬件配置,无需软件配置。当使用PWM 输入模式的时候必须将从模式控制器配置为复位
模式(配置寄存器SMCR 的位SMS[2:0]来实现),即当我们启动触发信号开始进行捕获的时候,同时
把计数器CNT 复位清零。下面我们以一个更加具体的时序图来分析下PWM 输入模式。
图12-12 PWM 输入模式时序
PWM 信号由输入通道TI1 进入,配置TI1FP1 为触发信号,上升沿捕获。当上升沿的时候IC1 和
IC2 同时捕获,计数器CNT 清零,到了下降沿的时候,IC2 捕获,此时计数器CNT 的值被锁存到捕
获寄存器CCR2 中,到了下一个上升沿的时候,IC1 捕获,计数器CNT 的值被锁存到捕获寄存器
CCR1 中。其中CCR2+1 测量的是脉宽,CCR1+1 测量的是周期。这里要注意的是CCR2 和CCR1
的值在计算占空比和频率的时候都必须加1,因为计数器是从0 开始计数的。从软件上来说,用PWM
输入模式测量脉宽和周期更容易,付出的代价是需要占用两个捕获寄存器。
第12 章.TIM 高级定时器
第128 页SAIUR2016
12.7.输出比较应用
输出比较模式总共有8 种,具体的由寄存器CCMRx 的位OCxM[2:0]配置。我们这里只讲解最
常用的PWM 模式,其他几种模式具体的看数据手册即可。
12.7.1. PWM 输出模式
PWM 输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器ARR 的
值决定,占空比由比较寄存器CCR 的值决定。PWM 模式分为两种,PWM1 和PWM2,总得来说是
差不多,就看你怎么用而已,具体的区别见表格12-1。
表格12-1 PWM1 与PWM2 模式的区别
下面我们以PWM1 模式来讲解,以计数器CNT 计数的方向不同还分为边沿对齐模式和中心对齐
模式。PWM 信号主要都是用来控制电机,一般的电机控制用的都是边沿对齐模式,FOC 电机一般用
中心对齐模式。我们这里只分析这两种模式在信号感官上(即信号波形)的区别,具体在电机控制中的
区别不做讨论,到了你真正需要使用的时候就会知道了。
12.7.2. PWM 边沿对齐模式
在递增计数模式下,计数器从0 计数到自动重载值( TIMx_ARR 寄存器的内容),然后重新从0
开始计数并生成计数器上溢事件
图12-13 PWM1 模式的边沿对齐波形
在边沿对齐模式下,计数器CNT 只工作在一种模式,递增或者递减模式。这里我们以CNT 工作
在递增模式为例,在中,ARR=8,CCR=4,CNT 从0 开始计数,当CNT<CCR 的值时, OCxREF 为
有效的高电平, 于此同时, 比较中断寄存器CCxIF 置位。当CCR=<CNT<=ARR 时,OCxREF 为
第12 章.TIM 高级定时器
SAIUR201 6 第129页
无效的低电平。然后CNT 又从0 开始计数并生成计数器上溢事件,以此循环往复。
12.7.3. PWM 中心对齐模式
图12-14 PWM1 模式的中心对齐波形
在中心对齐模式下,计数器CNT 是工作做递增/递减模式下。开始的时候,计数器CNT 从0 开
始计数到自动重载值减1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到1 并生
成计数器下溢事件。之后从0 开始重新计数。图12-14 是PWM1 模式的中心对齐波形,ARR=8,
CCR=4。第一阶段计数器CNT 工作在递增模式下,从0 开始计数,当CNT<CCR 的值时,OCxREF
为有效的高电平,当CCR=<CNT<<ARR 时,OCxREF 为无效的低电平。第二阶段计数器CNT 工作
在递减模式,从ARR 的值开始递减,当CNT>CCR 时,OCxREF 为无效的低电平,当CCR=>CNT>=1
时,OCxREF 为有效的高电平。
在波形图上我们把波形分为两个阶段,第一个阶段是计数器CNT 工作在递增模式的波形,这个
阶段我们又分为①和②两个阶段,第二个阶段是计数器CNT 工作在递减模式的波形,这个阶段我们
又分为③和④两个阶段。要说中心对齐模式下的波形有什么特征的话, 那就是①和③阶段的时间相等,
②和④阶段的时间相等。中心对齐模式又分为中心对齐模式1/2/3 三种,具体由寄存器CR1 位
CMS[1:0]配置。具体的区别就是比较中断中断标志位CCxIF 在何时置1:中心模式1 在CNT 递减
计数的时候置1,中心对齐模式2 在CNT 递增计数时置1,中心模式3 在CNT 递增和递减计数时
都置1。
12.8.定时器初始化结构体详解
在标准库函数头文件stm32f10x_tim.h 中对定时器外设建立了四个初始化结构体,分别为时基
初始化结构体TIM_TimeBaseInitTypeDef 、输出比较初始化结构体
TIM_OCInitTypeDef 、输入捕获初始化结构体TIM_ICInitTypeDef 和断路和死区初始化结构体
TIM_BDTRInitTypeDef , 高级控制定时器可以用到所有初始化结构体, 通用定时器不能使用
TIM_BDTRInitTypeDef 结构体,基本定时器只能使用时基结构体。接下来我们具体讲解下这四个结构
第12 章.TIM 高级定时器
第130 页SAIUR2016
体。
12.8.1. TIM_TimeBaseInitTypeDef
时基结构体TIM_TimeBaseInitTypeDef 用于定时器基础参数设置,与TIM_TimeBaseInit 函数配合
使用完成配置。
代码清单12-1 定时器基本初始化结构体
TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器计数时钟CK_CNT,它设定
PSC 寄存器的值。计算公式为:计数器时钟频率(fCK_CNT) 等于fCK_PSC / (PSC[15:0] + 1),可实现
1 至65536 分频。
TIM_CounterMode:定时器计数方式,可设置为向上计数、向下计数以及中心对齐。高级控制定时器
允许选择任意一种。
TIM_Period:定时器周期,实际就是设定自动重载寄存器ARR 的值,ARR 为要装载到实际自动重
载寄存器(即影子寄存器)的值,可设置范围为0 至65535。
TIM_ClockDivision:时钟分频,设置定时器时钟CK_INT 频率与死区发生器以及数字滤波器采样时
钟频率分频比。可以选择1、2、4 分频。
TIM_RepetitionCounter:重复计数器,只有8 位,只存在于高级定时器。
12.8.2. TIM_OCInitTypeDef
输出比较结构体TIM_OCInitTypeDef 用于输出比较模式,与TIM_OCxInit 函数配合使用完成指
定定时器输出通道初始化配置。高级控制定时器有四个定时器通道,使用时都必须单独设置。
代码清单12-2 定时器比较输出初始化结构体
TIM_OCMode:比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx 寄存器
第12 章.TIM 高级定时器
SAIUR201 6 第131页
OCxM[2:0]位的值。
TIM_OutputState:比较输出使能,决定最终的输出比较信号OCx 是否通过外部引脚输出。它设定
TIMx_CCER 寄存器CCxE/CCxNE 位的值。
TIM_OutputNState:比较互补输出使能,决定OCx 的互补信号OCxN 是否通过外部引脚输出。它设
定CCER 寄存器CCxNE 位的值。
TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器CCR 的值,决定脉冲宽度。可设置范围为0 至
65535。
TIM_OCPolarity:比较输出极性,可选OCx 为高电平有效或低电平有效。它决定着定时器通道有效
电平。它设定CCER 寄存器的CCxP 位的值。
TIM_OCNPolarity : 比较互补输出极性, 可选OCxN 为高电平有效或低电平有效。它设定
TIMx_CCER 寄存器的CCxNP 位的值。
TIM_OCIdleState:空闲状态时通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE
位为0)时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2 寄存器的OISx 位的值。
TIM_OCNIdleState:空闲状态时互补通道输出电平设置,可选输出1 或输出0,即在空闲状态
(BDTR_MOE 位为0)时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与
TIM_OCIdleState 相反。它设定是CR2 寄存器的OISxN 位的值。
12.8.3. TIM_ICInitTypeDef
输入捕获结构体TIM_ICInitTypeDef 用于输入捕获模式,与TIM_ICInit 函数配合使用完成定时器
输入通道初始化配置。如果使用PWM 输入模式需要与TIM_PWMIConfig 函数配合使用完成定时器
输入通道初始化配置。
代码清单12-3 定时器输入捕获初始化结构体
TIM_Channel:捕获通道ICx 选择,可选TIM_Channel_1、TIM_Channel_2、TIM_Channel_3 或
TIM_Channel_4 四个通道。它设定CCMRx 寄存器CCxS 位的值。
TIM_ICPolarity:输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。它设定
CCER 寄存器CCxP 位和CCxNP 位的值。
TIM_ICSelection : 输入通道选择, 捕获通道ICx 的信号可来自三个输入通道, 分别为
第12 章.TIM 高级定时器
第132 页SAIUR2016
TIM_ICSelection_DirectTI 、TIM_ICSelection_IndirectTI 或TIM_ICSelection_TRC , 具体的区别见图
33-15。如果是普通的输入捕获,4 个通道都可以使用,如果是PWM 输入则只能使用通道1 和通道
2。它设定CCRMx 寄存器的CCxS[1:0]位的值。
图12-15 输入通道与捕获通道IC 的映射图
TIM_ICPrescaler:输入捕获通道预分频器,可设置1、2、4、8 分频,它设定CCMRx 寄存器的
ICxPSC[1:0]位的值。如果需要捕获输入信号的每个有效边沿,则设置1 分频即可。IM_ICFilter:输
入捕获滤波器设置,可选设置0x0 至0x0F。它设定CCMRx 寄存器ICxF[3:0]位的值。一般我们不使
用滤波器,即设置为0。
12.8.4. TIM_BDTRInitTypeDef
断路和死区结构体TIM_BDTRInitTypeDef 用于断路和死区参数的设置,属于高级定时器专用,用
于配置断路时通道输出状态,以及死区时间。它与TIM_BDTRConfig 函数配置使用完成参数配置。这
个结构体的成员只对应BDTR 这个寄存器,有关成员的具体使用配置请参考手册BDTR 寄存器的详
细描述。
代码清单12-4 断路和死区初始化结构体
TIM_OSSRState:运行模式下的关闭状态选择,它设定BDTR 寄存器OSSR 位的值。
TIM_OSSIState:空闲模式下的关闭状态选择,它设定BDTR 寄存器OSSI 位的值。
TIM_LOCKLevel:锁定级别配置, BDTR 寄存器LOCK[1:0]位的值。
TIM_DeadTime:配置死区发生器,定义死区持续时间,可选设置范围为0x0 至0xFF。它设定BDTR
寄存器DTG[7:0]位的值。
第12 章.TIM 高级定时器
SAIUR201 6 第133页
TIM_Break:断路输入功能选择,可选使能或禁止。它设定BDTR 寄存器BKE 位的值。
TIM_BreakPolarity:断路输入通道BRK 极性选择,可选高电平有效或低电平有效。它设定BDTR 寄
存器BKP 位的值。
TIM_AutomaticOutput:自动输出使能,可选使能或禁止,它设定BDTR 寄存器AOE 位的值。
12.9. PWM 互补输出实验
输出比较模式比较多,这里我们以PWM 输出为例讲解,并通过示波器来观察波形。实验中不仅
在主输出通道输出波形,还在互补通道输出与主通道互补的的波形,并且添加了断路和死区功能。
12.9.1. 硬件设计
根据开发板引脚使用情况,并且参考表12-1 中定时器引脚信息,使用高级定时器TIM1 的通道1
及其互补通道作为本实验的波形输出通道,对应选择PA8 和PB13 引脚。将示波器的两个输入通道分
别与PA8 和PB13 引脚连接,用于观察波形,还有注意共地。为增加断路功能,需要用到TIM1_BKIN
引脚,这里选择PB12 引脚。程序我们设置该引脚为高电平有效,当BKIN 引脚被置高低电平的时候,
两路互补的PWM 输出就被停止,就好像是刹车一样。
12.9.2. 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参
考本章配套的工程。我们创建了两个文件: bsp_AdvanceTim.c 和bsp_AdvanceTim.h 文件用来存定时
器驱动程序及相关宏定义。
12.9.3. 编程要点
1. 定时器用到的GP IO 初始化
2. 定时器时基结构体TIM_TimeBaseInitTypeDef 初始化
3. 定时器输出比较结构体TIM_OCInitTypeDef 初始化
4. 定时器刹车和死区结构体TIM_BDTRInitTypeDef 初始化
第12 章.TIM 高级定时器
第134 页SAIUR2016
12.9.4. 软件分析
代码清单12-5 宏定义
使用宏定义非常方便程序升级、移植。有关每个宏的具体含义看程序注释即可。
代码清单12-6 定时器复用功能引脚初始化
第12 章.TIM 高级定时器
SAIUR201 6 第135页
ADVANCE_TIM_GPIO_Config()函数初始化了定时器用到的相关的GPIO,当使用不同的GPIO 的
时候,只需要修改头文件里面的宏定义即可,而不需要修改这个函数。
代码清单12-7 定时器模式配置
第12 章.TIM 高级定时器
第136 页SAIUR2016
ADVANCE_TIM_Mode_Config()函数中初始化了三个结构体,有关这三个结构体成员的具体含义可
参考“定时器初始化结构体详解”小节,剩下的程序参考注释阅读即可。如果需要修改PWM 的周期
和占空比, 修改头文件里面的ADVANCE_TIM_PERIOD ADVANCE_TIM_PSC 和
ADVANCE_TIM_PULSE 这三个宏即可。PWM 信号的频率的计算公司为: F =
TIM_CLK/{(ARR+1)*(PSC+1)},其中TIM_CLK 等于72MHZ,ARR 即自动重装载寄存器的值,对
应ADVANCE_TIM_PERIOD 这个宏,PSC 即计数器时钟的分频因子,对应ADVANCE_TIM_PSC 这
个宏。
代码清单12-8 main 函数
Main 函数很简单,调用了ADVANCE_TIM_Init() 函数, 该函数调用了
ADVANCE_TIM_GPIO_Config()和ADVANCE_TIM_Mode_Config()这两个函数完成了定时器GPIO 引
脚和工作模式的初始化,这时,相应的GPIO 引脚上就可以检测到互补输出的PWM 信号,而且带死
区时间,如果程序运行的过程中,BKIN 引脚被拉高的话,PWM 输出会被禁止,就好像是断路或者刹
车一样。
12.9.5. 下载验证
根据实验的硬件设计内容接好示波器输入通道和开发板引脚连接,,编译实验程序并下载到开发
板上,调整示波器到合适参数,在示波器显示屏和看到一路互补的带死区时间的PWM 波形,参考图
33-16。至于图中的信号有毛刺是因为信号的输出引脚还接了其他的芯片,受到了影响。
第12 章.TIM 高级定时器
SAIUR201 6 第137页
图12-16 PWM 互补带死区时间波形输出
BKIN 引脚接高电平时,PWM 输出被禁止,就好像是刹车一样,具体见图12-17。
图33-17 PWM 刹车输出
第13 章.I2C 通讯
第138 页SAIUR2016
第13 章.I2C 通讯
13.1.课前预习
在书上找到答案。
1. 什么是SDA?
2. 什么是数据有效协议?
13.2.概述
本章所讲内容:
(1)I2C通信协议
(2)使用I2C读写EEPROM
13.3. I2C 协议简介
I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps 公司开发的,由于它引脚少,硬件实现简单,
可扩展性强,不需要USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集
成电路(IC)间的通讯。在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核
层和片上外设;STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分
层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能
部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑, 统一收发双方的数据打包、
解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流, 协议层则规定我们用中文还是英文来
交流。下面我们分别对I2C 协议的物理层及协议层进行讲解。
第13 章.I2C 通讯
SAIUR201 6 第139页
13.3.1. I2C 物理层
I2C 通讯设备之间的常用连接方式见图13-1。
图13-1 常见的I2C 通讯系统
它的物理层有如下特点:
1. 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个I2C 通讯总线中,可连接多
个I2C 通讯设备,支持多个通讯主机及多个通讯从机。
2. 一个I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据
线即用来表示数据,时钟线用于数据收发同步。
3. 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
4. 总线通过上拉电阻接到电源。当I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出
高阻态时,由上拉电阻把总线拉成高电平。
5. 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
6. 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达
3.4Mbit/s,但目前大多I2C 设备尚不支持高速模式。
7. 连接到相同总线的IC 数量受到总线的最大电容400pF 限制。
第13 章.I2C 通讯
第140 页SAIUR2016
13.3.2. 协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
I2C 基本读写过程
先看看I2C 通讯过程的基本结构,它的通讯过程见图13-2、图13-3 及图13-4。
图13-2 主机写数据到从机
图13-3 主机由从机中读数据
图13-4 I2C 通讯复合格式
阴影部分数据由主机传输至从机S : 传输开始信号SLAVE_ADDRESS: 从机地址
空白部分数据由从机传输至主机R/W: 传输方向选择位,1 为读,0 为写A/ A : 应答(ACK)
或非应答(NACK)信号P : 停止传输信号
这些图表示的是主机和从机通讯时,SDA 线的数据包序列。
其中S 表示由主机的I2C 接口产生的传输起始信号(S),这时连接到I2C 总线上的所有从机都
会接收到这个信号。
起始信号产生后, 所有从机就开始等待主机紧接下来广播的从机地址信号(SLAVE_ADDRESS)。
在I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就
被选中了,没被选中的设备将会忽略之后的数据信号。根据I2C 协议,这个从机地址可以是7 位或10
第13 章.I2C 通讯
SAIUR201 6 第141页
位。
在地址位之后,是传输方向的选择位,该位为0 时,表示后面的数据传输方向是由主机传输至从
机,即主机向从机写数据。该位为1 时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号, 只有接收到
应答信号后,主机才能继续发送或接收数据。
写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址,接收到应答信号后,主
机开始正式向从机传输数据(DATA),数据包的大小为8 位,主机每发送完一个字节数据,都要等待
从机的应答信号(ACK),重复这个过程,可以向从机传输N 个数据,这个N 没有大小限制。当数据
传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应答信号后,从
机开始向主机返回数据(DATA),数据包大小也为8 位,从机每发送完一个数据,都会等待主机的应
答信号(ACK),重复这个过程,可以返回N 个数据,这个N 也没有大小限制。当主机希望停止接收
数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
读和写数据
除了基本的读写,I2C 通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信
号(S)。一般在第一次传输中,主机通过SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段
数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS 的区别);在第
二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是
读写的实际内容。
以上通讯流程中包含的各个信号分解如下:
13.3.3. 通讯的起始和停止信号
前文中提到的起始(S)和停止(P)信号是两种特殊的状态,见图24-5。当SCL 线是高电平时SDA
线从高电平向低电平切换,这个情况表示通讯的起始。当SCL 是高电平时SDA 线由低电平向高电平
切换,表示通讯的停止。起始和停止信号一般由主机产生。
第13 章.I2C 通讯
第142 页SAIUR2016
图13-5 起始和停止信号
数据有效性
I2C 使用SDA 信号线来传输数据,使用SCL 信号线进行数据同步。见图24-6。SDA 数据线在
SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候SDA 表示的数据有效,即此时
的SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL 为低电平时,SDA 的数据无效,
一般在这个时候SDA 进行电平切换,为下一次表示数据做好准备。
图13-6 数据有效性
每次数据传输都以字节为单位,每次传输的字节数不受限制。
地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过SDA 信号线发送设备地址
(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是7 位或10 位,实际中7 位的地址应
用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第8 位或
第11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。见图
12-7。
第13 章.I2C 通讯
SAIUR201 6 第143页
图13-7 设备地址(7 位)及数据传输方向
读数据方向时,主机会释放对SDA 信号线的控制,由从机控制SDA 信号线,主机接收信号,写
数据方向时,SDA 由主机控制,从机接收信号。
响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据
接收端时,当设备(无论主从机)接收到I2C 传输的一个字节数据或地址后, 若希望对方继续发送数据,
则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,
则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。见
图13-8。
图13-8 响应与非响应信号
传输时主机产生时钟,在第9 个时钟时,数据发送端会释放SDA 的控制权,由数据接收端控制SDA,
若SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
第13 章.I2C 通讯
第144 页SAIUR2016
13.4. STM32 的I2C 特性及架构
如果我们直接控制STM32 的两个GPIO 引脚,分别用作SCL 及SDA,按照上述信号的时序要
求,直接像控制LED 灯那样控制引脚的输出(若是接收数据时则读取SDA 电平),就可以实现I2C 通
讯。同样,假如我们按照USART 的要求去控制引脚,也能实现USART 通讯。所以只要遵守协议,
就是标准的通讯,不管您如何实现它,不管是ST 生产的控制器还是ATMEL 生产的存储器, 都能
按通讯标准交互。由于直接控制GPIO 引脚电平产生通讯时序时,需要由CPU 控制每个时刻的引脚
状态, 所以称之为“软件模拟协议”方式。相对地,还有“硬件协议”方式,STM32 的I2C 片上外设专
门负责实现I2C 通讯协议, 只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并
缓存起来,CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理
I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单。
13.4.1. STM32 的I2C 外设简介
STM32 的I2C 外设可用作通讯的主机及从机,支持100Kbit/s 和400Kbit/s 的速率,支7 位、
10 位设备地址,支持DMA 数据传输,并具有数据校验功能。它的I2C 外设还支SMBus2.0 协议,
SMBus 协议与I2C 类似,主要应用于笔记本电脑的电池管理中,本教程不展开,感兴趣的读者可参
考《SMBus20》文档了解。
第13 章.I2C 通讯
SAIUR201 6 第145页
13.4.2. STM32 的I2C 架构剖析
图13-9 I2C 架构图
通讯引脚
I2C 的所有硬件架构都是根据图中左侧SCL 线和SDA 线展开的(其中的SMBA 线用于SMBUS
的警告信号,I2C 通讯没有使用)。STM32 芯片有多个I2C 外设,它们的I2C 通讯信号引出到不同的
GPIO 引脚上,使用时必须配置到这些指定的引脚,见图13-1。关于GPIO 引脚的复用功能,以规格
书为准。
表13-1 STM32F10x 的I2C 引脚
第13 章.I2C 通讯
第146 页SAIUR2016
时钟控制逻辑
SCL 线的时钟信号,由I2C 接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时钟频率。
配置I2C 的CCR 寄存器可修改通讯速率相关的参数:
1. 可选择I2C 通讯的“标准/快速”模式,这两个模式分别I2C 对应100/400Kbit/s 的通讯速率。
2. 在快速模式下可选择SCL 时钟的占空比,可选Tlow/Thigh=2 或Tlow/Thigh=16/9 模式,我们知
道I2C 协议在SCL 高电平时对SDA 信号采样,SCL 低电平时SDA 准备下一个数据,修改
SCL 的高低电平比会影响数据采样,但其实这两个模式的比例差别并不大,若不是要求非常严格,
这里随便选就可以了。
3. CCR 寄存器中还有一个12 位的配置因子CCR,它与I2C 外设的输入时钟源共同作用,产生
SCL 时钟,STM32 的I2C 外设都挂载在APB1 总线上,使用APB1 的时钟源PCLK1,SCL 信
号线的输出时钟公式如下:
计算结果得出CCR 为30,向该寄存器位写入此值则可以控制IIC 的通讯速率为400KHz,其实
即使配置出来的SCL 时钟不完全等于标准的400KHz,IIC 通讯的正确性也不会受到影响,因为所有
数据通讯都是由SCL 协调的,只要它的时钟频率不远高于标准即可。
第13 章.I2C 通讯
SAIUR201 6 第147页
数据控制逻辑
I2C 的SDA 信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存
器(DR)、地址寄存器(OAR)、PEC 寄存器以及SDA 数据线。当向外发送数据的时候,数据移位寄存
器以“数据寄存器”为数据源,把数据一位一位地通过SDA 信号线发送出去;当从外部接收数据的时候,
数据移位寄存器把SDA 信号线采样到的数据一位一位地存储到“数据寄存器”中。若使能了数据校验,
接收到的数据会经过PCE 计算器运算,运算结果存储在“PEC 寄存器”中。当STM32 的I2C 工作在
从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与STM32 的自身的“I2C
地址寄存器”的值作比较,以便响应主机的寻址。STM32 的自身I2C 地址可通过修改“自身地址寄存器”
修改,支持同时使用两个I2C 设备地址,两个地址分别存储在OAR1 和OAR2 中。
整体控制逻辑
整体控制逻辑负责协调整个I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器
(CR1/CR2)”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR1 和
SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解I2C 的工作状态。除此之外,控制逻辑
还根据要求,负责控制产生I2C 中断信号、DMA 请求及各种I2C 的通讯信号(起始、停止、响应信
号等)。
13.4.3. 通讯过程
使用I2C 外设通讯时,在通讯的不同阶段它会对“状态寄存器(SR1 及SR2)”的不同数据位写入参
数,我们通过读取这些寄存器标志来了解通讯状态。
主发送器
见图13-10。图中的是“主发送器”流程,即作为I2C 通讯的主机端时,向外发送数据时的过程。
图13-10 主发送器通讯过程
主发送器发送流程及事件说明如下:
1. 控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对SR1 寄存器的“SB”位置1,
表示起始信号已经发送;
第13 章.I2C 通讯
第148 页SAIUR2016
2. 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”及“EV8”,这时SR1 寄
存器的“ADDR”位及“TXE”位被置1,ADDR 为1 表示地址已经发送,TXE 为1 表示数据寄存
器为空;
3. 以上步骤正常执行并对ADDR 位清零后,我们往I2C 的“数据寄存器DR”写入要发送的数据,
这时TXE 位会被重置0,表示数据寄存器非空,I2C 外设通过SDA 信号线一位位把数据发送出
去后,又会产生“EV8”事件,即TXE 位被置1,重复这个过程,就可以发送多个字节数据了;
4. 当我们发送数据完成后,控制I2C 设备产生一个停止信号(P),这个时候会产生EV8_2 事件,SR1
的TXE 位及BTF 位都被置1,表示通讯结束。假如我们使能了I2C 中断,以上所有事件产生
时,都会产生I2C 中断信号,进入同一个中断服务函数,到I2C 中断服务程序后,再通过检查
寄存器位来判断是哪一个事件。
主接收器
再来分析主接收器过程,即作为I2C 通讯的主机端时,从外部接收数据的过程,见图13-11。
图13-11 主接收器过程
主接收器接收流程及事件说明如下:
1. 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事件“EV5”,并会
对SR1 寄存器的“SB”位置1,表示起始信号已经发送;
2. 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”这时SR1 寄存器的
“ADDR”位被置1,表示地址已经发送。
3. 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件,
SR1 寄存器的RXNE 被置1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存
器清空,以便接收下一次数据。此时我们可以控制I2C 发送应答信号(ACK)或非应答信号(NACK),
若应答,则重复以上步骤接收数据,若非应答,则停止传输;
4. 发送非应答信号后,产生停止信号(P),结束传输。在发送和接收过程中,有的事件不只是标志了
我们上面提到的状态位,还可能同时标志主机状态之类的状态位,而且读了之后还需要清除标志
位,比较复杂。我们可使用STM32 标准库函数来直接检测这些事件的复合标志,降低编程难度。
第13 章.I2C 通讯
SAIUR201 6 第149页
13.5. I2C 初始化结构体详解
跟其它外设一样,STM32 标准库提供了I2C 初始化结构体及初始化函数来配置I2C 外设。初始
化结构体及函数定义在库文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中,编程时我们可以结合这两个文
件内的注释使用或参考库帮助文档。了解初始化结构体后我们就能对I2C 外设运用自如了,见代码清
单13-1。
代码清单13-1 I2C 初始化结构体
这些结构体成员说明如下,其中括号内的文字是对应参数在STM32 标准库中定义的宏:
1. 2C_ClockSpeed
本成员设置的是I2C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值经过运算后
把时钟因子写入到I2C 的时钟控制寄存器CCR。而我们写入的这个参数值不得高于400KHz。实际
上由于CCR 寄存器不能写入小数类型的时钟因子,影响到SCL 的实际频率可能会低于本成员设置
的参数值,这时除了通讯稍慢一点以外,不会对I2C 的标准通讯造成其它影响。
2. I2C_Mode
本成员是选择I2C 的使用方式, 有I2C 模式(I2C_Mode_I2C ) 和SMBus 主、从模式
(I2C_Mode_SMBusHost、I2C_Mode_SMBusDevice ) 。I2C 不需要在此处区分主从模式,直接设置
I2C_Mode_I2C 即可。
3. I2C_DutyCycle
本成员设置的是I2C 的SCL 线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平
时间为2:1 ( I2C_DutyCycle_2)和16:9 (I2C_DutyCycle_16_9)。其实这两个模式的比例差别并不大,
一般要求都不会如此严格,这里随便选就可以。
4. I2C_OwnAddress1
本成员配置的是STM32 的I2C 设备自己的地址,每个连接到I2C 总线上的设备都要有一个自
己的地址, 作为主机也不例外。地址可设置为7 位或10 位( 受下面I2C_AcknowledgeAddress 成员
决定),只要该地址是I2C 总线上唯一的即可。STM32 的I2C 外设可同时使用两个地址,即同时对两
第13 章.I2C 通讯
第150 页SAIUR2016
个地址作出响应,这个结构成员I2C_OwnAddress1 配置的是默认的、OAR1 寄存器存储的地址,若需
要设置第二个地址寄存器OAR2,可使用I2C_OwnAddress2Config 函数来配置,OAR2 不支持10 位
地址,只有7 位。
5. I2C_Ack_Enable
本成员是关于I2C 应答设置,设置为使能则可以发送响应信号。本实验配置为允许应答
(I2C_Ack_Enable),这是绝大多数遵循I2C 标准的设备的通讯要求,改为禁止应答(I2C_Ack_Disable)
往往会导致通讯错误。
6. I2C_AcknowledgeAddress
本成员选择I2C 的寻址模式是7 位还是10 位地址。这需要根据实际连接到I2C 总线上设备的
地址进行选择,这个成员的配置也影响到I2C_OwnAddress1 成员,只有这里设置成10 位模式时,
I2C_OwnAddress1 才支持10 位地址。配置完这些结构体成员值,调用库函数I2C_Init 即可把结构体
的配置写入到寄存器中。
13.6. I2C—读写EEPROM 实验
EEPROM 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时
候加载之。EEPOM 芯片最常用的通讯方式就是I2C 协议,本小节以EEPROM 的读写实验为大家讲
解STM32 的I2C 使用方法。实验中STM32 的I2C 外设采用主模式,分别用作主发送器和主接收
器,通过查询事件的方式来确保正常通讯。
13.6.1. 硬件设计
图13-12 EEPROM 硬件连接图
本实验板中的EEPROM 芯片(型号:AT24C02)的SCL 及SDA 引脚连接到了STM32 对应的
第13 章.I2C 通讯
SAIUR201 6 第151页
I2C 引脚中,结合上拉电阻,构成了I2C 通讯总线,它们通过I2C 总线交互。EEPROM 芯片的设备
地址一共有7 位,其中高4 位固定为:1010 b,低3 位则由A0/A1/A2 信号线的电平决定,见图
13-13,图中的R/W 是读写方向位,与地址无关。
图13-13 EEPROM 设备地址(摘自《AT24C02》规格书)
按照我们此处的连接,A0/A1/A2 均为0,所以EEPROM 的7 位设备地址是:101 0000b ,即
0x50。由于I2C 通讯时常常是地址跟读写方向连在一起构成一个8 位数,且当R/W 位为0 时,表
示写方向,所以加上7 位地址,其值为“0xA0”,常称该值为I2C 设备的“写地址”;当R/W 位为1 时,
表示读方向,加上7 位地址,其值为“0xA1”,常称该值为“读地址”。
EEPROM 芯片中还有一个WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当
引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。
13.6.2. 软件设计
为了使工程更加有条理,我们把读写EEPROM 相关的代码独立分开存储,方便以后移植。在“工
程模板”之上新建“bsp_i2c_ee.c”及“bsp_i2c_ee.h”文件,这些文件也可根据您的喜好命名,它们不属于
STM32 标准库的内容,是由我们自己根据应用需要编写的。
13.6.3. 编程要点
1. 配置通讯使用的目标引脚为开漏模式;
2. 使能I2C 外设的时钟;
3. 配置I2C 外设的模式、地址、速率等参数并使能I2C 外设;
4. 编写基本I2C 按字节收发的函数;
5. 编写读写EEPROM 存储内容的函数;
6. 编写测试程序,对读写数据进行校验。
第13 章.I2C 通讯
第152 页SAIUR2016
8.
13.6.4. 代码分析
我们把I2C 硬件相关的配置都以宏的形式定义到“bsp_i2c_ee.h”文件中,见代码清单13-2。
代码清单13-2 I2C 硬件配置相关的宏
以上代码根据硬件连接,把与EEPROM 通讯使用的I2C 号、引脚号都以宏封装起来,并且定
义了自身的I2C 地址及通讯速率,以便配置模式的时候使用。
利用上面的宏,编写I2C GPIO 引脚的初始化函数,见代码清单13-2。
代码清单13-3 I2C GPIO 初始化函数
开启相关的时钟并初始化GPIO 引脚,函数执行流程如下:
1. 使用GPIO_InitTypeDef 定义GPIO 初始化结构体变量,以便下面用于存储GPIO 配置;
2. 调用库函数RCC_APB1PeriphClockCmd(代码中为宏EEPROM_I2C_APBxClock_FUN) 使能
第13 章.I2C 通讯
SAIUR201 6 第153页
I2C 外设时钟, 调用RCC_APB2PeriphClockCmd ( 代码中为宏
EEPROM_I2C_GPIO_APBxClock_FUN)来使能I2C 引脚使用的GPIO 端口时钟,调用时我们使
用“|”操作同时配置两个引脚。
3. 向GPIO 初始化结构体赋值,把引脚初始化成复用开漏模式,要注意I2C 的引脚必须使用这种
模式。
4. 使用以上初始化结构体的配置,调用GPIO_Init 函数向寄存器写入参数,完成GPIO 的初始化。
以上只是配置了I2C 使用的引脚,还不算对I2C 模式的配置,见代码清单13-4。
第13 章.I2C 通讯
第154 页SAIUR2016
代码清单13-4 配置I2C 模式
熟悉STM32 I2C 结构的话,这段初始化程序就十分好理解,它把I2C 外设通讯时钟SCL 的低
/高电平比设置为2,使能响应功能,使用7 位地址I2C_OWN_ADDRESS7 以及速率配置为
I2C_Speed(前面在bsp_i2c_ee.h 定义的宏)。最后调用库函数I2C_Init 把这些配置写入寄存器,并调用
I2C_Cmd 函数使能外设。为方便调用,我们把I2C 的GPIO 及模式配置都用I2C_EE_Init 函数封装
起来。向EEPROM 写入一个字节的数据
初始化好I2C 外设后,就可以使用I2C 通讯,我们看看如何向EEPROM 写入一个字节的数据,
见代码清单13-5。
第13 章.I2C 通讯
SAIUR201 6 第155页
代码清单13-5 向EEPROM 写入一个字节的数据
第13 章.I2C 通讯
第156 页SAIUR2016
先来分析I2C_TIMEOUT_UserCallback 函数, 它的函数体里只调用了宏
EEPROM_ERROR,这个宏封装了printf 函数,方便使用串口向上位机打印调试信息,阅读代码时把
它当成printf 函数即可。在I2C 通讯的很多过程,都需要检测事件,当检测到某事件后才能继续下一
步的操作,但有时通讯错误或者I2C 总线被占用,我们不能无休止地等待下去,所以我们设定每个事
件检测都有等待的时间上限,若超过这个时间,我们就调用I2C_TIMEOUT_UserCallback 函数输出调
试信息(或可以自己加其它操作),并终止I2C 通讯。
了解了这个机制,再来分析I2C_EE_ByteWrite 函数,这个函数实现了前面讲的I2C
主发送器通讯流程:
1. 使用库函数I2C_GenerateSTART 产生I2C 起始信号,其中的EEPROM_I2C 宏是前面硬件定义
相关的I2C 编号;
2. 对I2CTimeout 变量赋值为宏I2CT_FLAG_TIMEOUT,这个I2CTimeout 变量在下面的while
循环中每次循环减1,该循环通过调用库函数I2C_CheckEvent 检测事件,若检测到事件,则进
入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测I2CT_FLAG_TIMEOUT 次
都还没等待到事件则认为通讯失败,调用前面的I2C_TIMEOUT_UserCallback 输出调试信息,并
退出通讯;
3. 调用库函数I2C_Send7bitAddress 发送EEPROM 的设备地址,并把数据传输方向设置为
I2C_Direction_Transmitter(即发送方向),这个数据传输方向就是通过设置I2C 通讯中紧跟地址后面
的R/W 位实现的。发送地址后以同样的方式检测EV6 标志;
4. 调用库函数I2C_SendData 向EEPROM 发送要写入的内部地址, 该地址是I2C_EE_ByteWrite
函数的输入参数,发送完毕后等待EV8 事件。要注意这个内部地址跟上面的EEPROM 地址不
一样,上面的是指I2C 总线设备的独立地址,而此处的内部地址是指EEPROM 内数据组织的
地址,也可理解为EEPROM 内存的地址或I2C 设备的寄存器地址;
5. 调用库函数I2C_SendData 向EEPROM 发送要写入的数据,该数据是I2C_EE_ByteWrite 函
数的输入参数,发送完毕后等待EV8 事件;
6. 一个I2C 通讯过程完毕,调用I2C_GenerateSTOP 发送停止信号。在这个通讯过程中,STM32 实
际上通过I2C 向EEPROM 发送了两个数据,但为何第一个数据被解释为EEPROM 的内存地
址?这是由EEPROM 的自己定义的单字节写入时序,见图13-14。
第13 章.I2C 通讯
SAIUR201 6 第157页
图13-14 EEPROM 单字节写入时序(摘自《AT24C02》规格书)
EEPROM 的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写
入的数据内容。所以我们需要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的
功能。
多字节写入及状态等待
单字节写入通讯结束后,EEPROM 芯片会根据这个通讯结果擦写该内存地址的内容, 这需要一
段时间,所以我们在多次写入数据时,要先等待EEPROM 内部擦写完毕。多个数据写入过程见代码
清单13-6。
代码清单13-6 多字节写入
这段代码比较简单,直接使用for 循环调用前面定义的I2C_EE_ByteWrite 函数一个字节一个字节
地向EEPROM 发送要写入的数据。在每次数据写入通讯前调用了I2C_EE_WaitEepromStandbyState
函数等待EEPROM 内部擦写完毕,该函数的定义见代码清单13-7。
第13 章.I2C 通讯
第158 页SAIUR2016
代码清单13-7 等待EEPROM 处于准备状态
函数主要实现是向EEPROM 发送它设备地址, 检测EEPROM 的响应, 若EEPROM 接收到地
址后返回应答信号,则表示EEPROM 已经准备好,可以开始下一次通讯。函数中检测响应是通过读
取STM32 的SR1 寄存器的ADDR 位及AF 位来实现的,当I2C 设备响应了地址的时候,ADDR 会
置1,若应答失败,AF 位会置1。
EEPROM 的页写入
在以上的数据通讯中,每写入一个数据都需要向EEPROM 发送写入的地址,我们希望向连续地
址写入多个数据的时候,只要告诉EEPROM 第一个内存地址address1,后面的数据按次序写入到
address2、address3… 这样可以节省通讯的时间,加快速度。为应对这种需求,EEPROM 定义了一种
页写入时序,见图13-15。
图13-15 EEPROM 页写入时序(摘自《AT24C02》规格书)
根据页写入时序,第一个数据被解释为要写入的内存地址address1,后续可连续发送n 个数据,
这些数据会依次写入到内存中。其中AT24C02 型号的芯片页写入时序最多可以一次发送8 个数据(即
n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输16 个数据。EEPROM 的页
写入代码实现见代码清单13-8。
第13 章.I2C 通讯
SAIUR201 6 第159页
代码清单13-8 EEPROM 的页写入
这段页写入函数主体跟单字节写入函数是一样的,只是它在发送数据的时候,使用for 循环控制
发送多个数据,发送完多个数据后才产生I2C 停止信号,只要每次传输的数据小于等于EEPROM 时
第13 章.I2C 通讯
第160 页SAIUR2016
序规定的页大小,就能正常传输。快速写入多字节利用EEPROM 的页写入方式,可以改进前面的“多
字节写入”函数,加快传输速度,见代码清单13-9。
代码清单13-9 快速写入多字节函数
第13 章.I2C 通讯
SAIUR201 6 第161页
很多读者觉得这段代码的运算很复杂,看不懂,其实它的主旨就是对输入的数据进行分页( 本型
号芯片每页8 个字节) , 见表24-2 。通过“ 整除” 计算要写入的数据NumByteToWrite 能写满多
少“完整的页”,计算得的值存储在NumOfPage 中,但有时数据不是刚好能写满完整页的,会多一点
出来,通过“求余”计算得出“不满一页的数据个数”就存储在NumOfSingle 中。计算后通过按页传输
NumOfPage 次整页数据及最后的NumOfSing 个数据,使用页传输,比之前的单个字节数据传输要快
很多。除了基本的分页传输,还要考虑首地址的问题,见表24-3。若首地址不是刚好对齐到页的首地
址,会需要一个count 值,用于存储从该首地址开始写满该地址所在的页,还能写多少个数据。实际
传输时,先把这部分count 个数据先写入,填满该页,然后把剩余的数据(NumByteToWrite-count),再
重复上述求出NumOPage 及NumOfSingle 的过程,按页传输到EEPROM。
1. 若writeAddress=16,计算得Addr=16%8= 0 ,count=8-0= 8;
2. 同时,若NumOfPage=22,计算得NumOfPage=22/8= 2,NumOfSingle=22%8= 6。
3. 数据传输情况如表13-2
第13 章.I2C 通讯
第162 页SAIUR2016
表13-2 首地址对齐到页时的情况
4. 若writeAddress=17,计算得Addr=17%8= 1,count=8-1= 7;
5. 同时,若NumByteToWrite =22,
6. 先把count 去掉,特殊处理,计算得新的NumByteToWrite =22-7= 1,
7. 计算得NumOfPage=15/8= 1,NumOfSingle=15%8= 7。
8. 数据传输情况如表13-3
表13-3 首地址未对齐到页时的情况
最后,强调一下,EEPROM 支持的页写入只是一种加速的I2C 的传输时序,实际上并不要求每
次都以页为单位进行读写,EEPROM 是支持随机访问的(直接读写任意一个地址),如前面的单个字节
写入。在某些存储器,如NAND FLASH,它是必须按照Block 写入的,例如每个Block 为512 或
4096 字节,数据写入的最小单位是Block,写入前都需要擦除整个Block;NOR FLASH 则是写入前
必须以Sector/Block 为单位擦除,然后才可以按字节写入。而我们的EEPROM 数据写入和擦除的最
小单位是“字节”而不是“页”,数据写入前不需要擦除整页。
EEPROM 读取数据
EEPROM 读取数据是一个复合的I2C 时序,它实际上包含一个写过程和一个读过程,见图
13-16。
第13 章.I2C 通讯
SAIUR201 6 第163页
图13-16 EEPROM 数据读取时序
读时序的第一个通讯过程中,使用I2C 发送设备地址寻址(写方向),接着发送要读取的“内存地
址”;第二个通讯过程中,再次使用I2C 发送设备地址寻址,但这个时候的数据方向是读方向;在这
个过程之后,EEPROM 会向主机返回从“内存地址”开始的数据,一个字节一个字节地传输,只要主机
的响应为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”,并以“停止信
号”结束通讯,作为从机的EEPROM 也会停止传输。实现代码见代码清单13-10。
代码清单13-10 从EEPROM 读取数据
第13 章.I2C 通讯
第164 页SAIUR2016
第13 章.I2C 通讯
SAIUR201 6 第165页
这段中的写过程跟前面的写字节函数类似, 而读过程中接收数据时, 需要使用库函数
I2C_ReceiveData 来读取。响应信号则通过库函数I2C_AcknowledgeConfig 来发送,DISABLE
时为非响应信号,ENABLE 为响应信号。
main 文件
EEPROM 读写测试函数
完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程序,见代码清单13-11。
代码清单13-11 EEPROM 读写测试函数
第13 章.I2C 通讯
第166 页SAIUR2016
代码中先填充一个数组,数组的内容为1,2,3 至N,接着把这个数组的内容写入到EEPROM 中,
写入时可以采用单字节写入的方式或页写入的方式。写入完毕后再从EEPROM 的地址中读取数据,把
读取得到的与写入的数据进行校验,若一致说明读写正常, 否则读写过程有问题或者EEPROM 芯片
不正常。其中代码用到的EEPROM_INFO 跟EEPROM_ERROR 宏类似,都是对printf 函数的封装,
使用和阅读代码时把它直接当成printf 函数就好。具体的宏定义在“bsp_i2c_ee.h 文件中”,在以后的代
码我们常常会用类似的宏来输出调试信息。
main 函数
最后编写main 函数,函数中初始化串口、I2C 外设,然后调用上面的I2C_Test 函数进行读写测
试,见代码清单13-12。
第13 章.I2C 通讯
SAIUR201 6 第167页
代码清单13-12 main 函数
13.6.5. 下载验证
用USB 线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程
序下载到开发板。在串口调试助手可看到EEPROM 测试的调试信息。
图13-17 EEPROM 测试成功
第13 章.I2C 通讯
第168 页SAIUR2016
13.7.课后练习
1. 在EEPROM 测试程序中,分别使用单字节写入及页写入函数写入数据,对比它们消耗的时间。
2. 尝试使用EEPROM 存储int 整型变量,float 型浮点变量,编写程序写入数据,并读出校验。
3. 尝试把I2C 通讯引脚的模式改成非开漏模式,测试是否还能正常通讯,为什么?
4. 查看“bsp_i2c_ee.h”文件中EEPROM_ERROR、EEPROM_INFO、EEPROM_DEBUG 宏,解释为何
要使用这样的宏输出调试信息,而不直接使用printf 函数。
第14 章.SPI 通讯
SAIUR201 6 第169页
第14 章.SPI 通讯
14.1.课前预习
在书上找到答案。
1. SPI和I2C 有什么区别?
14.2.概述
本章所讲内容:
(1)SPI 通信协议
(2)使用I2C读写串行FLASH
14.3. SPI 协议简介
SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是
一种高速全双工的通信总线。它被广泛地使用在ADC、LCD 等设备与MCU 间,要求通讯速率较高
的场合。学习本章时,可与I2C 章节对比阅读,体会两种通讯总线的差异以及EEPROM 存储器FLASH
存储器的区别。下面我们分别对SPI 协议的物理层及协议层进行讲解。
14.3.1. SPI 物理层
SPI 通讯设备之间的常用连接方式见图14-1。
第14 章.SPI 通讯
第170 页SAIUR2016
图14-1 常见的SPI 通讯系统
SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK、MOSI、MISO,片选线为SS ,它们的
作用介绍如下:
1. S S ( Slave Select):从设备选择信号线,常称为片选信号线,也称为NSS、CS,以下用NSS 表示。
当有多个SPI 从设备与SPI 主机相连时,设备的其它信号线SCK、MOSI 及MISO 同时并联到
相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3 条总线;而每个从设备都有独立
的这一条NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号
线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI 协议中没
有设备地址,它使用NSS 信号线来寻址,当主机要选择从设备时,把该从设备的NSS 信号线设
置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI 通讯。
所以SPI 通讯以NSS 线置低电平为开始信号,以NSS 线被拉高作为结束信号。
2. SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,
不同的设备支持的最高时钟频率不一样,如STM32 的SPI 时钟频率最大为fpclk/2,两个设备之
间通讯时,通讯速率受限于低速设备。
3. MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输
出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
4. MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,
从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
第14 章.SPI 通讯
SAIUR201 6 第171页
14.3.2. 协议层
与I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。
SPI 基本通讯过程
先看看SPI 通讯的通讯时序,见图14-2。
图14-2 SPI 通讯时序
这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而MISO 的信号由从机
产生,主机通过该信号线读取从机的数据。MOSI 与MISO 的信号只在NSS 为低电平的时候才有效,
在SCK 的每个时钟周期MOSI 和MISO 传输一位数据。
以上通讯流程中包含的各个信号分解如下:
通讯的起始和停止信号
在图14-2 中的标号①处,NSS 信号线由高变低,是SPI 通讯的起始信号。NSS 是每个从机各自
独占的信号线,当从机在自己的NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主
机通讯。在图中的标号⑥处,NSS 信号由低变高,是SPI 通讯的停止信号,表示本次通讯结束,从机
的选中状态被取消。
数据有效性
SPI 使用MOSI 及MISO 信号线来传输数据,使用SCK 信号线进行数据同步。MOSI 及MISO
数据线在SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先
行或LSB 先行并没有作硬性规定,但要保证两个SPI 通讯设备之间使用同样的协定,一般都会采用
图14-2 中的MSB 先行模式。
观察图中的②③④⑤标号处,MOSI 及MISO 的数据在SCK 的上升沿期间变化输出,在SCK 的
下降沿时被采样。即在SCK 的下降沿时刻,MOSI 及MISO 的数据有效,高电平时表示数据“1”,
为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及MISO 为下一次表示数据做准备。SPI 每
次数据传输可以8 位或16 位为单位,每次传输的单位数不受限制。
第14 章.SPI 通讯
第172 页SAIUR2016
CPOL/CPHA 及通讯模式
上面讲述的图14-2 中的时序只是SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们
的主要区别是总线空闲时SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性
CPOL”和“时钟相位CPHA”的概念。时钟极性CPOL 是指SPI 通讯设备处于空闲状态时,SCK 信号
线的电平信号(即SPI 通讯开始前、NSS 线为高电平时SCK 的状态)。CPOL=0 时, SCK 在空闲状
态时为低电平,CPOL=1 时,则相反。时钟相位CPHA 是指数据的采样的时刻,当CPHA=0 时,MOSI
或MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。当CPHA=1 时,数据线在SCK
的“偶数边沿”采样。见图14-3 及图14-4。
图14-3 CPHA=0 时的SPI 通讯模式
我们来分析这个CPHA=0 的时序图。首先,根据SCK 在空闲状态时的电平,分为两种情况。SCK
信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。无论CPOL=0 还是=1,因
为我们配置的时钟相位CPHA=0,在图中可以看到,采样时刻都是在SCK 的奇数边沿。注意当
CPOL=0 的时候,时钟的奇数边沿是上升沿,而CPOL=1 的时候,时钟的奇数边沿是下降沿。所以SPI
的采样时刻不是由上升/下降沿决定的。MOSI 和MISO 数据线的有效信号在SCK 的奇数边沿保持不
变,数据信号将在SCK 奇数边沿时被采样,在非采样时刻,MOSI 和MISO 的有效信号才发生切换。
类似地,当CPHA=1 时,不受CPOL 的影响,数据信号在SCK 的偶数边沿被采样,见图14-4。
第14 章.SPI 通讯
SAIUR201 6 第173页
图14-4 CPHA=1 时的SPI 通讯模式
由CPOL 及CPHA 的不同状态,SPI 分成了四种模式,见表25-1,主机与从机需要工作在相同
的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。
表14-1 SPI 的四种模式
14.4. STM32 的SPI 特性及架构
与I2C 外设一样,STM32 芯片也集成了专门用于SPI 协议通讯的外设。
14.4.1. STM32 的SPI 外设简介
STM32 的SPI 外设可用作通讯的主机及从机,支持最高的SCK 时钟频率为fpclk/2(STM32F103
型号的芯片默认fpclk1 为72MHz,fpclk2 为36MHz),完全支持SPI 协议的4 种模式,数据帧长度
可设置为8 位或16 位,可设置数据MSB 先行或LSB 先行。它还支持双线全双工(前面小节说明的
SPI 模式CPOL CPHA 空闲时SCK 时钟采样时刻
0 0 0 低电平奇数边沿
1 0 1 低电平偶数边沿
2 1 0 高电平奇数边沿
3 1 1 高电平偶数边沿
第14 章.SPI 通讯
第174 页SAIUR2016
都是这种模式)、双线单向以及单线模式。其中双线单向模式可以同时使用MOSI 及MISO 数据线向
一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到
影响。我们只讲解双线全双工模式。
14.4.2. TM32 的SPI 架构剖析
图14-5 SPI 架构图
通讯引脚
SPI 的所有硬件架构都从图14-5 中左侧MOSI、MISO、SCK 及NSS 线展开的。STM32 芯片有
多个SPI 外设,它们的SPI 通讯信号引出到不同的GPIO 引脚上,使用时必须配置到这些指定的引
脚,见表14-2。
表14-2 STM32F10x 的SPI 引脚
其中SPI1 是APB2 上的设备,最高通信速率达36Mbtis/s,SPI2、SPI3 是APB1 上的设备,最
高通信速率为18Mbits/s。除了通讯速率,在其它功能上没有差异。其中SPI3 用到了下载接口的引脚,
这几个引脚默认功能是下载,第二功能才是IO 口,如果想使用SPI3 接口,则程序上必须先禁用掉这
第14 章.SPI 通讯
SAIUR201 6 第175页
几个IO 口的下载功能。一般在资源不是十分紧张的情况下,这几个IO 口是专门用于下载和调试程
序,不会复用为SPI3。
时钟控制逻辑
SCK 线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk
时钟的分频因子,对fpclk 的分频结果就是SCK 引脚的输出时钟频率,计算方法见表14-3。
表14-3 BR 位对fpclk 的分频
其中的fpclk 频率是指SPI 所在的APB 总线频率,APB1 为fpclk1,APB2 为fpckl2。通过配
置“控制寄存器CR”的“CPOL 位”及“CPHA”位可以把SPI 设置成前面分析的4 种SPI 模式。
数据控制逻辑
SPI 的MOSI 及MISO 都连接到数据移位寄存器上,数据移位寄存器的数据来源及目标接收、
发送缓冲区以及MISO、MOSI 线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,
把数据一位一位地通过数据线发送出去;当从外部接收数据的时候, 数据移位寄存器把数据线采样到
的数据一位一位地存储到“接收缓冲区”中。通过写SPI 的“数据寄存器DR”把数据填充到发送F 缓冲
区中,通讯读“数据寄存器DR”,可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存
器CR1”的“DFF 位”配置成8 位及16 位模式;配置“LSBFIRST 位”可选择MSB 先行还是LSB 先
行。
整体控制逻辑
整体控制逻辑负责协调整个SPI 外设,控制逻辑的工作模式根据我们配置的“控制寄存器
(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI 模式、波特率、LSB 先行、主从模式、
单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读
取状态寄存器相关的寄存器位,就可以了解SPI 的工作状态了。除此之外,控制逻辑还根据要求,负
责控制产生SPI 中断信号、DMA 请求及控制NSS 信号线。实际应用中,我们一般不使用STM32 SPI
外设的标准NSS 信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯
起始和停止信号。
14.4.3. 通讯过程
STM32 使用SPI 外设通讯时,在通讯的不同阶段它会对“状态寄存器SR”的不同数据位写入参
数,我们通过读取这些寄存器标志来了解通讯状态。图14-6 中的是“主模式”流程,即STM32 作为SPI
通讯的主机端时的数据收发过程。
第14 章.SPI 通讯
第176 页SAIUR2016
图14-6 主发送器通讯过程
主模式收发流程及事件说明如下:
1. 控制NSS 信号线,产生起始信号(图中没有画出);
2. 把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;
3. 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则把
数据一位一位地存储进接收缓冲区中;
4. 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE 标志位”会被置1,表示传输完一帧,发
送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE 标志位”会被置1,表示传输完一帧,
接收缓冲区非空;
5. 等待到“TXE 标志位”为1 时,若还要继续发送数据,则再次往“数据寄存器DR” 写入数据即可;
等待到“RXNE 标志位”为1 时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。
假如我们使能了TXE 或RXNE 中断,TXE 或RXNE 置1 时会产生SPI 中断信号,进入同
一个中断服务函数,到SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行
处理。也可以使用DMA 方式来收发“数据寄存器DR”中的数据。
第14 章.SPI 通讯
SAIUR201 6 第177页
14.5. SPI 初始化结构体详解
跟其它外设一样,STM32 标准库提供了SPI 初始化结构体及初始化函数来配置SPI 外设。初始
化结构体及函数定义在库文件“stm32f10x_spi.h”及“stm32f10x_spi.c”中,编程时我们可以结合这两个文
件内的注释使用或参考库帮助文档。了解初始化结构体后我们就能对SPI 外设运用自如了,见代码清
单14-1。
代码清单14-1 SPI 初始化结构体
这些结构体成员说明如下,其中括号内的文字是对应参数在STM32 标准库中定义的宏:
1. SPI_Direction
本成员设置SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接
收(SPI_Direction_2Lines_RxOnly) , 单线只接收(SPI_Direction_1Line_Rx) 、单线只发送模式
(SPI_Direction_1Line_Tx)。
2. SPI_Mode
本成员设置SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式
的最大区别为SPI 的SCK 信号线的时序,SCK 的时序是由通讯中的主机产生的。若被配置为从机
模式,STM32 的SPI 外设将接受外来的SCK 信号。
3. SPI_DataSize
本成员可以选择SPI 通讯的数据帧大小是为8 位(SPI_DataSize_8b) 还是16 位
(SPI_DataSize_16b)。
4. SPI_CPOL 和SPI_CPHA
这两个成员配置SPI 的时钟极性CPOL 和时钟相位CPHA,这两个配置影响到SPI 的通讯模
式,关于CPOL 和CPHA 的说明参考前面“通讯模式”小节。时钟极性CPOL 成员,可设置为高电平
(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位CPHA 则可以设置为SPI_CPHA_1Edge(在
第14 章.SPI 通讯
第178 页SAIUR2016
SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在SCK 的偶数边沿采集数据) 。
5. SPI_NSS
本成员配置NSS 引脚的使用模式, 可以选择为硬件模式(SPI_NSS_Hard ) 与软件模式
(SPI_NSS_Soft ),在硬件模式中的SPI 片选信号由SPI 硬件自动产生,而软件模式则需要我们亲自把
相应的GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
6. SPI_BaudRatePrescaler
本成员设置波特率分频因子,分频后的时钟即为SPI 的SCK 信号线的时钟频率。这个成员参数
可设置为fpclk 的2、4、6、8、16、32、64、128、256 分频。
7. SPI_FirstBit
所有串行的通讯协议都会有MSB 先行(高位数据在前)还是LSB 先行(低位数据在前)的问题,而
STM32 的SPI 模块可以通过这个结构体成员,对这个特性编程控制。
8. SPI_CRCPolynomial
这是SPI 的CRC 校验中的多项式,若我们使用CRC 校验时,就使用这个成员的参数(多项式),
来计算CRC 的值。配置完这些结构体成员后,我们要调用SPI_Init 函数把这些参数写入到寄存器中,
实现SPI 的初始化,然后调用SPI_Cmd 来使能SPI 外设。
14.6. SPI—读写串行FLASH 实验
FLSAH 存储器又称闪存,它与EEPROM 都是掉电后数据不丢失的存储器,但FLASH 存储器容
量普遍大于EEPROM,现在基本取代了它的地位。我们生活中常用的U 盘、SD 卡、SSD 固态硬盘
以及我们STM32 芯片内部用于存储程序的设备,都是FLASH 类型的存储器。在存储控制上,最主
要的区别是FLASH 芯片只能一大片一大片地擦写,而在“I2C 章节”中我们了解到EEPROM 可以单个
字节擦写。本小节以一种使用SPI 通讯的串行FLASH 存储芯片的读写实验为大家讲解STM32 的
SPI 使用方法。实验中STM32 的SPI 外设采用主模式,通过查询事件的方式来确保正常通讯。
第14 章.SPI 通讯
SAIUR201 6 第179页
14.6.1. 硬件设计
图14-7 SPI 串行FLASH 硬件连接图
本实验板中的FLASH 芯片(型号:W25Q64)是一种使用SPI 通讯协议的NOR FLASH 存储器,
它的CS/CLK/DIO/DO 引脚分别连接到了STM32 对应的SPI 引脚
NSS/SCK/MOSI/MISO 上,其中STM32 的NSS 引脚虽然是其片上SPI 外设的硬件引脚,但实际上
后面的程序只是把它当成一个普通的GPIO,使用软件的方式控制NSS 信号,所以在SPI 的硬件设
计中,NSS 可以随便选择普通的GPIO,不必纠结于选择硬件NSS 信号。FLASH 芯片中还有WP 和
HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,
不使用写保护功能。HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出
高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。关于FLASH 芯片
的更多信息,可参考其数据手册《W25Q64》来了解。若您使用的实验板FLASH 的型号或控制引脚不
一样,只需根据我们的工程修改即可,程序的控制原理相同。
14.6.2. 软件设计
为了使工程更加有条理,我们把读写FLASH 相关的代码独立分开存储,方便以后移植。在“工程
模板”之上新建“bsp_spi_flash.c”及“bsp_spi_ flash.h”文件,这些文件也可根据您的喜好命名,它们不属于
STM32 标准库的内容,是由我们自己根据应用需要编写的。
第14 章.SPI 通讯
第180 页SAIUR2016
14.6.3. 编程要点
1. 初始化通讯使用的目标引脚及端口时钟;
2. 使能SPI 外设的时钟;
3. 配置SPI 外设的模式、地址、速率等参数并使能SPI 外设;
4. 编写基本SPI 按字节收发的函数;
5. 编写对FLASH 擦除及读写操作的的函数;
6. 编写测试程序,对读写数据进行校验。
14.6.4. 代码分析
我们把SPI 硬件相关的配置都以宏的形式定义到“bsp_spi_ flash.h”文件中,见代码清单14-2。
代码清单14-2 SPI 硬件配置相关的宏
以上代码根据硬件连接,把与FLASH 通讯使用的SPI 号、GPIO 等都以宏封装起来,并且定义
了控制CS(NSS)引脚输出电平的宏,以便配置产生起始和停止信号时使用。初始化SPI 的GPIO 利用
上面的宏,编写SPI 的初始化函数,见代码清单14-3。
第14 章.SPI 通讯
SAIUR201 6 第181页
代码清单14-3 SPI 的初始化函数(GPIO 初始化部分)
与所有使用到GPIO 的外设一样,都要先把使用到的GPIO 引脚模式初始化,配置好复用功能。
GPIO 初始化流程如下:
1. 使用GPIO_InitTypeDef 定义GPIO 初始化结构体变量,以便下面用于存储GPIO 配置;
2. 调用库函数RCC_APB2PeriphClockCmd 来使能SPI 引脚使用的GPIO 端口时钟。
3. 向GPIO 初始化结构体赋值,把SCK/MOSI/MISO 引脚初始化成复用推挽模式。而CS(NSS)引
脚由于使用软件控制,我们把它配置为普通的推挽输出模式。
4. 使用以上初始化结构体的配置,调用GPIO_Init 函数向寄存器写入参数,完成GPIO 的初始化。
配置SPI 的模式
以上只是配置了SPI 使用的引脚,对SPI 外设模式的配置。在配置STM32 的SPI 模式前,我
第14 章.SPI 通讯
第182 页SAIUR2016
们要先了解从机端的SPI 模式。根据FLASH 芯片的说明,它支持SPI 模式0 及模式3,支持双线
全双工,使用MSB 先行模式,支持最高通讯时钟为104MHz,数据帧长度为8 位。我们要把STM32
的SPI 外设中的这些参数配置一致。见代码清单14-4。
代码清单14-4 配置SPI 模式
这段代码中,把STM32 的SPI 外设配置为主机端,双线全双工模式,数据帧长度为8 位,使用
SPI 模式3(CPOL=1,CPHA=1),NSS 引脚由软件控制以及MSB 先行模式。代码中把SPI 的时钟频
率配置成了4 分频,实际上可以配置成2 分频以提高通讯速率,读者可亲自尝试一下。最后一个成员
为CRC 计算式,由于我们与FLASH 芯片通讯不需要CRC 校验,并没有使能SPI 的CRC 功能,
这时CRC 计算式的成员值是无效的。赋值结束后调用库函数SPI_Init 把这些配置写入寄存器,并调
用SPI_Cmd 函数使能外设。
使用SPI 发送和接收一个字节的数据
初始化好SPI 外设后,就可以使用SPI 通讯了,复杂的数据通讯都是由单个字节数据收发组成
的,我们看看它的代码实现,见代码清单14-5。
第14 章.SPI 通讯
SAIUR201 6 第183页
代码清单14-5 使用SPI 发送和接收一个字节的数据
SPI_FLASH_SendByte 发送单字节函数中包含了等待事件的超时处理,这部分原理跟I2C 中的一
样,在此不再赘述。SPI_FLASH_SendByte 函数实现了前面讲解的“SPI 通讯过程”:
1. 本函数中不包含SPI 起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始
和停止信号的操作;
2. 对SPITimeout 变量赋值为宏SPIT_FLAG_TIMEOUT。这个SPITimeout 变量在下面的while 循
环中每次循环减1,该循环通过调用库函数SPI_I2S_GetFlagStatus 检测事件,若检测到事件,则
进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测SPIT_FLAG_TIMEOUT
次都还没等待到事件则认为通讯失败, 调用的SPI_TIMEOUT_UserCallback 输出调试信息,并退
出通讯;
3. 通过检测TXE 标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数
据已经发送完毕;
第14 章.SPI 通讯
第184 页SAIUR2016
4. 等待至发送缓冲区为空后,调用库函数SPI_I2S_SendData 把要发送的数据“byte” 写入到SPI 的
数据寄存器DR,写入SPI 数据寄存器的数据会存储到发送缓冲区,SPI 外设发送出去;
5. 写入完毕后等待RXNE 事件,即接收缓冲区非空事件。由于SPI 双线全双工模式下MOSI 与
MISO 数据传输是同步的(请对比“SPI 通讯过程”阅读),当接收缓冲区非空时,表示上面的数据
发送完毕,且接收缓冲区也收到新的数据;
6. 等待至接收缓冲区非空时,通过调用库函数SPI_I2S_ReceiveData 读取SPI 的数据寄存器DR,
就可以获取接收缓冲区中的新数据了。代码中使用关键字“return” 把接收到的这个数据作为
SPI_FLASH_SendByte 函数的返回值,所以我们可以看到在下面定义的SPI 接收数据函数
SPI_FLASH_ReadByte,它只是简单地调用了SPI_FLASH_SendByte 函数发送数据“Dummy_Byte”,
然后获取其返回值(因为不关注发送的数据,所以此时的输入参数“Dummy_Byte”可以为任意值)。
可以这样做的原因是SPI 的接收过程和发送过程实质是一样的,收发同步进行,关键在于我们
的上层应用中,关注的是发送还是接收的数据。
控制FLASH 的指令
搞定SPI 的基本收发单元后,还需要了解如何对FLASH 芯片进行读写。FLASH 芯片自定义了
很多指令,我们通过控制STM32 利用SPI 总线向FLASH 芯片发送指令,FLASH 芯片收到后就会
执行相应的操作。而这些指令,对主机端(STM32)来说,只是它遵守最基本的SPI 通讯协议发送出的
数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。见表14-4。
第14 章.SPI 通讯
SAIUR201 6 第185页
表14-4 FLASH 常用芯片指令表
该表中的第一列为指令名,第二列为指令编码,第三至第N 列的具体内容根据指令的不同而有不
同的含义。其中带括号的字节参数,方向为FLASH 向主机传输,即命令响应,不带括号的则为主机
向FLASH 传输。表中“A0~A23” 指FLASH 芯片内部存储器组织的地址; “M0~M7” 为厂商号
(MANUFACTURER ID);“ID0-ID15”为FLASH 芯片的ID;“dummy”指该处可为任意数据;“D0~D7”
为FLASH 内部存储矩阵的内容。在FLSAH 芯片内部,存储有固定的厂商编号(M7-M0)和不同类型
FLASH 芯片独有的编号(ID15-ID0),见表14-5。
第14 章.SPI 通讯
第186 页SAIUR2016
表14-5 FLASH 数据手册的设备ID 说明
通过指令表中的读ID 指令“JEDEC ID”可以获取这两个编号,该指令编码为“9F h”,其中“9F h”是
指16 进制数“9F” (相当于C 语言中的0x9F)。紧跟指令编码的三个字节分别为FLASH 芯片输出的
“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)” 。此处我们以该指令为例,配合其指令时序图进行讲解,见图
14-8。
图14-8 FLASH 读ID 指令“JEDEC ID”的时序
主机首先通过MOSI 线向FLASH 芯片发送第一个字节数据为“9F h”,当FLASH 芯片收到该数
据后,它会解读成主机向它发送了“JEDEC 指令”,然后它就作出该命令的响应:通过MISO 线把它的
厂商ID(M7-M0)及芯片类型(ID15-0)发送给主机,主机接收到指令响应后可进行校验。常见的应用是主
机端通过读取设备ID 来测试硬件是否连接正常,或用于识别设备。对于FLASH 芯片的其它指令,
都是类似的,只是有的指令包含多个字节,或者响应包含更多的数据。
实际上,编写设备驱动都是有一定的规律可循的。首先我们要确定设备使用的是什么通讯协议。如
FLASH 型号厂商号(M7-M0) FLASH 型号(ID15-ID0)
W25Q64 EF h 4017 h
W25Q128 EF h 4018 h
第14 章.SPI 通讯
SAIUR201 6 第187页
上一章的EEPROM 使用的是I2C,本章的FLASH 使用的是SPI。那么我们就先根据它的通讯协议,
选择好STM32 的硬件模块,并进行相应的I2C 或SPI 模块初始化。接着,我们要了解目标设备的相
关指令,因为不同的设备,都会有相应的不同的指令。如EEPROM 中会把第一个数据解释为内部存储
矩阵的地址(实质就是指令)。而FLASH 则定义了更多的指令,有写指令,读指令,读ID 指令等等。
最后,我们根据这些指令的格式要求,使用通讯协议向设备发送指令,达到控制设备的目标。
定义FLASH 指令编码表
为了方便使用,我们把FLASH 芯片的常用指令编码使用宏来封装起来,后面需要发送指令编码
的时候我们直接使用这些宏即可,见代码清单14-6。
代码清单14-6 FLASH 指令编码表
读取FLASH 芯片ID
根据“JEDEC”指令的时序,我们把读取FLASH ID 的过程编写成一个函数,见代码清单14-7。
代码清单14-7 读取FLASH 芯片ID
第14 章.SPI 通讯
第188 页SAIUR2016
这段代码利用控制CS 引脚电平的宏“SPI_FLASH_CS_LOW/HIGH”以及前面编写的单字节收发函
数SPI_FLASH_SendByte, 很清晰地实现了“JEDEC ID”指令的时序:发送一个字节的指令编码
“W25X_JedecDeviceID”,然后读取3 个字节,获取FLASH 芯片对该指令的响应,最后把读取到的这
3 个数据合并到一个变量Temp 中,然后作为函数返回值,把该返回值与我们定义的宏“sFLASH_ID”
对比,即可知道FLASH 芯片是否正常。
FLASH 写使能以及读取当前状态
在向FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能,
见代码清单14-8。
代码清单14-8 写使能命令
EEPROM 一样,由于FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线
通讯结束的一瞬间完成的,所以在写操作后需要确认FLASH 芯片“空闲”时才能进行再次写入。为了
表示自己的工作状态,FLASH 芯片定义了一个状态寄存器,见图14-9。
第14 章.SPI 通讯
SAIUR201 6 第189页
图14-9 FLASH 芯片的状态寄存器
我们只关注这个状态寄存器的第0 位“BUSY”,当这个位为“1”时,表明FLASH 芯片处于忙碌状
态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作。利用指令表中的“Read Status
Register”指令可以获取FLASH 芯片状态寄存器的内容,其时序见图14-10。
图14-10 读取状态寄存器的时序
只要向FLASH 芯片发送了读状态寄存器的指令,FLASH 芯片就会持续向主机返回最新的状态
寄存器内容,直到收到SPI 通讯的停止信号。据此我们编写了具有等待FLASH 芯片写入结束功能的
函数,见代码清单14-9。
代码清单14-9 通过读状态寄存器等待FLASH 芯片空闲
第14 章.SPI 通讯
第190 页SAIUR2016
这段代码发送读状态寄存器的指令编码“W25X_ReadStatusReg”后,在while 循环里持续获取寄存
器的内容并检验它的“WIP_Flag 标志”(即BUSY 位),一直等待到该标志表示写入结束时才退出本函
数,以便继续后面与FLASH 芯片的数据通讯。
FLASH 扇区擦除
由于FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位
不能直接改写为“1”。所以这里涉及到数据“擦除”的概念,在写入前, 必须要对目标存储矩阵进行擦除
操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵,
在要存储数据“0”时,才更改该位。通常,对存储矩阵擦除的基本操作单位都是多个字节进行,如本例
子中的FLASH 芯片支持“扇区擦除”、“块擦除”以及“整片擦除”,见表14-6。
表14-6 本实验FLASH 芯片的擦除单位
FLASH 芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含16 个扇区,其内部存储矩阵分
布见图14-11。。
第14 章.SPI 通讯
SAIUR201 6 第191页
图14-11 FLASH 芯片的存储矩阵
使用扇区擦除指令“Sector Erase”可控制FLASH 芯片开始擦写,其指令时序见图14-14。
图14-12 扇区擦除时序
扇区擦除指令的第一个字节为指令编码,紧接着发送的3 个字节用于表示要擦除的存储矩阵地址。要
注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器
状态等待扇区擦除操作完毕,代码实现见代码清单14-10。
第14 章.SPI 通讯
第192 页SAIUR2016
代码清单25-10 擦除扇区
这段代码调用的函数在前面都已讲解,只要注意发送擦除地址时高位在前即可。调用扇区擦除指令
时注意输入的地址要对齐到4KB。
FLASH 的页写入
目标扇区被擦除完毕后,就可以向它写入数据了。与EEPROM 类似,FLASH 芯片也有页写入命
令,使用页写入命令最多可以一次向FLASH 传输256 个字节的数据,我们把这个单位为页大小。
FLASH 页写入的时序见图14-13。
图14-13 FLASH 芯片页写入
第14 章.SPI 通讯
SAIUR201 6 第193页
从时序图可知,第1 个字节为“页写入指令”编码,2-4 字节为要写入的“地址A”,接着的是要写
入的内容,最多个可以发送256 字节数据,这些数据将会从“地址A”开始,按顺序写入到FLASH 的
存储矩阵。若发送的数据超出256 个,则会覆盖前面发送的数据。与擦除指令不一样,页写入指令的
地址并不要求按256 字节对齐,只要确认目标存储单元是擦除状态即可(即被擦除后没有被写入过)。
所以,若对“地址x”执行页写入指令后,发送了200 个字节数据后终止通讯,下一次再执行页写入指
令,从“地址(x+200)”开始写入200 个字节也是没有问题的(小于256 均可)。只是在实际应用中由于
基本擦除单元是4KB,一般都以扇区为单位进行读写,想深入了解,可学习我们的“FLASH 文件系统”
相关的例子。把页写入时序封装成函数,其实现见代码清单14-11。
代码清单14-11 FLASH 的页写入
第14 章.SPI 通讯
第194 页SAIUR2016
这段代码的内容为:先发送“写使能”命令,接着才开始页写入时序,然后发送指令编码、地址,
再把要写入的数据一个接一个地发送出去,发送完后结束通讯,检查FLASH 状态寄存器,等待FLASH
内部写入结束。不定量数据写入应用的时候我们常常要写入不定量的数据,直接调用“页写入”函数并
不是特别方便, 所以我们在它的基础上编写了“不定量数据写入”的函数,基实现见代码清单14-12。
代码清单14-12 不定量数据写入
第14 章.SPI 通讯
SAIUR201 6 第195页
这段代码与EEPROM 章节中的“快速写入多字节”函数原理是一样的,运算过程在此不再赘述。
区别是页的大小以及实际数据写入的时候,使用的是针对FLASH 芯片的页写入函数,且在实际调用
这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态。
从FLASH 读取数据
相对于写入,FLASH 芯片的数据读取要简单得多,使用读取指令“Read Data”即可,其指令时序见
图14-14。
第14 章.SPI 通讯
第196 页SAIUR2016
图14-14 SPI FLASH 读取数据时序
发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回存储矩阵的内容,
读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。代码实现见代码清单
14-13。
代码清单14-13 从FLASH 读取数据
由于读取的数据量没有限制,所以发送读命令后一直接收NumByteToRead 个数据到结束即可。
第14 章.SPI 通讯
SAIUR201 6 第197页
main 函数
最后我们来编写main 函数,进行FLASH 芯片读写校验,见代码清单14-14。
代码清单14-14 main 函数
第14 章.SPI 通讯
第198 页SAIUR2016
函数中初始化了LED、串口、SPI 外设,然后读取FLASH 芯片的ID 进行校验,若ID 校验通
过则向FLASH 的特定地址写入测试数据,然后再从该地址读取数据,测试读写是否正常。
注意:由于实验板上的FLASH 芯片默认已经存储了特定用途的数据,如擦除了这些数据会影响
到某些程序的运行。所以我们预留了FLASH 芯片的“第0 扇区(0-4096 地址)”专用于本实验,如非
必要,请勿擦除其它地址的内容。如已擦除,可在配套资料里找到“刷外部FLASH 内容”程序,根据其
说明给FLASH 重新写入出厂内容。
14.6.5. 下载验证
用USB 线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程
序下载到开发板。在串口调试助手可看到FLASH 测试的调试信息。
14.7.课后练习
1. 在SPI 外设初始化部分,MISO 引脚可以设置为输入模式吗?为什么?实际测试现象如何?
2. 尝试使用FLASH 芯片存储int 整型变量,float 型浮点变量,编写程序写入数据,并读出校验。
3. 如果扇区未经擦除就写入,会有什么后果?请做实验验证。
4. 简述FLASH 存储器与EEPROM 存储器的区别。
第15 章.陀螺仪姿态检测
SAIUR201 6 第199页
第15 章.陀螺仪姿态检测
15.1.课前预习
在书上找到答案。
1. MPU6050 模块由什么功能?
15.2.概述
本章所讲内容:
(1)MPU6050 模块介绍
(2)使用MPU6050 模块进行姿态检测
15.3.姿态检测
15.3.1. 基本认识
在飞行器中,飞行姿态是非常重要的参数,见图50-1,以飞机自身的中心建立坐标系, 当飞机绕
坐标轴旋转的时候,会分别影响偏航角、横滚角及俯仰角。
第15 章.陀螺仪姿态检测
第200 页SAIUR2016
图15-1 表示飞机姿态的偏航角、横滚角及俯仰角
假如我们知道飞机初始时是左上角的状态,只要想办法测量出基于原始状态的三个姿态角的变化
量,再进行叠加,就可以获知它的实时姿态了。
15.3.2. 坐标系
抽象来说,姿态是“载体坐标系”与“地理坐标系”之间的转换关系。
图15-2 地球坐标系、地理坐标系与载体坐标系
我们先来了解三种常用的坐标系:
1. 地球坐标系:以地球球心为原点,Z 轴沿地球自转轴方向,X、Y 轴在赤道平面内的坐标系。
2. 地理坐标系:它的原点在地球表面(或运载体所在的点),Z 轴沿当地地理垂线的方向(重力加速度
第15 章.陀螺仪姿态检测
SAIUR201 6 第201页
方向),XY 轴沿当地经纬线的切线方向。根据各个轴方向的不同,可选为“东北天”、“东南天”、
“西北天”等坐标系。这是我们日常生活中使用的坐标系,平时说的东南西北方向与这个坐标系东
南西北的概念一致。
3. 载体坐标系:载体坐标系以运载体的质心为原点,一般根据运载体自身结构方向构成坐标系,如Z
轴上由原点指向载体顶部,Y 轴指向载体头部,X 轴沿载体两侧方向。上面说基于飞机建立的坐
标系就是一种载体坐标系,可类比到汽车、舰船、人体、动物或手机等各种物体。
地理坐标系与载体坐标系都以载体为原点,所以它们可以经过简单的旋转进行转换, 载体的姿态
角就是根据载体坐标系与地理坐标系的夹角来确定的。配合图50-1,发挥您的空间想象力,假设初始
状态中,飞机的Z 轴、X 轴及Y 轴分别与地理坐标系的天轴、北轴、东轴平行。如当飞机绕自身的
“Z”轴旋转,它会使自身的“Y”轴方向与地理坐标系的“南北”方向偏离一定角度,该角度就称为偏航角
(Yaw);当载体绕自身的“X”轴旋转,它会使自身的“Z”轴方向与地理坐标系的“天地”方向偏离一定角度,
该角度称为俯仰角(Pitch);当载体绕自身的“Y”轴旋转,它会使自身的“X”轴方向与地理坐标系的“东西”
方向偏离一定角度,该角度称为横滚角。
表50-1 姿态角的关系
这些角度也称欧拉角,是用于描述姿态的非常直观的角度。
15.4.利用陀螺仪检测角度
最直观的角度检测器就是陀螺仪了,见图50-3,它可以检测物体绕坐标轴转动的“角速度”,如同
将速度对时间积分可以求出路程一样,将角速度对时间积分就可以计算出旋转的“角度”。
图15-3 陀螺仪检测示意图
第15 章.陀螺仪姿态检测
第202 页SAIUR2016
陀螺仪检测的缺陷
由于陀螺仪测量角度时使用积分,会存在积分误差,见图50-4,若积分时间Dt 越小, 误差就越
小。这十分容易理解,例如计算路程时,假设行车时间为1 小时,我们随机选择行车过程某个时刻的
速度Vt 乘以1 小时,求出的路程误差是极大的,因为行车的过程中并不是每个时刻都等于该时刻速
度的,如果我们每5 分钟检测一次车速,可得到Vt1、Vt2、Vt3-Vt12 这12 个时刻的车速,对各个
时刻的速度乘以时间间隔(5 分钟),并对这12 个结果求和,就可得出一个相对精确的行车路程了,
不断提高采样频率,就可以使积分时间Dt 变小,降低误差。
图15-4 积分误差
同样地,提高陀螺仪传感器的采样频率,即可减少积分误差,目前非常普通的陀螺仪传感器的采
样频率都可以达到8KHz,已能满足大部分应用的精度要求。更难以解决的是器件本身误差带来的问题。
例如,某种陀螺仪的误差是0.1 度/秒,当陀螺仪静止不动时,理想的角速度应为0,无论它静止多久,
对它进行积分测量得的旋转角度都是0,这是理想的状态;而由于存在0.1 度/秒的误差,当陀螺仪
静止不动时,它采样得的角速度一直为0.1 度/秒,若静止了1 分钟,对它进行积分测量得的旋转角
度为6 度,若静止了1 小时,陀螺仪进行积分测量得的旋转角度就是360 度,即转过了一整圈,这
就变得无法忍受了。只有当正方向误差和负方向误差能正好互相抵消的时候,才能消除这种累计误差。
15.5. 利用加速度计检测角度
由于直接用陀螺仪测量角度在长时间测量时会产生累计误差,因而我们又引入了检测倾角的传感
器。
图15-5 T 字型水平仪
第15 章.陀螺仪姿态检测
SAIUR201 6 第203页
测量倾角最常见的例子是建筑中使用的水平仪,在重力的影响下,水平仪内的气泡能大致反映水柱
所在直线与重力方向的夹角关系,利用图15-5 中的T 字型水平仪,可以检测出图15-1 中说明的横
滚角与俯仰角,但是偏航角是无法以这样的方式检测的。在电子设备中,一般使用加速度传感器来检测
倾角,它通过检测器件在各个方向的形变情况而采样得到受力数据,根据F=ma 转换,传感器直接输
出加速度数据,因而被称为加速度传感器。由于地球存在重力场,所以重力在任何时刻都会作用于传
感器,当传感器静止的时候(实际上加速度为0),传感器会在该方向检测出加速度g,不能认为重力
方向测出的加速度为g,就表示传感器在该方向作加速度为g 的运动。当传感器的姿态不同时,它在
自身各个坐标轴检测到的重力加速度是不一样的,利用各方向的测量结果,根据力的分解原理,可求
出各个坐标轴与重力之间的夹角,见图15-6。
图15-6 重力检测
因为重力方向是与地理坐标系的“天地”轴固连的,所以通过测量载体坐标系各轴与重力方向的夹
角即可求得它与地理坐标系的角度旋转关系,从而获知载体姿态。加速度传感器检测的缺陷由于这种
倾角检测方式是利用重力进行检测的,它无法检测到偏航角(Yaw),原理跟T 字型水平仪一样,无论如
何设计水平仪,水泡都无法指示这样的角度。另一个缺陷是加速度传感器并不会区分重力加速度与外
力加速度,当物体运动的时候,它也会在运动的方向检测出加速度,特别在震动的状态下,传感器的数
据会有非常大的数据变化,此时难以反应重力的实际值。
15.6.利用磁场检测角度
为了弥补加速度传感器无法检测偏航角(Yaw)的问题,我们再引入磁场检测传感器, 它可以检测出
各个方向上的磁场大小,通过检测地球磁场,它可实现指南针的功能,所以也被称为电子罗盘。由于
地磁场与地理坐标系的“南北”轴固联,利用磁场检测传感器的指南针功能,就可以测量出偏航角(Yaw)
了。磁场检测器的缺陷与指南针的缺陷一样,使用磁场传感器会受到外部磁场干扰,如载体本身的电
第15 章.陀螺仪姿态检测
第204 页SAIUR2016
磁场干扰,不同地理环境的磁铁矿干扰等等。
15.7.利用GPS 检测角度
使用GPS 可以直接检测出载体在地球上的坐标,假如载体在某时刻测得坐标为A,另一时刻测得
坐标为B,利用两个坐标即可求出它的航向,即可以确定偏航角,且不受磁场的影响,但这种检测方
式只有当载体产生大范围位移的时候才有效(GPS 民用精度大概为10 米级)。
15.8. 姿态融合与四元数
可以发现,使用陀螺仪检测角度时,在静止状态下存在缺陷,且受时间影响,而加速度传感器检测
角度时,在运动状态下存在缺陷,且不受时间影响,刚好互补。假如我们同时使用这两种传感器,并设
计一个滤波算法,当物体处于静止状态时,增大加速度数据的权重,当物体处于运动状时,增大陀螺仪
数据的权重,从而获得更准确的姿态数据。同理,检测偏航角,当载体在静止状态时,可增大磁场检测
器数据的权重,当载体在运动状态时,增大陀螺仪和GPS 检测数据的权重。这些采用多种传感器数据
来检测姿态的处理算法被称为姿态融合。在姿态融合解算的时候常常使用“四元数”来表示姿态,它由
三个实数及一个虚数组成,因而被称之为四元数。使用四元数表示姿态并不直观,但因为使用欧拉角(即
前面说的偏航角、横滚角及俯仰角)表示姿态的时候会有“万向节死锁”问题,且运算比较复杂,所以
一般在数据处理的时候会使用四元数,处理完毕后再把四元数转换成欧拉角。在这里我们只要了解四元
数是姿态的另一种表示方式即可,感兴趣的话可自行查阅相关资料。
15.9.MPU6050 模块简介
15.9.1. MPU6050 模块功能及外观
接下来我们使用传感器实例来讲解如何检测物体的姿态。在我们的开发板上有引出I2C 总线接口,
方便用于扩展使用I2C 协议通讯的传感器模块,本节中我们将在板子上扩展MPU6050 陀螺仪模块,
见图50-9。它是一种六轴传感器模块,采用InvenSense 公司的MPU6050 作为主芯片,能同时检测三
轴加速度、三轴陀螺仪(三轴角速度)的运动数据以及温度数据。利用MPU6050 芯片内部的DMP 模块
(Digital Motion Processor 数字运动处理器),可对传感器数据进行滤波、融合处理,它直接通过I2C
接口向主控器输出姿态解算后的姿态数据,降低主控器的运算量。其姿态解算频率最高可达200Hz,
非常适合用于对姿态控制实时要求较高的领域。常见应用于手机、智能手环、四轴飞行器及计步器等的
姿态检测。
第15 章.陀螺仪姿态检测
SAIUR201 6 第205页
图15-9 MPU6050 模块外观
15.9.2. MPU6050 模块的引脚功能说明
该模块引出的8 个引脚功能说明见表15-3。
其中的SDA/SCL、XDA/XCL 通讯引脚分别为两组I2C 信号线。当模块与外部主机通讯时,使用
SDA/SCL,如与STM32 芯片通讯;而XDA/XCL 则用于MPU6050 芯片与其它I2C 传感器通讯时使
用,例如使用它与磁场传感器连接,MPU6050 模块可以把从主机SDA/SCL 接收的数据或命令通过
XDA/XCL 引脚转发到磁场传感器中。但实际上这种功能比较鸡肋,控制麻烦且效率低,一般会直接把
磁场传感器之类的I2C 传感器直接与MPU6050 挂载在同一条总线上(即都连接到SDA/SCL),使
用主机直接控制。
15.9.3. MPU6050 模块的硬件原理图
MPU6050 模块的硬件原理图见图15-10。
图15-10 MPU6050 模块原理图
它的硬件非常简单,SDA 与SCL 被引出方便与外部I2C 主机连接,看图中的右上角,可知该模
第15 章.陀螺仪姿态检测
第206 页SAIUR2016
块的I2C 通讯引脚SDA 及SCL 已经连接了上拉电阻,因此它与外部I2C 通讯主机通讯时直接使用
导线连接起来即可;而MPU6050 模块与其它传感器通讯使用的XDA、XCL 引脚没有接上拉电阻,
要使用时需要注意。模块自身的I2C 设备地址可通过AD0 引脚的电平控制,当AD0 接地时,设备
地址为0x68(七位地址),当AD0 接电源时,设备地址为0x69(七位地址)。另外,当传感器有新数据的
时候会通过INT 引脚通知STM32。由于MPU6050 检测时是基于自身中心坐标系的,见图50-11,
它表示的坐标系及旋转符号标出了MPU6050 传感器的XYZ 轴的加速度有角速度的正方向。所以在
安装模块时,您需要考虑它与所在设备的坐标系统的关系。
图15-11 MPU6050 传感器的坐标及方向
15.10. MPU6050 模块的特性参数
MPU6050 传感器模块的参数见表15-4。
表15-4 MPU6050 的特性参数
第15 章.陀螺仪姿态检测
SAIUR201 6 第207页
该表说明,加速度与陀螺仪传感器的ADC 均为16 位,它们的量程及分辨率可选多种模式,见
图15-13,量程越大,分辨率越低。
图50-12 加速度配置跟量程的关系
图15-13 陀螺仪的几种量程配置
从表中还可了解到传感器的加速度及陀螺仪的采样频率分别为1000Hz 及8000Hz,它们是指加速
度及角速度数据的采样频率,我们可以使用STM32 控制器把这些数据读取出来然后进行姿态融合解
算,以求出传感器当前的姿态(即求出偏航角、横滚角、俯仰角)。而如果我们使用传感器内部的DMP 单
元进行解算,它可以直接对采样得到的加速度及角速度进行姿态解算,解算得到的结果再输出给
STM32 控制器,即STM32 无需自己计算,可直接获取偏航角、横滚角及俯仰角,该DMP 每秒可输
出200 次姿态数据。
15.11. MPU6050—获取原始数据实验
这一小节我们学习如何使用STM32 控制MPU6050 传感器读取加速度、角速度及温度数据。在
控制传感器时,使用到了STM32 的I2C 驱动,就如同控制STM32 一样,对MPU6050 传感器的不
同寄存器写入不同内容可以实现不同模式的控制,从特定的寄存器读取内容则可获取测量数据,这部分
关于MPU6050 具体寄存器的内容我们不再展开,请您查阅《MPU-60X0 寄存器》手册获知。
15.11.1. 硬件设计
在实验前,我们先用杜邦线把STM32 开发板与该MPU6050 模块连接起来,见图15-14 及表
15-5。
第15 章.陀螺仪姿态检测
第208 页SAIUR2016
图15-14 STM32 与MPU6050 的硬件连接
表15-5 MPU6050 模块引脚说明
15.11.2. 配套程序简介
本章的MPU6050 模块一共配套了四个例程,用户可根据需求选择相应的程序来学习,各个例程
的基本功能介绍见表15-6。
表15-6 MPU6050 模块配套程序基本功能介绍
第15 章.陀螺仪姿态检测
SAIUR201 6 第209页
其中,例程1 和2 的功能类似,都是简单地获取数据,但由于硬件I2C 不支持同时使用液晶屏,
所以后面的2、3、4 例程都默认使用软件I2C 来驱动MPU6050,底层的软件I2C 驱动跟EEPROM 基
本本一致,本章中重点讲述上层的MPU6050 应用及接口。
15.11.3. 软件设计
本小节讲解的是“硬件STM32-MPU6050”实验,请打开配套的代码工程阅读理解。为了方便展示
及移植,我们把STM32 的I2C 驱动相关的代码都编写到“bsp_i2c.c”及“bsp_i2c.h”文件中,与
MPU6050 传感器相关的代码都写到“mpu6050.c”及“mpu6050.h”文件中,这些文件是我们自己编写的,
不属于标准库的内容,可根据您的喜好命名文件。
15.11.4. 程序设计要点
1. 初始化STM32 的I2C;
2. 使用I2C 向MPU6050 写入控制参数;
3. 定时读取加速度、角速度及温度数据。
15.11.5. 代码分析
本实验中的I2C 驱动与MPU6050 驱动分开主要是考虑到扩展其它传感器时的通用性,如使用磁
场传感器、气压传感器都可以使用同样一个I2C 驱动,这个驱动只要给出针对不同传感器时的不同读
写接口即可。本章讲解的I2C 驱动主要针对接口封装讲解,细节不再赘述。本实验中的I2C 硬件定义
见代码清单15-1。
代码清单15-1 I2C 的硬件定义(bsp_i2c.h 文件)
这些宏根据传感器使用的I2C 硬件封装起来了。初始化I2C 接下来利用这些宏对I2C 进行初始
化,初始化过程与I2C 读写EEPROM 中的无异,见代码清单15-2。
第15 章.陀螺仪姿态检测
第210 页SAIUR2016
代码清单15-2 初始化I2C(bsp_i2c.c 文件)
对读写函数的封装初始化完成后就是编写I2C 读写函数了,这部分跟EERPOM 的一样,主要是
调用STM32 标准库函数读写数据寄存器及标志位, 本实验的这部分被编
写进ST_Sensors_I2C_WriteRegister 及ST_Sensors_I2C_ReadRegister 中了,在它们之上,再封装成
了Sensors_I2C_WriteRegister 及Sensors_I2C_ReadRegister,见代码清单15-3。
第15 章.陀螺仪姿态检测
SAIUR201 6 第211页
代码清单15-3 对读写函数的封装(i2c.c 文件)
第15 章.陀螺仪姿态检测
第212 页SAIUR2016
封装后的函数主要是增加了错误重试机制,若读写出现错误,则会进行多次尝试,多次尝试均失败
后会返回错误代码。这个函数作为I2C 驱动对外的接口,其它使用I2C 的传感器调用这个函数进行
读写寄存器。
MPU6050 的寄存器定义
MPU6050 有各种各样的寄存器用于控制工作模式,我们把这些寄存器的地址、寄存器位使用宏定
义到了mpu6050.h 文件中了,见代码清单15-4。
代码清单15-4MPU6050 的寄存器定义(mpu6050.h)
初始化MPU6050
根据MPU6050 的寄存器功能定义,我们使用I2C 往寄存器写入特定的控制参数,见代码清单
15-5。
代码清单15-5 初始化MPU6050(mpu6050.c)
第15 章.陀螺仪姿态检测
SAIUR201 6 第213页
这段代码首先使用MPU6050_ReadData 及MPU6050_WriteRed 函数封装了I2C 的底层读写驱
动,接下来用它们在MPU6050_Init 函数中向MPU6050 寄存器写入控制参数,设置了MPU6050 的
采样率、量程(分辨率)。读传感器ID 初始化后,可通过读取它的“WHO AM I”寄存器内容来检测硬件
是否正常,该寄存器存储了ID 号0x68,见代码清单15-6。
代码清单15-6 读取传感器ID(mpu6050.c)
读取原始数据
若传感器检测正常,就可以读取它数据寄存器获取采样数据了,见代码清单15-7。
代码清单15-7 读取传感器数据(mpu6050.c)
第15 章.陀螺仪姿态检测
第214 页SAIUR2016
其中前以上三个函数分别用于读取三轴加速度、角速度及温度值,这些都是原始的ADC 数值(16
位长),对于加速度和角速度,把读取得的ADC 值除以分辨率,即可求得实际物理量数值。最后一个
函数MPU6050_ReturnTemp 展示了温度ADC 值与实际温度值间的转换,它是根据MPU6050 的说
明给出的转换公式进行换算的,注意陀螺仪检测的温度会受自身芯片发热的影响,严格来说它测量的
是自身芯片的温度,所以用它来测量气温是不太准确的。对于加速度和角速度值我们没有进行转换,
在下一小节中我们直接利用这些数据交给DMP 单元,求解出姿态角。
第15 章.陀螺仪姿态检测
SAIUR201 6 第215页
main 函数
最后我们来看看本实验的main 函数,见代码清单15-8。
代码清单15-8 main 函数
本实验中控制MPU6050 并没有使用中断检测,我们是利用Systick 定时器进行计时,隔一段时
间读取MPU6050 的数据寄存器获取采样数据的,代码中使用Task_Delay 变量来控制定时时间,在
Systick 中断里会每隔1ms 对该变量值减1,所以当它的值为0 时表示定时时间到。
在main 函数里,调用I2C_Bus_Init、MPU6050_Init 及MPU6050ReadID 函数后,就在whlie 循
环里判断定时时间,定时时间到后就读取加速度、角速度及温度值,并使用串口打印信息到电脑端。
15.11.6. 下载验证
使用杜邦线连接好开发板和模块,用USB 线连接开发板“USB TO UART”接口跟电脑,在电脑端
第15 章.陀螺仪姿态检测
第216 页SAIUR2016
打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到MPU6050 采样得到的调试
信息。
15.12. MPU6050—利用DMP 进行姿态解算
上一小节我们仅利用MPU6050 采集了原始的数据,如果您对姿态解算的算法深有研究,可以自
行编写姿态解算的算法,并利用这些数据,使用STM32 进行姿态解算,解算后输出姿态角。而由于
MPU6050 内部集成了DMP,不需要STM32 参与解算,可直接输出姿态角, 也不需要对解算算法作
深入研究,非常方便,本章讲解如何使用DMP 进行解算。实验中使用的代码主体是从MPU6050 官
方提供的驱动《motion_driver_6.12》移植过来的,该资料包里提供了基于STM32F4 控制器的源代码
(本工程正是利用该代码移植到STM32F1 上的)及使用python 语言编写的上位机,资料中还附带了
说明文档,请您充分利用官方自带的资料学习。
15.12.1. 硬件设计
硬件设计与上一小节实验中的完全一样,且软件中使用了INT 引脚产生的中断信号,本小节中的
代码默认使用软件I2C。
15.12.2. 软件设计
本小节讲解的是“MPU6050_python 上位机”实验,请打开配套的代码工程阅读理解。本工程是从
官方代码移植过来的(IAR 工程移植至MDK),改动并不多,我们主要给读者讲解一下该驱动的设计思
路,方便应用。由于本工程的代码十分庞大,在讲解到某些函数时,请善用MDK 的搜索功能,从而
在工程中查找出对应的代码。
15.12.3. 程序设计要点
1. 提供I2C 读写接口、定时服务及INT 中断处理;
2. 从陀螺仪中获取原始数据并处理;
3. 更新数据并输出。
15.12.4. 代码分析
官方的驱动主要是了MPL 软件库(Motion Processing Library),要移植该软件库我们需要为它提供
I2C 读写接口、定时服务以及MPU6050 的数据更新标志。若需要输出调试信息到上位机,还需要提
供串口接口。
第15 章.陀螺仪姿态检测
SAIUR201 6 第217页
I2C 读写接口
MPL 库的内部对I2C 读写时都使用i2c_write 及i2c_read 函数,在文件“inv_mpu.c”中给出了它
们的接口格式,见代码清单15-1。
代码清单15-9 I2C 读写接口(inv_mpu.c 文件)
这些接口的格式与我们上一小节写的I2C 读写函数Sensors_I2C_ReadRegister 及
Sensors_I2C_WriteRegister 一致,所以可直接使用宏替换。提供定时服务MPL 软件库中使用到了延时
及时间戳功能,要求需要提供delay_ms 函数实现毫秒级延时,提供get_ms 获取毫秒级的时间戳,
它们的接口格式也在“inv_mpu.c”文件中给出,见代码清单15-2。
代码清单15-10 定时服务接口(inv_mpu.c 文件)
我们为接口提供的Delay_ms 及get_tick_count 函数定义在bsp_SysTick.c 文件,我们使用
SysTick 每毫秒产生一次中断,进行计时,见代码清单15-11。
第15 章.陀螺仪姿态检测
第218 页SAIUR2016
代码清单15-11 使用Systick 进行定时(bsp_SysTick.c)
上述代码中的TimingDelay_Decrement 和TimeStamp_Increment 函数是在Systick 的中断服务函
数中被调用的,见代码清单50-12。systick 被配置为每毫秒产生一次中断,而每次中断中会对
TimingDelay 变量减1,对g_ul_ms_ticks 变量加1。它们分别用于Delay_ms 函数利用TimingDelay
的值进行阻塞延迟,而get_tick_count 函数获取的时间戳即g_ul_ms_ticks 的值。
第15 章.陀螺仪姿态检测
SAIUR201 6 第219页
代码清单15-12 Systick 的中断服务函数(stm32f10x_it.c 文件)
提供串口调试接口
MPL 代码库的调试信息输出函数都集中到了log_stm32.c 文件中,我们可以为这些函数提供串口
输出接口,以便把这些信息输出到上位机,见代码清单15-3。
代码清单15-13 串口调试接口(log_stm32.c 文件)
第15 章.陀螺仪姿态检测
第220 页SAIUR2016
上述代码中的fputcc 函数是我们自己编写的串口输出接口,它与我们重定向printf 函数定义的
fputc 函数功能很类似。下面的eMPL_send_quat 函数是MPL 库中的原函数,它用于打印“四元数信
息”,在这个log_stm32.c 文件中还有输出日志信息的_MLPrintLog 函数,输出原始信息到专用上位机
的eMPL_send_data 函数,它们都调用了fputcc 进行输出。MPU6050 的中断接口
与上一小节中的基础实验不同,为了高效处理采样数据,MPL 代码库使用了MPU6050 的INT 中
断信号,为此我们要给提供中断接口,见代码清单15-4。
代码清单15-14 中断接口(stm32f10x_it.c 文件)
在工程中我们把MPU6050 与STM32 相连的引脚配置成了中断模式,上述代码是该引脚的中断
服务函数, 在中断里调用了MPL 代码库的gyro_data_ready_cb 函数, 它设置了标志变量
hal.new_gyro,以通知MPL 库有新的数据,其函数定义见代码清单15-15。
代码清单15-15 设置标志变量(main.c 文件)
main 函数执行流程
了解MPL 移植需要提供的接口后,我们直接看main 函数了解如何利用MPL 库获取姿态数据,
见代码清单15-5。
第15 章.陀螺仪姿态检测
SAIUR201 6 第221页
代码清单15-16 使用MPL 进行姿态解算的过程
第15 章.陀螺仪姿态检测
第222 页SAIUR2016
第15 章.陀螺仪姿态检测
SAIUR201 6 第223页
第15 章.陀螺仪姿态检测
第224 页SAIUR2016
如您所见,main 函数非常长,而且我们只是摘抄了部分,在原工程代码中还有很多代码,以及不
同模式下的条件判断分支,例如加入磁场数据使用9 轴数据进行解算的功能(这是MPU9150 的功能,
MPU6050 不支持)以及其它工作模式相关的控制示例。上述main 函数的主要执行流程概括如下:
1. 初始化STM32 的硬件,如Systick、LED、调试串口、INT 中断引脚以及I2C 外设的初始化;
2. 调用MPL 库函数mpu_init 初始化传感器的基本工作模式(以下过程调用的大部分都是MPL 库
函数,不再强调);
3. 调用inv_init_mpl 函数初始化MPL 软件库,初始化后才能正常进行解算;
4. 设置各种运算参数, 如四元数运算(inv_enable_quaternion) 、6 轴或9 轴数据融合
(inv_enable_9x_sensor_fusion)等等;
5. 设置传感器的工作模式(mpu_set_sensors) 、采样率(mpu_set_sample_rate) 、分辨率
(inv_set_gyro_orientation_and_scale)等等;
6. 当STM32 驱动、MPL 库、传感器工作模式、DMP 工作模式等所有初始化工作都完成后进行
while 循环;
7. 在while 循环中检测串口的输入,若串口有输入,则调用handle_input 根据串口输入的字符(命
令),切换工作方式。这部分主要是为了支持上位机通过输入命令, 根据进行不同的处理,如开、
关加速度信息的采集或调试信息的输出等;
8. 在while 循环中检测是否有数据更新(if (hal.new_gyro && hal.dmp_on)),当有数据更新的时候产
生INT 中断,会使hal.new_gyro 置1 的,从而执行if 里的条件代码;
9. 使用dmp_read_fifo 把数据读取到FIFO,这个FIFO 是指MPL 软件库定义的一个缓冲区,用
来缓冲最新采集得的数据;
10. 调用inv_build_gyro、inv_build_temp、inv_build_accel 及inv_build_quat 函数处理数据角速度、
温度、加速度及四元数数据,并对标志变量new_data 置1;
11. 在while 循环中检测new_data 标志位,当有新的数据时执行if 里的条件代码;
12. 调用inv_execute_on_data 函数更新所有数据及状态;
13. 调用read_from_mpl 函数向主机输出最新的数据。
第15 章.陀螺仪姿态检测
SAIUR201 6 第225页
数据输出接口
在上面main 中最后调用的read_from_mpl 函数演示了如何调用MPL 数据输出接口,通过这些
接口我们可以获得想要的数据,其函数定义见代码清单15-17。
代码清单15-17 MPL 的数据输出接口(main.c)
第15 章.陀螺仪姿态检测
第226 页SAIUR2016
上述代码展示了使用inv_get_sensor_type_quat 、inv_get_sensor_type_accel 、
inv_get_sensor_type_gyro、inv_get_sensor_type_euler 及dmp_get_pedometer_step_count 函数分别获取四
元数、加速度、角速度、欧拉角及计步器数据。代码中的eMPL_send_data 函数是使用串口按照
PYTHON 上位机格式进行提交数据,上位机根据这些数据对三维模型作相应的旋转。另外我们自己在
代码中加入了液晶显示的代码(#ifdef USE_LCD_DISPLAY 宏内的代码),它把这些数据输出到实验板上
的液晶屏上。您可根据自己的数据使用需求,参考这个read_from_mpl 函数对数据输出接口的调用方
式,编写自己的应用。
15.12.5. 下载验证
直接下载本程序到开发板,在液晶屏上会观察到姿态角、温度、计步器数据,改变开发板的姿态,
数据会更新(计步器数据要模拟走路才会更新),若直接连接串口调试助手, 会接收到一系列的乱码信
息,这是正常的,这些数据需要使用上位机解码。
第15 章.陀螺仪姿态检测
SAIUR201 6 第227页
15.13. MPU6050—使用第三方上位机
上一小节中的实验必须配合使用官方提供的上位机才能看到三维模型,而且功能比较简单,所以
在小节中我们演示如何把数据输出到第三方的上位机,直观地观察设备的姿态。实验中我们使用的是“匿
名飞控地面站0512”版本的上位机,关于上位机的通讯协议可查阅《飞控通信协议》文档,或到他们
的官方网站了解。
15.13.1. 硬件设计
硬件设计与上一小节实验中的完全一样,同样使用了中断INT 引脚获取数据状态,默认使用软件
I2C 通讯。
15.13.2. 软件设计
本小节讲解的是“MPU6050_DMP 测试例程”实验,请打开配套的代码工程阅读理解。本小节的内
容主体跟上一小节一样,区别主要是当获取得到数据后,本实验根据“匿名飞控”上位机的数据格式要
求上传数据。
15.13.3. 程序设计要点
1. 了解上位机的通讯协议;
2. 根据协议格式上传数据到上位机;
15.13.4. 代码分析
通讯协议
要按照上位机的格式上传数据,首先要了解它的通讯协议,本实验中的上位机协议说明见表15-7。
第15 章.陀螺仪姿态检测
第228 页SAIUR2016
表15-7 匿名上位机的通讯协议(部分)
表中说明了两种数据帧,分别是STATUS 帧及SENSER 帧,数据帧中包含帧头、功能字、长度、
主体数据及校验和。“帧头”用于表示数据包的开始,均使用两个字节的0xAA 表示;“功能字”用于区分
数据帧的类型,0x01 表示STATUS 帧,0x02 表示SENSER 帧;“长度”表示后面主体数据内容的字节
数;“校验和”用于校验,它是前面所有内容的和。其中的STATUS 帧用于向上位机传输横滚角、俯仰
角及偏航角的值(100 倍),SENSER 帧用于传输加速度、角速度及磁场强度的原始数据。发送数据包根
据以上数据格式的要求,我们定义了两个函数,分别用于发送STATUS 帧及SENSER 帧,见代码清
单15-2。
代码清单15-18 发送数据包(main.c 文件)
第15 章.陀螺仪姿态检测
SAIUR201 6 第229页
第15 章.陀螺仪姿态检测
第230 页SAIUR2016
函数比较简单,就是根据输入的内容,一字节一字节地按格式封装好,然后调用串口发送到上位
机。
发送数据
与上一小节一样,我们使用read_from_mpl 函数输出数据,由于使用了不同的上位机, 所以我们
修改了它的具体内容,见代码清单15-3。
代码清单15-19 read_from_mpl 函数(main.c 文件)
代码中调用inv_get_sensor_type_euler 获取欧拉角,然后调用Data_Send_Status 格式上传到上位
机,而加速度及角速度的原始数据直接从sensors 结构体变量即可获取,获取后调end_Data 发送出去。
第15 章.陀螺仪姿态检测
SAIUR201 6 第231页
15.13.5. 下载验证
直接下载本程序到开发板,在液晶屏上会观察到姿态角、温度、计步器数据,改变开发板的姿态,
数据会更新(计步器数据要模拟走路才会更新),若直接连接串口调试助手, 会接收到一系列的乱码信
息,这是正常的,这些数据需要使用“匿名飞控地面站”上位机解码。若通过液晶屏的信息了解到
MPU6050 模块已正常工作,则可进一步在电脑上使用“ANO_TC 匿名飞控地面站-0512.exe”(以下简称
“匿名上位机”)软件查看可视化数据。实验步骤如下:
1. 确认开发板的USB TO USART 接口已与电脑相连,确认电脑端能查看到该串口设备。
2. 打开配套资料里的“匿名上位机”软件,在软件界面打开开发板对应的串口(波特率为115200),
把“基本收码”、“高级收码”、“飞控波形”功能设置为on 状态。点击上方图中的基本收发、波形显
示、飞控状态图标,会弹出窗口。具体见下文软件配置图。
3. 在软件的“基本收发”、“波形显示”、“飞控状态”页面可看到滚动数据、随着模块晃动而变化的波
形以及模块姿态的3D 可视化图形。
第15 章.陀螺仪姿态检测
第232 页SAIUR2016