串口实现modbus通讯
代码
main.c
modbus.c
mosbus.h
modbusCRC.c
modbusCRC.h
485.c
485.h
timer.c
timer.h

#include "stm32f10x.h" #include "led.h" #include "usart.h" #include "modbus.h" #include "modbusCRC.h" #include "time.h" #include "sys.h" //#include "delay.h" #include "485.h" #include "ds18b20.h" //说明 //硬件连接:连接好2485通讯跳线帽 //实验现象,开发板每隔一秒向电脑发送一段数据RS2485 OK u16 Reg[]={0x0000, //本设备寄存器中的值 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, }; int main(void) { u8 USART1_len,USART1_t; u8 RS485[] = "RS485 OK"; Mosbus_Init(); LED_Init(); TIME4_Init(999,71);//一毫秒 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2 DS18B20_Init(); while(1) { Mosbus_Event(); } }

#include "modbus.h" #include "modbusCRC.h" #include "led.h" #include "485.h" MODBUS modbus; extern u16 Reg[]; /* 因为波特率 9600 1位数据的时间为 1000000us/9600bit/s=104us 一个字节为 104us*10位 =1040us 所以 MODBUS确定一个数据帧完成的时间为 1040us*3.5=3.64ms ->10ms */ void Mosbus_Init() { modbus.myadd=4; //本从设备的地址 modbus.timrun=0; //MODbus定时器停止计时 RS485_Init(9600); } void Modbud_fun3() //3号功能码处理 ---主机要读取本从机的寄存器 { u16 Regadd; u16 Reglen; u16 byte; u16 i,j; u16 crc; Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要读取的寄存器的首地址 Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //得到要读取的寄存器的数量 i=0; modbus.Sendbuf[i++]=modbus.myadd;//本设备地址 modbus.Sendbuf[i++]=0x03; //功能码 byte=Reglen*2; //要返回的数据字节数 //modbus.Sendbuf[i++]=byte/256; // modbus.Sendbuf[i++]=byte%256; for(j=0;j<Reglen;j++) { modbus.Sendbuf[i++]=Reg[Regadd+j]/256; modbus.Sendbuf[i++]=Reg[Regadd+j]%256; } crc=crc16(modbus.Sendbuf,i); modbus.Sendbuf[i++]=crc/256; // modbus.Sendbuf[i++]=crc%256; LED8=!LED8; RS485_TX_EN=1; for(j=0;j<i;j++) { RS485_byte(modbus.Sendbuf[j]); } RS485_TX_EN=0; } void Modbud_fun6() //6号功能码处理 { u16 Regadd; u16 val; u16 i,crc,j; i=0; Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要修改的地址 val=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //修改后的值 Reg[Regadd]=val; //修改本设备相应的寄存器 //以下为回应主机 modbus.Sendbuf[i++]=modbus.myadd;//本设备地址 modbus.Sendbuf[i++]=0x06; //功能码 modbus.Sendbuf[i++]=Regadd/256; modbus.Sendbuf[i++]=Regadd%256; modbus.Sendbuf[i++]=val/256; modbus.Sendbuf[i++]=val%256; crc=crc16(modbus.Sendbuf,i); modbus.Sendbuf[i++]=crc/256; modbus.Sendbuf[i++]=crc%256; RS485_TX_EN=1; // for(j=0;j<i;j++) { RS485_byte(modbus.Sendbuf[j]); } RS485_TX_EN=0; // } void Mosbus_Event() { u16 crc; u16 rccrc; if(modbus.reflag==0) //没有收到MODbus的数据包 { return ; } crc= crc16(&modbus.rcbuf[0], modbus.recount-2); //计算校验码 rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1]; //收到的校验码 if(crc == rccrc) //数据包符号CRC校验规则 { LED5=!LED5; if(modbus.rcbuf[0] == modbus.myadd) //确认数据包是否是发给本设备的 { switch(modbus.rcbuf[1]) //分析功能码 { case 0: break; case 1: break; case 2: break; case 3: Modbud_fun3(); break; //3号功能码处理 case 4: break; case 5: break; case 6: Modbud_fun6(); break; //6号功能码处理 case 7: break; //.... } } else if(modbus.rcbuf[0] == 0) //广播地址 { } } modbus.recount=0; // modbus.reflag=0; }

#ifndef _modbus_ #define _modbus_ #include "stm32f10x_conf.h" #define RS485_RT_1 GPIO_SetBits(GPIOD, GPIO_Pin_3) //485发送状态 #define RS485_RT_0 GPIO_ResetBits(GPIOD, GPIO_Pin_3) //485置接收状态 typedef struct { u8 myadd;//本设备的地址 u8 rcbuf[100]; //MODBUS接收缓冲区 u16 timout;//MODbus的数据断续时间 u8 recount;//MODbus端口已经收到的数据个数 u8 timrun;//MODbus定时器是否计时的标志 u8 reflag;//收到一帧数据的标志 u8 Sendbuf[100]; //MODbus发送缓冲区 }MODBUS; extern MODBUS modbus; void Mosbus_Init(void); void Mosbus_Event(void); #endif

//========================================== #include "modbusCRC.h" /* CRC 高位字节值表 */ const uchar auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; /* CRC低位字节值表*/ const uchar auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 } ; /****************************************************************** 功能: CRC16校验 输入: 输出: ******************************************************************/ uint crc16( uchar *puchMsg, uint usDataLen ) { uchar uchCRCHi = 0xFF ; // 高CRC字节初始化 uchar uchCRCLo = 0xFF ; // 低CRC 字节初始化 unsigned long uIndex ; // CRC循环中的索引 while ( usDataLen-- ) // 传输消息缓冲区 { uIndex = uchCRCHi ^ *puchMsg++ ; // 计算CRC uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; } return ( uchCRCHi << 8 | uchCRCLo ) ; }

#ifndef _MODBUS_CRC_H_ #define _MODBUS_CRC_H_ //========================================== //#include "_TYPE.h" //========================================== #include "stm32f10x_conf.h" #define uint u16 #define uchar u8 //------------------------------------------ //#ifdef _MODBUS_CRC_C_ //#define global idata //#else //#define global extern idata //#endif //------------------------------------------ //========================================== //#undef global //========================================== uint crc16( uchar *puchMsg, uint usDataLen ); //========================================== #endif

#include "sys.h" #include "485.h" #include "delay.h" #include "modbus.h" void RS485_Init(u32 bound){ //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO时钟 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART2时钟 // GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);//调用重映射函数 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //LED0-->PA.8 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_SetBits(GPIOD,GPIO_Pin_3); // //USART1_TX GPIO // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //P // GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 // GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIO // // //USART1_RX GPIOA初始化 // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//PA // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 // GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIO //USART2_TX PA.2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //USART2_RX PA.3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART2, &USART_InitStructure); //初始化串口1 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断 USART_Cmd(USART2, ENABLE); //使能串口1 RS485_TX_EN=0; //默认为接收模式 } /***********************************************/ u8 RS485_8bit_PutChar_send(u8 ch) { RS485_TX_EN=1; USART_SendData(USART2, (u8) ch); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET) { } RS485_TX_EN=0; return ch; } void RS485_8bit_PutString_send(u8* buf , u16 len) { u8 i; // RS485_TX_EN=1; for(i=0;i<len;i++) { RS485_8bit_PutChar_send(*buf++); } // RS485_TX_EN=0; } u16 RS485_16bit_PutChar_send(u16 ch) { /* Write a character to the USART */ USART_SendData(USART2, (u8) ch); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET) { } return ch; } void RS485_16bit_PutString_send(u16* buf , u16 len) { u8 i; for(i=0;i<len;i++) { RS485_16bit_PutChar_send(*buf++); } } void RS485_byte(u8 d) //485发送一个字节 { // RS485_RT_1; //?? USART_SendData(USART2, d); while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); USART_ClearFlag(USART2,USART_FLAG_TC ); // RS485_RT_0; //? } void USART2_IRQHandler(void) //串口1中断服务程序 { u8 st,sbuf; if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET)//注意!不能使用if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)来判断 { sbuf=USART_ReceiveData(USART2); } st=USART_GetITStatus(USART2, USART_IT_RXNE); if(st==SET) // { sbuf=USART2->DR; if( modbus.reflag==1) //有数据包正在处理 { return ; } modbus.rcbuf[modbus.recount++]=sbuf; modbus.timout=0; if(modbus.recount==1) //收到主机发来的一帧数据的第一字节 { modbus.timrun=1; //启动定时 } USART_ClearITPendingBit(USART2,USART_IT_RXNE); } }

#ifndef __485_H #define __485_H #include "stdio.h" #include "sys.h" #define USART_REC_LEN 200 //定义最大接收字节数 200 #define EN_USART2_RX 1 //使能(1)/禁止(0)串口1接收 void RS485_Init(u32 bound); void RS485_8bit_PutString_send(u8* buf , u16 len); void RS485_16bit_PutString_send(u16* buf , u16 len); u8 RS485_8bit_PutChar_send(u8 ch); u16 RS485_16bit_PutChar_send(u16 ch); void RS485_byte(u8 d) ; #define RS485_TX_EN PDout(3) // PD void uart2_init(u32 bound); #endif

#include "time.h" #include "modbus.h" #include "led.h" void TIME4_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000为500ms TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 TIM_ITConfig( //使能或者失能指定的TIM中断 TIM4, //TIM2 TIM_IT_Update , ENABLE //使能 ); NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM3中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 TIM_Cmd(TIM4, ENABLE); //使能TIMx外设 } void TIM4_IRQHandler(void) //TIM3中断 { u8 st; if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 { st= TIM_GetFlagStatus(TIM4, TIM_FLAG_Update); if(st==SET) { TIM_ClearFlag(TIM4, TIM_FLAG_Update); if(modbus.timrun!=0) { modbus.timout++; if(modbus.timout>=8) //间隔时间达到了时间 { modbus.timrun=0;//关闭定时器--停止定时 modbus.reflag=1; //收到一帧数据 } } } TIM_ClearITPendingBit(TIM4, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源 } }

#ifndef __TIME_H #define __TIME_H #include "sys.h" void TIME4_Init(u16 arr,u16 psc); //#define LED_SPEET 1000 #endif
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现