【STM32H7教程】第66章 STM32H7的低功耗串口LPUART應用之串口FIFO和停機喚醒實現

完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第66章       STM32H7的低功耗串口LPUART應用之串口FIFO和停機喚醒實現

本章節為大家講解STM32H7的低功耗串口FIFO驅動實現和停機喚醒。

66.1 初學者重要提示

66.2 硬體設計

66.3 串口驅動設計

66.4 串口FIFO板級支援包(bsp_lpuart_fifo.c)

66.5 串口FIFO驅動移植和使用

66.6 實驗常式設計框架

66.7 實驗常式說明(MDK)

66.8 實驗常式說明(IAR)

66.9 總結

66.1 初學者重要提示

  1.   學習本章節前,務必優先學習第65章。
  2.   低功耗串口FIFO的實現跟前面章節通用串口FIFO的機制是一樣的。
  3.   大家自己做的板子,測試串口收發是亂碼的話,重點看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的原理圖:

關於232的PHY晶片SP3232E要注意以下幾個問題:

  •   SP3232E的作用是TTL電平轉RS232電平。
  •   電阻R130的作用是避免CPU複位期間,TX為高阻時串口線上出現異常數據。
  •   檢測SP3232E的好壞可以採用迴環的方式,即短接T1OUT和R1IN,對應到DB9插座上就是短接引腳2和引腳3。

實際效果如下:

通過這種方式,可以在應用程式中通過串口發送幾個字元,查看是否可以正確接收來判斷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的實現框圖:

  第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時鐘,程式程式碼如下,用戶可以根據需要,使能相應的宏定義:

//#define LPUART_CLOCK_SOURCE_LSE  #define LPUART_CLOCK_SOURCE_HSI  //#define LPUART_CLOCK_SOURCE_D3PCLK1    /*  *********************************************************************************************************  *    函 數 名: InitHardLPUart  *    功能說明: 配置串口的硬體參數(波特率,數據位,停止位,起始位,校驗位,中斷使能)適合於STM32-H7開發板  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void InitHardLPUart(void)  {      GPIO_InitTypeDef  GPIO_InitStruct;      RCC_PeriphCLKInitTypeDef   RCC_PeriphCLKInitStruct = {0};    /* 使用LSE(32768Hz),最高速度是10922bps,最低8bps */  #if defined (LPUART_CLOCK_SOURCE_LSE)      {          RCC_OscInitTypeDef RCC_OscInitStruct = {0};            RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;          RCC_OscInitStruct.LSEState = RCC_LSE_ON;          RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;            if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)          {              Error_Handler(__FILE__, __LINE__);          }            RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;          RCC_PeriphCLKInitStruct.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_LSE;          HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);      }  /* LPUART時鐘選擇HSI(64MHz),最高值是21MHz,最小值15625bps */  #elif defined (LPUART_CLOCK_SOURCE_HSI)      {            RCC_OscInitTypeDef RCC_OscInitStruct = {0};              RCC_OscInitStruct.OscillatorType      = RCC_OSCILLATORTYPE_HSI;            RCC_OscInitStruct.HSIState            = RCC_HSI_ON;            RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;            RCC_OscInitStruct.PLL.PLLState        = RCC_PLL_NONE;            if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)          {              Error_Handler(__FILE__, __LINE__);          }            RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;          RCC_PeriphCLKInitStruct.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI;          HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);      }  /* LPUART時鐘選擇D3PCLK1(100MHz),最大值33Mbps,最小值24414bps */  #elif defined (LPUART_CLOCK_SOURCE_D3PCLK1)        RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPUART1;      RCC_PeriphCLKInitStruct.Lptim1ClockSelection = RCC_LPUART1CLKSOURCE_D3PCLK1;      HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);  #else      #error Please select the LPTIM Clock source inside the bsp_lpuart_fifo.c file  #endif    #if LPUART1_FIFO_EN ==      /* 其它省略未寫 */  #endif  }

66.3.3 低功耗串口FIFO之相關的變數定義

低功耗串口驅動的核心文件為:bsp_lpuart_fifo.c, bsp_lpuart_fifo.h。

這裡面包括有串口硬體的配置函數、中斷處理函數,以及串口的讀寫介面函數。還有printf函數的實現。

每個串口都有2個FIFO緩衝區,一個是用於發送數據的TX_FIFO,一個用於保存接收數據的RX_FIFO。

我們來看下這個FIFO的定義,在bsp_lpuart_fifo.h文件。

/* 定義串口波特率和FIFO緩衝區大小,分為發送緩衝區和接收緩衝區, 支援全雙工 */  #if LPUART1_FIFO_EN == 1      #define LPUART1_BAUD         115200      #define LPUART1_TX_BUF_SIZE     1*1024      #define LPUART1_RX_BUF_SIZE     1*1024  #endif    /* 串口設備結構體 */  typedef struct  {      USART_TypeDef *uart;        /* STM32內部串口設備指針 */      uint8_t *pTxBuf;            /* 發送緩衝區 */      uint8_t *pRxBuf;            /* 接收緩衝區 */      uint16_t usTxBufSize;        /* 發送緩衝區大小 */      uint16_t usRxBufSize;        /* 接收緩衝區大小 */      __IO uint16_t usTxWrite;    /* 發送緩衝區寫指針 */      __IO uint16_t usTxRead;        /* 發送緩衝區讀指針 */      __IO uint16_t usTxCount;    /* 等待發送的數據個數 */        __IO uint16_t usRxWrite;    /* 接收緩衝區寫指針 */      __IO uint16_t usRxRead;        /* 接收緩衝區讀指針 */      __IO uint16_t usRxCount;    /* 還未讀取的新數據個數 */        void (*SendBefor)(void);     /* 開始發送之前的回調函數指針(主要用於RS485切換到發送模式) */      void (*SendOver)(void);     /* 發送完畢的回調函數指針(主要用於RS485將發送模式切換為接收模式) */      void (*ReciveNew)(uint8_t _byte);    /* 串口收到數據的回調函數指針 */      uint8_t Sending;            /* 正在發送中 */  }UART_T;

bsp_lpuart_fifo.c文件定義變數。

/* 定義低功耗串口結構體變數 */  #if LPUART1_FIFO_EN == 1      static LPUART_T g_tLPUart1;      static uint8_t g_TxBuf1[LPUART1_TX_BUF_SIZE];        /* 發送緩衝區 */      static uint8_t g_RxBuf1[LPUART1_RX_BUF_SIZE];        /* 接收緩衝區 */  #endif

關於FIFO的機制,我們在按鍵FIFO驅動已經做過詳細的介紹,這個地方就不贅述了。每個串口有兩個FIFO緩衝區,每個FIFO對應一個寫指針和一個讀指針。這個結構中還有三個回調函數。回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。

66.3.4 低功耗串口FIFO初始化

低功耗串口的初始化程式碼如下:

/*  *********************************************************************************************************  *    函 數 名: bsp_InitLPUart  *    功能說明: 初始化串口硬體,並對全局變數賦初值.  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_InitLPUart(void)  {      LPUartVarInit();        /* 必須先初始化全局變數,再配置硬體 */      InitHardLPUart();        /* 配置串口的硬體參數(波特率等) */  }

下面將初始化程式碼實現的功能依次為大家做個說明。

  •   函數LPUartVarInit

這個函數實現的功能比較好理解,主要是串口設備結構體變數的初始化,程式碼如下:

/*  *********************************************************************************************************  *    函 數 名: LPUartVarInit  *    功能說明: 初始化串口相關的變數  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void LPUartVarInit(void)  {  #if LPUART1_FIFO_EN == 1      g_tLPUart1.uart = LPUART1;                        /* STM32 串口設備 */      g_tLPUart1.pTxBuf = g_TxBuf1;                    /* 發送緩衝區指針 */      g_tLPUart1.pRxBuf = g_RxBuf1;                    /* 接收緩衝區指針 */      g_tLPUart1.usTxBufSize = LPUART1_TX_BUF_SIZE;         /* 發送緩衝區大小 */      g_tLPUart1.usRxBufSize = LPUART1_RX_BUF_SIZE;         /* 接收緩衝區大小 */      g_tLPUart1.usTxWrite = 0;                        /* 發送FIFO寫索引 */      g_tLPUart1.usTxRead = 0;                        /* 發送FIFO讀索引 */      g_tLPUart1.usRxWrite = 0;                        /* 接收FIFO寫索引 */      g_tLPUart1.usRxRead = 0;                        /* 接收FIFO讀索引 */      g_tLPUart1.usRxCount = 0;                        /* 接收到的新數據個數 */      g_tLPUart1.usTxCount = 0;                        /* 待發送的數據個數 */      g_tLPUart1.SendBefor = 0;                        /* 發送數據前的回調函數 */      g_tLPUart1.SendOver = 0;                        /* 發送完畢後的回調函數 */      g_tLPUart1.ReciveNew = 0;                        /* 接收到新數據後的回調函數 */      g_tLPUart1.Sending = 0;                             /* 正在發送中標誌 */  #endif  }
  •   函數InitHardLPUart

此函數主要用於串口的GPIO,中斷和相關參數的配置。

/* LPUART1的GPIO  PA9, PA10 */  #define LPUART1_CLK_ENABLE()              __HAL_RCC_LPUART1_CLK_ENABLE()    #define LPUART1_TX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()  #define LPUART1_TX_GPIO_PORT              GPIOA  #define LPUART1_TX_PIN                    GPIO_PIN_9  #define LPUART1_TX_AF                     GPIO_AF3_LPUART    #define LPUART1_RX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()  #define LPUART1_RX_GPIO_PORT              GPIOA  #define LPUART1_RX_PIN                    GPIO_PIN_10  #define LPUART1_RX_AF                     GPIO_AF3_LPUART    /*  *********************************************************************************************************  *    函 數 名: InitHardUart  *    功能說明: 配置串口的硬體參數和底層  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void InitHardUart(void)  {      GPIO_InitTypeDef  GPIO_InitStruct;      RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;        /* 時鐘初始化省略未寫 */  #if LPUART1_FIFO_EN == 1      /* 使能 GPIO TX/RX 時鐘 */      LPUART1_TX_GPIO_CLK_ENABLE();      LPUART1_RX_GPIO_CLK_ENABLE();        /* 使能 USARTx 時鐘 */      LPUART1_CLK_ENABLE();        /* 配置TX引腳 */      GPIO_InitStruct.Pin       = LPUART1_TX_PIN;      GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;      GPIO_InitStruct.Pull      = GPIO_PULLUP;      GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;      GPIO_InitStruct.Alternate = LPUART1_TX_AF;      HAL_GPIO_Init(LPUART1_TX_GPIO_PORT, &GPIO_InitStruct);        /* 配置RX引腳 */      GPIO_InitStruct.Pin = LPUART1_RX_PIN;      GPIO_InitStruct.Alternate = LPUART1_RX_AF;      HAL_GPIO_Init(LPUART1_RX_GPIO_PORT, &GPIO_InitStruct);        /* 配置NVIC the NVIC for UART */      HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 1);      HAL_NVIC_EnableIRQ(LPUART1_IRQn);        /* 配置波特率、奇偶校驗 */      bsp_SetLPUartParam(LPUART1,  LPUART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);        SET_BIT(LPUART1->ICR, USART_ICR_TCCF);   /* 清除TC發送完成標誌 */      SET_BIT(LPUART1->RQR, USART_RQR_RXFRQ);  /* 清除RXNE接收標誌 */      SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中斷 */  #endif  }

低功耗定時器的參數配置API如下:

/*  *********************************************************************************************************  *    函 數 名: bsp_SetLPUartParam  *    功能說明: 配置串口的硬體參數(波特率,數據位,停止位,起始位,校驗位,中斷使能)適合於STM32- H7開發板  *    形    參: Instance   USART_TypeDef類型結構體  *             BaudRate   波特率  *             Parity     校驗類型,奇校驗或者偶校驗  *             Mode       發送和接收模式使能  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_SetLPUartParam(USART_TypeDef *Instance,  uint32_t BaudRate, uint32_t Parity, uint32_t Mode)  {      /*##-1- 配置串口硬體參數 ######################################*/      /* 非同步串口模式 (UART Mode) */      /* 配置如下:        - 字長    = 8 位        - 停止位  = 1 個停止位        - 校驗    = 參數Parity        - 波特率  = 參數BaudRate        - 硬體流控制關閉 (RTS and CTS signals) */        UartHandle.Instance        = Instance;      UartHandle.Init.BaudRate   = BaudRate;      UartHandle.Init.WordLength = UART_WORDLENGTH_8B;      UartHandle.Init.StopBits   = UART_STOPBITS_1;      UartHandle.Init.Parity     = Parity;      UartHandle.Init.HwFlowCtl  = UART_HWCONTROL_NONE;      UartHandle.Init.Mode       = Mode;      UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;      UartHandle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;      UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;        if (HAL_UART_Init(&UartHandle) != HAL_OK)      {          Error_Handler(__FILE__, __LINE__);      }  }

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這個文件。下面展示的程式碼是低功耗串口的中斷服務程式:

#if LPUART1_FIFO_EN == 1  void LPUART1_IRQHandler(void)  {      LPUartIRQ(&g_tLPUart1);  }  #endif

下面,我們來看看UartIRQ函數的實現程式碼。

/*  *********************************************************************************************************  *    函 數 名: UartIRQ  *    功能說明: 供中斷服務程式調用,通用串口中斷處理函數  *    形    參: _pUart : 串口設備  *    返 回 值: 無  *********************************************************************************************************  */  static void UartIRQ(UART_T *_pUart)  {      uint32_t isrflags   = READ_REG(_pUart->uart->ISR);      uint32_t cr1its     = READ_REG(_pUart->uart->CR1);      uint32_t cr3its     = READ_REG(_pUart->uart->CR3);        /* 處理接收中斷  */      if ((isrflags & USART_ISR_RXNE) != RESET)      {          /* 從串口接收數據暫存器讀取數據存放到接收FIFO */          uint8_t ch;            ch = READ_REG(_pUart->uart->RDR);               /* 讀串口接收數據暫存器 */          _pUart->pRxBuf[_pUart->usRxWrite] = ch;         /* 填入串口接收FIFO */          if (++_pUart->usRxWrite >= _pUart->usRxBufSize) /* 接收FIFO的寫指針+1 */          {              _pUart->usRxWrite = 0;          }          if (_pUart->usRxCount < _pUart->usRxBufSize)    /* 統計未處理的位元組個數 */          {              _pUart->usRxCount++;          }            /* 回調函數,通知應用程式收到新數據,一般是發送1個消息或者設置一個標記 */          //if (_pUart->usRxWrite == _pUart->usRxRead)          //if (_pUart->usRxCount == 1)          {              if (_pUart->ReciveNew)              {                  _pUart->ReciveNew(ch); /* 比如,交給MODBUS解碼程式處理位元組流 */              }          }      }        /* 處理髮送緩衝區空中斷 */      if ( ((isrflags & USART_ISR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET)      {          //if (_pUart->usTxRead == _pUart->usTxWrite)          if (_pUart->usTxCount == 0)  /* 發送緩衝區已無數據可取 */          {          /* 發送緩衝區的數據已取完時, 禁止發送緩衝區空中斷 (注意:此時最後1個數據還未真正發送完畢)*/              //USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);              CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);                /* 使能數據發送完畢中斷 */              //USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);              SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE);          }          Else  /* 還有數據等待發送 */          {              _pUart->Sending = 1;                /* 從發送FIFO取1個位元組寫入串口發送數據暫存器 */              //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);              _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];              if (++_pUart->usTxRead >= _pUart->usTxBufSize)              {                  _pUart->usTxRead = 0;              }              _pUart->usTxCount--;          }        }      /* 數據bit位全部發送完畢的中斷 */      if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))      {          //if (_pUart->usTxRead == _pUart->usTxWrite)          if (_pUart->usTxCount == 0)          {              /* 如果發送FIFO的數據全部發送完畢,禁止數據發送完畢中斷 */              //USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE);              CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE);                /* 回調函數, 一般用來處理RS485通訊,將RS485晶片設置為接收模式,避免搶佔匯流排 */              if (_pUart->SendOver)              {                  _pUart->SendOver();              }                _pUart->Sending = 0;          }          else          {              /* 正常情況下,不會進入此分支 */                /* 如果發送FIFO的數據還未完畢,則從發送FIFO取1個數據寫入發送數據暫存器 */              //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);              _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];              if (++_pUart->usTxRead >= _pUart->usTxBufSize)              {                  _pUart->usTxRead = 0;              }              _pUart->usTxCount--;          }      }        /* 清除中斷標誌 */      SET_BIT(_pUart->uart->ICR, UART_CLEAR_PEF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_FEF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_NEF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_OREF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_IDLEF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_TCF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_LBDF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_CTSF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_CMF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_WUF);      SET_BIT(_pUart->uart->ICR, UART_CLEAR_TXFECF);  }

中斷服務程式的處理主要分為兩部分,接收數據的處理和發送數據的處理,詳情看程式注釋即可,已經比較詳細,下面重點把思路說一下。

  •   接收數據處理

接收數據的處理是判斷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 低功耗串口數據發送

低功耗串口數據的發送主要涉及到下面三個函數:

/*  *********************************************************************************************************  *    函 數 名: lpcomSendBuf  *    功能說明: 向串口發送一組數據。數據放到發送緩衝區後立即返回,由中斷服務程式在後台完成發送  *    形    參: _ucPort: 埠號(LPCOM1)  *              _ucaBuf: 待發送的數據緩衝區  *              _usLen : 數據長度  *    返 回 值: 無  *********************************************************************************************************  */  void lpcomSendBuf(LPCOM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen)  {      LPUART_T *pUart;        pUart = ComToLPUart(_ucPort);      if (pUart == 0)      {          return;      }        if (pUart->SendBefor != 0)      {          pUart->SendBefor();        /* 如果是RS485通訊,可以在這個函數中將RS485設置為發送模式 */      }        LPUartSend(pUart, _ucaBuf, _usLen);  }    /*  *********************************************************************************************************  *    函 數 名: lpcomSendChar  *    功能說明: 向串口發送1個位元組。數據放到發送緩衝區後立即返回,由中斷服務程式在後台完成發送  *    形    參: _ucPort: 埠號(LPCOM1)  *              _ucByte: 待發送的數據  *    返 回 值: 無  *********************************************************************************************************  */  void lpcomSendChar(LPCOM_PORT_E _ucPort, uint8_t _ucByte)  {      lpcomSendBuf(_ucPort, &_ucByte, 1);  }    /*  *********************************************************************************************************  *    函 數 名: LPUartSend  *    功能說明: 填寫數據到UART發送緩衝區,並啟動發送中斷。中斷處理函數發送完畢後,自動關閉發送中斷  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void LPUartSend(LPUART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)  {      uint16_t i;        for (i = 0; i < _usLen; i++)      {          /* 如果發送緩衝區已經滿了,則等待緩衝區空 */          while (1)          {              __IO uint16_t usCount;                DISABLE_INT();              usCount = _pUart->usTxCount;              ENABLE_INT();                if (usCount < _pUart->usTxBufSize)              {                  break;              }              else if(usCount == _pUart->usTxBufSize)/* 數據已填滿緩衝區 */              {                  if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0)                  {                      SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);                  }              }          }            /* 將新數據填入發送緩衝區 */          _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i];            DISABLE_INT();          if (++_pUart->usTxWrite >= _pUart->usTxBufSize)          {              _pUart->usTxWrite = 0;          }          _pUart->usTxCount++;          ENABLE_INT();      }        SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);    /* 使能發送中斷(緩衝區空) */  }

函數lpcomSendChar是發送一個位元組,通過調用函數lpcomSendBuf實現,而函數lpcomSendBuf又是通過調用函數LPUartSend實現,這個函數是重點。

函數LPUartSend的作用就是把要發送的數據填到發送緩衝區裡面,並使能發送空中斷。

  •   如果要發送的數據沒有超過發送緩衝區大小,實現起來還比較容易,直接把數據填到FIFO裡面,並使能發送空中斷即可。
  •   如果超過了FIFO大小,就需要等待有空間可用,針對這種情況有個重要的知識點,就是當緩衝剛剛填滿的時候要判斷發送空中斷是否開啟了,如果填滿了還沒有開啟,就會卡死在while循環中,所以多了一個剛填滿時的判斷,填滿了還沒有開啟發送空中斷,要開啟下。

注意:由於函數LPUartSend做了static作用域限制,僅可在bsp_lpuart_fifo.c文件中調用。函數lpcomSendChar和lpcomSendBuf是供用戶調用的。

函數lpcomSendBuf中調用了一個函數pUart = ComToLPUart(_ucPort),這個函數是將整數的COM埠號轉換為LPUART結構體指針。

/*  *********************************************************************************************************  *    函 數 名: ComToLPUart  *    功能說明: 將COM埠號轉換為LPUART指針  *    形    參: _ucPort: 埠號(LPCOM1)  *    返 回 值: uart指針  *********************************************************************************************************  */  LPUART_T *ComToLPUart(LPCOM_PORT_E _ucPort)  {      if (_ucPort == LPCOM1)      {          #if LPUART1_FIFO_EN == 1              return &g_tLPUart1;          #else              return 0;          #endif      }      else      {          Error_Handler(__FILE__, __LINE__);          return 0;      }  }

66.3.7 低功耗串口數據接收

下面我們再來看看接收的函數:

/*  *********************************************************************************************************  *    函 數 名: lpcomGetChar  *    功能說明: 從接收緩衝區讀取1位元組,非阻塞。無論有無數據均立即返回。  *    形    參: _ucPort: 埠號(LPCOM1)  *              _pByte: 接收到的數據存放在這個地址  *    返 回 值: 0 表示無數據, 1 表示讀取到有效位元組  *********************************************************************************************************  */  uint8_t lpcomGetChar(LPCOM_PORT_E _ucPort, uint8_t *_pByte)  {      LPUART_T *pUart;        pUart = ComToLPUart(_ucPort);      if (pUart == 0)      {          return 0;      }        return LPUartGetChar(pUart, _pByte);  }    /*  *********************************************************************************************************  *    函 數 名: LPUartGetChar  *    功能說明: 從串口接收緩衝區讀取1位元組數據 (用於主程式調用)  *    形    參: _pUart : 串口設備  *              _pByte : 存放讀取數據的指針  *    返 回 值: 0 表示無數據  1表示讀取到數據  *********************************************************************************************************  */  static uint8_t LPUartGetChar(LPUART_T *_pUart, uint8_t *_pByte)  {      uint16_t usCount;        /* usRxWrite 變數在中斷函數中被改寫,主程式讀取該變數時,必須進行臨界區保護 */      DISABLE_INT();      usCount = _pUart->usRxCount;      ENABLE_INT();        /* 如果讀和寫索引相同,則返回0 */      //if (_pUart->usRxRead == usRxWrite)      if (usCount == 0)    /* 已經沒有數據 */      {          return 0;      }      else      {          *_pByte = _pUart->pRxBuf[_pUart->usRxRead];        /* 從串口接收FIFO取1個數據 */            /* 改寫FIFO讀索引 */          DISABLE_INT();          if (++_pUart->usRxRead >= _pUart->usRxBufSize)          {              _pUart->usRxRead = 0;          }          _pUart->usRxCount--;          ENABLE_INT();          return 1;      }  }

函數lpcomGetChar是專門供用戶調用的,用於從接收FIFO中讀取1個數據。具體程式碼的實現也比較好理解,主要是接收FIFO的空間調整。

注意:由於函數LPUartGetChar做了static作用域限制,僅可在bsp_lpuart_fifo.c文件中調用。

66.3.8 低功耗串口printf實現

printf函數是標準c庫函數。最原來的意思是列印輸出到顯示器。在單片機,我們常用它來列印調試資訊到串口,通過電腦上運行的串口軟體來監視程式的運行狀態。

為什麼要用printf函數,而不用串口發送的函數。因為printf函數的形參功能很強大,它支援各種數值轉換。比如將整數、浮點數轉換為字元串,支援整數左對齊、右對齊顯示等。

我們設計的很多裸機例子都是用printf函數輸出運行結果的。因為如果加上顯示器驅動後,會將程式搞的很複雜,顯示部分的程式碼量超過了常式本身要演示的核心功能程式碼。用串口做輸出,移植很方便,現在很少有不帶串口的單片機。

實現printf輸出到串口,只需要在工程中添加兩個函數:

/*  *********************************************************************************************************  *    函 數 名: fputc  *    功能說明: 重定義putc函數,這樣可以使用printf函數從串口1列印輸出  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  int fputc(int ch, FILE *f)  {  #if 0    /* 將需要printf的字元通過串口中斷FIFO發送出去,printf函數會立即返回 */      lpcomSendChar(LPCOM1, ch);        return ch;  #else    /* 採用阻塞方式發送每個字元,等待數據發送完畢 */      /* 寫一個位元組到USART1 */      LPUART1->TDR = ch;        /* 等待發送結束 */      while((LPUART1->ISR & USART_ISR_TC) == 0)      {}        return ch;  #endif  }    /*  *********************************************************************************************************  *    函 數 名: fgetc  *    功能說明: 重定義getc函數,這樣可以使用getchar函數從串口1輸入數據  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  int fgetc(FILE *f)  {    #if 1    /* 從串口接收FIFO中取1個數據, 只有取到數據才返回 */      uint8_t ucData;        while(lpcomGetChar(LPCOM1, &ucData) == 0);        return ucData;  #else      /* 等待接收到數據 */      while((LPUART1->ISR & USART_ISR_RXNE) == 0)      {}        return (int)LPUART1->RDR;  #endif  }

通過上面程式碼中的條件編譯,可以設置printf函數阻塞和非阻塞方式,如果採用非阻塞方式,執行後會立即返回,串口中斷服務程式會陸續將數據發送出去。

66.3.9 低功耗串口停機喚醒方式

低功耗串口的喚醒主要是通過接收數據來喚醒,具體喚醒的方如下:

  •   檢測到起始位喚醒。

低功耗串口設置為起始位檢測方式如下,並且設置進入停機模式。

如果想喚醒H7,發一個起始位即可,簡單些也可以任意發送一個數據:

/* 使能LPUART的停機喚醒 */  HAL_UARTEx_EnableStopMode(&UartHandle);    /* 確保LPUART沒有在通訊中 */  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}    /* 接收起始位喚醒 */  WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;  if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)  {      Error_Handler(__FILE__, __LINE__);  }    /* 進入停機模式 */  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);    /* 退出停機模式要重新配置HSE和PLL*/  SystemClock_Config();    /* 關閉LPUART的停機喚醒 */  HAL_UARTEx_DisableStopMode(&UartHandle);
  •   檢測到RXNE標誌喚醒,即接收到數據。

低功耗串口設置為RXNE檢測方式如下,並且設置進入停機模式。

如果想喚醒H7,發一個任意數據即可。

/* 使能LPUART的停機喚醒 */  HAL_UARTEx_EnableStopMode(&UartHandle);    /* 確保LPUART沒有在通訊中 */  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}    /* 接收到數據喚醒,即RXNE標誌置位 */  WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;  if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)  {      Error_Handler(__FILE__, __LINE__);  }    /* 進入停機模式 */  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);    /* 退出停機模式要重新配置HSE和PLL*/  SystemClock_Config();    /* 關閉LPUART的停機喚醒 */  HAL_UARTEx_DisableStopMode(&UartHandle);
  •   檢測到匹配地址時喚醒。

低功耗串口設置為地址匹配檢測方式如下,並且設置進入停機模式。

如果想喚醒H7,必須發送指定的匹配地址。匹配地址支援7bit和4bit匹配兩種方式,比如我們採用7bit匹配,設置地址是0x19,那麼用戶喚醒的時候要將最高bit設置為1,即發生地址0x99(0b1001 1001)才可以喚醒。

/* 使能LPUART的停機喚醒 */  HAL_UARTEx_EnableStopMode(&UartHandle);    /* 確保LPUART沒有在通訊中 */  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}  while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}    /* 接收地址0x99(發送的數據MSB位要為1),可以喚醒 */  WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;  WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;  WakeUpSelection.Address       = 0x19;  if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)  {      Error_Handler(__FILE__, __LINE__);  }    CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 關閉串口接收中斷 */    /* 進入停機模式 */  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);    /* 退出停機模式要重新配置HSE和PLL*/  SystemClock_Config();    SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中斷 */    /* 關閉LPUART的停機喚醒 */  HAL_UARTEx_DisableStopMode(&UartHandle);

這裡有一點要特別注意,程式啟動後,調用下面兩個函數:

__HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停機狀態下可以繼續接收消息 */  __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進行初始化。

const char buf1[] = "接收到串口命令1rn";  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步:根據自己要使用的串口和收發緩衝大小,修改下面的宏定義即可。
#define    LPUART1_FIFO_EN    1    /* 定義串口波特率和FIFO緩衝區大小,分為發送緩衝區和接收緩衝區, 支援全雙工 */  #if LPUART1_FIFO_EN == 1      #define LPUART1_BAUD         115200      #define LPUART1_TX_BUF_SIZE     1*1024      #define LPUART1_RX_BUF_SIZE     1*1024  #endif
  •   第3步:這幾個驅動文件主要用到HAL庫的GPIO和串口驅動文件,簡單省事些可以添加所有HAL庫.C源文件進來。
  •   第4步,應用方法看本章節配套例子即可。

66.6 實驗常式設計框架

通過程式設計框架,讓大家先對配套常式有一個全面的認識,然後再理解細節,本次實驗常式的設計框架如下:

  第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

  第2階段,進入main函數:

  • 第1部分,硬體初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
  • 第2部分,應用程式設計部分,實現了三種停機喚醒方法。

66.7 實驗常式說明(MDK)

配套例子:

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

實驗目的:

  1. 學習低功耗串口的停機喚醒。

實驗內容:

  1. 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  2. 當前程式使用的串口列印就是用的低功耗串口,即USART1和LPUART1都可以使用PA9和PA10。
  3. 上電啟動了一個軟體定時器,每100ms翻轉一次LED2。
  4. USART1和LPUART都可以使用PA9和PA10引腳做串口列印功能,本例子是用的LPUART做開發板串口列印。
  5. 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。

實驗操作:

  1. K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒。
  2. K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒。
  3. K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒。

上電後串口列印的資訊:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1。

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 文件實現:

/*  *********************************************************************************************************  *    函 數 名: bsp_Init  *    功能說明: 初始化所有的硬體設備。該函數配置CPU暫存器和外設的暫存器並初始化一些全局變數。只需要調用一次  *    形    參:無  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_Init(void)  {      /* 配置MPU */      MPU_Config();        /* 使能L1 Cache */      CPU_CACHE_Enable();        /*         STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:         - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。         - 設置NVIV優先順序分組為4。       */      HAL_Init();        /*         配置系統時鐘到400MHz         - 切換使用HSE。         - 此函數會更新全局變數SystemCoreClock,並重新配置HAL_InitTick。      */      SystemClock_Config();        /*         Event Recorder:         - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。         - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章      */  #if Enable_EventRecorder == 1      /* 初始化EventRecorder並開啟 */      EventRecorderInitialize(EventRecordAll, 1U);      EventRecorderStart();  #endif    bsp_InitDWT();      /* 初始化DWT時鐘周期計數器 */      bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */      bsp_InitTimer();      /* 初始化滴答定時器 */      bsp_InitLPUart();    /* 初始化串口 */      bsp_InitExtIO();    /* 初始化FMC匯流排74HC574擴展IO. 必須在 bsp_InitLed()前執行 */      bsp_InitLed();        /* 初始化LED */      bsp_InitExtSDRAM(); /* 初始化SDRAM */  }

MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴展IO區。

/*  *********************************************************************************************************  *    函 數 名: MPU_Config  *    功能說明: 配置MPU  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void MPU_Config( void )  {      MPU_Region_InitTypeDef MPU_InitStruct;        /* 禁止 MPU */      HAL_MPU_Disable();        /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */      MPU_InitStruct.Enable           = MPU_REGION_ENABLE;      MPU_InitStruct.BaseAddress      = 0x24000000;      MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;      MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;      MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;      MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;      MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;      MPU_InitStruct.Number           = MPU_REGION_NUMBER0;      MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;      MPU_InitStruct.SubRegionDisable = 0x00;      MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        HAL_MPU_ConfigRegion(&MPU_InitStruct);          /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */      MPU_InitStruct.Enable           = MPU_REGION_ENABLE;      MPU_InitStruct.BaseAddress      = 0x60000000;      MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;      MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;      MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;      MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;      MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;      MPU_InitStruct.Number           = MPU_REGION_NUMBER1;      MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;      MPU_InitStruct.SubRegionDisable = 0x00;      MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        HAL_MPU_ConfigRegion(&MPU_InitStruct);        /*使能 MPU */      HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);  }    /*  *********************************************************************************************************  *    函 數 名: CPU_CACHE_Enable  *    功能說明: 使能L1 Cache  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void CPU_CACHE_Enable(void)  {      /* 使能 I-Cache */      SCB_EnableICache();        /* 使能 D-Cache */      SCB_EnableDCache();  }

每10ms調用一次蜂鳴器處理:

蜂鳴器處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*  *********************************************************************************************************  *    函 數 名: bsp_RunPer10ms  *    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求  *              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_RunPer10ms(void)  {      bsp_KeyScan10ms();  }

主功能:

主程式實現如下操作:

  • 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  • K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒。
  • K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒。
  • K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒。
/*  *********************************************************************************************************  *    函 數 名: main  *    功能說明: c程式入口  *    形    參: 無  *    返 回 值: 錯誤程式碼(無需處理)  *********************************************************************************************************  */  int main(void)  {      uint8_t ucKeyCode;    /* 按鍵程式碼 */      uint8_t ucReceive;          bsp_Init();        /* 硬體初始化 */      PrintfLogo();    /* 列印常式名稱和版本等資訊 */      PrintfHelp();    /* 列印操作提示 */        HAL_EnableDBGStopMode(); /* 使能停機模式下,LPUART工程可以繼續調試 */      __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); /* 從停機模式喚醒後使用HSI時鐘 */      __HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停機狀態下可以繼續接收消息 */      __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_WUF);/* 使能喚醒中斷 */          bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重裝的定時器 */        while (1)      {          bsp_Idle();        /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */            /* 判斷定時器超時時間 */          if (bsp_CheckTimer(0))          {              /* 每隔100ms 進來一次 */              bsp_LedToggle(2);          }            /* 按鍵濾波和檢測由後台systick中斷服務程式實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */          ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */          if (ucKeyCode != KEY_NONE)          {              switch (ucKeyCode)              {                  case KEY_DOWN_K1:    /* K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收到數據喚醒,即RXNE標誌置位 */                      WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        lpcomGetChar(LPCOM1, &ucReceive);                        printf("低功耗串口接收到數據 %x 後喚醒rn", ucReceive);                      break;                    case KEY_DOWN_K2:     /* K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收起始位喚醒 */                      WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        lpcomGetChar(LPCOM1, &ucReceive);                        printf("低功耗串口檢測到起始位(數據) %x 後喚醒rn", ucReceive);                      break;                    case KEY_DOWN_K3:    /* K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收地址0x99(發送的數據MSB位要為1),可以喚醒 */                      WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;                      WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;                      WakeUpSelection.Address       = 0x19;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 關閉串口接收中斷 */                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中斷 */                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        break;                    default:                      /* 其它的鍵值不處理 */                      break;              }          }      }  }

66.8 實驗常式說明(IAR)

配套例子:

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

實驗目的:

  1. 學習低功耗串口的停機喚醒。

實驗內容:

  1. 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  2. 當前程式使用的串口列印就是用的低功耗串口,即USART1和LPUART1都可以使用PA9和PA10。
  3. 上電啟動了一個軟體定時器,每100ms翻轉一次LED2。
  4. USART1和LPUART都可以使用PA9和PA10引腳做串口列印功能,本例子是用的LPUART做開發板串口列印。
  5. 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。

實驗操作:

  1. K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒。
  2. K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒。
  3. K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒。

上電後串口列印的資訊:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 文件實現:

/*  *********************************************************************************************************  *    函 數 名: bsp_Init  *    功能說明: 初始化所有的硬體設備。該函數配置CPU暫存器和外設的暫存器並初始化一些全局變數。只需要調用一次  *    形    參:無  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_Init(void)  {      /* 配置MPU */      MPU_Config();        /* 使能L1 Cache */      CPU_CACHE_Enable();        /*         STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:         - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。         - 設置NVIV優先順序分組為4。       */      HAL_Init();        /*         配置系統時鐘到400MHz         - 切換使用HSE。         - 此函數會更新全局變數SystemCoreClock,並重新配置HAL_InitTick。      */      SystemClock_Config();        /*         Event Recorder:         - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。         - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章      */  #if Enable_EventRecorder == 1      /* 初始化EventRecorder並開啟 */      EventRecorderInitialize(EventRecordAll, 1U);      EventRecorderStart();  #endif    bsp_InitDWT();      /* 初始化DWT時鐘周期計數器 */      bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */      bsp_InitTimer();      /* 初始化滴答定時器 */      bsp_InitLPUart();    /* 初始化串口 */      bsp_InitExtIO();    /* 初始化FMC匯流排74HC574擴展IO. 必須在 bsp_InitLed()前執行 */      bsp_InitLed();        /* 初始化LED */      bsp_InitExtSDRAM(); /* 初始化SDRAM */  }

MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴展IO區。

/*  *********************************************************************************************************  *    函 數 名: MPU_Config  *    功能說明: 配置MPU  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void MPU_Config( void )  {      MPU_Region_InitTypeDef MPU_InitStruct;        /* 禁止 MPU */      HAL_MPU_Disable();        /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */      MPU_InitStruct.Enable           = MPU_REGION_ENABLE;      MPU_InitStruct.BaseAddress      = 0x24000000;      MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;      MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;      MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;      MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;      MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;      MPU_InitStruct.Number           = MPU_REGION_NUMBER0;      MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;      MPU_InitStruct.SubRegionDisable = 0x00;      MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        HAL_MPU_ConfigRegion(&MPU_InitStruct);          /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */      MPU_InitStruct.Enable           = MPU_REGION_ENABLE;      MPU_InitStruct.BaseAddress      = 0x60000000;      MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;      MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;      MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;      MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;      MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;      MPU_InitStruct.Number           = MPU_REGION_NUMBER1;      MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;      MPU_InitStruct.SubRegionDisable = 0x00;      MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;        HAL_MPU_ConfigRegion(&MPU_InitStruct);        /*使能 MPU */      HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);  }    /*  *********************************************************************************************************  *    函 數 名: CPU_CACHE_Enable  *    功能說明: 使能L1 Cache  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  static void CPU_CACHE_Enable(void)  {      /* 使能 I-Cache */      SCB_EnableICache();        /* 使能 D-Cache */      SCB_EnableDCache();  }

每10ms調用一次蜂鳴器處理:

蜂鳴器處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*  *********************************************************************************************************  *    函 數 名: bsp_RunPer10ms  *    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求  *              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。  *    形    參: 無  *    返 回 值: 無  *********************************************************************************************************  */  void bsp_RunPer10ms(void)  {      bsp_KeyScan10ms();  }

主功能:

主程式實現如下操作:

  •  啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  •  K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒。
  •  K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒。
  •  K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒。
/*  *********************************************************************************************************  *    函 數 名: main  *    功能說明: c程式入口  *    形    參: 無  *    返 回 值: 錯誤程式碼(無需處理)  *********************************************************************************************************  */  int main(void)  {      uint8_t ucKeyCode;    /* 按鍵程式碼 */      uint8_t ucReceive;          bsp_Init();        /* 硬體初始化 */      PrintfLogo();    /* 列印常式名稱和版本等資訊 */      PrintfHelp();    /* 列印操作提示 */        HAL_EnableDBGStopMode(); /* 使能停機模式下,LPUART工程可以繼續調試 */      __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); /* 從停機模式喚醒後使用HSI時鐘 */      __HAL_RCC_LPUART1_CLKAM_ENABLE();     /* 激活LPUART的自主模式,即停機狀態下可以繼續接收消息 */      __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_WUF);/* 使能喚醒中斷 */          bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重裝的定時器 */        while (1)      {          bsp_Idle();        /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */            /* 判斷定時器超時時間 */          if (bsp_CheckTimer(0))          {              /* 每隔100ms 進來一次 */              bsp_LedToggle(2);          }            /* 按鍵濾波和檢測由後台systick中斷服務程式實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */          ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */          if (ucKeyCode != KEY_NONE)          {              switch (ucKeyCode)              {                  case KEY_DOWN_K1:    /* K1鍵按下,進入停機模式,低功耗串口接收任意位元組數據可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收到數據喚醒,即RXNE標誌置位 */                      WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        lpcomGetChar(LPCOM1, &ucReceive);                        printf("低功耗串口接收到數據 %x 後喚醒rn", ucReceive);                      break;                    case KEY_DOWN_K2:     /* K2鍵按下,進入停機模式,低功耗串口檢測到起始位可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收起始位喚醒 */                      WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_STARTBIT;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        lpcomGetChar(LPCOM1, &ucReceive);                        printf("低功耗串口檢測到起始位(數據) %x 後喚醒rn", ucReceive);                      break;                    case KEY_DOWN_K3:    /* K3鍵按下,進入停機模式,低功耗串口檢測到地址0x99可以喚醒 */                      /* 使能LPUART的停機喚醒 */                      HAL_UARTEx_EnableStopMode(&UartHandle);                        /* 確保LPUART沒有在通訊中 */                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_BUSY) == SET){}                      while(__HAL_UART_GET_FLAG(&UartHandle, USART_ISR_REACK) == RESET){}                        /* 接收地址0x99(發送的數據MSB位要為1),可以喚醒 */                      WakeUpSelection.WakeUpEvent   = UART_WAKEUP_ON_ADDRESS;                      WakeUpSelection.AddressLength = UART_ADDRESS_DETECT_7B;                      WakeUpSelection.Address       = 0x19;                      if (HAL_UARTEx_StopModeWakeUpSourceConfig(&UartHandle, WakeUpSelection)!= HAL_OK)                      {                          Error_Handler(__FILE__, __LINE__);                      }                        CLEAR_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 關閉串口接收中斷 */                        /* 進入停機模式 */                      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);                        /* 退出停機模式要重新配置HSE和PLL*/                      SystemClock_Config();                        SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE);  /* 使能串口接收中斷 */                        /* 關閉LPUART的停機喚醒 */                      HAL_UARTEx_DisableStopMode(&UartHandle);                        break;                    default:                      /* 其它的鍵值不處理 */                      break;              }          }      }  }

66.9 總結

本章節就為大家講解這麼多, 重點是低功耗串口的三種喚醒方式。