OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 94|回复: 0

《STM32H7R7开发指南 V1.1 》第三十九章 RS485实验

[复制链接]

1327

主题

1343

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5671
金钱
5671
注册时间
2019-5-8
在线时间
1524 小时
发表于 前天 09:35 | 显示全部楼层 |阅读模式
第三十九章 RS485实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

本章我们将向大家介绍如何使用STM32H7R7的串口实现485通信(半双工)。在本章中,我们将使用STM32H7R7的串口2来实现两块开发板之间的485通信,并将结果显示在TFTLCD模块上。
本章分为如下几个部分:
39.1 485简介
39.2 硬件设计
39.3 程序设计
39.4 下载验证


39.1 485简介
485(一般称作 RS485/EIA-485)隶属于OSI模型物理层,是串行通讯的一种。电气特性规定为2线,半双工,多点通信的类型。它的电气特性和RS-232大不一样。用缆线两端的电压差值来表示传递信号。RS485仅仅规定了接受端和发送端的电气特性。它没有规定或推荐任何数据协议。
RS485的特点包括:
1,接口电平低,不易损坏芯片。RS485的电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS232降低了,不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL电路连接。
2,传输速率高。可达300bps~250Kbps。
3,抗干扰能力强。RS485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
4,传输距离远,支持节点多。RS485总线最长可以传输1200m左右,更远的距离则需要中继传输设备支持但这时(速率≤100Kbps)才能稳定传输,一般最大支持32个节点,如果使用特制的485芯片,可以达到256个节点。
RS485推荐使用在点对点网络中,比如:线型,总线型网络等,而不能是星型,环型网络。理想情况下RS485需要2个终端匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。485推荐的一主多从连接方式如图39.1.1所示:


第三十九章 RS485实验844.png
图39.1.1 RS485连接

在上面的连接中,如果需要添加匹配电阻,我们一般在总线的起止端加入,也就是主机和设备4上面各加一个120Ω的匹配电阻。
由于RS485具有传输距离远、传输速度快、支持节点多和抗干扰能力更强等特点,所以RS485有很广泛的应用。
STM32H7R7开发板采用TPT8485作为收发器,该芯片支持3.3V供电,最大传输速度可达10Mbps,支持多达32个节点,并且支持输出短路保护。该芯片的框图如图39.1.2所示:


第三十九章 RS485实验1068.png
图39.1.2 TPT8485框图

图中A、B总线接口,用于连接485总线。RO是接收输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。

39.2 硬件设计

1. 例程功能
经过前面的学习我们知道实际的RS485仍是串行通讯的一种电平传输方式,那么我们实际通讯时可以使用串口进行实际数据的收发处理,使用485转换芯片将串口信号转换为485的电平信号进行传输,本章,我们只需要配置好串口2,就可以实现正常的485通信了,串口2的配置和串口1基本类似,只是串口2的时钟来自APB1,最大频率为150Mhz。
本章将实现这样的功能:通过连接两个STM32H7R7的RS485接口,然后由KEY0控制发送,当按下一个开发板的KEY0的时候,就发送5个数据给另外一个开发板,并在两个开发板上分别显示发送的值和接收到的值。

2. 硬件资源
1)LED灯
       LED0 – PD14
2)USART2,用于实际的485信号串行通讯。
3)独立按键  KEY0 –PE9
4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
5)RS485
6)开发板两块

3. 原理图
根据我们需要实现的程序功能,我们设计电路原理如下:


第三十九章 RS485实验1623.png
图39.2.2 RS485连接原理设计

从上图可以看出:开发板的串口2通过P8端口设置,连接到RS485。RS485_RE控制TPT8485的收发,当RS485_RE=0的时候,为接收模式;当RS485_RE=1的时候,为发送模式。
最后,我们用2根导线将两个开发板RS485端子的A和A,B和B连接起来。这里注意不要接反了,接反了会导致通讯异常!!


39.3 程序设计

39.3.1 RS485的HAL库驱动
由于485实际上是串口通讯,我们参照串口实验使用类似的HAL库驱动即可,在这里分析一下RS485配置步骤。
RS485配置步骤
1)使能串口和GPIO口时钟
本实验用到USART2串口,使用PD5和PD6作为串口的TX和RX脚,因此需要先使能USART2和GPIOD时钟。参考代码如下:

  1. __HAL_RCC_USART2_CLK_ENABLE();          /* 使能USART2时钟 */
  2. __HAL_RCC_GPIOD_CLK_ENABLE();           /* 使能GPIOD时钟 */
复制代码
2) 串口参数初始化(波特率、字长、奇偶校验等)
HAL库通过调用串口初始化函数HAL_UART_Init完成对串口参数初始化,详见例程源码。
该函数通常会调用:HAL_UART_MspInit函数来完成对串口底层的初始化,包括:串口及GPIO时钟使能、GPIO模式设置、中断设置等。但是本实验避免与USART1冲突,所以把串口底层初始化没有放在HAL_UART_MspInit函数里。
3)GPIO模式设置(速度,上下拉,复用功能等)
GPIO模式设置通过调用HAL_GPIO_Init函数实现,详见本例程源码。
4)开启串口相关中断,配置串口中断优先级
本实验我们使用串口中断来接收数据。我们使用HAL_UART_ENABLE_IT函数开启串口中断接收,并设置接收buffer及其长度。通过HAL_NVIC_EnableIRQ函数使能串口中断,通过HAL_NVIC_SetPriority函数设置中断优先级。
5)编写中断服务函数
串口2中断服务函数为:USART2_IRQHandler,当发生中断的时候,程序就会执行中断服务函数,在这里就可以对接收到的数据进行处理,详见本例程源码。
6)串口数据接收和发送
最后我们可以通过读写USART_DR寄存器,完成串口数据的接收和发送,HAL库也给我们提供了:HAL_UART_Receive和HAL_UART_Transmit两个函数用于串口数据的接收和发送。
大家可以根据实际情况选择使用哪种方式来收发串口数据。


41.3.2 程序解析

1. RS485驱动
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RS485驱动相关源码包括两个文件:rs485.c和rs485.h。
为方便修改,我们在rs485.h中使用宏定义485相关的控制引脚和串口编号,如果需要使用其它的引脚或者串口,修改宏和串口的定义即可,它们在rs485.h中定义,它们列出如下:

  1. /* 引脚定义 */
  2. #define RS485_RE_GPIO_PORT                  GPIOF
  3. #define RS485_RE_GPIO_PIN                   GPIO_PIN_11
  4. #define RS485_RE_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)  /* PF口时钟使能 */

  5. #define RS485_UARTX                         USART2
  6. #define RS485_UARTX_IRQn                    USART2_IRQn
  7. #define RS485_UARTX_IRQHandler              USART2_IRQHandler
  8. #define RS485_UARTX_CLK_ENABLE()            do { __HAL_RCC_USART2_CLK_ENABLE(); } while (0) /* USART2时钟使能 */

  9. #define RS485_UARTX_TX_GPIO_PORT            GPIOD
  10. #define RS485_UARTX_TX_GPIO_PIN             GPIO_PIN_6
  11. #define RS485_UARTX_TX_GPIO_AF              GPIO_AF7_USART2
  12. #define RS485_UARTX_TX_GPIO_CLK_ENABLE()    do { __HAL_RCC_GPIOD_CLK_ENABLE();} while (0)  /* PD口时钟使能 */

  13. #define RS485_UARTX_RX_GPIO_PORT            GPIOD
  14. #define RS485_UARTX_RX_GPIO_PIN             GPIO_PIN_5
  15. #define RS485_UARTX_RX_GPIO_AF              GPIO_AF7_USART2
  16. #define RS485_UARTX_RX_GPIO_CLK_ENABLE()    do { __HAL_RCC_GPIOD_CLK_ENABLE();} while (0)  /* PD口时钟使能 */

  17. /* 控制RS485_RE脚, 控制RS485发送/接收状态
  18. * RS485_RE = 0, 进入接收模式
  19. * RS485_RE = 1, 进入发送模式
  20. */
  21. #define RS485_RE(x)   do{ x ? \
  22.     HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_SET) : \
  23.     HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_RESET);\
  24.                       }while(0)

  25. /* 接收缓冲区相关定义 */
  26. #define RS485_RX_BUF_LEN                    64
复制代码

1)rs485_init函数
rs485_init的配置与串口类似,也需要设置波特率等参数,另外还需要配置收发模式的驱动引脚,我们的程序设计如下:

  1. /**
  2. * @brief   初始化RS485
  3. * [url=home.php?mod=space&uid=271674]@param[/url]   baudrate: 通信波特率
  4. * @retval  无
  5. */
  6. void rs485_init(uint32_t baudrate)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct;
  9.     RCC_PeriphCLKInitTypeDef usart2_clk_init = {0};
  10.    
  11.     /* 配置时钟 */
  12.     usart2_clk_init.PeriphClockSelection |= RCC_PERIPHCLK_USART234578;
  13.     usart2_clk_init.Usart234578ClockSelection = RCC_USART234578CLKSOURCE_PCLK1;
  14.     HAL_RCCEx_PeriphCLKConfig(&usart2_clk_init);
  15.    
  16.     /* 使能时钟 */
  17.     RS485_UARTX_CLK_ENABLE();
  18.     RS485_UARTX_TX_GPIO_CLK_ENABLE();
  19.     RS485_UARTX_RX_GPIO_CLK_ENABLE();
  20.     RS485_RE_GPIO_CLK_ENABLE();         /* 使能 RS485_RE 脚时钟 */
  21.    
  22.     /* 初始化TX引脚 */
  23.     gpio_init_struct.Pin = RS485_UARTX_TX_GPIO_PIN;
  24.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  25.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  26.     gpio_init_struct.Pull = GPIO_PULLUP;
  27.     gpio_init_struct.Alternate = RS485_UARTX_TX_GPIO_AF;
  28.     HAL_GPIO_Init(RS485_UARTX_TX_GPIO_PORT, &gpio_init_struct);
  29.    
  30.     /* 初始化RX引脚 */
  31.     gpio_init_struct.Pin = RS485_UARTX_RX_GPIO_PIN;
  32.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  33.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  34.     gpio_init_struct.Pull = GPIO_PULLUP;
  35.     gpio_init_struct.Alternate = RS485_UARTX_RX_GPIO_AF;
  36.     HAL_GPIO_Init(RS485_UARTX_RX_GPIO_PORT, &gpio_init_struct);
  37.    
  38.     /* 初始化RE引脚 */
  39.     gpio_init_struct.Pin = RS485_RE_GPIO_PIN;
  40.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
  41.     gpio_init_struct.Pull = GPIO_PULLUP;
  42.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  43. HAL_GPIO_Init(RS485_RE_GPIO_PORT, &gpio_init_struct);
  44. /* RS485_RE 脚 模式设置 */
  45.    
  46.     /* 配置UART中断 */
  47.     HAL_NVIC_SetPriority(RS485_UARTX_IRQn, 0, 0);
  48.     HAL_NVIC_EnableIRQ(RS485_UARTX_IRQn);
  49.    
  50.     /* 初始化UART */
  51.     rs485_uartx_handle.Instance = RS485_UARTX;
  52.     rs485_uartx_handle.Init.BaudRate = baudrate;
  53.     rs485_uartx_handle.Init.WordLength = UART_WORDLENGTH_8B;
  54.     rs485_uartx_handle.Init.StopBits = UART_STOPBITS_1;
  55.     rs485_uartx_handle.Init.Parity = UART_PARITY_NONE;
  56.     rs485_uartx_handle.Init.Mode = UART_MODE_TX_RX;
  57.     rs485_uartx_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  58.     rs485_uartx_handle.Init.OverSampling = UART_OVERSAMPLING_16;
  59.     rs485_uartx_handle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  60.     rs485_uartx_handle.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  61.     HAL_UART_Init(&rs485_uartx_handle);
  62.    
  63.     /* 默认为接收模式 */
  64.     RS485_RE(0);        
  65.    
  66.     /* 使能UART接收中断 */
  67.     __HAL_UART_ENABLE_IT(&rs485_uartx_handle, UART_IT_RXNE);
  68. }
复制代码
可以看到代码基本跟串口的配置一样,只是多了收发控制引脚的配置。

2)发送函数
发送函数用于输出485信号到485总线上,我的默认的485方式一般空闲时为接收状态,只有发送数据时我们才控制485芯片进入发送状态,发送完成后马上回到空闲接收状态,这样可以保证操作过程中485的数据丢失最小。我们实现的发送函数如下:

  1. /**
  2. * @brief   RS485发送数据
  3. * @param   buf: 数据
  4. * @param   len: 数据长度
  5. * @retval  无
  6. */
  7. void rs485_send_data(uint8_t *buf, uint8_t len)
  8. {
  9.     RS485_RE(1);
  10.     HAL_UART_Transmit(&rs485_uartx_handle, buf, len, 1000);
  11.     RS485_RE(0);
  12. }
复制代码

3)485接收中断函数
RS485的接收就与串口中断一样了,不过要注意空闲时要切换回接收状态,否则会收不到数据。我们定义了一个全局的缓冲区g_rs485_rx_buf进行接收测试,通过串口中断接收数据,编写的接收代码如下:

  1. /**
  2. * @brief       UART中断服务函数
  3. * @param       无
  4. * @retval      无
  5. */
  6. void RS485_UARTX_IRQHandler(void)
  7. {
  8.     uint8_t res;
  9.    
  10.     if (__HAL_UART_GET_IT(&rs485_uartx_handle, UART_IT_RXNE) != RESET)
  11.     {
  12.         HAL_UART_Receive(&rs485_uartx_handle, &res, 1, 1000);
  13.         if (g_rs485_rx_cnt < RS485_RX_BUF_LEN)
  14.         {
  15.             g_rs485_rx_buf[g_rs485_rx_cnt++] = res;
  16.         }
  17.     }
  18. }
复制代码

4)485查询接收数据函数
该函数用于查询485总线上接收到的数据,主要实现的逻辑是:先记录下当前接收计数器的值,接收到了数据并且接收完成了,记录本次数据长度,最后g_rs485_rx_cnt清零。函数实现如下:

  1. /**
  2. * @brief   RS485接收数据
  3. * @param   buf: 数据
  4. * @param   len: 数据长度
  5. * @retval  无
  6. */
  7. void rs485_recv_data(uint8_t *buf, uint8_t *len)
  8. {
  9.     uint8_t rxlen = g_rs485_rx_cnt;
  10.     uint8_t i = 0;
  11.     *len = 0;     /* 默认为0 */
  12.     delay_ms(10); /* 等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束 */

  13.     if (rxlen == g_rs485_rx_cnt && rxlen) /* 接收到了数据,且接收完成了 */
  14.     {
  15.         for (i = 0; i < rxlen; i++)
  16.         {
  17.             buf[i] = g_rs485_rx_buf[i];
  18.         }

  19.         *len = g_rs485_rx_cnt; /* 记录本次数据长度 */
  20.         g_rs485_rx_cnt = 0;    /* 清零 */
  21.     }
  22. }
复制代码
RS485的代码就讲到这里,基本是串口的知识,大家不明白的配置可以翻看之前串口章节的知识。

2. main.c代码
在main.c中编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t key;
  4.     uint8_t i = 0, t = 0;
  5.     uint8_t cnt = 0;
  6.     uint8_t rs485buf[5];
  7.    
  8.     sys_mpu_config();                   /* 配置MPU */
  9.     sys_cache_enable();                 /* 使能Cache */
  10.     HAL_Init();                         /* 初始化HAL库 */
  11.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  12.     delay_init(600);                    /* 初始化延时 */
  13.     usart_init(115200);                 /* 初始化串口 */
  14.     usmart_dev.init(300);               /* 初始化USMART */
  15.     led_init();                         /* 初始化LED */
  16.     key_init();                         /* 初始化按键 */
  17.     hyperram_init();                    /* 初始化HyperRAM */
  18.     lcd_init();                         /* 初始化LCD */
  19.     rs485_init(9600);                   /* 初始化RS485 */
  20.    
  21.     lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
  22.     lcd_show_string(30,  70, 200, 16, 16, "RS485 TEST", RED);
  23.     lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  24.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:Send", RED); /* 显示提示信息 */

  25.     lcd_show_string(30, 130, 200, 16, 16, "Count:", RED);    /* 显示当前计数值 */
  26.     lcd_show_string(30, 150, 200, 16, 16, "Send Data:", RED);/* 提示发送的数据 */
  27. lcd_show_string(30, 190, 200, 16, 16, "Receive Data:", RED);
  28. /* 提示接收到的数据 */

  29.     while (1)
  30.     {
  31.         key = key_scan(0);

  32.         if (key == KEY0_PRES)               /* KEY0按下,发送一次数据 */
  33.         {
  34.             for (i = 0; i < 5; i++)
  35.             {
  36.                 rs485buf[i] = cnt + i;      /* 填充发送缓冲区 */
  37.                 lcd_show_xnum(30 + i*32, 170, rs485buf[i], 3, 16, 0X80, BLUE);
  38.                 /* 显示数据 */
  39.             }

  40.             rs485_send_data(rs485buf, 5);   /* 发送5个字节 */
  41.         }

  42.         rs485_recv_data(rs485buf, &key);

  43.         if (key)                            /* 接收到有数据 */
  44.         {
  45.             if (key > 5)key = 5;            /* 最大是5个数据. */

  46.             for (i = 0; i < key; i++)
  47.             {
  48.                 lcd_show_xnum(30 + i*32, 210, rs485buf[i], 3, 16, 0X80, BLUE);
  49.                 /* 显示数据 */
  50.             }
  51.         }

  52.         t++;
  53.         delay_ms(10);

  54.         if (t == 20)
  55.         {
  56.             LED0_TOGGLE();                  /* LED0闪烁, 提示系统正在运行 */
  57.             t = 0;
  58.             cnt++;
  59.             lcd_show_xnum(30 + 48, 130, cnt, 3, 16, 0X80, BLUE);/* 显示数据 */
  60.         }
  61.     }
  62. }

复制代码
我们是通过按键控制数据的发送。在此部分代码中,cnt是一个累加数,一旦KEY0按下,就以这个数位基准连续发送5个数据。当485总线收到数据得时候,就将收到的数据直接显示在LCD屏幕上。

39.4 下载验证
在代码编译成功之后,我们通过下载代码到正点原子STM32H7R7开发板上(注意要2个开发板都下载这个代码哦),得到如图39.4.1所示:

第三十九章 RS485实验10935.png
图39.4.1 程序运行效果图

伴随DS0的不停闪烁,提示程序在运行。此时,我们按下KEY0就可以在另外一个开发板上面收到这个开发板发送的数据了。如图39.4.2和图39.4.3所示:

第三十九章 RS485实验11030.png
图39.4.2 发送RS485数据的开发板界面

第三十九章 RS485实验11056.png
图39.4.3 接收RS485数据的开发板

图39.4.2来自开发板A,发送了5个数据,图39.4.3来自开发板B,接收到了来自开发板A的5个数据。
本章介绍的485总线时通过串口控制收发的,我们只需要将P8的跳线帽稍作改变(将PD5/PD6连接COM2_RX/COM2_TX),该实验就变成了一个RS232串口通信实验了,通过对接两个开发板的RS232接口,即可得到同样的实验现象,不过RS232不需要使能脚,有兴趣的读者可以实验一下。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-5-14 02:53

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表