【STM32H7教程】第66章 STM32H7的低功耗串口LPUART應用之串口FIFO和停機喚醒實現
- 2020 年 3 月 8 日
- 筆記
完整教程下載地址: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 初學者重要提示
- 學習本章節前,務必優先學習第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的原理圖:

關於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方式)
實驗目的:
- 學習低功耗串口的停機喚醒。
實驗內容:
- 啟動一個自動重裝軟體定時器,每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。

程式設計:
系統棧大小分配:

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方式)
實驗目的:
- 學習低功耗串口的停機喚醒。
實驗內容:
- 啟動一個自動重裝軟體定時器,每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

程式設計:
系統棧大小分配:

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 總結
本章節就為大家講解這麼多, 重點是低功耗串口的三種喚醒方式。