きっかけ
ArduinoにおいてUART通信と外部割り込みを使用しており、この二つの割り込みフラグでスリープを解除することが目的でした。
しかし、UART通信の割り込みでスリープを解除できる、アイドルモードではスリープ→起動→スリープ...の繰り返しで目的の動作が得られません。
そこで今回は、この原因を調べる過程でわかった内容と対策(一時的な)についてメモしておきます。
Arduinoにおけるスリープ
そもそもArduino言語では、スリープをサポートしてないようです。
Arduinoは一部を除いてAVRのマイコンを使用しており、そのため、ArduinoでスリープするにはAVRのスリープ用ライブラリを読み込む必要があります。
スリープモードの種類
スリープモードの種類は幾つかあります。種類と動作について、なかのAVR電子工房を参考にしました。
動作確認
スリープを解除するには、そのそれぞれのモードで許可されている割り込み(今回はUART通信と外部割り込み)が発生させる必要があります。シンプルにスリープして、UART通信の割り込みが入ると起動するスケッチを作り動作確認をしました。
スリープのプログラムは、こちらを参考にしています。
#include <avr/sleep.h>
void setup()
{
cli();
//RXのプルアップ
pinMode(0,INPUT);
digitalWrite(0,HIGH);
//シリアル通信
Serial.begin(9600);
//スリープモードの設定(IDLEモード)
set_sleep_mode(SLEEP_MODE_IDLE);
sei();
}
void loop()
{
//-------------スリープ開始--------------
if(Serial.available() <= 0){
Serial.println("sleep");
sleep_mode();
Serial.println("wake up");
}
}
こちらは、ノイズ対策でプルアップする。
//RXのプルアップ
pinMode(0,INPUT);
digitalWrite(0,HIGH);
UART通信の受信データがない場合、スリープする。
if(Serial.available() <= 0){
Serial.println("sleep");
sleep_mode();
Serial.println("wake up");
}
スリープは、UARTによる解除なのでアイドルモードを使用する。
set_sleep_mode(SLEEP_MODE_IDLE);
結果
プログラムの実行結果になります。
原因
Arduinoのスケッチをコンパイルするとき、デフォルトで読み込まれるライブラリがいくつかあります。
AVRマイコンでできてArduinoでできない場合、大抵はライブラリにおける設定が原因です。
関連がありそうなライブラリを探っていくと、初期設定の内容が記述されているmain.cpp、UART通信の設定が記述されているHardwareSerial.cppにおけるUART通信の割り込み設定(送信の方)、タイマーの初期設定が原因でした。
これにより、スリープしてもすぐに解除されます。
対策
タイマー
タイマーの初期設定に手を加えます。まず、スケッチで記述するsetup関数とloop関数は、このmain.cppで呼び出すようになっています。
#include <Arduino.h>
int main(void)
{
init();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
serialEventRunは、スケッチの方で定義しUARTの受信バッファにデータがある場合に呼び出されます。無視してよいです。
注目すべきは、init関数の方です。この関数は、wiring.cで定義されています。以下は、wiring.cのinit関数を抜き出したものです。
void init()
{
// this needs to be called before setup() or some functions won't
// work there
sei();
/*ここからがタイマーの設定
// on the ATmega168, timer 0 is also used for fast hardware pwm
// (using phase-correct PWM would mean that timer 0 overflowed half as often
// resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(TCCR0A) && defined(WGM01)
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);
#endif
// set timer 0 prescale factor to 64:
#if defined(__AVR_ATmega128__)
// CPU specific: different values for the ATmega128
sbi(TCCR0, CS02);
#elif defined(TCCR0) && defined(CS01) && defined(CS00)
// this combination is for the standard atmega8
sbi(TCCR0, CS01);
sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
// this combination is for the standard 168/328/1280/2560
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
#elif defined(TCCR0A) && defined(CS01) && defined(CS00)
// this combination is for the __AVR_ATmega645__ series
sbi(TCCR0A, CS01);
sbi(TCCR0A, CS00);
#else
#error Timer 0 prescale factor 64 not set correctly
#endif
// enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
sbi(TIMSK0, TOIE0);
#else
#error Timer 0 overflow interrupt not set correctly
#endif
// timers 1 and 2 are used for phase-correct hardware pwm
// this is better for motors as it ensures an even waveform
// note, however, that fast pwm mode can achieve a frequency of up
// 8 MHz (with a 16 MHz clock) at 50% duty cycle
#if defined(TCCR1B) && defined(CS11) && defined(CS10)
TCCR1B = 0;
// set timer 1 prescale factor to 64
sbi(TCCR1B, CS11);
#if F_CPU >= 8000000L
sbi(TCCR1B, CS10);
#endif
#elif defined(TCCR1) && defined(CS11) && defined(CS10)
sbi(TCCR1, CS11);
#if F_CPU >= 8000000L
sbi(TCCR1, CS10);
#endif
#endif
// put timer 1 in 8-bit phase correct pwm mode
#if defined(TCCR1A) && defined(WGM10)
sbi(TCCR1A, WGM10);
#elif defined(TCCR1)
#warning this needs to be finished
#endif
// set timer 2 prescale factor to 64
#if defined(TCCR2) && defined(CS22)
sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
sbi(TCCR2B, CS22);
#else
#warning Timer 2 not finished (may not be present on this CPU)
#endif
// configure timer 2 for phase correct pwm (8-bit)
#if defined(TCCR2) && defined(WGM20)
sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
sbi(TCCR2A, WGM20);
#else
#warning Timer 2 not finished (may not be present on this CPU)
#endif
#if defined(TCCR3B) && defined(CS31) && defined(WGM30)
sbi(TCCR3B, CS31); // set timer 3 prescale factor to 64
sbi(TCCR3B, CS30);
sbi(TCCR3A, WGM30); // put timer 3 in 8-bit phase correct pwm mode
#endif
#if defined(TCCR4A) && defined(TCCR4B) && defined(TCCR4D) // beginning of timer4 block for 32U4 and similar
sbi(TCCR4B, CS42); // set timer4 prescale factor to 64
sbi(TCCR4B, CS41);
sbi(TCCR4B, CS40);
sbi(TCCR4D, WGM40); // put timer 4 in phase- and frequency-correct PWM mode
sbi(TCCR4A, PWM4A); // enable PWM mode for comparator OCR4A
sbi(TCCR4C, PWM4D); // enable PWM mode for comparator OCR4D
#else // beginning of timer4 block for ATMEGA1280 and ATMEGA2560
#if defined(TCCR4B) && defined(CS41) && defined(WGM40)
sbi(TCCR4B, CS41); // set timer 4 prescale factor to 64
sbi(TCCR4B, CS40);
sbi(TCCR4A, WGM40); // put timer 4 in 8-bit phase correct pwm mode
#endif
#endif // end timer4 block for ATMEGA1280/2560 and similar
#if defined(TCCR5B) && defined(CS51) && defined(WGM50)
sbi(TCCR5B, CS51); // set timer 5 prescale factor to 64
sbi(TCCR5B, CS50);
sbi(TCCR5A, WGM50); // put timer 5 in 8-bit phase correct pwm mode
#endif
ここまでがタイマーの設定*/
#if defined(ADCSRA)
// set a2d prescale factor to 128
// 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
// XXX: this will not work properly for other clock speeds, and
// this code should use F_CPU to determine the prescale factor.
sbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
// enable a2d conversions
sbi(ADCSRA, ADEN);
#endif
// the bootloader connects pins 0 and 1 to the USART; disconnect them
// here so they can be used as normal digital i/o; they will be
// reconnected in Serial.begin()
#if defined(UCSRB)
UCSRB = 0;
#elif defined(UCSR0B)
UCSR0B = 0;
#endif
}
ここでは、タイマー設定とA/D変換の初期設定をしています。
タイマーが原因なのと今回タイマーを使わないので、タイマーの初期設定部分をコメントアウトします。
タイマーを使う方は、自分で必要な設定を行ってください。
UARTの送信
UARTのライブラリでは、割り込みを使い送受信を行っています。
これをブラックボックス化することで、Arduinoの利用者が割り込みを意図せずに送受信を使える親切設計になっています。
今回は、この割り込みの送信設定が邪魔をしているので手を加えます。
まず、HardwareSerial.cppの205行から296行目をコメントアウトします。
HardwareSerialのコンストラクタにudre(任意ではない)、flag_n(任意)を追加します。HardwareSerialで宣言する際にもflag_nを追加します(最後の方)。
あとは、HardwareSerialのwrite関数で、割り込みを使わず送信できるようにします。
HardwareSerial::HardwareSerial(ring_buffer *rx_buffer, ring_buffer *tx_buffer,
volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
volatile uint8_t *ucsrc, volatile uint8_t *udr, uint8_t udre/*追加*/,
uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udrie, uint8_t u2x, uint8_t flag_n /*追加*/)
{
_rx_buffer = rx_buffer;
_tx_buffer = tx_buffer;
_ubrrh = ubrrh;
_ubrrl = ubrrl;
_ucsra = ucsra;
_ucsrb = ucsrb;
_ucsrc = ucsrc;
_udr = udr;
_udre = udre; //追加
_rxen = rxen;
_txen = txen;
_rxcie = rxcie;
_udrie = udrie;
_u2x = u2x;
_flag_n = flag_n; //追加
}
size_t HardwareSerial::write(uint8_t c)
{
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail);
_tx_buffer->buffer[_tx_buffer->head] = c;
_tx_buffer->head = i;
//sbi(*_ucsrb, _udrie); 割り込みは使わないのでコメントアウトする
// clear the TXC bit -- "can be cleared by writing a one to its bit location"
while(1){
if(_tx_buffer->head == _tx_buffer->tail)break;
unsigned char ch = _tx_buffer->buffer[_tx_buffer->tail];
_tx_buffer->tail = (_tx_buffer->tail + 1) % SERIAL_BUFFER_SIZE;
while(!(*_ucsra&(1<<_udre)));
//送信データを渡す
#if defined(UDR0) && !defined(UDR1) && !defined(UDR2) && !defined(UDR3)
if(_flag_n == 0) UDR0 = ch;
#elif defined(UDR0) && defined(UDR1) && !defined(UDR2) && !defined(UDR3)
if(_flag_n == 0) UDR0 = ch;
if(_flag_n == 1) UDR1 = ch;
#elif defined(UDR0) && defined(UDR1) && defined(UDR2) && !defined(UDR3)
if(_flag_n == 0) UDR0 = ch;
if(_flag_n == 1) UDR1 = ch;
if(_flag_n == 2) UDR2 = ch;
#elif defined(UDR0) && defined(UDR1) && defined(UDR2) && defined(UDR3)
if(_flag_n == 0) UDR0 = ch;
if(_flag_n == 1) UDR1 = ch;
if(_flag_n == 2) UDR2 = ch;
if(_flag_n == 3) UDR3 = ch;
#endif
}
transmitting = true;
// sbi(*_ucsra, TXC0); 割り込みは使わないのでコメントアウトする
return 1;
}
HardwareSerial::operator bool() {
return true;
}
// Preinstantiate Objects //////////////////////////////////////////////////////
uint8_t flag_n;
#if defined(UBRRH) && defined(UBRRL)
HardwareSerial Serial(&rx_buffer, &tx_buffer, &UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR, UDRE/*追加*/, RXEN, TXEN, RXCIE, UDRIE, U2X, flag_n = 0/*追加*/);
#elif defined(UBRR0H) && defined(UBRR0L)
HardwareSerial Serial(&rx_buffer, &tx_buffer, &UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0, UDRE0/*追加*/, RXEN0, TXEN0, RXCIE0, UDRIE0, U2X0, flag_n = 0/*追加*/);
#elif defined(USBCON)
// do nothing - Serial object and buffers are initialized in CDC code
#else
#error no serial port defined (port 0)
#endif
#if defined(UBRR1H)
HardwareSerial Serial1(&rx_buffer1, &tx_buffer1, &UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1, UDRE1/*追加*/, RXEN1, TXEN1, RXCIE1, UDRIE1, U2X1, flag_n = 1/*追加*/);
#endif
#if defined(UBRR2H)
HardwareSerial Serial2(&rx_buffer2, &tx_buffer2, &UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2, UDRE2/*追加*/, RXEN2, TXEN2, RXCIE2, UDRIE2, U2X2, flag_n = 2/*追加*/);
#endif
#if defined(UBRR3H)
HardwareSerial Serial3(&rx_buffer3, &tx_buffer3, &UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3, UDRE3/*追加*/, RXEN3, TXEN3, RXCIE3, UDRIE3, U2X3, flag_n = 3/*追加*/);
#endif
#endif // whole file
ヘッダファイルの方にも追加しておきます。
class HardwareSerial : public Stream
{
private:
ring_buffer *_rx_buffer;
ring_buffer *_tx_buffer;
volatile uint8_t *_ubrrh;
volatile uint8_t *_ubrrl;
volatile uint8_t *_ucsra;
volatile uint8_t *_ucsrb;
volatile uint8_t *_ucsrc;
volatile uint8_t *_udr;
uint8_t _udre; //追加
uint8_t _rxen;
uint8_t _txen;
uint8_t _rxcie;
uint8_t _udrie;
uint8_t _u2x;
uint8_t _flag_n; //追加
bool transmitting;
public:
uint8_t flag_n;
HardwareSerial(ring_buffer *rx_buffer, ring_buffer *tx_buffer,
volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
volatile uint8_t *ucsrc, volatile uint8_t *udr, uint8_t udre/*追加*/,
uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udrie, uint8_t u2x, uint8_t flag_n/*追加*/);
void begin(unsigned long);
void begin(unsigned long, uint8_t);
void end();
virtual int available(void);
virtual int peek(void);
virtual int read(void);
virtual void flush(void);
virtual size_t write(uint8_t);
inline size_t write(unsigned long n) { return write((uint8_t)n); }
inline size_t write(long n) { return write((uint8_t)n); }
inline size_t write(unsigned int n) { return write((uint8_t)n); }
inline size_t write(int n) { return write((uint8_t)n); }
using Print::write; // pull in write(str) and write(buf, size) from Print
operator bool();
};
#最後に
アイドルモードでスリープをした時、上手くいかなくて調べても調べても解説しているサイトに回り会えなかったので、途方にくれていました。なので、同じように困っていた方の助けになればと思います。修正に関してはオブジェクト指向もよくわからぬまま書き換えたものなので、無駄があるかもしれません。
間違い等があれば教えてください。