View previous topic :: View next topic |
Author |
Message |
kgng97ccs
Joined: 02 Apr 2022 Posts: 97
|
Purging UART data in the receive buffer |
Posted: Thu Apr 21, 2022 9:20 pm |
|
|
I will be using a PIC18LF46K22 MCU, and CCS PCWHD compiler v5.078 on MPLAB IDE v8.92.
I am planning to do the following:
1. Set up an ISR to read data from UART1 and enable int_rda interrupt to receive the data [command: enable_interrupts(int_rda);].
2. After reading the expected number of bytes, disable int_rda interrupt and process the data [command: disable_interrupts(int_rda);].
Questions:
Q1. After disabling the int_rda interrupt, how can I purge whatever data (which might be noise) that might be in the receive buffer, so that I know I will be receiving new data when I re-enable the interrupt in step 3?
Q2. After disabling the int_rda interrupt, do I need to clear the interrupt flag, that is, issue a command “clear_interrupt(int_rda);”?
3. Re-enable int_rda interrupt to receive new data only after the processing is complete [command: enable_interrupts(int_rda);].
I will appreciate any advice or suggestion. Thank you. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19369
|
|
Posted: Fri Apr 22, 2022 1:07 am |
|
|
Yes, and partially yes.
All you do is read the serial, while there is data.
So:
Code: |
//generate a 'wake_uart' routine like:
#byte U1RXREG=getenv("SFR:RCREG1")
#bit OERR=getenv("bit:OERR")
#bit FERR=getenv("bit:FERR")
#bit CREN=getenv("bit:CREN")
void wake_uart(void)
{
int dummy;
while (interrupt_active(INT_RDA))
dummy=U1RXREG;
if (OERR)
{
CREN=0; //disable receive
OERR=0;
CREN=1; //re-enable
}
if (FERR)
FERR=0;
clear_interrupt (INT_RDA);
enable_interrupts(INT_RDA); //serial receive
}
|
This flushes anything in the UART hardware buffer and then enables the
interrupt. If more data has arrived than the hardware buffer can hold
OERR will be set, and if it was noise, then FERR may well be set. All of
these can only be cleared after the affected character(s) have been
read. OERR, requires the UART receive enable to be 'blipped' off to
clear.
Call this at the start of your code when you first want to enable the
interrupt, and then again when you want to re-enable.
This uses direct access to the registers, so won't result in an
'interrupts disabled to prevent re-entrancy' error with the UART
read in the interrupt routine. |
|
![](templates/subSilver/images/spacer.gif) |
kgng97ccs
Joined: 02 Apr 2022 Posts: 97
|
|
Posted: Fri Apr 22, 2022 4:45 am |
|
|
Thank you, Ttelmah, for the code.
Is it correct that this "wake_uart()" function will affect both UART1 and UART2?
If I wish to do this data flushing only for UART1, is that possible? |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Fri Apr 22, 2022 6:18 am |
|
|
One question: what if you happen to process your data and re-enable interrupt right when new data is already coming in? In that case you'd have noise in your buffer anyway. Can you afford to loose it or are the messages repeated? How is the incoming data structured? Does it have some combination of characters that are always the same at the start? |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Fri Apr 22, 2022 10:35 am |
|
|
What I'm getting at is: why disabling serial interrupt at all? With let's say 20MHz clock one instruction takes 0,2us. Serial communication is much, much slower, by orders of magnitude. Maybe your incoming data is 8 bytes long, which would mean 206us at 38.400 for every byte or 2,1ms for the message (didn't go through Excel, just copied the data from a website). Then the messages aren't just following one another without a pause. If you compare instruction time with the message time, you have more than 10.000 machine cycles (minus the time you spend inside various interrupts) at your disposal to process the data. A lot. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19369
|
|
Posted: Fri Apr 22, 2022 11:10 am |
|
|
What is posted is only for UART1. You were referring to INT_RDA.
To make a version for UART2, you would have to create new byte and
bit defines for this.
Have to agree, just leav the UART enabled, and have a flag to say
to drop the data. |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Fri Apr 22, 2022 11:30 am |
|
|
Record new "good" data. Drop only if you are not finished with processing of the old, at the end of recording. Which would in any case mean either a too slow clock, some fault in the structure of the program or how it handles the data. |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Fri Apr 22, 2022 12:05 pm |
|
|
All academic questions and answers, of course. If you need a specific answer, you need to show some code. What are you expecting to receive? How do you parse it? What has to be done when you get the correct message? Mr. Ttelmah gave you an answer of how to flush the buffer, but you needed it flushed for a reason. |
|
![](templates/subSilver/images/spacer.gif) |
kgng97ccs
Joined: 02 Apr 2022 Posts: 97
|
|
Posted: Sat Apr 23, 2022 1:58 am |
|
|
Thank you, PrinceNai and Ttelmah, for your comments.
Here is my application:
1. A master processor transmits a few bytes of data to UART1 of PIC18LF46K22 (secondary processor) at intervals determined by the master processor. The number of bytes may vary from transmission to transmission (no fixed number or pattern).
2. Each time an incoming byte triggers a UART1 interrupt on the PIC18, the PIC18 reads the byte. The PIC18 then reads the next byte when the interrupt is triggered again, and so on.
3. A while(1) loop in the main() routine checks whether UART1 data is still coming in and whether it has reached a preset maximum number of bytes. Once the loop has determined that no more data is coming in or the maximum number of bytes has been reached, the UART1 interrupt is disabled, and the routine starts to process the data. The processing incudes checking the validity of the data.
4. Upon completing the processing, the UART1 interrupt flag is cleared and the UART1 interrupt is re-enabled to receive new data.
As PrinceNai has also suggested, there is always a chance that at the time the UART1 interrupt is re-enabled, there could be data that has already started coming in to UART1. If this happens, the data validity check later will eventually fail.
I am thinking that if I can totally clear the UART1 receive buffer and interrupt flag just before I re-enable the UART1 interrupt to receive new data, the chances of receiving old data waiting in the receive buffer of UART1 will be reduced. I am not sure whether this thinking is correct.
One reason I am thinking of flushing the receive buffer of just the UART1 is to provide for the possibility of using UART2 for purposes that may not need such flushing.
My current code, before incorporating Ttelmah’s suggested code, goes like this:
Code: |
#include "18LF46K22.h"
#fuses MCLR ECH_IO
#use delay(xtal=16MHz)
#use rs232(UART1, baud=115200, errors, stream=UART1)
#define UART1_data_rx_arr_sz 20
/* Declare global variables */
unsigned int8 UART1_data_rx_ui8g[UART1_data_rx_arr_sz]; /* for storing UART1 data */
int1 UART1_data_rx_flag_i1g=0; /* 1 means there is data in UART1_data_rx_ui8g[] */
unsigned int8 UART1_byte_rx_ct_ui8g=0; /* for counting bytes received from UART1 */
void main()
{
unsigned int8 previous_UART1_byte_rx_ct;
...
/* Enable interrupts */
enable_interrupts(int_rda);
enable_interrupts(global);
while (1)
{
/* Process data only if there is data in the UART1 data array and no
more incoming bytes for at least 200 us */
if (UART1_data_rx_flag_i1g == 1) {
while (1)
{
if (UART1_byte_rx_ct_ui8g == UART1_data_rx_arr_sz) break;
previous_UART1_byte_rx_ct = UART1_byte_rx_ct_ui8g;
delay_us(100);
if (UART1_byte_rx_ct_ui8g == previous_UART1_byte_rx_ct) {
delay_us(200);
if (UART1_byte_rx_ct_ui8g == previous_UART1_byte_rx_ct) break;
}
}
/* Disable UART1 interrupt to allow processing of data to complete
before receiving new UART1 data */
disable_interrupts(int_rda);
/* Process UART1 data, including checking validity of data */
...
}
/* Clear UART1 interupt flag and re-enable UART1 interrupt */
clear_interrupt(int_rda);
enable_interrupts(int_rda);
/* Return to main while (1) loop */
}
}
#int_rda
void UART1_read_one_byte_isr()
{
if (kbhit(UART1)==0) return;
UART1_data_rx_ui8g[UART1_byte_rx_ct_ui8g] = fgetc(UART1);
UART1_data_rx_flag_i1g = 1; /* 1 means there is UART1 data in array */
UART1_byte_rx_ct_ui8g++;
/* Stop reading data if number of bytes is equal to maximum array size, and
disable UART1 interrupt to allow processing of data to complete before
receiving new UART1 data */
if (UART1_byte_rx_ct_ui8g == UART1_data_rx_arr_sz)
disable_interrupts(int_rda);
}
|
Again, I will appreciate any comments or suggestions. Communicating on this CCS forum has been a great educational experience.
Last edited by kgng97ccs on Wed May 18, 2022 7:55 pm; edited 1 time in total |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19369
|
|
Posted: Sat Apr 23, 2022 9:59 am |
|
|
Don't
Just have two buffers. Have the UART always receiving into the 'unused'
buffer. When the 'full' condition is met, switch buffers and tell the code
to interpret the data in the buffer that has just been used. |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Sat Apr 23, 2022 10:16 am |
|
|
I happen to have a similar project going on. The way I did it is a bit different, no delays anywhere, everything is taken care of inside RDA and TIMER0 interrupts. The idea is to record all incoming data into a buffer. When you detect the line is idle and data is in there, parse the data in the buffer anyway you want. It always starts at position 0 in the buffer. I don't know the frequency with which master sends data, but it can't be too fast, speaking in processor terms. I'm too lazy to change the names to your nomenclature, but you'll get the idea.
Code: |
// ****************************************************************************
// INTERRUPTS
// ****************************************************************************
#INT_RDA
void RDA_isr(void)
{
tmp=getc(PORT1); // get received char and thus also clear interrupt flag
Have_Data = 1; // since the length of the message is unknown, indicate that data is present after first received byte
// fill all of the incomming data into a buffer that is bigger than the maximum data size you expect from the master, starting from position 0
Test_Buffer[Test_Buffer_Pointer]= tmp; // move received char to the appropriate place in buffer.
Test_Buffer_Pointer++; // increment next_in pointer
if(Test_Buffer_Pointer == Test_Buffer_Size - 1) { // prevent rollover. Numbering in the buffer begins with 0
Test_Buffer_Pointer = 0;
}
set_timer0(0); // reset timer and counter detecting idle time on the RX line of the PIC
RX_Idle_Timer = 0; // to determine if the line is idle for long enough
}
// ...........................................................................
#INT_TIMER0 // fires every 16ms (but can be whatever), increments and compares RX line idle counter with minimum required idle time. In my case 160ms
void TIMER0_isr(void)
{
RX_Idle_Timer++; // increase counter every 16ms
if(RX_Idle_Timer > MIN_RX_LINE_IDLE) // the point is to detect if the RX line is idle
{ // for a certain period of time (let's say 160ms)
RX_Idle_Timer = MIN_RX_LINE_IDLE;
}
}
|
main:
Code: |
if((Have_Data) && (RX_Idle_Timer >= MIN_RX_LINE_IDLE)){
Have_Data = 0; // prepare for next transmission
Test_Buffer_Pointer = 0;
// process your data here
}
|
|
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Sat Apr 23, 2022 10:42 am |
|
|
Ttelmah, I wanted to say that there is the code with two buffers floating around, it was last posted like two months ago. I have a question: if parsing code isn't fast enough to process one buffer in time, won't that eventually catch also the second one? |
|
![](templates/subSilver/images/spacer.gif) |
PrinceNai
Joined: 31 Oct 2016 Posts: 463 Location: Montenegro
|
|
Posted: Sat Apr 23, 2022 11:09 am |
|
|
One thing to mention: if master doesn't output null at the end of transmission, you'll have to manually insert it in the buffer when you detect idle line to be able to use any string related functions. Otherwise you'll have garbage there. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19369
|
|
Posted: Sat Apr 23, 2022 1:21 pm |
|
|
Given that serial is normally something like 1mSec/char, you have tens
of thousands of instruction times to parse each character.
I run parsers running at 250Kbaud and can still merrily keep up. |
|
![](templates/subSilver/images/spacer.gif) |
kgng97ccs
Joined: 02 Apr 2022 Posts: 97
|
|
Posted: Sun Apr 24, 2022 2:52 am |
|
|
Thank you, Ttelmah and PrinceNai. I will study your suggestions and code. Really appreciate your help. |
|
![](templates/subSilver/images/spacer.gif) |
|