CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

dsPIC Buffered Serial TX - INT_TBE

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
SamMeredith



Joined: 13 Jul 2018
Posts: 23

View user's profile Send private message

dsPIC Buffered Serial TX - INT_TBE
PostPosted: Wed Jul 07, 2021 2:57 am     Reply with quote

I'm trying to set up buffered serial TX on a dsPIC33EP512MC806.

I initially used ex_STISR.c but found that the TBE interrupt only fires once.
I found this post on the forum which suggests an alternative isr and indeed this works, but I don't understand why...

Code:

#include <33EP512MC806.h>

#use delay( crystal=12Mhz, clock=120Mhz, AUX:clock=48Mhz )

#pin_select U1TX=PIN_F2
#pin_select U1RX=PIN_B12

#use rs232( UART1, baud=115200, parity=N, bits=8, errors, TIMEOUT=1, stream=OUT )

#pragma module

#define TX_BUFFER_SIZE   39

BYTE tx_buffer[TX_BUFFER_SIZE];
BYTE tx_next_in = 0;
volatile BYTE tx_next_out = 0;

//------------------------------------------------------
//
//------------------------------------------------------
void bputc(char c)
{
   short restart;
    int ni;

    restart = tx_next_in == tx_next_out;
    tx_buffer[tx_next_in] = c;
    ni = (tx_next_in + 1) % TX_BUFFER_SIZE;
    // Hold while buffer full
    while (ni == tx_next_out);
    tx_next_in = ni;

    if (restart)
        // Re-trigger the interrupt if we've stopped sending
        enable_interrupts(INT_TBE);
}

//------------------------------------------------------
//
//------------------------------------------------------
int main( int argc, char* argv[] )
{
    delay_us(100); // Startup delay
   
   printf(bputc, "Hello World");

   while(TRUE) {};

    return 0;
}

//------------------------------------------------------
//
//------------------------------------------------------
#int_tbe
void tx_isr()
{
   // ISR Copied from EX_STISR.c
   /*if (tx_next_in != tx_next_out)
   {
      fputc(tx_buffer[tx_next_out], OUT);
      tx_next_out = (tx_next_out + 1) % TX_BUFFER_SIZE;
   }*/

   // ISR by FvM
   // Taken from https://www.ccsinfo.com/forum/viewtopic.php?t=39315
    fputc(tx_buffer[tx_next_out], OUT);
    tx_next_out = (tx_next_out + 1) % TX_BUFFER_SIZE;
    if (tx_next_in == tx_next_out)
        disable_interrupts(INT_TBE);
}


Compiled with V5.101

Could anyone explain why TX works as expected with the above ISR, but not with the (commented) ISR copied from EX_STISR.c?
Ttelmah



Joined: 11 Mar 2010
Posts: 19539

View user's profile Send private message

PostPosted: Wed Jul 07, 2021 6:18 am     Reply with quote

That unfortunately, has the error, that it should only ever be used with
binary buffer sizes. Otherwise if you use a byte sized division anywhere
else in the code, you will get a warning that interrupts have been disabled
to prevent re-entrancy.
It also can have issues if the interrupt triggers while it is in the bputc
routine. This can result in the counters getting miscalculated.
What I use is:
Code:

#define T_BUFF_SIZE 128
uint16_t ddata[T_BUFF_SIZE];
int din=0;
int dout=0;

#bit U1TXIF = getenv("BIT:U1TXIF") //for the UART involved

#INT_TBE //for whichever TX interrupt is involved
void send_char(void)
{
   fputc(ddata[dout++],DISPLAY);
   if (dout==T_BUFF_SIZE)
      dout=0;
   if(din==dout) //If same after transmission
      disable_interrupts(INT_TBE); //buffer empty   
}

void bputc(uint16_t chr)
{
   //interrupt driven TX
   short restart; //flag to restart TX
   disable_interrupts(INT_TBE);
   restart=(din==dout); //if buffer was empty must restart
   ddata[din++]=chr;
   if (din==T_BUFF_SIZE)
      din=0; //handle buffer wrap
   if(restart)
      U1TXIF = 1;  //force interrupt to trigger
   enable_interrupts(INT_TBE);
}


The difference is that on these PIC's, the interrupt only triggers when
the buffer actually 'empties'. It can be cleared, and will not re-trigger
unless the buffer empties again. Now on the PIC18/16, the interrupt
cannot be cleared if the buffer is empty, so you have to disable this
interrupt, and when you want to restart, simply enabling it will trigger
immediately. On these 'senior' PIC's, you instead have to actually set
the interrupt flag.
CCS do an example for the 'PCD' PIC's ex_stisr_pcd.c
This though retains the issue that exists in their PIC16/18 transmit ISR,
that only binary buffer sizes should really be used.
It also has an issue if the actual ISR triggers inside the maths for the
counter update (I was caught by this on a high speed serial ISR on
these chips).....
My code above avoids both these issues and has proven totally reliable.
It also (since it uses int, rather than byte for the sizes), can happily handle
very large buffers. I have one system with a 2K serial buffer, running
at 1MB/sec.
SamMeredith



Joined: 13 Jul 2018
Posts: 23

View user's profile Send private message

PostPosted: Wed Jul 07, 2021 9:10 am     Reply with quote

Ttelmah wrote:
That unfortunately, has the error, that it should only ever be used with
binary buffer sizes. Otherwise if you use a byte sized division anywhere
else in the code, you will get a warning that interrupts have been disabled
to prevent re-entrancy.

I don't understand this one, if I have a non-binary-sized buffer and then perform a (entirely unrelated?) division operation elsewhere in the code, I get this warning?

Ttelmah wrote:
On these 'senior' PIC's, you instead have to actually set
the interrupt flag.

So we set U1TXIF = 1 inside bputc(), rather than just enabling the interrupt...

Ttelmah wrote:
It also has an issue if the actual ISR triggers inside the maths for the
counter update

...and disable_interrupts() while updating the counter to avoid this issue.

Understood, I'll make those changes.

Ttelmah wrote:
CCS do an example for the 'PCD' PIC's ex_stisr_pcd.c

This doesn't seem to work at all. I think an additional enable_interrupts(INT_TBE) is required in bputc(), as you have in your code.

The one thing still not clear to me is why the isr in my posted code (which does not manually set U1TXIF) seems to work?
Ttelmah



Joined: 11 Mar 2010
Posts: 19539

View user's profile Send private message

PostPosted: Wed Jul 07, 2021 9:25 am     Reply with quote

If I remember correctly, you will find that it doesn't handle sending the
'first' character without the interrupt being explicitly triggered. What
happens instead is after a few characters are loaded, the interrupt does
trigger because the internal buffer is getting full. It then merrily starts.
Because you are sending 'long' strings (several characters), this works.
Ttelmah



Joined: 11 Mar 2010
Posts: 19539

View user's profile Send private message

PostPosted: Wed Jul 07, 2021 9:26 am     Reply with quote

I wrote to CCS pointing out that fault quite a time ago. They obviously still
haven't updated the example!...
SamMeredith



Joined: 13 Jul 2018
Posts: 23

View user's profile Send private message

PostPosted: Wed Jul 07, 2021 10:38 am     Reply with quote

Ttelmah wrote:
If I remember correctly, you will find that it doesn't handle sending the
'first' character without the interrupt being explicitly triggered. What
happens instead is after a few characters are loaded, the interrupt does
trigger because the internal buffer is getting full. It then merrily starts.
Because you are sending 'long' strings (several characters), this works.

I don't think this is the case here. It does work with a single character.

I've spotted the difference in execution between the two ISRs:
When (tx_next_out == tx_next_in) the ISR taken from the forums still calls fputc() before disable_interrupts(INT_TBE).
The result being that after we've processed the entire buffer, U1TXIF is set.
So when I call printf(bputc, "HELLO WORLD")

    1. 'H' is buffered, enables TBE interrupt
    2. ISR fires (since U1TXIF is always set intially), disables TBE interrupt, clears U1TXIF
    3. 'H' is transmitted, buffer empty, sets U1TXIF
    4. 'E' is buffered, enables TBE interrupt
    5. Repeat

Finally after 'D' is transmitted, U1TXIF is set but the TBE interrupt has already been disabled.

On the other hand, the ISR from ex_stisr.c does nothing when (tx_next_out == tx_next_in).
So when I call printf(bputc, "HELLO WORLD")

    1. 'H' is buffered, enables TBE interrupt
    2. ISR fires (since U1TXIF is always set intially), clears U1TXIF
    3. 'H' is transmitted, buffer empty, sets U1TXIF
    4. ISR immediately fires again, buffer is empty so disables TBE interrupt
    5. 'E' is buffered, enables TBE interrupt but U1TXIF is not set so we stop here

So you can get away without manually setting U1TXIF, provided it never gets cleared elsewhere.
Doesn't seem very robust, I'll stick with your suggested code.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group