Serial Rx & Tx Techniques

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

Hello everyone

 

I need to send an receive a maximum of 26 Bytes and at any one time @ 2400Baud but many multiple operations in a session. The Rx side will only ever be used in response to the Tx transmissions (response and data). Data put in to a volatile Array.

 

Question 1 Due to such low speeds would Tx Interrupts be the way to go or polling. I do need to do other things so I'm leaning towards ISRs.

 

Question 2 If I used Tx ISR's what is the best way to disable the Interrupt when not used -  I'm thinking that playing with the UCSRnB register bits. Is this the preferred  technique .

 

Question 3 - I need to write this data to FRAM over SPI (at 8MHz). I don't need specific code for SPI just help on the structure/technique for implementing this in my program. Would the SPI stuff be a separate routine or within main(), do I need to implement this in a ISR, how would I use a standard public C routine etc.

 

A bit of background I have most of what I need working (apart from 1& 2 above) but not combined or optimised in a program they don't teach you this on YT or in books. 

 

Thanks

 

Last Edited: Tue. Nov 10, 2020 - 12:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

1) You use interrupts when there is a chance that several successive bytes will arrive before you have a chance to get back to pick them up. Only you know if you have such "long work" happening in your design. But, yeah, int-Rx is often the "safe strategy". The interrupt does nothing but feed into a ring buffer so this effectively just adds buffering to the UART that the AVR otherwise doesn't have. Usually Tx does not need to use interrupts - when you want to transmit that is often your sole focus at that time so it doesn't hurt to poll for that.

 

2) Think again whether you need int-Tx, I suppose it's true that at such a low baud rate Tx time will be long so if you are sitting in a loop to send a number of bytes that could take quite a while and prevent the MCU getting on with "other things". But remember that those other things, assuming they "have to happen" can, themselves be interrupt driven - which might be a better use for priority (when you use interrupts you are effectively giving some action a "prirority"

 

3) always try to do stuff in a modular way - don't just put everything into one amorphous great blob in main(). This way the structure (in main) can be much clearer. Also the modular stuff (like SPI or specifically FRAM) can all be tested in isolation - not requiring to be part of everything else in main. In effect you make individual building blocks ("Lego bricks") then main() is just the place where you put them all together.

 

BTW great to see a post from someone actually thinking about the DESIGN first before rushing to implementation. We see far too many posts here from folks who just keep editing new bits into one huge main() and then wondering why they cannot isolate where problems lie or who are confused about "how can I make A work with B?" because of all the messy structure and interactions they end up with.

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

Hi clawson thank you for your kind words.

 

Trying to learnt the horrendous monster that is C I realised/noticed all that commands you use are functions (even in Arduino, C++, C# etc) so it made sense to me that the language is pretty much function driven / orientated. I then made the mental leap of treating them as sub-routines from my BASIC days and the learning curve flattened somewhat and conceptually things became clearer. 

The things which are killing me at the moment are the C techniques to do what I want, other than formal training or hands on working next to a C programmer I can find little to no information on 'how' to program but loads of info about the syntax of commands etc - very frustrating, (nearly as frustrating as having to put a semicolon at the end of every line Ggrrgghh!)

In some ways I think ASM is much easier than C/C++ 

 

Going back to my points, I think RX interrupt is a no brainer. Tx ??? I have CRCs to calculate and a screen to update so I didn't really want to use polling. Conceptually I am struggling with Tx interrupts and how to implement them, I can visualise polling - Data in array > loop > Data out > done. Presumably I would  have to toggle TXENn bit (USCRnB Register) - disabled the when building the Tx array data and enabled it when ready to send?

 

Your point 3 - are you inferring (ideally), to use MAIN as a glorified super IF....ELSE or CASE loop which reacts to flags or RETURN values set by functions?

This makes a lot of sense.

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

Personally I almost always do Rx-interrupts, Tx-Polled. I suppose the advantage of Tx-interrupt is that it is "fire and forget". You just fill your buffer to send, trigger it and then get on with other things so it does really depend on whether you think that "stop while I transit" is acceptable or do you need transmit as a "background task".

 

As for what I meant by main() a "clear main". I pushed some C++ code the other day ( https://github.com/wrightflyer/s... ). The main() looks like:

#include "encoder.h"
#include "uart.h"
#include "timer0.h"
#include "button.h"
#include "dualjoystick.h"

void timerUpdate(void);

Encoder enc1(&PORTC, 0, 1);
Encoder enc2(&PORTC, 6, 7);
Encoder enc3(&PORTD, 0, 1);
Encoder enc4(&PORTD, 2, 3);
Encoder enc5(&PORTD, 4, 5);
Encoder enc6(&PORTD, 6, 7);
Encoder enc7(&PORTB, 0, 1);
Encoder enc8(&PORTB, 2, 3);
Button but1(&PORTC, 2, Button::NO_PULL_UP);
Button but2(&PORTC, 3, Button::NO_PULL_UP);
Button but3(&PORTC, 4, Button::NO_PULL_UP);
Button but4(&PORTC, 5, Button::NO_PULL_UP);
Button but5(&PORTA, 4, Button::NO_PULL_UP);
Button but6(&PORTA, 5, Button::NO_PULL_UP);
Button but7(&PORTA, 6, Button::NO_PULL_UP);
Button but8(&PORTA, 7, Button::NO_PULL_UP);
Uart uart(9600);
Timer0 tim(Timer0::TIM0_CTC, 100);
DualJoystick joy(0, 1, 64);

int main(void) {
    int32_t prev1 = 0, prev2 = 0, pos;
    uint16_t prevX = 0, prevY = 0;

    uart.connectStdio();
    printf("Hello World\r\n");
    tim.start(64);
    tim.attachInterrupt(Timer0::TIM0_COMP_ISR, timerUpdate);
    while(1) {
        pos = enc1.read();
        if (pos != prev1) {
            printf("Pos1 = %ld\r\n", pos);
            prev1 = pos;
        }
        pos = enc2.read();
        if (pos != prev2) {
            printf("Pos2 = %ld\r\n", pos);
            prev2 = pos;
        }
        if (but1.read() == 0) {
            printf("Reset1");
            enc1.readAndReset();
        }
        if (but2.read() == 0) {
            printf("Reset2");
            enc2.readAndReset();
        }
        DualJoystick::reading_t reading;
        reading = joy.read();
        if (abs(reading.x - prevX) > 4 || abs(reading.y - prevY) > 4) {
            printf("Joy X=%d, Y=%d\r\n", reading.x, reading.y);
            prevX = reading.x;
            prevY = reading.y;
        }
    }
}

void timerUpdate(void) {
    enc1.intUpdate();
    enc2.intUpdate();
    enc3.intUpdate();
    enc4.intUpdate();
    enc5.intUpdate();
    enc6.intUpdate();
    enc7.intUpdate();
    enc8.intUpdate();
    but1.update();
    but2.update();
    but3.update();
    but4.update();
    but5.update();
    but6.update();
    but7.update();
    but8.update();
}

I like to believe that the overall structure of what that (unfinished!) code is doing is clear from this. All the "detail" is hidden in the C++ classes that are being constructed and accessed.

 

Also when I developed this I was able to test each of those things (Uart, timer, Encoder, Buttons, DualJoystick) in isolation before I started to tie things together.

 

BTW regarding your original question 2. You usually do it with TXC. In your Transmit_bufferful() routine you simply enable the TXC interrupt and put the first character to send into UDR to start the process. Each time the transmission of one byte completes it triggers the TXC ISR which then checks to see if there are more to send in the buffer. If there are it just loads the next character which restarts the entire process but when you get the interrupt and there's no more to send you then just clear TXCIE so it goes "quiet" again until you have new stuff to send later on.

Last Edited: Tue. Nov 10, 2020 - 11:34 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Docara wrote:
Trying to learnt the horrendous monster that is C

While I'm not sure that 'C' really qualifies as a "horrendous monster", it is certainly true that learning the language itself enough is quite enough - without all the added complications, restrictions, etc of a small embedded microcontroller.

 

Therefore, it is commonly recommended that you don't try to learn the language itself on a microcontroller - you will get a far more "comfortable" learning experience on a PC (or similar).

 

Then, once you have a good grasp of the language itself, you can move on to applying that in embedded microcontroller applications ...

 

clawson wrote:
great to see a post from someone actually thinking about the DESIGN first before rushing to implementation

+1

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

As for what I meant by main() a "clear main". I pushed some C++ code the other day ( https://github.com/wrightflyer/s... ). The main() looks like:

....code etc

.... code etc

.... code etc

 

Nice structure! Yes this was sort of thing I was referring to (though not very eloquently). 

 

While I'm not sure that 'C' really qualifies as a "horrendous monster", it is certainly true that learning the language itself enough is quite enough - without all the added complications, restrictions, etc of a small embedded microcontroller.

 

Nah!  I'll stick by my statement LOL cheeky. But I do agree about small embedded it is after all mainly for PC's. The 'beast' clearly shouts what it is - a massive iteration of ideas stemming from 40+yrs ago, which is now intrenched as a standard. 

 

I'm not a programmer I'm a particularly good electrician who's first 'trade' was electronics when Z80, PIO where the dogs danglies. I have to learn a smattering of C to fill in the gaps of functionality of my usual high level language (Flowcode) for a marine control project I'm working on in London. Coming in cold with a need to do something relatively simply gives me, I think, a good overview of C/C++ foibles together with the associated toolchains. I think it is bloated and generally restrictive. However, I do concede I am a novice and don't know what I'm talking about for more in-depth projects and certainly not studied the language properly.

 

For embedded systems and general smaller projects I  think there must be a better way. Perhaps a 'Graphical C/C++' or Embedded Graphical C/C++' is needed or helpful. Where functions could be boxes linked with lines to indicate program flow or one way variable transfer say or whatever - Flowcode is great in this regard but who ever is pulling strings is not in touch with what their customers wants or needs and, I think, killing the product as a consequence. XOD is close but TOO Arduino centric AND generally too restrictive. I here Conjure is the new protagonist but like all things the resultant code is large when compared to ASM.

 

Anyway I digress, 

BTW regarding your original question 2. You usually do it with TXC. In your Transmit_bufferful() routine you simply enable the TXC interrupt and put the first character to send into UDR to start the process. Each time the transmission of one byte completes it triggers the TXC ISR which then checks to see if there are more to send in the buffer. If there are it just loads the next character which restarts the entire process but when you get the interrupt and there's no more to send you then just clear TXCIE so it goes "quiet" again until you have new stuff to send later on.

Yes thank you for clarifying the principle, this was what I was getting at.

 

Thanks chaps I thik I have enough for some progress (for now ha ha )

 

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

You are in luck, it seems there is an App Note just for this sort of thing(who would have guessed): https://www.microchip.com/wwwApp...

It also has the code you need to implement either interrupt based, or polling!    Good luck with your project!

The AVRFreak favorite code by Peter Fleury is here: http://www.peterfleury.epizy.com...

Jim

 

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

Last Edited: Tue. Nov 10, 2020 - 01:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:
it seems there is an App Note just for this sort of thing(who would have guessed)

yes

 

and many more to be found on the Product Page:

 

https://www.avrfreaks.net/commen...

 

https://www.avrfreaks.net/commen...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Personally I almost always do Rx-interrupts, Tx-Polled

Like Cliff above I too use TX-polled most of the time, however in this case, with a slow baud rate of 2400, sending a single character takes almost no cpu time, but when sending a string, the cpu spins for quite a while waiting to send the next character.  So using a TX buffer here makes sense to me, as it frees the cpu for other tasks, of course one has to take into account any effect of periodic interrupts while the buffer is being sent.

As your app appears to be event driven, you may also want to review the real time task scheduler (RTOS) example in the tutorial section. https://www.avrfreaks.net/forum/...

Jim

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

Docara wrote:
For embedded systems and general smaller projects I  think there must be a better way. Perhaps a 'Graphical C/C++' or Embedded Graphical C/C++' is needed or helpful.
This perhaps...

 

https://xod.io/

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

Docara wrote:
XOD is close but TOO Arduino centric AND generally too restrictive.

It seems you want to have your cake & eat it?

 

The thing with small embedded systems is that they tend to have very specific requirements - so hard to create any "general" frameworks.

 

I here Conjure is the new protagonist but like all things the resultant code is large when compared to ASM

cake consumption & retention, again - I think?

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You didn't indicate which device (a "FRAM" I assume is on the other end of the AVR USART) is TX and RX or the format of the exchange.  So I'll assume that a packet block of 28 bytes goes from the AVR to the FRAM and that the FRAM then sends a few bytes a either a checksum or as an acknowledgement.

 

For 2400 baud use interrupts on both the transmit and receive.  The USART TX interrupt is a strange beast: it is different from all the other interrupts. The others will trigger in response to an event (timer compare match, IO pin change, etc...) but the TX interrupt with trigger constantly when it is enabled.  The TX IRQ routine would eat up 99% of your processing cycles. 

 

The TX interrupt is only enabled when there are more bytes in the transmit buffer to be sent, i.e. when the head pointer is greater than the tail pointer for the circular buffer.  When the last byte gets loaded into the USART for transmit,  the IRQ switches off the TX interrupt.  When the main code needs to send a 28 byte block, it loads all 28 bytes into the transmit buffer.  It adds to the head pointer with each byte loaded, and then it enables the TX interrupt. 

 

This interrupt then immediately happens because the USART TX data register is empty.  It loads that data byte from the transmit buffer to the USART data register, increments the tail pointer and then compares the head to the tail.  If equal, the IRQ gets turned off and the IRQ exited.  Then the other 27 bytes are loaded into the transmit buffer and the IRQ is re-enabled.  But this time, the IRQ is not immediately executed because the USART hasn't finished sending the first byte.  But when the TX is finished, then the TX IRQ will fire and send all 28 bytes in the background.