完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
本章教程为大家讲解USART应用之485总线。虽然这几年无线网络的使用率有所上升,有线的串行网络仍然提供最有力、最可靠的通信,特别是在恶劣的环境中。在需要抗噪、抗静电、抗电压故障的工业,建筑自动化领域仍然是有线通信的天下。
31.1 初学者重要提示
EIA-485(过去叫做RS-485或者RS485)是隶属于OSI模型物理层的电气特性规定为2线、半双工、平衡传输线多点通信的标准,是由电信行业协会(TIA)及电子工业联盟(EIA)联合发布的标准。实现此标准的数字通信网可以在有电子噪声的环境下进行长距离有效率的通信。在线性多点总线的配置下,可以在一个网络上有多个接收器。因此适用在工业环境中。 EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。电子工业联盟(EIA)已结束运作,此标准目前是电信行业协会(TIA)维护,名称为TIA-485,但工程师仍继续用RS-485来称呼此协议。
RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗干扰噪声性好。 RS-485最大的通信距离约为1219m,最高传输速率为10Mbsp,传输速率与传输距离成反比,在100Kb/S的传输速率下,才可以达到最大的通信距离,如果需传输更长的距离,需要加485中继器。RS-485总线一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。 关于RS485的逻辑状态,不同厂家的芯片的定义可能不同,但不影响正常的数据收发,这里以TI的为例做个说明,TI的定义方式如下: A表示非反向输出non-inverting output,B表示反向输出inverting output。 当VA > VB 的时候表示逻辑状态0,被称为ON。 当VA < VB 的时候表示逻辑状态1,被称为OFF。 对应到实际芯片框图上就是下面这样(DE发送使能,D是发送数据端,RE是接收使能,R是接收数据端): 当用户在D(Driver)引脚输入逻辑高电平时,将在485总线上实现逻辑状态0,即ON状态。接收端R(Receiver)将收到逻辑高电平。 当用户在D(Driver)引脚输入逻辑低电平时,将在485总线上实现逻辑状态1,即OFF状态。接收端R(Receiver)将收到逻辑低电平。 发送状态下,大于|±1.5V |可以有效表示逻辑状态1和逻辑状态0: 接收状态下,大于|±200mv|可以有效表示逻辑状态1和逻辑状态0: 31.3 RS485硬件设计 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个串口设备。
串口3,RS485 关于485的PHY芯片SP3485E要注意以下几个问题:
31.4 RS485驱动设计 RS485的驱动实现是建立在第31章讲解的串口FIFO基础上,关键的知识点已经在第31章节做了详细讲解,这里把485驱动涉及到的两个关键地方做个说明。 31.4.1 RS485驱动初始化 RS485驱动的初始化要对收发控制引脚进行配置,这点要注意,对应的代码如下: /*********************************************************************************************************** 函 数 名: bsp_InitUart* 功能说明: 初始化串口硬件,并对全局变量赋初值.* 形 参: 无* 返 回 值: 无**********************************************************************************************************/void bsp_InitUart(void){ UartVarInit(); /* 必须先初始化全局变量,再配置硬件 */ InitHardUart(); /* 配置串口的硬件参数(波特率等) */ RS485_InitTXE(); /* 配置RS485芯片的发送使能硬件,配置为推挽输出 */}/*********************************************************************************************************** 函 数 名: 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); } 31.4.2 RS485驱动回调函数初始化 由于RS485是半双工,收发要做切换,初始化的时候专门配套了回调函数: /*********************************************************************************************************** 函 数 名: UartVarInit* 功能说明: 初始化串口相关的变量* 形 参: 无* 返 回 值: 无**********************************************************************************************************/static void UartVarInit(void){#if UART3_FIFO_EN == 1 g_tUart3.uart = USART3; /* STM32 串口设备 */ g_tUart3.pTxBuf = g_TxBuf3; /* 发送缓冲区指针 */ g_tUart3.pRxBuf = g_RxBuf3; /* 接收缓冲区指针 */ g_tUart3.usTxBufSize = UART3_TX_BUF_SIZE; /* 发送缓冲区大小 */ g_tUart3.usRxBufSize = UART3_RX_BUF_SIZE; /* 接收缓冲区大小 */ g_tUart3.usTxWrite = 0; /* 发送FIFO写索引 */ g_tUart3.usTxRead = 0; /* 发送FIFO读索引 */ g_tUart3.usRxWrite = 0; /* 接收FIFO写索引 */ g_tUart3.usRxRead = 0; /* 接收FIFO读索引 */ g_tUart3.usRxCount = 0; /* 接收到的新数据个数 */ g_tUart3.usTxCount = 0; /* 待发送的数据个数 */ g_tUart3.SendBefor = RS485_SendBefor; /* 发送数据前的回调函数 */ g_tUart3.SendOver = RS485_SendOver; /* 发送完毕后的回调函数 */ g_tUart3.ReciveNew = RS485_ReciveNew; /* 接收到新数据后的回调函数 */ g_tUart3.Sending = 0; /* 正在发送中标志 */#endif} 上面代码中置红的部分是专用于485总线的,对应的代码如下: /*********************************************************************************************************** 函 数 名: RS485_SendBefor* 功能说明: 发送数据前的准备工作。对于RS485通信,请设置RS485芯片为发送状态,* 并修改 UartVarInit()中的函数指针等于本函数名,比如 g_tUart2.SendBefor = RS485_SendBefor* 形 参: 无* 返 回 值: 无**********************************************************************************************************/void RS485_SendBefor(void){ RS485_TX_EN(); /* 切换RS485收发芯片为发送模式 */}/*********************************************************************************************************** 函 数 名: RS485_SendOver* 功能说明: 发送一串数据结束后的善后处理。对于RS485通信,请设置RS485芯片为接收状态,* 并修改 UartVarInit()中的函数指针等于本函数名,比如 g_tUart2.SendOver = RS485_SendOver* 形 参: 无* 返 回 值: 无**********************************************************************************************************/void RS485_SendOver(void){ RS485_RX_EN(); /* 切换RS485收发芯片为接收模式 */}/*********************************************************************************************************** 函 数 名: RS485_ReciveNew* 功能说明: 接收到新的数据* 形 参: _byte 接收到的新数据* 返 回 值: 无**********************************************************************************************************///extern void MODH_ReciveNew(uint8_t _byte);void RS485_ReciveNew(uint8_t _byte){// MODH_ReciveNew(_byte);}
31.4.3 RS485发送处理 串口数据的发送主要涉及到下面四个函数,调用关系是如下: RS485_SendStr –> RS485_SendBuf –> comSendBuf -> UartSend 实际应用中,大家调用函数RS485_SendStr,RS485_SendBuf或者comSendBuf均可。另外特别注意代码中置红的部分,用于设置485发送使能。 /*********************************************************************************************************** 函 数 名: RS485_SendBuf* 功能说明: 通过RS485芯片发送一串数据。注意,本函数不等待发送完毕。* 形 参: _ucaBuf : 数据缓冲区* _usLen : 数据长度* 返 回 值: 无**********************************************************************************************************/void RS485_SendBuf(uint8_t *_ucaBuf, uint16_t _usLen){ comSendBuf(COM3, _ucaBuf, _usLen);}/*********************************************************************************************************** 函 数 名: RS485_SendStr* 功能说明: 向485总线发送一个字符串,0结束。* 形 参: _pBuf 字符串,0结束* 返 回 值: 无**********************************************************************************************************/void RS485_SendStr(char *_pBuf){ RS485_SendBuf((uint8_t *)_pBuf, strlen(_pBuf));}/*********************************************************************************************************** 函 数 名: 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);}/*********************************************************************************************************** 函 数 名: 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); /* 使能发送中断(缓冲区空) */} 函数UartSend的作用就是把要发送的数据填到发送缓冲区里面,并使能发送空中断。
注意:由于函数UartSend做了static作用域限制,仅可在bsp_uart_fifo.c文件中调用。函数RS485_SendStr,RS485_SendBuf或者comSendBuf是供用户调用的。 31.4.4 RS485数据接收 下面我们再来看看接收的函数: /*********************************************************************************************************** 函 数 名: 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文件中调用。 31.4.5 RS485驱动中断服务程序的处理 串口中断服务程序是实现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个串口的中断服务程序,RS485用的USART3。 #if UART1_FIFO_EN == 1void USART1_IRQHandler(void){ UartIRQ(&g_tUart1);}#endif#if UART2_FIFO_EN == 1void USART2_IRQHandler(void){ UartIRQ(&g_tUart2);}#endif#if UART3_FIFO_EN == 1void USART3_IRQHandler(void){ UartIRQ(&g_tUart3);}#endif#if UART4_FIFO_EN == 1void UART4_IRQHandler(void){ UartIRQ(&g_tUart4);}#endif#if UART5_FIFO_EN == 1void UART5_IRQHandler(void){ UartIRQ(&g_tUart5);}#endif#if UART6_FIFO_EN == 1void USART6_IRQHandler(void){ UartIRQ(&g_tUart6);}#endif#if UART7_FIFO_EN == 1void UART7_IRQHandler(void){ UartIRQ(&g_tUart7);}#endif#if UART8_FIFO_EN == 1void 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协议里面要用到。
31.5 RS485板级支持包(bsp_uart_fifo.c) 串口驱动文件bsp_uart_fifo.c主要实现了如下几个API供用户调用:
函数原型: void bsp_InitUart(void) 函数描述: 此函数主要用于串口的初始化,使用所有其它API之前,务必优先调用此函数。 使用举例: 串口的初始化函数在bsp.c文件的bsp_Init函数里面调用。 31.5.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)); 31.5.3 函数RS485_SendBuf 函数原型: void RS485_SendBuf(uint8_t *_ucaBuf, uint16_t _usLen) 函数描述: 此函数用于向RS485总线发送一组数据,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。此函数是通过调用函数comSendBuf实现的。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。 31.5.4 函数RS485_SendStr 函数原型: void RS485_SendStr(char *_pBuf) 函数描述: 此函数用于向RS485总线发送一个字符串,非阻塞方式,数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送。此函数是通过调用函数RS485_SendBuf实现的。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。 31.5.5 函数comGetChar 函数原型: uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte) 函数描述: 此函数用于从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。 函数参数:
调用此函数前,务必优先调用函数bsp_InitUart进行初始化。 比如从串口1读取一个字符就是:comGetChar(COM1, &read) 31.6 RS485驱动移植和使用 RS485移植步骤如下:
#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
/* PB2 控制RS485芯片的发送使能 */#define RS485_TXEN_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()#define RS485_TXEN_GPIO_PORT GPIOB#define RS485_TXEN_PIN GPIO_PIN_2#define RS485_RX_EN() RS485_TXEN_GPIO_PORT->BSRRH = RS485_TXEN_PIN#define RS485_TX_EN() RS485_TXEN_GPIO_PORT->BSRRL = RS485_TXEN_PIN
static void UartVarInit(void){#if UART3_FIFO_EN == 1 g_tUart3.uart = USART3; /* STM32 串口设备 */ g_tUart3.pTxBuf = g_TxBuf3; /* 发送缓冲区指针 */ g_tUart3.pRxBuf = g_RxBuf3; /* 接收缓冲区指针 */ g_tUart3.usTxBufSize = UART3_TX_BUF_SIZE; /* 发送缓冲区大小 */ g_tUart3.usRxBufSize = UART3_RX_BUF_SIZE; /* 接收缓冲区大小 */ g_tUart3.usTxWrite = 0; /* 发送FIFO写索引 */ g_tUart3.usTxRead = 0; /* 发送FIFO读索引 */ g_tUart3.usRxWrite = 0; /* 接收FIFO写索引 */ g_tUart3.usRxRead = 0; /* 接收FIFO读索引 */ g_tUart3.usRxCount = 0; /* 接收到的新数据个数 */ g_tUart3.usTxCount = 0; /* 待发送的数据个数 */ g_tUart3.SendBefor = RS485_SendBefor; /* 发送数据前的回调函数 */ g_tUart3.SendOver = RS485_SendOver; /* 发送完毕后的回调函数 */ g_tUart3.ReciveNew = RS485_ReciveNew; /* 接收到新数据后的回调函数 */ g_tUart3.Sending = 0; /* 正在发送中标志 */#endif}
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下: 第1阶段,上电启动阶段:
配套例子: V7-016_RS485多机通讯 实验目的:
上电后串口打印的信息: 波特率 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();} 主功能: 主功能的实现主要分为两部分:
/*********************************************************************************************************** 函 数 名: main* 功能说明: c程序入口* 形 参: 无* 返 回 值: 错误代码(无需处理)**********************************************************************************************************/int main(void){ uint8_t ucKeyCode; /* 按键代码 */ uint8_t ucDataTravel; /* 发送变量 */ uint8_t ucDataRec; /* 接收变量 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 获取其它开发板通过485总线发来的数据 */ if(comGetChar(COM3, &ucDataRec)) { switch (ucDataRec) { case KEY_DOWN_K1: /* 获得K1键按下消息 */ bsp_LedOn(1); printf("K1键按下, LED1点亮rn"); break; case KEY_UP_K1: /* 获得K1键释放消息 */ bsp_LedOff(1); printf("K1键弹起, LED1熄灭rn"); break; case KEY_DOWN_K2: /* 获得K1键按下消息 */ bsp_LedToggle(2); break; case JOY_DOWN_U: /* 获得摇杆UP键按下 */ printf("摇杆上键按下rn"); break; case JOY_DOWN_D: /* 获得摇杆DOWN键按下 */ printf("摇杆下键按下rn"); break; case JOY_DOWN_L: /* 获得摇杆LEFT键按下 */ printf("摇杆左键按下rn"); break; case JOY_DOWN_R: /* 获得摇杆RIGHT键按下 */ printf("摇杆右键按下rn"); break; case JOY_DOWN_OK: /* 获得摇杆OK键按下 */ printf("摇杆OK键按下rn"); break; case JOY_UP_OK: /* 获得摇杆OK键弹起 */ printf("摇杆OK键弹起rn"); break; default: /* 其它的键值不处理 */ break; } } /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔50ms 进来一次 */ bsp_LedToggle(2); /* 向其它开发板发送按键K2按下的消息 */ ucDataTravel = KEY_DOWN_K2; comSendChar(COM3, ucDataTravel); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下 */ ucDataTravel = KEY_DOWN_K1; comSendChar(COM3, ucDataTravel); bsp_LedOn(1); printf("K1键按下, LED1点亮rn"); break; case KEY_UP_K1: /* K1键弹起 */ ucDataTravel = KEY_UP_K1; comSendChar(COM3, ucDataTravel); bsp_LedOff(1); printf("K1键弹起, LED1熄灭rn"); break; case KEY_DOWN_K2: /* K2键按下 */ bsp_StartAutoTimer(0, 50); /* 启动1个50ms的自动重装的定时器 */ break; case KEY_DOWN_K3: /* K3键按下 */ bsp_StopTimer(0); /* 停止自动重装的定时器 */ break; case JOY_DOWN_U: /* 摇杆UP键按下 */ ucDataTravel = JOY_DOWN_U; comSendChar(COM3, ucDataTravel); printf("摇杆上键按下rn"); break; case JOY_DOWN_D: /* 摇杆DOWN键按下 */ ucDataTravel = JOY_DOWN_D; comSendChar(COM3, ucDataTravel); printf("摇杆下键按下rn"); break; case JOY_DOWN_L: /* 摇杆LEFT键按下 */ ucDataTravel = JOY_DOWN_L; comSendChar(COM3, ucDataTravel); printf("摇杆左键按下rn"); break; case JOY_DOWN_R: /* 摇杆RIGHT键按下 */ ucDataTravel = JOY_DOWN_R; comSendChar(COM3, ucDataTravel); printf("摇杆右键按下rn"); break; case JOY_DOWN_OK: /* 摇杆OK键按下 */ ucDataTravel = JOY_DOWN_OK; comSendChar(COM3, ucDataTravel); printf("摇杆OK键按下rn"); break; case JOY_UP_OK: /* 摇杆OK键弹起 */ ucDataTravel = JOY_UP_OK; comSendChar(COM3, ucDataTravel); printf("摇杆OK键弹起rn"); break; default: /* 其它的键值不处理 */ break; } } }} 31.9 实验例程说明(IAR) 配套例子: V7-016_RS485多机通讯 实验目的:
上电后串口打印的信息: 波特率 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();} 主功能: 主功能的实现主要分为两部分:
/*********************************************************************************************************** 函 数 名: main* 功能说明: c程序入口* 形 参: 无* 返 回 值: 错误代码(无需处理)**********************************************************************************************************/int main(void){ uint8_t ucKeyCode; /* 按键代码 */ uint8_t ucDataTravel; /* 发送变量 */ uint8_t ucDataRec; /* 接收变量 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 获取其它开发板通过485总线发来的数据 */ if(comGetChar(COM3, &ucDataRec)) { switch (ucDataRec) { case KEY_DOWN_K1: /* 获得K1键按下消息 */ bsp_LedOn(1); printf("K1键按下, LED1点亮rn"); break; case KEY_UP_K1: /* 获得K1键释放消息 */ bsp_LedOff(1); printf("K1键弹起, LED1熄灭rn"); break; case KEY_DOWN_K2: /* 获得K1键按下消息 */ bsp_LedToggle(2); break; case JOY_DOWN_U: /* 获得摇杆UP键按下 */ printf("摇杆上键按下rn"); break; case JOY_DOWN_D: /* 获得摇杆DOWN键按下 */ printf("摇杆下键按下rn"); break; case JOY_DOWN_L: /* 获得摇杆LEFT键按下 */ printf("摇杆左键按下rn"); break; case JOY_DOWN_R: /* 获得摇杆RIGHT键按下 */ printf("摇杆右键按下rn"); break; case JOY_DOWN_OK: /* 获得摇杆OK键按下 */ printf("摇杆OK键按下rn"); break; case JOY_UP_OK: /* 获得摇杆OK键弹起 */ printf("摇杆OK键弹起rn"); break; default: /* 其它的键值不处理 */ break; } } /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔50ms 进来一次 */ bsp_LedToggle(2); /* 向其它开发板发送按键K2按下的消息 */ ucDataTravel = KEY_DOWN_K2; comSendChar(COM3, ucDataTravel); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下 */ ucDataTravel = KEY_DOWN_K1; comSendChar(COM3, ucDataTravel); bsp_LedOn(1); printf("K1键按下, LED1点亮rn"); break; case KEY_UP_K1: /* K1键弹起 */ ucDataTravel = KEY_UP_K1; comSendChar(COM3, ucDataTravel); bsp_LedOff(1); printf("K1键弹起, LED1熄灭rn"); break; case KEY_DOWN_K2: /* K2键按下 */ bsp_StartAutoTimer(0, 50); /* 启动1个50ms的自动重装的定时器 */ break; case KEY_DOWN_K3: /* K3键按下 */ bsp_StopTimer(0); /* 停止自动重装的定时器 */ break; case JOY_DOWN_U: /* 摇杆UP键按下 */ ucDataTravel = JOY_DOWN_U; comSendChar(COM3, ucDataTravel); printf("摇杆上键按下rn"); break; case JOY_DOWN_D: /* 摇杆DOWN键按下 */ ucDataTravel = JOY_DOWN_D; comSendChar(COM3, ucDataTravel); printf("摇杆下键按下rn"); break; case JOY_DOWN_L: /* 摇杆LEFT键按下 */ ucDataTravel = JOY_DOWN_L; comSendChar(COM3, ucDataTravel); printf("摇杆左键按下rn"); break; case JOY_DOWN_R: /* 摇杆RIGHT键按下 */ ucDataTravel = JOY_DOWN_R; comSendChar(COM3, ucDataTravel); printf("摇杆右键按下rn"); break; case JOY_DOWN_OK: /* 摇杆OK键按下 */ ucDataTravel = JOY_DOWN_OK; comSendChar(COM3, ucDataTravel); printf("摇杆OK键按下rn"); break; case JOY_UP_OK: /* 摇杆OK键弹起 */ ucDataTravel = JOY_UP_OK; comSendChar(COM3, ucDataTravel); printf("摇杆OK键弹起rn"); break; default: /* 其它的键值不处理 */ break; } } }} 31.10 总结 本章节就为大家讲解这么多,485通信依然在实际项目中非常实用。有线的串行网络仍然提供最有力、最可靠的通信,特别是在恶劣的环境中。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1243 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1256 浏览 1 评论
660 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
491 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1215 浏览 2 评论
1695浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
373浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
353浏览 3评论
341浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
321浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-9-9 07:29 , Processed in 0.703627 second(s), Total 47, Slave 41 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号