潘多拉 IOT 开发板学习(HAL 库)—— 实验4 串口通讯实验(学习笔记)

本文代码参考正点原子例程

实验功能

正点原子例程中,全局数组 USART_RX_BUF[USART_REC_LEN] 用来存放串口接收到的数据,其中有两个位作为标志位(接收完成标志)。

u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA = 0;     //接收状态标记

例程源码:(main.c)

该实验实现了串口的接收和发送,接收是否完成在串口中断服务函数中判断(见后文 USART1_IRQHandler() 函数)。

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"

/*********************************************************************************
			  ___   _     _____  _____  _   _  _____  _____  _   __
			 / _ \ | |   |_   _||  ___|| \ | ||_   _||  ___|| | / /
			/ /_\ \| |     | |  | |__  |  \| |  | |  | |__  | |/ /
			|  _  || |     | |  |  __| | . ` |  | |  |  __| |    \
			| | | || |_____| |_ | |___ | |\  |  | |  | |___ | |\  \
			\_| |_/\_____/\___/ \____/ \_| \_/  \_/  \____/ \_| \_/

 *	******************************************************************************
 *	正点原子 Pandora STM32L475 IoT开发板	实验4
 *	串口通讯实验	HAL库版本
 *	技术支持:www.openedv.com
 *	淘宝店铺:http://openedv.taobao.com
 *	关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 *	广州市星翼电子科技有限公司
 *	作者:正点原子 @ALIENTEK
 *	******************************************************************************/

int main(void)
{
    
    
    u8 len;
    u16 times = 0;

    HAL_Init();
    SystemClock_Config();	//初始化系统时钟为80M
    delay_init(80); 		//初始化延时函数    80M系统时钟
    uart_init(115200);		//初始化串口,波特率为115200

    LED_Init();				//初始化LED
    KEY_Init();				//初始化KEY

    while(1)
    {
    
    
        if(USART_RX_STA & 0x8000)
        {
    
    
            len = USART_RX_STA & 0x3fff; //得到此次接收到的数据长度
            printf("\r\n您发送的消息为:\r\n");
            HAL_UART_Transmit(&UART1_Handler, (uint8_t*)USART_RX_BUF, len, 1000);	//发送接收到的数据

            while(__HAL_UART_GET_FLAG(&UART1_Handler, UART_FLAG_TC) != SET);		//等待发送结束

            printf("\r\n\r\n");//插入换行
            USART_RX_STA = 0;
        }
        else
        {
    
    
            times++;

            if(times % 5000 == 0)
            {
    
    
                printf("\r\nALIENTEK 潘多拉 STM32L475 IOT开发板 串口实验\r\n");
                printf("正点原子@ALIENTEK\r\n\r\n\r\n");
            }

            if(times % 200 == 0)printf("请输入数据,以回车键结束\r\n");
            if(times % 30 == 0)LED_B_TogglePin; //闪烁LED,提示系统正在运行.

            delay_ms(10);
        }
    }
}

代码剖析

HAL_Init()

HAL_Init() 定义如下:(具体实现的功能见注释)

HAL_StatusTypeDef HAL_Init(void)
{
    
    
  HAL_StatusTypeDef  status = HAL_OK;

  /* 配置 Flash 预取,指令缓存,数据缓存        */
  /* 默认配置为:预存取关闭 指令缓存和数据缓存开启 */     
#if (INSTRUCTION_CACHE_ENABLE == 0)  // Flash开启预存取配置,能加速CPU代码的执行
   __HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE == 0)
   __HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */ 

#if (PREFETCH_ENABLE != 0)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置 NVIC 优先级分组

  /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
  if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) //初始化滴答定时器,时钟节拍设置为 1ms
  {
    
    
    status = HAL_ERROR;
  }
  else
  {
    
    
    /* Init the low level hardware */
    HAL_MspInit(); // 低速的外设初始化,比如 GPIO、中断等的设置(使用 STM32CubeMx 生成代码时会将低速外设初始
                   // 代码当这类函数里,其他情况下可以忽略这个函数
  }

  /* Return function status */
  return status;
}

HAL_InitTick()
滴答定时器时钟节拍初始化函数

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    
    
  HAL_StatusTypeDef  status = HAL_OK;

  /*Configure the SysTick to have interrupt in 1ms time basis*/
  if (HAL_SYSTICK_Config(SystemCoreClock/1000UL) != 0U) // 系统时钟/1000,中断周期为 1ms
  {
    
    
    status = HAL_ERROR;
  }
  else
  {
    
    
    /*Configure the SysTick IRQ priority */
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0); // 将滴答定时器的中断优先级设置为最高
  }

  /* Return function status */
  return status;
}

SystemClock_Config()

SystemClock_Config()函数定义如下:(具体实现的功能见注释,仅供参考)

void SystemClock_Config(void)
{
    
    
    HAL_StatusTypeDef	ret = HAL_OK;

    RCC_OscInitTypeDef RCC_OscInitStruct; // 定义振荡器初始化结构体变量
    RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义时钟初始化结构体变量

    __HAL_RCC_PWR_CLK_ENABLE(); // 使能电源控制时钟

    /*Initializes the CPU, AHB and APB busses clocks*/
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 将 HSE(外部高速时钟)作为时钟源
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;  // 开启 HSE
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启 PLL(锁相环)
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 将 HSE 作为 PLL 的时钟源
    RCC_OscInitStruct.PLL.PLLM = 1; // PLL-VCO 输入时钟分频系数,1 表示 2 分频(8 / 2 = 4M,本开发板外部晶振频率为 8MHz)
    RCC_OscInitStruct.PLL.PLLN = 20; // PLL-VCO 输出时钟倍频系数,4 * 20 = 80M,即输出时钟频率为 80MHz
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI 时钟的分频系数
    RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC1, RNG 和 USB 的时钟分频系数
    RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 主系统时钟的分频系数

    ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); //初始化时钟配置

    if(ret != HAL_OK)	while(1);

    /*Initializes the CPU, AHB and APB busses clocks*/
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                  | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 将所有时钟同时进行配置
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 将 PLL 作为系统时钟源
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 不分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 不分频
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 不分频


    ret	= HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 配置时钟初始结构体变量,
    //使用 Flash 延迟4,等待状态(延迟)的数量需要根据CPU时钟(HCLK)的频率和内部电压范围来选择,具体怎么
    //选需要参考芯片手册

    if(ret != HAL_OK)	while(1);

    /*Configure the main internal regulator output voltage*/
    ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); //内部寄存器输出电压配置
    // 下面是 HAL_PWREx_ControlVoltageScaling() 函数说明的部分内容:
    //PWR_REGULATOR_VOLTAGE_SCALE1 Regulator voltage output range 1 mode, typical output voltage
    // at 1.2 V, system frequency up to 80 MHz.

    if(ret != HAL_OK)	while(1);
}

delay_init()

滴答定时器已经在 HAL_Init() 中进行了初始化,下面这个函数实际上就是给 fac_us 赋了一个值(目前暂不涉及操作系统,其他代码暂时不去研究)。

static u32 fac_us = 0;							//us延时倍乘数
/**
 * @brief	初始化延迟函数,SYSTICK的时钟固定为AHB时钟
 *
 * @param   SYSCLK	系统时钟频率
 *
 * @return  void
 */
void delay_init(u8 SYSCLK)
{
    
    
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
    u32 reload;
#endif
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
    fac_us = SYSCLK;						//不论是否使用OS,fac_us都需要使用

#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
    reload = SYSCLK;					  //每秒钟的计数次数 单位为K
    reload *= 1000000 / delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间
    //reload为24位寄存器,最大值:16777216,在80M下,约209.7ms左右
    fac_ms = 1000 / delay_ostickspersec;		//代表OS可以延时的最少单位
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
    SysTick->LOAD = reload; 					//每1/OS_TICKS_PER_SEC秒中断一次
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}

usart_Init()

串口的初始化函数,相关 GPIO 初始化不在该函数里(但其实也在这个函数里进行了 GPIO 配置)。

/**
 * @brief	初始化串口1函数
 *
 * @param	bound	串口波特率
 *
 * @return  void
 */
void uart_init(u32 bound)
{
    
    
    //UART 初始化设置
    UART1_Handler.Instance = USART1;					  //USART1
    UART1_Handler.Init.BaudRate = bound;				  //波特率
    UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
    UART1_Handler.Init.StopBits = UART_STOPBITS_1;	  //一个停止位
    UART1_Handler.Init.Parity = UART_PARITY_NONE;		  //无奇偶校验位
    UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
    UART1_Handler.Init.Mode = UART_MODE_TX_RX;		  //收发模式
    HAL_UART_Init(&UART1_Handler);					    //HAL_UART_Init()会使能UART1

    __HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_RXNE); //开启接收中断
    HAL_NVIC_EnableIRQ(USART1_IRQn);					//使能USART1中断通道
    HAL_NVIC_SetPriority(USART1_IRQn, 3, 3);				//抢占优先级3,子优先级3
}

HAL_UART_MspInit()
在上面的 HAL_UART_Init() 函数里,会调用 HAL_UART_MspInit() 这一函数,配置串口的引脚和中断功能,例程中该函数的定义如下:


/**
 * @brief	HAL库串口底层初始化,时钟使能,引脚配置,中断配置
 *
 * @param	huart	串口句柄
 *
 * @return  void
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    
    
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_Initure;

    if(huart->Instance == USART1) //如果是串口1,进行串口1 MSP初始化
    {
    
    
        __HAL_RCC_GPIOA_CLK_ENABLE();				//使能GPIOA时钟
        __HAL_RCC_USART1_CLK_ENABLE();				//使能USART1时钟

        GPIO_Initure.Pin = GPIO_PIN_9;				//PA9
        GPIO_Initure.Mode = GPIO_MODE_AF_PP;		//复用推挽输出
        GPIO_Initure.Pull = GPIO_PULLUP;			//上拉
        GPIO_Initure.Speed = GPIO_SPEED_FAST;		//高速
        GPIO_Initure.Alternate = GPIO_AF7_USART1;	//复用为USART1
        HAL_GPIO_Init(GPIOA, &GPIO_Initure);	   	//初始化PA9

        GPIO_Initure.Pin = GPIO_PIN_10;				//PA10
        HAL_GPIO_Init(GPIOA, &GPIO_Initure);	   	//初始化PA10
    }
}

LED_Init()

LED 初始化函数里配置了 LED 的引脚对应的 GPIO。

/**
 * @brief	LED IO初始化函数
 *
 * @param   void
 *
 * @return  void
 */
void LED_Init(void)
{
    
    
	/*
		LED-B	PE9
		LED-G	PE8
		LED-R	PE7	
	*/
    GPIO_InitTypeDef GPIO_InitStruct;  // 定义一个GPIO初始化结构体变量

    __HAL_RCC_GPIOE_CLK_ENABLE();  // 使能GPIOE的时钟

    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9; // 同时配置 3 个引脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;  // 默认上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  // 速度设为高速(25 MHz to 50 MHz)
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);  // 初始化结构体变量

	// 将 3 个引脚同时置高
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);
}

delay_ms()

delay_ms() 里运行的是 delay_us()delay_us()通过滴答定时器实现延时。上面的 delay_init() 已经将 fac_us 设置为了 80,滴答定时器计数 80 次需要用 10-6 秒(系统时钟为 80MHz),即 1us。

/**
 * @brief	延时毫秒(ms)函数
 *
 * @param   nms		需要延时多少毫秒
 *
 * @return  void
 */
void delay_ms(u16 nms)
{
    
    
    u32 i;

    for(i = 0; i < nms; i++) delay_us(1000);
}

/**
 * @brief	延时微秒(us)函数
 *
 * @remark	nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
 *
 * @param   nus		需要延时多少微秒
 *
 * @return  void
 */
void delay_us(u32 nus)
{
    
    
    u32 ticks;
    u32 told, tnow, tcnt = 0;
    u32 reload = SysTick->LOAD;				//LOAD的值
    ticks = nus * fac_us; 					//需要的节拍数
    told = SysTick->VAL;        			//刚进入时的计数器值

    while(1)
    {
    
    
        tnow = SysTick->VAL;

        if(tnow != told)
        {
    
    
            if(tnow < told)tcnt += told - tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
            else tcnt += reload - tnow + told;
			
            told = tnow;
            if(tcnt >= ticks)break;			//时间超过/等于要延迟的时间,则退出.
        }
    }
}

HAL_UART_Transmit()

串口发送函数,函数原型如下:

/**
  * @brief Send an amount of data in blocking mode.
  * @note When FIFO mode is enabled, writing a data in the TDR register adds one
  *       data to the TXFIFO. Write operations to the TDR register are performed
  *       when TXFNF flag is set. From hardware perspective, TXFNF flag and
  *       TXE are mapped on the same bit-field.
  * @param huart   UART handle.  串口句柄
  * @param pData   Pointer to data buffer. 要发送的数据
  * @param Size    Amount of data to be sent. 要发送的数据大小
  * @param Timeout Timeout duration.  超时时间
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

串口读写状态

main() 函数中

while(__HAL_UART_GET_FLAG(&UART1_Handler, UART_FLAG_TC) != SET);		//等待发送结束

用来获取串口的发送完成状态,类似的状态位还有:

#define UART_FLAG_TXE      USART_ISR_TXE           /*!< 串口发送数据空  */
#define UART_FLAG_TC       USART_ISR_TC            /*!< 串口发送数据完成   */
#define UART_FLAG_RXNE     USART_ISR_RXNE_RXFNE    /*!< 串口读取数据寄存器非空 */
#define UART_FLAG_RXFNE    USART_ISR_RXNE_RXFNE    /*!< 串口接收缓存非空  */
#define UART_FLAG_RXNE     USART_ISR_RXNE          /*!< 串口读数据非空  */
#define UART_FLAG_IDLE     USART_ISR_IDLE          /*!< 串口空闲标志  */
#define UART_FLAG_ORE      USART_ISR_ORE           /*!< UART overrun error                        */
#define UART_FLAG_NE       USART_ISR_NE            /*!< UART noise error                          */
#define UART_FLAG_FE       USART_ISR_FE            /*!< UART frame error                          */
#define UART_FLAG_PE       USART_ISR_PE            /*!< UART parity error                         */

重定向 printf()

在工程中添加以下代码,就能使用 printf() 打印数据到串口了。

#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
    
    
    int handle;
};

FILE __stdout;
/**
 * @brief	定义_sys_exit()以避免使用半主机模式
 *
 * @param	void
 *
 * @return  void
 */
void _sys_exit(int x)
{
    
    
    x = x;
}
/**
 * @brief	重定义fputc函数
 *
 * @param	ch		输出字符量
 * @param	f		文件指针
 *
 * @return  void
 */
int fputc(int ch, FILE *f)
{
    
    
    while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕

    USART1->TDR = (u8) ch;
    return ch;
}
#endif

LED 操作函数

LED 的控制函数是宏函数,分别用到了 HAL_GPIO_WritePin()HAL_GPIO_TogglePin() 两个库函数。

#define LED_B(n)			(n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET))
#define LED_B_TogglePin     HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_9)

USART1_IRQHandler()

例程的注释声明了本实验没有用 HAL 库的处理逻辑(使用回调函数),而是使用了直接定义中断服务函数的方法。代码逻辑不算复杂,例程里已经给了详细的注释。

u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA = 0;     //接收状态标记
/**
 * @brief	串口1中断服务程序
 *
 * @remark	下面代码我们直接把中断控制逻辑写在中断服务函数内部
 * 			说明:采用HAL库处理逻辑,效率不高。
 *
 * @param   void
 *
 * @return  void
 */
void USART1_IRQHandler(void)
{
    
    
    u8 Res;

    if((__HAL_UART_GET_FLAG(&UART1_Handler, UART_FLAG_RXNE) != RESET)) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    {
    
    
        HAL_UART_Receive(&UART1_Handler, &Res, 1, 1000);

        if((USART_RX_STA & 0x8000) == 0) //接收未完成
        {
    
    
            if(USART_RX_STA & 0x4000) //接收到了0x0d
            {
    
    
                if(Res != 0x0a)USART_RX_STA = 0; //接收错误,重新开始

                else USART_RX_STA |= 0x8000;	//接收完成了
            }
            else //还没收到0X0D
            {
    
    
                if(Res == 0x0d)USART_RX_STA |= 0x4000;
                else
                {
    
    
                    USART_RX_BUF[USART_RX_STA & 0X3FFF] = Res ;
                    USART_RX_STA++;

                    if(USART_RX_STA > (USART_REC_LEN - 1))USART_RX_STA = 0; //接收数据错误,重新开始接收
                }
            }
        }
    }
    HAL_UART_IRQHandler(&UART1_Handler);
}

HAL_UART_Receive()

串口接收函数,函数原型如下:


/**
  * @brief Receive an amount of data in blocking mode.
  * @note When FIFO mode is enabled, the RXFNE flag is set as long as the RXFIFO
  *       is not empty. Read operations from the RDR register are performed when
  *       RXFNE flag is set. From hardware perspective, RXFNE flag and
  *       RXNE are mapped on the same bit-field.
  * @param huart   UART handle. 串口句柄
  * @param pData   Pointer to data buffer. 接收的数据缓存
  * @param Size    Amount of data to be received. 要接收的数据大小
  * @param Timeout Timeout duration. 超时时间
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

HAL_UART_IRQHandler()

USART1_IRQHandler() 的最后一行调用了 HAL_UART_IRQHandler() 函数,下面是网上其他人对这个函数的解释:

调用HAL库中断处理公用函数。 功能:对接收到的数据进行判断和处理 ,判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。

在这里插入图片描述

——https://www.csdn.net/tags/MtjaYgxsMzE5MTYtYmxvZwO0O0OO0O0O.html

猜你喜欢

转载自blog.csdn.net/weixin_43772810/article/details/125299366