最終更新日:2022/4/20
RP2040に表示機能を追加します。まずは時刻表示を実現するためにTM1637が搭載された7セグ4桁のLEDボード(モデル名不明)を接続し動作確認したいと思います。
配線ですが、Groveのデジタルコネクタを利用します。Pinアサインを設定すれば融通が利くのですが、SPI0にNICを接続することを検討しておりGPIO20,21はちょっと避け、今回はGPIO18,19に接続します。
Grove | Groveケーブル配色 | XH4P | ||
@ | GND | B | GND | |
A | VCC | C | 5V | |
B | D2 | A | DIO | |
C | D1 | @ | SCK |
インクルードするライブラリはSeeed-Studioのものが良さそうです。
https://github.com/Seeed-Studio/Seeed_Grove_4Digital_Display_g
/* * 2022年3月23日 T.Wanibe * for 4-Digit Display * 最大2093056バイトのフラッシュメモリのうち、スケッチが59208バイト(2%)を使っています。 * 最大262144バイトのRAMのうち、グローバル変数が11156バイト(4%)を使っていて、ローカル変数で250988バイト使うことができます。 */ #include "TM1637.h" #define CLK 18 #define DIO 19 int loopCount = 0; int count = 0; int8_t NumTab[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; //0~9,A,b,C,d,E,F int8_t ListDisp[4]; TM1637 tm1637(CLK,DIO); //---------------- void setup(){ Serial.begin(115200); while (!Serial) { ; // シリアルポートが接続するのを待ちます。 ネイティブUSBポートにのみ必要 } pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); tm1637.init(); tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7; Serial.println(F("EndSetup")); } //---------------- void loop(){ if(loopCount % 2){ digitalWrite(LED_BUILTIN, HIGH); }else{ digitalWrite(LED_BUILTIN, LOW); } unsigned char i = 0; unsigned char count = 0; delay(150); while(count < 16){ i = count; count ++; //if(count == sizeof(NumTab)) count = 0; for(unsigned char BitSelect = 0;BitSelect < 4;BitSelect ++){ ListDisp[BitSelect] = NumTab[i]; i ++; if(i == sizeof(NumTab)) i = 0; } tm1637.display(0,ListDisp[0]); tm1637.display(1,ListDisp[1]); tm1637.display(2,ListDisp[2]); tm1637.display(3,ListDisp[3]); delay(300); } Serial.println(loopCount++); }
時刻表示をするためにはRTCとTimer割込を追加する必要があります。
まずは、適当な基準時刻をRTCにセットし、更新される時刻を7セグ4桁のLEDに表示します。
/* * 2022年3月24日 T.Wanibe * Timeというサンプルスケッチの動作確認を行う Earle F. Philhower、III氏によるスケッチ * * 現在時刻の設定と印刷の簡単なデモンストレーション * 最初の時間は、RTC、NTP、またはユーザーから取得する必要があります * Earle F. Philhower、IIIによってパブリックドメインにリリースされました<earlephilhower@yahoo.com> * 最大2093056バイトのフラッシュメモリのうち、スケッチが64172バイト(3%)を使っています。 * 最大262144バイトのRAMのうち、グローバル変数が11372バイト(4%)を使っていて、ローカル変数で250772バイト使うことができます。 */ #include <time.h> #include <sys/time.h> #include "TM1637.h" #define CLK 18 #define DIO 19 struct timeval tv; struct tm *t; time_t now; char buff[80]; int hour,minute,second; int loopCount; int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; TM1637 tm1637(CLK,DIO); //--------------- void setup() { Serial.begin(115200); while(!Serial); //シリアルオープンを待つ tv.tv_sec = 1642734855; // 1611198855 = Jan 21, 2021 3:14:15AM ...RPi Pico Release; // 1642734855 = 2022/3/24 00:00:00 //GPSから時刻を読み取る tv.tv_usec = 0; settimeofday(&tv, nullptr); //RTCに時刻をセットする pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); tm1637.init(); tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7; Serial.println(F("EndSetup")); } //--------------- void loop() { time(&now); t=localtime(&now); hour = t->tm_hour; minute = t->tm_min; second = t->tm_sec; sprintf(buff,"%02d:%02d:%02d", hour,minute,second); if(!second || !loopCount){ TimeDisp[0] = hour / 10; TimeDisp[1] = hour % 10; TimeDisp[2] = minute / 10; TimeDisp[3] = minute % 10; } if(++loopCount % 2){ digitalWrite(LED_BUILTIN, HIGH); tm1637.point(POINT_ON); }else{ digitalWrite(LED_BUILTIN, LOW); tm1637.point(POINT_OFF); } tm1637.display(TimeDisp); Serial.println(buff); delay(1000); }期待通りの結果になるはずです。
GPSから取得する時間をRTCにセットし、更新される時刻を7セグ4桁のLEDに表示します。
※最終的にはSTM32miniShieldで構築したGPS-NTPserverプロジェクトをRP2040に移植したいと思っています。その前段過程作業となります。RP2040には内部時計(RTC)があります。このクロック精度が悪いです。200ppmという結果が出ています。
この値は一般的なRTCchipの10倍のブレです。多分STM32F1よりも悪いようです。
200ppmズレているとなると1時間に1回GPSによる時刻校正をしたいところです。
また、内部RTCのアクセス方法も要検討ですね。表示のためにTimer割込で1秒毎に表示更新したいです。
/* * 2022年3月24日 T.Wanibe * UARTに接続したGPSから *GxRMC のセンテンスを解析して時刻情報を取得しRTCにセットする * GPSの解析はライブラリを使わず独自のモノにする。 * Timeというサンプルスケッチの動作確認を行う Earle F. Philhower、III氏によるスケッチ * * 現在時刻の設定と印刷の簡単なデモンストレーション * 最初の時間は、RTC、NTP、またはユーザーから取得する必要があります * Earle F. Philhower、IIIによってパブリックドメインにリリースされました<earlephilhower@yahoo.com> * 最大2093056バイトのフラッシュメモリのうち、スケッチが71828バイト(3%)を使っています。 * 最大262144バイトのRAMのうち、グローバル変数が11784バイト(4%)を使っていて、ローカル変数で250360バイト使うことができます。 */ #define TIMER_INTERRUPT_DEBUG 1 #define _TIMERINTERRUPT_LOGLEVEL_ 4 #include <time.h> #include <sys/time.h> #include "TM1637.h" #include "RPi_Pico_TimerInterrupt.h" #define CLK 18 #define DIO 19 #define DELIMITER "," #define debug 1 #define TIMER0_INTERVAL_MS 1000 struct timeval tv; struct timezone tz; struct tm *tt; //STM32dinoとRP2040の構造体が異なる time_t now; char buff[80]; int year = 2022; byte month = 3; byte day = 24; byte hour = 13; byte minute = 00; byte second = 00; int loopCount; int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; char chkHader[] = "RMC"; //このバージョンから3文字に変更 float timezone = 9.0; //TimeZone 日本は+9です。 const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };//const or compiler complains const unsigned long seventyYears = 2208988800UL; // NTP時刻(1900)とUNIX時刻(1970)のオフセット(2208988800秒)70年間の秒数 String nmea; tm tmm; /* 構造体 tm はtime.h の中で宣言され、以下の情報を含みます.STM32dinoとRP2040の構造体が異なる struct tm { int tm_sec; // 秒 [0-61] 最大2秒までのうるう秒を考慮 int tm_min; // 分 [0-59] int tm_hour; // 時 [0-23] int tm_mday; // 日 [1-31] int tm_mon; // 月 [0-11] 0から始まることに注意 int tm_year; // 年 [1900からの経過年数] int tm_wday; // 曜日 [0:日 1:月 ... 6:土] int tm_yday; // 年内の通し日数 [0-365] 0から始まることに注意 int tm_isdst; // 夏時間が無効であれば 0 }; */ long gLoopCount = 3; //1時間毎に更新予定 最初は1分後に更新したくて初期値を調整 bool GpsEnable = false; unsigned long baseMilis = millis(); bool toggle0 = false; bool started = false; TM1637 tm1637(CLK,DIO); RPI_PICO_Timer ITimer0(0); //Init RPI_PICO_Timerは、0?15の疑似ハードウェアタイマーを使用できます。 //-------------タイマ割込で呼ばれるルーチン bool TimerHandler0(struct repeating_timer *t){ if (!started){ started = true; pinMode(LED_BUILTIN, OUTPUT); } #if (TIMER_INTERRUPT_DEBUG > 0) Serial.print(F("ITimer0 called, millis() = ")); Serial.println(millis()); #endif time(&now); tt = localtime(&now); hour = tt->tm_hour; minute = tt->tm_min; second = tt->tm_sec; sprintf(buff,"\n%04d/%02d/%02d %02d:%02d:%02d", tt->tm_year,tt->tm_mon+1,tt->tm_mday,hour,minute,second); Serial.println(buff); TimeDisp[0] = hour / 10; TimeDisp[1] = hour % 10; TimeDisp[2] = minute / 10; TimeDisp[3] = minute % 10; if(toggle0) tm1637.point(POINT_ON); else tm1637.point(POINT_OFF); tm1637.display(TimeDisp); //タイマー割り込みはピンLED_BUILTINを切り替えます digitalWrite(LED_BUILTIN, toggle0); toggle0 = !toggle0; return true; } //---------------NTP since 1900/01/01 retouch2020/12/25 static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s,float timezone) { //2036年問題が存在する。unsigned long intの桁溢れが発生 //桁溢れが発生したら受信側が対応する必要がある。サーバ側では64bit化するべきだが動かなくなる。 //timezoneをfloat扱いにした int leapAdjustment = 0; if (y >= 1970) { y -= 1970; leapAdjustment = 2; // LM: 1970 was NOT a leap year! } uint16_t days = d; for (uint8_t i = 1; i < m; ++i) days += pgm_read_byte(daysInMonth + i - 1); /* if (m > 2 && y % 4 == 0) ++days; */ if (m > 2 && (y + leapAdjustment) % 4 == 0) ++days; // LM: Weak but okay for the present days += 365 * y + (y + 3) / 4 - 1; long offsetTime = long((float(h)- timezone) * 3600.0); return days*24L*3600L + offsetTime + mm*60L + s + seventyYears; } //------------------ bool readSensor(void){ if (Serial1.available()) { Serial1.readStringUntil('\n'); //読み捨てて先頭から読めるようにする nmea = Serial1.readStringUntil('\n'); #if debug Serial.println(nmea); #endif //$GNRMC,022757.000,A,3430.62587,N,13316.66175,E,0.63,144.24,231220,,,A,V*02 String nmeames = nmea.substring(3,6); // $GP*** int count = 0; while(!nmeames.equals(chkHader)){ nmea = Serial1.readStringUntil('\n'); nmeames = nmea.substring(3,6); // $GP*** //digitalWrite(LED2, count++%2); #if debug Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames); #endif if(count>30){ //digitalWrite(LED1, HIGH); break; } } int nextPos = 0; if ( nmeames.equals(chkHader)){ // get UTC nextPos = nmea.indexOf(DELIMITER);//int indexOf(char ch, unsigned int fromIndex) const; String shh = nmea.substring(nextPos+1,nextPos+3); String smm = nmea.substring(nextPos+3,nextPos+5); String sss = nmea.substring(nextPos+5,nextPos+7); nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip UTC nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip Status nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 緯度 nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 北緯か南緯か nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 経度 nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 東経か西経か nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 地表における移動の速度[knot] nextPos = nmea.indexOf(DELIMITER,nextPos+1); //skip 地表における移動の真方位[deg] String sdd = nmea.substring(nextPos+1,nextPos+3); String smn = nmea.substring(nextPos+3,nextPos+5); String syy = nmea.substring(nextPos+5,nextPos+7); Serial.println("20" + syy + smn + sdd + shh + smm + sss); tt->tm_year = syy.toInt() + 2000 - 1970; if ((tt->tm_year < 31) || (tt->tm_year > 100)){ //2001~2079をサポート return false; }else{ tt->tm_hour = shh.toInt(); hour = tt->tm_hour; tt->tm_min = smm.toInt(); minute = tt->tm_min; tt->tm_sec = sss.toInt(); second = tt->tm_sec; tt->tm_mday = sdd.toInt(); day = tt->tm_mday; tt->tm_mon = smn.toInt(); month = tt->tm_mon; year = syy.toInt() + 2000; return true; } }else{ //digitalWrite(LED1, HIGH); return false; } } return false; //要らないはずだけどコンパイルエラー回避のため } //------------------GPSにアクセスし、取得に成功したら内部時間を更新 bool ReadGPS(void){ bool valid = readSensor(); Serial.println(valid); if(valid) { if(gLoopCount>60 | !GpsEnable){ //rtclock.setTime(tmm); // 時刻の設定 一時間毎に更新 tv.tv_sec = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second,-9.0); tv.tv_usec = 0; Serial.println((unsigned long)(tv.tv_sec)); settimeofday(&tv, nullptr); //RTCに時刻をセットする baseMilis = millis(); //20201224追加 gLoopCount = 1; GpsEnable = true; } valid = true; }else{ delay(100); if(gLoopCount%60) Serial.print(F(".")); else{ Serial.println(F(".")); //GpsEnable = false; } } if(gLoopCount > 0x7FFFFFF0) gLoopCount = 1; return valid; } //--------------- void setup() { Serial.begin(115200); //while(!Serial); //シリアルオープンを待つ tv.tv_sec = 1611198855; // 1611198855 = Jan 21, 2021 3:14:15AM ...RPi Pico Release; // 1642734855 = 2022/3/24 00:00:00 //GPSから時刻を読み取る tv.tv_usec = 0; settimeofday(&tv, nullptr); //RTCに時刻をセットする pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); tm1637.init(); tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7; Serial.print(F("\nStarting TimerInterruptTest on ")); Serial.println(BOARD_NAME); Serial.println(RPI_PICO_TIMER_INTERRUPT_VERSION); Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); //マイクロ秒単位の間隔 if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerHandler0)){ Serial.print(F("Starting ITimer0 OK, millis() = ")); Serial.println(millis()); }else{ Serial.println(F("Can't set ITimer0. Select another freq. or timer")); } Serial.flush(); Serial.println(F("\nStart UART0")); Serial1.begin(9600); Serial.println(F("EndSetup")); } //--------------- void loop() { if (Serial1.available()){ bool valid = ReadGPS(); } }
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。