3

Consider the following:

#define IN1 9
#define IN2 10

pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);

void LeftMotor(Direction direction) {
  digitalWrite(IN1, direction == Forward ? LOW : HIGH);
  digitalWrite(IN2, direction == Forward ? HIGH : LOW);
}

How can I modify the LeftMotor method to achieve the same result without using digitalWrite?

  • 3
  • 2
    @MikaelPatel: according to the link i posted above, it will be faster, because it's "able to turn pins on and off very quickly, meaning within fractions of a microsecond ... . Direct port access can do the same job in a lot fewer clock cycles." (than digitalWrite). Now for an hbridge, it's not going to make a hill of beans diff, but the OP's question is legit. – dandavis Feb 19 at 21:41
  • 3
    Why do you think that digitalWrite isn't fast enough for your application? Direct port manipulation will be significantly faster than using digitalWrite, but for most things (like controlling a motor) digitalWrite is plenty fast enough. Port manipulation is harder to set up, harder to understand, and is not portable across devices (the port assignments are different for different Arduino models.) – Duncan C Feb 20 at 2:39
  • 2
    @DuncanC digitalWrite is slow. Teach the kid how to set the ports directly. Your advice is horrible. Setting the registers isn’t hard. – PhillyNJ Feb 22 at 1:10
  • 3
    @PhillyNJ, if the use case demands fast code, sure. If not, I'd say stick with digitalWrite(). Premature optimization is the root of all evil, after all. – Duncan C Feb 22 at 18:02
2
+100

Setting ports directly is a great way to learn AVR programming. I might recommend this book Make: Avr Programming.

First you need to set the direction of the port in the Data Direction Register as either input or output. If Input, check its state and handle it. Here is a small example.

#define HIGH 1
#define LOW 0

#define  SWI_PIN PORTB0
#define  BUTTON PORTB4


int main (void)
{
    // set the pin directions
    DDRB |=  (1 << SWI_PIN); // output
    DDRB &= ~(1<<PORTB4); //input
    while(true){

      if ((PINB & (1<<BUTTON)) == 0){ // pin is high! Rising edge
          PORTB &= ~(1<<SWI_PIN); // LOW turn off swi_pin
      }else {
          PORTB |= (1<<SWI_PIN); // HIGH  turn on swi_pin
      }

}
| improve this answer | |
  • Not all Arduino pins are on port B. There are macros to make your solution a bit more universal and as fast as yours. – DataFiddler Feb 28 at 14:38
  • @DataFiddler no of course not. This is an example not a full lesson on setting ports. – PhillyNJ Feb 28 at 14:51
5

chrisl provided a very good reference answer. Here I am just adding a few bits of information about the pin mapping and the pin electrical states.

Pin mapping

One of the firs things you need when dealing with direct port access is the mapping between the Arduino pin names and the names of the microcontroller (MCU) pins as given by the manufacturer. For the Arduino Nano the mapping is the following:

 Arduino │ MCU
─────────┼─────
   RX0   │ PD0
   TX1   │ PD1
   D2    │ PD2
        ... 
   D7    │ PD7
   D8    │ PB0
        ...
   D13   │ PB5
   A0    │ PC0
        ...
   A5    │ PC5

where “PD0” means “pin number 0 of port D” and so on. Notice that pins A6 and A7 are not listed here, as they are analog only, with no digital I/O capability.

The information above can be found on the product page of the board, under the “Documentation” tab. Alternatively, an image search for “Arduino <board name> pinout” will find some nice schematics like the one below:

Arduino Nano pinout

Pin states

This has already been covered by chrisl's answer in the description of the DDRx and PORTx registers. Below is a summary table showing the four available electrical states of the pins, and how they are selected with the DDR and PORT bits:

 DDR │ PORT │ electrical state │ equation
─────┼──────┼──────────────────┼─────────────────
  0  │   0  │  INPUT (high-Z)  │ I = 0
  0  │   1  │  INPUT_PULLUP    │ V = Vcc − Rpu I
  1  │   0  │  OUTPUT LOW      │ V = 0
  1  │   1  │  OUTPUT HIGH     │ V = Vcc

The provided equations relate the output voltage (V) and the output current (I) of the pin. Beware that these are just rough approximations, as:

  • they neglect the output resistance, which is of the order of 25 Ω
  • an input pin can have some (sub-µA) leakage current
  • the pullup resistor is not completely linear
  • the datasheet only provides weak guaranties, such as a range for the output voltage, conditioned on the output current being low enough.
| improve this answer | |
4

digitalWrite() is just a convenience function to hide away complexity from the user, so that she/he can just use a pin number and a value. This complexity takes some time to execute. If this is too slow for you, you need to go a bit deeper into the rabbit hole and use direct port manipulation:

Inside the microcontroller on the Arduino all the special functions for peripherals and such (digital IO, ADC, Serial(UART), SPI, Timer, ...) are configured via Special Function Registers (SFR). These are specific memory places, that are directly connected to the peripherals. You can configure the whole microcontroller by setting these registers to specific values.

For digital IO you first need to know, that the microcontrollers pins are ordered into groups of max 8 pins. These groups are called Ports. Each available port of the microcontroller gets a character, like PORTB. On the Arduino Nano for example PORTD includes the pins 0 to 7.

For each port we have 3 different SFRs (with the x being the character for the corresponding port), where each bit inside the registers corresponds to one pin:

  • DDRx controls the direction of the pins. If you set a bit to 1 in there, the corresponding pin is set as output. If you set it to zero, the pin will be an input (which is also called High-Z for high impedance, because a digital input pin has a very high resistance).

  • PORTx controls the output hardware of the digital pins. If you have set a pin as output (as described above) you can use the corresponding bit in the PORTx register to set it's state. If you write a 1 to the bit, the pin will become HIGH (means, the voltage on the pin will go to the same level as Vcc, the supply voltage of the micrcontroller). If you write a zero, the pin will go to LOW (meaning the same level as ground). The PORTx register has a different function, when you configured a pin as input. In this case, a 1 will turn on the interal pullup resistor of the pin, which pulls the level of the pin to HIGH, when nothing is pulling it down to ground (like a switch for example). A zero disables the pullup.

  • PINx reflects the current state of the pins. If you configured the pins as input, this is the voltage level, that currently lies at the corresponding pin (about the actual voltage levels, see below). A 1 at a bit means, that the corresponding pin is HIGH, else it is LOW.

You can read all of this in the datasheet of the used microcontroller. The datasheet is a must read (at least the relevant chapters), if you want to control the microcontroller via its registers. In most microcontrollers these registers are named as above. But some microcontrollers might define other names. The corresponding datasheet will list all the registers with rather detailed descriptions.

Now let's take that knowledge into actual code. Here I assume an Arduino Nano, though you can exchange that for most other microcontrollers.

// Configure pin D3 as output
DDRD |= (1 << PD3);
// Configure pin D2 as input
DDRD &= ~(1 << PD2);
// Check, if pin D2 is HIGH
if(PIND & (1 << PD2)){
    // Set pin D3 to HIGH
    PORTD |= (1 << PD3);
} else {
    // Set pin D3 to LOW
    PORTD &= ~(1 << PD3);
}

Excourse on bitwise operators: The code above can only be understood by knowing bitwise operators, which are very important for direct port manipulations. I will break down the operations, that are done there:

  • First we have PD2 and PD3. These are defines from the Arduino core. They are set to the index of the bit, that corresponds to the named pin. So PD2 is defined as 2, PD3 is defined as 3, PD0 as 0.

  • 1 << PD3: The << operator does a bitwise left shift, which means all bits of the value on the left are shifted to the left by the amount standing on the right of the operator. We shift the value 1 three digits to the left here. In binary that looks like this:

    0b00000001 << 3 = 0b00001000
    

    That set bit in the result is exactly at the place, where the bits for pin PD3 will be in the Special Function Registers.

  • DDRD |= (1 << PD3): This is the same as doing DDRD = DDRD | (1 << PD3), so the |= operator is just an abbreviation for it. The bitwise OR operator | will unite the bits from both sides. If a bit is set to 1 at either side, the result will also have set this bit to 1. This means, that we can set a bit to 1 with this.

  • ~(1 << PD2): The tilde ~ takes the inversion of the data. Alike above, the value inside the paranthesis can be written as binary value 0b00000100. The inversion will flip all bits to the other value: 0b11111011

  • DDRD &= ~(1 << PD2): The abbreviation is similar to the above. The bitwise AND operator & will also unite the bits of the values around it, but it will only set the resulting bits to 1, if both of the corresponding bits of the values are also set. Here this results in the third bit (the one for D2) being cleared to zero. All other bits (where you can see a 1 at the right value) are left untouched.

  • PIND & (1 << PD2): This is similar to the above, but now we don't have an inversion. We are doing PIND & 0b00000100 here. All bits of the result will be set to zero, except for the bit for D2. It will only be zero, if the corresponding bit was zero in PIND. Thus we have isolated the needed bit by this way.

You can also combine the same operations on a single register. Let's say we want to set both D2 and D3 as output. Then we can either write this as two seperate statements

DDRD |= (1 << PD2);
DDRD |= (1 << PD3);

or we can combine them into one statement by connecting them with the bitwise OR

DDRD |= (1 << PD2) | (1 << PD3);

The Arduino documentation also provides a site to explain direct port manipulation. Also again you should take the time to look into the datasheet of your used microcontroller. At first it will seems very difficult, but doing these direct port manipulations is a great way of learning how to use the SFRs, and then you can do all sorts of cool stuff with your microcontroller. So it really is worth it. You don't need to read or understand the whole datasheet at once.


How can I modify the LeftMotor method to achieve the same result without using digitalWrite?

After all this text now to the specific question. On the Nano pin 9 and 10 are PB1 and PB2. You used a conditional inside your digitalWrite() statement, which I will replace by a complete if statement here. So you can use

if(direction == Forward){
    PORTB &= ~(1 << PB1);
    PORTB |= (1 << PB2);
} else {
    PORTB |= (1 << PB1);
    PORTB &= ~(1 << PB2);
}

instead of your two digitalWrite() statements.

| improve this answer | |
  • I don't know why this question is downvoted. As for 'canonical' answer it is better than the other.Maybe someone has a problem with reading... – smajli Feb 28 at 8:50
1

How can I set up outputs without using digitalWrite?

So this question is actually about the fastest possible implementation of digitalWrite.

As described in other answers this can be achieved by using direct port manipulation. But there are actually libraries (for instance, https://github.com/mikaelpatel/Arduino-GPIO) that does this in a portable way and avoids some of the errors that can occur. The library is 10-100X faster than the Arduino core digital pin functions.

Pins in a MCU are organized in ports. On the AVR there are a set of 8-bit port registers that hold the current pin setting. To change the value of a pin the CPU has to read the register and change the corresponding bit associated with the pin. This has to be done as an atomic operation, i.e. an interrupt may not occur during the update of the port register.

To allow a very fast update, i.e. one instruction/1-2 clock cycle, the CPU has a set of special instructions. For the AVR there are bit set and clear instructions for the 32 first special registers. On the Arduino Mega some of the pins/ports have higher address special registers and cannot use the special instructions. To make the pin operation atomic explicit interrupt handling must be added.

Other issues with direct port manipulation is portability. Code written using the AVR io handling cannot be used on SAM (e.g. Arduino Due). There is actually even issues when changing between AVR MCUs.

In short, a portable faster implementation of digital pin functions is recommended.

Below is the code written with the GPIO. It is portable and as fast as direct port manipulation.

#include "GPIO.h"

GPIO<BOARD::D9> IN1;
GPIO<BOARD::D10> IN2;

IN1.output();
IN2.output();

void LeftMotor(Direction direction) {
  IN1 = (direction == Forward ? LOW : HIGH);
  IN2 = (direction == Forward ? HIGH : LOW);
}

or

void LeftMotor(Direction direction) {
  IN1 = direction != Forward;
  IN2 = direction == Forward;
}

or

void LeftMotor(Direction direction) {
  if (direction == Forward) {
    IN1.low();
    IN2.high();      
  }
  else {
    IN1.high();
    IN2.low();
  }
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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