资源使用说明: 2410+LINUX、UART(即RS-232串口)全双工通信、RS-485半双工通信
局部程序框图及其设计说明:
调试记录及调试结果:
MODBUS总结:
MODBUS协议
对比:
ASCII模式:用8位表示一个由内容字符转化而来的实际数值,直观;
RTU模式 :用4位表示一个由内容字符转化而来的实际数值,效率高。
其它编程注意点总结:
1、分母或乘数为2的n次方的乘除法用移位运算以提高效率,注意移位运算符的优先级比加减运算符低,别忘了加括号先算移位的;
2、short两个字节,long四个字节,int则与机器字长相关。
3、在使用多个输出函数连续进行多次输出时,有可能发现输出错误。因为下一个数据再上一个数据还没输出完毕,还在输出缓冲区中时,下一个printf就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误。 在 prinf();后加上fflush(stdout); 强制马上输出,避免错误。
4、menset()用法: char arr[20]; memset(arr,'\0',20);
5、#if 0 code #endif :
(1)code中定义的是一些调试版本的代码,此时code完全被编译器忽略。如果想让code生效,只需把#if 0改成#if 1
(2)#if 0还有一个重要的用途就是用来当成注释,如果你想要注释的程序很长,这个时候#if 0是最好的,保证不会犯错误。(但是林锐的书上说千万不要把#if 0 来当作块注释使用)
#if 1可以让其间的变量成为局部变量。
(3)这个结构表示你先前写好的code,现在用不上了,又不想删除,就用这个方法,比注释方便。
程序代码清单:
/**************************************************************************
// tzdcs.c, need insmod mfix.o & m485.o first.
//
// Data Collect System Ver1.0
//
// base on MODBUS protocol (Mode:ASCII)
// Processor: Arm2410
//
// MODBUS protocol:
// (Mode:ASCII)
// START FLAG ( 0x3A ---- ':' )
// NET ADDRESS HIGH
// NET ADDRESS LOW
// COMMAND HIGH
// COMMAND LOW
// DATA LENGTH ( two char: n like '02')
// DATABUF ( n char: data_0 ~ data_n-1 )
// LRC HIGH
// LRC LOW
// END FLAG ( two char: 0x0D 0x0A ---- 'CRLF' )
//
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// START_FLAG AddrHigh AddrLow CmdHigh CmdLow DataLenHigh DataLenLow DataBuf[] LrcHigh LrcLow END_FLAG_1 END_FLAG_2
// 0x3A data_0~data_n-1 0x0D 0x0A
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Author: JaneEstrong
// Email: JaneEstrong@gmail.com
// Date: 2012.09.10
//
**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
//#include <sys/mman.h>
#include <termios.h>
#define _485_IOCTRL_RE2DE (0x10) //send or receive
#define _485_RE 0 //receive
#define _485_DE 1 //send
//#define BAUDRATE B115200
#define COM1 "/dev/tts/1"//ttyS1 RS232
#define COM2 "/dev/tts/2"// RS485
#define DEV485 "/dev/485/0raw"
#define MASTER //主机标志
#define START_FLAG 0x3A // ':'
#define END_FLAG_1 0x0D // CR
#define END_FLAG_2 0x0A // LF
// rx_result
#define NOT_SEND_TO_US 0
#define ERROR_LENGTH 1
#define ERROR_LRC 2
#define ERROR_END_FLAG 3
#define VALID_PACKET 4
//uchStatus
#define MASTER_WAITING_RECEIVE_FROM_PC 1
#define NOT_MASTER_WAITING_RECEIVE_FROM_PC 2
typedef unsigned char uchar;
//struct
//{
//static uchar uchStartFlag;
static uchar uchOurAddrHigh='0',uchOurAddrLow='1'; //Local Address
static uchar uchNetAddrHigh,uchNetAddrLow;
static uchar uchCmd;
static uchar uchCmdHigh,uchCmdLow;
static uchar uchDataLen;
static uchar uchDataLenHigh, uchDataLenLow;
static uchar auchDataBuf[222];
static uchar uchCalcLrcCode;
static uchar uchCalcLrcHigh, uchCalcLrcLow;
static uchar uchRxLrcCode;
static uchar uchRxLrcHigh, uchRxLrcLow;
//static uchar uchEndFlag1,uchEndFlag2;
static uchar uchStatus=0;
//} Modbus_Protocol;
/************************** Functions Declare **************************/
uchar Ascii_To_Num(uchar a);
uchar Num_To_Ascii(uchar n);
void Float16_To_String(float f, uchar *str);
void Send_Char(int fdcom, uchar ch);
void Receive_Char(int fdcom, uchar *ch);
void Modbus_Send_Packet(int fdcom);
uchar Modbus_Receive_Decode(int fdcom);
void Report_Rx_Result(uchar result);
void Reply(int fdcom, uchar rx_result);
static void Help_Menu(void);
static int Get_Baudrate(char** argv);
/**********************************************************************
* 函数名称 : main
* 函数功能 : 程序主入口函数
* 入口参数 : 无
* 出口参数 : 无
**********************************************************************/
int main(int argc, char **argv)
{
int fd485, fdcom1, fdcom2;
struct termios oldtio1,newtio1,oldstdtio1,newstdtio1;
struct termios oldtio2,newtio2,oldstdtio2,newstdtio2;
int baud;
/
uchar rx_result;
/
if((argc > 3 ) ||(argc == 1)){
Help_Menu();
exit(0);
}
fd485 = open(DEV485,O_RDWR);
if(fd485 < 0)
{
printf("####s3c2410 485 device open fail####\n");
return (-1);
}
fdcom1 = open(COM1, O_RDWR );//| O_NOCTTY |O_NONBLOCK);
if (fdcom1 <0)
{
perror(COM1);
exit(-1);
}
fdcom2 = open(COM2, O_RDWR );
if (fdcom2 <0)
{
perror(COM2);
exit(-1);
}
if((baud=Get_Baudrate(argv)) == -1) {
printf("####s3c2410 RS device baudrate set failed####\n");
}
/*RS232 Init*/
tcgetattr(0,&oldstdtio1);//获取终端属性
tcgetattr(fdcom1,&oldtio1); /* save current modem settings */
tcgetattr(fdcom1,&newstdtio1); /* get working stdtio */
newtio1.c_cflag = baud | CRTSCTS | CS8 | CLOCAL | CREAD;/*ctrol flag*/
newtio1.c_iflag = IGNPAR; /*input flag*/
newtio1.c_oflag = 0; /*output flag*/
newtio1.c_lflag = 0;
newtio1.c_cc[VMIN]=1;
newtio1.c_cc[VTIME]=0;
tcflush(fdcom1, TCIFLUSH);//刷清未决输入和/或输出
tcsetattr(fdcom1,TCSANOW,&newtio1);//设置终端属性
/*RS485 Init*/
tcgetattr(0,&oldstdtio2);//获取终端属性
tcgetattr(fdcom2,&oldtio2); /* save current modem settings */
tcgetattr(fdcom2,&newstdtio2); /* get working stdtio */
newtio2.c_cflag = baud | CRTSCTS | CS8 | CLOCAL | CREAD;/*ctrol flag*/
newtio2.c_iflag = IGNPAR; /*input flag*/
newtio2.c_oflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*output flag*/
newtio2.c_lflag &= ~OPOST;//原始数据输出
newtio2.c_cc[VMIN]=1;//最少读取的字符数
newtio2.c_cc[VTIME]=0;//设置读超时:read操作将会阻塞不确定的时间
tcflush(fdcom2, TCIFLUSH);//刷清未决输入和/或输出
tcsetattr(fdcom2,TCSANOW,&newtio2);//设置终端属性
/
#ifdef MASTER
// while(1)
{
printf("\n/******************** PC--------->MASTER *********************/\n");
//-------------------------------------------------------------1. MASTER_WAITING_RECEIVE_FROM_PC; 232
uchStatus = MASTER_WAITING_RECEIVE_FROM_PC;
printf("\n1.MASTER_WAITING_RECEIVE_FROM_PC\n");
tcflush(fdcom1, TCIFLUSH);//Flush Memery Buf
rx_result = Modbus_Receive_Decode(fdcom1);
Report_Rx_Result(rx_result);
/* //Imitate PC send to master:
uchStatus = MASTER_WAITING_RECEIVE_FROM_PC;
printf("\n1.MASTER_WAITING_RECEIVE_FROM_PC\n");
uchNetAddrHigh = '0';//Set target net address: 02
uchNetAddrLow = '2';
uchCmdHigh = '6';//Set cmd 65:Get AD1 Value
uchCmdLow = '5';
strcpy(auchDataBuf,argv[2]); //Set Data
uchDataLen = strlen(auchDataBuf);//Set data length
uchDataLenHigh = Num_To_Ascii(uchDataLen>>4);
uchDataLenLow = Num_To_Ascii(uchDataLen&0x0F);
*/
uchStatus = NOT_MASTER_WAITING_RECEIVE_FROM_PC; //clear wait PC flag
/// if PC target net address is MASTER self or packet is invalid ///
if( (uchNetAddrHigh == uchOurAddrHigh) && (uchNetAddrLow == uchOurAddrLow) || (rx_result != VALID_PACKET) )//
{
//-------------------------------------------------------------2. MASTER_REPLY_TO_PC 232
printf("\n2.MASTER_REPLY_TO_PC\n");
Reply(fdcom1, rx_result);
}
/// PC target net address is not MASTER ///
else // if( rx_result == VALID_PACKET )
{
printf("\n/******************** MASTER<--------->OTHERS *********************/\n ");
//-------------------------------------------------------------2.MASTER_SEND_TO_OTHERS 485
printf("\n2.MASTER_SENDING_TO_OTHERS\n");
ioctl(fd485, _485_IOCTRL_RE2DE, _485_DE );//Set 485 mode: send
Modbus_Send_Packet(fdcom2);
//-------------------------------------------------------------3.MASTER_WAITING_REPLY_FROM_OTHERS 485
printf("\n3.MASTER_WAITING_REPLY_FROM_OTHERS\n");
ioctl(fd485, _485_IOCTRL_RE2DE, _485_RE );//Set 485 mode: receive
tcflush(fdcom2, TCIFLUSH);//Flush Memery Buf
rx_result = Modbus_Receive_Decode(fdcom2);
Report_Rx_Result(rx_result); //Report rx_result
//-------------------------------------------------------------4.MASTER_REPLY_TO_PC; 485
if( (rx_result != NOT_SEND_TO_US) ) //
{
printf("\n/******************** MASTER--------->PC *********************/\n");
printf("\n4.MASTER_REPLY_TO_PC\n");
Reply(fdcom1, rx_result);// MASTER reply to PC
}
}
}//END_while(1)
/***************************************************************************************************/
#else
//1.OTHERS_WAITING_RECEIVE_FROM_MASTER
while(1)
{
printf("\n1.OTHERS_WAITING_RECEIVE_FROM_MASTER\n");
ioctl(fd485, _485_IOCTRL_RE2DE, _485_RE );//Set 485 mode: receive
tcflush(fdcom2, TCIFLUSH);//Flush Memery Buf
rx_result = Modbus_Receive_Decode(fdcom2);
Report_Rx_Result(rx_result); //Report rx_result
if( rx_result != NOT_SEND_TO_US )
{
//2.OTHERS_REPLY_TO_MASTER
printf("\n2.OTHERS_REPLY_TO_MASTER\n");
ioctl(fd485, _485_IOCTRL_RE2DE, _485_DE );//Set 485 mode: send
Reply(fdcom2, rx_result);// Reply to MASTER
}
}
#endif
/
close(fdcom1);
close(fdcom2);
close(fd485);
return 0;
}
/*****************************************************
* 函数名称 : Ascii_To_Num
* 函数功能 : 将字符ASCII码值转换成实际数值
* 入口参数 : a为ASCII码
* 出口参数 : n为实际数值
*****************************************************/
uchar Ascii_To_Num(uchar a)
{
uchar n;
if(a>='0'&&a<='9')
n=a-0x30;
else if (a>='A'&&a<='Z')
n=a-0x41+10;
else if (a>='a'&&a<='z')
n=a-0x61+10;
return n;
}
/*****************************************************
* 函数名称 : Num_To_Ascii
* 函数功能 : 将实际数值转换成字符ASCII码值
* 入口参数 : n为实际数值
* 出口参数 : a为ASCII码
*****************************************************/
uchar Num_To_Ascii(uchar n)
{
uchar a;
if( n>=0 && n<=9 )
a=n+0x30;
else if ( n>=10 && n<=15 )
a=n-10+0x41;
return a;
}
/*****************************************************
* 函数名称 : Float16_To_String
* 函数功能 : 把(1位整数,6位小数的)浮点数转换成一个字符串(不含小数点)
* 入口参数 : 浮点数f, 转换后的字符串存到str[]
* 出口参数 : 无
*****************************************************/
void Float16_To_String(float f, uchar *str)
{ // 1-interger, 6-small-num like 2.123456 --> "2123456"
uchar i;
long l;
l = f * 1000000;
str[7]='\0';
for(i=6; i>=0; i--)
{
str[i] = l % 10 + 0x30;
l /= 10;
}
}
/*****************************************************
* 函数名称 : Send_Char
* 函数功能 : 发送单个ASCII字符到目标串口(并显示发送的字符)
* 入口参数 : 目标串口fdcom, 要发送的ASCII字符ch
* 出口参数 : 无
*****************************************************/
void Send_Char(int fdcom, uchar ch)
{
printf("%c", ch);
fflush(stdout);
write(fdcom,&ch,1);
usleep(1000);//8bit/115200=70us; continue send must >70us.
}
/*****************************************************
* 函数名称 : Receive_Char
* 函数功能 : 从目标串口接收单个ASCII字符(并显示接收的字符)
* 入口参数 : 目标串口fdcom, 接收到的ASCII字符存到*ch
* 出口参数 : 无
*****************************************************/
void Receive_Char(int fdcom, uchar *ch)
{
read(fdcom,ch,1);
printf("%c", *ch);
fflush(stdout);
}
/*****************************************************
* 函数名称 : Modbus_Send_Packet
* 函数功能 : 将MODBUS协议包逐个字符发送到目标串口(同时穿插进行LRC校验码的计算)
* 入口参数 : 目标串口fdcom
* 出口参数 : 无
* LRC校验算法:把每一个需要传输的[ASCII码值转化为实际数值]叠加(丢弃进位)后取反加1.
*****************************************************/
void Modbus_Send_Packet(int fdcom)
{
uchar i;
uchDataLen = (Ascii_To_Num(uchDataLenHigh)<<4) + Ascii_To_Num(uchDataLenLow) ; // combine Cmd
for( i = 0; i < 3; i++ ) // avoid the dirty char of begin
Send_Char(fdcom, 0x00);
Send_Char(fdcom, START_FLAG); // Send START_FLAG
uchCalcLrcCode = Ascii_To_Num(uchNetAddrHigh);
Send_Char(fdcom, uchNetAddrHigh); // Send NetAddrHigh
uchCalcLrcCode += Ascii_To_Num(uchNetAddrLow);
Send_Char(fdcom, uchNetAddrLow); // Send NetAddrLow
uchCalcLrcCode += Ascii_To_Num(uchCmdHigh);
Send_Char(fdcom, uchCmdHigh); // Send CmdHigh
uchCalcLrcCode += Ascii_To_Num(uchCmdLow);
Send_Char(fdcom, uchCmdLow); // Send CmdLow
uchCalcLrcCode += uchDataLen>>4;
Send_Char(fdcom, uchDataLenHigh); // Send DataLenHigh
uchCalcLrcCode += uchDataLen&0x0f;
Send_Char(fdcom, uchDataLenLow); // Send DataLenLow
for ( i = 0; i < uchDataLen; i++ )
{
uchCalcLrcCode += Ascii_To_Num(auchDataBuf[i]);
Send_Char( fdcom, auchDataBuf[i] ); // Send Data[]
}
uchCalcLrcCode = (~uchCalcLrcCode)+1; // Finished CalcLrcCode generation
uchCalcLrcHigh = Num_To_Ascii(uchCalcLrcCode>>4);
Send_Char(fdcom, uchCalcLrcHigh); // Send CalcLrcHigh
uchCalcLrcLow = Num_To_Ascii(uchCalcLrcCode&0x0f);
Send_Char(fdcom, uchCalcLrcLow); // Send CalcLrcLow
Send_Char(fdcom, END_FLAG_1); // Send EndFlag1
Send_Char(fdcom, END_FLAG_2); // Send EndFlag2
}
/*****************************************************
* 函数名称 : Modbus_Receive_Decode
* 函数功能 : 从目标串口逐个接收MODBUS协议包字符(同时穿插进行LRC校验码的计算)
* 入口参数 : 目标串口fdcom
* 出口参数 : 返回接收结果(有效包或者错误代号等)
* LRC校验算法:把每一个需要传输的[ASCII码值转化为实际数值]叠加(丢弃进位)后取反加1.
*****************************************************/
uchar Modbus_Receive_Decode(int fdcom)
{
uchar i,uch='\0';
while( uch != START_FLAG ) Receive_Char(fdcom,&uch);//detect Start Flag
Receive_Char(fdcom,&uchNetAddrHigh); // receive NetAddrHigh
if( (uchNetAddrHigh != uchOurAddrHigh) && (uchStatus != MASTER_WAITING_RECEIVE_FROM_PC) )
return (NOT_SEND_TO_US);
uchCalcLrcCode = Ascii_To_Num(uchNetAddrHigh);
Receive_Char(fdcom,&uchNetAddrLow); // receive NetAddrLow
if( (uchNetAddrLow != uchOurAddrLow) && (uchStatus != MASTER_WAITING_RECEIVE_FROM_PC) )
return (NOT_SEND_TO_US);
uchCalcLrcCode += Ascii_To_Num(uchNetAddrLow);
Receive_Char(fdcom,&uchCmdHigh); // receive CmdHigh
uchCalcLrcCode += Ascii_To_Num(uchCmdHigh);
Receive_Char(fdcom,&uchCmdLow); // receive CmdLow
uchCalcLrcCode += Ascii_To_Num(uchCmdLow);
uchCmd = (Ascii_To_Num(uchCmdHigh)<<4) + Ascii_To_Num(uchCmdLow); // combine Cmd
Receive_Char(fdcom,&uchDataLenHigh);// receive DataLenHigh
Receive_Char(fdcom,&uchDataLenLow); // receive DataLenLow
uchDataLen = (Ascii_To_Num(uchDataLenHigh)<<4) + Ascii_To_Num(uchDataLenLow); // combine DataLen
if ( uchDataLen > sizeof(auchDataBuf) )
return (ERROR_LENGTH); // Length overflow
uchCalcLrcCode += uchDataLen >> 4;
uchCalcLrcCode += uchDataLen & 0x0f;
for ( i = 0; i < uchDataLen; i++ )
{
Receive_Char(fdcom,&auchDataBuf[i]); // receive Data[]
uchCalcLrcCode += Ascii_To_Num(auchDataBuf[i]);
}
uchCalcLrcCode = (~uchCalcLrcCode)+1; // Finished RxLrcCode generation
uchCalcLrcHigh = Num_To_Ascii(uchCalcLrcCode>>4);
uchCalcLrcLow = Num_To_Ascii(uchCalcLrcCode&0x0f);
Receive_Char(fdcom,&uchRxLrcHigh);
Receive_Char(fdcom,&uchRxLrcLow);
uchRxLrcCode = (Ascii_To_Num(uchRxLrcHigh)<<4) + Ascii_To_Num(uchRxLrcLow); // combine uchRxLrcCode
if( uchRxLrcCode != uchCalcLrcCode)
return (ERROR_LRC); // Lrc can't match
Receive_Char(fdcom,&uch);//detect EndFlag1
if ( uch != END_FLAG_1 )
return(ERROR_END_FLAG);
Receive_Char(fdcom,&uch);//detect EndFlag2
if ( uch != END_FLAG_2 )
return(ERROR_END_FLAG);
return (VALID_PACKET); // Receive a valid packet
}
/*****************************************************
* 函数名称 : Report_Rx_Result
* 函数功能 : 报告显示接收结果以跟踪测试
* 入口参数 : 接收结果rx_result(有效包或者错误代号等)
* 出口参数 : 无
*****************************************************/
void Report_Rx_Result(uchar rx_result)
{
switch(rx_result)
{
// case NOT_SEND_TO_US : printf("NOT_SEND_TO_US!\n"); break;
case ERROR_LENGTH : printf("ERROR_LENGTH!\n"); break;
case ERROR_LRC : printf("ERROR_LRC!\n"); break;
case ERROR_END_FLAG : printf("ERROR_END_FLAG!\n"); break;
case VALID_PACKET : printf("VALID_PACKET!\n"); break;
default : printf("UNKNOWN ERROR!\n"); break;
}
fflush(stdout);
}
/*****************************************************
* 函数名称 : Reply
* 函数功能 : 1.修改目标地址为01;
2.若接收到有效包则根据命令执行相应功能并发送原包到目标串口作为应答;
若为接收到无效包,则修改包内容后再发送到目标串口作为应答(令uchCmdHigh_7=1,并将长度为1的错误代号作为数据)。
* 入口参数 : 目标串口fdcom, 接收结果rx_result(有效包或者错误代号等)
* 出口参数 : 无
*****************************************************/
void Reply(int fdcom, uchar rx_result)
{
uchNetAddrHigh = '0';//reply to MASTER adress: 01
uchNetAddrLow = '1';
if( rx_result != VALID_PACKET )
{
uchCmdHigh |= 0x80; //Receive failed ,change uchCmdHigh_7=1;
uchDataLenHigh = '0'; //Error number length: one uchar
uchDataLenLow = '1';
auchDataBuf[0] = Num_To_Ascii(rx_result);//Error number
}
else//if( rx_result == VALID_PACKET ) //exec Cmd
{
// Exec_Cmd(uchCmd);//Control motor or get AD value and repacket
}
Modbus_Send_Packet(fdcom);// Reply to MASTER 485
}
/*****************************************************
* 函数名称 : Help_Menu
* 函数功能 : 帮助菜单
* 入口参数 : 无
* 出口参数 : 无
*****************************************************/
static void Help_Menu(void)
{
printf("\n");
printf("DESCRIPTION\n");
printf(" Data Collect System \n");
printf(" arg0: tzdcs \n");
printf(" arg1: baudrate \n");
printf(" arg2: Send String \n");
printf("OPTIONS\n");
printf(" -h or --help: this menu\n");
printf("\n");
}
/*****************************************************
* 函数名称 : Get_Baudrate
* 函数功能 : 从主函数的参数获取设置的波特率
* 入口参数 : 主函数的参数char** argv
* 出口参数 : 设置的波特率 或 -1
*****************************************************/
static int Get_Baudrate(char** argv)
{
int v=atoi(argv[1]);
switch(v)
{
case 4800 : return B4800;
case 9600 : return B9600;
case 19200 : return B19200;
case 38400 : return B38400;
case 57600 : return B57600;
case 115200 : return B115200;
default : return -1;
}
}