STM32MINIShield基板仕様

最終更新日:2020年9月29日

【TOPICS】

ここではSTM32MINIShield基板1.1の仕様を示します。

  1. 概要
  2. 外観
  3. 回路図
  4. 実装想定部品一覧
  5. 実装イメージ
  6. Pinマップ
  7. 開発環境整備
  8. 動作確認プログラム
    1. コラム:I2Cの信号デバッグ
    2. コラム:BME280とBMP280
    3. コラム:BluePillのRTCについて
    4. コラム:ポートアクセスの効率
    5. コラム:850io以外のNIC接続のNIC接続
    6. コラム:MHコネクタの接続
  9. 応用例
    1. WebBrowserで利用するケース
    2. Modbusで利用するケース
    3. CANbusで利用するケース(未結)
    4. 気象観測で使用できないか?
    5. USBHIDデバイスを作成する
    6. GPSタイムサーバー
    7. MP3Player
    8. 環境センサによる自動制御
    9. 赤外線コントローラ
  10. 給電方法
  11. FAQ

概要】

この基板は一般にBluePillと呼ばれている、マイコン:STM32F103C8が搭載された40pinDIP-IC型モジュールに外部モジュールを接続するための基板です。
BluePill用のEtherNetShieldは見当たらないのでNeedsがあるかなと思っています。

直接半田付けしても良いのですが、ソケットを半田付けしておいて差替が出来るように使用する事を想定しています。

この基板に直接接続するモジュールとして想定しているのは以下の通りです。

外観】

外観: 50×80[mm]
マスク色:
取付穴: φ3.5、40×70[mm]
厚み 1.6[mm]
基板材質 FR-4 TG130
基板表面処理 鉛フリー半田レベラー

回路図】 PDF

実装想定部品一覧】

記号 名称 カテゴリ 数量 メーカ 備考
M1 STM32Mini MPU 1 RobotDyn BluePill相当品で代用可能
M2 Wiz850io NIC 1 Wiznet USR-ES1で代替可能
U1 23LC1024 SRAM 1 Microchip 必要ならば
P1 FH-2x20SG PinSocket 1 Useconn Electronics 必要ならば
P2 FH-2x20SG PinSocket 1 Useconn Electronics 必要ならば
P3 FH-1x6SG/RH PinSocket 1 Useconn Electronics 必要ならば
P4 FH-1x6SG/RH PinSocket 1 Useconn Electronics 必要ならば
CN1 B4B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN2 B4B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN3 B4B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN4 B6B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN5 B4B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN6 B4B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
CN7 B5B-XH-A(LF)(SN) コネクタ 1 JST XH以外のコネクタでも構わない
LED&KEY(TM1638)の接続想定
R1 MF1/4CC1501F 抵抗1.5KΩ 1 KOA 互換品
R2 MF1/4CC1501F 抵抗1.5KΩ 1 KOA 互換品
R3 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品
R4 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品
R5 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品
R6 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品
R7 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品
R8 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品I2CPullUp 400KHzなら2.2k
R9 MF1/4CC1002F 抵抗10KΩ 1 KOA 互換品I2CPullUp 400KHzなら2.2k
R10 MF1/4CC1003F 抵抗100KΩ

1

KOA 互換品10K〜100KΩをメーカ推奨
C1 RDER72E104K2K1H03B コンデンサ0.1uF 1 muRata 互換品
SW SKHHPPA010 タクトスイッチ 1 アルパイン 互換品
LED1 OS5RKA3131A LED(白) 1 オプトサプライ φ3mm
LED2 OS5RKA3131A LED(青) 1 オプトサプライ φ3mm
  LEDスペーサ   2   φ3mmt2mm
  基板スペーサ   4   φ3.5mmH5mm

※基板のスペーサですが、サンハヤト・MPS-08が利用できるはずです。オリジナルは北川工業MPS-08-0のはずです。

※USBケーブルですがマイクロBタイプのケーブルが必要です。2A程度は流せるモノをご検討ください。
TOPK Hi-Tensile Micro USB Cableは安価ですが2.4A流せるとありお勧めです。

実装イメージ】

    MHコネクタの接続】

    スイッチサイエンス殿にて取り扱って戴いている『基板+部品セット』では、各モジュールとの接続にて日圧(JST)社のXHコネクタにて接続することを想定しています。XHコネクタはピッチが2.5mmとなっており、milピッチと微妙にズレがありますが、ピン数が小さければ無視できると解釈しています。
    ArduinoではQIと云われるデュポン社のコネクタが想定されているかと思います。複数pinをセットで扱う場合、極性が気になりますがQIではなかなか対策困難です。そのため比較的流通しているXHコネクタにしました。

    JSTのセカンドソースであれば、安価にセット販売されています。

    問題があるとすれば、圧着工具をどうするかです。日圧の正規品は数万円します。手が出ません。ただ、XHコネクタは流通していることもあり、安価な工具も容易に入手出来ます。でも何を買うかは気になるところです。自分も何種類か購入しています。と云うのも汎用品はなかなかうまく圧着出来ないからです。汎用品の殆どは2回に分けて圧着するのですが、この考えがそもそも良くないです。
    XH用の圧着工具ですが、IZOKEE製端子圧着ペンチによる圧着がきれいに出来ました。\3000程度で購入できると思います。


      或いは、敢えてXHのハウジングを使わないで、QIコネクタケーブルを纏めて使うのも在処と思います。
      1本1本接続するのは問題があると思っています。しかし、QIハウジングもpin数に応じたハウジングが用意されています。デュポンラインのオスメスタイプを確認し、一本一本割いて、1pin用のハウジングを外し、必要なピン数のハウジングに付け直します。この方法で有れば圧着工具を使わなくても実現出来ます。

Pinマップ】

ここではArduinoIDE環境で使用することを想定しています。PINマップもその条件に準じています。

環境整備】

ArduinoIDE環境でBluePillが使える様にArduinoIDE環境も変更構成する必要があります。
この点ですが、Roger Clark 氏のサイトから情報を得ることが出来ます。
念のため自分が構築したWindowsOS下のArduinoIDE環境について紹介しておきます。

RobotDyn社のSTM32MINIにはArduinoIDE用のブートローダーが既に書き込まれている商品があります。
このモデルを利用するのが無難です。※パッケージで確認出来ます。『withArduino bootloader 』と云う記載があるはずです。

ArduinoIDEのボード設定を間違ったまま書込を実行しますとブートローダを簡単に壊します。ご注意ください。
※壊れたらブートローダを書込直すだけですが。

BluePillと云われる商品はArduinoIDE用のブートローダーを自分で書き込む必要があります。

このタイプのBluePillにはブートローダが書込済みの商品はないと思います。
自身でブートローダを書き込む作業が必要です。

Roger Clark氏のサイトにある情報に従って実現出来ます。

念のため、自分が実行しているWindows環境からBluePillに対してブートローダを書き込む方法を別ページで紹介しておきます。

動作確認】

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


応用例】

この基板はもともとパルスモータ制御用に作成したモノです。しかし、組み立ててみると汎用のBluePill用EtherNetShield基板だといえるなと思いました。
応用例を示しておきます。尚、NICを使用したサンプルスケッチに記述するMacアドレスやIPアドレスはダブらないようにして置きたいので管理表を示しておきます。
※掲載したMACアドレスは手元に保有しているモノではありません。あくまでソースコード内初期値として設定しているに過ぎません。購入されたNICに付加されたMACアドレスに読み替えてください。

Example MACアドレス IPアドレス PortNumber EID
WebServer 00:08:DC:54:4D:D0 192.168.0.200 TCP80 0x93
ModbusTCP 00:08:DC:54:4D:D2 192.168.0.202 TCP502,80 0x92
NTPServer(TM1638) 00:08:DC:54:4D:D1 192.168.0.197 UDP123,TCP80 0x91
NTPServer(TM1637) 00:08:DC:54:4D:D6 192.168.0.206 UDP123,TCP80 0x96
DFPlayerControl 00:08:DC:54:4D:D3 192.168.0.201 TCP50001,80 0x94
IrController 00:08:DC:54:4D:D5 192.168.0.205 TCP50005,80 0x95


給電方法】

PCからの書き込み時はPCからUSB経由で電源供給します。
単体で動かす場合もUSBから5V電源供給することとします。現状USBモバイルバッテリが安価で販売されていることからこの考えがリーズナブルかと考えております。
※φ5.5の電源プラグからの供給を検討した場合、この評価ボードにはレギュレータもないので困難です。
BluePillの5V端子に入力する場合、USB接続しないことを前提としてください。電源がバッティングします。BluePillの回路図を見る限り保護用ダイオードや、切替回路は搭載されていません。※RobotDynのSTM32miniはUSBのVbusと5Vpinの間にダイオードが挿入されていました。

上記構成で0.1Ah消費しております。LEDは電力消費が大きいのでLCDにした方がよいかもしれません。ただ、OLCD(SSD1331)に置き換えたところ0.24Ah消費しました。バックライトのないキャラクタディスプレイタイプのLCDが良さそうです。

ということで、秋月のキャラクタLCD『AQM1602XA-RN-GBW』で確認してみました。結果として0.03Aです。確かに省エネですが、バッテリが自動的に停止してしまいました。これはこれで問題です。

〔USBアダプタ調査〕

USB端子を持つACアダプタはモバイルバッテリー向けに多種多様存在します。しかし、モバイルバッテリ向けは数mA負荷の場合Sleepモードに陥る物が多々あることを確認しています。そこで一般的なUSB端子付きアダプタを調査しリストアップして置きます。

取扱業者 外観 コメント
@ スイッチサイエンス 価格1100
5V2A
45×35×22mm
A 秋月電子通商 [M-08312]
価格480
5V1A
51x30x24(mm)

[M-13658]
価格580
5V2A
52.0mm×32.0mm×25.0mm
B 秋月電子通商 [M-10507]
価格1100
5V2.5A
C UNIFIVE UB305 UB
5V1A
42.1*34.7*22
D UNIFIVE UWB305 UWB ACピン折り畳み式
5V/1.0A
52.6*38.8*17.5
E UNIFIVE UYZ305 UY
5V/1.0A
59*28*37.6
F UNIFIVE UWB324-USB4
5V/4.2A
91.3*48.9*27.8
¥3,135共立エレショップ価格
G HMUNII travel life Store

5V3.1A
2.4cm7cm6cm

一個$3.5です。
レビューを見る限りSleep機能は無いと思われます。

H NTONPOWER

5V 3A
168*55*29mm

US $13.59

3PのタップでUSB付きです。
安全性は不透明ですが興味深い商品です。


各機能単体検証プログラムを示しておきます。
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);
    }


I2Cの信号デバッグ】
I2Cのモジュールに接続してうまく信号が受信できない場合、電気的な遣り取りが出来ているのか確認する必要があります。自分は Digilent Inc. 社が販売しているAnalogDiscovery2を愛用しています。国内でも販売されているお店があり、容易に入手出来ます。特にLabVIEWから制御出来るため、独自のアプリケーションも作成可能です。

手元でのデバッグ環境を紹介しておきます。

I2CのバスPullUp抵抗値ですが、STM32MINIShieldではR8/R9が相当し、10KΩとしています。これは、Arduinoのデフォルトクロック値は100KHzで、この場合の推奨値が10KΩとなっているからです。
しかし、実際にはモジュール側にもPullUp抵抗が搭載されている場合が殆どで、10KΩが適正な抵抗値とは云えない場合があります。
実際に抵抗値を変更した信号モニタ画像を示しておきます。

AD2取得画像 CH1:SCL CH2:SDA コメント
@

SCLの数を数えると、1DIVに10個あり、
設定が100u/DIVで有ることから、100KHz
で有ることが確認出来ます。

そのときのSDAの立ち上がりが十分に
立ち上がっていない様に見えるモノが
あります。

A PullUp抵抗を3.3KΩにしてみました。
確かに立ち上がりが鋭くなりますが、
これでも十分とは云えないところもあります。
B PullUp抵抗を3.3KΩを拡大してみました。
3.3Vには立ち上がっておりこれなら十分の
様です。
C ちなみにPullUp抵抗を1KΩにしてみました。
明らかにきれいな矩形になりますが、
ちょっと仕様から外れるようで、一部の
モジュールでは認識されなくなりました。
後GND電位の変化が大きいことも判ります。
PullUp抵抗値が小さい過ぎるのも問題です。

矩形波形が鈍っている原因は配線材の容量が大きいからかもしれません。安価なケーブルはその傾向があります。うまく動作しないI2Cモジュールがあるかもしれません。この場合、PullUp抵抗値の変更も考慮してください。
PullUp抵抗値を低くしてやると確かに矩形波形改善の効果はあります。
ただ、VOL(Low-level Output Voltageの略)については注意した方がいいです。I2CではVOL<0.4Vという規格です。
信号でないところで0.4Vを超えるスパイクがあると誤作動するかと思います。PullUp抵抗値の変更で影響が出るかもしれません。VOLについては『LTC4313-1』のような対策chipも有るようです。
また、センサモジュールを引き延ばしたいという事もあるかと思います。その場合はラインバッファの挿入も検討しなくてはいけません。単なるバスリピータ基板は少ないのですが、5V<->3.3Vレベル変換基板がリピータ機能も持っているようですので代用できそうです。
※安価なモノだとトランジスタ1個で対応しているモノも有りますが、、、
マクニカサイトではLTC4315を挿入することを推奨したりしています。


実装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
     *  http://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);
    }

        BME280とBMP280】

          BME280搭載のモジュールは殆どBME/BMP280と云うような記載がありBME280とBMP280、どちらでも使用できるような基板となっています。
          BME280とBMP280の違いはBMEが湿度まで計測するのに、BMPは湿度を取得出来ず、温度と気圧が取得出来ます。
          BME280とBMP280のチップ単価はそれなりにあるようで、BMP280は可成り安価です。

          販売する側もこの違いを明確にせず、BME280と云う記載があるモジュールを購入したのに実際に届いたのはBMP280搭載モジュールだったという報告をよく見かけます。気をつけたいです。価格差が大きいのでなおさらです。

          BME280とBMP280が手元にあれば外観上の違いは確認可能です。チップ外形がほぼ正方形がBME280、明らかに長方形がBMP280です。

          Boschオリジナルだと1個768円(DigiKey) Boschオリジナルだと1個408円(DigiKey)


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秒待ってから再度時間を尋ねる
    }

850io以外のNIC接続】

このShieldはWIZ850ioを接続することを前提で作りました。SwitchScience殿がMACアドレスを付加して提供していることに重要な意味があります。
MACアドレスを別途容易に入手出来るのであれば他のChipModuleも接続対象となります。
※USBデバイスのID然りMACアドレスも独自に入手するのは大変だと思っています。

  • ENC28J60も使えるのですが、10Mbpsだけですし、プロトコルスタックをソフトウエアで賄わなければならないこともあり、使いづらいです。UNOだとコンパイルさえ通らないこともあり、敢えて使う必要はないのかと思っています。
  • PinIOが共通のWIZ820IOも使えますが、ライブラリ及びスケッチは弄る必要があります。後、消費電流に注意が要ります。WIZ850ioよりも電流を喰います。価格的にも敢えて使う必要はないのかと思います。
  • WIZ850ioとほぼ同じ仕様のUSR-ES1についてはそのまま使えることを確認しています。
    偶にAmzonJapanでも並行輸入品が出ています。基本的に安価です。メーカが曖昧で資料も曖昧です。
  • ROBOTDYNのethernet-module-w5500-3-3v-5vはソケット位置を変更して接続可能ですし、スケッチもそのまま使用可能です。
    安価なのですが入手性に難有りです。しっかりしたメーカ製ですので安心感はあるのですが。
  • “W5500”+“module”で検索すると真っ先に出てくる『W5500模擬』のシルク印刷されたモジュールは、安価でAmzonJapanでも並行輸入品が容易に見つかります。
    コネクタが2×5のpinヘッダです。一応pinアサインさえ間違えずに接続すれば同じスケッチで使えることを確認しています。


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!
     *        ------> http://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例に追加してみます。


    ポートアクセスの効率】

    Arduino環境でも話題になっていますが、digitalWrite()でデジタルpinをアクセスすると可成りオーバヘッドがあることが判っています。効率よくアクセスするためにはレジスタに直接アクセスすることです。同時にPIN操作する場合もポートアクセスする必要があります。
    AVRですとレジスタアクセスで10倍くらい高速処理が出来るそうです。

    STM32duinoでも同じです。Arduinoライクのコードが使える様に書かれているため、多少冗長なコードになっていると思われます。レジスタアクセスだとわかりやすいコード記述が困難になるかと思いますが、処理速度とのトレードオフとなります。一応検証結果を添えておきます。やはり10倍ぐらいの高速化は実現出来るようです。

    ※bitシフトの記述は(1<<8)がいいのか_BV(8)がいいのか

    /*
     * 20200325 T.Wanibe デジタルPinアクセス方法の処理時間を比較検証します。
     * Arduinoでも判っていたことですが digitalWrite()の記述はわかりやすいのですが
     * オーバヘッドが大きく問題がありました。レジスタによるアクセスで16倍ほど速くなると云う
     * 経験をしていましたがBluePillではもっと顕著だという事が判ります。
     * 結果ですが
     * ARDUINO_0 の場合 1375usec
     * ARDUINO_1 の場合 295usec
     * ARDUINO_2 の場合 295usec
     * ARDUINO_3 の場合 127usec
     * でした。10倍強早くなります。重要な情報です。
     * 最大131072バイトのフラッシュメモリのうち、スケッチが14860バイト(11%)を使っています。
     * 最大20480バイトのRAMのうち、グローバル変数が3128バイト(15%)を使っていて、ローカル変数で17352バイト使うことができます。
     */
    //test for arduino port execution speed
    //test routines - only one can be defined at a given time to test individual routines
    //#define ARDUINO_0                               //use arduino port routines
    //#define ARDUINO_1                             //direct port access. use |/& operators
    //#define ARDUINO_2                             //direct port access. use ^ operator
    #define ARDUINO_3                             //direct port access. use BSRR/BRR registers
    //#define DisableInterrupts                     //disable interrupts
    //hardware configuration
    #define LED     PB8                             //led output pin
    void setup() {
            Serial.begin(115200);                   //シリアル通信を開ます。
            delay(1000);
            // put your setup code here, to run once:
            pinMode(LED, OUTPUT);
    #if defined(DisableInterrupts)
            noInterrupts();                         //disable interrupts
    #endif
    }
    void loop() {
            // put your main code here, to run repeatedly:
            //flip pins here - hard-wired iterations x 50
            int start = micros();
            for(int i = 0; i < 1000; i++){
    #if defined(ARDUINO_0)
                    digitalWrite(LED, HIGH); digitalWrite(LED, LOW);
    #elif defined(ARDUINO_1)
                    GPIOB->regs->ODR |= (1<<8); GPIOB->regs->ODR &=~(1<<8); //approach 1
    #elif defined(ARDUINO_2)
                    GPIOB->regs->ODR ^= (1<<8); GPIOB->regs->ODR ^= (1<<8); //approach 2
    #elif defined(ARDUINO_3)
                    GPIOB->regs->BSRR = (1<<8); GPIOB->regs->BRR = (1<<8);  //approach 3
    #else
    #warning "no approach defined!!!!"
    #endif
            }
            Serial.print(micros() - start);Serial.println(F("[usec]"));
    }


[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: http://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
     * http://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"));
}



    FAQ】

    問い合わせ戴いた内容を元に纏めてゆきます。


    1. Q:STM32mini/BluePillがPCとUSB接続しても認識されなくなりました。どうしてですか?


      A:原因は特定できませんが、STM32mini/BluePillのブートローダと呼ばれる領域を壊してしまった
      可能性が高いです。この場合はブートローダを書き直すことで解決するかと思います。
      このリンク先を参照してブートローダの書き直しを実施してください。
      尚、ブートローダを壊してしまう原因はMPUにプログラムを書き込むときの設定間違いのケースが多いです。見直してください。


    2. Q:外部電源を使用して動かす場合はどうしますか?


      A:STM32MINIShield基板に電源入力の端子は設けておりません。
      STM32mini/BluePillのUSBポートから5Vの電源供給を受けることのみサポートします。
      モータ駆動用の電源等大きな電流が必要な場合はフォトカプラ等のアイソレーションを介して接続し、
      ドライブ側で電流を調達できる仕組みを用意してください。


    3. Q:適当なエンクロージャを紹介してください。

      既製のエンクロージャを調査してから基板製作に入れば良かったと後悔しています。
      現状お勧めできる既製エンクロージャが見いだせておりません。取付穴ピッチ40×70、M3というのは
      なさそうです。
      タカチ電機工業は追加工を依頼できるため、OP-125を加工して収めると云うのも有りかと思います。


    一部のオブジェクトはベクター殿のストレージをお借りしています。

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