2017年3月27日 星期一

「智語」上善若水

上善若水,水善利萬物而不爭,處為人之所惡,故幾於道。
居善地,心善淵,與善仁,言善信,正善治,事善能,動善時。夫唯不爭,故無尤。


  有道德的人就像水,水能夠跟萬物融合卻不會跟萬物相爭,還能處在大家都討厭的低下

地方,因為這些特性所以可以說水跟道很接近了。

        有道德的人像水處在低下的地方一樣的謙虛,心就像水一樣的淵博沈靜,

付出就像水滋養萬物一樣的不求回報,言行就像水照萬物的清晰一樣的有誠信,

為政就像水能除垢一樣的有政績,做事就像水能方能圓一樣的有最好的功用,

行動就像水能隨順外物變化一樣的順應時勢。有道德的人就像水的不爭,

因為不計較所以就沒有怨尤。


資料來源:

2017年3月26日 星期日

[Arduino]RS485 初嘗

買了一塊WaveShare 的UART轉RS485(5V)板子來玩玩看。

先簡介一下RS485:

EIA-485(過去叫做RS-485或者RS485])是隸屬於OSI模型實體層的電氣特性規定為2線,半雙工,多點通訊的標準。它的電氣特性和RS-232不大一樣。用纜線兩端的電壓差值來表示傳遞訊號,1極的電壓標識為邏輯1,另一段標識為邏輯0。兩端的電壓差最小為0.2V以上時有效,任何不大於12V或者不小於-7V的差值對接受端都被認為是正確的。

EIA-485僅僅規定了接受端和傳送端的電氣特性。它沒有規定或推薦任何資料協定。EIA-485可以應用於配置便宜的廣域網路和採用單機傳送,多機接受通訊連結。它提供高速的資料通訊速率(10m時35Mbit/s;1200m時100kbit/s)。EIA-485和EIA-422一樣使用雙絞線進行高電壓差分平衡傳輸,它可以進行大面積長距離傳輸(超過4000英尺,1200公尺)。
EIA-485經常和常用裝置UART一起使用來實現在飛機上的低速率資料傳輸,舉個例子,一些乘客控制單元採用這種裝置,從而只需要很少的線纜就可以實現幾端子共享線纜,從而減輕整個裝置的重量。

                                                          所以RS485上會常見到一些協定,例如Modbus,BacNet and Profibus  。

當然RS485 也可以利用2組A/B來達成全雙工(一對專送,一對專收)

以上資料來源:

EIA-485 WIKI


簡單來說,RS485並沒有規定使用任何界面,只要電氣特性相符即可。

最左邊是接UART , 你可以看到它提供3種介面的RS485 A/B ,除了杜邦線之外就是2P端子 ,以及一個
































腳立說明如下,資料來源:



























主要特點為RSE這一支腳,它控制了資料流的方向,當RSE為HIGH時,代表要送資料,

RSE為LOW時表示要接收資料,所以一開始Master會預設為發送,remote會預設為接收。


由於RS485是半雙工的通訊方式,所以每一個裝都只能是傳送或是接收其中一種狀態。

參考了以下網址的範例。

https://arduino-info.wikispaces.com/SoftwareSerialRS485Example

這個範例master每次只能傳送一個byte,然候就會切換到接收模式直到Serial有再次輸入,就會切回去發送模式(此時remote傳再多它也收不到了),而remote在接收到資料後會切換成傳送模式把資料發送回去,10ms後就會再回到接收模式。


接腳如下:

arduino              RS485
10                        RX
11                        TX
3                         RSE
5V                       VCC
GND                    GDN 

二塊板子之間的A接A,B接B。















































之後燒錄程式,一邊為Master,一邊為remote


Master端程式如下:

/* YourDuino SoftwareSerialExample1
   - Connect to another Arduino running "YD_SoftwareSerialExampleRS485_1Remote"
   - Connect this unit Pins 10, 11, Gnd
   - Pin 3 used for RS485 direction control
   - To other unit Pins 11,10, Gnd  (Cross over)
   - Open Serial Monitor, type in top window. 
   - Should see same characters echoed back from remote Arduino

   Questions: terry@yourduino.com 
*/

/*-----( Import needed libraries )-----*/
#include <SoftwareSerial.h>
/*-----( Declare Constants and Pin Numbers )-----*/
#define SSerialRX        10  //Serial Receive pin
#define SSerialTX        11  //Serial Transmit pin

#define SSerialTxControl 3   //RS485 Direction control

#define RS485Transmit    HIGH
#define RS485Receive     LOW

#define Pin13LED         13

/*-----( Declare objects )-----*/
SoftwareSerial RS485Serial(SSerialRX, SSerialTX); // RX, TX

/*-----( Declare Variables )-----*/
int byteReceived;
int byteSend;

void setup()   /****** SETUP: RUNS ONCE ******/
{
  // Start the built-in serial port, probably to Serial Monitor
  Serial.begin(9600);
  Serial.println("YourDuino.com SoftwareSerial remote loop example");
  Serial.println("Use Serial Monitor, type in upper window, ENTER");
  
  pinMode(Pin13LED, OUTPUT);   
  pinMode(SSerialTxControl, OUTPUT);    
  
  digitalWrite(SSerialTxControl, RS485Receive);  // Init Transceiver   
  
  // Start the software serial port, to another device
  RS485Serial.begin(4800);   // set the data rate 

}//--(end setup )---


void loop()   /****** LOOP: RUNS CONSTANTLY ******/
{
  digitalWrite(Pin13LED, HIGH);  // Show activity
  if (Serial.available())
  {
    byteSend = Serial.read();
    digitalWrite(SSerialTxControl, RS485Transmit);  // Enable RS485 Transmit   
    RS485Serial.write(byteSend);          // Send byte to Remote Arduino
    digitalWrite(Pin13LED, LOW);  // Show activity    
    delay(10);
    digitalWrite(SSerialTxControl, RS485Receive);  // Disable RS485 Transmit       
   
  }
    
  if (RS485Serial.available())  //Look for data from other Arduino
   {
    digitalWrite(Pin13LED, HIGH);  // Show activity
    byteReceived = RS485Serial.read();    // Read received byte
    if (byteReceived!=0 && byteReceived!=10 && byteReceived!=13)
     {
        Serial.print("receive:");        // Show on Serial Monitor
        Serial.write(byteReceived);
        Serial.println();
     }
     
    delay(10);
    digitalWrite(Pin13LED, LOW);  // Show activity   
   }  

}//--(end main loop )---

/*-----( Declare User-written Functions )-----*/

//NONE
//*********( THE END )***********

remote端如下

/* YourDuino SoftwareSerialExample1Remote
   - Used with YD_SoftwareSerialExampleRS485_1 on another Arduino
   - Remote: Receive data, loop it back...
   - Connect this unit Pins 10, 11, Gnd
   - To other unit Pins 11,10, Gnd  (Cross over)
   - Pin 3 used for RS485 direction control   
   - Pin 13 LED blinks when data is received  
   
   Questions: terry@yourduino.com 
*/

/*-----( Import needed libraries )-----*/
#include <SoftwareSerial.h>
/*-----( Declare Constants and Pin Numbers )-----*/
#define SSerialRX        10  //Serial Receive pin
#define SSerialTX        11  //Serial Transmit pin

#define SSerialTxControl 3   //RS485 Direction control
#define RS485Transmit    HIGH
#define RS485Receive     LOW

#define Pin13LED         13

/*-----( Declare objects )-----*/
SoftwareSerial RS485Serial(SSerialRX, SSerialTX); // RX, TX

/*-----( Declare Variables )-----*/
int byteReceived;
int byteSend;

void setup()   /****** SETUP: RUNS ONCE ******/
{
  // Start the built-in serial port, probably to Serial Monitor
  Serial.begin(9600);
  Serial.println("SerialRemote");  // Can be ignored
  
  pinMode(Pin13LED, OUTPUT);   
  pinMode(SSerialTxControl, OUTPUT);  
  
  digitalWrite(SSerialTxControl, RS485Receive);  // Init Transceiver
  
  // Start the software serial port, to another device
  RS485Serial.begin(4800);   // set the data rate 
}//--(end setup )---


void loop()   /****** LOOP: RUNS CONSTANTLY ******/
{
  //Copy input data to output  
  if (RS485Serial.available()) 
  {
    byteSend = RS485Serial.read();   // Read the byte 
    digitalWrite(Pin13LED, HIGH);  // Show activity
    delay(10);              
    digitalWrite(Pin13LED, LOW);   
    Serial.write(byteSend);
    digitalWrite(SSerialTxControl, RS485Transmit);  // Enable RS485 Transmit    
    if (byteSend!=0 && byteSend!=10 && byteSend!=13)
        RS485Serial.write(byteSend); // Send the byte back
    delay(10);   
    digitalWrite(SSerialTxControl, RS485Receive);  // Disable RS485 Transmit      
//    delay(100);
  }// End If RS485SerialAvailable
  
}//--(end main loop )---

/*-----( Declare User-written Functions )-----*/
//NONE

//*********( THE END )***********

程式碼來源:(我有稍微做一點修改)
https://arduino-info.wikispaces.com/SoftwareSerialRS485Example

master執行畫面(記得選擇為沒有結尾)




remote執行畫面(使用CoolTerm)
記得先修改一下傳送的結尾模式



























由master傳送一個字元a,會收到一個相同回應

















remote端也會將收到的字元顯示並回傳給Master
















如果一次輸入一堆字傳送,可能就會出現問題,例如我輸入1234567890abcdefg

















remote端收到的也是不正確的字元









所以使用rs485做傳輸時,一定要注意收和送的時間點,建議使用現有的協定,例如ModBus,因為在收和送之間需要切換,這個如果沒有搭配好,會出現異常的資料。

2017年3月17日 星期五

[Arduino] EEPROM


EEPROM,或寫作E2PROM,全稱電子抹除式可複寫唯讀記憶體 (英語:Electrically-
Erasable Programmable Read-Only Memory),是一種可以通過電子方式多次複寫的半導
體存儲裝置。相比EPROM,EEPROM不需要用紫外線照射,也不需取下,就可以用特定的
電壓,來抹除晶片上的資訊,以便寫入新的資料。

EEPROM有四種工作模式:讀取模式、寫入模式、擦拭模式、校驗模式。
讀取時,晶片只需要Vcc低電壓(一般+5V)供電。編程寫入時,
晶片通過Vpp(一般+25V, 較新者可能使用 12V 或 5V)獲得編程電壓,
並通過PGM編程脈衝(一般50ms)寫入資料。擦拭時,只需使用Vpp高電壓,
不需要紫外線,便可以擦拭指定位址的內容。為保證寫入正確,在每寫入一塊資料後,
都需要進行類似於讀取的校驗步驟,若錯誤就重新寫入。
現今的 EEPROM 通常已不再需要使用額外的 Vpp 電壓,且寫入時間也已有縮短。


以上資料來源:

電子抹除式可複寫唯讀記憶體https://zh.wikipedia.org/wiki/%E9%9B%BB%E5%AD%90%E6%8A%B9%E9%99%A4%E5%BC%8F%E5%8F%AF%E8%A4%87%E5%AF%AB%E5%94%AF%E8%AE%80%E8%A8%98%E6%86%B6%E9%AB%94


在Arduino的微控器晶片內,其實有0.5KB到4KB不等的EEPROM記憶體(視MCU不同而有差異),可以用來儲存一些斷電後仍要留存的資料。

EEPROM 有寫入次數的限制,ATmega 系列微控制器的 EEPROM 其每一個位置大約只能寫入 10 萬次,在使用的時候,最好盡量公平對待 EEPROM 的每一塊空間,不要對特定一個位置一直寫入,如果頻繁地使用固定的一塊位置,那麼該塊空間壽命會急速縮短。

以下是不同Arduino Chip EEPROM大小

Chip Sizp
ATmega328 1024 bytes
ATmega168 512 bytes
ATmega8 1024 bytes
ATmega1280 4KB (4096 bytes)
ATmega2560 4KB (4096 bytes)

參考資料來源:
http://coopermaa2nd.blogspot.tw/2010/12/arduino-eeprom.html
以下是EEPROM API

  • EEPROM Clear: Clear the bytes in the EEPROM. 
  • 清除所有EEPROM資料(所以位址都將使用了一次)
  • EEPROM Read: Read the EEPROM and send its values to the computer.
  • 讀取指定的位址(一個byte)
  • EEPROM Write: Stores values from an analog input to the EEPROM.
  • 把資料寫入指定的位址(一個byte)
  • EEPROM Crc: Calculates the CRC of EEPROM contents as if it was an array.
  • 計算整個EEPROM的CRC ,確認資料是否正確。
  • EEPROM Get: Get values from EEPROM and prints as float on serial.
  • 一次取得n個byte的資料,例如float是4個byte。
  • EEPROM Iteration: Understand how to go through the EEPROM memory locations.
  • 說明如何透過EEPROM儲存資料。
  • EEPROM Put: Put values in EEPROM using variable semantics.
  • 一次放入n個byte的資料,例如我可以直接把float寫入連入4個byte。
  • EEPROM Update: Stores values read from A0 into EEPROM, writing the value only if different, to increase EEPROM life.
  • update和write不同在於,如果此位址中的資料和欲寫入的相同,則不寫,節省一次EEPROM位址的存取。

參考資料來源:

http://makerpro.cc/2015/05/arduino%CA%BBs-new-eeprom-libraries/
要使用EEPROM,必須引入EEPROM.h

首先我們先來取得一下EEPROM大小

#include <EEPROM.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

 Serial.print("EEPROM length: ");
 Serial.println(EEPROM.length());
 
   
}

void loop() {
  // put your main code here, to run repeatedly:

}

執行結果

























寫入位置0,再讀取位址0
#include <EEPROM.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);


  
  EEPROM.write(0,10);

  byte b=EEPROM.read(0);

  Serial.println(b);
}

void loop() {
  // put your main code here, to run repeatedly:

}

執行結果












接下來我們把寫入那行註解起來,上傳程式碼,然候我們斷電再接上看看。


#include <EEPROM.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);


  
  //EEPROM.write(0,10);

  byte b=EEPROM.read(0);

  Serial.println(b);
}

void loop() {
  // put your main code here, to run repeatedly:

}

執行結果。























接下來我們來試試put和get


#include <EEPROM.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);


  
  //EEPROM.write(0,10);

  byte b=EEPROM.read(0);

  Serial.println(b);


  float pi=3.14159;
  EEPROM.put(2,pi);

  float readfloat=0;
  
  EEPROM.get(2,readfloat);
  Serial.println(readfloat);
  
}

void loop() {
  // put your main code here, to run repeatedly:

}

執行結果,看起來float寫入EEPROM有精度問題,只能存4個byte,3.14159只能寫入3.14