STM32F103を使ってみる

UDP通信とTCP通信の併用方法


前回、EEPROMとRTCを組み合わせて、ブートモードの判定方法を紹介し ま した。
ブートモードが判定できると、STANDBY機能を併用して、UDP通信とTCP通信を両方使うことができます。
UDP通信とTCP通信を両方使うことができるようになると、UDP通信で時刻合わせを行ったのち、
正確な時間を使ったTCP通信ができるようになります。

@PowerONモード
電源ONで起動するとこのモードになります。
EEPROMを初期化して、一時的にSTANDBY状態となります。
EEPROMを初期化しているので、STANDBY状態からの復帰後のブートモードはUDP通信モードになります。

AUDP通信モード
スケッチの書き込みを行うとこのモードになります。
@で起動後、一旦STANDBY状態になりますが、STANDBYからの復帰でもこのモードになります。
UDPを使ってNTPサーバから時刻を取り出してRTCに設定して、一時的にSTANDBY状態となります。
STANDBY状態からの復帰後のブートモードはTCP通信モードになります。

BTCP通信モード
RESETボタンを押すとこのモードになります。
Aで起動後、一旦STANDBY状態になりますが、STANDBYからの復帰でもこのモードになります。
TCPを使ってMQTTサーバーに接続し、現在時刻をPublishします。

スケッチは以下の通りです。
PA0/PA1/PA2にぞれぞれRed/Blue/GreenのLEDを繋げておくと、どのように動くのか分かります。
イーサネットモジュールにはW5500を使っています。

電源オン(PowerONモード)

Green点滅後、5秒間のSTANDBY

STANDBYから復帰(UDP通信モード)

Red点滅後、5秒間のSTANDBY

STANDBYから復帰(TCP通信モード)

Blue点滅し現在時刻をPublish

/*
 Set RTC & Send MQTT

 BootMode = Compile Mode(Blink RedLED)
 When you COMPLE skctch, it'll be this mode.
 Connect to NTP Server & set RTC & go to STANDBY mode & wake up.
 Next mode is Reset Mode.
 
 BootMode = Reset Mode(Blink BlueLED)
 When you push RESET button, it'll be this mode.
 Connect to MQTT Broker & Publish topic.

 BootMode = Power On Mode(Blink GreenLED)
 When you turn on board, it'll be this mode.
 Initialize EEPROM & goto STANDBY mode & wake up.
 Next mode is Compile Mode.
 

 W5x00 <--> STM32F103 Pinout
 SS    <--> PA4 BOARD_SPI1_NSS_PIN
 SCK   <--> PA5 BOARD_SPI1_SCK_PIN
 MISO  <--> PA6 BOARD_SPI1_MISO_PIN
 MOSI  <--> PA7 BOARD_SPI1_MOSI_PIN
 3.3V  <--> 3.3V
 GND   <--> GND
 3.3V  <--> 4.7KOhm <---> RST

*/
#include <SPI.h>        
#include <Ethernet_STM.h>   // https://github.com/rogerclarkmelbourne/Arduino_STM32
#include <EthernetUdp.h>    // https://github.com/rogerclarkmelbourne/Arduino_STM32
#include <PubSubClient.h> // https://github.com/knolleary/pubsubclient
#include <STM32Sleep.h>     // https://github.com/chacal/stm32sleep
#include <RTClock.h>        // https://github.com/rogerclarkmelbourne/Arduino_STM32/
#include <EEPROM.h>

RTClock rt (RTCSEL_LSE); // initialise
uint32 tt;
tm_t   tm;

//#define EPSIZE 252
#define EPSIZE 250

#define Red   PA0
#define Blue  PA1
#define Green PA2

#define INTERVAL        10
#define MQTT_SERVER     "192.168.10.40"
//#define MQTT_SERVER     "broker.hivemq.com"
//#define MQTT_SERVER     "iot.eclipse.org"
#define MQTT_PORT       1883
#define MQTT_TOPIC      "stm32f103" // You can change
#define MQTT_WILL_MSG   "I am leaving..." // You can change
#define TIME_ZONE       +9 // You can change

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
#if defined(WIZ550io_WITH_MACADDRESS) // Use assigned MAC address of WIZ550io
;
#else
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
#endif 

unsigned int localPort = 8888;      // local port to listen for UDP packets
IPAddress timeServer(133, 243, 238, 164); // NTP Server

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

EthernetUDP Udp;
EthernetClient ethClient;
PubSubClient pubsubClient(ethClient);

#define TCP   1
#define UDP   2

int execMode;
unsigned long lastTime = 0;

//Structure define of EEPROM(254Word)
struct EPROM{
  tm_t     tm; // Compile Date & Time
  uint16_t data[EPSIZE]; // User Data
};

const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

const char *dayName[7] = {
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

static void noop() {};

bool getTime(const char *str)
{
  int Hour, Min, Sec;

  if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
  tm.hour = Hour;
  tm.minute = Min;
  tm.second = Sec;
  return true;
}

bool getDate(const char *str)
{
  char Month[12];
  int Day, Year;
  uint8_t monthIndex;

  if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
  for (monthIndex = 0; monthIndex < 12; monthIndex++) {
    if (strcmp(Month, monthName[monthIndex]) == 0) break;
  }
  if (monthIndex >= 12) return false;
  tm.day = Day;
  tm.month = monthIndex + 1;
  tm.year = Year-1970;
  tm.weekday = dow(Year,tm.month,tm.day);
  return true;
}

// dow() [0-Sunday, 1-Monday etc.]
int dow(int y, int m, int d) {
  static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
//  y -= m < 3;
  if (m < 3) y--;
  return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}


int readEPROM(EPROM* hoge, tm_t* cdate) {
  uint16 AddressWrite = 0x10;
  uint16 Status;
  uint16 Data;
  uint32_t crc32;
 
  Serial.println("[readEPROM]");
  Status = EEPROM.read(AddressWrite++, &Data);
  if (Status != 0) return Status;
  cdate->year = (Data >> 8) & 0xFF;
  cdate->month = (Data >> 0) & 0xFF;
 
  Status = EEPROM.read(AddressWrite++, &Data);
  if (Status != 0) return Status;
  cdate->day = (Data >> 8) & 0xFF;
  cdate->weekday = (Data >> 0) & 0xFF;

  Status = EEPROM.read(AddressWrite++, &Data);
  if (Status != 0) return Status;
  cdate->pm = (Data >> 8) & 0xFF;
  cdate->hour = (Data >> 0) & 0xFF;

  Status = EEPROM.read(AddressWrite++, &Data);
  if (Status != 0) return Status;
  cdate->minute = (Data >> 8) & 0xFF;
  cdate->second = (Data >> 0) & 0xFF;

  for(int i=0;i<EPSIZE;i++) {
    Status = EEPROM.read(AddressWrite, &Data);
    if (Status != 0) return Status;
    hoge->data[i] = Data;
    AddressWrite++;
  }
  return 0;
}

int writeEPROM(EPROM* hoge, tm_t* cdate) {
  uint16 AddressWrite = 0x10;
  uint16 Status;
  uint16 Data;
  uint32_t crc32;

  Serial.println("[writeEPROM]");
  Data = (cdate->year << 8) + cdate->month;
  Status = EEPROM.write(AddressWrite++, Data);
  if (Status != 0) return Status;

  Data = (cdate->day << 8) + cdate->weekday;
  Status = EEPROM.write(AddressWrite++, Data);
  if (Status != 0) return Status;

  Data = (cdate->pm << 8) + cdate->hour;
  Status = EEPROM.write(AddressWrite++, Data);
  if (Status != 0) return Status;

  Data = (cdate->minute << 8) + cdate->second;
  Status = EEPROM.write(AddressWrite++, Data);
  if (Status != 0) return Status;

  for(int i=0;i<EPSIZE;i++) {
    Data = hoge->data[i];
    Status = EEPROM.write(AddressWrite, Data);
    if (Status != 0) return Status;
    AddressWrite++;
  }
  return 0;
}

void displayTM(char *title, tm_t hoge) {
  Serial.print(title);
  Serial.print(" year=");
  Serial.print(hoge.year);
  Serial.print(" monh=");
  Serial.print(hoge.month);
  Serial.print(" day=");
  Serial.print(hoge.day);
  Serial.print(" weekday=");
  Serial.print(hoge.weekday);
  Serial.print(" hour=");
  Serial.print(hoge.hour);
  Serial.print(" minute=");
  Serial.print(hoge.minute);
  Serial.print(" second=");
  Serial.println(hoge.second);
}

int difftm(tm_t tm1, tm_t tm2) {
  if (tm1.year != tm2.year) return 1;
  if (tm1.month != tm2.month) return 1;
  if (tm1.day != tm2.day) return 1;
  if (tm1.weekday != tm2.weekday) return 1;
  if (tm1.pm != tm2.pm) return 1;
  if (tm1.hour != tm2.hour) return 1;
  if (tm1.minute != tm2.minute) return 1;
  if (tm1.second != tm2.second) return 1;
  return 0;
}


void flashRed () {
  digitalWrite(Red,!digitalRead(Red));
}

void flashBlue () {
  digitalWrite(Blue,!digitalRead(Blue));
}

void flashGreen () {
  digitalWrite(Green,!digitalRead(Green));
}

void errorDisplay(char* buff) {
  int stat = 0;
  Serial.print("Error:");
  Serial.println(buff);
  while(1) {
    digitalWrite(Red,stat);
    digitalWrite(Blue,stat);
    digitalWrite(Green,stat);
    stat = !stat;
    delay(100);
  }
}

void setup() {
  struct EPROM romData;

  tt = rt.getTime();
  delay(1000);
  Serial.begin(9600);

  pinMode(Blue,OUTPUT);
  pinMode(Red,OUTPUT);
  pinMode(Green,OUTPUT);
  digitalWrite(Blue,LOW);
  digitalWrite(Red,LOW);
  digitalWrite(Green,LOW);

  if (tt != 0) { // Reset

    tm_t cdate;
    readEPROM(&romData,&cdate);
    displayTM("[cdate]",cdate);
 
    Serial.print("__TIME__=[");
    Serial.print(__TIME__);
    Serial.print("] __DATE__=[");
    Serial.print(__DATE__);
    Serial.println("]");
   
    // get the date and time the compiler was run
    if (getDate(__DATE__) && getTime(__TIME__)) {
      displayTM("[tm]",tm);
    }
 
    if (difftm(cdate, tm) == 0) { // Reset
      Serial.println("Compile date is MUCH!!");
      execMode = TCP;
      rt.attachSecondsInterrupt(flashBlue);
    } else { // Compile
      execMode = UDP;
      Serial.println("Compile date is NOT MUCH!!");
      writeEPROM(&romData,&tm);
      for(int i=0;i<20;i++) {
        digitalWrite(Red,HIGH);
        delay(50);
        digitalWrite(Red,LOW);
        delay(50);
      }
//      rt.attachSecondsInterrupt(flashRed);
    }
  } else { // Power ON
//    execMode = UDP;
    Serial.println("RTC is CLEAR!!");
    tm.year=0;
    writeEPROM(&romData,&tm);
    for(int i=0;i<20;i++) {
      digitalWrite(Green,HIGH);
      delay(50);
      digitalWrite(Green,LOW);
      delay(50);
    }
//    rt.attachSecondsInterrupt(flashGreen);
    rt.createAlarm(&noop, rt.getTime() + 5);
    goToSleep(STANDBY);
  }

  // start Ethernet
#if defined(WIZ550io_WITH_MACADDRESS)
  if (Ethernet.begin() == 0) {
#else
  if (Ethernet.begin(mac) == 0) {
#endif 
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    for(;;)
      ;
  }
  Serial.print("My IP: ");
  Serial.println(Ethernet.localIP());
  Serial.print("Netmask: ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("GW IP: ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("DNS IP: ");
  Serial.println(Ethernet.dnsServerIP());

  if (execMode == UDP) {
    Serial.println("UDP Mode");
    Udp.begin(localPort);
    while (1) {
      Serial.println("UDP packet send");
      sendNTPpacket(timeServer); // send an NTP packet to a time server
      delay(1000);
      if ( Udp.parsePacket() ) {
        break;
      }
      delay(10000);
    } 
    Serial.println("UDP packet receive");
   // We've received a packet, read the data from it
    Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord; 
    Serial.print("Seconds since Jan 1 1900 = " );
    Serial.println(secsSince1900);              

    // now convert NTP time into everyday time:
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;    
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears; 
    // print Unix time:
    Serial.print("Unix time = ");
    Serial.println(epoch);                              
    rt.setTime(epoch);
    tt = rt.getTime();
    Serial.print("The UNIX Time is ");
    Serial.print(rt.year()+1970);
    Serial.print('/');
    Serial.print(rt.month());
    Serial.print('/');
    Serial.print(rt.day());
    Serial.print(' ');
    Serial.print(rt.hour());
    Serial.print(':');
    Serial.print(rt.minute());
    Serial.print(':');
    Serial.println(rt.second());
    Serial.println();

    time_t localTime;                              
    localTime = rt.TimeZone(tt,TIME_ZONE); // Get Local Time
    Serial.print("Local Time = ");
    Serial.println(tt);
    rt.setTime(localTime); // 日本時間を設定
   
    Serial.print("The Local Time is ");
    Serial.print(rt.year()+1970);
    Serial.print('/');
    Serial.print(rt.month());
    Serial.print('/');
    Serial.print(rt.day());
    Serial.print(' ');
    Serial.print(rt.hour());
    Serial.print(':');
    Serial.print(rt.minute());
    Serial.print(':');
    Serial.println(rt.second());
    Serial.println();

    rt.createAlarm(&noop, rt.getTime() + 5);
    goToSleep(STANDBY);

    while(1) {}
  } else {
    Serial.println("TCP Mode");
    pubsubClient.setServer(MQTT_SERVER, MQTT_PORT);
 
    char clientid[30];
    IPAddress ip = Ethernet.localIP();
    Serial.print(ip[0]);
    Serial.print(".");
    Serial.print(ip[1]);
    Serial.print(".");
    Serial.print(ip[2]);
    Serial.print(".");
    Serial.println(ip[3]);
    sprintf(clientid,"STM32-%03d-%03d-%03d-%03d",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3]);
    Serial.print("clientid=");
    Serial.println(clientid);
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (!pubsubClient.connect(clientid,MQTT_TOPIC,0,0,MQTT_WILL_MSG)) {
      errorDisplay("connect Fail");
    }
    Serial.println("connected");
    lastTime = millis();
  }
}

void loop(){
  static int counter=0;
  static int value = 0;
  char msg[75];

  if (!pubsubClient.connected()) {
    errorDisplay("not connect");
  }
  pubsubClient.loop();

  long now = millis();
  if (now - lastTime > 1000) {
    lastTime = now;
    counter++;
    if (counter > INTERVAL) {
      ++value;
      snprintf (msg, 75, "I'm STM32F103 Local Time = %0d/%0d/%0d %02d:%02d:%02d",
      rt.year()+1970,
      rt.month(),
      rt.day(),
      rt.hour(),
      rt.minute(),
      rt.second());
      Serial.print("Publish message: ");
      Serial.println(msg);
      if (!pubsubClient.publish(MQTT_TOPIC, msg)) {
        errorDisplay("publish fail");
      }
      counter=0;
    }
  } 
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& 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;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // 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();
}

どのモードで起動しても最終的にはTCP通信モードにたどり着きます。
このモードで起動すると、以下の様にRTCの現在日時をPublishします。
STM32の場合、再起動がかかるとシリアルモニターが使えなくなるので、STANDBY後の動作を確認するには
LEDをLチカするとか1602LCDに表示するなどの処理が必要になります。


次回はRTCのアラーム機能を紹介します。

続く...