AN #30 - RS-485 Master Slave Communication
485modul.bas - description of implemented subroutines
Download source code in an30.zip
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 µsec (microseconds). If n is 195, then 195 times 256 µsec makes 49920 µsec 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.