資源使用說明: 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;
}
}
