最終更新日:2021年5月28日
【動作確認】
動作確認には以下の機能確認コードをご利用戴ければと思います。ArduinoIDEにて新規ファイルを作成しコードをコピペしてください。
各機能単体検証プログラムを示しておきます。
STM32duino環境が整ったArduinoIDEにて、新規スケッチを開き、コードをコピペして拡張子 ino として保存し、Buildしてください。
[TM1638の確認コード]
ライブラリはgithubから落とします。
https://github.com/rjbatista/tm1638-library
配線はSTM32MINIShield基板のTM1638コネクタにLED&KEYを接続します。
起動すると、
- LED&KEYの7セグLEDに 1.2.3.4.A B C D と表示されます。
- タクトスイッチを押すとその位置のLEDが押している間点灯します。
/* * Library examples for TM1638. * 2019/12/23 T.Wanibe * TM1638ライブラリは URL:https://github.com/rjbatista/tm1638-library より取得します。 */ #include <TM1638.h> #define dataPin PB4 #define clockPin PB3 #define strobePin PA15 // TM1638 module(dataPin, clockPin, strobePin); void setup() { // 16進数を表示し、左の4つのドットをONに設定します module.setDisplayToHexNumber(0x1234ABCD, 0xF0); } void loop() { byte keys = module.getButtons(); // 8個のLEDとボタンにおいて、押された位置のLEDが点灯します。 module.setLEDs((keys & 0xFF)); }LED&KEYは入出力デバイスとしてとても使いと思っています。chipはTM-1638が採用されており、このチップが搭載されたモジュールのバリエーションはいくつかあります。紹介します。いずれも https://github.com/rjbatista/tm1638-library が使用できるようです。
@ ![]()
一番入手容易なTM-1638モジュールだと思います。
AliExpressで購入すれば200円以下で入手可能です。使いやすいモジュールだと思っています。
A ![]()
このモジュールは設計が古いようです。入手困難です。 B ![]()
JY-LKM1638
購入先が限られますが、モジュール連結が出来るため有用です。高いのが難点か500円では買えないです。C ![]()
QYF-TM1638
入力ボタンが16あり、安価なのがうれしいです。200円以下。D ![]()
7セグLEDのみの表示に限っています。連結できるので使い道はあります。 E ![]()
TM1638は最大24(8×3)個の接点入力が出来ると有ります。
目一杯詰め込んだモジュールなのですが入手先が見当たりません。
[TSL2561(I2C)の確認コード]
メーカサイトにライブラリが残っていませんのでgithubから落とします。
https://github.com/sparkfun/SparkFun_TSL2561_Arduino_Library
配線はSTM32MINIShield基板のI2CコネクタにGY-2561を接続します。
実行結果はシリアルモニタに照度値がプロットされます。
/* SparkFun TSL2561 library example sketch * 2019/12/23 T.Wanibe */ #include <SparkFunTSL2561.h> #include <Wire.h> // Create an SFE_TSL2561 object, here called "light": SFE_TSL2561 light; // Global variables: boolean gain; // Gain setting, 0 = X1, 1 = X16; unsigned int ms; // Integration ("shutter") time in milliseconds void setup() { // Initialize the Serial port: Serial.begin(115200); Serial.println(F("TSL2561 example sketch")); // Initialize the SFE_TSL2561 library light.begin(); unsigned char ID; if (light.getID(ID)){ Serial.print(F("Got factory ID: 0x")); Serial.print(ID,HEX); Serial.println(F(", should be 0x5X")); }else{ byte error = light.getError(); printError(error); } // The light sensor has a default integration time of 402ms,and a default gain of low (1X). // If you would like to change either of these, you can do so using the setTiming() command. // If gain = 0, device is set to low gain (1X) // If gain = 1, device is set to high gain (16X) gain = 0; // If time = 0, integration will be 13.7ms // If time = 1, integration will be 101ms // If time = 2, integration will be 402ms // If time = 3, use manual start / stop to perform your own integration unsigned char time = 2; // setTiming() will set the third parameter (ms) to the // requested integration time in ms (this will be useful later): Serial.println(F("Set timing...")); light.setTiming(gain,time,ms); // To start taking measurements, power up the sensor: Serial.println(F("Powerup...")); light.setPowerUp(); } void loop() { //結果を取得する前に、測定と測定の間に待機します(測定が完了したときに割り込みを発行するようにセンサーを構成することもできます) //このスケッチでは、TSL2561の組み込みの統合タイマーを使用します。setTiming()で "time"を3(手動)に設定して、 //以下のコメント文のようにmanualStart()およびmanualStop()を実行することにより、 //独自の手動統合タイミングを実行することもできます。 //ms = 1000; //light.manualStart(); delay(ms); //light.manualStop(); //統合が完了したら、データを取得します。 //デバイスには、可視光用と赤外線用の2つの光センサーがあります。 両方のセンサーがルクス計算に必要です。 //デバイスからデータを取得します。 unsigned int data0, data1; if (light.getData(data0,data1)){ // getData() returned true, communication was successful Serial.print(F("data0: "));Serial.print(data0); Serial.print(F(" data1: "));Serial.println(data1); //luxを計算するには、すべての設定と測定値をgetLux()関数に渡します。 //getLux()関数は、計算が成功した場合は1を返し、センサーの一方または //両方が飽和している(光が多すぎる)場合は0を返します。 //これが発生した場合、統合時間やゲインを削減できます。 //詳細については、以下のフックアップガイドを参照してください。 //https://learn.sparkfun.com/tutorials/tsl2561-luminosity-sensor-hookup-guide/all double lux; // Resulting lux value boolean good; // True if neither sensor is saturated // Perform lux calculation: good = light.getLux(gain,ms,data0,data1,lux); // Print out the results: Serial.print(F(" lux: "));Serial.print(lux); if (good) Serial.println(F(" (good)")); else Serial.println(F(" (BAD)")); }else{ // getData() returned false because of an I2C error, inform the user. byte error = light.getError(); printError(error); } } //I2Cエラーがある場合、この関数で説明を出力します。 void printError(byte error){ Serial.print(F("I2C error: "));Serial.print(error,DEC);Serial.print(F(", ")); switch(error){ case 0: Serial.println(F("success")); break; case 1: Serial.println(F("data too long for transmit buffer")); break; case 2: Serial.println(F("received NACK on address (disconnected?)")); break; case 3: Serial.println(F("received NACK on data")); break; case 4: Serial.println(F("other error")); break; default: Serial.println(F("unknown error")); } }[MAX44009(I2C)の確認コード]
もっと単純な照度センサが有ります。MAX44009です。GY-49としてモジュールが提供されています。
Arduino Library Listにライブラリが有ります。このコードでそのまま使える様です。
https://www.arduinolibraries.info/libraries/max44009-library配線はSTM32MINIShield基板のI2CコネクタにGY-49を接続します。
実行結果はシリアルモニタに照度値がプロットされます。TSL2561の値と比較するのも面白いです。
/* * 2020/01/07 T.Wanibe * MAX44009 照度センサモジュールのBluePillでの動作確認プログラム * 最大131072バイトのフラッシュメモリのうち、スケッチが21004バイト(16%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が2992バイト(14%)を使っていて、ローカル変数で17488バイト使うことができます。 */ #include <Wire.h> #include <MAX44009.h> MAX44009 light; //------------- void setup() { Serial.begin(115200); Wire.begin(); delay(500); if(light.begin()){ Serial.println(F("Could not find a valid MAX44009 sensor, check wiring!")); while(1); } } //------------- void loop() { Serial.print(F("Light (lux):\t")); Serial.println(light.get_lux()); delay(1000); }
[SHT21(I2C)の確認コード]
メーカサイトにライブラリが残っているようですが、githubから落としたほうが、確実のようです。
https://github.com/adafruit/Adafruit_HTU21DF_Library配線はSTM32MINIShield基板のI2CコネクタにGY-21を接続します。
実行結果はシリアルモニタに温度値・湿度値がプロットされます。近くの温度計の値と比較してください。
/* * This is an example for the HTU21D-F Humidity & Temp Sensor * 2019/12/23 T.Wanibe */ #include <Wire.h> #include "Adafruit_HTU21DF.h" Adafruit_HTU21DF htu = Adafruit_HTU21DF(); void setup() { Serial.begin(115200); Serial.println(F("HTU21D-F test")); if (!htu.begin()) { Serial.println(F("Couldn't find sensor!")); while (1); } } void loop() { float temp = htu.readTemperature(); float rel_hum = htu.readHumidity(); Serial.print(F("Temp: ")); Serial.print(temp); Serial.print(F(" ℃\t\t")); Serial.print(F("Humidity: ")); Serial.print(rel_hum); Serial.println(F(" %")); delay(500); }
[実装LEDの確認コード]
タイマ割込を使用して1秒点灯1秒消灯の繰り返しを実施します。
STM32MINIShield基板にはLED1/LED2の2つのLEDが実装出来ます。割当ポートはPB8/PB9です。
- 割込を0.1msecで発生させ、カウントを起動します。
- 3000カウントの時にLED1にEventを発生させます。
- 6000カウントの時にLED2にEventを発生させます。
- カウンタは10000でカウントアップします。
- Eventを受けたLEDは点灯消灯を繰り返します。
/* * STM32 blue pill タイマ割込テスト * 2019/12/25 T.Wanibe * オリジナルソース “タイマー利用サンプル by たま吉 2017/01/05” * このコードはSTM32MINIShield基板のLED1,LED2を点滅させるサンプルです。 * 割込カウンタが起動して3000カウント(0.3秒)後にLED1が点灯、6000カウント(0.6秒)後にLED2が点灯 *10000カウントでクリアされ、3000カウント(0.3秒)後にLED1が消灯、6000カウント(0.6秒)後にLED2が消灯 *します。これを繰り返します。各LEDの点灯時間は1秒、消灯時間は1秒となるはずです。 * 一つの割込タイマで2つの割り込みハンドラを動かしています。 * TIMER_UPDATE_INTERRUPT, //カウンタアップ時に割込 * TIMER_CC1_INTERRUPT, //コンパレータ1合致時に割込 * TIMER_CC2_INTERRUPT, //コンパレータ2合致時に割込 * TIMER_CC3_INTERRUPT, //コンパレータ3合致時に割込 * TIMER_CC4_INTERRUPT, //コンパレータ4合致時に割込 * TIMER_COM_INTERRUPT, //COM割り込み * TIMER_TRG_INTERRUPT, //トリガー割り込み * TIMER_BREAK_INTERRUPT, //中断割り込み * このコードはちゃんと動きました。 * HardwareTimer * https://docs.leaflabs.com/static.leaflabs.com/pub/leaflabs/maple-docs/latest/lang/api/hardwaretimer.html#using-timer-interrupts * 最大131072バイトのフラッシュメモリのうち、スケッチが13780バイト(10%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が2848バイト(13%)を使っていて、ローカル変数で17632バイト使うことができます。 */ #define LED1_PIN PB8 #define LED2_PIN PB9 uint8_t toggle1 = 0; uint8_t toggle2 = 0; //------------ void handler_led1(void) { toggle1 ^= 1; digitalWrite(LED1_PIN, toggle1); } //------------ void handler_led2(void) { toggle2 ^= 1; digitalWrite(LED2_PIN, toggle2); } //------------ void setup() { pinMode(LED1_PIN, OUTPUT); pinMode(LED2_PIN, OUTPUT); Timer1.pause(); // タイマー停止 Timer1.setPrescaleFactor(7200); // システムクロック 72MHzを10kHzに分周 //※分周カウンタが16bitかもしれないので32000を超えないようにして置く Timer1.setOverflow(10000); // カウンタ値10000でカウントアップ 1秒 Timer1.setCompare(TIMER_CC1_INTERRUPT, 3000); // コンパレータ1にて割り込み発生 0.3秒でON Timer1.attachInterrupt(TIMER_CC1_INTERRUPT, handler_led1); Timer1.setCompare(TIMER_CC2_INTERRUPT, 6000); // コンパレータ2にて割り込み発生 0.6秒でON Timer1.attachInterrupt(TIMER_CC2_INTERRUPT, handler_led2); Timer1.setCount(0); //カウンタを0に設定 Timer1.refresh(); // タイマーの更新 Timer1.resume(); // タイマースタート } //------------ void loop() { ; }
[ADCの確認コード_1]
4chの高速取込例を示します。
標準装備ライブラリSTM32ADCをコールしています。接続例
/* * 20200110 T.Wanibe * この例では、ADCライブラリを使用して複数のチャネル/ピンを連続的にサンプリングする方法を示します。 * チャネルの取得は、DMAを循環モードで使用して行われます。 */ #include <STM32ADC.h> STM32ADC myADC(ADC1); #define BOARD_LED PC13 //this is for Maple Mini #define BOARD_TRG PB0 //Channels to be acquired. uint8 pins[] = {PA0,PA1,PA2,PA3}; const int maxSamples = 4; // 4 channels // Array for the ADC data uint16_t dataPoints[maxSamples]; //---------- void setup() { Serial.begin(115200); pinMode(BOARD_LED, OUTPUT); pinMode(BOARD_TRG, INPUT); //startup blink... good idea from Pig-O-Scope digitalWrite(BOARD_LED, HIGH); delay(1000); digitalWrite(BOARD_LED, LOW); delay(1000); //calibrate ADC myADC.calibrate(); // Set up our analog pin(s) for (unsigned int j = 0; j <4; j++) pinMode(pins[j], INPUT_ANALOG); myADC.setSampleRate(ADC_SMPR_1_5); //set the Sample Rate //ADC_SMPR_1_5, < 1.5 ADC cycles //ADC_SMPR_7_5, < 7.5 ADC cycles //ADC_SMPR_13_5, < 13.5 ADC cycles //ADC_SMPR_28_5, < 28.5 ADC cycles //ADC_SMPR_41_5, < 41.5 ADC cycles //ADC_SMPR_55_5, < 55.5 ADC cycles //ADC_SMPR_71_5, < 71.5 ADC cycles //ADC_SMPR_239_5,< 239.5 ADC cycles myADC.setScanMode(); //set the ADC in Scan mode. myADC.setPins(pins, 8); //set how many and which pins to convert. myADC.setContinuous(); //set the ADC in continuous mode. //set the DMA transfer for the ADC. //in this case we want to increment the memory side and run it in circular mode //By doing this, we can read the last value sampled from the channels by reading the dataPoints array myADC.setDMA(dataPoints, 8, (DMA_MINC_MODE | DMA_CIRC_MODE), NULL); //start the conversion. //because the ADC is set as continuous mode and in circular fashion, this can be done //on setup(). myADC.startConversion(); } //---------- void loop(){ //send the latest data acquired when the button is pushed. if(digitalRead(BOARD_TRG) == 1 ) { digitalWrite(BOARD_LED, HIGH); Serial.println(F("begin")); // Take our samples for(unsigned int i = 0; i < maxSamples; i ++) { Serial.print(F("sample[")); Serial.print(i); Serial.print(F("] = ")); Serial.println(dataPoints[i]); } digitalWrite(BOARD_LED, LOW); while(digitalRead(BOARD_TRG) == 1); //stay here. } }//end loop[ADCの確認コード_2]
タイマ割込を使用して4chを10[S/s]で連続集録するプログラムを実装します。
LabVIEWで記述したPC側ソフトからLAN経由で接続してデータ収録開始終了を実現します。PCソフトなのでグラフ化して表示します。
タイマ割込はArduinoのコードのままでは動かない部分があります。
ネットワークプロトコルはTCPソケット通信とします。ソケット番号は50001としています。接続例
結果はLabVIEWパネルに表示します。
ソケット通信は独自プロトコルを決めてから構築する必要があります。また、LabVIEW側からするとデータ受信ですが、一方的なデータ受信にするのであればUDPにしないと垂れ流しには出来ません。エラー56が発生してしまうでしょう。データ受信ブロック毎に何らかの応答をするようなプロトコルを制定しておく必要があります。
/* * STM32MINIShield_AnalogInput * 2019/12/27 T.Wanibe * */ #include <SPI.h> #include <Ethernet3.h> #define SocketPort 50001 #define TelnetPort 23 #define W550io_CS PA4 //PB12 SPI_1 #define W550io_Rst PA8 // #define AI_0_Pin PA0 #define AI_1_Pin PA1 #define AI_2_Pin PA2 #define AI_3_Pin PA3 #define LED1_Pin PB8 #define OKMSG "OK\r\n" #define NGMSG "NG\r\n" // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network. // gateway and subnet are optional: byte mac[6] = {0x00, 0x08, 0xDC, 0x54, 0x4D, 0xD0}; //WiZ550ioMAC byte ip[] = {192,168,0,200}; // byte subnet[] = {255,255,255,0}; byte gateway[] = {192,168,0,1}; int analogValue[4] = {0,0,0,0}; bool alreadyConnected = false; // whether or not the client was connected previously uint8_t toggle1 = 0; char gBuf[20]; bool gOut = false; // telnet defaults to port 23 EthernetServer server(SocketPort); EthernetClient client; size_t size; //------------------ void ADCRead(void) { Serial.print(F("*")); if(!gOut){ analogValue[0] = analogRead(AI_0_Pin); analogValue[1] = analogRead(AI_1_Pin); analogValue[2] = analogRead(AI_2_Pin); analogValue[3] = analogRead(AI_3_Pin); sprintf(gBuf,"%04x%04x%04x%04x\r\n",analogValue[0],analogValue[1],analogValue[2],analogValue[3]); //sprintf(buf,"%04x\r\n",analogValue[0]); Serial.print(gBuf); toggle1 ^= 1; digitalWrite(LED1_Pin, toggle1); gOut = true; } //EnableLAN = true; //Serial.print(F("\n")); } //--------------- void setup() { Serial.begin(115200); Serial.println(F("Start")); // Declare analogInputPin as INPUT_ANALOG: pinMode(AI_0_Pin, INPUT_ANALOG); pinMode(AI_1_Pin, INPUT_ANALOG); pinMode(AI_2_Pin, INPUT_ANALOG); pinMode(AI_3_Pin, INPUT_ANALOG); pinMode(LED1_Pin, OUTPUT); Timer1.pause(); // Timer1.setPrescaleFactor(7200); // 72MHz100uSEC Timer1.setOverflow(10000); //1000mSEC // initialize the ethernet device Ethernet.setCsPin(W550io_CS); // set Pin PA4 for CS Ethernet.setRstPin(W550io_Rst); // set Pin PA8 for RST // pinMode(W550io_Rst, OUTPUT); digitalWrite(W550io_Rst, LOW); delay(10); digitalWrite(W550io_Rst, HIGH); Ethernet.begin(mac, ip); // start listening for clients server.begin(); Serial.print(F("Chat server address:"));Serial.println(Ethernet.localIP()); } //------------------ void loop() { // wait for a new client: client = server.available(); // when the client sends the first byte, say hello: if (client) { if (!alreadyConnected) { // clead out the input buffer: client.flush(); Serial.println(F("We have a new client")); //client.println(F("Hello, client!")); alreadyConnected = true; } if ((size =client.available()) > 0) { // uint8_t* msg = (uint8_t*)malloc(size); delay(1); size = client.read((unsigned char*)msg, size); Serial.print(F("PacketSize = ")); Serial.println(size); switch (msg[0]) { case 'B': Serial.println(F("B recieved")); client.write(OKMSG); Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,ADCRead); Timer1.setCount(0); //0 Timer1.refresh(); Timer1.resume(); // break; case 'E': // Serial.println(F("E recieved")); Timer1.pause(); //Timer1.stop(); client.write(OKMSG); break; case 'R': break; default: client.write(NGMSG); break; } free(msg); } if(gOut){ client.write(gBuf); //client.flush(); gOut = false; } } }
[SRAMの確認コード]
このボードにはSPI接続のシリアルSRAM:23LC1024が実装出来るようにしています。特に実装する必要はないです。
ただ、実装可能なので動作確認だけは出来るようにしておきます。
使用するライブラリは、SpiRam_Extendedを使用します。
https://github.com/dmason1992/SpiRam_Extended
[BME280(I2C)の確認コード]
気圧の計測する場合のセンサとしてBME280は便利です。温度・湿度も同時に計測し、気圧値に対して補正を掛ける事が出来ます。と云う事で、一応検証しておきます。※I2CなのでArduinoのライブラリでも普通に動くはずですが、、、
https://github.com/finitespace/BME280/blob/master/src/BME280I2C.h
/* * 2020/01/21 T.Wanibe * このコードはI2C接続用のBME280Eモジュールのテストコードです。 * STM32MINIShieldで動作確認をしています。 * オリジナルコードは BME280I2C Modes.ino でライブラリは以下から取得しています。 * https://www.arduinolibraries.info/libraries/bme280 * Vin (Voltage In) -> 3.3V * Gnd (Ground) -> Gnd * SCK (Serial Clock) -> PB6 * SDA (Serial Data) -> PB7 * このコードはちゃんと動きますが、プログラムとしては判りづらいかなと思います。 * また、AdaflurtのBME280Eのライブラリはなぜかうまく動きませんでした。 * プログラムコードを見て判ったこと。BME280とBMP280が混在しており、 * BME280だと湿度が計測出来ますが、BMP280だと湿度計測が出来ません。 * モジュールシルクはBME/BMPと書かれていて共通で扱われています。 * chipを見ても判断は難しいと考えます。 * 最大131072バイトのフラッシュメモリのうち、スケッチが28420バイト(21%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4488バイト(21%)を使っていて、ローカル変数で15992バイト使うことができます。 */ #include <BME280I2C.h> #include <Wire.h> // #define SERIAL_BAUD 115200 /* 推奨モード- Bosch BME280I2C環境センサーデータシートに基づきます。 気象監視 : forced mode, 1 sample/minute pressure ×1, temperature ×1, humidity ×1, filter off Current Consumption = 0.16 μA RMS Noise = 3.3 Pa/30 cm, 0.07 %RH Data Output Rate 1/60 Hz 湿度検知 : forced mode, 1 sample/second pressure ×0, temperature ×1, humidity ×1, filter off Current Consumption = 2.9 μA RMS Noise = 0.07 %RH Data Output Rate = 1 Hz 屋内ナビゲーション : normal mode, standby time = 0.5ms pressure ×16, temperature ×2, humidity ×1, filter = x16 Current Consumption = 633 μA RMS Noise = 0.2 Pa/1.7 cm Data Output Rate = 25Hz Filter Bandwidth = 0.53 Hz Response Time (75%) = 0.9 s ゲーミング : normal mode, standby time = 0.5ms pressure ×4, temperature ×1, humidity ×0, filter = x16 Current Consumption = 581 μA RMS Noise = 0.3 Pa/2.5 cm Data Output Rate = 83 Hz Filter Bandwidth = 1.75 Hz Response Time (75%) = 0.3 s */ BME280I2C::Settings settings( BME280::OSR_X1, BME280::OSR_X1, BME280::OSR_X1, BME280::Mode_Forced, BME280::StandbyTime_1000ms, BME280::Filter_Off, BME280::SpiEnable_False, 0x76 // I2C address. I2C specific. ); BME280I2C bme(settings); //------------------- void setup() { Serial.begin(SERIAL_BAUD); while(!Serial) {} // Wait Wire.begin(); while(!bme.begin()){ Serial.println(F("Could not find BME280I2C sensor!")); delay(1000); } Serial.print(F("ID:0x"));Serial.println(bme.chipID(),HEX); // Deprecated. See chipModel(). switch(bme.chipModel()){ case BME280::ChipModel_BME280: Serial.println(F("Found BME280 sensor! Success.")); break; case BME280::ChipModel_BMP280: Serial.println(F("Found BMP280 sensor! No Humidity available.")); break; default: Serial.println(F("Found UNKNOWN sensor! Error!")); } // Change some settings before using. settings.tempOSR = BME280::OSR_X4; bme.setSettings(settings); } //------------------------ void loop() { printBME280Data(&Serial); delay(500); } //----------------------- void printBME280Data(Stream* client){ float temp(NAN), hum(NAN), pres(NAN); BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_Pa); bme.read(pres, temp, hum, tempUnit, presUnit); client->print(F("Temp: ")); client->print(temp); client->print("°"+ String(tempUnit == BME280::TempUnit_Celsius ? 'C' :'F')); client->print(F("\tHumidity: ")); client->print(hum); client->print(F("% RH")); client->print(F("\tPressure: ")); client->print(pres); client->println(F(" Pa")); delay(10); }
[DS3231(I2C)の確認コード]
STM32F103にはRTC機能が搭載されているのですが、精度が悪いと思っています。比較的安定しているDS3231をBluePillで使えるかどうかの確認をしておきます。
文字列を扱うため、安易にライブラリがそのまま使えるとは考えにくいです。
adafruit/RTClibを確認したところ、動きました。
https://github.com/adafruit/RTClib
※DS3231を搭載したモジュールはいくつかあります。今回はEEPROMが搭載されていない『DS3231ForPi』で動作確認しました。
/* * 2020/1/8 T.Wanibe * I2CおよびWire libを介して接続されたDS3231 RTCを使用した日付および時刻関数 * BluePillにはRTC機能が搭載されているのですが、精度的にはDS3231の方がいいように思います。 * そこで外部RTCモジュールを使って動作確認しました。 */ #include <Wire.h> #include "RTClib.h" RTC_DS3231 rtc; char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; //------- void setup () { Serial.begin(115200); delay(3000); // コンソールが開くのを待つ if (! rtc.begin()) { Serial.println(F("Couldn't find RTC")); while (1); } if (rtc.lostPower()) { Serial.println(F("RTC lost power, lets set the time!")); // 次の行は、このスケッチがコンパイルされた日付と時刻にRTCを設定します rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // この行は、明示的な日付と時刻でRTCを設定します。 // たとえば、2020年1月21日午前3時に設定すると、次のようになります。 // rtc.adjust(DateTime(2020, 1, 21, 3, 0, 0)); } } void loop () { DateTime now = rtc.now(); Serial.print(now.year(), DEC); Serial.print(F("/")); Serial.print(now.month(), DEC); Serial.print(F("/")); Serial.print(now.day(), DEC); Serial.print(F(" (")); Serial.print(daysOfTheWeek[now.dayOfTheWeek()]); Serial.print(F(") ")); Serial.print(now.hour(), DEC); Serial.print(F(":")); Serial.print(now.minute(), DEC); Serial.print(F(":")); Serial.println(now.second(), DEC); Serial.print(F(" since midnight 1/1/1970 = ")); Serial.print(now.unixtime()); Serial.print(F("秒 = ")); Serial.print(now.unixtime() / 86400L); //1日は24*3600=86400秒 Serial.println(F("日")); // 7日30秒先の日付を計算します DateTime future (now + TimeSpan(7,12,30,6)); Serial.print(F(" now + 7日 + 30秒: ")); Serial.print(future.year(), DEC); Serial.print(F("/")); Serial.print(future.month(), DEC); Serial.print(F("/")); Serial.print(future.day(), DEC); Serial.print(F("\t")); Serial.print(future.hour(), DEC); Serial.print(F(":")); Serial.print(future.minute(), DEC); Serial.print(F(":")); Serial.println(future.second(), DEC); Serial.println(); delay(3000); //3秒間隔で更新 }
[回転速度の計測確認コード]
風向や風速の計測のためにパルス入力を元に回転速度を計測する術を確認しておきます。
風速は風車の一定時間の回転数を計測することで求める事が出来るかと思います。或いは、音波が空中を伝搬するときにその速度が風速によって変化することを利用して超音波の伝搬時間を測定する方法もあるかと思います。
風向は吹き流しは一般なのでしょうが、電気的な値として取り出せないです。最大風速が得られる角度をサーボから電圧値である方法だったり、ポテンショメータで回転角を求めるのも可能です。超音波センサを3つ正三角形に配置して時分割送受信で風向判断することも出来そうです。ここでは回転角はさておいて回転速度の計測を確認するコードを置いておきます。
外部に回転数が変化可能なデバイスがあればいいのですが、テストなので、BluePill自身が特定のパルスを出力し、それを信号入力してカウントアップ、一定時間毎にカウンタ値を表示するという物です。
/* * 2020/1/14 T.Wanibe * トリガpinを用意し、信号変化毎にカウントアップするカウンタの検証をする。 * 一定時間毎にカウント計測すれば周波数カウンタとして使用できます。 * 表示にはTM1638を使った方がリーズナブルかと考えました。 * 検証のためBluePillのPWM出力を利用し、その出力を外部配線(P16->PB0)で * 信号入力しています。 * ちょっと精度に問題があります。パルス発生側の問題なのか、基準時間の問題なのか要調査 * 最大131072バイトのフラッシュメモリのうち、スケッチが19628バイト(14%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4272バイト(20%)を使っていて、ローカル変数で16208バイト使うことができます。 */ #include <TM1638.h> #include <libmaple/timer.h> #define TIMER_DIV 72 #define dataPin PB4 #define clockPin PB3 #define strobePin PA15 #define LED1_Pin PB8 #define LED2_Pin PB9 #define CI_Pin PB0 //入力pin #define CR_Pin PB1 #define PWM1_PIN PA6 //PWMpinは限定されます #define PWM2_PIN PA7 #define pFreq1 10000 //出力パルス周波数(0 〜 65535) #define pDuty1 2047 //デューティ比 (0〜 4095:4095で100%) #define SerialValid false volatile bool CntValid = false; volatile long gCountValue = 0; volatile long gViewValue = 0; // define a module on data pin PB4, clock pin PB3 and strobe pin PA15 TM1638 LEDandKEY(dataPin, clockPin, strobePin); //------------------信号変化割込でカウンタ値をインクリメントする。 void CountUp(void) { if(SerialValid) Serial.print(F("*")); gCountValue++; } //--------------- void ResetCount(void){ if(SerialValid) Serial.print(F("R")); gCountValue = 0; } //--------------- void ViewValue(void){ gViewValue = gCountValue; CntValid = true; ResetCount(); } uint8_t pwm1_out(uint8_t pin, uint16_t freq, uint16_t duty) { /* * このコードはたま吉さんによる周波数を指定したPWM実現関数です。感謝 * https://nuneno.cocolog-nifty.com/blog/2017/04/index.html * PWM出力 * 引数 * pin PWM出力ピン * freq 出力パルス周波数(0 〜 65535) * dcycle デューティ比 (0〜 4095:4095で100%) * 戻り値 * 0 正常 * 1 異常(PWMを利用出来ないピンを利用した) */ uint32_t dc; timer_dev *dev = PIN_MAP[pin].timer_device; // ピン対応のタイマーデバイスの取得 uint8_t cc_channel = PIN_MAP[pin].timer_channel; // ピン対応のタイマーチャンネルの取得 if (! (dev && cc_channel) ) return 1; uint32_t f =1000000/(uint32_t)freq; // 周波数をカウント値に換算 dc = f*(uint32_t)duty/4095; timer_set_prescaler(dev, TIMER_DIV); // システムクロックを1MHzに分周 timer_set_reload(dev, f); // リセットカウント値を設定 timer_set_mode(dev, cc_channel,TIMER_PWM); timer_set_compare(dev,cc_channel,dc); // 比較レジスタの初期値指定(デューティ比 0) return 0; } //--------------- void setup() { if(SerialValid) Serial.begin(115200); if(SerialValid) Serial.println(F("Start")); //カウンタ値0を表示 LEDandKEY.setDisplayToSignedDecNumber(gCountValue,true); //PINMODE設定 pinMode(LED1_Pin, OUTPUT); //LED出力設定 pinMode(LED2_Pin, OUTPUT); pinMode(CI_Pin, INPUT_PULLUP); //カウンタ入力Pin設定 pinMode(CR_Pin, INPUT_PULLUP); pinMode(PWM1_PIN, PWM); //パルス出力1 Timer1.pause(); //念のためタイマ停止 Timer1.setPrescaleFactor(7200); //システムクロック 72MHzを分周して100uSECにセット Timer1.setOverflow(10000); //オーバーフローを1SECに設定 Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,ViewValue); attachInterrupt(CI_Pin,CountUp,RISING); //RISING,FALLING,CHANGE attachInterrupt(CR_Pin,ResetCount,FALLING); Timer1.setCount(0); //カウンタを0に設定 Timer1.refresh(); Timer1.resume(); pwmWrite(PWM1_PIN, 0); //設定直後に出力を止める pwm1_out(PWM1_PIN,pFreq1,pDuty1); //duty50% 32767/65535 } //------------------ int LoopCount = 0; void loop() { if(CntValid){ //表示処理は割込で行うとパルスの取りこぼしが発生しやすい //引数のtrueは不要な0も表示 LEDandKEY.setDisplayToSignedDecNumber(gViewValue,true); if(SerialValid) Serial.println(gViewValue); //ledでタイミング表示していますが、この処理で遅れが出ます。 if(++LoopCount % 2){ digitalWrite(LED1_Pin,HIGH); digitalWrite(LED2_Pin,LOW); }else{ digitalWrite(LED1_Pin,LOW); digitalWrite(LED2_Pin,HIGH); } gViewValue = 0; CntValid = false; } }パルスカウンタの精度は可成り高いと考えます。一方PWMで作成した検証用パルスは精度が悪いです。10KHzを設定しても9.85KHz程度になります。そこで、外部にFGを用意して入力してみました。そのカウンタの精度は十分と考えます。
FGとしてAD2と用意して10KHzを出力し、カウンタ入力してみたのですが、『10000』と表示されました。
※AD2で矩形波Amp1Voffset1Vにすることで0-2Vの矩形波を出力出来ます。
※AD2にて1MHzの出力を掛けたところ、ほぼ『1000000』と表示されました。ところが、2MHzとしても『1000000』です。このことから、#define TIMER_DIV 72 が影響しているのかもしれません。
[水量/流水速度の計測確認コード]
気象観測の要素には雨量があります。一応方法論を元に検証プログラムを検討します。
雨量計測はWikipediaによると、貯水型雨量計、転倒ます型雨量計が有るそうです。気象業務法の縛りがありようです。
レーダーによる雨量計測方法もあるようですが、こちらは規模的な問題があります。
簡易的な雨量計測を考えると分解能0.5mmか0.2mmかで手法を検討した方が良さそうです。
MISOL社が提供するWH-SP-RGという部品が\2500程度で提供されています。出力信号についての詳細は判っていませんが、0.3mm毎にパルスカウントされるということは判っています。
日本では24時間雨量で922.5mmが最大との情報があり、そうすると24×60/(922.5/0.3)≒5.2[min/回]となり、この計数はすごく間隔が空くことが想像できます。大雨の場合の情報として10分間雨量というのがあり、最大観測値が50mmとなっています。※気象庁情報 10/(50/0.3)≒0.67[min/回]となり、この場合でもカウントインターバルが40秒もあります。となると1分間のカウント値を過去1日分積算できるよう1440ビンを用意して、1分ごとに指定されたビンにカウント値を入力して、過去1440個のビンを合計して雨量表示すれば良さそうです。
※Byte bin[1440] で1440Byte確保する必要があるわけで、なんとか確保出来るサイズです。
WH-SP-RG
/* * 2020/1/29 T.Wanibe * 雨量の計数方法を検討しました。 * 1分分解能24時間分bin=1440を用意します。 * 1分ごとに割込を掛けて計数値を指定されたbinに格納します。 * そして1440個のbinをsumし、表示します。 */ #include <TM1638.h> #define InPin PB10 #define LED1_Pin PB8 #define LED2_Pin PB9 #define TIMER_DIV 72 #define dataPin PB4 #define clockPin PB3 #define strobePin PA15 byte gCountValue = 0; int gUryou = 0; int BinNum = 1440; byte bin[1440]; int binCount = 0; bool SerialValid = true; int minCount = 1; TM1638 LEDandKEY(dataPin, clockPin, strobePin); //------------------信号変化割込でカウンタ値をインクリメントする。 void CountUp(void) { if(SerialValid) Serial.print(F("*")); gCountValue++; } //--------------- void ViewValue(void){ if(minCount++ % 60){ }else{ if(SerialValid) Serial.print(F("\nU")); bin[binCount] = gCountValue; binCount = (binCount + 1) % BinNum; gCountValue = 0; gUryou = 0; for(int i = 0; i<BinNum ;i++) gUryou += bin[i]; LEDandKEY.setDisplayToSignedDecNumber(gUryou,0,false); if(SerialValid) Serial.println(gUryou); minCount = 1; } } //-------------- void setup() { Serial.begin(115200); //シリアル通信を開ます。 pinMode(InPin,INPUT_PULLUP); pinMode(LED1_Pin, OUTPUT); //LED出力設定 pinMode(LED2_Pin, OUTPUT); for(int i = 0; i<BinNum ;i++) bin[i] = 0; LEDandKEY.setDisplayToSignedDecNumber(0,0,false); Timer1.pause(); //念のためタイマ停止 Timer1.setPrescaleFactor(7200); //システムクロック 72MHzを分周して100uSECにセット U16 Timer1.setOverflow(10000); //オーバーフローをSECに設定 U16 //Timer1.setPeriod(60000000L); //オーバーフローを60SECに設定 U32 Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,ViewValue); //60秒にセットしたいがうまくゆかないので1秒毎にカウントアップ attachInterrupt(InPin,CountUp,FALLING); //入力PINの割込設定 Timer1.setCount(0); //カウンタを0に設定 Timer1.refresh(); Timer1.resume(); } //-------------- void loop() { ; }[NTPを利用した時計]
NICを利用した確認プログラムも幾つか用意します。
まずはTM1638と接続し、起動するとインターネットにアクセスしてネットタイムプロトコル(ntp)にて時刻取得をし、時計をスタート。1日毎に時計時刻合わせをするというサンプルスケッチを検討しました。
DHCPでルータからIPアドレスを取得するサンプルでもあります。MACアドレスが重要です。
/* * 20200127 T.Wanibe * このコードはEthernet3のExample“Udp NTP Client”をSTM32MINIShieldで動く様にレタッチしました。. * BluePillにはRTCが搭載されていますが、精度が悪くバックアップ電源もありません。※接続は可能です * そこで起動時にNTPで時刻所得して登録し、LED&KEYに時刻表示します。 * 以後毎秒時刻更新します。 * 1時間毎にNTPにアクセスして時刻調整します。 * Michael Margolis氏が2010年9月4日に作成したモノをレタッチしています。 * 最大131072バイトのフラッシュメモリのうち、スケッチが45456バイト(34%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4736バイト(23%)を使っていて、ローカル変数で15744バイト使うことができます。 */ #include <SPI.h> #include <Ethernet3.h> #include <EthernetUdp3.h> #include <TM1638.h> #include <RTClock.h> //PIN #define W550io_Rst PA8 //NIC_Reset #define SPI1_NSS_PIN PA4 //NIC_ChipSelec #define UpdateInterval 86400 //更新間隔 #define dataPin PB4 #define clockPin PB3 #define strobePin PA15 #define BUILTIN_LED PC13 //コントローラのMACアドレスを以下に入力してください。 //新しいイーサネットシールドには、シールドのステッカーにMACアドレスが印刷されています byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD1}; //Wiznet byte ip[] = {192, 168, 0, 199}; byte dns_server[] = {192, 168, 0, 1}; byte gateway[] = {0, 0, 0, 0}; byte subnet[] = {255, 255, 255, 0}; // unsigned int localPort = 8888; //UDPパケットをリッスンするローカルポート char timeServer[] = "time.nist.gov"; //time.nist.gov NTPサーバー const int NTP_PACKET_SIZE = 48; //NTPタイムスタンプはメッセージの最初の48バイトにあります byte packetBuffer[ NTP_PACKET_SIZE]; //着信および発信パケットを保持するバッファ // char STRBUF[32]; int timezone = 9; // change to your timezone // EthernetUDP Udp; //UDPを介してパケットを送受信できるようにするUDPインスタンス TM1638 LedAndKey(dataPin, clockPin, strobePin); //データピンPB4、クロックピンPB3、およびストローブピンPA15でモジュールを定義する RTClock rtclock (RTCSEL_LSE); //RTC初期化 tm_t tmm; //-------指定されたアドレスのタイムサーバーにNTP要求を送信します unsigned long sendNTPpacket(char* address){ memset(packetBuffer, 0, NTP_PACKET_SIZE); //バッファ内のすべてのバイトを0に設定します //NTP要求を形成するために必要な値を初期化する packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; //階層、またはクロックのタイプ packetBuffer[2] = 6; //ポーリング間隔 packetBuffer[3] = 0xEC; //ピアクロックの精度 //ルート遅延およびルート分散用のゼロの8バイト packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; //すべてのNTPフィールドに値が与えられました。 //タイムスタンプを要求するパケットを送信できます: Udp.beginPacket(address, 123); //NTPリクエストはポート123へ Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } //---------------- bool checkNTP(){ sendNTPpacket(timeServer); //NTPパケットをタイムサーバーに送信する delay(2000); //返信が利用可能かどうかを確認するのを待ちます if ( Udp.parsePacket() ) { //パケットを受信し、そこからデータを読み取ります Udp.read(packetBuffer, NTP_PACKET_SIZE); //パケットをバッファに読み込みます //タイムスタンプは、受信したパケットのバイト40から始まり、4バイト、つまり2ワードの長さです。 まず、2つの単語を抽出します。 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); //4バイト(2ワード)を長整数に結合します //これはNTP時間(1900年1月1日からの秒数)です。 unsigned long secsSince1900 = highWord << 16 | lowWord; Serial.print(F("Seconds since Jan 1 1900 = " )); Serial.println(secsSince1900); //NTP時間をUTC時刻に変換します: Serial.print("Unix time = "); //Unix時間は1970年1月1日に始まります。数秒で、2208988800になります。 const unsigned long seventyYears = 2208988800UL; //引き算します。 unsigned long epoch = secsSince1900 - seventyYears; // print Unix time: Serial.println(epoch); //時、分、秒を印刷します。JST(+9) Serial.print(F("The JST time is ")); //UTCはグリニッジ子午線(GMT)の時刻です epoch = epoch + (9 * 3600); rtclock.setTime(time_t(epoch)); //JSTを内部時計にセット Serial.print((epoch % 86400L) / 3600); //時間を印刷します(86400は1日あたりの秒数に相当) Serial.print(':'); int min = (epoch % 3600) / 60; if ( min < 10 ) { Serial.print('0'); //各時間の最初の10分では、先頭に「0」が必要です。 } Serial.print(min); //分を印刷する(3600は1分あたりの秒数に等しい) Serial.print(':'); int sec = epoch % 60; if ( sec < 10 ) { Serial.print('0'); //各分の最初の10秒で、先頭に「0」が必要になります } Serial.println(sec); //秒を印刷 return true; }else{ return false; } } //---------------- void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); //シリアル通信を開ます。 pinMode(BUILTIN_LED,OUTPUT); //Ethernet3API Ethernet.setCsPin(SPI1_NSS_PIN); Ethernet.setRstPin(W550io_Rst); // pinMode(W550io_Rst, OUTPUT); digitalWrite(W550io_Rst, LOW); delay(10); digitalWrite(W550io_Rst, HIGH); Serial.print(F("NIC_Reset\n")); //イーサネットとUDPを開始します if (!Ethernet.begin(mac)) { //DHCPの場合、固定IPでもOK //gatewayを意識した場合、Ethernet.begin(mac, ip, subnet, gateway); Serial.println(F("Failed to configure Ethernet using DHCP")); //続ける意味がないので、永遠に何もしません: for (;;); } Udp.begin(localPort); Serial.print(F("server is at "));Serial.println(Ethernet.localIP()); checkNTP(); rtclock.getTime(tmm); sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second); LedAndKey.setDisplayToString("000000",0,2); } //---------------- int LoopCount = 0; void loop() { if(!(LoopCount % 3600)){ //1時間に一回 checkNTP(); } rtclock.getTime(tmm); sprintf(STRBUF,"%02u%02u%02u",tmm.hour,tmm.minute,tmm.second); LedAndKey.setDisplayToString(STRBUF,0,2); if(!(LoopCount++ % 60)) Serial.println(F(".")); else Serial.print(F(".")); digitalWrite(BUILTIN_LED,LoopCount % 2); delay(1000); //1秒待ってから再度時間を尋ねる }
[DAC機能を追加する(MCP4725)]
BluePillにはアナログ出力がありません。PWM出力はアナログ出力とは違います。そこでDACモジュールを検討しました。
流通しているのはマイクロチップ社のMCP4725を搭載したモジュールかと思います。SparkFunもAdafrultにも商品提供されています。GYモジュールが見つかりません。pin配置に注意が必要です。
それでテストコードを検討しました。そこで判ったのですが、Adafrultのライブラリでは動きません。Arduinoに特化したパラメータをライブラリ内部で使ってしまっているようです。SparkFun用に作られたライブラリは大丈夫でした。
https://github.com/enjoyneering/MCP4725このチップ自体出力インピーダンス1Ωとのことですが、そんなに重い負荷は電源供給出来ないです。資料にも『100μFと0.1μFのバイパスコンデンサをVddに追加』してと有るのですが、このモジュールには用意されていません。
BluePillからは基本3.3Vを供給しますので、DAC_REF_VOLTAGE=3.3Vの設定は必要です。bit値0x0FFF =3.3V になります。ライブラリにはパタン出力のサンプルスケッチも用意されています。ただ、パタン出力したいのであればSCL=400kの変更は必要だと思います。
固定値を出力するならEEPROMに書き込んでPowerOffにするのがいいのかと思います。
/* * 2020/2/6 T.Wanibe * このスケッチはDACモジュールMCP4725のテストコードです。BluePillで動作することを確認しています。 * ※AdafruiltのライブラリがBluePillでうまく動作しません。こちらのコードが代替となります。 * Adafruiltのライブラリにあった三角波のデモプログラムを作ってみました。 * 最大131072バイトのフラッシュメモリのうち、スケッチが23740バイト(18%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が3248バイト(15%)を使っていて、ローカル変数で17232バイト使うことができます。 */ #include <Wire.h> #include <MCP4725.h> // #define DAC_REF_VOLTAGE 3.3 //dac supply-reference voltage #define I2C_BUS_SPEED 400000 //i2c bus speed, 100 000Hz or 400 000Hz // uint16_t value = 0; float voltage = 0; // MCP4725 dac(MCP4725A0_ADDR_A00, DAC_REF_VOLTAGE); //---------------Main setup void setup() { Serial.begin(115200); delay(1000); while (dac.begin() != true){ Serial.println(F("MCP4725 is not connected")); //(F())文字列をフラッシュに保存し、動的メモリを解放します delay(5000); } Serial.println(F("MCP4725 is OK")); Wire.setClock(I2C_BUS_SPEED); //i2c bus speed 100kです。パタン出力したりするときは400K } //----------------Main loop unsigned long LoopCount = 0; void loop() { // 0..4095 の値をレジスタに書き込んで電源を落とします。 if(LoopCount++ % 60){ Serial.print(F("*")); }else{ Serial.println(F("*")); } // for(int i = 0; i<4096; 4*(i++)){ if (dac.setValue(i, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF) != true) Serial.println(F("Collision on i2c bus")); } // for(int i = 4095; i>-1; 4*(i--)){ if (dac.setValue(i, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF) != true) Serial.println(F("Collision on i2c bus")); } }
[SH1106の確認コード(表示器としてのOLCD128×64を使ってみる]
LCR-T4(M-Tester)ってご存じですか? 安価なマルチ計測器で、自分は愛用しています。
このガシェットはAVR328Pが搭載されています。Arduinoで書かれているのかどうかは判りませんが、Arduinoで実現出来るプロジェクトなのでしょう。
このLCR-T4で使用されているLCDディスプレイですが128×64のドットマトリックスでバックライト付きです。このディスプレイを入手したかったのですがSPI/I2Cにまとまった物が見つけられず、OLCD128×64に辿り着きました。
これはこれで使用例を作っておくことは意味がある思いました。使用ライブラリは玉吉版Adafruit_SH1106_STM32です。
https://github.com/Tamakichi/Adafruit_SH1106_STM32このスケッチはライブラリのサンプルスケッチをpinアサインだけ変更しました。
MOSI PA15
SCK PA13
OLED_CS PB12
OLED_DC PA9
OLED_RESET PA10
ちゃんと動くことは確認出来ますが面白みに欠けます。やっぱりスペクトルアナライザのような表示がしてみたいですね。検討します。
/* * 20200316 T.Wanibe OLCD128x64 TEST * * ********************************************************************* * This is an example for our Monochrome OLEDs based on SH1106 drivers * * Pick one up today in the adafruit shop! * ------> https://www.adafruit.com/category/63_98 * * This example is for a 128x64 size display using SPI to communicate * 4 or 5 pins are required to interface * * Adafruit invests time and resources providing this open source code, * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * * Written by Limor Fried/Ladyada for Adafruit Industries. * BSD license, check license.txt for more information * All text above, and the splash screen must be included in any redistribution * ********************************************************************* * 最大131072バイトのフラッシュメモリのうち、スケッチが35748バイト(27%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が5568バイト(27%)を使っていて、ローカル変数で14912バイト使うことができます。 */ #include <Adafruit_SH1106_STM32.h> #define SPI_PORT 2 // 1:SPI1, 2:SPI2 // hardware SPI1 (the default case): SDA(MOSI)=PA7, SCK=PA5 // SPI2 : SDA(MOSI)=PA15, SCK=PA13 #define OLED_DC PA9 #define OLED_CS PB12 #define OLED_RESET PA10 Adafruit_SH1106 olcd(OLED_DC, OLED_RESET, OLED_CS, SPI_PORT); #define NUMFLAKES 10 #define XPOS 0 #define YPOS 1 #define DELTAY 2 #define LOGO16_GLCD_HEIGHT 16 #define LOGO16_GLCD_WIDTH 16 static const unsigned char PROGMEM logo16_glcd_bmp[] = { B00000000, B11000000, B00000001, B11000000, B00000001, B11000000, B00000011, B11100000, B11110011, B11100000, B11111110, B11111000, B01111110, B11111111, B00110011, B10011111, B00011111, B11111100, B00001101, B01110000, B00011011, B10100000, B00111111, B11100000, B00111111, B11110000, B01111100, B11110000, B01110000, B01110000, B00000000, B00110000 }; #if (SH1106_LCDHEIGHT != 64) #error(F("Height incorrect, please fix Adafruit_SH1106.h!")); #endif //-------------------------------- void testdrawbitmap(const uint8_t *bitmap, uint8_t w, uint8_t h) { uint8_t icons[NUMFLAKES][3]; // initialize for (uint8_t f=0; f< NUMFLAKES; f++) { icons[f][XPOS] = random(olcd.width()); icons[f][YPOS] = 0; icons[f][DELTAY] = random(5) + 1; Serial.print(F("x: ")); Serial.print(icons[f][XPOS], DEC); Serial.print(F(" y: ")); Serial.print(icons[f][YPOS], DEC); Serial.print(F(" dy: ")); Serial.println(icons[f][DELTAY], DEC); } while (1) { // draw each icon for (uint8_t f=0; f< NUMFLAKES; f++) { olcd.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo16_glcd_bmp, w, h, WHITE); } olcd.display(); delay(200); // then erase it + move it for (uint8_t f=0; f< NUMFLAKES; f++) { olcd.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo16_glcd_bmp, w, h, BLACK); // move it icons[f][YPOS] += icons[f][DELTAY]; // if its gone, reinit if (icons[f][YPOS] > olcd.height()) { icons[f][XPOS] = random(olcd.width()); icons[f][YPOS] = 0; icons[f][DELTAY] = random(5) + 1; } } } } //-------------------------------- void testdrawchar(void) { olcd.setTextSize(1); olcd.setTextColor(WHITE); olcd.setCursor(0,0); for (uint8_t i=0; i < 168; i++) { if (i == '\n') continue; olcd.write(i); if ((i > 0) && (i % 21 == 0)) olcd.println(); } olcd.display(); } //-------------------------------- void testdrawcircle(void) { for (int16_t i=0; i<olcd.height(); i+=2) { olcd.drawCircle(olcd.width()/2, olcd.height()/2, i, WHITE); olcd.display(); } } //-------------------------------- void testfillrect(void) { uint8_t color = 1; for (int16_t i=0; i<olcd.height()/2; i+=3) { // alternate colors olcd.fillRect(i, i, olcd.width()-i*2, olcd.height()-i*2, color%2); olcd.display(); color++; } } //-------------------------------- void testdrawtriangle(void) { for (int16_t i=0; i<min(olcd.width(),olcd.height())/2; i+=5) { olcd.drawTriangle(olcd.width()/2, olcd.height()/2-i, olcd.width()/2-i, olcd.height()/2+i, olcd.width()/2+i, olcd.height()/2+i, WHITE); olcd.display(); } } //-------------------------------- void testfilltriangle(void) { uint8_t color = WHITE; for (int16_t i=min(olcd.width(),olcd.height())/2; i>0; i-=5) { olcd.fillTriangle(olcd.width()/2, olcd.height()/2-i, olcd.width()/2-i, olcd.height()/2+i, olcd.width()/2+i, olcd.height()/2+i, WHITE); if (color == WHITE) color = BLACK; else color = WHITE; olcd.display(); } } //-------------------------------- void testdrawroundrect(void) { for (int16_t i=0; i<olcd.height()/2-2; i+=2) { olcd.drawRoundRect(i, i, olcd.width()-2*i, olcd.height()-2*i, olcd.height()/4, WHITE); olcd.display(); } } //-------------------------------- void testfillroundrect(void) { uint8_t color = WHITE; for (int16_t i=0; i<olcd.height()/2-2; i+=2) { olcd.fillRoundRect(i, i, olcd.width()-2*i, olcd.height()-2*i, olcd.height()/4, color); if (color == WHITE) color = BLACK; else color = WHITE; olcd.display(); } } //-------------------------------- void testdrawrect(void) { for (int16_t i=0; i<olcd.height()/2; i+=2) { olcd.drawRect(i, i, olcd.width()-2*i, olcd.height()-2*i, WHITE); olcd.display(); } } //-------------------------------- void testdrawline() { for (int16_t i=0; i<olcd.width(); i+=4) { olcd.drawLine(0, 0, i, olcd.height()-1, WHITE); olcd.display(); } for (int16_t i=0; i<olcd.height(); i+=4) { olcd.drawLine(0, 0, olcd.width()-1, i, WHITE); olcd.display(); } delay(250); olcd.clearDisplay(); for (int16_t i=0; i<olcd.width(); i+=4) { olcd.drawLine(0, olcd.height()-1, i, 0, WHITE); olcd.display(); } for (int16_t i=olcd.height()-1; i>=0; i-=4) { olcd.drawLine(0, olcd.height()-1, olcd.width()-1, i, WHITE); olcd.display(); } delay(250); olcd.clearDisplay(); for (int16_t i=olcd.width()-1; i>=0; i-=4) { olcd.drawLine(olcd.width()-1, olcd.height()-1, i, 0, WHITE); olcd.display(); } for (int16_t i=olcd.height()-1; i>=0; i-=4) { olcd.drawLine(olcd.width()-1, olcd.height()-1, 0, i, WHITE); olcd.display(); } delay(250); olcd.clearDisplay(); for (int16_t i=0; i<olcd.height(); i+=4) { olcd.drawLine(olcd.width()-1, 0, 0, i, WHITE); olcd.display(); } for (int16_t i=0; i<olcd.width(); i+=4) { olcd.drawLine(olcd.width()-1, 0, i, olcd.height()-1, WHITE); olcd.display(); } delay(250); } //-------------------------------- void setup(){ Serial.begin(115200); delay(1000); // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) olcd.begin(SH1106_SWITCHCAPVCC); // init done // Show image buffer on the display hardware. // Since the buffer is intialized with an Adafruit splashscreen // internally, this will display the splashscreen. olcd.display(); delay(2000); // Clear the buffer. olcd.clearDisplay(); // draw a single pixel olcd.drawPixel(10, 10, WHITE); // Show the display buffer on the hardware. // NOTE: You _must_ call display after making any drawing commands // to make them visible on the display hardware! olcd.display(); delay(2000); olcd.clearDisplay(); // draw many lines testdrawline(); olcd.display(); delay(2000); olcd.clearDisplay(); // draw rectangles testdrawrect(); olcd.display(); delay(2000); olcd.clearDisplay(); // draw multiple rectangles testfillrect(); olcd.display(); delay(2000); olcd.clearDisplay(); // draw mulitple circles testdrawcircle(); olcd.display(); delay(2000); olcd.clearDisplay(); // draw a white circle, 10 pixel radius olcd.fillCircle(olcd.width()/2, olcd.height()/2, 10, WHITE); olcd.display(); delay(2000); olcd.clearDisplay(); testdrawroundrect(); delay(2000); olcd.clearDisplay(); testfillroundrect(); delay(2000); olcd.clearDisplay(); testdrawtriangle(); delay(2000); olcd.clearDisplay(); testfilltriangle(); delay(2000); olcd.clearDisplay(); // draw the first ~12 characters in the font testdrawchar(); olcd.display(); delay(2000); olcd.clearDisplay(); // text display tests olcd.setTextSize(1); olcd.setTextColor(WHITE); olcd.setCursor(0,0); olcd.println("Hello, world!"); olcd.setTextColor(BLACK, WHITE); // 'inverted' text olcd.println(3.141592); olcd.setTextSize(2); olcd.setTextColor(WHITE); olcd.print("0x"); olcd.println(0xDEADBEEF, HEX); olcd.display(); delay(2000); // miniature bitmap display olcd.clearDisplay(); olcd.drawBitmap(30, 16, logo16_glcd_bmp, 16, 16, 1); olcd.display(); // invert the display olcd.invertDisplay(true); delay(1000); olcd.invertDisplay(false); delay(1000); // draw a bitmap icon and 'animate' movement testdrawbitmap(logo16_glcd_bmp, LOGO16_GLCD_HEIGHT, LOGO16_GLCD_WIDTH); } //-------------------------------- void loop() { ; }検討しました。ArduinoForumでVUメータプロジェクトを。結構スレッドも伸びでいました。MP3Player例に追加してみます。
[TM1637の確認コード(GPSと7セグLEDによる時計)]
時計表示専用とも云える表示モジュールの検証コードです。
時計表示のためにTitan Micro社のTM1637というchipが搭載されたモジュールが流通しています。入手は容易だと思います。
このモジュールの検証をするコードを検討しました。GPSモジュールと組み合わせ表示させます。
GPSのストリームは1秒毎なので秒表示するとなると内部時計を併用する等の対策が必要ですが、時分だけなら別解釈も可能です。このスケッチは提供されているライブラリのサンプルスケッチを修正したため表示タイミングをタイマ割込しています。
使用ライブラリは以下の通りです。GPS文字列は自分で切り出しています。
https://github.com/tehniq3/TM1637-displayLED&KEY用のコネクタに4桁モジュールを割り当てています。
/* * 2020/03/28 T.Wanibe TM1637LED表示確認 * TM-1638では無くTM1637というコントローラの検証です。4桁の7セグLEDでコロン付きです。 * MM:SS 表示で秒毎にコロン点滅するようなものを検討します。折角ですのでGPSと絡めます。 * 参照したスケッチは割込を使用して表示をしています。これとメインループでGPSの読込を * させて構築したいと考えます。$GxRMCを解析して日時情報を求めます。 * RMC(時刻、位置、日付)$__RMC,hhmmss.ss,a, ddmm.mm ,a, ddmm.mm ,a,x.x,x.x,mmddyy,a,a,*hh<CR><LF> * $ センテンスの開始 * __RMC アドレスフィールド( 5 文字)、 __ は「 GN 」「 GP 」など * hhmmss.ss 時刻( UTC ) hh 時 mm 分 ss.ss 秒 * a ステータス * A=data valid * V=navigation receiver warming * ddmm.mm 経度 dd 度 mm.mm 分 * a 北緯( N )または南緯( S ) * ddmm.mm 経度 dd 度 mm.mm 分 * a 東経( E )または西経( W ) * x.x 対地速度( knot ) * x.x 対地コース(進路)(度) * mmddyy 日付 yy 年 mm 月 dd 日 * x.x 偏差 * a 偏差の方向 W または E * a モード * A :単独 * D :ディファレンシャルモード * E : Estimated (dead reckoning), * M :マニュアル入力 * S :シミュレーター入力 * N :無効 * *hh チェックサム( $ から * の間) * <CR><LF> センテンスの終了 * 参照したスケッチは以下の通りですが、殆どレタッチしています。 * Demo for 4-Digit Display only by Catalex * Hardware: A 4-Digit Display * Board: Catduino or Arduino UNO R3,Arduino Mega2560... * IDE: Arduino-1.0 * Function: Display the time on the digital tube. * Store: https://www.aliexpress.com/store/1199788 * * 最大131072バイトのフラッシュメモリのうち、スケッチが24464バイト(18%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4320バイト(21%)を使っていて、ローカル変数で16160バイト使うことができます。 */ //#include <TimerOne.h> #include "TM1637.h" #define ON 1 #define OFF 0 #define CLK PB3 //pins definitions for TM1637 and can be changed to other ports #define DIO PB4 #define LED1 PB8 #define LED2 PB9 #define BUILTIN_LED PC13 #define SERIAL_RX_BUFFER_SIZE 256 #define DELIMITER "," #define debug 1 int8_t TimeDisp[] = {0x00,0x00,0x00,0x00}; unsigned char ClockPoint = 1; unsigned char Update; unsigned long halfsecond = 0; int year = 2020; byte month = 2; byte day = 11; byte hour = 13; byte minute = 10; byte second = 00; uint32_t timestamp, tempval; String gnrmcMsg = ""; char chkHader[] = "RMC"; //このバージョンから3文字に変更 char STRBUF[50]; int timezone = 9; //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; // to convert unix time to epoch long gLoopCount = 3540; TM1637 tm1637(CLK,DIO); //------------------ bool readSensor(void){ if (Serial1.available()) { Serial1.readStringUntil('\n'); //読み捨てて先頭から読めるようにする String nmea = Serial1.readStringUntil('\n'); #if debug Serial.println(nmea); #endif 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(LED1, count++%2); #if debug Serial.print(nmea);Serial.print(F("\t"));Serial.println(nmeames); #endif } 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); #if debug Serial.println("20" + syy + smn + sdd + shh + smm + sss); #endif hour = (shh.toInt() + timezone) % 24; minute = smm.toInt(); second = sss.toInt(); day = sdd.toInt()-1; month = smn.toInt(); year = syy.toInt() + 2000; digitalWrite(LED1, LOW); digitalWrite(LED2, HIGH); if (year < 2000) return false; return true; }else{ digitalWrite(LED1, HIGH); return false; } } } //------------------GPSにアクセスし、取得に成功したら内部時間を更新 bool ReadGPS(){ if(readSensor()) { //hundredths = 0; // LM: GPS time is always acquired on the second (not used) //age = 0; // Not used in this adaptation }else{ delay(100); #if debug if(gLoopCount%100) Serial.print(F(".")); else{ Serial.println(F(".")); //GpsEnable = false; } #endif } } //----------------- 割込CallBack void TimeUpdate(void){ if(ClockPoint) tm1637.point(POINT_ON); else tm1637.point(POINT_OFF); TimeDisp[0] = hour / 10; TimeDisp[1] = hour % 10; TimeDisp[2] = minute / 10; TimeDisp[3] = minute % 10; ClockPoint = (~ClockPoint) & 0x01; tm1637.display(TimeDisp); } //----------------- void setup() { Serial.begin(115200); delay(1000); //GPS受信機との通信を開始する Serial1.begin(9600); //9600bps固定の場合 ReadGPS(); // pinMode(BUILTIN_LED,OUTPUT); pinMode(LED1,OUTPUT);digitalWrite(LED1, LOW); pinMode(LED2,OUTPUT);digitalWrite(LED2, LOW); tm1637.set(); tm1637.init(); Timer1.pause(); // Timer1.setPrescaleFactor(7200); // 72MHz100uSEC Timer1.setOverflow(10000); //1000mSEC Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,TimeUpdate); //割り込みサーブルーチンの宣言:TimeUpdate Timer1.setCount(0); //0 Timer1.refresh(); Timer1.resume(); } //----------------- void loop() { ReadGPS(); } //-----------------
[赤外線リモコン操作の確認コード(送受信)]
Arduino用に IRremote というライブラリが提供されていますが、このライブラリはSTM32duinoではそのままの利用は出来ません。タイマ関数の使い方が異なるのが第一の原因だと考えています。
また、AVRにて使用したとしても受信は出来ても送信がうまくゆかないというForum書込もありました。原因はキャリアの周波数精度が維持できないことのようです。キャリア周波数38KHzの場合、パルスインターバルは26usecになりますが、これが100usecぐらいになっているという書込を見ました。想像するにdigtalWrite()コマンドを使用している可能性を否定できません。ON/OFFするだけでも26usecは維持できないことが想像出来ます。そこでここでは、極力基本的なデータ送信、受信、その検証を実施して動作確認とします。
※赤外線リモコン対応はHA(HomeAutomation)の核だと思っています。送信側のSTM32MINIShieldと受信側のSTM32MINIShieldは別物とします。
送信側のコンソールから指定フォーマットで文字列入力し、送信ボタンを押します。その時点で解析後、IR-LEDからのデータ送信します。
受信側は4バイトのデータ部をLED&KEYにHEX文字列(8文字)で表示すると云う事にします。
NECフォーマットに特化します。送信時は N,,2,0xAA,0xBB,0xCC,0xDD というフォーマットとなります。
- セパレータはカンマでデリミタはCR/LFです。
- 最初のNはNECフォーマットを意味します。T値はソースコード固定とします。562usです。
- ,,2はリピート2回を意味します。AEHA方式を想定して間隔設定出来るようにして置きます。100を指定すると100T時間を意味します。
- データは1バイト毎にセパレータを入れて送信します。4バイトを想定しています。テストなので、辻褄が合わなくても任意文字列を許可します。
基本は38kHz Duty1/3のキャリアを作成し、NEC方式に従って32bitのパタンを送信。
受信側ではフォトダイオードにて受信。↓↑の時間間隔を計測し、NEC方式に従って解析 結果表示することとします。
※32bitの信号で無いとLED&KEYで表示出来ないのでこのようにしました。
NEC方式の場合 T = 560μs受信側コード
受信側はライブラリを使用しました。※使用しなくても受信できることは確認したのですが、汎用性のあるモノの方が後々いいと思った次第です。
使用ライブラリは以下の通りです。
https://github.com/KiLLAAA/Arduino-IRremote/
https://github.com/rjbatista/tm1638-library
/* * 2020/04/07 T.Wanibe 赤外線リモコン受信のサンプルスケッチです。 * このIRremoteライブラリはデータ長の長い信号は扱えません。32bit長のみと思ってください。 * 通信チェック用に使用します。 * 受信結果32bitを8-7segLED表示します。 * Ken Shirriff氏によるレタッチが為された Arduino-IRremote を使用する必要があります。 * STM32F1では受信は出来るとありますが、送信については要確認のようです。 * ※オリジナルのライブラリはアンインストールする必要があります。 * IRremote: IRrecvDump - dump details of IR codes with IRrecv * An IR detector/demodulator must be connected to the input RECV_PIN. * Version 0.1 July, 2009 * Copyright 2009 Ken Shirriff * https://arcfn.com * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) * LG added by Darryl Smith (based on the JVC protocol) * 最大131072バイトのフラッシュメモリのうち、スケッチが41056バイト(31%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が5200バイト(25%)を使っていて、ローカル変数で15280バイト使うことができます。 */ #include <IRremote.h> // default location @ libraries #include <TM1638.h> #define dataPin PB4 #define clockPin PB3 #define strobePin PA15 #if defined(__STM32F1__) int RECV_PIN = PA10; //ライブラリのデフォルト値はPC15でした。STM32MINIShieldではPA10とします。 #else int RECV_PIN = 11; #endif char BUF[10]; IRrecv irrecv(RECV_PIN); decode_results results; TM1638 LEDandKEY(dataPin, clockPin, strobePin); //------------------ void dump(decode_results *results) { // decode_results構造体をダンプします。 // IRrecv :: decode()の後にこれを呼び出します int count = results->rawlen; switch (results->decode_type){ case UNKNOWN: Serial.print(F("Unknown encoding: ")); break; case NEC: Serial.print(F("Decoded NEC: ")); break; case SONY: Serial.print(F("Decoded SONY: ")); break; case RC5: Serial.print(F("Decoded RC5: ")); break; case RC6: Serial.print(F("Decoded RC6: ")); break; case PANASONIC: Serial.print(F("Decoded PANASONIC - Address: ")); Serial.print(results->address, HEX); Serial.print(F(" Value: ")); break; case LG: Serial.print(F("Decoded LG: ")); break; case JVC: Serial.print(F("Decoded JVC: ")); break; case AIWA_RC_T501: Serial.print(F("Decoded AIWA RC T501: ")); break; case WHYNTER: Serial.print(F("Decoded Whynter: ")); break; default: break; } sprintf(BUF,"%08x",results->value); Serial.print(BUF); if(results->bits) LEDandKEY.setDisplayToString(BUF); Serial.print(F(" (")); Serial.print(results->bits, DEC); Serial.println(F(" bits)")); Serial.print(F("Raw (")); Serial.print(count, DEC); Serial.print(F("): ")); for (int i = 1; i < count; i++) { if (i & 1) { Serial.print(results->rawbuf[i]*USECPERTICK, DEC); }else{ Serial.write('-'); Serial.print((unsigned long) results->rawbuf[i]*USECPERTICK, DEC); } Serial.print(" "); } Serial.println(); } //------------------ void setup() { Serial.begin(115200); // set correct speed in serial monitor delay(1000); #if defined(__STM32F1__) #ifdef F_CPU Serial.print(F("F_CPU: ")); // main Arduino clock Serial.println(F_CPU); Serial.println(); #endif Serial.print(F("SYSCLOCK: ")); // SYSCLOCK is defined in boarddefs.h Serial.println(SYSCLOCK); Serial.println(); // irparams.blinkflag = 1; // option to test BLINKLED #endif LEDandKEY.setDisplayToString(" "); irrecv.enableIRIn(); // Start the receiver Serial.println(F("READY!")); } //------------------ void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); dump(&results); irrecv.resume(); // Receive the next value } } |
送信側コード
送信側はArduino-IRremoteでうまく実現出来ませんでした。そこで機能限定版を作りました。と云っても先人の知恵をレタッチしたのですが。
#define TESTMODE True
にすることで固定値を出力します。
#define TESTMODE False
にすることでコンソールから入力できます。フォーマットは N,200,1,F1,F2,F3,F4 です。
- N:NECフォーマットを意味します。固定と考えてください。
- 200:1フォームの有効時間で入力値×T[usec]です。108msec周期とする場合、T=562usecなので192辺りを設定してください。
- 1はパタンの繰り返し出力です。
- F1,F2,F3,F4がデータとなります。NECフォーマットの場合、F1,~F1,F3,~F3とすべきですがテストなので、任意の値を入力し、受信結果が期待通りか確認します。
/* * 2020/4/10 T.Wanibe 赤外線リモコン用モジュールを利用した通信テスト 送信側 * フォーマットはNECタイプとする。 * USB接続したままシリアルモニタの入力ラインから4文字入力してそのままNECフォーマットで出力する * 本来ならアドレス/データの扱いがあるが、それは入力データ側で対処してね * コンソール入力フォーマットは N,200,1,F1,F2,F3,F4 です。 * 結果は受信側のLED&KEYのHEX文字列8文字で判断する。 * ※キャリア38kDuty1/3をタイマ割込で作る方法はデータパタンの生成で失敗 * 今回の方法は T&H氏のコード https://tandh.work/note/making/arduino_remocon2/をレタッチして検証してみた。 * このコードは delayMicroseconds(); で時間設定し、実際の結果から調整するようになっています。 * 最大131072バイトのフラッシュメモリのうち、スケッチが49568バイト(37%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4912バイト(23%)を使っていて、ローカル変数で15568バイト使うことができます。 */ #define INCHK PB0 #define OUTPIN PA9 #define TESTMODE false int c1; int c2; char buff0[30] = {'0'}; char buff1[30] = {'0'}; bool buff2[512] = {'0'}; char buff4[4] = {'0'}; char test1[] = {'N', '1', '9', '2', '1'}; bool test2[] = {1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,0,0, 0,0,0,0,0,0,1,1}; char type = 'N'; int interval= 192; //192T ? 108msec int repeat = 1; int LON,LOFF; int cc = 0; //-------------------- void high(int t) { //t = 425(AEHA) or 562 (NEC) unsigned long start = micros(); while (1) { //38kHzキャリアDuty1/3 if (micros()+8 > start+t) break; GPIOA->regs->BSRR = (1<<9); //On delayMicroseconds(8); GPIOA->regs->BRR = (1<<9); //Off if (micros()+15 > start+t) break; delayMicroseconds(15); //18にすると認識できない } } //-------------------- void trans(char type,int delayt,int repeat,boolean data2[]) { int t; Serial.print(delayt);Serial.print("-");Serial.println(repeat); switch (type) { case 'A' : // AEHA t = 425; LON = 8; LOFF = 4; break; case 'B' : // Bose t = 500; LON = 2; LOFF = 3; break; case 'N' : // NEC default: t = 562; LON = 16; LOFF = 8; break; } for (int r=0; r<repeat; r++) { unsigned long endTime = t * delayt + micros(); // Leader code high(t*LON); //ON(burst) delayMicroseconds(t*LOFF-100); //OFF // Data code for (int i=0; i<sizeof(data2)*8; i++) { high(t); if (!data2[i]) { Serial.print(0); delayMicroseconds(t); } else { Serial.print(1); delayMicroseconds(t*3); } } // Stop bit high(t); // Serial.println(); if(endTime > micros()) delay(1); } } //-------------------- void setup() { Serial.begin(115200); delay(1000); pinMode(OUTPIN, OUTPUT); pinMode(INCHK, INPUT); c1 = 0; c2 = 0; Serial.println("Ready to trans"); } //-------------------- void loop(void) { if (TESTMODE) { Serial.println(F("TEST MODE")); trans('N',interval,repeat,test2); delay(5000); }else{ if (Serial.available() > 0) { char d = Serial.read(); buff1[cc++] = d; if( d == '\n'){ Serial.println(buff1); sscanf(buff1,"%c,%d,%d,%x,%x,%x,%x",&type,&interval,&repeat,&buff4[0],&buff4[1],&buff4[2],&buff4[3]); int bitIndex = 0; for (int i = 0;i<4;i++){ for (int j = 7; j >= 0 ; j--){ if(buff4[i] & (1<<j)) buff2[bitIndex++] = true; else buff2[bitIndex++] = false; } } trans(type,interval,repeat,buff2); cc = 0; buff1[0] = '\0'; } } } } |
送信側コードの別解
別解を作成しました。キャリア周波数をタイマ割込で作成しているため、この点はとても安定しています。
NECコードしか対応出来ていませんが、コンソールから次のように入力できます。フォーマットは N,200,2,F1,F2,F3,F4 です。N:NECフォーマットを意味します。固定と考えてください。
200:1フォームの有効時間で入力値×T[usec]です。108msec周期とする場合、T=562usecなので192辺りを設定してください。
2はリピートコードの出力を1回することを意味します。
F1,F2,F3,F4がデータとなります。NECフォーマットの場合、F1,~F1,F3,~F3とすべきですがテストなので、任意の値を入力し、受信結果が期待通りか確認します。
/* * 2020/4/14 T.Wanibe 赤外線リモコン用モジュールを利用した通信テスト 送信側 * 如何に安定したビットパターンを送信出来るのか考えた結果、 * キャリアはタイマ割込で実現すべきだと感じました。 * また、1フレームのONOFFは最初にビットパターンを作成してそのパタンに従って処理すべきだと思いました。 * キャリア周波数がまともで無いとLEDが光りません。そして受信側のLPFも機能しません。 * データパタンですが、一貫したタイミング維持をしないと読み取りミスを起こします。 * と云う事でこのコードを書きました。 * 使い方は コンソール入力でフォーマットは N,200,1,F1,F2,F3,F4 というような感じです。現状Loopは1回のみ * 内部ではTを構成するキャリアパルスのLoopカウントおよびTカウント列で処理します。 * 出力PINはPA9固定です。 * 最大131072バイトのフラッシュメモリのうち、スケッチが52344バイト(39%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が6528バイト(31%)を使っていて、ローカル変数で13952バイト使うことができます。 */ #define IR_OUT PA9 #define OUT_CHK PB0 char buff1[30] = {'0'}; int maxBitLength = 2048; bool gBuff[2048] = {'0'}; //512要素でも64byte 2048 256Byte char buff4[10] = {'0'}; char buff5[20] = {'0'}; char c[3] = {'0'}; char type = 'N'; int interval = 192; //192T ? 108msec int repeat = 1; int cc = 0; bool gEnable = false; //trueでパタン出力 int gTcount; //単位Tを満たす1/fcカウント int gSetCount; int t,gTf,gLength,LON,LOFF,RON,ROFF; //------------------------38kHz 1/3のキャリアパルス生成 void CarryOut(){ if(gEnable){ if(gBuff[gSetCount]){ GPIOA->regs->BSRR = (1<<9); //On delayMicroseconds(8); GPIOA->regs->BRR = (1<<9); //Off } if(gTcount==0){ gTcount = gTf; if(gSetCount++ > gLength) gEnable = false; }else{ gTcount--; } } } //------------------------送信データ作成 void MakeTransData(char type,int interval,int repeat,char buff4[],int len){ Serial.print(F("\ntype:"));Serial.println(type); Serial.print(F("interval:"));Serial.println(interval); Serial.print(F("repeat:"));Serial.println(repeat); Serial.print(F("buff4Length="));Serial.print(len); for (int i = 0; i < len;i++){ Serial.print(F("\t"));Serial.print(buff4[i],HEX); } Serial.println(); int bitIndex = 0; switch (type) { case 'A' : // AEHA t = 425; gTf = int(t/26.3); LON = 8; LOFF = 4; RON = 8; ROFF = 8; break; case 'B' : // Bose t = 500; gTf = int(t/26.3); LON = 2; LOFF = 3; break; case 'N' : // NEC default: t = 562; gTf = int(t/26.3); LON = 16; LOFF = 8; RON = 16; ROFF = 4; break; } // Leader for (int i = 0;i< LON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< LOFF;i++) gBuff[bitIndex++] = false; // Data code bit列の処理 for (int i = 0;i<len;i++){ for (int j = 7; j >= 0 ; j--){ if(buff4[i] & (1<<j)){ gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; gBuff[bitIndex++] = false; gBuff[bitIndex++] = false; //1はduty25%回 }else{ gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; //0はduty50%回 } } } // Stop bit列の処理 gBuff[bitIndex++] = true; gBuff[bitIndex++] = false; if(repeat>1){ for (int jj = 0;jj< (interval - bitIndex);jj++) gBuff[bitIndex++] = false; for (int i = 0;i< RON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< ROFF;i++) gBuff[bitIndex++] = false; gBuff[bitIndex++] = true; } for(int ii = 0; ii < (repeat -2);ii++){ for (int jj = 0;jj< (interval-RON-ROFF);jj++) gBuff[bitIndex++] = false; for (int i = 0;i< RON; i++) gBuff[bitIndex++] = true; for (int i = 0;i< ROFF;i++) gBuff[bitIndex++] = false; gBuff[bitIndex++] = true; } gSetCount = 0; gTcount = gTf; gLength = bitIndex; if(gLength < maxBitLength) gEnable = true; } //------------------------ void setup() { Serial.begin(115200); delay(1000); pinMode(IR_OUT,OUTPUT); pinMode(OUT_CHK,OUTPUT); digitalWrite(IR_OUT,LOW); digitalWrite(OUT_CHK,HIGH); Timer1.pause(); // Timer1.setPeriod(26); // 26uSEC ? 1/38kHz/ Timer1.attachInterrupt(TIMER_UPDATE_INTERRUPT,CarryOut); //割り込みサーブルーチンの宣言:CarryOut Timer1.resume(); Serial.println(F("Ready to trans")); } //------------------------ void loop() { char *endp; bool hasData = false; int index = 0; while (Serial.available() > 0) { hasData = true; if (Serial.available() > 0) { char d = Serial.read(); buff1[cc++] = d; if( d == '\n'){ Serial.println(buff1); sscanf(buff1,"%c,%d,%d,%s",&type,&interval,&repeat,buff5); Serial.print(F("buff5:"));Serial.println(buff5); int strLen = strlen(buff5); int LEN = (strLen+1) /3; Serial.print(F("size:"));Serial.println(strLen); int j = 0; int k = 0; for(int i=0;i<strLen;i++){ if(buff5[i] == ','){ c[k++] = '\0'; Serial.print(c); sscanf(c,"%x",&buff4[j++]); k = 0; }else{ c[k++] = buff5[i]; //Serial.print(c); } } sscanf(c,"%x",&buff4[j++]); MakeTransData(type,interval,repeat,buff4,j); cc = 0; buff1[0] = '\0'; } } } delay(5000); //5 second delay between each signal burst Serial.println(F("NEXT to trans")); } |
【ウオッチドックタイマ(WDT)の動作確認】
#include <libmaple/iwdg.h>を設定する事でWDTを実現出来ます。
BaseClockは40kHzだとLeafLabsの資料にはあります。これを信じると、25usecを何カウント待てるかという事になります。
MainLoopのループカウントを求めて、リセットカウントを決定すれば良いようです。
STM32ではWDTは一度設定すると途中で変更出来ないようです。
開始はsetup()の最後でリセットはloop()の先頭で実行するのが良いのかなと思います。
/* * 2021/01/07 T.Wanibe STM32でWDTを使う検証コードです。 * 資料によると、wdtのBaseClockは40kHzだと有ります。 * 最大131072バイトのフラッシュメモリのうち、スケッチが16124バイト(12%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が3128バイト(15%)を使っていて、ローカル変数で17352バイト使うことができます。 */ #include <libmaple/iwdg.h> #define LED1 PB8 #define LED2 PB9 #define iwdg_init_ms(N) iwdg_init(IWDG_PRE_256,((N)/6)) //--------------- void setup() { Serial.begin(115200); pinMode(LED1,OUTPUT); pinMode(LED2,OUTPUT); iwdg_init_ms(5000); //ウォッチドッグをオンにして、オーバーフロー時間を設定します。 //実測値で5000だと6000m秒弱でリセットが掛かります。 //6000だと6000m秒ではリセットが掛かりません。 //25usec×256×5000/6=5.3秒でリセットが掛かるという計算になります。 //25usec×256×6000/6=6.4秒でリセットが掛かるという計算になります。 } //--------------- void loop() { iwdg_feed(); //ウォッチドッグタイマーをリセットするには、ドッグ操作をフィードします Serial.println(millis()); digitalWrite(LED1,HIGH); delay(1000); digitalWrite(LED2,HIGH); delay(1000); digitalWrite(LED1,LOW); delay(1000); digitalWrite(LED2,LOW); delay(1000); digitalWrite(LED1,HIGH); digitalWrite(LED2,HIGH); delay(1000); digitalWrite(LED1,LOW); digitalWrite(LED2,LOW); delay(1000); Serial.println(millis()); } |
【Keyマトリックスによる入力動作確認】
Keyマトリックスモジュールは幾つかの種類があります。単純なメカニカルスイッチマトリックスも有れば、一歩進んでデコーダchipを介してI2CとかSPIで通信できる物も有ります。
或いは、抵抗アレーを介したモノはADCを介した電圧値での認識となるようなモジュールもあります。ここでは単純にDIOに接続し、表示器としてLED&KEYを用意しておいて7セグLEDに表示するようにします。アスタリスクとハッシュタグをどのように表現するか検討する必要がありますが、
arduino用の <Keypad.h>はSMT32duinoでは利用できない模様。コンパイルエラーになります。
<Adafruit_Keypad.h>のライブラリがIDEのライブラリのインクルードから検索して登録出来ます。このライブラリであればコンパイルが通ります。
Pinアサインを変更して動作確認して見ました。ボタンを押している間そのキーアサインをLED1に表示します。
/* * 20210531 T.Wanibe * この例は、Adafruit キーパッド製品で使用してください。 * キーパッドの製品 ID を知る必要があります。 * 概要は次のとおりです。 * PID3844 4x4 Matrix Keypad * PID3845 3x4 Matrix Keypad * PID1824 3x4 Phone-style Matrix Keypad * PID1332 Membrane 1x4 Keypad * PID419 Membrane 3x4 Matrix Keypad * KEYPAD_PID3844 を選択してください。 * Adafruit_Keypadバージョン1.2.0を使用中 * 最大131072バイトのフラッシュメモリのうち、スケッチが36800バイト(28%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が4648バイト(22%)を使っていて、ローカル変数で15832バイト使うことができます。 */ #include <Adafruit_Keypad.h> #include <TM1638.h> // define your specific keypad here via PID #define KEYPAD_PID3844 //PinAssign #define dataPin PB4 //STM32MINIShieldEvaluation基板に準拠 #define clockPin PB3 //STM32MINIShieldEvaluation基板に準拠 #define strobePin PA15 //STM32MINIShieldEvaluation基板に準拠 #define R1 PA7 #define R2 PA6 #define R3 PA5 #define R4 PA4 #define C1 PA3 #define C2 PA2 #define C3 PA1 #define C4 PA0 //上記の構成の後にこのインポート実施します。順番に依存してしまっています。 #include "keypad_config.h" unsigned long loopTime,preTime,postTime; char a; char buf[10]; int count; //NewKeypad クラスのインスタンスを初期化します Adafruit_Keypad customKeypad = Adafruit_Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS); TM1638 LEDandKEY(dataPin, clockPin, strobePin); //-------------------- void setup() { Serial.begin(115200); customKeypad.begin(); preTime = millis(); } //-------------------- void loop() { // ここにメイン コードを配置して、繰り返し実行します。 postTime = millis(); loopTime = postTime - preTime; preTime = postTime; customKeypad.tick(); while(customKeypad.available()){ keypadEvent e = customKeypad.read(); a = (char)e.bit.KEY; Serial.print(buf); if(e.bit.EVENT == KEY_JUST_PRESSED){ Serial.println(F(" pressed")); sprintf(buf,"%4d %1s",loopTime,&a); LEDandKEY.setDisplayToString(buf); }else if(e.bit.EVENT == KEY_JUST_RELEASED){ Serial.println(F(" released")); } } delay(10); Serial.println(count++); } |
PCF8574PやMAX7328を介して配線すればI2Cデバイスとして扱うことも可能です。この方法だと割込も扱いやすいですね。
【Sleep動作の確認を追加しました】
STM32にはSTM32Lという超低消費電力タイプが用意されていますが、STM32Fでも可成り消費電力化出来るという先人の情報があります。そこで試してみることにしました。
『Keyマトリックスによる入力動作確認』のコードを実行するとUSB端子端で60mAが消費されます。LED&KEYの消費が大きいと思います。そのため10000mAhのモバイルバッテリで約1週間の運用が出来ます。
これをスリープ関数を使用してどの程度改善できるのか確認する事にしました。STM32duinoにおいてスリープ関数に対応したライブラリを探したところ、Tom Vijlbrief氏のスケッチが存在することが判りました。まずは確かめてみることにしました。libmapleを使用します。
※Tom Vijlbrief氏が該当APIをライブラリ化してくれていれば良かったのですが、、、、
一部のオブジェクトはベクター殿のストレージをお借りしています。
https://www.vector.co.jp/vpack/browse/person/an051501.html
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権は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社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。