1.什么是STM32CubeMx和HAL库
HAL库对比标准库,封装程度更高,更具有移植性。STM32CUbeMx是一种图形化配置界面,用来完成对外设的初始化,比如RCC模块、NVIC、GPIO、串口、定时器。使用标准库都是先对某个外设的结构体赋值,最后调用Init函数将结构体写入寄存器,这个过程有点繁琐,因为某个外设的初始化都是差不多固定的,比如定时器的初始化都是先分配,设置ARR。使用CubeMx工具可以直接图形化设置,省去了自己去赋值外设结构体的步骤。
2.使用之前需要知道的几个关键词
句柄:
再标准库中如果要初始化一个结构体肯定要先定义一个结构体比如
GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定时器时基结构体
NVIC_InitTypeDef NVIC_InitStructure; //NVIC初始化结构体
这些都可以算作一个句柄,只不过在HAL库中,一个外设的结构体不再是单单做一些初始化配置,比如:
1 typedef struct 2 { 3 USART_TypeDef *Instance; /*!< UART registers base address */ 4 UART_InitTypeDef Init; /*!< UART communication parameters */ 5 uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ 6 uint16_t TxXferSize; /*!< UART Tx Transfer size */ 7 uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ 8 uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ 9 uint16_t RxXferSize; /*!< UART Rx Transfer size */ 10 uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ 11 DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */ 12 DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */ 13 HAL_LockTypeDef Lock; /*!< Locking object */ 14 __IO HAL_UART_StateTypeDef State; /*!< UART communication state */ 15 __IO uint32_t ErrorCode; /*!< UART Error code */ 16 }UART_HandleTypeDef;
这样的话,UART_HandleTypeDef这个结构体 定义一个变量,用来完成对串口的所有操作,包括之前的初始化结构体的基本配置,另外还添加了串口其他的操作:DMA,中断等。有点像C#里面的对象。
对象的所有变量和方法(函数),都可以通过一个对象名+.的方法完成。第4行UART_InitTypeDef 这个结构体类型包含一下内容,这些和标准库中串口初始化结构体的变量一样,
typedef struct { uint32_t BaudRate; uint32_t WordLength; uint32_t StopBits; uint32_t Parity; uint32_t Mode; uint32_t HwFlowCtl; uint32_t OverSampling; } UART_InitTypeDef;
回调函数:我们真正需要处理的函数:串口接收中断回调、发送中断回调、定时器更新中断回调、输入捕获回调等待。
回调函数本质上是定义在结构体内部的函数指针,这些回调函数在库中以weak关键字若声明,大部分内部为空,需要我们实现。
1 void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Half Complete Callback */ 2 void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Complete Callback */ 3 void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Half Complete Callback */ 4 void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */ 5 void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Error Callback */ 6 void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Complete Callback */ 7 void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */ 8 void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Receive Complete Callback */ 9 void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Wakeup Callback */ 10 11 void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback */ 12 void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInit callback */
__weak 关键词:弱声明,如果我们定义了同名函数,则编译器使用我们编写的函数。有点像类中的构造函数,你若没写,则使用构造函数,若重写了,则覆盖系统提供的构造函数。
3.对比标准库,HAL库的架构和使用流程
HAL库中,首先在CubeMx中设置外设的基本配置,比如串口的波特率、字长,定时器的PSC、ARR。然后在生成的工程中处理回调函数。
4.MSP函数
MSP函数是和CPU具体相关的配置。比如一个外设本身的配置在 外设名+Init函数中配置,比如串口的:
1 void MX_USART1_UART_Init(void) 2 { 3 4 huart1.Instance = USART1; 5 huart1.Init.BaudRate = 115200; 6 huart1.Init.WordLength = UART_WORDLENGTH_8B; 7 huart1.Init.StopBits = UART_STOPBITS_1; 8 huart1.Init.Parity = UART_PARITY_NONE; 9 huart1.Init.Mode = UART_MODE_TX_RX; 10 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 11 huart1.Init.OverSampling = UART_OVERSAMPLING_16; 12 if (HAL_UART_Init(&huart1) != HAL_OK) 13 { 14 Error_Handler(); 15 } 16 17 }
而与外设无关但是和CPU相关的配置,比如外设用到的GPIO引脚,这个引脚的配置是在MSP函数中配置:包括开时钟,配置GPIO引脚的模式、速度。
1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) 2 { 3 4 GPIO_InitTypeDef GPIO_InitStruct = {0}; 5 if(uartHandle->Instance==USART1) 6 { 7 /* USER CODE BEGIN USART1_MspInit 0 */ 8 9 /* USER CODE END USART1_MspInit 0 */ 10 /* USART1 clock enable */ 11 __HAL_RCC_USART1_CLK_ENABLE(); 12 13 __HAL_RCC_GPIOA_CLK_ENABLE(); 14 /**USART1 GPIO Configuration 15 PA9 ------> USART1_TX 16 PA10 ------> USART1_RX 17 */ 18 GPIO_InitStruct.Pin = GPIO_PIN_9; 19 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 20 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 21 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 22 23 GPIO_InitStruct.Pin = GPIO_PIN_10; 24 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 25 GPIO_InitStruct.Pull = GPIO_NOPULL; 26 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 27 28 /* USART1 interrupt Init */ 29 HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); 30 HAL_NVIC_EnableIRQ(USART1_IRQn); 31 /* USER CODE BEGIN USART1_MspInit 1 */ 32 33 /* USER CODE END USART1_MspInit 1 */ 34 } 35 }
除了GPIO、RCC、NVIC这些CPU本身的东西,别的只要涉及到外设的配置,都会有两个函数完成初始化:TIMER、ADC等
MX_外设名_Init //外设的具体配置 HAL_外设名_MspInit //外设和CPU连接的引脚配置
这样做的好处是方便移植,确实很方便。需要注意的是,
HAL_外设名_MspInit 函数被自动被 MX_外设名_Init调用。
4.注意事项:
4.1 打开sys下的debug,不然无法第二次下载程序
4.2自己的代码要写在每个begin和end之间,不然会被CubeMx重新生成的工程覆盖(丢失)
4.3修改外设配置最好从CubeMx中修改。由外设生成的代码,不要再MDK或IAR中直接修改,除非以后不想再使用这个工程的CubeMx配置工程