第1章 STM32概述
1.1 关于ARM内核
1.1.1 ARM含义:
1.ARM(高级精简指令集机器)是Advanced RISC Machine Limited公司的简称,主要设计RISC(精简指令集计算机)处理器。一个程序中80%的代码实现只用到计算机中20%的指令。这20%的机器指令能实现80%的功能。这20%的指令就是常用指令。
RISC:加速常用指令,不常用指令用常用指令组合。
CISC:复杂指令集,具有大量的指令和寻址方式。
2.一类微处理器芯片或产品的通称,即采用ARM公司提供方案的处理器。
1.1.2 ARM内核
ARM内核 包括了寄存器组、指令集、总线、存储器映射规则、中断逻辑和调试组件等。
内核是由ARM公司设计并以销售方式授权给个芯片厂商使用的(ARM公司本身不做芯片)。 比如为高速度设计的Cortex A8、A9都是ARMv7a 架构;Cortex M3、M4是ARMv7m架构;前者是处理器(就是内核),后者是指令集的架构(也简称架构)。
先有架构,再有内核,一个架构可以衍生出多种内核
内核就是MCU内部中最核心的逻辑处理部分,MCU的CPU。所以内核也可以叫做处理器。
1.2 什么是STM32
就是ST(意法半导体集团)公司设计的一系列以ARM Cortex-M为核心的32位微控制器。
1.3 MCU命名规范
例
1.4 STM32开发方式
1.直接基于寄存器开发
优点:执行效率高 对硬件理解深入 更换其他芯片上手快
缺点:开发效率低 代码移植不方便
2.基于标准库开发
优点:相比于寄存器开发效率高 开发难度低 开发移植容易
缺点:屏蔽一些硬件信息,不利于新手学习知识 官方停止标准库更新,新的芯片不提供标准库
3.基于HAL库开发
优点:ST主推,支持力度大 开发工具持续更新 开发难度大大降低 移植容易
缺点:屏蔽几乎所有硬件信息,不利于新手学习知识,只学习HAL,学完之后基础知识不牢固
1.5 STM32最小系统板
STM32单片机能工作的最小外围电路叫最小系统板
最小系统板通常包括:STM32芯片、电源、时钟、调试和复位5部分组成
二 创建工程
2.1 创建工程准备
1)创建需要的目录
2)准备启动文件
STM32 程序需要启动文件,我们需要提前准备好。先去ST官网下载官方提供的外设标准库,里面有提供标准的启动文件
标准库下载地址:STSW-STM32054 - STM32F10x标准外设库 - 意法半导体STMicroelectronicshttps://www.st.com.cn/zh/embedded-software/stsw-stm32054.html
3)创建目录放入启动文件和其他核心文件 为了方便管理,我们把启动文件放入专门目录中。在刚才创建的工程目录中创建一个目录:Start
去到我们刚才下载的标准外设库目录,找到我们需要的启动文件和其他核心文件copy 到Start目录。
拷贝完之后
4)创建工程
选择所需的芯片
2.2 工程配置
1)添加两个Project Group方便管理代码文件
先删除默认的Source Group 1,再添加两个:Start(启动相关的文件),User(我们自己写的代码)。
2)创建main.c
3)include path
4)编译器版本改为5 目前最新的Keil ARM用的是 Compiler version 6,与前面的core_cm3.c不兼容,所以需 要提前准备好Compiler version 5。
下载地址:https://developer.arm.com/downloads/view/ACOMP5 解压之后,把解压的后文件夹放入到Keil MDK的安装目录下
三 软件设计
3.1 点亮一个LED(基于寄存器)
我们只要让GPIOA的0口输出低电平就行了。代码需要按照下面的步骤来实现。对于其中一些概念,大家先有个了解,后面的课程会细讲。
1)开启时钟
在STM32中,让IO口工作,必须先开启对应的时钟。所以需要先查找到开启时钟的寄存器,然后通过该寄存器操作时钟的开启或关闭。我们要打开的是GPIOA的时钟。
我们需要知道RCC_APB2ENR这个寄存器的地址。如何查找呢?先知道RCC这个外设的基地址,然后加上这个寄存器的偏移地址就行了.
从上面可以看出来,RCC的基地址是0x4002 1000,APB2ENR的偏移量是0x18,所以 APB2ENR的地址值是0x4002 1000 + 0x18 有了地址,在这个地址写入一个数据,这个数据的二进制第2是1就行了。其他位暂时不管。我们写入4。这样就开启了GPIOA的时钟。
在代码中,我们需要把地址强转成指针才能给这个地址赋值。
*(uint32_t *)(0x40021000 + 0x18) = 4;
2)给IO口设置输出模式
在STM32中,如果要让IO口输出低电平或高电平,必须给要使用的IO设置为输出模式。
根据前面的思路,需要先找到GPIOA的基地址,再根据偏移地址找到要使用的寄存器的地址。GPIOA的基地址是0x4001 0800。
配置PA0口的输出模式的寄存器是GPIOA_CRL。
只需要让这个寄存器的最后4位是 0011,就是最大速度的推挽输出。
*(uint32_t *)(0x40010800 + 0x00) = 3;
3)给PA0口输出0
给指定PA0口输出0就可以点亮LED1了。用到的寄存器是ODR数据输出寄存器。
4)main.c
#include "stdint.h"
int main()
{
//点亮一个LED(基于寄存器配置)
//1.开启GPIO0的时钟
*(uint32_t *)(0x40021000 + 0x18) = 4; //0000 0004
//2.给IO设置工作模式:输出
*(uint32_t *)(0x40010800 + 0x00) = 3;
//给对应的IO设置:1/0
*(uint32_t *)((0x40010800 + 0x0c)) = 0xfffe;
}
3.2 编译工程
1)安装ST-LINK驱动
2)Keil中配置ST-LINK
3)下载程序
3.3 寄存器升级
3.3.1 进化1
在操作寄存器的时候,如果每次都查手册计算地址,是相当麻烦且无聊。ST公司早就 考虑到了这个问题,已经提前把每个外设寄存器的地址提前给我们用宏定义的方式给算好了, 我只需要直接使用即可。比如下面是定义的RCC各个寄存器地址。(stm32f10x.h中定义)
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC ((RCC_TypeDef *) RCC_BASE)
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
} RCC_TypeDef;
这里还巧妙的运用了结构体中各个成员地址是连续的特征。CR寄存器是RCC第0个32 位寄存器,所以它相对于基地址的偏移是0。CFGR相对于基地址的偏移是4,… APB2ENR的相对于基地址的偏移是6*4=24=0x18,和我们前面查找手册的结果是一致的。
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR = 4;
GPIOA->CRL = 3;
GPIOA->ODR = 0xfffe;
while (1)
{
}
}
3.3.2 进化2(位操作)
其实在上面的代码中还有一些问题。在STM32中一个寄存器是32位的,我们在编写代码的时候只是需要给某位或某几位赋值。由于STM32不支持位寻址,所以在前面的操作 中,我们其实是修改了所有位。这是非常不合理的,也许其他位在其他地方有赋值,我们重新赋值势必会覆盖了其他值,带来的后果也是很严重的。
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR |= 4;
GPIOA->CRL |= 1;
GPIOA->CRL |= 2;
GPIOA->CRL &= ~4;
GPIOA->CRL &= ~8;
GPIOA->ODR &= ~1;
}
3.3.3 进化3(移位操作)
#include "stm32f10x.h"
int main(void)
{
/* 开启GPIOA的时钟 第2位置1*/
RCC->APB2ENR |= 0x1 << 2;
/* GPIOA_CRL的最后4位置 0011 */
GPIOA->CRL &= ~(0x1 << 3);
GPIOA->CRL &= ~(0x1 << 2);
GPIOA->CRL |= 0x1 << 1;
GPIOA->CRL |= 0x1 << 0
/* GPIOA_ODR的第0位置0 */
GPIOA->ODR &= ~(0x1 << 0);
}
3.3.4 进化4(使用定义好的移位后的操作)
在上次的进化中,我们是给寄存器“或等”和“与等”了一些值,这些值都是通过相应 的“移位”操作得到的。比如要操作第2位,就需要把0x1左移2位得到。我们需要查找手 册才能知道要移位几。也是很不方便。 其实ST公司也把我们需要的移位后的值给提前计算好了,用宏定义的方式供我们使用。 比如前面的开启时钟,已经定义了好了这个值。正好就是1<<2 #define RCC_APB2ENR_IOPAEN ((uint32_t)0x00000004) 利用ST公司提前预定义的这些值,可以进一步进化代码为下面的形式。
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= ~GPIO_CRL_CNF0_1;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_MODE0_1;
GPIOA->CRL |= GPIO_CRL_MODE0_0;
GPIOA->ODR &= ~GPIO_ODR_ODR0;
}
四 GPIO外设
4.1 GPIO概述
GPIO(General-purpose input/output),通用型输入输出。简单理解就是我们可以控制输入输出的STM32引脚,统称为GPIO。
GPIO 存在的意义就是用程序控制或读取他们的输出或输入。
4.1.1 GPIO 总体说明
STM32 有多组GPIO,比如STM32F103ZET6共有7组GPIO端口, 他们分别是GPIOx(x从A-G),每组控制16个引脚,共有112个GPIO引脚。具体一个 其他STM32芯片有多少组GPIO,可以去查看他们的对应的数据手册。 每个引脚的电平是0-3.3V,部分引脚最高可以兼容到5V。
4.1.2 GPIO 的主要特点
(1)不同型号,IO口的数量可能不一样。
(2)快速翻转。最快可以达到每2个时钟周期翻转一次。(STM32F1系列最快可以达到50MHz的翻转速度)。
(3)每个IO都可以作为外部中断。
(4)支持8种工作模式
4.1.3 GPIO 的 8 种工作模式
GPIO 端口的每个位(引脚)可以由软件分别配置成8种模式,当然对同一个引脚同一 时间只能处于某一种模式中。
(1)输入浮空(Input floating)
(2)输入上拉(Input pull-up)
(3)输入下拉(Input-pull-down)
(4)模拟输入(Analog)
(5)通用开漏输出(Output open-drain)
(6)通用推挽式输出(Output push-pull)
(7)推挽式复用功能(Alternate function push-pull)
(8)开漏复用功能(Alternate function open-drain)
每个I/O端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问。
输出模式下可以控制端口输出高电平低电平,用于驱动LED,蜂鸣器等,如果是大功率器件(比如电机),还需要加上驱动器(小电流控制大电流)。
输入模式下可以读取端口的高低电平,用于读取外接按键,外接模拟信号的输入,ADC 电压采集,模拟通信协议接受数据等。
4.2 GPIO 工作模式
4.2.1 GPIO 每位的具体电路结构
4.2.2 每种模式详解
4.2.2.1 输出模式
输出流程(设置输出的时候可以读引脚状态)
驱动0能力强(让外部电流直接灌入接地Vss)
驱动1能力弱(VDD驱动电流小)
(1)输出缓冲器被激活。
(2)推挽模式:输出寄存器上的 1 将激活P-MOS,输出高电平。0 将激活N MOS,输出低电平。推挽实际就是描述MOS管输出高低电平的一个动作 输入高电平VDD将电流推出去 输入低电平将外面的电流挽回至Vss
(3)开漏模式:PMOS永远关闭。 输出寄存器上的 0 激活N-MOS,而输出寄存器上的1 将端口置于高阻状态(浮空),所以外部必须要接上拉电阻。
开漏模式作用1:(如果直接输出5V可能会烧毁外部的芯片,所以需要通过上拉电阻。输出低电平时可以直接将N-MOS导通即可)
开漏模式作用2:支持几个GPIO控制一个输入(如果不是开漏模式,一个输出高电平一个输出低电平会直接导通,烧毁MOS管)
设置为开漏输出,只要有其中一个输出低电平则输出低电平,都关闭时,由上拉电阻输出高电平
(4)施密特触发输入被激活。
(5)弱上拉和下拉电阻被禁止。
(6)出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器。
(7)在开漏模式时,对输入数据寄存器的读访问可得到I/O状态。
(8)在推挽模式时,对输出数据寄存器的读访问得到最后一次写的值。
总结
推挽输出和开漏输出的选择
使用推挽
1.驱动能力需求高的场合
2.高速信号传输
3.无需共用信号线的场合
使用开漏
1.多个设备共用信号线
2.不同电压系统之间的接口
3.需要外部上拉电阻来确定逻辑高电平场合
4.2.2.2 复用输出模式
(1)在开漏或推挽式配置中,输出缓冲器被打开。
(2)内置外设的信号驱动输出缓冲器(复用功能输出)。
(3)施密特触发输入被激活。
(4)弱上拉和下拉电阻被禁止。
(5)在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器。
(6)开漏模式时,读输入数据寄存器时可得到I/O口状态。
(7)在推挽模式时,读输出数据寄存器时可得到最后一次写的值。
4.2.2.3 输入模式
(1)2个保护二极管的作用是保护我们的芯片不会由于电压过高或过低而烧毁。 VDD是接电源(3.3V),VSS接地(0V)。如果IO引脚的输入电压高于VDD的值到 一定程度,上方保护二极管导通,则引脚电压被迫拉低到VDD(没有压降,两端电压应相等)。如果IO引脚的输入电压(负电压)低于VSS到一定程度,则下方保护二极管导通,电压被迫拉高到VSS。结点电压被嵌在Vss到Vdd之间。
(2)2个开关控制引脚没有输入的时候是上拉,下拉还是浮空。当上面的开关闭合的时候,输入被拉高到高电平。当下面的开关闭合的时候,输入被拉低到低电平。如果两个都不闭合,输入就是悬空状态。两个同时闭合,就是费电了,不会这么做的。
(3)施密特(图中翻译成肖特基触发器应该是翻译错误,英文版手册是TTL Schmitt trigger)触发器是包含正反馈的比较器电路。可以对信号进行波形整形。
(4)从施密特触发起出来的数据,进入到输入数据寄存器中,我们就可以从中读取数据了。
4.2.2.4 模拟输入模式
当配置为模拟输入时:
(1)输出部分被禁止。
(2)禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗。施密特触发输出值 被强置为0。 (3)弱上拉和下拉电阻被禁止。
(4)读取输入数据寄存器时数值永远为0。
4.3 与 GPIO 相关的7个寄存器
每个GPI/O端口有7个相关的:
2个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)。
2个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)。
1个32位置位/复位寄存器(GPIOx_BSRR)。
1个16位复位寄存器(GPIOx_BRR)。
1个32位锁定寄存器(GPIOx_LCKR)
4.3.1 GPIOx_CRL(端口配置低寄存器
上拉/下拉需要格外配置ODR寄存器(PxODR=0:下拉 =1:上拉)
4.3.2 GPIOx_CRH(端口配置高寄存器)
GPIOx_CRH(Port configuration register high)。
该寄存器配置的是每个端口的 8-15引脚,配置方式和低位寄存器完全一样。
4.3.3 GPIOx_IDR(端口输入数据寄存器)
保留位始终读为0。剩下的分别对应每个引脚的输入值(只读)。
4.3.4 GPIOx_ODR(端口输出数据寄存器)
4.3.5 GPIOx_BSRR(端口位设置/清除寄存器)
(1)高16位是用清除对应的数据输出寄存器的位(0-15)的值:设置为0不影响, 设置为1会清除ODR对应的位的值(置为0)。
(2)低16位是用设置对应的数据输出寄存器的位(0-15)的值:设置为0不影响, 设置为1会设置ODR对应的位的值(置为1)。
4.3.6 GPIOx_BRR(端口位清除寄存器)
这个寄存器具有了GPIOx_BSRR一半的功能:清除。
4.3.7 GPIOx_LCKR(端口配置锁定寄存器) 
该寄存器用来锁定端口位的配置。位[15:0]用于锁定GPIO端口的配置。在规定的写入操作期间,不能改变LCKP[15:0]。当对相应的端口位执行了LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。 每个锁定位锁定控制寄存器(CRL,CRH)中相应的4个位(CNF2位和MODE2位)。 第16位用来激活锁定寄存器,必须按照规定的时序来操作才行: 写1 -> 写0 -> 写1 -> 读0 -> 读1。 对0-15 位: 0:不锁定对应端口的配置。 1:锁定对应端口的配置
五 总体架构和时钟系统
5.1 STM32总体架构
3个被动单元
内部SRAM:存储程序执行时用到的变量(相当于电脑的运存)
内部闪存存储器(Flash):1.存储下载的程序 2.程序执行时用到的常量
AHB(高速系统总线)到APBx(外设总线,APB2高速 APB1低速)的桥:
桥1,通过APB2总线连接到APB2上的外设。高速外设最高72MHz。
桥2,通过APB1总线连接到APB1上的外设。高速外设最高36MHz。
4个驱动(主动)单元(主动的访问三个被动单元)
Cortex-M3内核DCode总线(D-BUS):通过外部的DCode总线连接到总线矩阵然后与闪存存储器的数据接口相连接,实现Flash常量加载和调试访问。
Cortex-M3内核系统总线(S-bus):通过外部System总线连接到总线矩阵
通用DMAx(Direct Memory Access):通过DMA总线,连接到总线矩阵。作用就是降低CPU负担,不通过CPU实现内存和外设之间的数据传输。
其他单元
Cortex-M3内核Icode总线:通过外部Icode总线连接到Flash,实现指令的读取。
FSMC(Flexible Static Memory Controller):用来扩展SRAM,Flash,连接LCD屏幕。
5.2 时钟系统
5.2.1 时钟树
在STM32中有3种不同的时钟源用来驱动系统时钟(SYSCLK):
(1)HSI振荡器时钟(High Speed Internal oscillator,高速内部时钟)
(2)HSE振荡器时钟(High Speed External(Oscillator / Clock),高速外部时钟)
(3)PLL时钟(Phase Locked Loop 锁相环/倍频器) 还有2种2级时钟:
(4)LSI时钟(Low Speed Internal,低速内部时钟)
(5)LSE时钟(Low Speed External oscillator,低速外部时钟)。
为什么提供这么多的时钟?节能!高速设备接高速时钟,低速设备接低速时钟,可以最 大程度的达到节能效果。详见下图时钟树
5.2.2 各个时钟介绍
1)HSE时钟
高速外部时钟是由外部时钟源提供,目前几乎所有的STM32单片机的设计都是在外部 接一个8MHz的晶振,经过PLL倍频(9倍频)后得到一个72MHz的系统时钟。我们系统 默认就是这个时钟。这个在启动文件可以看到。
2)HSI时钟
HSI 时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为 PLL 输入。HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。
3)PLL时钟
内部PLL用来倍频HSI RC的输出时钟或HSE晶体输出时钟。PLL的设置必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。 PLL 时钟一般都是对外部的8MHz的时钟信号经过9倍频后,得到72MHz的时钟频率, 这是STM32F1系列允许的最高时钟频率
4)LSE时钟
LSE 晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时 功能提供一个低功耗且精确的时钟源。 LSE 是不能驱动系统时钟的
5)LSI 时钟
LSI RC 担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立 看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间)。 LSI 也是不能驱动系统时钟的
六 HAL库入门
6.1 HAL 介绍
1)为什么要学习HAL库开发
到目前我们已经学会1种开发SMT32程序的姿势:基于寄存器开发。 寄存器开发效率比较低,在工作中如果想快速开发还是要会用到HAL库开发。 HAL库是目前ST主力推广的开发方式,所以你如果还要用ST的芯片,会使用HAL 库开发是势在必行的。而且最最关键的是,使用HAL开发有诸多的好处。 目前,HAL库已经支持STM32全线产品。
2)什么是HAL库
HAL库的全称是Hardware Abstraction Layer,翻译成硬件抽象层。HAL库是ST为STM32 最新推出的抽象层嵌入式软件,可以更好的确保跨STM32产品的最大可移植性。该库提供 了一整套一致的中间件组件,如RTOS,USB,TCP/IP和图形等。 HAL库是基于一个非限制性的BSD许可协议(Berkeley Software Distribution)而发布的开源代码。 ST制作的中间件堆栈(USB主机和设备库,STemWin)带有允许轻松重用的许可模式,只要是在ST公司的MCU芯片上使用,库中的中间件(USB 主机/设备 库,STemWin)协议栈即被允许随便修改,并可以反复使用。至于基于其它著名的开源解决方 案商的中间件(FreeRTOS,FatFs,LwIP和PolarSSL)也都具有友好的用户许可条款。
3)CMSIS和HAL库的关系
CMSIS(Cortex Microcontroller Software Interface Standard)是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。它是ARM公司制定的一个标准。它可以为处理器和外设实现 一致且简单的软件接口,从而简化软件的重用、缩短微控制器新开发人员的学习过程,并缩 短新设备的上市时间。简单来说,就是ARM公司制定标准,芯片厂商按照此标准编写相应 的程序,实现统一的接口,方便开发人员的使用
七 STM32中断系统
7.1 中断概述
7.1.1 中断的概念
在主程序运行过程中,出现了特定事件,使得CPU暂停当前正在运行的程序,转而去处理这个事件,等这个事件处理完成之后,CPU再回到刚才被打断的位置继续处理,这就是中断。
那个打断CPU执行的特定事件,我们一般称之为中断源。被中断源打断的位置我们称为断点。处理特定事件的过程,我们称为执行中断处理程序。
正在执行中断程序的时候,这个时候有可能被另外一个中断源给中断,CPU转而去执行另外一个中断源的中断处理程序,这叫中断嵌套。
中断B能否打断中断A,要看他们的优先级,优先级高的可以打断优先级低的,优先级低的无法打断优先级高的。
中断源可以是外部的,也可以是内部的。外部的叫外部中断源,内部的叫内部中断源 (有时候也叫异常)。
7.1.2 为什么需要中断
对单片机系统来说,中断至关重要。
比如我们要检测按键是否按下,如果没有中断,则需要循环的方式不断的去检测按键对应的IO口的电平,这是比较耗费CPU的时间的。如果要检测的更多的话,CPU有可能会导致阻塞。
有了中断事情就变的简单了,主程序不需要循环不断的去检测按键,当有按键按下的时候,CPU执行被打断,去执行按键处理程序就行了。当没有按键按下的时候,CPU完全可以正常执行代码,丝毫不受任何的影响
7.1.3 STM32 的中断
Cortext-M3 内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。 一般情况下,芯片厂商会对Cortex-M3的中断进行裁剪。
STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。 STM32F103 系列70个中断(咱们目前使用的芯片)有10个内核中断和60个可编程的外部中断。 下面的列表中,灰色背景的是内部中断(或者异常),其他的为外部中断。
下面的列表中,灰色背景的是内部中断(或者异常),其他的为外部中断。
7.1.4 STM32 的中断体系架构
7.1.5 NVIC 嵌套向量中断控制器
1)NVIC的介绍
NVIC(Nested vectored interrupt controller 嵌套向量中断控制器)和处理器核的接口紧密相连,可以实现低延迟的中断处理和高效地处理中断。嵌套向量中断控制器管理着包括内核异常,外部中断等所有中断。由NVIC决定哪个中断的处理程序交给CPU来执行。
每一个外部中断都可以被使能或者禁止,并且可以被设置为挂起状态(挂起:中断发生来不及执行先等待)或者清除状态。处理器的中断可以电平的形式的,也可以是脉冲形式(高->低或者低->高)的,这样中断控制器就可以处理任何中断源。
16 个IO的中断与PVD(电源电压检测),RTC(实时时钟),USB,以太网检测这20个外部中断会通过EXTI来控制,然后交给NVIC。其他中断都是直接交给NVC来处理。
2)中断优先级
NVIC 为了方便管理中断,可以通过软件给每个中断设置优先级。NVIC用4个位来控制优先级,值小的优先级高。把优先级分为两种:抢占优先级和响应优先级。
规则:
优先级值越小,优先级越高。
如果不设置优先级,则默认优先级为0。
先比较抢占优先级。抢占优先级高的可以打断抢占优先级低的。
若抢占优先级一样,再比较响应优先级。但是响应优先级不会导致中断嵌套。
若抢占优先级一样的同时挂起,则优先处理响应抢占优先级高的。
若挂起的优先级(抢占和响应)都一样,则查找中断向量表,值小的先响应。
NVIC 对优先级分了5组,在程序中先对中断进行分组,而且分组只能分一次,若多次分,只有最后一次生效。
7.1.6 外部中断控制器(EXTI)

八 USART串口通讯
8.1 通讯基础知识
8.1.1 通讯方式
按数据传送方式,通讯可分为:
串行通讯:按位传输,一次传输一位。
并行通讯:多位同时传输。比如8位32位。
按信息的传输方向:
单工:数据只能从A->B
半双工:数据可以从A->B,也可以从B->A同一时间只能一个方向
全双工:数据可以从A->B,也可以从B->A同一时间可以两个方向传输
按数据同步方式:
同步:有共同时钟
异步:无共同时钟
8.1.2 串口介绍
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息
8.1.3 串口通讯协议
1)波特率
“波特率”(Baudrate),它表示每秒钟传输了多少个码元。在二进制的世界码元和位是等价的。用每秒传输的比特数表示波特率。
STM32 提供的是串口异步通讯,异步通讯中由于没有时钟信号,所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为 4800、9600、 115200 等。
2)空闲位
串口协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据。
3)通讯的起始位
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。 因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
4)通讯的停止位
停止信号可由 0.5、1、1.5 或 2个逻辑1的数据位表示,只要双方约定一致即可。 5)有效数据位 在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或8位长。构成一个字符(一般都是8位)。先发送最低位, 最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
5)有效数据位
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或8位长。构成一个字符(一般都是8位)。先发送最低位, 最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
6)校验位
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。串口校验分几种方式:
(1)无校验(no parity)。
(2)奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果 “1”的数目是奇数,校验位为“0”。
(3)偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果 为奇数,校验位为“1”。
8.2 USART 外设
STM32 提供了USART(Universal Synchronous Asynchronous Receiver and Transmitter) 通用同步异步收发器。是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。
还有UART相比USART去掉了同步通讯功能。
STM32 的USART功能框图如下:
1)功能引脚说明
TX:发送数据输出引脚。
RX:接收数据输入引脚。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。 nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能RTS流控制, 当 USART接收器准备好接收新数据时就会将nRTS变成低电平;当接收寄存器已满时, nRTS将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能CTS流控制,发送器在发送下一帧数据之前会检测nCTS引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
2)波特率的产生
发送器和接收器的波特率是一致的,都是通过设置BRR寄存器来得到
这里的fck是给外设的时钟 ,看时钟树( usart1 在 APB2 上一般是72MHz,usart2,3,4,5 在APB1上一般为36MHz)。
假设我们需要的波特率是115200,则对应的分频值应该是:39.0625,把这个值写入到 BRR寄存器中。39.0625的小数部分:0.0625 * 16 = 1, 整数部分是:39(0x27)。
3)相关寄存器
8.3 串口案例1:计算机和串口通
8.3.1 需求描述
电脑通过串口向STM32发送数据,STM32原封不动的再发送过来。电脑可以借助串口助手来发送或接受数据。
8.3.2 硬件电路设计
目前很多电脑已经没有串口接口了,为了使用串口,STLink2.1拥有 USB转串口的功能
8.3.3 软件设计:轮询的方式接收(寄存器)
Driver_USART.c
#include "Driver_SUART.h"
void Driver_USART1_Init(void)
{
/* 1.开启时钟 */
/* 1.1 串口1外设的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
/* 1.2 GPIO时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2.配置GPIO引脚的工作模式
PA9=Tx(复用推挽:CNF=10,MODE=11)
PA10=Rx(浮空输入:CNF=01 MODE=00) */
GPIOA->CRH |= GPIO_CRH_CNF9_1;
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_MODE9;
GPIOA->CRH &= ~GPIO_CRH_CNF10_0;
GPIOA->CRH |= GPIO_CRH_CNF10_1;
GPIOA->CRH &= ~GPIO_CRH_MODE10;
/* 3.串口的参数配置 */
/* 3.1 配置波特率 115200 */
USART1->BRR = 0x271;
/* 3.2 配置一个字的长度 8位 */
USART1->CR1 &= ~USART_CR1_M;
/* 3.3 配置不需要校验位 */
USART1->CR1 &= ~USART_CR1_PCE;
/* 3.4 配置停止位的长度 */
USART1->CR2 &= ~USART_CR2_STOP;
/* 3.5 使能接收和发送 */
USART1->CR1 |= (USART_CR1_TE+USART_CR1_RE);
/* 4 使能串口 */
USART1->CR1 |= USART_CR1_UE;
}
/* 发送一个字节 */
void Driver_USART1_SendChar(uint8_t byte)
{
/* 1.等待发送寄存器为空 */
while((USART1->SR & USART_SR_TXE) == 0)
;
USART1->DR=byte;
}
/* 接收一个字符串 */
void Driver_USART1_SendString(uint8_t *str,uint16_t len)
{
for(int16_t i =0;i<len;i++)
{
Driver_USART1_SendChar(str[i]);
}
}
/* 接收一个字节 */
uint8_t Driver_USART1_ReceiveChar(void)
{
/* 等待数据寄存器非空 RXNE位:读数据寄存器非空 0:数据没有收到 1:数据收到 */
while((USART1->SR & USART_SR_RXNE) == 0)
;
return USART1->DR;
}
/* 接收变长数据,接收到的数据存入到buff中 IDLE位:监测到总线空闲 0:没有检测到总线空闲 1:检测到空闲总线*/
void Driver_USART1_ReceiveString(uint8_t buff[],uint8_t *len)
{
uint8_t i=0;
while(1)
{
//等待接收非空
while((USART1->SR & USART_SR_RXNE)==0)
{
//判断是否受到空闲帧
if(USART1->SR & USART_SR_IDLE)
{
*len = i;
return;
}
}
buff[i] = USART1->DR;
i++;
}
}
8.3.4 软件设计:中断的方式接收(寄存器)
#include "Driver_SUART.h"
void Driver_USART1_Init(void)
{
/* 1.开启时钟 */
/* 1.1 串口1外设的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
/* 1.2 GPIO时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2.配置GPIO引脚的工作模式 PA9=Tx(复用推挽:CNF=10,MODE=11) PA10=Rx(浮空输入:CNF=01 MODE=00) */
GPIOA->CRH |= GPIO_CRH_CNF9_1;
GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
GPIOA->CRH |= GPIO_CRH_MODE9;
GPIOA->CRH &= ~GPIO_CRH_CNF10_0;
GPIOA->CRH |= GPIO_CRH_CNF10_1;
GPIOA->CRH &= ~GPIO_CRH_MODE10;
/* 3.串口的参数配置 */
/* 3.1 配置波特率 115200 */
USART1->BRR = 0x271;
/* 3.2 配置一个字的长度 8位 */
USART1->CR1 &= ~USART_CR1_M;
/* 3.3 配置不需要校验位 */
USART1->CR1 &= ~USART_CR1_PCE;
/* 3.4 配置停止位的长度 */
USART1->CR2 &= ~USART_CR2_STOP;
/* 3.5 使能接收和发送 */
USART1->CR1 |= (USART_CR1_TE+USART_CR1_RE);
/* 3.6 使能串口的各种中断
TXEIE位:接收缓冲区非空中断使能
0:禁止产生中断
1:当USART_SR中的ORE或者RXNE为’1’时,产生USART中断。
IDLEIE位:IDLE中断使能
0:禁止产生中断;
1:当USART_SR中的IDLE为’1’时,产生USART中断。*/
USART1->CR1 |= USART_CR1_RXNEIE; //接收缓冲区非空中断
USART1->CR1 |= USART_CR1_IDLEIE; //总线空闲中断
/* 4 配置NVIC */
/* 4.1 配置优先级组 */
NVIC_SetPriorityGrouping(3);
/* 4.2 设置优先级 */
NVIC_SetPriority(USART1_IRQn,2);
/* 4.3 使能串口1的中断 */
NVIC_EnableIRQ(USART1_IRQn);
/* 5 使能串口 */
USART1->CR1 |= USART_CR1_UE;
}
/* 发送一个字节 */
void Driver_USART1_SendChar(uint8_t byte)
{
/* 等待发送寄存器为空
TXE位:发送数据寄存器空
0:数据还没有被转移到移位寄存器;
1:数据已经被转移到移位寄存器。*/
while((USART1->SR & USART_SR_TXE) == 0)
;
USART1->DR=byte;
}
/* 接收一个字符串 */
void Driver_USART1_SendString(uint8_t *str,uint16_t len)
{
for(int16_t i =0;i<len;i++)
{
Driver_USART1_SendChar(str[i]);
}
}
/* 接收一个字节 */
uint8_t Driver_USART1_ReceiveChar(void)
{
/* 等待数据寄存器非空
RXNE位:读数据寄存器非空
0:数据没有收到
1:数据收到 */
while((USART1->SR & USART_SR_RXNE) == 0)
;
return USART1->DR;
}
/* 接收变长数据,接收到的数据存入到buff中
IDLE位:监测到总线空闲
0:没有检测到总线空闲
1:检测到空闲总线*/
void Driver_USART1_ReceiveString(uint8_t buff[],uint8_t *len)
{
uint8_t i=0;
while(1)
{
//等待接收非空
while((USART1->SR & USART_SR_RXNE)==0)
{
//判断是否受到空闲帧
if(USART1->SR & USART_SR_IDLE)
{
*len = i;
return;
}
}
buff[i] = USART1->DR;
i++;
}
}
/* 缓冲接收到的数据 */
uint8_t buff[100] = {0};
/* 存储接收到的字节的长度 */
uint8_t len = 0;
uint8_t isToSend = 0;//标志位
void USART1_IRQHandler(void)
{
/* 数据接收寄存器非空,接收缓冲区非空中断 接收到了数据 */
if(USART1->SR & USART_SR_RXNE)
{
//对USART_DR的读操作可以将接收非空的中断位RXNE清零,不需要再手动清零
//USART1->SR &= ~USART_SR_RXNE;
buff[len]=USART1->DR;
len++;
}
/* 总线空闲中断 变长数据接收完毕*/
else if(USART1->SR & USART_SR_IDLE)
{
/* 当检测到总线空闲时,IDLE该位被硬件置位。
如果USART_CR1中的IDLEIE为’1’,则产生中断。
由软件序列清除该位(先读USART_SR,然后读USART_DR)。 */
USART1->SR;
USART1->DR;
isToSend=1;//Driver_USART1_SendString(buff,len)耗时操作,用标志位代替
/* 把接受字节长度清零*/
//len=0;
}
}
main.c
#include "Driver_LED.h"
#include "Driver_SUART.h"
/* 缓冲接收到的数据 */
extern uint8_t buff[100];
/* 存储接收到的字节的长度 */
extern uint8_t len;
extern uint8_t isToSend;//标志位
int main()
{
Driver_USART1_Init();
//Driver_USART1_SendChar('a');
while(1)
{
// uint8_t *str=("Hello World!");
// Driver_USART1_SendString(str,strlen((char *)str));
// Delay_s(1);
// uint8_t c=Driver_USART1_ReceiveChar();
// Driver_USART1_SendChar(c);
// Driver_USART1_ReceiveString(buff,&len);
// Driver_USART1_SendString(buff,len);
if(isToSend)
{
Driver_USART1_SendString(buff,len);
isToSend=0;
len=0;
}
}
}
九 I2C 通讯
9.1 I2C基础知识
I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
是一种简单的双向两线制总线协议标准,支持同步串行半双工通讯。
标准模式传输速率100kbit/s,快速模式为400kbit/s,高速模式下可达3.4Mbit/s。
发送方等待应答 接收方发送应答
9.2 I2C案例1:软件模拟I2C
EEPROM芯片最常用的通讯方式就是I2C协议。我们使用的芯片是M24C02
9.2.1 需求描述
我们向E2PROM写入一段数据,再读取出来,最后发送到串口,核对是否读写正确
9.2.2 硬件电路设计
9.2.2.1 硬件原理图

9.2.2.2 M24C02 简介
M24C02 的SCL及SDA 引脚连接到了STM32对应的I2C引脚中,结合上拉电阻,构成了I2C通讯总线,它们通过I2C总线交互。
E2PROM芯片的设备地址一共有7位,其中高4位固定为:1010,低3位则由E3/E2/E1 信号线的电平决定E2PROM设备地址。 R/W是读写方向控制位,与地址无关。
在我们电路图中由于E1/E2/E3均是接的低电平,所以它的地址是1010000即0x50。
由于I2C通讯时常常是地址跟读写方向连在一起构成一个8位数,且当R/W位为0 时, 表示写方向,所以加上7位地址,其值为“0xA0”,常称该值为I2C设备的“写地址”。当R/W位为1时,表示读方向,加上7位地址,其值为“0xA1”,常称该值为“读地址”
9.2.3 操作时序图整理
1)起始和停止信号
芯片内部的东西放驱动层,芯片外部与硬件相关放硬件接口层
2)数据有效性
3)响应和非响应
4)写入一个字节时序
5)读出一个字节时序
6)单次写入多个字节时序一次性写入多个字节,也叫页写入(Page Write)。W24C02每页只有16个字节,每次只能写入单独的一个页中,所以一次性最多只能写入16个字节。当一次性写入超过16个 字节的时候,则超过的部分会重新从这页的首地址重新写入
7)单次读出多个字节时序读出多个字节的时候没有限制,可以读出任意多个
代码实现
I2C2.C
#include "Driver_I2C2.h"
#include "Delay.h"
#define I2C_DELAY Delay_us(10)
/* 初始化 */
void Driver_I2C2_Init(void)
{
/* PB10->SCL CNF=01 MODE=11
多个设备时,此STM32可能作为主设备(SCL由此设备生成)也可能作为从设备(SCL由主设备生成)
开漏输出:既可以用于输出也可以用于输入
用作输出的时候,可以输出低电平,输出1时由上拉电阻拉至高电平(外界要有上拉电阻)
用于输入的时候,先输出1(MOS管都不导通),把线权交给外界
PB11->SDA 同理
CNF=01 MODE=11
*/
RCC->APB2ENR|=RCC_APB2ENR_IOPBEN;
GPIOB->CRH &= ~(GPIO_CRH_CNF10_1|GPIO_CRH_CNF11_1);
GPIOB->CRH |= (GPIO_CRH_CNF10_0|GPIO_CRH_MODE10|GPIO_CRH_CNF11_0|GPIO_CRH_MODE11);
}
/* 起始信号 */
void Driver_I2C2_Start(void)
{
/* 1.拉高SDA和SCL */
SDA_HIGH;
SCL_HIGH;
/* 2.延时 */
I2C_DELAY;
/* 3.拉低SDA*/
SDA_LOW;
/* 3.延时 */
I2C_DELAY;
}
/* 停止信号 */
void Driver_I2C2_Stop(void)
{
/* 1.SCL拉高 SDA拉低 */
SCL_HIGH;
SDA_LOW;
/* 2.延时 */
I2C_DELAY;
/* 3.SDA拉高 */
SDA_HIGH;
/* 4.延时 */
I2C_DELAY;
}
/* 接收方产生应答 */
void Driver_I2C2_Ack(void)
{
/* 1.拉高SDA和拉低SCL */
SDA_HIGH;
SCL_LOW;
/* 2.延时 */
I2C_DELAY;
/* 3.拉低SDA */
SDA_LOW;
/* 4.延时 */
I2C_DELAY;
/* 5.拉高SCL */
SCL_HIGH;
/* 6.延时 */
I2C_DELAY;
/* 7.拉低SCL */
SCL_LOW;
/* 8.延时 */
I2C_DELAY;
/* 9.拉高SDA */
SDA_HIGH;
/* 10.延时 */
I2C_DELAY;
}
/* 接收方产生非应答 */
void Driver_I2C2_NAck(void)
{
/*1.拉高SDA和拉低SCL */
SDA_HIGH;
SCL_LOW;
/*2.延时 */
I2C_DELAY;
/*3.拉高SCL */
SCL_HIGH;
/*4.延时 */
I2C_DELAY;
/*5.拉低SCL */
SCL_LOW;
/*6.延时 */
I2C_DELAY;
}
/* 等待接收方的应答 */
uint8_t Driver_I2C2_WaitAck(void)
{
/* 1.SDA拉高,SDA的主动权交给从机(EEPROM)*/
SDA_HIGH;
/* 2.拉低SCL*/
SCL_HIGH;
/* 3.延时 */
I2C_DELAY;
/* 4.拉高SCL */
SCL_HIGH;
/* 5.延时 */
I2C_DELAY;
/* 6.读SDA电平 */
uint8_t ack=ACK;
if(READ_SDA)
{
ack=NACK;
}
/* 7.拉低SCL */
SCL_LOW;
/* 8.延时 */
I2C_DELAY;
return ack;
}
/* 发送一个字节数据*/
void Driver_I2C2_SendByte(uint8_t byte)
{
for (uint8_t i = 0; i < 8; i++)
{
/* 1.SDA和SCL拉低 */
SDA_LOW;
SCL_LOW;
/* 2.延时 */
I2C_DELAY;
/* 3.数据放到SDA */
if(byte & 0x80)
{
SDA_HIGH;
}
else
{
SDA_LOW;
}
I2C_DELAY;
/* 4.SCL时钟拉高 */
SCL_HIGH;
/* 5.延时 */
I2C_DELAY;
/* 6.SCL拉低 */
SCL_LOW;
I2C_DELAY;
/* 7.左移1位,为下一次发送做准备 */
byte<<=1;
}
}
/* 读一个字节的数据 */
uint8_t Driver_I2C2_ReadByte(void)
{
uint8_t data=0;
for(uint8_t i=0;i<8;i++)
{
/* 1.拉低SCL */
SCL_LOW;
/* 2.延时 */
I2C_DELAY;
/* 3.拉高SCL */
SCL_HIGH;
I2C_DELAY;
/* 4.读一位数据 */
data<<=1; //第一次data=0,左移后还是0;
if(READ_SDA)
{
data |= 0x01;
}
/* 5.拉低SCL */
SCL_LOW;
/* 6.延时 */
I2C_DELAY;
}
return data;
}
I2C.h
#ifndef __DRIVER_I2C2_H
#define __DRIVER_I2C2_H
#include "Delay.h"
#include "stm32f10x.h"
#include "stdio.h"
#define ACK 0
#define NACK 1
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
#define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
void Driver_I2C2_Init(void);
void Driver_I2C2_Start(void);
void Driver_I2C2_Stop(void);
void Driver_I2C2_Ack(void);
void Driver_I2C2_NAck(void);
uint8_t Driver_I2C2_WaitAck(void);
void Driver_I2C2_SendByte(uint8_t byte);
uint8_t Driver_I2C2_ReadByte(void);
#endif
W24C02.c
#include "Inf_W24C02.h"
void Inf_W24C02_Init(void)
{
Driver_I2C2_Init();
}
void Inf_W24C02_WriteByte(uint8_t innerAddr,uint8_t byte)
{
/* 1.开始信号 */
Driver_I2C2_Start();
/* 2.发送写地址 */
Driver_I2C2_SendByte(ADDR);
/* 3.等待响应 */
uint8_t ack=Driver_I2C2_WaitAck();
if(ack==ACK) //响应
{
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 5.等待响应 */
Driver_I2C2_WaitAck();
/* 6.发送具体数据 */
Driver_I2C2_SendByte(byte);
/* 7.等待响应 */
Driver_I2C2_WaitAck();
/* 8.停止信号 */
Driver_I2C2_Stop();
}
Delay_ms(5);
}
uint8_t Inf_W24C02_ReadByte(uint8_t innerAddr)
{
/* 1.起始信号 */
Driver_I2C2_Start();
/* 2.发送一个写地址(假写)*/
Driver_I2C2_SendByte(ADDR);
/* 3.等待响应 */
Driver_I2C2_WaitAck();
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 5.等待响应 */
Driver_I2C2_WaitAck();
/* 6.起始信号 */
Driver_I2C2_Start();
/* 7.发送读地址(真读)*/
Driver_I2C2_SendByte(ADDR+1);
/* 8.等待响应 */
Driver_I2C2_WaitAck();
/* 9.读取一个字节 */
uint8_t byte = Driver_I2C2_ReadByte();
/* 10.给对方一个非应答 */
Driver_I2C2_NAck();
/* 11.停止信号 */
Driver_I2C2_Stop();
return byte;
}
/* 多字节写入 */
void Inf_W24C02_WriteBytes(uint8_t innerAddr,uint8_t *bytes,uint8_t len)
{
/* 1.开始信号 */
Driver_I2C2_Start();
/* 2.发送写地址 */
Driver_I2C2_SendByte(ADDR);
/* 3.等待响应 */
uint8_t ack=Driver_I2C2_WaitAck();
if(ack==ACK) //响应
{
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 5.等待响应 */
Driver_I2C2_WaitAck();
for(uint8_t i=0;i<len;i++)
{
/* 6.发送具体数据 */
Driver_I2C2_SendByte(bytes[i]);
/* 7.等待响应 */
Driver_I2C2_WaitAck();
}
/* 8.停止信号 */
Driver_I2C2_Stop();
}
Delay_ms(5);
}
/* 读多个字节 */
void Inf_W24C02_ReadBytes(uint8_t innerAddr,uint8_t *bytes,uint8_t len)
{
/* 1.起始信号 */
Driver_I2C2_Start();
/* 2.发送一个写地址(假写)*/
Driver_I2C2_SendByte(ADDR);
/* 3.等待响应 */
Driver_I2C2_WaitAck();
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 5.等待响应 */
Driver_I2C2_WaitAck();
/* 6.起始信号 */
Driver_I2C2_Start();
/* 7.发送读地址(真读)*/
Driver_I2C2_SendByte(ADDR+1);
/* 8.等待响应 */
Driver_I2C2_WaitAck();
for(uint8_t i=0;i<len;i++)
{
/* 9.读取一个字节 */
bytes[i]= Driver_I2C2_ReadByte();
if(i < len-1)
{
Driver_I2C2_Ack();
}
else
{
Driver_I2C2_NAck();
}
}
/* 11.停止信号 */
Driver_I2C2_Stop();
}
W24C02.h
#ifndef __INF_W24C02_H
#define __INF_W24C02_H
#include "Driver_I2C2.h"
#define ADDR 0xA0 //W24C02地址(最后读写位此时为0)
void Inf_W24C02_Init(void);
void Inf_W24C02_WriteByte(uint8_t innerAddr, uint8_t byte);
uint8_t Inf_W24C02_ReadByte(uint8_t innerAddr);
void Inf_W24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t len);
void Inf_W24C02_ReadBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t len);
#endif
9.3 I2C 案例 2:硬件实现I2C
9.3.1 需求描述
使用STM32的I2C外设读写E2PROM,基于寄存器操作。不需要手动控制引脚电平的输入输出,只需要操作I2C外设对应的寄存器即可。
9.3.2 硬件电路设计
9.3.2.1 I2C 外设简介
前面我们用软件模拟I2C协议实现了通讯,代码写起来比较复杂。
起始STM32有专门负责协议的I2C外设,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器, 就能完成数据收发。
这种由硬件外设处理 I2C 协议的方式减轻了CPU的工作,且使软件设计更加简单。
STM32 的 I2C 外设可用作通讯的主机及从机,支持100Kbit/s和400Kbit/s的速率,支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。
它的I2C外设还支持 SMBus2.0协议,SMBus协议与I2C类似
9.3.2.2 STM32的I2C外设的功能框图
I2C的所有硬件架构都是根据图中左侧SCL线和SDA线展开的(其中的SMBA线用于 SMBUS的警告信号,I2C通讯没有使用)。STM32芯片有多个I2C外设,STM32F103C8T6有2个I2C外设,它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚
I2C.c
#include "Driver_I2C2.h"
#include "Delay.h"
#define I2C_DELAY Delay_us(10)
/* 初始化 */
void Driver_I2C2_Init(void)
{
/* 1.开启时钟信号 */
/* 1.1 I2C硬件的时钟 */
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
/* 1.2 GPIO时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
/* 2.设置GPIO引脚的工作模式*/
/* PB10->SCL CNF=01 MODE=11
多个设备时,此STM32可能作为主设备(SCL由此设备生成)也可能作为从设备(SCL由主设备生成)
复用开漏输出:既可以用于输出也可以用于输入
用作输出的时候,可以输出低电平,输出1时由上拉电阻拉至高电平(外界要有上拉电阻)
用于输入的时候,先输出1(MOS管都不导通),把线权交给外界
PB11->SDA 同理
CNF=11 MODE=11
*/
GPIOB->CRH |= (GPIO_CRH_CNF10|GPIO_CRH_MODE10|GPIO_CRH_CNF11|GPIO_CRH_MODE11);
/* 3.设置I2C2 */
/* 3.1 配置硬件工作模式
SMBUS位:SMBus模式 0=I2C模式 1=SMBus模式*/
I2C2->CR1 &=~I2C_CR1_SMBUS;
/* 3.2 配置给I2C设备提供时钟的频率
FREQ[5:0]:I2C模块时钟频率fcplk*/
I2C2->CR2 |= 36<<0;
/* 3.3 配置主模式选项
F/S:I2C主模式选项 0:标准模式 1:快速模式 */
I2C2->CCR &= ~I2C_CCR_FS;
/* 3.4 配置I2C产生时钟的频率 100K or 400K
CCR:快速/标准模式下的时钟控制分频系数,该分频系数用于设置主模式下的SCL时钟
Thigh=CCR*Tcplk1*/
I2C2->CCR |= 180<<0;
/* 3.5 时钟信号的上升沿
TRISE:在快速/标准模式下的最大上升时间(主模式)
最大上升沿时间/时钟频率Tcplk再加1*/
I2C2->TRISE |= 37;
/* 3.5 使能I2C */
I2C2->CR1 |= I2C_CR1_PE;
}
/* 起始信号
START位:起始条件产生
SB位:起始位(主模式):0=未发送起始条件 1=起始条件已发送*/
uint8_t Driver_I2C2_Start(void)
{
I2C2->CR1 |= I2C_CR1_START;
uint16_t timeout = 0xffff;
while (((I2C2->SR1 & I2C_SR1_SB)== 0) && timeout)
{
timeout--;
}
return timeout ? SUCCESS : FAIL;//timeout非0返回SUCCESS否则返回FAIL
}
/* 停止信号
STOP:停止条件检产生(主模式)*/
void Driver_I2C2_Stop(void)
{
I2C2->CR1 |= I2C_CR1_STOP;
}
/* 接收方产生应答
ACK:应答使能
0:无应答返回;1:再接收到一个字节后返回一个应答*/
void Driver_I2C2_Ack(void)
{
I2C2->CR1 |=I2C_CR1_ACK;
}
/* 接收方产生非应答 */
void Driver_I2C2_NAck(void)
{
I2C2->CR1 &=~I2C_CR1_ACK;
}
/* 发送设备地址
ADDR:地址已发送(主模式)/地址匹配(从模式)
读SR1和SR2后清除该位或当PE=0
TxE:数据寄存器位空(发送时)*/
uint8_t Driver_I2C2_SendAddr(uint8_t addr)
{
I2C2->DR = addr;
uint16_t timeout = 0xffff;
while(((I2C2->SR1 & I2C_SR1_ADDR)== 0) && timeout)
{
timeout--;
}
if(timeout)
{
I2C2->SR2;
}
return timeout ? SUCCESS : FAIL;
}
/* 发送一个字节数据
DR:8位数据寄存器,用于存放接收到的数据或放置用于发送到总线的数据
TxE:数据寄存器位空(发送时)
BTF:字节发送结束*/
uint8_t Driver_I2C2_SendByte(uint8_t byte)
{
uint16_t timeout = 0xffff;
while (((I2C2->SR1 & I2C_SR1_TXE)== 0) && timeout)
{
timeout--;
}
I2C2->DR = byte;
timeout = 0xffff;
while(((I2C2->SR1 & I2C_SR1_BTF)== 0) && timeout)
{
timeout--;
}
return timeout ? SUCCESS : FAIL;
}
/* 读一个字节的数据
RxNE:数据寄存器非空(接收时)*/
uint8_t Driver_I2C2_ReadByte(void)
{
uint16_t timeout = 0xffff;
while(((I2C2->SR1 & I2C_SR1_RXNE)== 0) && timeout)
{
timeout--;
}
uint8_t data = timeout ? I2C2->DR : 0;
return data;
}
W24C02.c
#include "Inf_W24C02.h"
void Inf_W24C02_Init(void)
{
Driver_I2C2_Init();
}
void Inf_W24C02_WriteByte(uint8_t innerAddr,uint8_t byte)
{
/* 1. 开始信号 */
Driver_I2C2_Start();
/* 2. 发送写地址 */
Driver_I2C2_SendAddr(ADDR);
/* 4. 发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 6. 发送具体数据 */
Driver_I2C2_SendByte(byte);
/* 8. 停止信号 */
Driver_I2C2_Stop();
Delay_ms(5);
}
uint8_t Inf_W24C02_ReadByte(uint8_t innerAddr)
{
/* 1.起始信号 */
Driver_I2C2_Start();
/* 2.发送一个写地址(假写)*/
Driver_I2C2_SendAddr(ADDR);
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 6.起始信号 */
Driver_I2C2_Start();
/* 7.发送读地址(真读)*/
Driver_I2C2_SendAddr(ADDR+1);
/* 9.读取一个字节 */
uint8_t byte = Driver_I2C2_ReadByte();
/* 11.停止信号 */
Driver_I2C2_Stop();
return byte;
}
/* 多字节写入 */
void Inf_W24C02_WriteBytes(uint8_t innerAddr,uint8_t *bytes,uint8_t len)
{
/* 1.开始信号 */
Driver_I2C2_Start();
/* 2.发送写地址 */
Driver_I2C2_SendAddr(ADDR);
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
for(uint8_t i=0;i<len;i++)
{
/* 6.发送具体数据 */
Driver_I2C2_SendByte(bytes[i]);
}
/* 8.停止信号 */
Driver_I2C2_Stop();
Delay_ms(5);
}
/* 读多个字节 */
void Inf_W24C02_ReadBytes(uint8_t innerAddr,uint8_t *bytes,uint8_t len)
{
/* 1.起始信号 */
Driver_I2C2_Start();
/* 2.发送一个写地址(假写)*/
Driver_I2C2_SendAddr(ADDR);
/* 4.发送内部地址 */
Driver_I2C2_SendByte(innerAddr);
/* 6.起始信号 */
Driver_I2C2_Start();
/* 7.发送读地址(真读)*/
Driver_I2C2_SendAddr(ADDR+1);
for(uint8_t i=0;i<len;i++)
{
if(i < len-1)
{
Driver_I2C2_Ack();
}
else
{
Driver_I2C2_NAck();
/* 11.停止信号 */
Driver_I2C2_Stop();
}
/* 硬件执行速度问题需要先设置应答 */
bytes[i]= Driver_I2C2_ReadByte();
}
}
十 定时器
10.1 系统定时器
10.1.1 系统定时器简介
系统定时器(SysTick系统)是属于CM3内核,内嵌在NVIC中。
系统定时器是一个24bit的向下递减的计数器,计数器每计数一次的时间为1 / SYSCLK,一般我们设置系统时钟SYSCLK(与AHB相同)等于72M。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。
SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间SysTick的处理方式都是相同的。
系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。
10.1.2 与定时器有关的寄存器
1)CTRL
位31:17 保留位 - 必须保持清零状态。
位16 COUNTFLAG(计数完成标志):
返回1表示自上次读取该位后,定时器已计数到0。
位15:3 保留位 - 必须保持清零状态。
位2 CLKSOURCE(时钟源选择):
选择时钟源:
0:AHB时钟8分频(AHB/8)
1:处理器时钟(AHB)
位1 TICKINT(SysTick异常请求使能):
0:递减计数到0时不触发SysTick异常请求
1:递减计数到0时触发SysTick异常请求
注意:软件可通过COUNTFLAG判断SysTick是否曾计数到零。
位0 ENABLE(计数器使能):
启用计数器。当ENABLE置1时,计数器会从LOAD寄存器加载RELOAD值并开始递减计数。当计数到0时:
-
将COUNTFLAG置1
-
根据TICKINT的值决定是否触发SysTick异常请求
-
重新加载RELOAD值并继续计数
0:禁用计数器
1:启用计数器
2) LOAD
位31:24 保留位 - 必须保持清零状态。
位23:0 RELOAD[23:0]:重载值
LOAD寄存器指定当计数器启用或计数到0时,加载到VAL寄存器的起始值。
RELOAD值计算规则:
-
有效范围:0x00000001-0x00FFFFFF(十进制的1~16,777,215)
-
起始值0是允许的,但会导致SysTick异常请求和COUNTFLAG失效(因为这两个标志只在从1递减到0时触发)
RELOAD值的应用场景:
-
周期性定时器(多触发模式)
-
若需每N个处理器时钟周期触发一次SysTick中断:
RELOAD = N - 1
-
示例:每100个时钟脉冲触发一次中断 → 设RELOAD为99
-
-
单次延迟中断(单触发模式)
-
若需在N个处理器时钟周期后触发一次SysTick中断:
RELOAD = N
-
示例:400个时钟脉冲后触发中断 → 设RELOAD为400
-
(注:VAL寄存器会在计数器启用时自动加载RELOAD值,并在递减到0后重新加载实现连续计数)
3) VAL
位31:24
保留位 - 必须保持清零状态。
位23:0 CURRENT[23:0]
当前计数值
VAL寄存器保存SysTick计数器的实时数值:
-
读取:返回计数器的当前值。
-
写入(任意值):
-
将计数器值强制清零。
-
同时清除SysTick控制寄存器(STK_CTRL)中的 COUNTFLAG 标志位(置0)。
-
关键说明:
-
写入操作的特殊性:无论写入何值(如0x00、0x1234等),均会触发计数器清零和标志位清除。
-
COUNTFLAG联动:此操作会清除计数完成标志(COUNTFLAG),常用于手动重置计数器状态。
-
应用场景:通过写入VAL寄存器,可强制终止当前计数周期或重新同步定时器。
(注:VAL寄存器的值在计数器启用时会自动从LOAD寄存器加载RELOAD值,递减到0后重新加载,形成周期性计数。)
4) CALIB
位31 NOREF(无参考时钟标志)
-
读取值:始终为0
-
含义:指示存在独立参考时钟(频率 = HCLK/8)。此标志为0表示系统提供外部参考时钟源。
位30 SKEW(校准偏差标志)
-
读取值:始终为1
-
含义:由于TENMS校准值未知,无法保证1ms定时精度。此标志为1时,SysTick不适合用作高精度实时时钟(RTC)。
位29:24
保留位 - 必须保持清零状态。
位23:0 TENMS[23:0](10毫秒校准值)
-
功能:
当SysTick使用外部时钟(HCLK最大值/8)时,此字段提供1ms周期对应的计数值校准参数。-
典型场景:当HCLK配置为最大频率时,写入TENMS值可使SysTick精确产生1ms中断。
-
产品差异性:具体数值需查阅芯片手册的 SysTick Calibration Value 章节。
-
校准值计算:
若TENMS未知,通过公式推导:TENMS = (期望定时周期(ms) × 时钟频率(Hz)) / 8
示例:
若需1ms定时且HCLK=72MHz → TENMS = (0.001 × 72,000,000)/8 = 9,000
-
关键说明
-
NOREF与时钟源关系:
-
NOREF=0 → SysTick使用外部参考时钟(HCLK/8)
-
NOREF=1(不存在此状态,因该位固定读0)
-
-
校准失效场景:
SKEW=1时,需通过手动计算TENMS值实现精确定时。 -
硬件限制:
TENMS字段仅23位宽,最大校准值为8,388,607(0x7FFFFF),超限时需分频处理。
(注:HCLK指AHB总线时钟,通常与处理器主频相同)
10.2 基本定时器
定时器是STM32中一个非常强大的外设,功能强大,用途很广。STM32F103系列提供了8个定时器:2个基本定时器(TIM6,7),4个通用定时器(TIM2-5),2个高级定时器(TIM1和TIM8)。STM32F103C8T6定时器资源只有TIM1、TIM2、TIM3、TIM4。
高级定时器 | TIM1、TIM8 | APB2 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 |
基本定时器 | TIM6、TIM7 | APB1 |
10.2.1 基本定时器介绍
基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
这2个定时器是互相独立的,不共享任何资源。
这个2个基本定时器只能向上计数,由于没有外部IO,所以只能计时,不能对外部脉冲进行计数。
功能:定时中断,主模式,触发DAC。
基本定时器功能框图
时钟源:只有一种时钟源:内部时钟。一般为72MHz
触发控制器:通常用于触发其他外设的操作,使得不同的外设能够协同工作。比如触发DAC输出
基本定时器只是把时钟信号传输给时基单元。
PSC预分频寄存器:对过来的时钟信号(CK_PSC)进行分频(1-65536),然后把分频后的信号(CK_CNT)作为计数器的时钟。
当对预分频器写入的值会在第一次CNT计数溢出时写入PSC影子寄存器,一启动前默认0分频,启动时会快速进入一次更新中断。要想避免这种情况,需要在计数之前溢出一次,将PSC值写入影子寄存器
操作步骤:控制EGR寄存器的UG位,产生一次更新中断。但是产生UG之后会将UIF更新中断标志位置1。使能更新中断后还是会进入更新中断,所以还要讲UIF位清零。
自动重装载寄存器:包含两个寄存器(预加载寄存器和影子寄存器)。写数据到自动重装载寄存器时先写到预加载寄存器然后更新到影子寄存器。计数器是否溢出,是查看的影子寄存器的值。寄存器CR1的ARPE位决定更新时机(是否预加载),没有预加载时,写入的值会立即更新到影子寄存器。有预加载时,写入的值会等到产生更新事件(计数器溢出)才更新到影子寄存器。
CNT计数器:基本定时器只能向上计数,从0开始自增。自增到自动重装载寄存器的值时,下一个时钟上升沿到来后,计数器产生溢出,从0重新计数,并产生更新事件(UEV)。
计数模式(如果开启中断也会产生更新中断)
计算定时时间(多久产生一次更新事件)
1.计数器的时钟频率:内部时钟频率/(预分频系数+1 )预分频系数0表示1分频,1表示2分频.......
2.计数器的周期:(预分频系数+1)/内部时钟频率
3.计数器累加多少次产生一次更新时间:自动重装载值+1
4.定时时间:内部时钟频率/(预分频系数+1 )*(自动重装载值+1)
10.3 通用定时器
10.3.1 通用定时器介绍
通用定时器有4个分别是:TIM2、TIM3、TIM4、TIM5。它们拥有基本定时器所有功能。并增加如下功能:
- 多种时钟源。
- 向上计数(加),向下计数(减),向上/向下(先加后减)。当然我们使用的时候更喜欢向上计数。
- 输入捕获。
- 输出比较。
- PWM生成。
- 支持针对定位的增量(正交)编码器和霍尔传感器电路。
拥有基本定时器所用功能:内部时钟源,时基单元(PSC,ARR,CNT)
3种可选的时钟源:
1)内部时钟模式,一般为72MHz。与基本定时器一致(默认时钟源就是内部时钟)
2)外部时钟源模式1
1.使用定时器自身通道的输入信号作为时钟源
2.每个定时器都有4个输入通道
3.只有通道1和通道2的信号可以作为时钟信号源(只能二选一)
4.通道1和通道2的信号经过输入滤波和边缘检测器
5.成为了时钟源
3) 外部时钟源模式2
1.使用定时器的特殊引脚ETR引脚的信号作为时钟源
2.每个通用定时器都有一个ETR引脚,比如TIM2的ETR引脚是PA15
3.ETR引脚信号经过极性选择,边缘检测,预分频器,输入滤波,得到信号ETRF
4.ETRF就成为外部时钟源
外部时钟源一般用于定时器级联。大部分情况,内部时钟源足够使用。不配置是时钟源的情况下,默认选择的就是内部时钟源。
计时器3种计数模式
1)向上计数模式:与基本定时器一样,都是从0开始加,一直加到ARR的值。然后再来一个时钟信号,计数器溢出,产生更新事件。重新从0开始计数。
2)向下计数模式:从ARR的值开始计数,直至减到0。然后再来一个时钟信号,计数器溢出,产生更新事件。重新从ARR的值开始计数。
3)中央对齐模式(向上和向下计数):从0开始向上计数,一直计数到ARR的值-1。再来一个时钟信号会产生更新事件,然后继续从ARR的值向下计数。向下计数到1,再来一个时钟信号会产生更新事件,然后继续从0开始向上计数。
默认向上计数,CR1寄存器的DIR位
10.3.2 通用定时器案例1:LED呼吸灯——PWM脉冲
使用通用定时器的输出比较功能。
10.3.2.1 需求描述
输出占空比可调的PWM波形,作用到二极管,使二极管(LED2)呈现呼吸灯的效果。
10.3.2.2 如何生成PWM
1)PWM介绍
PWM(脉冲宽度调制)
是利用微处理器的数字输出对模拟电路进行控制的一种非常有效的技术
PWM通常用来控制电机,LED亮度调节等应用
被控制的电路必须有一定的“惯性”(比如LED即时断电了也不会立即熄灭,电机断电了也不会立即停止)
PWM的3个参数
周期T:连续两个上升沿或连续两个下降沿之间的宽度
频率f:周期的倒数。
占空比:高电平宽度t除以周期T。t/T
PWM的使用
在使用PWM驱动惯性电器时,一般不改变周期和频率,通过更改占空比达到对输出的有效电压的值。
2)定时器的输出比较功能:此项功能用来输出方波
输出比较部分,包含3部分内容
计数器部分
捕获比较部分:每个定时器有4个 可以实现4路比较
输出部分:4路输出,分别对应4个引脚
输出比较原理:核心思想比较寄存器的值和计数器的值进行大小比较,根据比较结果(><=)不同,产生不同输出:高电平或低电平
输出比较的8种模式:
由CCMR1寄存器的OCM1M[2,0],共3位来控制。
PWM模式1:波形频率=计数器溢出频率
波形占空比:CCR/重装载寄存器的值+1
PWM模式2:波形频率=计数器溢出频率
波形占空比:重装载寄存器的值+1-CCR/重装载寄存器的值+1
10.3.3 通用定时器实验2:测量PWM的频率/周期
10.3.3.1 需求描述
上一个案例我们输出了PWM波,这个案例我们使用输入捕获功能,来测试PWM波的频率/周期。
把测到的结果通过串口发送到电脑,检查测试的结果
10.3.3.2 如何测量PWM周期/频率
通用定时器的输入比较
1)输入捕获部分,包含3部分内容
计数器部分
输入部分:4路输入信号,每路都有自己的输入引脚(4路输入引脚和4路输出比较是一致的。对同一路引脚,只能处于输入捕获或者输出比较)
捕获寄存器部分:共有4路,与比较寄存器共用
2)输入捕获原理:以通道1为例
①信号经过通道1的引脚口进入通道1,得到TI1。
②TI1信号进入滤波器(滤掉一些毛刺信号)和边沿检测器(确定要捕获上升沿还是下降沿)
③从边沿检测器出来的上升沿或下降沿信号位TI1FP1
④TI1FP1经过信号选择器得到IC1
⑤IC1进入预分频器,可以对信号进行分频或不分频。如果信号的频率很高,可以分频
⑥信号从预分频器出来,信号为IC1PS。
A:会产生一个捕获比较事件
B:如果开启了中断也会产生捕获比较中断
C:立即把计数器寄存器的值存入到捕获寄存器中,在下次捕获事件产生之前,捕获寄存器的值不会产生变化
3)测量PWM周期原理
为了方便处理(不滤波 捕获上升沿 不分频)
1.假设对计数器时钟72分频(主要方便计算)则计数器时钟频率为1MHz,计数器累加一次的事件为1us
2.设置定时器自动重装载寄存器位为65535.把这个值设置为最大,尽量避免溢出
注意事项:
①假设测量周期小于65535us即频率大于16Hz
1.在一个周期内,计数器不会溢出
2.当第一个上升沿到来时,重置计数器的值(让计数器从0开始计数)
3.当第二个上升沿到来时,计数器的值会自动copy到捕获寄存器,读出捕获寄存器的值,这个值就表示信号的周期。单位us
②如果频率超过1MHz(超过计数器的时钟频率)
1.第二个上升沿到了之后,计数器还没有完成一次累加,则无法测量
2.可以考虑测试第一个上升沿和第n个上升沿的间隔。这种一般用来测量高频信号,频率超过了计数器的时钟频率。
10.3.3.2.1 触发输入和从模式
用一个定时器的2个通道同时测量频率和占空比。
测试频率好理解,连续的两个上升沿就可以了。测试占空比就需要连续的一个上升沿和一个下降沿,用前面的知识是无法实现了,因为需要在这个通道即要检测上升沿,也要检测下降沿,是无法实现的。
所以,要测量占空比,需要用到新的知识:定时器的从模式和PWM输入模式。
定时器触发信号
定时器的触发信号分两大类
1.触发输入信号(TRGI):
从外部过来(也可能是自己输入通道过来)到本定时器的信号。
用来控制本定时器一些动作,比如复位。这个时候本定时器就处于主从模式中的从模式
2.触发输出信号(TRGO):
是本定时器输出到其他定时器或其他外设的信号
用于其他定时器的级联(触发其他定时器的一些工作)或触发一些其他外设工作。这个时候本定时器就处于主从模式中的主模式
1)触发输入信号
第一类 TS[2:0]=000-011 共四个
来源于其他定时器的TRGO信号,经过芯片内部连接,来到本定时器的ITR0~ITR3(内部连接不能更改,比如TIM2的TRGO连接到了TIM1的ITR0)ITRx中某个信号经过信号选择器最终成为TRGI信号。TRGI信号通过从模式控制器控制本定时器实现复位或使能或更改计数方式等。
第二类 TS[2:0]=111 共1个
来源于外部触发引脚ETR(从来引脚),经过极性选择,边缘检测和预分频,输入滤波器成为TRGI信号。
第三类 TS[2:0]=100 共1个
来源于定时器自身的通道1信号,经过输入滤波器和边缘检测器,得到TI1F_ED信号。上升沿和下降沿都会产生TI1F_ED信号。经过信号选择器最终成为TRGI信号
第四类 TS[2:0]=101/110 共2个
来源于定时器自身的通道1信号或通道2信号,经过输入滤波器和边缘检测器,得到TI1FP1和TI2FP2。他们是上升沿或下降沿,只能选择一种,最终成为TRGI信号
这些TRGI信号要控制定时器,必须把定时器配置为从模式,从模式控制寄存器(SMCR)的SMS[2:0]位用来配置从模式工作模式
10.3.3.2.2 PWM输入模式
该模式是输入捕获模式的一个特例,操作与输入捕获模式相同
以信号从通道1输入为为例。经过输入滤波器和边沿检测器得到2路信号
TI1FP1和TI1FP2
TI1FP1和TI1FP2极性相反,一个得到输入的上升沿(TI1FP1),一个得到输入的下降沿(TI1FP2)。
TI1FP1得到IC1信号,在通道1,用来测量周期
TI1FP2得到IC2信号,在通道2,用来测量占空比
TI1FP1得到作为触发输入信号,开启从模式中的复位模式(重新初始化计数器)
当上升沿到
通道1捕获
通道2捕获
从模式复位计数器
当下降沿
通道2捕获
CCR2存储的就是高电平宽度
当上升沿到
通道1捕获 CCR1存储的就是周期
从模式复位计数器
10.4 高级定时器
10.4.1 高级定时器介绍
拥有通用定时器所有功能
相比通用定时器增加的功能:
重复计数器
死区时间可编程的互补输出
断路输入信号(刹车信号)
重复计数器包含2部分:重复次数计数器和REP寄存器(硬件叫RCR寄存器)
在基本定时器和通用定时器中,计数器每溢出一次,就产生一次更新事件。
在高级定时器中,计数器每溢出1次,会产生一个信号,让重复计数器的值-1。
当重复计数器的值减到0,如果计数器再溢出一次,则会产生更新事件。
重复计数器的初始化来源于RCR寄存器REP位。
如果REP=2,则CNT计数器溢出3次产生一次更新事件。
可以用重复计时器产生有限个周期的PWM
互补输出
高级定时器的通道1/2/3可以分别输出2路互补信号(频率周期相等,相位差180°):CH1和CH1N
互补输出一般用于驱动H桥电路,H桥电路常用于驱动电流较大的负载,比如电机
CH1高电平,Q1和Q4导通,电机正转
CH1低电平,Q3和Q2导通,电机反转
理论上CH1和CH1N永远反相,所以可以非常方便的控制电机的转动,实际情况下由于MOS管器件关断慢,开通快,如果Q1和Q4还没有完全关断,Q2和Q3导通,就会造成短路,导致器件烧毁,所以这种情况是严格禁止的。所以需要引出死区时间概念。
死区时间:延时一段时间再开通
10.4.2 高级定时器实验:输出有限个周期的PWM波
10.4.2.1 需求分析
用寄存器实现。输出5个周期的PWM波,频率2Hz,观察发光二极管闪烁5次,或者用示波器观察波形。
需求实现思路:使用高级定时器的重复计数器,当计数器溢出时,在溢出中断中停止定时器工作。重复计数器寄存器的值设置为4,即可输出5个周期的PWM波,发光二极管会闪烁5次。
十一 DMA直接存储访问
11.1 DMA介绍
直接存储器存取(direct memory access,DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
2个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
DMA控制器和Cortex™-M3核心共享系统数据总线,执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。
要注意的是DMA2只存在于大容量产品和互联型产品中。
11.2 DMA框图
1)DMA请求
如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA控制器收到请求信号之后,控制器会给外设一个应答信号,当外设得到控制器的应答信号后,外设会立即释放它的请求。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置。
2)通道
DMA具有12个独立可编程的通道,其中 DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
3)仲裁器
当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段,如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。
在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
4)传输方向
存储器到外设,外设到存储器,存储器到存储器。这里的存储器指的是ROM和RAM。注意DMA没有办法把数据从RAM传输到ROM(flash)。
11.3 DMA案例1:ROM到RAM
11.3.1需求分析
使用寄存器操作把ROM中的数据通过D MA传输到RAM,然后把数据通过printf发送到串口验证是否正确。
DMA传输不涉及外设,所以通道随便选。我们选DMA1的1通道。
十二 ADC(数模转换)
12.1 ADC概述
12.1.1 ADC介绍
ADC(模拟数字转换器)概念:用于将模拟信号转换成数字形式,以便在数字系统中进行处理
一般传感器会把观测的物理量转换为电压值,也就是所谓的模拟信号
计算机的世界里只有0和1,ADC转换器就是把模拟的物理量(电压值)转换为计算机认识的数字量
ADC工作原理
2个阶段
采样阶段:ADC首先对模拟信号进行采样,即在一定时间内获取信号的瞬时值
量化阶段:采样后的模拟信号通过量化器,将其转换为相应的数字形式,这个数字形式通常是二进制代码
左边模拟量 右边数字量
ADC主要参数
参考电压:ADC使用参考电压来确定模拟信号的幅度范围。
通常,ADC需要一个已知的电压作为参考,以便将模拟电压映射到数字代码
通道数:表示ADC能够同时处理的模拟输入通道的数量
多通道ADC可以同时转换多个信号
采样率:表示ADC每秒可以进行多少次采样
采样率越高,ADC能捕获更高频率的信号
分辨率:表示ADC可以将模拟信号分成多少个离散级别
以数字信号的位数N来表示,一般有10位,12位,16位等。例如12位ADC有4096个离散级别
转换时间:表示ADC从开始到完成所需的时间
转换时间通常由ADC的时钟频率和分辨率决定
12.1.2 STM32的ADC
STM32F103系列提供了3个ADC,精度为12位,每个ADC最多有16个通道和2个内部信号源。
STM32F103的ADC是一种逐次逼近型模拟数字转换器。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
12.1.3 逐次逼近型ADC工作原理
ADC常见类型
逐次逼近型:这是最常见的ADC类型之一,通过逼近法逼近模拟信号的大小。STM32的ADC常用这种类型。
Sigma-Delta型:通过比较信号的累积值和一个参考值,产生一个高精度的输出
Flash型号:通过一组比较器和编码器,以高速并行方式进行转换,适用于高速应用
逐次逼近型原理:
假设参考标准电压为255V(好算),ADC的分辨率为8位。
ADC核心组件
比较器:类似于天平,用来比较电压大小。
有两个输入端:+输入端 -输入端
有1个输出端:+ > - :输出高电平,反之输出低电平,相等不确定。
8位寄存器:类似天平中砝码,高位类似于较大的砝码,低位类似于较小的砝码
8位DA转换器:根据参考电压把寄存器的值换算成模拟电压。转化后的电压接入比较器的-端,与+端的待测电压进行比较
逻辑控制电路:类似测量中的人,增减砝码。
输入高电平(待测电压大),则保留上次的砝码
输入低电平(待测电压小),则去掉上次的砝码,添加小的砝码(下一位置为1)
输出部分:根据8位寄存器的值,输出最终转换后的结果
12.1.4 STM32 ADC外设
ADC功能框图
VDDA 模拟电源正
VSSA 模拟电源负
VREF+模拟参考电压正
VREF- 模拟参考电压负
在实际电路设计中,一般VREF+和VDDA接3.3V,VREF-和VSSA接地,所以ADC测量范围0-3.3V
输入通道:用来输入模拟电压的通道,一共多达18个通道,
16个外部通道(ADCx_IN0-ADCx_IN15),对应16个IO口
内部温度传感器(测量芯片温度),通道16。内部参考电压,通道17。
输入通道组织:我们可能会同时进行多路转换,输入通道组是把多个输入通道组织在一起,然后按组内顺序进行转换。
一共2个通道组:
规则通道:规则通道组内的通道转换完成之后,会把转换后的结果存储到规则通道数据寄存器中。规则组允许最多16个通道。由于规则通道寄存器只有一个,当多路要转换时,寄存器内的值要及时取走,否则后面通道转换结果会覆盖前面的结果。一般使用DMA把数据寄存器的数据取走。
注入通道:注入通道组内的通道转换完成后,会把转换后的结果存储到注入通道数据寄存器中。注入组允许最多4个通道。注入通道数据寄存器有4个,不用担心数据被覆盖问题。注入通道的转换可以打断规则通道的转换。
触发源
软件触发:操作控制寄存器ADC_CR2的一些相关位
硬件触发:使用定时器的TRGO触发
转换方式
ADC_CR1寄存器SCAN(扫描模式):
开启后,多通道,顺序扫描,挨个转换。关闭后,只转换通道组中第一个通道。
ADC_CR2寄存器CONT:连续转换
开启后,通道组内转换一轮后,继续转换下一路,不会停。关闭后,通道组内转换一轮后停止转换,等待下次启动。(单次转换)
1)中断
(1)转换结束后中断
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断服务程序。
(2)模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTR 和 ADC_HTR 设置。例如我们设置高阈值是2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。
2)DMA 请求
规则和注入通道转换结束后,除了产生中断外,还可以产生DMA请求,把转换好的数据直接存储在内存里面。要注意的是只有ADC1和ADC3可以产生DMA请求。
3)数据对齐
16位的寄存器只用到了其中的12位。
可以使用高12位,数据就是左对齐。
也可以使用低12位,数据就是右对齐。
右对齐:高4位补零,读取的值就是实际值。
左对齐:低4位补零,读取的值是实际值的16倍。
实际使用中,最好使用低16位,这样就得到数据可以直接使用。
4)转换时间
ADC使用若干个ADC_CLK周期对输入电压采样总转换时间如下计算:
TCONV = 采样时间 + 12.5个ADC周期。
当ADCCLK=14MHz,假设采样时间为1.5周期。
TCONV = 1.5 + 12.5 = 14周期 = 1μs。
5)电压转换
模拟电压经过ADC转换后,是一个12位的数字值,如果通过串口以16进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。
我们一般在设计原理图的时候会把ADC的输入电压范围设定在:0~3.3v,因为ADC是12位的,那么12位满量程对应的就是 3.3V,12位满量程(全部是1)对应的数字值是:2^12-1。数值 0 对应的就是0V。
如果转换后的数值为X ,X对应的模拟电压为 Y,那么会有这么一个等式成立:(2^12 -1)/ 3.3 = X / Y,所以Y =(3.3 * X )/(2^12 – 1)= 3.3 * X / 4095。
十三 SPI通信
13.1 SPI通信介绍
SPI,串行外围设备接口。主要应用于EEPROM,FLASH,各种传感器,AD转换器等。
SPI是一种高速的,全双工,同步的串行通信总线
物理层
1个SPI设备一般有4条线
SCLK:时钟信号线,用于通讯数据同步
它由通讯主机(MCU)产生,决定了通讯的速率
不同的设备支持的最高时钟频率不一样,如STM32的SPI时钟频率最大为fpclk/2
两个设备之间通讯时,通讯速率受限于低速设备
MOSI:主设备输出/从设备输入引脚
主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据
这条线上数据的方向为主机到从机
MISO:主设备输入/从设备输出引脚。
主机从这条信号线读入数据,从机的数据由这条信号线输出到主机。
这条线上数据的方向为从机到主机。
SS:片选线或者使能线。有时候也成为NSS或CS。
SPI通信时各个设备时没有地址的
主机和哪个从机通讯,就把谁的SS置为低电平。
如果主机要连接多条从设备就需要多条片选线。
只能主从设备之间进行通信,从与从之间是无法进行通信的。
协议层
主机和从机之间的数据交换
移位寄存器临时存储要交换的数据
输入寄存器存储最终得到的值
来一个时钟上升沿信号,主机和从机分别把自己的高位的值左移出来(一般高位先行)
来一个时钟下降沿,主机和从机分别读入数据,存储到移位寄存器的低位
经过8次同样的操作,就完成了一个字节的交换,最后从输出寄存器就可以读出数据了
时钟的极性和相位
上面的描述简单化了数据交换过程,其实什么时候读数据(数据采样)和设置的时钟极性和相位有关
时钟的极性
CPOL:Clock Polarity。通信的整个过程分为空闲时刻和通信时刻。
空闲状态SCLK是低电平,CPOL=0;
空闲状态SCLK是高电平,CPOL=1。
时钟的相位
CPHA:Clock Phase,就是时钟的相位。直接决定SPI中线从哪个跳变沿开始采样数据。
SPHA=0:表示从第一个跳变沿开始采样;
SPHA=1:表示从第二个跳变沿开始采样。
SPI的4种模式
时钟的两种极性和两种相位的不同组合,得到SPI的4种工作模式
模式0和模式2
模式1和模式3
13.2 W25Q32介绍
W25Q32是一种使用SPI通讯协议的NOR FLASH存储器,它CS/CLK/DI/DO引脚分别连接到了STM32对应的SPI引脚NSS/SCK/MOSI/MISO上,其中STM32的NSS引脚虽然是其片上SPI外设的硬件引脚,但实际上后面的程序只是把它当成一个普通的GPIO,使用软件的方式控制NSS信号,所以在SPI的硬件设计中,NSS可以随便选择普通的GPIO,不必纠结于选择硬件NSS信号。
FLASH 芯片中还有WP和HOLD引脚。WP引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。
注意:
- 这个flash芯片只支持,模式0和模式3.
- 写的时候必须是先擦除,擦除后再写入。(FLASH只能写0不能写1)
- 移位是高位优先
W25Q32框图
- 写入操作注意事项
- 写入操作前,必须先进行写使能。
- 写入操作结束后,芯片进入忙状态,不响应新的读写操作。
- 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。
- 写入数据前必须先檫除,檫除后,所有数据位变为1。擦除必须按最小擦除单元进行
- 每个数据位只能由1改写为0,不能由0改写为1。
2.读操作注意事项
- 直接调用读取时序,无需读使能,无需额外操作,没有页的限制。
- 读取操作结束后不会进入忙状态,但不能在忙状态时读取。
交换数据时序
我们一般使用SPI的模式0(CPOL=0,CPHA=0),下面是模式0的数据交换时序图。
13.3 SPI外设
与I2C外设一样,STM32芯片也集成了专门用于SPI协议通讯的外设。
1)SPI外设简介
STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 fpclk/2 (STM32F103 型号的芯片默认fpclk1为36MHz,fpclk2为72MHz。),完全支持 SPI协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。
STM32F103系列提供了3个SPI,SPI1挂在APB2总线,SPI2/3挂在APB1总线。
其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。
用的比较多还是双线全双工模式。
2)SPI外设框图
波特率发生器用于生成通信的同步时钟
MSS是片选信号
可以硬件自动控制
也可以使用软件控制。一般使用软件控制,代码方式输出高低电平
数据寄存器
数据寄存器对应两个缓冲区
一个用于写(发送缓冲区)。写操作将数据写到发送缓冲区。
一个用于读(接收缓冲区)。读操作将返回接收缓冲区里的数据。
移位寄存器
写入到发送缓冲区的数据,被送入到移位寄存器。
按位通过MOSI把数据发送出去。
按位把通过MISO接收到的数据存入移位寄存器。
接收数据完毕,数据被送入到接收缓冲区。
十四 存储器和寄存器
14.1 存储器
14.1.1常见的存储器介绍
存储器是计算机结构的重要组成部分。存储器是用来存储程序代码和数据的部件,有了存储器计算机才具有记忆功能。
14.1.1.1 RAM
1)SRAM
Static Random-Access Memory,静态随机存取存储器。是RAM的一种,所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。
是用电路存储数据,基本结构就是前面大家学习过的那种触发器结构(比如D触发器)。容量一般较低,用于高速缓存。比如芯片内部的寄存器就可以看成一种SRAM。
2)DRAM
动态随机存储器DRAM的存储单元以电容的电荷来表示数据,有电荷代表1,无电荷代表0。
但时间一长,代表1的电容会放电,代表0的电容会吸收电荷,因此它需要定期刷新操作,这就是“动态(Dynamic)”一词所形容的特性。
刷新操作会对电容进行检查,若电量大于满电量的1 / 2,则认为其代表1,并把电容充满电;若电量小于1 / 2,则认为其代表 0,并把电容放电,藉此来保证数据的正确性。
14.1.1.2 ROM
ROM 是“Read Only Memory”的缩写,意为只能读的存储器。由于技术的发展,后来设计出了可以方便写入数据的ROM,而这个“Read Only Memory”的名称被沿用下来了,现在一般用于指代非易失性半导体存储器,包括后面介绍的 FLASH 存储器,有些人也把它归到 ROM 类里边。
1)MASK ROM
MASK(掩膜)ROM 就是正宗的“Read Only Memory”,存储在它内部的数据是在出厂时使用特殊工艺固化的,生产后就不可修改,其主要优势是大批量生产时成本低。当前在生产量大,数据不需要修改的场合,还有应用。
2)PROM
PROM(Programable ROM)为可编程ROM。但是只供用户写入一次。
3) EPROM
EPROM(Erasable Programmable ROM)是可重复擦写的存储器,它解决了PROM芯片只能写入一次的问题。这种存储器使用紫外线照射(30分钟)芯片内部擦除数据,擦除和写入都要专用的设备。现在这种存储器基本淘汰,被EEPROM取代。
4) E²PROM
PROM(Electrically Erasable Programmable ROM)是电可擦除存储器。EEPROM可以重复擦写,它的擦除和写入都是直接使用电路控制,不需要再使用外部设备来擦写。而且可以按字节为单位修改数据,无需整个芯片擦除。现在主要使用的ROM芯片都是EEPROM。
5) FLASH
FLASH存储器又称为闪存,它也是可重复擦写的储器,部分书籍会把FLASH存储器称为 FLASH ROM,但它的容量一般比EEPROM大得多,且在擦除时,一般以多个字节为单位。
6)硬盘
又称磁盘,是靠磁性来存储数据的。
14.1.2 STM的存储器
STM32包含片内SRAM(64K):它可以以字节、半字(16位)或全字(32位)访问。SRAM的起始地址是0x2000 0000。
片内Flash(最大可达2M)。
14.1.3 存储器映射
什么叫存储器映射呢?存储器本身并不具备地址信息,那么CPU要准确找到存储某个信息的存储单元,就必须为这些单元分配一个相互可区分的标识,这个标识就是常说的地址编码。
STM32中集成多种存储器(各种外设也需要分配地址),同一类型的存储器当作一组block,为每一个block分配一个数值连续,存储单元数相等,以16进制表示的自然数集合作为存储器Block的地址编码。这种自然数集合与存储器Block的对应关系就是存储器映射。
存储器映射其实就是将芯片理论上的地址分配给各个存储器。
需要注意的是:存储器映射并不是只针对SROM和片内Flash做地址映射,其实所有的片内外设(比如IO口)都需要地址,也都需要做映射。
14.1.4 STM32具体存储器映射图
芯片能访问的存储空间有多大,是由谁定的?这个是由芯片的地址总线的数量决来定的,STM32芯片内部的地址总线为32根。所以STM32有4G的地址空间。(这个4GB的是STM32理论分配的地址空间。也就是说实际上并不是有这么大的存储单元,很多地址都是预留地址,空着还没用呢)。
程序存储器、数据存储器、寄存器和输入输出端口被组织在这个4GB的线性地址空间内。数据字节以小端格式(先存低位再存高位)存放在存储器中。
ARM把可访问的存储器空间分成8个主要块,每个块为512MB。这个容量是非常大的,因此芯片厂商就在每块容量范围内设计各自特色的外设。但是每块区域容量占用越大,芯片成本就越高,所以说我们使用的 STM32 芯片都是只用了其中一部分。ARM 在对这 4GB 容量分块的时候是按照其功能划分,每块都有它特殊的用途。
在这8个Block里面,要特别注意Block0、Block1和Block2这3个块。因为其中包含了STM32芯片的内部 Flash、RAM和片上外设。下面还是根据存储器映射图内信息来简单的介绍下这3个Block里面的具体区域功能划分。
1)Block0
0x0000 0000-0x0007 FFFF:取决于BOOT引脚,为 FLASH还是系统存储器还是SRAM 的别名。(512K)
0x0008 0000-0x07FF FFFF:预留。(1M)
0x0800 0000-0x0807 FFFF:片内 FLASH,我们编写的程序就放在这一区域(512K)
0x0808 0000-0x1FFF EFFF:预留。(383M)
0x1FFF F000-0x1FFF F7FF:系统存储器,里面存放的是 ST 出厂时烧写好的ISP自举程序,用户无法改动。使用串口下载的时候需要用到这部分程序。(2K)
0x1FFF F800-0x1FFF F80F:可选字节,用于配置读写保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。当芯片不小心被锁住之后,我们可以从RAM里面启动来修改这部分相应的寄存器位。
0x1FFF F810-0x1FFF FFFF:预留。
2) Block1
Block1用于设计片内的SRAM,例如STM32F103ZET6的SRAM是64KB。从存储器映射图中可以看到Block1内部又划分了几个功能块,我们按地址从低到高顺序依次介绍。
0x2000 0000-0x2000 FFFF:SRAM,容量为 64KB。
0x2001 0000-0x3FFF FFFF:预留。
3)Block2
Block2用于设计片内外设,根据外设总线速度的不同,Block2被划分为AHB和APB 两部分,APB又被分成APB1和APB2。这些都可以在上面存储器映射图中可看到。
0x4000 0000-0x4000 77FF:APB1总线外设。
0x4001 0000-0x4001 57FF:APB2总线外设。
0x4001 8000-0x4002 33FF:AHB总线外设。
4) Block3,4,5
在Block3、Block4、Block5中包含了FSMC扩展区域,可用于扩展如 SRAM,NORFLASH 和NANDFLASH等的外部存储器。
14.2 寄存器
14.2.1 什么是寄存器
前面我们学习了存储器ROM和RAM,还包括我们所有的片上外设我们都可以称为存储器,STM32通过存储器映射,就可以找到这些存储器。
我们编程的时候用的最多的还是寄存器,那么什么叫寄存器呢?
在存储器 Block2 这块区域,设计的是片上外设,它们以4个字节为1个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名。
这个别名就是我们经常说的寄存器。
一句话总结:寄存器是单片机内部一种特殊的内存,可以实现对单片机各个功能的控制。
14.2.2 寄存器映射
这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
寄存器映射在ST提供的头文件stm32f10x.h中已经通过预编译的形式完全映射好了,以后如果再操作某个特定外设的时候,就不用直接操作地址,直接操作对应的寄存器名就可以了。
比如PA这组IO端口的映射:
// 外设基址
#define PERIPH_BASE ((uint32_t)0x40000000)
// APB2外设的基址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
// GPIOA 外设的基址
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
// 做了类型转换,地址仍然是GPIOA 外设的基址
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
十五 FSMC控制器
15.1 FSMC概述
MCU自带的FLASH和SRAM资源是十分有限的,相比于PC机的存储空间而言要小的可怜。一般情况对于嵌入式应用来说这点存储空间一般也就够用了,但避免不了一些大量消耗内存的应用, 比如说图像处理。对于这类对内存要求较高的应用,我们往往需要扩展一个FLASH或者SRAM。STM32提供的FSMC就是用来完成这项功能的。
FSMC(Flexible static memory controller,灵活的静态存储器控制器),STM32可以通过FSMC与SRAM、ROM、PSRAM、Nor Flash和NandFlash存储器的引脚相连,从而进行数据的交换。要注意的是,FSMC 只能扩展静态的内存(S:static),不能是动态的内存,比如 SDRAM 就不能扩展。
FSMC把AHB总线上的数据转换为对应外设的通信协议,控制外设的访问时序,以至于我们可以直接在程序中寻址访问。
15.2 FSMC组成
FSMC主要由4部分组成:AHB总线接口(包括FSMC的配置寄存器)、NOR闪存/SRAM控制器、NAND闪存/PC卡控制器、外设接口四个部分构成。
本次实验需要用到的引脚如下:
- FMC_A[25:0]:地址总线
- FMC_D[31:0]:双向数据总线
- FMC_NE[4:1]:片选引脚,低电平有效
- FMC_NOE:读使能,低电平有效
- FMC_NWE:写使能,低电平有效
15.2.1 AHB总线接口
AHB总线接口是CPU、DMA等AHB总线主设备访问FSMC的通道,它负责把AHB总线事务转换成为外设通信的协议。AHB总线事务的请求可以是8、16或者32位的,但外设器件的数据线位宽是恒定的。如果两者宽度相同就不存在什么问题,如果总线事务的位宽大于外设的位宽,那么总线接口将把总线事务拆分为多个连续的8位或16位形式访问外设。 我们应当尽量避免总线事务宽度小于外设宽度的情况出现,因为这将可能导致数据的不一致,具体与外设类型有关系。
配置寄存器则描述了扩展外设的具体形式、通信协议和接口形式。用于总线接口将AHB总线事务转换为外设通信协议, 驱动NOR闪存/SRAM控制器和NAND闪存/PC卡控制器,进而控制外设。
15.2.2 NOR闪存/PSRAM控制器
NOR/PSRAM内存控制器支持各种同步和异步的内存。所谓同步内存就是在读写内存的时候需要一个同步时钟来指导数据的发送和接收, 与我们在串口通信中提到的同步/异步通信是一个道理。对于同步内存,FSMC只会在读写操作的时候产生驱动时钟,而且其频率是系统总线时钟HCLK的分频。
NOR/PSRAM控制器用于生成适当的时序,以驱动8位、16位、32位的异步SRAM和ROM、异步或者突发模式的PSRAM和NOR闪存。我们通过配置寄存器描述外设的特征和时序后,控制器就可以为我们生成对应的驱动时序。
15.2.3 NAND闪存/PC卡控制器
NAND/PC卡控制器用于驱动8位或者16位的NAND闪存以及16位的PC卡兼容设备。
15.2.4 外设接口
用于与要扩展外设联通用的。在接线时必须根据每个外设的特点,来进行合适的接线。
15.3 外部设备地址映射
从FSMC的角度看,可以把外部存储器划分为固定大小为4个256M字节的存储块。
存储块1用于访问最多4个NOR闪存或PSRAM存储设备。这个存储区被划分为4个NOR/PSRAM区并有4个专用的片选。存储块2和3用于访问NAND闪存设备,每个存储块连接一个NAND闪存。存储块4用于访问PC卡设备。
每一个存储块上的存储器类型是由用户在配置寄存器中定义的。
15.4 FSMC控制NOR闪存或PSRAM的时序
FSMC 外设支持输出多种不同的时序以便于控制不同的存储器,它具有6种模式:1,A,2/B,C,D,复用模式。
所有信号由内部时钟HCLK保持同步,但该时钟不会输出到外部扩展的存储器。FSMC始终在片选信号NE失效前对数据线采样,这样能够保证符合存储器的数据保持时序。
所有的控制器输出信号在内部时钟(HCLK)的上升沿变化,在同步写模式(PSRAM)下,读写的数据在存储器时钟(CLK)的下降沿变化。
我们以读写SRAM的模式A为例来介绍。
当内核发出访问某个指向外部存储器的地址时,FSMC外设会根据配置控制信号线产生时序访问存储器(硬件自动生成对应的时序),上图中的是访问外部 SRAM 时 FSMC 外设的读写时序。
在读时序中,一个存储器操作周期由1个地址建立周期(ADDSET),1个数据建立周期(DATASET)和2个HCLK周期组成。在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给 FSMC,FSMC 把它交给内核。
写时序类似,区别是它的一个存储器操作周期仅由1个地址建立周期(ADDSET)和1个数据建立周期(DATAST)组成,且在数据建立周期期间写使能信号线发出写信号,接着 FSMC 把数据通过数据线传输到存储器中。
15.5 FSMC应用案例:扩展外部SRAM
15.5.1 需求分析
使用FSMC扩展外部SRAM。然后把内存数据存储到外部SRAM中。
STM32F1 系列的芯片不支持扩展SDRAM(STM32F429 系列支持),它仅支持使用 FSMC 外设扩展 SRAM。由于引脚数量的限制,只有 STM32F103ZE 或以上型号的芯片才可以扩展外部 SRAM。
15.5.2 SRAM芯片IS62WV51216
1)SRAM介绍
开发板用的SRAM型号是IS62WV51216,就以这个为例来介绍SRAM
2)功能框图
3)信号线
信号线 | 类型 | 说明 |
A0-A18 | I | 地址输入 |
I/O0-I/O7 | I/O | 低8位字节的数据输入输出信号 |
I/O8-I/O15 | I/O | 高8位字节的数据输入输出信号 |
CS1和CS2 | I | 片选信号CS2高电平有效, CS1低电平有效 |
OE | I | 输出使能信号,低电平有效。 |
WE | I | 写使能信号,低电平有效 |
UB | O | 数据掩码信号,高位字节允许访问,低电平有效 |
LB | O | 数据掩码信号,低位字节允许访问,低电平有效 |
SRAM 的控制比较简单,只要控制信号线使能了访问,从地址线输入要访问的地址,即可从 I/O 数据线写入或读出数据。
4)几个重要的时间参数
这几个时间参数比较重要,设置FSMC参数的要用。
十六 LCD显示
16.1 使用液晶显示模块介绍
16.1.1 嵌入式LCD显示模块接口类型
LCD的接口有多种,分类很细。主要看LCD的驱动方式和控制方式,目前彩色LCD的连接方式一般有这么几种:MCU模式,RGB模式,SPI模式,VSYNC模式,MDDI模式,DSI模式。
但应用比较多的就是MCU模式和RGB模式。
1)MCU模式
因为主要针对单片机的领域在使用,因此得名,其主要特点是价格便宜。MCU-LCD接口的标准术语是Intel提出的8080总线标准,因此在很多文档中用 I80 来指MCU-LCD屏。
对于MCU接口的LCM(LCD Module),其内部的芯片就叫LCD驱动器,都带GRAM(显存)。主要功能是对主机发过的数据/命令,进行变换,变成每个像素的RGB数据,使之在屏上显示出来。
2)RGB模式
RGB模式是大屏采用较多的模式,比如我们电脑显示器。
对于RGB接口的LCM,主机输出的直接是每个像素的RGB数据,不需要进行变换(GAMMA校正等除外),对于这种接口,需要在主机部分有个LCD控制器(平常所说的显卡),以产生RGB数据和点、行、帧同步信号。
16.1.2 液晶模块

TFT LCD(薄膜晶体管液晶显示器)
(1)分辨率
十七 物联网通讯之CAN通讯
17.1 CAN通讯介绍
CAN(Controller Area Network 控制器局域网,简称CAN或者CAN bus)是一种功能丰富的车用总线标准。被设计用于在不需要主机(Host)的情况下,允许网络上的单片机和仪器相互通信。
它基于消息传递协议,设计之初在车辆上复用通信线缆,以降低铜线使用量,后来也被其他行业所使用。
CAN拥有了良好的弹性调整能力,可以在现有网络中增加节点而不用在软、硬件上做出调整。除此之外,消息的传递不基于特殊种类的节点,增加了升级网络的便利性。
17.1.1 物理层介绍
CAN网络结点
一个CAN控制器
一般由MCU提供。STM32内部提供了1个控制器
一个CAN收发器
收发器一般需要专门芯片提供。
控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连
收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连
CAN网络结点发数据
当CAN结点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器
收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CAN_High和CAN_Low输出到CAN总线网络
CAN网络结点收数据
而通过收发器接收总线上的数据到控制器时,则是相反的过程
收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器
CAN总线网络
当CAN线上接入多个设备时,就构成了CAN总线网络
根据接法不同,总线网络分两种
闭环总线网络
遵循IS011898标准的高速,短距离“闭环网络”
最大传输距离40m,通信速度最高为1Mbps。总线两端连120欧电阻
开环总线网络
遵顼ISO11519-2标准的低速,远距离“开环网络”
最大传输距离1000m,通信速度最高为125Kbps。两根总线是独立的,不形成闭环,要求每根总线上各串联有一个2.2KΩ电阻
差分信号
CAN_High及CAN_Low中走的是一对差分信号
传统的单端信号传输:一根信号线一根地线
差分传输是一种信号传输的技术,差分传输在两根线上都是传输信号,这两个信号的振幅相同,相位相反
信号端接收比较这两个电压的差值来判断发送端发送到逻辑状态
在电路板上,差分走线必须是等长,等宽,紧密靠近,且在同一层面的两根线
差分信号优缺点
优点
1.抗干扰能力强
干扰噪声一般会等值,同时的被加载到两根信号线上,而其差值为0.即噪声对信号的逻辑意义不产生影响
2.能有效抑制电磁干扰
缺点
差分信号一定要走两根等长,等宽,紧密靠近,且在同一层面的线。
差分信号在USB协议,485协议,以太网协议及CAN协议的物理层中,都使用了差分信号传输。
CAN中的差分信号
以高速CAN协议为例
逻辑1(隐性电平)
CAN_High=CAN_Low=2.5V
电压差VH-VL=0V
逻辑0(显性电平)
CAN_High=3.5V,CAN_Low=1.5V
电压差VH-VL=2V
17.1.2 协议层介绍
17.1.2.1 CAN的帧(报文)种类
CAN总线是广播类型的总线。这意味着所有节点都可以侦听到所有传输的报文。无法将报文单独发送给指定节点;所有节点都将始终捕获所有报文。但是CAN硬件能够提供本地过滤功能,让每个节点对报文有选择性地做出响应。
CAN使用短报文 – 最大实用负载是94位。报文中没有任何明确的地址;相反,可以认为报文是通过内容寻址,也就是说,报文的内容隐式地确定其地址。
CAN总线上有5种不同的报文类型(或“帧”):数据帧,远程帧,错误帧,过载帧和帧间隔。
1)数据帧
数据帧是最常见的报文类型,用于发送单元向接收单元发送数据。
2)远程帧(遥控帧)
远程帧用于接收单元向具有相同id的发送单元请求发送数据。
3)错误帧
错误帧当检测出错误时向其他单元通知错误的帧。
4)过载帧
过载帧并不常用,因为当今的CAN控制器会非常智能化地避免使用过载帧。
5)帧间隔
用于将数据帧及遥控帧与前面的帧分离开来的帧
其中错误帧、过载帧、帧间隔都是由硬件自动完成的,没有办法用软件来控制。对于一般使用者来说,只需要掌握数据帧与遥控帧。数据帧和遥控帧有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符。
17.1.2.2 数据帧介绍
有两种数据帧 标准帧和扩展帧
标准帧
帧起始(SOF):
1bit,显性电平(逻辑0)表示数据帧(或远程帧)的开始。只能在总线空闲的时候才可以发送帧起始。
仲裁段:
标识符位(ID):长度为11位(11bit),ID10-ID0。按照ID10-ID0顺序进行传输。是一个功能性的地址,CAN接收器通过标识符来过滤数据帧,不同节点的标识符位是不能相同的。
远程发送请求位(RTR):用来区分该帧是数据帧还是远程帧。显性(0)代表数据帧,隐形信号(1)代表数据帧。
控制段:
IDE位:1位。区分标准格式与扩展格式。显性电平(0)表示标准格式,隐形电平(1)代表扩展格式
R0位:1位。保留位
DCL位(Data Length Code):4位,表示本报文中的数据段含有多少个字节。用于DLC段表示的数字为0~8(0000-1000)。
数据段:
数据帧的核心内容,它是节点要发送的原始信息。由0~8字节组成,高位先行。
CRC段:
CRC校验码:15bit,用于校验传输是否正确
界定符:隐性位,表示校验码的结束
ACK段:
ACK确认位:1位,发送端的ACK确认位是隐性位,接收端收到正确的CRC校验位后,把这一位置为显性位。
界定符:1位隐性位,用于与后面的帧结束隔离开。
帧结束(EOF):
帧结束,帧结束段由发送节点发送7个隐性位表示结束。
扩展帧:与标准帧的不同在于仲裁段和控制段,其他完全一样。
SSR:替代远程要求位(RTR),隐性信号。
IDE:表示扩展帧还是标准帧。
18位扩展ID
RTR:表示数据帧还是远程帧
R1R0:保留位
17.1.2.3 远程帧介绍
与数据帧相比没有数据段
17.1.2.4 CAN总线仲裁
CAN总线处于空闲状态的时候,最先发送消息的单元获得发送权。
多个单元同时开始发送时,从仲裁段(报文id)的第一位开始进行仲裁。连续输出显性电平(0)最多的单元可以继续发送,即首先出现隐形电平的单元失去最总线的占有权变为接收。(即报文id小的优先级高)。
竞争失败,会自动检测总线空闲,在第一时间再次尝试发送。
17.1.2.5 CAN的位时序
位同步:CAN中提出了位同步的方式来确保通讯时序。
一帧中包含了很多位,CAN把1位分为4段,4段的总时间段构成了位时间(Bit Time),就是传输一个位所需的总时间。位时间通常被分为若干等长的时间单元,称为时间量化器(Time Quanta,Tq), 1个Tq的长度可以根据传输速率的需要设置。在STM32的CAB外设中,通过设置波特率分频器的值来确定Tq的大小。
同步段SS(Sync):大小固定为1Tq,若通讯节点检测到总线上信号的跳变沿被包含在SS段的范围之内,则表示节点与总线的时序是同步的。
传播时间段PTS(Prop-Seg):CAN总线上数据的传输会受到各种物理延迟,比如发送单元的发送延迟,总线上信号的传播延迟,接收单元的输入延迟等。PTS段就是用来补偿这些因素产生的时间延迟。PTS段长度至少为1个Tq。一般1-8Tq。
相位缓冲段1 PBS1(Phase1):主要用来补偿边沿阶段的误差。他的时间长度在重新同步阶段的时候可以被自动加长。PBS1段初始大小可以为1-8Tq。
相位缓冲段2 PBS2(Phase2):另一个相位缓冲段,也是用来补偿边沿阶段误差,他的时间长度在重新同步阶段时可以缩短。大小可以为2-8Tq。
采样点(Sampling point)位于PBS1和PBS2的交界处
数据同步:根据同步方式差异,CAN的数据同步分为硬同步和再同步
硬同步:
当一个结点检测到起始位时,他会执行硬同步,以便将其内部的时间基准与数据帧的时间基准对其。在硬同步过程中,控制器的时间基准会立即调整为与检测到的边沿对齐。
再同步:
在检测到总线上的时序与节点使用的时序有相位差时(即总线上的跳变沿不在节点时序的SS段范围内),通过延长PBS1段或缩短PBS2段来获得同步
所有的同步都是由CAN控制器硬件自动完成的。
CAN的波特率:
各个通讯节点约定好1个Tq的时间长度以及每一位占据多少个Tq,就可以确定CAN通讯的波特率。
17.2 STM32的CAN外设
17.2.1 CAN外设(CAN控制器)介绍
STM32的芯片中具有bxCAN控制器(Basic Extended CAN),它支持CAN协议2.0A 和2.0B Active标准。(CAN2.0A只能处理标准数据帧且扩展帧的内容会识别错误。而CAN2.0 B Active可以处理标准数据帧和扩展数据帧。CAN2.0 B Passive只能处理标准数据帧而扩展帧的内容会被忽略)。
该CAN控制器支持最高的通讯速率为1Mb/s;可以自动地接收和发送CAN报文,支持使用标准ID和扩展ID的报文;外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文;可配置成自动重发;不支持使用DMA进行数据收发。
17.2.2 CAN控制器的4种工作模式
CAN控制器有3种工作模式:初始化模式,正常模式,睡眠模式。
上电复位后CAN控制器默认会进入睡眠模式,作用是降低功耗。当需要将进行初始的时候(配置寄存器),会进入初始化模式。当需要通讯的时候,就进入正常模式。
17.2.3 CAN控制器的3种测试模式
有3种测试模式:静默模式、环回模式、环回静默模式。当控制器进入初始化模式的时候才可以配置测试模式。
静默模式可以用于检测总线的数据流量。环回模式可以用于自检(影响总线)。环回静默也是用于自检,不会影响到总线。
17.2.4 功能框图
发送邮箱:用来缓存待发送的报文,最多可以缓存3个报文。发送调度决定报文的发送顺序。
接收FIFO:共有2个接收FIFO(先进先出),每个FIFO都可以存放3个完整的报文。它们完全由硬件来管理。从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文。
接收滤波器(过滤器)
做用:对接到的报文进行过滤。最后放入FIFO 0或FIFO 1。
当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的存在,选择性接收有效报文,减轻系统负担。
有2种过滤模式:
- 标识符列表模式,它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。
- 掩码模式(屏蔽位模式),它把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO。
如果使能了筛选器,且报文的ID与所有筛选器的配置都不匹配,CAN外设会丢弃该报文,不存入接收FIFO。
每个CAN提供了14个位宽可变的、可配置的过滤器组(13~0)。每个过滤器组x由2个32位寄存器,CAN_FxR1和 CAN_FxR2组成。
屏蔽位写1,表示对应ID位必须要匹配才接收。屏蔽位写0对应的ID位可为1可为0都接收。
说明:
- 当工作于32位屏蔽位模式时,FR1保存标识符,FR2保存屏蔽。FR2某位是1表示来的ID的这位必须和FR1中对应的位一致,FR2某位是0,表示ID的这位不关心。
- 当工作于32位标识符模式时。FR1和FR2分别保存两个标识符。这意味着将来只有两个ID会匹配成功。
十八 物联网通信之以太网通讯
15.1 以太网通讯基础知识
15.1.1 什么是以太网
以太网(Ethernet)是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问控制的内容。以太网是目前应用最普遍的局域网技术,取代了其他局域网标准如令牌环、FDDI和ARCNET。
以太网的标准拓扑结构为总线型拓扑,但目前的快速以太网(100BASE-T、1000BASE-T标准)为了减少冲突,将能提高的网络速度和使用效率最大化,使用交换机(Switch hub)来进行网络连接和组织。如此一来,以太网的拓扑结构就成了星型;但在逻辑上,以太网仍然使用总线型拓扑和CSMA/CD(Carrier Sense Multiple Access/Collision Detection,即载波多重存取/碰撞侦测)的总线技术。
经过长期的发展,以太网已成为应用最为广泛的局域网,包括标准以太网(10 Mbit/s)、快速以太网(100 Mbit/s)、千兆以太网(1000 Mbit/s)和万兆以太网(10 Gbit/s)等。IEEE 802.3规范则是基于以太网的标准制定的,并与以太网标准相互兼容。
15.1.2 互联网和以太网的区别
- 简单来说,网络按照区域来划分,分为广域网和局域网。这只是按照使用区域大小来划分的。就像省和村的关系。
- 然后在这个小区域(局域网)里建设网络,就需要使用多种标准技术,其中电气标准中规定用双绞线还是单芯线等,这个电气标准中有以太网技术、令牌环网技术、ATM网技术、帧中继技术等,不要被以太网中这个网字迷惑,把它看成技术,我们是用了CSMA/CA技术(别名:以太网技术),使用方便、网络建造简洁,以太网技术就是流传开来。
- 局域网中物理网络按照以太网技术敷设完毕,还并不能通信,这个时候就需要其他技术标准,我们经常见到的TCP/IP技术,TCP/IP技术可以依托以太网技术、令牌环网技术等上使用,而且我们经常TCP/IP与以太网配合使用,所以我们日常中口语中容易将TCP/IP与以太网技术混在一起说。其实是不同层级的技术。
这些所有的东西组合在一起就是互联网了。
互联网是一个范围的概念,以太网只是这个范围内的一种技术。
15.1.3 以太网的层次
以太网采用无源的介质,按广播方式传播信息。它规定了物理层和数据链路层协议,规定了物理层和数据链路层的接口以及数据链路层与更高层的接口。
1)物理层
物理层规定了以太网的基本物理属性,如数据编码、时标、电频等。
物理层位于OSI参考模型的最底层,它直接面向实际承担数据传输的物理媒体(即通信通道),物理层的传输单位为比特(bit),即一个二进制位(“0”或“1”)。实际的比特传输必须依赖于传输设备和物理媒体,但是,物理层不是指具体的物理设备,也不是指信号传输的物理媒体,而是指在物理媒体之上为上一层(数据链路层)提供一个传输原始比特流的物理连接。
2)数据链路层
数据链路层是OSI参考模型中的第二层,介于物理层和网络层之间。数据链路层在物理层提供的服务的基础上向网络层提供服务,其最基本的服务是将源设备网络层转发过来的数据可靠地传输到相邻节点的目的设备网络层。
由于以太网的物理层和数据链路层是相关的,针对物理层的不同工作模式,需要提供特定的数据链路层来访问。这给设计和应用带来了一些不便。
为此,一些组织和厂家提出把数据链路层再进行分层,分为媒体接入控制子层(MAC)和逻辑链路控制子层(LLC)。这样不同的物理层对应不同的MAC子层,LLC子层则可以完全独立。
15.1.4 OSI7层模型
OSI(Open System Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。ISO为了更好的使网络应用更为普及,推出了OSI参考模型。其含义就是推荐所有公司使用这个规范来控制网络。这样所有公司都有相同的规范,就能互联了。
OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层),即ISO开放互连系统参考模型。
每一层实现各自的功能和协议,并完成与相邻层的接口通信。OSI的服务定义详细说明了各层所提供的服务。
15.1.5 TCP/IP 4层模型
众所周知,OSI参考模型是学术上和法律上的国际标准,是完整的权威的网络参考模型。而TCP/IP参考模型是事实上的国际标准,即现实生活中被广泛使用的网络参考模型。
OSI引入了服务、接口、协议、分层的概念,TCP/IP借鉴了OSI的这些概念建立TCP/IP模型。
OSI先有模型,后有协议,先有标准,后进行实践;而TCP/IP则相反,先有协议和应用再提出了模型,且是参照的OSI模型。
OSI是一种理论模型,而TCP/IP已被广泛使用,成为网络互联事实上的标准。
15.1.6 一些常见的网络协议
1)IP协议
IP 协议是TCP/IP协议族中最为核心的协议,更确切的说是网络层重要的协议之一。
IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层;相反,IP层也把从TCP或UDP层接收来的数据包传送到更低层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是否按顺序发送的或者有没有被破坏,IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)。
2)TCP协议
TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。
TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。
3)UDP协议
UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。
UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。
UDP与TCP位于同一层,但UDP不管数据包的顺序、错误或重发。
4)HTTP和HTTPS协议
HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。
HTTP 是一个基于 TCP/IP 通信协议来传递数据(HTML文件、图片文件、查询结果等)。
HTTPS 协议是 HyperText Transfer Protocol Secure(超文本传输安全协议)的缩写,是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包,HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换资料的隐私与完整性。
15.2 W5500芯片
要进行通讯,需要相应的硬件支持,在嵌入式应用领域,应用最广泛的一个以太网芯片就是W5500,素有以太之王的称号。
官方网址:https://www.w5500.com/index.html
15.2.1 W5500芯片介绍
是韩国半导体公司WIZnet提供的一款高性价比的以太网芯片。其全球独一无二的全硬件TCPIP协议栈专利技术,解决了嵌入式以太网的接入问题,简单易用,安全稳定,是物联网设备的首选解决方案。
W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。
久经市场考验的WIZnet全硬件 TCP/IP 协议栈支持 TCP,UDP,IPv4,ICMP,ARP,IGMP以及PPPoE协议。W5500内嵌32K字节片上缓存以供以太网包处理。 如果你使用 W5500,你只需要一些简单的Socket编程就能实现以太网应用。这将会比其他嵌入式以太网方案更加快捷、简便。
用户可以同时使用8个硬件Socket独立通讯。 W5500 提供了SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。而且,W5500的使用了新的高效SPI协议支持80MHz 速率,从而能够更好的实现高速网络通讯。为了减少系统能耗,W5500提供了网络唤醒模式(WOL)及掉电模式供客户选择使用。
15.2.2 W5500芯片特点
- 支持硬件TCP/IP协议:TCP,UDP,ICMP,IPv4,ARP,IGMP,PPPoE。
- 支持8个独立端口(Socket)同时通讯。
- 支持掉电模式。
- 支持网络唤醒。
- 支持高速串行外设接口(SPI模式 0,3)。
- 内部32K字节收发缓存。
- 内嵌10BaseT/100BaseTX 以太网物理层(PHY)。
- 支持自动协商(10/100-Based 全双工/半双工)。
- 不支持 IP 分片。
- 3.3V工作电压,I/O 信号口5V耐压。
- LED状态显示(全双工/半双工,网络连接,网络速度,活动状态)。
- LQFP48无铅封装(7x7mm,间距 0.5mm)
15.2.3 应用目标
W5500适合于以下嵌入式应用:
- 家庭网络设备:机顶盒、个人录像机、数码媒体适配器。
- 串行转以太网:门禁控制、LED 显示屏、无线 AP 继电器等。
- 并行转以太网:POS/微型打印机、复印机。
- USB 转以太网:存储设备、网络打印机。
- GPIO 转以太网:家庭网络传感器。
- 安全系统:数字录像机、网络摄像机、信息亭。
- 工厂和楼宇自动化控制系统。
- 医疗监测设备。
- 嵌入式服务器。
15.2.4 接入框图
15.2.5 主控芯片与W5500交互
1)SPI连接
W5500 提供了SPI(串行外部接口)作为外设主机接口,有SCSn、SCLK、MOSI、MISO 共4路信号,且作为SPI从机工作。
在W5500中只支持工作模式0和3,在这两种模式下数据总是在SCLK信号的上升沿被锁存,在SCLK信号的下降沿被输出。
MOSI和MISO信号无论是接收或发送,均遵从最高标志位(MSB)到最低标志位(LSB)的传输序列。
2)固定数据长度模式和可变数据长度模式
如果SPI工作模式设置为可变数据长度模式(VDM),SPI 的SCSn信号需要由外部主机通过SPI帧控制。
在可变数据长度模式下,SCSn控制SPI帧的开始和停止:SCSn信号拉低(高电平到低电平),即代表W5500的SPI帧开始(地址段);SCSn信号拉高(低电平到高电平),即代表W5500的 SPI帧结束(数据段的随机N字节数据结尾);
在我们的电路图设计中,ScSn接的是片选信号,所以我们应该选择可变数据长度模式。
3)W5500的内部存储器
- 1个普通寄存器block:这里配置了W5500的一些基本信息,如网络配置(IP,MAC地址,Socket中断配置等)。
- 8个Socket寄存器block:这里配置了每个Socket对应的信息,如Socket的模式,命令,状态,中断信息等。
- 8个Socket对应的接收缓冲寄存器block(共16k):初始时每个Socket分配为2k的缓存,用户可以自己重新通过修改相应的配置寄存器进行修改,但是要保证分配给8个Socket的缓冲大小之和不能超过16k,否则会报错。
8个Socket对应的发送缓冲寄存器block(共16k)。
15.3 以太网通讯案例1:网络搭建
15.3.1 需求描述
驱动W5500芯片,设置好IP,测试网络是否连通。
15.2.3 硬件电路设计
引脚说明
- W5500-RST:重置硬件,重置(Reset)低电平有效;该引脚需要保持低电平至少 500 us,才能重置 W5500;(正常使用应该高电平,需要重置芯片的时候置为低电平不少500us)。连接的是PG7。
- W5500-INT:中断输出(Interrupt output)低电平有效;低电平:W5500的中断生效。高电平:无中断;连接的是PG6。
- W5500-CS片选引脚。
- 连接的是STM32的SPI2外设。