No what I am saying is that there is no point in writing a macro that i will use for years to come relying on the current ability to write gibberish to an unused bits knowing that they are unused as in some future device my macro is also applicable to this could cause havoc. As suggested earlier for multi bit writes I will AND the bits to be left alone with 1's and the bit's of interest with 0's and then OR the bit's of no interest with 0's and the desired sequence for the bits of interest.
I did not say that the register names are undefined, they actually appear to be struct variables. But the bit names have not been defined you havo to use position numbers instead
They use bit masks now. Instead of writing (1 << PB2) you use PORT_PIN2_bm or something like that.
The structs are just a different way to tell the compiler how to find the register. This simplifies code, so a half dozen instances of a GPIO port can use the same exact register names (address offset) while referring to unique ports (base address).
Would examples not be helpful here? Here's some init I wrote last month:
/**
* Set up all the registers needed for operation
*/
void initialize() {
// Set default pin states
PORTA.OUT = BIT_PIN7;
PORTA.DIR = 0;
PORTB.OUT = BIT_PIN3 | BIT_PIN4 | BIT_PIN5 | BIT_PIN6 | BIT_SPI_RS;
PORTB.DIR = BIT_LED1 | BIT_LED2 | BIT_LED3 | BIT_SPI_RS;
// ...
// Initialize TCC0, 16 bits, PWM, MAX = 57600, PC4/OC1A (2048Hz)
TCC0.PER = PWM_PERIOD;
TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SS_gc;
TCC0.CTRLA = TC_CLKSEL_DIV1_gc;
TCC0.CCA = 0;
HIRESC.CTRLA = HIRES_HREN_TC0_gc;
TCC0.INTCTRLA = TC_OVFINTLVL_HI_gc;
// ...
// Enable interrupt sources
PMIC.CTRL = PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm;
sei();
// Done, turn on power LED
PORT_LED1.OUTSET = BIT_LED1;
}
("..." is a bunch of stuff I cut out here for simplicity's sake; it's long enough as it is!)
Meanwhile, in pindefs.h, I have all the names used above that aren't predefined. I generated these from a list of pin names (what the pins do in the circuit), so I don't have to maintain them by hand. Many SDKs include this sort of functionality for init and config and abstraction, although they're often ugly as sin (ST Cube for example..).
#ifndef PINDEFS_H_INCLUDED
#define PINDEFS_H_INCLUDED
#define BIT_PIN0 PIN0_bm
#define BIT_PIN1 PIN1_bm
#define BIT_PIN2 PIN2_bm
#define BIT_PIN3 PIN3_bm
#define BIT_PIN4 PIN4_bm
#define BIT_PIN5 PIN5_bm
#define BIT_PIN6 PIN6_bm
#define BIT_PIN7 PIN7_bm
#define PIN_VREF PIN0
#define BIT_VREF (1 << PIN0)
#define PORT_VREF PORTA
#define PIN_VO PIN1
#define BIT_VO (1 << PIN1)
#define PORT_VO PORTA
#define PIN_IO PIN2
#define BIT_IO (1 << PIN2)
#define PORT_IO PORTA
// ...
I named this as PIN_, BIT_ and PORT_ for each pin, followed by the name (VREF, VO and IO above, among others). This gives the assignment PORT_IO.OUTSET = PIN_IO, say to turn on the pin.
In older devices somewhere there was something like:
#define PA0 0
This no longer exists and PA0 in your code will cause a compile error. You now have to use "0"
If you like that convention, you can always define your own, you know. You aren't stuck with exactly only the headers in the SDK!
It took me under four hours to list all the pins I used (64 pin device), write the script to generate the code, and paste it in the header (with minor changes and additions). That's peanuts. If it takes someone else an hour or two to configure it for some other device or platform, that's no problem at all.
If you know it's going to save you a
week of work sometime later (say, porting to other platforms is
within the project scope), by all means spend the time worrying about how it's going to be set up.
But yeah, if you're just doing cutesy one-offs, like I did above, it doesn't freakin' matter, man. Don't sweat it, just sit down and write.
Tim