写在前面:
本篇学习笔记记录了笔者以往学习STM32的过程,其中大部分内容都是来源于江科大、野火、武汉理工大学电子科技学会、正点原子、Z小旋,由衷感谢产出如此优秀内容的博主。本篇学习笔记旨在记录cubemx操作和keil编写的细节、巩固自身知识体系,如果本篇笔记也能帮到你的话,哪怕只是一点点,我就很开心了。
英文缩写 | 名称 | 英文缩写 | 名称 |
---|---|---|---|
NVIC | 嵌套向量中断控制器(片内) | CAN | CAN通信 |
SysTick | 系统滴答定时器(片内) | USB | USB通信 |
RCC | 复位和时钟控制 | RTC | 实时时钟 |
GPIO | 通用IO口 | CRC | CRC校验 |
AFIO | 复用IO口 | PWR | 电源控制 |
EXTI | 外部中断 | BKP | 备份寄存器 |
TIM 定时器(高级、通用、基本) | IWDG | 独立看门狗 | (即时复位芯片) |
ADC | 模数转换器 | WWDG | 窗口看门狗 |
DMA | 直接内存访问(搬运数据) | DAC | 数模转换器 |
USART | 同步/异步串口通信 | SDIO | SD卡接口 |
I2C | I2C通信 | FSMC | 可变静态存储控制器 |
SPI | SPI通信 | USB OTG | USB主机接口 |
主动单元 | 被动单元 |
---|---|
主动单元(主动发起通信) | 被动单元 |
Cortex-M3内核DCode总线 | 内部FLASH |
Cortex-M3内核系统总线 | 内部SRAM |
通用DMA1 | FSMC |
通用DMA2 | AHB到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是配置启动模式
启动配置的作用就是指定程序开始运行的位置。一般情况下,程序都是在Flash程序存储器开始执行。但是在特殊情况下可以在别的地方开始执行,用以完成特殊的功能。01用于串口下载程序,11是用于程序调试的。
指令执行的工作是由编译工具链执行的(toolchains)
芯片上电后就会触发复位异常,并且会跳转到中断向量表特定偏移位置,获取里面的内容执行。
只要修改复位异常内的内容,就可以让处理器去执行我们指定的操作
启动文件(.s文件(汇编语言))中的中断向量表
STM32的所有的GPIO外设都挂接在APB2总线上,每个GPIO外设都有16个引脚,编号从0→15.每个GPIO模块主要包含了寄存器、驱动器等。
最右边的二极管起到保护作用,对电压进行限幅。(0~3.3)
可由输出数据寄存器(普通IO控制)或外设通过数据选择器接到输出控制部分
(1)GPIO_Mode_IN_FLOATING 浮空输入
(2)GPIO_Mode_IPU 上拉输入
(3)GPIO_Mode_IPD 下拉输入
(4)GPIO_Mode_AIN 模拟输入
(5)GPIO_Mode_Out_OD 开漏输出(带上拉或者下拉)
(6)GPIO_Mode_AF_OD 复用开漏输出(带上拉或者下拉)
(7)GPIO_Mode_Out_PP 推挽输出(带上拉或者下拉)
(8)GPIO_Mode_AF_PP 复用推挽输出(带上拉或者下拉)
(1)2MHZ (低速)
(2)25MHZ (中速)
(3)50MHZ (快速)
(4)100MHZ (高速)
需要会写出来的代码
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);锁定引脚的状态
后缀_t就是用typedef重命名的变量类型
关键字 | 位数 | 表示范围 | Stdint关键字 | ST关键字 |
---|---|---|---|---|
char | 8 | -128 ~ 127 | int8_t | s8 |
unsigned char | 8 | 0 ~ 255 | uint8_t | u8 |
short | 16 | -32768 ~ 32767 | int16_t | s16 |
unsigned short | 16 | 0 ~ 65535 | uint16_t | u16 |
Int(在51中int占16位) | 32 | -2147483648 ~ 2147483647 | int32_t | s32 |
unsigned int | 32 | 0 ~ 4294967295 | uint32_t | u32 |
long | 32 | -2147483648 ~ 2147483647 | ||
unsigned long | 32 | 0 ~ 4294967295 | ||
long long | 64 | -(2^64)/2 ~ (2^64)/2-1 | int64_t | |
unsigned long long | 64 | 0 ~ (2^64)-1 | uint64_t | |
float | 32 | -3.4e38 ~ 3.4e38 | ||
double | 64 | -1.7e308 ~ 1.7e308 |
中断优先级分为可编程和不可编程
中断函数的地址是由编译器来分配的,不是固定的,但是中断的跳转,由于硬件的限制,只能跳转到固定的地址执行程序。所以为了能然硬件跳转到一个不固定的中断函数里,这里就需要在内存定义一个地址的列表。列表的地址是弧顶的,中断发生后,就跳这个固定的位置,然后在这个固定的位置,由编译器再加上一条跳转到中断函数的代码。遮掩中断就可以跳转到任意位置了。中断地址的裂变,就叫做中断向量表,相当于中断跳转的一个跳板。
NVIC的名字叫做嵌套中断向量控制器,用于分配中断优先级和管理中断的,是一个内核外设。
分组方式 | 抢占优先级 | 响应优先级 |
---|---|---|
分组0 | 0位,取值为0 4位 | 取值为0~15 |
分组1 | 1位,取值为0~ 1 3位 | 取值为0~7 |
分组2 | 2位,取值为0~ 3 2位 | 取值为0~3 |
分组3 | 3位,取值为0~ 7 1位 | 取值为0~1 |
分组4 | 4位,取值为0~15 0位 | 取值为0 |
事件的完成若无法通过寄存器读取,那便是不可见的事件。这时需要通过事件触发中断或者是事件触发另一个事件进行反馈。
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号引脚会触发同一个中断。
中断触发之后到NVIC中断控制器中,进入向量表,偏移到入口地址,然后跳转到中断服务函数。中断服务函数如果没有被定义,就会默认是一个死循环
STM32中的所有中断函数都有固定的名字,只有找到这个名字,在这个固定的函数名下编写中断服务函数才有效。而所有的中断服务函数编号都在stm32f10x_it.c,我的理解是头文件放在这里了。而函数体放在starup_stm32f10x_ms_s文件中。在相应的EXTI线的中断号下编写代码。
中断的回调机制:
中断使能后,调用HAL_PPP_IRQHandler();中断服务函数
在HAL_PPP_IRQHandler();中调用中断处理公共函数
在其中自己定义的回调函数中写入中断后要处理的程序
系统将复位除时钟控制器寄存器CSR中的复位标志和备份区域中的寄存器以外的所有寄存器为他们的复位数值。
电源复位将复位除了备份区域外的所有寄存器
备份区域拥有两个专门的复位,他们只影响备份区域
时钟可以理解CLK脉冲信号(方波)“心跳”
类型 | 频率 | 材料 | 应用 |
---|---|---|---|
HSE | 8MHz | 晶体/陶瓷(成本高) | SYSCLK/RTC |
LSE | 32.768KHz | 晶体/陶瓷 | RTC |
HIS | 80MHz | RC(稳定性差) | SYSCK |
LSI | 40KHz | RC | RTC/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中的clock configuration就会出现该界面
避免使用半主机模式
半主机模式:通过仿真器实现开发板在电脑上的输入和输出
//支持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
会用就行
单工通信:数据只能沿一个方向传输
半双工通信:数据可以沿两个方向传输,但需要分时进行
全双工通信:数据可以同时进行双向传输
同步通信:共用同一时钟信号
异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号
通信接口 | 接口引脚 | 数据同步方式 | 数据传输方式 |
---|---|---|---|
UART(通用异步收发器) | TXD:发送端 RXD:接收端 GND:公共地 | 异步通信 | 全双工 |
1-wire | DQ:发送/接收端 | 异步通信 | 半双工 |
IIC | SCL:同步时钟SDA:数据输入/输出端 | 同步通信 | 半双工 |
SPI | SCK:同步时钟MISO:主机输入,从机输出MOSI:主机输出,从机输入CS:片选信号 | 同步通信 | 全双工 |
串口:串行通信接口:指按位发送和接收的接口
(DB9)一共有9个引脚
逻辑 | RS-232 | COMS | TTL |
---|---|---|---|
1 | -15v~ 3v | 3.3v | 5v |
0 | 3v ~ 15v | 0v | 0v |
Universal synchronous asynchronous receiver transmitter,通用同步异步收发器
Universal asynchronous receiver transmitter,通用异步收发器
USART/UART都可以与外部设备进行全双工异步通信
USART,我们常用的也是异步通信
1,全双工异步通信
2,单线半双工通信
3,单独的发送器和接收器使能位
4,可配置使用DMA的多缓冲器通信
5, 多个带标志的中断源
TX输出引脚
RX输入引脚
SW_RX是芯片内部的引脚
RTS和CTS是与同步双工有关的引脚
SCLK是同步时钟
只能操作数据寄存器DR
下方通过波特率发生器作为时钟控制发送器和接收器
TE和RE是使能位
波特率计算公式:baud=“fck” /(16*USARTDIV)
“其中fck” 是串口的时钟,如:USART1的时钟是PCLK2,其他串口都是PCLK1
首先系统的时钟的频率是一定的(72M/36M),再一起约定好波特率baud,就能算出我认为的一个中间值USARTDIV,之后取整数部分作为DIV_Mantissa,小数部分作为DIV_FACTION
整数部分的数值写入BRR高12位,小数部分写入低4位。
该寄存器需要完成的配置:
位13:使能USART
位12:配置8个数据位
位10:禁止检验控制
位5:使能接收缓冲区非空中断
位3:使能发送
位2:使能接收
该寄存器需要完成的配置:配置1个停止位
只需要操作第12、13位
设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器
根据TC位可以知道能否发数据,根据RXNE位知道是否收到数据
__weak关键字的使用是定义一个弱函数,这个函数的函数体通常是空的方便用户重写一个自己的函数HAL_PPP/PPPP_MspInit,来覆盖之前库函数中定义的函数带有__weak关键字的HAL_PPP/PPPP_MspInit函数,编译器在编译的时候,如果检查到有重名的(但不含__weak关键字)HAL_PPP_MspInit的函数,此时就会默认编译这个用户写的函数
以URAT为例,HAL_UART_Init();会调用HAL_UART_MSPInit();
用户需要自己定义一个新的MSP回调函数。
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()
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
作用:以中断的方式接收指定字节的数据
形参 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);
while (1)
{
printf("test\n");
HAL_Delay(1000);
}
函数的路径十分曲折
(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中配置引脚、波特率、时钟树等。
#include
#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 */
感觉键寄存器是开启喂狗的开关
而重装载寄存器是控制喂狗食物的数量
查询预分频值与重装载值是否更新
Tout=pscrlr/fIWDG(Tout是看门狗溢出时间、fIWDG是看门狗时钟源频率,psc是看门狗预分频系数,rlr是看门狗重装载值)
寄存器设置分频系数方法:psc=42^prerprer是IWDG_PR后三位设置的数值
增加头文件跟字符串
#include
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本质上是一个递减计数器。初始化的时候需要设置窗口上限值。
判断是否发生了WWDG提前唤醒中断
Tout是WWDG超时时间(没喂狗)
Fwwdg是WWDG的时钟源频率
4096是WWDG固定的预分频系数
2^WDGTB是WWDG_CFR寄存器设置的预分频系数值
T[5:0]是WWDG计数器低6位
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_Init | WWDG_CR/WWDG_CFR | 使能WWDG,设置预分频系数和窗口值等 |
HAL_WWDG_Refresh | WWDG_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); //喂狗
}
时钟源 | LSI(40KHz或32KHz) | PCLK1或PCLK3 |
---|---|---|
复位条件 | 递减计数到0 | 计数值大于W[6:0]值喂狗或减到0x3F |
中断 | 没有中断 | 计数值减到0x40可产生中断 |
递减计数器位数 | 12位(最大计数范围:4096~0) | 7位(最大计数范围:127 ~ 63) |
应用场合 | 防止程序跑飞,死循环,死机 | 检测程序时效,防止软件异常 |
使用纯软件(CPU死等)的方式实现延时的功能
就是直接在while(1)里面Delay但是这样的延时是不精准的
一方面arm是流水线架构
另一方面是进入程序的压栈和出栈需要消耗时间
定时器类型 | 主要功能 |
---|---|
基本定时器 | 没有输入输出通道,常用作时基,即定时功能 |
通用定时器 | 具有多路独立通道,可用于输入捕获/输出比较,也可用作时基 |
高级定时器 | 除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等) |
SMT32F1系列共有8个定时器: | |
高级定时器(TIM1、TIM8);通用定时器(TIM2、TIM3、TIM4、TIM5);基本定时器(TIM6、TIM7)。 |
高级定时器(TIM1,TIM8)的主要功能:
高级定时器具有基本,通用定时器的所有的功能,
还具有控制交直流电动机所有的功能,
输出6路互补带死区的信号,刹车功能等等
位于APB2总线上
总括:基本定时器就是单纯的定时计数器,通用定时器多了四个通道,相对应的增加了功能,高级定时器具有基本,通用定时器的所有的功能,并且添加了其他功能
影子寄存器是实际起作用的寄存器,不可以直接访问
溢出条件是CNT==ARR(自动重装载寄存器的重装载值)
ARR是自动重装载寄存器,起到缓冲的作用(ARPE位决定ARR是否具有缓冲)
预装载寄存器的值和分频系数将会在中断发生时写入影子寄存器
APB1的分频是从AHB上分过来的,72MHz最少是分一半到APB2
如图,Timer2 ~ Timer7接到APB1总线上。Timer1 ~ Timer8接到APB2总线上。当锁相环倍频至72MHz是,APB2预分频系数为2,此时APB2达到最大频率32MHz,由于预分频系数不为1,所以定时器实际的频率为72MHz。对于APB1,预分频系数可以是1,所以所有定时器最高频率都是72MHz。
计数器模式 | 溢出条件 |
---|---|
计数器模式 溢出条件 | |
递增计数模式 | CNT==ARR(影子) |
递减计数模式 | CNT==0 |
中心对齐模式 | CNT== ARR-1、CNT==1 |
该时序图是PSC预分频系数为1、ARR计数初值为36的情况。可以看到预分频系数PSC为1的情况下,时钟线也需要经过两个周期计数值才会+1.这是因为Psc设置为1相当于分频系数为2(这是为什么呢?我也想知道)。计数值向上递增到ARR == 36才会触发中断,也就是触发一次更新事件。
该时序图也是PSC预分频系数为1、ARR计数初值为36的情况。与递增计数模式类似。
用于设置预分频系数,范围是0 ~ 65535
实际的预分频系数为PSC+1
函数 | 主要寄存器 | 主要功能 |
---|---|---|
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() | 无 | 定时器更新中断回调函数,由用户重定义 |
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;
设置高速外部时钟HSE 选择外部时钟源
设置高速外部时钟HSE 选择外部时钟源
Counter Mode(计数模式) Up(向上计数模式)
Counter Period(自动重装载值) : 4999
CKD(时钟分频因子) : No Division 不分频
选项: 可以选择二分频和四分频
auto-reload-preload(自动重装载) : Enable 使能
TRGO Parameters 触发输出 (TRGO)
定时器溢出时间:
这里我们 arr=4999 psc=7199 Tclk=72Mhz Tout = (5000*7200)/72 us = 500ms
void TIM3_IRQHandler(void);//首先进入中断函数
HAL_TIM_IRQHandler(&htim2);//之后进入定时器中断处理函数判断产生的是哪一类定时器中断(溢出中断/PWM中断.....) 和定时器通道
void HAL_TIM_PeriodElapsedCallback(&htim2);//进入相对应中断回调函数在中断回调函数中添加用户代码
在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);
}
TIM2/ TIM3/ TIM4/ TIM5/
通用定时器较基本定时器多了触发事件、输入捕获、输出比较
四个独立通道:用于触发事件、输入捕获、输出比较、输出PWM、单脉冲模式
使用外部信号控制定时器且可实现多个定位器互连的同步电路
支持编码器和活儿传感器电路
计数器时钟选择类型 | 设置方法 |
---|---|
内部时钟(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节 |
控制器可以接到其他DAC/ADC,接到其他定时器可以完成定时器的级联。
将IO口复用为CHx中的一个通道,信号从从外部输入,进过异或门进入TI1,进过滤波器整形和边沿检测器(检测上升沿和下降沿),得到两个信号:一个是TI1FP1,一个是TI1FOP2。选择一个信号捕获。信号捕获到触发捕获事件,CNT计数器的值转移到捕获/比较寄存器。
在捕获/比较寄存器和输出比较一起使用时,先往捕获/比较寄存器写入比较值,要使用的时候就是产生事件将比较值写入影子寄存器,在CNT计数器的值与影子寄存器的值相等时,产生输出参考信号和比较事件。输出控制有八种输出模式。同时ETRF信号也能控制OCxREF信号将其强制清零。TIMx_CHx是分时复用
捕获/比较寄存器,输出时是写入捕获/比较预装载寄存器
影子寄存器不可以直接访问
预装载寄存器和影子寄存器之间满足比较条件才可以进行转移(就是中间的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.配置定时器基础工作参数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()
函数 | 主要寄存器 | 主要功能 |
---|---|---|
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 | 使能通道预装载 |
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;
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
定义变量
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);
}
当CC1S配置不是00是就是输入模式
CC1R是写CC1R高位和低位的操作
CC1E硬件捕获
CC1G软件捕获
以捕获测量高电平脉宽为例
假设:递增计数模式
ARR:自动重装载寄存器的值
首先是将通道x配置为上升沿检测模式,当时间达到t1,此时CNT的值会自动转移到CCRx寄存器,此时需要做两件事情,一是将CNT清零,二是将上升沿检测改成下降沿检测。
没有溢出的情况是,脉宽直接等于CCRx2
溢出的情况是,脉宽等于N*(ARR+1)+CCRx2(N是溢出次数)
中断是为了保存中断次数
函数 | 主要寄存器 | 主要功能 |
---|---|---|
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() | 无 | 定时器输入捕获回调函数,由用户重定义 |
```c
typedef struct
{
uint32_t ICPolarity; /* 输入捕获触发方式选择,比如上升、下降沿捕获 */
uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */
uint32_t ICPrescaler; /* 输入捕获分频系数 */
uint32_t ICFilter; /* 输入捕获滤波器设置 */
} TIM_IC_InitTypeDef;
通过定时器5通道1来捕获按键高电平脉宽时间,通过串口打印出来
1,确定计数器工作频率Tout =((ARR+1)*(PSC+1))/Ft
1MHz计数频率为例,PSC=71,ARR=65535
2,配置输入捕获方式:上升沿捕获、输入通道1映射在TI1上、不分频、不滤波
定义变量
/* 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 */
使用通用定时器脉冲输入计数需要使用外部时钟模式1和外部时钟模式2
外部输入模式1只能用通道一和通道二
外部输入模式1是直接向定时器的输入通道输入脉冲,通过边沿将检测器检测上升沿或者下降沿,通过T1FPx进入从模式控制器,种模式选择外部时钟1,进入时基模块计数。
除了要配置 选择要使用的通用定时器(TIM2~TIM5),选择计时器的时钟源为内部时钟(CK_INT)、根据要定时的时间计算预分频系数(TIMx_PSC)、自动重装载值(TIMx_ARR)、内部时钟的分频系数以外,更重要的还需要配置:
捕获/比较模式寄存器1/2(TIMx_CCMR1/2):有2个寄存器,一个寄存器只能设置2个通道;该寄存器用于设置通道的输入(捕获模式)或输出(比较模式)参数,就输入捕获而言,主要设置滤波器、预分频器、输入映射关系
捕获/比较使能寄存器(TIMx_CCER):配置捕获触发的信号级性、捕获使能,每个定时器有4个通道,这里的CC1P/CC1E表示的是第一个通道
从模式控制寄存器(TIMx_SMCR):选择触发输入源和从模式
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()
3.TIM
那个极性判断应该是下降沿判断,选错了,giao
因为我手里的板子定时器输入通道1是接在PA0和接地,然后让PA0引脚输出为1,当按键按下时,就是产生下降沿
4.USART
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
(1)重复计数器
(2)死区时间带可编程的互补输出
(3)断路输入,用于将定时器的输出信号置于用户可选的安全配置
1.重复计数器
使用高级计数器之前需要先往REP寄存器里边写入一个值,产生溢出每次减1,减到0时才会产生更新中断
2.输出比较
输出部分通道1.2.3都有互补通道
3.刹车功能
计数器每次上溢或者下溢都能使重复计数器减1,减到0时,再发生一次溢出就会产生更新事件
(0溢事件)
###3 2.高级定时器框图
TIMx_RCR只有后八位REP是有效的
REP寄存器就是重复寄存器TIMx_RCR的后八位,同样他也有影子寄存器
如果设置RCR寄存器为N,更新事件将在N+1次溢出时发生
1. 配置边沿对齐模式输出PWM
2. 指定输出N个PWM,则把N-1写入RCR
3. 在更新中断内关闭计数器
4. 高级定时器通道输出必须把MOE位置1
输出比较模式是要将输出模式配置为翻转
当CNT=CCRx,OCxREF将会翻转,OCx将会翻转,CHx将会翻转,输出电平将会翻转
比较输出本质上也是输出PWM波
输出的周期是2*(ARR+1)*(PSC+1)/FT
(周期由ARR决定,相位由CCRx决定)
占空比固定为半个周期
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()
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);
输出比较模式是要将输出模式配置为翻转
当CNT=CCRx(CCRx是捕获比较寄存器 的值,就是比较值咯),输出参考信号OCxREF将会翻转,OCx将会翻转,CHx(IO口复用)将会翻转,IO输出电平将会翻转
比较输出本质上也是输出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()
输出比较模式是要将输出模式配置为翻转
当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.TIM
定时器的从模式,Tigger Source都是不用配置的,时钟资源配置为内部时钟,四个通道统统用上。
预分频系数设置为71,计数模式设置为递增计数模式,自动重转载值设置为999,不分频,失能自动重装载计数器,重复计数器初值为0.
下方四个通道的设置
模式设置为翻转模式
电极设置为High
输出脉冲为2500
CH Idle state是指空闲状态的通道是SET来时RESET
在主函数中设置重转载值
__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);
//开启将寄存器的值写入影子寄存器的值
实际的使用会比上边的简图复杂,而且实际使用中多用MOS管而不是三极管(原理理解罢了)
上述三极管都是NPN型的三极管。Q1、Q4接的是输出通道,Q2、Q3接的是互补通道。
输出通道跟互补输出通道不能同时置于有效电平(对于上图来说是高点平)。
如图,高级定时器多了互补输出通道和死区时间发生器,经过死区发生器可以看到控制寄存器控制的输出通道和互补输出通道都会被使能(就是被置为11)
下方的MOE相当于使能总开关
使能刹车:将TIMx_BDTR的BKE位置1,刹车输入信号的极性由BKR位设置
上面的的框图,输入信号用IO口的复用功能,输入经过BRK极性选择与时钟控制器的时钟故障事件一起接到或门,从而发出中断信号
使能刹车功能后:由TIMx_BDTR的MOE、OSSI、OSSR位,
TIMx_CR2的OISx、OISxN位,TIMx_CCER的CCxE、CCxNE位控制OCx和OCxN输出状态
无论何时,OCx和OCxN输出都不能同时处在有效电平
发生刹车后
函数 | 主要寄存器 | 主要功能 |
---|---|---|
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;
通过定时器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桥为例,配置通道输出极性以及互补输出极性
定时器时钟源选择内部时钟Internal Clock
通道1选择PWM互补输出
开启刹车
通道配置
可以看到PA7是输出通道,PA8是互补通达,PA6是刹车输入引脚(我设置的是低电平有效)
生成代码后记得要去看一下GPIO口上拉下拉的状态
主函数配置
/* 定时器通道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);
这里时钟源我选择内部时钟,频率是72MHz,所以时基单元记一个数的周期为1/72MHz秒。大约13.8ns(这里也是确定;量器的精度)
通过计算上升沿下降沿计数值之差,就能算出PWM波输出高电平/低电平的时间
反过来也是一样的哈
哥们白画了上边那个图[衰]
PWM输入信号从TIMx_CH1输入,通过边沿检测器和滤波器分成两个通道T1FP1和T1FP2分别负责上升沿检测和下降沿检测,进入捕获/比较寄存器就会产生捕获事件。
从模式选择复位模式。上升沿会触发从模式的复位,这时计数值重置为初值(递增计数模式初值就为0,递减计数模式就为初值),下降沿到的时候就能测出时间了(因为这是的CNT的值在上升沿来的被置0了,直接读取CNT的值下降沿来的时候CNT’的值就是时间了)。
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;
通过定时器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
TIM1
RCC
时钟树
TIM3
上边配置cube的过程有可能会有错误(tired)
可是我手头没有示波器(悲)
to be continue->