STM32 HAL库

2023-05-16

STM32 HAL库

    • 第三章 MDK5 软件入门
      • bug解决
      • 关键文件介绍
      • 程序仿真
      • User Keywords
      • 语法提示
      • 代码编辑/查看技巧
    • 第四章 STM32F1 基础知识入门
      • MDK 下 C 语言基础复习
      • STM32F103 时钟系统
        • STM32F103 时钟树概述
        • STM32F103 时钟系统配置
      • STM32F1 时钟使能和配置
      • 端口复用和重映射
      • STM32 NVIC 中断优先级管理
    • 第五章 SYSTEM 文件夹介绍
      • delay
    • 第九章 串口通信实验
      • 串口配置和使用的方法
    • 第十章 外部中断实验
      • 配置步骤
    • 第十一章 独立看门狗(IWDG)实验
    • 第十二章 窗口门狗(WWDG)实验
    • 第十三章 定时器中断实验
      • 配置步骤
    • 第十四章 PWM 输出实验
      • 配置步骤
        • STM32 TIM1重映射
        • STM32 TIM2重映射
        • STM32 TIM3重映射
    • 第十五章 输入捕获实验
      • 配置步骤
        • IO复用
    • 第二十六章 DMA 实验
      • 配置步骤

第三章 MDK5 软件入门

bug解决

问题:
error in include chain (cmsis_armcc.h): expected identfieror
解决:
打开UVCC.ini(这个文件在MDK5\UV4目录下),添加如下命令

cmsis_armcc.h   = *

关键文件介绍

sm32f1xx_hal.c :包含 HAL 通用 API(比如HAL_Init,HAL_DeInit,HAL_Delay 等)。
stm32f1xx_hal_conf.h :主要用来选择使能何种外设以及一些时钟相关参数设置。
stm32f1xx_it.h 中主要是一些中断服务函数的申明。
stm32f1xx_hal_msp.c文件定义了两个函数 HAL_MspInitHAL_MspDeInit。这两个函数分别被文件 stm32f1xx_hal.c 中的 HAL_InitHAL_DeInit 所调用。
HAL_MspInit 函数的主要作用是进行 MCU 相关的硬件初始化操作。
HAL_DeInit()函数通过写复位寄存器,将所有模块复位。
HAL_Init——HAL_PPP_Init—— HAL_PPP_MspInit

程序仿真

在这里插入图片描述

User Keywords

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

语法提示

在这里插入图片描述

代码编辑/查看技巧

TAB、SHIFT+TAB

第四章 STM32F1 基础知识入门

MDK 下 C 语言基础复习

%.4f 输出小数位为4的浮点数
#define 标识符 字符串
结构体
(1)结构体变量名字.成员名
比如要引用 GPIOA 的成员 Mode,方法是:GPIOA. Mode;
(2)结构体指针成员变量引用方法是通过“->”符号实现
比如要访问 GPIOC 结构体指针指向的结构体的成员变量 Speed,方法是:
struct G_TYPE *GPIOC;//定义结构体指针变量 GPIOC;
GPIOC-> Speed;

STM32F103 时钟系统

STM32F103 时钟树概述

时钟树
有五个时钟源,为 HSI、HSE、LSI、LSE、PLL。
HIS,HSE 以及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
其中 HSE 和 LSE 是外部时钟源,其他的是内部时钟源。
①、HSI 是高速内部时钟,RC 振荡器,频率为 8MHz。
②、HSE 是高速外部时钟,,频率范围为 4MHz~16MHz。我的是 8M 的晶振。
③、LSI 是低速内部时钟,RC 振荡器,频率为 40kHz。独立看门狗的时钟源只能是 LSI,同
时 LSI 还可以作为 RTC 的时钟源。
④、LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。
⑤、PLL 为锁相环倍频输出,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为
2~16 倍,但是其输出频率最大不得超过 72MHz。
STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者 HSE。系统时钟最大频率为 72MHz。
APB1 上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2 上面连接的是高速外设包括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。

STM32F103 时钟系统配置

HAL库的 SystemInit 函数除了打开 HSI 之外,没有任何时钟相关配置,所以使用 HAL 库我们必须编写自己的时钟配置函数。
RCC_OscInitTypeDef结构体中镶嵌了RCC_PLLInitTypeDef结构体

typedef struct 
{ 
    uint32_t OscillatorType;     //需要选择配置的振荡器类型         
    uint32_t HSEState;          //HSE 状态         
	uint32_t HSEPredivValue; // Prediv1 值   
    uint32_t LSEState;            //LSE 状态                                   
    uint32_t HSIState;            //HIS 状态
    uint32_t HSICalibrationValue;   //HIS 校准值 
    uint32_t LSIState;              //LSI 状态         
    RCC_PLLInitTypeDef PLL;      //PLL 配置 
}RCC_OscInitTypeDef; 
typedef struct 
{ 
    uint32_t PLLState;    //PLL 状态 
    uint32_t PLLSource;  //PLL 时钟源 
    uint32_t PLLMUL;     //PLL VCO 输入时钟的乘法因子 
}RCC_PLLInitTypeDef; 

HAL_RCC_ClockConfig()函数

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef  *RCC_ClkInitStruct, uint32_t FLatency)

该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 AHB,APB1 和 APB2 的分频系数。第二个入口参数
FLatency 用来设置 FLASH 延迟
Stm32_Clock_Init(RCC_PLL_MUL9)
PLL时钟为PLLCLK=HSE9=8MHz9=72MHz
同时我们选择系统时钟源为PLL,所 以 系 统 时 钟 SYSCLK=72MHz。
AHB分频系数为1,故 其 频 率 为HCLK=SYSCLK/1=72MHz。
APB1分频系数为2,故其频率为PCLK1=HCLK/2=36MHz。
APB2分频系数为1,故其频率为PCLK2=HCLK/1=72/1=72MHz。

STM32F1 时钟使能和配置

常用使能外设时钟的宏定义标识符使用方法:
__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 时钟
__HAL_RCC_DMA1_CLK_ENABLE();//使能 DMA1 时钟
__HAL_RCC_USART2_CLK_ENABLE();//使能串口 2 时钟
__HAL_RCC_TIM1_CLK_ENABLE();//使能 TIM1 时钟
常用的禁止外设时钟的宏定义标识符使用方法:
__HAL_RCC_DMA1_CLK_DISABLE();//禁止 DMA1 时钟
__HAL_RCC_USART2_CLK_DISABLE();//禁止串口 2 时钟
__HAL_RCC_TIM1_CLK_DISABLE();//禁止 TIM1 时钟

端口复用和重映射

STM32F1 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。

STM32 NVIC 中断优先级管理

IP[59]~IP[0]分别对应中断 59~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
STM32 将中断分为 5 个组,组 0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。
在这里插入图片描述
第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
我们要进行中断优先级分组设置,只需要修改 HAL_Init 函数内部的这行代码即可。

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); //设置优先级分组2

HAL_NVIC_SetPriority 是用来设置单个优先级的抢占优先级和响应优先级的值。
HAL_NVIC_EnableIRQ 是用来使能某个中断通道。
HAL_NVIC_DisableIRQ 是用来清除某个中断使能的,也就是中断失能。

第五章 SYSTEM 文件夹介绍

delay

void delay_osschedlock(void);
void delay_osschedunlock(void);
void delay_ostimedly(u32 ticks);
void SysTick_Handler(void);

void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
前面 4 个函数,仅在支持操作系统(OS)的时候,需要用到,而后面三个函数,则不论是否支持 OS 都需要用到。
支持 OS 需要用到的三个宏定义(以 UCOSII 为例)即
#define delay_osrunning OSRunning //OS 是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
宏定义:delay_osrunning,用于标记 OS 是否正在运行,当 OS 已经开始运行时,该宏定义
值为 1,当 OS 还未运行时,该宏定义值为 0。
宏定义:delay_ ostickspersec,用于表示 OS 的时钟节拍,即 OS 每秒钟任务调度次数。
宏定义:delay_ osintnesting,用于表示 OS 中断嵌套级别,即中断嵌套次数,每进入一个
中断,该值加 1,每退出一个中断,该值减 1。
支持 OS 需要用到的 4 个函数,即
函数:delay_osschedlock,用于 delay_us 延时,作用是禁止 OS 进行调度,以防打断 us 级
延时,导致延时时间不准。
函数:delay_osschedunlock,同样用于 delay_us 延时,作用是在延时结束后恢复 OS 的调度,
继续正常的 OS 任务调度。
函数:delay_ostimedly,则是调用 OS 自带的延时函数,实现延时。该函数的参数为时钟节
拍数。
函数:SysTick_Handler,则是 systick 的中断服务函数,该函数为 OS 提供时钟节拍,同时
可以引起任务调度。

第九章 串口通信实验

**目的:**将利用串口1不停的打印消息到电脑上,同时接收从串口发来的数据,把发送过来的数据直接回给电脑
与串口相关的寄存器
1,串口时钟使能。串口作为STM32的一个外设,其时钟由外设时钟使能寄存器控制,串口1是在ANP2ENR寄存器的14位。除了串口1的时钟在ABPENR寄存器,其他时钟串口都在ABPENR寄存器。
2,串口复位。当外设出现异常时可以通过复位寄存器里面的对应位置设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统开始配置外设的时候,都会先执行复位外设的操作。串口1的复位就是通过配置ABP2RSTR寄存器的第14位来实现的。通过向该位写1复位串口1,写0结束复位。其他串口的复位在ABP1RSTR。
3,串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR,通过设置该寄存器就可以达到配置不同波特率的目的。
4,串口控制。STM32的每个串口都有3个控制寄存器USAT_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里我们只用到USART_CR1就可以实现我们的功能了。该寄存器的高18位没有用到,低14位用于串口功能的设置。UE为串口使能位,通过该位置1,以使能串口。M为字长选择,当该位为0时设置串口为8个字长外加n个停止位,停止位的个数是通过USART_CR2的[13:12]位设置来决定的,默认为0。PCE为校验使能位,设置为0,则禁止校验,否则使能校验。PS为校验位选择,设置为0为偶检验,否则为奇校验。TXIE为发送缓冲区空中断使能位,设置该位为1,当USAT_SR中TXE位为1时,将产生串口中断。TCIE发送完成中断使能,设置为1,当USART_SR中的TC位为1时,将产生串口中断。RXNEIE为接收缓冲区非空中断使能,设置该位为1,当USART_SR中的ORE或者RXNE位为1时,将产生串口中断。TE为发送使能位,设置为1,将开启串口的发送功能。RE为接收使能位。
5,数据的发送和接收。STM32的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。他虽然是一个32位寄存器,但是只用了低9位(DR[8:0]),其他都是保留。一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写功能。TDR寄存器提供了内部总线和输出移位寄存器之间的并行的接口。RDR寄存器提供了输入移位寄存器和内部总线的接口。当使能校验位(USART_CR1中PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不同,MSB是低7位或者第8位)会被后来的校验位位取代。当使能校验位进行接收时,读到MSB位是接收的校验位。
6,串口状态。串口的状态可以通过寄存器USART_SR来读取。
RXNE(读数据寄存器非空),当该位被置为1时,就是提示已经有数据接收到了,并且可以读出来了。这时我们就要取读取USART_DR,通过读USART_DR可以将改位清0,也可以向该位写0,直接清除。
TC(发送完成),当该位被置位时,表示USART_DR内的数据已经发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1)读USART_SR,写USART_DR。2)直接向该位写0.。

串口配置和使用的方法

  1. 串口参数初始化(波特率/停止位等),并使能串口。
    一般情况下载调用函数 HAL_UART_Init 对串口进行初始化的时候,我们只需要先设置 InstanceInit 两个成员变量的值。
    函数 HAL_UART_Init 内部会调用串口使能函数使能相应串口,所以调用了该函数之后我们就不需要重复使能串口了。
    在调用的初始化函数 HAL_UART_Init 内部,会先调用 MSP 初始化回调函数进行 MCU 相关的初始化
	UART1_Handler.Instance=USART1;					    //USART1
	UART1_Handler.Init.BaudRate=bound;				    //波特率
	UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B;   //字长为8位数据格式
	UART1_Handler.Init.StopBits=UART_STOPBITS_1;	    //一个停止位
	UART1_Handler.Init.Parity=UART_PARITY_NONE;		    //无奇偶校验位
	UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;   //无硬件流控
	UART1_Handler.Init.Mode=UART_MODE_TX_RX;		    //收发模式
	HAL_UART_Init(&UART1_Handler);					    //HAL_UART_Init()会使能UART1
  HAL_UART_Receive_IT(&huart1,&aRxBuffer,1);//开启接收中断,每次接收1个字符后表示接收结束从而进入
  //回调函数AL_UART_RxCpltCallback进行相应处理,当串口接收到一个字符后,他会保存在缓存aRxBuffer中,

2)使能串口和 GPIO 口时钟

		__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟
		__HAL_RCC_USART1_CLK_ENABLE();			//使能USART1时钟
		__HAL_RCC_AFIO_CLK_ENABLE();

3)GPIO 口初始化设置(速度,上下拉等)以及复用映射配置

		__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟
		__HAL_RCC_USART1_CLK_ENABLE();			//使能USART1时钟
		__HAL_RCC_AFIO_CLK_ENABLE();
	
		GPIO_Initure.Pin=GPIO_PIN_9;			//PA9
		GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出
		GPIO_Initure.Pull=GPIO_PULLUP;			//上拉
		GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
		HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA9

		GPIO_Initure.Pin=GPIO_PIN_10;			//PA10
		GPIO_Initure.Mode=GPIO_MODE_AF_INPUT;	//模式要设置为复用输入模式!	
		HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA10

4) 开启串口相关中断,配置串口中断优先级

//在接收和发送的函数里边自动调用
__HAL_UART_ENABLE_IT(huart, UART_IT_TXE);	//开启发送完成中断
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);	//开启接收完成中断 
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);	//关闭发送完成中断
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);	//关闭接收完成中断
//中断优先级配置
HAL_NVIC_EnableIRQ(USART1_IRQn);      //使能 USART1 中断通道 
HAL_NVIC_SetPriority(USART1_IRQn,3,3);    //抢占优先级 3,子优先级 3 

5) 编写中断服务函数

void USART1_IRQHandler(void) 

6) 串口数据接收和发送

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

软件设计
HAL_UART_Receive_IT:作用是开启接收中断,同时设置接收的缓存区以及接收的数
据量。
串口中断处理逻辑我们会最终在函数 HAL_UART_IRQHandler 内部执行。而该函数是 HAL 库已经定义好,而且用户一般不能随意修改。
在函数 HAL_UART_IRQHandler 内部通过判断中断类型是否为接收完成中断,确定是否调用 HAL 另外一个函数 UART_Receive_IT()。
UART_Receive_IT():用是把每次中断接收到的字符保存在串口句柄的缓存指针 pRxBuffPtr 中,同时每次接收一个字符,其计数器 RxXferCount 减 1,直到接收完成 RxXferSize 个字符之后 RxXferCount 设置为0,同时调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。
HAL_UART_RxCpltCallback

第十章 外部中断实验

配置步骤

  1. 使能 IO 口时钟,初始化 IO 口为输入
  2. 设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系。
  3. 配置中断优先级(NVIC),并使能中断。
  4. 编写中断服务函数。
    void EXTI0_IRQHandler();
    void EXTI1_IRQHandler();
    void EXTI2_IRQHandler();
    void EXTI3_IRQHandler();
    void EXTI4_IRQHandler();
    void EXTI9_5_IRQHandler();
    void EXTI15_10_IRQHandler();
    5) 编写中断处理回调函数 HAL_GPIO_EXTI_Callback
//外部中断初始化
void EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_GPIOA_CLK_ENABLE();               //开启GPIOA时钟
	__HAL_RCC_GPIOB_CLK_ENABLE();               //开启GPIOA时钟
    
    GPIO_Initure.Pin=GPIO_PIN_8; 	            //PA8
    GPIO_Initure.Mode=GPIO_MODE_IT_FALLING;     //下降沿触发
    GPIO_Initure.Pull=GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
    
	GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; 	//PB0,1
    GPIO_Initure.Mode=GPIO_MODE_IT_FALLING;     //下降沿触发
    GPIO_Initure.Pull=GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
	
    //中断线8-PA8
    HAL_NVIC_SetPriority(EXTI9_5_IRQn,2,0);       //抢占优先级为2,子优先级为0
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);             //使能中断线8
    
    //中断线0-PB0
    HAL_NVIC_SetPriority(EXTI0_IRQn,2,2);       //抢占优先级为2,子优先级为2
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);             //使能中断线0
    
    //中断线1-PB1
    HAL_NVIC_SetPriority(EXTI1_IRQn,2,3);   	//抢占优先级为2,子优先级为3
    HAL_NVIC_EnableIRQ(EXTI1_IRQn);         	//使能中断线1
}


//中断服务函数
void EXTI9_5_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);		//调用中断处理公用函数
}

void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);		//调用中断处理公用函数
}

void EXTI1_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);		//调用中断处理公用函数
}

//中断服务程序中需要做的事情
//在HAL库中所有的外部中断服务函数都会调用此函数
//GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(100);      //消抖
    switch(GPIO_Pin)
    {
        case GPIO_PIN_1:
            if(WK_UP==0) 
            {
				LED0=!LED0;					//控制LED0反转
            }
            break;
        case GPIO_PIN_0:
            if(KEY1==0)  					//控制LED1反转
            {
				LED1=!LED1;
            }
            break;
        case GPIO_PIN_8:
            if(KEY0==0)  
            {
				LED0=!LED0;
				LED1=!LED1; 
            }
            break;
    }
}

第十一章 独立看门狗(IWDG)实验

IWDG(Independent watchdog)独立看门狗,可以用来检测并解决由于软件错误导致的故障,当计数器到达给定的超时值时,会触发一个中断或产生系统复位
来配置独立看门狗的步骤
1)取消寄存器写保护(向 IWDG_KR 写入 0X5555)
2)重载计数值喂狗(向 IWDG_KR 写入 0XAAAA)
3) 启动看门狗(向 IWDG_KR 写入 0XCCCC)
__HAL_IWDG_START(hiwdg);在HAL_IWDG_Init中定义了
在这里插入图片描述

第十二章 窗口门狗(WWDG)实验

HAL 库定义了一个中断处理共用函数 HAL_WWDG_IRQHandler,我们在 WWDG 中断服务函数中会调用该函数。同时该函数内部,会经过一系列判断,最后调用回调函数HAL_WWDG_WakeupCallback,所以提前唤醒中断逻辑我们一般些在回调函数 HAL_WWDG_WakeupCallback 中。

Fwwdg=PCLK1/(40962^fprer)=36000/(40968)(hz)
Twwdg=(4096×2^WDGTB×(T[5:0]+1)) /Fpclk1=4096864/36000(ms)

第十三章 定时器中断实验

TIME1 和 TIME8 等高级定时器
TIME2~TIME5 等通用定时器:
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~
65535 之间的任意数值。
3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C.PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外
一个定时器)的同步电路。
5)如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
TIME6 和TIME7 等基本定时器

配置步骤

1)TIM3 时钟使能。
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。

HAL_TIM_Base_Init 

3)使能定时器更新中断,使能定时器
使能定时器更新中断和使能定时器两个操作可以在函数HAL_TIM_Base_Start_IT()中一次完成。
4)TIM3 中断优先级设置。
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中
断优先级。
5)编写中断服务函数。
这里使用的是更新(溢出)中断
Tout= ((arr+1)(psc+1))/Tclk=50007200/72000(ms)=500ms

TIM_HandleTypeDef TIM3_Handler;      //定时器句柄 

//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Init(u16 arr,u16 psc)
{  
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_Base_Init(&TIM3_Handler);
    
    HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE 和使能定时器  
}

//定时器底册驱动,开启时钟,设置中断优先级
//此函数会被HAL_TIM_Base_Init()函数调用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
		HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    //设置中断优先级,抢占优先级1,子优先级3
		HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断   
	}
}

//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM3_Handler);
}

//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
        LED1=!LED1;        //LED1反转
    }
}

第十四章 PWM 输出实验

STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。
在这里插入图片描述
我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。

配置步骤

1)开启 TIM3 和 GPIO 时钟,配置 PB5 选择复用功能输出。

STM32 TIM1重映射

在这里插入图片描述

STM32 TIM2重映射

在这里插入图片描述

STM32 TIM3重映射

在这里插入图片描述

__HAL_AFIO_REMAP_TIM3_ENABLE()			//TIM3通道引脚全部重映射使能
__HAL_AFIO_REMAP_TIM3_PARTIAL();		//TIM3通道引脚部分重映射使能
__HAL_AFIO_REMAP_TIM3_DISABLE()			//TIM3通道关闭重映射

2)初始化 TIM3(TIM2),设置 TIM3 ((TIM2))的 ARR 和 PSC 等参数。

 HAL_TIM_PWM_Init
 //初始化与 MCU 无关的步骤
 HAL_TIM_PWM_MspInit

3)设置 TIM3_CH2 的 PWM 模式,输出比较极性,比较值等参数。
4)使能 TIM3,使能 TIM3 的 CH2 输出。
5)修改 TIM3_CCR2 来控制占空比。

//高电平输入捕获
TIM_HandleTypeDef 	TIM2_Handler;      	//定时器句柄 
TIM_OC_InitTypeDef 	TIM2_CH2Handler;	//定时器2通道2句柄

//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM2_Init(u16 arr,u16 psc)
{  
    TIM2_Handler.Instance=TIM2;                          //通用定时器2
    TIM2_Handler.Init.Prescaler=psc;                     //分频系数
    TIM2_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM2_Handler.Init.Period=arr;                        //自动装载值
    TIM2_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_Base_Init(&TIM2_Handler);
    
    HAL_TIM_Base_Start_IT(&TIM2_Handler); //使能定时器2和定时器2更新中断:TIM_IT_UPDATE   
}

//TIM3 PWM部分初始化 
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM2_PWM_Init(u16 arr,u16 psc)
{  
    TIM2_Handler.Instance=TIM2;          	//定时器2
    TIM2_Handler.Init.Prescaler=psc;       //定时器分频
    TIM2_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式
    TIM2_Handler.Init.Period=arr;          //自动重装载值
    TIM2_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&TIM2_Handler);       //初始化PWM
    
    TIM2_CH2Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1,不管是向上计数或者向下计数,只要CNT<CCRx就是有效电平,PW2与他相反
    TIM2_CH2Handler.Pulse=arr/2;            //初始化,设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
    TIM2_CH2Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低
    HAL_TIM_PWM_ConfigChannel(&TIM2_Handler,&TIM2_CH2Handler,TIM_CHANNEL_2);//配置TIM2通道2
	
    HAL_TIM_PWM_Start(&TIM2_Handler,TIM_CHANNEL_2);//开启PWM通道2
	 	   
}

//定时器底册驱动,开启时钟,设置中断优先级
//此函数会被HAL_TIM_Base_Init()函数调用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM2)
	{
		__HAL_RCC_TIM2_CLK_ENABLE();            //使能TIM2时钟
		HAL_NVIC_SetPriority(TIM2_IRQn,1,3);    //设置中断优先级,抢占优先级1,子优先级3
		HAL_NVIC_EnableIRQ(TIM2_IRQn);          //开启ITM2中断   
	}
}

//定时器底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	GPIO_InitTypeDef GPIO_Initure;
	
    if(htim->Instance==TIM2)
	{
		__HAL_RCC_TIM2_CLK_ENABLE();			//使能定时器2
//		__HAL_AFIO_REMAP_TIM3_PARTIAL();		//TIM3通道引脚部分重映射使能
		__HAL_RCC_GPIOA_CLK_ENABLE();			//开启GPIOA时钟
		
		GPIO_Initure.Pin=GPIO_PIN_1;           	//PA1
		GPIO_Initure.Mode=GPIO_MODE_AF_PP;  	//复用推挽输出
		GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
		GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
		HAL_GPIO_Init(GPIOA,&GPIO_Initure); 	
	}
}

//设置TIM通道2的占空比
//compare:比较值
void TIM_SetTIM2Compare2(u32 compare)
{
	TIM2->CCR2=compare; 
}

//获取TIM捕获/比较寄存器值
u32 TIM_GetTIM2Capture2(void)
{
	return  HAL_TIM_ReadCapturedValue(&TIM2_Handler,TIM_CHANNEL_2);
}

//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM2_Handler);
}

//回调函数,定时器中断服务函数调用
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//{
//    if(htim==(&TIM3_Handler))
//    {
//        LED1=!LED1;        //LED1反转
//    }
//}

  //下面是输入捕获相关源码实验相关函数源码
TIM_HandleTypeDef TIM3_Handler;         //定时器3句柄

//定时器5通道1输入捕获配置
//arr:自动重装值(TIM5是16位的!!)
//psc:时钟预分频数
void TIM3_CH1_Cap_Init(u32 arr,u16 psc)
{  
    TIM_IC_InitTypeDef TIM3_CH1Config;  
    
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频银子
    HAL_TIM_IC_Init(&TIM3_Handler);						//初始化输入捕获时基参数
    
    TIM3_CH1Config.ICPolarity=TIM_ICPOLARITY_RISING;    //上升沿捕获
    TIM3_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到TI1上
    TIM3_CH1Config.ICPrescaler=TIM_ICPSC_DIV1;          //配置输入分频,不分频
    TIM3_CH1Config.ICFilter=0;                          //配置输入滤波器,不滤波
    HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH1Config,TIM_CHANNEL_1);//配置TIM3通道1
	
    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_1);   //开启TIM5的捕获通道1,并且开启捕获中断
    __HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);   //使能更新中断
	
	HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //设置中断优先级,抢占优先级2,子优先级0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断通道  
}

//定时器5底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_IC_Init()调用
//htim:定时器5句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();			//开启GPIOA时钟
	
    GPIO_Initure.Pin=GPIO_PIN_6;            //PA6
    GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; 	//复用推挽输入
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);

    HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //设置中断优先级,抢占优先级2,子优先级0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断通道  
}

//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
u8  TIM3CH1_CAPTURE_STA=0;							//输入捕获状态		    				
u16	TIM3CH1_CAPTURE_VAL;							//输入捕获值(TIM3是16位)

//定时器5中断服务函数
void TIM3_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&TIM3_Handler);				//定时器共用处理函数
}
 
//定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//更新中断(溢出)发生时执行
{
	if((TIM3CH1_CAPTURE_STA&0X80)==0)				//还未成功捕获
	{
		if(TIM3CH1_CAPTURE_STA&0X40)				//已经捕获到高电平了
		{
			if((TIM3CH1_CAPTURE_STA&0X3F)==0X3F)	//高电平太长了
			{
				TIM3CH1_CAPTURE_STA|=0X80;			//标记成功捕获了一次
				TIM3CH1_CAPTURE_VAL=0XFFFF;
			}else TIM3CH1_CAPTURE_STA++;
		}	 
	}		
}

//定时器输入捕获中断处理回调函数,该函数在HAL_TIM_IRQHandler中会被调用
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行
{
	if((TIM3CH1_CAPTURE_STA&0X80)==0)				//还未成功捕获
	{
		if(TIM3CH1_CAPTURE_STA&0X40)				//捕获到一个下降沿 		
		{	  			
			TIM3CH1_CAPTURE_STA|=0X80;				//标记成功捕获到一次高电平脉宽
            TIM3CH1_CAPTURE_VAL=HAL_TIM_ReadCapturedValue(&TIM3_Handler,TIM_CHANNEL_1);//获取当前的捕获值.
			TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1);   //一定要先清除原来的设置!!
            TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//配置TIM5通道1上升沿捕获
		}else  										//还未开始,第一次捕获上升沿
		{
			TIM3CH1_CAPTURE_STA=0;					//清空
			TIM3CH1_CAPTURE_VAL=0;
			TIM3CH1_CAPTURE_STA|=0X40;				//标记捕获到了上升沿
			__HAL_TIM_DISABLE(&TIM3_Handler);      	//关闭定时器3
			__HAL_TIM_SET_COUNTER(&TIM3_Handler,0);
			TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1);   //一定要先清除原来的设置!!
			TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//定时器3通道1设置为下降沿捕获
			__HAL_TIM_ENABLE(&TIM3_Handler);		//使能定时器3
		}		    
	}		
}

第十五章 输入捕获实验

通用定时器作为输入捕获的使用
通过两次捕获(次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度(注意,对于脉宽太长的情况,还要计算定时器溢出的次数)。

配置步骤

1)开启 TIM5 时钟,配置 PA0 为复用功能(AF2),并开启下拉电阻。

IO复用

在这里插入图片描述

GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; 	//复用推挽输入

2)初始化 TIM5,设置 TIM5 的 ARR 和 PSC。

HAL_TIM_IC_Init
//初始化与 MCU 无关的步骤
 HAL_TIM_IC_MspInit

3)设置 TIM5 的输入捕获参数,开启输入捕获。

HAL_TIM_IC_ConfigChannel

4)使能捕获和更新中断(设置 TIM5 的 DIER 寄存器)

__HAL_TIM_ENABLE_IT(&TIM5_Handler,TIM_IT_UPDATE);      //使能更新中断 
//数同时用来开启定时器的输入捕获通道和使能捕获中断
 HAL_TIM_IC_Start_IT
//不需要开启捕获中断,只是开启输入捕获功能
 HAL_TIM_IC_Start

5)使能定时器(设置 TIM5 的 CR1 寄存器)

//在步骤 4 中,如果我们调用了函数 HAL_TIM_IC_Start_IT  来开启输入捕获通道以及输入捕获中断,实际上它同时也开启了相应的定时器。单独的开启定时器的方法为: 
__HAL_TIM_ENABLE();    //开启定时器方法 

6)设置 NVIC 中断优先级
一般情况下 NVIC 配置我们都会放在 MSP 回调函数中
7)编写中断服务函数

void TIM5_IRQHandler(void); 

第二十六章 DMA 实验

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
DMA1 有 7 个通道。DMA2 有 5个通道。
存储器和存储器间的传输、外设和存储器,存储器和外设的传输
DMA1 各通道一览表:
在这里插入图片描述
通道 1 的几个 DMA1 请求(ADC1、TIM2_CH3、TIM4_CH1),这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个。

配置步骤

1)使能 DMA1 时钟

__HAL_RCC_DMA1_CLK_ENABLE();			//DMA1时钟使能 
  1. 初始化 DMA1 数据流 4,包括配置通道,外设地址,存储器地址,传输数据量等。
    hdma_usart3_tx.Instance = DMA1_Channel2;						//通道选择2					
    hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;			//存储器到外设
    hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;				//外设非增量模式
    hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;					//存储器增量模式
    hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;	//外设数据长度:8位
    hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;		//存储器数据长度:8位
    hdma_usart3_tx.Init.Mode = DMA_NORMAL;							//外设普通模式
    hdma_usart3_tx.Init.Priority = DMA_PRIORITY_LOW;				//低优先级
//HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接 DMA 和外设句柄。
__HAL_LINKDMA(huart,hdmatx,hdma_usart3_tx);						//将DMA与USART3联系起来(发送DMA)

3)使能串口 1 的 DMA 发送

void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
    HAL_DMA_Start(huart->hdmatx, (u32)pData, (uint32_t)&huart->Instance->DR, Size);//开启DMA传输
    huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口DMA发送
}	

4)使能 DMA1 数据流 4,启动传输。
5)查询 DMA 传输状态
6)DMA 中断使用方法

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32 HAL库 的相关文章

随机推荐