会社のでっかい先輩からarduinoのアドベントカレンダーがスカスカなのでなんか無いかと誘われたので、地味なところを書きます。
#digitalWriteって良いよね
arduinoのdigitalWrite関数
digitalWrite(pin, value)
んで、その中身は
void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer(pin);
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *out;
if (port == NOT_A_PIN) return;
// If the pin that support PWM output, we need to turn it off
// before doing a digital write.
if (timer != NOT_ON_TIMER) turnOffPWM(timer);
out = portOutputRegister(port);
uint8_t oldSREG = SREG;
cli();
if (val == LOW) {
*out &= ~bit;
} else {
*out |= bit;
}
SREG = oldSREG;
}
ってなっていますね。
一時変数が多いですね、嫌ですね。見るからに遅いっすね。
丁寧に見ていきましょう。```
uint8_t timer = digitalPinToTimer;
面倒なので詳細は省きますが、要するに、PROGMEM領域で指定されたピンの対応タイマを返す関数です。
なんでタイマを見てるかというと```
if (timer != NOT_ON_TIMER) turnOffPWM(timer);
```はい。タイマが対応してるピンであれば、PWMを切っておこうねってことですね。
はい次!
``` uint8_t bit = digitalPinToBitMask(pin); ```
```uint8_t port = digitalPinToPort(pin); ```
まとめていきましょう。
digitalPinToPortはそのピンに対応したポートを返します。digitalPinToBitMaskはそのポート内でのピンに対応するビットを1にしたuint8_tのビット列が返ってきます。
んでんで、
```if (port == NOT_A_PIN) return;```
これ、これを過信すると死にます。多分。
digitalWriteに対応していないピンがあったら関数を抜けるってことなんですけど、digitalPinToPort関数自体は内部に持ってる配列にアクセスしているだけなので、対応範囲外のピン番号が入ってくると、配列の範囲外を参照してきて所謂未定義動作になります。本番前にハマると死にます。こんなミスしてると来世も極貧です。悲しい。
ここまできたら後は簡単ですね。
正直このif(port == NOT_A_PIN)を過信しないでねって言いたかっただけです。
```c++
uint8_t oldSREG = SREG;
cli();
if (val == LOW) {
*out &= ~bit;
} else {
*out |= bit;
}
SREG = oldSREG;
後はまとめて、oldSREGには現在のキャリーフラグや割り込みフラグを一時退避します。正直ここはcurrentだと思うのですが...。
で割り込みを禁止して、*outレジスタに値を書き込んで、抜けます。
あれ?sei()なくね?割り込み禁止しっぱなしじゃん!!って思ったあなたとは友達になれそうです。
SREG = oldSREGで割り込みフラグ等の状態を戻すのでsei()は呼ばなくて良いんです。多分。
#ってことは
これ一時変数保存しとけばちょっと早くなりそうじゃない?
ってのでdigitalPinのオブジェクトを作ってしまうのが最近のマイブームです。
struct DigitalWritePin{
public:
DigitalWritePin(){}
void init(const uint8_t pin){
pinMode(pin,OUTPUT);
digitalWrite(pin, LOW);
sfdBit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
sfdOut = portOutputRegister(port);
}
void operator = (const uint8_t val){
currentSREG = SREG;
cli();
if(val == 0){
*sfdOut &= ~sfdBit;
}else{
*sfdOut |= sfdBit;
}
SREG = currentSREG;
}
private:
uint8_t currentSREG;
uint8_t sfdBit;
volatile uint8_t* sfdOut;
};
これで良いんじゃないの?と因みにタイマーとかNOT_A_PINの判定は要りません。最初っから気をつけて書きなさい。
init内部でdigitalWrite呼ぶのイケてないよねって思う人とは友達になりたいです。
こうなるとpinModeも分解したいっすよね。それはまた別の機会に。