GR-SAKURA
GR-KURUMI
GR-COTTON
GR-CITRUS
GR-PEACH
GR-KAEDE
GR-ADZUKI
GR-LYCHEE
GR-ROSE
GR-MANGO(*)
SNShield
Web Compiler
IDE for GR
TOPPERS関連
女子美コラボ
その他
※プロデューサミーティング中
作り方使い方資料
イベント関連
作品記事
体験記事
ライブラリ
ツール
その他・過去ファイル
まとめておくとそのうち修正されたりされなかったりするんじゃないかという期待を込めて立てました。
惜しい、クリティカルパスだと言う認識はしていたのでしょうから、RTOSでない時は単純に割込み禁止とかにしておいても良かった気がする。
割り込み禁止期間をどれほど許してよいか? という問題があるのでそう単純にできるものではない気がします。例えば pulseIn() という関数は最悪引数で与えられたタイムアウト時間までポートを監視続けますが、その期間割り込みを禁止し続けるというのは色々と問題があります(…という以前に pulseIn() の実装ヒドいな)。
unsigned long pulseIn(uint8_t u8Pin, uint8_t u8Value, unsigned long u32Timeout) { uint8_t u8Port, u8Bit, u8StateMask; volatile uint8_t *Px; unsigned long u32PulseLength = 0; unsigned long u32LoopCounts, u32MaxLoopCounts; int skip_flag = 0; FUNC_MUTEX_LOCK; if (u8Pin < NUM_DIGITAL_PINS) { u8Port = g_au8DigitalPortTable[u8Pin]; u8Bit = g_au8DigitalPinMaskTable[u8Pin]; u8StateMask = (u8Value ? u8Bit : 0); if (u8Port != NOT_A_PIN) { u32PulseLength = 0; Px = getPortInputRegisterAddr(u8Port); u32LoopCounts = 0; u32MaxLoopCounts = microsecondsToClockCycles(u32Timeout) / 16; while ((*Px & u8Bit) == u8StateMask) { if (u32LoopCounts++ >= u32MaxLoopCounts) { skip_flag = 1; break; } } if (skip_flag == 0) { while ((*Px & u8Bit) != u8StateMask) { if (u32LoopCounts++ >= u32MaxLoopCounts) { skip_flag = 1; break; } } if (skip_flag == 0) { while ((*Px & u8Bit) == u8StateMask) { if (u32LoopCounts++ >= u32MaxLoopCounts) { break; } u32PulseLength++; } } } if (u32LoopCounts++ >= u32MaxLoopCounts) { u32PulseLength = 0; } else { // コンパイルオプションにより乗数(x 38)を調整する必要あり。 u32PulseLength = clockCyclesToMicroseconds(u32PulseLength * 38); } } } FUNC_MUTEX_UNLOCK; return u32PulseLength; }
このpulseInにしても、RTOSの時はmutexを意識しますって感じでコードが書かれているのに、実態はただのマクロが有るだけで、「何を対象に排他制御を行います」が完全に抜けているので、それはRTOSではないなぁと。
FUNC_MUTEX_LOCK;は何をしたかったのだろう?
Fujitaさん、chobichanさん、millisの指摘と、RTCについてのご提案ありがとうございます。
RTCは次で反映しようかと思います。定周期割り込み機能がありましたね。
FUNC_MUTEX_LOCK/UNLOCKは、実は私も経緯を知らず、担当も不在のため分からないですが、イベントが落ち着いて後検討させてください。しかしFujitaさん、よく気づかれますね。。ありがたいです。
RTC の反映期待しております。喜ばれる方もいらっしゃるのではないかと思います。
定期間指定の引数を
typedef enum { RTCConstantPeriodTimeNone = 0, RTCConstantPeriodTimeHalfSecond = 1, RTCConstantPeriodTime1Second = 2, RTCConstantPeriodTime1Minute = 3, RTCConstantPeriodTime1Hour = 4, RTCConstantPeriodTime1Day = 5, RTCConstantPeriodTime1Month = 6, } RTCConstantPeriodTime; int rtc_set_constant_period_interrupt_time(RTCConstantPeriodTime ct = RTCConstantPeriodTime1Second);
としてましたが、曜日の指定の RTC_WEEK_SUNDAY やアラームの引数 RTC_ALARM_EVERYDAY に合わせて
typedef enum { RTC_CONSTANT_PERIOD_TIME_NONE = 0, RTC_CONSTANT_PERIOD_TIME_HALFSECOND = 1, RTC_CONSTANT_PERIOD_TIME_1SECOND = 2, RTC_CONSTANT_PERIOD_TIME_1MINUTE = 3, RTC_CONSTANT_PERIOD_TIME_1HOUR = 4, RTC_CONSTANT_PERIOD_TIME_1DAY = 5, RTC_CONSTANT_PERIOD_TIME_1MONTH = 6, } RTC_CONSTANT_PERIOD_TIME; int rtc_set_constant_period_interrupt_time(RTC_CONSTANT_PERIOD_TIME ct = RTC_CONSTANT_PERIOD_TIME_1SECOND);
等とした方が良かったかもしれません。
millis() 解決編
解決案その1
上位 16bit(1回目)、下位 16bit、上位 16bit(2回目)を読み出し、上位 16bit(1回目)と上位 16bit(2回目)の値を比較する
unsigned long millis(void) { unsigned long u32ms; FUNC_MUTEX_LOCK; #ifdef USE_RTOS u32ms = xTaskGetTickCount() / portTICK_RATE_MS; #else unsigned short hi = ((volatile unsigned short*)&g_u32timer_millis)[1]; unsigned short lo = ((volatile unsigned short*)&g_u32timer_millis)[0]; if ((((unsigned short*)&u32ms)[1] = ((volatile unsigned short*)&g_u32timer_millis)[1]) != hi) { lo = 0; } ((unsigned short*)&u32ms)[0] = lo; #endif FUNC_MUTEX_UNLOCK; return u32ms; }
少々コードが煩くなるのが欠点。割り込みを止めないで済む点は良い。
解決案その2
一時的に割り込みを禁止する
unsigned long millis(void) { unsigned long u32ms; FUNC_MUTEX_LOCK; #ifdef USE_RTOS u32ms = xTaskGetTickCount() / portTICK_RATE_MS; #else bool di = isNoInterrupts(); noInterrupts(); u32ms = g_u32timer_millis; if (!di) { interrupts(); } #endif FUNC_MUTEX_UNLOCK; return u32ms; }
シンプルで良い。一時的にしろ割り込み禁止を嫌う人もいるかも。
解決案その3
一時的に割り込みを禁止する(その2) iodefine.h に
union un_psw { unsigned char psw; struct { unsigned char cy :1; unsigned char isp0 :1; unsigned char isp1 :1; unsigned char rbs0 :1; unsigned char ac :1; unsigned char rbs1 :1; unsigned char z :1; unsigned char ie :1; } BIT; };
#define PSW (*(volatile union un_psw *)0xFFFFA)
以上の内容を追加し、
unsigned long millis(void) { unsigned long u32ms; FUNC_MUTEX_LOCK; #ifdef USE_RTOS u32ms = xTaskGetTickCount() / portTICK_RATE_MS; #else byte _psw = PSW.psw; noInterrupts(); u32ms = g_u32timer_millis; PSW.psw = _psw; #endif FUNC_MUTEX_UNLOCK; return u32ms; }
恐らく出力コードは最も短くなる。PSW 中の IE 以外のフラグを上書きすることを嫌う人もいるかも。
以上の何れかが修正方法として適当と思われるが、RLduino78_basic.cpp の中で g_u32timer_millis を参照している箇所は millis() 以外にもあるので、それぞれ millis() を呼び出す等修正する必要がある。他、g_u32delay_timer、g_timer05_overflow_count、g_u32ToneDuration、g_u32ToneInterruptCount 等の変数や他のソースにも同様の問題がないか確認する必要がある。
RLduino78.h の中で、割り込み許可/禁止のマクロが
#define interrupts() asm("EI;") #define noInterrupts() asm("DI;")
と定義されているが、gcc のインラインアセンブラで asm() を使うと最適化の際に勝手に命令を移動してくれる可能性あり、割り込みの許可/禁止をプログラム中の意図した箇所で行いたい場合には
#define interrupts() __asm __volatile("EI;") #define noInterrupts() __asm __volatile("DI;")
等とするべき。 また、iodefine.h に
以上の定義があれば割り込み許可/禁止状態の確認がアセンブラを使わずにできるようになるので、現在 RLduino78_basic.cpp にある
extern "C" bool isNoInterrupts(); __asm __volatile( "_isNoInterrupts: \n" " clrb a \n" " bt psw.7, $1f \n" " oneb a \n" "1: mov r8, a \n" " ret \n" );
は削除し、RLduino78.h に以下の内容を追加するのが良い。割り込み許可/禁止状態の確認のコストが小さくなる。
/** * 割り込みが有効かを判定します。 * @return 割り込みが有効だと1、無効だと0 * @attention なし */ #define isInterrupts() (PSW.BIT.ie == 1) /** * 割り込みが禁止かを判定します。 * @return 割り込みが禁止だと1、有効だと0 * @attention なし */ #define isNoInterrupts() (PSW.BIT.ie == 0)
RTC機能追加期待しています。時計を作った時に時報と同期しているのを見るのはすごく気持ちいい。割り込みならレジスタの変化をポーリングしないで済むし。
millisの件、解決案その3の一時的割り込み禁止をPSW直書きで実装します。RTCのenumの件も了解です。
以下はちょっと先にのばします。
>他、g_u32delay_timer、g_timer05_overflow_count、g_u32ToneDuration、g_u32ToneInterruptCount 等の変数や他のソースにも同様の問題がないか確認する必要がある。
宜しくおねがいします。
pulseIn() にケチつけたついでに pulseIn() の動作テスト
#include <RLduino78.h> #include <Servo.h> Servo servo; const int OutputPin = 3; const int InputPin = 4; void setup() { Serial.begin(115200); Serial.println("start"); servo.attach(OutputPin); } void loop() { static int outputPulseWidth = 100; servo.writeMicroseconds(outputPulseWidth); delay(100); unsigned long inputPulseWidth = pulseIn(InputPin, HIGH, 100000UL); Serial.print("output pulse width: "); Serial.print(outputPulseWidth); Serial.println(); Serial.print("input pulse width: "); Serial.print(inputPulseWidth); Serial.println(); Serial.println(); if ((outputPulseWidth += 100) > 19999) { outputPulseWidth = 100; } }
GR-KURUMI の 3番ピンと 4番ピンを結線し上記のプログラムを動作させる。Servo ライブラリを使用し 100~19900μ秒の幅のパルスを 3番ピンに出力し、その出力を 4番ピンに入力して pulseIn() でパルス幅を計る。
結果
start output pulse width: 100 input pulse width: 528 output pulse width: 200 input pulse width: 530 output pulse width: 300 input pulse width: 528 output pulse width: 400 input pulse width: 530 output pulse width: 500 input pulse width: 528 output pulse width: 600 input pulse width: 584 output pulse width: 700 input pulse width: 684 output pulse width: 800 input pulse width: 787 output pulse width: 900 input pulse width: 883 output pulse width: 1000 input pulse width: 984 output pulse width: 1100 input pulse width: 1084 output pulse width: 1200 input pulse width: 1182 output pulse width: 1300 input pulse width: 1283 output pulse width: 1400 input pulse width: 1383 output pulse width: 1500 input pulse width: 1483 output pulse width: 1600 input pulse width: 1580 output pulse width: 1700 input pulse width: 1680 output pulse width: 1800 input pulse width: 1778 output pulse width: 1900 input pulse width: 1881 output pulse width: 2000 input pulse width: 1981 output pulse width: 2100 input pulse width: 2081 output pulse width: 2200 input pulse width: 2179 output pulse width: 2300 input pulse width: 2281 output pulse width: 2400 input pulse width: 2376 output pulse width: 2500 input pulse width: 2380 output pulse width: 2600 input pulse width: 2378 output pulse width: 2700 input pulse width: 2382 output pulse width: 2800 input pulse width: 2378 output pulse width: 2900 input pulse width: 2380 output pulse width: 3000 input pulse width: 2376
[以下略]
output pulse width: 2500以降はinput pulseは頭打ちなんですかね。
なんでだろう?
Servo クラスの writeMicroseconds() が
void Servo::writeMicroseconds(int value) { // calculate and store the values for the given channel byte channel = this->servoIndex; if( (channel < MAX_SERVOS) ) // ensure channel is valid { if( value < SERVO_MIN() ) // ensure pulse width is valid value = SERVO_MIN(); else if( value > SERVO_MAX() ) value = SERVO_MAX(); value = value - TRIM_DURATION; value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009 #if defined(REL_GR_KURUMI) servos[channel].ticks = value; #else noInterrupts(); servos[channel].ticks = value; interrupts(); #endif } }
となっており、SERVO_MIN() と SERVO_MAX() と比較して下限と上限への切り詰めを行っています。
SERVO_MIN() と SERVO_MAX() の実装は何ぞや? と確認すると
#define SERVO_MIN() (MIN_PULSE_WIDTH - this->min * 4) // minimum value in uS for this servo #define SERVO_MAX() (MAX_PULSE_WIDTH - this->max * 4) // maximum value in uS for this servo
ということになっており、MIN_PULSE_WIDTH と MAX_PULSE_WIDTH はそれぞれ
#define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo #define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo
という値になっています。this->min と this->max の値は
uint8_t Servo::attach(int pin, int min, int max) { if(this->servoIndex < MAX_SERVOS ){ pinMode( pin, OUTPUT); // set servo pin to output servos[this->servoIndex].Pin.nbr = pin; // todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128 this->min = (MIN_PULSE_WIDTH - min)/4; // resolution of min/max is 4 uS this->max = (MAX_PULSE_WIDTH - max)/4; // initialize the timer if it has not already been initialized timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); if(isTimerActive(timer) == false) initISR(timer); servos[this->servoIndex].Pin.isActive = true; // this must be set after the check for isTimerActive } return this->servoIndex; }
で設定されてるので、gr_sketch.cpp の setup() から呼んでる
servo.attach(OutputPin);
を
servo.attach(OutputPin, 0, 19999);
に変えると(リファレンスマニュアルの記述とは違うものの)良さそうですが、実際それで試してみると
start output pulse width: 100 input pulse width: 1008 output pulse width: 200 input pulse width: 1008 output pulse width: 300 input pulse width: 1008 output pulse width: 400 input pulse width: 1008 output pulse width: 500 input pulse width: 1008 output pulse width: 600 input pulse width: 1005 output pulse width: 700 input pulse width: 1008 output pulse width: 800 input pulse width: 1008 output pulse width: 900 input pulse width: 1008 output pulse width: 1000 input pulse width: 1008 output pulse width: 1100 input pulse width: 1084 output pulse width: 1200 input pulse width: 1182 output pulse width: 1300 input pulse width: 1283 output pulse width: 1400 input pulse width: 1381 output pulse width: 1500 input pulse width: 1483 output pulse width: 1600 input pulse width: 1580 output pulse width: 1700 input pulse width: 1681 output pulse width: 1800 input pulse width: 1778 output pulse width: 1900 input pulse width: 1881 output pulse width: 2000 input pulse width: 1980 output pulse width: 2100 input pulse width: 2081 output pulse width: 2200 input pulse width: 2179 output pulse width: 2300 input pulse width: 2281 output pulse width: 2400 input pulse width: 2376 output pulse width: 2500 input pulse width: 2481 output pulse width: 2600 input pulse width: 2566 output pulse width: 2700 input pulse width: 2569 output pulse width: 2800 input pulse width: 2566 output pulse width: 2900 input pulse width: 2569 output pulse width: 3000 input pulse width: 2563
下限が 1000ちょっと?、上限が2560ちょっと? とわけわからん感じです。
確認したところ、Servo クラスの
class Servo { public: Servo(); uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes. void detach(); void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds void writeMicroseconds(int value); // Write pulse width in microseconds int read(); // returns current pulse width as an angle between 0 and 180 degrees int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) bool attached(); // return true if this servo is attached, otherwise false private: uint8_t servoIndex; // index into the channel data for this servo int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH };
min と max が 8bit の変数となっており、
や
this->min = (MIN_PULSE_WIDTH - min)/4; // resolution of min/max is 4 uS this->max = (MAX_PULSE_WIDTH - max)/4;
の計算でオーバーフローを起こしているようです。
試しに min と max の型を int16_t に変更し、ビルド、実行してみたところ、
start output pulse width: 100 input pulse width: 86 output pulse width: 200 input pulse width: 187 output pulse width: 300 input pulse width: 286 output pulse width: 400 input pulse width: 387 output pulse width: 500 input pulse width: 484 output pulse width: 600 input pulse width: 585 output pulse width: 700 input pulse width: 684 output pulse width: 800 input pulse width: 787 output pulse width: 900 input pulse width: 883 output pulse width: 1000 input pulse width: 984
[中略]
output pulse width: 19000 input pulse width: 18932 output pulse width: 19100 input pulse width: 19030 output pulse width: 19200 input pulse width: 19130 output pulse width: 19300 input pulse width: 19232 output pulse width: 19400 input pulse width: 19332 output pulse width: 19500 input pulse width: 19432 output pulse width: 19600 input pulse width: 19533 output pulse width: 19700 input pulse width: 19627 output pulse width: 19800 input pulse width: 19730 output pulse width: 19900 input pulse width: 19828
一応は 100~19900μ秒 の幅のパルスが出るようなりました。
writeMicroseconds() の動作のリファレンスとの齟齬を解消するには
void Servo::writeMicroseconds(int value) { // calculate and store the values for the given channel byte channel = this->servoIndex; if( (channel < MAX_SERVOS) ) // ensure channel is valid { if( value < 0 ) // ensure pulse width is valid value = 0; else if( value >= g_servo_refresh_interval ) value = g_servo_refresh_interval - 1; value = value - TRIM_DURATION; value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009 #if defined(REL_GR_KURUMI) servos[channel].ticks = value; #else noInterrupts(); servos[channel].ticks = value; interrupts(); #endif } }
等に書き換える必要があると思います(未確認)。
> 一応は 100~19900μ秒 の幅のパルスが出るようなりました。
おお!