最終更新日:2020年12月18日
2020/3/25 :ファームウエアを更新しました。 2020/8/11 :ファームウエアを更新しました。 IO拡張を検討しました。 自動再起動の必要性
オブジェクト自体はベクター殿のストレージをお借りしています。
https://www.vector.co.jp/soft/winnt/hardware/se521019.html 出来る事は、
- LabVIEW2014開発環境(32bit)環境で独自のアプリケーション開発が出来ます。
- テンプレート環境にて動作確認が行えるため、必要なセンサをスレーブデバイスに拡張が可能です。
このプロジェクトの目的は、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/
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> ")); 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らしい機能が欲しいと思いました。変更点
- HoldRegisterに上限下限の値を入力出来るようにし、InputRegisterの値と比較して、LED&KEYのLED1〜8を点灯させるという物です
- InputRegisterはアドレス0..3が対象です。HoldRegisterはアドレス8..15が対象です。挙動は以下に従います。
※WORD型ということもあり単位系の対策をしています。
信号名 挙動 InputRegister_0 ADC値0〜4095 Hold_8の値を下回ったらLED&KEYのLED1_ON Hold値0は無効を意味する。
Hold_9の値を上回ったらLED&KEYのLED2_ON Hold値0は無効を意味する。InputRegister_1 温度0〜5000
20.51℃ = 2051Hold_10の値を下回ったらLED&KEYのLED3_ON Hold値0は無効を意味する。
Hold_11の値を上回ったらLED&KEYのLED4_ON Hold値0は無効を意味する。InputRegister_2 湿度0〜9999
49.99% = 4999Hold_12の値を下回ったらLED&KEYのLED5_ON Hold値0は無効を意味する。
Hold_13の値を上回ったらLED&KEYのLED6_ON Hold値0は無効を意味する。InputRegister_3 気圧900〜1100
1気圧=1013hPaHold_14の値を下回ったらLED&KEYのLED7_ON Hold値0は無効を意味する。
Hold_15の値を上回ったらLED&KEYのLED8_ON Hold値0は無効を意味する。変更ではないのですが、計装で扱うことを考えると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> ")); 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に時刻登録するようにしました。幾つかインクルードファイルを追加しています。変更点
- Reset後、インターネット上のNTPサーバにアクセスしてCPU搭載のRTCに時刻登録します。
アクセスするサーバはソースコードに直接書いてしまっています。
いずれEEPROMの対象としたいです。- LED&KEYのボタンS4を押すと時刻表示します。S8が有効になっていれば時刻更新します。
LED&KEYのボタンS7を押すと日付表示します。使用しているライブラリには変更はありませんが、インクルードしているヘッダは増えています。
/* * 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の割込処理を一括しているようです。
そこで、ソースコードをレタッチしてみました。変更点
- setup画面でsubmitを押したとき、本体側がその処理完了後に自動再起動を行います。
実行にはnvic_sys_reset();を使っています。- Pageを追加しました。意図的にリモートで再起動を実行出来るボタン等を追加して見ました。
スケッチサイズが47%消費なのでまだ余裕があると判断しました。使用しているライブラリリストです。
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権は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社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。