基于HAL库实现GPIO输出状态控制输入状态查询及定时器PWM波和串口收发数据
1.基于HAL库实现GPIO输出状态控制
相对于标准库的GPIO输出状态控制,HAL库在结合了CubeMX软件后实现GPIO输出状态控制更加简单,一些基本的配置由CubeMX帮助我们完成,且对GPIO输出状态的控制也有相应库函数的实现。我们用三个外部器件LED,BEEP和继电器来展示控制GPIO引脚的电平变化。以控制LED灯为例,我们看CubeMX配置和程序实现。


void LED_Control(uint8_t device,uint8_t cmd);
void LED_Test(void);**
*@brief 控制LED1 LED2 亮灭的函数
*
*@note 详细描述细节,ON 用来控制led亮, OFF用来控制LED熄灭
*
*@param device: 可以是LED1, LED2 , 是两个宏,定义在gpio.h中
*
*@param cmd: 用来控制LED的亮灭命令
* ON : 点亮LED
* OFF :熄灭LED
*
*@retval None
*/
void LED_Control(uint8_t device,uint8_t cmd)
{if(device == LED1){if(cmd == ON){HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);}else if(cmd == OFF){HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);}}else if(device == LED2){if(cmd == ON){HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);}else if(cmd == OFF){HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);}}
}/**
* @brief LED1 LED2 测试函数
*
* @note 验证LED1 LED2的功能
*
* @retval None
*/
void LED_Test(void)
{LED_Control(LED1,ON);LED_Control(LED2,OFF);HAL_Delay(500);LED_Control(LED1,OFF);LED_Control(LED2,ON);HAL_Delay(500);
}采用函数封装,对外接口来实现功能模块化。对于该功能的实现,我们可以看到主要依赖于HAL库中的函数HAL_GPIO_WritePin来实现,该函数的功能就是对相应GPIO口的高低电平状态进行设置。
2.基于HAL库实现GPIO输入状态的读取
对于GPIO输入状态的读取,同样HAL都有相应的函数来实现,这里我们用其对按键是否按下进行轮询读取,下面时CubeMX配置和程序实现:

#define LED1 1
#define LED2 2
#define ON 1
#define OFF 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_LEFT 3
#define KEY_RIGHT 4
#define KEY_OK 5
#define KEY_ESC 6uint8_t KEY_Scan(void);
void KEY_Test(void);/**
*@brief 获取键值的函数
*
*@param None
*
*@retval 按键的键值,没有按键按下时返回0
*/
uint8_t KEY_Scan(void)
{if(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port,KEY_UP_Pin) == GPIO_PIN_RESET){return KEY_UP;}else if(HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port,KEY_DOWN_Pin) == GPIO_PIN_RESET){return KEY_DOWN;}else if(HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port,KEY_LEFT_Pin) == GPIO_PIN_RESET){return KEY_LEFT;}else if(HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port,KEY_RIGHT_Pin) == GPIO_PIN_RESET){return KEY_RIGHT;}else if(HAL_GPIO_ReadPin(KEY_OK_GPIO_Port,KEY_OK_Pin) == GPIO_PIN_RESET){return KEY_OK;}else if(HAL_GPIO_ReadPin(KEY_ESC_GPIO_Port,KEY_ESC_Pin) == GPIO_PIN_RESET){return KEY_ESC;}return 0;
}/**
* @brief 按键测试函数
*
* @note 验证按键的功能
*
* @retval None
*/
void KEY_Test(void)
{if(KEY_Scan() == KEY_UP){LED_Control(LED1,ON);}else if(KEY_Scan() == KEY_DOWN){LED_Control(LED1,OFF);}else if(KEY_Scan() == KEY_LEFT){LED_Control(LED2,ON);}else if(KEY_Scan() == KEY_RIGHT){LED_Control(LED2,OFF);}else if(KEY_Scan() == KEY_OK){BEEP_Control(ON);}else if(KEY_Scan() == KEY_ESC){BEEP_Control(OFF);}
}在CubeMX中时钟树的配置与GPIO输出电平状态控制一样,将GPIO配置为上拉输入即可,程序依旧采用模块化编写,在.c文件时实现底层逻辑,.h文件中对外公开接口。由以上程序可见读取GPIO口状态主要由HAL_GPIO_ReadPin函数实现,该函数的功能就是读出相应IO口的电平状态。
3.基于HAL库实现PWM
下面来看实现该功能的CubeMX配置和程序,

在main函数中需要对PWM波的最初占空比进行初始化和开始PWM波,程序如下:
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,100); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,100);HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4);在外部中断回调函数中,实现按键按下对PWM波占空比的调节。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if(GPIO_Pin == KEY_UP_Pin){//LED_Control(LED1,ON);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,0); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,0);}else if(GPIO_Pin == KEY_DOWN_Pin){//LED_Control(LED1,OFF);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,20); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,20);}else if(GPIO_Pin == KEY_LEFT_Pin){//LED_Control(LED2,ON);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,40); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,40);}else if(GPIO_Pin == KEY_RIGHT_Pin){//LED_Control(LED2,OFF);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,60); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,60);}else if(GPIO_Pin == KEY_OK_Pin){//BEEP_Control(ON);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,80); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,80);}else if(GPIO_Pin == KEY_ESC_Pin){//BEEP_Control(OFF);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,100); __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_4,100);}
}可见在实现该功能时主要用到了函数__HAL_TIM_SET_COMPARE该函数的功能就是设置相应定时器相应通道的PWM波占空比,HAL_TIM_PWM_Start该函数的功能就是开启PWM波的产生。
4.基于HAL库实现串口数据的收发
对于串口数据的收发有很多种方式,如轮询式,中断式,DMA式,接收数据有定长和不定长,这里我们就实现采用DMA中断接收不定长数据然后阻塞式发送数据。


//DMA接收一半中断
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == USART1){uint8_t Length = DMA_BUF_SIZE/2 - RX1_Offset;HAL_UART_Transmit(&huart1,RX1_Buf+RX1_Offset,Length,HAL_MAX_DELAY);printf("HLength=%d\n",Length);RX1_Offset+=Length;}
}//DMA接收满中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == USART1){uint8_t Length = DMA_BUF_SIZE - RX1_Offset;HAL_UART_Transmit(&huart1,RX1_Buf+RX1_Offset,Length,HAL_MAX_DELAY);printf("CLength=%d\n",Length);RX1_Offset = 0;}
}void USER_UART_IRQHandler(UART_HandleTypeDef *huart)
{if(huart->Instance == USART1){if(__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE)!=RESET) //判断是否发生空闲中断{__HAL_UART_CLEAR_IDLEFLAG(huart); //清空空闲中断标志位 uint8_t Length = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx) - RX1_Offset; //接收到的数据的长度HAL_UART_Transmit(&huart1,RX1_Buf+RX1_Offset,Length,HAL_MAX_DELAY);RX1_Offset += Length;printf("ILength=%d\n",Length);}}
}可见其主要的函数有HAL_UART_RxHalfCpltCallback该函数是一个回调函数,在串口使用DMA模式接收数据下,接收数据完成一半时会执行该函数。HAL_UART_RxCpltCallback该函数也是一个回调函数,在串口使用DMA模式接收数据下,接收数据完成全部时会执行该函数。HAL_UART_Transmit该函数的为串口发送数据函数,采用的是阻塞机制发送数据只有将要发送的数据全部发送完成程序才会继续执行,__HAL_UART_GET_FLAG该函数的功能是读取相应标志位,在上面的程序中为读取接收空闲中断标志位,若读取到该标志位返回SET,没有读取到返回RESET,__HAL_DMA_GET_COUNTER是一个宏函数,在程序中的功能是读取接收缓冲区中还剩下多少字节空间。
