呼吸灯实验

首先定义延迟函数,自带的HAL_Delay是毫秒级别的,我们需要微秒级别的。

void Delay(int i){
    while(--i);
}

然后进行循环:

while(1){
    for(int i=0; i<5000; i++){
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
        Delay(i);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
        Delay(5000-i);
    }
    for(int i=5000; i<0; i--){
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
        Delay(i);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
        Delay(5000-i);
    }
}

总结:

  1. 让它慢慢亮(亮的时间慢慢变长,暗/灭的时间慢慢变短)
  2. 让它慢慢变暗(暗/灭的时间慢慢变长,亮的时间慢慢变短)

按钮控制灯的亮灭

使用 KEY UP(WKUP)按钮控制灯的亮灭,WK UP 的电路图如下:

image-20250303202831781

我们需要下拉电阻,将 KEY UP 下拉来保证其属于低电平位置,且需要输入CPU,所以选择Input。WK UP 在PA5引脚处。

HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5) 函数是将PA5引脚进行反转,从而实现灯的亮灭。

if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET){
    HAL_Delay(20);  // 防抖动
    if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET){
        HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5); // 实现灯的亮/灭转换
        while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET); // 一直按着直到抬起则结束循环
        HAL_Delay(20);  // 防抖动
    }
}

串口输入控制灯的亮灭

通过使用串口来控制灯的亮灭,在串口调试工具中输入 on 来控制灯亮,输入off来控制灯灭,使用HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) 函数来接收串口中输入的数据,使用 HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout) 来输出串口中输入的数据。

// 定义变量
uint8_t rxbuff[4];   // 存储on/off
uint8_t renum = 0;  // 控制串口输入和输出按序执行

if (renum == 0) {
    HAL_UART_Receive(&huart1, rxbuff, sizeof(rxbuff), 1000);
    renum = 1;
}

if (renum) {

    if (strcmp((char *)rxbuff, "on") == 0) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); 
        HAL_UART_Transmit(&huart1, (uint8_t *)"LED0 ON", sizeof("LED0 ON"), 100); 
		memset(rxbuff,0,sizeof(rxbuff));  // 清除rxbuff存储空间中的数据
    } else if (strcmp((char *)rxbuff, "off") == 0) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);   
        HAL_UART_Transmit(&huart1, (uint8_t *)"LED0 OFF", sizeof("LED0 OFF"), 100);
		memset(rxbuff,0,sizeof(rxbuff));
    }
		renum=0;
}
  1. memset(rxbuff, 0, sizeof(rxbuff)); 指的是清除rxbuff存储空间中的数据
  2. HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    1. *huart: 指向 UART 外设的句柄,包含 UART 的配置信息(如波特率、数据位、校验位等)
    2. *pData: 指向接收数据缓冲区的首地址,用于存储接收到的字节数据
    3. Size: 指定期望接收的字节数。函数会持续尝试接收直至达到该数量或超时
    4. Timeout: 设置超时时间。若在指定时间内未完成接收,函数返回 HAL_TIMEOUT
    5. 返回值:
      1. HAL_OK:成功接收指定数量的数据。
      2. HAL_ERROR:参数错误(如缓冲区未分配或 UART 未初始化)。
      3. HAL_BUSY:UART 正在执行其他操作(如发送数据)。
      4. HAL_TIMEOUT:超时未完成接收

串口控制灯的亮灭-中断控制

// 定义
uint8_t rxbuff[256]={0};
uint8_t datbuff[256]={0};
uint8_t datcount=0;

// 改写 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance == USART1) // 判断当前UART句柄是否指向USART1外设的寄存器基地址
	{
		datbuff[datcount++] = rxbuff[0];
		if(datcount >= 255)
		{
		  datcount=0;
		}
//		HAL_UART_Transmit(&huart1, rxbuff, strlen((char *)rxbuff), 100);
		HAL_UART_Receive_IT(&huart1, rxbuff, 1); // 重新初始化代码保持持续监听
	}
  
}

int main(void)
{
    HAL_UART_Receive_IT(&huart1, rxbuff, 1); // 以中断的形式,接收定长的数据(1字节)
}
while(1)
{
    if(strcmp((char *)datbuff, "ON")==0)
		{
		    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
			HAL_UART_Transmit(&huart1, (uint8_t *)"ON", sizeof("ON"), 100);
			memset(datbuff, 0, sizeof(datbuff));
			datcount=0;
		}
    else if (strcmp((char *)datbuff, "OFF")==0)
		{
		    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
			HAL_UART_Transmit(&huart1, (uint8_t *)"OFF", sizeof("OFF"), 100);
			memset(datbuff, 0, sizeof(datbuff));
			datcount=0;
		}
}

分析:

  1. 中断接收机制

    通过 HAL_UART_Receive_IT() (UART1)启动单字节中断接收,每次接收到数据后触发HAL_UART_RxCpltCallback回调函数,将数据存入datbuff缓冲区,并重新使能接收中断。

  2. 指令解析逻辑

    主循环中通过strcmp比对datbuff内容,若匹配”ON”/“OFF”,则控制GPIO引脚电平并通过串口返回响应,最后清空缓冲区和计数清零。

  3. if(huart->Instance == USART1)

    在 STM32 中,可能存在多个 UART/USART 外设(如 USART1、USART2、USART3)。通过 huart->Instance == USART1 可以明确当前代码操作的是哪个外设实例,避免混淆。

  4. HAL_UART_Receive_IT(&huart1, rxbuff, 1);

    1. 将接收缓冲区的起始地址和长度写入串口句柄(huart1)中;
    2. 使能串口接收中断(RXNEIE),允许硬件在接收到数据时触发中断;
    3. 单次接收特性:此函数仅触发一次接收中断,完成1字节接收后会自动关闭中断。若需持续接收,必须在回调函数中**重新调用HAL_UART_Receive_IT()**以重新使能中断;
    4. 这段代码通过中断机制实现了非阻塞式单字节接收,适用于需要实时响应但数据量较小的场景。使用时需注意在回调函数中重新初始化接收,以保持持续监听

扩展:printf

  1. printf 将数据输出到 stdout 文件
    单片机没有文件系统,需要改变 printf 的输出方向,往串口输出

  2. 可以重定义 fputc 函数,改变输出方向

    int fputc(int c, FILE * stream)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&c, 1, 1000);
        return c;
    }