PDA

View Full Version : ASM help



Charles Linquis
- 18th October 2009, 17:32
I'm trying to re-create a hardware serial port with pure software. I need a non-blocking RECEIVE port because I need to add a 3rd port to my 8723.

Most of the interrupt-driven routines I have seen have a very large processor overhead because they sample all the time and because they sample often so as to get the bit timing right.

I'm trying an approach that uses INT0. It watches for the first falling edge of the START bit, then starts a TMR4 interrupt to time the rest of the bits. Using this approach should use zero processing power when a byte isn't coming in, and not too many cycles when one is. INT0 is turned on only to detect the falling edge of the START bit. TMR4 int is turned on only when receiving bits.

The trouble is - the following code always returns 0xff whenever I send it a character. Can someone tell me what I'm doing wrong? RxD is coming through a MAX232 and is fed to PORTB.0



T4CON = %00000110 ; T4 ON, /16 prescale, no postscalers
INTCON = %11000000 ; Global Int enable, peripheral INT enable
INTCON2 = %00000000

PIE3.3 = 0 ;Turn off Int on tmr 4
IPR3.3 =1 ; High priority for INT4


bit_cntr VAR BYTE
rcv_byte VAR BYTE
byteready VAR BYTE
rcv_buffer VAR BYTE
;-----------------------------------------------------------------------

INCLUDE "DT_INTS-18.bas" ; Base Interrupt System


ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler INT0_INT, GetStart, ASM, no
INT_Handler TMR4_INT, BitTimer, ASM, no

endm
INT_CREATE ; Creates the interrupt processor
ENDASM

Goto OverInt

'---[INT - interrupt handler]---------------------------------------------------

ASM
GetStart
bcf T4CON,2 ; Turn off Timer 4
movlw 0x97 ; w /16 prescaler gives 156uSec dly @ 40Mhz = 1.5 bit times
movwf TMR4
bsf T4CON,2 ; Turn timer 4 on
bsf PIE3,3 ; turn on the Timer4 INT
movlw 0x08 ; load the bit counter
movwf _bit_cntr
bcf INTCON,4 ; shut off the INT0 interrupt
bcf INTCON,1 ; clear INT0 int flag bit
INT_RETURN
ENDASM

ASM
BitTimer
bcf T4CON,2 ; Turn Timer4 off
movlw 0xBF ; 104uSec / 16, 40 Mhz 1 bit time
movwf TMR4 ; load it
bsf T4CON,2 ; Turn it back on
btfss PORTB,0 ; check status of portB.0
bcf STATUS,C ; move bit into carry
btfsc PORTB,0 ; check status of portb.0
bsf STATUS,C ; move bit into carry
rrcf _rcv_byte,f ; shift carry into rec'd byte
decfsz _bit_cntr,f ; decrement the bit counter
bra Donebit ; get out and wait for next int
bra Donebyte ; got 8 bits

Donebit
bcf PIR3,3 ; clear the int flag
bsf PIE3,3 ; start the TMR4 int again
INT_RETURN

Donebyte
movff _rcv_byte,_rcv_buffer
movff _rcv_buffer,PORTD ; this is a debug, just to see rec'd byte on LEDS
bcf PIR3,3 ; clear the int flag
bsf INTCON,4 ; turn on the INT0 interrupt for next byte
INT_RETURN
ENDASM


OVERINT:

TRISD = 0
PORTD = 0
TRISE = 0
INTCON.1 = 0
PIE3.3 = 0
PIR3.3 = 0

PAUSE 100

@ INT_ENABLE INT0_INT


WAITT:

GOTO WAITT

Jerson
- 18th October 2009, 18:03
You need to consider the interrupt latency along with the bit time and adjust the timer accordingly. Look at the generated listing to know how much it is. Another improvement is here



BitTimer
bcf T4CON,2 ; Turn Timer4 off
movlw 0xBF ; 104uSec / 16, 40 Mhz 1 bit time
movwf TMR4 ; load it
bsf T4CON,2 ; Turn it back on
bsf STATUS,C ; move bit into carry
btfss PORTB,0 ; check status of portB.0
bcf STATUS,C ; move bit into carry
rrcf _rcv_byte,f ; shift carry into rec'd byte
decfsz _bit_cntr,f ; decrement the bit counter
bra Donebit ; get out and wait for next int
bra Donebyte ; got 8 bits

Charles Linquis
- 18th October 2009, 18:41
I understand your improvement, and I realized that I hadn't considered latency, but at 40Mhz, it is only a few uSec.

Even with a small timing error, it should get the first few bits right - but doesn't!

Darrel Taylor
- 18th October 2009, 21:19
In Donebyte, before re-enabling the INT0 interrupt, it clears the Timer4 flag.
INT0IF will still be set and you'll get a false start when there's no data.
bcf PIR3,3 ; clear the int flag
bsf INTCON,4 ; turn on the INT0 interrupt for next byte


And I think the 1-1/2 bit time is off.
97 would be right if it were decimal, but it's hex.
Try it with 0x61.


Knowing you, this is probably part of a much bigger program with lots of variables ...
You'll probably want to put the variables used in ASM into BANK0
bit_cntr VAR BYTE BANK0
rcv_byte VAR BYTE BANK0
rcv_buffer VAR BYTE BANK0


There's really no need to stop timer4 when reloading since it's an 8-bit timer.
But adding the reload value to TMR4 instead of just loading it will cancel out any latencies.
<br>

Charles Linquis
- 18th October 2009, 22:07
Yes, this *WILL* be part of a much bigger program!

I made some changes -

I cleared INTCON.1 before I re-enabled it in the
Donebyte routine



Donebyte
movff _rcv_byte,_rcv_buffer
movff _rcv_buffer,PORTD ; this is a debug, just to see rec'd byte on LEDS
bcf PIR3,3 ; clear the int flag
bcf INTCON,1
bsf INTCON,4 ; turn on the INT0 interrupt for next byte
INT_RETURN


But that didn't help. It shouldn't have been set anyway, since I clear it
after I shut off INT0 in the
GetStart section.

I changed the TMR4 reload value to 0x61

I put the variables in BANK0

I made the change recommend by Jerson to take one line out of the setb/clrb
sequence.

Still, when I hit ANY character, all the LEDs on PORTD light up (high).

Something small still isn't right.

Darrel Taylor
- 18th October 2009, 22:40
I think you need to disable the TMR4 interrupts in Donebyte, instead of clearing the flag.

I'll try to run it here on a 4520.

No Timer4, but Timer2 should do the same thing for me.
<br>

Darrel Taylor
- 18th October 2009, 23:22
OK, got it working here ...

You do need to disable T4 ints (last post).

And I haven't figured out why yet, but it was missing a bit in each byte, so I increased _bit_cntr to 9 and now it works.

I'll play with it some more and post the final results.
<br>

Darrel Taylor
- 19th October 2009, 00:26
Here ya go ...

The 9-bit issue was due to flags not being cleared first before enabling.

I've changed it to put the bit periods in PR4 instead of TMR4, this eliminates the latencies without having to reload the timer.

Since I was using an 18F4520 (no TMR4), I made it so you can use any of the 8-bit timers that count up to PR?, TMR2, 4 or 6.

I've also changed several other things, so look carefully if you're trying to pick them out.

My debug method was sending the received byte out the USART, instead of PORTD.


DEFINE OSC 40

DEFINE RX_TMR 4 ; can be 2, 4 or 6
DEFINE TMR_INT TMR4_INT ; int must match timer selection

;RCSTA = $90 ' Enable serial port & continuous receive
;TXSTA = $20 ' Enable transmit, BRGH = 0
;SPBRG = 64 ' 9600 Baud @ 40MHz, 0.16%



bit_cntr VAR BYTE BANK0
rcv_byte VAR BYTE BANK0
byteready VAR BYTE BANK0
rcv_buffer VAR BYTE BANK0
;-----------------------------------------------------------------------

INCLUDE "DT_INTS-18.bas" ; Base Interrupt System


ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler INT0_INT, GetStart, ASM, yes
INT_Handler TMR_INT, BitTimer, ASM, yes

endm
INT_CREATE ; Creates the interrupt processor
ENDASM

Goto OverInt

'---[INT - interrupt handler]---------------------------------------------------
ASM
#define TMR_VAL TMR#v(RX_TMR)
#define TMR_CON T#v(RX_TMR)CON
#define TMR_PR PR#v(RX_TMR)
ENDASM

ASM
GetStart
movlw 0x02 ; T? OFF, /16 prescale, no postscalers
movwf TMR_CON
movlw 0x61 ; 156uSec dly @ 40Mhz = 1.5 bit times
movwf TMR_PR
clrf TMR_VAL
bsf TMR_CON,2 ; Turn timer on
INT_ENABLE TMR_INT ; turn on the Timer INT
movlw 0x08 ; load the bit counter
movwf _bit_cntr
INT_DISABLE INT0_INT ; shut off the INT0 interrupt
INT_RETURN
ENDASM

ASM
BitTimer
movlw 0x41 ; load PR2 for 1 bit period
movwf TMR_PR
btfss PORTB,0 ; check status of portB.0
bcf STATUS,C ; move bit into carry
btfsc PORTB,0 ; check status of portb.0
bsf STATUS,C ; move bit into carry
rrcf _rcv_byte,f ; shift carry into rec'd byte
decfsz _bit_cntr,f ; decrement the bit counter
bra Donebit ; get out and wait for next int
bra Donebyte ; got 8 bits
Donebit
INT_RETURN

Donebyte
movff _rcv_byte,_rcv_buffer
;TXwait
; btfss PIR1, TXIF
; bra TXwait
; movff _rcv_buffer, TXREG

movff _rcv_buffer,PORTD ; this is a debug, just to see rec'd byte on LEDS
bcf TMR_CON,2
INT_DISABLE TMR_INT
INT_CLEAR INT0_INT
INT_ENABLE INT0_INT ; turn on the INT0 interrupt for next byte
INT_RETURN
ENDASM


OVERINT:

TRISD = 0
PORTD = 0
TRISE = 0
INTCON2.6 = 0 ; int0 on falling edge

PAUSE 100

@ INT_CLEAR INT0_INT
@ INT_ENABLE INT0_INT


WAITT:

GOTO WAITT

HTH,

Charles Linquis
- 19th October 2009, 00:31
Darrel,

Thanks a million! While I don't know for sure, I'd guess that your little program is the start of something that will be really useful to all those people who post "how can I receive bytes while....... with my 16Fxxx". Tomorrow, I'll work on the SEND side and the buffering.

Darrel Taylor
- 19th October 2009, 00:37
Sure, no problem Charles,


Tomorrow, I'll work on the SEND side ...
Already done ...

Tmr_tx-18
http://www.picbasic.co.uk/forum/showpost.php?p=53931&postcount=28

Or, at least it's a good start.
<br>

Mike, K8LH
- 19th October 2009, 02:20
Charles,

Just for an additional example and reference, I posted a 9600 baud half-duplex example on PICLIST several years ago that uses Timer 2 and IOC. I also have a full-duplex demo and another half-duplex demo that uses Timer 0 that shows how to reload a free-running 8 bit timer.

Hope the examples help. Have a great day.

Regards, Mike


;************************************************* *****************
;* *
;* Filename: 12F683 Half-Duplex 9600 Demo.asm *
;* Author: Mike McLaren, K8LH (k8lh_at_arrl.net) *
;* Date: 02-Jun-05 (last revision 02-Dec-05) *
;* *
;* Half Duplex Bit-Banged 9600 Baud Serial I/O Demo *
;* (with 16-byte circular receive character buffer) *
;* *
;* ·Uses 12F683 INTOSC running at 8-MHz *
;* ·Bit rate error 0.16% plus or minus 1.0% for INTOSC *
;* ·Bit-banged 9600 baud serial I/O *
;* ·Half Duplex (should not TX and RX simultaneously) *
;* ·TMR2 interrupts at 104-usec intervals (every 208 *
;* instruction cycles) and IOC (interrupt on change) *
;* for RX start bit leading edge detection on RXPIN *
;* ·Circular 16-byte RX character buffer *
;* ·Inverted TX and RX signals (MAX232A or similar *
;* inverting RS-232 interface required) *
;* ·Relatively small - the ISR and the support routines *
;* Init232, Put232, and Get232 use 102 words of code *
;* space at locations 0004 through 0069 *
;* ·Worst case 23% ISR 'overhead' (24-usecs) when a *
;* complete RX character is added to the circular *
;* buffer once every 1.04-msecs while receiving *
;* *
;* MPLab: 7.21 (tabs=8) *
;* MPAsm: 4.02 *
;* *
;************************************************* *****************

Darrel Taylor
- 19th October 2009, 02:29
That is by far, the smallest program I've ever seen. :D
<br>

Mike, K8LH
- 19th October 2009, 03:20
Yeah, that didn't quite work out right Darrel (LOL). I'll go back and add the file...

Regards, Mike

Oops! Too late. I'll add it here (below). Sorry Gentlemen.

Charles Linquis
- 19th October 2009, 04:53
Thanks Mike,

I thought that I was the first guy who dreamed up (what I think is) a relatively elegant way to save processor cycles. I should have known better!

Mike, K8LH
- 19th October 2009, 12:42
Thanks Mike,

I thought that I was the first guy who dreamed up (what I think is) a relatively elegant way to save processor cycles. I should have known better!

You're welcome Sir. And btw, just about everything I ever ended up patting myself on the back for inventing seems to have been done before too (LOL)...

Regards, Mike

Melanie
- 19th October 2009, 15:20
But at least you have the satisfaction of INDEPENDANTLY coming up with the idea, which makes you as much the owner of that idea as anyone else who thought of it earlier or may do so later than you. Did Mr Bell or Mr Gray invent the telephone first? Personally I thought it was my kindergarden teacher with two cans of coffee and a length of string...

Charles Linquis
- 21st October 2009, 03:08
Thank you, Melanie!

You know that I will always consider you the "Princess of the PICs"!