当前位置:   article > 正文

STM32实现基于RS485的简单的Modbus协议_stm32 modbus

stm32 modbus

背景

我这里用STM32实现,其实可以搬移到其他MCU,之前有项目使用STM32实现Modbus协议

这个场景比较正常,很多时候都能碰到

这里主要是Modbus和变频器通信

最常见的是使用Modbus实现传感器数据的采集,我记得之前用过一些传感器都是Modbus协议

这就需要MCU实现Modbus协议,不过实际使用的Modbus协议往往都是简化版本的

可能只是几条Modbus协议格式的指令而已

初学者,网上一搜Modubus协议,往往越看越糊涂

原理图

如下图所示,使用STM32 UART2,采用485接口设计引出

解释一下为什么这里的485电路设计的这么复杂

这里考虑485带电插拔操作,以及客户要求隔离功能等,所以硬件上设计比常用电路复杂很多

其实主要功能都是一致的

软件设计 

初始化串口,这里写的比较复杂,因为考虑了串口2也就是485接口的波特率是可以配置的,并且配置后掉电保存,所以有个波特率的接口,当然同时也有校验位可配置

如下配置,串口采用中断模式,使用串口2,对应管脚PA2/PA3

  1. void Bsp_usart2_cfg(u8 baud, u8 checkbit)
  2. {
  3. NVIC_InitTypeDef NVIC_InitStructure;
  4. GPIO_InitTypeDef GPIO_InitStructure;
  5. USART_InitTypeDef USART_InitStructure;
  6. u32 BaudRate;
  7. switch(baud)
  8. {
  9. case 0:
  10. {
  11. BaudRate = 300;
  12. break;
  13. }
  14. case 1:
  15. {
  16. BaudRate = 600;
  17. break;
  18. }
  19. case 2:
  20. {
  21. BaudRate = 1200;
  22. break;
  23. }
  24. case 3:
  25. {
  26. BaudRate = 2400;
  27. break;
  28. }
  29. case 4:
  30. {
  31. BaudRate = 4800;
  32. break;
  33. }
  34. case 5:
  35. {
  36. BaudRate = 9600;
  37. break;
  38. }
  39. case 6:
  40. {
  41. BaudRate = 19200;
  42. break;
  43. }
  44. case 7:
  45. {
  46. BaudRate = 38400;
  47. break;
  48. }
  49. case 8:
  50. {
  51. BaudRate = 57600;
  52. break;
  53. }
  54. case 9:
  55. {
  56. BaudRate = 115200;
  57. break;
  58. }
  59. default:
  60. {
  61. BaudRate = 9600;
  62. break;
  63. }
  64. }
  65. RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);
  66. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
  67. /*
  68. * USART2_TX -> PA2 , USART2_RX -> PA3
  69. */
  70. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  71. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  72. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  73. GPIO_Init(GPIOA, &GPIO_InitStructure);
  74. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  75. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  76. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  77. GPIO_Init(GPIOA, &GPIO_InitStructure);
  78. USART_InitStructure.USART_BaudRate = BaudRate;
  79. ///USART_InitStructure.USART_WordLength = USART_WordLength_9b;//9位数据
  80. USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  81. //if(checkbit == 0)
  82. //USART_InitStructure.USART_StopBits = USART_StopBits_2;//1位停止位
  83. //else
  84. USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
  85. if((checkbit == 0) || (checkbit == 3))
  86. USART_InitStructure.USART_Parity = USART_Parity_No;//
  87. else if(checkbit == 1)
  88. USART_InitStructure.USART_Parity = USART_Parity_Even;//偶校验
  89. else if(checkbit == 2)
  90. USART_InitStructure.USART_Parity = USART_Parity_Odd;//奇校验
  91. else
  92. USART_InitStructure.USART_Parity = USART_Parity_No;//
  93. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制失能
  94. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //发送和接受使能
  95. USART_Init(USART2, &USART_InitStructure);
  96. NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  97. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = USART2_IRQCHANNELPP;// 设置抢占优先级
  98. NVIC_InitStructure.NVIC_IRQChannelSubPriority = USART2_IRQCHANNELSP;
  99. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  100. NVIC_Init(&NVIC_InitStructure);
  101. // USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 使能USART接收中断,这里先不开启接收中断
  102. USART_Cmd(USART2, ENABLE);
  103. USART_ClearITPendingBit(USART2, USART_IT_TC);//清除中断TC位
  104. while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);//等待传输完成,否则第一位数据容易丢失
  105. }

串口2的中断处理函数如下

这里很简单,就是把串口2的数据收集起来放到队列comrx2xQueue中

  1. void USART2_IRQHandler(void)
  2. {
  3. portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
  4. uint8_t cChar;
  5. uint16_t msg;
  6. if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET) // ORE中断
  7. {
  8. USART_ReceiveData(USART2);
  9. }
  10. if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) // 接收数据中断
  11. {
  12. cChar = USART_ReceiveData( USART2 );
  13. msg = MSG_USART_EVT | (cChar);
  14. xQueueSendFromISR( comrx2xQueue, &msg, &xHigherPriorityTaskWoken );
  15. }
  16. portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
  17. }

在串口2的接收任务中进行

协议帧格式匹对

如下代码,使用状态机跳转到接收处理位置

  1. void tskcomrx2( void *pvParameters )
  2. {
  3. uint16_t Msg;
  4. QueueHandle_t pq = pvParameters;
  5. uint8_t stt = FSM_IDLE,*prx;
  6. uint16_t tmp16,len;
  7. while(1)
  8. {
  9. if( xQueueReceive( pq, &Msg, 20 ) == pdPASS )
  10. {
  11. if(MSG_NAME(Msg) == MSG_USART_EVT)
  12. {
  13. tmp16 = MSG_DATA(Msg);
  14. //调试语句,打印接受数据到调试串口1
  15. // while((USART1->SR&0x40)==0);//等待上一次发送完毕
  16. // USART1->DR = tmp16;
  17. //
  18. switch (stt)
  19. {
  20. case FSM_IDLE :
  21. {
  22. prx = StdDatBufIn2;
  23. *prx = 0;
  24. len = 0;
  25. if (tmp16 == FlashParagma.addr)
  26. {
  27. /*数据开始*/
  28. len++;
  29. *prx++ = tmp16;
  30. *prx = 0;
  31. stt = FSM_HEAD;
  32. }
  33. break;
  34. }
  35. case FSM_HEAD :
  36. {
  37. len++;
  38. *prx++ = tmp16;
  39. *prx = 0;
  40. if ((tmp16 == 0x03) || (tmp16 == 0x06))
  41. {
  42. stt = FSM_ASCII_DATA;
  43. }
  44. else
  45. {
  46. stt = FSM_IDLE;//异常处理
  47. }
  48. break;
  49. }
  50. case FSM_ASCII_DATA :
  51. {
  52. len++;
  53. *prx++ = tmp16;
  54. *prx = 0;
  55. if(len > 7)
  56. {
  57. //处理接收数据
  58. modbusEventPro(StdDatBufIn2, len);
  59. stt = FSM_IDLE;//异常处理
  60. }
  61. break;
  62. }
  63. default:
  64. {
  65. stt = FSM_IDLE;
  66. break;
  67. }
  68. }
  69. }/**end of if(MSG_NAME(pMsg)*/
  70. }
  71. }/*end of while(1)*/
  72. }/*end of void tskDatRxCOM1(void * pdata) */

根据modbus协议指令分类进行数据处理,代码如下

功能码03、06进行处理

  1. // Modbus事件处理函数
  2. void modbusEventPro(u8 *src, u16 len)
  3. {
  4. u16 crc,rccrc;
  5. //收到数据包长度判定
  6. //通过读到的数据帧计算CRC
  7. crc = Modbus_CRC16(&src[0], len - 2);
  8. // 读取数据帧CRC
  9. rccrc = src[len - 2] + src[len - 1] * 256;
  10. if(crc == rccrc) //CRC校验成功,开始分包
  11. {
  12. if(src[0] == FlashParagma.addr) //检测是否是自己的地址
  13. {
  14. switch(src[1]) //分析modbus功能码
  15. {
  16. case 3:
  17. {
  18. Modbus_Func3(src, len);
  19. break;
  20. }
  21. case 6:
  22. {
  23. Modbus_Func6(src, len);
  24. break;
  25. }
  26. default:
  27. break;
  28. }
  29. }
  30. else if(src[0] == 0) //广播地址不予回应
  31. {
  32. }
  33. }
  34. }

发送modbus协议指令,这里需要先把发送模式打开,发送数据完成后,注意要延时一段时间再切换为接收模式,这个延时时间需要自己根据调试情况进行实际调整

控制不同类型的从机,延时时间要求可能不太一样

  1. void Modbus_USRAT2_SendStr(u8 *scr, u16 len)
  2. {
  3. u16 i;
  4. // 开始返回Modbus数据
  5. Modbus_USART2_TX_Mode;
  6. vTaskDelay(5);
  7. for(i = 0; i < len; i++)
  8. {
  9. while((USART2->SR&0x40)==0);//等待上一次发送完毕
  10. USART2->DR = scr[i];
  11. }
  12. vTaskDelay(5);
  13. Modbus_USART2_RX_Mode;
  14. }

总结

这实现的比较简单,且常用的Modbus协议

协议格式如下,采用高字节在前方式

地址

功能码

从机地址

数据

校验

485从机地址

03H(读)、06H(写)

CRC

1byte

1byte

2byte

4byte

2byte

上述Modbus协议,实现03、06指令,即可完成对从机地址的读写。

上述代码实现,也是根据表格中的格式进行实现的,可以和代码对的上。

其他

网上搜集了一下关于RS485和Modbus协议的解释,这里拿出来比较关键的,供参考

关于RS485(主要是关注传输距离、接口线、电平)

RS-485是美国电子工业协会(EIA)在1983年批准了一个新的平衡传输标准(balanced transmission standard),EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。目前标准名称为TIA-485,但工程师及应用指南仍继续使用RS-485来称呼此标准。

RS-485仅是一个电气标准,描述了接口的物理层,像协议、时序、串行或并行数据以及链路全部由设计者或更高层协议定义。RS-485定义的是使用平衡(也称作差分)多点传输线的驱动器(driver)和接收器(receiver)的电气特性。

  • 差分传输增加噪声抗扰度,减少噪声辐射
  • 长距离链路,最长可达4000英尺(约1219米)
  • 数据速率高达10Mbps(40英寸内,约12.2米)
  • 同一总线可以连接多个驱动器和接收器
  • 宽共模范围允许驱动器和接收器之间存在地电位差异,允许最大共模电压-7-12V

关于Modbus协议

MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。

Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。

Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。另外,Modbus采用主从方式定时收发数据,在实际使用中如果某Slave站点断开后(如故障或关机),Master端可以诊断出来,而当故障修复后,网络又可自动接通。因此,Modbus协议的可靠性较好。

对于Modbus的ASCII、RTU和TCP协议来说,其中TCP和RTU协议非常类似,我们只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。

1通讯传送方式:

通讯传送分为独立的信息头,和发送的编码数据。以下的通讯传送方式定义也与ModBusRTU通讯规约相兼容:

初始结构 = ≥4字节的时间

地址码 = 1 字节

功能码 = 1 字节

数据区 = N 字节

错误校检 = 16位CRC码

结束结构 = ≥4字节的时间

地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。并且每个从机都有具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。

功能码:通讯传送的第二个字节。ModBus通讯规约定义功能号为1到127。本仪表只利用其中的一部分功能码。作为主机请求发送,通过功能码告诉从机执行什么动作。作为从机响应,从机发送的功能码与从主机发送来的功能码一样,并表明从机已响应主机进行操作。如果从机发送的功能码的最高位为1(比如功能码大与此同时127),则表明从机没有响应操作或发送出错。

数据区:数据区是根据不同的功能码而不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。

CRC码:二字节的错误检测码。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/665091
推荐阅读
相关标签
  

闽ICP备14008679号