ATtiny1616 USART TX interrupt flags: is it me or the datasheet?

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

 

I began experimenting with an ATtiny1616, writing some simple UART routines. It seems something's not right with the TXC interrupt flag.

 

If I understand the datasheet correctly, the TX module has a Data Buffer (TXDATA) which transfers its contents to a Shift Register, from which the bits are sent to the IO pin.

Now, there are two TX flags:

DREIF - set to 1 when the Data Buffer is empty (meaning we can write a byte to the buffer, even if the Shift Register is still working). This works just fine as far as I can see.

TXCIF - Set to 1 when the shift Register is done, and no further data is pending in the Data Buffer, i.e. when the IO pin returns to the idle state.

 

It is also stated for TXCIF that "This flag is automatically cleared when the transmit complete interrupt vector is executed." [emphasis mine] However, if I write the ISR like so-

 

ISR (USART0_TXC_vect) {

    // This is a status variable of my own
    _UARTTXBusy = 0;

}

 

It garbles up the rest of the transmission (my test program sent bytes sequentially). I put another command in the ISR to toggle an output pin and it toggles like crazy all the time, except for a few microseconds following a write to TXDATA. The baudrate doens't effect this - 9600, 115200, whatever.

 

However, if I ignore the datasheet and clear the flag manually (by writing 1 to it):

 

ISR (USART0_TXC_vect) {

    // Datasheet error? TXCIF has to be cleared manually?
    USART0_STATUS |= USART_TXCIF_bm;
    _UARTTXBusy = 0;

}

Suddenly it behaves as expected, raising just one interrupt event when the transmission is completed.

 

Of course the global interrupts and TX are enabled, and so are TXCIE and, while transmitting, DREIE.

 

So... 1) why is TXCIF set and causing interrupts when the UASRT is still sending? 2) Why doesn't it get cleared in the ISR as the datasheet says?

 

[ Edit: I tried to debug it live or with the Simulator in AS7, and it seems TXCIF isn't cleared automatically, but I'm not sure I'm using the debug tools correctly; for instance, they never go into ISR code unless I "Run to cursor" explicitly (edit^2: found the solution for debugging with ISRs - https://www.avrfreaks.net/forum/... :-) but doesn't help my issue ) ]

Last Edited: Sat. Aug 18, 2018 - 03:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think we'd need to see more of your code, the non-ISR stuff, to help debug this.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#define F_CPU 20000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

const uint32_t UART_BAUDRATE = 115200UL;
const uint8_t UART_ENABLE_RX = 0;
const uint8_t UART_ENABLE_TX = 1;

volatile uint8_t *UARTByteSource;
volatile uint8_t UARTBytesLeft;
volatile uint8_t _UARTTXBusy = 0;

//=========================================================
void setupClock() {

	// Unlock Configuration Change Protection
	CPU_CCP = CCP_IOREG_gc;
	// CLKOUT enabled (PB5/Pin 6), Clock source is OSC20M
	CLKCTRL_MCLKCTRLA = 0x80;
	// Unlock Configuration Change Protection
	CPU_CCP = CCP_IOREG_gc;
	// No prescaler on main clock
	CLKCTRL_MCLKCTRLB = 0x00;

}

//=========================================================
uint8_t UARTTXBusy() {
	return _UARTTXBusy;
}

//=========================================================
void setupUART_8N1(const uint32_t baudRate, const uint8_t enableRX, const uint8_t enableTX) {

	uint8_t sregBackup = CPU_SREG;

	// Recommended setup for full-duplex mode (p. 258)

	// For interrupt-driven operation, we disable global interrupts during initialization
	CPU_SREG &= ~CPU_I_bm;

	// Set TX pin to High, then set to Output
    PORTB_OUTSET = 0x04;
	PORTB_DIRSET = 0x04;

	// Set baudrate, Formula on p. 259
	USART0_BAUD = (64UL * F_CPU) / (16UL * baudRate);

	// Set operation mode: Asynchronous, N, 1, 8-bit (see p. 278)
	USART0_CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_PMODE_DISABLED_gc |
	               USART_SBMODE_1BIT_gc | USART_CHSIZE_8BIT_gc;

	if (enableRX) {
		USART0_CTRLB |= USART_RXEN_bm;
		USART0_CTRLA |= USART_RXCIE_bm;
	}

	if (enableTX) {
		USART0_CTRLB |= USART_TXEN_bm;
		USART0_CTRLA |= USART_TXCIE_bm;
	}

	// Restore SREG
	CPU_SREG = sregBackup;

}

//=========================================================
// Non-blocking, except waiting for TX
void UARTSendBytes(uint8_t *p, const uint8_t n) {

  UARTByteSource = p;
  UARTBytesLeft = n;

  while (UARTTXBusy()) {
  }

  _UARTTXBusy = 1;
  // Trigger the interrupt immediately (the flag is set when there's NO activity)
  USART0_CTRLA |= USART_DREIE_bm;  

}

//=========================================================
void setup() {

	setupClock();
	setupUART_8N1(UART_BAUDRATE, UART_ENABLE_RX, UART_ENABLE_TX);

}

//=========================================================
int main(void) {

    uint8_t str[10] = "A";

    setup();

	// Enable interrupts
	CPU_SREG |= CPU_I_bm;

	while (1) {

		_delay_ms(10);
		UARTSendBytes(str, 1);

        }

}

//=========================================================
// USART receive completed
ISR (USART0_RXC_vect) {
	// TODO
}

//=========================================================
// USART transmit buffer ready for new data
ISR (USART0_DRE_vect) {

    USART0_TXDATAL = *UARTByteSource++;
    if (0 == --UARTBytesLeft) {
	     // Disable this interrupt
	     USART0_CTRLA &= ~USART_DREIE_bm;
    }

}

//=========================================================
// USART transmission completed
ISR (USART0_TXC_vect) {

	// Datasheet error? TXCIF has to be cleared manually?
	USART0_STATUS |= USART_TXCIF_bm;
	_UARTTXBusy = 0;

}

Not easy cleaning up this mess :-)

 

As it is above, with manually clearing of TXCIF in the ISR, everything works - I get the letter "A" output on the UART every 10ms. Actual output duration is ~80us.

If I comment out the manual clear, the output is only sent once every ~125ms, as far as I can tell it's because the TXC ISR is being called over and over again.

 

[Edit: P.S. yeah, initialized a uint8_t array as string... :-D ]

Last Edited: Sat. Aug 18, 2018 - 05:53 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The one problem I see (which may or may not be causing the results you're having) is 

USART0_STATUS |= USART_TXCIF_bm;

This is not the right way to clear these write-1-to-clear flags.  What if another interrupt flag is set in the register during the RMW operation, and you inadvertently clear the pending interrupt with your |= operation (which is happening in ISR code, making this more likely)?  You could end up losing that interrupt, or otherwise confusing the hardware/software.  No idea if this is your problem, but you should just write a 1 to the flag bit you intend to clear, no |= of the register.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kk6gm wrote:

The one problem I see (which may or may not be causing the results you're having) is 

USART0_STATUS |= USART_TXCIF_bm;

This is not the right way to clear these write-1-to-clear flags.  What if another interrupt flag is set in the register during the RMW operation, and you inadvertently clear the pending interrupt with your |= operation (which is happening in ISR code, making this more likely)?  You could end up losing that interrupt, or otherwise confusing the hardware/software.  No idea if this is your problem, but you should just write a 1 to the flag bit you intend to clear, no |= of the register.

 

Is there direct bit access in TinyAVR 1-series using C/C++? How would you write this command?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are aware that the following sets str to { 'A', 0, 0, 0, 0, 0, 0, 0, 0, 0 }?

    uint8_t str[10] = "A";

 

If you #include <util/atomic.h> you can do this:

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // init USART
    }

and everything within the block is executed with interrupts disabled, then SREG 'I' flag is then restored to its previous state.

 

A safer way of working with the Configuration Change Protection is to use the following:

    _PROTECTED_WRITE(CLKCTRL_MCLKCTRLA, 0x80);
    _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);

The macro expands to assembly code that always conforms to the CCP protocol, regardless of optimization setting.

 

Greg Muth

Portland, OR, US

Xplained/Pro/Mini Boards mostly

 

Make Xmega Great Again!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

igendel wrote:

Is there direct bit access in TinyAVR 1-series using C/C++? How would you write this command?

Since writing a 0 to any other bit in the register has no effect, just write 

USART0_STATUS = USART_TXCIF_bm;

These kinds of flag registers are very common in more modern uC designs.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

igendel wrote:
TXCIF - Set to 1 when the shift Register is done, and no further data is pending in the Data Buffer, i.e. when the IO pin returns to the idle state.

I'd have to look at the text in that particular datasheet, but your i.e. is not correct IME.  The TX-complete flag is set when each byte has been shifted out.

 

And [almost] all AVR8 interrupt flags are cleared when the ISR is entered.  Again, I'd have to check the datasheet for that particular model.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greg_Muth wrote:

You are aware that the following sets str to { 'A', 0, 0, 0, 0, 0, 0, 0, 0, 0 }?

    uint8_t str[10] = "A";

 

Yes. This is not related to the issue at hand.

 

Greg_Muth wrote:

If you #include <util/atomic.h> you can do this:

Cool, I wasn't aware of these... well, not enough to be working with them regularly laugh But again, I don't think it's related to my issue with the TXC flag.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

igendel wrote:
TXCIF - Set to 1 when the shift Register is done, and no further data is pending in the Data Buffer, i.e. when the IO pin returns to the idle state.

I'd have to look at the text in that particular datasheet, but your i.e. is not correct IME.  The TX-complete flag is set when each byte has been shifted out.

 

p. 275:

Bit 6 – TXCIF: USART Transmit Complete Interrupt Flag
This flag is set when the entire frame in the Transmit Shift Register has been shifted out and there are no
new data in the transmit buffer
(TXDATA).
 

As I understand the above, if I feed a new byte into the buffer before the shift register is finished, TXCIF will only be set after the new byte was sent out.

 

theusch wrote:

And [almost] all AVR8 interrupt flags are cleared when the ISR is entered.  Again, I'd have to check the datasheet for that particular model.

 

It makes sense but my measurements disagree smiley

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kk6gm wrote:

Since writing a 0 to any other bit in the register has no effect, just write 

USART0_STATUS = USART_TXCIF_bm;

These kinds of flag registers are very common in more modern uC designs.

 

Hmmm... yes, the datasheet isn't too explicit about it (at least in the parts I read so far) but it seems right. Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It certainly looks like errata.
I confirmed that TXCIE is not automatically cleared in tiny 1616 and mega 4809.
Does anyone know a good way to report to Microchip?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

I confirmed that TXCIE is not automatically cleared in tiny 1616 and mega 4809.

 

Hopefully TXCIE (the interrupt enable bit) is not cleared.  What should be cleared automatically is TXCIF (the interrupt flag).

 

--Mike

 

EDIT: interrupt flag name

 

Last Edited: Mon. Aug 20, 2018 - 04:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am sorry, it is a mistake in writing.
TXCIF in USART0.STATUS is not automatically cleared.
I confirmed the operation with real chip.
Those who have the operating environment please verify.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kabasan wrote:

TXCIF in USART0.STATUS is not automatically cleared.
I confirmed the operation with real chip.

 

The 4809 too? Good thing you caught it, I was about to start investigating it too smiley

Thanks for confirming.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

However the datasheet also contradicts itself on the interrupt controller chapter:

Interrupt flags are not automatically cleared after the interrupt is executed. The respective INTFLAGS

register descriptions provide information on how to clear specific flags.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I confirm that the TXCIFflag does not get cleared automatically with the interrupt execution on my Tiny414. If it is not cleared manually, the ISR will be called again immediately.

 

Two more things that were unexpected, at least for me:

1) Disabling the USART transmitter automatically sets the pin as input. If you want to use the transmitter again (re-enabling it) one must set the pin as output manually before. The datasheet does mention this.

2) Setting the TX pin as output seems to trigger a single TXC interrupt, at least on my device. I needed to cli() and sei() around the pin direction setting in order to prevent this from happening. The datasheet does NOT mention this ;)

 

Best regards

  Andre

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The data sheet shows the pin direction when TXEN is disabled.

 

 

When switching the direction of the TX pin, if you really get a TXC interrupt, you can not suppress it, even with cli and sei.
In fact, no interruption occurred in my experiment. Please check again.

 

This is my test code.

// ATmega3208
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/cpufunc.h>
#include "phy_init.h"

volatile uint8_t dat;

int main(void)
{
    phy_init();

    // PORTA
    PORTA.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc;         // TXD
    PORTA.PIN1CTRL = PORT_PULLUPEN_bm;                  // RXD
    PORTA.PIN2CTRL = PORT_PULLUPEN_bm | PORT_INVEN_bm;  // TRIG1
    PORTA.PIN3CTRL = PORT_PULLUPEN_bm | PORT_INVEN_bm;  // TRIG2
    PORTA.OUT = 0b00000001;
    PORTA.DIR = 0b00000001;

    // USART0
    USART0.CTRLB = USART_RXEN_bm | USART_TXEN_bm;
    USART0.CTRLA = USART_RXCIE_bm | USART_TXCIE_bm;

    sei();
    while (1){
        _NOP();
        if (PORTA.IN & 0x04){
            // TRIG1 : TX data(loop back to RXD)
            _NOP();
            USART0.TXDATAL = 0x55;
        }
        if (PORTA.IN & 0x08){
            // TRIG2 : TX disable & enable
            _NOP();
            USART0.CTRLB &= ~USART_TXEN_bm;
            _NOP();
            PORTA.DIRSET = 0x01;
            _NOP();
            USART0.CTRLB |= USART_TXEN_bm;
        }
    }
}

ISR(USART0_TXC_vect){
    _NOP();
    USART0.STATUS = USART_TXCIF_bm;
    _NOP();
}

ISR(USART0_RXC_vect){
    _NOP();
    dat = USART0.RXDATAL;
    _NOP();
    USART0.STATUS = USART_RXCIF_bm;
    _NOP();
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to make it clear:

No interrupts on AVR8X devices (Starting with tiny817 and all AVRs released since, including mega4809) clear the flags just by executing the interrupt vector. You have to do something, like write a '1' to the flag bit to clear it or, as is the case for the ADC, read the ADC result register. The reason for this is that some interrupts share the same vector, so you have to read the flags to see which interrupt that triggered the vector. So to avoid having some interrupts that clear on vector execution, and some that don't, all interrupts are "manually" cleared.

 

The TXCIF flag is not automatically cleared when the transmit complete interrupt vector is executed.
 

Yes, there are bugs in the datasheet. I've reported this in our bug-tracker.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've reported this in our bug-tracker.

Where is the link to the bug tracker?  It is vital to be aware of such issues. 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry, it's not public.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

They need a GitHub for datasheets, one place where you know you have the latest info. Since users/customers are doing a lot of proofreading/verification, why not give everyone a single place to provide corrections/suggestions/etc.  Or simply provide us an 'unofficial-datasheet-corrections.txt' file if its too much work to fire up the pdf editing software. As it is now, everyone has to bump into the same errors/problems and work through them. That may provide more traffic for google and avrfreaks, but is a waste of time for everyone (including mchp, which I'm sure has to answer the same questions over and over in support tickets). These are not hard cover books, so its a little puzzling why these things take so long to correct. 

 

 

back to TXCIF (although the topic may be a little old)-

 

I'm not sure if this is normal practice when using txcif as I rarely need/use it, but for some reason recently needed a way to know if transmitting was complete (I am using dre isr and some blocking tx also). Since there is no shift register status bit in these avr0/1, there needs to be a way to check it just using txcif. But unless you take care of the txcif flag in some way, you will never know when tx is actually done. If you clear it anywhere, or don't clear it, you will be left with a flag that tells you nothing. So it seems to me the correct thing to do is to clear that flag only when you write to the tx data register. Then you end up with a flag that always tells you the shift register status (except on boot, where the flag is 0). You can still use txc irq, just don't clear the flag there (clear txcie instead). Almost a real shift register status bit.

 

Maybe this is common knowledge or there may be some flaw in my thinking, but I noticed some code in this thread which cleared the txcif flag in the txc isr and thought maybe a different solution could be helpful. Or not.