完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
18.1关于 RS485
RS485是美国电子工业协会(Electronic Industries Association,EIA)于1983年发布的串行通信接口标准,经通讯工业协会(TIA)修订后命名为TIA/EIA-485-A RS485具有支持多节点(32个节点)、传输距离远(最大1219m)、接收灵敏度高(200mV电压)、连接简单(在构成通信网络时,仅需要一对双绞线作传输线)、能抑制共模干扰(差分传输)、成本低廉等特点,在多站、远距离通信等多种工控环境中获得了广泛应用。 RS485比RS232晚出现20多年,很多RS232的缺点,在RS485上有了改进。 RS232的电平从-15V至+15V,较高的电平值易损坏接口电路的芯片,而RS485采用差分信号后,电平范围为-6V至+6V,相对不易损坏接口电路芯片,同时RS485接口信号电平与TTL信号电平兼容,便于连接TTL电路。 RS232传输速率比较低,传输速率为20Kbps,而RS485最高传输速率达10Mbps。过高的传输速率会降低传输距离,在实际应用中,RS485传输速率往往设置为9600bps或更低。 RS232采用逻辑电平,共地传输容易产生共模干扰,抗噪声干扰性弱,传输距离有限,常用传输距离就几十米左右。而RS485采用平衡发送和差分接收方式,具有抑制共模干扰的能力,加之总线收发器具有高灵敏度,能检测低至200mV的电压,因此RS485的传输距离达到千米以外。 RS232在总线上只允许连接1个收发器,即单站能力,而RS485在总线上允许连接多达128个收发器,即具有多站能力,可以利用单一的RS485方便地建立起设备网络,如图 18.1.1 所示,为RS485通信网络结构。 在RS485通信网络中,通常使用485收发器将TTL电平转换成RS485的差分信号。MCU的串口控制器TxD发送数据,经485收发器转换成差分信号,传输到总线上。接收数据时,485收发器将总线上的差分信号转化成TTL信号由RxD到串口控制器。整个通信网络中,通常只有一个主机,剩下的全部为从机。在RS485总线中,通常还需要在总线起止端分别加上约120Ω的终端匹配电阻,以保证RS485总线的稳定性。 RS485同样可以使用DB9接口将信号引脚引出,实际工程中通常使用接线端子引出,如图 18.1.2 所示。图中左边的为螺钉式接线端子,适合固定连接的场合,图中右边为插拔式接线端子,适合需要调整的场合。本开发板使用的插拔式接线端子,如上图 3.3.1 中编号10部分所示。 18.2 硬件设计 如图 18.2.1 为开发板RS485部分的原理图,U16为3.3V低功耗半双工收发器,满足RS-485和RS-422标准。 USART的RX和TX,经过U16转换,变为RS485的A、B。 U16的2脚RE����为接收使能,上划线表示低电平有效,即当U16的2脚为低电平时,U16接收数据。U16的3 脚DE为输出使能,高电平有效,即当U16的3脚为高电平是,U16发送数据。 因此,RS485除了USART,还多了一个收发控制引脚,该引脚使用的PC5。R64为终端匹配电阻,阻值为120Ω。 结合前面图 17.2.1 电路和表格可知,如果需要将USART2分配给RS485使用,还要将J11(蓝色拨码开关)的1号脚拨到ON位置。 18.3软件设计 18.3.1软件设计思路 实验目的:RS485是差分信号,收发数据时,A、B都在工作。开发板也只提供了一个RS485接口,因此不能自发自收实验,需要至少两个RS485设备进行实验。这里假设两个开发板进行RS485通信,一个做主机,一个做从机,主机发送数据给从机,从机收到数据再发给主机,实现两个设备的收发数据,供读者参考和方便移植。
18.3.2软件设计讲解 GPIO 引脚选择与串口选择 本实验会用到两个串口,USART1用于调试、USART2用于RS485,在代码框架上,将每个串口都单独放在“.c”文件里,方便修改裁剪。 代码段 18.3.1 调试串口 USART1 相关宏定义(driver_usart1.h) /********************* * 引脚宏定义 **********************/ #define DEBUG_USART USART1 #define DEBUG_USART_RX_PIN GPIO_PIN_10 #define DEBUG_USART_TX_PIN GPIO_PIN_9 #define DEBUG_USART_PORT GPIOA #define DEBUG_USART_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE() #define DEBUG_USART_CLK_EN() __HAL_RCC_USART1_CLK_ENABLE() #define DEBUG_USART_CLK_DIS() __HAL_RCC_USART1_CLK_DISABLE() #define DEBUG_USART_IRQn USART1_IRQn 代码段 18.3.2 调试串口 USART2 相关宏定义(driver_usart2.h) /********************* * 引脚宏定义 **********************/ #define RS485 USART2 #define RS485_RX_PIN GPIO_PIN_3 #define RS485_TX_PIN GPIO_PIN_2 #define RS485_PORT GPIOA #define RS485_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE() #define RE_DE_PIN GPIO_PIN_5 #define RE_DE_PORT GPIOC #define RE_DE_GPIO_CLK_EN() __HAL_RCC_GPIOC_CLK_ENABLE() /********************* * 函数宏定义 **********************/ // 此引脚高电平是发送有效接收无效;低电平时接收有效发送无效 #define RE_DE_TX() HAL_GPIO_WritePin(RE_DE_PORT, RE_DE_PIN, GPIO_PIN_SET) #define RE_DE_RX() HAL_GPIO_WritePin(RE_DE_PORT, RE_DE_PIN, GPIO_PIN_RESET) #define RS485_IRQn USART2_IRQn #define RS485_IRQHandler USART2_IRQHandler #define RS485_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE() #define RS485_CLK_DISABLE() __HAL_RCC_USART2_CLK_DISABLE() 分别定义了两个串口、对应GPIO、时钟使能,方便代码复用,同时定义了RS485的收发控制引脚。 初始化USART USART初始化包含两部分:协议部分和硬件部分。 协议部分放在各自“.c”文件里,硬件部分都是调用“HAL_UART_Init()”,单独创建一个“.c”文件处理。USART1作为调试串口,初始化和前面的实验一样,这里直接跳过。USART2作为RS485,初始化如代码段 18.3.3 所示。 代码段 18.3.3 USART2 初始化(driver_usart2.c) /* * 函数名:void RS485_Init(uint32_t baudrate) * 输入参数:baudrate-串口波特率 * * 输出参数:无 * 返回值:无 * 函数作用:初始化 USART 的波特率,收发选择,有效数据位等 */ void RS485_Init(uint32_t baudrate) { husart2.Instance = RS485; // 选择 USART2 husart2.Init.BaudRate = baudrate; // 配置波特率 husart2.Init.WordLength = USART_WORDLENGTH_8B; // 配置数据有效位为 8bit husart2.Init.StopBits = USART_STOPBITS_1; // 配置一位停止位 husart2.Init.Parity = USART_PARITY_NONE; // 不设校验位 husart2.Init.Mode = USART_MODE_TX_RX; // 可收可发 husart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 使用库函数初始化 USART2 的参数 if (HAL_UART_Init(&husart2) != HAL_OK) { Error_Handler(); } } RS485的本质还是串口,串口的初始化和之前的基本一样。RS485通常也遵循“96-N-8-1”格式,96指波特率9600,N指无校验,8指8bits数据位,1指1bit停止位。 串口协议初始化完后,都调用“HAL_UART_Init()”进行设置,在“HAL_UART_Init()”调用 “HAL_UART_MspInit()”初始化串口硬件部分。 代码段 18.3.4 USART MSP 初始化(driver_msp_usart.c) /* * 函数名:void AL_USART_MspInit(USART_HandleTypeDef* husart) * 输入参数:husart-USART 句柄 * 输出参数:无 * 返回值:无 * 函数作用:使能 USART1、2 的时钟,使能引脚时钟,并配置引脚的复用功能 */ void HAL_UART_MspInit(UART_HandleTypeDef* husart) { // 定义 GPIO 结构体对象 GPIO_InitTypeDef GPIO_InitStruct = {0}; if(husart->Instance==DEBUG_USART) { // 使能 USART1 的时钟 DEBUG_USART_CLK_EN(); // 使能 USART1 的输入输出引脚的时钟 DEBUG_USART_GPIO_CLK_EN(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = DEBUG_USART_TX_PIN; // 选择 USART1 的 TX 引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能 GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快 HAL_GPIO_Init(DEBUG_USART_PORT, &GPIO_InitStruct); // 初始化 TX 引脚 GPIO_InitStruct.Pin = DEBUG_USART_RX_PIN; // 选择 RX 引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; // 配置为输入 HAL_GPIO_Init(DEBUG_USART_PORT, &GPIO_InitStruct); // 初始化 RX 引脚 } else if(husart->Instance==RS485) { // 使能 USART2 的时钟 RS485_CLK_ENABLE(); // 使能 USART2 的输入输出和方向引脚的时钟 RS485_GPIO_CLK_EN(); RE_DE_GPIO_CLK_EN(); /**USART2 GPIO Configuration PA2 ------> USART2_TX PA3 ------> USART2_RX */ GPIO_InitStruct.Pin = RS485_TX_PIN; // 选择 USART2 的 TX 引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快 HAL_GPIO_Init(RS485_PORT, &GPIO_InitStruct); // 初始化 TX 引脚 GPIO_InitStruct.Pin = RS485_RX_PIN; // 选择 RX 引脚 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 配置为输入 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉 HAL_GPIO_Init(RS485_PORT, &GPIO_InitStruct); // 初始化 RX 引脚 GPIO_InitStruct.Pin = RE_DE_PIN; // 选择方向引脚 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 配置为输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉 HAL_GPIO_Init(RE_DE_PORT, &GPIO_InitStruct); // 初始化方向引脚 RE_DE_RX(); // 初始化后默认处于接收状态 HAL_NVIC_SetPriority(RS485_IRQn, 1, 1); // 设置 USART2 的中断等级(0-15)(0-15) // 规则:(0,0)最高,(0,1)次之依次由高到低排序到(15,15) HAL_NVIC_EnableIRQ(RS485_IRQn); // 使能 USART2 的中断 } } 先后初始化了USART1和USART2的硬件部分,其中USART2设置了中断优先级和使能了中断,便可以使用“HAL_UART_Receive_IT()”和“HAL_UART_Transmit_IT()”收发数据。接着将RS485的收发函数进行封装,如代码段 18.3.5 所示。 代码段 18.3.5 RS485 收发函数(driver_usart2.c) /* * 函数名:void RS485_Tx(uint8_t *pdata, uint16_t sz) * 输入参数:pdata->指向发送数据所存储的首地址 sz->发送数据个数 * 输出参数:无 * 返回值:无 * 函数作用:USART2 的发送函数 */ void RS485_Tx(uint8_t *pdata, uint16_t sz) { usart2_tx_finish = 0; RE_DE_TX(); HAL_UART_Transmit_IT(&husart2, pdata, sz); } /* * 函数名:void RS485_Rx(uint8_t *pdata, uint16_t sz) * 输入参数:pdata->指向接收数据所存储的首地址 sz->接收数据个数 * 输出参数:无 * 返回值:无 * 函数作用:USART2 的接收函数 */ void RS485_Rx(uint8_t *pdata, uint16_t sz) { usart2_rx_finish = 0; HAL_UART_Receive_IT(&husart2, pdata, sz); } 11行:usart2_tx_finish为一个全局变量,用来标记USART2是否发送完成。这里将其设置为0,USART2发送完成后,在中断函数将其置为1,通过该标记便可得知USART2是否发送完成; 13行:RS485设备通常默认为接收状态,以方便接收数据。这里发送数据,需要手动临时改为发送状态; 14行:调用串口中断函数发送数据; 27行:usart2_rx_finish为一个全局变量,用来标记USART2是否接收完成。这里将其设置为0,USART2接收数据完成后,在中断函数将其置为1,通过该标记便可得知USART2是否接收到数据; 29行:RS485设备通常默认为接收状态,这里无需其它设置,直接调用串口中断函数接收数据; 中断回调函数 当USART2发生中断时,将自动调用“USART2_IRQHandler()”,“USART2_IRQHandler()”又调用“HAL_UART_IRQHandler()”,最后调用“HAL_UART_TxCpltCallback()”或“HAL_UART_RxCpltCallback()”, 在这两个回调函数里修改USART2接收/发送完成标志,以便后面查询是否收发成功。 代码段 18.3.6 USART2 中断回调函数(driver_msp_usart.c) void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == RS485) { usart2_tx_finish = 1; RE_DE_RX(); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == RS485) { usart2_rx_finish = 1; } } 在发送完成回调函数里,需要将RS485设置为默认的接收模式,以方便随时接收数据。 按键中断函数 因为是通过按键来控制RS485主机发送数据,这里还需要编写按键中断函数。参考前面的按键中断实验,首先初始化按键引脚、设置中断优先级、使能中断,便可在发生按键事件时,自动调用中断回调函数“HAL_GPIO_EXTI_Callback()”,在中断回调函数里,修改按键标志,以便随时查询是否该按键按下,按键中断回调函数如代码段 18.3.7 所示。 代码段 18.3.7 按键中断回调函数(driver_key.c) /* * 函数名:void HAL_GPIO_EXTI_Callback(void) * 输入参数:无 * 输出参数:无 * 返回值:无 * 函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == KEY_UP_GPIO_PIN) { step = 0; }
代码段 18.3.8 RS485 主机主函数(master_main.c) // 初始化 USART1,设置波特率为 115200 bps DEBUG_USART_Init(115200); // 初始化 USART2,设置波特率为 9600 bps RS485_Init(9600); // 初始化按键 KeyInit(); // 在 windows 下字符串nr 表示回车 // 如果工程在编译下面这句中文的时候报错,请在“Option for target”->"C/C++"->"Misc Controls"添加“ --locale=english” printf("百问科技 www.100ask.netnr"); printf("RS485 收发实验nr"); printf("当前设备:主机nr"); printf("nr"); // 初始化 RS485 CAN 的发送信息 RS485_Msg.ID = 0x305; RS485_Msg.length = 8; for(i=0; i<8; i++) { RS485_Msg.tx_data = i; } RS485_Rx((uint8_t*)&RS485_Msg.rx_data[0], RS485_Msg.length); while(1) { if(step == 0) // KEY1 按下 { RS485_Tx((uint8_t*)&RS485_Msg.tx_data[0], RS485_Msg.length); printf("主机 ----> 从机 数据:nr"); for(i=0; i<8; i++) { printf("0x%x ", RS485_Msg.tx_data); } printf("nr"); step = 0xFF; } if(usart2_rx_finish == 0x01) { usart2_rx_finish = 0; printf("主机 <---- 从机 数据:nr"); for(i=0; i<8; i++) { printf("0x%x ", RS485_Msg.rx_data); } printf("nr"); RS485_Rx((uint8_t*)&RS485_Msg.rx_data[0], RS485_Msg.length); } } 1~8行:初始化调试串口、RS485和按键; 17~23行:创建要发送的数据,RS485_Msg为自己创建的数据结构,包含ID、数据长度、数据内容,其中ID可用于设备识别,整个数据数据结构,可方便数据传输; 25行:先接收数据,将设备设置为默认的接收模式; 29~40行:如果按键按下,则按键标志step变为0,此时调用“RS485_Tx()”发送数据; 42~53行:如果接到数据,则接收标志usart2_rx_finish变为1,此时将接收数据打印处理,并再次调用“RS485_Rx()”接收数据; 在RS485从机的主函数如代码段 18.3.9 所示。 代码段 18.3.9 RS485 从机主函数(slave_main.c) // 初始化 USART1,设置波特率为 115200 bps DEBUG_USART_Init(115200); // 初始化 USART2,设置波特率为 9600 bps RS485_Init(9600); // 初始化按键 KeyInit(); // 在 windows 下字符串nr 表示回车 // 如果工程在编译下面这句中文的时候报错,请在“Option for target”->"C/C++"->"Misc Controls"添加“ --locale=english” printf("百问科技 www.100ask.netnr"); printf("RS485 收发实验nr"); printf("当前设备:从机nr"); printf("nr"); // 初始化 RS485 CAN 的发送信息 RS485_Msg.ID = 0x305; RS485_Msg.length = 8; for(i=0; i<8; i++) { RS485_Msg.tx_data = i ^ 0xAB; } RS485_Rx((uint8_t*)&RS485_Msg.rx_data[0], RS485_Msg.length); while(1) { if(usart2_rx_finish == 0x01) { usart2_rx_finish = 0; printf("从机 <---- 主机 数据:nr"); for(i=0; i<8; i++) { printf("0x%x ", RS485_Msg.rx_data); } printf("nr"); RS485_Tx((uint8_t*)&RS485_Msg.tx_data[0], RS485_Msg.length); printf("从机 ----> 主机 数据:nr"); for(i=0; i<8; i++) { printf("0x%x ", RS485_Msg.tx_data); } printf("nr"); RS485_Rx((uint8_t*)&RS485_Msg.rx_data[0], RS485_Msg.length); } } 1~8行:初始化调试串口、RS485和按键; 17~23行:创建要发送的数据; 25行:先接收数据,将设备设置为默认的接收模式; 29~49行:如果接收到数据,先打印接收的数据,然后调用“RS485_Tx()”发送数据,最后再调用“RS485_Rx()”接收数据;
本实验对应配套资料的“5_程序源码 10_通信—RS485”。准备两个开发板,连接好下载器。在Keil中,分别切换到RS485主机工程和RS485从机工程,编译,分别给两个开发板下载RS485主机程序和RS485从机程序。 将两个开发板的J11(蓝色拨码开关)的1脚拨为ON,使用配套的插拔式接线端子将两个板子的RS485接口连接,注意RS485不需要交叉,即两个开发板RS485的A对A,B对B,最后连接好两个开发板串口和电源,如图 18.4.1 所示。 启动电源后,串口会有打印当前设备是主机还是从机。按下主机的KEY1_U,主机将数据发送给从机,从机接收到数据后,发送新数据给主机,如图 18.4.2 所示。 【总结】 调试串口、RS232、RS485本质都是一样的,不同的部分由转换芯片实现,用户几乎不用关心转换实现。因此,用户只需要控制串口收发数据即可。 串口的编程,可分为三步: |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1760 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1613 浏览 1 评论
1058 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1670 浏览 2 评论
1932浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
723浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
564浏览 3评论
590浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
548浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-20 11:42 , Processed in 0.920820 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号