|
|
View previous topic :: View next topic |
Author |
Message |
SamMeredith
Joined: 13 Jul 2018 Posts: 23
|
dsPIC Buffered Serial TX - INT_TBE |
Posted: Wed Jul 07, 2021 2:57 am |
|
|
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: 19535
|
|
Posted: Wed Jul 07, 2021 6:18 am |
|
|
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
|
|
Posted: Wed Jul 07, 2021 9:10 am |
|
|
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: 19535
|
|
Posted: Wed Jul 07, 2021 9:25 am |
|
|
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: 19535
|
|
Posted: Wed Jul 07, 2021 9:26 am |
|
|
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
|
|
Posted: Wed Jul 07, 2021 10:38 am |
|
|
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. |
|
|
|
|
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
|