RS-485 Master Slave Communication
485modul.bas
– description of implemented subroutines
Download files in .ZIP file
Introduction
485modul.bas
is not a program; it is only a program backbone, which has all subroutines
necessary for communication between micro controllers. And because time
management is also very handy, there is also a timer interrupt subroutine, which
uses TIMER0. TIMER1 is used for timing off the communication staff and should
not be used by programmer.
TIMER0
interrupt subroutine and time management
First, lets
make clear: For communication we do not need time management and any timer
interrupt routine at all. But,
may be, somebody will find it useful and educational when solving a similar
problem
The Interrupt
subroutine should be as short and quick as possible. Therefore some of the time
management code we placed in the main program loop. There is also some initial
work, which has to be done in the initial phase of the main program:
' //////////////////TIMER0
////////////////
Config Timer0 = Timer , Gate = Internal , Mode = 1
On Timer0 Timer0isr
Enable Timer0
Load Timer0 , 195
' load 195 at mode = 1 and crystal = 12 MHz means 50
msec
At Mode = 1
and crystal frequency 12 MHz stands, timer interrupt will occur after n times
256 usec (microseconds). If n is 195, then 195 times 256 usec makes 49920 usec
which is approximately 50 msec (milliseconds). Another fact, which makes time management inaccurate is, we need time to
disable interrupts. So as you see, we are not very exact at time management, but
for most purposes it serves. If we need accurate time management, better use the
devices specially designed for it.
Dim Seconds As Byte
Dim Minutes As Byte
Dim Hours As Byte
Dim Days As Byte
Seconds = 0
Minutes = 0
Hours = 0
Days = 0
Start Timer0
' ////////////////////////////////////////
Enable Interrupts
' Main program loop
Do
If Seconds >= 60 Then
Seconds = Seconds - 60
Incr Minutes
If Minutes >= 60 Then
Minutes = Minutes - 60
Incr Hours
If Hours >= 24 Then
Hours = Hours - 24
Incr Days
End If
End If
End If
...........
Here the main program proceeds his work in loop ......
In the main
program we assume somebody else (well, we know it will be the TIMER0 interrupt
subroutine) will take care to increment byte Seconds each second. In the main
program we only take care to do all the necessary adjustment when seconds
counter reaches 60. How to set initial contents to hours, minutes and days, is
now not our concern. Let us now have a look at the timer0isr timer0 interrupt
subroutine!
'---------timer0 interrupt subroutine
takes control every 50 msec
Timer0isr:
Dim Ticounter As Byte
'every 50 msec:
Load Timer0 , 195
Incr Ticounter
If Ticounter >= 20 Then
' every second
Ticoutner = 0
Incr Seconds
End If
Return
'---------timer0 interrupt subroutine – end ----
If we get
timer an interrupt and if timer interrupt subroutine takes control every 50
msec, we need to count until 20 to reach a second. That is why we have in the
timer interrupt subroutine a special counter of timer interrupts, which is
incremented every time the timer interrupt routine takes control. When this
counter reaches 20, then timer interrupt routine increment counter Seconds. Then
the Timer interrupt routine returns control to main program, which proceeds its
work exactly there, where he was interrupted for a very short time.
RS232/RS485
sending data
This is
supposed to be the most simple communication between micro controllers where we
have one master and some slaves who only respond on a master request. May be, somebody shall contribute the code for SNAP protocol,
well for the very beginning I found it to complicated.
We send
messages with a standard length for the whole system. First byte in the message
is always 02 STX, last byte is always 03 ETX, second byte is slave address for
which the message is meant to, or if slave is sending the message, which slave
is sending the message. When slave is sending the message it is always meant for
the master. The last byte but one is CRC8 check byte.
In the main
program we have the following code:
$baud = 1200
$crystal = 12000000
' Message format if n = 8:
' Stx Adr Cmd P01 P02 P03 Crc Etx
' INDX 1 2 3 . . . . N
Const N = 8 'message length
Const Nminusone = N - 1
Const Nminustwo = N - 2
Dim Xx1(n) As Byte 'input area
Dim Xx2(n) As Byte 'buffer area
Dim Xx3(n) As Byte 'output and process area
Dim X1 As Byte
Dim X3 As Byte
Dim Msg As Bit
Declare Sub Sendsr
Print ;
In the
statement Const N=8 we set the length of all messages in the system to 8. So
there are defined areas XX1, XX2, and XX3 for messages with the same length.
For sending
only XX3 is relevant. The other two areas are needed for reading the messages.
Communication rate is defined in the statements $baud and $crystal. But to
mislead the Basom8051 to use these information to set accurately timer1, which
is responsible for baud rate, we must issue statement Print ; If not, we should
calculate the correct values and set timer1 appropriately. It is easier to use
BASCOM for doing this.
Before we are
in the main program call subroutine SENDIT, we must put the message itself into
the XX3 area. Subroutine SENDIT when called does the following:
Puts value 2
(STX) into XX3(1)
Calculates
CRC8 check byte and puts it into XX3(Nminusone). As work byte while calculating
CRC8 check byte it uses also XX3(n)
Puts value 3
(ETX) into XX3(n)
Then it
disables interrupts.
Then in the
loop sends all bytes to Sbuf. Scon.1 bit will be set by the micro controller
when the byte is sent, and then we may put next byte into Sbuf.
On my testing
board bit P1.1 is used to control MAX485 to switch Send/Receive.
Sub Sendsr
Xx3(1) = 2
Xx3(n) = 0
Xx3(nminusone) = 0
‘calculation of the CRC8 checkbyte
For X3 = 1 To Nminustwo
Xx3(n) = Xx3(nminusone) Xor Xx3(x3)
Xx3(nminusone) = Lookup(xx3(n) , Crc8)
Next ' Check byte is calculated and put
into right place
Xx3(n) = 3 'ETX
Disable Interrupts
Reset Scon.1
Set P1.1 ‘switch MAX485 to send
For X3 = 1 To N
Sbuf = Xx3(x3)
Bitwait Scon.1 , Set
Reset Scon.1
Next
Reset P1.1 ‘switch MAX485 to receive
Enable Interrupts
End Sub
For actual
calculation of CRC8 check byte a CRC8 lookup table is needed at the end of
program. It is big and it is not copied to this document.
RS232/RS485
receiving data
Receiving
data is programmed in a serial interrupt subroutine. While the main program is
working whatever is designed to do, with one eye – serial interrupt subroutine
– is watching what is happening on the communication line. When some byte
appears on input, serial interrupt subroutine is activated to place the received
byte to appropriate place, without to interrupt the logic of main program. When the last byte of message is received and message is checked and
message is meant to us, then serial interrupt subroutine moves the message from
input area XX1 to buffer area XX2 and set the bit named Msg to signal the main
program, there is message to be proceed, have a look!
Main program
does not process the message immediately. Not earlier then main program comes in
the processing loop to the place, where the processing of input messages is
provided. This can take some time. Meantime on the line some other bytes are
received. That is why the buffer message area is necessary.
To activate
serial interrupt receiving routine there is little code in main program:
Enable Serial
On Serial Serrut
Reset Msg
X1 = N + 1
' Index X1 to put received bytes to the right place we set
' in the way, that interrupt receiving routine will
synchronize
' on the beginning of message on STX byte
' set max485 on Receive (for RS232 not necessary)
Reset P1.1
New Message
begins with byte 02 = STX. But not every byte 02 is the beginning of message. It
might also be data in the middle of message. Only if index byte X1 is outside
message, it means, the passed message is fully read, then byte 02 is with high
probability the beginning of new message. So
we interpret byte 02 as beginning of new message only when index byte X1 points
outside message that is greater then message length. This is why we set X1 to
N+1 at the very beginning.
When message
of fixed length N is fully received, the byte received last when X1 = N must bi
STX = 03. If so, then we check, if the message is meant for us, that is if the address bytes match our slave address, (if we are master, all
messages from slaves are meant to master) in if so, then the input routine
checks if CRC8 check byte fits. If everything is OK, then we move the message
from input area XX1 to buffer area XX2 in set the bit Msg to inform the main
program.
Serial
interrupt subroutine is like this:
Serrut:
Dim Swrk1 As Byte
Swrk1 = Inkey()
Scon.1 = 0
' Msg Format:
' Stx Adr Cmd P01 P02 P03 Crc Etx
' INDX 1 2 3 . . . . N
If Swrk1 = 2 And X1 > N Then
' If received byte is 02 and index greater then N, we
synchronize
For X1 = 2 To N
Xx1(x1) = 0
Next
X1 = 1
End If
If X1 <= N Then
Xx1(x1) = Swrk1
End If
If X1 = N And Swrk1 = 3 Then
' If it is end of
message
' If Xx1(2) = Slaveadr Then
' if program is master and not slave we comment this test
' Crc8 check:
Swrk1 = 0 'tmp
Xx1(n) = 0 'crc
For X1 = 1 To Nminusone
Swrk1 = Xx1(n) Xor Xx1(x1)
Xx1(n) = Lookup(swrk1 , Crc8)
Next
‘ move message to buffer area if CRC8 OK
If Swrk1 = 0 Then
For X1 = 1 To N
Xx2(x1) = Xx1(x1)
Next
Xx2(n) = 3
Set Msg
End If
' End If
End If
Incr X1
Return
There is no
CRC8 Lookup table to be seen here, it is in attached modul485.bas
Attached
sample programs are:
Modul485.bas
which is not a program, but only skeleton which can be used, when starting
writing a new program, with the serial communication.
485oddaj.bas
is a sample program, which sends each second a message. Something like this:
‘Hi, slave no “O”, my clock shows this time’
and puts the
same info on LCD
485sprej.bas
is a sample slave program, which only reads message from line, and puts info
about master’s clock on LCD
485rxtx.bas
is a sample slave program, who after receiving masters clock info, sends back
slave’s clock info.
485master.bas
is a master sample program, who sends the message the same way the 485oddaj.bas
does, but waits for an answer from slave. Both messages go on LCD.
Vilko Sustic Ljubljana Slovenia |