0

I'm a newbie to arduino but been programming lots. I have a project where I would like to output 4 bits to adjacent pins and read 4 bits from another (4) adjacent pins. (Imagine a matrix keyboard application). To do it in C-C++ would take lots of instructions (and time) but machine code

OUTPUT: OR (mask) -> output port ; this clears bits target bits while leaving other bits as they were AND (newdata) -> output port ; this sets new data into target bits

INPUT: port -> register AND (register) (mask) -> register

Is there a way of implementing this as a call from IDE for instance. I have come across many systems where multi language components can be compiled into one executable.

If it is not normally done, why not? Is it something that I should avoid for some reason?

Thanks Dave

5
  • 3
    Looks at the PORT and PIN registers (assuming AVR). They can be manipulated directly from C or C++, no need for assembly.
    – Mat
    Commented Aug 17, 2021 at 5:57
  • If you have an AVR Arduino (like Uno, Nano, ...) this is a must read for this topic in the documentation.
    – chrisl
    Commented Aug 17, 2021 at 8:54
  • What do you mean by "a call from IDE"? You might want to do some tutorials to learn the basics and technical terms. There are examples for matrix keyboards, I'm sure. Commented Aug 17, 2021 at 9:14
  • Thanks Mat and Chris. Excellent info. Juraj, I'm using a 'clone' UNO but want the thing to work efficiently keep programme to minimum. Busybee, Sorry. I know IDE is not the correct term but wanted the question to be as elementary as possible.
    – Dave
    Commented Aug 18, 2021 at 0:02
  • If it's about manually triggered mechanical buttons, rather take care to be SLOW enough to get rid of bouncing effects. While waiting a few ms until a button settles, it does not matter how you read the button states. Commented Aug 18, 2021 at 12:40

1 Answer 1

1

You can manipulate the I/O ports directly from C++ without having to resort to assembly code.

The AVR micro in your Arduino (ATmega328P) has three ports (PORTB, PORTC and PORTD) with eight pins each (Px0, Px1, ... Px7). You control the ports using three 8-bit registers. Each bit in a register represents one physical pin (LSB is pin 0, MSB pin 7):

  • DDRx: data direction register. A bit set to "1" corresponds to output mode, "0" to input.
  • PORTx: data register. In output mode, "1" sets the port HIGH, "0" sets it LOW. In input mode, controls whether pull-ups are enabled ("1") or not ("0").
  • PINx: input and toggle register. Read it to get the status of all eight pins of the port at once. Writing to it toggles (LOW -> HIGH or HIGH -> LOW) the selected pins (e.g. PINC = 0b00100001; toggles pins PC0 and PC5).

With an Arduino, you don't have access to all the pins of all the ports. To get the mapping between Arduino pin names and ports, look at any good Arduino pinout reference (e.g. on the Arduino Uno page in the "Documentation" tab). You can see for example that all pins of port D are available as Arduino digital pins D0 to D7 on an Uno for example. The analog pins are from PORTC.

Arduino Uno Rev 3 pinout

Macros are provided (by the AVR toolchain) for port and pin names to make things easier. For example, PORTB is an alias for the PORTB register, you can assign to it as if it was a regular C++ variable. PB5 is the index of pin 5 in port B. So setting port B pin 5 to output without affecting the others can be written as:

DDRB |= (1 << PB5);

If you wanted to set the 4 lower pins of port D to output, and the 4 others to input, you could:

DDRD = 0b00001111; // or 0x0f

Here's an example of a minimal blink sketch:

void setup() {
  // Set PB5 (D13 - builtin led) LOW
  PORTB &= ~(1 << PB5);
  // Set PB5 mode to OUTPUT
  DDRB |= (1 << PB5);
}

void loop() {
  delay(1000);
  // toggle PB5
  PINB |= (1 << PB5);
}

The assembly generated is optimal, single instructions to set or clear a bit in I/O memory: (See See compiled assembly output (Visual Micro) for how to get it)

// setup
  PORTB &= ~(1 << PB5);
 1f4:   2d 98           cbi     0x05, 5 ; 5
  DDRB |= (1 << PB5);
 1f6:   25 9a           sbi     0x04, 5 ; 4

// loop
  PINB |= (1 << PB5);
 246:   1d 9a           sbi     0x03, 5 ; 3

Another example, that blinks a led connected to A3 only when A2 is high:

void setup() {
  // Set PC2 and PC3 LOW (A2 and A3)
  PORTC &= ~((1 << PC2) | (1 << PC3));
  // Set PC2 to input, PC3 mode to output
  DDRC = (DDRC & ~(1 << PC2)) | (1 << PC3);
}

void loop() {
  delay(1000);
  // toggle PC3 only if PC2 is high
  if (PINC & (1 << PC2)) {
    PINC = (1 << PC3);
  }
}

Again, assembly generated is quite terse:

  if (PINC & (1 << PC2)) {
    PINC = (1 << PC3);
 202:   18 e0           ldi     r17, 0x08       ; 8
 252:   32 99           sbic    0x06, 2 ; 6
 254:   16 b9           out     0x06, r17       ; 6

(The SBIC instruction is fun: skip the next instruction if a bit in I/O memory is clear.)

You can even save the use of that register r17 by using PINC |= ... in this case:

  // toggle PC3 only if PC2 is high
  if (PINC & (1 << PC2)) {
    PINC |= (1 << PC3);
 250:   32 99           sbic    0x06, 2 ; 6
 252:   33 9a           sbi     0x06, 3 ; 6

Of course you can also resort to inline assembly, but that is an art in and of itself, and the syntax a bit peculiar. But for simple things, it is straightforward:

asm("sbi 0x03, 5;"); // toggle PB5

Some references:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.