Pages

How to do RS232

RS232 is an asynchronous serial communications protocol, widely used on computers. Asynchronous means it doesn't have any separate synchronising clock signal, so it has to synchronise itself to the incoming data - it does this by the use of 'START' and 'STOP' pulses. The signal itself is slightly unusual for computers, as rather than the normal 0V to 5V range, it uses +12V to -12V - this is done to improve reliability, and greatly increases the available range it can work over - it isn't necessary to provide this exact voltage swing, and you can actually use the PIC's 0V to 5V voltage swing with a couple of resistors to make a simple RS232 interface which will usually work well, but isn't guaranteed to work with all serial ports. For this reason I've designed the Serial Board to use the MAX232 chip, this is a chip specially designed to interface between 5V logic levels and the +12V/-12V of RS232 - it generates the +12V/-12V internally using capacitor charge pumps, and includes four converters, two transmit and two receive, the Serial Board only makes use of one of each - the other two are clearly marked on the circuit, and can be used for something else if required.


There are various data types and speeds used for RS232, I'm going to concentrate on the most common type in use, known as 8N1 - the 8 signifies '8 Data Bits', the N signifies 'No Parity' (can also be E 'Even Parity' or O 'Odd Parity'), the final 1 signifies '1 Stop Bit'. The total data sent consists of 1 start bit, 8 data bits, and 1 stop bit - giving a total of 10 bits. For the speed, I'm going to concentrate on 9600BPS (Bits Per Second), as each byte sent has 10 bits this means we can transfer a maximum of 960 bytes of data per second - this is fairly fast, but pretty easy to do in software, it's easily modified if you need faster or slower speeds, all you need to do is alter the delay timings - but I find 9600BPS is a pretty good speed to use.

We now know that we will be sending or receiving 960 ten bit data bytes per second, from that it's simple to calculate how long each bit is - simply divide 1 second by 9600 - this gives 104uS per bit. This value is crucial to successful RS232 communication, it doesn't have to be exact as the stop pulse allows resynchronisation after each data byte, but it must be accurate enough to maintain reading in the correct bit throughout each byte. The data is sent low bit first, so the example in the diagrams below is sending '01001011 Binary', '4B Hex', '75 Decimal'.

OK, now we know all the details of the protocol we are using, I'll explain how we transmit a byte:

The RS232 signal needs to be in the 'STOP CONDITION', at -12V, as the MAX232 inverts (a '1' is -12V and a '0' +12V) we need to make sure the PIC output pin is set HIGH, this should be done in the initialisation section of the program - this pin should always be high, EXCEPT when we are sending data, when it can be either high or low. 
The RS232 line is now happily sat at -12V, and the receiving device is waiting for a 'START BIT', to generate this all we need to do is set the PIC output pin low, the MAX232 inverts the signal and takes the RS232 line up to +12V. As we know that all bits should be 104uS long we now delay 104uS, before we do anything else. 
Now we can transmit the 8 data bytes, starting with the low bit, after each bit is set on the output pin we again wait 104uS, so each bit is the correct length. 
That only leaves the 'STOP BIT', for this we set the PIC output pin HIGH (as in section 1 above), and wait 104uS to give the correct bit length - now the 'STOP BIT' doesn't have to be only 104uS long, it simply signifies the end of the data byte. If it is the last data byte it could be a considerable time before another 'START BIT' is sent - this is shown in the diagrams by the large gap between the end of the 'STOP BIT' (shown by the dotted line) and the next 'START BIT'. If you are sending data as fast as possible the next 'START BIT' will start on that dotted line, immediately after the 104uS 'STOP BIT'. 
This is an example of a signal on an RS232 line, initially it sits at -12V, known as the 'STOP CONDITION', this condition lasts until a signal is sent. To send a signal we first need to let the receiving device know we are starting to send data, to do this we set the line to +12V, this is called the 'START BIT' - the receiving device is waiting for this to happen, once it does it then gets ready to read the next 9 bits of data (eight data bits and one stop bit). 

This is the identical signal as it leaves (or enters) the PIC pin, as the MAX232 inverts the signal this looks to be inverted, but is actually the correct way up - RS232 logic levels are inverted compared to normal levels. 

To receive a data byte is pretty straightforward as well:

Test the PIC input pin, and loop until it goes low, signifying the beginning of the 'START BIT'. 
Now we wait just half a bit time (52uS) and check again to make sure it's still low - this 52uS delay means we are reading the 'START BIT' pretty well in the centre of the pulse, where it should be the most reliable. 
Following a successful 'START BIT' we can now read the data bits, as we are currently in the centre of the 'START BIT' we can simply wait 104uS, which will take us to the centre of the first data bit, then read the input pin again, remembering to invert the polarity of the bit. We then read the next seven bits in the same way, waiting 104uS before each one. 
Lastly we need to account for the 'STOP BIT', again we wait 104uS for the centre of the bit and could read the port pin if we wanted, if it isn't high there has obviously been an error, but for simplicity we just exit the routine. 
We now can transfer the received byte to where we wish, and either wait for another byte or do something else. 
Here are the actual serial routines we will be using, they consist of a number of small subroutines, and require four data registers allocating:

Xmit_Byte - this is used to store the transmitted byte (passed in W). 
Rcv_Byte - this is used for the received byte, and is copied to W on exiting the routine. 
Bit_Cntr - used to count the number of bits sent or received, set to 8 and decremented. 
Delay_Count - used in the two delay routines. 
The routines themselves consist of three subroutines that are called, and two internal subroutines, not normally called from elsewhere:

SER_INIT - this is only ever called once, usually when the program first runs, as part of the normal initialisation stages, it sets the input and output pins to the correct direction, and sets the output pin to the correct polarity - high, so the RS232 line sets at -12V. 
XMIT_RS232 - this is the transmit routine, simply load the byte to be transmitted into the W register and call this subroutine (CALL XMIT_RS232). 
Rcv_RS232 - this is the receive routine, when you call this is waits for a received byte, there's no timeout, so it will wait for ever if it doesn't receive a byte. To use the subroutine simply call it (CALL Rcv_RS232) and it returns the received byte in the W register. 
Start_Delay - internal subroutine that delays 52uS, used by the Rcv_RS232 subroutine to delay half a bit length. 
Bit_Delay - used by both the transmit and receive subroutines, to provide a 104uS (one bit) delay. 
;Serial routines
Xmit_Byte Equ 0x20 ;holds byte to xmit
Rcv_Byte Equ 0x21 ;holds received byte 
Bit_Cntr Equ 0x22 ;bit counter for RS232
Delay_Count Equ 0x23 ;delay loop counter
SER_INIT
BSF STATUS, RP0 ;select bank 1
BCF TRISB, 6 ;set B6 as an output
BSF TRISB, 7 ;set B7 as an input
BCF STATUS, RP0 ;select bank 0
BSF PORTB, 6 ;set B6 high
RETURN

XMIT_RS232 MOVWF Xmit_Byte ;move W to Xmit_Byte
MOVLW 0x08 ;set 8 bits out
MOVWF Bit_Cntr
BCF PORTB, 6
CALL Bit_Delay
Ser_Loop RRF Xmit_Byte , f ;send one bit
BTFSS STATUS , C
BCF PORTB, 6
BTFSC STATUS , C
BSF PORTB, 6
CALL Bit_Delay
DECFSZ Bit_Cntr , f ;test if all done
GOTO Ser_Loop
BSF PORTB, 6
CALL Bit_Delay
RETURN

Rcv_RS232 BTFSC PORTB, 7 ;wait for start bit
GOTO Rcv_RS232
CALL Start_Delay ;do half bit time delay
BTFSC PORTB, 7 ;check still in start bit
GOTO Rcv_RS232
MOVLW 0x08 ;set up to read 8 bits
MOVWF Bit_Cntr
CLRF Rcv_Byte
Next_RcvBit CALL Bit_Delay
BTFSS PORTB, 7
BCF STATUS , C
BTFSC PORTB, 7
BSF STATUS , C
RRF Rcv_Byte , f
DECFSZ Bit_Cntr , f ;test if all done
GOTO Next_RcvBit
CALL Bit_Delay
MOVF Rcv_Byte, W
RETURN

Start_Delay MOVLW 0x0C
MOVWF Delay_Count
Start_Wait NOP
DECFSZ Delay_Count , f
GOTO Start_Wait
RETURN

Bit_Delay MOVLW 0x18
MOVWF Delay_Count
Bit_Wait NOP
DECFSZ Delay_Count , f
GOTO Bit_Wait
RETURN

The routines presented here use PortB pin 6 as the output, and PortB pin 7 as the input, they are based on a 4MHz clock frequency. As it's all done in software you can easily change the port and pin designations, and simply alter the delay timings for different clock speeds or baud rates.

Tutorial 7.1 - required hardware, Main Board and Serial Board.

This first sample program simply transmits a few ASCII characters out of the serial board, it displays 'RS232'. In this example each character is individually loaded in to the W register and the XMIT_RS232 subroutine is called.

Tutorial 7.2 - required hardware, Main Board and Serial Board.

This second sample program transmits two lines of text, the text is stored as a string (terminated by '0x00') in the top page of memory, the two characters '0x0A' and '0x0D' in the string are LF and CR to move the cursor to the start of the next line. The XMIT_RS232 subroutine is called repeatedly in the loop which reads the string.

Tutorial 7.3 - required hardware, Main Board, LCD Board and Serial Board.

This third sample program receives data one character at a time and displays it on the LCD module. Please note that both this, and the next tutorial, can only handle one character at a time - as there's no handshaking involved the routine on the PIC must finish whatever it has to before the next character arrives - if a continuous stream of data is incoming it only has 52uS before the next byte arrives, and this is too fast for the LCD to have finished displaying the previous character. There are various ways of overcoming this - firstly, as long as you are typing the characters on the keyboard there won't be a problem (you can't type fast enough), secondly you could arrange for the transmitted protocol to have more than one stop bit (two stop bits would give three times as long to display the characters, three stop bits would give five times as long, and so on). Or you could buffer the characters in PIC data registers, this still wouldn't allow a continuous data stream, but would probably do all that's required. (For a further possibility see Tutorial 7.7a)

Tutorial 7.4 - required hardware, Main Board, LCD Board and Serial Board.

This fourth sample program receives data one character at a time, displays it on the LCD module (as in 7.3) and then echo's the character back to the PC screen.

Tutorial 7.5 - required hardware, Main Board, LED Board and Serial Board.

This fifth sample program receives data one character at a time, displays it on the LED board and then echo's the character back to the PC screen, the ports have been swapped around (PortA now connects to the serial board, and PortB connects to the LED board, because pin A4 is an open-collector output). This would make a nice simple way of providing eight switched outputs controlled from a serial port.

Tutorial 7.6 - required hardware, Main Board, Switch Board and Serial Board.

This sixth sample program receives one data byte from the PC (any byte - just to initiate a read), reads the switches connected to PortB, and sends the result back to the PC as a string of eight 1's and 0's - B0 first, and B7 last - followed by CRLF. If you examine the code, the routine for sending the PortB reading as a series of ASCII 1's and 0's is based on the XMIT_RS232 code, it reads the bits in turn in exactly the same way, only the action taken for each bit is different. As the previous example makes a nice easy way of writing eight bits, this one makes a nice easy way of reading eight bits.

Tutorial 7.7a - required hardware, Main Board 2, LCD Board and Serial Board.

This seventh sample program works exactly like Tutorial 7.4, but is based on the 16F876 at 20MHz, and uses the hardware USART rather than software emulation. As it uses hardware to receive the serial data, this gives a lot more time for processing and displaying characters, around 1mS or so. There isn't a 16F628 version of this tutorial yet as I have to change the serial board connections over, as soon as this is done I'll post a 16F628 version as well - if you want to do it, the values for the USART are SPBRG=25 and BRGH=1

How to do hardware PWM.

The PWM hardware has up to 10 bit resolution, which means you can have 1024 different steps from zero to full power, for our purposes this is a little excessive, and I've decided on 128 steps forward (0-127), and 128 steps in reverse (128-255), using a single byte for the speed and direction, with the highest bit signifying reverse.

It's actually very easy to use, once you've set the PWM up all you need to do is write to the CCPR1L and CCPR2L registers to set the speed, the listed routine uses an initialise subroutine which sets everything up, then subroutines to set the left and right motor speeds.

The initialise subroutine sets various registers, they are commented in the code, but I'll explain them here as well:

First we turn off the analogue to digital converters for PortA, they default to analogue, so it's good practice to set them as digital I/O if they are not being used, if we need them later we can turn them back on (or simply remove the code which turns them off).
Secondly we set all the pins of PortC as outputs, we'll be using six of the pins, pins 1 and 2 are the PWM outputs, and pins 0, 3, 4 and 5 will be used for direction switching.
Next we set the CCP1CON and CCP2CON registers to operate as PWM, CCP1 and CCP2 can operate in various modes, so we need to specifically set them as PWM.
Then we set the PR2 register, this is a step which often causes confusion, it basically sets the value of a comparison register which the actual PWM value will be compared against, in this case we set it to 126 which means the highest PWM value will be 126, if the PWM is 127 the comparator will never reach that value and the output will stay permanently high - just as we need for full power!. If the PWM value is zero, the comparator will always equal that value as it starts, so the output will remain permanently low - again, just as we need for zero power.
The next step is to set T2CON, this sets the frequency of the PWM, as it's derived from the 20MHz system clock it runs too at too high a frequency, there are two possibilities here - setting the prescaler divides the frequency before the PWM section, and the postscaler afterwards. For this example we set the prescaler to divide by 16, this gives us a PWM frequency of 2500Hz.
The next two lines set both PWM channels to zero, so both motors are off when it starts up.
The last line actually starts the PWM system by turning Timer2 on, once this line runs the PWM is independent of the rest of the code, we can do pretty well whatever we like (unless we alter the register settings) and the PWM will carry on running regardless.
The main program itself is just a demonstration of how to use the PWM subroutines, it simply sets four different PWM and direction settings with 5 second delays in between them. It should be pretty self evident how to use it from your own programming. I've included various delay routines, including a new one called 'Delay100W', this delays 100mS multiplied by the value in W when the routine is called - in this example we load W with 50 to give a 5 second delay.


; 16F876 PWM example code
;
; Device 16F876
LIST P=16F876, W=2, X=ON, R=DEC
#INCLUDE P16F876.INC
__CONFIG 0x393A

cblock 0x20 ;start of general purpose registers
count ;used in delay routine
count1 ;used in delay routine
counta ;used in delay routine
countb ;used in delay routine
temp ;temp storage
endc



RL Equ 0x00 ;pin for left motor reverse
FL Equ 0x03 ;pin for left motor forward
RR Equ 0x04 ;pin for right motor reverse
FR Equ 0x05 ;pin for right motor forward

;pins 1 and 2 are the 2 PWM channels



ORG 0x0000
NOP ;for bootloader compatibility
NOP
NOP
GOTO START
ORG 0x0010

START CALL Initialise

MainLoop:
MOVLW d'64'
CALL SpeedL ;both half speed forwards
CALL SpeedR
CALL Long_Delay

MOVLW d'64'
CALL SpeedL ;left half speed forwards
MOVLW d'192'
CALL SpeedR ;right half speed reverse
CALL Long_Delay

MOVLW d'10'
CALL SpeedL ;slow speed forwards
MOVLW d'228'
CALL SpeedR ;fast speed reverse
CALL Long_Delay

MOVLW d'228'
CALL SpeedL ;fast speed reverse
MOVLW d'10'
CALL SpeedR ;slow speed forwards
CALL Long_Delay

GOTO MainLoop

Initialise:
BANKSEL ADCON1 ;turn off A2D
MOVLW 0x06
MOVWF ADCON1
BANKSEL PORTA
BANKSEL TRISC
MOVLW 0 ;set PORTC as all outputs
MOVWF TRISC
BANKSEL PORTC

MOVF CCP1CON,W ;set CCP1 as PWM
ANDLW 0xF0
IORLW 0x0C
MOVWF CCP1CON

MOVF CCP2CON,W ;set CCP2 as PWM
ANDLW 0xF0
IORLW 0x0C
MOVWF CCP2CON

MOVLW 126 ;set highest PWM value
BANKSEL PR2 ;over this (127) is permanently on
MOVWF PR2
BANKSEL TMR2

MOVF T2CON,W ;set prescaler to 16
ANDLW 0xF8 ;PWM at 2500HZ
IORLW 0x02
MOVWF T2CON

MOVF T2CON,W ;set postscaler to 1
ANDLW 0x07
IORLW 0x00
MOVWF T2CON

CLRF CCPR1L ;set PWM to zero
CLRF CCPR2L

BSF T2CON, TMR2ON ;and start the timer running
RETURN

SpeedL: ;use value in W to set speed (0-127)
MOVWF temp
BTFSC temp, 7 ;if more than 128 set speed in reverse
CALL ReverseL ;so '1' is very slow forward
BTFSS temp, 7 ;and '129' is very slow reverse
CALL ForwardL
ANDLW 0x7F
MOVWF CCPR1L
RETURN

SpeedR:
MOVWF temp
BTFSC temp, 7
CALL ReverseR
BTFSS temp, 7
CALL ForwardR
ANDLW 0x7F
MOVWF CCPR2L
RETURN

ReverseL:
BSF PORTC, RL ;set pins for reverse
BCF PORTC, FL
RETURN

ReverseR:
BSF PORTC, RR
BCF PORTC, FR
RETURN

ForwardL:
BCF PORTC, RL ;set pins for forward
BSF PORTC, FL
RETURN

ForwardR:
BCF PORTC, RR
BSF PORTC, FR
RETURN

;Delay routines

Long_Delay
movlw d'50' ;delay 5 seconds
call Delay100W
return

Delay100W movwf count ;delay W x 100mS
d2 call Delay100 ;maximum delay 25.5 seconds
decfsz count ,f
goto d2
return

Delay255 movlw 0xff ;delay 255 mS
goto d0
Delay100 movlw d'100' ;delay 100mS
goto d0
Delay50 movlw d'50' ;delay 50mS
goto d0
Delay20 movlw d'20' ;delay 20mS
goto d0
Delay10 movlw d'10' ;delay 10mS
goto d0
Delay1 movlw d'1' ;delay 1mS
goto d0
Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)
d0 movwf count1
d1 movlw 0xE7
movwf counta
movlw 0x04
movwf countb
Delay_0 decfsz counta, f
goto $+2
decfsz countb, f
goto Delay_0

decfsz count1 ,f
goto d1
return

;end of Delay routines

END

0 comments:

Post a Comment