完整教程下载地址: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. Appium(四):真实机第一个appium程序、模拟器第一个appium程序、查看元素

    1. 真实机第一个appium程序 学完了前面的知识,也将环境搭建好了,接下来我们就正式开始appium的学习了. 在做app自动化的时候,我们肯定是针对某个产品.某个软件进行测试,那么我们一定是先让 ...

  2. pytorch 中的Variable一般常用的使用方法

    Variable一般的初始化方法,默认是不求梯度的 import torch from torch.autograd import Variable x_tensor = torch.randn(2, ...

  3. Unable to open debugger port: java.net.SocketException

    网上都说是tomcat端口被占用,其实不是,这是因为文件权限不够,脚本不能执行,debug当然不能接受网络连接的数据 可以在Event Log里看到 所以只需要更改文件的级别就可以了(可读可写可执行) ...

  4. element form 对单个字段做验证

    this.$refs[formName].validateField('phone', phoneError => { //验证手机号码是否正确 if (!phoneError) { conso ...

  5. Python 教你识别淘宝刷单,买到称心如意的商品

    发际线堪忧的小 Q,为了守住头发最后的尊严,深入分析了几十款防脱洗发水的评价,最后综合选了一款他认为最完美的防脱洗发水. 一星期后,他没察觉到任何变化. 一个月后,他用卷尺量了量,发际线竟然后退了 0 ...

  6. [python / selenium] - 用python刷公选课是一种什么体验?

    前言 看公选课还是能学到很多知识的,这里是给大家提供一个selenium的使用思路(好好学公选课,我真的看了) 思路 当观看者移动鼠标到某一范围时就会停止播放,就让selenium一直将鼠标悬停在视频 ...

  7. 【docker构建】基于docker构建wordpress博客网站平台

    WordPress是使用PHP语言开发的博客平台,用户可以在支持PHP和MySQL数据库的服务器上架设属于自己的网站.也可以把 WordPress当作一个内容管理系统(CMS)来使用. WordPre ...

  8. SpringCloud微服务(02):Ribbon和Feign组件,实现服务调用的负载均衡

    本文源码:GitHub·点这里 || GitEE·点这里 一.Ribbon简介 1.基本概念 Ribbon是一个客户端的负载均衡(Load Balancer,简称LB)器,它提供对大量的HTTP和TC ...

  9. 好用的js片段收藏

    1.判断浏览器信息,如果是手机,就跳到手机页面 if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) { wind ...

  10. Eclipse中Junit测试中@Before不执行

    场景 在使用Junit进行单元测试时,一部分获取JPA的entityManager的代码将其放在了 @Before标注的方法中,这样每次执行@TEST标注的方法时会首先执行@Before标注的方法. ...