We need to start looking further afield! Now that we know other devices are controllable, we need to know what control we can implement.... Years ago I had the opportunity to program CNC machinery.... These machines made use of the RS232 connection. Serial communication takes on many guises... SPI, I2C, RS485, CANBUS, ONEWIRE and RS232 to name a couple..
The first to get our heads round is the RS232... One of the simplest ways to connect to a PC.. The basic circuit that was first built will do the job...
Serial communication can be simpler than first thought, the idea behind serial communication stems from morse code.. Dot dot dash dash etc...
Reading a stream of bits coming into a port at a pre-determined rate. This is the format of the RS232 protocol.
Notice the bit sequence :- S = Start bit.. D0~D7 = data bits... P = stop bits... The X is the extension to the stop bits, the stop bits can be 1, 1.5 or 2 bits long..
The next part to understand is the “pre-determined” time... Known as the baud rate, invented by
Emile Baudot, needs to be known at both ends so bit detection can be made.. The micro can spill out the bits and the PC can read these bits and act on the byte that has been received.
Virtually every micro today has a USART or EUSART, Enhanced Universal Asynchronous Receiver Transmitter, module built in... All of the 8051's have at least one...
Once you place your data in the relevant register the micro or PC will send the data byte for you... They also include “flags” to notify you when this is completed so you don't have to sit and wait..
Lets say we want to send some data from the micro to the PC running a terminal program. The terminal programs use several extensions to ASCII to display data received...These extensions are very similar but each have a slightly different command set... But as we are going to be using straight ASCII, we need not worry too much about the protocol transport.
Now the first code we always write is the echo!! Wait for a character on the serial bus and echo it back when it does...
The baud rate is calculated as.. (2^SMOD) / 32 * (11095200 /(12*[256-TH1]))..
so by substitution TH1 = 256 – (11095200/(384 * baud )) = 253 ( 0xFD )..
Ok! Here we see the reason for the 11.0952Mhz crystal...Standard baud rates are near perfect when using crystals that were designed for serial communication...
Here is the C version
Now we have a bit of a chore!! The next examples need maths ( sorry math in USA ).. If we need to start sending reasonable information, its something we have to get our heads round.. Lets assume we have a simple calculation to make... Lets say we need to display a voltage on a LCD display or even send the voltage to a PC for display! The humble 8051 only understands 8 bit math(s). This is a simple map that is used to change a voltage sampled on the ADC to a value that represents the “real” value we need to view...
Digital value representation = ( ADC input / Digital value representation span * ADC maximum resolution ) - Digital value representation minimum..
As you can see there needs to be some calculations around 16 bit values 0 ~ 65535... You will soon find that 16 bit isn't enough, but will suit our needs..
The next couple of examples are only for ASM because in C it's all built in... C comes with extensive math(s) libraries, even 32bit floating point math(s).
The first thing we need is 16 bit addition...
This will cover all our needs for the next few examples... As you will see using math(s) in C is completely automatic.
For now I wanted to send something to the PC... I have used the DS18S20 in the past... 1-wire serial connection..
Using timer 0 for the 1-wire and timer 1 for the serial connection to the PC... Our math routines and a hex2bcd utility... We have a complete application. This code will read the DS18S20, convert to ASCII and send to the PC in a readable format..
This is quite a large application... We shall start to place code in other files and include them when we need them.. This will make it more manageable...
From now on I can make the ASM files smaller... I will let you know what files to include....
Next up will be I2C and SPI... They will also be bit banged as some 8051 derivatives do not have a MSSP module...
The first to get our heads round is the RS232... One of the simplest ways to connect to a PC.. The basic circuit that was first built will do the job...
Serial communication can be simpler than first thought, the idea behind serial communication stems from morse code.. Dot dot dash dash etc...
Reading a stream of bits coming into a port at a pre-determined rate. This is the format of the RS232 protocol.
Notice the bit sequence :- S = Start bit.. D0~D7 = data bits... P = stop bits... The X is the extension to the stop bits, the stop bits can be 1, 1.5 or 2 bits long..
The next part to understand is the “pre-determined” time... Known as the baud rate, invented by
Emile Baudot, needs to be known at both ends so bit detection can be made.. The micro can spill out the bits and the PC can read these bits and act on the byte that has been received.
Virtually every micro today has a USART or EUSART, Enhanced Universal Asynchronous Receiver Transmitter, module built in... All of the 8051's have at least one...
Once you place your data in the relevant register the micro or PC will send the data byte for you... They also include “flags” to notify you when this is completed so you don't have to sit and wait..
Lets say we want to send some data from the micro to the PC running a terminal program. The terminal programs use several extensions to ASCII to display data received...These extensions are very similar but each have a slightly different command set... But as we are going to be using straight ASCII, we need not worry too much about the protocol transport.
Now the first code we always write is the echo!! Wait for a character on the serial bus and echo it back when it does...
The baud rate is calculated as.. (2^SMOD) / 32 * (11095200 /(12*[256-TH1]))..
so by substitution TH1 = 256 – (11095200/(384 * baud )) = 253 ( 0xFD )..
Code:
org 0 ; Reset vector
sjmp Start
org 30H ; Code starts here
Start:
acall SerInit ; Initialize Serial
While:
acall getch ; wait for character
acall putch ; echo back
sjmp While ; Back to loop
;Initialise serial port
;------------------------
SerInit:
mov SCON,#52H ; Mode 1 REN = on, TI = ready
mov PCON,#0H ; SMOD = off normal baud
mov TH1,#0FDH ; FDH = 9600 baud
mov TMOD,#20H ; Timer 1, 8 bit with auto reload
setb TR1
ret
;Get a character
;----------------
getch:
jnb RI,$ ; wait for Receive interrupt flag
clr RI ; Ok char here clear flag
mov A,SBUF ; move into Accumulator
ret ; done
;Send a character
;-----------------
putch:
mov SBUF,A ; Send A to serial buffer
jnb TI,$ ; wait until gone
clr TI ; clear interrupt
ret ; done
END
Here is the C version
C:
#include<8051.h> // definition file
unsigned char getch(void)
{
while(!RI); // Wait here until SBUF clear
RI = 0;
return SBUF;
}
void putch(unsigned char ch)
{
while(!TI); // Wait here until SBUF clear
TI = 0;
SBUF = ch;
}
void SerInit(void)
{
SCON = 0x52; // Mode 1...REN = on
PCON = 0; // SMOD = 0
TH1 = 0xFD; // 9600 baud
TMOD = 0x20; // 8 bit timer 1 with auto reload
TR1 = 1; // timer on
}
void main(void) // Main entry point
{
SerInit(); // Get port ready
while(1) // Forever loop
{
putch(getch()); // When something arrives, send it back
}
}
Digital value representation = ( ADC input / Digital value representation span * ADC maximum resolution ) - Digital value representation minimum..
As you can see there needs to be some calculations around 16 bit values 0 ~ 65535... You will soon find that 16 bit isn't enough, but will suit our needs..
The next couple of examples are only for ASM because in C it's all built in... C comes with extensive math(s) libraries, even 32bit floating point math(s).
The first thing we need is 16 bit addition...
Code:
OP1low equ 020H ; Operand 1
OP1high equ 021H
OP2low equ 022H ; Operand 2
OP2high equ 023H
res1 equ 024H ; Result registers
res2 equ 025H ;
res3 equ 026H ;
res3 equ 027H ;
mov A,OP1low ;
add A,OP2low ; Add the two low bytes
mov res1,A ; store the answer in res1
mov A,OP1high ; Add the two high bytes +
addc A,OP2high ; the carry from the low bytes
Mov res2,A ; Store
mov A,#00h ; Clear the accumulator
Addc A,#00h ; Add carry from high byte sum
mov res3,A ; store
ret ;
; And then subtraction 16 x 16....
mov A,OP1low ; Get the first low-byte
clr C ; Clear carry
subb A,OP2low ; Subtract the second low-byte
mov res1,A ; Store
mov A,OP1high ; Get the first high-byte
subb A,OP2high ; Subtract the second high-byte
mov res2,A ; Store
ret
; And then multiplication 16x16...
clr C
mov A,OP1high ; Multiply high bytes
mov B,OP2high
mul AB
mov res3,A
mov res4,B
mov A,OP1low ; Multiply high bytes
mov B,OP2low
mul AB
mov res1,A
mov res2,B
mov A,OP1high ; Now low and high
mov B,OP2low
mul AB
add A,res2 ; add in result
mov res2,A
mov A,B
addc A,res3 ; add in result
mov res3,A
clr A
addc A,res4 ; carry into MSbyte
mov res4,A
mov A,OP1low ; Now high and low
mov B,OP2high
mul AB
add A,res2 ; add in result
mov res2,A
mov A,B
addc A,res3 ; add in result
mov res3,A
clr A
addc A,res4
mov res4,A ; carry into Msbyte
; Lastly division.. This is quite long as it is 32 bit x 16 bit division....
mov R7,#0
mov R6,#0 ;zero out partial remainder
mov res1,#0
mov res2,#0
mov res3,#0 ; Clear result ready!!
mov res4,#0
mov R1,OP1_2 ;load divisor
mov R0,OP1_1
mov R5,#32 ;loop count
Div_loop:
acall Shift_D ; shift the dividend and return MSB in C
mov A,R6 ; shift carry into LSB of partial remainder
rlc A
mov R6,A
mov A,R7
rlc A
mov R7,A
; now test to see if R7:R6 >= R1:R0
clr C
mov A,R7 ; subtract R1 from R7 to see if R1 < R7
subb A,R1 ; A = R7 - R1, carry set if R7 < R1
jc Cant_sub
; at this point R7>R1 or R7=R1
jnz Can_sub ; jump if R7>R1
;if R7 = R1, test for R6>=R0
clr C
mov A,R6
subb A,R0 ; A = R6 - R0, carry set if R6 < R0
jc Cant_sub
Can_sub:
;subtract the divisor from the partial remainder
clr C
mov A,R6
subb A,R0 ; A = R6 - R0
mov R6,A
mov A,R7
subb A,R1 ; A = R7 - R1 - Borrow
mov R7,A
setb C ; shift a 1 into the quotient
sjmp Quot
Cant_sub:
;shift a 0 into the quotient
clr C
Quot:
;shift the carry bit into the quotient
acall Shift_Q
; Test for competion
djnz R5,Div_loop
; Now we are all done, move the TMP values back into OP
mov OP2_1,res1
mov OP2_2,res2 ; put into result!!
mov OP2_3,res3
mov OP2_4,res4
ret ; All done
Shift_D:
;shift the dividend one bit to the left and return the MSB in C
clr C
mov A,OP2_1
rlc A
mov OP2_1,A ; all four bytes
mov A,OP2_2 ; shifted right!!
rlc A
mov OP2_2,A
mov A,OP2_3
rlc A
mov OP2_3,A
mov A,OP2_4
mov A
mov OP2_4,A
ret
Shift_Q:
;shift the quotient one bit to the left and shift the C into LSB
mov A,res1
rlc A
mov res1,A ; All four bytes
mov A,res2 ; Shifted right...
rlc A
mov res2,A
mov A,res3
rlc A
mov res3,A
mov A,res4
rlc A
mov res4,A
ret
For now I wanted to send something to the PC... I have used the DS18S20 in the past... 1-wire serial connection..
Using timer 0 for the 1-wire and timer 1 for the serial connection to the PC... Our math routines and a hex2bcd utility... We have a complete application. This code will read the DS18S20, convert to ASCII and send to the PC in a readable format..
Code:
scr1 equ 020H ; Scratch arry
tmp1 equ 02AH ; temp storage
tmp2 equ 02BH
tmp3 equ 02CH
tmp4 equ 02DH
OP1_1 equ 030H ; Maths Operand 1
OP1_2 equ 031H
OP2_1 equ 032H ; Maths Operand 2
OP2_2 equ 033H
OP2_3 equ 034H
OP2_4 equ 035H
res1 equ 036H ; Maths Result registers
res2 equ 037H
res3 equ 038H
res4 equ 039H
BCD1 equ 03AH ; BCD results
BCD2 equ 03BH
BCD3 equ 03CH
BCD4 equ 03DH
org 0 ; Reset vector
sjmp Start
org 30H ; Code starts here
Start:
mov TMOD,#21H
acall SerInit
acall OWInit
While:
acall Convert ; DS18S20 to convert
acall delay
acall GetTemp ; read scratch register
mov A,scr1
mov OP1_1,A
mov OP1_2,#0H ;
mov OP2_1,#5H ; Temperature needs multiplying
mov OP2_2,#0H ; .5 degrees
acall MUL16
mov R1,res2
mov R2,res1
acall hex2bcd ; Convert to BCD so we can send to PC
mov A,R5
add A,#30H ; First digit -> ascii -> PC
acall putch
mov A,R4
add A,#30H ; Second digit -> ascii -> PC
acall putch
mov A,#2eH ; decimal point -> ascii -> PC
acall putch
mov A,R3
add A,#30H ; decimal digit -> ascii -> PC
acall putch
mov A,#0DH ; Carraige return
acall putch
sjmp While ; Back to loop
SerInit:
mov SCON,#52H ; Mode 1 REN = on, TI = ready
mov PCON,#0H ; SMOD = off normal baud
mov TH1,#0FDH ; FDH = 9600 baud
setb TR1
ret
putch: mov SBUF,A ; Send A to serial buffer
jnb TI,$ ; wait until gone
clr TI ; clear interrupt
ret ; done
; High level 1 wire
;------------------
OWInit:
acall OWreset ; Get bus
mov A,#0CCH ; Skip ROM
acall writeOw ;
mov A,#04EH ; Read Scratch pad
acall writeOw
mov A,#00H ; Write to scratch??
acall writeOw
mov A,#00H ; For identifying
acall writeOw
ret
Convert:
acall OWreset ; Get bus
mov A,#0CCH ; Skip ROM
acall writeOw
mov A,#044H ; Convert
acall writeOw
ret
GetTemp:
mov R2,#9
mov R1,#scr1 ; Array pointer
acall OWreset ; gET BUS
mov A,#0CCH ; Skip ROM
acall writeOw
mov A,#0BEH ; Read scratch
acall writeOw
gt1:
acall readOw ; 9 bytes
mov @R1,A
inc R1
djnz R2,gt1
ret
; Medium level 1 wire
;---------------------
writeOw:
mov R0,#8
nxtW:
acall OWwrite ; Write 8 bits LSBit first
rrc A
djnz R0,nxtW
ret
readOw:
mov R0,#7 ; Read 8 bits LSbit first
clr A
nxtR:
acall OWread
clr C
rrc A
djnz R0,nxtR
acall OWread
ret
; Low level 1 wire
;------------------
OWwrite:
jnb Acc.0,notone
mov TH0,#0FFH ; 15uS
mov TL0,#0F9H
setb TR0
clr P3.2
jnb TF0,$
setb P3.2
clr TR0
clr TF0
mov TH0,#0FFH ; rest of 70uS
mov TL0,#0CCH
setb TR0
jnb TF0,$
clr TR0
clr TF0
ret
notone:
clr P3.2
clr TF0
mov TH0,#0FFH ; 55uS
mov TL0,#0CCH
setb TR0
jnb TF0,$
setb P3.2
clr TR0
clr TF0
mov TH0,#0FFH ; Rest of 70uS
mov TL0,#0F6H
setb TR0
clr TR0
clr TF0
ret
OWread:
mov TH0,#0FFH ; Send 15uS pulse
mov TL0,#0F9H
setb TR0
clr P3.2
jnb TF0,$
clr TR0
clr TF0
setb P3.2
mov TH0,#0FFH ; Wait 15uS
mov TL0,#0F9H
setb TR0
jnb TF0,$
jnb P3.2,nobit ; High or low
setb Acc.7 ; set bit 7
nobit:
clr TR0
clr TF0
mov TH0,#0FFH ; Rest of 70uS
mov TL0,#0CCH
setb TR0
jnb TF0,$
clr TR0
clr TF0
ret
OWreset:
clr A
mov TH0,#0FEH ; 480uS reset pulse
mov TL0,#045H
setb TR0
clr P3.2
jnb TF0,$
clr TR0
clr TF0
setb P3.2
mov TH0,#0FFH ; Wait for device
mov TL0,#0B9H
setb TR0
jnb TF0,$
jnb P3.2,nodev
inc A ; device found!!
nodev:
clr TR0
clr TF0
mov TH0,#0FEH ; rest of 960uS
mov TL0,#045H
setb TR0
jnb TF0,$
ret
delay: ; conversion delay @ 750mS
mov R7,#6
mov R6,#0
mov R5,#0
D1: djnz R5,D1
djnz R6,D1
djnz R7,D1
ret
;BCD routine
Hex2bcd:
mov R3,#00h ; Clear
mov R4,#00h ; all
mov R5,#00h ; BDC
mov R6,#00h ; Registers
mov R7,#00h
mov B,#10 ; Divide low byte by 10
mov A,R2 ;
div AB
mov R3,B ; Store it in R3
mov B,#10 ; Divide remainder
div AB
mov R4,B ; store in R4
mov R5,A ; And in R5
cjne R1,#0H,HighByte ; Check the high byte
sjmp EndD
HighByte: ; Now you need to factor the high byte
mov A,#6 ; For every bit add 256 to total
add A,R3 ; 6
mov B,#10
div AB
mov R3,B
add A,#5 ; 5
add A,R4
mov B,#10
div AB
mov R4,B
add A,#2 ; 2
add A,R5
mov B,#10
div AB
mov R5,B
cjne R6,#00,AddIt ; This could take a while..
sjmp Cont
AddIt:
add A,R6
Cont:
mov R6,A
djnz R1,HighByte ; 255 iterations
mov B, #10
mov A,R6
div AB
mov R6,B ; finally R6
mov R7,A ; and R7
EndD: ret
; Addition
ADD16:
mov A,OP1_1 ;
add A,OP2_1 ; Add the two low bytes
mov res1,A ; store the answer in res1
mov A,OP1_2 ; Add the two high bytes +
addc A,OP2_2 ; the carry from the low bytes
Mov res2,A ; Store
mov A,#00h ; Clear the accumulator
Addc A,#00h ; Add carry from high byte sum
mov res3,A ; store
ret ;
; And then subtraction 16 x 16....
SUB16:
mov A,OP1_1 ; Get the first low-byte
clr C ; Clear carry
subb A,OP2_1 ; Subtract the second low-byte
mov res1,A ; Store
mov A,OP1_2 ; Get the first high-byte
subb A,OP2_2 ; Subtract the second high-byte
mov res2,A ; Store
ret
; And then multiplication 16x16...
MUL16:
clr C
mov A,OP1_2 ; Multiply high bytes
mov B,OP2_2
mul AB
mov res3,A
mov res4,B
mov A,OP1_1 ; Multiply high bytes
mov B,OP2_1
mul AB
mov res1,A
mov res2,B
mov A,OP1_2 ; Now low and high
mov B,OP2_1
mul AB
add A,res2 ; add in result
mov res2,A
mov A,B
addc A,res3 ; add in result
mov res3,A
clr A
addc A,res4 ; carry into MSbyte
mov res4,A
mov A,OP1_1 ; Now high and low
mov B,OP2_2
mul AB
add A,res2 ; add in result
mov res2,A
mov A,B
addc A,res3 ; add in result
mov res3,A
clr A
addc A,res4
mov res4,A ; carry into Msbyte
ret
; Lastly division.. This is quite long as it is 32 bit x 16 bit division....
DIV16:
mov R7,#0
mov R6,#0 ;zero out partial remainder
mov res1,#0
mov res2,#0
mov res3,#0 ; Clear result ready!!
mov res4,#0
mov R1,OP1_2 ;load divisor
mov R0,OP1_1
mov R5,#32 ;loop count
Div_loop:
acall Shift_D ; shift the dividend and return MSB in C
mov A,R6 ; shift carry into LSB of partial remainder
rlc A
mov R6,A
mov A,R7
rlc A
mov R7,A
; now test to see if R7:R6 >= R1:R0
clr C
mov A,R7 ; subtract R1 from R7 to see if R1 < R7
subb A,R1 ; A = R7 - R1, carry set if R7 < R1
jc Cant_sub
; at this point R7>R1 or R7=R1
jnz Can_sub ; jump if R7>R1
;if R7 = R1, test for R6>=R0
clr C
mov A,R6
subb A,R0 ; A = R6 - R0, carry set if R6 < R0
jc Cant_sub
Can_sub:
;subtract the divisor from the partial remainder
clr C
mov A,R6
subb A,R0 ; A = R6 - R0
mov R6,A
mov A,R7
subb A,R1 ; A = R7 - R1 - Borrow
mov R7,A
setb C ; shift a 1 into the quotient
sjmp Quot
Cant_sub:
;shift a 0 into the quotient
clr C
Quot:
;shift the carry bit into the quotient
acall Shift_Q
; Test for competion
djnz R5,Div_loop
; Now we are all done, move the TMP values back into OP
mov OP2_1,res1
mov OP2_2,res2 ; put into result!!
mov OP2_3,res3
mov OP2_4,res4
ret ; All done
Shift_D:
;shift the dividend one bit to the left and return the MSB in C
clr C
mov A,OP2_1
rlc A
mov OP2_1,A ; all four bytes
mov A,OP2_2 ; shifted right!!
rlc A
mov OP2_2,A
mov A,OP2_3
rlc A
mov OP2_3,A
mov A,OP2_4
rlc A
mov OP2_4,A
ret
Shift_Q:
;shift the quotent one bit to the left and shift the C into LSB
mov A,res1
rlc A
mov res1,A ; All four bytes
mov A,res2 ; Shifted right...
rlc A
mov res2,A
mov A,res3
rlc A
mov res3,A
mov A,res4
rlc A
mov res4,A
ret
END
From now on I can make the ASM files smaller... I will let you know what files to include....
Next up will be I2C and SPI... They will also be bit banged as some 8051 derivatives do not have a MSSP module...