I'm learning Embedded System by following this tutorial. In their attached code for LED blinking on TM4C123, they created the variable ulLoop which made me confused, since they just asigned the click enabling byte to ulLoop but never used it afterwards. However, I tried deleting the line writing ulLoop = SYSCTL_RCGCGPIO_R; and the LED stop blinking, as they said in the tutorial "The uloop variable and the statement containing uloop is present there only to halt 3 clock cycles before moving to peripherals, which is a must while working with TIVA."
I cannot understand what did they mean by "halt 3 clock cycles" and "moving to peripherals", and why it needs to halt 3 clock cycles, not 4, or 5 cycles, or not at all. In addition, if I know nothing about what's mentioned in the tutorial regarding the magic variable, just finding out the program not working, how am I supposed to know where the problem is without further information, since during building there is 0 errors and warnings. Pealse pardon with me if the question is not asked in a right way or sounds silly.
#define SYSCTL_RCGCGPIO_R (*(( volatile unsigned long *)0x400FE608 ) )
#define GPIO_PORTF_DATA_R (*(( volatile unsigned long *)0x40025038 ) )
#define GPIO_PORTF_DIR_R (*(( volatile unsigned long *)0x40025400 ) )
#define GPIO_PORTF_DEN_R (*(( volatile unsigned long *)0x4002551C ) )
#define GPIO_PORTF_CLK_EN 0x20
#define GPIO_PORTF_PIN1_EN 0x02
#define LED_ON1 0x02
#define GPIO_PORTF_PIN2_EN 0x04
#define LED_ON2 0x04
#define GPIO_PORTF_PIN3_EN 0x08
#define LED_ON3 0x08
#define DELAY_VALUE 1000000
volatile unsigned long j=0;
static void Delay(void){
for (j=0; j<DELAY_VALUE ; j++);
}
int main ( void )
{
volatile unsigned long ulLoop ; // I don't understand why creating this variable
SYSCTL_RCGCGPIO_R |= GPIO_PORTF_CLK_EN ;
ulLoop = SYSCTL_RCGCGPIO_R; // But if not adding this line the LED won't blink
GPIO_PORTF_DIR_R |= GPIO_PORTF_PIN1_EN ;
GPIO_PORTF_DEN_R |= GPIO_PORTF_PIN1_EN ;
GPIO_PORTF_DIR_R |= GPIO_PORTF_PIN2_EN ;
GPIO_PORTF_DEN_R |= GPIO_PORTF_PIN2_EN ;
GPIO_PORTF_DIR_R |= GPIO_PORTF_PIN3_EN ;
GPIO_PORTF_DEN_R |= GPIO_PORTF_PIN3_EN ;
// Loop forever .
while (1)
{
GPIO_PORTF_DATA_R &= LED_ON3;
GPIO_PORTF_DATA_R &= LED_ON2;
GPIO_PORTF_DATA_R |= LED_ON1;
Delay();
GPIO_PORTF_DATA_R &= LED_ON1;
GPIO_PORTF_DATA_R &= LED_ON2;
GPIO_PORTF_DATA_R |= LED_ON3;
Delay();
GPIO_PORTF_DATA_R &= LED_ON3;
GPIO_PORTF_DATA_R &= LED_ON1;
GPIO_PORTF_DATA_R |= LED_ON2;
Delay();
}
}
Since in this the previous line
SYSCTL_RCGCGPIO_R |= GPIO_PORTF_CLK_EN ;
The program is enabling the clock, this line
ulLoop = SYSCTL_RCGCGPIO_R;
is just a dummy code that gives a little bit of time to the microcontroller clock to stabilize.
You will find this valid for any microcontroller you will be working with, that after clock setting you must allow some time for the clock to stabilize.
Now, Why 3 clock cycles? This info you should find when reading the microcontroller datasheet in which it shall be specified how many clock cycles are needed to stabilize the clock?
Why not 5 or more? Of course, you don't want to waste so many clock cycles in this operation, so the rest of the program can be executed as soon as possible.
How does this dummy line correspond to 3 clock cycles?
ulLoop = SYSCTL_RCGCGPIO_R;
Well, this is really different from one controller to another or more specifically from compiler to the other. The compiler does translate this c-code line into assembly code. each assembly line takes one clock cycle for execution. So it seems like whoever wrote this code, just looked out at the generated assembly code from the compiler and finds out this line is translated to 3 assembly instructions.
how am I supposed to know where the problem is without further information
In the embedded system world, this can be achieved by debugging. Some of the issues are really hard to debug especially when it's something in the controller initialization sequence.
You should be very careful when initializing the controller (clock, peripherals) by following the datasheet instructions/recommendations.
Related
With timers, I want to toggle an LED every one second. I'm using ATMega32 and the clock frequency is 1MHz. I can get to 0.1 second using the 8-bit counter, and for each 10 timer interrupts, I blink the led.
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
typedef unsigned char u8_t;
typedef unsigned short u16_t;
void func();
int main(void)
{
DDRC = 0x80;
TCCR0 |= (1 << WGM01); // CTC bit.
TCCR0 |= (1 << CS02) | (1 << CS00); // Prescalar = 1024.
OCR0 = 98; // 98 ticks correspond to roughly 0.1 second with 1024 prescaler
TIMSK |= (1 << OCIE0);
TCNT0 = 0;
sei();
while(1)
{
if (!(TIFR & (1 << OCF0))) {
func();
}
}
}
void func()
{
static u8_t extra_time = 0;
if (extra_time == 10)
{
extra_time = 0;
PORTC ^= 0x80;
}
else extra_time++;
TIFR |= (1 << OCF0);
}
In the preceding code, I do not define an ISR for the TIMER0_COMP_vect interrupt.
From the datasheet:
The OCF0 bit is set (one) when a compare match occurs between the Timer/Counter0 and the
data in OCR0 – Output Compare Register0. OCF0 is cleared by hardware when executing the
corresponding interrupt handling vector. Alternatively, OCF0 is cleared by writing a logic one to
the flag. When the I-bit in SREG, OCIE0 (Timer/Counter0 Compare Match Interrupt Enable), and
OCF0 are set (one), the Timer/Counter0 Compare Match Interrupt is executed.
emphasis mine.
Therefore, by the emphasized sentence, I can check for the OCF0 bit for being a zero, and if so, I can "handle" the on-compare-match event.
However, the LED blinks much more frequently (not even a tenth second between each blink but I cannot measure of course).
This works fine if I just set an ISR on TIMER0_COMP_vect and check for nothing, but I want to know why is the OCF0 always(?) logic 0, hence "on", even though I set it to high on each func() call. And what's the problem with this method.
Keep reading the next line in the data sheet
When the I-bit in SREG, OCIE0 (Timer/Counter0 Compare Match Interrupt Enable), and OCF0 are set (one), the Timer/Counter0 Compare Match Interrupt is executed.
then take a look at your code
you have Enabled Compare Match Interrupt
TIMSK |= (1 << OCIE0);
you have Enabled the Global interrupt (I-bit in SREG)
sei();
so whenever output compare flag OCF0 is set then all the 3 conditions for interrupt have occurred and interrupt is automatically fired
when an interrupt has been fired the program flow of execution will jump to a specific memory location corresponding to this interrupt to execute the code and handle it,
but you did not provide any code for this interrupt (no ISR), so the microcontroller does not know what he can do because you did not tell him, so simply he will RESET
and so on, interrupt with no Handler keep fired makes the microcontroller keep
reset
finally, when you add an empty ISR you Provide a code which tell the microcontroller to do nothing if this interrupt is fired and the micro will not reset because he knows how to handle it
if you want to keep track OCF0 flag by yourself delete this line
TIMSK |= (1 << OCIE0);
I am using atmega328P , as it shown in the attached picture
when the interruption is executed , the program doesn't turn back to the main to execute the rest of the program ?
i made 2 functions ; one to blink led in portC and the other in PORT D
the Led in PORT D (interruption) is working fine but the Led for PORT C in the main is not executed
is there a problem ??
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define ACK 0x01
void portc_led(void)
{
PORTC|=(1<<5);
_delay_ms(100);
PORTC=(0<<5) ;
_delay_ms(100);
PORTC|=(1<<5);
_delay_ms(100);
PORTC=(0<<5) ;
_delay_ms(100);
}
void portd_led(void)
{
PORTD|=(1<<7);
_delay_ms(1000);
PORTD=(0<<7) ;
_delay_ms(100);
PORTD|=(1<<7);
_delay_ms(1000);
PORTD=(0<<7) ;
_delay_ms(100);
PORTD|=(1<<7);
_delay_ms(1000);
PORTD=(0<<7) ;
_delay_ms(100);
PORTD|=(1<<7);
_delay_ms(1000);
PORTD=(0<<7) ;
_delay_ms(100);
}
int main(void)
{
DDRB |= (1<<2)|(1<<3)|(1<<5); // SCK, MOSI and SS as outputs
DDRB &= ~(1<<4); // MISO as input
SPCR |= (1<<MSTR); // Set as Master
SPCR |= (1<<SPR0)|(1<<SPR1); // divided clock by 128
SPCR |= (1<<SPIE); // Enable SPI Interrupt
SPCR |= (1<<SPE); // Enable SPI
DDRC= 0xFF ; // set PORT C as output
DDRD = 0xFF ;
sei();
spi_send_data(ACK);
portc_led();
}
ISR(SPI_STC_vect)
{
portd_led();
}
first of all your code will have a compile Error! because you don't provide a reference to spi_send_data function
but let us imagine that you include it above this piece of code
analyse your code
you say that
interruption is executed , the program doesn't turn back to the main
the program execution path definitely will ruturn to main routine... where will it go ?! :)
the code will execut the portc_led function one time (and only one time) maybe before interrupt or maybe after interrupt or maybe interrupt happen in between the function ...
so maybe portc_led alredy excuted happen befor interupt but you did not see it becuse it executed in only 400 ms !! and after finsh interupting nothing to do just wait for other interupt ! ..
simple solution : try to change the 100ms delay in portc_led to bigger delay and you will see ...
little advices for improve practicing techniques
look at this code PORTB=(0<<5) equivalent to portB=0b00000000 when you try to clear single bit in register you clear all register's bits ! which is not good ...use this code PORTB&=~(1<<5) for clear single bit which make bitwaise & between portc and 0b11101111 only change single bit and keep other bits as it is
always in interrupt routine make it small as much as you can ... just raise a flags and handle it in the main loop ... read more way you should make it small routine
your program dose not have a mian loop !! (sometimes called super loop)..
this loop is simply an infinity loop come after initalization of your systerm and run over and over ... some compiler add empty infinity loop at the end of main routine and other compiler dose not add ... it's a good practice to have a main loop in the main routine even if you not use it ! To keep your program alive
modify the code
not that the following code will not excute blancking in parallel (simultaneously) it will excute them in series (not simultaneously).. if you like to have a parallel blanking use timer interupt in portc_led insted of delay /or use an RTOS (a little bit advanced topic)
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define ACK 0x01
volatile char spi_interupt_flag = 0;
void portd_led(void)
{
// this will blank led in pd7 for 3 sec
for(char i=0;i<6;i++){
PORTD^=(1<<PD7) ; // toggle pd7
_delay_ms(500); // you will see 500 ms blank
}
}
int main(void)
{
DDRB |= (1<<2)|(1<<3)|(1<<5); // SCK, MOSI and SS as outputs
DDRB &= ~(1<<4); // MISO as input
SPCR |= (1<<MSTR); // Set as Master
SPCR |= (1<<SPR0)|(1<<SPR1); // divided clock by 128
SPCR |= (1<<SPIE); // Enable SPI Interrupt
SPCR |= (1<<SPE); // Enable SPI
DDRC= 0xFF ; // set PORT C as output
DDRD = 0xFF ;
sei();
spi_send_data(ACK); // this code will make compile error if you not provide a source for implementation
while(1){
if (spi_interupt_flag)
{
//this code only execute when data received from SPI
portd_led(); //do whatever you want to do for handle it
spi_interupt_flag = 0; //reset the flag again
}
PORTC^=(1<<PC5); //toggle pc5 for ever
_delay_ms(1000); // pc5 will toggle every 1 sec unless ther is interupt
}
}
ISR(SPI_STC_vect)
{
// just set a flag for handle interrupt in main
spi_interupt_flag = 1;
}
You have two conceptional errors in your code:
As long as the interrupt service routine is running, the main function can not run.
After portc_led(), the main() functions returns. Depending on the runtime environment of your compiler system (presumably some GCC) it finally runs in an endless loop, doing nothing. Only the interrupts keep triggering.
I'm using an Atmega328. I have output pins that are "randomly" distributed along the pinout, i.e. they don't belong to the same ports. For example, my output pins could be PB0, PB4, PC1, PC3 and PD1.
I have always used Assembly to program microcontrollers, so this would be my first time using C. What I want to know is if there is a way to avoid using DDRx and Px for each pin to set them or clear them.
For example, I would like to use something like this to set the first bit of Port B:
#define NAME_1 DDRB,0
sbi NAME_1;
Is this possible?
EDIT:
Maybe I didn't express myself clearly. What I want is to be able to refer to certain I/O port pins with some meaningful names. For example, name PD3 "blue_LED", so that the code is more readable and, if the blue LED's position is changed later, the code can be easily modified. Stated another way, I want to be able to turn on and off certain pins with their names not being hard-coded. Is there a way?
The sbi instruction is special in that it directly manipulates a bit in an I/O Port on the AVR platform. The normal course with I/O ports is that you have to use other instructions (like out) to copy whole words between the I/O port and a register.
That said, there's no sbi in C. C just doesn't know about these special features of one particular platform. For the assembly you give as an example, you would write in C:
DDRB |= 1<<0;
I personally think this looks quite concise, but of course you COULD define a macro
#define sbi(x,b) (x) |= 1<<(b)
sbi(DDRB, 0);
(where b is the "bit number") and maybe the other way around
#define cbi(x,b) (x) &= ~(1<<(b))
cbi(DDRB, 0)
This would work, but I recommend not to use it. While the first notation DDRB |= 1<<0; is obvious to any C programmer, using macros like this probably isn't.
As a final note, if you are concerned about performance: I haven't verified this, but I'm pretty sure avr-gcc is smart enough to emit sbi and cbi instructions when a bit mask operation on an I/O port effectively just changes a single bit. edit: see JLH's answer for an experimental result that gcc-avr indeed is smart enough to emit these sbi/cbi instructions.
I use avg-gcc on to program Atmel AVRs and the support packages most definitely know about cbi, sbi, and all instructions for that matter. So there's no need to resort to assembly unless you want to. Here's some disassembled C to prove it.
PORTD |= (1 << PD0);
d8: 58 9a sbi 0x0b, 0 ; 11
Now I'll show you how. You can set each bit of each port to do either input or output very easily.
DDRA |= (1<<PA0);
has the effect of making pin 0 on port A an output as shown:
bit76543210
00000001 // or-ing the 1 adds 1 leaving the other bits alone.
To also make pin A3 an output, do this:
DDRA |= (1<<PA3);
bit76543210
00001001 // now the DDRA register looks like this, pins 0 and 3 set as outputs
Tedious, right? Well, these right-side expressions evaluate to constants so you can combine these into one statement:
DDRA |= (1<<PA0) | (1<<PA3);
and the compiler will fold the right hand side into one constant and |= it into the register. So, you really need only one statement like this per port, and the compiler makes it very efficient.
That takes care of direction -- input or output. The next is to set and clear outputs.
To set an output:
PORTD |= (1<<PD); // turns pin 0 on port D on (high) as an output.
PORTD &= ~(1<<PD); // turns pin 0 on port D off (low) as an output.
PORTD ^= (1<<PD); // toggles it (high-> low) or (low->high as an output.
EDIT:
To the extra requirements you named about using meaningful names, of course you can do that. I routinely do this to avoid having to remember what's connected to what. For example, from that same project:
#define LED_INDICATOR_PIN PA0
#define LED_INDICATOR_PORT PORTA
#define LED_INDICATOR_DDR DDRA
I refer to the 'friendly' names in my code:
void initialize(void)
{
// Set up an output pin to blink an LED to show sign of life.
LED_INDICATOR_DDR |= (1 << LED_INDICATOR_PIN);
// Set up TTY port.
init_uart();
}
void toggleLED(void)
{
LED_INDICATOR_PORT ^= (1 << LED_INDICATOR_PIN);
}
Much easier to read. Is that what you're after? I know it's what I do every time.
With C++ I wrote something like this:
//IO = I/O Subsystem
inline void Io_LEDred_Configure() {DDRB |= 1<<0; } //Port B0
inline void Io_LEDred_On() {PORTB |= 1<<0; }
inline void Io_LEDred_Off() {PORTB &= ~(1<<0);}
inline void Io_LEDgreen_Configure() {DDRD |= 1<<3; } //Port D3
inline void Io_LEDgreen_On() {PORTD |= 1<<3; }
inline void Io_LEDgreen_Off() {PORTD &= ~(1<<3);}
If you have to switch an IO to a different port you have to change only those three lines and you're done.
In C++ the compiler emits exactly that code what you as an assembler coder would write. In C you have to use a regular function. But the calls coming with some overhead. If you want to avoid this you have to use macros:
#define IO_LEDRED_CONFIGURE DDRB |= 1<<0
#define IO_LEDRED_ON PORTB |= 1<<0
...
So I'm programming a TI Tiva C Launchpad (TM4C123GH6PM) to just simply change LED color based on switch input. This should be relatively simple, but I'm having trouble unlocking my desired Port F and writing the commit bits. When I go to write the CR bits (in other words, to enable the switches) the debugger goes straight to "BusFault_Handler."
My code up to that point is as follows:
#define GPIOLock_PortF (*(unsigned int*)0x40025520)
#define GPIOCR_PortF (*(unsigned int*)0x40025524)
#define Unlock ((unsigned int)0x4C4F434B)
#define SWs ((unsigned int)0x11)
int main(){
GPIOLock_PortF = Unlock; //undo GPIOLock to get to GPIOCR
GPIOCR_PortF = SWs; //enable commit for switches
As soon I try to run the first assembly instruction for that last line of C...
MOVS R0, #17
...the debugger just drops to "BusFault_Handler."
Can anyone see something obviously wrong here? Have I misunderstood the datasheet?
Thanks!
You have missed to enable the clock for GPIOs for PortF
The Initialisation Procedure is stated in the datasheet page 656 here
http://www.ti.com/lit/ds/spms376e/spms376e.pdf
you have to enable clock to Port F page 340.
#define SYSCTL_RCGC2_R (*(unsigned int *)0x400FE108)
#define GPIOLock_PortF (*(unsigned int*)0x40025520)
#define GPIOCR_PortF (*(unsigned int*)0x40025524)
#define Unlock ((unsigned int)0x4C4F434B)
#define SWs ((unsigned int)0x11)
int main(){
SYSCTL_RCGC2_R |= 0x00000020; // enable clock for PortF
GPIOLock_PortF = Unlock; //undo GPIOLock to get to GPIOCR
GPIOCR_PortF = SWs; //enable commit for switches
I am currently working on setting up a framework in C for usage between several microcontrollers.
The framework has to carry all device specific code, so the application only contains the abstract usage of peripherals (like SerialPort_Read, write, SetBaudRate, etc etc.)
One of the things I am struggling with to find a solution for in C are the I/O pin map. I've seen projects (like the very very popular Arduino) where the pin map is putten in a LUT (look up table) which is used during runtime. However, this LUT will never be modified during runtime, so there is no use to have this in the memory.
For example, this function resolves some bit indexes and registers from some 'const uint' tables, and either sets or clears a bit:
void pinMode(uint8_t pin, uint8_t mode)
{
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *reg;
if (port == NOT_A_PIN) return;
// JWS: can I let the optimizer do this?
reg = portModeRegister(port);
if (mode == INPUT) {
uint8_t oldSREG = SREG;
cli();
*reg &= ~bit;
SREG = oldSREG;
} else {
uint8_t oldSREG = SREG;
cli();
*reg |= bit;
SREG = oldSREG;
}
}
Because this is actual C code running on the controller it's draining effiency and speed. I'd rather define some sort of macro that does the same thing, but is already resolved during compilation to a 'one-liner' that can be compiled much more efficiently:
GPIO_Write(PORTA, 5, 1); // Write '1' to pin 5 on PORTA
> LATA |= 1<<5; // Sets bit 5 high
GPIO_Tris(PORTA, 4, OUTPUT); // Set pin 4 on PORTA to output
> PORTA &= ~(1<<4); // sets pin 4 as output I/O type
Does anyone know if it's possible (and how) to define and use a look-up table with a macro in C?
At this moment I am using the MicroChip C30 compiler, which I believe is based in GCC. It's supposed to be portable between different compilers, including MicroChip C18, C32 and in further also ARM and AVR.
For your specific example, something along these lines will work:
#define WRITE_PORTA LATA
#define GPIO_Write(port, pin, value) \
(value ? WRITE_##port |= (1U << (pin)) \
: WRITE_##port &= ~(1U << (pin)))
#define INPUT 0
#define OUTPUT 1
#define GPIO_Tris(port, pin, direction) \
((direction) == INPUT ? port |= (1U << (pin)) \
: port &= ~(1U << (pin)))
You'll have to make sure to define LATA and PORTA in a way the system will understand - in particular trying to overload its meaning the way it seems to be in your example might be hard to resolve.
Which processor or microcontroller are you targeting?
You might be underestimating the usefulness of the LUT.
For many processors, the LUT does more than map a 'logical' pin number to a single value, the 'physical' pin number. The LUT maps the 'logical' pin number to several pieces of information.
In general, the 'logical' pin is mapped to the port address of the appropriate read/input or write/output register, and the bit offset within the read or write register. So the pin value, on many MCU's, is really mapped to a struct. It might also include a mapping to the data direction register and fields within it, as well as registers which set the state of pull-up or pull-down resistors.
For example, I have code to multiplex a 8x8 display. At run-time, I need to use pinMode to turn a pin from an output to a high impedance input, and so that information needs to be encoded somehow.
It is possible to do this sort of thing, with some ingenuity, on some MCU's.
ARM MCU's (and I believe 8051, though I've never used one) using 'bit band addressing' http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/CHDJHIDF.html
This assigns a unique memory address for each port pin, and fixed offsets can derive the address of the pin for the other data register, and other functions. This is not magic, the code encodes the information that is often stored in the LUT.
For other MCU's, they really do need both port and bit position, so it is two values for each pin number.
If you are willing to ditch the idea of using integers for pins, and instead use names, like P0, P1, then you could initialise a lot of const struct's, one per pin name, and your functions would take the const struct values. The struct would contain the initialised port and bit offset or bit mask values. The compiler may be able to optimise for speed. This would avoid having a LUT, but would still use similar amounts of space for pins that are used. You might be able to arrange it so that unused pins would not need to be included in the code, and hence saving space.
Edit: If you are willing to use C++, I'd suggest C++ templates which can give a much better solution than macros. They can be type safe, and are often easier to debug (if you have hardware debugging, e.g. JTAG and gdb)
Consider the following macro:
#define write(port, pin, value) do { \
if (value) \
LAT##port |= 1 << pin; \
else \
LAT##port &= ~(1 << pin); \
} while (0)
Usage:
write(A, 3, 1); // compiles to LATA |= 1 << 3;
write(B, 2, 0); // compiles to LATB &= ~(1 << 2);
Is that the kind of thing you were after?
I've seen it done (https://github.com/triffid/Teacup_Firmware/blob/Gen7/arduino.h) with a couple macros:
/// Read a pin
#define _READ(IO) (IO ## _RPORT & MASK(IO ## _PIN))
/// write to a pin
#define _WRITE(IO, v) do { if (v) { IO ## _WPORT |= MASK(IO ## _PIN); } else { IO ## _WPORT &= ~MASK(IO ## _PIN); }; } while (0)
/// set pin as input
#define _SET_INPUT(IO) do { IO ## _DDR &= ~MASK(IO ## _PIN); } while (0)
/// set pin as output
#define _SET_OUTPUT(IO) do { IO ## _DDR |= MASK(IO ## _PIN); } while (0)
// why double up on these macros? see http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
/// Read a pin wrapper
#define READ(IO) _READ(IO)
/// Write to a pin wrapper
#define WRITE(IO, v) _WRITE(IO, v)
/// set pin as input wrapper
#define SET_INPUT(IO) _SET_INPUT(IO)
/// set pin as output wrapper
#define SET_OUTPUT(IO) _SET_OUTPUT(IO)
with:
#define DIO0_PIN PIND0
#define DIO0_RPORT PIND
#define DIO0_WPORT PORTD
#define DIO0_PWM &OCR0B
#define DIO0_DDR DDRD
#define DIO1_PIN PIND1
#define DIO1_RPORT PIND
#define DIO1_WPORT PORTD
#define DIO1_PWM &OCR2B
#define DIO1_DDR DDRD
...
You could modify the macros to take straight integers rather than DIOn