完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章节为大家讲解STM32H7的低功耗串口FIFO驱动实现和停机唤醒。
目录
第66章 STM32H7的低功耗串口LPUART应用之串口FIFO和停机唤醒实现
66.1 初学者重要提示
66.2 硬件设计
66.3 低功耗串口FIFO驱动设计
66.3.1 低功耗串口FIFO框架
66.3.2 低功耗串口时钟选择
66.3.3 低功耗串口FIFO之相关的变量定义
66.3.4 低功耗串口FIFO初始化
66.3.5 低功耗串口中断服务程序工作流程
66.3.6 低功耗串口数据发送
66.3.7 低功耗串口数据接收
66.3.8 低功耗串口printf实现
66.3.9 低功耗串口停机唤醒方式
66.4 低功耗串口FIFO板级支持包(bsp_lpuart_fifo.c)
66.4.1 函数bsp_InitLPUart
66.4.2 函数lpcomSendBuf
66.4.3 函数lpcomSendChar
66.4.4 函数lpcomGetChar
66.5 低功耗串口FIFO驱动移植和使用
66.6 实验例程设计框架
66.7 实验例程说明(MDK)
66.8 实验例程说明(IAR)
66.9 总结
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个串口设备。
下面是RS232的原理图:
关于232的PHY芯片SP3232E要注意以下几个问题:
实际效果如下:
通过这种方式,可以在应用程序中通过串口发送几个字符,查看是否可以正确接收来判断232 PHY
芯片是否有问题。
为了方便大家理解,先来看下低功耗串口FIFO的实现框图:
第1阶段,初始化:
第2阶段,低功耗串口中断服务程序:
第3阶段,低功耗串口数据的收发:
我们这里实现了三种时钟选择:
最高速度是10922bps,最低8bps(计算方法3x < 32768 < 4096x,x表示波特率)。
最高值是21MHz,最小值15625bps(计算方法3x < 64MHz < 4096x,x表示波特率)。
最大值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 }
低功耗串口驱动的核心文件为: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对应一个写指针和一个读指针。这个结构中还有三个回调函数。回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
低功耗串口的初始化代码如下;
/* ********************************************************************************************************* * 函 数 名: bsp_InitLPUart * 功能说明: 初始化串口硬件,并对全局变量赋初值. * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_InitLPUart(void) { LPUartVarInit(); /* 必须先初始化全局变量,再配置硬件 */ InitHardLPUart(); /* 配置串口的硬件参数(波特率等) */ }
下面将初始化代码实现的功能依次为大家做个说明。
这个函数实现的功能比较好理解,主要是串口设备结构体变量的初始化,代码如下:
/* ********************************************************************************************************* * 函 数 名: 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 }
此函数主要用于串口的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__); } }
串口中断服务程序是最核心的部分,主要实现如下三个功能
下面我们分析一下串口中断处理的完整过程。
当产生串口中断后,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收发器进入到接收状态。
低功耗串口数据的发送主要涉及到下面三个函数:
/* ********************************************************************************************************* * 函 数 名: 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的作用就是把要发送的数据填到发送缓冲区里面,并使能发送空中断。
注意:由于函数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; } }
下面我们再来看看接收的函数:
/* ********************************************************************************************************* * 函 数 名: 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文件中调用。
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函数阻塞和非阻塞方式,如果采用非阻塞方式,执行后会立即返回,串口中断服务程序会陆续将数据发送出去。
低功耗串口的唤醒主要是通过接收数据来唤醒,具体唤醒的方如下:
低功耗串口设置为起始位检测方式如下,并且设置进入停机模式。
如果想唤醒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检测方式如下,并且设置进入停机模式。
如果想唤醒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);/* 使能唤醒中断 */
串口驱动文件bsp_lpuart_fifo.c主要实现了如下几个API供用户调用:
函数原型:
void bsp_InitLPUart(void)
函数描述:
此函数主要用于串口的初始化,使用所有其它API之前,务必优先调用此函数。
使用举例:
串口的初始化函数在bsp.c文件的bsp_Init函数里面调用。
函数原型:
void lpcomSendBuf(LPCOM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen);
函数描述:
此函数用于向串口发送一组数据,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。
函数参数:
注意事项:
使用举例:
调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。
const char buf1[] = "接收到串口命令1\r\n"; lpcomSendBuf(LPCOM1, (uint8_t *)buf1, strlen(buf1));
函数原型:
void lpcomSendChar(LPCOM_PORT_E _ucPort, uint8_t _ucByte);
函数描述:
此函数用于向串口发送1个字节,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。此函数是通过调用函数lpcomSendBuf实现的。
注意事项:
使用举例:
调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。比如通过串口1发送一个字符c:
lpcomSendChar(LPCOM1, 'c')。
函数原型:
uint8_t lpcomGetChar(LPCOM_PORT_E _ucPort, uint8_t *_pByte)
函数描述:
此函数用于从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。
函数参数:
注意事项:
使用举例:
调用此函数前,务必优先调用函数bsp_InitLPUart进行初始化。
比如从串口1读取一个字符就是:lpcomGetChar(LPCOM1, &read)。
串口FIFO移植步骤如下:
#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
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
第2阶段,进入main函数:
配套例子:
V7-046_低功耗串口的停机唤醒(串口FIFO方式)
实验目的:
实验内容:
LPUART时钟选择LSE(32768Hz),最高速度是10922bps,最低8bps。
LPUART时钟选择HSI(64MHz),最高值是21MHz,最小值15625bps。
LPUART时钟选择D3PCLK1(100MHz),最大值33Mbps,最小值24414bps。
实验操作:
上电后串口打印的信息:
波特率 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(); }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: 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 后唤醒\r\n", 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 后唤醒\r\n", 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; } } } }
配套例子:
V7-046_低功耗串口的停机唤醒(串口FIFO方式)
实验目的:
实验内容:
LPUART时钟选择LSE(32768Hz),最高速度是10922bps,最低8bps。
LPUART时钟选择HSI(64MHz),最高值是21MHz,最小值15625bps。
LPUART时钟选择D3PCLK1(100MHz),最大值33Mbps,最小值24414bps。
实验操作:
上电后串口打印的信息:
波特率 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(); }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: 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 后唤醒\r\n", 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 后唤醒\r\n", 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; } } } }
本章节就为大家讲解这么多, 重点是低功耗串口的三种唤醒方式。