基于单片机的modbus从机程序

一 、开发思路

我这里开发的平台是新唐M031,它是Cortex-M0的内核、32位单片机。因为要和上位机进行RS485通讯,所以选用了Modbus-RTU来作为通讯协议。我这是用串口接收中断+定时器中断来接收一帧数据,然后modbus从机程序自己手撸。

二、 Modbus介绍

modbus没什么好介绍的,熟悉下功能码就ok了。直接上链接 https://www.cnblogs.com/endv/p/8650491.html

三、 串口初始化及中断处理函数

各模块时钟初始化统一放到sys_init里面

void SYS_Init(void)
{
    /* Unlock protected registers */
    SYS_UnlockReg();

    /* Enable HIRC */
    CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);

    /* Waiting for HIRC clock ready */
    CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);

    /* Switch HCLK clock source to HIRC */
    CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));
   /* Set both PCLK0 and PCLK1 as HCLK/2 */
 
    CLK->PCLKDIV = (CLK_PCLKDIV_APB0DIV_DIV2 | CLK_PCLKDIV_APB1DIV_DIV2);

    CLK_EnableModuleClock(TMR0_MODULE);
    CLK_EnableModuleClock(TMR1_MODULE);
    /* Select IP clock source */
    CLK_SetModuleClock(TMR0_MODULE, CLK_CLKSEL1_TMR0SEL_HIRC, 0);
    CLK_SetModuleClock(TMR1_MODULE, CLK_CLKSEL1_TMR1SEL_HIRC, 0);	
	SystemCoreClockUpdate();
    SYS_LockReg();
}

rs485.h

nRxBuff用来存串口1接收到的modbus请求帧,nRxLenth用来存放收到的字节长度。

#ifndef __RS485_H__
#define __RS485_H__

#include "NuMicro.h"
#include "stdio.h"
#include "string.h"
#include "modbus_slave.h"

#define nBunfSize 128
#define BaudRate 115200

typedef struct{
	uint16_t nRxLenth;
	uint8_t nRxBuff[nBunfSize];
}UART0DATA;

void Rs485_Init(void);
void Time0_Init(void);
void delay_us(uint16_t nDelay);
void delay_ms(uint16_t nDelay);
void UART13_IRQHandler(void);
void TMR0_IRQHandler(void);

#endif

rs485.c

思路:我这里在串口接收中断里面,接收到第一个字节,然后定时器0才开始计时,然后在定时器计时12.5ms,然后关闭定时器。有人会问为什么是12.5ms,首先我这里波特率是很高的115200,我认为12.5ms内一帧modbus请求帧已经发完。你们这个时间可以自己根据自己的情况去调。

3.1 串口初始化

UART0DATA Uart0Data;
volatile uint16_t nRevNum = 0;
uint8_t RevBuff[32] = {0};
void Rs485_Init(void)
{
	/* Switch UART1 clock source to HIRC */
    CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));

    /* Enable UART peripheral clock */
    CLK_EnableModuleClock(UART1_MODULE);
	
    /* Update System Core Clock */
    /* User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. */
    SystemCoreClockUpdate();	
    SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA2MFP_Msk | SYS_GPA_MFPL_PA3MFP_Msk)) |   \
                    ( SYS_GPA_MFPL_PA2MFP_UART1_RXD | SYS_GPA_MFPL_PA3MFP_UART1_TXD);		
    /* Reset UART1 */
    SYS_ResetModule(UART1_RST);

    /* Configure UART0 and set UART0 baud rate */
    UART_Open(UART1, BaudRate);
	UART_SetLine_Config(UART1,BaudRate,UART_WORD_LEN_8,UART_PARITY_NONE,UART_STOP_BIT_1);
	//接收超时中断
	UART_ENABLE_INT(UART1,UART_INTEN_RDAIEN_Msk|UART_INTEN_RXTOIEN_Msk|UART_INTEN_RLSIEN_Msk);
	NVIC_EnableIRQ(UART13_IRQn);
	NVIC_SetPriority(UART13_IRQn,0);
}

3.2 串口接收中断

void UART13_IRQHandler(void)
{
	if(UART_GET_INT_FLAG(UART1,UART_INTSTS_RDAIF_Msk | UART_INTSTS_RXTOIF_Msk | UART_INTSTS_RXTOINT_Msk))
	{
		UART_ClearIntFlag(UART1, (UART_INTSTS_RLSINT_Msk| UART_INTSTS_BUFERRINT_Msk));
		while(!UART_GET_RX_EMPTY(UART1))
		{
			//sTemp = UART0->DAT;
			Uart0Data.nRxBuff[nRevNum++] = UART1->DAT;
			TIMER_Start(TIMER0);	//打开定时器
			if(nRevNum > nBunfSize){
				nRevNum = 0;
				memset(Uart0Data.nRxBuff,0,sizeof(Uart0Data.nRxBuff));
			}
		}//end while
	}
}

四、 定时器初始化及中断处理函数
4.1 定时器初始化

void Time0_Init(void)
{

    /* Open Timer0 in periodic mode, enable interrupt and 1 interrupt tick per second */
    TIMER_Open(TIMER0, TIMER_PERIODIC_MODE, 80);		//10ms进一次中断
    TIMER_EnableInt(TIMER0);
	//TIMER_SET_CMP_VALUE(TIMER0, 0xFFFFFF);//修改比较寄存器的值
	//TIMER_SET_PRESCALE_VALUE(TIMER0, 0x0);//修改预分频的值
	/* Enable Timer0NVIC */
    NVIC_EnableIRQ(TMR0_IRQn);

	/* Start Timer0 counting */
    //TIMER_Start(TIMER0);
	NVIC_SetPriority(TMR0_IRQn,1);	
}

4.2 定时器中断函数

Modbus_Data_Parse()是modbus从机入口函数,也可以放到main函数的while(1)里面

void TMR0_IRQHandler(void)
{
//	uint16_t i = 0;
    if(TIMER_GetIntFlag(TIMER0) == 1)
    {
        // Clear Timer0 time-out interrupt flag 
        TIMER_ClearIntFlag(TIMER0);
		TIMER_Stop(TIMER0);	//关闭定时器
		Uart0Data.nRxLenth = nRevNum;
		nRevNum = 0;
		//modbus从机入口函数
		Modbus_Data_Parse();
    }
}

五、 Modbus从机程序
这里才是关键,先简单介绍这里的开发思路。Modbus_Data_Parse是modbus从机入口函数,进来首先是读下从机地址(Modbus地址由3位的拨码开关决定),然后对串口1接收的数据进行判断并进行解析,校验成功的数据丢到Md_Recv_t结构体里面。最后根据功能码执行不同的函数对请求帧返回对应的响应帧,注意我这里把01/02,03/04看成一样的,执行同一个函数。

modbus_slave.h

#ifndef __MODBUS_SLAVE_H__
#define __MODBUS_SLAVE_H__

#include "NuMicro.h"
#include "stdio.h"
#include "string.h"
#include "stk8321.h"
#include "gpio_coil.h"
#include "filter.h"
#include "rs485.h"

#define SENDLENTH 64
#define READLENTH 64

#define ReadCoilSta		0x01		//读线圈量
#define ReadInputDis    0x02		//读输入离散量
#define ReadHoldReg		0x03		//读保持寄存器
#define ReadInputReg    0x04		//读输入寄存器
#define ForceSingleCoil 0x05		//写位
#define WriteMulCoils   0x0F		//写多位
#define WriteSingleReg 	0x06		//写单个寄存器
#define WireMulReg		0x10		//写多个寄存器

//线圈0-15
#define Coil0_0 0x1000	//LED1
#define Coil0_1 0x1001	//LED2
#define Coil0_2 0x1002	//LED3
#define Coil0_3 0x1003	//LED4
#define Coil0_4 0x1004	//LED5
#define Coil0_5 0x1005	//LED6
#define Coil0_6 0x1006	//LED7
#define Coil0_7 0x1007	//LED8
#define Coil1_0 0x1008	//LED9
#define Coil1_1 0x1009	//LED10
#define Coil1_2 0x100A	//LED11
#define Coil1_3 0x100B	//LED12
#define Coil1_4 0x100C
#define Coil1_5 0x100D
#define Coil1_6 0x100E
#define Coil1_7 0x100F

//可读可写寄存器
#define Adjust_Level_Reg 0x2000	//Led调光寄存器

//保持寄存器(版本号寄存器)
#define VersionReg_1 0x0001
#define VersionReg_2 0x0002
#define VersionReg_3 0x0003
#define VersionReg_4 0x0004
#define VersionReg_5 0x0005
#define VersionReg_6 0x0006
#define VersionReg_7 0x0007
#define VersionReg_8 0x0008
#define VersionReg_9 0x0009
#define VersionReg_10 0x000A

//保持寄存器(加速度值寄存器)
#define AxReg 0x3000	//请求寄存器数量1-3
#define AyReg 0x3001	//请求寄存器数量1-2
#define AzReg 0x3002	//请求寄存器数量1

//modbus错误码
typedef enum{
	ERR1 = 1,	//非法功能码
	ERR2 = 2,	//非法数据地址
	ERR3 = 3,	//非法数据值
	RRR4 = 4,	//从机设备故障
	RRR5 = 8,	//校验错误	
}Error_Code;


typedef struct{
    uint16_t nAddr;	
    uint8_t nCmd;
    uint16_t nRegStartAddr;
    uint8_t nRegLen;
    uint16_t Md_Data[20];	
}Md_Recv_t;

//void Read_Coil_Data(void);
void Modbus_Data_Parse(void);
uint8_t Modbus_Data_Check(uint8_t *RecvBuf, uint32_t nLen, Md_Recv_t *pMd_Recv);
void ParseRecieve(void);
void Modbus_Send_Error(uint8_t Cmd, uint8_t Err);

#endif	//end __MODBUS_SLAVE_H__

modbus_slave.c

#include "modbus_slave.h"

uint8_t SlaveAddr = 0x01;				//从机设备地址
extern int16_t sHoldRegValue[4];
extern UART0DATA Uart0Data;
extern int Light_Grade;
Error_Code Md_Err;
//uint8_t	Uart0Data.nRxBuff[8];	 // 接收缓冲区
uint8_t nSendBuff[SENDLENTH] = {0};
uint8_t nReadBuff[READLENTH] = {0};

uint32_t sCoilvalue[20] = {0};

//协议版本号 M31_LPD_T01_1001
//int8_t *version = "M31_LPD_T01_1001";
int8_t version[32] = "M31_LPD_T01_1001";

Modbus_Data_Parse()

/**************************************
//函数功能:解析上位机发过来的modbus数据帧
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Data_Parse(void)
	Md_Recv_t Md_Recv;
	//delay_ms(10);
	SlaveAddr = Get_ModbusAddr();
	if(Modbus_Data_Check(Uart0Data.nRxBuff,Uart0Data.nRxLenth,&Md_Recv) == 0)
	{
		switch(Md_Recv.nCmd)
		{
			case ReadCoilSta:
			case ReadInputDis:
				Modbus_Read_Coil(&Md_Recv);
				break;			
			case ReadHoldReg:
			case ReadInputReg:		
				Modbus_Read_Reg(&Md_Recv);
				break;				
			case ForceSingleCoil:
				Modbus_Write_SingleCoil(&Md_Recv);	
				break;
			case WriteSingleReg:
				Modbus_Write_Reg(&Md_Recv);
				break;
			case WriteMulCoils:	
				Modbus_Write_MuilCoil(&Md_Recv);
				break;
			case WireMulReg:
				Modbus_Write_MuilReg(&Md_Recv);
				break;
			default:
				break;
		}
		memset(&Md_Recv, 0, sizeof(Md_Recv));
		memset(Uart0Data.nRxBuff, 0, sizeof(Uart0Data.nRxBuff));
		Uart0Data.nRxLenth = 0;
	}		
}

Modbus_Data_Check

/**************************************
//函数功能:对上位机发来的帧进行判断
//函数返回值:无
//函数形参:RecvBuf:串口实际接收到的数据,nLen:接收到的长度,pMd_Recv:modbus结构指针
//时间:2020/06/22
//备注:无
*************************************/
uint8_t Modbus_Data_Check(uint8_t *RecvBuf, uint32_t nLen, Md_Recv_t *pMd_Recv)
	//printf("FILE:%s\tLINE:%d\r\n", __FILE__, __LINE__);		
	uint16_t nCRC = 0;
	uint16_t i = 0;	
	uint16_t nRetByte = 0;	
	//先进行长度判断,Modbus请求帧和控制帧长度应大于等于8
	if(nLen >= 8)
	{	
		nCRC = CRC16(RecvBuf, nLen-2);
		//判断CRC校验,校验成功说明是modbus标准帧
		if(nCRC == ((uint16_t)(RecvBuf[nLen - 2] << 8 | RecvBuf[nLen - 1])))
		{
			pMd_Recv->nAddr = RecvBuf[0];
			pMd_Recv->nCmd = RecvBuf[1];
			if(pMd_Recv->nAddr == SlaveAddr)	
			{
				switch(RecvBuf[1])
				{
					case ReadCoilSta:
					case ReadInputDis:	
					case ReadHoldReg:
					case ReadInputReg:		
						pMd_Recv->nRegStartAddr = (uint16_t)(RecvBuf[2] << 8 | RecvBuf[3]);
						pMd_Recv->nRegLen = (uint16_t)(RecvBuf[4] << 8 | RecvBuf[5]);						
						break;
					case ForceSingleCoil:
					case WriteSingleReg:	
						//01 05 00 00 ff 00 crc1 crc2  8byte
						//01 06 00 01 aa bb crc1 crc2  8byte
						pMd_Recv->nRegLen = 2;
						pMd_Recv->nRegStartAddr = (uint16_t)(RecvBuf[2] << 8 | RecvBuf[3]);
						pMd_Recv->Md_Data[0] = (uint16_t)(RecvBuf[4] << 8 | RecvBuf[5]);
						break;
					case WireMulReg:	
						//01 10 00 13 00 02 04 aa aa bb bb crc1 crc ---- 7+len+2字节
						pMd_Recv->nRegStartAddr = (uint16_t)(RecvBuf[2] << 8 | RecvBuf[3]);
						pMd_Recv->nRegLen = (uint16_t)(RecvBuf[4] << 8 | RecvBuf[5]);	
						for(i = 0; i < pMd_Recv->nRegLen; i++)
						{
							pMd_Recv->Md_Data[i] = (uint16_t)(RecvBuf[7+2*i] << 8 | RecvBuf[8+2*i]);
						}
						break;
					case WriteMulCoils:	
						pMd_Recv->nRegStartAddr = (uint16_t)(RecvBuf[2] << 8 | RecvBuf[3]);
						pMd_Recv->nRegLen = (uint16_t)(RecvBuf[4] << 8 | RecvBuf[5]);
						nRetByte = (pMd_Recv->nRegLen / 8);
						if((pMd_Recv->nRegLen % 8) != 0){
							nRetByte++;
						}
						for(i = 0; i < nRetByte; i++)
						{
							pMd_Recv->Md_Data[i] = (uint16_t)RecvBuf[7+i];
						}	
						break;
					default:
						break;
				}
				return 0;//success
			}
			else
			{
				return 3;
			}
		}
		else
		{	
			return 2;
		}
	}
	else
	{
		return 1;
	}
}

CRC校验函数

/**************************************
//函数功能:标准CRC校验函数
//函数返回值:CRC(2字节)
//函数形参:Msg-->校验帧,len-->校验长度
//时间:2020/06/13
//备注:多项式0xA001
*************************************/
uint16_t CRC16(uint8_t *Msg, uint16_t len)
{
    uint16_t nCRC = 0xffff;
    uint16_t i,temp;
    while(len--)
    {
        nCRC = nCRC^(*Msg++);
        for(i=0;i++<8;)
        {
            if(nCRC&0x0001)
                nCRC = (nCRC>>1)^0xa001;
            else
                nCRC>>=1;
        }
    }
    temp = nCRC&0xff;
    nCRC = ((nCRC>>8)&0xff)+(temp<<8);     
    return(nCRC);
}

发送错误码函数

/**************************************
//函数功能:Modbus错误码发送
//函数返回值:无
//函数形参:Cmd:命令码,Err:错误类型
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Send_Error(uint8_t Cmd, uint8_t Err)
{
	uint16_t nCRC = 0;	

	nSendBuff[0] = SlaveAddr;
	nSendBuff[1] = 0x80 + Cmd;			
	nSendBuff[2] = Err;
	nCRC = CRC16(nSendBuff,3);
	nSendBuff[3] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[4] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,5);	
	memset(nSendBuff,0,SENDLENTH);
}

/**************************************
//函数功能:Modbus读版本号
//函数返回值:无
//函数形参:pMd_Recv
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Read_Version(Md_Recv_t *pMd_Recv)
{
	uint16_t nCRC = 0;	
	uint16_t SendIndex = 0;
	
	nSendBuff[SendIndex++] = pMd_Recv->nAddr;
	nSendBuff[SendIndex++] = pMd_Recv->nCmd;
	nSendBuff[SendIndex++] = pMd_Recv->nRegLen*2;
	
	memcpy((char *)nSendBuff+SendIndex,version,pMd_Recv->nRegLen*2);
	SendIndex += pMd_Recv->nRegLen*2;
	nCRC = CRC16(nSendBuff,SendIndex);
	nSendBuff[SendIndex++] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[SendIndex++] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,SendIndex);	
	memset(nSendBuff,0,SENDLENTH);	
}

/**************************************
//函数功能:对上位机01/02命令处理
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Read_Coil(Md_Recv_t *pMd_Recv)
	uint16_t nCRC = 0;
	uint16_t i = 0;
	uint16_t j = 0;
	//uint16_t nOffset = 0;
	//uint16_t uStatus[16] = {0};
	uint16_t nSendByte = 0;
	uint32_t nCoilValue = 0;
	uint32_t nCoilAddr = 0;	
	uint16_t nExitFlag = 0;
	memset(nSendBuff,0,sizeof(nSendBuff));
	memset(nReadBuff,0,sizeof(nSendBuff));
	
	if((pMd_Recv->nRegStartAddr < Coil0_0) || (pMd_Recv->nRegStartAddr > Coil1_7) || pMd_Recv->nRegLen > 0x0010 || (pMd_Recv->nRegStartAddr + pMd_Recv->nRegLen) > 0x1010)	//超出范围
	{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;
	}	
	//nOffset = pMd_Recv->nRegStartAddr - Coil0_0;	//先计算器寄存器偏移量
	nCoilAddr = pMd_Recv->nRegStartAddr;
	nSendBuff[0] = pMd_Recv->nAddr;
	nSendBuff[1] = pMd_Recv->nCmd;	
	nSendByte = pMd_Recv->nRegLen / 8;	//字节数

	if(pMd_Recv->nRegLen % 8){			//有余数字节数加1
		nSendByte++;
	}
	nSendBuff[2] = (uint8_t)nSendByte;	
	//小端模式,低位存放在低地址
	for(i = 0; i < nSendByte; i++)
	{
		nSendBuff[3 + i] = 0x00;
		
		for(j = 0; j < 8; j++)
		{
			//读线圈状态
			Read_Coil_Data(nCoilAddr,&nCoilValue);
			nSendBuff[3 + i] |= nCoilValue << j;
			nCoilAddr++;	
			if(nCoilAddr >= pMd_Recv->nRegLen + pMd_Recv->nRegStartAddr){
				nExitFlag = 1;
				break;
			}			
		}
		if(nExitFlag == 1){
			break;
		}
	}
	
	nCRC = CRC16(nSendBuff,nSendByte+3);
	nSendBuff[nSendByte+3] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[nSendByte+4] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,nSendByte+5);
	memset(nSendBuff,0,SENDLENTH);	
}

/**************************************
//函数功能:对上位机03/04命令处理
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Read_Reg(Md_Recv_t *pMd_Recv)
{
	//printf("FILE:%s\tLINE:%d\r\n", __FILE__, __LINE__);	
	uint16_t nCRC = 0;
	uint16_t i = 0;
    //uint16_t uStatus[4] = {0};
    int16_t iStatus[4] = {0};	
	uint16_t SendIndex = 0;
	memset(nSendBuff,0,sizeof(nSendBuff));
	memset(nReadBuff,0,sizeof(nSendBuff));	
	
	if(pMd_Recv->nRegStartAddr == 0x0001 && pMd_Recv->nRegLen != 0){
			Modbus_Read_Version(pMd_Recv);
			return;
	}
	else if((pMd_Recv->nRegStartAddr > AzReg) || (pMd_Recv->nRegStartAddr < Adjust_Level_Reg) || pMd_Recv->nRegLen > 0x0003 || (pMd_Recv->nRegStartAddr + pMd_Recv->nRegLen) > 0x3003)	//超出范围
	{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;
	}
	for(i = 0; i < pMd_Recv->nRegLen; i++)
	{
		//printf("FILE:%s\tLINE:%d\r\n", __FILE__, __LINE__);	
		switch(pMd_Recv->nRegStartAddr++)
		{
			case AxReg:
				iStatus[i] = sHoldRegValue[0];
				//STK8321_Get_AxData(&iStatus[i]);
				break;
			case AyReg:
				iStatus[i] = sHoldRegValue[1];	
				//STK8321_Get_AyData(&iStatus[i]);				
				break;
			case AzReg:
				iStatus[i] = sHoldRegValue[2];	
				//STK8321_Get_AzData(&iStatus[i]);			
				break;
			case Adjust_Level_Reg:
				iStatus[i] = Light_Grade;
				break;			
		}
		nReadBuff[2*i]	= iStatus[i] >> 8 & 0xFF;
		nReadBuff[2*i+1] = iStatus[i] & 0xFF;
	}
	pMd_Recv->nRegStartAddr -= i;
	nSendBuff[SendIndex++] = pMd_Recv->nAddr;
	nSendBuff[SendIndex++] = pMd_Recv->nCmd;
	nSendBuff[SendIndex++] = pMd_Recv->nRegLen*2;	
	memcpy(&nSendBuff[SendIndex],nReadBuff,(2*i));
	SendIndex += 2*i;
	nCRC = CRC16(nSendBuff,SendIndex);
	nSendBuff[SendIndex++] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[SendIndex++] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,SendIndex);	
	memset(nSendBuff,0,SENDLENTH);
}
/**************************************
//函数功能:对上位机0x05写单个线圈处理
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Write_SingleCoil(Md_Recv_t *pMd_Recv)
{
	uint16_t nCRC = 0;
	uint16_t SendIndex = 0;	
	uint32_t nCoilValue = 0;
	uint32_t nCoilAddr = 0;	
	uint32_t nONOFF= 0;	
	
	nCoilAddr = pMd_Recv->nRegStartAddr;
	if(nCoilAddr < Coil0_0 || nCoilAddr > Coil1_7)
	{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;		
	}		

	nONOFF = pMd_Recv->Md_Data[0];
	if(nONOFF == 0xFF00){
		nCoilValue = 1;
	}
	else if(nONOFF == 0x0000){
		nCoilValue = 0;		
	}
	Set_Coil_Data(nCoilAddr,nCoilValue);
	
	nSendBuff[SendIndex++] = pMd_Recv->nAddr;
	nSendBuff[SendIndex++] = pMd_Recv->nCmd;	
	nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->nRegStartAddr >> 8) & 0x00ff);	
	nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->nRegStartAddr & 0x00ff);	
	nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->Md_Data[0] >> 8) & 0x00ff);	
	nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->Md_Data[0] & 0x00ff);	
	nCRC = CRC16(nSendBuff,SendIndex);
	nSendBuff[SendIndex++] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[SendIndex++] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,SendIndex);	
	memset(nSendBuff,0,SENDLENTH);	
}

/**************************************
//函数功能:对上位机0x0F写多个线圈处理
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:01 0F 10 03 00 0A 02 CD 01 61 9A
//100A 1009 1008 1007 1006 1005 1004 1003
//	1   1     0    0    1    1    0    1
// 	其余位补0					100C 100B
//								  0    1
*************************************/
void Modbus_Write_MuilCoil(Md_Recv_t *pMd_Recv)
{
	uint16_t nCRC = 0;
	uint16_t i = 0;
	uint16_t j = 0;	
	uint16_t SendIndex = 0;
	uint16_t nSendByte = 0;
	uint16_t nCoilAddr = 0;
	uint16_t nCoilValue = 0;	
	uint16_t nExitFlag = 0;
	
	nCoilAddr = pMd_Recv->nRegStartAddr;	
	nSendByte = pMd_Recv->nRegLen / 8;
	if(pMd_Recv->nRegLen % 8){
		nSendByte++;
	}	
	if(nCoilAddr < Coil0_0 || nCoilAddr > Coil1_7 || pMd_Recv->nRegLen > 0x10 || (nCoilAddr + pMd_Recv->nRegLen) > 0x1010)
	{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;		
	}	
	for(i = 0; i < nSendByte; i++)
	{
		for(j = 0; j < 8; j++)
		{
			nCoilValue = (pMd_Recv->Md_Data[i] >> j) & 0x01;
			Set_Coil_Data(nCoilAddr,nCoilValue);
			nCoilAddr++;
			if(nCoilAddr >= pMd_Recv->nRegLen+pMd_Recv->nRegStartAddr){
				nExitFlag = 1;
				break;
			}
		}
		if(nExitFlag == 1){
			break;
		}
	}
	nSendBuff[SendIndex++] = pMd_Recv->nAddr;
	nSendBuff[SendIndex++] = pMd_Recv->nCmd;	
	nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->nRegStartAddr >> 8) & 0x00ff);	
	nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->nRegStartAddr & 0x00ff);
	nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->nRegLen >> 8) & 0x00ff);	
	nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->nRegLen & 0x00ff);
	nCRC = CRC16(nSendBuff,SendIndex);
	nSendBuff[SendIndex++] = (uint8_t)((nCRC>>8)&0x00ff);
	nSendBuff[SendIndex++] = (uint8_t)(nCRC&0x00ff);
	UART_Write(UART1,nSendBuff,SendIndex);	
	memset(nSendBuff,0,SENDLENTH);
}
/**************************************
//函数功能:对上位机06命令写单个寄存器处理
//函数返回值:无
//函数形参:无
//时间:2020/06/22
//备注:无
*************************************/
void Modbus_Write_Reg(Md_Recv_t *pMd_Recv)
{
	uint16_t nCRC = 0;
	uint16_t SendIndex = 0;	
//	uint32_t nRegValue = 0;
	uint32_t nRegAddr = 0;	

	nRegAddr = pMd_Recv->nRegStartAddr;
	
	if(nRegAddr < Adjust_Level_Reg || nRegAddr > AzReg)
	{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;		
	}		
	switch(nRegAddr)
	{
		case Adjust_Level_Reg:
			Light_Grade = pMd_Recv->Md_Data[0];
			break;
		default:
			break;
	}
	if(Light_Grade == pMd_Recv->Md_Data[0]){
		nSendBuff[SendIndex++] = pMd_Recv->nAddr;
		nSendBuff[SendIndex++] = pMd_Recv->nCmd;		
		nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->nRegStartAddr >> 8) & 0x00ff);	
		nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->nRegStartAddr & 0x00ff);	
		nSendBuff[SendIndex++] = (uint8_t)((pMd_Recv->Md_Data[0] >> 8) & 0x00ff);	
		nSendBuff[SendIndex++] = (uint8_t)(pMd_Recv->Md_Data[0] & 0x00ff);	
	nCRC = CRC16(nSendBuff,SendIndex);
		nSendBuff[SendIndex++] = (uint8_t)((nCRC>>8)&0x00ff);
		nSendBuff[SendIndex++] = (uint8_t)(nCRC&0x00ff);
		UART_Write(UART1,nSendBuff,SendIndex);	
		memset(nSendBuff,0,SENDLENTH);			
	}else{
		Modbus_Send_Error(pMd_Recv->nCmd,ERR2);
		return;		
	}
}

六、 总结
我这里用接收中断+定时器并不是最优的处理方式,当我定时器还在计时发了2条请求帧,我的程序只能处理第一条。所以这里我感觉可以搞个队列、或者搞一个链表,将一帧一帧的数据存到队列或者链表,然后从里面取数据去解析。原谅我是个菜鸡,写不出这样的代码,芜湖。祝大家快乐的撸码,写的代码都没bug。

  • 10
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
//单片机Modbus RTU Slave程序 //==================================================================================================== //波 特 率:9600bps //起 始 位:1位 //数 据 位:8位 //校 验 位:偶校验 //停 止 位:1位 //系统时钟:11.0592MHz //物 理 层:RS485 //从站地址:0x01 //使用串口:STC12C5A60S2单片机串口1 //功 能 码:支持01、02、03、04、05、06、15、16等功能码 //01功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行读操作 //02功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输入多路进行读操作 //03功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行读操作 //04功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输入多路进行读操作 //05功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出一路进行写操作 //06功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出一路进行写操作 //15功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行写操作 //16功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行写操作
以下是基于STM32单片机Modbus从机程序示例: ```c #include "modbus.h" /* Modbus function codes */ #define READ_COILS 0x01 #define READ_DISCRETE_INPUTS 0x02 #define READ_HOLDING_REGISTERS 0x03 #define READ_INPUT_REGISTERS 0x04 #define WRITE_SINGLE_COIL 0x05 #define WRITE_SINGLE_REGISTER 0x06 #define WRITE_MULTIPLE_COILS 0x0F #define WRITE_MULTIPLE_REGISTERS 0x10 /* Modbus frame structure */ typedef struct { uint8_t address; uint8_t function_code; uint8_t data[MODBUS_MAX_DATA_SIZE]; uint16_t data_size; uint16_t crc; } modbus_frame_t; /* Modbus handler function */ void modbus_handler(uint8_t* rx_buffer, uint16_t rx_size, uint8_t* tx_buffer, uint16_t* tx_size) { modbus_frame_t frame; uint16_t crc; /* Check the received frame for errors */ if (modbus_check_frame(rx_buffer, rx_size, &frame)) { /* Process the modbus function based on the function code */ switch (frame.function_code) { case READ_COILS: /* Process read coils function */ break; case READ_DISCRETE_INPUTS: /* Process read discrete inputs function */ break; case READ_HOLDING_REGISTERS: /* Process read holding registers function */ break; case READ_INPUT_REGISTERS: /* Process read input registers function */ break; case WRITE_SINGLE_COIL: /* Process write single coil function */ break; case WRITE_SINGLE_REGISTER: /* Process write single register function */ break; case WRITE_MULTIPLE_COILS: /* Process write multiple coils function */ break; case WRITE_MULTIPLE_REGISTERS: /* Process write multiple registers function */ break; default: /* Invalid function code */ break; } /* Construct the response frame */ frame.address = MODBUS_ADDRESS; frame.function_code += 0x80; crc = modbus_calculate_crc((uint8_t*)&frame, frame.data_size + 2); frame.crc = (crc << 8) | (crc >> 8); /* Copy the response frame to the transmit buffer */ *tx_size = modbus_build_frame(tx_buffer, &frame); } } ``` 该程序使用了modbus.h头文件中定义的函数进行Modbus通信处理。在处理Modbus帧时,程序首先对接收到的帧进行错误检查,然后根据帧的功能码进行相应的处理。最后,程序构建响应帧并将其复制到传输缓冲区以进行发送。 需要注意的是,该程序仅提供了Modbus通信处理的基本框架,具体的Modbus功能需要根据实际需求进行实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值