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

第30章       STM32H7的USART应用之八个串口FIFO实现

本章节为大家讲解STM327的8个串口的FIFO驱动实现,后面的ESP8266,GPS,RS485,GPRS等试验都是建立在这个驱动的基础上实现。

除了串口FIFO的驱动实现,RS232通信也通过本章节做个讲解。

30.1 初学者重要提示

30.2 硬件设计

30.3 串口驱动设计

30.4 串口FIFO板级支持包(bsp_uart_fifo.c)

30.5 串口FIFO驱动移植和使用

30.6 实验例程设计框架

30.7 实验例程说明(MDK)

30.8 实验例程说明(IAR)

30.9 总结

30.1 初学者重要提示

  1. 学习本章节前,务必优先学习第29章。
  2. 串口FIFO的实现跟前面章节按键FIFO的机制是一样的。
  3. 本章节比较重要,因为后面的ESP8266,GPS,RS485,GPRS等试验都是建立在这个驱动的基础上实现。
  4. 大家自己做的板子,测试串口收发是乱码的话,重点看stm32h7xx_hal_conf.h文件中的HSE_VALUE的大小跟板子上实际晶振大小是否一致,然后再看PLL配置。
  5. CH340/CH341的USB转串口Windows驱动程序的安装包,支持32/64位 Windows 10/8.1/8/7。http://www.armbbs.cn/forum.php?mod=viewthread&tid=32826

30.2 硬件设计

STM32H743XIH6最多可以支持8个独立的串口。其中串口4和串口5和SDIO的GPIO是共用的,也就是说,如果要用到SD卡,那么串口4和串口5将不能使用。串口7和SPI3共用,串口8和RGB硬件接口共用。串口功能可以分配到不同的GPIO。我们常用的引脚分配如下:

串口USART1  TX = PA9,   RX = PA10

串口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脚,然后使用串口助手进行自收发测试即可。

30.3 串口FIFO驱动设计

30.3.1 串口FIFO框架

为了方便大家理解,先来看下串口FIFO的实现框图:

  第1阶段,初始化:

  • 通过函数bsp_InitUart初始化串口结构体,串口硬件参数。

  第2阶段,串口中断服务程序:

  • 接收中断是一直开启的。
  • 做了发送空中断和发送完成中断的消息处理。

  第3阶段,串口数据的收发:

  • 串口发送函数会开启发送空中断。
  • 串口接收中断接收到函数后,可以使用函数comGetChar获取数据。

30.3.2 串口FIFO之相关的变量定义

串口驱动的核心文件为:bsp_uart_fifo.c, bsp_uart_fifo.h。

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

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

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

/* 定义串口波特率和FIFO缓冲区大小,分为发送缓冲区和接收缓冲区, 支持全双工 */
#if UART1_FIFO_EN == 1
#define UART1_BAUD 115200
#define UART1_TX_BUF_SIZE 1*1024
#define UART1_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_uart_fifo.c文件定义变量。我们以串口1为例,其他的串口都是一样的代码。

/* 定义每个串口结构体变量 */
#if UART1_FIFO_EN == 1
static UART_T g_tUart1;
static uint8_t g_TxBuf1[UART1_TX_BUF_SIZE]; /* 发送缓冲区 */
static uint8_t g_RxBuf1[UART1_RX_BUF_SIZE]; /* 接收缓冲区 */
#endif

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

30.3.3 串口FIFO初始化

串口的初始化代码如下;

/*
*********************************************************************************************************
* 函 数 名: bsp_InitUart
* 功能说明: 初始化串口硬件,并对全局变量赋初值.
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitUart(void)
{ UartVarInit(); /* 必须先初始化全局变量,再配置硬件 */ InitHardUart(); /* 配置串口的硬件参数(波特率等) */ RS485_InitTXE(); /* 配置RS485芯片的发送使能硬件,配置为推挽输出 */
}

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

  •   函数UartVarInit

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

/*
*********************************************************************************************************
* 函 数 名: UartVarInit
* 功能说明: 初始化串口相关的变量
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void UartVarInit(void)
{
#if UART1_FIFO_EN == 1
g_tUart1.uart = USART1; /* STM32 串口设备 */
g_tUart1.pTxBuf = g_TxBuf1; /* 发送缓冲区指针 */
g_tUart1.pRxBuf = g_RxBuf1; /* 接收缓冲区指针 */
g_tUart1.usTxBufSize = UART1_TX_BUF_SIZE; /* 发送缓冲区大小 */
g_tUart1.usRxBufSize = UART1_RX_BUF_SIZE; /* 接收缓冲区大小 */
g_tUart1.usTxWrite = ; /* 发送FIFO写索引 */
g_tUart1.usTxRead = ; /* 发送FIFO读索引 */
g_tUart1.usRxWrite = ; /* 接收FIFO写索引 */
g_tUart1.usRxRead = ; /* 接收FIFO读索引 */
g_tUart1.usRxCount = ; /* 接收到的新数据个数 */
g_tUart1.usTxCount = ; /* 待发送的数据个数 */
g_tUart1.SendBefor = ; /* 发送数据前的回调函数 */
g_tUart1.SendOver = ; /* 发送完毕后的回调函数 */
g_tUart1.ReciveNew = ; /* 接收到新数据后的回调函数 */
g_tUart1.Sending = ; /* 正在发送中标志 */
#endif
/* 串口2-8的初始化省略未写 */
}
  •   函数InitHardUart

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

.    /* 串口1的GPIO  PA9, PA10   RS323 DB9接口 */
. #define USART1_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE()
.
. #define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
. #define USART1_TX_GPIO_PORT GPIOA
. #define USART1_TX_PIN GPIO_PIN_9
. #define USART1_TX_AF GPIO_AF7_USART1
.
. #define USART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
. #define USART1_RX_GPIO_PORT GPIOA
. #define USART1_RX_PIN GPIO_PIN_10
. #define USART1_RX_AF GPIO_AF7_USART1
.
. /* 串口2-8的引脚和时钟宏定义未写 */
.
. /*
17. ******************************************************************************************************
18. * 函 数 名: InitHardUart
19. * 功能说明: 配置串口的硬件参数(波特率,数据位,停止位,起始位,校验位,中断使能)适合于STM32-H7开
20. * 发板
21. * 形 参: 无
22. * 返 回 值: 无
23. ******************************************************************************************************
24. */
. static void InitHardUart(void)
. {
. GPIO_InitTypeDef GPIO_InitStruct;
. RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
.
. /*
31. 下面这个配置可以注释掉,预留下来是为了方便以后选择其它时钟使用
32. 默认情况下,USART1和USART6选择的PCLK2,时钟100MHz。
33. USART2,USART3,UART4,UART5,UART6,UART7和UART8选择的时钟是PLCK1,时钟100MHz。
34. */
. RCC_PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART16;
. RCC_PeriphClkInit.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
. HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit);
.
. #if UART1_FIFO_EN == 1 /* 串口1 */
. /* 使能 GPIO TX/RX 时钟 */
. USART1_TX_GPIO_CLK_ENABLE();
. USART1_RX_GPIO_CLK_ENABLE();
.
. /* 使能 USARTx 时钟 */
. USART1_CLK_ENABLE();
.
. /* 配置TX引脚 */
. GPIO_InitStruct.Pin = USART1_TX_PIN;
. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
. GPIO_InitStruct.Pull = GPIO_PULLUP;
. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
. GPIO_InitStruct.Alternate = USART1_TX_AF;
. HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);
.
. /* 配置RX引脚 */
. GPIO_InitStruct.Pin = USART1_RX_PIN;
. GPIO_InitStruct.Alternate = USART1_RX_AF;
. HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);
.
. /* 配置NVIC the NVIC for UART */
. HAL_NVIC_SetPriority(USART1_IRQn, , );
. HAL_NVIC_EnableIRQ(USART1_IRQn);
.
. /* 配置波特率、奇偶校验 */
. bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);
.
. SET_BIT(USART1->ICR, USART_ICR_TCCF); /* 清除TC发送完成标志 */
. SET_BIT(USART1->RQR, USART_RQR_RXFRQ); /* 清除RXNE接收标志 */
. // USART_CR1_PEIE | USART_CR1_RXNEIE
. SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中断 */
. #endif
. /* 串口2-8的初始化省略未写 */
. }
  • 第2-12行,以宏定义的方式设置串口1-8的GPIO时钟、引脚和串口时钟,方便修改。
  • 第35-37行,这里的配置可以注释掉,预留下来仅仅是为了方便以后选择其它时钟使用。默认情况下,USART1和USART6选择的PCLK2,时钟100MHz。USART2,USART3,UART4,UART5,UART6,UART7和UART8选择的时钟是PLCK1,时钟100MHz。
  • 第61-62行,配置串口中断优先级并使能串口中断,用户可以根据实际工程修改优先级大小。
  • 第65行,配置串口的基本参数,具体配置在函数里面有注释。
/*
*********************************************************************************************************
* 函 数 名: bsp_SetUartParam
* 功能说明: 配置串口的硬件参数(波特率,数据位,停止位,起始位,校验位,中断使能)适合于STM32- H7开发板
* 形 参: Instance USART_TypeDef类型结构体
* BaudRate 波特率
* Parity 校验类型,奇校验或者偶校验
* Mode 发送和接收模式使能
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetUartParam(USART_TypeDef *Instance, uint32_t BaudRate, uint32_t Parity, uint32_t Mode)
{
UART_HandleTypeDef UartHandle; /*##-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.Init.Prescaler = UART_PRESCALER_DIV1;
UartHandle.Init.FIFOMode = UART_FIFOMODE_DISABLE;
UartHandle.Init.TXFIFOThreshold = UART_TXFIFO_THRESHOLD_1_8;
UartHandle.Init.RXFIFOThreshold = UART_RXFIFO_THRESHOLD_1_8;
UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
  •   函数RS485_InitTXE

此函数主要用于485 PHY芯片的发送使能,直接配置引脚为推挽输出模式即可使用。具体代码如下:

/*
*********************************************************************************************************
* 函 数 名: RS485_InitTXE
* 功能说明: 配置RS485发送使能口线 TXE
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void RS485_InitTXE(void)
{
GPIO_InitTypeDef gpio_init; /* 打开GPIO时钟 */
RS485_TXEN_GPIO_CLK_ENABLE(); /* 配置引脚为推挽输出 */
gpio_init.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init.Pull = GPIO_NOPULL; /* 上下拉电阻不使能 */
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* GPIO速度等级 */
gpio_init.Pin = RS485_TXEN_PIN;
HAL_GPIO_Init(RS485_TXEN_GPIO_PORT, &gpio_init);
}

30.3.4 串口中断服务程序工作流程

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

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

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

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

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

#if UART1_FIFO_EN == 1
void USART1_IRQHandler(void)
{
UartIRQ(&g_tUart1);
}
#endif #if UART2_FIFO_EN == 1
void USART2_IRQHandler(void)
{
UartIRQ(&g_tUart2);
}
#endif #if UART3_FIFO_EN == 1
void USART3_IRQHandler(void)
{
UartIRQ(&g_tUart3);
}
#endif #if UART4_FIFO_EN == 1
void UART4_IRQHandler(void)
{
UartIRQ(&g_tUart4);
}
#endif #if UART5_FIFO_EN == 1
void UART5_IRQHandler(void)
{
UartIRQ(&g_tUart5);
}
#endif #if UART6_FIFO_EN == 1
void USART6_IRQHandler(void)
{
UartIRQ(&g_tUart6);
}
#endif #if UART7_FIFO_EN == 1
void UART7_IRQHandler(void)
{
UartIRQ(&g_tUart7);
}
#endif #if UART8_FIFO_EN == 1
void UART8_IRQHandler(void)
{
UartIRQ(&g_tUart8);
}
#endif

大家可以看到,这8个中断服务程序都调用了同一个处理函数UartIRQ。我们只需要调通一个串口FIFO驱动,那么其他的串口驱动也就都通了。

下面,我们来看看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 = ;
}
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 == ) /* 发送缓冲区已无数据可取 */
{
/* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后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 = ; /* 从发送FIFO取1个字节写入串口发送数据寄存器 */
//USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
_pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];
if (++_pUart->usTxRead >= _pUart->usTxBufSize)
{
_pUart->usTxRead = ;
}
_pUart->usTxCount--;
} }
/* 数据bit位全部发送完毕的中断 */
if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
//if (_pUart->usTxRead == _pUart->usTxWrite)
if (_pUart->usTxCount == )
{
/* 如果发送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 = ;
}
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 = ;
}
_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收发器进入到接收状态。

30.3.5 串口数据发送

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

/*
*********************************************************************************************************
* 函 数 名: comSendBuf
* 功能说明: 向串口发送一组数据。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送
* 形 参: _ucPort: 端口号(COM1 - COM8)
* _ucaBuf: 待发送的数据缓冲区
* _usLen : 数据长度
* 返 回 值: 无
*********************************************************************************************************
*/
void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen)
{
UART_T *pUart; pUart = ComToUart(_ucPort);
if (pUart == )
{
return;
} if (pUart->SendBefor != )
{
pUart->SendBefor(); /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */
} UartSend(pUart, _ucaBuf, _usLen);
} /*
*********************************************************************************************************
* 函 数 名: comSendChar
* 功能说明: 向串口发送1个字节。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送
* 形 参: _ucPort: 端口号(COM1 - COM8)
* _ucByte: 待发送的数据
* 返 回 值: 无
*********************************************************************************************************
*/
void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte)
{
comSendBuf(_ucPort, &_ucByte, );
}
/*
*********************************************************************************************************
* 函 数 名: UartSend
* 功能说明: 填写数据到UART发送缓冲区,并启动发送中断。中断处理函数发送完毕后,自动关闭发送中断
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)
{
uint16_t i; for (i = ; i < _usLen; i++)
{
/* 如果发送缓冲区已经满了,则等待缓冲区空 */
while ()
{
__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) == )
{
SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);
}
}
} /* 将新数据填入发送缓冲区 */
_pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i]; DISABLE_INT();
if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
{
_pUart->usTxWrite = ;
}
_pUart->usTxCount++;
ENABLE_INT();
} SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); /* 使能发送中断(缓冲区空) */
}

函数comSendChar是发送一个字节,通过调用函数comSendBuf实现,而函数comSendBuf又是通过调用函数UartSend实现,这个函数是重点。

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

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

注意:由于函数UartSend做了static作用域限制,仅可在bsp_uart_fifo.c文件中调用。函数comSendChar和comSendBuf是供用户调用的。

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

/*
*********************************************************************************************************
* 函 数 名: ComToUart
* 功能说明: 将COM端口号转换为UART指针
* 形 参: _ucPort: 端口号(COM1 - COM8)
* 返 回 值: uart指针
*********************************************************************************************************
*/
UART_T *ComToUart(COM_PORT_E _ucPort)
{
if (_ucPort == COM1)
{
#if UART1_FIFO_EN == 1
return &g_tUart1;
#else
return ;
#endif
}
else if (_ucPort == COM2)
{
#if UART2_FIFO_EN == 1
return &g_tUart2;
#else
return ;
#endif
}
else if (_ucPort == COM3)
{
#if UART3_FIFO_EN == 1
return &g_tUart3;
#else
return ;
#endif
}
else if (_ucPort == COM4)
{
#if UART4_FIFO_EN == 1
return &g_tUart4;
#else
return ;
#endif
}
else if (_ucPort == COM5)
{
#if UART5_FIFO_EN == 1
return &g_tUart5;
#else
return ;
#endif
}
else if (_ucPort == COM6)
{
#if UART6_FIFO_EN == 1
return &g_tUart6;
#else
return ;
#endif
}
else if (_ucPort == COM7)
{
#if UART7_FIFO_EN == 1
return &g_tUart7;
#else
return ;
#endif
}
else if (_ucPort == COM8)
{
#if UART8_FIFO_EN == 1
return &g_tUart8;
#else
return ;
#endif
}
else
{
Error_Handler(__FILE__, __LINE__);
return ;
}
}

30.3.6 串口数据接收

下面我们再来看看接收的函数:

/*
*********************************************************************************************************
* 函 数 名: comGetChar
* 功能说明: 从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。
* 形 参: _ucPort: 端口号(COM1 - COM8)
* _pByte: 接收到的数据存放在这个地址
* 返 回 值: 0 表示无数据, 1 表示读取到有效字节
*********************************************************************************************************
*/
uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte)
{
UART_T *pUart; pUart = ComToUart(_ucPort);
if (pUart == )
{
return ;
} return UartGetChar(pUart, _pByte);
} /*
*********************************************************************************************************
* 函 数 名: UartGetChar
* 功能说明: 从串口接收缓冲区读取1字节数据 (用于主程序调用)
* 形 参: _pUart : 串口设备
* _pByte : 存放读取数据的指针
* 返 回 值: 0 表示无数据 1表示读取到数据
*********************************************************************************************************
*/
static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte)
{
uint16_t usCount; /* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */
DISABLE_INT();
usCount = _pUart->usRxCount;
ENABLE_INT(); /* 如果读和写索引相同,则返回0 */
//if (_pUart->usRxRead == usRxWrite)
if (usCount == ) /* 已经没有数据 */
{
return ;
}
else
{
*_pByte = _pUart->pRxBuf[_pUart->usRxRead]; /* 从串口接收FIFO取1个数据 */ /* 改写FIFO读索引 */
DISABLE_INT();
if (++_pUart->usRxRead >= _pUart->usRxBufSize)
{
_pUart->usRxRead = ;
}
_pUart->usRxCount--;
ENABLE_INT();
return ;
}
}

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

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

30.3.7 串口printf实现

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

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

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

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

/*
*********************************************************************************************************
* 函 数 名: fputc
* 功能说明: 重定义putc函数,这样可以使用printf函数从串口1打印输出
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int fputc(int ch, FILE *f)
{
#if 1 /* 将需要printf的字符通过串口中断FIFO发送出去,printf函数会立即返回 */
comSendChar(COM1, ch); return ch;
#else /* 采用阻塞方式发送每个字符,等待数据发送完毕 */
/* 写一个字节到USART1 */
USART1->TDR = ch; /* 等待发送结束 */
while((USART1->ISR & USART_ISR_TC) == )
{} return ch;
#endif
} /*
*********************************************************************************************************
* 函 数 名: fgetc
* 功能说明: 重定义getc函数,这样可以使用getchar函数从串口1输入数据
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int fgetc(FILE *f)
{ #if 1 /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */
uint8_t ucData; while(comGetChar(COM1, &ucData) == ); return ucData;
#else
/* 等待接收到数据 */
while((USART1->ISR & USART_ISR_RXNE) == )
{} return (int)USART1->RDR;
#endif
}

printf函数是非阻塞的,执行后会立即返回,串口中断服务程序会陆续将数据发送出去。

30.4 串口FIFO板级支持包(bsp_uart_fifo.c)

串口驱动文件bsp_uart_fifo.c主要实现了如下几个API供用户调用:

  • bsp_InitUart
  • comSendBuf
  • comSendChar
  • comGetChar

30.4.1 函数bsp_InitUart

函数原型:

void bsp_InitUart(void)

函数描述:

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

使用举例:

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

30.4.2 函数comSendBuf

函数原型:

void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen);

函数描述:

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

函数参数:

  • 第1个参数_ucPort是端口号,范围COM1 - COM8。
  • 第2个参数_ucaBuf是待发送的数据缓冲区地址。
  • 第3个参数_usLen是要发送数据的字节数。

注意事项:

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

使用举例:

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

const char buf1[] = "接收到串口命令1\r\n";
comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));

30.4.3 函数comSendChar

函数原型:

void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte);

函数描述:

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

函数参数:

  • 第1个参数_ucPort是端口号,范围COM1 - COM8。
  • 第2个参数_ucByte是待发送的数据。

注意事项:

  • 此函数的解读在本章30.3.2小节。

使用举例:

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

comSendChar(COM1, 'c')。

30.4.4 函数comGetChar

函数原型:

uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte)

函数描述:

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

函数参数:

  • 第1个参数_ucPort是端口号,范围COM1 - COM8。
  • 第2个参数_pByte用于存放接收到的数据。
  • 返回值,返回0表示无数据, 1 表示读取到有效字节。

注意事项:

  • 此函数的解读在本章30.3.6小节。

使用举例:

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

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

30.5 串口FIFO驱动移植和使用

串口FIFO移植步骤如下:

  • 第1步:复制bsp_uart_fifo.h和bsp_uart_fifo.c到自己的工程目录,并添加到工程里面。
  • 第2步:根据自己要使用的串口和收发缓冲大小,修改下面的宏定义即可。
    #define    UART1_FIFO_EN    1
    #define UART2_FIFO_EN 0
    #define UART3_FIFO_EN 0
    #define UART4_FIFO_EN 0
    #define UART5_FIFO_EN 0
    #define UART6_FIFO_EN 0
    #define UART7_FIFO_EN 0
    #define UART8_FIFO_EN 0 /* 定义串口波特率和FIFO缓冲区大小,分为发送缓冲区和接收缓冲区, 支持全双工 */
    #if UART1_FIFO_EN == 1
    #define UART1_BAUD 115200
    #define UART1_TX_BUF_SIZE 1*1024
    #define UART1_RX_BUF_SIZE 1*1024
    #endif #if UART2_FIFO_EN == 1
    #define UART2_BAUD 9600
    #define UART2_TX_BUF_SIZE 10
    #define UART2_RX_BUF_SIZE 2*1024
    #endif #if UART3_FIFO_EN == 1
    #define UART3_BAUD 9600
    #define UART3_TX_BUF_SIZE 1*1024
    #define UART3_RX_BUF_SIZE 1*1024
    #endif #if UART4_FIFO_EN == 1
    #define UART4_BAUD 115200
    #define UART4_TX_BUF_SIZE 1*1024
    #define UART4_RX_BUF_SIZE 1*1024
    #endif #if UART5_FIFO_EN == 1
    #define UART5_BAUD 115200
    #define UART5_TX_BUF_SIZE 1*1024
    #define UART5_RX_BUF_SIZE 1*1024
    #endif #if UART6_FIFO_EN == 1
    #define UART6_BAUD 115200
    #define UART6_TX_BUF_SIZE 1*1024
    #define UART6_RX_BUF_SIZE 1*1024
    #endif #if UART7_FIFO_EN == 1
    #define UART7_BAUD 115200
    #define UART7_TX_BUF_SIZE 1*1024
    #define UART7_RX_BUF_SIZE 1*1024
    #endif #if UART8_FIFO_EN == 1
    #define UART8_BAUD 115200
    #define UART8_TX_BUF_SIZE 1*1024
    #define UART8_RX_BUF_SIZE 1*1024
    #endif
  • 第3步:这几个驱动文件主要用到HAL库的GPIO和串口驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
  • 第4步,应用方法看本章节配套例子即可。

30.6 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

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

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  • 第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
  • 第2部分,应用程序设计部分,实现了一个串口接收命令,返回消息的简单功能。

30.7 实验例程说明(MDK)

配套例子:

V7-015_串口和PC机通信(驱动支持8串口FIFO)

实验目的:

  1. 学习串口与PC通信。

实验内容:

  1. 启动一个自动重装软件定时器,每100ms翻转一次LED2。

实验操作:

  1. 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  2. 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  3. 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  4. 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  5. K1按键按下,串口打印"按键K1按下"。
  6. K2按键按下,串口打印"按键K2按下"。
  7. K3按键按下,串口打印"按键K3按下"。

上电后串口打印的信息:

波特率 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_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}

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。
  • 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  • 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  • 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  • 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  • K1按键按下,串口打印"按键K1按下"。
  • K2按键按下,串口打印"按键K2按下"。
  • K3按键按下,串口打印"按键K3按下"。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode;
uint8_t read;
const char buf1[] = "接收到串口命令1\r\n";
const char buf2[] = "接收到串口命令2\r\n";
const char buf3[] = "接收到串口命令3\r\n";
const char buf4[] = "接收到串口命令4\r\n"; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(, ); /* 启动1个100ms的自动重装的定时器 */ /* 主程序大循环 */
while ()
{
/* CPU空闲时执行的函数,在 bsp.c */
bsp_Idle(); /* 判断定时器超时时间 */
if (bsp_CheckTimer())
{
/* 每隔100ms 进来一次 */
/* 翻转LED2的状态 */
bsp_LedToggle();
} /* 接收到的串口命令处理 */
if (comGetChar(COM1, &read))
{
switch (read)
{
case '':
comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));
break; case '':
comSendBuf(COM1, (uint8_t *)buf2, strlen(buf2));
break; case '':
comSendBuf(COM1, (uint8_t *)buf3, strlen(buf3));
break; case '':
comSendBuf(COM1, (uint8_t *)buf4, strlen(buf4));
break; default:
break;
}
} /* 处理按键事件 */
ucKeyCode = bsp_GetKey();
if (ucKeyCode > )
{
/* 有键按下 */
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* 按键K1键按下 */
printf("按键K1按下\r\n");
bsp_LedToggle();
break; case KEY_DOWN_K2: /* 按键K2键按下 */
printf("按键K2按下\r\n");
bsp_LedToggle();
break; case KEY_DOWN_K3: /* 按键K3键按下 */
printf("按键K3按下\r\n");
bsp_LedToggle();
break; default:
break;
}
}
}
}

30.8 实验例程说明(IAR)

配套例子:

V7-015_串口和PC机通信(驱动支持8串口FIFO)

实验目的:

  1. 学习串口与PC通信。

实验内容:

  1. 启动一个自动重装软件定时器,每100ms翻转一次LED2。

实验操作:

  1. 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  2. 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  3. 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  4. 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  5. K1按键按下,串口打印"按键K1按下"。
  6. K2按键按下,串口打印"按键K2按下"。
  7. K3按键按下,串口打印"按键K3按下"。

上电后串口打印的信息:

波特率 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_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}

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。
  • 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  • 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  • 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  • 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  • K1按键按下,串口打印"按键K1按下"。
  • K2按键按下,串口打印"按键K2按下"。
  • K3按键按下,串口打印"按键K3按下"。
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode;
uint8_t read;
const char buf1[] = "接收到串口命令1\r\n";
const char buf2[] = "接收到串口命令2\r\n";
const char buf3[] = "接收到串口命令3\r\n";
const char buf4[] = "接收到串口命令4\r\n"; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(, ); /* 启动1个100ms的自动重装的定时器 */ /* 主程序大循环 */
while ()
{
/* CPU空闲时执行的函数,在 bsp.c */
bsp_Idle(); /* 判断定时器超时时间 */
if (bsp_CheckTimer())
{
/* 每隔100ms 进来一次 */
/* 翻转LED2的状态 */
bsp_LedToggle();
} /* 接收到的串口命令处理 */
if (comGetChar(COM1, &read))
{
switch (read)
{
case '':
comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));
break; case '':
comSendBuf(COM1, (uint8_t *)buf2, strlen(buf2));
break; case '':
comSendBuf(COM1, (uint8_t *)buf3, strlen(buf3));
break; case '':
comSendBuf(COM1, (uint8_t *)buf4, strlen(buf4));
break; default:
break;
}
} /* 处理按键事件 */
ucKeyCode = bsp_GetKey();
if (ucKeyCode > )
{
/* 有键按下 */
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* 按键K1键按下 */
printf("按键K1按下\r\n");
bsp_LedToggle();
break; case KEY_DOWN_K2: /* 按键K2键按下 */
printf("按键K2按下\r\n");
bsp_LedToggle();
break; case KEY_DOWN_K3: /* 按键K3键按下 */
printf("按键K3按下\r\n");
bsp_LedToggle();
break; default:
break;
}
}
}
}

30.9 总结

本章节就为大家讲解这么多, 重点是8串口FIFO的实现,而且移植也比较简单,可放心用于项目实战。

【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现的更多相关文章

  1. 【STM32H7教程】第31章 STM32H7的USART应用之RS485

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第31章       STM32H7的USART应用之RS48 ...

  2. 【STM32H7教程】第29章 STM32H7的USART串口基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第29章       STM32H7的USART串口基础知识和 ...

  3. 【STM32H7教程】第12章 STM32H7的HAL库框架设计学习

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第12章       STM32H7的HAL库框架设计学 ...

  4. 【STM32H7教程】第8章 STM32H7的终极调试组件Event Recorder

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第8章   STM32H7的终极调试组件Event Re ...

  5. 【STM32H7教程】第2章 STM32H7的开发环境搭建

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第2章    STM32H7的开发环境搭建 本章主要为大 ...

  6. 【安富莱STM32H7教程】第1章 初学STM32H7的准备工作

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第1章   初学STM32H7的准备工作 俗话说万事开头 ...

  7. 【STM32H7教程】第22章 STM32H7的SysTick实现多组软件定时器

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第22章       STM32H7的SysTick实现 ...

  8. 【STM32H7教程】第34章 STM32H7的定时器应用之TIM1-TIM17的PWM实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第34章       STM32H7的定时器应用之TIM1-T ...

  9. 【STM32H7教程】第21章 STM32H7的NVIC中断分组和配置(重要)

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第21章       STM32H7的NVIC中断分组和配置( ...

随机推荐

  1. Scrum Framework, Process and Story Point

    Scrum Framework Roles Ceremonies Artifacts Scrum Master Sprint Planning Product Backlog Product Owne ...

  2. MySQL 8.0部分弃用的参数整理

    最近整理了一下MySQL 8.0的自动化安装,其中用到了一个MySQL 5.7版本的自定义配置文件,由于没有对(MySQL 8.0)做针对性修改,导致安装过程中出现了一些错误其中部分原因就是MySQL ...

  3. Pipe——高性能IO(三)

    Pipelines可以替换掉那些丑陋的封装(kludge).变通(workaround)或妥协(compromise)——用一个在框架中设计优雅的专门的解决方案. 敢肯定,下面所覆盖的那些痛点,对于那 ...

  4. 函数式接口 & lambda表达式 & 方法引用

    拉呱: 终于,学习jdk8的新特性了,初体验带给我的感觉真爽,代码精简的不行,可读性也很好,而且,spring5也是把jdk8的融入到血液里,总之一句话吧,说的打趣一点,学的时候自己难受,学完了写出来 ...

  5. ASP.NET是什么?

    ASP.NET简介 简单来说,ASP.NET 是一个使用 HTML.CSS.JavaScript 和服务器脚本创建网页和网站的开发框架. 微软在2001年开发的第一个版本的ASP.NET,是一种建立在 ...

  6. MySql-8.0.16-winx64 安装

    参考文章: https://www.cnblogs.com/lxlin/p/9635350.html https://www.cnblogs.com/xc1234/p/9050149.html MyS ...

  7. MySQL触发器学习总结

    1.What     触发器是MySQL响应DELETE,INSERT,UPDATE语句前后而自动执行的一条MySQL语句 2.Why(使用情形)     增加一个订单对应库存-1     删除一行在 ...

  8. PostgreSQL 中字段类型varchar

    PostgreSql数据库中varchar类型与sql server中字段用法有差别,PostgreSql中如果字段设置为varchar类型长度为10,则无论存字母.数字或其它符号,长度最大为10个, ...

  9. 【Unity】 关于Package Manager 无限加载的问题(Loading Packages),以及可能的解决办法(待补充。)

    ·版本:2019.1.8f 官方论坛对于此问题的讨论:地址>Package Manager 许多人都遇到了这个问题,但是无法定位问题出在哪里.官方技术人员提供了一个名为 Package Mana ...

  10. JS 注释

    JS 注释 JavaScript 注释可用于提高代码的可读性. 单行注释 // 输出标题: document.getElementById("myH1").innerHTML=&q ...