PLC、STM32单片机、PC端485modbus通信调试过程
设备明细:
PLC:埃森ARS-010-32PLC,软件AR Logic Editor。
单片机:STM32F103RCT6,软件keil。
测试软件:modbus串口调试软件、modbus poll。
调试过程:
PLC:从机,站号1
STM32:从机,站号2
接线
PLC的485端口和单片机的485接口,都连接到USB转485模块,
电脑识别USB转485模块为COM7。
PLC通信调试
埃森PLC有两路485串口,随意一组测试。
PLC软件端参数设置如下图:站号1,波特率9600,检验位:NONE。
PLC modbus通信代码区:
使用modbus poll软件,连接串口COM7,波特率设置为9600,无校验位,进行通信调试。
通信效果如下图:PLC232串口连接PC,在线调试,
串口软件模拟发送指令,写入读取:
000006-Tx:01 06 05 64 00 00 C8 D9
000007-Rx:01 06 05 64 00 00 C8 D9
000008-Tx:01 03 05 64 00 01 C5 19
000009-Rx:01 03 02 00 00 B8 44
PLC在线更改数值,串口软件发指令读取:
000010-Tx:01 03 05 64 00 01 C5 19
000011-Rx:01 03 02 00 09 78 42
STM32通信调试
STM32,使用UART5,波特率设置为9600,
// rs485.c
#include "sys.h"
#include "rs485.h"
#include "delay.h"
#include "usart.h"
#include "modbus.h"
#if EN_USART3_RX //如果使能了接收
//接收到的数据长度
u8 RS485_RX_CNT=0;
unsigned char Rs485_Recok; //接收完成标志
#endif
u8 RS485_RX_BUFF[8] = {0};
u8 RS485_TX_BUFF[8] = {0};
u8 DataH = 0;
u8 DataL = 0;
//初始化IO 串口5
//bound:波特率
void RS485_Init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD, ENABLE); //使能,时钟,GPIOCD
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);//UART5
USART_DeInit(UART5); //复位串口5
//USART5_TX PC12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PC12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化PC12
//USART5_RX PPD2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOD, &GPIO_InitStructure); //初始化PPD2
//PB5 ENABLE
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB0
RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART5,ENABLE);//复位串口2
RCC_APB1PeriphResetCmd(RCC_APB1Periph_UART5,DISABLE);//停止复位
#if EN_USART3_RX
//Usart5 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//115200
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(UART5, &USART_InitStructure); //初始化串口
USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);//开启中断
USART_ITConfig(UART5, USART_IT_IDLE, ENABLE);//开启空闲中断
USART_Cmd(UART5, ENABLE); //使能串口
#endif
RS485_TX_EN=0; //默认为接收模式
}
//RS485发送len个字节.
//buf:发送区首地址
//len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
void RS485_Send_Data(u8 *buf,u8 len)
{
u8 t;
RS485_TX_EN=1; //设置为发送模式
for(t=0;t<len;t++) //循环发送数据
{
while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==RESET); //等待发送结束
USART_SendData(UART5,buf[t]); //发送数据
}
while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==RESET); //等待发送结束
RS485_RX_CNT=0;
RS485_TX_EN=0; //设置为接收模式
}
//RS485查询接收到的数据
//buf:接收缓存首地址
//len:读到的数据长度
void RS485_Receive_Data(u8 *buf,u8 *len)
{
u8 rxlen=RS485_RX_CNT;
u8 i=0;
*len=0; //默认为0
delay_ms(10); //等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束
if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了
{
for(i=0;i<rxlen;i++)
{
buf[i]=RS485_RX_BUFF[i];
}
*len=RS485_RX_CNT; //记录本次数据长度
RS485_RX_CNT=0; //清零
}
}
void UART5_IRQHandler(void)
{
u8 res;
u8 err;
u8 i;
u16 RegAddr;
u16 RegNumb;
u16 calCRC;
if(USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)//接收到数据
{
if(USART_GetFlagStatus(UART5,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE))
err = 1;//检测到噪音、帧错误或校验错误
else
err = 0;
res =USART_ReceiveData(UART5);//读取接收到的数据UART5->DR
if((RS485_RX_CNT < 8)&&(err == 0))
{
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
}
if(RS485_RX_CNT == 8)
{
RS485_RX_CNT = 0;
if(RS485_RX_BUFF[0] == 0x02)
{
if((RS485_RX_BUFF[1] == 0x03) || (RS485_RX_BUFF[1] == 0x06) || (RS485_RX_BUFF[1] == 0x16))
{
RegAddr = (((u16)RS485_RX_BUFF[2])<<8) | RS485_RX_BUFF[3];
RegNumb = (((u16)RS485_RX_BUFF[4])<<8) | RS485_RX_BUFF[5];
if((CRC_Calculate(RS485_RX_BUFF,8))==0)
{
switch(RS485_RX_BUFF[1])
{
case 0x03:
RS485_TX_BUFF[0] = RS485_RX_BUFF[0];
RS485_TX_BUFF[1] = RS485_RX_BUFF[1];
RS485_TX_BUFF[2] = RegNumb * 2;
Uart1_SendIntToString(DataH);
Uart1_SendIntToString(DataL);
for(i=0;i<RegNumb;i++)
{
RS485_TX_BUFF[3+i*2] = DataH; // //先发送高字节--在发送低字节
RS485_TX_BUFF[4+i*2] = DataL; //
}
calCRC = CRC_Calculate(RS485_TX_BUFF,RegNumb*2+3);
RS485_TX_BUFF[RegNumb*2+3] = calCRC/256; //CRC的高8位
RS485_TX_BUFF[RegNumb*2+4] = calCRC%256; //CRC的低8位
RS485_TX_EN = 1;
RS485_Send_Data(RS485_TX_BUFF,RegNumb*2+5);
RS485_TX_EN =0;
break;
case 0x06:
if(RegAddr == 0x2001)
{
DataH = RS485_RX_BUFF[4];
DataL = RS485_RX_BUFF[5];
}
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC = CRC_Calculate(RS485_TX_BUFF,6);
RS485_TX_BUFF[6] = (calCRC>>8)&0xFF;
RS485_TX_BUFF[7] = (calCRC)&0xFF;
RS485_TX_EN = 1;
RS485_Send_Data(RS485_TX_BUFF,8);
RS485_TX_EN = 0;
break;
case 0x16:
break;
}
}
else
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x04; //异常码
RS485_TX_EN = 1;
RS485_Send_Data(RS485_TX_BUFF,3);
RS485_TX_EN = 0;
}
}
else
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_TX_EN = 1;
RS485_Send_Data(RS485_TX_BUFF,3);
RS485_TX_EN = 0;
}
}
}
}
}
#ifndef __RS485_H
#define __RS485_H
#include "sys.h"
extern u8 RS485_RX_BUFF[8]; //接收缓冲,最大64个字节
extern u8 RS485_RX_CNT; //接收到的数据长度
extern unsigned char Rs485_Recok;
extern u16 Flash_Modbus_ADDR;
//模式控制
#define RS485_TX_EN PBout(5) //485模式控制.0,接收;1,发送.
//如果想串口中断接收,设置EN_USART5_RX为1,否则设置为0
#define EN_USART3_RX 1 //0,不接收;1,接收.
void RS485_Init(u32 bound);
void RS485_Send_Data(u8 *buf,u8 len);
void RS485_Receive_Data(u8 *buf,u8 *len);
void MODBUS_send();
#endif
#include "modbus.h"
#include "rs485.h"
#include "usart.h"
u16 Flash_Modbus_ADDR ; //Modbus ID?
u16 slave_add;
//u8 canbuf[8]={0xff,0x00,0x30,0x31,0x32,0x01,0x02,0x03};
//unsigned int UsartNUM;
#define MODBUS_LENGTH 100
const unsigned char auchCRCHi[] = /* CRC*/
{
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
} ;
const unsigned char auchCRCLo[] = /* CRC*/
{
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
} ;
/******************************************************************************
CRCjiaoyan
*******************************************************************************/
unsigned int CRC_Calculate(unsigned char *pdata,unsigned char num)
{
unsigned char uchCRCHi = 0xFF ;
unsigned char uchCRCLo = 0xFF ;
unsigned char uIndex ;
while(num --)
{
uIndex = uchCRCHi^*pdata++ ;
uchCRCHi = uchCRCLo^auchCRCHi[uIndex];
uchCRCLo = auchCRCLo[uIndex];
}
return (uchCRCHi << 8 | uchCRCLo) ;
}
///******************************************************************************
// 03 ma gong neng han shu
//参数1从机地址,参数2起始地址,参数3寄存器个数
//*******************************************************************************/
void MODBUS_03_Return(unsigned int addr, unsigned int number)
{
unsigned char send_length;
unsigned char send_buf[8];
unsigned int crc;
slave_add = Flash_Modbus_ADDR;
send_length = 0;
send_buf[send_length++] = Flash_Modbus_ADDR;
send_buf[send_length++] = 0x03; //03???
send_buf[send_length++] = (addr / 256);
send_buf[send_length++] = (addr % 256); //地址:4x1380 RW100
send_buf[send_length++] = (number / 256);
send_buf[send_length++] = (number % 256); //1个字节长度
crc = CRC_Calculate(send_buf,send_length);
send_buf[send_length++] = crc/256;
send_buf[send_length++] = crc%256;
RS485_Send_Data(send_buf,send_length);
}
///******************************************************************************
// send MODBUS date :string
//*******************************************************************************/
void MODBUS_string_Return(unsigned int addr,unsigned int code)
{
unsigned char send_buf[8];
unsigned int crc;
send_buf[0] = Flash_Modbus_ADDR;
send_buf[1] = 0x06;
send_buf[2] = (addr / 256);
send_buf[3] = (addr % 256);
send_buf[4] = (code / 256);
send_buf[5] = (code % 256);
crc = CRC_Calculate(send_buf,6);
send_buf[6] = (crc / 256);
send_buf[7] = (crc % 256);
RS485_Send_Data(send_buf,8);
}
#ifndef __MODBUS_H
#define __MODBUS_H
#include "sys.h"
extern u16 Flash_Modbus_ADDR;
extern unsigned short modbus_reg[];
unsigned int CRC_Calculate(unsigned char *pdata,unsigned char num);
u16 CRC_16( u8 *vptr, u8 len);
//void MODBUS_03_Return(u16 addrL,u16 addrH,u16 number);
void MODBUS_string_Return(unsigned int addr,unsigned int code);
void MODBUS_03_Return(unsigned int addr, unsigned int number);
#endif
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "stdio.h"
#include "stm32f10x_flash.h"
#include "stdlib.h"
#include "string.h"
#include "wdg.h"
#include "timer.h"
#include "stm32f10x_tim.h"
#include "rs485.h"
#include "modbus.h"
extern u8 RS485_RX_BUFF[8],Rs485_Recok,RS485_RX_CNT;
int main(void)
{
delay_init();
NVIC_Configuration();
uart_init(115200);
/***************** 485 *******************/
RS485_Init(9600);
Flash_Modbus_ADDR=0x01;
// TIM3_Int_Init(49999,7199); //5s
IWDG_Init(7,625); //
while(1)
{
}
}
modbus poll软件参数设置:
000027-Tx:02 06 05 64 00 06 48 E8
000028-Rx:02 06 05 64 00 06 48 E8
000029-Tx:02 06 05 64 00 06 48 E8
000030-Rx:02 06 05 64 00 06 48 E8
000031-Tx:02 03 05 64 00 01 C5 2A
000032-Rx:02 03 02 00 00 FC 44
000033-Tx:02 03 05 64 00 01 C5 2A
000034-Rx:02 03 02 00 00 FC 44
但是,读取数据都是0.
PLC端
经测试,PLC端作为从机,代码改动,系统块不改动,结果不受影响。
PLC端485接线端子,由485(2)改为485(1),结果也没影响,前提是没改PLC系统块参数,
再测试改动系统块,如下图:
结果证明:485(1)通讯断开,如下图:
PLC的485(2)端口,通讯正常,如下图:
结论:PLC系统块参数需设置为主机,才能正常通讯。而且作为从机接收modbus数据时,跟代码中RTU功能块的参数设置没有关系。比如rtu03:port为2或者3不影响结果,ID为随意数,无影响。
结论:空代码不可以。