#122 ATMEGA 328P Port Manipulation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* // Pinout of the 328P: __ __ 1 (RESET) |1 \/28| [D19][A5] PC5 (SDA) 2 (RX) PD0 [D0] |2 27| [D18][A4] PC4 (SDA) 3 (TX) PD1 [D1] |3 26| [D17][A3] PC3 4 PD2 [D2] |4 25| [D16][A2] PC2 5 PD3 [~D3] |5 24| [D15][A1] PC1 6 PD4 [D4] |6 23| [D14][A0] PC0 7 VCC |7 22| GND 8 GND |8 21| AREF 9 (XTAL) PB6 |9 20| VCC 10 (XTAL) PB7 |10 19| [D13] PB5 11 PD5 [~D5] |11 18| [D12] PB4 12 PD6 [~D6] |12 17| [D11~] PB3 13 PD7 [D7] |13 16| [D10~] PB2 14 PB0 [D8] |14 15| [D9~] PB1 ------- */ |
Port manipulation was not important to me when I started working with microcontrollers, in fact I didn’t even know it existed and never felt the need for it especially after programming mostly in java script and PHP using functions like digitalread(pin); or digitalwrite(pin); etc. was second nature and speed or efficiency considerations were not really necessary or thought about that much.
Then after intermittently playing around with embedded ICs I got serious during the corruption virus of 2019 during lockdowns and other dilemmas while a lot of corrupt and diabolical cronies enriched themselves. I had a lot of time to focus on a few different things. There was no way I was going to let the circumstances get to me I made sure I was achieving at least one big goal each year and not get bogged down.
I took a look at the IC market and even though there was a chip shortage there were some IC’s available. This mad me try and get into efficient code and cost per IC. I came to the conclusion that I could get hold of IC’s with low memory + flash and very affordable prices. However in order to use them I would have to learn to use port manipulation, macros and interrupts in very efficient ways to the best of my ability at the time.
Now I needed to find new and comprehensive writeups of the principal and so I found the SpenceKonde
megaTinyCore for the new at the time tinyAVR 0/1/2-series IC’s. The core has a lot explained over the years and was very helpful and easy enough to understand but took a bit of time and real world practical implementations which made learning way more fun and easy. I always need to do practical with theory when it comes to learning I find it very hard to picture learning anything without using it practically.
Now this is about port manipulation on the 328P and I’ll get into the actual implementation now without further ado.
The ATMEGA 328P has 3 ports namely B, C, D with B (digital pin 8 to 13), C (Analog input pins) and D (digital pins 0 to 7).
For the basics you only need to understand 3 types of registers you will be manipulating.
For example:
Port B
PORTB – Maps to Arduino digital pins 0 to 7 Data Register – read/write
DDRB – The Port B Data Direction Register – read/write
PINB – The Port B Input Pins Register – read only
Port C
PORTC – Maps to Arduino digital pins 8 to 7 Data Register – read/write
DDRC – The Port C Data Direction Register – read/write
PINC – The Port C Input Pins Register – read only
Port D
PORTD – Maps to Arduino digital pins 14 to 19 Data Register – read/write
DDRD – The Port D Data Direction Register – read/write
PIND – The Port D Input Pins Register – read only
So an example using DDRD (Data Direction Register) to set input and output pins:
1 2 3 4 |
// DDRD = 0b11111110; // sets Arduino pins 1 to 7 as outputs, pin 0 as input DDRD = DDRD | 0b11111100; // this is safer as it sets pins 2 to 7 as outputs // without changing the value of pins 0 & 1, which are RX & TX |
Another example is to set digital pins 7,5,3 high:
1 2 |
// PORTD = 0b10101000; // sets digital pins 7,5,3 HIGH |
Now you can do port manipulation with a bit shift instead of binary or hex. this makes things a bit more readable… excuse the pun. 🙂
For example to set the input or output of a single pin you could use:
1 2 3 |
// DDRB |= (1 << PB3);//replaces pinMode(DATA_PIN, OUTPUT); DDRB &= ~(1 << PB3); //replaces pinMode(LEARN_PIN, INPUT); |
Example using hexadecimal:
1 2 |
// DDRC = 0x00; // Define all Pins As Input |
Now to initiate a pullup on a single pin you could use:
1 2 |
// PORTB |= (1 << PB3);//initiate learn pin pullup |
Example using hexadecimal:
1 2 |
// PORTC = 0x20; // Enable Internal Pull Up Resistor on Pin 5 |
Example using binary:
1 2 |
// PORTC = 0b00110000;// Enable Internal Pull Up Resistor on Pin 5 and Pin 4 |
Looking at how to read a pin you can use the Input Pins Register PINB:
1 2 3 4 |
// uint8_t alarmState = PINB&8;//read alarm pin 3, notice 8 uint8_t learnState = PINB&16;//read learn pin 4, notice 16 uint8_t pinValue = (PINB & (1 << PINB4)) >> PINB4;//read a 0 or 1 |
it is also possible to toggle a pin and there’s different ways. Some are faster then others:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// PINB = (1 << PB3);//toggle fastest PINB |= (1 << PB3);//toggle 2nd fastest PINB |= _BV(PINB5);//toggle 2nd fastest also PORTB ^= 0b00100000;//toggle 3rd fastest PORTB ^= (1 << PB3);// toggle 3rd fastest also // ~70ns pulse using 4 bytes FASTEST PINB = 0b00100000;//toggle PINB = 0b00100000;//toggle // ~130ns pulse using 2 bytes PINB |= _BV(PINB5);//toggle PINB |= _BV(PINB5);//toggle // ~200ns pulse using 8 bytes PORTB ^= B00100000;//toggle PORTB ^= B00100000;//toggle |
You can also easily write to a pin low or high:
1 2 3 4 |
// PORTB |= (1 << PB3);//write high PORTB |= (1 << PORTB3);//write high with PORTB3 PORTB &= ~(1 << PB3);//write low |
There’s also an atomic operation to writing low or high:
1 2 3 |
// PORTB &= ~_BV(PB3); // write single pin (atomic op) write low PORTB |= _BV(PB3); // write single pin (atomic op) write high |
So now that we understand the basics we can get into making life easy by creating macros
Below are 4 functions made easy to iterate over all the pins in each port B, C, D
set_PIN_HIGH(pin);
set_PIN_LOW(pin);
read_PIN_STATE(pin);
toggle_PIN_STATE(pin);
This makes it easy to call the function without having to define the port each time and it’s still fast.
See the example below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
// // LED pin masks #define LP0 1<<0 #define LP1 1<<1 #define LP2 1<<2 #define LP3 1<<3 #define LP4 1<<4 #define LP5 1<<5 #define LP6 1<<6 #define LP7 1<<7 #define LP8 1<<0 #define LP9 1<<1 #define LP10 1<<2 #define LP11 1<<3 #define LP12 1<<4 #define LP13 1<<5 #define LP14 1<<0 #define LP15 1<<1 // pin mask array uint8_t ledpin_mask[] = {LP0,LP1,LP2,LP3,LP4,LP5,LP6,LP7,LP8,LP9,LP10,LP11,LP12,LP13,LP14,LP15}; // turn on LED void set_PIN_HIGH(uint8_t pin) { if (pin < 8) { PORTD |= (ledpin_mask[pin]); } else if ( pin < 14) { PORTB |= (ledpin_mask[pin]); } else { PORTC |= (ledpin_mask[pin]); } } // turn off LED void set_PIN_LOW(uint8_t pin) { if (pin < 8) { PORTD &= ~(ledpin_mask[pin]);//write low } else if ( pin < 14) { PORTB &= ~(ledpin_mask[pin]);//write low } else { PORTC &= ~(ledpin_mask[pin]);//write low } } // read button uint8_t read_PIN_STATE(uint8_t pin) { if (pin < 8) { //uint8_t pinValue = (PIND & (ledpin_mask[pin])) >> ledpin_mask[pin];//read 1 or 0 uint8_t pinValue = PIND & (ledpin_mask[pin]); return pinValue; } else if ( pin < 14) { //uint8_t pinValue = (PINB & (ledpin_mask[pin])) >> ledpin_mask[pin];//read 1 or 0 uint8_t pinValue = PINB & (ledpin_mask[pin]); return pinValue; } else { //uint8_t pinValue = (PINC & (ledpin_mask[pin])) >> ledpin_mask[pin];//read 1 or 0 uint8_t pinValue = PINC & (ledpin_mask[pin]); return pinValue; } } // read button void toggle_PIN_STATE(uint8_t pin) { if (pin < 8) { PIND = (ledpin_mask[pin]);//toggle fastest } else if ( pin < 14) { PINB = (ledpin_mask[pin]);//toggle fastest } else { PINC = (ledpin_mask[pin]);//toggle fastest } } //example of using it void StartUp() { for(uint8_t i = 0; i < LED_AMOUNT; i++)// { tone(D_SPEAKER_PIN, D_NOTE_HIGH); set_PIN_HIGH(LEDS[i]); delay(D_DELAY_TIME); noTone(D_SPEAKER_PIN); set_PIN_LOW(LEDS[i]); delay(D_DELAY_TIME); } } |
Links: