News:

Let's find out together what makes a PIC Tick!

Main Menu

Understanding USART-Interrupt-Buffer

Started by Dave-S, Jan 16, 2023, 01:19 PM

Previous topic - Next topic

Dave-S

Have a couple of problems and have been reading Datasheet.
Have PC Master Modbus which is connected to several slave including this Pic 18F47K40 which I am setting up. The interrupt uses EOM as discussed before, sending from Master to 18F47K40 works ok and then send back requested reg data, but the the next time the request data from the Master has at the start of it the first 3 bytes from the last reply to the master.  I thought the Transmit and receive were independent so do not understand how it is get that data. I found by putting "RXBYTE = RC1REG" seem to solve the problem.

The second problem is similar, when the Master sends a request to another slave the data is Received by 18F47K40 but the ID is incorrect as it should be and there is no EOM so does nothing but the data is not cleared and therefore the next time a genuine request to the 18F47K40 the data has part of the data sent to the other slave.   I am clearing the buffers and pointer.
RXPNTR = 0    Clear RXBUFFER   EOM_FLAG = 0   Clear ResultBuffer
Reading RC1REG does not seem to work.
How do I clear the DATA?

EOMAction:
                 RXBYTE = RC1REG    'clear HSerial buffer
                 'recieved from modbus network
                 DelayMS 200
         If  RXBUFFER[0] = 20 Then    'ID is 20
                   Length = 6
                      For x = 0 To 5
                             CRCbuffer[x] = RXBUFFER[x]   'extract data for CRC
                       Next
                   GoSub CRC_16
                   LowCRC = CRC16[0]
                   HighCRC = CRC16[1]
        If RXBUFFER[6] <> LowCRC Or RXBUFFER[7] <> HighCRC Then
                        'error  send
                        'SerOut PORTD.1, 84, ["CRC failed", 13]
                        GoTo  EOMend
                   EndIf
               If RXBUFFER[3] = 6 Then      ' reg 6 SOC/CYC Reg  SOCvalue/CYCLEvalue  requested
                      'send request back  to Modbus network information from battery
                       ResultBuffer[0] = 20   '32       'H20   'ID
                       ResultBuffer[1] = 04    ' Function
                       ResultBuffer[2] = 00       ' number of bytes
                       ResultBuffer[3] = 04       ' number of bytes
                       ResultBuffer[4] = SOCvalue.HighByte
                       ResultBuffer[5] = SOCvalue.LowByte
                       ResultBuffer[6] = CYCLEvalue.HighByte
                       ResultBuffer[7] = CYCLEvalue.LowByte
                       Length = 8
                       For x = 0 To 7
                             CRCbuffer[x] = ResultBuffer[x]  'pass data to CRC
                       Next
                       GoSub CRC_16
                      ResultBuffer[8] = CRC16[0]
                      ResultBuffer[9] = CRC16[1]
                     Dim CRCStringL As String * 3
                         CRCStringL = ResultBuffer[8]
                     Dim CRCStringH As String * 3
                         CRCStringH = ResultBuffer[9]
                    For x = 0 To 7
                        HRSOut ResultBuffer[x]
                    Next
                         HRSOut CRCStringL
                         HRSOut CRCStringH
                EndIf
         Else
              'SerOut PORTD.1, 84, ["ID = ", Dec RXBUFFER[0], 13]
             'take no action wrong ID   
         EndIf   
       RXPNTR = 0
       Clear RXBUFFER
       EOM_FLAG = 0
       Clear ResultBuffer     
             
EOMend:           
Return             
'*************************************************************
 '*******************************************************************************
ISR_Handler:
    Context Save FSR1L, FSR1H  'Save any compiler system variables and any SFRs used before we perform the

interrupt code
' Check the USART1 RX interrupt
     If RXIF = 1 Then
            If RXPNTR = RX_BUFFERSIZE Then   ' INCREMEMENT POINTER
                 RXBYTE = RC1REG             ' LOAD BYTE INTO ARRAY
                 RXPNTR = 0
                 GoTo RX_FULL
            EndIf
              RXBYTE = RC1REG
              RXBUFFER[RXPNTR] = RXBYTE
              Inc RXPNTR
          If  RXBYTE = EOM Then
               Dec RXPNTR
               EOM_FLAG = 1     ' WE HAVE BYTE IN THE BUFFER
               RXIF = 0
               GoTo EXIT_INT
         EndIf
    EndIf

RX_FULL:
   

EXIT_INT:
    Context Restore  ' Restore any compiler system variables and SFRs, then exit the interrupt routine
 '*********************************************************************************

Thanks for any help
David

RGV250

Hi Dave,
It probably will not help but I had a lot of issues with Modbus (ESP32) and used a software emulator which helped with the string I was sending / receiving.
https://www.simplymodbus.ca/index.html it is free for around 6 messages and then you need to restart but I found it very useful. I will probably buy it if I need it again.

Bob

tumbleweed

QuoteI found by putting "RXBYTE = RC1REG" seem to solve the problem.
Get rid of that. All it's doing is masking another problem. You can get rid of the 'RXIF = 0' in the ISR as well since RXIF is a read-only flag and is only cleared when the RCREG is read.

You're going to have all sorts of issues with that code trying to access variables used by the ISR in your main program... you can't access RXPNTR and RXBUFFER like that with the RX interrupt enabled.

You should look into using a proper circular buffer for the RX interrupt. That way you can get rid of the EOM flag in the ISR and stop trying to mess around with the RXPNTR in your main code. You should never have to clear the RXBUFFER... you just read bytes out of it and put them into a local buffer until you detect the EOM byte.

Dave-S

Thanks for the reply.
You mean to use what les posted for RX interrupt in the other post where we discussing it before then?
I do not see if the  RXPNTR is not cleared outside of the ISR how it can work.

Another question, in the Datasheet it states  "Set SKCP bit if inverted Transmit is desired" does this mean you do not need the Max 232?

Thanks David

tumbleweed

I think Les's last example is for both a serial RX and TX buffer.

Personally, I find a TX interrupt not worth the bother as you usually want to want until data is sent out anyway, but YMMV.
There is probably some other serial input buffer code floating around, like the "Ser_Buffer.bas" in the sample folder.

With a circular buffer, it keeps two index pointers... one for data going in and another for data going out.

You never modify those, you just use the "get_char" routine to read a byte from it once there's data available to read.
In the Ser_Buffer.bas example this is done using

Display:                                ' Dump the buffer to the LCD
    If ErrFlag  > 0 Then Error1         ' Handle error if needed
    If Index_in = Index_out Then Loop   ' loop if nothing in buffer

    GoSub GetBuf                        ' Get a character from buffer
    Print Bufchar                       ' Send the character to LCD

In your main code you build up a input command using something like this pseudo code:
rxptr = 0
while 1
    if (serial buffer has a char) then    ' check to see if isr buffer has a char
        ch = GetBuf                    ' get char from rx isr buffer and add it to ours
        mybuf[rxptr] = ch
        if (ch = EOM) then            ' got a message...
            ' code to process message in mybuf[]
            rxptr = 0               ' prepare for next message
        else
            rxptr = rxptr + 1
        endif
    endif
wend

You can add any timeouts and/or error processing, but that's the basic gist of things.

Quotein the Datasheet it states  "Set SKCP bit if inverted Transmit is desired" does this mean you do not need the Max 232?

A MAX232 does two things: it inverts the data and it also converts it to/from logic levels to +/- RS232 voltage levels. You can leave out the MAX232 only if you're directly connecting a logic-level TX->RX.

JonW

Circular buffers are great for data streaming, I use the linear buffer in the ISR as we have a known string format that is short, variable length but needs to be processed quickly after a EOM character is received and is sent very infrequently and always has a EOM character, in this use case they are perfectly adequate and code efficient in small devices. You should add Overrun checking to avoid the Uart Fifo from being blocked in the event of an overrun.  For simple comms protocols, linear buffers can work well however a circular may be better suited to your application. As mentioned above they use two (sometimes 3) pointers in a head and tail format and if they reach the top of the buffer memory space they then overlap and rewrite over the old data, hence circular. 

 





tumbleweed

As long as you're aware of all the pitfalls of ISR and mainline code interaction then a linear buffer can be ok.
I prefer not to process anything inside the ISR since everything you add complicates things (context saving, atomic access, etc).

Done properly you only need the input and output indexes (no other flags req'd), and the actual response time is not really much greater than trying to do it in the ISR (unless you actually try to process the message in the ISR, which just makes all of the above worse). There's a small latency but it should only be a few instructions.

I've done it both ways, but I always have less problems when I decouple the RX receive process from the reset of the code. Surprisingly it typically works butter for fast communications (I've run links > 460K baud) or when there's a lot of traffic on the line.

JonW

Was just looking at the Serial buffer code Tumbleweed was stating.  Does anyone know what the MIN statement does in the ISR.  Never seen that before Index_in = (Index_in - 1) Min (Buffer_size - 1)

Dompie

#8
@JONW I thought it returned the smaller of the two as a result but I can't find it anywhere. It must have been somewhere on the old forum. It's even not a Protected Compiler Word!

Johan

Edit: I believe it's better to wait on Les his comment rather than my guess

John Lawton

MIN and MAX used to be in the compiler but were removed quite some time ago.
Specifically, in Fixes and Amendments for version 3.5.4.8 of the Proton8 Compiler for 8-bit devices. (9th September 2012).

Dompie

@John Lawton Wauwwwwww, you have good administration
But removed from the manual does not mean removed from the compiler.

Johan

John Lawton

Lol, I remembered the change so looked in the Whats New.htm file.

Dompie

Ohhhh yess OLDDDDD stuf (manual 3.4.6 and my memory was right):
Max
Returns the maximum of two numbers. Its use is to limit numbers to a specific value. Its syntax is: -
' Set Var2 to the larger of Var1 and 100 (Var2 will be between 100 and 255)
Var2 = Var1 Max 100
Max does not (as yet) support Dword, or Float type variables. Therefore the highest value ob-tainable is 65535.
Min
Returns the minimum of two numbers. Its use is to limit numbers to a specific value. Its syntax is: -
' Set Var2 to the smaller of Var1 and 100 (Var2 cannot be greater than 100)
Var2 = Var1 Min 100
Min does not (as yet) support Dword, or Float type variables. Therefore the highest value ob-tainable is 65535.
Ncd
Priority encoder of a 16-bit value. Ncd takes a 16-bit value, finds the highest bit containing a 1 and returns the bit position plus one (1 through 16). If no bit is set, the input value is 0. Ncd re-turns 0. Ncd is a fast way to get an answer to the question "what is the largest power of two that this value is greater than or equal to?" The answer that Ncd returns will be that power, plus one. Example: -
Wrd1=%00011 Print Dec Ncd Wrd1 ' Display the Ncd of Wrd1(4).
001 ' Highest bit set is bit 3.
Ncd does not (as yet) support Dword, or Float type variables.

Pfffff removing some dust from my eyes
Johan