完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>
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个串口设备。
关于232的PHY芯片SP3232E要注意以下几个问题:
通过这种方式,可以在应用程序中通过串口发送几个字符,查看是否可以正确接收来判断232 PHY芯片是否有问题。
30.3.1 串口FIFO框架 为了方便大家理解,先来看下串口FIFO的实现框图: 第1阶段,初始化:
串口驱动的核心文件为: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 = 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 串口中断服务程序工作流程 串口中断服务程序是最核心的部分,主要实现如下三个功能
下面我们分析一下串口中断处理的完整过程。 当产生串口中断后,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); } 中断服务程序的处理主要分为两部分,接收数据的处理和发送数据的处理,详情看程序注释即可,已经比较详细,下面重点把思路说一下。
特别注意里面的ReciveNew处理,这个在Modbus协议里面要用到。
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: 待发送的数据 * 返 回 值: 无 ********************************************************************************************************* */ void comSendChar(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 ; 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的作用就是把要发送的数据填到发送缓冲区里面,并使能发送空中断。
注意:由于函数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; } 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 == 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); return ch; #else /* 采用阻塞方式发送每个字符,等待数据发送完毕 */ /* 写一个字节到USART1 */ USART1->TDR = ch; /* 等待发送结束 */ while((USART1->ISR & USART_ISR_TC) == 0) {} return ch; #endif } /* ********************************************************************************************************* * 函 数 名: fgetc * 功能说明: 重定义getc函数,这样可以使用getchar函数从串口1输入数据 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int fgetc(FILE *f) { #if 1 /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */ uint8_t ucData; while(comGetChar(COM1, &ucData) == 0); return ucData; #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供用户调用:
函数原型: 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);函数描述: 此函数用于向串口发送一组数据,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。 const char buf1[] = "接收到串口命令1rn";comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));30.4.3 函数comSendChar 函数原型: void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte);函数描述: 此函数用于向串口发送1个字节,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。此函数是通过调用函数comSendBuf实现的。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。比如通过串口1发送一个字符c: comSendChar(COM1, 'c')。 30.4.4 函数comGetChar 函数原型: uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte)函数描述: 此函数用于从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。 比如从串口1读取一个字符就是:comGetChar(COM1, &read)。 30.5 串口FIFO驱动移植和使用 串口FIFO移植步骤如下:
|
|
|
|
提示:
作者被禁止或删除 内容自动屏蔽
|
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1148 浏览1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1171 浏览1 评论
599 浏览2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
448 浏览2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1082 浏览2 评论
1643浏览9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
308浏览4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
311浏览3评论
304浏览3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
276浏览3评论
小黑屋|手机版|Archiver|德赢Vwin官网(湘ICP备2023018690号)
GMT+8, 2024-8-26 03:04, Processed in 1.033693 second(s), Total 80, Slave 63 queries .
Powered by德赢Vwin官网 网
© 2015bbs.elecfans.com
关注我们的微信
下载发烧友APP
德赢Vwin官网 观察
版权所有 © 湖南华秋数字科技有限公司
德赢Vwin官网 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号