Go Down

Topic: Failed to use USART transmit interrupt (solved) (Read 1 time) previous topic - next topic

DrRobertPatzke

Sep 05, 2013, 12:17 pm Last Edit: Sep 10, 2013, 06:42 pm by DrRobertPatzke Reason: 1
Thinking about serial communication via Serial 1,2,3 I wondered, why the transmit interrupt is not used. Transmission events seem to be polled in the main loop and when some application function holds the CPU, transmission should stop also.
So for me it seemed to be a good training for C++ to program my own USART-Class and replace that of Arduino (e.g. via #define MyUSART).

But when I started my new USART drivers, the CPU seems to hang up.
I did not find the reason. Simply when initialising the USART, nothing else works any more.
Ok, seems to be my mistake and some misunderstanding, how to use SAM3.

Then I made some tests with the existing USARTClass (USARTClass.cpp/USARTClass.h in ...\arduino-1.5.3\hardware\arduino\sam\cores\arduino).
And I get the same effect if I only enable the transmit interrupt _pUsart->US_IER = .... | US_IER_TXRDY .
Ok, you should not do this without having prepared for this interrupt in the IRQ handler.
But with my own class, I was prepared and I got the same effect.
I have no idea what reason stops my programms with enabled TXD-Interrupts on USART.

Now I started to create a workaround based on my sysTick-Scheduler avoiding the transmit interrupt.
But if anybody goes for programming the USART with transmit interrupt, I would appreciate a contact for exchanging know how and questions.

Thanks for reading ....

Some time later ...
After installing a polling routine calling the transmit IRQ handler, I can state, that at least my new class is working. And I can show, that it is really the TXD-Interrupt-Enable which stops everything.
Did the Arduino experts make the same experience and is that the reason for not using transmit interrupt?

Graynomad

Quote
Did the Arduino experts make the same experience and is that the reason for not using transmit interrupt?

I doubt it, more likely they just never got around to implementing an interrupt-driven version of the class.

How do you know your handler is not being entered? Normally one would perform a very simple and non-intrusive task like pulsing a pin to see if you got there.

______
Rob
Rob Gray aka the GRAYnomad www.robgray.com

DrRobertPatzke

Thanks for Your interest.
No, the IRQ-Handler is definitely called for the receive-interrupts. That works. And because I overwrite the USART3_Handler and there are no different handlers for transmit and receive, I can state, that the driver is running (tested for receive).
The mess is, that ist is not a futilely waiting for the TX-Interrupt, no, no ...
I have other program parts which are started after reset and which send some text via Serial (UART, not USART).
So, when I open the serial monitor at Arduino IDE, I see these messages.
But when I only set the TXD-InterruptEnable on USART3, these messages will not be there. So I think, the processor is stopped.
With my first tests I thought I made a mistake when overwriting USART3_Handler. There is a __halt (a while(1) endless loop) behind the basic handlers "weak" attribute (see USART3_Handler in ...\hardware\arduino\sam\cores\arduino\cortex_handlers.c). And the behaviour "smells" like this. But I repeat: Receive-interrupt is running!

So far now I am happy with using sysTick instead of TX-Interrupt. I am polling the USART3-transmitter every millisecond with a simple check of "transmitter holding register empty". The disadvantage is, that there are gaps between the characters if I use a transmission speed of more than 9600 bits/s. The maximum speed is just 1000 characters per second. If I need more speed, I will have to use an extra timer for the polling.
But I am very stingy with the timers. I think, You never have enough (for several measurement and control tasks). That ist the reason for using sysTick, it is just there.

My next test will be whether only the serial communication is influenced by enabling the transmit interrupt. But how should USART influence UART? As far as I know, that are different controllers on SAM3.

Now I am working on a new class for the serial connected LCD display. That was the reason for dealing with the transmit interrupts for serial communication. I do not want to wait, until the characters to control the LCD are transmitted.

Graynomad

Quote
I can state, that the driver is running (tested for receive).

But have you proved the ISR gets called for Tx? You have not detailed how this has been proved.

The fact that the ISR is entered for Rx is good and if there's only a single ISR for both it means you have correctly trapped the interrupt, but it has little bearing on the Tx because the code path in the ISR will be different for Rx and Tx. That's why you have to do some really low-level debugging and start pulsing pins in various parts of the code to see exactly where it goes.

Maybe you should post the ISR code as well, not that I'm an expert on low-level SAM stuff but others may be and there may also be a simple error in the logic.

_____
Rob
Rob Gray aka the GRAYnomad www.robgray.com

DrRobertPatzke

Thanks again for Your interest.
By the way, I always wanted to thank You for Your fabulous pinout diagram. It is really a great help, especially if you think about using the alternative functions of the pins.

The interrupt handling is very simple:
Code: [Select]
void SerialCom::IrqHandler()
{
  status = usartPtr->US_CSR;

// ACHTUNG
// Sendeinterrupt nicht aktiv, weil System nicht laeuft,
// sobald der freigeschaltet wird

  // --------------------------------------------------------------------------
  // Transmit-Interrupt (Sendepuffer ist frei)
  // --------------------------------------------------------------------------
  //
  if((status & US_CSR_TXRDY) && (restSend > 0))
  {
    // Der Sendepuffer ist frei und
    // es sind noch Zeichen zum Senden da
    //
    usartPtr->US_THR = *ptrSend;
    ptrSend++;
    restSend--;
  }

  // --------------------------------------------------------------------------
  // Receive-Interrupt (Zeichen wurde empfangen)
  // --------------------------------------------------------------------------
  //
  if((status & US_CSR_RXRDY) && (restRec > 0))
  {
    // Ein Byte wurde empfangen und
    // es sind noch Speicherplaetze frei
    //
    *ptrRec = usartPtr->US_RHR;
    ptrRec++;
    restRec--;
  }



Now there is no TX-Interrupt, because I did not switch it on:

Code: [Select]

  // Interrupts freischalten
  //
  NVIC_EnableIRQ((IRQn_Type)pidUsart);
  usartPtr->US_IER =
  US_IER_RXRDY | US_IER_OVRE | US_IER_FRAME;
  // | US_IER_TXRDY;
  // ACHTUNG, System arbeitet nicht mehr,
  // sobald TX-Interrupt freigegeben wird


Instead I copied the content of the TX-Handler into a polling function which will be called from the sysTick-Handler. Currently, for testing, it is simply called in the main loop.

Code: [Select]

void  SerialCom::run(void)
{
  status = usartPtr->US_CSR;

  // --------------------------------------------------------------------------
  // Abfrage, ob Sendepuffer frei und Zeichen zu senden sind
  // --------------------------------------------------------------------------
  //
  if((status & US_CSR_TXRDY) && (restSend > 0))
  {
    // Der Sendepuffer ist frei und
    // es sind noch Zeichen zum Senden da
    //
    usartPtr->US_THR = *ptrSend;
    ptrSend++;
    restSend--;
  }

}


I have to repeat: If the TX-Interrupt is enabled, NOTHING works, my Arduino Due is "dead", as far as the normal serial communication via USB (Serial) should run. My testing environment is a command communication via Serial, it starts printing version and help information and then waits for commands from the PC keyboard. This is not running, if I only enable the TX-Interrupt:
US_IER_RXRDY | US_IER_OVRE | US_IER_FRAME | US_IER_TXRDY; (compare with running code above)
And remember, I have the same effect if I do not use my own class but enable the transmit interrupt in the given library class "USARTClass".

Graynomad

It looks pretty straight forward, you need prove that you are getting to that point, and the best way to do that is toggle a pin and look on a scope or analyser.

For example
Code: [Select]

TOGGLE(5);   // proves we got to the ISR

if((status & US_CSR_TXRDY) && (restSend > 0))
  {
  TOGGLE (6);   // proves the above condition was satisfied


Where TOGGLE is a macro that uses direct port manipulation to toggle a pin.

This is where a real debugger is great, you can break at that point and single step to see what happens.

_____
Rob

Rob Gray aka the GRAYnomad www.robgray.com

DrRobertPatzke

#6
Sep 09, 2013, 09:49 am Last Edit: Sep 09, 2013, 11:23 am by DrRobertPatzke Reason: 1
Now I made more tests and it turns out to be the worst case, it could be:
The (whole) system is no more running, if I enable USART transmit interrupt!

What I did on testing:
I placed some LEDs at port Pins showing the run state of the system. A counter variable is incremented and every (% 10, % 100, and % 1000) an accordingly LED is complemented. With a delay(1) in the main loop, I have now 3 LEDs blinking every 2, 0.2 and 0.02 seconds. This works fine and shows, that main loop is running.
Now I change only one thing: I enable the transmit interrupt of USART3, nothing else.
And then starting again, but the LEDs stay quite. So main loop is not called any more, the system is dead.

What could that be?
I thought again about the cortex_handlers with their __halt default. Could it be, that there is a mistake with the default transmit interrupt handler and it runs on __halt ? (Though I do not send any character till now, might be a ghost interrupt.)
So I added such a heartbeat showing function with (other) LEDs also into the _halt-function of the cortex_handlers in
...\arduino-1.5.3\hardware\arduino\sam\cores\arduino\cortex_handlers.c
But they also stay quite.

I repeat, it is the same behaviour, if I enable the transmit interrupt in the given USARTClass of Arduino library.
For me, it now looks like a severe bug at the SAM3 microcontroller or at least some wrong explanations in the SAM3 data book (I am using ATMEL, AT91SAM ARM-based Flash MCU, SAM3X SAM3A Series, 11057B-ATARM-28-May-12, 1467 pages).

What makes me insecure: Handling of transmit interrupts is a very important thing for serial communication. It cannot be, that I am the only one dealing with this problem on SAM3. So someone else should have addressed this problem, if it is not only a mistake by myself.
I found a hint on interrupt handled serial communication with SAM3 at the FreeRTOS environment (www.freertos.org). But I do not have the time now, to care for that. I would need many hours to install and understand the FreeRTOS environment.

Thanks again for Your interest. May be, some day someone else is interested in interrupt handled serial communication for SAM3 and together we may find the solution.

Some time later: .... the problem badgers me ...
I downloaded FreeRTOS and investigated the source code for serial communication.
Aah ... even they do not use transmit interrupt for USART, only receive interrupt (on USART1).
...\FreeRTOSV7.5.2\FreeRTOS\Demo\CORTEX_ATSAM3X_Atmel_Studio\src\serial.c

So I have a growing suspicion, that it is a bug with the microcontroller.
I do not know, where to ask at ATMEL. I do not want to discuss with the sellers until they understand the problem.

Correction:
They do enable transmit interrupt with USART, but it is inside the character sending function.
I have to stop my investigations on that now, it takes too much time and there are more important things to do. I could not find the mistake and the comparison with FreeRTOS did not give me any hint, why the Arduino Due blocks as soon as I enable the transmit interrupt of USART.

Graynomad

Quote
but the LEDs stay quite. So main loop is not called any more, the system is dead.

No, it's just not doing what you want it to do, there's a huge difference.

It's not really practical to debug a problem like this with LEDs, you should have proper equipment. You have to know where the code gets to, staring at non-flashing LEDs is no help.

That said you could set the LED in the ISR instead of the PULSE macro I suggested. That will at least tell you what part of the ISR (if any) you execute.

_____
Rob
Rob Gray aka the GRAYnomad www.robgray.com

DrRobertPatzke

I found the mistake.
I say, it is a bug at the microcontroller.
Your hint to find the location, where the CPU is running, was a good hint, as well as the sending function in FreeRTOS (see my posting before).

So what happens ....
TXD-Interrupt is thrown repeatedly endless after enabling it (full CPU load, no time for the main task).
That means, the IRQ-Handler is called again and again allthough any character was ever sent.
This explains also the programming of FreeRTOS with enabling TxD-Interrupt in the character sending function.
I wonder, what I will have to do in the sending function. May be, I will have to enable TxD-Interrupt after filling the THR and only, if I have more characters to send. And after sendig the last character, I will have to disable TxD-Interrupt, because it may jump again in the endless loop when the THR is empty.

That is from my experience really a strange behaviour. If we extrapolate this to the receive interrupt, it would mean, that the IRQ-Handler would stay in an endless loop after receiving a character, if we do not read the RHR. I will not test that, because it simply does not happen with my IRQ-Handler.

Is it the same behaviour with other ATMEL (or ARM) microcontrollers? Does someone know it?
(To say, it is not a bug but a feature.)
I have to state, that my experience with microcontrollers is based on 8051, C167 and long time ago with my own drivers for DOS on Intel(AMD)-CPUs.
They throw 1(!) interrupt, if the THR gets empty or the RHR gets filled.
This behaviour of SAM3X is (from my point of view) really special.

Thanks for Your valuable hints.

Graynomad

#9
Sep 10, 2013, 04:26 pm Last Edit: Sep 10, 2013, 04:29 pm by Graynomad Reason: 1
Quote
And after sendig the last character, I will have to disable TxD-Interrupt, because it may jump again in the endless loop when the THR is empty.

I'm thinking when you get an interrupt and there are no more characters to send you should clear the last interrupt, but there should not be any need to disable it entirely. If you disable it you will have to be careful when you enable again because there may still be a pending interrupt from the last character.

So I would try just clearing first.

I assume that it's not auto cleared by the act of entering the ISR because the Tx and Rx share the same ISR, but I'm not that familiar with the SAM.

EDIT: Actually I don't see you clearing it in that ISR code, that could be a bug.


_____
Rob
Rob Gray aka the GRAYnomad www.robgray.com

DrRobertPatzke

I could not find an extra bit for clearing interrupt. Normally TxD-Interrupt is cleared by writing THR and RxD-Interrupt is cleared by reading RHR.
I am happy with my new interrupt handled serial communication class now, everything works perfectly.
Here is my final IRQ-Handler:
Code: [Select]

void SerialCom::IrqHandler()
{
  status = usartPtr->US_CSR;

  // --------------------------------------------------------------------------
  // Transmit-Interrupt (Sendepuffer ist frei)
  // --------------------------------------------------------------------------
  //
  if(status & US_CSR_TXRDY)
  {
    // Der Sendepuffer ist frei
    //
    if(restSend > 0)
    {
      // es sind noch Zeichen zum Senden da
      //
      usartPtr->US_THR = *ptrSend;
      ptrSend++;
      restSend--;
    }

    // Der Transmit-Interrupt wird gesperrt,
    // wenn das letzte Zeichen ausgegeben wurde.
    if(restSend < 1)
      usartPtr->US_IDR = US_IDR_TXRDY;
  }

  // --------------------------------------------------------------------------
  // Receive-Interrupt (Zeichen wurde empfangen)
  // --------------------------------------------------------------------------
  //
  if((status & US_CSR_RXRDY) && (restRec > 0))
  {
    // Ein Byte wurde empfangen und
    // es sind noch Speicherpl├Ątze frei
    //
    *ptrRec = usartPtr->US_RHR;
    ptrRec++;
    restRec--;
  }
}


And here is the sending function:
Code: [Select]

void SerialCom::write(uint8_t *wrPtr, int nrOfBytes)
{
  restSend  = nrOfBytes - 1;
  usartPtr->US_THR = *wrPtr;

  if(nrOfBytes > 1)
  {
    ptrSend   = wrPtr + 1;
    usartPtr->US_IER = US_IER_TXRDY;
  }
}


Thank You again for Your interest and valuable comments.
For me this issue is solved.

Go Up