485 Modbus协议程序实例(串口中断接收/发送)

基于串口接收中断与发送中断完成的485通讯实例

优点:逻辑简单,响应快速。

缺点:容易受到单片机本身资源占用以及运行环境干扰的影响出现丢包情况。

对应程序初始化后在主循环运行Uart1_Communicate即可。

使用联合union来将字分解成组成这个对象的各个字节或位。

    typedef 
	union{
		struct {
			u8	b0:1;//定义位域长为1
			u8	b1:1;
			u8	b2:1;
			u8	b3:1;
			u8	b4:1;
			u8	b5:1;
			u8	b6:1;
			u8	b7:1;
			u8	b8:1;
			u8	b9:1;
			u8	b10:1;
			u8	b11:1;
			u8	b12:1;
			u8	b13:1;
			u8	b14:1;
			u8	b15:1;
		} bit;
		struct{
			u8	low;	/* low	8 bit */
			u8	high;	/* high 8 bit */
		}byte;
		u16 	word;
	}word_def;

    #define TX_MODE			(GPIOB->ODR |= GPIO_Pin_8)
	#define RX_MODE			(GPIOB->ODR &= ~GPIO_Pin_8)
	
	word_def UartStateAdr;
	#define ReceiveVerifyFlag						UartStateAdr.bit.b3//接收数据需求校验标志位
	#define NeedReplyFlag						UartStateAdr.bit.b2//接收处理完毕需求回复标志
	#define ReceiveCompletedFlag				UartStateAdr.bit.b1//接收完毕标志
	#define ReceiveHeaderFlag					UartStateAdr.bit.b0//数据帧头接收标志

	typedef struct     //对应存放寄存器数据的数组坐标的结构体
	{
	  u8 A;
	  u8 B;
	} ModbusRegs_Coordinate;  

	ModbusRegs_Coordinate  G_Coordinate;//数组坐标结构体变量(全局)
	
	u8 RXmodeFlag;//大循环中持续置位GPIO,避免在其他实践中遇到在中断直接操作GOIO运行后却未置位的情况
	u8 IDcode=0x02;//Modbus协议从机ID
	u8 Functioncode;//接收的功能码
	u8 G_SendByteTable_8u[256];//发送区数组
	u8 G_BufferReceive_8u[256];//接收缓冲区
	u8 G_ReceiveByteTable[256];//校验后实际接收数据
	u8 ReceiveDataTemp;//接收传递变量
	
	
	u16 G_ModbusRegs_16u[6][128];  //定义700个寄存器
    u16 Read_RegAddress;//读取寄存器起始地址
	u16 Read_ProcessRegAddress;//读取寄存器过程中地址
	u16 Read_RegNum;//读取寄存器的个数
	u16 Write_RegAddress;//写寄存器地址 或 写多个寄存器起始地址
	u16 Write_ProcessRegAddress;//写寄存器地址 或 写多个寄存器末尾地址
	u16 Write_RegNum;//写寄存器的个数
	u16 ReceivePacketState;//接收数据位(无效数据、接收校验完毕清零,接收中按数据长度自增)
	u16 ReceiveLength=8;//接收数据长度初始化为8
	u16 SendPacketState;//发送数据位
	u16 SendLength;//发送数据长度
	u16 CRC_Check;//根据前面数据计算获得的校验值(与接收到的校验比较验证)
	u16 SendCRC_Check;//发送数据CRC校验码
/*v将寄存器地址至数组行列坐标转化v*/
	ModbusRegs_Coordinate RegCoordinateCalcuate(u16 Address)
	{
		ModbusRegs_Coordinate  Coordinate;
		Coordinate.A=(Address>>7);//第A+1行
		Coordinate.B=(Address%128);//第B+1列
		return (Coordinate);
	}
/*^将寄存器地址至数组行列坐标转化^*/

	
/*vCRC校验计算v*/
	u16 Crc_cal_value(u8 *data_value,u8 data_length)
	{
		u16  crc_value=0xFFFF;
		u8	i,j;
		for (j=0; j> 1;
					crc_value = crc_value ^ 0xa001;
				}
				else
				{
					crc_value = crc_value >> 1;
				}
			}
		}
		return(crc_value);
	}	
/*^CRC校验计算^*/
	
	void Uart1_IO_Init(void)
	{
		GPIO_InitTypeDef GPIO_InitStruct;
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
		
		GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_8;					//485使能端口
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		
		GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_13;				//LED
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
		GPIO_Init(GPIOC,&GPIO_InitStruct);
		//设置复用功能的类型
		GPIO_PinAFConfig(GPIOB,GPIO_Pin_6,GPIO_AF_0);
		GPIO_PinAFConfig(GPIOB,GPIO_Pin_7,GPIO_AF_0);
		//复用开漏输出
		GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_6;
		GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		//复用输入上拉
		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		RX_MODE;
	}

	void Uart1_Init(void)
	{
		NVIC_InitTypeDef NVIC_InitStructure;
		//启动外围时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
		//8倍过采样模式
		USART1->CR1 = (1<<15);
		//关闭溢出检测
		USART1->CR3 = (1<<12);
		//波特率的计算
		// BORD = Fpclk/(8*(2 - OVER8)*DIV)
		//目标波特率 9600 DIV = 625
		//2400 DIV = 2500

		USART1->BRR = (52<<4);

		//使能串口,清空TC标志
		USART1->CR1 |=(1<<0);						// 使能串口
		USART1->CR1 |=(1<<3)+(1<<2);				// 允许接收和发送
		USART1->ICR |=(1<<6);						// 清除发送完成标志
		/* Enable the USARTx Interrupt */
		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPriority = 0x03;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
		USART_ITConfig(USART1, USART_IT_TC, ENABLE);
	}
	
    
	void Uart1_Communicate(void)
	{
		u8 Cycle;//缓冲区传送确认区的过程变量
		u8 a,b;//a用于写寄存器的过程变量,b用于读寄存器的过程变量
		u8 timecnt;//RX_Mode转TX_Mode的延时(必须有延时,否则当485芯片高阻态建立较慢时通讯数据会紊乱)
		
		/*v485状态再置位v*/
		if(RXmodeFlag)RX_MODE;//避免在中断中运行GPIOB->ODR &= ~GPIO_Pin_8后不置位的情况
		/*v485状态再置位v*/	
		/*v数据校验v*/
		if(ReceiveVerifyFlag)
		{
			CRC_Check=Crc_cal_value(G_BufferReceive_8u,ReceiveLength-2);
			if( CRC_Check == (G_BufferReceive_8u[ReceiveLength-2]+G_BufferReceive_8u[ReceiveLength-1]*256) )//校验正确
			{
				for(Cycle = 0; Cycle < ReceiveLength; Cycle++)
							{
								G_ReceiveByteTable[Cycle] = G_BufferReceive_8u[Cycle];
							}
				ReceiveCompletedFlag=1;
			}
			ReceivePacketState=0;
			ReceiveVerifyFlag=0;
		}
		/*^数据校验^*/
		/*v接收数据处理v*/
		if(ReceiveCompletedFlag)
		{
			switch(Functioncode)
			{
				case 0x03://读
				{
					Read_RegAddress=G_BufferReceive_8u[2]*256+G_BufferReceive_8u[3];
					Read_RegNum=G_BufferReceive_8u[4]*256+G_BufferReceive_8u[5];
					//读数据赋值发送缓存数组在发送处理中
					break;
				}
				case 0x04://读
				{
					Read_RegAddress=G_BufferReceive_8u[2]*256+G_BufferReceive_8u[3];
					Read_RegNum=G_BufferReceive_8u[4]*256+G_BufferReceive_8u[5];
					//读数据赋值发送缓存数组在发送处理中
					break;
				}
				case 0x06://写单个
				{
					Write_RegAddress=G_BufferReceive_8u[2]*256+G_BufferReceive_8u[3];
					Write_RegNum=1;
					G_Coordinate = RegCoordinateCalcuate(Write_RegAddress);
					G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B]=G_BufferReceive_8u[4]*256+G_BufferReceive_8u[5] ;
					break;
				}
				case 0x10://写n个(起始地址与寄存器数量在中断中已经计算)
				{
					Write_ProcessRegAddress=Write_RegAddress;
					for(a=0;a>8);
						G_SendByteTable_8u[b*2+4]=(u8)G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B];
						Read_ProcessRegAddress++;
					}	
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,SendLength-2);
					G_SendByteTable_8u[SendLength-2]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[SendLength-1]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x04://读的回复
				{
					SendLength=Read_RegNum*2+5;
					SendPacketState=1;
					Read_ProcessRegAddress = Read_RegAddress;
					G_SendByteTable_8u[0]=IDcode;
					G_SendByteTable_8u[1]=0x04;
					G_SendByteTable_8u[2]=(u8)(Read_RegNum*2);
					for(b=0;b>8);
						G_SendByteTable_8u[b*2+4]=(u8)G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B];
						Read_ProcessRegAddress++;
					}	
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,SendLength-2);
					G_SendByteTable_8u[SendLength-2]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[SendLength-1]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x06://写单个的回复
				{
					SendLength=8;
					SendPacketState=1;
					for(b=0;bTDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x10://写n个(起始地址与寄存器数量在中断中已经计算)的回复
				{
					SendLength=8;
					SendPacketState=1;
					for(b=0;b<6;b++)G_SendByteTable_8u[b] = G_ReceiveByteTable[b];//06的回复数据与接收数据相同
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,6);
					G_SendByteTable_8u[6]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[7]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				default :
				{
					break;
				}
			}
			NeedReplyFlag=0;
		}
		/*^发送处理^*/
	}
	
	void USART1_IRQHandler(void)
	{
		/*v发送v*/
	   if((USART1->ISR & USART_ISR_TC) == USART_ISR_TC)
		{
			USART1->ICR = USART_ISR_TC;
			if(SendPacketStateTDR = (G_SendByteTable_8u[SendPacketState] & (uint16_t)0x00FF);	
				SendPacketState ++;
			}
			else 
			{
				RX_MODE;
				RXmodeFlag=1;
			}
		}		
		/*^发送^*/
		/*v接收处理v*/
		if((USART1->ISR & USART_ISR_RXNE) == USART_ISR_RXNE)
		{
			USART1->ICR = USART_ISR_RXNE;
	
			ReceiveDataTemp = (USART1->RDR&0x00FF);//取接收的数据
			
			if(ReceiveDataTemp == IDcode&&ReceiveVerifyFlag==0&&ReceivePacketState==0)// 找到帧头且无需求校验的数据条且接收位为头位
			{		
				ReceiveHeaderFlag = 1;
				G_BufferReceive_8u[ReceivePacketState] = ReceiveDataTemp;
				ReceivePacketState++;
			}
			else if(ReceiveHeaderFlag)
			{
				if(ReceivePacketState==1)//接收第二位功能码识别
				{
					if(ReceiveDataTemp==0x03||ReceiveDataTemp==0x04||ReceiveDataTemp==0x06||ReceiveDataTemp==0x10)//若为四个有效功能码之一
					{
						Functioncode=ReceiveDataTemp;
						G_BufferReceive_8u[ReceivePacketState] = Functioncode;
						ReceivePacketState++;
					}	
					else//为无效功能码
					{
						ReceivePacketState=0;
						ReceiveHeaderFlag=0;//本帧头无效,等待下一个帧头
					}	
				}
				else if(ReceivePacketState

你可能感兴趣的:(通讯,stm32)