Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Software based UART

Status
Not open for further replies.

haxan

New Member
Hi, i am going to ask a rather stupid question but i really need to know if it is possible or not.

I have a PIC18F4520 which has only one UART port.

Is it possible to somehow add multiple UARTs only in software?

Maybe one connected to RS-485 IC and one connected to RS-232 :)
 
Hi, i am going to ask a rather stupid question but i really need to know if it is possible or not.

I have a PIC18F4520 which has only one UART port.

Is it possible to somehow add multiple UARTs only in software?

Maybe one connected to RS-485 IC and one connected to RS-232 :)

hi,
Yes, you can add a 'software uart' to the PIC.

Look at Nigel's tutorials, link is near my signature, I know that its for an 18F that you are asking but the 16F method described should help.
 
Last edited:
Thank you very much :) I will give the links a look and bother again if something turns up.

Bytheway, are you as experienced as your avatar (display image) ?

hi,
If you mean is that me in the image, it is..:)
 
sorry but i have tried to search and implement two or more software UARTs in PIC but cannot seem to do it.

What i understand so far is that if one wants multiple UARTs, he can use one hardware and make like two software UART but those seem to put load on the controller.

One software UART can be implemented using timer and external interrupt for RX

How can we implement more UARTs like second one in software (total three [1 hardware, 2 softwares])

Also how can we set different baud rates for each.
 
You can have a lot of software UARTs on one PIC.

You need what is, in effect, a multitasking system.
The main loop calls each task in turn.
All your tasks, including the USARTs, have to return as soon as possible. Tasks never hog the processor by waiting. If something needs to be done, it is done and the process returns to the main loop. If there is any waiting to be done, timers are used. If the timer isn't at the right value, the process returns to the main loop. When the timer is at the right value, whatever is needed is done. Either way, each task only runs very briefly, all task can be run very frequently.

You need a timer running without any tasks resetting it. It's a good idea to make the timer period a multiple of the USART bit period.
Each USART transmit task sends a new bit, if there is one to send, each time the appropriate bit of the timer changes.
Each USART receive tasks looks for the start of the start bit. When one arrives, the value of the timer is stored. Then the next input bit is read when the timer has increased by the correct amount.
 
will i need to make the complete multitasking system myself or are there a few available.

This seems too much of a task just to run 3 serial ports :( I think it would be much easier to use a different microchip MCU. Thanks though.
 
Are you writing in C or assembly?

Here is assembly code for 2 serial receivers and one serial transmitters on a 12F629. That hasn't got any USARTs, and the serial data I was working with didn't use the normal start bit, 7/8/9 data bits and 1/2 stop bits. That meant that I had to bit-bash each serial line.

As the two incoming lines were not synchronised, they could overlap, so I had to be able to receive on both at the same time. All serial signals use the tmr0. Each receive task stores the time that it has to read the next bit, in clock_in_time_main and clock_in_time_sec.

When the transmit routine has to wait for 1 second, it also uses tmr0. Each time the msb of tmr0 changes, the registers for the 1 second wait are incremented. Whether or not the msb of tmr0 has changed, the wait routine returns to the main loop

The main loop is incredibly simple.
Code:
main_loop

	call	clock_out
	call 	clock_in_main
	call 	clock_in_sec
	
	goto 	main_loop

And that is all you need. Each task called as often as possible, and each task returns to the main loop as soon as the calculations are done. No task ever waits for anything.

There are no interrupts.

Code:
;Hacking a Rover 75
;This accepts the data strings from 2 sets of window switches
;and puts together a composite that fools the car into thinking that there is just one set

;FONT ALERT:- There is no L or l (lowercase L) (which can look like 1 (one)
;The data is at 1200 baud
;The data is sent 4 times
;It is sent when the signal changes or every second
;There are 26 bits of data
;10ab0cd0e0f1110gh0ij0e1k11
;a = front right down
;b = front right up
;c = front left down
;d = front left up
;e = disable rear window switches
;f = parity for bits a,b,c,d and e
;g = rear right down
;h = rear right up
;i = rear left down
;j = rear left up
;k = parity for bits g, h, i, j, e and 1

;The Output drivers invert the signals so the output driving commands are inverted

	#include p12f629.inc
zero equ z
carry equ c

data_out_p			equ 0x05
data_out			equ 0x02
main_in_p			equ 0x05 
main_in				equ 0x01
sec_in_p			equ 0x05
sec_in				equ 0x00


send_now_r			equ 0x20
send_now			equ 0x00	;just a bit
sendbit_r			equ 0x20
sendbit				equ 0x01
parity_r			equ 0x20
parity				equ 0x02
send_fast_r			equ 0x20
send_fast			equ 0x03


clock_out_mode		equ 0x21
time1s_msb			equ 0x22
time1s_lsb			equ 0x23
bit_out_count		equ 0x24
clock_in_mode_main	equ 0x25
clock_in_time_main	equ 0x26
clock_in_mode_sec	equ 0x27
clock_in_time_sec	equ 0x28



data_out1			equ 0x30
data_out2			equ 0x31
data_out3			equ 0x32

data_shift_reg_1	equ 0x34
data_shift_reg_2	equ 0x35
data_shift_reg_3	equ 0x36

data_in_main_1		equ 0x38
data_in_main_2		equ 0x39
data_in_main_3		equ 0x3a

data_in_sec_1		equ 0x3c
data_in_sec_2		equ 0x3d
data_in_sec_3		equ 0x3e

data_main_1			equ 0x40
data_main_2			equ 0x41
data_main_3			equ 0x42

data_sec_1			equ 0x44
data_sec_2			equ 0x45
data_sec_3			equ 0x46

Initialise

	bsf 	status, rp0
	movlw	b'11000100'
	movwf	option_reg
	
	movlw	b'00000011'
	movwf	gpio
	
	bcf 	status, rp0
	
	movlw	0x07
	movwf	cmcon

	clrf	clock_out_mode
	clrf	time1s_msb	
	clrf	time1s_lsb			
	clrf	bit_out_count
	
	clrf	data_out1
	clrf	data_out2	
	clrf	data_out3
	
	clrf	data_main_1
	clrf	data_main_2
	clrf	data_main_3

	clrf	data_sec_1
	clrf	data_sec_2
	clrf	data_sec_3

;set up outputs and timers
;with a 19.6608 MHz crystal there are 4096
;instruction cycles for each data bit at 
;1200 baud

;tmr0 msb toggles at 1200 Hz
;so tmr0 rolls over 600 Hz
;600*256*4*32=19660800 so prescale of 32 is needed


main_loop

	call	clock_out
	call 	clock_in_main
	call 	clock_in_sec
	
	goto 	main_loop

clock_out
;clock_out_mode is 0 while waiting 
;1 to 4 when sending

;Sending mode must wait until the stop bits have been sent before ending.
;Timer will be reset when it ends
	movf 	clock_out_mode, w
	btfss 	status, zero
	goto 	sending

	clrf	bit_out_count

;load up new data, which doesn't need to be done each time, but it doesn't hurt

;data_main_1
;data_sec_1
;data_out1

	movf	data_main_1, w
	iorwf	data_sec_1, w
	movwf	data_out1

	movf	data_sec_2, w
	andlw	b'01111111'			;cut rear window disable bit out of secondary signal
	iorwf	data_main_2, w
	movwf	data_out2

	movf	data_sec_3, w
	andlw	b'11111011'			;cut rear window disable bit out of secondary signal
	iorwf	data_main_3, w
	movwf	data_out3

	bcf  	data_out2, 5	;clear parity bit of data_out2
	movlw	b'00100000'		;parity bit of data_out2	
	btfsc	data_out1, 5
	xorwf	data_out2, f	;parity for bit a
	btfsc	data_out1, 4
	xorwf	data_out2, f	;parity for bit b
	btfsc	data_out1, 2
	xorwf	data_out2, f	;parity for bit c
	btfsc	data_out1, 1
	xorwf	data_out2, f	;parity for bit d
	btfsc	data_out2, 7
	xorwf	data_out2, f	;parity for bit e

	bsf  	data_out3, 0	;set parity bit of data_out3 ('cos of the 1 after the second e)
	movlw	b'00000001'		;parity bit of data_out2	
	btfsc	data_out2, 0
	xorwf	data_out3, f	;parity for bit a
	btfsc	data_out3, 7
	xorwf	data_out3, f	;parity for bit b
	btfsc	data_out3, 5
	xorwf	data_out3, f	;parity for bit c
	btfsc	data_out3, 4
	xorwf	data_out3, f	;parity for bit d
	btfsc	data_out3, 2
	xorwf	data_out3, f	;parity for bit e

	movf	data_out1, w
	movwf	data_shift_reg_1
	movf	data_out2, w
	movwf	data_shift_reg_2
	movf	data_out3, w
	movwf	data_shift_reg_3
	
	
	
;10ab0cd0 e0f1110g	h0ij0e1k  11
;a = front right down
;b = front right up
;c = front left down
;d = front left up
;e = disable rear window switches
;f = parity for bits a,b,c,d and e
;g = rear right down
;h = rear right up
;i = rear left down
;j = rear left up
	
;We need to change the repeat frequency if any of the main 8 bits are set

	bcf		send_fast_r, send_fast

	movlw	b'00110110'			;checking if bit a, b, c or d is there
	andwf	data_out1, w
	btfss	status, zero
	bsf		send_fast_r, send_fast
	
	btfsc	data_out2, 0		;checking bit g
	bsf		send_fast_r, send_fast

	movlw	b'10110000'			;checking if bit h, i, or j is there
	andwf	data_out3, w
	btfss	status, zero
	bsf		send_fast_r, send_fast
		
;if the flag is set to send immediately,
;do so

	btfss 	send_now_r, send_now
	goto 	wait_1s

	incf 	clock_out_mode, f		;which had to be zero to get here.
	bcf		send_now_r, send_now	;clear the bit that says it's time to send now

	return

wait_1s
;increment time1s_lsb if its bit0 doesn't 
;match bit7 of tmr0

	btfss	time1s_lsb, 0
	goto	lsb_clr
	
	btfsc	tmr0, 7
	return
	goto	increment_time

lsb_clr	
	btfss	tmr0, 7
	return

increment_time

	incf 	time1s_lsb, f
	btfsc 	status, zero
	incf 	time1s_msb, f
;the maximum gap between data
;bursts is 1200-(4x26)=1096
;or 4x256+72

;or 1200/5-(4x26)=136
;or 0x256+136

	movlw	d'0'
	btfss	send_fast_r, send_fast
	movlw	d'4'
	subwf 	time1s_msb, w
	btfss 	status, carry
	return
	btfss	status, zero
	goto 	time_done


	movlw	d'136'
	btfss	send_fast_r, send_fast
	movlw	d'72'
	subwf	time1s_lsb, w
	btfss	status, carry
	return
time_done
	incf	clock_out_mode, f
	return

sending

;This has to put out a bit every time the msb of tmr0 changes
;As the first bits are nulls (high) the first bit doesn't need to be syncronised

	btfss	sendbit_r, sendbit
	goto	sendbit_clear
	
	btfsc	tmr0, 7
	return
	goto	change_sendbit

sendbit_clear	
	btfss	tmr0, 7
	return

change_sendbit
	movlw	1<<sendbit
	xorwf	sendbit_r, f


;If the prog gets here it's time to output the next bit
	
	btfsc	data_shift_reg_1, 7
	bcf 	data_out_p, data_out
	btfss	data_shift_reg_1, 7
	bsf 	data_out_p, data_out

	bsf 	status, carry		;'cos the last 2 bits are always 1
	rlf		data_shift_reg_3, f
	rlf		data_shift_reg_2, f
	rlf		data_shift_reg_1, f

	incf	bit_out_count, f

	movlw	d'26'
	subwf	bit_out_count, w
	
	btfss	status, zero
	return

	clrf	bit_out_count
	incf	clock_out_mode, f

	movf	data_out1, w
	movwf	data_shift_reg_1
	movf	data_out2, w
	movwf	data_shift_reg_2
	movf	data_out3, w
	movwf	data_shift_reg_3

	movlw	d'5'
	subwf	clock_out_mode, w 

	btfsc	status, zero
	clrf	clock_out_mode
	
	clrf	time1s_msb
	clrf	time1s_lsb
	
	return	

clock_in_main

;clock_in_mode_main

	movf	clock_in_mode_main, w
	btfss	status, zero
	goto	clock_in_main_running

	btfsc	main_in_p, main_in
	return

;At this point the signal has just started

	movf	tmr0, w
	addlw	0x40
	movwf	clock_in_time_main

	bsf 	data_in_main_3, 0	;first bit is always 1
	incf	clock_in_mode_main, f	

	return

clock_in_main_running
	movf	tmr0, w
	subwf	clock_in_time_main, w
	andlw	0x80
	btfsc	status, zero
	return							;see if time is big enough

	movlw	0x80
	addwf	clock_in_time_main, f

	incf	clock_in_mode_main, f

	movlw	d'25'
	subwf	clock_in_mode_main, w
	btfsc	status, carry
	goto	main_data_done

	bcf 	status, carry
	btfsc	main_in_p, main_in
	bsf 	status, carry

	rlf 	data_in_main_3, f
	rlf 	data_in_main_2, f
	rlf 	data_in_main_1, f

	return
main_data_done

	btfsc	main_in_p, main_in
	goto	main_end_bit_ok

;something has to be done here to fail the result
	clrf 	data_in_main_3
	clrf 	data_in_main_2
	clrf 	data_in_main_1

main_end_bit_ok
	movlw	d'26'
	subwf	clock_in_mode_main, w
	btfss	status, carry
	return

	clrf	clock_in_mode_main
;data check
;data is of the form:-	10ab0cd0e0f1110gh0ij0e1k11
;the last 2 bits have been checked in real time
;so we must check the bytes are of the form:- 10xx0xx0 x0p1110x x0xx0x1p

	movlw	b'01001001'			;checking that first byte is of the form x0xx0xx0
	andwf	data_in_main_1, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return
	
	comf	data_in_main_1, w	;checking that the first byte is of the form 1xxxxxxx
	andlw	b'10000000'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return
	
	movlw	b'01000010'			;checking that second byte is of the form x0xxxx0x
	andwf	data_in_main_2, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	comf	data_in_main_2, w	;checking that the second byte is of the form xxx111xx
	andlw	b'00011100'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	movlw	b'01001000'			;checking that third byte is of the form x0xx0xxx
	andwf	data_in_main_3, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	comf	data_in_main_3, w	;checking that the third byte is of the form xxxxxx1x
	andlw	b'00000010'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

;10xx0xx0 x0p1110x x0xx0x1p

	bcf  	parity_r, parity	;clear parity bit
	movlw	1<<parity			
	btfsc	data_in_main_1, 5
	xorwf	parity_r, f		;parity for bit a
	btfsc	data_in_main_1, 4
	xorwf	parity_r, f		;parity for bit b
	btfsc	data_in_main_1, 2
	xorwf	parity_r, f		;parity for bit c
	btfsc	data_in_main_1, 1
	xorwf	parity_r, f		;parity for bit d
	btfsc	data_in_main_2, 7
	xorwf	parity_r, f		;parity for bit e
	btfsc	data_in_main_2, 5
	xorwf	parity_r, f		;parity for bit f

	btfsc	parity_r, parity
	goto	data_fail		;this might be just a return


;10xx0xx0 x0p1110x x0xx0x1p
;10ab0cd0 e0f1110g h0ij0e1k 11

	bsf  	parity_r, parity	;set parity bit, 'cos bit 2 is always 1, which has been already checked
	movlw	1<<parity			
	btfsc	data_in_main_2, 0
	xorwf	parity_r, f		;parity for bit g
	btfsc	data_in_main_3, 7
	xorwf	parity_r, f		;parity for bit h
	btfsc	data_in_main_3, 5
	xorwf	parity_r, f		;parity for bit i
	btfsc	data_in_main_3, 4
	xorwf	parity_r, f		;parity for bit j
	btfsc	data_in_main_3, 2
	xorwf	parity_r, f		;parity for bit e
	btfsc	data_in_main_3, 0
	xorwf	parity_r, f		;parity for bit k

	btfsc	parity_r, parity
	goto	data_fail		;this might be just a return


;The bit to send immediately is set if the data has changed

	movf	data_in_main_1, w
	xorwf	data_main_1, w
	btfss	status, zero
	bsf 	send_now_r, send_now	
	
	movf	data_in_main_2, w
	xorwf	data_main_2, w
	btfss	status, zero
	bsf 	send_now_r, send_now	

	movf	data_in_main_3, w
	xorwf	data_main_3, w
	btfss	status, zero
	bsf 	send_now_r, send_now	

;Now the data is considered good so we can copy it to where it gets used

	movf	data_in_main_1, w
	movwf	data_main_1
	movf	data_in_main_2, w
	movwf	data_main_2
	movf	data_in_main_3, w
	movwf	data_main_3

data_fail
	return

clock_in_sec

;clock_in_mode_main

	movf	clock_in_mode_sec, w
	btfss	status, zero
	goto	clock_in_sec_running

	btfsc	sec_in_p, sec_in
	return

;At this point the signal has just started

	movf	tmr0, w
	addlw	0x40
	movwf	clock_in_time_sec

	bsf 	data_in_sec_3, 0	;first bit is always 1
	incf	clock_in_mode_sec, f	

;	bsf	5, 2

	return

clock_in_sec_running
	movf	tmr0, w
	subwf	clock_in_time_sec, w
	andlw	0x80
	btfsc	status, zero
	return							;see if time is big enough

	movlw	0x80
	addwf	clock_in_time_sec, f

	incf	clock_in_mode_sec, f

;	btfsc	clock_in_mode_sec, 0
;	bsf		5, 2
;	btfss	clock_in_mode_sec, 0
;	bcf		5, 2

	movlw	d'25'
	subwf	clock_in_mode_sec, w
	btfsc	status, carry
	goto	sec_data_done

	bcf 	status, carry
	btfsc	sec_in_p, sec_in
	bsf 	status, carry

	rlf 	data_in_sec_3, f
	rlf 	data_in_sec_2, f
	rlf 	data_in_sec_1, f

	return
sec_data_done

	btfsc	sec_in_p, sec_in
	goto	sec_end_bit_ok

;something has to be done here to fail the result
	clrf 	data_in_sec_3
	clrf 	data_in_sec_2
	clrf 	data_in_sec_1

sec_end_bit_ok
	movlw	d'26'
	subwf	clock_in_mode_sec, w
	btfss	status, carry
	return

	clrf	clock_in_mode_sec
;data check
;data is of the form:-	10ab0cd0e0f1110gh0ij0e1k11
;the last 2 bits have been checked in real time
;so we must check the bytes are of the form:- 10xx0xx0 x0p1110x x0xx0x1p

	movlw	b'01001001'			;checking that first byte is of the form x0xx0xx0
	andwf	data_in_sec_1, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return
	
	comf	data_in_sec_1, w	;checking that the first byte is of the form 1xxxxxxx
	andlw	b'10000000'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return
	
	movlw	b'01000010'			;checking that second byte is of the form x0xxxx0x
	andwf	data_in_sec_2, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	comf	data_in_sec_2, w	;checking that the second byte is of the form xxx111xx
	andlw	b'00011100'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	movlw	b'01001000'			;checking that third byte is of the form x0xx0xxx
	andwf	data_in_sec_3, w
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

	comf	data_in_sec_3, w	;checking that the third byte is of the form xxxxxx1x
	andlw	b'00000010'
	btfss	status, zero
	goto	sec_data_fail		;this might be just a return

;10xx0xx0 x0p1110x x0xx0x1p

	bcf  	parity_r, parity	;clear parity bit
	movlw	1<<parity			
	btfsc	data_in_sec_1, 5
	xorwf	parity_r, f		;parity for bit a
	btfsc	data_in_sec_1, 4
	xorwf	parity_r, f		;parity for bit b
	btfsc	data_in_sec_1, 2
	xorwf	parity_r, f		;parity for bit c
	btfsc	data_in_sec_1, 1
	xorwf	parity_r, f		;parity for bit d
	btfsc	data_in_sec_2, 7
	xorwf	parity_r, f		;parity for bit e
	btfsc	data_in_sec_2, 5
	xorwf	parity_r, f		;parity for bit f

	btfsc	parity_r, parity
	goto	sec_data_fail		;this might be just a return

;10xx0xx0 x0p1110x x0xx0x1p
;10ab0cd0 e0f1110g h0ij0e1k 11

	bsf  	parity_r, parity	;clear carry bit, 'cos bit 2 is always 1, which has been already checked
	movlw	1<<parity			
	btfsc	data_in_sec_2, 0
	xorwf	parity_r, f		;parity for bit g
	btfsc	data_in_sec_3, 7
	xorwf	parity_r, f		;parity for bit h
	btfsc	data_in_sec_3, 5
	xorwf	parity_r, f		;parity for bit i
	btfsc	data_in_sec_3, 4
	xorwf	parity_r, f		;parity for bit j
	btfsc	data_in_sec_3, 2
	xorwf	parity_r, f		;parity for bit e
	btfsc	data_in_sec_3, 0
	xorwf	parity_r, f		;parity for bit k

	btfsc	parity_r, parity
	goto	sec_data_fail		;this might be just a return

;The bit to send immediately is set if the data has changed

	movf	data_in_sec_1, w
	xorwf	data_sec_1, w
	btfss	status, zero
	bsf 	send_now_r, send_now	
	
	movf	data_in_sec_2, w
	xorwf	data_sec_2, w
	btfss	status, zero
	bsf 	send_now_r, send_now	

	movf	data_in_sec_3, w
	xorwf	data_sec_3, w
	btfss	status, zero
	bsf 	send_now_r, send_now	

;Now the data is considered good so we can copy it to where it gets used

	movf	data_in_sec_1, w
	movwf	data_sec_1
	movf	data_in_sec_2, w
	movwf	data_sec_2
	movf	data_in_sec_3, w
	movwf	data_sec_3

sec_data_fail
	return
 
Thank you very much Diver300 for your effort. I am however actually trying to do this in C language.

I need one serial port with interrupt enabled (which can be the hardware UART) and the other ones (software based) can be based on polling.

The above assembly code seems to be like an RTOS isn't it?
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top