最終更新日:2023年3月27日
2023/3/27 STM32F1の入手性の悪さを考慮し、RP2040でModbusTCPを実現するように検討しました。->
2020/12/22 ファームを更新しました。->
2020/2/7 変更版を作成しました ->
ModbusSlaveデバイスを簡単に構築出来ないかと思い検討しました。
手元にはテクノウエーブ社のLANX-I2219をModbus/TCPSlaveに仕立てたモノがあります。このデバイスを使用して、LabVIEWのホスト側コードを書きデバッグしてきました。その過程で、もっとユーザ環境に近いModbusデバイスが簡単に構築出来ないかなと思いました。
このデバイスですとDIOやアナログ信号の入出力は出来ますが、センサデバイスをコントロールしようとすると二の足を踏みます。
今ではArduinoとかRasberyPiを利用してセンサデバイスを含むSlaveデバイスを構築出来るかと思いますので、これを利用して自在なModbusSlavedeviceの構築を検討しました。
ターゲットは構築しやすいArduinoIDE環境で利用できるデバイスを検討したいです。ただ、AVR328ではSRAMが足りないためModbusTCPとして構築するのが困難です。Mega256で有れば足りるのですが、折角なのでBluePillデバイスを使う事にします。想定としては幾つかのブロック毎にセンサ類が配置されていて、それをSCADAで管理する様なイメージです。
ModbusのマスタはLabVIEWで構築しました。基本的にブラウザは使用しませんがSetupの為には使用します。そのためSketchにもhttp用のコードが含まれています。
Modbusライブラリは Plasmionique Modbus Master v1.3.5.3 を採用しました。Plasmionique社から無償提供されています。
https://lavag.org/files/file/286-plasmionique-modbus-master/Modbusはアドレスに決まりがあります。データ・アドレスには4種類有り区別しています。
- コイル(Coil)
コイルは、フィールドへの ON / OFF 出力である DO(Discrete Output)やスレーブデバイスの状態やモードを変更する為にスイッチとして用いられます。
参照・変更が可能な2値のデータで、可能アドレス範囲は 1 から 9999 という仕様です。
このプログラムではDOとして設定出来る4bit分のみを割り当てています。1から4です。
- 入力ステータス(Input Status)
入力ステータスは、フィールドからの ON / OFF 入力である DI(Discrete Input)やスレーブデバイスの状態入力として用いられます。
参照のみで変更はできない 2 値のデータで、可能アドレス範囲は10001 から 19999 という仕様です。
このプログラムではDIとして4bit用意しました。1から4です。
- 入力レジスタ(Input Register)
入力レジスタは、フィールドからのAI(Analog Input)やスレーブデバイス内の情報として用いられます。
16ビット長のデータで、参照するのみで変更はできません。
可能アドレス範囲は30001から39999です。
複数の連続したアドレスを割当てることにより、単精度実数、倍精度実数などのデータを扱うこともできます。
このプログラムではAIの4ポートとI2Cで取得した温度、湿度、照度を割り当てています。1から7です。
※ソースコードは公開していますので適宜修正してください。
- 保持レジスタ(Holding Register)
保持レジスタは、フィールドからの AO(Analog Output)やスレーブデバイス内の設定情報として用いられます。
16 ビット長のデータで、参照・変更ができます。可能アドレス範囲は 40001 から 49999です。
複数の連続したアドレスを割当てることによって、単精度実数、倍精度実数などのデータを扱うこともできます。
SRAMに余りがあれば配列を確保して読み書きできるようにすればいいです。一応このプログラムでは16アドレス分確保してあります。
上記評価プログラムはSTM32MINIShieldEvaluationボードに以下のような実装をして動作するようにしています。
ただ、Witte Software社のModbus Slaveと云うソフトを使用して1台のPCでSelfSendして確認することも可能です。Setupの画面は殆ど変わりません。Macアドレス及びネットワーク環境の設定をはじめに実施する必要があります。ブラウザで実現出来ますのでLINUX/MAC/WINDOWSいずれのOSからでも設定可能です。
STM32duinoソースコード(Sketch)はこんな感じになります。必ず、“MgsModbus.h”“MgsModbus.cpp”を同じフォルダに入れてください。
“MgsModbus.h”“MgsModbus.cpp”はオリジナルコードをレタッチしています。オリジナルコードの作者はMarco Gerritse氏[V-0.1.1著作権(C)2013]です。断りを入れているわけではないです。Arduino用のコードはどうしてもうまく実現できませんでした。LabVIEWのソースコードはLV2014版をここにリンクしておきます。
このコードを実行するに当たり追加が必要なライブラリは、
/* * 20191219 T.Wanibe STM32MINIShieldEvaluation用に書き直しました。 * CoilData:16bit 4bit接続 * DiscreteData:16bit 4bit接続 * InputData:8ワード 4ポートAI、3ポートI2C * HoldData:16ワード 未使用 * I2Cデバイスが接続されていなくても動く様に対策していますが、必ずPulUp抵抗は実装してください。 * STM32MINIShieldEvaluation基板の場合R8/R9に10kΩ実装することになります。 * * コンパイル結果を添えておきます。 * 最大131072バイトのフラッシュメモリのうち、スケッチが60312バイト(46%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が5096バイト(24%)を使っていて、ローカル変数で15384バイト使うことができます。 */ #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 照度モジュールのドライバ #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 GY21ADRS 0x40 //HTU21DF_ADRS #define GY2561ADRS 0x39 //TSL2561_ADRS #define HttpPort 80 #define LED PC13 MgsModbus Mb; int inByte = 0; // incoming serial byte // Ethernet settings (depending on MAC and Local network) byte mac[] = {0x00,0x08,0xDC,0x54,0x4D,0xD0}; //WIZNET byte ip[] = {192, 168, 0, 200}; byte dns_server[] = {192, 168, 0, 1}; byte gateway[] = {192, 168, 0, 1}; byte subnet[] = {255, 255, 255, 0}; int dOsPins[] = {PB12, PB13, PB14, PB15}; int dIsPins[] = {PB11, PB10, PB1, PB0}; //int aOsPins[] = {}; int aIsPins[] = {PA0,PA1,PA2,PA3}; //配列要素数を求めておく int DObits = sizeof(dOsPins)/sizeof(int); int DIbits = sizeof(dIsPins)/sizeof(int); int AIbits = sizeof(aIsPins)/sizeof(int); char buf1[32]; EthernetServer webServer(HttpPort); EthernetClient client; Adafruit_HTU21DF htu = Adafruit_HTU21DF(); String HTTP_req; // stores the HTTP request boolean gain; // HTU用Gain setting, 0 = X1, 1 = X16; unsigned int ms; // HTU用Integration ("shutter") time in milliseconds unsigned int data0, data1; // HTU用 SFE_TSL2561 light; // Light用 // これは、HTMLコードを「流す」ためのバッファーです。 // ””を含む最長の文字チェーン+1と同じ大きさでなければなりません。 char buffer[100]; // これはすべて切り詰められたHTMLコードです。 エディターでHTMLコードを作成し、バッファに収まるように切り分けます。 // HTMLをすべての場所で切り取ることができますが、\ "部分ではできません。HTML内でsimple"の代わりに\ "を // 使用する必要があります。そうしないと機能しません。 const byte ID = 0x92; /* EEPROMの有効なデータが「知っている」ビットであるかどうかを識別するために使用されます。 これがEEPROMに書き込まれていれば、スケッチは前に実行されました これを使用するので、最初にこのスケッチを実行すると、上記の値が使用されます。 どのデータにどのEEPROMアドレスを使用しているかを定義する */ const char titleStr[] = "STM32MINIShieldEvaluation"; byte ErrorHTU,ErrorLight; //---------------------------------- 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> ")); 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()) { 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; } } 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で点灯 } return OK; } //---------------------------- int SetI2CDeviceInfo(){ if(!ErrorHTU){ //HOLDREGISTERのIndex=0に温度をセット INTにする必要があるので100倍して整数化 25.65->2565 Mb.MbInputData[4] = int(htu.readTemperature()*100); //HOLDREGISTERのIndex=1に湿度をセット INTにする必要があるので100倍して整数化 57.88->5788 Mb.MbInputData[5] = int(htu.readHumidity()*100); } if(!ErrorLight){ //HOLDREGISTERのIndex=2に照度をセット INT化する double lux; // Resulting lux value boolean good; // True if neither sensor is saturated if (light.getData(data0,data1)){ // getData() returned true, communication was successful good = light.getLux(gain,ms,data0,data1,lux); // Perform lux calculation: if(good) Mb.MbInputData[6]=int(lux);else Mb.MbHoldData[6]=0xFFFF; } } } //-------------------------- 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")); 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")); //ErrorHTUcheck Wire.beginTransmission(GY21ADRS); Serial.print(F("trans_Start\n")); ErrorHTU = Wire.endTransmission(); Serial.println(ErrorHTU); if(!ErrorHTU){ Serial.print(F("HTU_Start\n")); htu.begin(); } Serial.print(F("htu_CheckEnd\n")); delay(100); //ErrorLight Wire.beginTransmission(GY2561ADRS); Serial.print(F("trans_Start\n")); ErrorLight = Wire.endTransmission(); Serial.println(ErrorLight); if(!ErrorLight){ Serial.print(F("Light_Start\n")); light.begin(); } Serial.print(F("light_CheckEnd\n")); // unsigned char ID; if(!ErrorLight){ if (light.getID(ID)){ Serial.print(F("Got factory ID: 0X")); Serial.print(ID,HEX); Serial.println(F(", should be 0X5X")); }else{ // Most library commands will return true if communications was successful, // and false if there was a problem. You can ignore this returned value, // or check whether a command worked correctly and retrieve an error code: byte error = light.getError(); printError(error); } gain = 0; //Gain setting, 0 = X1, 1 = X16; unsigned char time = 2; //Integration ("shutter") time in milliseconds light.setTiming(gain,time,ms); light.setPowerUp(); } //ModBusメモリ 初期化 for(int i = 0;i<MbCoilDataLen;i++) Mb.MbCoilData[i]=0; //メモリ初期化 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; //メモリ初期化 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], HIGH); //LEDはLOWで点灯 } 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) checkWebPage(client); client.stop(); // コネクションを閉じる。 GetDeviceInfo(); Mb.MbsRun(); //SlaveServer SetDeviceInfo(); SetI2CDeviceInfo(); if(!(LoopCount%1000)) Serial.print(F(".")); if(!(LoopCount%40000)) Serial.println(F("")); } //----------------------------setup画面のHTMLコードですが、参考としたコードは PROGMEM =”” を採用していました。この場合レタッチがとても大変です。
obj.println(F("")); で書いた方が制限が少なくて扱いやすいです。必ずF("")を遣う必要があります。そうしないとSRAMが足りなくなります。Setupを実行したら必ずBluePillをリセットしてください。起動した時点で内容が更新されます。
保持レジスタに書き込まれた値を元にアナログ出力値を変更するコードを書きました。
I2Cのモジュール・MCP4725を接続しました。コネクタの関係で、I2CモジュールをBME280とMCP4725に変更しています。ホスト側パネルも一寸弄りましたがコード自体は弄っていません。
スレーブ側のコードはデバイスを変更した部分を弄っています。また、LED&KEYで押したボタンに相当するデータを7セグLEDで表示出来るようにしました。
- コイル(Coil)
- LED1(PB8)
- LED2(PB9)
- 入力ステータス(Input Status)
- AI0(PA0) DAC(MCP4725)の出力を入力
- BME280 温度データ x100
- BME280 湿度データ x100
- BME280 大気圧データ ÷100 [mbar]
- 入力レジスタ(Input Register)
- IN0(PB11)
- IN1(PB10)
- IN2(PB1)
- IN3(PB0)
- 保持レジスタ(Holding Register)
- DAC(MCP4725) 0..4095
S1:温度表示[℃]
S2:湿度表示[%]
S3:大気圧表示[Pa]
S5:DAC設定値
S6:ADC測定値ソースコードも添えておきます。
このコードを実行するに当たり追加が必要なライブラリは、
/* * 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> ")); 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; } } } //----------------------------
ホスト側ModbusTCPソフトはiPod/iPhoneでも提供されています。iAppでModbusソフトを探してみました。
残念ながら汎用品は無いようです。どれも専用のスレーブデバイス用のようです。
そこで、どのようにして専用化しているのか調査してみました。具体的にはSTM32MINIShieldに実際にアクセスした際にどのようなコマンドを投げかけているのか調査です。結果をお知らせします。
とりあえず制限はあるもののmTCPは使える様です。
![]()
Sealevel Modbus Connect 一瞬接続して、すぐに勝手にdisconnectされてしまいます。原因不明
- “PING”機能があります。PINGは問題なく成功しました。
- 接続しようとするとSealevelデバイスでは無いと云われます。
REQUEST:00 03 00 00 00 02 f7 45
というメッセージが流れてくるのです。他のソフトの場合、コネクト時にメッセージは送られてこないのです。多分このメッセージに応答出来ないので Sealevel デバイスではないという解釈をされて
いると考えます。
このメッセージの解釈ですが、
解釈として、
00 03 は単にシリアルカウント値かと思います。回数を追う毎にインクリメントされます。
0x0000は開始アドレスかと思われます。
0x0002はメッセージサイズかと思います。
0xf7は指定したUnitIDのようです。ID=247を意味します。
0x45はデリミタでしょうか?判りません。独自の処理についてはこれ以上追わないことにします。
![]()
irBoard Lite 主旨が違いました ![]()
CS Modbus 接続出来ました。
コネクトすると設定条件に応じた要求メッセージを送り、応答メッセージをコンソールに書き込んでいるようです。ただ、これでは解釈できないです。![]()
mTCP これちゃんと接続及び読み込みできました。
https://apps.apple.com/jp/app/mtcp/id1467488841#?platform=iphone
書込なのですが、Holdレジスタのみ書込が出来る事を確認しました。
Coilの書込方法が判りません![]()
TCP ModBus 接続出来ました。
コマンドが3と6しか無いです。![]()
Q-view 主旨が違うような、、 ![]()
ET-7000: a Modbus TCP connect tool for the ET-7000/ PET-7000 serious modules of ICP DAS 接続出来ませんでした。Slaveデバイスを選択してからアクセスするのですが、詳細わからず。
Connectを実行したところ以下の要求コマンドが発信されました。
REQUEST:01 02 00 00 00 06 01 13 df e4 00 00
7バイト目の0x01は要求IDであることは判りました。それ以外は
8バイト目の0x13は固定でしょうか?
9バイト目10バイト目は指定ターゲットに起因するようです。![]()
ModComm: Peek & Poke 作者は日本人の方のようです。Kei Sakaguchiとあります。
使ってみました。これ期待通り操作できます。
まず読込を実行し、値を確認しますこのとき範囲指定が枠内で無いとエラーが出るところも有りがたいです。
Coilと保持レジスタは値を変更して書込が出来ます。ちゃんと反映しました。
無償です。使えます。ファームウエアを更新しました。更新内容ですが、
- ソフトウエアリセットを追加しました。Setup画面でSubmitを実行した際に、自動でマイコンのリセットを実現しました。
- Setup画面の他にユーティリティ画面を設けました。
![]()
ソースコードも添えておきます。
このコードを実行するに当たり追加が必要なライブラリは、
/* * 20201222 T.Wanibe * softresetに対応しました。また、ユーティリティ頁も追加しています。 * 温度湿度圧力について移動平均を導入してみる。ここまで来てもメモリに余裕があるため * またMCP23017をCoil及びInputStatusに割り当ててみる。 * 10回移動平均にしたところ確実にバラツキを押さえることが出来ている。 * 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バイトのフラッシュメモリのうち、スケッチが67296バイト(51%)を使っています。 * 最大20480バイトのRAMのうち、グローバル変数が5752バイト(28%)を使っていて、ローカル変数で14728バイト使うことができます。 */ #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 //実際には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 #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/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); 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をすべての場所で切り取ることができますが、\ "部分ではできません。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]; const char FVirsion[] = "1.0.7"; char MacAddressStr[20]; char IPAddressStr[20]; char SubnetMaskStr[20]; char GatewayStr[20]; char NTPserverStr[20]; char DateTimeStr[20]; //---------------------------------- 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); } sprintf(MacAddressStr,"%02x.%02x.%02x.%02x.%02x.%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); for (int i = 0; i < 4; i++){ ip[i] = EEPROM.read(i+7); } sprintf(IPAddressStr,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); for (int i = 0; i < 4; i++){ subnet[i] = EEPROM.read(i+11); } sprintf(SubnetMaskStr,"%d.%d.%d.%d",subnet[0],subnet[1],subnet[2],subnet[3]); for (int i = 0; i < 4; i++){ gateway[i] = EEPROM.read(i+15); } sprintf(GatewayStr,"%d.%d.%d.%d",gateway[0],gateway[1],gateway[2],gateway[3]); //アクセスNTPサーバー for (int i = 0; i < 4; i++){ timeServer[i] = EEPROM.read(i+19); } sprintf(NTPserverStr,"%d.%d.%d.%d",timeServer[0],timeServer[1],timeServer[2],timeServer[3]); } 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")); } //-------------------------- 2020/12/22 void SetUtilPage( 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>Modbus ")); client.print(titleStr); client.print(F(" Utility Page</TITLE>\n</HEAD>\n")); client.print(F("<BODY marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\" BGCOLOR=\"#ffffff\">\n")); client.print(F("<FORM>\n<BLOCKQUOTE><BLOCKQUOTE>\n")); client.print(F("<P><table bgcolor=\"#17A1A5\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\" CELLSPACING=\"2\">\n")); client.print(F("<tr><td>Modbus ")); client.print(titleStr); client.print(F(" Utility Page</td></tr></table><br>\n")); // client.print(F("<TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">")); client.print(F("<TR>\n\t<TD WIDTH=\"24%\">FirmwareVirsion:</TD>\n\t<TD WIDTH=\"29%\"></TD>\n\t<TD WIDTH=\"47%\">")); client.print(FVirsion); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"24%\">MacAddress:</TD>\n\t<TD WIDTH=\"29%\"></TD>\n\t<TD WIDTH=\"47%\">")); client.print(MacAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"24%\">IPAddress:</TD>\n\t<TD WIDTH=\"29%\"></TD>\n\t<TD WIDTH=\"47%\">")); client.print(IPAddressStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"24%\">SubnetMask:</TD>\n\t<TD WIDTH=\"29%\"></TD>\n\t<TD WIDTH=\"47%\">")); client.print(SubnetMaskStr); client.print(F("</TD>\n</TR>")); client.print(F("<TR>\n\t<TD WIDTH=\"24%\">Data acquisition time:</TD>\n\t<TD WIDTH=\"29%\"></TD>\n\t<TD WIDTH=\"47%\">")); client.print(DateTimeStr); client.print(F("</TD>\n</TR></TABLE></P>\n")); //DI client.print(F("<P><HR ALIGN=LEFT></P>\n<P><TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\" HEIGHT=\"589\">")); client.print(F("<TR>\n\t<TD ROWSPAN=\"2\" VALIGN=\"TOP\" WIDTH=\"25%\">DI(02)InputStatus</TD>\n")); client.print(F("\t<TD WIDTH=\"14%\" VALIGN=\"TOP\" HEIGHT=\"41\"><P ALIGN=RIGHT>0</TD>\n\t<TD WIDTH=\"7%\" HEIGHT=\"41\"></TD>\n")); client.print(F("\t<TD WIDTH=\"27%\" HEIGHT=\"41\" NOWRAP>\n")); for (int i = 0;i<8;i++){ client.print(F("\t\t<INPUT TYPE=\"checkbox\" NAME=\"DI_")); client.print(i); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_DISCRETE_INPUT,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n<TD WIDTH=\"27%\" HEIGHT=\"41\" NOWRAP>\n")); for (int i = 8;i<16;i++){ client.print(F("\t\t<INPUT TYPE=\"checkbox\" NAME=\"DI_")); client.print(i); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_DISCRETE_INPUT,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n</TR>\n<TR>\n\t<TD WIDTH=\"14%\" VALIGN=\"TOP\" HEIGHT=\"41\">")); client.print(F("<P ALIGN=RIGHT>1</TD>\n\t<TD WIDTH=\"7%\" HEIGHT=\"41\"></TD>\n")); client.print(F("\t<TD WIDTH=\"27%\" HEIGHT=\"41\" NOWRAP>\n")); for (int i = 16;i<24;i++){ client.print(F("\t\t<INPUT TYPE=\"checkbox\" NAME=\"DI_")); client.print(i); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_DISCRETE_INPUT,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"41\" NOWRAP>\n")); for (int i = 24;i<32;i++){ client.print(F("\t\t<INPUT TYPE=\"checkbox\" NAME=\"DI_")); client.print(i); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_DISCRETE_INPUT,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n</TR>\n<TR>\n\t<TD COLSPAN=\"5\"><HR ALIGN=LEFT></TD>\n</TR>\n")); //Coil client.print(F("<TR>\n\t<TD ROWSPAN=\"2\" VALIGN=\"TOP\" WIDTH=\"25%\">Coil(01)CoilStatus</TD>\n")); client.print(F("\t<TD ROWSPAN=\"2\" VALIGN=\"TOP\" WIDTH=\"14%\"><P ALIGN=RIGHT>0</TD>\n\t<TD WIDTH=\"7%\" HEIGHT=\"23\" ALIGN=\"RIGHT\">T</TD>\n")); client.print(F("\t<TD WIDTH=\"27%\" HEIGHT=\"23\" NOWRAP>\n")); for (int i = 0;i<8;i++){ client.print(F("\t\t<INPUT TYPE=\"radio\" NAME=\"CL_")); sprintf(buf1,"%02x",i); client.print(buf1); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_COILS,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\" NOWRAP>\n")); for (int i = 8;i<16;i++){ client.print(F("\t\t<INPUT TYPE=\"radio\" NAME=\"CL_")); sprintf(buf1,"%02x",i); client.print(buf1); client.print(F("\" disabled=\"disabled\" ")); if(Mb.GetBit(MB_FC_READ_COILS,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n</TR>\n<TR>\n\t<TD WIDTH=\"7%\" HEIGHT=\"23\" ALIGN=\"RIGHT\">F</TD>\n")); client.print(F("\t<TD WIDTH=\"27%\" HEIGHT=\"23\" NOWRAP>\n")); for (int i = 0;i<8;i++){ client.print(F("\t\t<INPUT TYPE=\"radio\" NAME=\"CL_")); sprintf(buf1,"%02x",i); client.print(buf1); client.print(F("\" disabled=\"disabled\" ")); if(!Mb.GetBit(MB_FC_READ_COILS,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\" NOWRAP>\n")); for (int i = 8;i<16;i++){ client.print(F("\t\t<INPUT TYPE=\"radio\" NAME=\"CL_")); sprintf(buf1,"%02x",i); client.print(buf1); client.print(F("\" disabled=\"disabled\" ")); if(!Mb.GetBit(MB_FC_READ_COILS,i)) client.print(F("CHECKED")); client.print(F(">\n")); } client.print(F("\t</TD>\n</TR>\n<TR>\n\t<TD COLSPAN=\"5\"><HR ALIGN=LEFT></TD>\n</TR>\n")); //IR client.print(F("<TR>\n\t<TD ROWSPAN=\"4\" VALIGN=\"TOP\" WIDTH=\"25%\">IR(04)InputRegistor</TD>\n")); for (int i = 0;i<4;i++){ client.print(F("\t<TD WIDTH=\"14%\" ALIGN=\"RIGHT\" VALIGN=\"TOP\" HEIGHT=\"23\">")); client.print(i); client.print(F("</TD>\n\t<TD WIDTH=\"7%\" HEIGHT=\"23\"></TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\">")); client.print(Mb.MbInputData[i]); client.print(F("</TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\"></TD>\n</TR>\n")); } client.print(F("<TR>\n\t<TD COLSPAN=\"5\"><HR ALIGN=LEFT></TD>\n</TR>\n")); //HR client.print(F("<TR>\n\t<TD ROWSPAN=\"16\" VALIGN=\"TOP\" WIDTH=\"25%\">HR(03)HolingRegistor</TD>\n")); for (int i = 0;i<16;i++){ client.print(F("\t<TD WIDTH=\"14%\" ALIGN=\"RIGHT\" VALIGN=\"TOP\" HEIGHT=\"23\">")); client.print(i); client.print(F("</TD>\n\t<TD WIDTH=\"7%\" HEIGHT=\"23\"></TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\">")); client.print(Mb.MbHoldData[i]); client.print(F("</TD>\n\t<TD WIDTH=\"27%\" HEIGHT=\"23\"></TD>\n</TR>\n")); } client.print(F("</TABLE>\n")); // client.print(F("</BLOCKQUOTE></BLOCKQUOTE>\n</form>\n</BODY></html>")); } //-------------------------- void checkWebPage( EthernetClient client) { Serial.println(F("new webClient")); boolean restart = false; if (client) { TextFinder finder(client); //TextFinderを使ってしまうと舐めた文字列は削除されてしまいます。つまり幾つかの候補が存在するかの確認には使いづらいです。 //一方indexOfは舐めても文字列は削除されません。しかし、今回の様な文字列をString型のまま扱うとコードステップ数が多くなりすぎるばかりで無く、見通しが悪いです。 // while (client.connected()) { //digitalWrite(LED, HIGH); if (client.available()) { //この部分はすべてのテキスト検索を行います。見つかればポインタはそこで止まります。 if( finder.find("GET /") ) { Serial.println(F("GETIn")); // 「setup」とい語が見つかった場合は、さらに探してください。 // その単語が見つからない場合は、検索を停止して先に進みます。 // これにより、後でスケッチに独自のWebページを配置できます。 if (finder.findUntil("setup", "\n\r")){ long cord = finder.getValue(); sprintf(buf1,"setupIn=%d",cord); Serial.println(buf1); switch(cord){ case 1: // 「SBM」という単語が見つかった場合は、さらに探し続けます。 // その言葉が見つからない場合は、探して停止します。 // SUBMITボタンが押されていない、何も押されていないことを意味します // セットアップページが構築されている場所に移動し、クライアントのブラウザに表示します。 Serial.print(F("SETUP")); if (finder.findUntil("SBM", "\n\r")){ Serial.println(F(" SBM")); //submitが押されている。 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をリセットする必要があります。 //ハードウェアリセットボタンを使用する必要があります。 restart = true; }else{ Serial.print(F("\n")); } // この時点から、セットアップページの構築を開始し、クライアントのブラウザーに表示できます。 SetWebPage(client); break; case 2: // 新規にユーティリティ頁を作りました。 Serial.println(F("utilIn")); rtclock.getTime(mtt); sprintf(DateTimeStr,"%04u/%02u/%02u %02u:%02u:%02u",mtt.year+1970, mtt.month, mtt.day,mtt.hour, mtt.minute, mtt.second); SetUtilPage(client); break; default: Serial.println(F("None1")); pageIntroduction(client); break; } }else{ Serial.println(F("None2")); pageIntroduction(client); } } break; } } //digitalWrite(LED, LOW); delay(1); client.stop(); if(restart) nvic_sys_reset(); } } //---------------------------- 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 sprintf(buf1,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); client.print("<HTML>\n<HEAD></HEAD>\n<BODY>\n<P>IT WORKS: go to <B><A HREF=\"https://"); client.print(buf1);client.print("/setup1\">"); client.print(buf1);client.print("/setup1</A></B></P>\n"); client.print("<BLOCKQUOTE>\n\t<BLOCKQUOTE>\n\t\t<P>or</P>\n\t</BLOCKQUOTE>\n</BLOCKQUOTE>\n"); client.print("<P>IT WORKS: go to <B><A HREF=\"https://"); client.print(buf1);client.print("/setup2\">"); client.print(buf1);client.print("/setup2</A></B></P>\n</BODY>\n</HTML>\n"); // put your own html until here 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 } //ModbusSlave6で追加16BIT分追加 16..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)); //LEDはLOWで点灯 } //ModbusSlave6で追加16BIT分追加 16..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]; } } //InputDataのIndex=0に温度をセット INTにする必要があるので100倍して整数化 25.65->2565 if(!ErrorTMP) Mb.MbInputData[1] = int(getTempAve(temp)*100); //InputDataのIndex=1に湿度をセット INTにする必要があるので100倍して整数化 57.88->5788 if(!ErrorHUM) Mb.MbInputData[2] = int(getHumAve(hum)*100); //InputDataのIndex=1に湿度をセット INTにする必要がある[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, 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(); } //---------------------------- 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) //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; } 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 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")); rtclock.getTime(mtt); sprintf(s, "%04u%02u%02u",mtt.year+1970, mtt.month, mtt.day); LEDandKEY.setDisplayToString(s); } //---------------------------- 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); //チャタリング対策として100msecdelay delay(100); //20201221 追加 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; } } } //---------------------------- |
オブジェクト自体はベクター殿のストレージをお借りしています。
https://www.vector.co.jp/vpack/browse/person/an051501.html 免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。