完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章节为大家讲解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 总结
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;
}
}
}
}
本章节就为大家讲解这么多, 重点是低功耗串口的三种唤醒方式。