passing a port to class and storing as a variable

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

Hi, I am stuck on passing the port to the class and using it as a variable. I found on these forms (I think) how to pass a port to a function, but I have not been able to pass the port to the class and use it as desired.

I am using the ATmega328p

 

The code below is simplified for this post.

onewire.cpp

#include <stdint.h>
#include "OneWire.h"

OneWire::OneWire(volatile uint8_t *ddr, volatile uint8_t *port, volatile uint8_t *pins, uint8_t pin) {
	OneWire::ddr = *ddr;
	OneWire::port = *port;
	OneWire::pins = *pins;
	OneWire::bus = (1 << pin);

        release_bus();

void OneWire::pull_bus_low(void) {
	bit_set(ddr, bus);
	bit_clear(port, bus);
}

void OneWire::release_bus(void){
	bit_clear(ddr, bus);
	bit_clear(port, bus);
}

// other functions that will also use the port

main.cpp

#include <avr/io.h>
#include "OneWire.h"

int main(void) {
	OneWire onewire(&DDRD, &PORTD, &PIND, 5);
	// do something with the onewire bus
}

Thank you all as these forms have helped me a lot in the past.

 

This topic has a solution.
Last Edited: Tue. Jan 26, 2021 - 07:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So what that's doing is passing pointers to the port's registers - that should work.

 

I think it's common that the PORT, DDR, and PIN registers are contiguous & in  a known order;  so you don't actually need to pass all three - you just need one, and the others can be deduced from that.

 

samundson 2021 wrote:
I have not been able to pass the port to the class and use it as desired

So what happens when you try? 

 

You need to give a complete example, and say

 

  1. What you expected to happen
  2. What actually happened
  3. What you've discovered so far in trying to reconcile the two

 

EDIT

 

	OneWire onewire( &DDRD, &PORTD, &PIND, 5 );

Aren't DDRD, PORTD, and PIND already addresses?

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: Tue. Jan 26, 2021 - 06:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
Aren't DDRD, PORTD, and PIND already addresses?
No they are dereferenced pointers - it's perfectly valid to get the address. For example if I access PORTB I might read 0x98 but if I access &PORTB it might be 0x04 (or whatever the IO address of PORTB is. Basically location 0x04 contains 0x98.

 

The AVR-LibC FAQ even has:

 

https://www.nongnu.org/avr-libc/...

 

which shows that what OP is doing is quite valid.

 

I recently threw together some C++ for AVR that does pretty much what the OP is trying to do:

 

https://github.com/wrightflyer/s...

 

For example:

 

https://github.com/wrightflyer/s...

 

in which the c'tor is:

Encoder(volatile uint8_t * port, uint8_t pin1, uint8_t pin2);

with the implementation as:

 

https://github.com/wrightflyer/s...

Encoder::Encoder(volatile uint8_t * port, uint8_t pin1, uint8_t pin2) :
                mPos(0)
{
    mPin1 = (1 << pin1);
    mPin2 = (1 << pin2);
    mPORT = port;
    mDDR  = port - 1;
    mPIN  = port - 2;
    // this uses the "trick" that GPIO ports in memory are laid out as:
    // n + 0: PINx
    // n + 1: DDRx
    // n + 2: PORTx
    //
    // so given PORTx you can write DDR with (PORT-1) and PIN with (PORT-2)
    *mDDR  &= ~(mPin1 | mPin2); // DDR bits to input
    *mPORT |=   mPin1 | mPin2; // write to PORT to enable pull-ups
    // read initial state..
    uint8_t s = 0;
    if (*mPIN & mPin1) s |= 2;
    if (*mPIN & mPin2) s |= 1;
    mState = s;
}

then the example code calls this as:

 

https://github.com/wrightflyer/s...

 

///////////// PORT A //////////////
DualJoystick joy(0, 1, 64); // on A0/A1
// A2 - unused
Button  butJoy(&PORTA, 3, Button::NO_PULL_UP);
Button  but1(  &PORTA, 4, Button::NO_PULL_UP);
Button  but2(  &PORTA, 5, Button::NO_PULL_UP);
Button  but3(  &PORTA, 6, Button::NO_PULL_UP);
Button  but4(  &PORTA, 7, Button::NO_PULL_UP);

///////////// PORT B //////////////
Encoder enc1(  &PORTB, 0, 1);
Encoder enc2(  &PORTB, 2, 3);
Encoder enc3(  &PORTB, 4, 5); // SPI on 5/6/7 (with SS on 4)
// B6 / B7 - unused

///////////// PORT C //////////////
Encoder enc4(  &PORTC, 0, 1);
Button  but5(  &PORTC, 2, Button::NO_PULL_UP); // 2..5 are the JTAG pins
Button  but6(  &PORTC, 3, Button::NO_PULL_UP);
Button  but7(  &PORTC, 4, Button::NO_PULL_UP);
Button  but8(  &PORTC, 5, Button::NO_PULL_UP);
Encoder enc5(  &PORTC, 6, 7);

///////////// PORT D //////////////
Uart uart(9600); // on D0 / D1
Encoder enc6(  &PORTD, 2, 3);
Encoder enc7(  &PORTD, 4, 5);
Encoder enc8(  &PORTD, 6, 7);

So most of my IO pins are buttons or encoders.

 

To be honest, reading #1 it looks an awful lot like my code so I'm not sure why it would not be working.

 

My code certainly does: https://www.youtube.com/watch?v=... (a video that proves how difficult it is to hold a mobile/camera in one hand and operate a joystick or encoder with another !!

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh wait a mo...

OneWire::OneWire(volatile uint8_t *ddr, volatile uint8_t *port, volatile uint8_t *pins, uint8_t pin) {
	OneWire::ddr = *ddr;
	OneWire::port = *port;
	OneWire::pins = *pins;

What are the "*" doing here? - you don't want to read what is at those locations (the 0x98 in my example) you want to store the actual addresses (0x04 etc).

 

It might have helped if OneWire.h was also shown. Presumably it currently has:

class OneWire {
public:
    // stuff
private:
    uint8_t ddr;
    uint8_t port;
    uint8_t pins;
    // etc.
}

when it should be:

class OneWire {
public:
    // stuff
private:
    volatile uint8_t * ddr;
    volatile uint8_t * port;
    volatile uint8_t * pins;
    // etc.
}

It's the ADDRESSES not the contents you need to hold in the class. Then at access time it is things like *ddr you will be reading/writing.

 

(see my code for examples).

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

Thanks for your response awneil.

 

Attached is a zip of the code.

 

1. What I expected to happen is that the bits on the port would change when using the variables as it would when calling the port directly. So the behavior of

void OneWire::pull_bus_low(void) {
	bit_set(ddr, bus);
	bit_clear(port, bus);

and

void OneWire::pull_bus_low(void) {
	bit_set(DDRD, BIT(5));
	bit_clear(PORTD, BIT(5));

would be the same.

 

2. When using variables the pin value doesn't change.

3. When I hover over DDRD Eclipse tells me it's expanded to

(*(volatile uint8_t *)((0x0A) + 0x20))

Rather new to AVR and C/C++ so still trying to figure things out.

Attachment(s): 

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

https://godbolt.org/z/rP8139

 

sorry, corrected mistake

 

 

and if you wanted a pin class that can be used in various places-

 

https://godbolt.org/z/6GqMhx

Last Edited: Tue. Jan 26, 2021 - 08:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would pass a pointer to the port class.

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

Port classes (well strict) are only in Xmega derivatives. Original tiny/mega define the registers as separate entities not groups.

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

clawson wrote:
Original tiny/mega define the registers as separate entities not groups.

But they[1] are in a known relation to each other - so, given just one, you can compute the other two ...

 

EDIT

 

[1]  that is, specifically, the PORT, DDR, and PIN registers of a port.

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. Jan 27, 2021 - 09:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
so, given just one, you can compute the other two ...
Err yes...

clawson #3 wrote:

    mPORT = port;
    mDDR  = port - 1;
    mPIN  = port - 2;
    // this uses the "trick" that GPIO ports in memory are laid out as:
    // n + 0: PINx
    // n + 1: DDRx
    // n + 2: PORTx
    //
    // so given PORTx you can write DDR with (PORT-1) and PIN with (PORT-2)

But how does that work for a timer or a UART or an SPI or ...?

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

clawson wrote:
But how does that work for a timer or a UART or an SPI or ...?

probably not - I've never looked into it.

 

The thread title & OP just referred to a port - I didn't mean to suggest that it was universally applicable to all peripherals (see #2)

 

EDIT

 

#9 updated to clarify this.

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. Jan 27, 2021 - 09:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to be clear I was responding to steve17's post #7. He suggested passing a pointer to class but only the Xmega derived io.h headers have the peripherals defined as class (well struct) the traditional tiny/mega do not (and thread is about 328P). That was my point.

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

clawson wrote:

Just to be clear I was responding to steve17's post #7. He suggested passing a pointer to class but only the Xmega derived io.h headers have the peripherals defined as class (well struct) the traditional tiny/mega do not (and thread is about 328P). That was my point.

Okay, I change my post.  I'd quit programming AVRs and take up stamp collecting. smiley  Actually that's not quite true.  The first AVR I programmed was the one on the Butterfly.  It came with a 169 which I replaced with a 649.  I managed to have port classes. The port registers were contiguous so could be a class/struct except what I called the interrupt enable registers.  Atmel called them PCMSK0 and PCMSK1.  I had separate files called pinChange0interruptRegs.h and pinChange1interruptRegs.h. I don't remember the details.

 

I also programmed in C++ an ARM7 chip.  This thing had the interrupt request pins of all devices in one 32 bit register.

 

Eventually I got tired of kludgy classes.  Apparently Atmel did too, so I migrated to Xmegas.

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

Yeah but those classes don't come with the compiler - it's something you have to write in the case of the tiny/mega. Whereas for Xmega they come supplied. Basically the difference between:

(mega328P)

#define UCSR0A _SFR_MEM8(0xC0)
#define UCSR0B _SFR_MEM8(0xC1)
#define UCSR0C _SFR_MEM8(0xC2)
#define UBRR0 _SFR_MEM16(0xC4)
#define UBRR0L _SFR_MEM8(0xC4)
#define UBRR0H _SFR_MEM8(0xC5)
#define UDR0 _SFR_MEM8(0xC6)

and

(mega4809.h)

/* Universal Synchronous and Asynchronous Receiver and Transmitter */
typedef struct USART_struct
{
    register8_t RXDATAL;  /* Receive Data Low Byte */
    register8_t RXDATAH;  /* Receive Data High Byte */
    register8_t TXDATAL;  /* Transmit Data Low Byte */
    register8_t TXDATAH;  /* Transmit Data High Byte */
    register8_t STATUS;  /* Status */
    register8_t CTRLA;  /* Control A */
    register8_t CTRLB;  /* Control B */
    register8_t CTRLC;  /* Control C */
    _WORDREGISTER(BAUD);  /* Baud Rate */
    register8_t CTRLD;  /* Control D */
    register8_t DBGCTRL;  /* Debug Control */
    register8_t EVCTRL;  /* Event Control */
    register8_t TXPLCTRL;  /* IRCOM Transmitter Pulse Length Control */
    register8_t RXPLCTRL;  /* IRCOM Receiver Pulse Length Control */
    register8_t reserved_1[1];
} USART_t;

When the latter then does:

#define USART0              (*(USART_t *) 0x0800) /* Universal Synchronous and Asynchronous Receiver and Transmitter */
#define USART1              (*(USART_t *) 0x0820) /* Universal Synchronous and Asynchronous Receiver and Transmitter */
#define USART2              (*(USART_t *) 0x0840) /* Universal Synchronous and Asynchronous Receiver and Transmitter */
#define USART3              (*(USART_t *) 0x0860) /* Universal Synchronous and Asynchronous Receiver and Transmitter */

it's easy to simply pass round those class/struct pointers. Same not true for original tiny/mega.

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

I did have a quick look at the register summary in the  328P datasheet, and it seems that not all its peripherals have all their registers in a contiguous block.

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. Jan 27, 2021 - 05:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>but only the Xmega derived io.h headers have the peripherals defined as class (well struct) the traditional tiny/mega do not

 

There is nothing preventing the creation of your own header/struct/whatever- especially on these simple mcu's. Not directed at you (cliff), but it seems too many think these headers are written in stone and you are forever resigned to use them. Not so. You are free to do something better, and if you are going to be using a series of mcu's for a while you will also have the benefit of something better for quite a while. The process of doing something better also has benefits that will not be wasted- you will understand existing headers better, what works well and what does not. In any case you will end up like a cook who knows how to use ingredients vs a cook (assembler) who only can only use something ready for the microwave/oven (me).

 

 

>I would pass a pointer to the port class.

 

This probably gets to heart of the matter- what exactly does this onewire class need? Its not really interested in a port, it uses a pin and has no need to know what port this pin is on or even how to manipulate pins. Most things could care less about a port and are interested in pins. So why not provide a pin. The same can be said for a uart class- what does it need, and why would anyone outside a uart class need to know about its registers or how it does its business.

 

An updated example, where a Uart class is added, with is own access to its registers (notice avr/io.h header is not used). The added onewire code (OneStrand) is mostly a guess from a brief look at its protocol, but you get the idea.

https://godbolt.org/z/v71bc3

In the case of an mcu which needs the tx/rx pins to be setup for something like a uart, you just tell it what pins to use and it calls the pin class function to setup the pins as needed- no need to deal with ports or do the pin work in the uart class, as that's not its job.

 

This is no different than normal C coding- you keep everything you can as private as you can. If only a function needs a static var, you put it in the function. If a c source file only needs access to a var or function, you make it static, and so on. A class deals in higher levels for things it has no need to do on its own, just like a user has no need to deal in the lower levels of what a class may do. Everyone works at a higher level until you get to where the work needs to be done, and these lower levels where the work is done are also quite simple as seen from the above example. The high level to low level is a pretty shallow level system, so it is also not difficult to find what is actually taking place with any high level code.

 

Its also similar in that it takes some thinking and iterations to finally figure out what you really want/need. The pin class idea in the above example probably took me while to figure out, and now I use a pin class for every mcu I touch (avr0/1,nrf52,stm32,samd,pic32mm,etc.), and they are all mostly the same. Although you can still access whole ports if needed (simply by allowing direct register access from a pin instance, or creating a port class, etc.), it seems to be  rare thing to do. Even when you do something like a 4bit lcd, it makes little difference in time to manipulate 4 pins individually (any pins you want) as opposed to writing to 4 consecutive pins on the same port and the benefit of using any pin easily outweighs the benefit of saving a few clock cycles (especially for an lcd which is not a big data mover).

 

I wanted to check out what the new raspberry pico would be like, so I just created a pin class in the online compiler to get a feel for the documentation, how the gpio works, etc.-

https://godbolt.org/z/jnKdqs

some of these mcu's do get a little difficult to manage their alternate functions (at least when you want to verify at compile time a valid pin/function is being used for a peripheral), and was just trying something different in this case

 

</rambleOff>

Last Edited: Wed. Jan 27, 2021 - 05:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


awneil wrote:

But they[1] are in a known relation to each other - so, given just one, you can compute the other two ...

 

EDIT

 

[1]  that is, specifically, the PORT, DDR, and PIN registers of a port.

clawson wrote:

awneil wrote:

so, given just one, you can compute the other two ...

Err yes...

Is it the exception that proves the rule?  [Mega128 port F, for those that are curious]

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

curtvm wrote:
Not directed at you (cliff), but it seems too many think these headers are written in stone and you are forever resigned to use them

No indeed. Somewhere on here you'll find my Python generator that regenerates headers for tiny/mega from the XML in the ADTF so it can group the peripheral registers into structs to give "Xmega like" headers to the pre-Xmegas. I actually did it so gdb/ddd could give a better display of peripherals but the structs obviously also lend themselves to the possibility of passing around base pointers to allow C++ classes to reuse a layout.

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

I don't use Atmel's register names.  They are too cryptic.  If most statements need comments, you are writing bad code, in my humble opinion.

 

The code that follows, which I guess was written by Atmel, makes  no sense to me.

 

register8_t RXDATAL;  /* Receive Data Low Byte */
    register8_t RXDATAH;  /* Receive Data High Byte */
    register8_t TXDATAL;  /* Transmit Data Low Byte */
    register8_t TXDATAH;  /* Transmit Data High Byte */
    register8_t STATUS;  /* Status */
    register8_t CTRLA;  /* Control A */
    register8_t CTRLB;  /* Control B */
    register8_t CTRLC;  /* Control C */
    _WORDREGISTER(BAUD);  /* Baud Rate */
    register8_t CTRLD;  /* Control D */
    register8_t DBGCTRL;  /* Debug Control */
    register8_t EVCTRL;  /* Event Control */
    register8_t TXPLCTRL;  /* IRCOM Transmitter Pulse Length Control */
    register8_t RXPLCTRL;  /* IRCOM Receiver Pulse Length Control */
    register8_t reserved_1[1];
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>I don't use Atmel's register names.  They are too cryptic

 

They are following the datasheet names, so no problem there (that is what you want). The flaw is they did not go to the next step- there is no connection from their enums to their structs, so you are left with doing both the work of figuring out which enum belongs to which register in a struct, and you also have to hunt down defines to make it all work.

 

Example of using their header, and then modifying a bit to do something a little better (just picking out rxmode as an example)-

https://godbolt.org/z/Mf34fe

 

So instead of having to know that rxmode is located in ctrlb, and finding a define to get involved in the bit manipulation in addition to an enum-

USART0.CTRLB = (USART0.CTRLB & ~USART_RXMODE_gm) | USART_RXMODE_CLK2X_gc; //register, define, enum

 

you could otherwise just let the compiler know what you wanted, and move the work to the compiler where it belongs-

USART0.RXMODE = USART_RXMODE_CLK2X; //register (bit group name), enum

 

and with the benefit of C++, you can keep the registers and enums inside the class, which means you can have short sensible names (since they are not in a global namespace). You also at the same time can eliminate the end user code from ever having to deal with registers at all, and you get real enforcement of types (enums) so you also get a free compile time assert for many things-

Usart0 usart0;

usart0.rxMode( usart0.CLK2X );

although a poor example for C++, as clock2x is also something the end user code should not be required to deal with- the user wants a baud rate, the usart class can deal with making it happen and use clk2x if it is needed.

 

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

curtvm wrote:

They are following the datasheet names, so no problem there (that is what you want).

Not what I want.  I have the name used in the manual in a comment after each of my register names.  I use that to search the manual.  By the way, the Xmega registers are in what Atmel calls the manual, not the data sheet.

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

>I have the name used in the manual in a comment after each of my register names

 

Now you have to do a reverse lookup via comments every time you want to figure out what your code is doing-

 

USART0.RXMODE = USART_RXMODE_CLK2X; //names directly from datasheet

vs

USART0.RECEIVE_MODE = USART_FAST; //now need to look these up via comments to see what registers and values are being used

 

If the goal was to use names than make more sense or more readable, then make a function. The function uses the datasheet names inside, the register names are then are hidden from normal use, but using the datasheet names in the function means you can easily verify a function is doing the right thing when there is some problem trying to be solved.  

 

Do whatever works for you.

 

 

>By the way, the Xmega registers are in what Atmel calls the manual, not the data sheet.

 

And stm32 uses the term reference manual, nordic uses product specification, an atmel samd uses datasheet, rpi rp2040 uses datasheet, an nxp lpc uses user manual, and on and on. Datasheet works fine as a generic term. I doubt anyone was confused.

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

The code should be self explanatory.  Xmegas have a manual and a data sheet.  Some Xmega users seem to be perpetually confused.

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

Not that I have checked it (I don't have the chip) but my guess is that PINF also is on memory addr $60 where it should be!

By placing it at $00 it can be bit addressed.

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

>The code should be self explanatory

 

At some point you have to match the datasheet. Matching the datasheet is easier when you use the same names.

 

void usart0_clock2x(){ USART0.RECEIVE_MODE = USART_FAST; }

 

void usart0_clock2x(){ USART0.RXMODE = USART_RXMODE_CLK2X; }

 

void usart0_clock2x(){ USART0.CTRLB = (USART0.CTRLB & ~USART_RXMODE_gm) | USART_RXMODE_CLK2X_gc; }

 

void usart0_clock2x(){ *(volatile uint8_t*)0x806 = (*(uint8_t*)0x806 & ~6) | 2; }

 

void usart0_clock2x(){ USART0.CTRLB = (USART0.CTRLB & 0b11110011) | 0b00000010; }

 

When clock2x appears to not be working and you want to figure out why, the first version will require another translation when you want to see what register/value is being used. The second is no guarantee you didn't make a mistake in their creation but the names match what you are looking at in the datasheet. The third is using the already provided mcu header and also matches the datasheet names but also requires some mental gymnastics to confirm the bit manipulation is being done correctly.

 

In all cases, once the function is correct its internal code should not have to be revisited again, nor should you ever need to use these register names/enums/defines again. But when you get a few dozen of those functions in a peripheral source file, its nice to be able to quickly decode a function while looking at the datasheet, where you do not have to translate defines or do bit manipulation computations in your head. The above #2 usart0_clock2x() function can more easily be decoded- the RXMODE bit group is being set to CLK2X, so I would rather be looking at a bunch of those type of functions than a bunch of any of the others.

 

A better example of looking at a number of functions-

https://godbolt.org/z/ro6nWs

Most of the functions are simple and can easily see what they do. Even though the end user code does not have to deal with the internals, you will- either when creating, modifying, or troubleshooting which will require the datasheet in all cases, and in all cases you have to deal with the datasheet register names. The friendly names come from the function names, and there is no need to introduce a redefinition of register names that will simply require you to decode them back into the names you need.

 

So choose whatever method you wish, I'm just explaining my thoughts and the size of this post is inversely proportional to its importance.