ModbusTCP マスター

最終更新日:2020年12月18日

  • 2020/3/25 :ファームウエアを更新しました。
  • 2020/8/11 :ファームウエアを更新しました。
  • IO拡張を検討しました。
  • 自動再起動の必要性

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

    https://www.vector.co.jp/soft/winnt/hardware/se521019.html

    出来る事は、


    このプロジェクトの目的は、ModbusTCPのアプリケーションというよりもデバッグで使用できるModbusSlaveデバイスの構築です。

    ModbusSlaveを手軽に構築しようとした際に、BluePillをArduinoIDE環境下で比較的容易に構築し、それに対応したアプリケーション構築が出来ればという意図があります。
    ターゲットボードは『STM32miniShield』です。この基板自体はスイッチサイエンス殿にて有償頒布して戴いています。

    ModbusSlaveはModbusTCPでホストコンピュータ(SCADA)と接続します。
    Coilには接点出力を割り当てます。ターゲットボードではLEDに割り当てています。
    Input Statusは接点入力を割り当てます。ターゲットボードでは4連ディップスイッチを割り当てています。
    Input Registerにはアナログ入力とI2Cデバイス(BME280)を割り当てています。
    Holding Registerにはアナログ出力を割り当てています。

    Modbusアプリケーションは市販されている汎用品でも構いません。この点が規格化されたModbusを採用している良い点です。
    例えばWitte Software社の『ModbusPoll』からでも、このターゲットデバイスにはアクセス可能です。

    ターゲットデバイスも無償で構築可能なArduinoIDEでコーディングできます。必要なセンサデバイスをほぼ無尽蔵に接続可能です。


    【ホストアプリケーション】

    ライブラリはPlasmionique社から提供されているPlasmionique Modbus Master v1.3.5.3 を採用しました。
    https://lavag.org/files/file/286-plasmionique-modbus-master/

    @ 接続ボタン Lで示されたアドレスのModbusデバイスと接続します。
    接続したら緑LED点等します。
    A ポール接続 このチェックを入れるとMのポーリング間隔時間で接続し、得られたデータをDEのグラフ
    に表示します。
    B 切断ホタン Modbusデバイスと切断します。
    C 終了ボタン アプリケーションを終了します。
    D デジタルグラフ Coil及びInput Statusをデジタルグラフ表示します。
    E 波形グラフ Input RegisterとHolding Registerを波形グラフ表示します。
    此処ではHolding Registerに設定した値でDAC出力した値をADCで読み取っています。
    また、BME280の気温[]×100℃]、湿度[×100%]、気圧[mbar]表示しています。
    F Coil スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスでは16bit割り当てています。
    bit0=LED1/bit1=LED2、bit8=LED&KEY・LED1〜bit15=LED&KEY・LED8
    G Coil書込ボタン このボタンを押したタイミングでコイルを書き換えます。
    H Input Status スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスでは要素4wordです。
    ADC0/温度(BME280)/湿度(BME280)/気圧(BME280)
    I Holding Register スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスではアドレス0にDACの設定値を割り当てています。
    J Holding Register
    書込ボタン
    このボタンを押したタイミングでレジスタを書き換えます。
    K Input Register スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスでは要素4bitです。
    L スレーブアドレス ModbusスレーブデバイスのIPアドレスを入力します。
    M アクセスパラム Modbusはポートアドレス502固定ですが変更可能です。
    ポーリング間隔はOの値よりも長くしてください。
    N エラー表示器  
    O 実行時間 この時間を参照してポーリング間隔を決定します。
    P 送信データ列 送信データ列を表示します。
    Q 受信データ列 受信データ列を表示します。

    @ コイルステータス-Updateボタン Coilのデータ列を一気に読み込みます。
    範囲はStartアドレスから要素数分です。
    A コイルステータス要素表示 @が実行されたときの結果表示
    B コイルステータス読込アドレス指定 読込たいアドレスを指定します。
    C コイルステータス読込ボタン 読込実行します
    D コイルステータス要素値表示 実行後の結果表示します。
    E コイルステータス書込アドレス指定 書込たいアドレスを指定します。
    F コイルステータス書込要素値 スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスでは16bit割り当てています。
    bit0=LED1/bit1=LED2、bit8=LED&KEY・LED1〜bit15=LED&KEY・LED8
    G コイルステータス書込ボタン 書込実行します。結果はAの表示で確認出来ます。
    @ 入力ステータス-Updateボタン 入力ステータスのデータ列を一気に読み込みます。
    範囲はStartアドレスから要素数分です。
    A 入力ステータス要素表示 @が実行されたときの結果表示
    B 入力ステータス読込アドレス指定 読込たいアドレスを指定します。
    C 入力ステータス読込ボタン 読込実行します
    D 入力ステータス要素値表示 実行後の結果表示します。
    @ 保持-Updateボタン Coilのデータ列を一気に読み込みます。
    範囲はStartアドレスから要素数分です。
    A 保持要素表示 @が実行されたときの結果表示
    B 保持読込アドレス指定 読込たいアドレスを指定します。
    C 保持読込ボタン 読込実行します
    D 保持要素値表示 実行後の結果表示します。
    E 保持書込アドレス指定 書込たいアドレスを指定します。
    F 保持書込要素値 スタートアドレスと要素数を設定します。基本スタートアドレスは0です。
    このターゲットデバイスでは16bit割り当てています。
    bit0=LED1/bit1=LED2、bit8=LED&KEY・LED1〜bit15=LED&KEY・LED8
    G 保持書込ボタン 書込実行します。結果はAの表示で確認出来ます。
    @ 入力レジスタ-Updateボタン Coilのデータ列を一気に読み込みます。
    範囲はStartアドレスから要素数分です。
    A 入力レジスタ要素表示 @が実行されたときの結果表示
    B 入力レジスタ読込アドレス指定 読込たいアドレスを指定します。
    C 入力レジスタ読込ボタン 読込実行します
    D 入力レジスタ要素値表示 実行後の結果表示します。

  • LVRTE付きのインストーラリンク(LV2016版)
  • ソースコード(Project)(LV2014版)
  • ModbusPollでは一定時間間隔でポーリングできますが、スケーリングしたグラフ表示は出来ません。この点がLabVIEWで構築する意味合いかなと思っています。
    テンプレートがあれば比較的容易に短時間で構築出来ます。


    一応ModbusSlave側のコードも紹介しておきます。Arduino(STM32duino)のスケッチです。

    /*
     * 20200206 T.Wanibe STM32MINIShieldEvaluation用に書き直しました。
     * CoilData:16bit 4bit接続
     * DiscreteData:16bit 4bit接続
     * InputData:8ワード アドレス1:MCP4735の値を読み込む Address2:MCP280 温度 Address3:rerserved Address4:MCP280 気圧
     * HoldData:16ワード アドレス1の値が書き込まれたときにMCP4725のEEPROMを書き換え
     * I2Cデバイスが接続されていなくても動く様に対策していますが、必ずPulUp抵抗は実装してください。
     * 実際に動かしていて幾つか修正を思い立ちました。
     * ●クライアントがコネクト中はBuiltINLEDを転倒させる
     * ●LED&KEYのLEDをに割り付ける
     * ●LED&KEYで表示更新し続けるか設定出来るようにする。DO7のbitをフラグにしてみます。
     * iPhoneで提供されているModbusアプリがいくつかあり、アクセスしたところ、コネクトは普通に出来ます。ただ、通信がちゃんと出来ているのか確認出来るだけのMasterがないです。残念です。
     * STM32MINIShieldEvaluation基板の場合R8/R9に10kΩ実装することになります。
     * 
     * コンパイル結果を添えておきます。
     * 最大131072バイトのフラッシュメモリのうち、スケッチが58672バイト(44%)を使っています。
     * 最大20480バイトのRAMのうち、グローバル変数が5312バイト(25%)を使っていて、ローカル変数で15168バイト使うことができます。
     */
    #include <SPI.h>
    #include <Ethernet3.h>                  //WizNetからライブラリを取得する
    #include "MgsModbus.h"                  //このSketchと同じ階層に置く
    #include <EEPROM.h>
    #include <avr/pgmspace.h>
    #include <TextFinder.h>                 //WebSetting
    #include <Wire.h>
    //#include "Adafruit_HTU21DF.h"           //GY21  温度・湿度モジュールのドライバ
    //#include <SparkFunTSL2561.h>            //GY2161 照度モジュールのドライバ
    #include <MCP4725.h>                    //DAC
    #include <BME280I2C.h>                  //温湿度 気圧センサ
    #include <TM1638.h>
    #define dataPin                 PB4
    #define clockPin                PB3
    #define strobePin               PA15
    #define DEBUG                   1
    #define OK                      0
    #define NG                      1
    #define W550io_Rst              PA8     //STM32MINIShieldEvaluation基板に準拠
    #define SPI1_NSS_PIN            PA4     //SPI_1
    #define ModBusPort              502     //決まり事
    #define MbCoilDataLen           2       // length of the MbCoilDataLen array (Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbDiscreteDataLen       2       // length of the MbDiscreteDataLen array(Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbHoldDataLen           16      // length of the MbHoldDataLen array
    #define MbInputDataLen          8       // length of the MbInputDataLen array
                                            //実際には7つ実装しています。AI×4、温度(x100[℃])湿度(x100[%])照度(x1[lux])
    #define HttpPort                80
    #define LED                     PC13
    #define LED1                    PB8     //LED出力1
    #define LED2                    PB9     //LED出力2
    #define DAC_REF_VOLTAGE         3.3     //dac supply-reference voltage
    #define I2C_BUS_SPEED           400000  //i2c bus speed, 100 000Hz or 400 000Hz
    int     inByte          =       0;      // incoming serial byte
    // Ethernet settings (depending on MAC and Local network)
    byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD2};      //WIZNET
    byte ip[]               = {192, 168, 0, 202};
    byte dns_server[]       = {192, 168, 0, 1};
    byte gateway[]          = {192, 168, 0, 1};
    byte subnet[]           = {255, 255, 255, 0};
    int dOsPins[]           = {LED1,LED2};
    int dIsPins[]           = {PB11, PB10, PB1, PB0};
    //int aOsPins[]         = {};
    int aIsPins[]           = {PA0};
    //配列要素数を求めておく
    int DObits              = sizeof(dOsPins)/sizeof(int);
    int DIbits              = sizeof(dIsPins)/sizeof(int);
    int AIbits              = sizeof(aIsPins)/sizeof(int);
    char buf1[32];
    byte    ErrorTMP        = false;
    byte    ErrorHUM        = false;
    byte    ErrorPRS        = false;
    uint16_t value          = 0;
    float   voltage         = 0;
    float   temp(NAN), hum(NAN), pres(NAN);
    EthernetServer  webServer(HttpPort);
    EthernetClient  client;
    TM1638          LEDandKEY(dataPin, clockPin, strobePin);
    BME280I2C       bme;                                            // Default : forced mode, standby time = 1000 ms
    MCP4725 dac(MCP4725A0_ADDR_A00, DAC_REF_VOLTAGE);
    String          HTTP_req;                                       // stores the HTTP request
    MgsModbus       Mb;
    word            preData = Mb.MbHoldData[0];                     //初期値確認
    byte            lastKey;
    // これは、HTMLコードを「流す」ためのバッファーです。
    // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。
    char    buffer[100];
    // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。
    // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を
    // 使用する必要があります。そうしないと機能しません。
    const byte      ID = 0x92;
    /*
            EEPROMの有効なデータが「知っている」ビットであるかどうかを識別するために使用されます。
            これがEEPROMに書き込まれていれば、スケッチは前に実行されました
            これを使用するので、最初にこのスケッチを実行すると、上記の値が使用されます。 
            どのデータにどのEEPROMアドレスを使用しているかを定義する
     */
    const char      titleStr[]   = "STM32MINIShield";
    char            outputSTR[9];
    //----------------------------------
    void LANSetup(){
            int idcheck = EEPROM.read(0);
            if (idcheck != ID){
                    //idがIDの値ではない場合、
                    //このスケッチがシールドを設定する前に使用されていなかったことを意味します
                    //スケッチの始めに書かれた値を使用するだけです
            }
            if (idcheck == ID){
                    //idがIDと同じ値の場合、
                    //これは、このスケッチがシールドを設定するために使用されたことを意味します。
                    //EERPOMの値を読み取ってシールドを設定します。
                    for (int i = 0; i < 6; i++){
                            mac[i] = EEPROM.read(i+1);
                    }
                    for (int i = 0; i < 4; i++){
                            ip[i] = EEPROM.read(i+7);
                    }
                    for (int i = 0; i < 4; i++){
                            subnet[i] = EEPROM.read(i+11);
                    }
                    for (int i = 0; i < 4; i++){
                            gateway[i] = EEPROM.read(i+15);
                    }
            }
            Ethernet.begin(mac, ip);
    //      Ethernet.begin(mac);                            //DHCPの場合
    //      Ethernet.begin(mac, ip, subnet);                //SubnetMaskを意識した場合。
    //      Ethernet.begin(mac, ip, subnet, gateway);       //gatewayを意識した場合。
    }
    //--------------------------
    void SetWebPage( EthernetClient client){
            client.print(F("<!DOCTYPE HTML PUBLIC \"\"><html><HEAD><META http-equiv=\"Content-Type\" charset=UTF-8\">"));
            client.print(F("<META http-equiv=\"Content-Style-Type\"><TITLE>"));
            client.print(titleStr);
            client.print(F(" Setup Page</TITLE></HEAD>"));
            client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\"><BLOCKQUOTE><BLOCKQUOTE>"));
            client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">"));
            client.print(F("<tr><td>&nbsp;"));
            client.print(titleStr);
            client.print(F(" Setup Page</td></tr></table><br>"));
            //
            client.print(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>"));
            client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\"><table><tr><td>MAC:</td><td>"));
            client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
            client.print(mac[0],HEX);
            client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
            client.print(mac[1],HEX);
            client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
            client.print(mac[2],HEX);
            client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
            client.print(mac[3],HEX);
            client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
            client.print(mac[4],HEX);
            client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
            client.print(mac[5],HEX);
            //
            client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
            client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
            client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
            client.print(F("\"></td></tr><tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
            client.print(ip[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
            client.print(ip[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
            client.print(ip[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
            client.print(ip[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
            client.print(subnet[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
            client.print(subnet[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
            client.print(subnet[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
            client.print(subnet[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
            client.print(gateway[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
            client.print(gateway[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
            client.print(gateway[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
            client.print(gateway[3],DEC);
            client.print(F("\"></td></tr><tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
            //
            client.print(F("Onclick=\"document.getElementById('T2').value "));
            client.print(F("= hex2num(document.getElementById('T1').value);"));
            client.print(F("document.getElementById('T4').value = hex2num(document.getElementById('T3').value);"));
            client.print(F("document.getElementById('T6').value = hex2num(document.getElementById('T5').value);"));
            client.print(F("document.getElementById('T8').value = hex2num(document.getElementById('T7').value);"));
            client.print(F("document.getElementById('T10').value = hex2num(document.getElementById('T9').value);"));
            client.print(F("document.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
            //
            client.print(F("></td><td></td></tr></form></table></BLOCKQUOTE></BLOCKQUOTE></BODY></html>"));
    }
    //--------------------------
    void checkWebPage( EthernetClient client)
    {
            Serial.println(F("new webClient"));
            if (client) {
                    TextFinder  finder(client );
                    while (client.connected()) {
                            //digitalWrite(LED, HIGH);
                            if (client.available()) {
                                    //この部分はすべてのテキスト検索を行います。
                                    if( finder.find("GET /") ) {
                                            // 「setup」とい語が見つかった場合は、さらに探してください。
                                            // その単語が見つからない場合は、検索を停止して先に進みます。
                                            // これにより、後でスケッチに独自のWebページを配置できます。
                                            if (finder.findUntil("setup", "\n\r")){
                                                    // 「SBM」という単語が見つかった場合は、さらに探し続けます。
                                                    // その言葉が見つからない場合は、探して停止します。
                                                    // SUBMITボタンが押されていない、何も押されていないことを意味します
                                                    // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。
                                                    if (finder.findUntil("SBM", "\n\r")){
                                                            byte SET = finder.getValue();
                                                            // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、
                                                            // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。
                                                            while(finder.findUntil("DT", "\n\r")){
                                                                    int val = finder.getValue();
                                                                    // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。
                                                                    if(val >= 1 && val <= 6) {
                                                                            mac[val - 1] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。
                                                                    if(val >= 7 && val <= 10) {
                                                                            ip[val - 7] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。
                                                                    if(val >= 11 && val <= 14) {
                                                                            subnet[val - 11] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。
                                                                    if(val >= 15 && val <= 18) {
                                                                            gateway[val - 15] = finder.getValue();
                                                                    }
                                                            }
                                                            // すべてのデータを取得したので、EEPROMに保存できます
                                                            for (int i = 0 ; i < 6; i++){
                                                                    EEPROM.write(i + 1,mac[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 7, ip[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 11, subnet[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 15, gateway[i]);
                                                            }
                                                            // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                                                            EEPROM.write(0, ID); 
                                                            // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。
                                                            //ハードウェアリセットボタンを使用する必要があります。
                                                    }
                                                    // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。
                                                    client.println("HTTP/1.1 200 OK");
                                                    client.println("Content-Type: text/html");
                                                    client.println();
                                                    //
                                                    SetWebPage(client);
                                                    
                                                    break;
                                            }
                                    }
                                    client.println("HTTP/1.1 200 OK");
                                    client.println("Content-Type: text/html");
                                    client.println();
                                    // put your own html from here on
                                    client.print("IT WORKS: go to ");
                                    client.print(ip[0],DEC);
                                    for (int i= 1; i < 4; i++){
                                            client.print(".");
                                            client.print(ip[i],DEC);
                                    }
                                    client.print("/setup");
                                    // put your own html until here 
                                    break;  
                            }
                    }
                    //digitalWrite(LED, LOW);
                    delay(1);
                    client.stop();
            }     
    }
    //----------------------------
    int     GetDeviceInfo(){
            for (int i=0;i<DIbits;i++){
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit 論理反転
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i]=analogRead(aIsPins[i]);                               //U16
            }
            return OK;            
    }
    //----------------------------
    int     SetDeviceInfo(){
            for (int i=0;i<DObits;i++){          
                    digitalWrite(dOsPins[i],!Mb.GetBit(MB_FC_READ_COILS,i));                 //LEDはLOWで点灯                            
            }
            if(Mb.MbHoldData[0] != preData){
                    preData =       Mb.MbHoldData[0];
                    dac.setValue(preData, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF);
                    Serial.print(F("SetDAC = "));Serial.println(preData);
            }
            return OK;
    }
    //----------------------------
    int     SetI2CDeviceInfo(){
            //BME280計測
            double rf;
            BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
            BME280::PresUnit presUnit(BME280::PresUnit_Pa);
            bme.read(pres, temp, hum, tempUnit, presUnit);
            //InputDataのIndex=0に温度をセット INTにする必要があるので100倍して整数化 25.65->2565
            if(!ErrorTMP)   Mb.MbInputData[1]        = int(temp*100);
            //InputDataのIndex=1に湿度をセット INTにする必要があるので100倍して整数化 57.88->5788
            if(!ErrorHUM)   Mb.MbInputData[2]        = int(hum*100);
            //InputDataのIndex=1に湿度をセット INTにする必要がある[mbar]101300->1013 i16 に収めるため
            if(!ErrorPRS)   Mb.MbInputData[3]        = int(pres/100);
    }
    //--------------------------
    void printError(byte error){
            // If there's an I2C error, this function will
            // print out an explanation.
            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"));
            }
    }
    //----------------------------
    void setup()
    {
            // serial setup
            Serial.begin(115200);
            delay(1000);
            Serial.println(F("Serial interface started"));
            pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);
            SPI.begin();                                                    //Initialize the SPI_1 port.
            SPI.setBitOrder(MSBFIRST);                                      // Set the SPI_1 bit order
            SPI.setClockDivider(SPI_CLOCK_DIV4);                            //SPI_CLOCK_DIV16 (72 / 16 = 4.5 MHz SPI_1 speed)
                                                                            //SPI_CLOCK_DIV4 (72 / 4 = 18 MHz SPI_1 speed)
            //Ethernet3で使用可能なAPI
            pinMode(SPI1_NSS_PIN, OUTPUT);                                  //NIC_CS出力設定
            Ethernet.setCsPin(SPI1_NSS_PIN);                                //NIC_CSアサイン
            Ethernet.setRstPin(W550io_Rst);                                 //NIC_RSTアサイン
            //NICリセット処理
            pinMode(W550io_Rst, OUTPUT);
            digitalWrite(W550io_Rst, LOW);
            delay(10);                                                      //10msecパルス幅で初期化
            digitalWrite(W550io_Rst, HIGH);
            LANSetup();
            Serial.println(F("Ethernet interface started")); 
            Serial.print(F("My IP address: "));Serial.println(Ethernet.localIP());// print your local IP address:
            webServer.begin();
            //I2Cは接続されているかどうかチェックしてから対応することにした。
            Wire.begin();
            Serial.print(F("I2C_Start\n"));
            //BME280初期化 BMP280の対策
            while(!bme.begin())
            {
                    Serial.println(F("Could not find BME280 sensor!"));
                    delay(1000);
            }
            // bme.chipID(); // 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."));
                            ErrorHUM = true;
                            break;
                    default:
                            Serial.println(F("Found UNKNOWN sensor! Error!"));
                            ErrorTMP = true;
                            ErrorHUM = true;
                            ErrorPRS = true;
            }
            //DAC(MCP4725)の初期化
            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        
            //ModBusメモリ 初期化
            for(int i = 0;i<MbCoilDataLen;i++)      Mb.MbCoilData[i]=0x0000;//メモリ初期化
            for(int i = 0;i<MbDiscreteDataLen;i++)  Mb.MbDiscreteData[i]=0; //メモリ初期化
            for(int i = 0;i<MbHoldDataLen;i++)      Mb.MbHoldData[i]=0;     //メモリ初期化
            for(int i = 0;i<MbInputDataLen;i++)     Mb.MbInputData[i]=0;    //メモリ初期化
            //出力LED初期化
            for (int i=0;i<DObits;i++){          
                    Mb.SetBit(MB_FC_READ_COILS,i,false);                    //メモリマップはMb.MbData[0]
                    pinMode(dOsPins[i],OUTPUT);
                    digitalWrite(dOsPins[i], LOW);                         //LEDはHIGHで点灯                            
            }
            //入力pin初期化
            for (int i=0;i<DIbits;i++){
                    pinMode(dIsPins[i],INPUT);
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit メモリマップはMb.MbData[1]
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i] = analogRead(aIsPins[i]);             //U16
            }
            Serial.println(F("EndSetUp"));
    }
    //----------------------------
    int LoopCount = 0;
    void loop()
    {
            if(!LoopCount++)        Serial.println(F("LoopStart"));
            client = webServer.available();
            if(client){
                    //digitalWrite(LED, LOW);
                    checkWebPage(client);
            }else{
                    //digitalWrite(LED, HIGH);           
            }
            client.stop();                                                  // コネクションを閉じる。
            GetDeviceInfo();
            Mb.MbsRun();                                                    //SlaveServer
            SetDeviceInfo();
            SetI2CDeviceInfo();
            if(!(LoopCount%1000))   Serial.print(F("."));
            if(!(LoopCount%40000))  Serial.println(F(""));
            byte keys = LEDandKEY.getButtons();
            switch(keys){
                    case 0x01:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x02:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x04:
                            LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x10:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x20:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
            } 
            LEDandKEY.setLEDs(Mb.MbCoilData[0]>>8);                   //LED&KEYのLED点灯  
            if(Mb.GetBit(MB_FC_READ_COILS,7) && lastKey){
                    switch(lastKey){
                            case 0x01:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                                    break;
                            case 0x02:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                                    break;
                            case 0x04:
                                    LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                                    break;
                            case 0x10:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                                    break;
                            case 0x20:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                                    break;
                    }
            }        
    }
    //----------------------------

    SlaveデバイスのIPアドレスやMACアドレスはBluePill内に書き込んでいます。Webブラウザでアクセスして変更が可能です。


    ケースに入れてみました。

    タカチのOP-125と云うケースに穴を開けて入れてみました。厚みがちょっと足りないのですが、大きさとしては余裕がありすぎでした。

    ケースの外形サイズは
    100(W)×125(D)×45(H)
    で550円(税抜)です。

    底にボスがあり、5mmあります。ピッチが基板と合わないので、この高さを避けるようなスペーサが必要となり、高さが足りないことになります。

    材質がABSですので接着性はいいです。また加工もそんなに大変ではありませんが、フライス加工したいですね。


    ファームウエアを更新しました。LabVIEW(Master)側もそのままで使用可能です。※LabVIEW側に一寸バグがありました。後日修正します。
    少しぐらいModbusSlaveらしい機能が欲しいと思いました。

    変更点

    変更ではないのですが、計装で扱うことを考えると24Vの入出力を想定した追加モジュールも検討しておきます。

    使用しているライブラリには変更はありません。

    /*
     * 20200326 T.Wanibe LED&KEYの操作機能を見直しました。また、カウンタのキャリーオーバー時の対応を付加しました。
     * STM32MINIShieldEvaluation用に書き直しました。
     * CoilData:16bit 4bit接続
     * DiscreteData:16bit 4bit接続
     * InputData:8ワード アドレス1:MCP4735の値を読み込む Address2:MCP280 温度 Address3:rerserved Address4:MCP280 気圧
     * HoldData:16ワード アドレス1の値が書き込まれたときにMCP4725のEEPROMを書き換え
     * I2Cデバイスが接続されていなくても動く様に対策していますが、必ずPulUp抵抗は実装してください。
     * 実際に動かしていて幾つか修正を思い立ちました。
     * ●クライアントがコネクト中はBuiltINLEDを転倒させる
     * ●LED&KEYのLEDをに割り付ける
     * ●LED&KEYで表示更新し続けるか設定出来るようにする。DO7のbitをフラグにしてみます。
     * iPhoneで提供されているModbusアプリがいくつかあり、アクセスしたところ、コネクトは普通に出来ます。ただ、通信がちゃんと出来ているのか確認出来るだけのMasterがないです。残念です。
     * STM32MINIShieldEvaluation基板の場合R8/R9に10kΩ実装することになります。
     * 
     * コンパイル結果を添えておきます。
     * 最大131072バイトのフラッシュメモリのうち、スケッチが59048バイト(45%)を使っています。
     * 最大20480バイトのRAMのうち、グローバル変数が5320バイト(25%)を使っていて、ローカル変数で15160バイト使うことができます。
     */
    #include <SPI.h>
    #include <Ethernet3.h>                  //WizNetからライブラリを取得する
    #include "MgsModbus.h"                  //このSketchと同じ階層に置く
    #include <EEPROM.h>
    #include <avr/pgmspace.h>
    #include <TextFinder.h>                 //WebSetting
    #include <Wire.h>
    #include <MCP4725.h>                    //DAC
    #include <BME280I2C.h>                  //温湿度 気圧センサ
    #include <TM1638.h>
    #define dataPin                 PB4
    #define clockPin                PB3
    #define strobePin               PA15
    #define DEBUG                   1
    #define OK                      0
    #define NG                      1
    #define W550io_Rst              PA8     //STM32MINIShieldEvaluation基板に準拠
    #define SPI1_NSS_PIN            PA4     //SPI_1
    #define ModBusPort              502     //決まり事
    #define MbCoilDataLen           2       // length of the MbCoilDataLen array (Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbDiscreteDataLen       2       // length of the MbDiscreteDataLen array(Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbHoldDataLen           16      // length of the MbHoldDataLen array
    #define MbInputDataLen          8       // length of the MbInputDataLen array
                                            //実際には7つ実装しています。AI×4、温度(x100[℃])湿度(x100[%])照度(x1[lux])
    #define HttpPort                80
    #define LED                     PC13
    #define LED1                    PB8     //LED出力1
    #define LED2                    PB9     //LED出力2
    #define DAC_REF_VOLTAGE         3.3     //dac supply-reference voltage
    #define I2C_BUS_SPEED           400000  //i2c bus speed, 100 000Hz or 400 000Hz
    int     inByte          =       0;      // incoming serial byte
    // Ethernet settings (depending on MAC and Local network)
    byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD2};      //WIZNET
    byte ip[]               = {192, 168, 0, 202};
    byte dns_server[]       = {192, 168, 0, 1};
    byte gateway[]          = {192, 168, 0, 1};
    byte subnet[]           = {255, 255, 255, 0};
    int dOsPins[]           = {LED1,LED2};
    int dIsPins[]           = {PB11, PB10, PB1, PB0};
    //int aOsPins[]         = {};
    int aIsPins[]           = {PA0};
    //配列要素数を求めておく
    int DObits              = sizeof(dOsPins)/sizeof(int);
    int DIbits              = sizeof(dIsPins)/sizeof(int);
    int AIbits              = sizeof(aIsPins)/sizeof(int);
    char buf1[32];
    byte    ErrorTMP        = false;
    byte    ErrorHUM        = false;
    byte    ErrorPRS        = false;
    uint16_t value          = 0;
    float   voltage         = 0;
    float   temp(NAN), hum(NAN), pres(NAN);
    EthernetServer  webServer(HttpPort);
    EthernetClient  client;
    TM1638          LEDandKEY(dataPin, clockPin, strobePin);
    BME280I2C       bme;                                            // Default : forced mode, standby time = 1000 ms
    MCP4725 dac(MCP4725A0_ADDR_A00, DAC_REF_VOLTAGE);
    String          HTTP_req;                                       // stores the HTTP request
    MgsModbus       Mb;
    word            preData = Mb.MbHoldData[0];                     //初期値確認
    byte            lastKey;
    // これは、HTMLコードを「流す」ためのバッファーです。
    // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。
    char    buffer[100];
    // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。
    // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を
    // 使用する必要があります。そうしないと機能しません。
    const byte      ID = 0x92;
    /*
            EEPROMの有効なデータが「知っている」ビットであるかどうかを識別するために使用されます。
            これがEEPROMに書き込まれていれば、スケッチは前に実行されました
            これを使用するので、最初にこのスケッチを実行すると、上記の値が使用されます。 
            どのデータにどのEEPROMアドレスを使用しているかを定義する
     */
    const char      titleStr[]   = "STM32MINIShield";
    char            outputSTR[9];
    //----------------------------------
    void LANSetup(){
            int idcheck = EEPROM.read(0);
            if (idcheck != ID){
                    //idがIDの値ではない場合、
                    //このスケッチがシールドを設定する前に使用されていなかったことを意味します
                    //スケッチの始めに書かれた値を使用するだけです
            }
            if (idcheck == ID){
                    //idがIDと同じ値の場合、
                    //これは、このスケッチがシールドを設定するために使用されたことを意味します。
                    //EERPOMの値を読み取ってシールドを設定します。
                    for (int i = 0; i < 6; i++){
                            mac[i] = EEPROM.read(i+1);
                    }
                    for (int i = 0; i < 4; i++){
                            ip[i] = EEPROM.read(i+7);
                    }
                    for (int i = 0; i < 4; i++){
                            subnet[i] = EEPROM.read(i+11);
                    }
                    for (int i = 0; i < 4; i++){
                            gateway[i] = EEPROM.read(i+15);
                    }
            }
            Ethernet.begin(mac, ip);
    //      Ethernet.begin(mac);                            //DHCPの場合
    //      Ethernet.begin(mac, ip, subnet);                //SubnetMaskを意識した場合。
    //      Ethernet.begin(mac, ip, subnet, gateway);       //gatewayを意識した場合。
    }
    //--------------------------
    void SetWebPage( EthernetClient client){
            client.print(F("<!DOCTYPE HTML PUBLIC \"\"><html><HEAD><META http-equiv=\"Content-Type\" charset=UTF-8\">"));
            client.print(F("<META http-equiv=\"Content-Style-Type\"><TITLE>"));
            client.print(titleStr);
            client.print(F(" Setup Page</TITLE></HEAD>"));
            client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\"><BLOCKQUOTE><BLOCKQUOTE>"));
            client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">"));
            client.print(F("<tr><td>&nbsp;"));
            client.print(titleStr);
            client.print(F(" Setup Page</td></tr></table><br>"));
            //
            client.print(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>"));
            client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\"><table><tr><td>MAC:</td><td>"));
            client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
            client.print(mac[0],HEX);
            client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
            client.print(mac[1],HEX);
            client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
            client.print(mac[2],HEX);
            client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
            client.print(mac[3],HEX);
            client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
            client.print(mac[4],HEX);
            client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
            client.print(mac[5],HEX);
            //
            client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
            client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
            client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
            client.print(F("\"></td></tr><tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
            client.print(ip[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
            client.print(ip[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
            client.print(ip[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
            client.print(ip[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
            client.print(subnet[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
            client.print(subnet[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
            client.print(subnet[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
            client.print(subnet[3],DEC);
            //
            client.print(F("\"></td></tr><tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
            client.print(gateway[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
            client.print(gateway[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
            client.print(gateway[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
            client.print(gateway[3],DEC);
            client.print(F("\"></td></tr><tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
            //
            client.print(F("Onclick=\"document.getElementById('T2').value "));
            client.print(F("= hex2num(document.getElementById('T1').value);"));
            client.print(F("document.getElementById('T4').value = hex2num(document.getElementById('T3').value);"));
            client.print(F("document.getElementById('T6').value = hex2num(document.getElementById('T5').value);"));
            client.print(F("document.getElementById('T8').value = hex2num(document.getElementById('T7').value);"));
            client.print(F("document.getElementById('T10').value = hex2num(document.getElementById('T9').value);"));
            client.print(F("document.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
            //
            client.print(F("></td><td></td></tr></form></table></BLOCKQUOTE></BLOCKQUOTE></BODY></html>"));
    }
    //--------------------------
    void checkWebPage( EthernetClient client)
    {
            Serial.println(F("new webClient"));
            if (client) {
                    TextFinder  finder(client );
                    while (client.connected()) {
                            //digitalWrite(LED, HIGH);
                            if (client.available()) {
                                    //この部分はすべてのテキスト検索を行います。
                                    if( finder.find("GET /") ) {
                                            // 「setup」とい語が見つかった場合は、さらに探してください。
                                            // その単語が見つからない場合は、検索を停止して先に進みます。
                                            // これにより、後でスケッチに独自のWebページを配置できます。
                                            if (finder.findUntil("setup", "\n\r")){
                                                    // 「SBM」という単語が見つかった場合は、さらに探し続けます。
                                                    // その言葉が見つからない場合は、探して停止します。
                                                    // SUBMITボタンが押されていない、何も押されていないことを意味します
                                                    // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。
                                                    if (finder.findUntil("SBM", "\n\r")){
                                                            byte SET = finder.getValue();
                                                            // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、
                                                            // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。
                                                            while(finder.findUntil("DT", "\n\r")){
                                                                    int val = finder.getValue();
                                                                    // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。
                                                                    if(val >= 1 && val <= 6) {
                                                                            mac[val - 1] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。
                                                                    if(val >= 7 && val <= 10) {
                                                                            ip[val - 7] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。
                                                                    if(val >= 11 && val <= 14) {
                                                                            subnet[val - 11] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。
                                                                    if(val >= 15 && val <= 18) {
                                                                            gateway[val - 15] = finder.getValue();
                                                                    }
                                                            }
                                                            // すべてのデータを取得したので、EEPROMに保存できます
                                                            for (int i = 0 ; i < 6; i++){
                                                                    EEPROM.write(i + 1,mac[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 7, ip[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 11, subnet[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 15, gateway[i]);
                                                            }
                                                            // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                                                            EEPROM.write(0, ID); 
                                                            // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。
                                                            //ハードウェアリセットボタンを使用する必要があります。
                                                    }
                                                    // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。
                                                    client.println("HTTP/1.1 200 OK");
                                                    client.println("Content-Type: text/html");
                                                    client.println();
                                                    //
                                                    SetWebPage(client);
                                                    
                                                    break;
                                            }
                                    }
                                    client.println("HTTP/1.1 200 OK");
                                    client.println("Content-Type: text/html");
                                    client.println();
                                    // put your own html from here on
                                    client.print("IT WORKS: go to ");
                                    client.print(ip[0],DEC);
                                    for (int i= 1; i < 4; i++){
                                            client.print(".");
                                            client.print(ip[i],DEC);
                                    }
                                    client.print("/setup");
                                    // put your own html until here 
                                    break;  
                            }
                    }
                    //digitalWrite(LED, LOW);
                    delay(1);
                    client.stop();
            }     
    }
    //----------------------------
    int     GetDeviceInfo(){
            for (int i=0;i<DIbits;i++){
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit 論理反転
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i]=analogRead(aIsPins[i]);                               //U16
            }
            return OK;            
    }
    //----------------------------
    int     SetDeviceInfo(){
            for (int i=0;i<DObits;i++){          
                    digitalWrite(dOsPins[i],!Mb.GetBit(MB_FC_READ_COILS,i));                 //LEDはLOWで点灯                            
            }
            if(Mb.MbHoldData[0] != preData){
                    preData =       Mb.MbHoldData[0];
                    dac.setValue(preData, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF);
                    Serial.print(F("SetDAC = "));Serial.println(preData);
            }
            return OK;
    }
    //----------------------------
    int     SetI2CDeviceInfo(){
            //BME280計測
            double rf;
            BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
            BME280::PresUnit presUnit(BME280::PresUnit_Pa);
            bme.read(pres, temp, hum, tempUnit, presUnit);
            //InputDataのIndex=0に温度をセット INTにする必要があるので100倍して整数化 25.65->2565
            if(!ErrorTMP)   Mb.MbInputData[1]        = int(temp*100);
            //InputDataのIndex=1に湿度をセット INTにする必要があるので100倍して整数化 57.88->5788
            if(!ErrorHUM)   Mb.MbInputData[2]        = int(hum*100);
            //InputDataのIndex=1に湿度をセット INTにする必要がある[mbar]101300->1013 i16 に収めるため
            if(!ErrorPRS)   Mb.MbInputData[3]        = int(pres/100);
    }
    //--------------------------
    void printError(byte error){
            // If there's an I2C error, this function will
            // print out an explanation.
            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"));
            }
    }
    //----------------------------
    void setup()
    {
            // serial setup
            Serial.begin(115200);
            delay(1000);
            Serial.println(F("Serial interface started"));
            pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);
            SPI.begin();                                                    //Initialize the SPI_1 port.
            SPI.setBitOrder(MSBFIRST);                                      // Set the SPI_1 bit order
            SPI.setClockDivider(SPI_CLOCK_DIV4);                            //SPI_CLOCK_DIV16 (72 / 16 = 4.5 MHz SPI_1 speed)
                                                                            //SPI_CLOCK_DIV4 (72 / 4 = 18 MHz SPI_1 speed)
            //Ethernet3で使用可能なAPI
            pinMode(SPI1_NSS_PIN, OUTPUT);                                  //NIC_CS出力設定
            Ethernet.setCsPin(SPI1_NSS_PIN);                                //NIC_CSアサイン
            Ethernet.setRstPin(W550io_Rst);                                 //NIC_RSTアサイン
            //NICリセット処理
            pinMode(W550io_Rst, OUTPUT);
            digitalWrite(W550io_Rst, LOW);
            delay(10);                                                      //10msecパルス幅で初期化
            digitalWrite(W550io_Rst, HIGH);
            LANSetup();
            Serial.println(F("Ethernet interface started")); 
            Serial.print(F("My IP address: "));Serial.println(Ethernet.localIP());// print your local IP address:
            webServer.begin();
            //I2Cは接続されているかどうかチェックしてから対応することにした。
            Wire.begin();
            Serial.print(F("I2C_Start\n"));
            //BME280初期化 BMP280の対策
            while(!bme.begin())
            {
                    Serial.println(F("Could not find BME280 sensor!"));
                    delay(1000);
            }
            // bme.chipID(); // 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."));
                            ErrorHUM = true;
                            break;
                    default:
                            Serial.println(F("Found UNKNOWN sensor! Error!"));
                            ErrorTMP = true;
                            ErrorHUM = true;
                            ErrorPRS = true;
            }
            //DAC(MCP4725)の初期化
            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        
            //ModBusメモリ 初期化
            for(int i = 0;i<MbCoilDataLen;i++)      Mb.MbCoilData[i]=0x0000;//メモリ初期化
            for(int i = 0;i<MbDiscreteDataLen;i++)  Mb.MbDiscreteData[i]=0; //メモリ初期化
            for(int i = 0;i<MbHoldDataLen;i++)      Mb.MbHoldData[i]=0;     //メモリ初期化
            for(int i = 0;i<MbInputDataLen;i++)     Mb.MbInputData[i]=0;    //メモリ初期化
            //出力LED初期化
            for (int i=0;i<DObits;i++){          
                    Mb.SetBit(MB_FC_READ_COILS,i,false);                    //メモリマップはMb.MbData[0]
                    pinMode(dOsPins[i],OUTPUT);
                    digitalWrite(dOsPins[i], LOW);                         //LEDはHIGHで点灯                            
            }
            //入力pin初期化
            for (int i=0;i<DIbits;i++){
                    pinMode(dIsPins[i],INPUT);
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit メモリマップはMb.MbData[1]
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i] = analogRead(aIsPins[i]);             //U16
            }
            Serial.println(F("EndSetUp"));
    }
    //----------------------------
    int LoopCount = 0;
    void loop()
    {
            if(!LoopCount++)                Serial.println(F("LoopStart"));
            if(LoopCount > 0x7FFFFFF0)      LoopCount = 0;
            client = webServer.available();
            if(client){
                    //digitalWrite(LED, LOW);
                    checkWebPage(client);
            }else{
                    //digitalWrite(LED, HIGH);           
            }
            client.stop();                                                  // コネクションを閉じる。
            GetDeviceInfo();
            Mb.MbsRun();                                                    //SlaveServer
            SetDeviceInfo();
            SetI2CDeviceInfo();
            if(!(LoopCount%1000))           Serial.print(F("."));
            if(!(LoopCount%40000))          Serial.println(F(""));
            byte keys = LEDandKEY.getButtons();
            switch(keys){
                    case 0x01:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x02:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x04:
                            LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x10:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x20:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x80:
                            if(Mb.GetBit(MB_FC_READ_COILS,7))       Mb.SetBit(MB_FC_READ_COILS,7,false);
                            else                                    Mb.SetBit(MB_FC_READ_COILS,7,true);
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;                        
            } 
            //LEDandKEY.setLEDs(Mb.MbCoilData[0]>>8);                   //LED&KEYのLED点灯  仕様変更
            byte LEDPTN = 0;
            for(int i = 0;i < 4;i++){
                    word L_limit = Mb.MbHoldData[2*i+8];
                    word H_Limit = Mb.MbHoldData[2*i+9];
                    word ActualV = Mb.MbInputData[i];
                    Serial.print(F("L_limit = ")); Serial.print(L_limit);
                    Serial.print(F("\tH_Limit = ")); Serial.print(H_Limit);
                    Serial.print(F("\tActualV = ")); Serial.print(ActualV);
                    if((L_limit !=0) && (ActualV < L_limit)){
                            LEDPTN = LEDPTN | 1<<(2*i);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,false);
                    }
                    if((H_Limit !=0) && (ActualV > H_Limit)){
                            LEDPTN = LEDPTN | 1<<(2*i+1);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,false);
                    }
                    Serial.print(F("\tLEDPTN = 0x"));Serial.println(LEDPTN,HEX);
            }
            LEDandKEY.setLEDs(LEDPTN);
            if(Mb.GetBit(MB_FC_READ_COILS,7) && lastKey){
                    switch(lastKey){
                            case 0x01:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                                    break;
                            case 0x02:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                                    break;
                            case 0x04:
                                    LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                                    break;
                            case 0x10:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                                    break;
                            case 0x20:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                                    break;
                    }
            }        
    }
    //----------------------------



    ファームウエアを更新しました。LabVIEW(Master)側もそのままで使用可能です。
    大きな変更ではないのですが、起動時にNTPサーバにアクセスして、時刻取得し、STM32F内部のRTCに時刻登録するようにしました。幾つかインクルードファイルを追加しています。

    変更点

    使用しているライブラリには変更はありませんが、インクルードしているヘッダは増えています。

    /*
     * 20200810 T.Wanibe RTC追加 NTPによる時刻同期 起動時にアクセスしてセットする
     * LED&KEYの操作機能を見直しました。また、カウンタのキャリーオーバー時の対応を付加しました。
     * STM32MINIShieldEvaluation用に書き直しました。
     * CoilData:16bit 4bit接続
     * DiscreteData:16bit 4bit接続
     * InputData:8ワード アドレス1:MCP4735の値を読み込む Address2:MCP280 温度 Address3:rerserved Address4:MCP280 気圧
     * HoldData:16ワード アドレス1の値が書き込まれたときにMCP4725のEEPROMを書き換え
     * I2Cデバイスが接続されていなくても動く様に対策していますが、必ずPulUp抵抗は実装してください。
     * 実際に動かしていて幾つか修正を思い立ちました。
     * ●クライアントがコネクト中はBuiltINLEDを転倒させる
     * ●LED&KEYのLEDをに割り付ける
     * ●LED&KEYで表示更新し続けるか設定出来るようにする。DO7のbitをフラグにしてみます。
     * iPhoneで提供されているModbusアプリがいくつかあり、アクセスしたところ、コネクトは普通に出来ます。ただ、通信がちゃんと出来ているのか確認出来るだけのMasterがないです。残念です。
     * STM32MINIShieldEvaluation基板の場合R8/R9に10kΩ実装することになります。
     * 
     * コンパイル結果を添えておきます。
     * 最大131072バイトのフラッシュメモリのうち、スケッチが61440バイト(46%)を使っています。
     * 最大20480バイトのRAMのうち、グローバル変数が5456バイト(26%)を使っていて、ローカル変数で15024バイト使うことができます。
     */
    #include <SPI.h>
    #include <Ethernet3.h>                  //WizNetからライブラリを取得する
    #include <EthernetUdp3.h>
    #include "MgsModbus.h"                  //このSketchと同じ階層に置く
    #include <EEPROM.h>
    #include <avr/pgmspace.h>
    #include <TextFinder.h>                 //WebSetting
    #include <Wire.h>
    #include <MCP4725.h>                    //DAC
    #include <BME280I2C.h>                  //温湿度 気圧センサ
    #include <RTClock.h>
    #include <TM1638.h>
    #define dataPin                 PB4
    #define clockPin                PB3
    #define strobePin               PA15
    #define DEBUG                   1
    #define OK                      0
    #define NG                      1
    #define W550io_Rst              PA8     //STM32MINIShieldEvaluation基板に準拠
    #define SPI1_NSS_PIN            PA4     //SPI_1
    #define ModBusPort              502     //決まり事
    #define MbCoilDataLen           2       // length of the MbCoilDataLen array (Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbDiscreteDataLen       2       // length of the MbDiscreteDataLen array(Equivalent16bit*2)
                                            //実際には4bitしか実装していません。
    #define MbHoldDataLen           16      // length of the MbHoldDataLen array
    #define MbInputDataLen          8       // length of the MbInputDataLen array
                                            //実際には7つ実装しています。AI×4、温度(x100[℃])湿度(x100[%])照度(x1[lux])
    #define HttpPort                80
    #define LED                     PC13
    #define LED1                    PB8     //LED出力1
    #define LED2                    PB9     //LED出力2
    #define DAC_REF_VOLTAGE         3.3     //dac supply-reference voltage
    #define I2C_BUS_SPEED           400000  //i2c bus speed, 100 000Hz or 400 000Hz
    int     inByte          =       0;      // incoming serial byte
    // Ethernet settings (depending on MAC and Local network)
    byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD2};      //WIZNET
    byte ip[]               = {192, 168, 0, 202};
    byte dns_server[]       = {192, 168, 0, 1};
    byte gateway[]          = {192, 168, 0, 1};
    byte subnet[]           = {255, 255, 255, 0};
    int dOsPins[]           = {LED1,LED2};
    int dIsPins[]           = {PB11, PB10, PB1, PB0};
    //int aOsPins[]         = {};
    int aIsPins[]           = {PA0};
    //byte timeServer[]     = {130, 69, 251, 23};                   //東京大学  ntp.nc.u-tokyo.ac.jp 2020/08/10 pingが通らなかった
    byte timeServer[]       = {133, 31, 180, 6};                    //東京理科大学 ntp.nc.u-tokyo.ac.jp   2020/08/10 9msec応答
    //byte timeServer[]     = {130, 34, 11,  117};                  //東北大学  ntp1.tohoku.ac.jp       2020/08/10 14msec応答
    //byte timeServer[]     = {130, 34, 48,  32};                   //東北大学  ntp2.tohoku.ac.jp       2020/08/10 pingが通らなかった
    //配列要素数を求めておく
    int DObits              = sizeof(dOsPins)/sizeof(int);
    int DIbits              = sizeof(dIsPins)/sizeof(int);
    int AIbits              = sizeof(aIsPins)/sizeof(int);
    char buf1[32];
    byte    ErrorTMP        = false;
    byte    ErrorHUM        = false;
    byte    ErrorPRS        = false;
    uint16_t value          = 0;
    float   voltage         = 0;
    float   temp(NAN), hum(NAN), pres(NAN);
    const int NTP_PACKET_SIZE       = 48;                           // NTP time is in the first 48 bytes of message
    byte    packetBuffer[NTP_PACKET_SIZE];                          //buffer to hold incoming & outgoing packets
    char    s[16];                                                 // for sprintf
    EthernetServer  webServer(HttpPort);
    EthernetClient  client;
    TM1638          LEDandKEY(dataPin, clockPin, strobePin);
    BME280I2C       bme;                                            // Default : forced mode, standby time = 1000 ms
    MCP4725 dac(MCP4725A0_ADDR_A00, DAC_REF_VOLTAGE);
    String          HTTP_req;                                       // stores the HTTP request
    MgsModbus       Mb;
    word            preData = Mb.MbHoldData[0];                     //初期値確認
    byte            lastKey;
    // これは、HTMLコードを「流す」ためのバッファーです。
    // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。
    char    buffer[100];
    // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。
    // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を
    // 使用する必要があります。そうしないと機能しません。
    //
    //RTC
    RTClock rtclock (RTCSEL_LSE);                                   // initialise
    const int       timeZone        = 9;                            // change to your timezone
    time_t  tt, tt1;
    tm_t    mtt;
    uint8_t dateread[11];
    EthernetUDP Udp;
    unsigned int localPort  = 8888;                                 // local port to listen for UDP packets
    
    const byte      ID = 0x92;
    /*
            EEPROMの有効なデータが「知っている」ビットであるかどうかを識別するために使用されます。
            これがEEPROMに書き込まれていれば、スケッチは前に実行されました
            これを使用するので、最初にこのスケッチを実行すると、上記の値が使用されます。 
            どのデータにどのEEPROMアドレスを使用しているかを定義する
     */
    const char      titleStr[]   = "STM32MINIShield";
    char            outputSTR[9];
    //----------------------------------
    void LANSetup(){
            int idcheck = EEPROM.read(0);
            if (idcheck != ID){
                    //idがIDの値ではない場合、
                    //このスケッチがシールドを設定する前に使用されていなかったことを意味します
                    //スケッチの始めに書かれた値を使用するだけです
            }
            if (idcheck == ID){
                    //idがIDと同じ値の場合、
                    //これは、このスケッチがシールドを設定するために使用されたことを意味します。
                    //EERPOMの値を読み取ってシールドを設定します。
                    for (int i = 0; i < 6; i++){
                            mac[i] = EEPROM.read(i+1);
                    }
                    for (int i = 0; i < 4; i++){
                            ip[i] = EEPROM.read(i+7);
                    }
                    for (int i = 0; i < 4; i++){
                            subnet[i] = EEPROM.read(i+11);
                    }
                    for (int i = 0; i < 4; i++){
                            gateway[i] = EEPROM.read(i+15);
                    }
    		//アクセスNTPサーバー
                    for (int i = 0; i < 4; i++){
                            timeServer[i] = EEPROM.read(i+19);
                    }
            }
            Ethernet.begin(mac, ip);
    //      Ethernet.begin(mac);                            //DHCPの場合
    //      Ethernet.begin(mac, ip, subnet);                //SubnetMaskを意識した場合。
    //      Ethernet.begin(mac, ip, subnet, gateway);       //gatewayを意識した場合。
    }
    //--------------------------
    void SetWebPage( EthernetClient client){
            client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=UTF-8\">\n"));
            client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>"));
            client.print(titleStr);
            client.print(F(" Setup Page</TITLE>\n</HEAD>\n"));
            client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\">\n<BLOCKQUOTE><BLOCKQUOTE>\n"));
            client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">\n"));
            client.print(F("<tr><td>"));
            client.print(titleStr);
            client.print(F(" Setup Page</td></tr></table><br>\n"));
            //
            client.print(F("<script>\n\tfunction hex2num (s_hex) {\n\t\teval(\"var n_num=0X\" + s_hex);\n\t\treturn n_num;\n\t}\n</script>\n"));
            client.print(F("<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\">\n<table>\n<tr><td>MAC:</td><td>"));
            client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
            client.print(mac[0],HEX);
            client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
            client.print(mac[1],HEX);
            client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
            client.print(mac[2],HEX);
            client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
            client.print(mac[3],HEX);
            client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
            client.print(mac[4],HEX);
            client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
            client.print(mac[5],HEX);
            //
            client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
            client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
            client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
            client.print(F("\"></td></tr>\n<tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
            client.print(ip[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
            client.print(ip[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
            client.print(ip[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
            client.print(ip[3],DEC);
            //
            client.print(F("\"></td></tr>\n<tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
            client.print(subnet[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
            client.print(subnet[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
            client.print(subnet[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
            client.print(subnet[3],DEC);
            //
            client.print(F("\"></td></tr>\n<tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
            client.print(gateway[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
            client.print(gateway[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
            client.print(gateway[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
            client.print(gateway[3],DEC);
            //
            //client.print(F("\"></td></tr>\n<tr><td><br></td COLSPAN='2'>\n<HR ALIGN=LEFT>"));
            client.print(F("\"></td></tr><tr><td COLSPAN='2'>\n<HR ALIGN=CENTER></td><td>"));                      //1行空けたい
    	//
            client.print(F("</td></tr><tr><td>NTPd: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT19\" value=\""));
            client.print(timeServer[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT20\" value=\""));
            client.print(timeServer[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT21\" value=\""));
            client.print(timeServer[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT22\" value=\""));
            client.print(timeServer[3],DEC);
            client.print(F("\"></td></tr>\n<tr><td><br></td></tr><tr><td><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
            //
            client.print(F("Onclick=\""));
            client.print(F("document.getElementById('T2').value = hex2num(document.getElementById('T1').value);\n"));
            client.print(F("\t\tdocument.getElementById('T4').value = hex2num(document.getElementById('T3').value);\n"));
            client.print(F("\t\tdocument.getElementById('T6').value = hex2num(document.getElementById('T5').value);\n"));
            client.print(F("\t\tdocument.getElementById('T8').value = hex2num(document.getElementById('T7').value);\n"));
            client.print(F("\t\tdocument.getElementById('T10').value = hex2num(document.getElementById('T9').value);\n"));
            client.print(F("\t\tdocument.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
            //
            client.print(F("></td><td>\n</td></tr></form>\n</table>\n</BLOCKQUOTE></BLOCKQUOTE>\n</BODY>\n</html>\n"));
    }
    //--------------------------
    void checkWebPage( EthernetClient client)
    {
            Serial.println(F("new webClient"));
            if (client) {
                    TextFinder  finder(client );
                    while (client.connected()) {
                            //digitalWrite(LED, HIGH);
                            if (client.available()) {
                                    //この部分はすべてのテキスト検索を行います。
                                    if( finder.find("GET /") ) {
                                            // 「setup」とい語が見つかった場合は、さらに探してください。
                                            // その単語が見つからない場合は、検索を停止して先に進みます。
                                            // これにより、後でスケッチに独自のWebページを配置できます。
                                            if (finder.findUntil("setup", "\n\r")){
                                                    // 「SBM」という単語が見つかった場合は、さらに探し続けます。
                                                    // その言葉が見つからない場合は、探して停止します。
                                                    // SUBMITボタンが押されていない、何も押されていないことを意味します
                                                    // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。
                                                    if (finder.findUntil("SBM", "\n\r")){
                                                            byte SET = finder.getValue();
                                                            // これで、「DT」という文字を探している間に、「DT」の後ろにあるすべての数字を覚えて、
                                                            // 値を一致させて、mac、ip、subnet、およびgatewayに入れる必要があります。
                                                            while(finder.findUntil("DT", "\n\r")){
                                                                    int val = finder.getValue();
                                                                    // 「DT」のvalが1?6の場合、対応する値はMAC値でなければなりません。
                                                                    if(val >= 1 && val <= 6) {
                                                                            mac[val - 1] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが7?10の場合、対応する値はIP値でなければなりません。
                                                                    if(val >= 7 && val <= 10) {
                                                                            ip[val - 7] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが11?14の場合、対応する値はMASK値でなければなりません。
                                                                    if(val >= 11 && val <= 14) {
                                                                            subnet[val - 11] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが15?18の場合、対応する値はGW値でなければなりません。
                                                                    if(val >= 15 && val <= 18) {
                                                                            gateway[val - 15] = finder.getValue();
                                                                    }
                                                                    // 「DT」のvalが19?22の場合、対応する値はTimeServer値でなければなりません。
                                                                    if(val >= 19 && val <= 22) {
                                                                            timeServer[val - 19] = finder.getValue();
                                                                    }                                                        }
                                                            // すべてのデータを取得したので、EEPROMに保存できます
                                                            for (int i = 0 ; i < 6; i++){
                                                                    EEPROM.write(i + 1,mac[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 7, ip[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 11, subnet[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 15, gateway[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 19, timeServer[i]);
                                                            }                                                        // IDを既知のビットに設定します。したがって、Arduinoをリセットすると、EEPROM値が使用されます。
                                                            EEPROM.write(0, ID); 
                                                            // すべてのデータがEEPROMに書き込まれている場合、arduinoをリセットする必要があります。
                                                            //ハードウェアリセットボタンを使用する必要があります。
                                                    }
                                                    // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。
                                                    client.println("HTTP/1.1 200 OK");
                                                    client.println("Content-Type: text/html");
                                                    client.println();
                                                    //
                                                    SetWebPage(client);
                                                    
                                                    break;
                                            }
                                    }
                                    pageIntroduction(client);
                                    break;  
                            }
                    }
                    //digitalWrite(LED, LOW);
                    delay(1);
                    client.stop();
            }     
    }
    //----------------------------
    void pageIntroduction( EthernetClient client){
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println();
            // put your own html from here on
            client.print("<HTML>\n<BODY>\nIT WORKS: go to <B><A HREF=\"https://");
            client.print(ip[0],DEC);
            for (int i= 1; i < 4; i++){
                    client.print(".");
                    client.print(ip[i],DEC);
            }
            client.print("/setup\">");
            client.print(ip[0],DEC);
            for (int i= 1; i < 4; i++){
                    client.print(".");
                    client.print(ip[i],DEC);
            }
            client.print("/setup</A></B>\n</BODY>\n</HTML>\n");
            // put your own html until here 
    }
    //----------------------------
    int     GetDeviceInfo(){
            for (int i=0;i<DIbits;i++){
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit 論理反転
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i]=analogRead(aIsPins[i]);                               //U16
            }
            return OK;            
    }
    //----------------------------
    int     SetDeviceInfo(){
            for (int i=0;i<DObits;i++){          
                    digitalWrite(dOsPins[i],!Mb.GetBit(MB_FC_READ_COILS,i));                 //LEDはLOWで点灯                            
            }
            if(Mb.MbHoldData[0] != preData){
                    preData =       Mb.MbHoldData[0];
                    dac.setValue(preData, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF);
                    Serial.print(F("SetDAC = "));Serial.println(preData);
            }
            return OK;
    }
    //----------------------------
    int     SetI2CDeviceInfo(){
            //BME280計測
            double rf;
            BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
            BME280::PresUnit presUnit(BME280::PresUnit_Pa);
            bme.read(pres, temp, hum, tempUnit, presUnit);
            //InputDataのIndex=0に温度をセット INTにする必要があるので100倍して整数化 25.65->2565
            if(!ErrorTMP)   Mb.MbInputData[1]        = int(temp*100);
            //InputDataのIndex=1に湿度をセット INTにする必要があるので100倍して整数化 57.88->5788
            if(!ErrorHUM)   Mb.MbInputData[2]        = int(hum*100);
            //InputDataのIndex=1に湿度をセット INTにする必要がある[mbar]101300->1013 i16 に収めるため
            if(!ErrorPRS)   Mb.MbInputData[3]        = int(pres/100);
    }
    //--------------------------
    void printError(byte error){
            // If there's an I2C error, this function will
            // print out an explanation.
            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"));
            }
    }
    //----------------------------
    time_t getNtpTime(){
            while (Udp.parsePacket() > 0) ;                                 // discard any previously received packets
            Serial.println(F("Transmit NTP Request"));
            sendNTPpacket(timeServer);
            uint32_t beginWait = millis();
            while (millis() - beginWait < 1500) {
                    int size = Udp.parsePacket();
                    if (size >= NTP_PACKET_SIZE) {
                            Serial.println(F("Receive NTP Response"));
                            Udp.read(packetBuffer, NTP_PACKET_SIZE);        // read packet into the buffer
                            unsigned long secsSince1900;
                            // convert four bytes starting at location 40 to a long integer
                            secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
                            secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
                            secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
                            secsSince1900 |= (unsigned long)packetBuffer[43];
                            return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
                    }
            }
            Serial.println(F("No NTP Response :-("));
            return 0;                                                       // return 0 if unable to get the time
    }
    //----------------------------send an NTP request to the time server at the given address
    void sendNTPpacket(byte address[]){
            // set all bytes in the buffer to 0
            memset(packetBuffer, 0, NTP_PACKET_SIZE);
            // Initialize values needed to form NTP request(see URL above for details on the packets)
            packetBuffer[0]         = 0b11100011;                           // LI, Version, Mode
            packetBuffer[1]         = 0;                                    // Stratum, or type of clock
            packetBuffer[2]         = 6;                                    // ポーリング64秒デフォルト値
            packetBuffer[3]         = 0xEC;                                 // 精度
            // 8 bytes of zero for Root Delay & Root Dispersion
            packetBuffer[12]        = 49;                                   // '1'
            packetBuffer[13]        = 0x4E;                                 // 'N'
            packetBuffer[14]        = 49;                                   // '1'
            packetBuffer[15]        = 52;                                   // '4'
            // all NTP fields have been given values, now you can send a packet requesting a timestamp:                 
            Udp.beginPacket(address, 123);                                  //NTP requests are to port 123
            Udp.write(packetBuffer, NTP_PACKET_SIZE);
            Udp.endPacket();
    }
    //----------------------------
    void setup()
    {
            // serial setup
            Serial.begin(115200);
            delay(1000);
            Serial.println(F("Serial interface started"));
            pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);
            SPI.begin();                                                    //Initialize the SPI_1 port.
            SPI.setBitOrder(MSBFIRST);                                      // Set the SPI_1 bit order
            SPI.setClockDivider(SPI_CLOCK_DIV4);                            //SPI_CLOCK_DIV16 (72 / 16 = 4.5 MHz SPI_1 speed)
                                                                            //SPI_CLOCK_DIV4 (72 / 4 = 18 MHz SPI_1 speed)
            //Ethernet3で使用可能なAPI
            pinMode(SPI1_NSS_PIN, OUTPUT);                                  //NIC_CS出力設定
            Ethernet.setCsPin(SPI1_NSS_PIN);                                //NIC_CSアサイン
            Ethernet.setRstPin(W550io_Rst);                                 //NIC_RSTアサイン
            //NICリセット処理
            pinMode(W550io_Rst, OUTPUT);
            digitalWrite(W550io_Rst, LOW);
            delay(10);                                                      //10msecパルス幅で初期化
            digitalWrite(W550io_Rst, HIGH);
            LANSetup();
            Serial.println(F("Ethernet interface started")); 
            Serial.print(F("My IP address: "));Serial.println(Ethernet.localIP());// print your local IP address:
            webServer.begin();
            //I2Cは接続されているかどうかチェックしてから対応することにした。
            Wire.begin();
            Serial.print(F("I2C_Start\n"));
            //BME280初期化 BMP280の対策
            while(!bme.begin())
            {
                    Serial.println(F("Could not find BME280 sensor!"));
                    delay(1000);
            }
            // bme.chipID(); // 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."));
                            ErrorHUM = true;
                            break;
                    default:
                            Serial.println(F("Found UNKNOWN sensor! Error!"));
                            ErrorTMP = true;
                            ErrorHUM = true;
                            ErrorPRS = true;
            }
            //DAC(MCP4725)の初期化
            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        
            //ModBusメモリ 初期化
            for(int i = 0;i<MbCoilDataLen;i++)      Mb.MbCoilData[i]=0x0000;//メモリ初期化
            for(int i = 0;i<MbDiscreteDataLen;i++)  Mb.MbDiscreteData[i]=0; //メモリ初期化
            for(int i = 0;i<MbHoldDataLen;i++)      Mb.MbHoldData[i]=0;     //メモリ初期化
            for(int i = 0;i<MbInputDataLen;i++)     Mb.MbInputData[i]=0;    //メモリ初期化
            //出力LED初期化
            for (int i=0;i<DObits;i++){          
                    Mb.SetBit(MB_FC_READ_COILS,i,false);                    //メモリマップはMb.MbData[0]
                    pinMode(dOsPins[i],OUTPUT);
                    digitalWrite(dOsPins[i], LOW);                         //LEDはHIGHで点灯                            
            }
            //入力pin初期化
            for (int i=0;i<DIbits;i++){
                    pinMode(dIsPins[i],INPUT);
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit メモリマップはMb.MbData[1]
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i] = analogRead(aIsPins[i]);             //U16
            }
            //NTPサーバにアクセスして時刻セット
            Udp.begin(localPort);
            rtclock.setTime(getNtpTime());
            //
            Serial.println(F("EndSetUp"));
    }
    //----------------------------
    int LoopCount = 0;
    void loop()
    {
            if(!LoopCount++)                Serial.println(F("LoopStart"));
            if(LoopCount > 0x7FFFFFF0)      LoopCount = 0;
            client = webServer.available();
            if(client){
                    //digitalWrite(LED, LOW);
                    checkWebPage(client);
            }else{
                    //digitalWrite(LED, HIGH);           
            }
            client.stop();                                                  // コネクションを閉じる。
            GetDeviceInfo();
            Mb.MbsRun();                                                    //SlaveServer
            SetDeviceInfo();
            SetI2CDeviceInfo();
            if(!(LoopCount%1000))           Serial.print(F("."));
            if(!(LoopCount%40000))          Serial.println(F(""));
            byte keys = LEDandKEY.getButtons();
            switch(keys){
                    case 0x01:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x02:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x04:
                            LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x08:                                                              //20200810 追加
                            rtclock.getTime(mtt);
                            sprintf(s,"  %02u%02u%02u",mtt.hour, mtt.minute, mtt.second);
                            LEDandKEY.setDisplayToString(s);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x10:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x20:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x40:                                      //20200810 追加
                            rtclock.getTime(mtt);
                            sprintf(s, "%04u%02u%02u",mtt.year+1970, mtt.month, mtt.day);
                            LEDandKEY.setDisplayToString(s);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x80:
                            if(Mb.GetBit(MB_FC_READ_COILS,7))       Mb.SetBit(MB_FC_READ_COILS,7,false);
                            else                                    Mb.SetBit(MB_FC_READ_COILS,7,true);
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;                        
            } 
            //LEDandKEY.setLEDs(Mb.MbCoilData[0]>>8);                   //LED&KEYのLED点灯  仕様変更
            byte LEDPTN = 0;
            for(int i = 0;i < 4;i++){
                    word L_limit = Mb.MbHoldData[2*i+8];
                    word H_Limit = Mb.MbHoldData[2*i+9];
                    word ActualV = Mb.MbInputData[i];
                    //Serial.print(F("L_limit = ")); Serial.print(L_limit);
                    //Serial.print(F("\tH_Limit = ")); Serial.print(H_Limit);
                    //Serial.print(F("\tActualV = ")); Serial.print(ActualV);
                    if((L_limit !=0) && (ActualV < L_limit)){
                            LEDPTN = LEDPTN | 1<<(2*i);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,false);
                    }
                    if((H_Limit !=0) && (ActualV > H_Limit)){
                            LEDPTN = LEDPTN | 1<<(2*i+1);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,false);
                    }
                    //Serial.print(F("\tLEDPTN = 0x"));Serial.println(LEDPTN,HEX);
            }
            LEDandKEY.setLEDs(LEDPTN);
            if(Mb.GetBit(MB_FC_READ_COILS,7) && lastKey){
                    switch(lastKey){
                            case 0x01:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                                    break;
                            case 0x02:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                                    break;
                            case 0x04:
                                    LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                                    break;
                            case 0x08:
                                    rtclock.getTime(mtt);
                                    sprintf(s,"  %02u%02u%02u",mtt.hour, mtt.minute, mtt.second);
                                    LEDandKEY.setDisplayToString(s);
                                    break;        
                            case 0x10:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                                    break;
                            case 0x20:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                                    break;
                            case 0x40:
                                    rtclock.getTime(mtt);
                                    sprintf(s, "%04u%02u%02u",mtt.year+1970, mtt.month, mtt.day);
                                    LEDandKEY.setDisplayToString(s);
                                    break;
                    }
            }        
    }
    //----------------------------

    起動時にアクセスするNTPサーバのアドレスをEEPROMに登録しておく必要を感じています。ソースコード固定では問題でしょう。



    IO拡張を検討しました。※事前に https://www.toolsbox.biz/nonsense/IO%20Expander/index.html で検討済みです。
    Coilには接点出力を、Input Statusは接点入力を割り当てるのですが、STM32Fでは接点数は限られます。
    そこで、接点拡張を検討します。
    事前調査で使えそうなchipを検討しましたがMicrochip社のMCP23x17、MCP23x08辺りがリーズナブルかと思っています。ただ、検証用にはModule化されたボードを購入して検討したいです。そしてTTLではなくフォトカプラ入出力としたいです。Modbusのメモリ環境は現状のままでも割付は問題なさそうです。
    ※MCPの場合SPI/I2Cで別のchipとなります。Pin配置は共有出来るようになっており、モジュールボードは共有化できます。実際の市場ではSPI版のボードは殆どありません。DIPタイプであれば簡単に半田付け対応もできそうですが、基本SMDです。明らかにI2C版がリーズナブルで入手しやすいです。

    検討等価回路図は以下の通りです。MCP23017のIDは8通り(0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27)です。
    現状Coil/Input Status共に32bit用意しているので、16bit〜31bit目に割り付けたいです。

    使用しているライブラリには変更はありませんが、インクルードしているヘッダは増えています。

    /*
     * 20200817 T.Wanibe 
     * 
     * MCP23017CoilInputStatus
     * 10
     * RTCNTP
     * LED&KEY
     * STM32MINIShieldEvaluation
     * CoilData:16bit 4bit
     * DiscreteData16bit4bit
     * InputData81MCP4735Address2:MCP280 Address3rerservedAddress4MCP280
     * HoldData161MCP4725EEPROM
     * I2CPulUp
     * 
     * BuiltINLED
     * LED&KEYLED
     * LED&KEYDO7bit
     * iPhoneModbusMaster
     * STM32MINIShieldEvaluationR8/R910k
     * 
     * 
     * 1310726258447%
     * 20480RAM560027%14880
     */
    #include <SPI.h>
    #include <Ethernet3.h>                  //WizNet
    #include <EthernetUdp3.h>
    #include "MgsModbus.h"                  //Sketch
    #include <EEPROM.h>
    #include <avr/pgmspace.h>
    #include <TextFinder.h>                 //WebSetting
    #include <Wire.h>
    #include <MCP4725.h>                    //DAC
    #include <BME280I2C.h>                  //
    #include <RTClock.h>
    #include <Adafruit_MCP23017.h>
    #include <TM1638.h>
    #define dataPin                 PB4
    #define clockPin                PB3
    #define strobePin               PA15
    #define DEBUG                   1
    #define OK                      0
    #define NG                      1
    #define W550io_Rst              PA8     //STM32MINIShieldEvaluation
    #define SPI1_NSS_PIN            PA4     //SPI_1
    #define ModBusPort              502     //
    #define MbCoilDataLen           2       // length of the MbCoilDataLen array (Equivalent16bit*2)
                                            //4bit
    #define MbDiscreteDataLen       2       // length of the MbDiscreteDataLen array(Equivalent16bit*2)
                                            //4bit
    #define MbHoldDataLen           16      // length of the MbHoldDataLen array
    #define MbInputDataLen          8       // length of the MbInputDataLen array
                                            //7AI4(x100[])(x100[%])(x1[lux])
    #define HttpPort                80
    #define LED                     PC13
    #define LED1                    PB8     //LED1
    #define LED2                    PB9     //LED2
    #define DAC_REF_VOLTAGE         3.3     //dac supply-reference voltage
    #define I2C_BUS_SPEED           400000  //i2c bus speed, 100 000Hz or 400 000Hz
    #define ID1                     0x20    
    #define ID2                     0x21    
    int     inByte          =       0;      // incoming serial byte
    // Ethernet settings (depending on MAC and Local network)
    byte mac[]              = {0x00,0x08,0xDC,0x54,0x4D,0xD2};      //WIZNET
    byte ip[]               = {192, 168, 0, 202};
    byte dns_server[]       = {192, 168, 0, 1};
    byte gateway[]          = {192, 168, 0, 1};
    byte subnet[]           = {255, 255, 255, 0};
    int dOsPins[]           = {LED1,LED2};
    int dIsPins[]           = {PB11, PB10, PB1, PB0};
    //int aOsPins[]         = {};
    int aIsPins[]           = {PA0};
    //byte timeServer[]     = {130, 69, 251, 23};                   //  ntp.nc.u-tokyo.ac.jp 2020/08/10 ping
    byte timeServer[]       = {133, 31, 180, 6};                    // ntp.nc.u-tokyo.ac.jp   2020/08/109msec
    //byte timeServer[]     = {130, 34, 11,  117};                  //  ntp1.tohoku.ac.jp       2020/08/1014msec
    //byte timeServer[]     = {130, 34, 48,  32};                   //  ntp2.tohoku.ac.jp       2020/08/10ping
    //
    int DObits              = sizeof(dOsPins)/sizeof(int);
    int DIbits              = sizeof(dIsPins)/sizeof(int);
    int AIbits              = sizeof(aIsPins)/sizeof(int);
    char buf1[32];
    byte    ErrorTMP        = false;
    byte    ErrorHUM        = false;
    byte    ErrorPRS        = false;
    uint16_t value          = 0;
    float   voltage         = 0;
    float   temp(NAN), hum(NAN), pres(NAN);
    int     cntTemp,cntHum,cntPres;
    float   sumTemp,sumHum,sumPres;
    int     movingAverageLength     = 10;
    float   tempAve[10];
    float   humAve[10];
    float   presAve[10];
    const int NTP_PACKET_SIZE       = 48;                           // NTP time is in the first 48 bytes of message
    byte    packetBuffer[NTP_PACKET_SIZE];                          //buffer to hold incoming & outgoing packets
    char    s[16];                                                 // for sprintf
    EthernetServer  webServer(HttpPort);
    EthernetClient  client;
    TM1638          LEDandKEY(dataPin, clockPin, strobePin);
    BME280I2C       bme;                                            // Default : forced mode, standby time = 1000 ms
    MCP4725 dac(MCP4725A0_ADDR_A00, DAC_REF_VOLTAGE);
    String          HTTP_req;                                       // stores the HTTP request
    MgsModbus       Mb;
    word            preData = Mb.MbHoldData[0];                     //
    byte            lastKey;
    Adafruit_MCP23017 mcpIn;                                        //ID=0x20
    Adafruit_MCP23017 mcpOut;                                       //ID=0x21
    // HTML
    // +1
    char    buffer[100];
    // HTML HTML
    // HTML\ "HTMLsimple"\ "
    // 
    //
    //RTC
    RTClock rtclock (RTCSEL_LSE);                                   // initialise
    const int       timeZone        = 9;                            // change to your timezone
    time_t  tt, tt1;
    tm_t    mtt;
    uint8_t dateread[11];
    EthernetUDP Udp;
    unsigned int localPort  = 8888;                                 // local port to listen for UDP packets
    
    const byte      ID = 0x92;
    /*
            EEPROM
            EEPROM
             
            EEPROM
     */
    const char      titleStr[]   = "STM32MINIShield";
    char            outputSTR[9];
    //----------------------------------
    void LANSetup(){
            int idcheck = EEPROM.read(0);
            if (idcheck != ID){
                    //idID
                    //
                    //
            }
            if (idcheck == ID){
                    //idID
                    //
                    //EERPOM
                    for (int i = 0; i < 6; i++){
                            mac[i] = EEPROM.read(i+1);
                    }
                    for (int i = 0; i < 4; i++){
                            ip[i] = EEPROM.read(i+7);
                    }
                    for (int i = 0; i < 4; i++){
                            subnet[i] = EEPROM.read(i+11);
                    }
                    for (int i = 0; i < 4; i++){
                            gateway[i] = EEPROM.read(i+15);
                    }
    		//NTP
                    for (int i = 0; i < 4; i++){
                            timeServer[i] = EEPROM.read(i+19);
                    }
            }
            Ethernet.begin(mac, ip);
    //      Ethernet.begin(mac);                            //DHCP
    //      Ethernet.begin(mac, ip, subnet);                //SubnetMask
    //      Ethernet.begin(mac, ip, subnet, gateway);       //gateway
    }
    //--------------------------
    void SetWebPage( EthernetClient client){
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println();
            //
            client.print(F("<!DOCTYPE HTML PUBLIC \"\">\n<html>\n<HEAD>\n\t<META http-equiv=\"Content-Type\" charset=UTF-8\">\n"));
            client.print(F("\t<META http-equiv=\"Content-Style-Type\">\n\t<TITLE>"));
            client.print(titleStr);
            client.print(F(" Setup Page</TITLE>\n</HEAD>\n"));
            client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\">\n<BLOCKQUOTE><BLOCKQUOTE>\n"));
            client.print(F("<table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\">\n"));
            client.print(F("<tr><td>"));
            client.print(titleStr);
            client.print(F(" Setup Page</td></tr></table><br>\n"));
            //
            client.print(F("<script>\n\tfunction hex2num (s_hex) {\n\t\teval(\"var n_num=0X\" + s_hex);\n\t\treturn n_num;\n\t}\n</script>\n"));
            client.print(F("<tbody>\n<FORM><input type=\"hidden\" name=\"SBM\" value=\"1\">\n<table>\n<tr><td>MAC:</td><td>"));
            client.print(F("<input id=\"T1\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT1\" value=\""));
            client.print(mac[0],HEX);
            client.print(F("\">.<input id=\"T3\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT2\" value=\""));
            client.print(mac[1],HEX);
            client.print(F("\">.<input id=\"T5\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT3\" value=\""));
            client.print(mac[2],HEX);
            client.print(F("\">.<input id=\"T7\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT4\" value=\""));
            client.print(mac[3],HEX);
            client.print(F("\">.<input id=\"T9\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT5\" value=\""));
            client.print(mac[4],HEX);
            client.print(F("\">.<input id=\"T11\" type=\"text\" size=\"3\" maxlength=\"2\" name=\"DT6\" value=\""));
            client.print(mac[5],HEX);
            //
            client.print(F("\"><input id=\"T2\" type=\"hidden\" name=\"DT1\"><input id=\"T4\" type=\"hidden\" name=\"DT2"));
            client.print(F("\"><input id=\"T6\" type=\"hidden\" name=\"DT3\"><input id=\"T8\" type=\"hidden\" name=\"DT4"));
            client.print(F("\"><input id=\"T10\" type=\"hidden\" name=\"DT5\"><input id=\"T12\" type=\"hidden\" name=\"DT6"));
            client.print(F("\"></td></tr>\n<tr><td>IP:</td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT7\" value=\""));
            client.print(ip[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT8\" value=\""));
            client.print(ip[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT9\" value=\""));
            client.print(ip[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT10\" value=\""));
            client.print(ip[3],DEC);
            //
            client.print(F("\"></td></tr>\n<tr><td>MASK: </td><td><input type= \"text\" size=\"3\" maxlength=\"3\" name=\"DT11\" value=\""));
            client.print(subnet[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT12\" value=\""));
            client.print(subnet[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT13\" value=\""));
            client.print(subnet[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT14\" value=\""));
            client.print(subnet[3],DEC);
            //
            client.print(F("\"></td></tr>\n<tr><td>GW: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT15\" value=\""));
            client.print(gateway[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT16\" value=\""));
            client.print(gateway[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT17\" value=\""));
            client.print(gateway[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT18\" value=\""));
            client.print(gateway[3],DEC);
            //
            //client.print(F("\"></td></tr>\n<tr><td><br></td COLSPAN='2'>\n<HR ALIGN=LEFT>"));
            client.print(F("\"></td></tr><tr><td COLSPAN='2'>\n<HR ALIGN=CENTER></td><td>"));                      //1
    	//
            client.print(F("</td></tr><tr><td>NTPd: </td><td><input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT19\" value=\""));
            client.print(timeServer[0],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT20\" value=\""));
            client.print(timeServer[1],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT21\" value=\""));
            client.print(timeServer[2],DEC);
            client.print(F("\">.<input type=\"text\" size=\"3\" maxlength=\"3\" name=\"DT22\" value=\""));
            client.print(timeServer[3],DEC);
            client.print(F("\"></td></tr>\n<tr><td><br></td></tr><tr><td COLSPAN='2'><P ALIGN=RIGHT><input id=\"button1\"type=\"submit\" value=\"SUBMIT\" "));
            //
            client.print(F("Onclick=\""));
            client.print(F("document.getElementById('T2').value = hex2num(document.getElementById('T1').value);\n"));
            client.print(F("\t\tdocument.getElementById('T4').value = hex2num(document.getElementById('T3').value);\n"));
            client.print(F("\t\tdocument.getElementById('T6').value = hex2num(document.getElementById('T5').value);\n"));
            client.print(F("\t\tdocument.getElementById('T8').value = hex2num(document.getElementById('T7').value);\n"));
            client.print(F("\t\tdocument.getElementById('T10').value = hex2num(document.getElementById('T9').value);\n"));
            client.print(F("\t\tdocument.getElementById('T12').value = hex2num(document.getElementById('T11').value);\""));
            //
            client.print(F("></td></tr></tbody>\n</table>\n</BLOCKQUOTE></BLOCKQUOTE>\n</form>\n</BODY>\n</html>\n"));
    }
    //--------------------------
    void checkWebPage( EthernetClient client)
    {
            Serial.println(F("new webClient"));
            if (client) {
                    TextFinder  finder(client );
                    while (client.connected()) {
                            //digitalWrite(LED, HIGH);
                            if (client.available()) {
                                    //
                                    if( finder.find("GET /") ) {
                                            // setup
                                            // 
                                            // Web
                                            if (finder.findUntil("setup", "\n\r")){
                                                    // SBM
                                                    // 
                                                    // SUBMIT
                                                    // 
                                                    if (finder.findUntil("SBM", "\n\r")){
                                                            byte SET = finder.getValue();
                                                            // DTDT
                                                            // macipsubnetgateway
                                                            while(finder.findUntil("DT", "\n\r")){
                                                                    int val = finder.getValue();
                                                                    // DTval16MAC
                                                                    if(val >= 1 && val <= 6) {
                                                                            mac[val - 1] = finder.getValue();
                                                                    }
                                                                    // DTval710IP
                                                                    if(val >= 7 && val <= 10) {
                                                                            ip[val - 7] = finder.getValue();
                                                                    }
                                                                    // DTval1114MASK
                                                                    if(val >= 11 && val <= 14) {
                                                                            subnet[val - 11] = finder.getValue();
                                                                    }
                                                                    // DTval1518GW
                                                                    if(val >= 15 && val <= 18) {
                                                                            gateway[val - 15] = finder.getValue();
                                                                    }
                                                                    // DTval1922TimeServer
                                                                    if(val >= 19 && val <= 22) {
                                                                            timeServer[val - 19] = finder.getValue();
                                                                    }                                                        }
                                                            // EEPROM
                                                            for (int i = 0 ; i < 6; i++){
                                                                    EEPROM.write(i + 1,mac[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 7, ip[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 11, subnet[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 15, gateway[i]);
                                                            }
                                                            for (int i = 0 ; i < 4; i++){
                                                                    EEPROM.write(i + 19, timeServer[i]);
                                                            }                                                        // IDArduinoEEPROM
                                                            EEPROM.write(0, ID); 
                                                            // EEPROMarduino
                                                            //
                                                    }
                                                    // 
                                                    SetWebPage(client);
                                                    break;
                                            }
                                    }
                                    pageIntroduction(client);
                                    break;  
                            }
                    }
                    //digitalWrite(LED, LOW);
                    delay(1);
                    client.stop();
            }     
    }
    //----------------------------
    void pageIntroduction( EthernetClient client){
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println();
            // put your own html from here on
            client.print("<HTML>\n<BODY>\nIT WORKS: go to <B><A HREF=\"https://");
            client.print(ip[0],DEC);
            for (int i= 1; i < 4; i++){
                    client.print(".");
                    client.print(ip[i],DEC);
            }
            client.print("/setup\">");
            client.print(ip[0],DEC);
            for (int i= 1; i < 4; i++){
                    client.print(".");
                    client.print(ip[i],DEC);
            }
            client.print("/setup</A></B>\n</BODY>\n</HTML>\n");
            // put your own html until here 
    }
    //----------------------------
    int     GetDeviceInfo(){
            for (int i=0;i<DIbits;i++){
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit 
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i]=analogRead(aIsPins[i]);                               //U16
            }
            //ModbusSlave616BIT16..31
            for (int i=0;i<16;i++){
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i+16,mcpIn.digitalRead(i));         //
            }        
            return OK;            
    }
    //----------------------------
    int     SetDeviceInfo(){
            for (int i=0;i<DObits;i++){          
                    digitalWrite(dOsPins[i],!Mb.GetBit(MB_FC_READ_COILS,i));                 //LEDLOW                            
            }
            //ModbusSlave616BIT16..31
            for (int i=0;i<16;i++){
                    mcpOut.digitalWrite(i,Mb.GetBit(MB_FC_READ_COILS,i+16));                //
            }        
            if(Mb.MbHoldData[0] != preData){
                    preData =       Mb.MbHoldData[0];
                    dac.setValue(preData, MCP4725_FAST_MODE, MCP4725_POWER_DOWN_OFF);
                    Serial.print(F("SetDAC = "));Serial.println(preData);
            }
            return OK;
    }
    //----------------------------
    int     SetI2CDeviceInfo(bool init = false){
            //BME280
            double rf;
            BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
            BME280::PresUnit presUnit(BME280::PresUnit_Pa);
            bme.read(pres, temp, hum, tempUnit, presUnit);
            if(init){
                    for (int i = 0;i<movingAverageLength;i++){
                            tempAve[i] = temp;
                            sumTemp    += tempAve[i];
                            humAve[i]  = hum;
                            sumHum     += humAve[i];
                            presAve[i] = pres;
                            sumPres    += presAve[i];
                    }              
            }
            //InputDataIndex=0INT10025.65->2565
            if(!ErrorTMP)   Mb.MbInputData[1]        = int(getTempAve(temp)*100);
            //InputDataIndex=1INT10057.88->5788
            if(!ErrorHUM)   Mb.MbInputData[2]        = int(getHumAve(hum)*100);
            //InputDataIndex=1INT[mbar]101300->1013 i16 
            if(!ErrorPRS)   Mb.MbInputData[3]        = int(getPresAve(pres)/100);
    }
    //--------------------------
    void printError(byte error){
            // If there's an I2C error, this function will
            // print out an explanation.
            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"));
            }
    }
    //----------------------------
    time_t getNtpTime(){
            while (Udp.parsePacket() > 0) ;                                 // discard any previously received packets
            Serial.println(F("Transmit NTP Request"));
            sendNTPpacket(timeServer);
            uint32_t beginWait = millis();
            while (millis() - beginWait < 1500) {
                    int size = Udp.parsePacket();
                    if (size >= NTP_PACKET_SIZE) {
                            Serial.println(F("Receive NTP Response"));
                            Udp.read(packetBuffer, NTP_PACKET_SIZE);        // read packet into the buffer
                            unsigned long secsSince1900;
                            // convert four bytes starting at location 40 to a long integer
                            secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
                            secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
                            secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
                            secsSince1900 |= (unsigned long)packetBuffer[43];
                            return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
                    }
            }
            Serial.println(F("No NTP Response :-("));
            return 0;                                                       // return 0 if unable to get the time
    }
    //----------------------------send an NTP request to the time server at the given address
    void sendNTPpacket(byte address[]){
            // set all bytes in the buffer to 0
            memset(packetBuffer, 0, NTP_PACKET_SIZE);
            // Initialize values needed to form NTP request(see URL above for details on the packets)
            packetBuffer[0]         = 0b11100011;                           // LI, Version, Mode
            packetBuffer[1]         = 0;                                    // Stratum, or type of clock
            packetBuffer[2]         = 6;                                    // 64
            packetBuffer[3]         = 0xEC;                                 // 
            // 8 bytes of zero for Root Delay & Root Dispersion
            packetBuffer[12]        = 49;                                   // '1'
            packetBuffer[13]        = 0x4E;                                 // 'N'
            packetBuffer[14]        = 49;                                   // '1'
            packetBuffer[15]        = 52;                                   // '4'
            // all NTP fields have been given values, nowyou can send a packet requesting a timestamp:                 
            Udp.beginPacket(address, 123);                                  //NTP requests are to port 123
            Udp.write(packetBuffer, NTP_PACKET_SIZE);
            Udp.endPacket();
    }
    //----------------------------
    float   getTempAve(float value){
            if(cntTemp == movingAverageLength)  cntTemp = 0;
            sumTemp         -= tempAve[cntTemp];                            //
            tempAve[cntTemp] = value;                                       //
            sumTemp         += tempAve[cntTemp];
            cntTemp++;
            return sumTemp/movingAverageLength;
    }
    //----------------------------
    float   getHumAve(float value){
            if(cntHum == movingAverageLength)  cntHum = 0;
            sumHum         -= humAve[cntHum];                               //
            humAve[cntHum] = value;                                         //
            sumHum         += humAve[cntHum];
            cntHum++;
            return sumHum/movingAverageLength;
    }
    //----------------------------
    float   getPresAve(float value){
            if(cntPres == movingAverageLength)  cntPres = 0;
            sumPres         -= presAve[cntPres];                            //
            presAve[cntPres] = value;                                       //
            sumPres         += presAve[cntPres];
            cntPres++;
            return sumPres/movingAverageLength;
    }
    //----------------------------
    void setup()
    {
            // serial setup
            Serial.begin(115200);
            delay(1000);
            Serial.println(F("Serial interface started"));
            pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);
            SPI.begin();                                                    //Initialize the SPI_1 port.
            SPI.setBitOrder(MSBFIRST);                                      // Set the SPI_1 bit order
            SPI.setClockDivider(SPI_CLOCK_DIV4);                            //SPI_CLOCK_DIV16 (72 / 16 = 4.5 MHz SPI_1 speed)
                                                                            //SPI_CLOCK_DIV4 (72 / 4 = 18 MHz SPI_1 speed)
            //Ethernet3API
            pinMode(SPI1_NSS_PIN, OUTPUT);                                  //NIC_CS
            Ethernet.setCsPin(SPI1_NSS_PIN);                                //NIC_CS
            Ethernet.setRstPin(W550io_Rst);                                 //NIC_RST
            //NIC
            pinMode(W550io_Rst, OUTPUT);
            digitalWrite(W550io_Rst, LOW);
            delay(10);                                                      //10msec
            digitalWrite(W550io_Rst, HIGH);
            LANSetup();
            Serial.println(F("Ethernet interface started")); 
            Serial.print(F("My IP address: "));Serial.println(Ethernet.localIP());// print your local IP address:
            webServer.begin();
            //I2C
            Wire.begin();
            Serial.print(F("I2C_Start\n"));
            //BME280BMP280
            while(!bme.begin())
            {
                    Serial.println(F("Could not find BME280 sensor!"));
                    delay(1000);
            }
            // bme.chipID(); // 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."));
                            ErrorHUM = true;
                            break;
                    default:
                            Serial.println(F("Found UNKNOWN sensor! Error!"));
                            ErrorTMP = true;
                            ErrorHUM = true;
                            ErrorPRS = true;
            }
            SetI2CDeviceInfo(true);
            mcpIn.begin(ID1);
            mcpOut.begin(ID2);
            for(int i = 0;i<16;i++){
                    mcpIn.pinMode(i, INPUT);
                    mcpIn.pullUp(i, HIGH);                              // turn on a 100K pullup internally
                    mcpOut.pinMode(i, OUTPUT);
            }
            
            //DAC(MCP4725)
            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 100k400K        
            //ModBus 
            for(int i = 0;i<MbCoilDataLen;i++)      Mb.MbCoilData[i]=0x0000;//
            for(int i = 0;i<MbDiscreteDataLen;i++)  Mb.MbDiscreteData[i]=0; //
            for(int i = 0;i<MbHoldDataLen;i++)      Mb.MbHoldData[i]=0;     //
            for(int i = 0;i<MbInputDataLen;i++)     Mb.MbInputData[i]=0;    //
            //LED
            for (int i=0;i<DObits;i++){          
                    Mb.SetBit(MB_FC_READ_COILS,i,false);                    //Mb.MbData[0]
                    pinMode(dOsPins[i],OUTPUT);
                    digitalWrite(dOsPins[i], LOW);                         //LEDHIGH                            
            }
            //pin
            for (int i=0;i<DIbits;i++){
                    pinMode(dIsPins[i],INPUT);
                    Mb.SetBit(MB_FC_READ_DISCRETE_INPUT,i,!digitalRead(dIsPins[i]));        //bit Mb.MbData[1]
            }
            for (int i=0;i<AIbits;i++){
                    Mb.MbInputData[i] = analogRead(aIsPins[i]);             //U16
            }
            //NTP
            Udp.begin(localPort);
            rtclock.setTime(getNtpTime());
            //
            Serial.println(F("EndSetUp"));
    }
    //----------------------------
    int LoopCount = 0;
    void loop()
    {
            if(!LoopCount++)                Serial.println(F("LoopStart"));
            if(LoopCount > 0x7FFFFFF0)      LoopCount = 0;
            client = webServer.available();
            if(client){
                    //digitalWrite(LED, LOW);
                    checkWebPage(client);
            }else{
                    //digitalWrite(LED, HIGH);           
            }
            client.stop();                                                  // 
            GetDeviceInfo();
            Mb.MbsRun();                                                    //SlaveServer
            SetDeviceInfo();
            SetI2CDeviceInfo();
            if(!(LoopCount%1000))           Serial.print(F("."));
            if(!(LoopCount%40000))          Serial.println(F(""));
            byte keys = LEDandKEY.getButtons();
            switch(keys){
                    case 0x01:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x02:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x04:
                            LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x08:                                                              //20200810
                            rtclock.getTime(mtt);
                            sprintf(s,"  %02u%02u%02u",mtt.hour, mtt.minute, mtt.second);
                            LEDandKEY.setDisplayToString(s);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x10:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x20:
                            LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x40:                                      //20200810
                            rtclock.getTime(mtt);
                            sprintf(s, "%04u%02u%02u",mtt.year+1970, mtt.month, mtt.day);
                            LEDandKEY.setDisplayToString(s);
                            lastKey = keys;
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;
                    case 0x80:
                            if(Mb.GetBit(MB_FC_READ_COILS,7))       Mb.SetBit(MB_FC_READ_COILS,7,false);
                            else                                    Mb.SetBit(MB_FC_READ_COILS,7,true);
                            Serial.print(F("keys=0x"));Serial.println(lastKey,HEX);
                            break;                        
            } 
            //LEDandKEY.setLEDs(Mb.MbCoilData[0]>>8);                   //LED&KEYLED  
            byte LEDPTN = 0;
            for(int i = 0;i < 4;i++){
                    word L_limit = Mb.MbHoldData[2*i+8];
                    word H_Limit = Mb.MbHoldData[2*i+9];
                    word ActualV = Mb.MbInputData[i];
                    //Serial.print(F("L_limit = ")); Serial.print(L_limit);
                    //Serial.print(F("\tH_Limit = ")); Serial.print(H_Limit);
                    //Serial.print(F("\tActualV = ")); Serial.print(ActualV);
                    if((L_limit !=0) && (ActualV < L_limit)){
                            LEDPTN = LEDPTN | 1<<(2*i);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+8,false);
                    }
                    if((H_Limit !=0) && (ActualV > H_Limit)){
                            LEDPTN = LEDPTN | 1<<(2*i+1);
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,true);
                    }else{
                            Mb.SetBit(MB_FC_READ_COILS,2*i+9,false);
                    }
                    //Serial.print(F("\tLEDPTN = 0x"));Serial.println(LEDPTN,HEX);
            }
            LEDandKEY.setLEDs(LEDPTN);
            if(Mb.GetBit(MB_FC_READ_COILS,7) && lastKey){
                    switch(lastKey){
                            case 0x01:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[1]),4,false);
                                    break;
                            case 0x02:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[2]),4,false);
                                    break;
                            case 0x04:
                                    LEDandKEY.setDisplayToDecNumber(int(pres),0,false);
                                    break;
                            case 0x08:
                                    rtclock.getTime(mtt);
                                    sprintf(s,"  %02u%02u%02u",mtt.hour, mtt.minute, mtt.second);
                                    LEDandKEY.setDisplayToString(s);
                                    break;        
                            case 0x10:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbHoldData[0]),0,false);
                                    break;
                            case 0x20:
                                    LEDandKEY.setDisplayToDecNumber(int(Mb.MbInputData[0]),0,false);
                                    break;
                            case 0x40:
                                    rtclock.getTime(mtt);
                                    sprintf(s, "%04u%02u%02u",mtt.year+1970, mtt.month, mtt.day);
                                    LEDandKEY.setDisplayToString(s);
                                    break;
                    }
            }        
    }
    //----------------------------


    自動再起動の必要性

    setup画面で変更内容を送信した後、その変更を反映させるためにはCPU本体の再起動/RESETが必要です。これまでは当たり前のように本体のリセットボタンを押していましたが、本体をケースに入れた場合、これは気になります。電源の再起動でも良いのでしょうが、やはりソフト側から自動リセットをすべきだと思いました。こんなことを思ったのはESP32では実現出来ているからです。STM32で実現する方法があるはずだと調べたところ、

    nvic_sys_reset();

    を実行することで実現出来ることが判り、実際に動作確認も出来ました。
    尚、NVICというのはネスト型ベクタ割り込みコントローラで、STM32の割込処理を一括しているようです。
    そこで、ソースコードをレタッチしてみました。

    変更点

    使用しているライブラリリストです。

    製作物に戻る


    免責事項

    本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
    あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。

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