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関連
女子美コラボ
その他
※プロデューサミーティング中
作り方使い方資料
イベント関連
作品記事
体験記事
ライブラリ
ツール
その他・過去ファイル
まとめておくとそのうち修正されたりされなかったりするんじゃないかという期待を込めて立てました。
サイズ縮小の要望
いまのライブラリで生成されるROMイメージファイルは無駄にでかいので、改善していただきたい。
『クルミの研究』 の投稿に改善案として修正したものを添付しています。
GR-SAKURA ライブラリ V2 と同様に bitbucket 等でリポジトリを公開して pull req とかできるようなってると良いですね。
RLduino78_basic.cppにある↓関数は、修正されないのでしょうか?
* @attention 温度センサの精度(あるいは変換式)に問題があるため正しい温度になりません。
ちゃんとした値が返ってこないし、float使う必要もないと思う。
/** * MCUに内蔵されている温度センサから温度(摂氏/華氏)を取得します。 * * @param[in] u8Mode 摂氏/華氏を指定します。 * @arg TEMP_MODE_CELSIUS : 摂氏 * @arg TEMP_MODE_FAHRENHEIT : 華氏 * * @return 温度を返却します。 * * @attention 温度センサの精度(あるいは変換式)に問題があるため正しい温度になりません。 ***************************************************************************/int getTemperature(uint8_t u8Mode){ int s16Result; float fResult;
FUNC_MUTEX_LOCK; s16Result = _analogRead(ADS_TEMP_SENSOR);
// 取得したアナログ値を摂氏へ変換 // 温度 = (温度センサの出力電圧 - 1140 mV) / 温度係数(-3.6mV) // 温度センサ出力電圧 = A/D変換結果 / 1024 * 基準電圧(Vdd) // // ∴ 温度 = ((A/D変換結果 / 1024 * 5V) - 1140mV) / -3.6mV // fResult = (((float)s16Result / 1024 * 5) - 1.140) / -0.0036; if (u8Mode == TEMP_MODE_FAHRENHEIT) { // 摂氏を華氏へ変換 fResult = (fResult * 5 / 9 + 32) + 0.5; } FUNC_MUTEX_UNLOCK; return (int)fResult;}
鈴木さんが引用されてるコードはどっからのものでしょうか?
私が見てる GR-KURUMI_Sketch_V1.09 では ↓ の内容ですが。
/** * MCUに内蔵されている温度センサから温度(摂氏/華氏)を取得します。 * * @param[in] u8Mode 摂氏/華氏を指定します。 * @arg TEMP_MODE_CELSIUS : 摂氏 * @arg TEMP_MODE_FAHRENHEIT : 華氏 * * @return 温度を返却します。 * ***************************************************************************/ int getTemperature(uint8_t u8Mode) { int s16Result1, s16Result2; float fResult; FUNC_MUTEX_LOCK; s16Result1 = _analogRead(ADS_TEMP_SENSOR); s16Result2 = _analogRead(ADS_REF_VOLTAGE); fResult = (1450 * (float)s16Result1 / (float)s16Result2 - 1050) / -3.6 + 25; if (u8Mode == TEMP_MODE_FAHRENHEIT) { // 摂氏を華氏へ変換 fResult = 1.8 * fResult + 32; } FUNC_MUTEX_UNLOCK; return (int)fResult; }
『クルミの研究』 の中で、主にコードサイズ縮小を目的として整数演算に書き換えたものに置き換えてますが
/** * MCUに内蔵されている温度センサから温度(摂氏/華氏)を取得します。 * * @param[in] u8Mode 摂氏/華氏を指定します。 * @arg TEMP_MODE_CELSIUS : 摂氏 * @arg TEMP_MODE_FAHRENHEIT : 華氏 * * @return 温度を返却します。 * ***************************************************************************/ int getTemperature(uint8_t u8Mode) { #if 0 int s16Result1, s16Result2; float fResult; FUNC_MUTEX_LOCK; s16Result1 = _analogRead(ADS_TEMP_SENSOR); s16Result2 = _analogRead(ADS_REF_VOLTAGE); fResult = (1450 * (float)s16Result1 / (float)s16Result2 - 1050) / -3.6 + 25; if (u8Mode == TEMP_MODE_FAHRENHEIT) { // 摂氏を華氏へ変換 fResult = 1.8 * fResult + 32; } FUNC_MUTEX_UNLOCK; return (int)fResult; #else int s16Result1, s16Result2; long s32Temp; int s16Result; FUNC_MUTEX_LOCK; s16Result1 = _analogRead(ADS_TEMP_SENSOR); s16Result2 = _analogRead(ADS_REF_VOLTAGE); if (s16Result2 == 0) { s16Result2 = 1; } volatile long n14500L = 14500L; s32Temp = n14500L * s16Result1 / s16Result2 - 10500L; if (u8Mode == TEMP_MODE_FAHRENHEIT) { s16Result = s32Temp / -20; s16Result += 77; } else { s16Result = s32Temp / -36; s16Result += 25; } FUNC_MUTEX_UNLOCK; return s16Result; #endif }
あんま結果は変わらんですね。
浮動小数点で計算してる版は、浮動小数点 → 整数 の変換の際に正の値も不の値も 0 の方向に切り捨て/切り上げされるので、そこは違うと思いますが(未確認)。
@chobichanさん、
精度の高い1秒周期割り込みがすげ簡単に作れそうだったので実装してみました。
プロジェクト中の
gr_common/RLduino78/cores/RLduino78_RTC.hgr_common/RLduino78/cores/RLduino78_RTC.cpp
を添付のアーカイブのものと入れ替えてください。
こんな感じ↓で RTC の秒の繰上げに同期した割り込みが使えるようになります。
/*GR-KURUMI Sketch Template Version: V1.09*/ #include <RLduino78.h> #include <RLduino78_RTC.h> int led_red = 22; int led_green = 23; int led_blue = 24; void timerFunc(void); void setup() { rtc_init(); rtc_attach_constant_period_interrupt_handler(timerFunc); rtc_set_constant_period_interrupt_time(RTCConstantPeriodTime1Second); } void loop() { } void timerFunc(void) { static int n = 1; analogWrite(led_red, 255 - (n & 1 ? 20 : 0)); analogWrite(led_green, 255 - (n & 2 ? 20 : 0)); analogWrite(led_blue, 255 - (n & 4 ? 20 : 0)); if (++n >= 8) { n = 1; } }
ありがとうございます。
家に帰ったら試してみますね。
標準で入れて欲しいですね。
試しました。上手く行っている様子です。
アラ良かった。
millis() 不具合
gr_common/RLduino78/cores/RLduino78_basic.cpp の millis() のコードは下記の内容となっていて、1/1000 秒(正確には 33/32768 秒)のタイマーで発生する割り込み処理でインクリメントされる 32bit の変数 g_u32timer_millis を読み出していますが、
unsigned long millis(void) { unsigned long u32ms; FUNC_MUTEX_LOCK; #ifdef USE_RTOS u32ms = xTaskGetTickCount() / portTICK_RATE_MS; #else u32ms = g_u32timer_millis; #endif FUNC_MUTEX_UNLOCK; return u32ms; }
GR-KURUMI に搭載されてる RL78/G13 は 16bit アーキテクチャのプロセッサであり、32bit のオブジェクトをアクセスするには複数の命令を組み合わせる必要があり、事実 millis() のコンパイルされたコードは下記の内容となっています。
Disassembly of section .text.millis: 00000000 <_millis>: 0: af 00 00 movw ax, !f0000 <.LLST109+0xee85a> 3: bd f4 movw 0xffef4, ax 5: af 00 00 movw ax, !f0000 <.LLST109+0xee85a> 8: bd f6 movw 0xffef6, ax a: ad f4 movw ax, 0xffef4 c: bd f0 movw 0xffef0, ax e: ad f6 movw ax, 0xffef6 10: bd f2 movw 0xffef2, ax 12: d7 ret
これは、g_u32timer_millis の値を下位 16bit 読み出しレジスタにストア、更に上位 16bit を読み出してレジスタにストアした後、返し値の 32bit の値にまとめて返すという内容ですが、もしこれで、
0: af 00 00 movw ax, !f0000 <.LLST109+0xee85a>
や
3: bd f4 movw 0xffef4, ax
の命令を実行した直後にたまたま g_u32timer_millis を更新する割り込み処理が発生した場合、g_u32timer_millis の値の下位 16bit と上位 16bit の値に不整合が生じ、millis() の返す値が不正なものとなる可能性があります。
millis() の返す値は 32bit の範囲でオーバーフローが発生するまで(2**32 ミリ秒≒49.7日)は一律に上がり続けるため、その例外を除けば millis() が返す値は前回に millis() が返した値と等しいか、より大きくなります。下記のコードは millis() が返した値が前回返した値よりも小さかった場合に前回返した値と今回返した値を Serial に出力するというものですが、
#include <RLduino78.h> unsigned long before = 0; void setup() { Serial.begin(9600); for (;;) { unsigned long after = millis(); if (after < before) { Serial.print("before: 0x"); Serial.print(before, HEX); Serial.println(); Serial.print("after: 0x"); Serial.print(after, HEX); Serial.println(); Serial.println(); } before = after; } } void loop() { }
試しにこのコードを GR-KURUMI で実行すると
before: 0x6FFFF after: 0x60000 before: 0x8FFFF after: 0x80000 before: 0x3EFFFF after: 0x3E0000 before: 0x44FFFF after: 0x440000 before: 0x55FFFF after: 0x550000 before: 0x56FFFF after: 0x560000 before: 0xA7FFFF after: 0xA70000 before: 0xB2FFFF after: 0xB20000 before: 0xCAFFFF after: 0xCA0000 before: 0xCCFFFF after: 0xCC0000 before: 0xE5FFFF after: 0xE50000
以上のように、(運がいいと)不整合が起こった場合の出力が確認できます。
上の
before: 0x6FFFF after: 0x60000
の場合、g_u32timer_millis の値が 0x0005FFFF の状態で millis() が呼ばれ、下位 16bit の値 0xffff が読み出された後にタイマー割り込みで g_u32timer_millis の値が更新され 0x00060000 となり、上位 16bit の値 0x0006 が読み出され、millis() が返す値として 0x0006ffff となってしまったことが不整合の原因と考えられます。32bit の変数 g_u32timer_millis で下位 16bit から上位 16bit に繰上げが発生するのは 2**16/1000秒≒1分ちょっとに一度のことであり、millis() の内部の処理で下位 16bit と上位 16bit の読み出す間にちょうどその割り込み処理が行われるということは可能性としては非常に低いのですが、実際にそれは起こりうるということです。
以下、解決編に続く
割り込みで更新する32bit以上の値は注意が必要と言うことですね。しかしmillisのmutexはなにしてんだろう?
FUNC_MUTEX~ は RTOS 用のマクロで、RTOS を使用していない GR-KURUMI ライブラリではなんもしてないですね。
#ifdef USE_RTOS #define FUNC_MUTEX_LOCK xSemaphoreTake(xFuncMutex, portMAX_DELAY) //!< 関数用MUTEX LOCKマクロ #define FUNC_MUTEX_UNLOCK xSemaphoreGive(xFuncMutex) //!< 関数用MUTEX UNLOCKマクロ #else #define FUNC_MUTEX_LOCK //!< 関数用MUTEX LOCKマクロ #define FUNC_MUTEX_UNLOCK //!< 関数用MUTEX UNLOCKマクロ #endif
惜しい、クリティカルパスだと言う認識はしていたのでしょうから、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μ秒 の幅のパルスが出るようなりました。
おお!
『ルネサスナイト7「入門ワークショップ」のページ』の
------サンプルプログラム2------ analogWrite #include <Arduino.h> #define INTERVAL 125 void setup(){ pinMode(3,OUTPUT);} void loop(){ analogWrite(3, 0); delay(INTERVAL); analogWrite(3, 32); delay(INTERVAL); analogWrite(3, 64); delay(INTERVAL); analogWrite(3, 96); delay(INTERVAL); analogWrite(3, 128); delay(INTERVAL); analogWrite(3, 160); delay(INTERVAL); analogWrite(3, 192); delay(INTERVAL); analogWrite(3, 224); delay(INTERVAL); analogWrite(3, 255); delay(INTERVAL); analogWrite(3, 224); delay(INTERVAL); analogWrite(3, 192); delay(INTERVAL); analogWrite(3, 160); delay(INTERVAL); analogWrite(3, 128); delay(INTERVAL); analogWrite(3, 96); delay(INTERVAL); analogWrite(3, 64); delay(INTERVAL); analogWrite(3, 32); delay(INTERVAL);}
------サンプルプログラム2------
#include <Arduino.h>
#define INTERVAL 125
void setup(){ pinMode(3,OUTPUT);}
void loop(){ analogWrite(3, 0); delay(INTERVAL); analogWrite(3, 32); delay(INTERVAL); analogWrite(3, 64); delay(INTERVAL); analogWrite(3, 96); delay(INTERVAL); analogWrite(3, 128); delay(INTERVAL); analogWrite(3, 160); delay(INTERVAL); analogWrite(3, 192); delay(INTERVAL); analogWrite(3, 224); delay(INTERVAL); analogWrite(3, 255); delay(INTERVAL); analogWrite(3, 224); delay(INTERVAL); analogWrite(3, 192); delay(INTERVAL); analogWrite(3, 160); delay(INTERVAL); analogWrite(3, 128); delay(INTERVAL); analogWrite(3, 96); delay(INTERVAL); analogWrite(3, 64); delay(INTERVAL); analogWrite(3, 32); delay(INTERVAL);}
上の analogWrite() の不具合への対策修正案
static void _pinMode(uint8_t u8Pin, uint8_t u8Mode, int8_t s8Value = -1);
void analogWrite(uint8_t u8Pin, int s16Value) { uint8_t u8Timer; unsigned short u16Duty; FUNC_MUTEX_LOCK; if (u8Pin < NUM_DIGITAL_PINS) { if (s16Value <= PWM_MIN || s16Value >= PWM_MAX) { u8Timer = g_au8TimerPinTable[u8Pin]; if (u8Timer != NOT_ON_TIMER) { if (g_u8AnalogWriteAvailableTable[u8Pin]) { if ((u8Timer & 0xF0) != SWPWM_PIN) { _stopTimerChannel(u8Timer); } g_u8AnalogWriteAvailableTable[u8Pin] = false; } } // 出力モードに設定 _pinMode(u8Pin, OUTPUT, s16Value <= PWM_MIN ? LOW : HIGH); } else { u8Timer = g_au8TimerPinTable[u8Pin]; if (u8Timer == NOT_ON_TIMER) { /////////////////////// // PWM未対応ピンの場合 /////////////////////// _pinMode(u8Pin, OUTPUT, s16Value < (PWM_MAX / 2) ? LOW : HIGH); // 出力モードに設定 } else if((u8Timer & 0xF0) == SWPWM_PIN){ /////////////////////// // Software PWM対応ピンの場合 /////////////////////// #if defined(REL_GR_KURUMI) int i; _startTAU0(TIMER_CLOCK); if( g_u8AnalogWriteAvailableTable[u8Pin] == false ){ _pinMode(u8Pin, OUTPUT, LOW); // 初期時のみ出力モードを設定 g_u8AnalogWriteAvailableTable[u8Pin] = true; } g_u8SwPwmValue[u8Timer & 0x0F] = s16Value; // SoftwarePWMの設定 if (!(TE0.te0 & 0x0040)) { // No pin uses Software PWM _startTimerChannel( SW_PWM_TIMER, 0x0001, SWPWM_MIN, false, true ); } #endif } else { /////////////////////// // PWM対応ピンの場合 /////////////////////// _startTAU0(TIMER_CLOCK); if (!(TE0.te0 & 0x0001)) { // Masterチャネルの設定 TT0.tt0 |= 0x0001; // タイマ停止 TMR00.tmr00 = PWM_MASTER_MODE; // 動作モードの設定 TDR00.tdr00 = g_u16TDR00; // PWM出力の周期の設定 TO0.to0 &= ~0x0001; // タイマ出力の設定 TOE0.toe0 &= ~0x0001; // タイマ出力許可の設定 // マスタチャネルのタイマ動作許可 TS0.ts0 |= 0x00001; } u16Duty = (unsigned short)(((unsigned long)s16Value * (g_u16TDR00 + 1)) / PWM_MAX); if(g_u8AnalogWriteAvailableTable[u8Pin] == false){ _pinMode(u8Pin, OUTPUT, LOW); // 出力モードに設定 // Slaveチャネルの設定 _startTimerChannel(u8Timer, PWM_SLAVE_MODE, u16Duty, true, false); g_u8AnalogWriteAvailableTable[u8Pin] = true; } else { _modifyTimerPeriodic(u8Timer, u16Duty); } } } } FUNC_MUTEX_UNLOCK; }
static void _pinMode(uint8_t u8Pin, uint8_t u8Mode, int8_t s8Value) { uint8_t u8Port, u8Bit; volatile uint8_t *PMx, *Px, *PIMx, *POMx, *PUx; if (u8Pin < NUM_DIGITAL_PINS) { // アナログピンかどうか? if (14 <= u8Pin && u8Pin <= 21) { // ピンモードをデジタルモードに変更 if (u8Pin == 20) { PMC14.pmc14 &= ~0x80;// P147をデジタルポートに設定 } else if (u8Pin == 21) { PMC12.pmc12 &= ~0x01;// P120をデジタルポートに設定 } else { uint8_t oldadpc = ADPC.adpc; uint8_t newadpc = (u8Pin - 14) + ANALOG_ADPC_OFFSET - 1; if ((oldadpc == 0x00 ) || (oldadpc > newadpc)) { ADPC.adpc = newadpc; } } } u8Port = g_au8DigitalPortTable[u8Pin]; u8Bit = g_au8DigitalPinMaskTable[u8Pin]; if (u8Port != NOT_A_PIN) { PMx = getPortModeRegisterAddr(u8Port); PIMx = getPortInputModeRegisterAddr(u8Port); POMx = getPortOutputModeRegisterAddr(u8Port); PUx = getPortPullUpRegisterAddr(u8Port); Px = getPortOutputRegisterAddr(u8Port); #ifdef WORKAROUND_READ_MODIFY_WRITE if (u8Mode == INPUT) { sbi(PMx, u8Bit); // 入力モードに設定 sbi(PIMx, u8Bit); // TTL入力バッファに設定 cbi(PUx, u8Bit); // プルアップ抵抗を無効に設定 } else if (u8Mode == INPUT_PULLUP) { sbi(PMx, u8Bit); // 入力モードに設定 cbi(PIMx, u8Bit); // CMOS入力バッファに設定 sbi(PUx, u8Bit); // プルアップ抵抗を有効に設定 } else { if (s8Value == LOW) { cbi(Px, u8Bit); // 出力をLOWに設定 } else if (s8Value == HIGH) { sbi(Px, u8Bit); // 出力をHIGHに設定 } cbi(PMx, u8Bit); // 出力モードに設定 cbi(POMx, u8Bit); // 通常出力モードに設定 } #else if (u8Mode == INPUT) { *PMx |= u8Bit; // 入力モードに設定 *PIMx |= u8Bit; // TTL入力バッファに設定 *PUx &= ~u8Bit; // プルアップ抵抗を無効に設定 } else if (u8Mode == INPUT_PULLUP) { *PMx |= u8Bit; // 入力モードに設定 *PIMx &= ~u8Bit; // CMOS入力バッファに設定 *PUx |= u8Bit; // プルアップ抵抗を有効に設定 } else { if (s8Value == LOW) { *Px &= ~u8Bit; // 出力をLOWに設定 } else if (s8Value == HIGH) { *Px |= u8Bit; // 出力をHIGHに設定 } *PMx &= ~u8Bit; // 出力モードに設定 *POMx &= ~u8Bit; // 通常出力モードに設定 } #endif } } }
なんか怪しいのでナシ 修正済み やっぱナシで。
いつの間にか V1.10 に更新されてたのですが、
gr_common/include/RLduino78.h:
#define RLDUINO78_VERSION 0x0108 //!< RLduino78ライブラリのバージョン情報
なんかバージョン情報が戻ってますね。
…ということは GR-KURUMI_TSUBAME_E010 もか。
失礼しました。analogWriteの修正兼ねて、次のバージョンで修正します。ご指摘、解決策ありがとうございました_o_