Each I/O pin is represented in a 16-bit 'port', a bunch of memory locations which:
- control the pin direction (whether pins are inputs or outputs),
- the value of the pins that are inputs, and
- the state of output pins
- a few other things ...
This is described in the ST Micro manual RM0008, in section 9 "General-purpose and alternate-function I/Os (GPIOs and AFIOs)". e.g.
http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf
To figure out which port digital pin 4 is on, go look at the Maple pinmap, e.g.
http://leaflabs.com/docs/hardware/maple.html#master-pin-map
You can see that D4 (data pin 4) is "PB5", that is port B, pin 5.
So Port B is GPIOB
hence the call is gpio_read_bit(GPIOB, 5);
Or, you can 'do it yourself' and just write
... GPIOB_BASE->IDR & BIT(5) ...
which has a similar effect.
The part of the expression & BIT(5)
is extracting the specific pin value.
Then you might use a test for zero, e.g.
if (GPIOB_BASE->IDR & BIT(5)) { Counter ++; }
if you actually want to use the bit value, the code would have to shift the bit.
As an example, the code could use the value as an integer value 0 or 1, either:
loop() {
Counter += GPIOB_BASE->IDR& BIT(5)? 1 : 0;
}
or
loop() {
Counter += (GPIOB_BASE->IDR& BIT(5)) >> 5;
}
The if test disappears, though the compiler might generate a better code sequence for an if
compared to the ...? 1 : 0;
I'd have to go look.
I think the second one (GPIOB_BASE->IDR& BIT(5)) >> 5
) might be quickest because the Cortex-M3 has a fast shift or bit test which might do that in one cycle (the compiler will know what to do :-)
Theoretically, that specific expression GPIOB_BASE->IDR& BIT(5)) >> 5
, once set up, could run in 3 cycles (.e. 24MHz), but the address of GPIO_BASE->IDR will have to be loaded so you'll not see that unless you have a long piece of code to keep testing the pin.
There is an even quicker way using 'bit banding'.
Every I/O pin has a second 'shadow address' where a whole memory word is used to set or test the pin value, so the & BIT(5)
becomes unnecessary.
Pete Harrison has an article at:
http://www.micromouseonline.com/2010/07/14/bit-banding-in-the-stm32/#axzz1aWWAbNQg
Instead of using the bit band region for memory:
#define RAM_BASE 0x20000000
#define RAM_BB_BASE 0x22000000
You would be using the bit band region for I/O registers:
#define IO_BASE 0×40000000
#define IO_BB_BASE 0×42000000
and change the macros to match.
Essentially, the code Pete shows converts a memory address, and the bit within the memory address to a memory address for a bit.
You could modify the macros to convert the I/O port address and pin within that port to a bit-band memory address, and use that to test the pin (I've called that IOGetBit()).
All of that can be calculated at compile time, so there is no need for the & BIT(5)
, and the bit is always in bit position 0.
So, it is possible to test a bit in two cycles once the bit-band address is loaded into a register. That is 36MHz. Again, you'll never see it go that fast unless it is in a long piece of code with all the addresses loaded into CPU registers.
So your code:
void loop()
{
if (digitalRead(4) == HIGH){
Counter ++;
}
}
would become
void loop()
{
Counter += IOGetBit(GPIO_BASE->IDR, 5);
}
which wouldn't do much useful (just count like crazy), but the overhead of calling loop()
would be humungous by comparison with the time needed to test a pin.
The time spent doing:
int start_time = millis();
for (int i=0; i<6000000; ++i) { // 6 million
Counter += IOGetBit(GPIO_BASE->IDR, 5);
}
int end_time = millis();
would be slightly more for
loop than the pin test.
I guess that is less than 1 second, but it's late and I'm sleepy.
Hence 'unrolling` the for loop:
int start_time = millis();
for (int i=0; i<1000000; ++i) { // 1 million
Counter += IOGetBit(GPIO_BASE->IDR, 5);
Counter += IOGetBit(GPIO_BASE->IDR, 5);
Counter += IOGetBit(GPIO_BASE->IDR, 5);
Counter += IOGetBit(GPIO_BASE->IDR, 5);
Counter += IOGetBit(GPIO_BASE->IDR, 5);
Counter += IOGetBit(GPIO_BASE->IDR, 5);
}
int end_time = millis();
should be noticeably faster.
[WARNING: I have not compiled and tested the code]
(Note to self: I think Pete's macros and the gpio.h header don't declare GPIOB_BASE as the address of a volatile
so GPIOB->regs->IDR might be better because regs
is volatile
)
(full disclosure: I am not a member of LeafLabs staff)