2016年10月2日 星期日

使用Arduino 掃描I2C裝置的位址

#Arduino #Arduino UNO #Arduino MEGA2560 #Arduino DUE #I2C #IIC #I2C LCD #1602 LCD #LCD

這裏我先用一張圖來解釋Arduino 專案的發展需要的工具
我們常常到購物網買一些Arduino的sensor。。。由於Sensor 百花齊放,做的人沒有提供資料,賣的人也不清楚,技術資料轉來轉去,大家一片混亂,拿I2C界面LCD來講,明明範例的位置是0x27,但是我們照着寫就是動不了,差半天才知道LCD模組的位置不同,現在我就提供大家一個方法I2C address Scan 程式有了它就不用再傷腦了。

完整的程式碼如下:

// I2C Scanner
// Written by Nick Gammon http://gammon.com.au/i2c
// Date: 20th April 2011
// 2016-09-30
//中文註解李進衛
//IIC 位址掃描程式
//適用於Arduino UNO,DUE,MEGA2560
// 技術參考網址 http://playground.arduino.cc/Main/I2cScanner
// 作者網址(他有詳細說明I2C通信格式與原理) : http://gammon.com.au/i2c

#include

void setup() {
  Serial.begin (115200);

  // Leonardo: wait for serial port to connect
  while (!Serial)
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
 
  Wire.begin();
  for (byte i = 8; i < 120; i++) //地址掃描
  {
    Wire.beginTransmission (i); //對裝置發出I2C位址函數解釋請參考: https://www.arduino.cc/en/Reference/WireBeginTransmission
    if (Wire.endTransmission () == 0) //假如裝置有ACK 就顯示其位址資訊藉由RS232傳送出來
// 函數解釋請參考: https://www.arduino.cc/en/Reference/WireEndTransmission
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}


2016年9月24日 星期六

Rotary Encoder的程式動作原理以及Arduino程式的實現

#Arduino
#Rotary Encoder
#編碼器
#旋轉編碼器
Rotary Encoder 在一般的儀器或者家電產品常常看到他的蹤影在此特別的說明一下他的原理與程式的實現。






Rotary Encoder 外觀


這一張圖是ENCODER的工作原理【橘色】的部分是接地片或這是電源片,A,B兩點是信號它是利用角度的時間差來判斷正傳或者是反轉的,我個人覺得他的圖畫的很好再次引用說明。
圖片來源:http://howtomechatronics.com/tutorials/arduino/rotary-encoder-works-use-arduino/
【圖片版權屬該網站作者所有】


分別處於兩個角度當旋轉是ON、OFF就會輸出AB相位信號。
根據這個信號圖撰寫程式,程式如附檔這個程式有實測過是OK的
寫ENCODER程式會有幾個環節必須注意
  1. 外部中斷的設定方式:
Arduino 建議的語法如下:
attachInterrupt(digitalPinToInterrupt(18), interrupt1,RISING); //CHANGE);
必須使用這個語法才能動作,官方網站建議接腳的定義要寫這個【digitalPinToInterrupt(18)】,不能寫IO PORT
一般網路的寫法如下:
 attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
我在Arduino MEGA2560測試一直無法成功啓動外部中斷我不知道爲什麼,因爲無法看到compiler之後的組合語言所以無法得知爲什麼。
還有TOUCH功能和VR功能還沒有實現,繼續努力。

Arduino 範例:
void ENCODER_SUB(void)
{
if ((f_cw_start ==0)||(f_ccw_start==0))
{
// if(digitalRead(outputB)==1)
if (bit_is_set(P_ENC,BP_ENCB))
{
f_cw_start = 1;
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,RISING);  //CHANGE);
} else{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,RISING);  //CHANGE);
f_ccw_start = 1;
}
}

if ((f_cw_start ==1)||(f_ccw_start==1))
{
// if(digitalRead(outputB)==0)
if (bit_is_clear(P_ENC,BP_ENCB))
{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,FALLING);  if(encoder_counter <13 div="">
{
encoder_counter++;
}else{ 
encoder_counter = 0;
}
f_cw_start = 0;
}else{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,FALLING); 
if (encoder_counter >0)
{
encoder_counter--;
}else{
encoder_counter = 13;
}
f_ccw_start = 0;
}


}
 Serial.print("Interrupt-->");
 Serial.println(encoder_counter);
}


這個程式經過測試是OK的沒有漏格現象,但是由於我的Encoder他的AB相位只差1.3ms~3ms所以當你旋轉速度過快程式會反應不及,因爲Arduino的IO函數有延遲,使用我將digitalRead()改爲bit_is_set or bit_is_clear 兩個巨集指令。



2016年9月16日 星期五

使用Arduino 來偵測Rotary Encoder

# Arduino
# Rotary Encoder
# Interrutp
# Ext interrupt
# Arduino for Encoder
# Arduino for Rotary
# 兩個剪貼搞定 Rotary Encoder
使用Arduino來偵測Rotary Encoder 在網路的範例很多,但是他們似乎是不太適合我用,他們的方法,當我轉動Encoder會加二,或者會誤動作,讓人誤以爲Encoder不良,或者是漏格現象
針對這個問題我寫了一個即簡單有好用的程式碼在此公開給大家參考,如需要再改建,或者有錯誤,請讓我知道。
這是我們可以買得到的Rotary Encoder 

這個是Rotary Encoder的波形解析圖我的程式是來自於這張圖,在網路衆多圖片中我覺得這一張圖片畫的最好。

Encoder 和Arduino的接法【只是建議】,實際上的應用還是要以你的需要而定。
在硬體方面我的PinA是接在Arduino的外部中斷腳,PinB可以接在一般的Digital pin即可。
在Arduino的 Setup{

pinMode(outputB,INPUT); //設定19爲input
digitalWrite(outputB,HIGH); // 內部pull high 電阻開啓
attachInterrupt(digitalPinToInterrupt(outputA),ENCODER_SUB,FALLING);  
}
 宣告外部中斷。
外部中短的語法www.arduino.cc網站中建議使用
attachInterrupt(digitalPinToInterrupt(outputA),ENCODER_SUB,FALLING);
digitalPinToInterrupt(outputA)這個寫法在我這次的MEGA2560才有辦法啓動外部中斷
這一點要特別注意。

中斷的副程式內容如下:

void ENCODER_SUB(void)
{

if ((f_cw_start ==0)||(f_ccw_start==0))
{
if(digitalRead(outputB)==1)
{
f_cw_start = 1;
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,RISING);

} else{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,RISING);
f_ccw_start = 1;
}
}

if ((f_cw_start ==1)||(f_ccw_start==1))
{
if(digitalRead(outputB)==0)
{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,FALLING);
encoder_counter++;
f_cw_start = 0;

}else{
attachInterrupt(digitalPinToInterrupt(outputA), ENCODER_SUB,FALLING);
encoder_counter--;
f_ccw_start = 0;


}

}

}

2016年4月17日 星期日

使用Arduino 製作生產線counter


有時候管理者無法第一時間知道生產線的不良,這時候如果有一台警報器可以提醒管理者就很不錯了,依照慣例還是以Arduino來完成它,或許日後可以加上WIFI功能
這一些寫工作都是下班時間利用自己的時間逐步完成的。



#include // Main Loop 使用的函數
#include // IIC communication I2C使用的函數
#include // I2C LCD 使用的函數 使用LiquidCrystal目錄之下的LiquidCrystal_I2C.h
#include // SPI使用的函數【與涂書田馬達通信的函數】
#include   // EEPROM 
//#include
/*
//***********************************************************************************
//  程式設計:李進衛
//  Arduino UNO 
//  Compiler Arduino IDE
//  延續以前的程式架構採用main loop 架構
// main loop 5ms 
// 2014-06-29將microchip程式keyboard process base移植到arduino 系統
// 2014-06-29 移植check time process  to Arduino 
// 2014-06-29 compiler OK 需要再做hardware test 
// Pin 13 has a LED connected on most Arduino boards
// 因為LCD顯示速度比較慢如果還要讓文字有閃爍效果會造成MAIN LOOP的check time副程式超時時序產生錯誤,所以MAIN LOOP改為20MS【2016-03-23李進衛修改】
// 2016-03-23 新增加可以設定NG的數字使它更有彈性
//【按住P_clear按鍵--》開電--》當版本顯示完之後放開P_clear按鍵LCD顯示Set...閃爍狀態就可輸入set counter次數,設定完之後再按一次P_clear
// 確認就可以了】
//


//***********************************************************************************


*/

#define TBASE 20   // main loop time base for 20mS
// #define T100MS 20  // 
#define T20MS 20/TBASE  //4
#define T30MS 30/TBASE  //6
#define T100MS 100/TBASE
#define T150MS 150/TBASE
#define T200MS 200/TBASE
#define T250MS 250/TBASE
#define T300MS 300/TBASE  //60
//*******************2014-08-10 由PAD TEST V3 移植過來
#define P_counter 2
#define  P_clear 3
#define P_version 4
#define P_revers 5
#define P_relay 8
#define P_ss 10
#define P_LED 13
//#define swd 10
#define adcwait 20
#define ADC_margin 250  //ADC  margin
#define relayOn 1
#define relayOff 0
#define user_setcounter_address 0
#define user_counter_address 1 

const byte startport = 2; //設定起始pin為第2腳
const byte stopport = 13; //設定掃描線結束位置為13pin
int adcval = 0;
int padval = 0;
byte counter = 0; //counter NG
byte set_counter = 5;
byte keycount = 0;
byte usercounter = 0;
boolean f_keyon;
 boolean f_Warning = 0;
boolean f_setting = 0;
boolean f_user_set_counter = 0;
// boolean key4ok = 0;
// boolean key5ok = 0;
// boolean key6ok = 0;
// boolean key7ok = 0;
// boolean key8ok = 0;
// boolean key9ok = 0;
// boolean key10ok = 0;
// boolean key11ok = 0;
// boolean key12ok = 0;

boolean allok = 0;
byte keycode;
//*****************************************************
byte ledState;
boolean  Blink1HZ; //1HZ閃爍旗標
boolean Blink2HZ; //2HZ閃爍旗標
boolean TMAIN;
byte T_CNT1;
byte SQN = 0;
byte TB_100MS;
byte TB_250MS;
byte TB_500MS;
byte TB_1SEC;
byte TB_1MINS;
byte TB_1HOURS;
byte T_Blink_Count;
byte KEY_NEW = 0xff;   // = 0xFF;
byte KEY_OLD;   // = 0xFF;
byte KEY_CMD;   // = 0xFF;
byte KEY_CHT;         // = T20MS;
byte KEY_SQN  = 0;         // = 1;
boolean F_CMDON;
boolean F_KEYREP;
byte KEY_CHEN;
byte KEY_REP;
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

void setup()
{
DDRD = 0x00;  //設定埠D為輸入模式
// pinMode(P_ss,OUTPUT);
pinMode(P_LED,OUTPUT); // LED output pin for output mode
pinMode(P_relay,OUTPUT); // control relay port
digitalWrite(P_LED,LOW); // LED OFF
T_CNT1 = 0;  // CLEAR T_CNT1
TMAIN = 0;
usercounter = EEPROM.read(user_setcounter_address);
if (usercounter ==0xA5)
{
set_counter = EEPROM.read(user_counter_address);
}
  //pinMode(LED, OUTPUT);
//*************************
    //****** LCD setting ********
//*************************
  lcd.begin(16,2);         // initialize the lcd for 20 chars 4 lines, turn on backlight
  lcd.backlight(); //點亮背光
  // Print a message to the LCD.
  lcd.setCursor(2, 0); //設定游標在第2列第0行
  lcd.print("YA Horng PE ");
  lcd.setCursor(2, 1);
  lcd.print("Lee Chin Wei");
//  Serial.println("Ya horng");
//*******************************************
//設定通信模式
//*******************************************
  Serial.begin(115200);  // 開啟UART 鮑率為 115200
 // SPI.begin();
 // SPI.setBitOrder(MSBFIRST); // MSB先送
 // SPI.setDataMode(SPI_MODE1); // setting SPI mode clock 由0--> 1 資料由負緣取樣 
 // SPI.setClockDivider(SPI_CLOCK_DIV64); // SPI clock
  
delay(2500);
lcd.clear();
   lcd.setCursor(2, 0);  //設定游標在第2列第0行
  lcd.print("Counter ");
  lcd.setCursor(2, 1);
  lcd.print(counter,DEC);

  // Disable Arduino's default millisecond counter (from now on, millis(), micros(),
  // delay() and delayMicroseconds() will not work)
disableMillis();
  // Prepare Timer1 to send notifications every 1000000us (1s)
  // On 16 MHz Arduino boards, this function has a resolution of 4us for intervals <= 260000,
  // and a resolution of 16us for other intervals
  // On 8 MHz Arduino boards, this function has a resolution of 8us for intervals <= 520000,
  // and a resolution of 32us for other intervals
//  startTimer1(1000000L);
startTimer1(20000);  // main loop setting for 20mS by Lee Chin Wei 2016-03-23 modify 
if (!digitalRead(P_clear))
{
delay(20);
if (!digitalRead(P_clear))
{
f_setting = 1;
while ( !digitalRead(P_clear));
delay(20);
}
}  
}


void read_key(void)
{
if (!digitalRead(P_counter))
{
KEY_NEW = 1;
// Serial.println("KEY_NEW=1");
    return;
if (!digitalRead(P_clear))
{
KEY_NEW = 2;
//      Serial.println("KEY_NEW=2");
          return;
}
KEY_NEW = 0xff;
 //Serial.println("KEY_NEW=0xff");
}

//************************************
//***** NG 按鍵處理
//************************************
void key1_pro(void)
{
  if (F_KEYREP ==1) // 已經執行過了就返回不再重複執行
return;
if (f_setting ==1)
{
EEPROM.write(user_setcounter_address,0xA5);
set_counter++;
} else{
counter++; 
}
  lcd.setCursor(2, 0);  //設定游標在第2列第0行
  lcd.print("Counter ");
  lcd.setCursor(2, 1);
  if (f_setting ==1)
  {
 lcd.print(set_counter,DEC);

  } else{
lcd.print(counter,DEC);
}
if ((counter >= set_counter ) &&(f_setting ==0))
{
  digitalWrite(P_LED,HIGH); // LED ON
      digitalWrite(P_relay,relayOn);
  lcd.setCursor(6, 1);
  lcd.print("Warning !!");
  f_Warning = 1;
}
}

//******************************************
//***** CLEAR counter
//******************************************
void key2_pro(void)
{
if (F_KEYREP ==1) 
return;
if ( f_setting ==1)
{
EEPROM.write(user_counter_address,set_counter);
f_setting = 0;
lcd.setCursor(10, 0);  //設定游標在第2列第0行
lcd.print("      ");
}
counter = 0;
lcd.clear();  
lcd.setCursor(2, 0);  //設定游標在第2列第0行
lcd.print("Counter ");
lcd.setCursor(2, 1);
lcd.print(counter,DEC); // 
digitalWrite(P_LED,LOW); // LED Off
digitalWrite(P_relay,relayOff); // Relay turn off
f_Warning = 0;   //clear Warning flag
}
//******************************************
//***** 顯示版本處理副程式
//******************************************
/*
void key3_pro(void)
{
if (F_KEYREP ==1)
return;
  check_version();

}  
*/
//******************************************
//***** 馬達正轉 反轉處理副程式
//******************************************
/*
void key4_pro(void)
{
if (F_KEYREP ==1)
return;
  if (f_rf == 0)
  {
    f_rf = 1;
  motor_reverse();
  }else{
    f_rf = 0;
    motor_forward();
  }
}

void key5_pro(void)
{
if (F_KEYREP ==1)
return;

}

*/


//******************************************************************************
        //;****************************
        //;*****  KEYON_PROCESS   *****
        //;****************************
        //;FOR CHECK KEY FUNCTION WHEN FIRST KEY PRESSED
        //;當KEY按住不放時,仍然只執行一次
void    KEYON_PROCESS()
{
        switch ( KEY_CMD )
        {
case 1 : key1_pro(); break;
case 2 : key2_pro(); break;
// case 3 : key3_pro(); break;
// case 4 : key4_pro(); break;
// case 5 : key5_pro(); break;
// case 6 : key6_pro(); break;
// case 7 : key7_pro(); break;
// case 8 : key8_pro(); break;
// case 9 : key9_pro(); break;
// case 10 : key10_pro(); break;
// case 11 : key11_pro(); break;
// case 12 : key12_pro(); break;
// case 13 : key13_pro(); break;
// case 14 : key14_pro(); break;
// case 15 : key15_pro(); break;
// case 16 : key16_pro(); break;
        }
}


        //;****************************
        //;*****  KEYOFF_PROCESS  *****
        //;****************************
        //;FOR CHECK KEY FUNCTION WHEN KEY RELEASED
void    KEYOFF_PROCESS()
{
        switch ( KEY_CMD )
        {
// case 1: key1_off_pro(); break;
// case 2: key2_off_pro(); break;
// case 3: key3_off_pro(); break;
// case 4: key4_off_pro(); break;
// case 5: key5_off_pro(); break;
// case 6: key6_off_pro(); break;
// case 7: key7_off_pro(); break;
// case 8: key8_off_pro(); break;
// case 9: key9_off_pro(); break;
// case 10: key10_off_pro(); break;
// case 11: key11_off_pro(); break;
// case 12: key12_off_pro(); break;
// case 13: key13_off_pro(); break;
// case 14: key14_off_pro(); break;
// case 15: key15_off_pro(); break;
// case 16: key16_off_pro(); break;
        }
}


void    KEYCHT_440()
{
        KEY_SQN = 1;
        KEYOFF_PROCESS();       // 按鍵放開的第一時間處理
}


void    initial_key()                   //.............. Step 0
{
        KEY_NEW = 0xFF;
        KEY_OLD = 0xFF;
        KEY_CMD = 0xFF;
        KEY_CHT = T20MS;
        KEY_SQN = 1;
}


void    new_old_check()                 //.............. Step 1
{
        if ( (KEY_NEW!=KEY_OLD)&&(KEY_NEW!=0XFF) )
        {
        KEY_OLD = KEY_NEW;
        KEY_CHT = T100MS;
        KEY_SQN = 2;
        }
        if ( (KEY_NEW!=KEY_OLD)&&(KEY_NEW==0XFF) )
        {
        KEY_SQN = 0;
        }
}


void    confirm_key()                   //.............. Step 2
{
        if ( KEY_NEW == KEY_OLD )
        {
 //               Serial.println("key confirm !!");
                KEY_CMD = KEY_NEW;
                F_CMDON = 1;            // 按鍵生效
                KEY_REP = T200MS;       // 原來的值 T700MS,T500MS
                KEY_CHT = T30MS;
                KEYON_PROCESS();        // 按鍵按下的第一時間處理
                KEY_SQN = 3;
        }
        else
        {
                if ( (KEY_NEW!=0xFF) || ((KEY_NEW==0xFF)&&(KEY_CHT==0)) )
                {
                KEY_OLD = KEY_NEW;
                KEY_SQN = 1;
                }
        }
}


void    hold_key()                      //.............. Step 3
{
        if ( KEY_NEW == KEY_OLD )       // === KEY REPEAT 
        {
                if (KEY_NEW==0)
                {
                        KEY_CHT = T20MS;
                        F_KEYREP = 0;           //
                        KEY_SQN = 4;            //2004.12.07 APPEND BY JEREMY(對應按鍵失效)
                }
                KEY_CHT = T30MS;
                if ( KEY_REP == 0 )
                {
                       KEY_REP  = T200MS;      // 200 ms time out
                       F_CMDON  = 1;           // 按鍵生效
                       F_KEYREP = 1;           // KEY REPEAT
  KEY_COD_PROCESS(); // 2016-03-21 by leecw Append for key on process
                }
        } else

                if ( (KEY_NEW==0xFF)&&(KEY_CHT==0) )
                {
                        KEY_OLD = KEY_NEW;
                        KEY_CHT = T20MS;
                        F_KEYREP = 0;           // CLEAR KEY REPEAT FLAG
                        KEY_SQN = 4;            //執行release_key的判斷
                }
//        else if ( KEY_NEW < KEY_OLD )                   // === 按住 X 鍵(KEY CODE較大的按鍵)再按 Y 鍵(KEY CODE較小的按鍵)
//        {
//                if ( (KEY_CHT == 0) && (KEY_NEW == 1) || (KEY_NEW == 2) )//KEY_CODE(BEND-)=1,KEY_CODE(BEND+)=2 
//                {
//                        F_KEYREP = 0;
//                        KEYCHT_440();   //須執行按鍵的動作
//                }
 //       }
//        else if ( KEY_NEW > KEY_OLD )                   // === KEY RELEASE或再按住 X 鍵(KEY CODE較大的按鍵)
//        {       
//                if ( (KEY_NEW==0xFF)&&(KEY_CHT==0) )
//                {
//                        KEY_OLD = KEY_NEW;
//                        KEY_CHT = T20MS;
//                        F_KEYREP = 0;           // CLEAR KEY REPEAT FLAG
//                        KEY_SQN = 4;            //執行release_key的判斷
//                }
//                else if ( KEY_NEW != 0xFF )
//                {
//                        if ( (KEY_CHEN==0)&&( (KEY_NEW==1)||(KEY_NEW==2) ) )    // 對應按住 BEND- 放開後迅速按 BEND+ 的處理
//                        {
//                                F_KEYREP = 0;           // CLEAR KEY REPEAT FLAG
//                                KEYCHT_440();           //
//                        }
//                }
//        }
}


void    release_key(void)                   //.............. Step 4
{        
        if ( KEY_NEW == KEY_OLD )
        {
                if ( KEY_CHT == 0 )
                KEYCHT_440();
        }
        else if ( (KEY_NEW!=KEY_OLD)&&(KEY_NEW!=KEY_CMD) )              // 對應在處理 KEY_CHT 的 timming 再按到 BEND +/-  
        {                                                               //
                if ( (KEY_CHEN==0)&&((KEY_NEW==1)||(KEY_NEW==2)) )      //
                {                                                       //
                KEYCHT_440();                                           //
                }                                                       //
                else                                                    //
                {                                                       //
                KEY_OLD = KEY_NEW;                                      //
                KEY_CHT = T30MS;                                        //
                }                                                       //
        }
        else if ( (KEY_NEW!=KEY_OLD)&&(KEY_NEW==KEY_CMD) )
        {
                KEY_OLD = KEY_NEW;
                KEY_SQN = 3;
        }
}




        //;***********************************
        //;*****  KEY CHATTER  &  CHECK  *****
        //;*****  HAVE  ANY  KEY  INPUT  *****
        //;***********************************
        //;IN     :KEY_NEW,KEY_SQN,KEY_CHT
        //;OUT    :KEY_OLD,KEY_CMD,F_KEYREP
        //;KEY_SQN =  0 --> INITIAL ALL REGISTER
        //;KEY_SQN =  1 --> CHECK KEY_NEW & KEY_OLD
        //;KEY_SQN =  2 --> KEY ON CHATTER CHECK
        //;KEY_SQN =  3 --> KEY REPEAT CHECK
        //;KEY_SQN =  4 --> KEY OFF CHATTER
        //;KEY_SQN = 32 --> 按住 X 按鍵再按 Y 鍵
        
void    KEY_CHT_PROCESS()
{
        switch ( KEY_SQN )
        {
        case  0:
                initial_key();          // *** 初始所使用的暫存器
                break;
        case  1:
                new_old_check();        // *** 檢查新碼和舊碼
                break;
        case  2:
                confirm_key();          // *** 承認按下的按鍵
                break;
        case  3:
                hold_key();             // *** 保持按鍵按下的處理
                break;
        case  4:
                release_key();          // *** 放開按鍵
                break;
        default:
                KEY_SQN = 0;
                break;
        }
}

void K_counter_add(void)
{
if (f_setting ==1)
{
set_counter++;
} else{
counter++;
}
  if ((counter >= set_counter ) &&(f_setting ==0))
  {
       digitalWrite(P_LED,HIGH);  // LED ON
      digitalWrite(P_relay,relayOn);
lcd.setCursor(6, 1);
lcd.print("Warning !!");
f_Warning = 1;
  }    
    lcd.setCursor(2, 0);  //設定游標在第2列第0行
  lcd.print("Counter ");
  lcd.setCursor(2, 1);
  if (f_setting ==1)
  {
 lcd.print(set_counter,DEC);

  } else{
lcd.print(counter,DEC);
}
  
  }

        //;*****************************************
        //;*****  KEY CODE PROCESS SUBROUTINE  *****
        //;*****************************************
        //;IN     :KEY_CMD
        //;OUT    :當KEY按住不放時,利用KEY_REP的設定,可重複執行KEY CODE動作
void    KEY_COD_PROCESS()
{
        if ( F_CMDON == 1 )
        {

        F_CMDON = 0;
                switch ( KEY_CMD )
                {
//                        case 0 : K_PITCH();     break;
                        case 1 :   K_counter_add(); break;  //K_PIT_M();     break;
//                        case 2 : K_PIT_P();     break;
//                        case 3 : K_STOP();      break;
//                        case 4 : K_SINGLE();    break;
//                        case 5 : K_TIME();      break;
//                        case 6 : K_PLYPAS();    break;
//                        case 7 : K_PTEN();      break;
//                        case 8 : K_CUE1();      break;
//                        case 9 : K_BSKIP();     break;
//                        case 10: K_FSKIP();     break;
//                        case 11: K_FBS();       break;
//                        case 12: K_FFS();       break;
//                        case 13: K_OPCL();      break;
//                        case 14: K_BPM();       break;
                }
        }
}


//******************************************************************************


void t100ms_process()
{
  if (TB_500MS ==0)
  {
    TB_500MS = 5;
    //0.5sec要處理的程式放這裡
 //     ledState ^= 1;
      Blink1HZ ^=1;  // Blink1HZ與1 互斥或  【反向的意思】
  } else {
           
           TB_500MS--;
            
  }
  
  
}

//***************************
//***** 系統時間
//***************************
void check_time()
{
  if (TB_100MS ==0)
  {
    TB_100MS = T100MS;
    t100ms_process();    
  } else {
    
            TB_100MS--;
          }
    
  if (TB_250MS ==0)
  {
      TB_250MS = T250MS;
      Blink2HZ ^=1;  // Blink2HZ與1 互斥或  【反向的意思】
      
    } else{
        TB_250MS--;
      }
  if (T_Blink_Count ==0)
  {
      T_Blink_Count = 50; //100;    
//      Blink1HZ ^=1; // Blink1HZ與1 互斥或  【反向的意思】
//      Blink2HZ ^=1; // Blink2HZ與1 互斥或  【反向的意思】
      
    } else{
        T_Blink_Count--;
        if (T_Blink_Count == 25)  //50) 
        {
//                      Blink2HZ ^=1;
       
        }
    
    }
      
  if (T_CNT1 !=0)
   T_CNT1--;
   
   if (KEY_CHT !=0)
   KEY_CHT--;
   
   if (KEY_REP !=0)
   KEY_REP--;
   


}

void display_process()
{

if (f_setting ==1)
{
              if (Blink1HZ == 1)
 {
 lcd.setCursor(10, 0);
              lcd.print("Set...");
 } else{
lcd.setCursor(10, 0);
lcd.print("      ");
 
 
}
}
    if (f_Warning ==1)
    {
    if ( Blink1HZ==1)
    {
    digitalWrite(P_LED,HIGH);
    digitalWrite(P_relay,relayOn);
    lcd.setCursor(6, 1);
    lcd.print("Warning !!");
    
    } else {
              digitalWrite(P_LED,LOW);
              digitalWrite(P_relay,relayOff);
              lcd.setCursor(6, 1);
              lcd.print("          ");
          
    }
    } 
}

//********************************************************************
//********************************************************************
//*****  Main Loop 
//*****  Program by Chin Wei Lee
//*****  Date : 2014-06 28
//********************************************************************
//********************************************************************

void loop()
{
  while (TMAIN){
check_time();   // 檢查系統時間【所有的時間處理】
read_key(); // 讀取按鍵
KEY_CHT_PROCESS(); //按鍵的反彈跳處理及按鍵的處理
        display_process();   // Warning 的處理
TMAIN = 0;
}

}

// Define the function which will handle the notifications
ISR(timer1Event)
{
  TMAIN = 1;
  // Reset Timer1 (resetTimer1 should be the first operation for better timer precision)
//    Serial.println("time interrupt 5ms");
  resetTimer1();
//  check_time();
  // For a smaller and faster code, the line above could safely be replaced with a call
  // to the function resetTimer1Unsafe() as, despite its name, it IS safe to call
  // that function in here (interrupts are disabled)
  
  // Make sure to do your work as fast as possible, since interrupts are automatically
  // disabled when this event happens (refer to interrupts() and noInterrupts() for
  // more information on that)
}



WEMOS D1 MINI ESP8266 製作 NTP + 網路斷線自動重置裝置

我的同事建議我將 NTP 加上網路斷線偵測可以重新reset的功能