第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 初学者重要提示

学习本章节前,务必优先学习第29章。

串口FIFO的实现跟前面章节按键FIFO的机制是一样的。

本章节比较重要,因为后面的ESP8266,GPS,RS485,GPRS等试验都是建立在这个驱动的基础上实现。

大家自己做的板子,测试串口收发是乱码的话,重点看stm32h7xx_hal_conf.h文件中的HSE_VALUE的大小跟板子上实际晶振大小是否一致,然后再看PLL配置。

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

/*串口设备结构体*/typedefstruct{

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

staticUART_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 == 1g_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= 0; /*发送FIFO写索引*/g_tUart1.usTxRead= 0; /*发送FIFO读索引*/g_tUart1.usRxWrite= 0; /*接收FIFO写索引*/g_tUart1.usRxRead= 0; /*接收FIFO读索引*/g_tUart1.usRxCount= 0; /*接收到的新数据个数*/g_tUart1.usTxCount= 0; /*待发送的数据个数*/g_tUart1.SendBefor= 0; /*发送数据前的回调函数*/g_tUart1.SendOver= 0; /*发送完毕后的回调函数*/g_tUart1.ReciveNew= 0; /*接收到新数据后的回调函数*/g_tUart1.Sending= 0; /*正在发送中标志*/

#endif

/*串口2-8的初始化省略未写*/}

函数InitHardUart

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

1. /*串口1的GPIO PA9, PA10 RS323 DB9接口*/

2. #define USART1_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE()

3.4. #define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()

5. #define USART1_TX_GPIO_PORT GPIOA

6. #define USART1_TX_PIN GPIO_PIN_9

7. #define USART1_TX_AF GPIO_AF7_USART1

8.9. #define USART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()

10. #define USART1_RX_GPIO_PORT GPIOA

11. #define USART1_RX_PIN GPIO_PIN_10

12. #define USART1_RX_AF GPIO_AF7_USART1

13.14. /*串口2-8的引脚和时钟宏定义未写*/

15.16. /*17. ******************************************************************************************************

18. * 函 数 名: InitHardUart

19. * 功能说明: 配置串口的硬件参数(波特率,数据位,停止位,起始位,校验位,中断使能)适合于STM32-H7开

20. * 发板

21. * 形 参: 无

22. * 返 回 值: 无

23. ******************************************************************************************************

24.*/

25. static void InitHardUart(void)26. {27. GPIO_InitTypeDef GPIO_InitStruct;28. RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;29.30. /*31. 下面这个配置可以注释掉,预留下来是为了方便以后选择其它时钟使用

32. 默认情况下,USART1和USART6选择的PCLK2,时钟100MHz。

33. USART2,USART3,UART4,UART5,UART6,UART7和UART8选择的时钟是PLCK1,时钟100MHz。

34.*/

35. RCC_PeriphClkInit.PeriphClockSelection =RCC_PERIPHCLK_USART16;36. RCC_PeriphClkInit.Usart16ClockSelection =RCC_USART16CLKSOURCE_D2PCLK2;37. HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit);38.39. #if UART1_FIFO_EN == 1 /* 串口1 */

40. /*使能 GPIO TX/RX 时钟*/

41. USART1_TX_GPIO_CLK_ENABLE();42. USART1_RX_GPIO_CLK_ENABLE();43.44. /*使能 USARTx 时钟*/

45. USART1_CLK_ENABLE();46.47. /*配置TX引脚*/

48. GPIO_InitStruct.Pin =USART1_TX_PIN;49. GPIO_InitStruct.Mode =GPIO_MODE_AF_PP;50. GPIO_InitStruct.Pull =GPIO_PULLUP;51. GPIO_InitStruct.Speed =GPIO_SPEED_FREQ_VERY_HIGH;52. GPIO_InitStruct.Alternate =USART1_TX_AF;53. HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);54.55. /*配置RX引脚*/

56. GPIO_InitStruct.Pin =USART1_RX_PIN;57. GPIO_InitStruct.Alternate =USART1_RX_AF;58. HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);59.60. /*配置NVIC the NVIC for UART*/

61. HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);62. HAL_NVIC_EnableIRQ(USART1_IRQn);63.64. /*配置波特率、奇偶校验*/

65. bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);66.67. SET_BIT(USART1->ICR, USART_ICR_TCCF); /*清除TC发送完成标志*/

68. SET_BIT(USART1->RQR, USART_RQR_RXFRQ); /*清除RXNE接收标志*/

69. //USART_CR1_PEIE | USART_CR1_RXNEIE

70. SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /*使能PE. RX接受中断*/

71. #endif

72. /*串口2-8的初始化省略未写*/

73. }

第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 = 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收发器进入到接收状态。

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 == 0)

{return;

}if (pUart->SendBefor != 0)

{

pUart->SendBefor(); /*如果是RS485通信,可以在这个函数中将RS485设置为发送模式*/}

UartSend(pUart, _ucaBuf, _usLen);

}/**********************************************************************************************************

* 函 数 名: comSendChar

* 功能说明: 向串口发送1个字节。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送

* 形 参: _ucPort: 端口号(COM1 - COM8)

* _ucByte: 待发送的数据

* 返 回 值: 无

**********************************************************************************************************/

voidcomSendChar(COM_PORT_E _ucPort, uint8_t _ucByte)

{

comSendBuf(_ucPort,&_ucByte, 1);

}/**********************************************************************************************************

* 函 数 名: UartSend

* 功能说明: 填写数据到UART发送缓冲区,并启动发送中断。中断处理函数发送完毕后,自动关闭发送中断

* 形 参: 无

* 返 回 值: 无

**********************************************************************************************************/

static void UartSend(UART_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); /*使能发送中断(缓冲区空)*/}

函数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 0;#endif}else if (_ucPort ==COM2)

{#if UART2_FIFO_EN == 1

return &g_tUart2;#else

return 0;#endif}else if (_ucPort ==COM3)

{#if UART3_FIFO_EN == 1

return &g_tUart3;#else

return 0;#endif}else if (_ucPort ==COM4)

{#if UART4_FIFO_EN == 1

return &g_tUart4;#else

return 0;#endif}else if (_ucPort ==COM5)

{#if UART5_FIFO_EN == 1

return &g_tUart5;#else

return 0;#endif}else if (_ucPort ==COM6)

{#if UART6_FIFO_EN == 1

return &g_tUart6;#else

return 0;#endif}else if (_ucPort ==COM7)

{#if UART7_FIFO_EN == 1

return &g_tUart7;#else

return 0;#endif}else if (_ucPort ==COM8)

{#if UART8_FIFO_EN == 1

return &g_tUart8;#else

return 0;#endif}else{

Error_Handler(__FILE__, __LINE__);return 0;

}

}

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 == 0)

{return 0;

}returnUartGetChar(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 == 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;

}

}

函数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);returnch;#else /* 采用阻塞方式发送每个字符,等待数据发送完毕 */

/*写一个字节到USART1*/USART1->TDR =ch;/*等待发送结束*/

while((USART1->ISR & USART_ISR_TC) == 0)

{}returnch;#endif}/**********************************************************************************************************

* 函 数 名: fgetc

* 功能说明: 重定义getc函数,这样可以使用getchar函数从串口1输入数据

* 形 参: 无

* 返 回 值: 无

**********************************************************************************************************/

int fgetc(FILE *f)

{#if 1 /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */uint8_t ucData;while(comGetChar(COM1, &ucData) == 0);returnucData;#else

/*等待接收到数据*/

while((USART1->ISR & USART_ISR_RXNE) == 0)

{}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)

实验目的:

学习串口与PC通信。

实验内容:

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

实验操作:

串口接收到字符命令'1',返回串口消息"接收到串口命令1"。

串口接收到字符命令'2',返回串口消息"接收到串口命令2"。

串口接收到字符命令'3',返回串口消息"接收到串口命令3"。

串口接收到字符命令'4',返回串口消息"接收到串口命令4"。

K1按键按下,串口打印"按键K1按下"。

K2按键按下,串口打印"按键K2按下"。

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();#endifbsp_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(0, 100); /*启动1个100ms的自动重装的定时器*/

/*主程序大循环*/

while (1)

{/*CPU空闲时执行的函数,在 bsp.c*/bsp_Idle();/*判断定时器超时时间*/

if (bsp_CheckTimer(0))

{/*每隔100ms 进来一次*/

/*翻转LED2的状态*/bsp_LedToggle(2);

}/*接收到的串口命令处理*/

if (comGetChar(COM1, &read))

{switch(read)

{case '1':

comSendBuf(COM1, (uint8_t*)buf1, strlen(buf1));break;case '2':

comSendBuf(COM1, (uint8_t*)buf2, strlen(buf2));break;case '3':

comSendBuf(COM1, (uint8_t*)buf3, strlen(buf3));break;case '4':

comSendBuf(COM1, (uint8_t*)buf4, strlen(buf4));break;default:break;

}

}/*处理按键事件*/ucKeyCode=bsp_GetKey();if (ucKeyCode > 0)

{/*有键按下*/

switch(ucKeyCode)

{case KEY_DOWN_K1: /*按键K1键按下*/printf("按键K1按下\r\n");

bsp_LedToggle(1);break;case KEY_DOWN_K2: /*按键K2键按下*/printf("按键K2按下\r\n");

bsp_LedToggle(3);break;case KEY_DOWN_K3: /*按键K3键按下*/printf("按键K3按下\r\n");

bsp_LedToggle(4);break;default:break;

}

}

}

}

30.8 实验例程说明(IAR)

配套例子:

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

实验目的:

学习串口与PC通信。

实验内容:

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

实验操作:

串口接收到字符命令'1',返回串口消息"接收到串口命令1"。

串口接收到字符命令'2',返回串口消息"接收到串口命令2"。

串口接收到字符命令'3',返回串口消息"接收到串口命令3"。

串口接收到字符命令'4',返回串口消息"接收到串口命令4"。

K1按键按下,串口打印"按键K1按下"。

K2按键按下,串口打印"按键K2按下"。

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();#endifbsp_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(0, 100); /*启动1个100ms的自动重装的定时器*/

/*主程序大循环*/

while (1)

{/*CPU空闲时执行的函数,在 bsp.c*/bsp_Idle();/*判断定时器超时时间*/

if (bsp_CheckTimer(0))

{/*每隔100ms 进来一次*/

/*翻转LED2的状态*/bsp_LedToggle(2);

}/*接收到的串口命令处理*/

if (comGetChar(COM1, &read))

{switch(read)

{case '1':

comSendBuf(COM1, (uint8_t*)buf1, strlen(buf1));break;case '2':

comSendBuf(COM1, (uint8_t*)buf2, strlen(buf2));break;case '3':

comSendBuf(COM1, (uint8_t*)buf3, strlen(buf3));break;case '4':

comSendBuf(COM1, (uint8_t*)buf4, strlen(buf4));break;default:break;

}

}/*处理按键事件*/ucKeyCode=bsp_GetKey();if (ucKeyCode > 0)

{/*有键按下*/

switch(ucKeyCode)

{case KEY_DOWN_K1: /*按键K1键按下*/printf("按键K1按下\r\n");

bsp_LedToggle(1);break;case KEY_DOWN_K2: /*按键K2键按下*/printf("按键K2按下\r\n");

bsp_LedToggle(3);break;case KEY_DOWN_K3: /*按键K3键按下*/printf("按键K3按下\r\n");

bsp_LedToggle(4);break;default:break;

}

}

}

}

30.9 总结

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

stm32h7 串口idle_【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现相关推荐

  1. stm32h7 串口idle_【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现

    31.       下面这个配置可以注释掉,预留下来是为了方便以后选择其它时钟使用 32.       默认情况下,USART1和USART6选择的PCLK2,时钟100MHz. 33.        ...

  2. stm32h7关串口中断怎么弄_【STM32H7教程】第33章 STM32H7的定时器应用之TIM1-TIM17的中断实现...

    第33章       STM32H7的定时器应用之TIM1-TIM17的中断实现 本章教程为大家讲解定时器应用之TIM1 – TIM17所有定时器的周期性中断实现.实际项目中用到的地方较多,特别是周期 ...

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

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

  4. 【STM32H7教程】第65章 STM32H7的低功耗串口LPUART基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第65章       STM32H7的低功耗串口LPUART基 ...

  5. stm32h7高速通信_【STM32H7教程】第48章 STM32H7的FMC总线应用之是32路高速IO扩展

    第48章       STM32H7的FMC总线应用之是32路高速IO扩展 本章教程为大家讲解利用STM32H7的FMC总线扩展出32路高速IO,且使用简单,实际项目中也比较有实用价值. 48.1 初 ...

  6. 【STM32H7教程】第51章 STM32H7的LTDC应用之LCD汉字显示和2D图形显示

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第51章       STM32H7的LTDC应用之LCD汉字 ...

  7. 【STM32H7的DSP教程】第38章 STM32H7的FIR高通滤波器实现(支持逐个数据的实时滤波)

    完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547 第38章       STM32H7的FIR高通滤波器实现( ...

  8. 【STM32H7教程】第76章 STM32H7的FMC总线应用之驱动AD7606(8通道同步采样, 16bit, 正负10V)

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第76章       STM32H7的FMC总线应用之驱动AD ...

  9. 【STM32H7教程】第4章 STM32H7工程模板建立(MDK5)

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第4章   STM32H7工程模板建立(MDK5) 本章 ...

最新文章

  1. ocr智能图文识别 tess4j 图文,验证码识别
  2. 三星全球首家量产18nm DRAM
  3. Linux的centos7.2部署rocketMq3.5.8
  4. EM算法 大白话讲解
  5. C#值类型与引用类型
  6. 解决org.apache.hadoop.io.nativeio.NativeIOException: 当文件已存在时,无法创建该文件。
  7. 织梦dede模板|白绿色户外拓展教育培训网站织梦dede模板源码[带手机版数据同步]
  8. Java解决循环注入问题
  9. 初学C#中遇到的问题!
  10. python图形绘制含注释
  11. 今日头条反省整改;微信QQ封杀短视频;雷军10亿赌约将见分晓| CSDN极客头条
  12. BZOJ 1179: [Apio2009]Atm Tar$滑稽 ra$jan
  13. java 3000并发,还被面试官怼并发编程?来,吃点能量!Java并发编程技术
  14. 特殊类型窗体制作: 制作字形窗体
  15. 复杂作业车间调度-单机调度问题研究
  16. c语言标识符等价类测试,c语言基础测试题--牧码南山招新检测题
  17. linux 分区100g整数,160GB硬盘双系统整数分区推荐方案
  18. 中国研修网计算机培训心得,网络研修培训心得体会
  19. Fiddler中inspector的用法2-2
  20. three.js绘制墙体,通过不规则路径生成墙体,3D墙体绘制

热门文章

  1. pat 甲级 A1008 Elevator
  2. CSS Sprite
  3. C# 复制Word(复制全部内容、部分内容、页眉页脚)
  4. mininet和ryu简单实现自定义topo
  5. 青蛙过河 猴子爬山 兔子繁殖 开宝箱2 找气球 指针函数 铺地砖
  6. RuntimeError: mat1 and mat2 shapes cannot be multiplied (1024x1 and 1024x3)
  7. 谦卑的程序员(The Humble Programmer) by E.W.Dijkstra,1972
  8. Pathon基础篇数据类型之——数值、字符串与列表内容
  9. 利用一个竞态漏洞root三星s8的方法
  10. 谷歌面试题之扔鸡蛋的问题(蓝桥杯摔手机的问题)