1. The basic principles of serial communication:
Generally, there are two ways for computers to communicate with external devices:
Parallel transmission: The transmission amount is 8 bits (1 byte) at a time, through a parallel port, such as a printer
Serial transmission: only one bit is transmitted at a time, through a serial port, such as RS-232
The concept of bits and bytes:
Each bit of 0 and 1 in binary is called a bit, and every 8 bits constitute a byte.
The rightmost bit in a byte is called bit 0 and the leftmost bit is called bit 7.
Types of bytes during transmission: There are generally two types.
1. When text (characters, letters, punctuation marks, etc.) is stored in a computer, each different character is represented by a different numerical value. These numerical values usually range from 0-127 or 0-255.
7 bits: ASCII code, one spare bit for each byte
8 bits: The first 128 bits follow the ASCII code rules, and the remaining 128 bits are used to encode extended characters, numeric symbols, graphic characters, etc.
2. Binary data:
Some executable instruction files and graphic image files are stored in binary form rather than ASCII code.
A data can be stored in binary form and can occupy multiple bytes. In the field of communications, this type of data is often called binary data.
What I want to talk about today is how to process binary data.
The text processing method is relatively simple. I have written a test software before and published it on the box. You can download it from this address: http://www.delphibox.com/article.asp?articleid=2877
Several concepts:
Baud rate: The maximum voltage state change rate that can be generated per second (the number of oscillations in one second) bps
The two communicating parties must achieve the same communication speed. After the original signal is sampled at different baud rates, the results obtained are completely different. For example, when the sampling speed is only half of the original, the signal is sampled in a skipped manner, resulting in data errors.
Data bit: 5, 6, 7, 8
Stop bit: The stop bit sent or received after the parity bit (select with parity) or data bit (select without parity). The length of the stop bit can be selected from 1, 1.5 or 2 bits).
Parity Bit: Data transmission is followed by the optional parity bit to be sent and received. The status of the parity bit depends on the selected parity type. If odd parity is selected, the number of bits that are 1 in the character data is added to the parity bit and the result should be an odd number. The options are odd, even or none.
To ensure smooth communication, the above 4 settings of both parties must be consistent.
A byte is 8 bits, the data bits can be 7 bits, and then a check bit makes 8 bits.
These parameters can be set by yourself. However, if you want to ensure smooth communication, the above 4 settings of both parties must be consistent.
2. Common controls commonly used for serial communication in Delphi
For serial communication, you can use Windows API functions:
The Win32 API has been declared in Delphi's Windows.pas unit file, so when using the API in Delphi, just add Windows to the uses section to make it reference the unit file.
Serial communication related functions:
CreateFile: Create a file, here use to open the communication port
CloseHandle: Closes the file created by CreateFile, and is used here to close the communication port
GetCommState: Get the setting parameters of the computer serial port
SetCommState: Set the parameters of the computer serial port
WriteFile: Write data to a file, here used to send data out of the serial port
ReadFile: Read data from a file, here used to get the data sent to the serial port
ClearCommError: Clear serial port errors and get information
PurgeComm: Clear the buffer on the serial port
EscapeCommFunction: Control the hardware status of the serial port
SetCommMask: Set the event mask to trigger the event
WaitCommEvent: Wait for the occurrence of the setting event
GetCommModemStatus: Get the hardware line status on the serial port
The use of Windows API functions is not recommended here.
Although API functions can achieve very powerful and flexible functions, it is bound to spend more time and energy on communication details. Delphi is a classic representative of RAD, and of course there is a simpler way, which is to use packaged controls.
The commonly used controls are spcomm, mscomm, comport, apro, etc. Among them, mscomm is an ActiveX control, and the other three controls are Delphi controls with their own source code, which can be downloaded from websites such as Delphi Box, Delphi Garden, and SourceForge. The specific usage is not introduced in detail here.
3. The concept of data frame
Today we are mainly talking about binary data processing, so let's first introduce the concept of data frame.
If we want to communicate data, then both parties must follow a certain protocol so that they can understand each other's data.
Frame is the basic unit for transmitting information. Each frame consists of six fields: frame start mark field, control field, data length field, data field, frame information longitudinal check field and frame end field. Each field consists of several bytes.
For example, there is such a frame format:
Code Bytes Description
68H 1 Frame start character
RTUA 4 Terminal Logical Address
MSTA 2 master station address and command number
68H 1 Frame start character
C 1 Control code
L 2 Data length
DATA variable length data field
CS 1 Check code
16H 1 End code
From this data frame format, we can see that a data frame has at least 13 bytes. And there are rules for the front, back, and middle. In this way, we can judge the meaning of this data frame by processing and analyzing some of the bytes, and then carry out other related work.
Of course, different systems have different data frame formats. We will use this format as an example today.
4. Receiving and sending data
Today I will only introduce how to receive and send data using the Comport control.
Receive data: in the OnRxChar event.
Prototype of onRxChar:procedur TForm1.ComPortRxChar(Sender: TObject; Count: Integer)
This event is triggered when there is data in the receive buffer, and count is the number of bytes in the buffer.
Send data: This control provides 6 functions for sending data:
WaitForAsync
WaitForEvent
Write
WriteAsync
WriteStr
WriteStrAsync
The more commonly used one is WriteStr.
function WriteStr(const Str: String): Integer;
The parameter is a string type and returns the number of bytes actually sent.
Apro is very powerful, and the company that developed this control group has gone bankrupt and contributed all the source code.
Except for mscomm, the other three have source code.
Why use WriteStr? Here we need to explain the string type.
Delphi's String type is very powerful and is compatible with PChar, Array of Char, WideString and other string or character array types. Another key function is that it can be used as a dynamic Byte array.
Although this usage is not introduced in many reference books, after many tests and practical experience, I found that there were no adverse reactions.
For example, if you want to send the characters $68 80 50 60 20 30 10 00 00 20, you can define a string A: string; and then use the following code:
setlength(A,10);
A[1]:=Chr(68);
A[2]:=Chr(80);
. . . .
A[9]:=Chr(00)
A[10]:=Chr(20)
writestr(A);
Of course it looks cumbersome, but this is not the advantage of String. Its real advantage is
1. No need to manage memory, leave it to Delphi.
2. It can be easily processed as a parameter or variable. More on this later.
5. Data processing
The previous 4 sections are all nonsense. The focus is on this section.
If data is continuously sent from the terminal to the server, for example, a frame of data is sent every 10 milliseconds, how can we distinguish this data?
You should know that serial communication is transmitted bit by bit, and received byte by byte, not frame by frame. In order to determine whether a byte is the data of the previous frame or the next frame, we can only determine it byte by byte.
Let's look at a piece of code first, and then I'll explain this code. In this way, we can clearly get the data of each frame. After that, I'll explain how to use Delphi's object-oriented features to process the received frame data.
The frame format uses the one just introduced.
First define a few global variables:
FDataStatus:Word; //0 Ready 1. Frame header 1 2. Frame header 2
FNextLength:Word; //The length to be read next
FCurrentLength:Word; //Current length
FtmpStr: string; // one frame of data
The code is as follows:
procedure ComPortRxChar(Sender: TObject; Count: Integer);
var
S1: string;
J:Integer;
begin
case FDataStatus of
0:begin
J:=0;
FtmpStr:='';
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
end;
1: begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
end;
2: begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
end;
end;
end;
end;
case FDataStatus of
Determine the data segment currently received.
0:begin
J:=0;
FtmpStr:='';
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
end;
If it is the beginning of a frame, then the relevant parameters are initialized and the data frame is initialized to empty.
J:=0;
FtmpStr:='';
Then start reading until you see the frame start character $68 or finish reading. If not, discard the data you read (of course you can also write it down separately, but it doesn't make much sense).
repeat
ComPort.ReadStr(S1,1);
J:=J+1;
until (Ord(S1[1])=$68) or (J=Count);
J is the data read, and J=count means that the buffer has been read.
Ord(S1[1])=$68, S1 is a string, and ORD(S1[1]) represents the first byte of S1.
ComPort.ReadStr(S1,1); reads only one byte at a time, so there is only 1 byte in S1.
If $68 is in the second byte you read, then isn't that a sham?
If $68 is read, the read state is set to the second stage, the frame data is added with $68, the length of the next stage is 10, and the number of bytes read is 0. Then the function ends.
if Ord(S1[1])=$68 then
begin
FtmpStr:=S1;
FDataStatus:=1;
FNextLength:=10;
FCurrentLength:=0;
end;
If there is still data in the buffer, it will trigger the OnRxChar event again.
As long as there is data in the buffer and it has not been processed or is not being processed, the OnRxChar event will continue to be triggered.
The read state has just been set to the second stage, so the second stage code will now be executed:
Case statement to judge.
1: begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
end;
J:=FCurrentLength; //Counter, record the total number of reads.
RTUA 4 Terminal Logical Address
MSTA 2 master station address and command number
68H 1 Frame start character
C 1 Control code
L 2 Data length
These are the 10 bytes we will read in the second stage.
The second stage of processing is to read 10 bytes, no matter how many bytes are in the buffer and no matter how many times they are read, until 10 bytes are read.
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
FNextLength=10 is set in the first stage.
After reading 10 bytes, the third stage will begin.
if FCurrentLength=FNextLength then
begin
FDataStatus:=2;
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
FCurrentLength:=0;
end;
How many books should I read in the next stage?
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
DATA variable length data field
CS 1 Check code
16H 1 End code
This is the number of bytes to be read in the final stage.
But DATA is variable length, so we divide it into one more stage. If it is fixed length, we only need to determine the frame start character and then read the required number of bytes.
Fortunately, there is
L 2 Data length
Indicates the length of DATA.
So there is this sentence.
FNextLength:=(Ord(FtmpStr[11]) shl 8) + Ord(FTmpStr[10])+2;
+2 is the last two bytes.
Next comes the third stage:
2: begin
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
end;
end;
end;
Foshan-sky A (11116580) 20:55:21
The reading method in the third stage is the same as the previous one, which is to read one and count one until the required bytes are read.
J:=FCurrentLength;
repeat
ComPort.ReadStr(S1,1);
FtmpStr:=FtmpStr+S1;
FCurrentLength:=FCurrentLength+1;
until (FCurrentLength=FNextLength) or (FCurrentLength-J=Count);
Initialize after reading:
if FCurrentLength=FNextLength then
begin
FDataStatus:=0;
FNextLength:=0;
FCurrentLength:=0;
FReceiveFrame.Str:=FtmpStr;
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
Finally, we get a complete frame of data FtmpStr, and then send this data to the function that processes the data.
SendMessage(CommServer.Handle,XM_OutData,0,LongWord(@FReceiveFrame));
The relevant function will handle it.
In this way, no matter what comes or when, a complete frame of data is in the string FtmpStr.
Of course, the specific reading method depends on the format of the data frame, but the essence remains the same: comparing the read bytes with the given format.
With this knowledge, you can basically cope with writing serial communication programs.
Considering the object-oriented nature of Delphi, it would be a pity not to use classes to standardize the format of the data frame.
Let's take the frame data format as an example:
We can define the base class like this.
TCustomGY = class(TObject)
//0 Base class of specification
private
FFrameBegin:Byte; //Frame start character
FTerminalLogicAddr:T4Byte; //Terminal logical address
FMasterStation:T2Byte; //Master station address and command number
FFrameBegin2:Byte; //Frame start character
FFrameControl:Byte; //Control code
FDataLength:Word; //Data length
FFrameVerify:Byte; //Verification code
FFrameEnd:Byte; //End code
FPosSend:TFramePosTerminal; //Frame initiator
FPosReceive:TFramePosTerminal; //Frame receiving end
procedure SetDataLength(const Value: Word);
procedure SetFrameBegin(const Value: Byte);
procedure SetFrameBegin2(const Value: Byte);
procedure SetFrameControl(const Value: Byte);
procedure SetFrameEnd(const Value: Byte);
procedure SetFrameVerify(const Value: Byte);
function ReadFrameDataLengthHi: Byte;
function ReadFrameDataLengthLo: Byte;
function ReadFrameFSEQ: Byte;
function ReadFrameISEQ: Byte;
function ReadFrameMSTA: Byte;
function ReadFrameMSTA2: Byte;
procedure SetPosReceive(const Value: TFramePosTerminal);
procedure SetPosSend(const Value: TFramePosTerminal);
procedure SetMS1(const Value: Byte);
procedure SetMS2(const Value: Byte);
procedure SetFrameFSEQ(const Value: Byte);
procedure SetFrameISEQ(const Value: Byte);
procedure SetFrameMSTA(const Value: Byte);
procedure SetFrameMSTA2(const Value: Byte);
function GetTerminalAddr: Word;
function GetTerminalLogicAddr(const AIndex: T4Integer): Byte;
procedure SetTerminalAddr(const Value: Word);
procedure SetTerminalLogicAddr(const AIndex: T4Integer;
const Value: Byte);
function GetMasterStation(const AIndex: T2Integer): Byte;
procedure SetMasterStation(const AIndex: T2Integer; const Value: Byte);
function GetTerminalLogicAddrStr: string;
procedure SetTerminalLogicAddrStr(const Value: string);
function GetMasterstationStr: string;
procedure SetMasterstationStr(const Value: string);
protected
procedure SetData(const Value: string); virtual; abstract; //Set the data area virtual function, the implementation method is in the subclass
function ReadData:string; virtual; abstract; //ReadData virtual function, overridden by subclasses.
function ToVerifyFrame: Boolean; virtual; //Is the frame correct?
function ReadWholeFrame: string; virtual;
procedure setWholeFrame(const Value: string); virtual;
public
property FrameBegin:Byte read FFrameBegin write SetFrameBegin; //frame start character
property RTUA[const AIndex:T4Integer]:Byte
read GetTerminalLogicAddr write SetTerminalLogicAddr; //Terminal logical address
property RTUAStr:string read GetTerminalLogicAddrStr write SetTerminalLogicAddrStr;
property TerminalAddr:Word read GetTerminalAddr write SetTerminalAddr; //Terminal address
property MSTAs[const AIndex:T2Integer]:Byte read GetMasterStation write SetMasterStation; //Master station address and command number
property MSTAStr:string read GetMasterstationStr write SetMasterstationStr;
property FrameBegin2:Byte read FFrameBegin2 write SetFrameBegin2; //The second frame start character
property FrameControl:Byte read FFrameControl write SetFrameControl; //Control code
property DataLength:Word read FDataLength write SetDataLength; //Data length, 2 bits, low bit first, high bit last
property Data: string read ReadData write SetData; //data, virtual property, implemented in subclass
property CS:Byte read FFrameVerify write SetFrameVerify; //Frame check bit
property FrameEnd:Byte read FFrameEnd write SetFrameEnd; //frame end code
property FrameIsRight:Boolean read ToVerifyFrame; //Check if the frame is correct
property WholeFrame:string read ReadWholeFrame write setWholeFrame; //whole frame information, virtual function, subclass implementation
property MS1:Byte read FMasterStation[0] write SetMS1; //MS1
property MS2:Byte read FMasterStation[1] write SetMS2; //MS2
property MSTA:Byte read ReadFrameMSTA write SetFrameMSTA; //MSTA
property MSTA2:Byte read ReadFrameMSTA2 write SetFrameMSTA2; //MSTA2, valid when both the transmitter and receiver are master stations
property ISEQ:Byte read ReadFrameISEQ write SetFrameISEQ; //ISEQ
property FSEQ:Byte read ReadFrameFSEQ write SetFrameFSEQ; //FSEQ
property DataLengthLo:Byte read ReadFrameDataLengthLo; //data length low bit
property DataLengthHi:Byte read ReadFrameDataLengthHi; //data length high bit
property PosSend:TFramePosTerminal read FPosSend write SetPosSend; //frame sender
property PosReceive:TFramePosTerminal read FPosReceive write SetPosReceive; //Frame receiving end
class function VerifyFrame(S:string): Boolean; //Is the frame correct?
class function GetVerifyByte(S: string):Byte; //Get the verification code
constructor Create; virtual;
destructor Destory; virtual;
function DecodeFrame(const S:string;NoError:Boolean=True):Boolean; //dynamic; //Return True after decomposition operation
function SumVerify:Byte; dynamic; //Calculate the data frame CS code
function SumDataLength:Word; dynamic; //Calculate data length
end;
All judgment and processing are handed over to this class.
Then different commands derive different subclasses.
Leave two interfaces, one for input and one for output.
The other intermediate data can be handled at will.
The implementation code will not be posted.
Here is another simplest application derived from the base class:
TSimplyGY = class(TCustomGY)
private
FData: TarrayByte; //Processed Data, the data that needs to be reversed has been reversed
protected
procedure SetData(const Value: string); override; //Set data area
function ReadData:string; override; //ReadData
public
end;
The subclass implements the virtual functions specified by the base class.
With such a class, you can derive different subclasses for the Data area. All you need to do is process the Data. The format of the Data field is different for different commands.
The processing principle of the Data field is similar to that mentioned above, which is nothing more than byte comparison and judgment.
Let me share a few more functions that I often use in serial communication. Those who are interested can take a look. Here are some for beginners to refer to:
function StrToHexStr(const S:string):string;
//Convert the string into hexadecimal string
var
I:Integer;
begin
for I:=1 to Length(S) do
begin
if I=1 then
Result:=IntToHex(Ord(S[1]),2)
else Result:=Result+' '+IntToHex(Ord(S[I]),2);
end;
end;
function HexStrToStr(const S:string):string;
//Convert hexadecimal string into string
var
t:Integer;
ts:string;
M,Code:Integer;
begin
t:=1;
Result:='';
while t<=Length(S) do
begin
while not (S[t] in ['0'..'9','A'..'F','a'..'f']) do
inc(t);
if (t+1>Length(S))or(not (S[t+1] in ['0'..'9','A'..'F','a'..'f'] )) then
ts:='$'+S[t]
else
ts:='$'+S[t]+S[t+1];
Val(ts,M,Code);
if Code=0 then
Result:=Result+Chr(M);
inc(t,2);
end;
end;
function StrToTenStr(const S:string):string;
//Convert the string into a decimal string
var
I:Integer;
begin
for I:=1 to Length(S) do
begin
if I=1 then
Result:=IntTostr(Ord(S[1]))
else Result:=Result+' '+IntToStr(Ord(S[I]));
end;
end;
function StrToByteArray(const S:string):TarrayByte;
//Convert the string into the corresponding Byte
var
I:Integer;
begin
SetLength(Result,Length(S));
for I:=1 to Length(S) do
Result[I-1]:=Ord(S[I]);
end;
function ByteArrayToStr(const aB:TarrayByte):string; //Byte is converted into the corresponding string
var
I:Integer;
begin
SetLength(Result,High(aB)+1);
for I:=0 to High(aB) do
Result[I+1]:=Chr(aB[I]);
end;
function StrToBcdByteArray(const S:string; const Rotate:Boolean=False):TarrayByte; //Convert string to corresponding BCDByte
var
ts:string;
I:Integer;
begin
if Odd(Length(S)) then
ts:='0'+S
else ts:=S;
SetLength(Result,Length(ts) div 2);
for I:=0 to High(Result) do
begin
if Rotate then
Result[High(Result)-I]:=StrToInt('$'+ts[2*I+1]+ts[2*I+2])
else
Result[I]:=StrToInt('$'+ts[2*I+1]+ts[2*I+2]);
end;
end;
function BcdByteArrayToStr(const aNum:TarrayByte; const Rotate:Boolean=False):string; //Convert BCDByte to corresponding string
var
I:Integer;
t:string;
begin
SetLength(Result,2*(High(aNum)+1));
for I:=0 to High(aNum) do
begin
t:=IntToHex(aNum[I],2);
if Rotate then
begin
Result[2*(High(aNum)-I)+1]:=t[1];
Result[2*(High(aNum)-I)+2]:=t[2];
end
else begin
Result[2*I+1]:=t[1];
Result[2*I+2]:=t[2];
end;
end;
end;
//Convert the collection into an array
function SetToByteArray(s:array of const):TarrayByte;
var
I:Byte;
begin
SetLength(Result,High(s)+1);
for I:=0 to High(s) do
Result[I]:=S[I].VInteger;
end;
function SetToWordArray(s:array of const):TarrayWord;
var
I:Byte;
begin
SetLength(Result,High(s)+1);
for I:=0 to High(s) do
Result[I]:=S[I].VInteger;
end;
function StrToBinStr(const S:string):string;
//Convert the string to a binary string
var
I:Integer;
begin
Result:='';
for I:=1 to Length(S) do
begin
if I=1 then
Result:=BinStrArray[Ord(S[1])]
else Result:=Result+' '+BinStrArray[Ord(S[I])];
end;
end;
function ByteToBinStr(const B:Byte):string;
// Convert Byte type to binary string
var
I:Integer;
begin
Result:='';
for I:=7 downto 0 do
Result:=Result+Chr(((B shr I) and 1)+48);
end;
function BinStrToByte(const S:string):Byte;
//Convert binary string to number
var
S1: string[8];
L:Integer;
I:Integer;
begin
S1:='00000000';
L:=min(8,Length(S));
for I:=L downto 1 do
if S[I]='1' then
S1[8+IL]:=S[I]
else
S1[8+IL]:='0';
Result:=0;
for I:=0 to 7 do
Result:=Result+(((Ord(S1[I+1])-48) shl (7-I)));
end;
function T2ByteToWord(const T:T2Byte;const LowFront:Boolean=False):Word;
//Convert T2Byte type to Word type
var
a1,a2:Byte;
begin
if LowFront then
begin
a1:=T[1];
a2:=T[0];
end
else begin
a1:=T[0];
a2:=T[1];
end;
Result:=a1;
Result:=(Result shl 8) + a2;
end;
function WordToT2Byte(const aNum:Word;const LowFront:Boolean=False):T2Byte;//Convert Word type to T2Byte type
var
a1,a2:Byte;
begin
a1:=aNum;
a2:=aNum shr 8;
if LowFront then
begin
Result[0]:=a1;
Result[1]:=a2;
end
else begin
Result[0]:=a2;
Result[1]:=a1;
end;
end;
function ByteToBCDByte(const B:Byte;const aType:TBCDType=bt8421):Byte;
//Byte converted to BCD code
var
B1,B2:Byte;
function T2421(const P:Byte):Byte;
begin
if P<5 then
Result:=P
else Result:=P+6;
end;
begin
if B>99 then
Result:=0
else begin
B1:=B div 10;
B2:=B mod 10;
case aType of
bt8421:begin
Result:=(B1 shl 4)+B2;
end;
bt2421:begin
Result:=(T2421(B1) shl 4)+T2421(B2);
end;
btR3:begin
Result:=((B1+3) shl 4)+(B2+3)
end;
else Result:=(B1 shl 4)+B2;
end;
end;
end;
function BCDByteToByte(const B:Byte;const aType:TBCDType=bt8421):Byte;
//Convert BCD to Byte
var
B1,B2:Byte;
function Ts2421(const P:Byte):Byte;
begin
if P<5 then
Result:=P
else Result:=P-6;
end;
begin
B1:=(B and $F0) shr 4;
B2:=B and $0F;
case aType of
bt8421: begin
Result:=B1*10+B2;
end;
bt2421: begin
Result:=(Ts2421(B1)*10)+Ts2421(B2);
end;
btR3: begin
Result:=((B1-3)*10)+(B2-3)
end;
else Result:=B1*10+B2;
end;
end;
function BCDByteToByteFF(const B:Byte;Auto:Boolean=True):Byte;
//Convert BCD to Byte type FF-->FF 8421 code
var
B1,B2:Byte;
begin
if (B=$FF)or(Auto and (B>=$AA)) then
Result:=B
else begin
B1:=(B and $F0) shr 4;
B2:=B and $0F;
Result:=B1*10+B2;
end;
end;
function ByteToBCDByteFF(const B:Byte;Auto:Boolean=True):Byte;
//Byte converted to BCD code FF-->FF 8421 code
var
B1,B2:Byte;
begin
if (B=$FF)or(Auto and (B>=$AA)) then
Result:=B
else if B>99 then
Result:=0
else begin
B1:=B div 10;
B2:=B mod 10;
Result:=(B1 shl 4)+B2;
end;
end;
6. Conclusion
No matter hard disk space, memory space, CPU cache space, serial port cache space, etc., they are just a carrier. What is filled in them is completely meaningless if it is not interpreted according to certain rules. The communication program is just the application of some simpler rules.