背景
STM32开发平台,时至今日发展的已经相当成熟了,尤其对于外围硬件接口的抽象封装库,即HAL。好多基于STM32开发的工程师,习惯于直接操作外围接口相关的寄存器来完成所谓的驱动开发,其实,ST公司早就为大家准备好了对于这些外围接口的驱动框架,我们只需要直接拿来就可以使用。
可是以前,基于STM32平台开发时,存在一个问题就是,变换MCU型号时,需要在新老MCU之间移植驱动程序,移植过程中可能出现很多莫名其妙的问题,导致浪费了很多宝贵的开发时间,如何避免这种问题呢?其实,ST早就为大家考虑到了这一点,其开发了STM32CubeMX工具,基于该工具,我们可以轻松的完成各种外围接口的配置工作,而且最为神奇的就是,配置完成之后,该工具会自动生成工程代码,我们基于此,就可以直接进行开发了,免去了很多不必要的硬件寄存器配置工作。当然,对于特殊需求,我们还是需要去手动配置一些硬件寄存器,但是,这项工作大部分时间是不需要的。
既然,ST公司为我们提供了这么好用的工具,我们为什么不用起来呢?下面就基于UART的配置过程,来逐步的讲解一下,如何基于STM32CubeMX、Kiel和STM32 HAL来配置生成一个可用的开发环境。
开发步骤
配置环境
工欲善其事,必先利其器。首先第一步就是安装STM32CubeMX环境,关于STM32CubeMX环境的安装,网上有好多写的特别棒的文中,这里引用一篇ybhuangfugui的博客,在此感谢ybhuangfugui的无私贡献。大家可以阅读该文章完成STM32CubeMX环境的安装。
在这里说一句,STM32CubeMX提供所有STM32家族的MCU芯片的初始配置抽象,并提供了HAL,封装、抽象了关于底层硬件的配置、使用过程,这是一把双刃剑,有利有弊:利是它增强了STM32不同MCU之间的兼容性,大大加快了初始开发进度,其提供的HAL为开发者提供了统一的开发接口,这样节省了宝贵开发时间。同时,基于其提供的配套的中间件(RTOS、TCP/IP、FS等),开发者可以快速的完成更高级需求的环境定制,这对于开发者来说是不小的福音;弊是,STM32 MCU毕竟是比较低级的硬件开发平台,其开发过程中涉及到大量的底层硬件操作,如果我们想彻底理解这些基础的硬件操作原理的话,可能还是需要从最为原始的硬件寄存器配置开始。
不过,个人感觉STM32CubeMX利大于弊,就像通用操作系统,其屏蔽了所有的硬件相关的操作,为开发者抽象出了进程、内存管理、文件系统、网络等等,易于开发、理解的开发接口,也是正是操作系统的逐步成熟,才最终促使了后来的计算机一次又一次的革命。我想,对于MCU来说,其过程是类似的。如今,已经进入了物联网时代,各种各样的的硬件开发平台层出不穷,各种层次的开发者都涌入到了这一开发领域。试想对于一个原来从事于Web开发的人员来说,其不可能从头去理解底层的硬件知识,其需要的就是一个快速、高效的配置工具,完成原始开发环境的搭建。这时,STM32CubeMX就可以提供很大的帮助。
相信,STM32CubeMX未来肯定会发展的更为智能,任何一个有志于基于该平台开发的开发者,都会被其强度的功能所折服的,零基础的开发人员也能够快速上手该平台。我想,这可能也是STM32CubeMX未来的目标吧。
最后说明一下,本文所使用的STM32CubeMX的版本是:5.5.0。温馨提示,不同版本的操作步骤也能不太一样,所以建议使用相同的版本进行配置。
开始配置
首先,我们需要确定我们所使用的STM32的MCU具体型号,然后,打开STM32CubeMX,创建一个新的工程。
我们选择第一个“ACCESSTO MCU SELECTOR”,开始创建一个工程。我们选择的MCU是STM32L010K8,安装如下图的步骤创建工程。
- 选择MCU型号
- 查看MCU型号信息
- 创建项目
然后,选择Project Manager项目,配置本项目的一些信息。
接下来,我们进行Pinout & Configuration配置,这里面会涉及到MCU提供的所有外围硬件的相关配置。本文主要关注的是UART的配置过程,STM32L010K8提供一个UART2和LPUART,我们选择的配置项为UART2。
UART2的基本配置步骤如下,
- 选择UART2配置项。
- 选择UART2的工作模式,这里选择的是Asynchronous。
- 配置UART2的参数,参数分为Basic Parameters、Advanced Paramters、Advanced Features几类。
- 基本参数配置就是波特率、字长、校验方式、停止位;
- 高级参数配置就是数据传输方向、采集相关的配置;
- 高级特性配置:就是UART2提供的一些比较高级的功能,比如,自适应波特率、Overrun等。
我们选择UART2的中断方式处理数据的传输模式,所以,需要进一步配置NVIC。配置步骤如下:
- 点击System Core中的NVIC配置。
- 使能USART2中断,并配置中断抢占优先级为0。
还有就是需要确定UART2使用GPIO引脚,配置方式如下:
这里使用的是PA2和PA3。
最后,配置SysClock,我们最终选择的系统主时钟频率为16MHz。
完成上述配置之后,点击GENERATE CODE完成项目代码的生成。
HAL
项目代码生成之后,我们使用Kiel打开,如下图:
下面是main中的芯片初始化代码:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
我们看到,STM32CubeMX自动生成了关于HAL、系统时钟、UART2的初始化代码。下面看一下UART2的初始化代码:
static void MX_USART2_UART_Init(void) { /* USER CODE BEGIN USART2_Init 0 */ /* USER CODE END USART2_Init 0 */ /* USER CODE BEGIN USART2_Init 1 */ /* USER CODE END USART2_Init 1 */ huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART2_Init 2 */ /* USER CODE END USART2_Init 2 */ }
stm32l0xx_it.c中定义了UART2的中断处理函数,代码如下:
void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ }
该中断处理函数用来处理所有与UART2相关的中断事件。那如何才能开启UART2的数据发送和接收呢?这就需要HAL中关于UART2数据发送接收的接口函数:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
当我们需要已中断的方式接收数据时,就调用HAL_UART_Receive_IT函数,其中第一个参数是UART的描述符,pData保存数据的缓存区,Size是缓存区的大小。注意,调用完该接口之后,UART2开始已中断的方式接收数据,数据的大小为Size,当接收了Size个字节的数据之后,UART2就不会再接收数据。若想再次接收数据,需要再一次调用HAL_UART_Receive_IT函数。一个比较好的实现就是,在重新覆盖实现下面的函数,
`__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)`
该函数会在数据接收完之后被调用,然后我们在函数中,可以将数据拷贝走,然后再次调用HAL_UART_Receive_IT。
对于数据的发送,处理过程与接收类似,所不同的就是发送的接口函数为:
HAL_UART_Transmit_IT,该发送完成之后的回调函数是:
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
好了,至此我们基本完成了基于STM32CubeMX的UART的配置过程,可以看到基于此种方式,我们基本上不会触碰到硬件寄存器的配置过程,所要做的就是基于HAL框架提供的接口完成APP的开发。这也许就是STM32CubeMX的魅力所在吧,简单,高效,可移植性强。 UART_TxCpltCallback(UART_HandleTypeDef *huart)
好了,至此我们基本完成了基于STM32CubeMX的UART的配置过程,可以看到基于此种方式,我们基本上不会触碰到硬件寄存器的配置过程,所要做的就是基于HAL框架提供的接口完成APP的开发。这也许就是STM32CubeMX的魅力所在吧,简单,高效,可移植性强。