(软件04)基于串口485的Modbus RTU通讯示例,支持0x03、0x06、0x10操作码

本文目录

  •     软件学习前言
  •     代码思路
  •     实操练习

软件学习前言

        本篇介绍Modbus RTU的一些基本操作指令规则与代码示例讲解,Modbus RTU通常采用RS-485作为物理层,是一套业内共认的通讯协议,免费使用。主要的操作码有0x03、0x06、0x10,另外为了方面实际使用,我加了一个自定义操作码0xFF,用来跳过从机地址匹配情况,直接修改设备的从机地址,软件中也介绍了如何将从机地址写入单片机内部的Flash中去,掉电保存。本文通过讲解软件思路与实际编写代码,希望帮助大家快速地把Modbus RTU应用起来。

        介绍一下本篇前面的一些软件介绍,之前讲解了时基操作、串口通讯、本篇属于串口通讯的上层应用。

        (软件01)时基处理,单片机一种不错的代码思路icon-default.png?t=N7T8https://blog.csdn.net/BEXZJ/article/details/134686717?spm=1001.2014.3001.5501

        (软件03)单片机串口处理思路,超时接收的方法icon-default.png?t=N7T8https://blog.csdn.net/BEXZJ/article/details/134827083

         老规矩,一张封面图进入今天的分享。

        

代码思路

1.Modbus RTU简介

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。 [1]Modbus比其他通信协议使用的更广泛的主要原因有: [2] 1.公开发表并且无版权要求 2.易于部署和维护 3.对供应商来说,修改移动本地的比特或字节没有很多限制 Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。icon-default.png?t=N7T8https://baike.baidu.com/item/Modbus%E9%80%9A%E8%AE%AF%E5%8D%8F%E8%AE%AE/5972462?fr=ge_ala

2.Modbus RTU常用操作码介绍

        0x03:读多个寄存器,示例(01从机地址)如下

        发送:01 03 00 00 00 03 CRC_H8 CRC_L8

        发送解析:

        01:从机地址

        03:读多个操作码

        00 00:寄存器开始地址

        00 03:读3个数据

        CRC_H8 CRC_L8:ModbusCRC16校验,H8为高八位,L8为低八位。

        回复:01 03 06 AB AB CD CD EF EF CRC_H8 CRC_L8

        回复解析:

        01:从机地址

        03:读多个操作码

        06:数据个数

        AB AB:寄存器00 00的值

        CD CD:寄存器00 01的值

        EF EF:寄存器00 02的值

        CRC_H8 CRC_L8:ModbusCRC16校验,H8为高八位,L8为低八位。

      

        0x06:写单个寄存器,示例(01从机地址)如下

        发送:01 06 00 00 00 01 CRC_H8 CRC_L8

        发送解析:

        01:从机地址

        06:写单个操作码

        00 00:目标寄存器地址

        00 03:写入的数据

        CRC_H8 CRC_L8:ModbusCRC16校验,H8为高八位,L8为低八位。

        回复:01 06 00 00 00 01 CRC_H8 CRC_L8

        回复解析:与发送一致。

        

        0x10:写多个寄存器

        发送:01 10 00 00 00 03 06 AA BB CC DD EE FF CRC_H8 CRC_L8

        发送解析:

        01:从机地址

        10:写多个操作码

        00 00:目标寄存器开始地址

        00 03:寄存器数量

        06 :数据个数

        AA BB:写入寄存器00 00的值

        CC DD:写入寄存器00 01的值

        EE FF:写入寄存器00 02的值

        CRC_H8 CRC_L8:ModbusCRC16校验,H8为高八位,L8为低八位。

        回复:01 10 00 00 00 03 CRC_H8 CRC_L8

        回复解析:

        01:从机地址

        10:写多个操作码

        00 00:目标寄存器开始地址

        00 03:寄存器数量

         CRC_H8 CRC_L8:ModbusCRC16校验,H8为高八位,L8为低八位。

3.代码设计

        接收一帧的串口数据存在BUFF数组中,判断BUFF[0]是否为对应的从机地址,或者判断

BUFF[1]的操作码是否为自己定义的0xFF(走后门用,用来修改跳过从机地址修改从机地址,有点儿绕)。接着,对接收一帧的串口数据进行CRC校验,校验通过后,根据操作码进行Switch-Case分支进行处理,0x03的一类、0x06的一类、0x10的一类、以及我们自定义的0xFF的一类。再根据各自报文的解析规则、回复规则进行设计即可。

4.亮点设计

        知道从机地址时,可以通过0x06进行改写从机地址;

        不知道从机地址时,可以通过0xFF进行改写从机地址;

        从机地址修改完后触发Flash写操作,把从机地址写入单片机片内Flash,掉电保存,下次上电后从Flash读取回来,作为从机地址。

实操练习

        回顾之前的硬件篇,需要通过485来控制电机的运行,我们定义需要控制的Modbus点表。(硬件02)按键+电位器+485控制的电机调速电路实战,上篇icon-default.png?t=N7T8https://blog.csdn.net/BEXZJ/article/details/134784629

        代码部分(随着代码越来越复杂了,就不再像之前一样详细放出来了,这次就放一些关键的,感兴趣的粉丝可以留言评论,我私信给你,另外提一下,最近好多类似机器人的粉丝,我判断是正常的粉丝的话我会互关你的!)

        串口接收处理源码

void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen)
{
	while(mLen--)
	{
		USART_SendData(BSP_485_USART,*pBuf++); //库函数,串口发送单个字节数据
		while(USART_GetFlagStatus(BSP_485_USART, USART_FLAG_TRAC) == RESET); 
	}
}

void zj_app_uart_process(void)
{
	if((zj_485_uart.rx_overtime_flag == TRUE) && (zj_485_uart.rx_overtime_ms==0))
	{
		//rx_overtime_flag=TRUE :有超时接收,rx_overtime_ms=0 :最后一个字节接收完成

		zj_485_uart.rx_len =  zj_485_uart.rx_index; //接收到的长度
		zj_485_uart.rx_index = 0;                   //接收BUFF回到首位,为下次接收做准备
		zj_485_uart.rx_overtime_flag = FALSE;       //重置超时接收标记,为下次接收做准备
		
		zj_485_uart.rx_finish_flag = TRUE;          //标记接收完成
	}
}


void zj_app_uart_10ms_process(void)
{
	if(zj_485_uart.rx_finish_flag) //根据接收完成标记处理数据
	{
	  zj_485_uart.rx_finish_flag = FALSE;//重置开始接收标记,为下次处理数据做准备

      zj_app_uart_modbus_process(); //modbus
	}
}

        Modbus RTU 实例代码

#include "zj_public.h"

/*
寄存器地址 / 值 / 读写 / 值说明 
0x0000 / 0-1 / WR / 开关控制:1开0关 默认关
0x0001 / 0-1 / WR / 顺逆方向:1逆0顺 默认顺
0x0002 / 0-1000 / WR / 速度等级千分比: 500代表一半的速度 

0xFFFF / 1-247 /WR / 从机地址:01代表默认01的从机地址

	uint8_t motor_onoff;
	uint8_t motor_cwccw;
	uint8_t motor_speed_level;
*/

void zj_app_uart_modbus_process(void)
{
	uint16_t nCRC_Recv;
	uint16_t nCRC_Check;
	
	uint8_t nIndex = 0;
	
	uint8_t CMD_REG_BUFF[64] = {0}; 
	

	if(zj_485_uart.rx_buff[0] == zj_app_info.modbus_address || zj_485_uart.rx_buff[1] == 0xFF)
	{
		nCRC_Recv =  zj_485_uart.rx_buff[zj_485_uart.rx_len-2] *256 + zj_485_uart.rx_buff[zj_485_uart.rx_len-1];
		nCRC_Check =  CRC16_Modbus_Calculate(zj_485_uart.rx_buff, zj_485_uart.rx_len-2);
		
		if(nCRC_Check == nCRC_Recv)
		{
			switch(zj_485_uart.rx_buff[1])
			{
				case 0x03:
						if(zj_485_uart.rx_buff[5] <= 14 && zj_485_uart.rx_buff[4] == 0)
						{
							zj_485_uart.tx_buff[0] = zj_app_info.modbus_address;
							zj_485_uart.tx_buff[1] = 0x03;
							zj_485_uart.tx_buff[2] = zj_485_uart.rx_buff[5]*2;
							zj_485_uart.tx_len = zj_485_uart.tx_buff[2] + 5;
							
							
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_onoff / 256 % 256;
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_onoff % 256;	
							
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_cwccw / 256 % 256;
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_cwccw % 256;	
						
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_speed_level / 256 % 256;
							CMD_REG_BUFF[nIndex++] = zj_app_info.motor_speed_level % 256;	
							

							memcpy(&zj_485_uart.tx_buff[3],&CMD_REG_BUFF[zj_485_uart.rx_buff[3]*2],zj_485_uart.tx_buff[2]);

							
							nCRC_Check = CRC16_Modbus_Calculate(zj_485_uart.tx_buff,zj_485_uart.tx_len - 2);
							zj_485_uart.tx_buff[zj_485_uart.tx_len - 2] = nCRC_Check / 256 % 256;
							zj_485_uart.tx_buff[zj_485_uart.tx_len - 1] = nCRC_Check % 256;
							
							zj_app_uart_send(zj_485_uart.tx_buff,zj_485_uart.tx_len);
						}
					break;
				case 0x06:
						if(zj_485_uart.rx_buff[2] == 0)
						{
							switch(zj_485_uart.rx_buff[3])
							{
								case 0x00:
											if(zj_485_uart.rx_buff[4] == 0x00 && zj_485_uart.rx_buff[5] == 0x01)
											{
												zj_app_info.motor_onoff = TRUE;//开始

												zj_app_uart_send(zj_485_uart.rx_buff,8);
											}
											else if(zj_485_uart.rx_buff[4] == 0x00 && zj_485_uart.rx_buff[5] == 0x00)
											{
												zj_app_info.motor_onoff = FALSE;//停止
												
												zj_app_uart_send(zj_485_uart.rx_buff,8);
											}
									break;
								case 0x01:
											if(zj_485_uart.rx_buff[4] == 0x00 && zj_485_uart.rx_buff[5] == 0x01)
											{
												zj_app_info.motor_cwccw = TRUE; //逆时针
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
																		
												zj_app_uart_send(zj_485_uart.rx_buff,8);
											}

											else if(zj_485_uart.rx_buff[4] == 0x00 && zj_485_uart.rx_buff[5] == 0x00)
											{
												zj_app_info.motor_cwccw = FALSE;	//顺时针
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
												
												zj_app_uart_send(zj_485_uart.rx_buff,8);
											}
									break;
											
								case 0x02:
											if((zj_485_uart.rx_buff[4] * 256  + zj_485_uart.rx_buff[5]) <= 1000)
											{
												zj_app_info.motor_speed_level = zj_485_uart.rx_buff[4] * 256  + zj_485_uart.rx_buff[5];
												
												zj_app_uart_send(zj_485_uart.rx_buff,8);
											}
									break;
											
			
								default:
									break;
							}
						}
						else
						{
							if((zj_485_uart.rx_buff[2] == 0xFF) && (zj_485_uart.rx_buff[3] == 0xFF))
							{
								if(zj_485_uart.rx_buff[4] == 0x00)
								{
									if(zj_485_uart.rx_buff[5] > 0 && zj_485_uart.rx_buff[5] <= 247)
									{
										zj_app_info.modbus_address = zj_485_uart.rx_buff[5];
										
										zj_app_info.flash_save_flag =  TRUE;
										zj_app_info.flash_save_s_time = FLASH_SAVE_WAIT_S_TIME;
										zj_app_uart_send(zj_485_uart.rx_buff,8);
									}
								}
							}
						}
					break;
				case 0x10:	
						if(zj_485_uart.rx_buff[2] == 0)
						{
							if(zj_485_uart.rx_buff[5]*2 != zj_485_uart.rx_buff[6])
								return;
							
							switch(zj_485_uart.rx_buff[5])
							{
								case 1:
									switch(zj_485_uart.rx_buff[3])
									{
										case 0x00:
											if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0001)
											{
												zj_app_info.motor_onoff = TRUE;//开始
											}
											else if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0000)
											{
												zj_app_info.motor_onoff = FALSE;//停止
											}
											break;
										case 0x01:
											if(zj_485_uart.rx_buff[7] == 0x00 && zj_485_uart.rx_buff[8] == 0x01)
											{
												zj_app_info.motor_cwccw = FALSE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											else if(zj_485_uart.rx_buff[7] == 0x00 && zj_485_uart.rx_buff[8] == 0x00)
											{
												zj_app_info.motor_cwccw = TRUE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											break;
										case 0x02:
											if((zj_485_uart.rx_buff[7] * 256  + zj_485_uart.rx_buff[8]) <= 1000)
											{
												zj_app_info.motor_speed_level = zj_485_uart.rx_buff[7] * 256  + zj_485_uart.rx_buff[8];
											}
										default:break;
									}
									break;
								case 2:
									switch(zj_485_uart.rx_buff[3])
									{
										case 0x00:
											if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0001)
											{
												zj_app_info.motor_onoff = TRUE;//开始
											}
											else if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0000)
											{
												zj_app_info.motor_onoff = FALSE;//停止
											}
											
											
											if(zj_485_uart.rx_buff[9] == 0x00 && zj_485_uart.rx_buff[10] == 0x01)
											{
												zj_app_info.motor_cwccw = FALSE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											else if(zj_485_uart.rx_buff[9] == 0x00 && zj_485_uart.rx_buff[10] == 0x00)
											{
												zj_app_info.motor_cwccw = TRUE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											break;
										case 0x01:
											if(zj_485_uart.rx_buff[7] == 0x00 && zj_485_uart.rx_buff[8] == 0x01)
											{
												zj_app_info.motor_cwccw = FALSE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											else if(zj_485_uart.rx_buff[7] == 0x00 && zj_485_uart.rx_buff[8] == 0x00)
											{
												zj_app_info.motor_cwccw = TRUE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											
											
											if((zj_485_uart.rx_buff[9] * 256  + zj_485_uart.rx_buff[10]) <= 1000)
											{
												zj_app_info.motor_speed_level = zj_485_uart.rx_buff[9] * 256  + zj_485_uart.rx_buff[10];
											}
											break;
										case 0x02:
											//不满足数量
											break;	
										default:break;
									}
									break;
								case 3:
									switch(zj_485_uart.rx_buff[3])
									{
										case 0x00:
											if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0001)
											{
												zj_app_info.motor_onoff = TRUE;//开始
											}
											else if(zj_485_uart.rx_buff[7]*256+zj_485_uart.rx_buff[8] == 0x0000)
											{
												zj_app_info.motor_onoff = FALSE;//停止
											}
											
											if(zj_485_uart.rx_buff[9] == 0x00 && zj_485_uart.rx_buff[10] == 0x01)
											{
												zj_app_info.motor_cwccw = FALSE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											else if(zj_485_uart.rx_buff[9] == 0x00 && zj_485_uart.rx_buff[10] == 0x00)
											{
												zj_app_info.motor_cwccw = TRUE;//
												zj_app_info.motor_onoff = FALSE; //切换正反转需要让电机停下来
											}
											
											if((zj_485_uart.rx_buff[11] * 256  + zj_485_uart.rx_buff[12]) <= 1000)
											{
												zj_app_info.motor_speed_level = zj_485_uart.rx_buff[11] * 256  + zj_485_uart.rx_buff[12];
											}
											break;
										case 0x01:
											//不满足数量
											break;
										case 0x02:
											//不满足数量
											break;
										default:break;
									}
									break;
									
								default:
									break;
							}
							
							
							memcpy(&zj_485_uart.tx_buff,zj_485_uart.rx_buff,6);
							nCRC_Check = CRC16_Modbus_Calculate(zj_485_uart.tx_buff,6);
							zj_485_uart.tx_buff[6] = nCRC_Check / 256 % 256;
							zj_485_uart.tx_buff[7] = nCRC_Check % 256;
							zj_app_uart_send(zj_485_uart.tx_buff,8);
						}
					break;
				case 0xFF:		
					if(zj_485_uart.rx_buff[2] == 0xFF && zj_485_uart.rx_buff[3] == 0xFF)
					{
						zj_app_info.modbus_address = zj_485_uart.rx_buff[5];
										
						zj_app_info.flash_save_flag =  TRUE;
						zj_app_info.flash_save_s_time = FLASH_SAVE_WAIT_S_TIME;
						
						zj_app_uart_send(zj_485_uart.rx_buff,8);
					}
					break;
						
				default:
					break;
			}
		}
	}
}

        从机地址保存到Flash处理,我设计的思路是每一次收到要保存到Flash的操作时,都给其一个倒计时FLASH_SAVE_WAIT_S_TIME = 3秒,在最后一需要保存的3秒后进行保存,如此可以减少Flash的擦写次数,让单片机更加耐用。

        

void zj_app_config_para_flash_write(void)
{
	FLASH_Unlock();
	
	
	FLASH_ErasePage(FLASH_PARA_SAVE_ADDRESS);
	
	PARA_SAVE_BUFF[0] =  zj_app_info.modbus_address	% 256;;
	
	FLASH_ProgramByte(FLASH_PARA_SAVE_ADDRESS+0,PARA_SAVE_BUFF[0]);

	FLASH_Lock();
}



void zj_app_config_para_flash_read(void)
{
	memcpy((uint8_t *)PARA_SAVE_BUFF ,(uint8_t *)FLASH_PARA_SAVE_ADDRESS, PARA_SAVE_LEN);
	
	if(PARA_SAVE_BUFF[0] == 0xFF)
	{
		zj_app_info.modbus_address = 0x01;
	}
	else
		zj_app_info.modbus_address = PARA_SAVE_BUFF[0];
	
	
	zj_app_config_para_flash_write();
	
}

        Flash相关时基处理

static void TimeProcess_1000MS(void)
{
	if(zj_app_info.flash_save_flag)
	{
		if(zj_app_info.flash_save_s_time)
			zj_app_info.flash_save_s_time--;
		else
		{
			zj_app_info.flash_save_flag = 0;
			
			zj_app_config_para_flash_write();
		}
	}
}

        整个工程对应的头文件zj_public.h

#ifndef __ZJ_PUBLIC_H__
#define __ZJ_PUBLIC_H__

#include <at32f4xx.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include "at32f4xx_syscfg.h"
#include "at32f4xx_tim.h"
#include "at32f4xx_usart.h"
#include "at32f4xx_rcc.h"
#include "at32f4xx_iwdg.h"

#include "CRC16_Algorithm.h"

#define DEBUG   1


#define TRUE   	1
#define FALSE   0

#define	HAL_Delay_nMS  					Delay_ms
#define Delay_Us 						Delay_us

#define BSP_UART1_TX_GPIO_PIN      		GPIO_Pins_6
#define BSP_UART1_TX_GPIO_AF1_Source	GPIO_PinsSource6
#define BSP_UART1_TX_GPIO_PORT   	  	GPIOB
#define BSP_UART1_RX_GPIO_PIN      		GPIO_Pins_7
#define BSP_UART1_RX_GPIO_AF1_Source	GPIO_PinsSource7
#define BSP_UART1_RX_GPIO_PORT   	  	GPIOB
 
#define BSP_485_USART   	  			USART1
#define BSP_485_USART_IRQHandler 		USART1_IRQHandler
#define BSP_485_USART_BANDRATE 			115200
 
#define BSP_485_USART_IRQN  		    USART1_IRQn
#define BSP_485_USART_IRQN_LEVEL  		2
#define BSP_485_USART_IRQN_HANDLER  	USART1_IRQHandler
 
//时基定时器
#define BSP_TIME_BASE_TIMER  			TMR6
#define BSP_TIME_BASE_TIMER_ARR  	    10-1
#define BSP_TIME_BASE_TIMER_PSC  		7200-1
#define BSP_TIME_BASE_IRQN  			TMR6_GLOBAL_IRQn
#define BSP_TIME_BASE_IRQN_LEVEL  		0
#define BSP_TIME_BASE_IRQN_HANDLER  	TMR6_GLOBAL_IRQHandler


//LED灯
#define BSP_LED_RUN_GPIO_PIN      		GPIO_Pins_10
#define BSP_LED_RUN_GPIO_PORT     		GPIOA
#define BSP_LED_RUN_ON      			GPIO_ResetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)
#define BSP_LED_RUN_OFF      			GPIO_SetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)
 
 
//串口数据缓存长度
#define	UART_DATA_BUFF_LEN 				512

/*
  *            @arg GPIO_AF_0: EVENTOUT, TIM15, SPI1, TIM17, MCO, SWDAT, SWCLK, TIM14,
  *                            USART1, IR_OUT, SPI2 
  *            @arg GPIO_AF_1: USART2, TMR3, USART1, USART2, EVENTOUT, I2C1, I2C2, TMR15, IR_OUT 
  *            @arg GPIO_AF_2: TMR2, TMR1, EVENTOUT, TMR16, TMR17
  *            @arg GPIO_AF_3: USART2, I2C1, TMR15, EVENTOUT 
  *            @arg GPIO_AF_4: I2C2, TMR14, USART2, I2C1
  *            @arg GPIO_AF_5: TMR1, TMR15, TMR16, TMR17, I2C2, MCO
  *            @arg GPIO_AF_6: EVENTOUT, SPI2
  *            @arg GPIO_AF_7: COMP1 OUT, COMP2 OUT, I2C2, SPI2
*/
//ADC
#define BSP_ADC							ADC1
#define BSP_ADC_CHANNEL_NUMBER 			3
#define BSP_ADC_0_7_PORT 				GPIOA

#define BSP_ADC_SPEED_IN_PIN      		GPIO_Pins_0
#define BSP_ADC_SPEED_IN_CHANNEL   		ADC_Channel_0
#define BSP_ADC_SPEED_IN_CHANNEL_INDEX  1

#define BSP_ADC_DMA_IRQN  				DMA1_Channel1_IRQn
#define BSP_ADC_DMA_IRQN_LEVEL  		1
#define BSP_ADC_DMA_IRQN_HANDLER  	    DMA1_Channel1_IRQHandler

#define FLASH_SAVE_WAIT_S_TIME 	 	    3 //3秒后保存
#define FLASH_PARA_SAVE_ADDRESS 	 	0x800F000
#define PARA_SAVE_LEN 				    16


#define FLASH_APP_START_ADDRESS 	 	0x8000000 // 0x8003000

typedef struct
{
	uint8_t rx_buff[UART_DATA_BUFF_LEN];
	uint16_t rx_index;
	uint8_t rx_finish_flag;
	uint8_t rx_overtime_flag;
	uint16_t rx_overtime_ms;
	uint16_t rx_len;
	
	uint8_t tx_buff[UART_DATA_BUFF_LEN];
	uint16_t tx_len;
	
}UART_INFO;
extern UART_INFO zj_485_uart;

typedef struct
{
	uint8_t modbus_address;
	
	uint8_t motor_onoff;
	uint8_t motor_cwccw;
	uint8_t motor_speed_level;
	
	uint8_t flash_save_flag;
	uint8_t flash_save_s_time;
	
}APP_INFO;
extern APP_INFO zj_app_info;


extern volatile uint64_t gTimeBase;

extern __IO uint16_t adc1_ordinary_valuetab[30];

extern uint8_t PARA_SAVE_BUFF[PARA_SAVE_LEN];

/*Delay function*/
void Delay_init(void);
void Delay_us(u32 nus);
void Delay_ms(u16 nms);
void Delay_sec(u16 sec);

void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void EXTI_Configuration(void);

void zj_bsp_config(void);

void zj_app_init(void);

void zj_app_timebase_process(void);
void zj_app_timebase_1ms_process(void);

void zj_app_adc_avrg_process(void);
void zj_app_adc_ms_process(void);


void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen);
void zj_app_uart_process(void);
void zj_app_uart_10ms_process(void);

void zj_app_uart_modbus_process(void);

void zj_app_config_para_flash_write(void);


#endif

        如此,我们便实现了基于串口485的Modbus RTU通讯示例,支持0x03、0x06、0x10操作码。网上我看很少有0x10写多个的源码介绍的,我之前公司产品是也写了一个,如果写入的寄存器比较多的话,整个代码量是很大的,复制过程中也容易出错,重要是要预想到具体写多少个寄存器,一共10个寄存器可写的话,判断写1个有10个分支,写2个的有9个分支,3个的有8个分支...4个7分支...5个6分支...5分支...4分支...3分支...2分支...1分支,一共就55个分支了,想想都有点头大!

        好了,11.27开始写博客分享到现在10天了,5K阅读量,100粉丝,感谢大家支持了,欢迎大家继续点点关注,有问题疑惑的也欢迎评论区留言,与大家共同学习。

小弟感谢大家的关注!

      (利他之心,原创分享)

  • 49
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BEXZJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值