mbolivar
Out of curiosity, did you also try separate inline and "standard" versions ...
No. I was experimenting, and I wanted it simple and 'lite', so only one version.
Also, because my digitalRead and digitalWrite were intended to be simple and very compact, ideally smaller than a call of digitalRead and digitalWrite on int
, non-inline versions made no sense to me (IIRC compiling with debug and using GDB works properly)
... plus use of __builtin_constant_p() to determine which to use (as tried by Arduino)?
Definitely not.
IMHO part of the attraction of the Arduino library is most of it is readable. That looks unnecessarily complex.
A lot of beginners just declare things like int led = 13;
which I think fails the __builtin_constant_p()
test.
So the extra complexity only helps a more experienced developer, but a newbie would have to wade through quite subtle and sophisticated code to understand how it works.
I'd rather encourage folks to look at the MCU reference manual (e.g. RM0008) than the C++ specification :-)
My plan was to have a new type which would easily discriminate between the two variations of digitalRead/digitalWrite/pinMode.
It was also intended to be simpler and more efficient anyway, so faffing around with integer pin numbers was minimised.
Here's a rough cut early version which I've reconstituted; it didn't even use templates.
I think digitalRead/digitalWrite are faster and smaller than the standard digitalRead/digitalWrite, but less safe in this version.
One objective was to make the type no bigger than the default int
pin number, so indiscriminate use of Pin is no worse than int.
inline volatile uint32_t* pin_to_bitband(int pin_numb) { // luxury?
// should validate pin number
return bb_perip(PIN_MAP[pin_numb].gpio_device, PIN_MAP[pin_numb].gpio_bit);
}
class Pin {
public:
volatile uint32_t* const bitbandaddress;
// Construct pin from port address and bit number
Pin(volatile void* addr, uint8_t bit): bitbandaddress(bb_perip(addr, bit)) {}
private:
Pin(): bitbandaddress((volatile uint32_t*)0xFFFFFFFF) {} // cheap and nasty: force an exception
};
inline void digitalWrite(Pin p, uint32_t value) { *(p.bitbandaddress) = value; }
inline uint32_t digitalRead(Pin p) { return *(p.bitbandaddress+MAGIC_CONSTANT); }
void pinMode(Pin p, WiringPinMode mode) {
/* quick and dirty: find pin in PIN_MAP, then call pinMode(int,...) */
for (int i=0; i<BOARD_NR_GPIO_PINS; i++) {
if (p.bitbandaddress == pin_to_bitband(i)) {
pinMode(i, mode);
break;
}
}
/* proper: use the bitband address to derive control register addresses */
}
const Pin D0(GPIOA, 3);
// ...
const Pin D5(GPIOB, 6);
// ...
const Pin D13(GPIOA, 5);
// -- blink --
Pin led = D13;
void setup() {
pinMode(led, OUTPUT);
}
void loop() {
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
It doesn't do pinMode 'properly'.
It searches for a matching bitband address in PIN_MAP, and used the existing pinMode.
It should calculate the control registers from the bit band address, and hence be more direct, and not dependant on PIN_MAP.
Edit {
I think an inline Pin digitalWrite or digitalRead could be the same number of instructions as the call of the standard int digitalWrite or digitalRead, and hence always smaller because the body of a 'standard' digitalWrite and digitalRead isn't needed. I also think a sequence of inline-calls might be fewer instructions than a sequence of standard calls because the compiler could optimise better.
Hopefully we can agree that:
inline void digitalWrite(Pin p, uint32_t value) { *(p.bitbandaddress) = value; }
inline uint32_t digitalRead(Pin p) { return *(p.bitbandaddress+MAGIC_CONSTANT); }
isn't too sophisticated C/C++ ?
So someone trying to understand this is best directed to ARM Cortex-M3 documentation, and the MCU Reference Manual :-)
} End-Edit
The next step was to use templating so that the Pin instances were proper compile time constants.
That might have made the inline digitalRead and digitalWrite smaller and quicker by removing the load-bitband-address-from-RAM, because the bitband address would be a comple-time constant.
I didn't complete that templated variation because, at the time, some of the error messages I accidentally triggered were so opaque I felt people would be left flummoxed; the error messages mentioned templates, which I felt was too off-putting for non-C++ programmers. Maybe I should have tried more variations to try to make the errors easier?
Edit:
Trimmed the Pin class to bare essentials; I hope it's clearer.
Also, I realise e.g.
inline void digitalWrite(Pin p, uint32_t value) ...
etc. could be
inline void digitalWrite(Pin& p, uint32_t value)
, but I didn't do that experiment.
I don't think C programmers would normally use references, so this way is a useful test.
(WARNING: I may have made errors reconstituting this)