Elegantly interfacing one function to multiple peripherals eg UART0 / USART1

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

Hi,

 

Now there are a  lot of different ways of achieving what i want to achieve, but i'm wondering how people do this elegantly!

 

Imagine i have a function, that needs to be able to work with multiple periferals of the same type, say for example, a function that writes a byte to a uart. however,i'd like to programatically have that function write to any of the multiple uarts on the chip, eh UART0 or UART1 to UARTn basically.

 

Calling the function could be  uart_write(0,0xFF)  eg write 0xFF to Uart0 (eg to UDR0)

 

various programming options exist:

 

1) Have some form of if() or State.Case afair in the function, that basically logically decides to which register to write

         clunky, and difficult to keep upto date as different chips have different numbers of registers

 

 

2) Send the function an address pointer, where some other external array does the setting of that pointer to the appropriate value, and that array is preloaded with the address of the register needed

    seems like a lot of work, addresses will have to be setup before run time (could be done with consts by #defines as it wouldn't change at run time)

 

  What about if that function needs to access more than one register? Say i need to send a byte to the uart transmisson register (UDR) but also then enable the transmission complete interrupt for that register, which if obviously at a completely different location. Now i either need more logic, or more look up tables to "map" all that together!

 

I'm sure there is an elegant way to handle this (in "c" with AVRstudio) but i just can't quite think what it might be right now!

 

And suggestions anybody?

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

On newer chips the layout of all registers for a given peripheral type are all based around a structure with fixed locations so that you only need to know the base address for that individual peripheral.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

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

Jim  ka7ehk  asked essentially the same question only a couple of months ago:

 

https://www.avrfreaks.net/forum/handling-multiple-similar-internal-peripherals

 

My take on it is there:

 

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

 

EDIT

 

Have you looked at how ASF does it?

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...
Last Edited: Wed. Jun 17, 2020 - 04:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm currently on a mega1284, so not a new chip (;-)  but it actually has it's UART registers cluestered together at the end, down in the 0xC0 to 0xCE offset range, and they do look to be identical in terms of offset for each register for each numbered periferal, but this may not always be the case?

 

 

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

i've not looked at ASF, because, well, there are not enough hours in the day already!  lol!

 

(actually, that's a pretty good idea )

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

max_torque_2008 wrote:
eg UART0 / USART1 

For that particular case, you're going to have to account for the fact that one is just a UART, but the other is a USART ...

 

As noted in the other thread, the question is whether it's worth the effort of trying to make it all entirely "generic".

 

Trying to be entirely generic - "all things to all users of all chips" - is (one of?) the key thing(s) that gets ASF its reputation for being bloated and over-complicated ...

 

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

max_torque_2008 wrote:
there are not enough hours in the day

exactly my point - "is it worth it?"

 

cool

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: 2

Normally one would use the standard IO functions provided by the toolchain and included in <stdio.h>

 

Jim

 

 

(Possum Lodge oath) Quando omni flunkus, moritati.

"I thought growing old would take longer"

 

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

The "halfway house" as mentioned in the other thread would perhaps seem to be having a file (.c & >h) for each instance of a peripheral, and then bringing the data together under one 'umbrella' file?

 

For example,

 

in file UART0.c could be a function  uart0_tx()

in file UART1.c could be a function  uart1_tx()

 

and those functions handles all the necessary housekeeping for their own peripheral (eg setting up registers, interrupts, flags etc)

 

the umbrella file SERIALCOMS.c then uses a simple look up table with the address of the appropriate specific function (eg the address of uart0_tx() is held in an address pointer array under entry 0) so when i call serial_tx(0) that function can effectively forward to the appropriate handler in the individual files that then splits it out within those peripherals?

 

Given that realistically, we are talking about a max of 4 of any peripheral, that is perhaps the best way, without as you say, becoming "bloatware" and trying to be too clever!

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

Which AVR? All the Xmega, AVR0, AVR1, AVRDA ones, asnoted above, have common UART_t so you just need to pass the base address of the struct.

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

old mega1284 in this case,  #oldskool  :-)

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

Hmm, did not notice that common UART_t. That will change my thinking (in the referenced parallel thread) somewhat.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

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

Jim,

 

Yup it's almost like they invented C++ ;-)

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

max_torque_2008 wrote:
The "halfway house" as mentioned in the other thread would perhaps seem to be having a file (.c & >h) for each instance of a peripheral, and then bringing the data together under one 'umbrella' file?

 

I did exactly what you are describing here for the 4 USARTS on a mega2560 way back when. (back when I used ICC and before xmega)

It is quite simple / straight forward and very effective.

I had arrays of function pointers to hold each USART function which could then be called with the USART number as offset, e.g. ClrTxBuffer[0..4].

To make use of status registers I had to do the same with UCSRA & UCSRB, i.e. arrays of pointers to the USARTnA|B regs, as well as define the bits with 'n' stripped off, e.g. TXC0 -> TXC. (This required the register bits are the same for each USART, even if the registers are out of order)

 

So in usage you might go...

 

USART_INI[0](115k,8,n,1);       // console/terminal
USART_INI[1](19200,8,n,1);      // modbus

while (!DataInRxBuffer[0]()){;} // wait for console key press

puts[0]("Here is some console output");

for (i = 0; i < size; i++)      // send modbus packet
    putc[1](*(packet+i));

while (!CHKBIT(*UCSRA[1],TXC)){;}   // wait for transmission complete

And of course, by defining the array index as a variable, you could redirect USARTS dynamically at run time.

 

If interested, PM me for the code.  It is ICC but should be very portable / easy to port.

 

Cheers,

Steve.

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

ki0bk wrote:
Normally one would use the standard IO functions provided by the toolchain and included in <stdio.h>

This is THE most elegant solution by far.

You will totally abstract away all the nitty gritty detail of receiving/sending characters from your function.

 

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

Those with access to a c++ compiler should at least take a peek at what is possible. It is a tool that makes these things easy to do, and the only downside is it takes a little time to learn. Using c++ doesn't mean you have to give up all your c code, it just means when it comes time to use c++ things you have to be using the c++ compiler. 

 

Here is a mega1284 version of a usart-

https://godbolt.org/z/UYKiq6

I don't know if this actually works on a '1284, but have no reason to think it wouldn't be spitting out strings from both usarts unless I'm missing something.

 

Obviously a simplified and limited example, but with very little code (and no io.h used here) you can get a peripheral 'driver' that will work for any avr with the same register layout and capabilities. It can also be a header-only 'driver'. The method used above will result in separate code generated for each instance, but in some cases that can be a good thing, and with limited instances (usarts in this case) it makes little difference. The real goal is not to generate a single function to handle any usart, but rather to write a single function that can handle any usart.

 

A solution in c++ can probably be described as 'elegant'. The best description you can get out of any c version will be 'it works, quit staring at me'.

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

curtvm wrote:
Here is a mega1284 version of a usart-
Wow, nice example!

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

Thanks for the suggestions people!  Some interesting reading  :-)

 

I've started putting together something that should work. It's slightly complicated in that it also handles CAN data transmission via an external CAN controller (driven over SPI) so there is a fair bit of adstraction between the drivers and their end peripheral. However i have come up with what should be a workable solution, with a single file creating a unified data structure to house system wide serial data from all sources (USART TTlL for direct controller debug/programming, USART over RS-485 for application controls and slave diagnostics / programming and CAN via SPI for in environment setup, calibration, controls and programming!  All those data sources/sinks are unified by my "SDI" function (Std Data Interface) where each serial bus is assigned a channel ID, which is used by the SDI to send out TX buffer data to the appropriate channel, and to label RX data in the RX buffer with where it originated.  It also allows cross transmission by acting as a data bridge between the various physical busses, so i should be able to connect to a slave module on the RS-485 application network by connecting via CAN to the system master.   The TX and RX function for each peripheral effectively looks the same to hthe SDI, and there is a single lookup table in that functions files that sets the addresses of those transmission and reception functions so the SDI can forward data to any end point.

 

What is quite nice is that this abstraction also means the SDI function can easily work with external peripherals, ie a USART over SPI for example, because the driver for that channel abstracts away the in-between bits :-)

 

 

My next  challenge is to decide how to incorporate "master as a slave" on my RS-485 network, which is half duplex, so normally slaves are quiet unless spoken too (like victorian children......) and the master is responsible for bus scheduling to avoid contention between Tx and Rx packets.  Current thought is to include a repetitive ghost packet from the master to a "tester" that isn't actually normally present so normally there won't be any return.  But when the tester is inserted in the network, the master (doing the scheduling) will get a "i'm here" reply from the tester, and that reply can then command the master to release control of the bus to the tester, become a slave, and the tester to become the master. Handing back er, mastery, to the master is easy because the tester can just send the master (as a slave) a message that says "you've got it" and it can once again drive the bus.........