STM32学习笔记

写在前面:
本篇学习笔记记录了笔者以往学习STM32的过程,其中大部分内容都是来源于江科大、野火、武汉理工大学电子科技学会、正点原子、Z小旋,由衷感谢产出如此优秀内容的博主。本篇学习笔记旨在记录cubemx操作和keil编写的细节、巩固自身知识体系,如果本篇笔记也能帮到你的话,哪怕只是一点点,我就很开心了。

文章目录

一、STM32简介

(一)关于STM32F103c8t6

  • 系列:主流系列STM32F1
  • 内核:ARM Cortex-M3
  • 主频:72MHz
  • RAM:20K(SRAM)
  • ROM:64K(Flash)
  • 供电:2.0~3.6V(标准3.3V)
    (51跟USB都是5v(需要加降压芯片))
  • 封装:LQFP48

(二)片上资源/外设

英文缩写名称英文缩写名称
NVIC嵌套向量中断控制器(片内)CANCAN通信
SysTick系统滴答定时器(片内)USBUSB通信
RCC复位和时钟控制RTC实时时钟
GPIO通用IO口CRCCRC校验
AFIO复用IO口PWR电源控制
EXTI外部中断BKP备份寄存器
TIM 定时器(高级、通用、基本)IWDG独立看门狗(即时复位芯片)
ADC模数转换器WWDG窗口看门狗
DMA直接内存访问(搬运数据)DAC数模转换器
USART同步/异步串口通信SDIOSD卡接口
I2CI2C通信FSMC可变静态存储控制器
SPISPI通信USB OTGUSB主机接口

(三)命名规则

在这里插入图片描述

(四)系统结构

在这里插入图片描述
F1架构分为四个主动单元和四个被动单元

主动单元被动单元
主动单元(主动发起通信)被动单元
Cortex-M3内核DCode总线内部FLASH
Cortex-M3内核系统总线内部SRAM
通用DMA1FSMC
通用DMA2AHB到APB的桥,连接到所有APB外设

(五)引脚定义

在这里插入图片描述
红色是电源相关引脚、蓝色是最小系统相关引脚、绿色是IO口、功能口,S是电源,I口是输入,O口是输出,FT表示能容忍5v的电压,主功能是上电后默认的功能,一般与引脚名称相同。要是需要用到一个引脚上复用的两个功能,可以重定义映射到其他端口上。
1号引脚VBAT是用以给备用电池供电的引脚,可接一个3v的电池,当系统断电时,备用电池可以给内部的RTC时钟和备份寄存器提供电源。
2号引脚可用作IO口或者侵入检测或者RTC(IO口是读取高低电平)(侵入检测是用来做安全保障的功能)(RTC可以用来输出RTC校准时钟脉冲或秒脉冲)
3号引脚和4号引脚可用作IO口或者是外接32.768KHz的RTC晶振
5号引脚和6号引脚接系统主晶振,一般是8MHz,芯片内有锁相环电路,可以对 8MHz进行倍频,最终产生72MHz的频率,作为系统的主时钟(这个功能在今后会时常应用)
7号引脚系统复位引脚,N代表低电平复位
8号引脚和9号引脚是内部模拟部分的电源,比如ADC,RC振荡器,VSS是负极接GND,VDD是正极接3.3V
10号引脚至19号引脚是IO口。其中10号引脚有唤醒STM32的功能
20号引脚是IO口和BOOT1复用。BOOT1用来配置启动模式的其中一位。
21号引脚和22号引脚是IO口
23号引脚和24号引脚VSS-1和VDD-1是主电源口
35号引脚36号引脚和47号引脚48号引脚也是系统主电源
这是因为STM32内部采用分区供电的方式,所以供电口多,使用时将VSS都接GND,所有VDD接3.3v就行
25号引脚至33引脚IO口
34号引脚37号引脚44号引脚都是IO口或者调试端口 默认调试
STM32支持SWD与JTAG两种调试方式
SWD需要SWDIO和SWCLK两根线(只用占PA13和PA14两个IO口,PA15、PB3、PB4可做普通IO口使用,但是需要配置)
JTAG需要五根线JTMS、JTCK、JTDI、JTDO、NJTRST
41号引脚至43号引脚、 45号引脚至46号引脚都是IO口
44号引脚BOOT0是配置启动模式

(六)IO口复用功能

  1. IO口引脚通用:IO端口的输入和输出是由GPIO外设控制
  2. IO口引脚复用:IO端口的输入或输出是由其他非GPIO外设控制,我们称之为复用
  3. 同一时间IO只能复用一种功能
  4. 重映射to be continue

(七)启动配置

在这里插入图片描述启动配置的作用就是指定程序开始运行的位置。一般情况下,程序都是在Flash程序存储器开始执行。但是在特殊情况下可以在别的地方开始执行,用以完成特殊的功能。01用于串口下载程序,11是用于程序调试的。
指令执行的工作是由编译工具链执行的(toolchains)
芯片上电后就会触发复位异常,并且会跳转到中断向量表特定偏移位置,获取里面的内容执行。
只要修改复位异常内的内容,就可以让处理器去执行我们指定的操作
启动文件(.s文件(汇编语言))中的中断向量表

  1. 设置堆栈指针(初始化)让指针指向栈顶
  2. 设置PC指针(指向当前指令再加8的位置)
  3. 设置中断向量表的入口
  4. 配置系统时钟
  5. 跳转到.c初始化堆栈跳转到我们编写的代码
    中断向量表中的DCD会为指定目标分配一块以字(一个字为4个字节)为单位的空间

(八)STM32的寻址范围

二、GPIO

(一)GPIO简介

  1. GPIO(General Purpose Input Output)通用输入输出口IO口
  2. 可配置为8种输入输出模式
  3. 引脚电平:0V~3.3V,部分引脚可容忍5V
  4. 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等
  5. 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

(二)GPIO的基本结构

在这里插入图片描述
STM32的所有的GPIO外设都挂接在APB2总线上,每个GPIO外设都有16个引脚,编号从0→15.每个GPIO模块主要包含了寄存器、驱动器等。

(三)GPIO位结构

在这里插入图片描述

最右边的二极管起到保护作用,对电压进行限幅。(0~3.3)

输入部分电路

  1. 上拉电阻和下拉电阻可以通过程序控制两个电阻主要是为了给输入提供一个默认的输入电平高电平或低电平(弱上拉/弱下拉)
    (1) 上边导通下面断开就是上拉模式(默认作为高电平的输出模式)
    (2) 上面断开下面导通就是下拉模式(默认作为低电平的输出模式)
    (3) 两个都断开就是浮空输入模式
  2. 施密特触发器
    对电压进行整形的装置,如果输入电压大于某一阈值,输出就会瞬间升为高电平,如果输入电压小于某一阈值,输出就会瞬间升为低电平。
  3. 通过读取输入数据寄存器就能在知道输入的信号是高电平还是低电平
  4. 模拟输入接在施密特触发器之前
  5. 复用功能输入是接到需要获取数据的其他外设(接收的是数字量)

输出部分的电路

可由输出数据寄存器(普通IO控制)或外设通过数据选择器接到输出控制部分

  1. 位设置/清除寄存器
    可以独立操作输出寄存器的某一位而不影响其他位(这是由于输出数据寄存器同时控制16个端口且只能整体读写)
    想要进行位操作有三种方式:
    (1) 用C语言的&=、|=进行操作
    (2) 用位寄存器进行操作(库函数)
    (3) 读写STM32的位带区域
  2. 位带是STM32中专门分配的一段地址区域,这段地址映射了RAM和外设寄存器的所有位(相当于51中的位寻址),读写这段地址中的数据就相当于读写所映射位置的某一位。
  3. 输出控制接到了两个MOS管上
    用信号控制开关的导通与关闭,开关负责将IO口接到VDD或VSS上。可选择推挽、开漏、关闭三种模式。
    (1) 推挽模式下,PMOS和NMOS均有效,数据寄存器为1是,上管导通,下管断开,输出直接接到VDD,就是高电平;强推输出模式,此时STM32对IO口有绝对的控制权
    (2) 开漏输出的模式下PMOS是无效的,只有NMOS工作。也就是说只有低电平有驱动能力,高电平没有。I2C总线就是这种模式,在多级通信的情况呢下可以避免各个设备的互相干扰。此时的IO口可以输出5v的电压。因为可以在IO口外部接一个5v的上拉电阻,由外部上拉电阻拉高至5v,用于兼容5v设备。
    (3) 关闭模式,当引脚配置为输入模式时,两个mos管都无效,输出关闭,端口的信号由外部控制。
    (4) 在推挽输出的模式下,GPIO口的驱动能力比较强,但是开漏输出可以实现线与的功能—I2C总线。多个设备挂接在同一个输出的IO口,只要总线输入接0,整条线的电平就都是0.

(四)GPIO特点

  1. 不同型号的IO开口数量可能不同
  2. 可以实现快速反转,最快的周期是50MHz
  3. 每一个IO都可以用于中断
  4. 有八种工作模式

(五)GPIO的工作模式

4种输入模式

(1)GPIO_Mode_IN_FLOATING 浮空输入
(2)GPIO_Mode_IPU 上拉输入
(3)GPIO_Mode_IPD 下拉输入
(4)GPIO_Mode_AIN 模拟输入

4种输出模式

(5)GPIO_Mode_Out_OD 开漏输出(带上拉或者下拉)
(6)GPIO_Mode_AF_OD 复用开漏输出(带上拉或者下拉)
(7)GPIO_Mode_Out_PP 推挽输出(带上拉或者下拉)
(8)GPIO_Mode_AF_PP 复用推挽输出(带上拉或者下拉)

  1. 浮空/上拉/下拉输入
    在这里插入图片描述

  2. 模拟输入
    在这里插入图片描述

  3. 开漏/推挽输出
    在这里插入图片描述

  4. 复用开漏/推挽输出
    在这里插入图片描述

4种最大输出速度

(1)2MHZ (低速)
(2)25MHZ (中速)
(3)50MHZ (快速)
(4)100MHZ (高速)

(六)GPIO的电气特性

  1. STM32的工作电压范围2~3.6
  2. GPIO识别的电压范围coms端口-0.3v ~ 1.164v逻辑01.833v ~ 3.6v逻辑1
  3. GPIO的最大输出电流位25mA

(七)需要自己会写出来的代码

需要会写出来的代码

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);//写入引脚的状态
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);//读出引脚的状态
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//翻转引脚的状态
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);锁定引脚的状态

(八)关于cubemx的设置

1.设置RCC
请添加图片描述

  1. 点击RCC
  2. 高速时钟(HSE)选择外部晶振
  3. 软件自动配置管脚
    2.GPIO的各种模式设置
    请添加图片描述
    GPIO output level 引脚电平设置 高/低
    GPIO mode GPIO模式 推挽输出/开漏输出
    GPIO Pull-up/Pull-dowm 上拉下拉电阻 上拉电阻/下拉电阻/无上拉或下拉
    Maxinum output speed 引脚速度设置 低速/中速/高速
    User Label 用户标签 给引脚设置名称 如LED0

三、C语言知识补充

(一)C语言数据类型

后缀_t就是用typedef重命名的变量类型

关键字位数表示范围Stdint关键字ST关键字
char8-128 ~ 127int8_ts8
unsigned char80 ~ 255uint8_tu8
short16-32768 ~ 32767int16_ts16
unsigned short160 ~ 65535uint16_tu16
Int(在51中int占16位)32-2147483648 ~ 2147483647int32_ts32
unsigned int320 ~ 4294967295uint32_tu32
long32-2147483648 ~ 2147483647
unsigned long320 ~ 4294967295
long long64-(2^64)/2 ~ (2^64)/2-1int64_t
unsigned long long640 ~ (2^64)-1uint64_t
float32-3.4e38 ~ 3.4e38
double64-1.7e308 ~ 1.7e308

(二)C语言宏定义

  1. 关键字:#define
  2. 用途:用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改
  3. 定义宏定义:#define ABC 12345
  4. 引用宏定义:int a = ABC; //等效于int a = 12345;

(三)C语言typedef

  1. 关键字:typedef
  2. 用途:将一个比较长的变量类型名换个名字,便于使用
  3. 定义typedef:typedef unsigned char uint8_t;
  4. 引用typedef: uint8_t a; //等效于unsigned char a;
  5. 对于宏定义而言 变量的新名字在左边whiletypedef的新名字在右边。而typedef只能给变量换名字,只是对于变量类型的重命名而言,使用typedef跟安全

(四)C语言结构体

  1. 关键字:struct可以理解为一种新的变量类型
  2. 用途:数据打包,不同类型变量的集合(数组只能实现同一类型的打包 )
  3. 定义结构体变量:
    struct{char x; int y; float z;} StructName;
    因为结构体变量类型较长,所以通常用typedef更改变量(struct{char x; int y; float z;})类型名
  4. 引用结构体成员:
    StructName.x = ‘A’;
    StructName.y = 66;
    StructName.z = 1.23;
    或 pStructName->x = ‘A’; //pStructName为结构体的地址 pStructName->y = 66;
    pStructName->z = 1.23;

(五)C语言枚举

  1. 关键字:enum
  2. 用途:定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合 在STM32里好像只能赋值字符串,不能直接赋值
  3. 定义枚举变量:
    enum{FALSE = 0, TRUE = 1} EnumName;
    因为枚举变量类型较长,所以通常用typedef更改变量类型名
  4. 引用枚举成员:
    EnumName = FALSE;
    EnumName = TRUE;

四、EXTI外部中断

(一)中断系统

  1. 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
  2. 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
  3. 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

(二)中断执行流程

在这里插入图片描述

(三)STM32中断

  1. 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
  2. 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
中断优先级分为可编程和不可编程
中断函数的地址是由编译器来分配的,不是固定的,但是中断的跳转,由于硬件的限制,只能跳转到固定的地址执行程序。所以为了能然硬件跳转到一个不固定的中断函数里,这里就需要在内存定义一个地址的列表。列表的地址是弧顶的,中断发生后,就跳这个固定的位置,然后在这个固定的位置,由编译器再加上一条跳转到中断函数的代码。遮掩中断就可以跳转到任意位置了。中断地址的裂变,就叫做中断向量表,相当于中断跳转的一个跳板。

(四)NVIC基本结构

在这里插入图片描述
NVIC的名字叫做嵌套中断向量控制器,用于分配中断优先级和管理中断的,是一个内核外设。

(五)NVIC优先级分组

  1. NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级。但是STM323只使用了M3内核里支持8bit优先级的高四位
  2. 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
  3. 小值优先
  4. 打断就会发生中断嵌套,不打断就会挂起。没办法打断排队的状态叫做中断悬起
  5. 抢占优先级可以打断中断
  6. 响应优先级要等待上一个中断结束后才能进项运算。
  7. 抢断优先级>响应优先级>RQ编号
分组方式抢占优先级响应优先级
分组00位,取值为0 4位取值为0~15
分组11位,取值为0~ 1 3位取值为0~7
分组22位,取值为0~ 3 2位取值为0~3
分组33位,取值为0~ 7 1位取值为0~1
分组44位,取值为0~15 0位取值为0

(六)EXTI简介

  1. EXTI(Extern Interrupt)外部中断
  2. EXTI可以监测指定GPIO口的电平信号,(一)当其指定的GPIO口产生电平变化时,(二)EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
  3. 支持的触发方式:上升沿/下降沿/双边沿/软件触发
  4. 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(就是不同分区的同一号引脚不能同时触发中断)
  5. 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
  6. 触发响应方式:中断响应/事件响应(如果触发响应的方式选择时间响应,外部中断的信号就不会通向CPU了,就直接通向外设(外设之间的联动操作))

(七)EXTI基本结构

在这里插入图片描述
事件的完成若无法通过寄存器读取,那便是不可见的事件。这时需要通过事件触发中断或者是事件触发另一个事件进行反馈。
GPIO的三部分ABC的各自16个引脚都接到AFIO(中断引脚选择寄存器)上,引出16个脚接到EXTI上,所以不同分区的外设不能使用同1个引脚号的引脚进行中断。
EXTI于IO口的关系
EXTI线0与引脚号相同
通过AFIO_EXTICR1的EXTI[3:0]四位控制那根IO引脚与EXTI接到一起
一共有四个CRx寄存器
在这里插入图片描述

之后ETIO只把外部中断的9号引脚~ 5号引脚,15号引脚~ 10号引脚分配到一个通道上。也就是说外部中断的9号引脚~ 5号引脚号引脚和15号引脚~10号引脚会触发同一个中断。

(八)AFIO复用IO口(数据选择器)

  1. AFIO主要用于引脚复用功能的选择和重定义
  2. 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

(九)EXTI框图

在这里插入图片描述

1.外部中断的执行流程

  1. 数据输入线先进过触发选择处理器,所以可以选择上升沿/下降沿/双边沿触发。经过一个或门,再经过一个与门。
  2. 输出端:
    (1) NVIC中断控制器是嵌套的;
    (2) 请求挂起寄存器是用来控制中断请求的生成
    (3) 中断请求有生成且中断未被屏蔽,就能导NVIC
  3. 脉冲发生器是产生一个波形与其他设备进行联动的唤醒MCU

中断触发之后到NVIC中断控制器中,进入向量表,偏移到入口地址,然后跳转到中断服务函数。中断服务函数如果没有被定义,就会默认是一个死循环

2.中断服务函数

STM32中的所有中断函数都有固定的名字,只有找到这个名字,在这个固定的函数名下编写中断服务函数才有效。而所有的中断服务函数编号都在stm32f10x_it.c,我的理解是头文件放在这里了。而函数体放在starup_stm32f10x_ms_s文件中。在相应的EXTI线的中断号下编写代码。

在这里插入图片描述
中断的回调机制:
中断使能后,调用HAL_PPP_IRQHandler();中断服务函数
在HAL_PPP_IRQHandler();中调用中断处理公共函数
在其中自己定义的回调函数中写入中断后要处理的程序

五、复位

(一)系统复位

系统将复位除时钟控制器寄存器CSR中的复位标志和备份区域中的寄存器以外的所有寄存器为他们的复位数值。

  1. NRST引脚上的低电平复位(复位按钮)
  2. 窗口看门狗
  3. 独立看门狗i
  4. 软件复位
  5. 低功耗管理复位

(二)电源复位

电源复位将复位除了备份区域外的所有寄存器

(三)后备域复位

备份区域拥有两个专门的复位,他们只影响备份区域

六、STM32时钟

时钟可以理解CLK脉冲信号(方波)“心跳”

(一)时钟源:

类型频率材料应用
HSE8MHz晶体/陶瓷(成本高)SYSCLK/RTC
LSE32.768KHz晶体/陶瓷RTC
HIS80MHzRC(稳定性差)SYSCK
LSI40KHzRCRTC/IWDG

高速部分的时钟源经过PLL锁相环倍频后充当系统时钟。到达AHB总线进行分频,桥接到APB1和APB2和内核和外设。
低速的时钟源中LSI(接内部低速晶振可以为IWDG独立看门狗或RTC实时时钟)LSE只接RTC
HIS是集成在芯片内部的时钟
HSE晶振
PLL(锁相环倍频时钟)
使用时一般是用HIS芯片内时钟然后用PLL进行倍频获得一个较高的频率作为系统时钟。
最常用的是50%的方波
在这里插入图片描述
HSE晶振充当时钟源,原本8MHz的频率被PLLx9到72MHz,不分频提供给其他外设使用。因为APB1工作道德最大频率为36MHz,所以要进行二分频
STM32的时钟可以根据需求进行配置
时钟的频率越高,功耗也就越高,要考虑芯片的工作条件
时钟的配置

(二)外设时钟的使能和失能

_HAL_RCC_GPIOA_CLK_ENABLE()
_HAL_RCC_GPIOA_CLK_DISABLE()

(三)cubemx时钟树配置

在这里插入图片描述
点击cubemx中的clock configuration就会出现该界面

  1. 时钟源(前文以提过不再赘述)
  2. PLL Sourse Multiplexer(Multiplexer是一种可以将多个输入信号转化为一个输出信号的电子电路)锁相环倍频资源配置控制器输入电路是HSI(High Speed Interior)、HSE(High Speed External)。输出可以选择PLL、PLLMul倍频系数
  3. 系统时钟选择控制器。输入电路是HSI、HSE、PLLCLK。
  4. 系统实时时钟,AHB总线频率、AHB分频系数、;桥接到APB1、APB2外设上的时钟频率。需要注意的是APB1总线频率最多为32MHz,需要至少2分频。最右侧就是挂接在总线上的各种外设。

(四)系统定时器

  1. systick系统定时器是属于Cortex-M内核的一个外设,内嵌于NVIC。
  2. systick计时器的时钟源来自AHB挂接的外设。
  3. SysTick系统计时器含有一个计数宽度为24bit(2^24)的向下递减(计数器的工作模式)的自动重装计数器,计时器每计数一次的时间为1/CLKsoure。一般我们设置CLKSoure为系统时钟 。以f103为例,CLKSoure可以配置为72MHz。计数器的工作周期为1/CLKsoure递减的频率为72MHz
  4. 当重装载数值寄存器的值递减到0的时候,Systick计时器可以配置产生一次中断。(在中断向量表也可以查得到)此时重转载寄存器就会将设置的处置重装至24位递减计数器
  5. 当重装载数值寄存器是属于与Corex—M内核的外设,所以一般基于Cortex—M内核的单片机都具备这个系统定时器。这使得软件在Cortex—M单片机中可与很容易的移植
  6. 产生时基,维持心跳
  7. 计数,产生ms,us级别的延时,产生特定的时序
    在这里插入图片描述

七、串口

(一)printf

1.printf的使用

  • printf的使用
  • printf(”字符串\r\n”);
  • printf(“%d\r\n”,x);
  • printf(“%%\r\n”);
  • printf(“\\r\n”);
  • printf(“\”\”\r\n”);

2.printf()函数的支持

避免使用半主机模式
半主机模式:通过仿真器实现开发板在电脑上的输入和输出

  1. 微库法:
    在魔术棒->勾选Use Micro LIO即可避免使用半主机模式
    在这里插入图片描述
  2. 代码法:
    一个预处理
    两个定义
    三个函数
    (1)在main函数与USART函数里加入#include<stdio.h>
    (2)在USART.c函数加入以下代码
//支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (uint8_t) ch;      
	return ch;
}
#endif 

会用就行

(二) 通信基础

1.串口串行/并行通信

在这里插入图片描述
在这里插入图片描述

2.单工/半双工/全双工

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
单工通信:数据只能沿一个方向传输
半双工通信:数据可以沿两个方向传输,但需要分时进行
全双工通信:数据可以同时进行双向传输

3.同步/异步

在这里插入图片描述
同步通信:共用同一时钟信号
异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号

4.比特率

  • 比特率:每秒钟传送的比特数,单位bit/s
  • 波特率:每秒钟传送的码元数,单位Baud
  • 比特率 = 波特率 * log2 M ,M表示每个码元承载的信息量
  • 二进制系统中,波特率数值上等于比特率

5.常见的串行通信接口

通信接口接口引脚数据同步方式数据传输方式
UART(通用异步收发器)TXD:发送端 RXD:接收端 GND:公共地异步通信全双工
1-wireDQ:发送/接收端异步通信半双工
IICSCL:同步时钟SDA:数据输入/输出端同步通信半双工
SPISCK:同步时钟MISO:主机输入,从机输出MOSI:主机输出,从机输入CS:片选信号同步通信全双工

(三)串口(RS-232)

串口:串行通信接口:指按位发送和接收的接口

1.RS-232

(DB9)一共有9个引脚

  • TXD(Pin3):串口数据输出
  • RXD(Pin2):串口数据输入
  • RTS(Pin7):请求发送
  • CTS(Pin8):清除发送
  • DSR(Pin6):数据发送就绪
  • DCD(Pin1):数据载波检测
  • DTR(Pin4):数据终端就绪
  • GND(Pin5):信号地
  • RI(Pin2):振铃指示
  • 异步所用的信号线

2.电平对比

逻辑RS-232COMSTTL
1-15v~ 3v3.3v5v
03v ~ 15v0v0v

3.STM32与电脑USB口通信

在这里插入图片描述

4.RS-232异步通信

在这里插入图片描述

  • 启动位:必须占1个位长,保持逻辑0电平
  • 有效数据位:可选5、6、7、8、9个位长,LSB在前,MSB在后
  • 校验位:可选占1个位长,也可以没有该位
  • 停止位:必须有,可选占0.5、1、1.5、2个位长,保持逻辑1电平

(四)STM32USART

1.STM32USART简介

Universal synchronous asynchronous receiver transmitter,通用同步异步收发器
Universal asynchronous receiver transmitter,通用异步收发器
USART/UART都可以与外部设备进行全双工异步通信
USART,我们常用的也是异步通信

2.STM32的USART主要特征

1,全双工异步通信
2,单线半双工通信
3,单独的发送器和接收器使能位
4,可配置使用DMA的多缓冲器通信
5, 多个带标志的中断源

3.STM32框图

在这里插入图片描述
TX输出引脚
RX输入引脚
SW_RX是芯片内部的引脚
RTS和CTS是与同步双工有关的引脚
SCLK是同步时钟
只能操作数据寄存器DR
下方通过波特率发生器作为时钟控制发送器和接收器
TE和RE是使能位

4.波特率的设置

在这里插入图片描述

波特率计算公式:baud=“fck” /(16*USARTDIV)
“其中fck” 是串口的时钟,如:USART1的时钟是PCLK2,其他串口都是PCLK1
首先系统的时钟的频率是一定的(72M/36M),再一起约定好波特率baud,就能算出我认为的一个中间值USARTDIV,之后取整数部分作为DIV_Mantissa,小数部分作为DIV_FACTION
整数部分的数值写入BRR高12位,小数部分写入低4位。
在这里插入图片描述

在这里插入图片描述

5.USART寄存器

在这里插入图片描述

(1)控制寄存器1(USART_CR1)

该寄存器需要完成的配置:
位13:使能USART
位12:配置8个数据位
位10:禁止检验控制
位5:使能接收缓冲区非空中断
位3:使能发送
位2:使能接收

(2)控制寄存器2(USART_CR2)

在这里插入图片描述
该寄存器需要完成的配置:配置1个停止位
只需要操作第12、13位

(3)控制寄存器3(USART_CR3)

在这里插入图片描述
该寄存器需要完成的配置:配置不选择半双工模式

(4)数据寄存器(DR)

在这里插入图片描述
设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器

(5)状态寄存器(SR)

在这里插入图片描述
根据TC位可以知道能否发数据,根据RXNE位知道是否收到数据

6.时序

在这里插入图片描述

  1. 启动位:必须占1个位长,保持逻辑0电平
  2. 有效数据位:8个位长,LSB在前,MSB在后
  3. 校验位:不要该位
  4. 停止位:1个位长,保持逻辑1电平

7.回调函数MSP

__weak关键字的使用是定义一个弱函数,这个函数的函数体通常是空的方便用户重写一个自己的函数HAL_PPP/PPPP_MspInit,来覆盖之前库函数中定义的函数带有__weak关键字的HAL_PPP/PPPP_MspInit函数,编译器在编译的时候,如果检查到有重名的(但不含__weak关键字)HAL_PPP_MspInit的函数,此时就会默认编译这个用户写的函数
以URAT为例,HAL_UART_Init();会调用HAL_UART_MSPInit();
用户需要自己定义一个新的MSP回调函数。

8.USART/UART通信配置步骤

1,配置串口工作参数
HAL_UART_Init()这里边是初始化很多变量
HAL_UART_Init(&UART1_Handler);
在MX_USART1_UART_Init(void);里边
然后在外部定义UART_HandleTypeDef UART1_Handler;//UART句柄
2,串口底层初始化
HAL_UART_ Msplnit() 配置GPIO、NVIC、CLOCK等
3,开启串口异步接收中断
HAL_UART_Receive IT()
写在初始化函数里
4,设置优先级,使能中断
HAL NVIC_SetPriority() HAL_NVIC_EnablelRQ()
中斷使能也需要自己配置
5,编写中断服务函数
USARTx_IRQHandler() or UARTx_IRQHandler()
6,串口数据发送
USART_DR, HAL_UART_Transmit()

9.UART关键结构体

UART_HandleTypeDef * huart1;
关键结构体(F1):
typedef struct
{ uint32_t BaudRate; /* 波特率 /
uint32_t WordLength; /
字长 /
uint32_t StopBits; /
停止位 /
uint32_t Parity; /
奇偶校验位 /
uint32_t Mode; /
UART 模式 /
uint32_t HwFlowCtl; /
硬件流设置 /
uint32_t OverSampling; /
过采样设置 */
}UART_InitTypeDef

10.需要掌握的代码

作用:以中断的方式接收指定字节的数据
形参 1 是 UART_HandleTypeDef 结构体类型指针变量
形参 2 是指向接收数据缓冲区
形参 3 是要接收的数据大小,以字节为单位

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

作用:以阻塞的方式发送指定字节的数据
形参 1 :UART_HandleTypeDef 结构体类型指针变量
形参 2:指向要发送的数据地址
形参 3:要发送的数据大小,以字节为单位
形参 4:设置的超时时间,以ms单位

HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

11.编程实战

1.USART发送
  1. 重新定义printf(前面已经提及)
  2. 直接在主函数中加入以下代码:
 
  while (1)
  {
		printf("test\n");
		HAL_Delay(1000);
  }
 
2.USART接收
  1. 在main中第一次调用接收中断函数
  2. 进入接收中断,接收完数据 后进入中断回调函数
  3. 重新定义HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,
  4. 回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

函数的路径十分曲折
(1) HAL_UART_Receive_IT(中断接收函数) (在主函数死循环中调用)
(2) USART2_IRQHandler(void)(中断服务函数) (重新在外部定义中断服务函数)
(3)HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数)(在中断服务函数中调用中断处理函数)
(4)UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数)(在中断处理公函数中接收中断)
(5)HAL_UART_RxCpltCallback(huart);(中断回调函数)(也是在函数体外定义回调函数)

具体代码实现
先在cube中配置引脚、波特率、时钟树等。

RCC请添加图片描述
串口设置
请添加图片描述

  1. 点击USATR1
  2. 设置MODE为异步通信(Asynchronous)
  3. 基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能
  4. GPIO引脚设置 USART1_RX/USART_TX
  5. NVIC Settings 一栏使能接收中断

设置时钟
请添加图片描述
外部晶振为8MHz

  1. 选择外部时钟HSE 8MHz
  2. PLL锁相环倍频72倍
  3. 系统时钟来源选择为PLL
  4. 设置APB1分频器为 /2
#include <string.h>
 
#define RXBUFFERSIZE  256     //最大接收字节数
char RxBuffer[RXBUFFERSIZE];   //接收数据
uint8_t aRxBuffer;			//接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0;		//接收缓冲计数

在main()主函数中,调用一次接收中断函数

	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);

在main.c下方添加中断回调函数

/* USER CODE BEGIN 4 */
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
 
	if(Uart1_Rx_Cnt >= 255)  //溢出判断
	{
		Uart1_Rx_Cnt = 0;
		memset(RxBuffer,0x00,sizeof(RxBuffer));
		HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); 	
        
	}
	else
	{
		RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
	
		if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
		{
			HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			Uart1_Rx_Cnt = 0;
			memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
}
/* USER CODE END 4 */

八、看门狗

(一)独立看门狗IWDG

  1. IWDG的全称:Independent watchingdog
  2. IWDG的本质:能产生系统复位信号的计数器
  3. IWDG的特性:递减的计数器
    时钟由独立的RC振荡器提供(可在待机和停止模式下运行)
    看门狗被激活后,当递减计数器计数到达0x000时产生复位
  4. 喂狗:在计数器计数到0之前,重装载计数器的值,防止复位

(二)系统复位

  1. NRST引脚上的低电平
  2. 窗口看门狗计数中止
  3. 独立看门狗计数中止
  4. 软件复位
  5. 低功耗管理复位

(三)IWDG作用

  1. 异常:外界电磁干扰或者自身系统(硬件/软件)异常
  2. 作用:主要用于检测外界电磁干扰,或是硬件异常导致的程序跑飞问题(软件一般是WWDG)
  3. 应用:在一些需要高稳定性的产品中,并且对时间精度要求较低的场合。
  4. 独立看门狗是异常处理最后的手段,不可以依赖,应在设计时尽可能避免异常的发生。
    在这里插入图片描述
    启动IWDG后,LSI时钟会自动开启
    LSI时钟的频率并不准确,F1用40KHz

(四)IWDG寄存器

1.键寄存器

在这里插入图片描述
键寄存器的三个用处:

  1. 喂狗
  2. 解除PR和RLR寄存器写访问保护
  3. 启动看门狗

4.预分频寄存器

在这里插入图片描述
只有低三位控制分频系数,其他位都是保留位

3.重装载寄存器

在这里插入图片描述
感觉键寄存器是开启喂狗的开关
而重装载寄存器是控制喂狗食物的数量

4.状态寄存器

在这里插入图片描述
查询预分频值与重装载值是否更新

(五)IWDG溢出时间的计算

Tout=pscrlr/fIWDG(Tout是看门狗溢出时间、fIWDG是看门狗时钟源频率,psc是看门狗预分频系数,rlr是看门狗重装载值)
寄存器设置分频系数方法:psc=4
2^prerprer是IWDG_PR后三位设置的数值
在这里插入图片描述

(六)编程实战

1.cubemx配置

(1)时钟树

请添加图片描述

(2)RCC

将时钟设置为外部高频晶振请添加图片描述

(3)系统时钟

系统时钟的Debeg设置为Serial Wire
请添加图片描述

(4)IWDG
  1. IWDG时钟预分频系数 4分频 (内部低速时钟频率约为40KHz)
  2. 计数器重装载值 4095
  3. 此时的溢出时间大致为0.4095us
    请添加图片描述

2.keil配置

增加头文件跟字符串

#include<string.c>
char*str2="Yes";
char*str1="No";
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init(); 
  MX_IWDG_Init();
  MX_USART1_UART_Init();
  HAL_UART_Transmit(&huart1, (uint8_t*)&str1, strlen(str1), 1000);
  while (1)
  {
      HAL_Delay(500);
	  HAL_UART_Transmit(&huart1, (uint8_t*)&str2,strlen(str2), 1000);
  }
}

看门狗复位的时间差不多是400us,要是在while(1)中Delay超过500us肯定会触发看门狗复位。然后发送字符串给上位机。

(七)窗口看门狗WWDG

  1. WWDG全称:WindowWatchingdog
  2. WWDG本质:能产生系统复位信号和提前唤醒中断的计时器
  3. WWDG特性:递减的计时器
    (1)当递减计时器值从0x40减到0x3f时复位(即t6为跳变到0)
    (2)计数器的值大于W[6:0]值时喂狗会复位
    (3)提前唤醒中断(EWI):当递减计数器等于0x40时可以产生
  4. 喂狗:在窗口期中喂狗

(八) WWDG作用

  1. 用于检测单片机程序运行时效是否精准,主要检测软件异常
  2. 用于需要精准程序运行时间的场合

(九)WWDG原理

在这里插入图片描述
由于WWDG本质上是一个递减计数器。初始化的时候需要设置窗口上限值。

  1. 在计数值没有小于窗口上限时进行喂狗,就会产生复位。
  2. 在计时器到达窗口期时可以喂狗。
  3. 在计数值递减到0x40时可以产生一个中断。
  4. 在计数值继续减1到达0x3F后还是会产生复位。
    在这里插入图片描述在这里插入图片描述
    F103的时钟源是APB1的36MHZ的RCC实时时钟,然后必须经过4096的分频,再经过预分频,之后进入看门狗控制寄存器。最大初始值是63.若是想产生复位信号,首先是WDGA激活位置1估计那个产生复位信号前一个门是与门。复位一种方式是T6位置0进过非门跟或门输出1.另一种是在非窗口期喂狗也会输出1.

(十)WWDG寄存器

1.控制寄存器

在这里插入图片描述
使能看门狗工作;即是喂狗

2.配置寄存器

在这里插入图片描述
提前唤醒中断需要两次使能:

  1. 位9置1
  2. NVIC中断向量控制寄存器的看门狗开关打开
    设置预分频系数
    设置窗口上限值>=窗口上限值就可以喂狗了

3.状态寄存器

判断是否发生了WWDG提前唤醒中断

在这里插入图片描述

(十一)WWDG超时时间计算

在这里插入图片描述
Tout是WWDG超时时间(没喂狗)
Fwwdg是WWDG的时钟源频率
4096是WWDG固定的预分频系数
2^WDGTB是WWDG_CFR寄存器设置的预分频系数值
T[5:0]是WWDG计数器低6位
在这里插入图片描述

(十二)WWDG配置步骤

1. HAL_WWDG_Init()
使能WWDG,设置预分频系数和窗口上限
2. HAL_WWDG_MspInit
3. HAL_NVIC_SetPriority HAL_NVIC_EnableIRQ
4. WWDG_IRQHandler()->HAL_WWDG_IRQHandler()->
HAL_WWDG_EarlywakeupCallback()
5. HAL_WWDG_Refresh()
重装载计时器->喂狗

(十三)需要掌握的代码

关键结构体

typedef struct 
{
  WWDG_TypeDef *Instance; /* WWDG 寄存器基地址 */ 
  WWDG_InitTypeDef Init;      /* WWDG 初始化参数 */
}WWDG_HandleTypeDef;
typedef struct 
{
  uint32_t Prescaler; /* 预分频系数 */
  uint32_t Window;    /* 窗口值 */
  uint32_t Counter;    /* 计数器值 */
  uint32_t EWIMode;  /* 提前唤醒中断使能 */ 
}WWDG_InitTypeDef;

相关函数

函数主要寄存器主要功能
HAL_WWDG_InitWWDG_CR/WWDG_CFR使能WWDG,设置预分频系数和窗口值等
HAL_WWDG_RefreshWWDG_CR重装载计数器,即喂狗
在HAL库中,每进行完一个中断,并不会立刻退出,而是会进入到中断回调函数中。
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hwwdg);
  HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_15);  //LED翻转  --闪烁表示喂狗成功
  HAL_WWDG_Refresh(hwwdg);      //喂狗
}

(十五)IWDG和WWDG的主要区别

时钟源LSI(40KHz或32KHz)PCLK1或PCLK3
复位条件递减计数到0计数值大于W[6:0]值喂狗或减到0x3F
中断没有中断计数值减到0x40可产生中断
递减计数器位数12位(最大计数范围:4096~0)7位(最大计数范围:127 ~ 63)
应用场合防止程序跑飞,死循环,死机检测程序时效,防止软件异常

九、定时器

(一)原理

1.软件计时

使用纯软件(CPU死等)的方式实现延时的功能
就是直接在while(1)里面Delay但是这样的延时是不精准的
一方面arm是流水线架构
另一方面是进入程序的压栈和出栈需要消耗时间

2.定时器定时原理

使用精准时基,通过硬件的方式,实现定时的功能 核心是计数器
在这里插入图片描述

3.STM32定时器分类

在这里插入图片描述

4.STM32定时器特性表

在这里插入图片描述
在这里插入图片描述

5.STM32基本定时器、通用定时器和高级定时器比较

定时器类型主要功能
基本定时器没有输入输出通道,常用作时基,即定时功能
通用定时器具有多路独立通道,可用于输入捕获/输出比较,也可用作时基
高级定时器除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等)
SMT32F1系列共有8个定时器:
高级定时器(TIM1、TIM8);通用定时器(TIM2、TIM3、TIM4、TIM5);基本定时器(TIM6、TIM7)。
(1)基本定时器功能(TIM6、TIM7):
  • 16位向上、向下、向上/下自动装载计数器
  • 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
  • 触发DAC的同步电路 注:此项是TIM6/7独有功能.
  • 位于APB1总线上
(2)通用定时器(TIM2~TIM5)的主要功能:
  • 16位向上、向下、向上/下自动装载计数器
  • 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
  • 4 个独立通道(TIMx_CH1~4)可以用作:
  1. 测量输入信号的脉冲长度( 输入捕获)
  2. 输出比较
  3. 单脉冲模式输出
  4. PWM输出(边缘或中间对齐模式)
    支持针对定位的增量(正交)编码器和霍尔传感器电路
    如下事件发生时产生中断/DMA:
    更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
    触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    输入捕获
    输出比较
    位于APB1总线上

高级定时器(TIM1,TIM8)的主要功能:

高级定时器具有基本,通用定时器的所有的功能,
还具有控制交直流电动机所有的功能,
输出6路互补带死区的信号,刹车功能等等
位于APB2总线上
总括:基本定时器就是单纯的定时计数器,通用定时器多了四个通道,相对应的增加了功能,高级定时器具有基本,通用定时器的所有的功能,并且添加了其他功能

(二)基本定时器

1.简介

  • 基本定时器:TIM6/TIM7
  • 主要特性:16位递增计数器(计数值:0~65535)
    16位预分频器(分频系数1~65536)
    分频系数等于预分频器(TIMx_PSC)的值+1
  • 可用于触发DAC
  • 在更新事件(计时器溢出)中,可产生中断/触发DAC

2.基本定时器框图

在这里插入图片描述

(1)时钟源

在这里插入图片描述
定时器的时钟源只能来自内部时钟

(2)控制器

在这里插入图片描述
当计数器溢出的时候可以产生中断信号

(3)计数器

在这里插入图片描述
影子寄存器是实际起作用的寄存器,不可以直接访问
溢出条件是CNT==ARR(自动重装载寄存器的重装载值)
ARR是自动重装载寄存器,起到缓冲的作用(ARPE位决定ARR是否具有缓冲)
预装载寄存器的值和分频系数将会在中断发生时写入影子寄存器
APB1的分频是从AHB上分过来的,72MHz最少是分一半到APB2

3.关于定时器的频率

在这里插入图片描述
如图,Timer2 ~ Timer7接到APB1总线上。Timer1 ~ Timer8接到APB2总线上。当锁相环倍频至72MHz是,APB2预分频系数为2,此时APB2达到最大频率32MHz,由于预分频系数不为1,所以定时器实际的频率为72MHz。对于APB1,预分频系数可以是1,所以所有定时器最高频率都是72MHz。

4.STM32定时器计数模式及溢出条件

计数器模式溢出条件
计数器模式 溢出条件
递增计数模式CNT==ARR(影子)
递减计数模式CNT==0
中心对齐模式CNT== ARR-1、CNT==1
在这里插入图片描述
  • 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
  • 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
  • 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
(1)递增计数模式

在这里插入图片描述
该时序图是PSC预分频系数为1、ARR计数初值为36的情况。可以看到预分频系数PSC为1的情况下,时钟线也需要经过两个周期计数值才会+1.这是因为Psc设置为1相当于分频系数为2(这是为什么呢?我也想知道)。计数值向上递增到ARR == 36才会触发中断,也就是触发一次更新事件。

(2)递减计数模式

在这里插入图片描述
该时序图也是PSC预分频系数为1、ARR计数初值为36的情况。与递增计数模式类似。

(3)中心对齐模式

在这里插入图片描述
这里是PSC=0,ARR=6的情况。

5.定时器中断相关寄存器

(1)TIM6和TIM7控制寄存器

在这里插入图片描述

  1. 用于设置ARR寄存器往影子寄存器写入数据的时候有无缓冲
  2. 使能/关闭计数器
(2)TIM6和TIM7DMA/中断使能控制器

在这里插入图片描述
用于使能更新中断

(3)TIM6和TIM7状态寄存器

在这里插入图片描述
判断是否发生了更行中断,由硬件置1,软件清零

(4)TIM6和TIM7计数器

在这里插入图片描述
计数范围:0 ~ 65535

(5)TIM6和TIM7计数器预分频器

在这里插入图片描述
用于设置预分频系数,范围是0 ~ 65535
实际的预分频系数为PSC+1

(6)TIM6和TIM7自动重转载寄存器

重装载值为0 ~ 65535
在这里插入图片描述

5.定时器计算溢出时间

  • Tout是定时器溢出时间
  • Ft是定时器的时钟源频率
  • ARR是自动重装载寄存器的值
  • PSC是预分频器寄存器的值
    加粗样式

6.定时器中断配置步骤

  1. 配置定时器基础工作参数HAL_TIM_Base_Init()
  2. 定时器基础MSP初始化HAL_TIM_Base_MspInit() 配置NVIC、CLOCK等
  3. 使能更新中断并启动计数器HAL_TIM_Base_Start_IT()
  4. 设置优先级,使能中断 HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()
  5. 编写中断服务函数TIMx_IRQHandler()等 HAL_TIM_IRQHandler()
  6. 编写定时器更新中断回调函数HAL_TIM_PeriodElapsedCallback()

7.相关库函数

函数主要寄存器主要功能
HAL_TIM_Base_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_Base_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_Base_Start_IT()DIER、CR1能更新中断并启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_PeriodElapsedCallback()定时器更新中断回调函数,由用户重定义

8.关键结构体

typedef struct 
{ 
    TIM_TypeDef *Instance;            /* 外设寄存器基地址 */ 
    TIM_Base_InitTypeDef Init;     /* 定时器初始化结构体*/
     ...
}TIM_HandleTypeDef;
typedef struct 
{ 
    uint32_t Prescaler;                      /* 预分频系数 */ 
    uint32_t CounterMode;             /* 计数模式 */ 
    uint32_t Period;                           /* 自动重载值 ARR */ 
    uint32_t ClockDivision;             /* 时钟分频因子 */ 
    uint32_t RepetitionCounter;   /* 重复计数器寄存器的值 */ 
    uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;

9.编程实战

(1)cubemx配置
1.设置RCC

设置高速外部时钟HSE 选择外部时钟源
请添加图片描述
设置高速外部时钟HSE 选择外部时钟源

2. 设置时钟

请添加图片描述
我的是 外部晶振为8MHz

  • 选择外部时钟HSE 8MHz
  • PLL锁相环倍频72倍
  • 系统时钟来源选择为PLL
  • 设置APB1分频器为 /2
  • 这时候定时器的时钟频率为72Mhz
3.定时器设置

请添加图片描述

  1. 选择TIM2
  2. 定时器时钟选择内部时钟
    Clock Source(时钟来源)
    选项1 :Internal Clock 内部时钟
    选项2 : ETR2 外部触发输入(ETR)(仅适用TIM2,3,4)
    请添加图片描述
    Prtscaler (定时器分频系数) : 7199

Counter Mode(计数模式) Up(向上计数模式)

Counter Period(自动重装载值) : 4999

CKD(时钟分频因子) : No Division 不分频

选项: 可以选择二分频和四分频

auto-reload-preload(自动重装载) : Enable 使能

TRGO Parameters 触发输出 (TRGO)

4.使能定时器中断

请添加图片描述
定时器溢出时间:
请添加图片描述
这里我们 arr=4999 psc=7199 Tclk=72Mhz Tout = (5000*7200)/72 us = 500ms

(2)程序的流转
 void TIM3_IRQHandler(void);//首先进入中断函数
 HAL_TIM_IRQHandler(&htim2);//之后进入定时器中断处理函数判断产生的是哪一类定时器中断(溢出中断/PWM中断.....) 和定时器通道
 void HAL_TIM_PeriodElapsedCallback(&htim2);//进入相对应中断回调函数在中断回调函数中添加用户代码

(3)Keil配置

在main.c主函数上方初始化使能定时器2

    HAL_TIM_Base_Start_IT(&htim2);

在main.c主函数下方添加中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
            HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
}

(三)通用定时器

1.通用定时器简介

TIM2/ TIM3/ TIM4/ TIM5/
通用定时器较基本定时器多了触发事件、输入捕获、输出比较
四个独立通道:用于触发事件、输入捕获、输出比较、输出PWM、单脉冲模式
使用外部信号控制定时器且可实现多个定位器互连的同步电路
支持编码器和活儿传感器电路

2.通用定时器框图

在这里插入图片描述

(1)时钟源部分

![在这里插入图片描述](https://img-blog.csdnimg.cn/8b79e47d7ba749ba93dbd9e85cc5ff0c.png

  1. 1TIMxCLK来自外设APB总线上的实时时钟RCC——内部时钟
  2. ITR内部触发时钟输入
  3. 来自可以复用为ETR引脚的IO口——外部时钟模式二
  4. TI1F_ED和TI1FP1和TI2FP2是外部时钟模式一来自外部定时器通道1,2
计数器时钟选择类型设置方法
内部时钟(CK_INT)设置TIMx_SMCR的SMS=000
外部时钟模式1:外部输入引脚(TIx) 设置TIMx_SMCR的SMS=111
外部时钟模式2:外部触发输入(ETR) 设置TIMx_SMCR的ECE=1
内部触发输入(ITRx)设置可参考STM32F10xxx参考手册_V10(中文版).pdf,14.3.15节
(2)控制器

在这里插入图片描述
控制器可以接到其他DAC/ADC,接到其他定时器可以完成定时器的级联。

(3)时基单元

在这里插入图片描述
与基本定时器相同

(4) 输入捕获

在这里插入图片描述
将IO口复用为CHx中的一个通道,信号从从外部输入,进过异或门进入TI1,进过滤波器整形和边沿检测器(检测上升沿和下降沿),得到两个信号:一个是TI1FP1,一个是TI1FOP2。选择一个信号捕获。信号捕获到触发捕获事件,CNT计数器的值转移到捕获/比较寄存器。

(5) 捕获/比较(共用)

在这里插入图片描述

(6) 输出比较

在这里插入图片描述
在捕获/比较寄存器和输出比较一起使用时,先往捕获/比较寄存器写入比较值,要使用的时候就是产生事件将比较值写入影子寄存器,在CNT计数器的值与影子寄存器的值相等时,产生输出参考信号和比较事件。输出控制有八种输出模式。同时ETRF信号也能控制OCxREF信号将其强制清零。TIMx_CHx是分时复用

3.外部输入模式1

在这里插入图片描述

4.外部输入模式2

7

5.通用定时器输出PWM

捕获/比较通道1的主电路
在这里插入图片描述

捕获/比较寄存器,输出时是写入捕获/比较预装载寄存器
影子寄存器不可以直接访问
预装载寄存器和影子寄存器之间满足比较条件才可以进行转移(就是中间的compare_transfer)
中间的compare_tranfer是一个与门
(1)最上面是CCR1高位和低位的写入操作,当他进行写入操作的时候没办法将预装载寄存器的值写入影子寄存器,因为这是的写操作那一行是输出1,通过非门输出0,与门中有一个是0输出结果就是0了转移之后比较值会和计数器进行比较之后输出结果。
(2)中间CC1S配置输出模式/输入模式00就是配置为输出内模式
(3)OC1PE是配置寄存器有无缓冲默认为0(使能预装载)
UEV是更新事件
捕获/比较通道的输出部分
在这里插入图片描述

输出模式控制器由三个部分决定
(1) TIMx_CCMR1的CC1S[1:0]位配置为00就能配置为输出
(2) OC1M有三个位8种输出模式
(3) ETRF由OC1CE控制,OC1CE设置为0时不受ETRF影响,检测到高电平时有效
输出极性选择CC1P
输出使能CC1E
通用计时器输出PWM原理

(1)通用定时器输出中断框图

在这里插入图片描述

(2)通用计时器输出PWM原理
PWM模式

在这里插入图片描述

通用定时器PWM输出配置步骤

1.配置定时器基础工作参数HAL_TIM_PWM_Init()
1.定时器PWM输出MSP初始化HAL_TIM_PWM_MspInit() 配置NVIC、CLOCK、GPIO等
3.配置PWM模式/比较值等HAL_TIM_PWM_ConfigChannel()
4.使能输出并启动计数器HAL_TIM_PWM_Start()
5.修改比较值控制占空比(可选) __HAL_TIM_SET_COMPARE()
6.使能通道预装载(可选) __HAL_TIM_ENABLE_OCxPRELOAD()

(3)相关HAL库函数
函数主要寄存器主要功能
HAL_TIM_PWM_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_PWM_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_PWM_ConfigChannel()CCMRx、CCRx、CCER配置PWM模式、比较值、输出极性等
HAL_TIM_PWM_Start()CCER、CR1使能输出比较并启动计数器
__HAL_TIM_SET_COMPARE()CCRx修改比较值
__HAL_TIM_ENABLE_OCxPRELOAD()CCER使能通道预装载
(4)关键结构体介绍
typedef struct 
{ 
   uint32_t OCMode; 	  /* 输出比较模式选择 */
   uint32_t Pulse; 	            /* 设置比较值 */
   uint32_t OCPolarity;       /* 设置输出比较极性 */
   uint32_t OCNPolarity;    /* 设置互补输出比较极性 */
   uint32_t OCFastMode;   /* 使能或失能输出比较快速模式 */
   uint32_t OCIdleState;     /* 空闲状态下OC1输出 */
   uint32_t OCNIdleState;  /* 空闲状态下OC1N输出 */ 
} TIM_OC_InitTypeDef;

(5)编程实战(其一:呼吸灯)
  1. 通过定时器输出的PWM控制LED0,实现类似手机呼吸灯的效果1,确定PWM波的周期/频率
    Tout =((ARR+1)*(PSC+1))/Ft
    2KHz为例,PSC=71,ARR=499
  2. 配置输出比较模式为:PWM模式1,通道输出极性为:低电平有效
first.cube配置
设置RCC

设置高速外部时钟HSE 选择外部时钟源
请添加图片描述

设置定时器

请添加图片描述

  1. 选择TIM3
  2. 设置定时器时钟源为内部时钟源
    设置定时器CH1为PWM模式
  3. 对应管脚自动设置为复用模式
  4. 可自行选择是否开启定时器中断
    Channel1~4 就是设置定时器通道的功能 (输入捕获、输出比较、PWM输出、单脉冲模式)
    请添加图片描述
    Mode 选择PWM模式1
    Pulse(占空比值) 先给0
    Fast Mode PWM脉冲快速模式 : 和我们配置无关,不使能
    PWM 极性: 设置为低电平 PS: 由于LED是低电平点亮,所以我们把极性设置为low
    请添加图片描述
    在 Parameter Settings 页配置预分频系数为 71,计数周期(自动加载值)为 499,定时器溢出频率,即PWM的周期,就是 72MHz/(71+1)/(499+1) = 2kHz

PWM频率:

Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)

arr 是计数器值
psc 是预分频值
占空比:

duty circle = TIM3->CCR1 / arr(单位:%)
TIM3->CCR1 用户设定值
比如 定时器频率Tclk = 72Mhz arr=499 psc=71 那么PWM频率就是720000/500/72= 2000Hz,即2KHz

arr=499,TIM3->CCR1=250 则pwm的占空比为50%

改CCR1可以修改占空比,修改arr可以修改频率

时钟源设置

1选择外部时钟HSE 8MHz
2PLL锁相环倍频72倍
3系统时钟来源选择为PLL
4设置APB1分频器为 /2
请添加图片描述

Second.Keil配置

定义变量

    uint16_t pwmVal=0;   //PWM占空比  
    uint8_t dir=1;    

然后使能TIM3的PWM Channel1 输出。

  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
  /* USER CODE END 2 */
  while (1)
  {
	  while (pwmVal< 500)
	  {
		  pwmVal++;
		  __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwmVal);    //修改比较值,修改占空比
//		  TIM3->CCR1 = pwmVal;    与上方相同
		  HAL_Delay(1);
	  }
	  while (pwmVal)
	  {
		  pwmVal--;
		  __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwmVal);    //修改比较值,修改占空比
//		  TIM3->CCR1 = pwmVal;     与上方相同
		  HAL_Delay(1);
	  }
	  HAL_Delay(200);
 }

6.通用定时器输入捕获脉宽

(1)输入捕获框图

在这里插入图片描述

捕获/比较通道输入部分

在这里插入图片描述

捕获/比较通道1主电路

在这里插入图片描述
当CC1S配置不是00是就是输入模式
CC1R是写CC1R高位和低位的操作
CC1E硬件捕获
CC1G软件捕获

(2)通用定时器脉宽测量原理

以捕获测量高电平脉宽为例
假设:递增计数模式
ARR:自动重装载寄存器的值
在这里插入图片描述
首先是将通道x配置为上升沿检测模式,当时间达到t1,此时CNT的值会自动转移到CCRx寄存器,此时需要做两件事情,一是将CNT清零,二是将上升沿检测改成下降沿检测。
没有溢出的情况是,脉宽直接等于CCRx2
溢出的情况是,脉宽等于N*(ARR+1)+CCRx2(N是溢出次数)
中断是为了保存中断次数

(3)输入捕获配置步骤
  1. 配置定时器基础工作参数HAL_TIM_IC_Init()
  2. 定时器输入捕获MSP初始化HAL_TIM_IC_MspInit() 配置NVIC、CLOCK、GPIO等
  3. 配置输入通道映射、捕获边沿等HAL_TIM_IC_ConfigChannel()
  4. 设置优先级,使能中断HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()
  5. 使能定时器更新中断__HAL_TIM_ENABLE_IT()
  6. 使能捕获、捕获中断及计数器HAL_TIM_IC_Start_IT()
  7. 编写中断服务函数TIMx_IRQHandler()等 HAL_TIM_IRQHandler()
  8. 编写更新中断和捕获回调函数HAL_TIM_PeriodElapsedCallback() HAL_TIM_IC_CaptureCallback()
(4)相关库函数介绍
函数主要寄存器主要功能
HAL_TIM_IC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_IC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_IC_ConfigChannel()CCMRx、CCER配置通道映射、捕获边沿、分频、滤波等
__HAL_TIM_ENABLE_IT()DIER使能更新中断等
HAL_TIM_IC_Start_IT()CCER、DIER、CR1使能输入捕获、捕获中断并启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_PeriodElapsedCallback()定时器更新中断回调函数,由用户重定义
HAL_TIM_IC_CaptureCallback()定时器输入捕获回调函数,由用户重定义
(5)关键结构体介绍

```c
typedef struct
{ 
    uint32_t ICPolarity;    /* 输入捕获触发方式选择,比如上升、下降沿捕获 */ 
    uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */ 
    uint32_t ICPrescaler; /* 输入捕获分频系数 */ 
    uint32_t ICFilter;         /* 输入捕获滤波器设置 */ 
} TIM_IC_InitTypeDef;

(6)编程实战(其二:输入捕获)

通过定时器5通道1来捕获按键高电平脉宽时间,通过串口打印出来
1,确定计数器工作频率Tout =((ARR+1)*(PSC+1))/Ft
1MHz计数频率为例,PSC=71,ARR=65535
2,配置输入捕获方式:上升沿捕获、输入通道1映射在TI1上、不分频、不滤波

cubemx配置
  1. 设置RCC
    设置高速外部时钟HSE 选择外部时钟源请添加图片描述
  2. 设置时钟请添加图片描述
    我的是 外部晶振为8MHz
  • 选择外部时钟HSE 8MHz
  • PLL锁相环倍频72倍
  • 系统时钟来源选择为PLL
  • 设置APB1分频器为 /2
  • 这时候定时器的时钟频率为72Mhz
  1. 时钟树
    不知道为什么在我的cubemx里定时器只有TIMER1-TIMER4.这里我配置的是定时器2
    在这里插入图片描述
keil配置

定义变量

/* USER CODE BEGIN 0 */
    uint32_t capture_Buf[3] = {0};   //存放计数值
    uint8_t capture_Cnt = 0;    //状态标志位
    uint32_t high_time;   //高电平时间
/* USER CODE END 0 */
while (1)
{
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  switch (capture_Cnt){
	case 0:
		capture_Cnt++;
		__HAL_TIM_SET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
		HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);	//启动输入捕获       或者: __HAL_TIM_ENABLE(&htim5);
		break;
	case 3:
		high_time = capture_Buf[1]- capture_Buf[0];    //高电平时间
		HAL_UART_Transmit(&huart1, (uint8_t *)high_time, 1, 0xffff);   //发送高电平时间
				
				
		HAL_Delay(1000);   //延时1S
		capture_Cnt = 0;  //清空标志位
		break;
				
	}
}
/* USER CODE END 3 */

回调函数

/* USER CODE BEGIN 4 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	
	if(TIM5 == htim->Instance)
	{
		switch(capture_Cnt){
			case 1:
				capture_Buf[0] = HAL_TIM_ReadCapturedValue(&htim5,TIM_CHANNEL_1);//获取当前的捕获值.
				__HAL_TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);  //设置为下降沿捕获
				capture_Cnt++;
				break;
			case 2:
				capture_Buf[1] = HAL_TIM_ReadCapturedValue(&htim5,TIM_CHANNEL_1);//获取当前的捕获值.
				HAL_TIM_IC_Stop_IT(&htim5,TIM_CHANNEL_1); //停止捕获   或者: __HAL_TIM_DISABLE(&htim5);
				capture_Cnt++;    
		}
	
	}
	
}
/* USER CODE END 4 */

7.通用定时器脉冲计数

使用通用定时器脉冲输入计数需要使用外部时钟模式1和外部时钟模式2
外部输入模式1只能用通道一和通道二
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
外部输入模式1是直接向定时器的输入通道输入脉冲,通过边沿将检测器检测上升沿或者下降沿,通过T1FPx进入从模式控制器,种模式选择外部时钟1,进入时基模块计数。
在这里插入图片描述

(1)框图

请添加图片描述
除了要配置 选择要使用的通用定时器(TIM2~TIM5),选择计时器的时钟源为内部时钟(CK_INT)、根据要定时的时间计算预分频系数(TIMx_PSC)、自动重装载值(TIMx_ARR)、内部时钟的分频系数以外,更重要的还需要配置:

捕获/比较模式寄存器1/2(TIMx_CCMR1/2):有2个寄存器,一个寄存器只能设置2个通道;该寄存器用于设置通道的输入(捕获模式)或输出(比较模式)参数,就输入捕获而言,主要设置滤波器、预分频器、输入映射关系
捕获/比较使能寄存器(TIMx_CCER):配置捕获触发的信号级性、捕获使能,每个定时器有4个通道,这里的CC1P/CC1E表示的是第一个通道
从模式控制寄存器(TIMx_SMCR):选择触发输入源和从模式

(3)配置步骤

1,配置定时器基础工作参数HAL_TIM_IC_Init()
2,定时器输入捕获MSP初始化HAL_TIM_IC_MspInit() 配置NVIC、CLOCK、GPIO等
3,配置定时器从模式等HAL_TIM_SlaveConfigSynchro()
4,使能输入捕获并启动计数器HAL_TIM_IC_Start()
5,获取计数器的值__HAL_TIM_GET_COUNTER()
6,设置计数器的值__HAL_TIM_SET_COUNTER()

(4)编程实战
cubemx

1RCC
在这里插入图片描述
2.SYS

在这里插入图片描述
3.TIM
在这里插入图片描述
那个极性判断应该是下降沿判断,选错了,giao
因为我手里的板子定时器输入通道1是接在PA0和接地,然后让PA0引脚输出为1,当按键按下时,就是产生下降沿
4.USART

在这里插入图片描述
时钟树还是锁相环将系统时钟的频率倍频到72MHz。

keil

timer

#include "tim.h"
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;//分频系数
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;//计数模式选择递增计数模式
  htim2.Init.Period = 65535;//自动重转载值
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;//时钟的预分频系数
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//不用自动重装
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  //配置从模式
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
  sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;//通道接TI1FP1
  sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_FALLING;//下降沿触发
  sSlaveConfig.TriggerFilter = 0;//分频系数
  if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
	 HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_1);

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_baseHandle->Instance==TIM2)
  {

    __HAL_RCC_TIM2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**TIM2 GPIO Configuration
    PA0-WKUP     ------> TIM2_CH1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode =GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed= GPIO_SPEED_FREQ_HIGH; 
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  }
}

main

int main(void)
{
  uint16_t curcnt;
  uint16_t oldcnt;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();

  while (1)
  {
        curcnt = __HAL_TIM_GET_COUNTER(&htim2);
        if(oldcnt != curcnt)
        {
            oldcnt = curcnt;
            printf("CNT:%d\r\n", oldcnt);
        }
        
  }
}

记得把这一段代码弄到USART.c文件里去

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (uint8_t) ch;      
	return ch;
}
#endif

同时加上那个#include<stdio.h>

(二)高级定时器

1.高级定时器简介

(1)重复计数器
(2)死区时间带可编程的互补输出
(3)断路输入,用于将定时器的输出信号置于用户可选的安全配置

(1)高级定时器与通用定时器不同的地方

1.重复计数器
使用高级计数器之前需要先往REP寄存器里边写入一个值,产生溢出每次减1,减到0时才会产生更新中断
2.输出比较
输出部分通道1.2.3都有互补通道
3.刹车功能

(2)重复计数器特性

计数器每次上溢或者下溢都能使重复计数器减1,减到0时,再发生一次溢出就会产生更新事件
(0溢事件)
在这里插入图片描述
###3 2.高级定时器框图
在这里插入图片描述

2.寄存器

在这里插入图片描述
TIMx_RCR只有后八位REP是有效的
REP寄存器就是重复寄存器TIMx_RCR的后八位,同样他也有影子寄存器
如果设置RCR寄存器为N,更新事件将在N+1次溢出时发生

3.高级定时器输出指定个数PWM实验原理

(1)原理

在这里插入图片描述
1. 配置边沿对齐模式输出PWM
2. 指定输出N个PWM,则把N-1写入RCR
3. 在更新中断内关闭计数器
4. 高级定时器通道输出必须把MOE位置1
在这里插入图片描述

(2)配置步骤

4.高级定时器输出比较模式的实验

(1)原理

输出比较模式是要将输出模式配置为翻转
当CNT=CCRx,OCxREF将会翻转,OCx将会翻转,CHx将会翻转,输出电平将会翻转
比较输出本质上也是输出PWM波
输出的周期是2*(ARR+1)*(PSC+1)/FT
(周期由ARR决定,相位由CCRx决定)
占空比固定为半个周期

(2)配置步骤

1,配置定时器基础工作参数HAL_TIM_PWM_Init()
2,定时器PWM输出MSP初始化HAL_TIM_PWM_MspInit() 配置NVIC、CLOCK、GPIO等
3,配置PWM模式/比较值等HAL_TIM_PWM_ConfigChannel()
4,设置优先级,使能中断HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()
5,使能定时器更新中断__HAL_TIM_ENABLE_IT()
6,使能输出、主输出、计数器HAL_TIM_PWM_Start()
7,编写中断服务函数TIMx_IRQHandler()等  HAL_TIM_IRQHandler()
8,编写更新中断回调函数HAL_TIM_PeriodElapsedCallback()

(3)编程实战(其一)
cubemx配置
  1. RCC
    在这里插入图片描述

  2. 时钟树
    在这里插入图片描述

  3. timer配置
    在这里插入图片描述

  4. 串口
    在这里插入图片描述

keil

TIM_NPWM_SET()函数

void TIM_NPWM_SET(uint8_t NPWM)
{
	if(NPWM==0)return;
	NPWM_remain=NPWM;
	HAL_TIM_GenerateEvent(&htim1,TIM_EVENTSOURCE_UPDATE);
	__HAL_TIM_ENABLE(&htim1);
}

在tim.c文件中定义静态变量

static uint8_t NPWM_remain;

在初始化函数void MX_TIM1_Init(void)使能中断和PWM通道一

  HAL_TIM_MspPostInit(&htim1);
  __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
  HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);

在Tim回调函数中配置中断优先级

    __HAL_AFIO_REMAP_TIM1_PARTIAL();
	HAL_NVIC_SetPriority(TIM1_UP_IRQn,1,3);
	HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);  

请添加图片描述

5.高级定时器输出比较模式实验

(1)原理

在这里插入图片描述

输出比较模式是要将输出模式配置为翻转
当CNT=CCRx(CCRx是捕获比较寄存器 的值,就是比较值咯),输出参考信号OCxREF将会翻转,OCx将会翻转,CHx(IO口复用)将会翻转,IO输出电平将会翻转
比较输出本质上也是输出PWM波
输出的周期是2*(ARR+1)*(PSC+1)/FT
(周期由ARR决定,相位由CCRx决定)
占空比固定为半个周期

(2)配置步骤

1,配置定时器基础工作参数HAL_TIM_OC_Init()
2,定时器输出比较MSP初始化HAL_TIM_OC_MspInit() 配置NVIC、CLOCK、GPIO等
3,配置输出比较模式等HAL_TIM_OC_ConfigChannel()
4,使能通道预装载__HAL_TIM_ENABLE_OCxPRELOAD()
5,使能输出、主输出、计数器HAL_TIM_OC_Start()
6,修改捕获/比较寄存器的值__HAL_TIM_SET_COMPARE()

(3)相关函数介绍

输出比较模式是要将输出模式配置为翻转
当CNT=CCRx,OCxREF将会翻转,OCx将会翻转,CHx将会翻转,输出电平将会翻转
比较输出本质上也是输出PWM波
输出的周期是2*(ARR+1)*(PSC+1)/FT
(周期由ARR决定,相位由CCRx决定)
占空比固定为半个周期
1,配置定时器基础工作参数HAL_TIM_OC_Init()
2,定时器输出比较MSP初始化HAL_TIM_OC_MspInit() 配置NVIC、CLOCK、GPIO等
3,配置输出比较模式等HAL_TIM_OC_ConfigChannel()
4,使能通道预装载__HAL_TIM_ENABLE_OCxPRELOAD()
5,使能输出、主输出、计数器HAL_TIM_OC_Start()
6,修改捕获/比较寄存器的值__HAL_TIM_SET_COMPARE()
相关函数介绍

函数主要寄存器主要功能
HAL_TIM_OC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_OC_MspInit存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_OC_ConfigChannel()CCMRx、CCRx、CCER设置输出比较模式、比较值、输出极性等
__HAL_TIM_ENABLE_OCxPRELOAD()CCMRx使能通道预装载
HAL_TIM_OC_Start()CR1、CCER、BDTR使能输出比较、主输出、启动计数器
__HAL_TIM_SET_COMPARE()CCRx修改捕获/比较寄存器的值
(4)编程实战
cube

1.RCC
在这里插入图片描述
外部高速晶振
2.SYS
在这里插入图片描述
3.USART

在这里插入图片描述

4.TIM
在这里插入图片描述
定时器的从模式,Tigger Source都是不用配置的,时钟资源配置为内部时钟,四个通道统统用上。

在这里插入图片描述
预分频系数设置为71,计数模式设置为递增计数模式,自动重转载值设置为999,不分频,失能自动重装载计数器,重复计数器初值为0.
下方四个通道的设置
模式设置为翻转模式
电极设置为High
输出脉冲为2500
CH Idle state是指空闲状态的通道是SET来时RESET

keil

在主函数中设置重转载值

  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 250 - 1);

在tim.c文件中开启输出

    HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);

也可以加上这一句

  __HAL_TIM_ENABLE_OCxPRELOAD(&htim1, TIM_CHANNEL_1);
  //开启将寄存器的值写入影子寄存器的值

6.高级定时器互补输出带死区控制

(1)互补输出带死区控制

在这里插入图片描述
就是酱紫咯

(2)带死区控制的互补输出应用—H桥

在这里插入图片描述
实际的使用会比上边的简图复杂,而且实际使用中多用MOS管而不是三极管(原理理解罢了)
上述三极管都是NPN型的三极管。Q1、Q4接的是输出通道,Q2、Q3接的是互补通道。
输出通道跟互补输出通道不能同时置于有效电平(对于上图来说是高点平)。

(3)捕获/比较通道输出部分

在这里插入图片描述
如图,高级定时器多了互补输出通道和死区时间发生器,经过死区发生器可以看到控制寄存器控制的输出通道和互补输出通道都会被使能(就是被置为11)
下方的MOE相当于使能总开关

(4)死区时间的计算
  1. 确定周期tDTS的值(可以通过计算频率得到):
    在这里插入图片描述
    其中Ft是系统时钟频率,F1系列的时钟频率为72MHz
    CKD由寄存器TIMx-CR1控制
    在这里插入图片描述
  2. 判断DTG[7:5],选择计算公式
  3. TIMx_BDTR
    在这里插入图片描述
  4. 举个栗子(F1为例):DTG[7:0]=250,即二进制:1111 1010,选第四条
    DT = (32+26)1655.56 ns=51.55968us
    但其实cubemx会帮你计算死区时间
    步骤:
  5. 通过配置TIMx-CR1得到分频系数,;从而计算出频率,取倒数就可以计算出周期tDTS,再通过配置TIMx_BDTR选择计算公式,先计算出dtg,再计算出DT持续时间
(5)刹车功能

使能刹车:将TIMx_BDTR的BKE位置1,刹车输入信号的极性由BKR位设置
在这里插入图片描述
上面的的框图,输入信号用IO口的复用功能,输入经过BRK极性选择与时钟控制器的时钟故障事件一起接到或门,从而发出中断信号

使能刹车功能后:由TIMx_BDTR的MOE、OSSI、OSSR位,
TIMx_CR2的OISx、OISxN位,TIMx_CCER的CCxE、CCxNE位控制OCx和OCxN输出状态
无论何时,OCx和OCxN输出都不能同时处在有效电平

发生刹车后

  1. MOE位被清零,OCx和OCxN为无效、空闲或复位状态(OSSI位选择)
    相当于使能位清零(置1是运行模式,0是空闲模式)
  2. OCx和OCxN的状态:由相关控制位状态决定,
    当使用互补输出时:根据情况自动控制输出电平,参考参考手册使用刹车(断路)功能小节
  3. BIF(刹车中断的标志位)位置1,如果使能了BIE位,还会产生刹车中断;如果使能了TDE位,会产生DMA请求
  4. 如果AOE位置 1,在下一个 更新事件UEV时,MOE位被自动置1
    上表MOE位为一此时是运行模式
    下表是空闲模式
    OSSI是空闲模式才能配置的寄存器
    OSSR运行模式的关闭状态
    CCxE是输出使能
    CCxNE是互补通道使能

在这里插入图片描述

(6)配置步骤
  1. 配置定时器基础工作参数HAL_TIM_PWM_Init()
  2. 定时器PWM输出MSP初始化HAL_TIM_PWM_MspInit() 配置NVIC、CLOCK、GPIO等
  3. 配置PWM模式/比较值等HAL_TIM_PWM_ConfigChannel()
  4. 配置刹车功能、死区时间等HAL_TIMEx_ConfigBreakDeadTime()
  5. 使能输出、主输出、计数器HAL_TIM_PWM_Start()
  6. 使能互补输出、主输出、计数器HAL_TIMEx_PWMN_Start()
    相关函数介绍
函数主要寄存器主要功能
HAL_TIM_PWM_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_PWM_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_PWM_ConfigChannel()CCMRx、CCRx、CCER配置PWM模式、比较值、输出极性等
HAL_TIMEx_ConfigBreakDeadTime()BDTR配置刹车功能、死区时间等
HAL_TIM_PWM_Start()CCER、CR1使能输出、主输出、启动计数器
HAL_TIMEx_PWMN_Start()CCER、CR1使能互补输出、主输出、启动计数器

关键结构体介绍

typedef struct 
{
    uint32_t OffStateRunMode;    /* 运行模式下的关闭状态选择 */ 
    uint32_t OffStateIDLEMode;   /* 空闲模式下的关闭状态选择 */ 
    uint32_t LockLevel; 		 /* 寄存器锁定设置 */ 
    uint32_t DeadTime; 	          /* 死区时间设置 */ 
    uint32_t BreakState; 	          /* 是否使能刹车功能 */ 
    uint32_t BreakPolarity;		 /* 刹车输入极性 */ 
    uint32_t BreakFilter; 		 /* 刹车输入滤波器(F1/F4系列没有) */ 
    uint32_t AutomaticOutput; 	/* 自动恢复输出使能,即使能AOE位 */
} TIM_BreakDeadTimeConfigTypeDef;
(7)编程实战

通过定时器1通道1输出频率为1KHz,占空比为70%的PWM,使用PWM模式1使能互补输出并设置死区时间控制:设置DTG为100(5.56us),进行验证死区时间是否正确使能刹车功能:刹车输入信号高电平有效,配置输出空闲状态等,最后用示波器验证
1,确定PWM波的周期/频率Tout =((ARR+1)*(PSC+1))/Ft
1KHz为例,PSC=71,ARR=999
2,以H桥为例,配置通道输出极性以及互补输出极性

cube

时钟树
在这里插入图片描述
定时器1
在这里插入图片描述

定时器时钟源选择内部时钟Internal Clock
通道1选择PWM互补输出
开启刹车
通道配置
请添加图片描述
在这里插入图片描述

可以看到PA7是输出通道,PA8是互补通达,PA6是刹车输入引脚(我设置的是低电平有效)
生成代码后记得要去看一下GPIO口上拉下拉的状态

keil

主函数配置

/* 定时器通道1输出PWM */
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
/* 定时器通道1互补输出PWM */
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
//占空比百分之30
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,300);

7.高级定时器PWM输入模式实验

(1)原理

在这里插入图片描述
这里时钟源我选择内部时钟,频率是72MHz,所以时基单元记一个数的周期为1/72MHz秒。大约13.8ns(这里也是确定;量器的精度)
通过计算上升沿下降沿计数值之差,就能算出PWM波输出高电平/低电平的时间
反过来也是一样的哈
在这里插入图片描述
哥们白画了上边那个图[衰]
PWM输入信号从TIMx_CH1输入,通过边沿检测器和滤波器分成两个通道T1FP1和T1FP2分别负责上升沿检测和下降沿检测,进入捕获/比较寄存器就会产生捕获事件。
从模式选择复位模式。上升沿会触发从模式的复位,这时计数值重置为初值(递增计数模式初值就为0,递减计数模式就为初值),下降沿到的时候就能测出时间了(因为这是的CNT的值在上升沿来的被置0了,直接读取CNT的值下降沿来的时候CNT’的值就是时间了)。

(2)时序

在这里插入图片描述

(3)步骤

1,配置定时器基础工作参数HAL_TIM_IC_Init()
2,定时器捕获输入MSP初始化HAL_TIM_IC_MspInit() 配置NVIC、CLOCK、GPIO等
3,配置IC1/2映射、捕获边沿等HAL_TIM_IC_ConfigChannel()
4,配置从模式,触发源等HAL_TIM_SlaveConfigSynchro()
5,设置优先级,使能中断HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()
6,使能捕获、捕获中断及计数器HAL_TIM_IC_Start_IT()、 HAL_TIM_IC_Start()
7,编写中断服务函数TIMx_IRQHandler() HAL_TIM_IRQHandler()
8,编写输入捕获回调函数 HAL_TIM_IC_CaptureCallback()

函数主要寄存器主要功能
HAL_TIM_IC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_IC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_IC_ConfigChannel()CCMRx、CCER配置通道映射、捕获边沿、分频、滤波等
HAL_TIM_SlaveConfigSynchro()SMCR、CCER配置从模式、触发源、触发边沿等
HAL_TIM_IC_Start_IT()CCER、DIER、CR1使能输入捕获、捕获中断并启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_IC_CaptureCallback()定时器输入捕获回调函数,由用户重定义

关键结构体介绍

typedef struct
{ 
    uint32_t ICPolarity;    /* 输入捕获触发方式选择,比如上升、下降沿捕获 */ 
    uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */ 
    uint32_t ICPrescaler; /* 输入捕获分频系数 */ 
    uint32_t ICFilter;         /* 输入捕获滤波器设置 */ 
} TIM_IC_InitTypeDef;
typedef struct 
{ 
    uint32_t SlaveMode;              /* 从模式选择 */ 
    uint32_t InputTrigger;           /* 输入触发源选择 */ 
    uint32_t TriggerPolarity;      /* 输入触发极性 */ 
    uint32_t TriggerPrescaler;   /* 输入触发预分频 */ 
    uint32_t TriggerFilter;           /* 输入滤波器设置 */ 
} TIM_SlaveConfigTypeDef;
(4)编程实战

通过定时器3通道2(PB5)输出PWM将PWM输入到定时器8通道1(PC6),测量PWM的频率/周期、占空比等信息定时器8的采样时钟频率固定72MHzTout =((ARR+1)*(PSC+1))/Ft72MHz采样频率( 精度约13.8ns ),PSC=0,ARR=65535不考虑溢出情况下,测量的最长PWM周期为910.2us

cube

TIM1
在这里插入图片描述
RCC
在这里插入图片描述
时钟树
在这里插入图片描述
TIM3
在这里插入图片描述
上边配置cube的过程有可能会有错误(tired)
可是我手头没有示波器(悲)

keil

to be continue->

十、关于F4系列STM32的补充

(一)时钟树

在这里插入图片描述
锁相环
F407的时钟树有两个锁相环倍频,一个是main PLL(配置系统时钟),一个是PLLI2S专门给I2S提供时钟的专属锁相环。
F429的时钟树有三个锁相环。一个mainPLL,一个PLLI2S专门给I2S提供时钟,一个PLLSAI专门给SAI外设提供时钟。
时钟源
F407/F429

类型频率材料应用
HSE4-26MHz晶体/陶瓷(成本高)SYSCLK/RTC
LSE32.768KHz晶体/陶瓷RTC
HIS16MHzRC(稳定性差)SYSCK
LSI32KHzRCRTC/IWDG

H:high
L:low
S:speed
I:internal
E:external

STM32F4时钟树简图
在这里插入图片描述
高速部分:
HSE/HSI都可以作为系统时钟,F407与F429的系统时钟频率是不一样的。外部高速时钟与内部高速时钟通过分频、锁相环倍频(一个倍频器,两个分频器,P分频作为系统时钟,Q分频作为外设时钟)。系统时钟给AHB总线提供时钟需要再进行一次分频(一般设置为一分频),AHB总线再桥接到APB1跟APB2,可以挂载外设与内核。
低速部分:
LSI可以给独立看门狗与RTC实时时钟提供时钟,LSE专门给RTC提供时钟(优先选择)。
时钟树
在这里插入图片描述
内部跟外部的高速振荡器锁相环倍频的部分。首先HSI与HSE会接在VCO压控振荡器上(V是电压,C是控制器,O是振荡器),然后N倍频,倍频之后,再下来经过P/Q/R分频,P分频之后是接到系统时钟上,Q分频是接到全速USB接口上 。
在这里插入图片描述

  • 13
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值