找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4072|回复: 5
收起左侧

我的ModBus主机-UART篇

  [复制链接]
ID:446156 发表于 2021-11-15 09:46 | 显示全部楼层 |阅读模式
众所周知,ModBus从机很好实现,而主机就稍微麻烦一点。下面我将介绍这几年来我用到的ModBus主机方案,既作为分享又作为一个记录与总结。
谈到ModBus就不得不说UART,UART作为ModBus协议的承载是整个ModBus通信的基础。
UART的基本收发功能通过文件“UartDebug.c”和“UartDebug.h”来实现,首先来看“UartDebug.h”文件的内容。
  1. #ifndef __UartDebug_H
  2. #define __UartDebug_H

  3. #include "Header.h"
  4. #include "usart.h"

  5. #define BUFMAX 32
  6. #define SLIENTTIME 5

  7. #define RE_DE1 PCout(1)
  8. #define RE_DE2 PFout(8)

  9. struct UartDebugMember
  10. {
  11.     void (*REDE)(uint8_t a);
  12.     UART_HandleTypeDef *Uart;
  13.     uint16_t *UartData;
  14.     uint8_t TransmitBuf[BUFMAX+1];
  15.     uint8_t ReceiveBuf[BUFMAX+1];
  16.     uint8_t RecPointClearEn;
  17.     uint8_t ReceivePoint;
  18.     uint8_t DataTimeCount;
  19.     uint8_t DataTimeCountEn;
  20.     uint8_t ReceiveFinish;
  21. };

  22. extern struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;

  23. void UartDebugInit(void);
  24. void DataReceive(struct UartDebugMember *UDM);
  25. void TimeCountReceive(struct UartDebugMember *UDM);
  26. void ClearRxData(struct UartDebugMember *UDM);
  27. void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length);
  28. void SendString(struct UartDebugMember *UDM,char *String);
  29. void RS485_REDE_1(uint8_t a);
  30. void RS485_REDE_2(uint8_t a);
  31. void Null(uint8_t a);

  32. #endif
复制代码

"Header.h"文件包含了基本的单片机信息,移植代码的时候只需要将相应的头文件替换掉就可以了,本章最后会贴出"Header.h"的具体内容。

“#define BUFMAX 32” 设置收发缓冲区的大小;
“#define SLIENTTIME 5” 总线静默时间阈值,用于判断该帧数据是否接收完毕;
“#define RE_DE1 PCout(1)”和“#define RE_DE2 PFout(8)”为RS485芯片收发控制IO;

下面将介绍“struct UartDebugMember”结构体成员
“void (*REDE)(uint8_t a);” 该函数指针为RS485芯片收发控制函数;
“UART_HandleTypeDef *Uart;” 使用的UART端口,该成员涉及到底层若更换其他MCU或者使用其他库函数需要作出相应修改;
“uint16_t *UartData;” 串口接收到的一个字节;
“uint8_t TransmitBuf[BUFMAX+1];” 发送缓冲区;
“uint8_t ReceiveBuf[BUFMAX+1];” 接收缓冲区;
“uint8_t RecPointClearEn;” 接收字节数清零使能;
“uint8_t ReceivePoint;” 接收字节数;
“uint8_t DataTimeCount;” 当前的总线静默时间
“uint8_t DataTimeCountEn;” 当前的总线静默时间计时使能
“uint8_t ReceiveFinish;” 帧数据接收完成标志

关于函数我们将在下面的"UartDebug.c"中进行介绍。
  1. #include "UartDebug.h"

  2. struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;

  3. void UartDebugInit(void)
  4. {
  5.         U_D_Uart2.Uart = &huart2;
  6.         U_D_Uart2.UartData = &Rdata_UART2;
  7.         U_D_Uart2.REDE = Null;
  8.         HAL_UART_Receive_IT(U_D_Uart2.Uart,(uint8_t *)U_D_Uart2.UartData,1);
  9.         
  10.         U_D_Uart3.Uart = &huart3;
  11.         U_D_Uart3.UartData = &Rdata_UART3;
  12.         U_D_Uart3.REDE = Null;
  13.         HAL_UART_Receive_IT(U_D_Uart3.Uart,(uint8_t *)U_D_Uart3.UartData,1);
  14.         
  15.         U_D_Uart4.Uart = &huart4;
  16.         U_D_Uart4.UartData = &Rdata_UART4;
  17.         U_D_Uart4.REDE = RS485_REDE_1;
  18.         HAL_UART_Receive_IT(U_D_Uart4.Uart,(uint8_t *)U_D_Uart4.UartData,1);
  19.         
  20.         U_D_Uart7.Uart = &huart7;
  21.         U_D_Uart7.UartData = &Rdata_UART7;
  22.         U_D_Uart7.REDE = RS485_REDE_2;
  23.         HAL_UART_Receive_IT(U_D_Uart7.Uart,(uint8_t *)U_D_Uart7.UartData,1);
  24. }
  25. /*******************************************************************************
  26. *Function Name    : DataReceive
  27. *Input            :
  28. *Return           :
  29. *Description      : 串口接收数据
  30. *******************************************************************************/
  31. void DataReceive(struct UartDebugMember *UDM)
  32. {
  33.         if(UDM->RecPointClearEn)
  34.         {
  35.                 UDM->ReceivePoint=0;
  36.                 UDM->RecPointClearEn=0;
  37.         }
  38.         if(UDM->ReceivePoint<=BUFMAX)
  39.         {
  40.                 UDM->ReceiveBuf[UDM->ReceivePoint]=*(uint8_t *)UDM->UartData;
  41.                 UDM->ReceivePoint++;
  42.         }
  43.         UDM->DataTimeCount=0;
  44.         UDM->DataTimeCountEn=1;
  45.         HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
  46. }
  47. /*******************************************************************************
  48. *Function Name    : TimeCountReceive
  49. *Input            :
  50. *Return           :
  51. *Description      : 接收计时
  52. *******************************************************************************/
  53. void TimeCountReceive(struct UartDebugMember *UDM)
  54. {
  55.         if(!UDM->DataTimeCountEn)
  56.         {
  57.                 UDM->DataTimeCount=0;
  58.         }
  59.         /*需要根据波特率以及帧与帧之间的间隔时间调整触发时间*/
  60.         else if(UDM->DataTimeCount > SLIENTTIME)
  61.         {
  62.                 UDM->ReceiveFinish=1;
  63.                 UDM->DataTimeCountEn=0;
  64.         }
  65.         else
  66.         {
  67.                 UDM->DataTimeCount++;
  68.         }
  69. }
  70. /*******************************************************************************
  71. *Function Name    : UartClearBuffer
  72. *Input            :
  73. *Return           :
  74. *Description      : 清除接收缓冲区
  75. *******************************************************************************/
  76. void ClearRxData(struct UartDebugMember *UDM)
  77. {
  78.         UDM->ReceivePoint=0;
  79.         UDM->RecPointClearEn=1;
  80.         UDM->ReceiveFinish=0;
  81. }
  82. /*******************************************************************************
  83. *Function Name    : TransmitData
  84. *Input            :
  85. *Return           :
  86. *Description      : 串口发送一帧数据
  87. *******************************************************************************/
  88. void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length)
  89. {
  90.         unsigned char i;
  91.         UDM->REDE(1);
  92.         for(i=0;i<Length;i++)
  93.         {
  94.                 HAL_UART_Transmit(UDM->Uart,&Buf[i],1,1);
  95.         }
  96.         UDM->REDE(0);
  97.         HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
  98. }
  99. /*******************************************************************************
  100. *Function Name    : SendString
  101. *Input            :
  102. *Return           :
  103. *Description      : 串口发送字符串
  104. *******************************************************************************/
  105. void SendString(struct UartDebugMember *UDM,char *String)
  106. {
  107.         UDM->REDE(1);
  108.         while(*String!='\0')
  109.         {
  110.                 HAL_UART_Transmit(UDM->Uart,(uint8_t *)String,1,1);
  111.                 String++;
  112.         }
  113.         UDM->REDE(0);
  114.         HAL_UART_Receive_IT(UDM->Uart,(uint8_t *)UDM->UartData,1);
  115. }
  116. /**/
  117. void RS485_REDE_1(uint8_t a)
  118. {
  119.         if(a)
  120.         {
  121.                 RE_DE1 = 1;
  122.         }
  123.         else
  124.         {
  125.                 RE_DE1 = 0;
  126.         }
  127. }
  128. void RS485_REDE_2(uint8_t a)
  129. {
  130.         if(a)
  131.         {
  132.                 RE_DE2 = 1;
  133.         }
  134.         else
  135.         {
  136.                 RE_DE2 = 0;
  137.         }
  138. }
  139. void Null(uint8_t a)
  140. {

  141. }
复制代码
“struct UartDebugMember U_D_Uart2,U_D_Uart3,U_D_Uart4,U_D_Uart7;” 因为电路板上使用了usart2、usart3、usart4、usart7,所以需要定义4个相应的UartDebugMember 结构体实体。
“void UartDebugInit(void)” 串口初始化,该函数涉及底层,若使用其他型号单片机或者使用其他库函数需要作出相应修改。这里使用的是HAL库。
“void DataReceive(struct UartDebugMember *UDM)” 串口接收函数,该函数需要在串口接收中断里调用如下所示。
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  2. {
  3.   /* NOTE: This function should not be modified, when the callback is needed,
  4.            the HAL_UART_RxCpltCallback can be implemented in the user file
  5.    */
  6.         if(huart == U_D_Uart2.Uart)
  7.         {
  8.                 DataReceive(&U_D_Uart2);
  9.         }
  10.         if(huart == U_D_Uart3.Uart)
  11.         {
  12.                 DataReceive(&U_D_Uart3);
  13.         }
  14.         if(huart == U_D_Uart4.Uart)
  15.         {
  16.                 DataReceive(&U_D_Uart4);
  17.         }
  18.         if(huart == U_D_Uart7.Uart)
  19.         {
  20.                 DataReceive(&U_D_Uart7);
  21.         }
  22. }
复制代码


该函数的功能一是将接收到的数据存进接收缓冲区;二是将DataTimeCount清零以及将DataTimeCountEn置1,这点很重要。
“void TimeCountReceive(struct UartDebugMember *UDM)”该函数用来计算总线静默时间和判断帧数据接收是否完成,该函数需要每隔1ms执行一次,如下所示。
  1. static void Task_1ms(void)
  2. {
  3.     TimeCountReceive(&U_D_Uart2);
  4.     TimeCountReceive(&U_D_Uart3);
  5.     TimeCountReceive(&U_D_Uart4);
  6.     TimeCountReceive(&U_D_Uart7);
  7. }
复制代码

“void ClearRxData(struct UartDebugMember *UDM)”该函数用来清除接收缓冲区(其实仅清除的接收个数)和接收完成标志,需要在数据处理完成后调用,具体用法会在后续章节中介绍。
“void TransmitData(struct UartDebugMember *UDM,unsigned char *Buf,unsigned char Length)”和“void SendString(struct UartDebugMember *UDM,char *String)”都是发送函数没什么特别注意的地方。
“void RS485_REDE_1(uint8_t a)”、“void RS485_REDE_2(uint8_t a)”和“void Null(uint8_t a)”都是RS485芯片收发控制的函数,可以在“void UartDebugInit(void)”中看到“U_D_Uart2”和“U_D_Uart3”使用的是函数“Null”而“U_D_Uart4”和“U_D_Uart7”分别使用了函数“void RS485_REDE_1(uint8_t a)”和“void RS485_REDE_2(uint8_t a)”这是因为usart2和usart3连接的是RS232芯片而usart4和usart7连接的是RS485芯片。
至此UART篇就介绍完了,下面是"Header.h"的具体内容。
  1. #ifndef __HEADER_H
  2. #define __HEADER_H

  3. #include "stm32f4xx_hal.h"
  4. #include "gpio_bool-M4.h"

  5. #endif
复制代码
  1. #ifndef __GPIO_BOOL_H
  2. #define __GPIO_BOOL_H

  3. #include "stm32f4xx_hal.h"


  4. //位带操作,实现51类似的GPIO控制功能
  5. //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了.
  6. //IO口操作宏定义
  7. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
  8. #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
  9. #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
  10. //IO口地址映射
  11. #define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x40020014
  12. #define GPIOB_ODR_Addr (GPIOB_BASE+20) //0x40020414
  13. #define GPIOC_ODR_Addr (GPIOC_BASE+20) //0x40020814
  14. #define GPIOD_ODR_Addr (GPIOD_BASE+20) //0x40020C14
  15. #define GPIOE_ODR_Addr (GPIOE_BASE+20) //0x40021014
  16. #define GPIOF_ODR_Addr (GPIOF_BASE+20) //0x40021414
  17. #define GPIOG_ODR_Addr (GPIOG_BASE+20) //0x40021814
  18. #define GPIOH_ODR_Addr (GPIOH_BASE+20) //0x40021C14
  19. #define GPIOI_ODR_Addr (GPIOI_BASE+20) //0x40022014
  20. #define GPIOJ_ODR_ADDr (GPIOJ_BASE+20) //0x40022414
  21. #define GPIOK_ODR_ADDr (GPIOK_BASE+20) //0x40022814

  22. #define GPIOA_IDR_Addr (GPIOA_BASE+16) //0x40020010
  23. #define GPIOB_IDR_Addr (GPIOB_BASE+16) //0x40020410
  24. #define GPIOC_IDR_Addr (GPIOC_BASE+16) //0x40020810
  25. #define GPIOD_IDR_Addr (GPIOD_BASE+16) //0x40020C10
  26. #define GPIOE_IDR_Addr (GPIOE_BASE+16) //0x40021010
  27. #define GPIOF_IDR_Addr (GPIOF_BASE+16) //0x40021410
  28. #define GPIOG_IDR_Addr (GPIOG_BASE+16) //0x40021810
  29. #define GPIOH_IDR_Addr (GPIOH_BASE+16) //0x40021C10
  30. #define GPIOI_IDR_Addr (GPIOI_BASE+16) //0x40022010
  31. #define GPIOJ_IDR_Addr (GPIOJ_BASE+16) //0x40022410
  32. #define GPIOK_IDR_Addr (GPIOK_BASE+16) //0x40022810

  33. //IO口操作,只对单一的IO口!
  34. //确保n的值小于16!
  35. #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
  36. #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入

  37. #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
  38. #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入

  39. #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
  40. #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入

  41. #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
  42. #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入

  43. #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
  44. #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入

  45. #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
  46. #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入

  47. #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
  48. #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入

  49. #define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n) //输出
  50. #define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n) //输入

  51. #define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n) //输出
  52. #define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n) //输入

  53. #define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr,n) //输出
  54. #define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr,n) //输入

  55. #define PKout(n) BIT_ADDR(GPIOK_ODR_Addr,n) //输出
  56. #define PKin(n) BIT_ADDR(GPIOK_IDR_Addr,n) //输入

  57. #endif
复制代码

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:88060 发表于 2022-1-4 14:14 | 显示全部楼层
留个脚印
回复

使用道具 举报

ID:467334 发表于 2022-1-15 11:26 | 显示全部楼层
努力学习一下,虽然对初学的我有点难
回复

使用道具 举报

ID:476652 发表于 2022-1-31 13:34 | 显示全部楼层
楼主讲了这么多,辛苦了!!
若能完美制作一个Modbus RTU主从站,配合着主从站例程讲解,大家就能够更好的理解楼主的Modbus了。
是对大家都科普,也是对楼主知识的验证和提高!!
毕竟Modbus RTU在单片机里不容易做好的,能把这个做好,还是有2把刷子的,
回复

使用道具 举报

ID:27536 发表于 2023-5-12 16:45 | 显示全部楼层
对初学的我有点难,工作需要啊
1
回复

使用道具 举报

ID:1109866 发表于 2024-1-23 16:31 | 显示全部楼层
楼主太有心了,写的这么详细,对学习很有用
回复

使用道具 举报

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

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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