最終更新日:2021年5月28日

動作確認】

動作確認には以下の機能確認コードをご利用戴ければと思います。ArduinoIDEにて新規ファイルを作成しコードをコピペしてください。


各機能単体検証プログラムを示しておきます。
STM32duino環境が整ったArduinoIDEにて、新規スケッチを開き、コードをコピペして拡張子 ino として保存し、Buildしてください。


TM1638の確認コード]

ライブラリはgithubから落とします。
https://github.com/rjbatista/tm1638-library

    配線はSTM32MINIShield基板のTM1638コネクタにLED&KEYを接続します。

    起動すると、

    /*
     * 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です。

    /*
     * 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』で動作確認しました。

回転速度の計測確認コード]

風向や風速の計測のためにパルス入力を元に回転速度を計測する術を確認しておきます。

風速は風車の一定時間の回転数を計測することで求める事が出来るかと思います。或いは、音波が空中を伝搬するときにその速度が風速によって変化することを利用して超音波の伝搬時間を測定する方法もあるかと思います。
風向は吹き流しは一般なのでしょうが、電気的な値として取り出せないです。最大風速が得られる角度をサーボから電圧値である方法だったり、ポテンショメータで回転角を求めるのも可能です。超音波センサを3つ正三角形に配置して時分割送受信で風向判断することも出来そうです。

ここでは回転角はさておいて回転速度の計測を確認するコードを置いておきます。
外部に回転数が変化可能なデバイスがあればいいのですが、テストなので、BluePill自身が特定のパルスを出力し、それを信号入力してカウントアップ、一定時間毎にカウンタ値を表示するという物です。

パルスカウンタの精度は可成り高いと考えます。一方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確保する必要があるわけで、なんとか確保出来るサイズです。

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-display

LED&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社の登録商標です。
    その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
    すべての商標および登録商標は、それぞれの所有者に帰属します。