ToB企服应用市场:ToB评测及商务社交产业平台

标题: STM32 HAL库函数入门指南:从原理到实践 [打印本页]

作者: 卖不甜枣    时间: 2025-1-18 00:45
标题: STM32 HAL库函数入门指南:从原理到实践
1 STM32 HAL库概述

STM32 HAL(Hardware Abstraction Layer)库是ST公司专门为STM32系列微控制器开发的一套硬件抽象层函数库。它的核心设计理念是在应用层与硬件层之间创建一个抽象层,这个抽象层屏蔽了底层硬件的具体实现细节,为开发者提供了一套同一的、标准化的应用程序接口(API)。这种设计极大地进步了代码的可移植性和重用性,使得开发者能够更加专注于应用功能的实现,而不必过多关注硬件细节。
1.1 HAL库架构设计

HAL库的架构设计接纳了模块化的头脑,将整个库函数分为多个功能模块。每个模块都对应着STM32微控器的一个或多个外设,如GPIO、UART、SPI、I2C等。这种模块化的设计使得代码结构清楚,便于管理和维护。在每个模块中,又将功能细分为初始化设置、控制操纵、状态查询等不同类别的函数,形成了一个井井有条的函数调用体系。
1.2 错误处置惩罚

在错误处置惩罚方面,HAL库实现了完善的错误检测和处置惩罚机制。每个HAL函数都会返回执行状态,通过HAL_StatusTypeDef枚举范例来表示函数执行的结果,包罗HAL_OK、HAL_ERROR、HAL_BUSY和HAL_TIMEOUT等状态。这种机制使得开发者能够及时发现和处置惩罚程序运行中的非常情况,进步了程序的可靠性和稳定性。
1.3 驱动模板

HAL库还提供了丰富的外设驱动模板和示例程序。这些模板和示例涵盖了绝大多数常用的应用场景,开发者可以基于这些模板快速开发自己的应用程序。每个外设驱动都包含了完整的初始化代码、中断处置惩罚函数和基本的操纵函数,为开发者提供了可靠的参考。
1.4 中断处置惩罚

在中断处置惩罚方面,HAL库接纳了同一的中断处置惩罚框架。它定义了标准的中断回调函数接口,开发者只必要实现相应的回调函数,就可以处置惩罚各种中断事件。这种设计大大简化了中断处置惩罚的编程工作,同时也包管了中断处置惩罚代码的规范性和可维护性。
1.5 HAL库优势

为了进步程序的执行服从,HAL库在设计时充分考虑了性能优化题目。它提供了多种操纵模式,如轮询模式、中断模式和DMA模式,开发者可以根据现实需求选择合适的操纵模式。同时,HAL库也支持低功耗模式的设置和管理,有助于开发低功耗应用。
在使用HAL库时,必要注意的是,全部的外设操纵都必要通过相应的句柄(Handle)来进行。句柄是一个包含外设设置信息和状态信息的数据结构,它在外设初始化时创建,在后续的操纵中用于标识和控制特定的外设实例。这种基于句柄的设计方式,既包管了代码的可重入性,也便于多外设的并行操纵。
HAL库还提供了强大的调试支持。通过设置适当的调试级别,开发者可以获取详细的运行时信息,这对于题目定位和性能优化非常有帮助。HAL库还集成了断言机制,可以在开发阶段及时发现和定位程序中的逻辑错误。
2 HAL库使用步骤

使用HAL库开发程序通常遵照以下步骤:必要设置时钟系统。这包罗设置系统时钟源、设置PLL倍频系统以及设置各个总线的分频系数。这些设置通常在SystemClock_Config()函数中完成。初始化外设使用的GPIO引脚。每个外设都必要特定的GPIO引脚设置,包罗引脚的工作模式、上下拉状态等。设置并初始化具体的外设模块。这包罗设置外设的工作模式、中断优先级等参数。
2.1 工程初始化阶段

在使用HAL库开发STM32项目时,第一步是创建基础工程框架。这必要包含须要的HAL库头文件,此中最基本的是"stm32f4xx_hal.h"(以STM32F4系列为例)。同时,必要在项目中添加相应的HAL库源文件,这些文件通常位于ST官方提供的固件包中。
2.2 系统初始化

在main函数的开始,必须首先调用HAL_Init()函数来初始化HAL库。这个函数会完成以下几个重要使命:设置系统滴答定时器(SysTick)、初始化默认的HAL库状态、设置NVIC中断分组等。紧接着必要设置系统时钟,这通常通过调用SystemClock_Config()函数实现。该函数负责设置PLL、AHB、APB1、APB2等时钟参数,确保系统以精确的频率运行。范例的初始化代码结构如下:
  1. int main(void)
  2. {
  3.     HAL_Init();                    //HAL库初始化
  4.     SystemClock_Config();          //系统时钟配置
  5.    
  6.     /* 用户代码开始 */
  7.     while (1)
  8.     {
  9.     }
  10. }
复制代码
2.3 外设时钟使能

外设时钟使能是使用任何外设前的须要步骤。在STM32中,全部外设都必要先使能当时钟才能正常工作。HAL库提供了同一的宏定义来完成这个使命,比方:
  1. __HAL_RCC_GPIOA_CLK_ENABLE();     //使能GPIOA时钟
  2. __HAL_RCC_USART1_CLK_ENABLE();    //使能USART1时钟
  3. __HAL_RCC_DMA1_CLK_ENABLE();      //使能DMA1时钟
复制代码
2.4 外设初始化设置

HAL库接纳句柄(Handle)的方式管理每个外设,因此必要先定义相应的句柄结构体,然后进行参数设置。以GPIO为例,设置过程包罗:定义GPIO初始化结构体、设置引脚参数(模式、上下拉、速率等)、调用初始化函数。示例代码如下:
  1. GPIO_InitTypeDef GPIO_InitStruct = {0};
  2. GPIO_InitStruct.Pin = GPIO_PIN_5;
  3. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  4. GPIO_InitStruct.Pull = GPIO_NOPULL;
  5. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  6. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
复制代码
2.5 中断设置

假如外设必要使用中断,则必要设置NVIC并编写中断处置惩罚函数。HAL库提供了同一的中断回调函数机制,用户只必要实现相应的回调函数即可。比方:
  1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  2. {
  3.     if(GPIO_Pin == GPIO_PIN_0)
  4.     {
  5.         //中断处理代码
  6.     }
  7. }
复制代码
2.6 错误处置惩罚

几乎全部的HAL库函数都会返回执行状态(HAL_OK、HAL_ERROR等),建议在关键操纵后都进行状态检查:
  1. if(HAL_UART_Init(&huart1) != HAL_OK)
  2. {
  3.     Error_Handler();
  4. }
复制代码
2.7 外设功能使用

HAL库为每个外设提供了完整的操纵函数集,包罗数据收发、状态查询、参数修改等。这些函数都遵照同一的命名规范:HAL_PPP_Function(),此中PPP代表具体的外设名称。比方:
  1. //UART发送数据
  2. HAL_UART_Transmit(&huart1, TxData, sizeof(TxData), HAL_MAX_DELAY);
  3. //ADC开始转换
  4. HAL_ADC_Start(&hadc1);
  5. //定时器启动
  6. HAL_TIM_Base_Start_IT(&htim2);
复制代码
2.8 使用总结

初始化完成后,就可以在主循环中实现具体的应用功能。值得注意的是,HAL库的大多数函数都提供了壅闭和非壅闭两种版本,可以根据应用需求选择合适的方式。UART传输既可以使用壅闭式的HAL_UART_Transmit(),也可以使用非壅闭的HAL_UART_Transmit_IT()或HAL_UART_Transmit_DMA()。
在开发过程中,建议充分利用HAL库提供的DEBUG功能。可以通过设置assert_param宏来启用参数检查,这对于调试程序非常有帮助。同时,建议养成精良的错误处置惩罚习惯,合理使用HAL_Delay()函数进行延时,避免使用空循环延时。
3 GPIO的HAL库函数

GPIO(通用输入输出接口)是STM32微控制器最基础也是最常用的外设之一。HAL库为GPIO操纵提供了一套完整的函数库,这些函数不仅简化了GPIO的设置和控制过程,还提供了多种工作模式的灵活设置选项。
3.1 了解GPIO结构

在使用GPIO之前,首先必要了解GPIO的基本结构。STM32的每个GPIO引脚都可以设置为不同的工作模式,包罗输入模式、输出模式、复用功能模式和模拟模式每个引脚还可以设置上拉、下拉或者浮空状态,而且可以设置不同的输出速率品级。HAL库通过GPIO_InitTypeDef结构体来管理这些设置参数。
GPIO的设置过程主要包含以下几个关键步骤:
   
  3.2 使能GPIO时钟

必须使能对应GPIO端口的时钟。这是由于STM32接纳了时钟门控技能来低落功耗,只有使能了时钟的外设才能正常工作。时钟使能可以通过__HAL_RCC_GPIOx_CLK_ENABLE()宏函数来实现,此中x表示具体的GPIO端口(A、B、C等)
3.3 定义GPIO初始化结构体

接下来是GPIO初始化结构体的设置。GPIO_InitTypeDef结构体包含了以下重要参数:
   
  在现实的GPIO操纵中,HAL库提供了一系列函数来实现不同的控制需求。HAL_GPIO_Init()函数用于初始化GPIO引脚,它会根据初始化结构体中的设置参数来设置相应的寄存器。对于输出操纵,HAL_GPIO_WritePin()函数可以设置引脚的输出状态,HAL_GPIO_TogglePin()函数可以翻转引脚的状态。而对于输入操纵,HAL_GPIO_ReadPin()函数可以读取引脚的当前电平状态。
3.4 中断应用

GPIO可以设置为外部中断源。通过将Mode参数设置为GPIO_MODE_IT_RISING(上升沿触发)、GPIO_MODE_IT_FALLING(下降沿触发)或GPIO_MODE_IT_RISING_FALLING(双边沿触发),可以实现对引脚电平变化的中断检测。当设置为中断模式时,还必要设置中断优先级并使能中断。HAL库提供了HAL_GPIO_EXTI_IRQHandler()函数来处置惩罚GPIO外部中断,并通过HAL_GPIO_EXTI_Callback()回调函数来实现用户的具体中断服务程序。
对于必要快速相应的应用,HAL库还提供了一些直接操纵GPIO寄存器的宏。比如__HAL_GPIO_SET_PIN()和__HAL_GPIO_RESET_PIN()可以直接设置或清除引脚状态,这些操纵比调用标准的HAL函数更快。但使用这些宏时必要格外小心,由于它们会直接操纵硬件寄存器。
在现实应用中,一个范例的GPIO设置示比方下:
  1. void GPIO_LED_Init(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct = {0};
  4.    
  5.     // 使能GPIOA时钟
  6.     __HAL_RCC_GPIOA_CLK_ENABLE();
  7.    
  8.     // LED引脚配置
  9.     GPIO_InitStruct.Pin = GPIO_PIN_5;                  // 选择PA5引脚
  10.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;        // 推挽输出模式
  11.     GPIO_InitStruct.Pull = GPIO_NOPULL;                // 无上拉下拉
  12.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;       // 低速模式
  13.    
  14.     // 初始化GPIO
  15.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  16. }
复制代码
在进行GPIO设置时,还必要注意一些特别情况的处置惩罚。比方,当GPIO引脚被设置为复用功能时,除了常规的GPIO设置外,还必要精确设置复用功能编号。同时,某些引脚可能有默认的复用功能(如调试端口),在使用这些引脚时必要特别注意是否会影响系统的其他功能
3.5 GPIO锁定功能

HAL库还提供了GPIO锁定功能,通过HAL_GPIO_LockPin()函数可以锁定引脚的设置,防止设置被意外修改。这在一些必要高可靠性的应用中特别有效。但必要注意的是,一旦引脚被锁定,在系统复位之前将无法修改其设置。
4 HAL库中断设置与处置惩罚

中断系统是STM32单片机的核心功能之一,它允许微控制器及时相应外部事件和内部状态变化。在HAL库中,中断的设置和处置惩罚接纳了同一的框架,使得中断处置惩罚变得更加规范和简便。
中断源可以分为外部中断和内部中断两大类。外部中断主要来自GPIO引脚的电平变化,而内部中断则包罗定时器中断、ADC转换完成中断、UART接收发送中断等。无论是哪种中断,其设置过程都遵照相似的步骤。
使用中断时必要:
     4.1 外部中断设置

在STM32中,任何GPIO引脚都可以设置为外部中断源。设置过程主要包罗以下步骤:
  1. // 第一步:GPIO初始化结构体配置
  2. GPIO_InitTypeDef GPIO_InitStruct = {0};
  3. __HAL_RCC_GPIOA_CLK_ENABLE();    //使能GPIO时钟
  4. GPIO_InitStruct.Pin = GPIO_PIN_0;             //选择PA0引脚
  5. GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;   //上升沿触发中断
  6. GPIO_InitStruct.Pull = GPIO_PULLDOWN;         //下拉
  7. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速模式
  8. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  9. // 第二步:配置NVIC
  10. HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);    //设置中断优先级
  11. HAL_NVIC_EnableIRQ(EXTI0_IRQn);            //使能中断线
复制代码
对于中断处置惩罚,HAL库接纳了分层的方式首先是中断服务函数(ISR),这是在启动文件中定义的一级中断处置惩罚函数。然后是HAL库的中断处置惩罚函数,它会进行须要的状态检查和清除中断标志。最后是用户的回调函数,这是现实进行业务处置惩罚的地方。以外部中断为例:
  1. // 中断服务函数(在启动文件中)
  2. void EXTI0_IRQHandler(void)
  3. {
  4.     HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  5. }
  6. // 用户回调函数(在用户代码中实现)
  7. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  8. {
  9.     if(GPIO_Pin == GPIO_PIN_0)
  10.     {
  11.         // 在这里添加中断处理代码
  12.         HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);    //翻转LED
  13.     }
  14. }
复制代码
4.2 内部中断设置

内部中断的设置也遵照类似的模式。以定时器中断为例,设置过程如下:
  1. // 定时器初始化配置
  2. TIM_HandleTypeDef htim2;
  3. htim2.Instance = TIM2;
  4. htim2.Init.Prescaler = 7199;                  //预分频值
  5. htim2.Init.CounterMode = TIM_COUNTERMODE_UP;  //向上计数模式
  6. htim2.Init.Period = 9999;                     //周期值
  7. htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  8. if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  9. {
  10.     Error_Handler();
  11. }
  12. // 启动定时器中断
  13. HAL_TIM_Base_Start_IT(&htim2);
  14. // 配置NVIC
  15. HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
  16. HAL_NVIC_EnableIRQ(TIM2_IRQn);
复制代码
对应的中断处置惩罚函数:
  1. void TIM2_IRQHandler(void)
  2. {
  3.     HAL_TIM_IRQHandler(&htim2);
  4. }
  5. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  6. {
  7.     if(htim->Instance == TIM2)
  8.     {
  9.         // 定时器中断处理代码
  10.     }
  11. }
复制代码
4.3 中断优先级设置

在使用中断时,必要特别注意中断优先级的设置。STM32使用抢占优先级和子优先级的组合来管理中断优先级。HAL库在初始化时会设置默认的优先级分组(通常是4位抢占优先级,0位子优先级)。可以通过HAL_NVIC_SetPriorityGrouping()函数修改分组方式:
  1. // 配置中断优先级分组
  2. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);  //4位抢占优先级,0位子优先级
复制代码
在中断处置惩罚中,还必要注意以下几点:

  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  2. {
  3.     if(huart->Instance == USART1)
  4.     {
  5.         if(__HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE))
  6.         {
  7.             // 接收到新数据
  8.             __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_RXNE);
  9.         }
  10.     }
  11. }
复制代码

  1. // DMA中断配置
  2. HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
  3. HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
  4. // DMA中断回调
  5. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  6. {
  7.     if(huart->Instance == USART1)
  8.     {
  9.         HAL_UART_Receive_DMA(huart, RxBuffer, RXBUFFERSIZE);
  10.     }
  11. }
复制代码
5 定时器的HAL库函数

STM32微控制器的定时器系统是一个功能强大的模块,它包含了多种范例的定时器,可以满足不同应用场景的需求。根据功能复杂度,STM32的定时器可以分为三类:基本定时器(Basic Timer)、通用定时器(General-Purpose Timer)和高级定时器(Advanced Timer)。HAL库为这些定时器提供了同一的操纵接口,使得开发者能够方便地实现各种定时功能。
5.1 基本结构

基本定时器是最简朴的定时器范例,主要用于基本的定时功能和触发DAC转换它只包含一个16位或32位向上计数器、预分频器和重装载寄存器。通用定时器在基本定时器的基础上增加了捕获/比较通道,可以用于PWM生成、输入捕获等功能。而高级定时器则具有最完整的功能,除了包含通用定时器的全部特性外,还支持互补输出、死区控制、断路控制等高级功能,特别恰当于电机控制等应用。
5.2 工作原理

在使用定时器之前,首先必要了解定时器的基本工作原理。定时器的时基单元包含了预分频器(Prescaler)和计数器(Counter)。预分频器用于对输入时钟进行分频,从而低落计数频率;计数器则根据设置的方向(向上、向下或双向)进行计数,当计数值到达设定的自动重装载值(ARR)时,会产生更新事件,计数器重新开始计数。定时器的时间计算公式如下:
  1. 定时时间 = (预分频值 + 1) * (重装载值 + 1) / 定时器时钟频率
复制代码
5.3 设置步骤

HAL库通过TIM_HandleTypeDef结构体来管理定时器的设置和状态。定时器的基本设置过程包罗以下步骤:首先使能定时器时钟,然后设置定时器的基本参数,包罗预分频值、计数模式、重装载值等。假如必要使用中断功能,还必要设置NVIC并使能相应的中断。
定时器设置步骤:
     关键函数:
   
  以下是一个基本定时器设置的示例:
  1. void Timer_Init(void)
  2. {
  3.     TIM_HandleTypeDef htim2;
  4.    
  5.     // 使能TIM2时钟
  6.     __HAL_RCC_TIM2_CLK_ENABLE();
  7.    
  8.     // 基本配置
  9.     htim2.Instance = TIM2;
  10.     htim2.Init.Prescaler = 7199;                // 预分频值
  11.     htim2.Init.CounterMode = TIM_COUNTERMODE_UP;// 向上计数模式
  12.     htim2.Init.Period = 9999;                   // 重装载值
  13.     htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  14.     htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  15.    
  16.     // 初始化定时器
  17.     HAL_TIM_Base_Init(&htim2);
  18.    
  19.     // 启动定时器
  20.     HAL_TIM_Base_Start_IT(&htim2);
  21.    
  22.     // 配置NVIC
  23.     HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
  24.     HAL_NVIC_EnableIRQ(TIM2_IRQn);
  25. }
  26. // 定时器中断服务函数
  27. void TIM2_IRQHandler(void)
  28. {
  29.     HAL_TIM_IRQHandler(&htim2);
  30. }
  31. // 定时器中断回调函数
  32. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  33. {
  34.     if(htim->Instance == TIM2)
  35.     {
  36.         // 在这里添加定时器中断处理代码
  37.     }
  38. }
复制代码
5.5 PWM应用

对于PWM应用,HAL库提供了专门的PWM设置和控制函数。PWM设置必要设置定时器的基本参数,并设置输出通道的参数,包罗PWM模式、极性、输出状态等。以下是PWM设置的示例:
  1. void PWM_Init(void)
  2. {
  3.     TIM_HandleTypeDef htim3;
  4.     TIM_OC_InitTypeDef sConfigOC = {0};
  5.    
  6.     // 配置定时器基本参数
  7.     htim3.Instance = TIM3;
  8.     htim3.Init.Prescaler = 71;
  9.     htim3.Init.Period = 999;
  10.     htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  11.     HAL_TIM_PWM_Init(&htim3);
  12.    
  13.     // 配置PWM通道
  14.     sConfigOC.OCMode = TIM_OCMODE_PWM1;
  15.     sConfigOC.Pulse = 500;  // 设置占空比为50%
  16.     sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  17.     HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
  18.    
  19.     // 启动PWM输出
  20.     HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  21. }
复制代码
定时器的输入捕获功能用于测量外部信号的周期、脉宽等参数。设置输入捕获时,必要设置捕获通道的触发边沿、滤波器、预分频等参数。HAL库提供了完整的输入捕获函数集,包罗设置函数和捕获回调函数。
5.6 精确时序控制

对于必要精确时序控制的应用,定时器还可以设置为主从模式,实现多个定时器的同步运行。通过设置触发源和从模式,可以实现定时器之间的级联控制,这在复杂的定时控制场景中特别有效。
在使用定时器时,必要特别注意以下几点:
     6  UART通讯的HAL库函数

UART(Universal Asynchronous Receiver/Transmitter)是STM32中最常用的串行通讯接口之一,它实现了异步串行通讯,广泛应用于设备间的数据传输和调试。在HAL库中,UART的设置和使用都有同一的接口函数。
6.1 设置步骤

首先,我们来看UART的基本初始化设置。在使用UART前,必要先使能相关的时钟并设置对应的GPIO引脚。
   
  范例的初始化代码如下:
  1. // 定义UART句柄
  2. UART_HandleTypeDef huart1;
  3. void UART1_Init(void)
  4. {
  5.     // 第一步:使能时钟
  6.     __HAL_RCC_USART1_CLK_ENABLE();
  7.     __HAL_RCC_GPIOA_CLK_ENABLE();
  8.    
  9.     // 第二步:配置GPIO
  10.     GPIO_InitTypeDef GPIO_InitStruct = {0};
  11.    
  12.     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;  // TX:PA9, RX:PA10
  13.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;        // 复用推挽输出
  14.     GPIO_InitStruct.Pull = GPIO_PULLUP;            // 上拉
  15.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  // 高速
  16.     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;   // 复用为USART1
  17.    
  18.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  19.    
  20.     // 第三步:配置UART参数
  21.     huart1.Instance = USART1;
  22.     huart1.Init.BaudRate = 115200;                 // 波特率
  23.     huart1.Init.WordLength = UART_WORDLENGTH_8B;   // 8位数据位
  24.     huart1.Init.StopBits = UART_STOPBITS_1;        // 1位停止位
  25.     huart1.Init.Parity = UART_PARITY_NONE;         // 无校验
  26.     huart1.Init.Mode = UART_MODE_TX_RX;            // 收发模式
  27.     huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;   // 无硬件流控
  28.     huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  29.    
  30.     if (HAL_UART_Init(&huart1) != HAL_OK)
  31.     {
  32.         Error_Handler();
  33.     }
  34. }
复制代码
UART通讯支持多种数据传输模式,包罗轮询模式、中断模式和DMA模式。让我们分别来看这些模式的使用方法。
6.2 轮询模式

轮询模式是最简朴的传输方式,适用于数据量小、实时性要求不高的场合:
  1. // 发送数据(阻塞式)
  2. uint8_t TxData[] = "Hello World\r\n";
  3. HAL_UART_Transmit(&huart1, TxData, sizeof(TxData), HAL_MAX_DELAY);
  4. // 接收数据(阻塞式)
  5. uint8_t RxData[20];
  6. HAL_UART_Receive(&huart1, RxData, sizeof(RxData), HAL_MAX_DELAY);
复制代码
6.3 中断模式

中断模式适用于必要及时相应但数据量不大的场合。使用中断模式必要设置NVIC并实现相应的回调函数:
  1. // 配置UART中断
  2. HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  3. HAL_NVIC_EnableIRQ(USART1_IRQn);
  4. // 启动中断接收
  5. HAL_UART_Receive_IT(&huart1, RxData, 1);  // 每次接收1个字节
  6. // 中断服务函数
  7. void USART1_IRQHandler(void)
  8. {
  9.     HAL_UART_IRQHandler(&huart1);
  10. }
  11. // 接收完成回调
  12. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  13. {
  14.     if(huart->Instance == USART1)
  15.     {
  16.         // 处理接收到的数据
  17.         // 重新启动接收
  18.         HAL_UART_Receive_IT(&huart1, RxData, 1);
  19.     }
  20. }
复制代码
6.4 DMA模式

DMA模式最恰当大量数据的传输,它可以在不占用CPU的情况下完成数据传输:
  1. // DMA配置
  2. DMA_HandleTypeDef hdma_usart1_rx;
  3. DMA_HandleTypeDef hdma_usart1_tx;
  4. void UART_DMA_Init(void)
  5. {
  6.     // 使能DMA时钟
  7.     __HAL_RCC_DMA2_CLK_ENABLE();
  8.    
  9.     // 配置DMA参数(以发送DMA为例)
  10.     hdma_usart1_tx.Instance = DMA2_Stream7;
  11.     hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;
  12.     hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  13.     hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
  14.     hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
  15.     hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  16.     hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  17.     hdma_usart1_tx.Init.Mode = DMA_NORMAL;
  18.     hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
  19.     hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  20.    
  21.     HAL_DMA_Init(&hdma_usart1_tx);
  22.    
  23.     // 关联DMA与UART
  24.     __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);
  25.    
  26.     // 配置DMA中断
  27.     HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);
  28.     HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
  29. }
  30. // 使用DMA发送数据
  31. uint8_t TxBuffer[] = "DMA Test\r\n";
  32. HAL_UART_Transmit_DMA(&huart1, TxBuffer, sizeof(TxBuffer));
  33. // DMA传输完成回调
  34. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
  35. {
  36.     if(huart->Instance == USART1)
  37.     {
  38.         // 发送完成处理
  39.     }
  40. }
复制代码
6.5 串口调试

为了实现更好的串口调试功能,我们通常会重定向printf函数到串口:
  1. // 重定向printf到串口
  2. int fputc(int ch, FILE *f)
  3. {
  4.     uint8_t temp[1] = {ch};
  5.     HAL_UART_Transmit(&huart1, temp, 1, HAL_MAX_DELAY);
  6.     return ch;
  7. }
复制代码
在现实应用中,还必要考虑数据的封装和解析。这里给出一个简朴的数据帧处置惩罚示例:
  1. // 定义数据帧结构
  2. typedef struct
  3. {
  4.     uint8_t header;    // 帧头 0xAA
  5.     uint8_t length;    // 数据长度
  6.     uint8_t data[32];  // 数据
  7.     uint8_t checksum;  // 校验和
  8. } UART_Frame_TypeDef;
  9. // 数据帧处理
  10. void UART_Frame_Process(uint8_t data)
  11. {
  12.     static UART_Frame_TypeDef frame;
  13.     static uint8_t rxState = 0;
  14.     static uint8_t rxCount = 0;
  15.    
  16.     switch(rxState)
  17.     {
  18.         case 0:  // 等待帧头
  19.             if(data == 0xAA)
  20.             {
  21.                 frame.header = data;
  22.                 rxState = 1;
  23.             }
  24.             break;
  25.             
  26.         case 1:  // 接收长度
  27.             frame.length = data;
  28.             rxCount = 0;
  29.             rxState = 2;
  30.             break;
  31.             
  32.         case 2:  // 接收数据
  33.             frame.data[rxCount++] = data;
  34.             if(rxCount >= frame.length)
  35.                 rxState = 3;
  36.             break;
  37.             
  38.         case 3:  // 接收校验和
  39.             frame.checksum = data;
  40.             // 验证校验和
  41.             if(Check_Sum(&frame) == HAL_OK)
  42.             {
  43.                 // 数据帧处理
  44.             }
  45.             rxState = 0;
  46.             break;
  47.     }
  48. }
复制代码
6.6 注意事项

在使用UART时,还必要注意以下几点:
   
  1. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
  2. {
  3.     if(huart->Instance == USART1)
  4.     {
  5.         if(__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE))
  6.         {
  7.             __HAL_UART_CLEAR_OREFLAG(huart);
  8.         }
  9.         // 重新启动接收
  10.         HAL_UART_Receive_IT(huart, RxData, 1);
  11.     }
  12. }
复制代码
7  ADC转换器的HAL库函数

ADC(模数转换器)是STM32中重要的模拟外设,它能将模拟信号转换为数字信号。STM32的ADC具有多通道、高精度、可设置采样时间等特点。HAL库提供了完整的ADC操纵接口,使得ADC的设置和使用变得简朴直观。
ADC设置步骤:
     7.1 ADC基本设置

使用ADC前必要完成时钟使能和GPIO设置:
  1. // 定义ADC句柄
  2. ADC_HandleTypeDef hadc1;
  3. void ADC1_Init(void)
  4. {
  5.     // 使能时钟
  6.     __HAL_RCC_ADC1_CLK_ENABLE();
  7.     __HAL_RCC_GPIOA_CLK_ENABLE();
  8.    
  9.     // 配置ADC引脚
  10.     GPIO_InitTypeDef GPIO_InitStruct = {0};
  11.     GPIO_InitStruct.Pin = GPIO_PIN_0;          // PA0作为ADC通道0
  12.     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;   // 模拟输入模式
  13.     GPIO_InitStruct.Pull = GPIO_NOPULL;        // 无上下拉
  14.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  15.    
  16.     // 配置ADC参数
  17.     hadc1.Instance = ADC1;
  18.     hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;     // ADC时钟4分频
  19.     hadc1.Init.Resolution = ADC_RESOLUTION_12B;                // 12位分辨率
  20.     hadc1.Init.ScanConvMode = DISABLE;                        // 禁用扫描模式
  21.     hadc1.Init.ContinuousConvMode = ENABLE;                   // 连续转换模式
  22.     hadc1.Init.DiscontinuousConvMode = DISABLE;               // 禁用不连续模式
  23.     hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;  // 禁用外部触发
  24.     hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;               // 数据右对齐
  25.     hadc1.Init.NbrOfConversion = 1;                           // 转换通道数量
  26.     hadc1.Init.DMAContinuousRequests = DISABLE;               // 禁用DMA连续请求
  27.     hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;            // 单次转换结束选择
  28.    
  29.     if (HAL_ADC_Init(&hadc1) != HAL_OK)
  30.     {
  31.         Error_Handler();
  32.     }
  33. }
复制代码
7.2 设置ADC通道

STM32的ADC支持多个通道,每个通道都可以单独设置采样时间:
  1. void ADC_Channel_Config(void)
  2. {
  3.     ADC_ChannelConfTypeDef sConfig = {0};
  4.    
  5.     // 配置通道0
  6.     sConfig.Channel = ADC_CHANNEL_0;           // 选择通道0
  7.     sConfig.Rank = 1;                          // 转换序列顺序
  8.     sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;  // 采样时间
  9.    
  10.     if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  11.     {
  12.         Error_Handler();
  13.     }
  14. }
复制代码
7.3 ADC的收罗方式

包罗单次收罗、连续收罗、DMA收罗等。下面分别介绍这些模式:

  1. // 启动单次转换
  2. HAL_ADC_Start(&hadc1);
  3. // 等待转换完成
  4. HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
  5. // 获取转换结果
  6. uint32_t adcValue = HAL_ADC_GetValue(&hadc1);
  7. // 停止ADC转换
  8. HAL_ADC_Stop(&hadc1);
复制代码

  1. // 启动连续转换
  2. HAL_ADC_Start(&hadc1);
  3. // 在主循环中读取数据
  4. while(1)
  5. {
  6.     if(HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK)
  7.     {
  8.         uint32_t value = HAL_ADC_GetValue(&hadc1);
  9.         // 处理ADC数据
  10.     }
  11. }
复制代码

  1. // 配置ADC中断
  2. HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);
  3. HAL_NVIC_EnableIRQ(ADC_IRQn);
  4. // 启动中断模式转换
  5. HAL_ADC_Start_IT(&hadc1);
  6. // ADC转换完成回调函数
  7. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
  8. {
  9.     if(hadc->Instance == ADC1)
  10.     {
  11.         uint32_t value = HAL_ADC_GetValue(hadc);
  12.         // 处理ADC数据
  13.     }
  14. }
复制代码

  1. // DMA配置
  2. DMA_HandleTypeDef hdma_adc1;
  3. uint16_t ADC_DMA_Buffer[8];  // DMA缓冲区
  4. void ADC_DMA_Init(void)
  5. {
  6.     __HAL_RCC_DMA2_CLK_ENABLE();
  7.    
  8.     hdma_adc1.Instance = DMA2_Stream0;
  9.     hdma_adc1.Init.Channel = DMA_CHANNEL_0;
  10.     hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  11.     hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  12.     hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  13.     hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  14.     hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  15.     hdma_adc1.Init.Mode = DMA_CIRCULAR;
  16.     hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
  17.    
  18.     HAL_DMA_Init(&hdma_adc1);
  19.    
  20.     __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
  21.    
  22.     // 启动ADC DMA传输
  23.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_DMA_Buffer, 8);
  24. }
复制代码
7.4 现实应用

我们经常必要对ADC数据进行处置惩罚,比方滤波、校准等:
  1. // 移动平均滤波
  2. #define FILTER_LENGTH 16
  3. uint16_t filter_buffer[FILTER_LENGTH];
  4. uint8_t filter_index = 0;
  5. uint16_t ADC_Filter(uint16_t new_value)
  6. {
  7.     uint32_t sum = 0;
  8.    
  9.     filter_buffer[filter_index] = new_value;
  10.     filter_index = (filter_index + 1) % FILTER_LENGTH;
  11.    
  12.     for(uint8_t i = 0; i < FILTER_LENGTH; i++)
  13.     {
  14.         sum += filter_buffer[i];
  15.     }
  16.    
  17.     return sum / FILTER_LENGTH;
  18. }
  19. // ADC值转换为实际电压
  20. float ADC_To_Voltage(uint16_t adc_value)
  21. {
  22.     return (float)adc_value * 3.3f / 4096.0f;  // 12位ADC, 参考电压3.3V
  23. }
复制代码
7.5 多通道扫描

ADC还支持多通道扫描模式,恰当必要收罗多个通道的应用:
  1. // 多通道配置
  2. void ADC_MultiChannel_Config(void)
  3. {
  4.     ADC_ChannelConfTypeDef sConfig = {0};
  5.    
  6.     // 配置通道0
  7.     sConfig.Channel = ADC_CHANNEL_0;
  8.     sConfig.Rank = 1;
  9.     sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  10.     HAL_ADC_ConfigChannel(&hadc1, &sConfig);
  11.    
  12.     // 配置通道1
  13.     sConfig.Channel = ADC_CHANNEL_1;
  14.     sConfig.Rank = 2;
  15.     HAL_ADC_ConfigChannel(&hadc1, &sConfig);
  16.    
  17.     // 启动DMA传输
  18.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_DMA_Buffer, 2);
  19. }
复制代码
在使用ADC时,必要注意以下几点:
     8 DMA的HAL库函数

STM32的HAL库提供了一系列用于设置和控制DMA传输的函数。DMA初始化的核心函数是HAL_DMA_Init(),该函数必要传入一个DMA_HandleTypeDef结构体指针,该结构体包含了DMA的设置信息。在使用DMA之前,我们首先必要设置DMA的基本参数,包罗传输方向、源地点和目标地点的数据宽度、地点是否自增、传输优先级等。
DMA设置步骤:
     8.1 DMA初始化

以下是DMA初始化的核心代码示例:
  1. void DMA_Init(void) {
  2.     hdma_usart1_rx.Instance = DMA1_Stream5;
  3.     hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
  4.     hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  5.     hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  6.     hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
  7.     hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  8.     hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  9.     hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
  10.     hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
  11.     hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  12.    
  13.     HAL_DMA_Init(&hdma_usart1_rx);
  14. }
复制代码
8.2 启动普通传输

除了初始化函数,HAL库还提供了启动传输、制止传输、查询状态等功能函数。HAL_DMA_Start()用于启动普通传输,HAL_DMA_Start_IT()用于启动带中断的传输。这些函数的原型分别如下:
  1. HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,
  2.                                uint32_t DstAddress, uint32_t DataLength);
  3.                               
  4. HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,
  5.                                   uint32_t DstAddress, uint32_t DataLength);
复制代码
8.3 DMA使用实例

ADC连续采样
在这个例子中,我们使用DMA将ADC采样数据直接传输到内存数组中,无需CPU干预:
  1. #define ADC_BUFFER_SIZE 1000
  2. uint16_t adc_buffer[ADC_BUFFER_SIZE];
  3. void ADC_DMA_Config(void) {
  4.     // ADC配置部分
  5.     hadc1.Instance = ADC1;
  6.     hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  7.     hadc1.Init.ContinuousConvMode = ENABLE;
  8.     hadc1.Init.ScanConvMode = DISABLE;
  9.     HAL_ADC_Init(&hadc1);
  10.    
  11.     // DMA配置部分
  12.     hdma_adc1.Instance = DMA2_Stream0;
  13.     hdma_adc1.Init.Channel = DMA_CHANNEL_0;
  14.     hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  15.     hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  16.     hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  17.     hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  18.     hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  19.     hdma_adc1.Init.Mode = DMA_CIRCULAR;
  20.     hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
  21.    
  22.     HAL_DMA_Init(&hdma_adc1);
  23.    
  24.     // 关联ADC和DMA
  25.     __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
  26.    
  27.     // 启动ADC和DMA传输
  28.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);
  29. }
复制代码
在现实开发中,建议参考ST官方提供的示例代码和文档,深入理解每个模块的具体使用方法。同时,建议在使用HAL库时养成精良的错误处置惩罚习惯,确保程序的稳定性和可靠性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4