PDA

View Full Version : Ring buffer hardware Usart



retepsnikrep
- 15th November 2010, 21:04
Thanks to excellent examples on here I now have a ring buffer working on my 16F88 accepting a stream
of 9600 baud 12 byte data packets and squirting the bytes back out of the TX as quickly as they arrive.

That's great but i now need to manipulate the bytes in the packets before they are tramsmitted
and before they are overwritten by fresh incomming data.

It's general ideas I am after rather than example code.

Should I have a seperate RX & TX buffer and then move the bytes from one to the other
when i'm ready to transmit something.

Or should i just wait until first packet recd and whilst second packet is coming in manipulate the first packet and transmit it?

I have to drive a serial lcd in the spare time so need the interrupt driven capability.

So for example 12 byte packet arrives in 24 byte RX buffer. When first 12 byte packet has arrived and whilst second twelve bytes are arriving the first 12 bytes are manipulated as reqd and then transmitted.

There is some spare time between packets about 12ms in fact. I don't think the incomming data will cathc
up with the tx data.

Another issue is the packets have specific header bytes $87 or $AA, how can i make the ring buffer
RX routine wait until they appear before starting to recieve the following 11 bytes? I need to have th bytes in a consistent places in the buffer to manipulate them so for instance buffer position 0 always contains $87 and position 12 always contains $AA?

Can you also have an interrupt driven TX routine?

Thanks Peter

HenrikOlsson
- 16th November 2010, 06:32
Hi Peter,

If it where me and I had the RAM to spare I would probably go with a dual buffer, one for transmitting and one for receiving, and maintain "pointers" into both indicating where to get and put the next byte.

You can have a 'start of packet flag' which you set in your receive routine if the received byte is either $87 or $AA. Each time you enter the receive routine you check if the flag is set or not, if it is set you write the received byte to the buffer, if it's not set you check the byte for being $87 or $AA.

Another idea is to always write the received byte to the buffer and have a 'start of packet pointer' - a byte variable containing the packets 'start adress' in the buffer array. You then use that 'adress' as the starting point for your manipulating.

You can have a interrupt driven transmit routine. When enabled the TX interrupt fires as soon as there's room in the USARTS TX register. In the interrupt rounting you take your character from your ring buffer and stuff it into the TX-reg, increment your 'pointer' so you know which character is next, decide if you've sent everything or not and either disable the interrupt or leave it enabled. Then you exit the interrupt service routine. As soon as the USART is finished sending the character the interrupt fires again and the process repeats.

Mostly 'general' advice and not much specifics but hopefully you'll get some ideas from it - as always there are several ways to achieve the same thing.

Good luck!
/Henrik.

Ioannis
- 16th November 2010, 09:53
... As soon as the USART is finished sending the character the interrupt fires again and the process repeats.

This can be very tricky and lead to an endless loop of interrupts in case you do not have to send something for a while.

I never had a reliable Tx Interrupt routine although Darrel was more than helpful when I was trying to make a succesful Tx Int Routine.

Ioannis

HenrikOlsson
- 16th November 2010, 11:15
This can be very tricky and lead to an endless loop of interrupts in case you do not have to send something for a while.
Correct, that's why I said you need to decide if you should keep the interrupt enabled or disable it when you are in the ISR. You need a way to figure out if the character just loaded into the TX-reg was the last character to send or not. You can have an end of line character, fixed length or you need to have a variable indicating the location of the last character in the circular buffer.

You can also do it in software. Have a DataToSend flag which you set when there's data to send. The main routine checks this flag each iteration, if set AND there's room in TX-reg you GOSUB your transmit routine. The transmit routine loads one character from the buffer into the TX-reg, checks if all data is sent, if it is it resets the DataToSend flag if not it leaves it set. Then it returns to the main routine.

This aproach works OK as long as you don't have a lot of pause statements or other commands which "hangs" the MCU in which case the serial output will slow down - which might or might not matter.

Out of curiosity Ioannis, did you end up in that endless loop or what happend?

/Henrik.

Ioannis
- 16th November 2010, 13:12
It just kept on triggering the INT and after many changes (which led to a spaggeti program) I gave up.

I just used the good ol' array transmit like this: Hserout [str array\n].

When I feel like in a good mood I may try it again and see how it goes.

As for the Rx Buffer, you ideas are good.

I once tried the index pointer with a long array and no end of fle check. If characters kept coming then just over writen the previous characters in the array.

Ioannis

HenrikOlsson
- 16th November 2010, 17:29
Hi,
I just made a quick and dirty test here using DT_Ints. The Main routine waits for PortB.0 going low, when it does it enables the TX interrupt. The TX interrupt service routine extracts characters from the buffer array and puts them in the TX register of the USART. When it sees a CR it disables the interrupt.


'************************************************* ***************
'* Name : HWTX.BAS *
'* Author : Henrik Olsson *
'* Notice : Copyright (c) 2010 Henrik Olsson 2010 *
'* : All Rights NOT Reserved - use as you see fit. *
'* Date : 2010-11-16 *
'* Version : 0.0 *
'* Notes : Test for interrupt based USART transmit using *
'* : DT_ints-18 on a 18F4520 *
'************************************************* ***************
DEFINE LOADER_USED 1 ' We're using a bootloader
DEFINE OSC 20 ' 20Mhz x-tal is used
DEFINE HSER_BAUD 38400 ' Baudrate is 38400
CMCON = 7 ' Comparators OFF
ADCON1 = %00001111 ' No analog inputs
TRISC.7 = 1 ' RxPin is input
TRISC.6 = 0 ' TxPin is output
TRISB.0 = 1 ' PortB.0 is input
TxBuffer VAR BYTE[128] ' Transmit buffer array.
TxPointer VAR Byte ' Pointer into the above array
INCLUDE "DT_INTS-18.bas" ' Include Darrels interrupt engine.
INCLUDE "ReEnterPBP-18.bas" ' And the stub needed for interrupts in PBP

ASM ; Set up the USART transmit interrupt.
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TX_INT, _USART_TX, PBP, no
endm
INT_CREATE ; Creates the interrupt processor
ENDASM
Boot:
HSEROUT ["Program start",13] ' A dummy HSEROUT here to init the USART and show we're alive.

' Now load the array with something we want to send. The string is, in this case, terminated with a CR (13).
ArrayWrite TxBuffer,["This is the TX Array Buffer and we're sending it with an interupt based routine",13]

Main:
If PortB.0 = 0 then ' PortB.0 going low is what
TxPointer = 0 ' Initialize pointer to beginning of array
@ INT_ENABLE TX_INT ; Enable USART TX interrupt
Pause 100 ' Simple debounce for the button
ENDIF
Goto Main ' And do it again.

USART_TX:
TXREG = TxBuffer[TxPointer] ' Load character from array into USART TX register
If TxBuffer[TxPointer] = 13 then ' If that character was a CR we're done so we.....
@ INT_DISABLE TX_INT ; ...disable the any further USART TX interrupts.
ELSE ' If the character was not a CR we.....
TxPointer = TxPointer + 1 ' ...increase prepare to send the next character.
ENDIF
@ INT_RETURN ; And exits the interrupt.

Seems to work here, try it out if you like.

/Henrik.

retepsnikrep
- 16th November 2010, 23:05
Thanks for all the replies very useful.

Can my ring buffer RX routine below coexist with your TX routine I wonder?
Can you have two interrupt driven things going on?



'-------------------------------------
' PBP BCM CODE 8mhz 1963 Words
'-------------------------------------

'MPASM Config
@ __config _CONFIG1, _INTRC_IO & _WDT_OFF & _LVP_OFF & _MCLR_OFF & _CP_OFF
@ __config _CONFIG2, _IESO_OFF & _FCMEN_OFF

DEFINE OSC 8 'Set oscilator speed to 8mHz
OSCCON = %01111000 '8 Mhz
CMCON = %00000111 'COMPARATORS OFF
CCP1CON = 0 'COMPARATORS OFF
CVRCON = 0 'COMPARATORS OFF
INTCON = 0 'Disable interrupts
TRISB = %00000100 'SET PORTB RB2(RX) as input, others to OUTPUT
TRISA = %11110111 'SET PORTA AS INPUTS except PORTA.3

ANSEL = %00000001 'AN0 Analog Input
ADCON0 = %01000001 'Turns A/D on, AN0 Fosc/16
ADCON1 = %11000000 'Right justified and Fosc/16

'-----------------------------------------------------------------------------
INCLUDE "DT_INTS-14.bas"
INCLUDE "ReEnterPBP.bas" 'Include if using PBP interrupts

ASM
INT_LIST macro ;IntSource, Label, Type, ResetFlag?
INT_Handler RX_INT, _INT_Serin, PBP, no
endm
INT_CREATE ;Creates the interrupt processor
ENDASM

@ INT_ENABLE RX_INT ;Enable external (INT) interrupts
'-----------------------------------------------------------------------------


'Setup the hardware USART for 9600 baud

DEFINE HSER_BAUD 9600 'Set Baud rate to 9600bps
DEFINE HSER_BITS 9 'Set to 9 bit mode
DEFINE HSER_EVEN 1 'Set Even Parity
DEFINE HSER_CLROERR 1 'Clear overflow error automatically

'Debug is used as it is the fastest and smallest serial routine to drive the Lcd display

DEFINE DEBUG_REG PORTB 'Set Debug pin port B
DEFINE DEBUG_BIT 4 'Set Debug pin bit for Lcd Display PORTB.4
'DEFINE DEBUG_BAUD 38400 'Set Debug baud rate 38400 approx 3840 cps or 0.26ms per character
DEFINE DEBUG_BAUD 9600 'Set Debug baud rate 9600 approx 960 cps or 1ms per character
DEFINE DEBUG_MODE 0 'Set Debug mode: 0 = true, 1 = inverted (T=True Idle High)(N=Inverted Idle Low)
DEFINE DEBUG_PACING 1000 'Set Debug Character Pacing mode to 1ms between bytes

include "modedefs.bas" 'Allows the use of the standard serin/serout command


'Variables Constants and I/O definitions

'------------------------ Variables 16bit --------------------------------------

Amps var WORD 'Amps Word Variable
Soc var WORD 'Soc Word Variable
TempH var WORD 'High Temperature Word Variable
TempL var WORD 'Low Temperature Word Variable
Gen1 var WORD 'Gen1 General variable & Key input up/down/left/right/centre
Watts var WORD 'IMA Power in Watts (Battery Volts x Battery Amps)
MaxWatts var WORD 'Maximum IMA Power in Watts during trip
Errors var WORD 'Number of RS485 Data Checksum Errors
Packets var WORD 'Number of RS485 Data Packets

'------------------- Variables 8bit --------------------------------------------

Volts var BYTE 'Volts Byte Variable (0-255)
BCM87 var BYTE[11] 'Define BCM87 as a Byte array (12 Bytes 0-11) (BCM87 = BCM Data)
BCMAA var BYTE[11] 'Define BCMAA as a Byte array (12 Bytes 0-11) (BCMAA = BCM Data)
Cnt var BYTE 'General Byte Counter Variable (0-255)
Text var BYTE 'General Byte Text Variable (0-255)
Gen0 var BYTE 'Gen0 is a Byte variable (0-255)
Config var BYTE 'Gauge Config Byte (8 bits)
AmpSign var BYTE 'Amps Sign Byte Variable (43 = +) (45 = -)
TempHSign var BYTE 'TempH Sign Byte Variable (43 = +) (45 = -)
TempLSign var BYTE 'TempL Sign Byte Variable (43 = +) (45 = -)
CheckSum var BYTE 'BCM Data Checksum calculation Byte
MaxVolts var BYTE 'Maximum Voltage during trip
MinVolts var BYTE 'Minimum voltage during trip
MaxAsst var BYTE 'Maximum Assist Amps during trip
MaxRegen var BYTE 'Maximum Regen Amps during trip
MaxTemp var BYTE 'Maximim Temp during trip
MinTemp var BYTE 'Minimum Temp during trip
MaxSoc var BYTE 'Maximum OEM Soc during trip
MinSoc var BYTE 'Minimum OEM Soc during trip
Adr1 VAR BYTE 'Number for start of menu data
Adr2 VAR BYTE 'Number for end of menu data
Display VAR BYTE 'Display Mode 0 = Normal, 1 = Binary, 2 = Max/Min, 3 = Splash Screen


'PortA Input Variables
OverV VAR PORTA.0 'Define PortA.0 Pin as OverV (Over Voltage Input) (Low = Over V)
UnderV VAR PORTA.7 'Define PortA.7 Pin as UnderV (Under Voltage Input) (Low = Under V)

'PortA Output Variables
Spare VAR PORTA.3 'Define PortA.3 Pin as Data Out

'PORTB Input Variables
BcmIn Var PORTB.2 'Define PortB.2 Pins as RS485 In

'PORTB Ouput Variables
Led1 VAR PORTB.0 'Define PortB.0 Pin as Led1
Led2 VAR PORTB.1 'Define PortB.1 Pin as Led2
Piezo VAR PORTB.3 'Define PortB.3 Pin as Piezo
BcmOut Var PORTB.5 'Define PortB.5 Pins as RS485 Out

;-- Place a copy of these variables in your Main program -------------------
;-- The compiler will tell you which lines to un-comment --
;-- Do Not un-comment these lines --
;---------------------------------------------------------------------------

;wsave VAR BYTE $20 SYSTEM ' location for W if in bank0
;wsave VAR BYTE $70 SYSTEM ' alternate save location for W
' if using $70, comment wsave1-3

' --- IF any of these three lines cause an error ?? ------------------------
' Comment them out to fix the problem ----
' -- Which variables are needed, depends on the Chip you are using --
;wsave1 VAR BYTE $A0 SYSTEM ' location for W if in bank1
;wsave2 VAR BYTE $120 SYSTEM ' location for W if in bank2
;wsave3 VAR BYTE $1A0 SYSTEM ' location for W if in bank3


'************************************************* **********************


'Alias & Vars
RCIF VAR PIR1.5 'Receive interrupt flag (1=full , 0=empty)
TXIF VAR PIR1.4 'Transmit interrupt flag (1=empty, 0=full)
OERR VAR RCSTA.1 'Alias OERR (USART Overrun Error Flag)
CREN VAR RCSTA.4 'Alias CREN (USART Continuous Receive Enable)
BufMax CON 48 'Sets the size of the ring buffer
Buffer VAR BYTE[BufMax] 'Array variable for holding received characters
Ptr_in VAR BYTE 'Pointer - next empty location in buffer
Ptr_out VAR BYTE 'Pointer - location of oldest character in buffer
Bufchar VAR BYTE 'Stores the character retrieved from the buffer
ErrFlag VAR BYTE 'Holds error flags
ptr_in = 0 'Clear buffer pointer (in)
ptr_out = 0 'Clear buffer pointer (out)
Errflag = 0 'Clear error flag


'************************************************* **********************

'Skip over the interupt handler & subroutines

debug 254,1 'Clear Screen
goto MainLoop

'************************************************* **********************

'-------------------------------------
' INTERUPT HANDLER
'-------------------------------------

INT_Serin: 'Serial Interrupt routine

IF OERR Then usart_error 'Check for USART errors
ptr_in = (ptr_in + 1) 'Increment ptr_in pointer (0 to 63)
IF ptr_in > (BufMax-1) Then ptr_in = 0 'Reset pointer if outside of buffer
IF ptr_in = ptr_out Then Buffer_Error 'Check for buffer overrun
HSerin [buffer[ptr_in]] 'Read USART and store data in next empty location
IF RCIF Then INT_Serin 'Check for another character while we're here

@ INT_RETURN ;Return to program

Buffer_Error: 'Error - Run out of buffer space

errflag.1 = 1 'Set the error flag for software

'Move pointer back to avoid corrupting the buffer.
'MIN insures that it ends up within the buffer.

ptr_in = (ptr_in - 1) MIN (BufMax - 1)
HSerin [buffer[ptr_in]] 'Overwrite the last data stored (clear the int.)

USART_Error: 'Error - USART has overrun (data too fast?)

errflag.0 = 1 'Set the error flag for hardware

@ INT_RETURN ;Return to program


'-------------------------------------
' Subroutines
'-------------------------------------

GetBuf: 'Move the next character in buffer to bufchar

@ INT_DISABLE RX_INT ;Dont want to interupt this, so Disable interupts

ptr_out = (ptr_out + 1) 'Increment ptr_out pointer (0 to 63)
IF ptr_out > (BufMax-1) Then ptr_out = 0 'Reset pointer if outside of buffer
bufchar = buffer[ptr_out] 'Read buffer location

@ INT_ENABLE RX_INT ;All done, Enable interupts

Return


'-------------------------------------
' Error Handler
'-------------------------------------

ErrorHandler: 'Display error message if buffer has overrun

IF errflag.1 Then 'Determine the error
DEBUG 254,128,"Buffer Overflow" 'Display buffer error
Else
DEBUG 254,128,"USART Overrun" 'Display usart error
EndIF

errflag = 0 'Reset the error flag
CREN = 0 'Disable continuous receive to clear overrun flag
CREN = 1 'Enable continuous receive

GoTo MainLoop 'Errors cleared, time to work.

'-------------------------------------
' RS485 Passthru Test
'-------------------------------------

MainLoop:

IF errflag Then ErrorHandler 'Handle error if needed
IF ptr_in = ptr_out Then MainLoop 'loop if nothing in buffer

GoSub GetBuf 'Get a character from buffer (stored in bufchar)
HSEROUT [BufChar] 'Send the character

goto MainLoop 'Loop forever


END

HenrikOlsson
- 17th November 2010, 06:11
Hi,
Yes, you can have multiple interrupts and I can't see why the transmit interrupt routine wouldn't work with your code. Just remember that you need a way to determine when the last character has been sent. In my example I used the CR character but as said before it might be better to maintain a pointer (like you are doing in the receive buffer) and stopping after n number of bytes.

/Henrik.

Ioannis
- 11th December 2010, 19:15
Hi Henrik.

Thanks for the snippet. I test it on a 877 at 4MHz and works very good. As expected...

My mistake when I tried the TX interrupts must have been the Reset flag that I was set it to YES.

Ioannis