On the 328P, this is easy. Just suppose I wanted to enable pull-ups on 5 of the pins on port d:
PORTC |= 0b00111111;
which would enable pull-ups in PD0-PD4.
I am struggling to accomplish the same on the 4809. Its a different processor, and looking at the datasheet I think I need to write to PORTD.PINnCTRL, but how to I write a 1 to the PULLUPEN bit) of that? I am presuming that I need to specify a port number for 'n', for example PORTD.PIN2CTRL and then somehow write a 1 to bit 3, so would something like this do it:
PORTD.PIN2CTRL = PORTD.PIN2CTRL | (1<<3);
The other problem is that this does not lend itself to iteration so it seems I can't do this in a loop for all pins but would need 8 individual commands for 8 pins?
Surely there must be an easier way?
Yes, it can probably be done with a for loop and pinMode() if you had a pin map, but I can't believe that setting the register directly should be THAT complex?
As much research as I will take time to do suggests that this is, indeed, the case.
Here's a macro chatGPT wrote, I cannot test it
#define ENABLE_PORT_PULLUPS(port) \
do { \
(port).DIRCLR = 0xFF; /* Set all pins to input */ \
for (uint8_t i = 0; i < 8; i++) { \
(&(port).PIN0CTRL)[i] |= PORT_PULLUPEN_bm; \
} \
} while (0)
So you can
ENABLE_PORT_PULLUPS(PORTA); // Enables pull-ups on all PA0–PA7
Part of it seems to be moving away from confusing multiple meaings of writing like we are accustomed to doing PORTD = 0xff;
Its an interesting idea to set up a macro, but I am little puzzled by what AI has provided here although it may just be a neat trick:
Suppose I pass it PORTD, then its taking the address of PORTD.PIN0CTRL register which would be a pointer, and then iterating from that pointer to find the subsequent pin registers and changing the PULLUP bit for each. If it actually works, that could be a very neat solution.
I also just found a helpful discussion here:
After reading through that, it would seem that what AI told me below cannot be correct:
I'm not a macro wizard, but if you copy the entirety of the definition from the code window it looks like it just loops over all pins of the port you pass in as an argument.
The backslashes mean it's seen as one line, the do { } while (0) is a well-known macro hack so you can use the it like a single statement - you provide the semicolon.
ENABLE_PORT_PULLUPS(PORTA) ;
The fist part is the expansion of the macro.
Google "C macro do while" for a more competent explanation.
You could do that same thing with a function call, that would be my preference.
And yeah, squinting at your AI's output makes mine look smarter.
Aside from its use of the constant PORT_PULLUPEN_bm instead of what your code does with 1, the method is identical. Set a bit in the pin control register, something the 328p has not.
Hmm, I tried adding some bits and compiling but its telling me that PORT does not have DIR method, nor an IN method nor an OUT method nor a PINnCTRL method, but obviously the datasheets says it does, so I must be missing something....
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp: In function 'void readyGpibDbus()':
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp:1294:9: error: 'class PORTDClass' has no member named 'DIR'
PORTD.DIR &= 0b00000000;
^~~
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp:1298:14: error: 'class PORTDClass' has no member named 'PIN0CTRL'
&PORTD.PIN0CTRL)[i] |= PORT_PULLUPEN_bm; \
^~~~~~~~
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp: In function 'uint8_t readGpibDbus()':
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp:1307:17: error: 'class PORTDClass' has no member named 'IN'
return ~PORTD.IN;
^~
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp: In function 'void setGpibDbus(uint8_t)':
/mnt/ntfs/Data/Data/Programming/Arduino/AR488/Testing/Mega4809/AR488/AR488_Layouts.cpp:1315:9: error: 'class PORTDClass' has no member named 'OUT'
PORTD.OUT = ~db;
^~~
exit status 1
Compilation error: 'class PORTDClass' has no member named 'DIR'
Oh, it isn't that complex. And, importantly, it's using Arduino standard functions and it's portable between many different Arduino boards with different chips.
Does the extra performance you get from direct port manipulation really justify moving away from those standards and portability? Only you know for sure, but please try to be honest with yourself about it. ATMEGA4809 will never be as important or long lived as ATMEGA328, they are the old generation and have already been outclassed by MCU based on ARM and that may be in turn replaced by RISC V and so on in ever quicker succession.