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 とかできるようなってると良いですね。
Software PWM の修正(ピン可変と不具合修正)作業をしていたのだけれど、作業してた PC がお亡くなりになったので中断中。復帰したら投稿します。
それはそれですごい嬉しいですし、運営人員を増やすきっかけになればいいとは思いますね。
PCが復帰されることを心からお祈りしています。
GR-KURUMIへの要望として、シリアルの送受信バッファサイズを調整できる様にできませんか?
送受別に設定します。
ヘッダファイル書き換えでいいと思っていましたが、APIとして用意した方がいいですか?どっちでもいいですか?
あまりC++判らないのですが、バッファ自体はインスタンス生成時に動的に生成されるのですよね?
ならbeginの引数にボーレート以外にオーバーライドでバッファサイズの指定をできるようにならないのかなぁ?って思いです。
応用によっては送信バッファが必要無い!とか受信バッファが必要無いとかもありえますし。
バッファは、HardwareSerial.cpp の中でサイズが
#define SERIAL_BUFFER_SIZE 64
となってて、リングバッファの定義が
struct ring_buffer { unsigned char buffer[SERIAL_BUFFER_SIZE]; volatile unsigned int head; volatile unsigned int tail; };
で、あとはチャンネルごとに受信と送信のバッファが
ring_buffer rx_buffer = { { 0 }, 0, 0}; ring_buffer tx_buffer = { { 0 }, 0, 0};
みたいな感じで(現在は)静的に確保されてますね。
静的でいいとは思ってますが使わないチャネルも一律に確保されているので、そこは変更した方がいいかもしれませんね。
RTCに定周期割り込みのハンドラーの登録機能が欲しい。
RTCに定周期割り込みのハンドラーの登録機能
↓辺りとはまた別に、ということでしょうか?
attachIntervalTimerHandler() attachMicroIntervalTimerHandler() attachCyclicHandler()
サイクリックハンドラの場合はloop関数内で処理されていますが、実はloop関数を使用していません(笑)。
他の1ms周期のやつはベースのクロックがシステムクロックになると思われるので、RTCの32768Hzのクロックよりか精度が落ちそうな気がしないでもない。
精度の高い1秒周期で割り込みハンドラが使えると、1000を数えなくても良いので、ちょっとだけ処理が楽になります。
二重書き込みになっちゃった。
1m 秒は精度の高い RTC 用の 32.768kHz のサブクロックをソースにしているので割り込み間隔の精度は高いです。
但し、33/32768 秒を 1m 秒ということにしているので時計的な用途にはそのまゝでは使い物になりませんが。
精度の件は了解です。
ただ1秒割り込みとか便利なので、実現して欲しいなぁ。
今はハンドラの中で経過時間に補正を掛けるロジックを追加しています。
RLduino78_basic.cpp の中の周期起動コールバック関数を呼び出す関数
void execCyclicHandler() { int i; for (i = 0; i < MAX_CYCLIC_HANDLER; i++) { if (g_afCyclicHandler[i] != NULL) { if (g_u32timer_millis >= g_au32CyclicHandlerPerformTime[i]) { g_au32CyclicHandlerPerformTime[i] = g_au32CyclicTime[i] + g_u32timer_millis; (*g_afCyclicHandler[i])(g_u32timer_millis); } } } }
g_au32CyclicHandlerPerformTime[i] についてオーバーフローが考慮されてないので 42億ミリ秒とちょっと(約49.7日)でおかしくなる感じ。
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さん、よく気づかれますね。。ありがたいです。