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.
digitalWrite()
. Premature optimization is the root of all evil, after all. – Duncan C Feb 22 '20 at 18:02