Help us understand the problem. What is going on with this article?

Arduino UNOのdigitalWrite関数が遅い(ので自作で作った)

前置き

Arduino UNOは作りが単純なので、学習用やちょっとした工作によく使っています。
Arduinoの組み込み関数はよくできていて、digitalWriteなどの関数はボードの違いやレジスタの違いなどをいい感じに吸収してくれるみたいで、そのおかげで特に何も考えなくてもGPIOが使えます。
が、ソースコードを見たことはないものの、レジスタ直いじりに比べて格段に遅いので、おそらく内部で色々な判定をしていると思います。
高速でIOしたいときにはスピードが足りないので、UNO専用かつ使い勝手を犠牲にしない範囲でdigitalWrite関数を自作してみようかと思います。

digitalWrite関数はどれくらいのスピードか

速度を計ってみます。
貧乏なのでオシロスコープなどないので、割り込みタイマで計ってみます。

#define pin_IO  5

unsigned long time[3];

void setup() 
{
    pinMode(pin_IO, OUTPUT);
    Serial.begin(9600);   
}

void loop() 
{   
    time[0] = micros();
    for (int i = 0; i < 10000; i++)
    {
        digitalWrite(pin_IO,HIGH);
    }
    time[1] = micros();
    for (int i = 0; i < 10000; i++)
    {
        digitalWrite(pin_IO,HIGH);
        digitalWrite(pin_IO,HIGH);
    }
    time[2] = micros();
    unsigned long buf;
    buf = time[2] - time[1];
    buf = buf - (time[1] - time[0]);
    Serial.println(buf);
    while (1)
    {
      /* 無限ループ */
    }
}
実行結果
[Starting] Opening the serial port - COM6
[Info] Opened the serial port - COM6
37724

大体10000回で38msくらいかかってます。

どうやって速くするか

特に難しいことは考えず、ピン番号からレジスタとmaskを引き出して代入するだけです。
どこのレジスタをいじればいいか調べてみました。
http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=850

PORTB,PORTC,PORTDの3つのレジスタのどれかをいじればいいみたいです。

ちょっと初心者向け:「PORTB」とはなにか

物理アドレス上でIOレジスタのアドレスを指し示すエイリアスだと思います。
ちょっと試してみます。

void setup() 
{
    Serial.begin(9600);   
}

void loop() 
{   
    Serial.println((unsigned int)(&(PORTB)));
}
実行結果
[Starting] Opening the serial port - COM6
[Info] Opened the serial port - COM6
37
37
37

PORTBは「どこかのアドレスを指し示すポインタ先の値」なので、アンパサンドでアドレスにして数値にキャストしてprintしました。
37番地のアドレスらしいです。

速いdigitalWrite関数を書いて速度を計ってみる

書いてみました。

#define pin_IO  5

unsigned long time[3];
typedef struct
{
    volatile uint8_t*  Io_register;
    unsigned char     mask; 
} stl_write_reg;

stl_write_reg stl_io_regs[14] = 
{
    {&PORTD,0b00000001}, /* pin0   PD0 */
    {&PORTD,0b00000010}, /* pin1   PD1 */
    {&PORTD,0b00000100}, /* pin2   PD2 */
    {&PORTD,0b00001000}, /* pin3   PD3 */
    {&PORTD,0b00010000}, /* pin4   PD4 */
    {&PORTD,0b00100000}, /* pin5   PD5 */
    {&PORTD,0b01000000}, /* pin6   PD6 */
    {&PORTD,0b10000000}, /* pin7   PD7 */
    {&PORTB,0b00000001}, /* pin8   PB0 */
    {&PORTB,0b00000010}, /* pin9   PB1 */
    {&PORTB,0b00000100}, /* pin10  PB2 */
    {&PORTB,0b00001000}, /* pin11  PB3 */
    {&PORTB,0b00010000}, /* pin12  PB4 */
    {&PORTB,0b00100000}  /* pin13  PB5 */
};

void setup() 
{
    pinMode(pin_IO,OUTPUT);
    Serial.begin(9600);   
}

void loop() 
{   
    time[0] = micros();
    for (int i = 0; i < 10000; i++)
    {
        hi_speed_digitalWrite(pin_IO,HIGH);
    }
    time[1] = micros();
    for (int i = 0; i < 10000; i++)
    {
        hi_speed_digitalWrite(pin_IO,HIGH);
        hi_speed_digitalWrite(pin_IO,HIGH);
    }
    time[2] = micros();
    unsigned long buf;
    buf = time[2] - time[1];
    buf = buf - (time[1] - time[0]);
    Serial.println(buf);
    while (1)
    {
      /* 無限ループ */
    }
}

void hi_speed_digitalWrite(int pin,char _io)
{
    stl_write_reg*  reg = &(stl_io_regs[pin]);
    if (_io == HIGH)
    {
        *(reg->Io_register) |= reg->mask;
    }
    else
    {
        *(reg->Io_register) &= ~(reg->mask);
    }   
}
実行結果
[Starting] Opening the serial port - COM6
[Info] Opened the serial port - COM6
6292

6.3ms程になりました。
大体6倍近く速くなったことになります。

使い方としては普通のdigitalWrite関数と同じようにピン番号とHIGH or LOWを渡すだけなので、使い勝手は変わらないと思います。

ronkabu
転職しました。 ADASのシステム開発やってます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away