Oshonsoft programs with INTERRUPTS and PARSE

sagor

Joined Mar 10, 2019
903
C, the golden rule for any interrupt routine is to make it as short as possible. An Interrupt is to process whatever triggered it, save the bit of information (like a byte that triggered a UART receive interrupt), set a flag or status, then exit the interrupt. Your main program then checks the "flag" and then checks the saved data and only then determines if it is time to process all of it, or wait for more data.
An interrupt routine should never have things like HSEROUT, as that call alone takes more time than the interrupt itself. That is, if receiving characters at 9600 baud, an interrupt processed on byte at a time, and sending a string out simply locks up the next received byte (it is missed while sending output).
Things like string outputs should only be done in the main program. It can be interrupted as it sends, which is ok, provided the interrupt routine gets the trigger event and saves it, then sets a flag.
 

nsaspook

Joined Aug 27, 2009
13,079
C, the golden rule for any interrupt routine is to make it as short as possible. An Interrupt is to process whatever triggered it, save the bit of information (like a byte that triggered a UART receive interrupt), set a flag or status, then exit the interrupt. Your main program then checks the "flag" and then checks the saved data and only then determines if it is time to process all of it, or wait for more data.
An interrupt routine should never have things like HSEROUT, as that call alone takes more time than the interrupt itself. That is, if receiving characters at 9600 baud, an interrupt processed on byte at a time, and sending a string out simply locks up the next received byte (it is missed while sending output).
Things like string outputs should only be done in the main program. It can be interrupted as it sends, which is ok, provided the interrupt routine gets the trigger event and saves it, then sets a flag.
That's really only a golden rule for limited controllers (and limited programming languages) with a single interrupt vector. If you have at least high/low vectors it's fine to have the no-delay I/O functions in the high priority interrupt while having extensive processing in the low priority interrupt code as a task in addition to main loop processing. Proper locking for atomic operations and synchronization is necessary but that's in the game with only a single interrupt vector.

Modern controllers, even at the 8-bit level now have Vectored Interrupt Controllers for even better interrupt control with higher speed and lower I/O latency with each interrupt source having a dedicated interrupt vector address.
http://ww1.microchip.com/downloads/en/AppNotes/90003162A.pdf
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
That's really only a golden rule for limited controllers (and limited programming languages) with a single interrupt vector. If you have at least high/low vectors it's fine to have the no-delay I/O functions in the high priority interrupt while having extensive processing in the low priority interrupt code as a task in addition to main loop processing. Proper locking for atomic operations and synchronization is necessary but that's in the game with only a single interrupt vector.

Modern controllers, even at the 8-bit level now have Vectored Interrupt Controllers for even better interrupt control with higher speed and lower I/O latency with each interrupt source having a dedicated interrupt vector address.
http://ww1.microchip.com/downloads/en/AppNotes/90003162A.pdf
Hi N,
You're reply is on a much higher level, than I am capable of, I don't even know what a Vector is, but thanks for it.
C.
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
C, the golden rule for any interrupt routine is to make it as short as possible. An Interrupt is to process whatever triggered it, save the bit of information (like a byte that triggered a UART receive interrupt), set a flag or status, then exit the interrupt. Your main program then checks the "flag" and then checks the saved data and only then determines if it is time to process all of it, or wait for more data.
An interrupt routine should never have things like HSEROUT, as that call alone takes more time than the interrupt itself. That is, if receiving characters at 9600 baud, an interrupt processed on byte at a time, and sending a string out simply locks up the next received byte (it is missed while sending output).
Things like string outputs should only be done in the main program. It can be interrupted as it sends, which is ok, provided the interrupt routine gets the trigger event and saves it, then sets a flag.
Hi S,
I can see what you mean, and understand that the INTERRUPT must only be used as a trigger, then fly back to the MAIN LOOP.

Actually the INTERRUPT concept is a bit hard to understand, as it's kind of magic. I also found the hardware of a PIC, similar, in that it goes on inside the PIC, but isn't seen.

I think I can find the actual INTERRUPT trigger, by watching a simulation. I'll have a go at writing a new routine and post each step here, hoping for corrections.

At the moment the DATA is placed in a VARIABLE STRING, and I've also got to figure out how to put it in a BUFFER instead. I noticed that in the Simulation, with the UART HARDWARE box open, it shows the incoming DATA going through a BUFFER, and I also saw a reference to BUFFER + 1, which must count through it.

Thanks, C.
 

MrChips

Joined Oct 2, 2009
30,707
Hi C, why do you dumb down yourself so much?
AAC is a learning site, of members helping each other.
Take a little time and effort to understand the little bits thrown your way and you will reap the rewards.
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
Hi MrC,
It's a habit, I've got into, when I'm frustrated, that I should know something, especially, when I may have been shown before.

Actually I was teaching a mate of mine a radio menu, and he has the same habit:)

I've been checking and I think I've found the INTERRUPT TRIGGER:
-----------------------------------------
If PIR1.RCIF = 1 Then nxt_rxin:
-------------------------------------
Next to figure out how to jump back to the MAIN LOOP.
C.
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
Hi,
I've just tried cutting the whole CODE of the INTERRUPT routine apart from, what I think is the trigger.

It compiles, and when a $Sentence is put in the Hardware UART it interrupts and jumps into the MAIN LOOP, and fills the STR1 VARIABLE with all of the digits.

Somethings not right, but am I on the right lines?
C
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
Hi,
With my limited programming skills, instead of what has been suggested, that is too difficult for me, I've made most of #3 INTERRUPT into a SUBROUTINE, apart from the trigger.
Does this look ok for now?
C.
Here:
---------------------------------------------------------------------------

Goto main (LOOP)
End

On High Interrupt
Save System

'Await GPS RXD -----------------GPS GNRMC ----------------------
''''''PASTE: $GNRMC,123519,A,4807.038,N,01131.000,W,022.4,084.4,230394,003.1,W*6A?
'''OR PASTE: $REMOTE,1100,1200,1300,1400,1500,1600W,? (Nonsense, just for testing)
'''Or paste: $QEIDEG,123,?

If RCSTA.OERR = 1 Then 'BIT1..if over run error then flush RXD buffer
RCSTA.CREN = 0
RCSTA.CREN = 1 'ENABLES RECEIVER
char = RCREG '1
char = RCREG '2
PIR1.RCIF = 0 '0 = The EUSART receive buffer is empty
Hserout "RX Err!", CrLf
Goto skip1
Endif

If PIR1.RCIF = 1 Then
Gosub nxt_rxin
Endif

skip1:
Resume
-----------------------------------------------------------------------------------------------
nxt_rxin:
'Add timed wait and DATASWITCH here, needs thought!!!!!!!!!!!!!!
If PIR1.RCIF = 0 Then Goto nxt_rxin
Hserin char
If char = "?" Or char = 0x0a Then Goto msg_eol
If char = "$" Then
str1(0) = "$" 'CHAR = $
rxi = 0
Endif
If str1(0) = "$" Then
str1(rxi) = char
rxi = rxi + 1
Goto nxt_rxin
If rxi > 79 Then
Hserout " OVER STR1 LIMIT", CrLf 'Over STR1() limit
rxi = 0
Endif
Endif
Goto nxt_rxin
Goto skip1

''Hserout CrLf

msg_eol:
'strtp = ""
'strpr = ""
strtx1 = ""
strtx2 = ""
strtx3 = ""
strtx4 = ""
strtx5 = ""
'str1(0) = 0 '????????????????????
strtim = ""
strlat = ""
strlong = ""
msg1 = ""

'For x = 0 To rxi 'REMOVE
'Hserout str1(x)
'Next x

Hserout CrLf, CrLf
csv = 0
'If BAUDCON.RCIDL = 1 Then'NEVER 1! in SIM
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
For txi = 1 To rxi
char = str1(txi)
strchr = Chr(char)
If strchr <> "," Then
msg1 = msg1 + strchr
Else
csv = csv + 1
If str1(5) = "C" Then '1 in DATASWITCH sequence
Gosub get_valg 'GPS
datasw = 2 'Set to next in DATASWITCH sequence
Endif
If str1(1) = "R" Then '2 in sequence
Gosub get_valr 'REMOTE
datasw = 3 'Set to next in sequence
Endif
If str1(1) = "Q" Then '3 in DATASWITCH sequence
Gosub get_valq 'QEIDEG
datasw = 1 'Set to next in sequence
Endif
Endif
Next txi
'Endif
Return
'This subr extracts the main values from the REMOTE string, into named value messages
'also converts the string to a named numeric value, for maths
'----------------------------------------------------------------------
'To avoid a framing error, you are best to switch DATASWITCH between Ascii characters
'by checking BAUDCON.RCIDL=1. If it is =1, Then the UART is idle And Not receiving any
'serial data stream.
'----------------------------------------------------------------------
''This subr extracts the main values from the QEIDEG string, into named value messages.
''also converts the string to a named numeric value, for maths.
get_valq: '$QEIDEG,359,?
'Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
qeideg = MidStr(msg1, 7, 3)
Return
msg1 = ""
Return
get_valr: 'REMOTE= ,12,20,50,? [remvolt,remalt,remdist]
''Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'If BAUDCON.RCIDL = 1 Then
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
'Endif
Select Case csv 'COMMA POSITION VALUES
Case 2
strremvolt = LeftStr(msg1, 3)
Case 3
strremalt = LeftStr(msg1, 3)
Case 4
strremdist = LeftStr(msg1, 3)
EndSelect
msg1 = ""
Return
'This subr extracts the main values from the GPS string, into named value messages
'also converts the string to a named numeric value, for maths
get_valg: 'GPS $GNRMC,123519,A,4807.038,N,01131.000,W,022.4,084.4,230394,003.1,W*6A?
''Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'If BAUDCON.RCIDL = 1 Then
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
'Endif
Select Case csv 'COMMA POSITION VALUES
Case 2
strtim = LeftStr(msg1, 2) + ":" + MidStr(msg1, 3, 2) + ":" + MidStr(msg1, 5, 2)
Case 4
strlat = LeftStr(msg1, 10) 'Number of digits in the STRING
sinlat = StrValS(strlat)
Case 6
strlong = RightStr(msg1, 11)
sinlong = StrValS(strlong)
Case Else
EndSelect
msg1 = ""
Return
 

jjw

Joined Dec 24, 2013
823
Hi,
With my limited programming skills, instead of what has been suggested, that is too difficult for me, I've made most of #3 INTERRUPT into a SUBROUTINE, apart from the trigger.
Does this look ok for now?
C.
Here:
---------------------------------------------------------------------------

Goto main (LOOP)
End

On High Interrupt
Save System

'Await GPS RXD -----------------GPS GNRMC ----------------------
''''''PASTE: $GNRMC,123519,A,4807.038,N,01131.000,W,022.4,084.4,230394,003.1,W*6A?
'''OR PASTE: $REMOTE,1100,1200,1300,1400,1500,1600W,? (Nonsense, just for testing)
'''Or paste: $QEIDEG,123,?

If RCSTA.OERR = 1 Then 'BIT1..if over run error then flush RXD buffer
RCSTA.CREN = 0
RCSTA.CREN = 1 'ENABLES RECEIVER
char = RCREG '1
char = RCREG '2
PIR1.RCIF = 0 '0 = The EUSART receive buffer is empty
Hserout "RX Err!", CrLf
Goto skip1
Endif

If PIR1.RCIF = 1 Then
Gosub nxt_rxin
Endif

skip1:
Resume
-----------------------------------------------------------------------------------------------
nxt_rxin:
'Add timed wait and DATASWITCH here, needs thought!!!!!!!!!!!!!!
If PIR1.RCIF = 0 Then Goto nxt_rxin
Hserin char
If char = "?" Or char = 0x0a Then Goto msg_eol
If char = "$" Then
str1(0) = "$" 'CHAR = $
rxi = 0
Endif
If str1(0) = "$" Then
str1(rxi) = char
rxi = rxi + 1
Goto nxt_rxin
If rxi > 79 Then
Hserout " OVER STR1 LIMIT", CrLf 'Over STR1() limit
rxi = 0
Endif
Endif
Goto nxt_rxin
Goto skip1

''Hserout CrLf

msg_eol:
'strtp = ""
'strpr = ""
strtx1 = ""
strtx2 = ""
strtx3 = ""
strtx4 = ""
strtx5 = ""
'str1(0) = 0 '????????????????????
strtim = ""
strlat = ""
strlong = ""
msg1 = ""

'For x = 0 To rxi 'REMOVE
'Hserout str1(x)
'Next x

Hserout CrLf, CrLf
csv = 0
'If BAUDCON.RCIDL = 1 Then'NEVER 1! in SIM
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
For txi = 1 To rxi
char = str1(txi)
strchr = Chr(char)
If strchr <> "," Then
msg1 = msg1 + strchr
Else
csv = csv + 1
If str1(5) = "C" Then '1 in DATASWITCH sequence
Gosub get_valg 'GPS
datasw = 2 'Set to next in DATASWITCH sequence
Endif
If str1(1) = "R" Then '2 in sequence
Gosub get_valr 'REMOTE
datasw = 3 'Set to next in sequence
Endif
If str1(1) = "Q" Then '3 in DATASWITCH sequence
Gosub get_valq 'QEIDEG
datasw = 1 'Set to next in sequence
Endif
Endif
Next txi
'Endif
Return
'This subr extracts the main values from the REMOTE string, into named value messages
'also converts the string to a named numeric value, for maths
'----------------------------------------------------------------------
'To avoid a framing error, you are best to switch DATASWITCH between Ascii characters
'by checking BAUDCON.RCIDL=1. If it is =1, Then the UART is idle And Not receiving any
'serial data stream.
'----------------------------------------------------------------------
''This subr extracts the main values from the QEIDEG string, into named value messages.
''also converts the string to a named numeric value, for maths.
get_valq: '$QEIDEG,359,?
'Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
qeideg = MidStr(msg1, 7, 3)
Return
msg1 = ""
Return
get_valr: 'REMOTE= ,12,20,50,? [remvolt,remalt,remdist]
''Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'If BAUDCON.RCIDL = 1 Then
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
'Endif
Select Case csv 'COMMA POSITION VALUES
Case 2
strremvolt = LeftStr(msg1, 3)
Case 3
strremalt = LeftStr(msg1, 3)
Case 4
strremdist = LeftStr(msg1, 3)
EndSelect
msg1 = ""
Return
'This subr extracts the main values from the GPS string, into named value messages
'also converts the string to a named numeric value, for maths
get_valg: 'GPS $GNRMC,123519,A,4807.038,N,01131.000,W,022.4,084.4,230394,003.1,W*6A?
''Break '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'If BAUDCON.RCIDL = 1 Then
'SET DATASWITCH: 'S0=1 S1=0 =TX4431, S0=0 S1=H =HC-12, S0=0 S1=0 =GPS
'Endif
Select Case csv 'COMMA POSITION VALUES
Case 2
strtim = LeftStr(msg1, 2) + ":" + MidStr(msg1, 3, 2) + ":" + MidStr(msg1, 5, 2)
Case 4
strlat = LeftStr(msg1, 10) 'Number of digits in the STRING
sinlat = StrValS(strlat)
Case 6
strlong = RightStr(msg1, 11)
sinlong = StrValS(strlong)
Case Else
EndSelect
msg1 = ""
Return
This still spends a lot of time in the interrupt and slows the main program.

The idea in the interrupt routine is to read one character from the USART, put it in the message buffer, and return (resume)
Of course checking of the start and end of the message should be done.

When the end of message is found, the routine sets for example
a variable msg_rdy =1
The main program checks the msg_rdy in one ore more places in the program and acts on it as necessary.
Instead of the strings the message buffer should be an array of bytes, because max length of the string sets all strings to that length and it consumes a lot memory.

For example Dim msg_buf(80) as byte

In the interrupt:
- read a char from USART
- check a start and end characters and errors.
- msg_buf ( index) = char
- index=index +1
- if end of message then msg_rdy=1
_ resume
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
This still spends a lot of time in the interrupt and slows the main program.

The idea in the interrupt routine is to read one character from the USART, put it in the message buffer, and return (resume)
Of course checking of the start and end of the message should be done.

When the end of message is found, the routine sets for example
a variable msg_rdy =1
The main program checks the msg_rdy in one ore more places in the program and acts on it as necessary.
Instead of the strings the message buffer should be an array of bytes, because max length of the string sets all strings to that length and it consumes a lot memory.

For example Dim msg_buf(80) as byte

In the interrupt:
- read a char from USART
- check a start and end characters and errors.
- msg_buf ( index) = char
- index=index +1
- if end of message then msg_rdy=1
_ resume
Hi J,
I just tried my method live, and it didn't work any better than before, so I'll have a go at your method, if I can.

How is a BUFFER different from a VARIABLE?
Does a buffer have varying length depending on the $Sentence?
Thanks, C.
 

joeyd999

Joined Jun 6, 2011
5,234
In the interrupt:
- read a char from USART
- check a start and end characters and errors.
- msg_buf ( index) = char
- index=index +1
- if end of message then msg_rdy=1
_ resume
I have no idea how this language works, but your pseudocode will clearly result in a buffer overflow.

A circular buffer must be used for async reception -- you never know when the world will send more data than the size of your buffer.

I use head and tail pointers (indices). I write to the head upon character reception, and read from the tail in the main userspace code.

It's easy to tell two conditions: data available to main and buffer full by comparing the two pointers.

I also would not look for EOM in the interrupt routine. A USART driver should be protocol agnostic. Let the main code parse the received characters.
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
I have no idea how this language works, but your pseudocode will clearly result in a buffer overflow.

A circular buffer must be used for async reception -- you never know when the world will send more data than the size of your buffer.

I use head and tail pointers (indices). I write to the head upon character reception, and read from the tail in the main userspace code.

It's easy to tell two conditions: data available to main and buffer full by comparing the two pointers.

I also would not look for EOM in the interrupt routine. A USART driver should be protocol agnostic. Let the main code parse the received characters.
Hi J3,
I thought there was buffer overflow protection in my CODE, but no matter.

I can almost understand what you suggest, but it difficult for me.

I'll try the suggestions and post them.

Thanks. c.
 

jjw

Joined Dec 24, 2013
823
Hi J,
I just tried my method live, and it didn't work any better than before, so I'll have a go at your method, if I can.

How is a BUFFER different from a VARIABLE?
Does a buffer have varying length depending on the $Sentence?
Thanks, C.
Buffer is here just a term for an array of bytes.
Can be anything, message_buffer etc.
Dim msg_buf(100) as Byte
The length is fixed, but can be made for the longest message.
 

ericgibbs

Joined Jan 29, 2010
18,766
How is a BUFFER different from a VARIABLE?
Does a buffer have varying length depending on the $Sentence?
Hi C,
We were using an array buffer back in 2015.!
Clip:
from a One GPS message capture, no Interrupt used

Dim str1(80) As Byte 'UART RXD buffer array

sync1: 'wait for a $ start of string
If PIR1.RCIF = 0 Then Goto sync1
char = RCREG
If char <> 0x24 Then Goto sync1 '$'
str1(1) = char
rxi = 2

getmsg: 'read and save GPGGA msg
If PIR1.RCIF = 0 Then Goto getmsg
char = RCREG
str1(rxi) = char
rxi = rxi + 1
If rxi > 80 Then Goto get_neo 'msg bfr over run
If char = 0x0a Then Goto eomsg ''''''''''''''''''''''''''
Goto getmsg

eomsg:
If rxi < 60 Then 'invalid msg
Goto get_neo
Endif
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
Buffer is here just a term for an array of bytes.
Can be anything, message_buffer etc.
Dim msg_buf(100) as Byte
The length is fixed, but can be made for the longest message.
Hi J,
As you will recall, I didn't write the INTERRUPT CODE, and wonder why STRINGS were used instead of BUFFERS e,g, (STR1) and likewise it needed the max length setting up.

I'll try to re-write it and substitute STR1 with msg_buf, but why? To me it seems the same.

Am I correct that as with a STRING, with a BUFFER there are as many INTERRUPTs and $Sentence digits? and if another $Sentence is behind the first then it just get's added on the end inside the BUFFER?
C.
 

Thread Starter

camerart

Joined Feb 25, 2013
3,724
Hi C,
We were using an array buffer back in 2015.!
Clip:
from a One GPS message capture, no Interrupt used

Dim str1(80) As Byte 'UART RXD buffer array

sync1: 'wait for a $ start of string
If PIR1.RCIF = 0 Then Goto sync1
char = RCREG
If char <> 0x24 Then Goto sync1 '$'
str1(1) = char
rxi = 2

getmsg: 'read and save GPGGA msg
If PIR1.RCIF = 0 Then Goto getmsg
char = RCREG
str1(rxi) = char
rxi = rxi + 1
If rxi > 80 Then Goto get_neo 'msg bfr over run
If char = 0x0a Then Goto eomsg ''''''''''''''''''''''''''
Goto getmsg

eomsg:
If rxi < 60 Then 'invalid msg
Goto get_neo
Endif
Hi E,
Ah so! Is the fact that STRINGS are being used a result of my programming speed and it was kind of left over and?
C.
 

jjw

Joined Dec 24, 2013
823
Hi J,
As you will recall, I didn't write the INTERRUPT CODE, and wonder why STRINGS were used instead of BUFFERS e,g, (STR1) and likewise it needed the max length setting up.

I'll try to re-write it and substitute STR1 with msg_buf, but why? To me it seems the same.

Am I correct that as with a STRING, with a BUFFER there are as many INTERRUPTs and $Sentence digits? and if another $Sentence is behind the first then it just get's added on the end inside the BUFFER?
C.
Actually Str1 in#36 is not a String variable, but an array of bytes (buffer)
You can copy the message when it is complete from the buffer to the main program ( another buffer ) and start receiving the next message with interrupts and start parsing the first message or do whatever is needed.
 
Top