顯示具有 protocol 標籤的文章。 顯示所有文章

S35390A driver for 8051 part 2

Posted by: 邱小新 at 下午4:11 in ,
原始碼下載

● 警報中斷 alarm interrupt

  1. alarm 的設置很簡單,只要先設置好警報時間,再設置中斷模式即可。
  2. 中斷模式有很多種,主要使用 alarm interrupt 模式,即 INT1AE=1,INT1ME=0,INT1FE=0。
  3. 當警報觸發時,INT1 pin 會從 HIGH 變成 LOW,直到設置 INT1AE=0,INT1 pin 才又恢復成 HIGH。
  4. 當警報觸發時,register status1 中的 INT1 會變成 1,只要讀取 status1 後自動變成 0,即下次再讀取 INT1 的值為 0。
  5. 當警報觸發時,而且 INT1AE=1,每隔一秒就會重置 status1(INT1=1)。也就是讀取完 status1 後,再次讀取 INT1 的值為 0,但是在下一秒再次讀取 status1,則 INT1 又會變成 1,每隔一秒就再次變成 1,除非當 INT1AE=0,則此動作就取消。
  6. 手動設定連接 INT1 pin 的 port 是沒有作用的,也就是手動設定 INT1 並不會觸發中斷。
  7. S35390A 的 alarm interrupt 剛好符合 8051 的 external interrupt,可以直接用 external interrupt 來偵測。如果沒有多餘的腳位,則可改成讀取 status1 來判斷 interrupt。
  8. 如果一開始就把 INT1 pin 拉 LOW,就會造成當 alarm interrupt 發生後要取消回 HIGH 時,不會自動回復成 HIGH,而是一直呈現在 LOW 的狀態下。

void s35390a_write_alarm1(unsigned char xdata *value) { unsigned char i=10, j; while (--i) { // set alarm time i2c_start(); if(i2c_write(ADDR_INT1 | I2C_WRITE)) continue; for (j=0; j<RTC_ALARM_LENGTH; j++) { if(i2c_write(value[j])) break; } if (j<RTC_ALARM_LENGTH) continue; i2c_stop(); // start alarm j = s35390a_read_status_2(); j = j & INT1_MASK | INT1AE; s35390a_write_status_2(j); break; } }

● free SRAM register

  1. S35390A 可使用的 SRAM 只有二個 bytes,一個是完整的 Free register,一個是分散在各地的 Scratch bits。
  2. Scratch bits 中的 SC0/SC1 存在 status1,所以任意時間都可以用。而 SC2~SC7 存在 INTx register,只有當 INTxME=0 and INTxFE=1,才可以使用。

S35390A driver for 8051 part 1

Posted by: 邱小新 at 下午1:34 in ,
原始碼下載

● 簡介

S35390A 是由精工電子有限公司開發製造,Seiko Instruments Inc. 是一家日本公司,台灣也有子公司,網址為 http://www.sii.com.tw/。

datasheet 下載點: 簡體中文版,英文版,日文版。

● 電源 Power Supply

  1. 一般來說 S35390A 的電源是由鈕扣電池(CR2032 3V)所提供,一開始我還以為是由主板上的電源所提供,害我一直誤解為啥 Power-on 就要重置時間。原來是說當電池沒電或換了新電池時,才會有 power-on detection。
  2. BLD 為電壓降低偵測旗標,S35390A 一秒才會做一次電壓降低偵測,所以只要電池拔掉再裝回去的時間在一秒內都不會引起 BLD。
  3. POC 為 Power-on Detection,只要把電池拔掉約五秒,再次裝上電池就會觸發POC。
  4. 當 POC or BLD 被設置,都需要做一次 chip reset。

● 協定 protocol

  1. S35390A 是使用 I2C protocol,在 1.5V 有 100kHz 速度,在 3.0V 有 400kHz 速度,算是快速的 I2C chip。
  2. 在早期的 datasheet 裏跟現在的 datasheet(Rev.2.5_00) 對於經由 command 所取的 data 描述大不同。主要是早期接收 bit 時,是從 LSB 到 MSB,剛好跟 command 的傳送方式相反,造成一些問題。而新的 datasheet 就把 data 的描述顛倒過來,使得 command 跟 data 的 bit order 剛好一致,但本質是不變的。
  3. 參數設定 command 對於 bit order 不同是沒有差異的,主要是時間 command 對於 bit order 是很敏感的。由於 bit order 接收是 little endian 而非普遍的 big endian,所以對於 I2C protocol 必須重新設計,不能使用舊的方法。
  4. 如果是按照新的 datasheet 來說,是不用重新設計 protocol,但是由於接收下來的時間卻 little endian,所以必須另外寫一個轉換函數把 little endian 轉換成 big endian。
  5. 執行 command(Real-time data 1 access),照原始 I2C protocol 來說,讀取最後一個 byte 回傳 ack=1,讀取中間的 bytes 一律回傳 ack=0。當然 S35390A datasheet 也是如此寫,沒有錯誤。但是實際上在 coding 的過程中卻發現完全相反,讀取中間的 bytes 必須回傳 ack=1,才能得到正確值;如果回傳 ack=0,傳回值卻都是 0。真是奇也怪哉,莫名奇妙,害我玩了好多天才找到問題所在。
  6. 在設定時間時,必須一次寫入完整 7 bytes,如果漏了一個 byte 就執行 i2c_stop, S35390A 則不會存入這次寫入的時間。

● slave address

S35390A 的 slave address 為 7-bit addressing 變化型,也就是把前 4 bits 固定,後 3 bits 來當做 command。因此在 I2C bus 上的 chip,前 4 bits 不能為 0110,也就是不能為 0x6? 的 slave address,否則會跟 S35390A 相衝突。

● chip init

按照 datasheet 所陳述的初始化流程圖,有下列需要注意的事項。

  1. 當電源接上時(也就是裝上電池),會設置 POC=1;在此時需要設置 RESET=1,讓 chip reset。
  2. 當電源拔掉時(也就是突然接觸不良),會設置 BLD=1;在此時需要設置 RESET=1,讓 chip reset。
  3. 當有不明原因讓 chip 進入 test mode 時,會設置 TEST=1;在此時需要設置 RESET=1,讓 chip reset。
  4. chip reset 完之後,必需再次檢查上述三項條件。
void s35390a_init(void) { unsigned char tmp; while (1) { // check status 1 tmp = s35390a_read_status_1(); if ((tmp & (POC | BLD)) || ((tmp & H24) == 0)) { s35390a_reset(); continue; } // check status 2 tmp = s35390a_read_status_2(); if (tmp & TEST) { s35390a_reset(); continue; } break; } // read datetime s35390a_read_data1(rtc_data); }

to be continue...

ir remote control 加入 repeat/long key

Posted by: 邱小新 at 下午1:47 in

在 ir remote control NEC protocol 裏只是提到如何處理一個按鍵的處理。在實際應用上,常常會有遇到按著不放時,要重覆處理這個按鍵,或是按住某個按鍵多久才處理。現在我們就來討論這二個應用方法。

下面主要是用來做一個 buffer,免得 mcu 處理來不急,ir_put 都放在 interrupt 內使用,並沒有給一般常式使用;而 ir_get 只有 ir_process 會用到,但是 counter 跟 ir_put 共用到,所以加上中斷排除,免得發生同時修改的慘況。

#define IR_BUFFER_MAX 4 #define IR_BUFFER_MASK 3 static xdata unsigned char ir_buffer[IR_BUFFER_MAX]; static data unsigned char get_index, put_index, counter; static unsigned char ir_debound, ir_key, ir_repeat_key, ir_long_key; static unsigned char code ir_repeat[] = { IR_UP, IR_DOWN, IR_LEFT, IR_RIGHT }; #define IR_REPEAT_MAX (sizeof(ir_repeat)/sizeof(unsigned char)) static void ir_put(unsigned char value) { if (counter < IR_BUFFER_MAX) { ir_buffer[put_index] = value; counter++; put_index++; put_index &= IR_BUFFER_MASK; } } static unsigned char ir_get(void) { unsigned char value; value = ir_buffer[get_index]; ET0 = LOW; counter --; ET0 = HIGH; get_index++; get_index &= IR_BUFFER_MASK; return value; }

下面是在 interrupt 內會用到 ir_put 的片斷,只有二個地方;一個是輸入 repeat 訊號,一個是輸入按鍵訊號。

if (IS_IR_REPEAT(pulse)) { if (ir_pos == 32) ir_put(IR_REPEAT); return; } if(ir_pos >= 32) { ir_mode = 0; stop_timer(); if ((ir_code[0] | ir_code[1]) != 0xef) return; if ((ir_code[2] | ir_code[3]) != 0xff) return; if (ir_code[0] == 0x40) ir_put(ir_code[2]); }

下面是放在 main loop 裏的函數,ir_function 用來處理按鍵事件,而 ir_process 是用來計算處理 normal,repeat,long 按鍵的函數。

void ir_function(unsigned char key) { switch (key) { default: printf_tiny("ir recv: %x,%d,%d,%d\n\r", ir_key, get_index, put_index, counter); } } void ir_process(void) { unsigned char key; if (counter == 0) { return; } key = ir_get(); switch (key) { case IR_REPEAT: if (ir_debound == 0) { ir_debound++; if (ir_key == IR_LONG_KEY) { ir_long_key = IR_LONG_KEY; break; } for (key=0; key<IR_REPEAT_MAX; key++) { if (ir_key == ir_repeat[key]) break; } if (key < IR_REPEAT_MAX) ir_repeat_key = ir_key; } else if (ir_repeat_key != IR_NONE) { if (ir_debound > IR_REPEAT_COUNT) ir_function(ir_repeat_key); else ir_debound++; } else if (ir_long_key != IR_NONE) { if (ir_debound < IR_LONG_COUNT) { ir_debound++; } else if (ir_debound == IR_LONG_COUNT) { ir_function(ir_long_key); ir_debound++; } } break; default: ir_debound = 0; ir_long_key = IR_NONE; ir_repeat_key = IR_NONE; ir_key = key; if (key != IR_LONG_KEY)&#12288;ir_function(key); } }

I2C protocol 時間計算

Posted by: 邱小新 at 上午10:23 in

I2C protocol 原理及應用有提供了一個範例程式碼,裏面的 i2c_wait 在當時是用試誤法來測出需要幾個 nop 指令。最近正好在做新的案子,使用不同的 crystal,就想用時脈來計算出真正需要多少個 nop 指令。

以下推導過程,使用 22.1184MHz 的振盪器,I²C匯流排速度為標準模式(100 Kbit/s)。

  1. 1 clock = 1/22.1184 us
  2. 1 machine cycle = 12 clock = 12/22.1184 us
  3. 1 nop = 1 machine cycle = 12/22.1184 us = 542.534722 ns
  4. 100 kbit/s = 1bit per 1/100000s = 10 us
  5. 1 bit cycle = 10/0.542 = 18.432 machine cycle
  6. 2 * i2c_wait = 1 bit cycle ==> i2c_wait = 9.2 machine cycle = 9 nop

實際上使用的 nop 有 8 個,可以正常 read/write 256 bytes eeprom。但是由於還會呼叫 lcall,ret 等指令,理論上應該少更多才對。不知是 eeprom 不穩,無法達到 100Kbit/s,還是其它原因?

以下推導過程,使用 40MHz 的振盪器,I²C匯流排速度為標準模式(100 Kbit/s)。

  1. 1 clock = 1/40 us
  2. 1 machine cycle = 12 clock = 12/40 us
  3. 1 nop = 1 machine cycle = 12/40 us = 0.3 us
  4. 100 kbit/s = 1bit per 1/100000s = 10 us
  5. 1 bit cycle = 10/0.3 = 33.33 machine cycle
  6. 2 * i2c_wait = 1 bit cycle ==> i2c_wait = 16.67 machine cycle = 16 nop

實際上使用的 nop 有 10 個,只有測試讀寫 1 byte。由於還會呼叫 lcall,ret 等指令,所以少了 6 個 nop 是可以理解的。但是實際上測試需要做讀寫大量的資料,不然像上例的 eeprom,單獨讀寫一個沒問題,大量讀寫時,卻出現很多錯誤。

時間單位

Posted by: 邱小新 at 上午10:56 in
  1. second: 1s = 1000ms
  2. millisecond: 1ms = 1000us
  3. microsecond: 1us = 1000ns
  4. nanosecond: 1ns = 1000ps
  5. picosecond: 1ps = 1000fs
  6. femtosecond: 1fs = 1000as
  7. attosecond: 1as = 10-18 s

SPI (Serial Peripheral Interface)

Posted by: 邱小新 at 上午9:35 in

SPI,Serial Peripheral Interface,顧名思義就是串列周邊介面。是 Motorola 首先在其 MC68HCXX 系列處理器上定義的。SPI 主要應用在轉換器(ADC and DAC),記憶體(EEPROM and FLASH),即時時鐘(RTC),感測器(溫度、壓力)等。SPI 是一種高速串列傳輸匯流排,全雙工通訊協定,主僕控制架構。

PWM (Pulse Width Modulation) 原理與應用

Posted by: 邱小新 at 下午3:09 in

PWM (Pulse Width Modulation) 稱為脈波寬度調變,常用於直流馬達的控制、電源變換器之穩壓控制、甚至是直流轉換交流弦波的控制等,是控制直流馬達轉速最常見的方法。

duty cycle

  1. duty cycle D = DT / T
  2. 也就是指 Ymax 的部份在一個 period(T) 佔有多少比率。
  3. 台灣稱 duty cycle 為「責任週期」。
  4. 大陆称 duty cycle 为「占空比」。

duty cycle 不同說法

  1. duty cycle high time = TH / T。
  2. duty cycle low time = TL / T。
  3. duty cycle high/low = TH / TL。
  4. TH = 維持在 HIGH 的時間。
  5. TL = 維持在 LOW 的時間。
  6. T = TH+TL。

紅外線感應器

Posted by: 邱小新 at 下午4:11 in
MOTION SENSOR (PASSIVE INFRARED TYPE) MP MOTION SENSOR‘NaPiOn’

紅外線感應器的種類非常之多,通常可以區分為量子型與熱型兩大類。而量子型感應器主要動作原理是利用光電效應,例如光二極體,以及利用光電效應的 CdS、PbS 等元件,此類型最為一般所常見。

此外尚有利用焦電效應的焦電型紅外線感應器,其動作原理是熱型的代表作,而所謂焦電效應(pyroelectric effect)簡單地說就是利用溫度的變化而產生電荷之現象。因此,我們可以說若是沒有溫度變化,即無法生電荷輸出訊號。

焦電型紅外線感應器俗稱 PIR 是 Passive Infrared Sensor 的英文簡寫,它主要的感應方法為利用溫度之變化,來感應待測物體之移動。舉例來說,人在 PIR 的感應範圍之內移動,PIR 上的感應點會因為溫度的變化而轉化成電流訊號輸出去,這個訊號就可以使電燈開啟、警報器作響……,達成我們所要的需求。

但是上述情形的感應方式會有一些問題,那就是 PIR 的感應距離太近(約 1~2 公尺),而且感應點會不夠多,所以最簡單的解決方法就是為 PIR 戴眼鏡,我們可以利用凸透鏡可以將光線聚焦的原理,將較遠處的人體的紅外線能量聚焦至 PIR 元件的感應點上,如此有可大大增加了感應距離,另一方面我們再把凸透鏡集合在一起,形成一個透鏡陣列 (Lens Arrays),這樣就可以增加許多感應點。至於透鏡的材質,必須要讓人體所發出的紅外光能夠順利通過,為了避免誤動,我們選擇對可見光阻絕性好的高密度 PE 可以避掉許多光線之干擾,而減少誤動之情形。




在自然界,任何高於絕對溫度(-273℃)時物體都將產生紅外光譜,不同溫度的物體,其釋放的紅外能量的波長是不一樣的,因此紅外波長與溫度的高低是相關的。

人體輻射的紅外線中心波長為 9~10μm,而探測元件的波長靈敏度在 0.2~20μm 範圍內幾乎穩定不變。在熱釋感測器頂端開設了一個裝有濾光鏡片的視窗,這個濾光片可通過光的波長範圍為 7~10μm,正好適合於人體紅外輻射的探測,而對其他波長的紅外線由濾光片予以吸收,這樣便形成了一種專門用作探測人體輻射的紅外線感測器。

在被動紅外探測器中有兩個關鍵性的元件,一個是熱釋電紅外感測器,它能將波長為 8-12μm 之間的紅外信號變化轉變為電信號,並能對自然界中的白光信號具有抑制作用,因此在被動紅外探測器的探測範圍內,當無人體移動時,熱釋電紅外感應器感應到的只是背景溫度,當人體進人探測範圍,通過菲涅爾透鏡,熱釋電紅外感應器感應到的是人體溫度與背景溫度的差異信號,因此,紅外探測器的紅外探測的基本概念就是感應移動物體與背景物體的溫度的差異。

另外一個元件就是菲涅爾透鏡,菲涅爾透鏡有兩種形式,即折射式和反射式。菲涅爾透鏡作用有兩個:一是聚焦作用,即將熱釋的紅外信號折射(反射)在熱釋電紅外感測器上,第二個作用是將探測範圍內分為若干個明區和暗區,使進入探測範圍的移動物體能以溫度變化的形式在熱釋電紅外感測器上產生變化熱釋紅外信號,這樣熱釋電紅外感測器就能產生變化的電信號。

人體都有恒定的體溫,一般在 37 度,所以會發出特定波長 10 微米左右的紅外線,被動式紅外探頭就是靠探測人體發射的 10 微米左右的紅外線而進行工作的。人體發射的 10 微米左右的紅外線通過菲泥爾濾光片增強後聚集到紅外感應源上。紅外感應源通常採用熱釋電元件,這種元件在接收到人體紅外輻射溫度發生變化時就會失去電荷平衡,向外釋放電荷,後續電路經檢測處理後就能產生信號。




紅外線 (Infrared) 在防盜系統用途上,可區分為主動式紅外線 twin-bean infrared sensor (感應器本身會發射紅外線光束偵測物體移動),與被動式紅外線 PIR (感應器本身不會發射紅外線光束,而是靠物體之熱源移動觸發感應器)。

一般而言,主動式紅外線大多數皆為2個(壹組)一起使用,一為發射紅外線光束 (Transimter),另一為接收紅外線光束 (Reciver),適用於室內或室外點對點之直線距離使用 (例如:對照式圍牆用紅外線投射器)。

被動式紅外線則適用於室內封閉空間使用,例如:一般室內防盜常用之紅外線感應器。之所以會有此區別用途,乃因室外溫差氣候變化大,例如:馬路溫度、汽車溫度、下雨天、刮風天.....等等外在因素,皆有可能會導致被動式紅外線誤報。而主動式紅外線因本身會持續發射紅外線光束,故受干擾誤報機會不大,相對的,其所能偵測的範圍僅限於點對點之直線距離,又不如被動式紅外線 PIR 有著整個空間的大涵蓋警戒範圍。




  1. PIR sensor 啟動時,需要有一段時間做 Calibration,依據不同的 sensor 有不同的時間,大約 10~60 秒,這個時間所輸出的值不具參考值。
  2. 沒有任何物體被偵測到移動時,輸出為 LOW。
  3. 當有物體被偵測到移動時,輸出為 HIGH,然後又回到 LOW。
  4. 如果有物體不斷的移動,輸出就會變成 HIGH LOW 不停變換,而不是維持在 HIGH 喔。
  5. 使用 parallax PIR Sensor (#555-28027) 則可以設定成輸出一直維持在 HIGH。



  1. 純硬體設計線路不用軟體來解,使用 KC7788B IC。
  2. PIR 輸出 PWM (digital) -> 經過積分器輸出 DC (analog) -> 經過 ADC 輸出 0~255 -> MCU,這樣才可以控制 PIR 的靈敏度,不然只依據 HIGH/LOW 來判斷,只要一隻小鳥飛過也會觸發。
  3. 如果要用類比的電路更複雜,而且不穩定,會飄。更複雜的還有溫度的補償,PIR 很容易受溫度影響。

ir remote control NEC protocol

Posted by: 邱小新 at 下午4:32 in

一般指令時脈圖

  1. 圖一:0 與 1 的時脈圖。

  1. 圖二:傳送一個完整指令的時脈圖。
  1. 根據圖一,每次都會先送出 560us 的紅外線,再停止一段時間,再送出 560us 紅外線........直到資料送完。
  2. 根據圖一,送出一個 bit 1 需時 2.25ms 的時間。
  3. 根據圖一,送出一個 bit 0 需時 1.12ms 的時間。
  4. 根據圖二,會先送出一個 start sync (13.5ms),再依序送出 32bit 的資料,總共耗時 67.42ms。
  5. 一般來說 address 我們稱為 custom code,遙控器上的每一個按鍵送出的 address 都一樣,而只有 command 不一樣,以做區別。

重複指令時脈圖

  1. 圖三:傳送一個重複指令的時脈圖。
  1. 圖四:重複指令放大的時脈圖。
  1. 根據圖四,傳送一個重複指令需時 11.25ms。
  2. 根據圖三,傳送一個完整重複指令需時 110ms,所以後面空白時間為 110-11.25-0.56=98.19ms。
  3. 但是實際拿了三支不同的遙控器來試,結果發現三支的空白時間均不同,分別為 96.18ms、96.78ms、97.26ms。所以切記不要拿空白時間來當 repeat 訊號。

紅外線接收圖

  1. 圖五:紅外線接受器的訊號輸出圖。
  1. 根據圖五可以知道,當收到紅外線時,會產生一個 falling edge-triggered,剛好可以配合 8051 的 external interrupt。
  2. 只要利用二個 falling edge-triggered,就可以算出一個 pulse 所需的時間,進而知道是那一種訊號。
  3. 另外再利用 timer 8-bit auto-reload mode 的功能,可以精確計時。

範例程式

原始碼下載 void start_timer(void) { TR0 = LOW; timer_count = 0; TL0 = TIMER_COUNTER_60us; TR0 = HIGH; } void stop_timer(void) { TR0 = LOW; } int get_timer0(void) { int count; stop_timer(); count = timer_count; start_timer(); return count; } void timer0_isr(void) interrupt (1) { if(timer_count < 1500) { timer_count++; } else { stop_timer(); ir_control = 0; } } void init_timer0(void) { TR0 = LOW; TF0 = LOW; TMOD = (TMOD & TIMER1_MASK) | TIMER0_MODE_8BIT_AUTO; TH0 = TIMER_COUNTER_60us; PT0 = HIGH; ET0 = HIGH; } 發現了一個大 bug,i=ir_pos>>3; 要改成 i=(ir_pos&0x1f)>>3; 才可以,不然當接收到太多錯誤訊號時,會造成 ir_pos 溢位,而修改到其它變數。過了好久才發現這個問題,還是不小心發現的,還要感謝同事的遙控器溢波干擾,讓我看到這個大 bug。 void ext0_isr(void) interrupt (0) { static char ir_pos; int pulse; char i; if (ir_control == 0) { ir_control = IR_START; start_timer(); return; } pulse = get_timer0(); if (IS_IR_START(pulse)) { ir_control |= IR_READ; ir_pos = 0; return; } if (IS_IR_REPEAT(pulse)) { if (ir_control == IR_START) { printf_tiny("\n\rrepeat(%d)", pulse); } return; } i = (ir_pos & 0x1f) >> 3; if (IS_BIT_0(pulse)) { ir_code[i] >>= 1; ir_pos++; } else if (IS_BIT_1(pulse)) { ir_code[i] >>= 1; ir_code[i] |= 0x80; ir_pos++; } else { printf_tiny("\n\rerror(%d)", pulse); ir_control = 0; stop_timer(); return; } if(ir_pos >= 32) { ir_control ^= IR_READ; printf_tiny("\n\rcode = %x,%x,%x,%x", ir_code[0], ir_code[1], ir_code[2], ir_code[3]); ir_control = 0; stop_timer(); } } void ir_init(void) { init_timer0(); ir_control = 0; IT0 = HIGH; EX0 = 1; P3_2= HIGH; }

I2C protocol 原理及應用

Posted by: 邱小新 at 下午4:14 in
原始碼下載

I²C (Inter-Integrated Circuit) 是內部整合電路的稱呼,是一種串列通訊匯流排,使用多主從架構,由飛利浦公司在 1980 年代為了讓主機板、嵌入式系統或手機用以連接低速週邊裝置而發展。I²C 的正確讀法為 "I-squared-C" ,而 "I-two-C" 則是另一種錯誤但被廣泛使用的讀法,在大陸地區則多以 "I方C" 稱之。截至 2006 年 11 月 1 日為止,使用 I²C 協定不需要為其專利付費,但製造商仍然需要付費以獲得 I²C Slave (從屬裝置位址)。

I²C的參考設計使用一個7位元長度的位址空間但保留了16個位址,所以在一組匯流排最多可和112個節點通訊。常見的I²C匯流排依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時脈頻率可被允許下降至零,這代表可以暫停通訊。而新一代的I²C匯流排可以和更多的節點(支援10位元長度的位址空間)以更快的速率通訊:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

I2C 的啟動條件及停止條件

  1. I2C start condition 有二種情況,如上圖所示,虛線表示 read 動作時的第二次 start condition,實線表示 r/w 時的第一次 start condition。
  2. I2C stop condition 只有一種情況,如上圖所示。
void i2c_start(void) { // for second start signal on i2c_read I2C_SDA = HIGH; I2C_SCL = HIGH; i2c_wait(); // send start signal I2C_SDA = LOW; i2c_wait2(); I2C_SCL = LOW; } void i2c_stop(void) { i2c_wait2(); I2C_SDA = LOW; i2c_wait2(); I2C_SCL = HIGH; i2c_wait2(); I2C_SDA = HIGH; }

I2C 的讀寫動作

  1. 當 SCL=HIGH 時,表示 SDA 穩定,可以做讀取動作。
  2. 當 SCL=LOW 時,表示 SDA 混亂,不可以讀取;因為此時可以設定 SDA 的值,也就是做寫入動作。
  3. master 每一次傳送八個 bit,最後 slave 會回傳一個 ack bit,表示接受是否完成。
  4. master 每一次接受八個 bit,最後 master 要傳送一個 ack bit,表示接受已經完成。
  5. 在傳送完第八個 bit 之後,再等待 slave 接受完成後,需將 SDA 設成 HIGH,此時 slave 會將 SDA 拉回 LOW,表示接受動作完成。如果 acknowledge=HIGH,也就是 slave 沒有拉成 LOW 則表示傳送失敗。
bit i2c_write(unsigned char value) { char i=9; bit ack; // upload data while(--i) { i2c_wait2(); I2C_SDA = (value & 0x80) ? HIGH : LOW; i2c_wait2(); // send data I2C_SCL = HIGH; i2c_wait(); value <<= 1; I2C_SCL = LOW; } // get acknowledgement i2c_wait2(); I2C_SDA = HIGH; i2c_wait2(); I2C_SCL = HIGH; i2c_wait2(); ack = I2C_SDA; i2c_wait2(); I2C_SCL = LOW; // return acknowledge return ack; } unsigned char i2c_read(bit acknowledge) { char i=9; unsigned char value=0; // read data while(--i) { value <<= 1; i2c_wait(); I2C_SCL = HIGH; i2c_wait2(); value |= I2C_SDA; i2c_wait2(); I2C_SCL = LOW; } // send acknowledge i2c_wait2(); I2C_SDA = acknowledge; i2c_wait2(); I2C_SCL = HIGH; i2c_wait(); I2C_SCL = LOW; // return data return value; }

標準 I2C 讀寫流程 by Philips

  1. 讀取完最後一個 byte 時,記得要回傳 no acknowledge(SDA=HIGH),表示已經沒有要繼續讀取資料。
  2. 讀取完最後一個 byte 時,回傳 acknowledge(SDA=LOW),再傳送 stop signal,則會造成後續的讀寫動作失敗。(這是在讀取 PCF8593 的經驗)
完整寫入流程

完整讀取流程

複合式讀寫流程

常用 I2C NVRAM 讀寫流程 by Philips PCF8953

  1. 一般 RTC 都有帶一些 NVRAM 或是讀寫 EEPROM,需要先指定讀寫的 register address 才可以。所以在寫時,先寫入 slave address 後,需再寫入 register address,讓 chip 知道你要寫入的起始位址,接下來才能寫入 data。
  2. 由於讀取前也要先寫入 register address,所以一般 NVRAM 讀取動作都是使用標準複合式流程,也就是先寫入 register address,再下一次 start conditon,再做讀取動作。
void i2c_write_byte(unsigned char slave_addr, unsigned char reg_addr, unsigned char value) { char i=10; EA = 0; while (--i) { i2c_start(); if(i2c_write(slave_addr)) continue; if(i2c_write(reg_addr)) continue; if(i2c_write(value)) continue; i2c_stop(); break; } EA = 1; } unsigned char i2c_read_byte(unsigned char slave_addr, unsigned char reg_addr) { char i=10; unsigned char value=0; EA = 0; while (--i) { // send register address i2c_start(); if(i2c_write(slave_addr&0xfe)) continue; if(i2c_write(reg_addr)) continue; // read data i2c_start(); if(i2c_write(slave_addr | 1)) continue; value = i2c_read(1); i2c_stop(); break; } EA = 1; return value; }