你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的低功耗串口LPUART应用之串口FIFO和停机唤醒实现

[复制链接]
STMCU小助手 发布时间:2021-11-2 23:28
66.1 初学者重要提示
  学习本章节前,务必优先学习第65章。
  低功耗串口FIFO的实现跟前面章节通用串口FIFO的机制是一样的。
  大家自己做的板子,测试串口收发是乱码的话,重点看stm32h7xx_hal_conf.h文件中的HSE_VALUE的大小跟板子上实际晶振大小是否一致,然后再看PLL配置。
66.2 硬件设计
STM32H743XIH6最多可以支持8个独立的通用串口和一个低功耗串口LPUART1。其中串口4和串口5和SDIO的GPIO是共用的,也就是说,如果要用到SD卡,那么串口4和串口5将不能使用。串口7和SPI3共用,串口8和RGB硬件接口共用。串口功能可以分配到不同的GPIO。我们常用的引脚分配如下:

低功耗串口LPUART TX = PA9,   RX = PA10

串口USART1  TX = PA9,   RX = PA10 (低功耗串口和USART1用的相同引脚)

串口USART2  TX = PA2,   RX = PA3

串口USART3  TX = PB10,  RX = PB11

串口UART4   TX = PC10,  RX = PC11 (和SDIO共用)

串口UART5   TX = PC12,  RX = PD2  (和SDIO共用)

串口USART6  TX = PG14,  RX = PC7  

串口UART7   TX = PB4,   RX = PB3  (和SPI1/3共用)

串口UART8   TX = PJ8,   RX =PJ9   (和RGB硬件接口共用)

STM32-V7开发板使用了4个串口设备。

  串口1用于RS232接口,很多例子的pritnf结果就是输出到串口1
  串口2用于GPS
  串口3用于RS485接口
  串口6 用于TTL串口插座,板子上有GPRS插座和串口WIFI插座。
下面是RS232的原理图:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


关于232的PHY芯片SP3232E要注意以下几个问题:

  SP3232E的作用是TTL电平转RS232电平。
  电阻R130的作用是避免CPU复位期间,TX为高阻时串口线上出现异常数据。
  检测SP3232E的好坏可以采用回环的方式,即短接T1OUT和R1IN,对应到DB9插座上就是短接引脚2和引脚3。

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


实际效果如下:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


通过这种方式,可以在应用程序中通过串口发送几个字符,查看是否可以正确接收来判断232 PHY

芯片是否有问题。

  由于这里是TTL转RS232,如果电脑端自带DB9串口,可以找根交叉线直接接上。如果电脑端没有,就需要用RS232转USB的串口线。这里要注意是RS232转USB,不是TTL转USB。像我们用的CH340就是RS232转USB芯片。
  检测串口线的好坏跟板子上的232 PHY一样,将电脑端的串口助手打开,串口线接到电脑端并短接串口线的2脚和3脚,然后使用串口助手进行自收发测试即可。
66.3 低功耗串口FIFO驱动设计
66.3.1 低功耗串口FIFO框架
为了方便大家理解,先来看下低功耗串口FIFO的实现框图:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  第1阶段,初始化:

通过函数bsp_InitLPUart初始化低功耗串口结构体,低功耗串口硬件参数。
  第2阶段,低功耗串口中断服务程序:

  接收中断是一直开启的。
  做了发送空中断和发送完成中断的消息处理。
  第3阶段,低功耗串口数据的收发:

低功耗串口发送函数会开启发送空中断。
低功耗串口接收中断接收到函数后,可以使用函数lpcomGetChar获取数据。
66.3.2 低功耗串口时钟选择
我们这里实现了三种时钟选择:

LPUART时钟选择LSE(32768Hz)
最高速度是10922bps,最低8bps(计算方法3x < 32768 < 4096x,x表示波特率)。

LPUART时钟选择HSI(64MHz)
最高值是21MHz,最小值15625bps(计算方法3x < 64MHz < 4096x,x表示波特率)。

LPUART时钟选择D3PCLK1(100MHz)
最大值33Mbps,最小值24414bps(计算方法3x < 100MHz < 4096x,x表示波特率)。

如果需要低功耗模式唤醒,必须使用LSE或者HSI时钟,程序代码如下,用户可以根据需要,使能相应的宏定义:

  1. //#define LPUART_CLOCK_SOURCE_LSE      
  2. #define LPUART_CLOCK_SOURCE_HSI   
  3. //#define LPUART_CLOCK_SOURCE_D3PCLK1   

  4. /*
  5. *********************************************************************************************************
  6. *    函 数 名: InitHardLPUart
  7. *    功能说明: 配置串口的硬件参数(波特率,数据位,停止位,起始位,校验位,中断使能)适合于STM32-H7开发板
  8. *    形    参: 无
  9. *    返 回 值: 无
  10. *********************************************************************************************************
  11. */
  12. static void InitHardLPUart(void)
  13. {
  14.     GPIO_InitTypeDef  GPIO_InitStruct;
  15.     RCC_PeriphCLKInitTypeDef   RCC_PeriphCLKInitStruct = {0};

  16. /* 使用LSE(32768Hz),最高速度是10922bps,最低8bps */   
  17. #if defined (LPUART_CLOCK_SOURCE_LSE)
  18.     {
  19.         RCC_OscInitTypeDef RCC_OscInitStruct = {0};

  20.         RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
  21.         RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  22.         RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;

  23.         if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)
  24.         {
  25.             Error_Handler(__FILE__, __LINE__);        
  26.         }

  27.         RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;
  28.         RCC_PeriphCLKInitStruct.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_LSE;
  29.         HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
  30.     }   
  31. /* LPUART时钟选择HSI(64MHz),最高值是21MHz,最小值15625bps */   
  32. #elif defined (LPUART_CLOCK_SOURCE_HSI)
  33.     {

  34.         RCC_OscInitTypeDef RCC_OscInitStruct = {0};

  35.           RCC_OscInitStruct.OscillatorType      = RCC_OSCILLATORTYPE_HSI;
  36.           RCC_OscInitStruct.HSIState            = RCC_HSI_ON;
  37.           RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  38.           RCC_OscInitStruct.PLL.PLLState        = RCC_PLL_NONE;

  39.         if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)
  40.         {
  41.             Error_Handler(__FILE__, __LINE__);        
  42.         }

  43.         RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;
  44.         RCC_PeriphCLKInitStruct.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI;
  45.         HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
  46.     }
  47. /* LPUART时钟选择D3PCLK1(100MHz),最大值33Mbps,最小值24414bps */   
  48. #elif defined (LPUART_CLOCK_SOURCE_D3PCLK1)

  49.     RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;
  50.     RCC_PeriphCLKInitStruct.Lptim1ClockSelection = RCC_LPUART1CLKSOURCE_D3PCLK1;
  51.     HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
  52. #else
  53.     #error Please select the LPTIM Clock source inside the bsp_lpuart_fifo.c file
  54. #endif

  55. #if LPUART1_FIFO_EN ==
  56.     /* 其它省略未写 */
  57. #endif
  58. }
复制代码

66.3.3 低功耗串口FIFO之相关的变量定义
低功耗串口驱动的核心文件为:bsp_lpuart_fifo.c, bsp_lpuart_fifo.h。

这里面包括有串口硬件的配置函数、中断处理函数,以及串口的读写接口函数。还有printf函数的实现。

每个串口都有2个FIFO缓冲区,一个是用于发送数据的TX_FIFO,一个用于保存接收数据的RX_FIFO。

我们来看下这个FIFO的定义,在bsp_lpuart_fifo.h文件。

  1. /* 定义串口波特率和FIFO缓冲区大小,分为发送缓冲区和接收缓冲区, 支持全双工 */
  2. #if LPUART1_FIFO_EN == 1
  3.     #define LPUART1_BAUD         115200
  4.     #define LPUART1_TX_BUF_SIZE     1*1024
  5.     #define LPUART1_RX_BUF_SIZE     1*1024
  6. #endif

  7. /* 串口设备结构体 */
  8. typedef struct
  9. {
  10.     USART_TypeDef *uart;        /* STM32内部串口设备指针 */
  11.     uint8_t *pTxBuf;            /* 发送缓冲区 */
  12.     uint8_t *pRxBuf;            /* 接收缓冲区 */
  13.     uint16_t usTxBufSize;        /* 发送缓冲区大小 */
  14.     uint16_t usRxBufSize;        /* 接收缓冲区大小 */
  15.     __IO uint16_t usTxWrite;    /* 发送缓冲区写指针 */
  16.     __IO uint16_t usTxRead;        /* 发送缓冲区读指针 */
  17.     __IO uint16_t usTxCount;    /* 等待发送的数据个数 */

  18.     __IO uint16_t usRxWrite;    /* 接收缓冲区写指针 */
  19.     __IO uint16_t usRxRead;        /* 接收缓冲区读指针 */
  20.     __IO uint16_t usRxCount;    /* 还未读取的新数据个数 */

  21.     void (*SendBefor)(void);     /* 开始发送之前的回调函数指针(主要用于RS485切换到发送模式) */
  22.     void (*SendOver)(void);     /* 发送完毕的回调函数指针(主要用于RS485将发送模式切换为接收模式) */
  23.     void (*ReciveNew)(uint8_t _byte);    /* 串口收到数据的回调函数指针 */
  24.     uint8_t Sending;            /* 正在发送中 */
  25. }UART_T;


  26. bsp_lpuart_fifo.c文件定义变量。

  27. /* 定义低功耗串口结构体变量 */
  28. #if LPUART1_FIFO_EN == 1
  29.     static LPUART_T g_tLPUart1;
  30.     static uint8_t g_TxBuf1[LPUART1_TX_BUF_SIZE];        /* 发送缓冲区 */
  31.     static uint8_t g_RxBuf1[LPUART1_RX_BUF_SIZE];        /* 接收缓冲区 */
  32. #endif
复制代码


关于FIFO的机制,我们在按键FIFO驱动已经做过详细的介绍,这个地方就不赘述了。每个串口有两个FIFO缓冲区,每个FIFO对应一个写指针和一个读指针。这个结构中还有三个回调函数。回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

66.3.4 低功耗串口FIFO初始化
低功耗串口的初始化代码如下;

/*
*********************************************************************************************************
*    函 数 名: bsp_InitLPUart
*    功能说明: 初始化串口硬件,并对全局变量赋初值.
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitLPUart(void)
{
    LPUartVarInit();        /* 必须先初始化全局变量,再配置硬件 */
    InitHardLPUart();        /* 配置串口的硬件参数(波特率等) */
}


下面将初始化代码实现的功能依次为大家做个说明。

  函数LPUartVarInit
这个函数实现的功能比较好理解,主要是串口设备结构体变量的初始化,代码如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: LPUartVarInit
  4. *    功能说明: 初始化串口相关的变量
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void LPUartVarInit(void)
  10. {
  11. #if LPUART1_FIFO_EN == 1
  12.     g_tLPUart1.uart = LPUART1;                        /* STM32 串口设备 */
  13.     g_tLPUart1.pTxBuf = g_TxBuf1;                    /* 发送缓冲区指针 */
  14.     g_tLPUart1.pRxBuf = g_RxBuf1;                    /* 接收缓冲区指针 */
  15.     g_tLPUart1.usTxBufSize = LPUART1_TX_BUF_SIZE;         /* 发送缓冲区大小 */
  16.     g_tLPUart1.usRxBufSize = LPUART1_RX_BUF_SIZE;         /* 接收缓冲区大小 */
  17.     g_tLPUart1.usTxWrite = 0;                        /* 发送FIFO写索引 */
  18.     g_tLPUart1.usTxRead = 0;                        /* 发送FIFO读索引 */
  19.     g_tLPUart1.usRxWrite = 0;                        /* 接收FIFO写索引 */
  20.     g_tLPUart1.usRxRead = 0;                        /* 接收FIFO读索引 */
  21.     g_tLPUart1.usRxCount = 0;                        /* 接收到的新数据个数 */
  22.     g_tLPUart1.usTxCount = 0;                        /* 待发送的数据个数 */
  23.     g_tLPUart1.SendBefor = 0;                        /* 发送数据前的回调函数 */
  24.     g_tLPUart1.SendOver = 0;                        /* 发送完毕后的回调函数 */
  25.     g_tLPUart1.ReciveNew = 0;                        /* 接收到新数据后的回调函数 */
  26.     g_tLPUart1.Sending = 0;                             /* 正在发送中标志 */
  27. #endif
  28. }
复制代码



  函数InitHardLPUart
此函数主要用于串口的GPIO,中断和相关参数的配置。

  1. /* LPUART1的GPIO  PA9, PA10 */
  2. #define LPUART1_CLK_ENABLE()              __HAL_RCC_LPUART1_CLK_ENABLE()

  3. #define LPUART1_TX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
  4. #define LPUART1_TX_GPIO_PORT              GPIOA
  5. #define LPUART1_TX_PIN                    GPIO_PIN_9
  6. #define LPUART1_TX_AF                     GPIO_AF3_LPUART

  7. #define LPUART1_RX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
  8. #define LPUART1_RX_GPIO_PORT              GPIOA
  9. #define LPUART1_RX_PIN                    GPIO_PIN_10
  10. #define LPUART1_RX_AF                     GPIO_AF3_LPUART

  11. /*
  12. *********************************************************************************************************
  13. *    函 数 名: InitHardUart
  14. *    功能说明: 配置串口的硬件参数和底层
  15. *    形    参: 无
  16. *    返 回 值: 无
  17. *********************************************************************************************************
  18. */
  19. static void InitHardUart(void)
  20. {
  21.     GPIO_InitTypeDef  GPIO_InitStruct;
  22.     RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;

  23.     /* 时钟初始化省略未写 */
  24. #if LPUART1_FIFO_EN == 1        
  25.     /* 使能 GPIO TX/RX 时钟 */
  26.     LPUART1_TX_GPIO_CLK_ENABLE();
  27.     LPUART1_RX_GPIO_CLK_ENABLE();

  28.     /* 使能 USARTx 时钟 */
  29.     LPUART1_CLK_ENABLE();   

  30.     /* 配置TX引脚 */
  31.     GPIO_InitStruct.Pin       = LPUART1_TX_PIN;
  32.     GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
  33.     GPIO_InitStruct.Pull      = GPIO_PULLUP;
  34.     GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
  35.     GPIO_InitStruct.Alternate = LPUART1_TX_AF;
  36.     HAL_GPIO_Init(LPUART1_TX_GPIO_PORT, &GPIO_InitStruct);   

  37.     /* 配置RX引脚 */
  38.     GPIO_InitStruct.Pin = LPUART1_RX_PIN;
  39.     GPIO_InitStruct.Alternate = LPUART1_RX_AF;
  40.     HAL_GPIO_Init(LPUART1_RX_GPIO_PORT, &GPIO_InitStruct);

  41.     /* 配置NVIC the NVIC for UART */   
  42.     HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 1);
  43.     HAL_NVIC_EnableIRQ(LPUART1_IRQn);

  44.     /* 配置波特率、奇偶校验 */
  45.     bsp_SetLPUartParam(LPUART1,  LPUART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);

  46.     SET_BIT(LPUART1->ICR, USART_ICR_TCCF);   /* 清除TC发送完成标志 */
  47.     SET_BIT(LPUART1->RQR, USART_RQR_RXFRQ);  /* 清除RXNE接收标志 */
  48.     SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中断 */
  49. #endif
  50. }
复制代码

低功耗定时器的参数配置API如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_SetLPUartParam
  4. *    功能说明: 配置串口的硬件参数(波特率,数据位,停止位,起始位,校验位,中断使能)适合于STM32- H7开发板
  5. *    形    参: Instance   USART_TypeDef类型结构体
  6. *             BaudRate   波特率
  7. *             Parity     校验类型,奇校验或者偶校验
  8. *             Mode       发送和接收模式使能
  9. *    返 回 值: 无
  10. *********************************************************************************************************
  11. */   
  12. void bsp_SetLPUartParam(USART_TypeDef *Instance,  uint32_t BaudRate, uint32_t Parity, uint32_t Mode)
  13. {
  14.     /*##-1- 配置串口硬件参数 ######################################*/
  15.     /* 异步串口模式 (UART Mode) */
  16.     /* 配置如下:
  17.       - 字长    = 8 位
  18.       - 停止位  = 1 个停止位
  19.       - 校验    = 参数Parity
  20.       - 波特率  = 参数BaudRate
  21.       - 硬件流控制关闭 (RTS and CTS signals) */

  22.     UartHandle.Instance        = Instance;
  23.     UartHandle.Init.BaudRate   = BaudRate;
  24.     UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
  25.     UartHandle.Init.StopBits   = UART_STOPBITS_1;
  26.     UartHandle.Init.Parity     = Parity;
  27.     UartHandle.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
  28.     UartHandle.Init.Mode       = Mode;
  29.     UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
  30.     UartHandle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  31.     UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;

  32.     if (HAL_UART_Init(&UartHandle) != HAL_OK)
  33.     {
  34.         Error_Handler(__FILE__, __LINE__);
  35.     }
  36. }
复制代码



66.3.5 低功耗串口中断服务程序工作流程
串口中断服务程序是最核心的部分,主要实现如下三个功能

  收到新的数据后,会将数据压入RX_FIFO。
  检测到发送缓冲区空后,会从TX_FIFO中取下一个数据并发送。
  如果是RS485半双工串口,发送前会设置一个GPIO=1控制RS485收发器进入发送状态,当最后一个字节的最后一个bit传送完毕后,设置这个GPIO=0让RS485收发器进入接收状态。


下面我们分析一下串口中断处理的完整过程。

当产生串口中断后,CPU会查找中断向量表,获得中断服务程序的入口地址。入口函数为LPUART1_IRQHandler,这个函数在启动文件startup_stm32h743xx.s汇编代码中已经有实现。我们在c代码中需要重写一个同样名字的函数就可以重载它。如果不重载,启动文件中缺省的中断服务程序就是一个死循环,等于 while(1);

我们将串口中断服务程序放在bsp_lpuart_fifo.c文件,没有放到 stm32h7xx_it.c。当应用不需要串口功能时,直接从工程中删除bsp_lpuart_fifo.c接口,不必再去整理stm32h7xx_it.c这个文件。下面展示的代码是低功耗串口的中断服务程序:

  1. #if LPUART1_FIFO_EN == 1
  2. void LPUART1_IRQHandler(void)
  3. {
  4.     LPUartIRQ(&g_tLPUart1);
  5. }
  6. #endif
复制代码

下面,我们来看看UartIRQ函数的实现代码。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: UartIRQ
  4. *    功能说明: 供中断服务程序调用,通用串口中断处理函数
  5. *    形    参: _pUart : 串口设备
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void UartIRQ(UART_T *_pUart)
  10. {
  11.     uint32_t isrflags   = READ_REG(_pUart->uart->ISR);
  12.     uint32_t cr1its     = READ_REG(_pUart->uart->CR1);
  13.     uint32_t cr3its     = READ_REG(_pUart->uart->CR3);

  14.     /* 处理接收中断  */
  15.     if ((isrflags & USART_ISR_RXNE) != RESET)
  16.     {
  17.         /* 从串口接收数据寄存器读取数据存放到接收FIFO */
  18.         uint8_t ch;

  19.         ch = READ_REG(_pUart->uart->RDR);               /* 读串口接收数据寄存器 */
  20.         _pUart->pRxBuf[_pUart->usRxWrite] = ch;         /* 填入串口接收FIFO */
  21.         if (++_pUart->usRxWrite >= _pUart->usRxBufSize) /* 接收FIFO的写指针+1 */
  22.         {
  23.             _pUart->usRxWrite = 0;
  24.         }
  25.         if (_pUart->usRxCount < _pUart->usRxBufSize)    /* 统计未处理的字节个数 */
  26.         {
  27.             _pUart->usRxCount++;
  28.         }

  29.         /* 回调函数,通知应用程序收到新数据,一般是发送1个消息或者设置一个标记 */
  30.         //if (_pUart->usRxWrite == _pUart->usRxRead)
  31.         //if (_pUart->usRxCount == 1)
  32.         {
  33.             if (_pUart->ReciveNew)
  34.             {
  35.                 _pUart->ReciveNew(ch); /* 比如,交给MODBUS解码程序处理字节流 */
  36.             }
  37.         }
  38.     }

  39.     /* 处理发送缓冲区空中断 */
  40.     if ( ((isrflags & USART_ISR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET)
  41.     {
  42.         //if (_pUart->usTxRead == _pUart->usTxWrite)
  43.         if (_pUart->usTxCount == 0)  /* 发送缓冲区已无数据可取 */
  44.         {
  45.         /* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后1个数据还未真正发送完毕)*/
  46.             //USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);
  47.             CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);

  48.             /* 使能数据发送完毕中断 */
  49.             //USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);
  50.             SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE);
  51.         }
  52.         Else  /* 还有数据等待发送 */
  53.         {
  54.             _pUart->Sending = 1;

  55.             /* 从发送FIFO取1个字节写入串口发送数据寄存器 */
  56.             //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
  57.             _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];
  58.             if (++_pUart->usTxRead >= _pUart->usTxBufSize)
  59.             {
  60.                 _pUart->usTxRead = 0;
  61.             }
  62.             _pUart->usTxCount--;
  63.         }

  64.     }
  65.     /* 数据bit位全部发送完毕的中断 */
  66.     if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  67.     {
  68.         //if (_pUart->usTxRead == _pUart->usTxWrite)
  69.         if (_pUart->usTxCount == 0)
  70.         {
  71.             /* 如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断 */
  72.             //USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE);
  73.             CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE);

  74.             /* 回调函数, 一般用来处理RS485通信,将RS485芯片设置为接收模式,避免抢占总线 */
  75.             if (_pUart->SendOver)
  76.             {
  77.                 _pUart->SendOver();
  78.             }

  79.             _pUart->Sending = 0;
  80.         }
  81.         else
  82.         {
  83.             /* 正常情况下,不会进入此分支 */

  84.             /* 如果发送FIFO的数据还未完毕,则从发送FIFO取1个数据写入发送数据寄存器 */
  85.             //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
  86.             _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];
  87.             if (++_pUart->usTxRead >= _pUart->usTxBufSize)
  88.             {
  89.                 _pUart->usTxRead = 0;
  90.             }
  91.             _pUart->usTxCount--;
  92.         }
  93.     }

  94.     /* 清除中断标志 */
  95.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_PEF);
  96.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_FEF);
  97.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_NEF);
  98.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_OREF);
  99.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_IDLEF);
  100.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_TCF);
  101.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_LBDF);
  102.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_CTSF);
  103.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_CMF);
  104.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_WUF);
  105.     SET_BIT(_pUart->uart->ICR, UART_CLEAR_TXFECF);   
  106. }
复制代码

中断服务程序的处理主要分为两部分,接收数据的处理和发送数据的处理,详情看程序注释即可,已经比较详细,下面重点把思路说一下。

  接收数据处理
接收数据的处理是判断ISR寄存器的USART_ISR_RXNE标志是否置位,如果置位表示RDR接收寄存器已经存入数据。然后将数据读入到接收FIFO空间。

特别注意里面的ReciveNew处理,这个在Modbus协议里面要用到。

  发送数据处理
发送数据主要是发送空中断TEX和发送完成中断TC的处理,当TXE=1时,只是表示发送数据寄存器为空了,此时可以填充下一个准备发送的数据了。当为TDR发送寄存器赋值后,硬件启动发送,等所有的bit传送完毕后,TC标志设置为1。如果是RS232全双工通信,可以只用TXE标志控制发送过程。如果是RS485半双工通信,就需要利用TC标志了,因为在最后一个bit传送完毕后,需要设置RS485收发器进入到接收状态。

66.3.6 低功耗串口数据发送
低功耗串口数据的发送主要涉及到下面三个函数:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: lpcomSendBuf
  4. *    功能说明: 向串口发送一组数据。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送
  5. *    形    参: _ucPort: 端口号(LPCOM1)
  6. *              _ucaBuf: 待发送的数据缓冲区
  7. *              _usLen : 数据长度
  8. *    返 回 值: 无
  9. *********************************************************************************************************
  10. */
  11. void lpcomSendBuf(LPCOM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen)
  12. {
  13.     LPUART_T *pUart;

  14.     pUart = ComToLPUart(_ucPort);
  15.     if (pUart == 0)
  16.     {
  17.         return;
  18.     }

  19.     if (pUart->SendBefor != 0)
  20.     {
  21.         pUart->SendBefor();        /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */
  22.     }

  23.     LPUartSend(pUart, _ucaBuf, _usLen);
  24. }

  25. /*
  26. *********************************************************************************************************
  27. *    函 数 名: lpcomSendChar
  28. *    功能说明: 向串口发送1个字节。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送
  29. *    形    参: _ucPort: 端口号(LPCOM1)
  30. *              _ucByte: 待发送的数据
  31. *    返 回 值: 无
  32. *********************************************************************************************************
  33. */
  34. void lpcomSendChar(LPCOM_PORT_E _ucPort, uint8_t _ucByte)
  35. {
  36.     lpcomSendBuf(_ucPort, &_ucByte, 1);
  37. }

  38. /*
  39. *********************************************************************************************************
  40. *    函 数 名: LPUartSend
  41. *    功能说明: 填写数据到UART发送缓冲区,并启动发送中断。中断处理函数发送完毕后,自动关闭发送中断
  42. *    形    参: 无
  43. *    返 回 值: 无
  44. *********************************************************************************************************
  45. */
  46. static void LPUartSend(LPUART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)
  47. {
  48.     uint16_t i;

  49.     for (i = 0; i < _usLen; i++)
  50.     {
  51.         /* 如果发送缓冲区已经满了,则等待缓冲区空 */
  52.         while (1)
  53.         {
  54.             __IO uint16_t usCount;

  55.             DISABLE_INT();
  56.             usCount = _pUart->usTxCount;
  57.             ENABLE_INT();

  58.             if (usCount < _pUart->usTxBufSize)
  59.             {
  60.                 break;
  61.             }
  62.             else if(usCount == _pUart->usTxBufSize)/* 数据已填满缓冲区 */
  63.             {
  64.                 if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0)
  65.                 {
  66.                     SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);
  67.                 }  
  68.             }
  69.         }

  70.         /* 将新数据填入发送缓冲区 */
  71.         _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf<i>;</i>

  72.         DISABLE_INT();
  73.         if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
  74.         {
  75.             _pUart->usTxWrite = 0;
  76.         }
  77.         _pUart->usTxCount++;
  78.         ENABLE_INT();
  79.     }

  80.     SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);    /* 使能发送中断(缓冲区空) */
  81. }
复制代码

函数lpcomSendChar是发送一个字节,通过调用函数lpcomSendBuf实现,而函数lpcomSendBuf又是通过调用函数LPUartSend实现,这个函数是重点。

函数LPUartSend的作用就是把要发送的数据填到发送缓冲区里面,并使能发送空中断。

  如果要发送的数据没有超过发送缓冲区大小,实现起来还比较容易,直接把数据填到FIFO里面,并使能发送空中断即可。
  如果超过了FIFO大小,就需要等待有空间可用,针对这种情况有个重要的知识点,就是当缓冲刚刚填满的时候要判断发送空中断是否开启了,如果填满了还没有开启,就会卡死在while循环中,所以多了一个刚填满时的判断,填满了还没有开启发送空中断,要开启下。

注意:由于函数LPUartSend做了static作用域限制,仅可在bsp_lpuart_fifo.c文件中调用。函数lpcomSendChar和lpcomSendBuf是供用户调用的。

函数lpcomSendBuf中调用了一个函数pUart = ComToLPUart(_ucPort),这个函数是将整数的COM端口号转换为LPUART结构体指针。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: ComToLPUart
  4. *    功能说明: 将COM端口号转换为LPUART指针
  5. *    形    参: _ucPort: 端口号(LPCOM1)
  6. *    返 回 值: uart指针
  7. *********************************************************************************************************
  8. */
  9. LPUART_T *ComToLPUart(LPCOM_PORT_E _ucPort)
  10. {
  11.     if (_ucPort == LPCOM1)
  12.     {
  13.         #if LPUART1_FIFO_EN == 1
  14.             return &g_tLPUart1;
  15.         #else
  16.             return 0;
  17.         #endif
  18.     }
  19.     else
  20.     {
  21.         Error_Handler(__FILE__, __LINE__);
  22.         return 0;
  23.     }
  24. }
复制代码



66.3.7 低功耗串口数据接收
下面我们再来看看接收的函数:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: lpcomGetChar
  4. *    功能说明: 从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。
  5. *    形    参: _ucPort: 端口号(LPCOM1)
  6. *              _pByte: 接收到的数据存放在这个地址
  7. *    返 回 值: 0 表示无数据, 1 表示读取到有效字节
  8. *********************************************************************************************************
  9. */
  10. uint8_t lpcomGetChar(LPCOM_PORT_E _ucPort, uint8_t *_pByte)
  11. {
  12.     LPUART_T *pUart;

  13.     pUart = ComToLPUart(_ucPort);
  14.     if (pUart == 0)
  15.     {
  16.         return 0;
  17.     }

  18.     return LPUartGetChar(pUart, _pByte);
  19. }

  20. /*
  21. *********************************************************************************************************
  22. *    函 数 名: LPUartGetChar
  23. *    功能说明: 从串口接收缓冲区读取1字节数据 (用于主程序调用)
  24. *    形    参: _pUart : 串口设备
  25. *              _pByte : 存放读取数据的指针
  26. *    返 回 值: 0 表示无数据  1表示读取到数据
  27. *********************************************************************************************************
  28. */
  29. static uint8_t LPUartGetChar(LPUART_T *_pUart, uint8_t *_pByte)
  30. {
  31.     uint16_t usCount;

  32.     /* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */
  33.     DISABLE_INT();
  34.     usCount = _pUart->usRxCount;
  35.     ENABLE_INT();

  36.     /* 如果读和写索引相同,则返回0 */
  37.     //if (_pUart->usRxRead == usRxWrite)
  38.     if (usCount == 0)    /* 已经没有数据 */
  39.     {
  40.         return 0;
  41.     }
  42.     else
  43.     {
  44.         *_pByte = _pUart->pRxBuf[_pUart->usRxRead];        /* 从串口接收FIFO取1个数据 */

  45.         /* 改写FIFO读索引 */
  46.         DISABLE_INT();
  47.         if (++_pUart->usRxRead >= _pUart->usRxBufSize)
  48.         {
  49.             _pUart->usRxRead = 0;
  50.         }
  51.         _pUart->usRxCount--;
  52.         ENABLE_INT();
  53.         return 1;
  54.     }
  55. }
复制代码

函数lpcomGetChar是专门供用户调用的,用于从接收FIFO中读取1个数据。具体代码的实现也比较好理解,主要是接收FIFO的空间调整。

注意:由于函数LPUartGetChar做了static作用域限制,仅可在bsp_lpuart_fifo.c文件中调用。

66.3.8 低功耗串口printf实现
printf函数是标准c库函数。最原来的意思是打印输出到显示器。在单片机,我们常用它来打印调试信息到串口,通过计算机上运行的串口软件来监视程序的运行状态。

为什么要用printf函数,而不用串口发送的函数。因为printf函数的形参功能很强大,它支持各种数值转换。比如将整数、浮点数转换为字符串,支持整数左对齐、右对齐显示等。

我们设计的很多裸机例子都是用printf函数输出运行结果的。因为如果加上显示屏驱动后,会将程序搞的很复杂,显示部分的代码量超过了例程本身要演示的核心功能代码。用串口做输出,移植很方便,现在很少有不带串口的单片机。

实现printf输出到串口,只需要在工程中添加两个函数:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: fputc
  4. *    功能说明: 重定义putc函数,这样可以使用printf函数从串口1打印输出
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. int fputc(int ch, FILE *f)
  10. {
  11. #if 0    /* 将需要printf的字符通过串口中断FIFO发送出去,printf函数会立即返回 */
  12.     lpcomSendChar(LPCOM1, ch);

  13.     return ch;
  14. #else    /* 采用阻塞方式发送每个字符,等待数据发送完毕 */
  15.     /* 写一个字节到USART1 */
  16.     LPUART1->TDR = ch;

  17.     /* 等待发送结束 */
  18.     while((LPUART1->ISR & USART_ISR_TC) == 0)
  19.     {}

  20.     return ch;
  21. #endif
  22. }

  23. /*
  24. *********************************************************************************************************
  25. *    函 数 名: fgetc
  26. *    功能说明: 重定义getc函数,这样可以使用getchar函数从串口1输入数据
  27. *    形    参: 无
  28. *    返 回 值: 无
  29. *********************************************************************************************************
  30. */
  31. int fgetc(FILE *f)
  32. {

  33. #if 1    /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */
  34.     uint8_t ucData;

  35.     while(lpcomGetChar(LPCOM1, &ucData) == 0);

  36.     return ucData;
  37. #else
  38.     /* 等待接收到数据 */
  39.     while((LPUART1->ISR & USART_ISR_RXNE) == 0)
  40.     {}

  41.     return (int)LPUART1->RDR;
  42. #endif
  43. }
复制代码

通过上面代码中的条件编译,可以设置printf函数阻塞和非阻塞方式,如果采用非阻塞方式,执行后会立即返回,串口中断服务程序会陆续将数据发送出去。

66.3.9 低功耗串口停机唤醒方式
低功耗串口的唤醒主要是通过接收数据来唤醒,具体唤醒的方如下:

  检测到起始位唤醒。
低功耗串口设置为起始位检测方式如下,并且设置进入停机模式。

如果想唤醒H7,发一个起始位即可,简单些也可以任意发送一个数据:

  1. /* 使能LPUART的停机唤醒 */
  2. HAL_UARTEx_EnableStopMode(&UartHandle);

  3. /* 确保LPUART没有在通信中 */
  4. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  5. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  6. /* 接收起始位唤醒 */
  7. WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;
  8. if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  9. {
  10.     Error_Handler(__FILE__, __LINE__);                        
  11. }

  12. /* 进入停机模式 */
  13. HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  14. /* 退出停机模式要重新配置HSE和PLL*/
  15. SystemClock_Config();

  16. /* 关闭LPUART的停机唤醒 */
  17. HAL_UARTEx_DisableStopMode(&UartHandle);
复制代码


  检测到RXNE标志唤醒,即接收到数据。
低功耗串口设置为RXNE检测方式如下,并且设置进入停机模式。

如果想唤醒H7,发一个任意数据即可。

  1. /* 使能LPUART的停机唤醒 */
  2. HAL_UARTEx_EnableStopMode(&UartHandle);

  3. /* 确保LPUART没有在通信中 */
  4. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  5. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  6. /* 接收到数据唤醒,即RXNE标志置位 */
  7. WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;
  8. if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  9. {
  10.     Error_Handler(__FILE__, __LINE__);                        
  11. }

  12. /* 进入停机模式 */
  13. HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  14. /* 退出停机模式要重新配置HSE和PLL*/
  15. SystemClock_Config();

  16. /* 关闭LPUART的停机唤醒 */
  17. HAL_UARTEx_DisableStopMode(&UartHandle);

复制代码

  检测到匹配地址时唤醒。
低功耗串口设置为地址匹配检测方式如下,并且设置进入停机模式。

如果想唤醒H7,必须发送指定的匹配地址。匹配地址支持7bit和4bit匹配两种方式,比如我们采用7bit匹配,设置地址是0x19,那么用户唤醒的时候要将最高bit设置为1,即发生地址0x99(0b1001 1001)才可以唤醒。

  1. /* 使能LPUART的停机唤醒 */
  2. HAL_UARTEx_EnableStopMode(&UartHandle);

  3. /* 确保LPUART没有在通信中 */
  4. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  5. while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  6. /* 接收地址0x99(发送的数据MSB位要为1),可以唤醒 */
  7. WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;
  8. WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;
  9. WakeUpSelection.Address       = 0x19;
  10. if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  11. {
  12.     Error_Handler(__FILE__, __LINE__);                        
  13. }

  14. CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 关闭串口接收中断 */

  15. /* 进入停机模式 */
  16. HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  17. /* 退出停机模式要重新配置HSE和PLL*/
  18. SystemClock_Config();

  19. SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中断 */

  20. /* 关闭LPUART的停机唤醒 */
  21. HAL_UARTEx_DisableStopMode(&UartHandle);

复制代码

这里有一点要特别注意,程序启动后,调用下面两个函数:

  1. __HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停机状态下可以继续接收消息 */
  2. __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_WUF);/* 使能唤醒中断 */
复制代码

66.4 低功耗串口FIFO板级支持包(bsp_lpuart_fifo.c)
串口驱动文件bsp_lpuart_fifo.c主要实现了如下几个API供用户调用:

  bsp_InitLPUart
  lpcomSendBuf
  lpcomSendChar
  lpcomGetChar
66.4.1 函数bsp_InitLPUart
函数原型:

void bsp_InitLPUart(void)

函数描述:

此函数主要用于串口的初始化,使用所有其它API之前,务必优先调用此函数。

使用举例:

串口的初始化函数在bsp.c文件的bsp_Init函数里面调用。

66.4.2 函数lpcomSendBuf
函数原型:

void lpcomSendBuf(LPCOM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen);

函数描述:

此函数用于向串口发送一组数据,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。

函数参数:

  第1个参数_ucPort是端口号。
  第2个参数_ucaBuf是待发送的数据缓冲区地址。
  第3个参数_usLen是要发送数据的字节数。
注意事项:

此函数的解读在本章66.3.5小节。
发送的数据最好不要超过bsp_lpuart_fifo.h文件中定义的发送缓冲区大小,从而实现最优的工作方式。因为超过后需要在发送函数等待有发送空间可用。
使用举例:

调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。

  1. const char buf1[] = "接收到串口命令1\r\n";
  2. lpcomSendBuf(LPCOM1, (uint8_t *)buf1, strlen(buf1));
复制代码

66.4.3 函数lpcomSendChar
函数原型:

void lpcomSendChar(LPCOM_PORT_E _ucPort, uint8_t _ucByte);

函数描述:

此函数用于向串口发送1个字节,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。此函数是通过调用函数lpcomSendBuf实现的。

函数参数:
  第1个参数_ucPort是端口号。
  第2个参数_ucByte是待发送的数据。
注意事项:

  此函数的解读在本章66.3.2小节。
使用举例:

调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。比如通过串口1发送一个字符c:

lpcomSendChar(LPCOM1, 'c')。

66.4.4 函数lpcomGetChar
函数原型:

uint8_t lpcomGetChar(LPCOM_PORT_E _ucPort, uint8_t *_pByte)

函数描述:

此函数用于从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。

函数参数:

  第1个参数_ucPort是端口号。
  第2个参数_pByte用于存放接收到的数据。
  返回值,返回0表示无数据, 1 表示读取到有效字节。
注意事项:

  此函数的解读在本章66.3.6小节。
使用举例:

调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。

比如从串口1读取一个字符就是:lpcomGetChar(LPCOM1, &read)。

66.5 低功耗串口FIFO驱动移植和使用
串口FIFO移植步骤如下:

  第1步:复制bsp_lpuart_fifo.h和bsp_lpuart_fifo.c到自己的工程目录,并添加到工程里面。
  第2步:根据自己要使用的串口和收发缓冲大小,修改下面的宏定义即可。
  1. #define    LPUART1_FIFO_EN    1

  2. /* 定义串口波特率和FIFO缓冲区大小,分为发送缓冲区和接收缓冲区, 支持全双工 */
  3. #if LPUART1_FIFO_EN == 1
  4.     #define LPUART1_BAUD         115200
  5.     #define LPUART1_TX_BUF_SIZE     1*1024
  6.     #define LPUART1_RX_BUF_SIZE     1*1024
  7. #endif
复制代码

  第3步:这几个驱动文件主要用到HAL库的GPIO和串口驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
  第4步,应用方法看本章节配套例子即可。
66.6 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  第1阶段,上电启动阶段:

这部分在第14章进行了详细说明。
  第2阶段,进入main函数:

第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
第2部分,应用程序设计部分,实现了三种停机唤醒方法。
66.7 实验例程说明(MDK)
配套例子:

V7-046_低功耗串口的停机唤醒(串口FIFO方式)

实验目的:

学习低功耗串口的停机唤醒。
实验内容:

启动一个自动重装软件定时器,每100ms翻转一次LED2。
当前程序使用的串口打印就是用的低功耗串口,即USART1和LPUART1都可以使用PA9和PA10。
上电启动了一个软件定时器,每100ms翻转一次LED2。
USART1和LPUART都可以使用PA9和PA10引脚做串口打印功能,本例子是用的LPUART做开发板串口打印。
LPUART可以选择HSI时钟,LSE时钟和D3PCLK1时钟,在bsp_lpuart_fifo.c文件开头可以配置。如果需要低功耗模式唤醒,必须使用LSE或者HSI时钟,波特率在bsp_lpuart_fifo.h定义,本例子是用的HSI时钟。
LPUART时钟选择LSE(32768Hz),最高速度是10922bps,最低8bps。

LPUART时钟选择HSI(64MHz),最高值是21MHz,最小值15625bps。

LPUART时钟选择D3PCLK1(100MHz),最大值33Mbps,最小值24414bps。

实验操作:

K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒。
K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒。
K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒。
上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
  38.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  39.     bsp_InitTimer();      /* 初始化滴答定时器 */
  40.     bsp_InitLPUart();    /* 初始化串口 */
  41.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  42.     bsp_InitLed();        /* 初始化LED */   
  43.     bsp_InitExtSDRAM(); /* 初始化SDRAM */
  44. }
复制代码


  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.     MPU_InitStruct.BaseAddress      = 0x24000000;
  17.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.     MPU_InitStruct.SubRegionDisable = 0x00;
  25.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.     HAL_MPU_ConfigRegion(&MPU_InitStruct);


  27.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  28.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  29.     MPU_InitStruct.BaseAddress      = 0x60000000;
  30.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  31.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  32.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  33.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  34.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  35.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  36.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  37.     MPU_InitStruct.SubRegionDisable = 0x00;
  38.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  39.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  40.     /*使能 MPU */
  41.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  42. }

  43. /*
  44. *********************************************************************************************************
  45. *    函 数 名: CPU_CACHE_Enable
  46. *    功能说明: 使能L1 Cache
  47. *    形    参: 无
  48. *    返 回 值: 无
  49. *********************************************************************************************************
  50. */
  51. static void CPU_CACHE_Enable(void)
  52. {
  53.     /* 使能 I-Cache */
  54.     SCB_EnableICache();

  55.     /* 使能 D-Cache */
  56.     SCB_EnableDCache();
  57. }
复制代码

  每10ms调用一次蜂鸣器处理:

蜂鸣器处理是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_RunPer10ms
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.     bsp_KeyScan10ms();
  13. }
复制代码

  主功能:

主程序实现如下操作:

启动一个自动重装软件定时器,每100ms翻转一次LED2。
K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒。
K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒。
K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒。
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: c程序入口
  5. *    形    参: 无
  6. *    返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.     uint8_t ucKeyCode;    /* 按键代码 */
  12.     uint8_t ucReceive;


  13.     bsp_Init();        /* 硬件初始化 */
  14.     PrintfLogo();    /* 打印例程名称和版本等信息 */
  15.     PrintfHelp();    /* 打印操作提示 */

  16.     HAL_EnableDBGStopMode(); /* 使能停机模式下,LPUART工程可以继续调试 */
  17.     __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); /* 从停机模式唤醒后使用HSI时钟 */
  18.     __HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停机状态下可以继续接收消息 */
  19.     __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_WUF);/* 使能唤醒中断 */


  20.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */

  21.     while (1)
  22.     {
  23.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  24.         /* 判断定时器超时时间 */
  25.         if (bsp_CheckTimer(0))   
  26.         {
  27.             /* 每隔100ms 进来一次 */  
  28.             bsp_LedToggle(2);
  29.         }

  30.         /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  31.         ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  32.         if (ucKeyCode != KEY_NONE)
  33.         {
  34.             switch (ucKeyCode)
  35.             {
  36.                 case KEY_DOWN_K1:    /* K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒 */
  37.                     /* 使能LPUART的停机唤醒 */
  38.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  39.                     /* 确保LPUART没有在通信中 */
  40.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  41.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  42.                     /* 接收到数据唤醒,即RXNE标志置位 */
  43.                     WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;
  44.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  45.                     {
  46.                         Error_Handler(__FILE__, __LINE__);                        
  47.                     }

  48.                     /* 进入停机模式 */
  49.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  50.                     /* 退出停机模式要重新配置HSE和PLL*/
  51.                     SystemClock_Config();

  52.                     /* 关闭LPUART的停机唤醒 */
  53.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  54.                     lpcomGetChar(LPCOM1, &ucReceive);

  55.                     printf("低功耗串口接收到数据 %x 后唤醒\r\n", ucReceive);
  56.                     break;

  57.                 case KEY_DOWN_K2:     /* K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒 */
  58.                     /* 使能LPUART的停机唤醒 */
  59.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  60.                     /* 确保LPUART没有在通信中 */
  61.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  62.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  63.                     /* 接收起始位唤醒 */
  64.                     WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;
  65.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  66.                     {
  67.                         Error_Handler(__FILE__, __LINE__);                        
  68.                     }

  69.                     /* 进入停机模式 */
  70.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  71.                     /* 退出停机模式要重新配置HSE和PLL*/
  72.                     SystemClock_Config();

  73.                     /* 关闭LPUART的停机唤醒 */
  74.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  75.                     lpcomGetChar(LPCOM1, &ucReceive);

  76.                     printf("低功耗串口检测到起始位(数据) %x 后唤醒\r\n", ucReceive);
  77.                     break;

  78.                 case KEY_DOWN_K3:    /* K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒 */
  79.                     /* 使能LPUART的停机唤醒 */
  80.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  81.                     /* 确保LPUART没有在通信中 */
  82.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  83.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  84.                     /* 接收地址0x99(发送的数据MSB位要为1),可以唤醒 */
  85.                     WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;
  86.                     WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;
  87.                     WakeUpSelection.Address       = 0x19;
  88.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  89.                     {
  90.                         Error_Handler(__FILE__, __LINE__);                        
  91.                     }

  92.                     CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 关闭串口接收中断 */

  93.                     /* 进入停机模式 */
  94.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  95.                     /* 退出停机模式要重新配置HSE和PLL*/
  96.                     SystemClock_Config();

  97.                     SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中断 */

  98.                     /* 关闭LPUART的停机唤醒 */
  99.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  100.                     break;

  101.                 default:
  102.                     /* 其它的键值不处理 */
  103.                     break;
  104.             }
  105.         }
  106.     }
  107. }
复制代码


66.8 实验例程说明(IAR)
配套例子:

V7-046_低功耗串口的停机唤醒(串口FIFO方式)

实验目的:

学习低功耗串口的停机唤醒。
实验内容:

启动一个自动重装软件定时器,每100ms翻转一次LED2。
当前程序使用的串口打印就是用的低功耗串口,即USART1和LPUART1都可以使用PA9和PA10。
上电启动了一个软件定时器,每100ms翻转一次LED2。
USART1和LPUART都可以使用PA9和PA10引脚做串口打印功能,本例子是用的LPUART做开发板串口打印。
LPUART可以选择HSI时钟,LSE时钟和D3PCLK1时钟,在bsp_lpuart_fifo.c文件开头可以配置。如果需要低功耗模式唤醒,必须使用LSE或者HSI时钟,波特率在bsp_lpuart_fifo.h定义,本例子是用的HSI时钟。
LPUART时钟选择LSE(32768Hz),最高速度是10922bps,最低8bps。

LPUART时钟选择HSI(64MHz),最高值是21MHz,最小值15625bps。

LPUART时钟选择D3PCLK1(100MHz),最大值33Mbps,最小值24414bps。

实验操作:

K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒。
K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒。
K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒。
上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
  38.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  39.     bsp_InitTimer();      /* 初始化滴答定时器 */
  40.     bsp_InitLPUart();    /* 初始化串口 */
  41.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  42.     bsp_InitLed();        /* 初始化LED */   
  43.     bsp_InitExtSDRAM(); /* 初始化SDRAM */
  44. }
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.     MPU_InitStruct.BaseAddress      = 0x24000000;
  17.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.     MPU_InitStruct.SubRegionDisable = 0x00;
  25.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.     HAL_MPU_ConfigRegion(&MPU_InitStruct);


  27.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  28.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  29.     MPU_InitStruct.BaseAddress      = 0x60000000;
  30.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  31.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  32.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  33.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  34.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  35.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  36.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  37.     MPU_InitStruct.SubRegionDisable = 0x00;
  38.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  39.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  40.     /*使能 MPU */
  41.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  42. }

  43. /*
  44. *********************************************************************************************************
  45. *    函 数 名: CPU_CACHE_Enable
  46. *    功能说明: 使能L1 Cache
  47. *    形    参: 无
  48. *    返 回 值: 无
  49. *********************************************************************************************************
  50. */
  51. static void CPU_CACHE_Enable(void)
  52. {
  53.     /* 使能 I-Cache */
  54.     SCB_EnableICache();

  55.     /* 使能 D-Cache */
  56.     SCB_EnableDCache();
  57. }

复制代码

  每10ms调用一次蜂鸣器处理:

蜂鸣器处理是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_RunPer10ms
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.     bsp_KeyScan10ms();
  13. }
复制代码

  主功能:

主程序实现如下操作:

启动一个自动重装软件定时器,每100ms翻转一次LED2。
K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒。
K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒。
K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒。
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: c程序入口
  5. *    形    参: 无
  6. *    返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.     uint8_t ucKeyCode;    /* 按键代码 */
  12.     uint8_t ucReceive;


  13.     bsp_Init();        /* 硬件初始化 */
  14.     PrintfLogo();    /* 打印例程名称和版本等信息 */
  15.     PrintfHelp();    /* 打印操作提示 */

  16.     HAL_EnableDBGStopMode(); /* 使能停机模式下,LPUART工程可以继续调试 */
  17.     __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); /* 从停机模式唤醒后使用HSI时钟 */
  18.     __HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停机状态下可以继续接收消息 */
  19.     __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_WUF);/* 使能唤醒中断 */


  20.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */

  21.     while (1)
  22.     {
  23.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  24.         /* 判断定时器超时时间 */
  25.         if (bsp_CheckTimer(0))   
  26.         {
  27.             /* 每隔100ms 进来一次 */  
  28.             bsp_LedToggle(2);
  29.         }

  30.         /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  31.         ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  32.         if (ucKeyCode != KEY_NONE)
  33.         {
  34.             switch (ucKeyCode)
  35.             {
  36.                 case KEY_DOWN_K1:    /* K1键按下,进入停机模式,低功耗串口接收任意字节数据可以唤醒 */
  37.                     /* 使能LPUART的停机唤醒 */
  38.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  39.                     /* 确保LPUART没有在通信中 */
  40.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  41.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  42.                     /* 接收到数据唤醒,即RXNE标志置位 */
  43.                     WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;
  44.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  45.                     {
  46.                         Error_Handler(__FILE__, __LINE__);                        
  47.                     }

  48.                     /* 进入停机模式 */
  49.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  50.                     /* 退出停机模式要重新配置HSE和PLL*/
  51.                     SystemClock_Config();

  52.                     /* 关闭LPUART的停机唤醒 */
  53.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  54.                     lpcomGetChar(LPCOM1, &ucReceive);

  55.                     printf("低功耗串口接收到数据 %x 后唤醒\r\n", ucReceive);
  56.                     break;

  57.                 case KEY_DOWN_K2:     /* K2键按下,进入停机模式,低功耗串口检测到起始位可以唤醒 */
  58.                     /* 使能LPUART的停机唤醒 */
  59.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  60.                     /* 确保LPUART没有在通信中 */
  61.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  62.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  63.                     /* 接收起始位唤醒 */
  64.                     WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;
  65.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  66.                     {
  67.                         Error_Handler(__FILE__, __LINE__);                        
  68.                     }

  69.                     /* 进入停机模式 */
  70.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  71.                     /* 退出停机模式要重新配置HSE和PLL*/
  72.                     SystemClock_Config();

  73.                     /* 关闭LPUART的停机唤醒 */
  74.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  75.                     lpcomGetChar(LPCOM1, &ucReceive);

  76.                     printf("低功耗串口检测到起始位(数据) %x 后唤醒\r\n", ucReceive);
  77.                     break;

  78.                 case KEY_DOWN_K3:    /* K3键按下,进入停机模式,低功耗串口检测到地址0x99可以唤醒 */
  79.                     /* 使能LPUART的停机唤醒 */
  80.                     HAL_UARTEx_EnableStopMode(&UartHandle);

  81.                     /* 确保LPUART没有在通信中 */
  82.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}
  83.                     while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}

  84.                     /* 接收地址0x99(发送的数据MSB位要为1),可以唤醒 */
  85.                     WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;
  86.                     WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;
  87.                     WakeUpSelection.Address       = 0x19;
  88.                     if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)
  89.                     {
  90.                         Error_Handler(__FILE__, __LINE__);                        
  91.                     }

  92.                     CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 关闭串口接收中断 */

  93.                     /* 进入停机模式 */
  94.                     HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

  95.                     /* 退出停机模式要重新配置HSE和PLL*/
  96.                     SystemClock_Config();

  97.                     SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中断 */

  98.                     /* 关闭LPUART的停机唤醒 */
  99.                     HAL_UARTEx_DisableStopMode(&UartHandle);

  100.                     break;

  101.                 default:
  102.                     /* 其它的键值不处理 */
  103.                     break;
  104.             }
  105.         }
  106.     }
  107. }
复制代码


66.9 总结
本章节就为大家讲解这么多, 重点是低功耗串口的三种唤醒方式。



收藏 1 评论0 发布时间:2021-11-2 23:28

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版