You are on page 1of 41

BÀI 1: THIẾT KẾ ỨNG DỤNG SỬ DỤNG GIAO TIẾP I2C

1.1. Tổng quan về I2C


I2C (Inter‐Intergrated Circuit) được phát minh bởi Phillips vào thập niên 80 của
thế kỷ 20 với mục đích ban đầu là tạo ra giao tiếp giữa các vi mạch (IC) với nhau mà
sử dụng ít chân (pin) nhất có thể. Bên cạnh các giao tiếp nối tiếp phổ thông khác (hình
1.1), ngày nay I2C có mặt ở hầu hết các bộ vi điều khiển, chúng được sử dụng để giao
tiếp giữa các vi điều khiển với nhau, giữa vi điều khiển với các vi mạch ứng dụng như
ROM, ADC...

Hình 1.1. Một số chuẩn giao tiếp nối tiếp phổ thông.(Nguồn DesignCon 2003)

6
Bảng 1.1. Thông số của một chuẩn giao tiếp nối tiếp (Nguồn DesignCon 2003)

Bus của I2C gồm 02 đường dây (SDA, SCL).


- SDA (Serial Data): Đường truyền dữ liệu nối tiếp 2 hướng.
-SCL( Serial Clock): Đường truyền xung đồng hồ.Trong giao tiếp I2C, xung
đồng hồ sẽ do vi mạch chủ (Master) phát.
Mỗi dây SDA hay SCL đều được nối với điện áp dương của nguồn cấp thông qua
một điện trở pull-up (hình 1.2). Giá trị của điện trở pull-up năm trong khoảng từ
2KΩ đến 10KΩ. Lý do cần có các điện trở này là các chân SDA và SCL được thiết kế
theo dạng open-drain hoặc open-collector.

7
Hình 1.2. Kiến trúc của bus I2C
Trong bus I2C có nhiều thiết bị cùng được kết nối, mỗi thiết bị sẽ có một địa chỉ
duy nhất. Một thiết bị khi kết nối vào bus I2C ngoài một địa chỉ duy nhất để phân biệt,
nó còn được cấu hình là thiết bị chủ (Master) hoặc tớ (Slave). Trong giao tiếp I2C
quyền điều khiển thuộc về thiết bị chủ. Thiết bị chủ nắm vai trò tạo xung đồng hồ cho
hệ thống, khi thiết bị chủ giao tiếp với thiết bị tớ, thiết bị chủ có nhiệm vụ tao xung
đồng hồ và quản lý địa chỉ của thiết bị tớ trong suốt quá trình giao tiếp. Thiết bị chủ
giữ vai trò chủ động còn thiết bị tớ giữ vai trò bị động trong khi giao tiếp.
1.2. Module I2C trên vi điều khiển PIC18F4520
PIC18F4520 được trang bị module I2C với 02 chế độ hoạt động Master và Slave.
Các đường dây SDA và SCL tương ứng với các chân RC4/SDI và RC3/SCL/SCK
trên vi điều khiển (hình 1.3)

8
Hình 1.3. Sơ đồ khối I2C tích hợp trên PIC18F4520
1.2.1.Các thanh ghi.
Khối MSSP (Master Synchronous Serial Port) có 6 thanh ghi để điều khiển chế
độ I2C, bao gồm:
+ Thanh ghi điều khiển MSSP1 (SSPCON1)
+ Thanh ghi điều khiển MSSP2 (SSPCON2)
+ Thanh ghi trạng thái MSSP (SSPSTAT)
+ Thanh ghi đệm truyền nhận nối tiếp (SSPBUF)
+ Thanh ghi dịch MSSP (SSPSR).
+ Thanh ghi địa chỉ MSSP (SSPADD)
Thanh ghi SSPBUF chứa dữ liệu sẽ được truyền đi hoặc nhận được và đóng vai
trò như một thanh ghi đệm cho thanh ghi dịch dữ liệu SSPSR. Thanh ghi SSPSR
không thể truy xuất trực tiếp được, muốn truy xuất nó ta phải thông qua thanh ghi
SSPBUF.
Thanh ghi SSPADD chứa địa chỉ của thiết bị ngoại vi cần truy xuất dữ liệu của I2C
khi hoạt động ở chế độ tớ. Khi hoạt động ở chế độ master, thanh ghi SSPADD chứa
giá trị tạo ra tốc độ baud cho xung clock dùng để truyền nhận dữ liệu.

9
Trong quá trình nhận dữ liệu, cả 2 thanh ghi SSPSR và SSPBUF tạo thành bộ đệm
nhận dữ liệu 2 lớp. khi SSPSR thực hiện nhận 1 byte hoàn tất, nó sẽ ghi tới thanh ghi
SSPBUF và cờ ngắt SSPIF được đặt.
Trong quá trình truyền, chúng không đóng vai trò là bộ đệm dữ liệu 2 lớp. Khi ghi
tới SSPBUF lúc này sẽ ghi tới cả SSPBUF và SSPSR.
Thanh ghi SSPSTAT : Thanh ghi trạng thái MSSP(chế độ I2C)

Bit 7 SMP : bit điều khiển tốc độ


Trong chế độ master hoặc chế độ tớ :
1= không điều khiển tốc độ, để chế độ tốc độ chuẩn (100 KHZ và 1MHZ ).
0= cho phép điều khiển tốc độ, tốc độ cao (400 KHZ)
Bit 6 CKE : bit chọn SMBUS
Trong chế độ master hoặc chế độ tớ :
1= cho phép đầu vào SMBUS
0= không cho phép đầu vào SMBUS
Bit 5 D/ A bit dữ liệu hoặc bit địa chỉ
Trong chế độ master :
Dùng để dự chữ.
Trong chế độ tớ :
1= byte cuối của quá trình truyền hoặc nhận là dữ liệu.
0= byte cuối của quá trình truyền hoặc nhận là địa chỉ.
Bit 4 P : bit stop
1= xác định bit stop
0= không xác định bit stop
Bit 3 S : bit start
1= phát hiện bit start
0= không phát hiện bit start
Bit 2 R/ W : đọc hoặc ghi thông tin
Trong chế độ tớ :
1= đọc.

10
0= ghi.
Trong chế độ master :
1= cho phép truyền.
0= không cho phép truyền.
Bit 1 UA : bit cập nhật địa chỉ ( chế độ tớ 10 bit )
1= cập nhật địa chỉ vào thanh ghi SSPADD
0= không cập nhật địa chỉ
Bit 0 BF : bit báo trạng thái đầy bộ đệm
Trong chế độ truyền :
1= SSPBUF đầy.
0= SSPBUF rỗng.
Trong chế độ nhận :
1= SSPBUF đầy ( không bao gồm ACK và bit stop ).
0= SSPBUF rỗng ( không bao gồm ACK và bit stop ).
Thanh ghi SSPCON1 : Thanh ghi điều khiển 1 (chế độ I2C)

Bit 7 WCOL : bit phát hiện xung đột lúc ghi


Truyền trong chế độ master :
1= dữ liệu truyền được đưa vào thanh ghi SSPBUF trong khi chế độ truyền dữ
liệu của I2C chưa sẵn sàng.
0= không có sự xung đột.
Truyền trong chế độ tớ :
1= dữ liệu mới được đưa vào thanh ghi SSPBUF trong khi chưa truyền xong dữ
liệu cũ.
0= không có sự xung đột.
Trong chế độ nhận (chế độ master hoặc tớ )
Không sử dụng đến bit này.
Bit 6 SSPOV : bit cờ tràn nhận
Chế độ nhận :

11
1= một byte dữ liệu mới được nhận trong khi dữ liệu cũ vẫn còn trong thanh ghi
SSPBUF.
0= không có tràn.
Chế độ truyền :
Không sử dụng đến bit này.
Bit 5 SSPEN : bit cho phép cổng nối tiếp
1= cho phép cổng nối tiếp và thiết lập SDA và SCL là các chân của cổng nối tiếp.
0= không cho phép cổng nối tiếp và thiết lập chúng như các chân vào ra.
Bit 4 CKP : SCK : bit điều khiển tác động
Chế độ tớ :
1= cho xung clock tác động
0= giữ xung clock ở mức thấp (để đảm bảo thời gian thiết lập dữ liệu)
Chế độ master :
Không sử dụng trong chế độ này.
Bit 3-0 SSPM3 : SSPM0 : các bit lựa chọn chế độ hoạt động cổng nối tiếp
1111 : I2C chế độ tớ, 10 bit địa chỉ, cho phép ngắt khi phát hiện bit start và bit
stop.
1110 : I2C chế độ tớ, 7 bit địa chỉ, cho phép ngắt khi phát hiện bit start và bit
stop.
1011 : dùng phần mềm điều khiển chế độ master (chế độ tớ không dùng)
1000 : I2C chế độ master, clock = Fosc/(4*(SSPADD+1))
0111 : I2C chế độ tớ , 10 bit địa chỉ.
0110 : I2C chế độ tớ, 7 bit địa chỉ.
Thanh ghi SSPCON2 : Thanh ghi điều khiển MSSP 2(chế độ I2C)

Bit 7 GCEN : bit cho phép gọi chung. (duy nhất trong chế độ tớ )
1= cho phép ngắt khi địa chỉ 0000h được nhận vào trong SSPSR.
0= không cho phép gọi địa chỉ trên.
Bit 6 ACKSTAT : bit trạng thái ACK (duy nhất trong chế độ master:
truyền dữ liệu )
12
1= chưa nhận được xung ACK từ tớ
0= nhận được xung từ tớ.
Bit 5 ACKDT : bit dữ liệu ACK (chỉ có trong chế độ master : nhận dữ liệu )
1= không có xung ACK
0= có xung ACK
Bit 4 ACKEN : bit cho phép xung ACK.(chỉ có trong chế độ master: nhận dữ liệu )
1= khởi tạo xung ACK trên các chân SDA và SCL. Bit dữ liệu truyền ACKDT ).
Tự động xóa bằng phần cứng.
0= không cho phép xung ACK.
Bit 3 RCEN : bit cho phép nhận (duy nhất trong chế độ master )
1= cho phép nhận dữ liệu.
0= không cho phép nhận dữ liệu.
Bit 2 PEN : bit cho phép điều kiện stop.
1= khởi tạo điều kiện stop trên các chân SDA và SCL. Tự động xóa bằng phần
cứng.
0= không cho phép điều kiện stop.
Bit 1 RSEN : bit cho phép điều kiện start (duy nhất trong chế độ master )
1= khởi tạo điều kiện start trên các chân SDA và SCL. Tự động xóa bằng phần
cứng.
0= không cho phép điều kiện start.
Bit 0 SEN : bit cho phép điều kiện start hoặc cho phép khóa.
Chế độ master :
1= khởi tạo điều kiện start trên các chân SDA và SCL. Tự động xóa bằng phần
cứng.
0= không cho phép điều kiện start.
Chế độ tớ :
1= cho phép khóa xung clock từ chế độ tớ truyền và nhận.
0= không cho phép khóa xung clock.

1.2.2. Các chế độ hoạt động

1.2.2.1. Chế độ master

13
Chế độ master được xác lập bằng cách đưa các giá trị thích hợp vào các bit
SSPM của thanh ghi SSPCON và set bit SSPEN. Ở chế độ Master, các chân SCK và
SDA sẽ được điều khiển bởi phần cứng của MSSP.

Hình 1.4. Sơ đồ khối I2C ở chế độ master


I2C Master đóng vai trò tích cực trong quá trình giao tiếp và điều khiển các I2C
Slave thông qua việc chủ động tạo ra xung giao tiếp và các điều kiện Start, Stop khi
truyền nhận dữ liệu. Một byte dữ liệu có thể được bắt đầu bằng điều kiện Start, kết
thúc bằng điều kiện Stop hoặc bắt đầu và kết thúc với cùng một điều kiện khởi động
lặp lại (Repeated Start Condition). Xung giao tiếp nối tiếp sẽ được tạo ra từ BRG
(Baud Rate Generator), giá trị ấn định tần số xung clock nối tiếp được lấy từ 7 bit thấp
của thanh ghi SSPADD. Khi dữ liệu được đưa vào thanh ghi SSPBUF, bit BF được set
và BRG tự động đếm ngược về 0 và dừng lại, chân SCL được giữ nguyên trạng thái
trước đó.Khi dữ liệu tiếp theo được đưa vào, BRG sẽ cần một khoảng thời gian TBRG
tự động reset lại giá trị để tiếp tục quá trình đếm ngược. Mỗi vòng lệnh (có thời gian
TCY) BRG sẽ giảm giá trị 2 lần.

14
Hình 1.5. Sơ đồ khối BRG của I2C Chế độ master.
Các giá trị cụ thể của tần số xung nối tiếp do BRG tạo ra được cho như sau:

Trong đó giá trị BRG là giá trị được lấy từ 7 bit thấp của thanh ghi SSPADD. Do
I2C ở chế độ Chế độ master, thanh ghi SSPADD sẽ không được sử dụng để chứa địa
chỉ, thay vào đó chức năng của SSPADD là thanh ghi chứa giá trị của BRG.
Để tạo được điều kiện Start, trước hết cần đưa hai chân SCL và SDA lên mức
logic cao và bit SEN (SSPCON2<0>) phải được set . Khi đó BRG sẽ tự động đọc giá
trị 7 bit thấp của thanh ghi SSPADD và bắt đầu đếm. Sau khoảng thời gian TBRG, chân
SDA được đưa xuống mức logic thấp. Trạng thái chân SDA ở mức logic thấp và chân
SCL ở mức logic cao chính là điều kiện Start của I2C Chế độ master. Khi đó bit S
(SSPSTAT<3>) sẽ được set. Tiếp theo BRG tiếp tục lấy giá trị từ thanh ghi SSPADD
để tiếp tục quá trình đếm, bit SEN được tự động xóa và cờ ngắt SSPIF được đặt bằng
một. Trong trường hợp chân SCL và SDA ở trạng thái logic thấp, hoặc là trong quá
trình tạo điều kiện Start, chân SCL được đưa về trạng thái logic thấp trước khi chân
SDA được đưa về trang thái logic thấp, điều kiện Start sẽ không được hình thành, cờ
ngắt BCLIF sẽ được đặt bằng một và I2C sẽ ở trạng thái tạm ngưng hoạt động (Idle).

15
Hình 1.6. Giản đồ xung I2C Chế độ master trong quá trình tạo điều kiện Start
Tín hiệu Stop sẽ được đưa ra chân SDA khi kết thức dữ liệu bằng cách đặt bằng
một bit PEN (SSPCON2<2>). Sau cạnh xuống của xung clock thứ 9 và với tác động
của bit điều khiển PEN, chân SDA cũng được đưa xuống mức thấp, BRG lại bắt đầu
quá trình đếm. Sau một khoảng thời gian TBRG, chân SCL được đưa lên mức logic cao
và sau một khoảng thời gian TBRG nữa chân SDA cũng được đưa lên mức cao. Ngay
tại thời điểm đó bit P (SSPSTAT<4>) được đặt bằng 1, nghĩa là điều kiện Stop đã
được tạo ra. Sau một khoảng thời gian TBRG nữa, bit PEN tự động được xóa và cờ ngắt
SSPIF được đặt bằng 1.

Hình 1.7. Giản đồ xung I2C Chế độ master trong quá trình tạo điều kiện Stop.
Để tạo được điều kiện Start lặp lại liên tục trong quá trình truyền dữ liệu, trước
hết cần đặt bằng một bit RSEN (SSPCON2<1>). Sau khi đặt bằng một bit RSEN, chân
SCL được đưa xuống mức logic thấp, chân SDA được đưa lên mức logic cao, BRG lấy
giá trị từ thanh ghi SSPADD vào để bắt đầu quá trình đếm. Sau khoảng thời gian
TBRG, chân SCL cũng được đưa lên mức logic cao trong khoảng thời gian TBRG tiếp
theo. Trong khoảng thời gian TBRG kế tiếp, chân SDA lại được đưa xuống mức logic
16
thấp trong khi SCL vẫn được giữ ở mức logic cao. Ngay thời điểm đó bit S
(SSPSTAT<3>) được đặt bằng 1 để báo hiệu điều kiện Start được hình thành, bit
RSEN tự động được xóa và cờ ngắt SSPIF sẽ được đặt bằng một sau một khoảng thời
gian TBRG nữa. Lúc này địa chỉ của I2C Slave có thể được đưa vào thanh ghi
SSPBUF, sau đó ta chỉ việc đưa tiếp địa chỉ hoặc dữ liệu tiếp theo vào thanh ghi
SSPBUF mỗi khi nhận được tín hiệu ACK từ I2C Slave, I2C Master sẽ tự động tạo tín
hiệu Start lặp lại liên tục cho quá trình truyền dữ liệu liên tục. Cần chú ý là bất cứ một
trình tự nào sai trong quá trình tạo điều kiện Start lặp lại sẽ làm cho bit BCLIF được
đặt bằng một và I2C được đưa về trạng thái “Idle”.

Hình 1.8. Giản đồ xung I2C Chế độ master trong quá trình tạo điều kiện Start
liên tục.
Xét quá trình truyền dữ liệu, xung clock sẽ được đưa ra từ chân SCL và dữ liệu
được đưa ra từ chân SDA. Byte dữ liệu đầu tiên phải là byte địa chỉ xác định I2C Slave
cần giao tiếp và bit R/W (trong trường hợp này R/W = 0). Đầu tiên các giá trị địa chỉ
sẽ được đưa vào thanh ghi SSPBUF, bit BF tự động được đặt bằng một lên 1 và bộ
đếm tạo xung clock nối tiếp BRG (Baud Rate Generator) bắt đầu hoạt động. Khi đó
từng bit dữ liệu (hoặc địa chỉ và bit ACK) sẽ được dịch ra ngoài theo từng cạnh xuống
của xung clock sau khi cạnh xuống đầu tiên của chân SCL được nhận diện (điều kiện
Start), BRG bắt đầu đếm ngược về 0. Khi tất cả các bit của byte dữ liệu được đã được
đưa ra ngoài, bộ đếm BRG mang giá trị 0. Sau đó, tại cạnh xuống của xung clock thứ 8,
I2C Master sẽ ngưng tác động lên chân SDA để chờ đợi tín hiệu từ I2C Slave . Tại
17
cạnh xuống của xung clock thứ 9, I2C Master sẽ lấy mẫu tín hiệu từ chân SDA để
kiểm tra xem địa chỉ đã được I2C Slave nhận dạng chưa, trạng thái được đưa vào bit
ACKSTAT (SSPCON2<6>). Cũng tại thời điểm cạnh xuống của xung clock thứ 9, bit
BF được tự động clear, cờ ngắt SSPIF được đặt bằng một và BRG tạm ngưng hoạt
động cho tới khi dữ liệu hoặc địa chỉ tiếp theo được đưa vào thanh ghi SSPBUF, dữ
liệu hoặc địa chỉ sẽ tiếp tục được truyền đi tại cạnh xuống của xung clock tiếp theo.

Hình 1.9. Giản đồ xung I2C Chế độ master trong quá trình truyền dữ liệu.
Xét quá trình nhận dữ liệu ở chế độ I2C Chế độ master. Trước tiên ta cần đặt
bằng một bit cho phép nhận dữ liệu RCEN (SSPCON2<3>). Khi đó BRG bắt đầu quá
trình đếm, dữ liệu sẽ được dịch vào I2C Master qua chân SDA tại cạnh xuống của
chân SCL. Tại cạnh xuống của xung clock thứ 8, bit cờ hiệu cho phép nhận RCEN tự
động được xóa, dữ liệu trong thanh ghi SSPSR được đưa vào thanh ghi SSPBUF, cờ
hiệu BF được đặt bằng một, cờ ngắt SSPIF được đặt bằng một, BRG ngưng đếm và
chân SCL được đưa về mức logic thấp. Khi đó MSSP ở trạng thái tạm ngưng hoạt
động để chờ đợi lệnh tiếp theo. Sau khi đọc giá trị thanh ghi SSPBUF, cờ hiệu BF tự
động được xóa về 0.

18
Hình 1.10. Giản đồ xung I2C Chế độ master trong quá trình nhận dữ liệu.

19
1.2.2.2. Chế độ slave
Để hoạt động được ở chế độ này, trước tiên là phải đăt các chân SCL và SDA
thành chiều vào. I2C của vi điều khiển sẽ được điều khiển bởi một vi điều khiển hoặc
một thiết bị ngoại vi khác thông qua các địa chỉ. Khi địa chỉ này chỉ đến vi điều khiển,
thì tại thời điểm này và tại thời điểm dữ liệu đã được truyền nhận xong sau đó, vi điều
khiển sẽ tạo ra xung để báo hiệu kết thúc dữ liệu, giá trị trong thanh ghi SSPSR sẽ
được đưa vào thanh ghi SSPBUF. Tuy nhiên xung sẽ không được tạo ra nếu một trong
các trường hợp sau xảy ra:
- Bit BF (SSPSTAT<0>) báo hiệu buffer đầy đã được đặt bằng 1 trước khi quá
trình truyền nhận xảy ra.
- Bit SSPOV (SSPCON<6>) được đặt bằng 1 trước khi quá trình truyền nhận xảy
ra (SSPOV được đặt bằng 1 trong trường hợp khi một byte khác được nhận vào trong
khi dữ liệu trong thanh ghi SSPBUF trước đó vẫn chưa được lấy ra).
Trong các trường hợp trên, thanh ghi SSPSR sẽ không đưa giá trị vào thanh ghi
SSPBUF, nhưng bit SSPIF (PIR1<3>) sẽ được đặt bằng 1. Để quá trình truyền nhận
dữ liệu được tiếp tục, cần đọc dữ liệu từ thanh ghi SSPBUF vào trước, khi đó bit BF sẽ
tự động được xóa, còn bit SSPOV phải được xóa bằng chương trình.
Khi MSSP được kích hoạt, nó sẽ chờ tín hiệu để bắt đầu hoạt động. Sau khi nhận
được tín hiệu bắt đầu hoạt động (cạnh xuống đầu tiên của pin SDA), dữ liệu 8 bit sẽ
được dịch vào thanh ghi SSPSR. Các bit đưa vào sẽ được lấy mẫu tại cạnh lên của
xung clock. Giá trị nhận được từ thanh ghi SSPSR sẽ được so sánh với giá trị trong
thanh ghi SSPADD tại cạnh xuống của xung clock thứ 8. Nếu kết quả so sánh bằng
nhau, tức là I2C Master chỉ định đối tượng giao tiếp là vi điều khiển đang ở chế độ
Slave mode (ta gọi hiện tượng này là address match), bit BF và SSPOV sẽ được xóa
về 0 và gây ra các tác động sau:
- Giá trị trong thanh ghi SSPSR được đưa vào thanh ghi SSPBUF.
- Bit BF tự động được đặt bằng 1.
- Một xung ACK được tạo ra.
- Cờ ngắt SSPIF được đặt bằng 1 (ngắt được kích hoạt nếu được cho phép trước
đó) tại cạnh xuống của xung clock thứ 9.
Qúa trình nhận dạng địa chỉ của MSSP ở chế độ I2C Slave mode 10 bit địa chỉ
như sau:
- 2 bit MSB của 10 bit địa chỉ được nhận trước, bit SSPIF, BF và UA
(SSPSTAT<1>) được đặt bằng 1.
(byte địa chỉ đầu tiên có định dạng là ‘11110 A9 A8 0’) .
20
- Cập nhật vào 8 bit địa chỉ thấp của thanh ghi SSPADD, bit UA sẽ được xóa bởi
vi điều khiển để khởi tạo xung clock ở chân SCL sau khi quá trình cập nhật hoàn tất.
- Đọc giá trị thanh ghi SSPBUF (bit BF sẽ được xóa về 0) và xóa cờ ngắt SSPIF.
- Nhận 8 bit địa chỉ cao, bit SSPIF, BF và UA được đặt bằng 1.
- Cập nhật 8 bit địa chỉ đã nhận được vào 8 bit địa chỉ cao của thanh ghi
SSPADD, nếu địa chỉ nhận được là đúng (address match), xung clock ở chân SCL
được khởi tạo và bit UA được đặt bằng 1.
- Đọc giá trị thanh ghi SSPBUF (bit BF sẽ được xóa về 0) và xóa cờ ngắt SSPIF.
- Nhận tín hiệu Start.
- Nhận byte địa chỉ cao (bit SSPIF và BF được đặt bằng 1).
- Đọc giá trị thanh ghi SSPBUF (bit BF được xóa về 0) và xóa cờ ngắt SSPIF.

Hình 1.11. Truyền dữ liệu ở chế độ 7bit địa chỉ

21
Hình 1.12. Nhận dữ liệu ở chế độ 7bit địa chỉ
1.3. Các hàm trong thư viện i2c.h
Thư viện i2c.h cung cấp một số hàm cho phép người dùng có thể đặt các thông
số cho module I2C cũng như truyền/nhận dữ liệu mà không cần truy xuất các thanh
ghi (bảng 1.2).
Bảng 1.2. Các hàm cơ bản sử dung cho giao tiếp I2C
Tên hàm Mô tả
AckI2C Tạo bit Ack
CloseI2C Đóng (cấm) chức năng của module I2C trên vi điều khiển.
DataRdyI2C Kiểm tra byte dữ liệu mới đã có trong bộ bộ đệm?
getsI2C Đọc/nhận một chuỗi các byte dữ liệu.
IdleI2C Hàm chờ cho đến khi Bus I2C hết bận.
NotAckI2C Tạo bit NotAcK
OpenI2C Khởi tạo module SPI
putsI2C Truyền một chuỗi các byte dữ liệu.
ReadI2C Đọc/nhận một byte dữ liệu.
RestartI2C Khởi tạo lại giao tiếp
StartI2C Khởi tạo giao tiếp
StopI2C Kết thúc giao tiếp
WriteI2C Truyền một byte dữ liệu.

22
• Hàm AckI2C
Chức năng : Tạo xung phản hồi Ack
Nguyên mẫu : void AckI2C( void );
• Hàm DataRdyI2C
Chức năng : Kiểm tra bộ đệm (SSPBUF) có chứa byte dữ liệu hay không.
Nguyên mẫu : void DataRdyI2C ( void );
Chú thích : Hàm này được sử dụng để kiểm tra trước khi đọc một byte dữ liệu
từ bộ đệm.
Giá trị trả về : Bằng 1 nếu bộ đệm chứa một byte dữ liệu.
Bằng 0 nếu bộ đệm không chứa một byte dữ liệu (chưa nhận về).

Ví dụ: while(!DataRdyI2C()); // chờ đọc dữ liệu từ bộ đệm.


• Hàm ReadI2C
Chức năng : Đọc một byte dữ liệu trên bus I2C.
Nguyên mẫu : unsigned char ReadI2C ( void );
Ví dụ: char x;
x = ReadI2C ( void );
• Hàm getsI2C
Chức năng : Đọc một chuỗi các byte dữ liệu trên bus I2C.
Nguyên mẫu : void getsI2C( unsigned char *rdptr,
unsigned char length );
Trong đó: rdptr là con trỏ chỉ ra nơi lưu các byte dữ liệu đọc về từ
bus SPI; length là độ dài của chuỗi (số lượng byte cần đọc).
Ví dụ: unsigned char wrptr[10];
getsI2C(wrptr, 10);
• Hàm OpenI2C
Chức năng : Khởi tạo module I2C.
Nguyên mẫu : void OpenI2C( unsigned char sync_mode,
unsigned char slew );
Trong đó:
sync_mode: Biến quy định chế độ hoạt động của module I2C:
SLAVE_7 Chế độ slave, 7 bit địa chỉ.

23
SLAVE_10 Chế độ slave, 10 bit địa chỉ.
MASTER Chế độ master.
slew: Biến quy định tốc độ truyền dữ liệu của Bus I2C:
SLEW_OFF Chế đố tốc độ truyền tiêu chuẩn 100kHz
SLEW_OFF Chế đố tốc độ truyền nhanh 400kHz
Ví dụ: OpenI2C(MASTER, SLEW_OFF);
Sẽ khởi tạo module I2C ở chế độ master, tốc độ truyền dữ liệu 100kHz.
• Hàm WriteI2C
Chức năng : Ghi (truyền) một byte dữ liệu lên bus I2C.
Nguyên mẫu : unsigned char WriteI2C(
unsigned char data_out );
Ví dụ: WriteI2C(0x41);

• Hàm putsI2C
Chức năng : Đọc một chuỗi các byte dữ liệu trên bus SPI.
Nguyên mẫu : void putsI2C( unsigned char *wrptr );
Trong đó: wrptr là con trỏ chỉ tới các byte dữ liệu cần truyền.
Ví dụ: unsigned char wrptr[] = “Hello!”;
putsI2C(wrptr);
1.4. Thiết kế ứng dụng sử dụng I2C

1.4.1. Giao tiếp DS1307


DS1307 là một bộ đồng hồ thời gian thực của hãng Maxim có tích hợp giao thức
I2C. Vi mạch này cung cấp thông tin về năm, tháng, ngày, giờ, phút, giây dưới dạng
mã BCD . Thông tin về lịch sẽ được vi mạch tự cập nhật sau khi được cài đặt. Thời
gian cập nhật lên tới năm 2100. Hình 1.13 mô tả cách kết nối vi mạch này với bộ vi
điều khiển. Để có thể hoạt động, vi mạch này cần một nguồn PIN (3V) và một bộ dao
động thạch anh (32.768 Khz).

24
Hình 1.13. Mạch điện giao tiếp DS1307 với vi điều khiển
Người dùng có thể cài đặt thời gian thực cho vi mạch bằng cách ghi thông tin
thời gian vào các thanh ghi tương ứng của DS1307 (hình 1.14). Sau khi được cài đặt,
DS1307 sẽ trở thành một đồng hồ thời gian thực (real time clock). Thông tin về thời
gian được đọc bằng cách truy xuất các thanh ghi tương ứng.

Hình 1.14. Các thanh ghi của DS1307.


Bit 7 của thanh ghi seconds (địa chỉ 00h) là bit clock halt (CH). CH=1, mạch
tạo dao động (oscillator) trên DS1307 sẽ bị cấm, thông tin về thời gian trên vi mạch sẽ
không được cập nhật. Chức năng này được sử dụng cho mục đích tiết kiệm năng lượng
trên PIN khi cần thiết (DS1307 chỉ sử dụng dòng điện 10nA ở chế độ oscillator off
thay vì 480 nA ở chế độ oscillator on). Khi xuất xưởng, nhà sản xuất mặc định CH=1,
ngày/tháng/năm-giờ:phút:giây = 01/01/00-00:00:00.
25
Bit 6 trong thanh ghi hours (địa chỉ 02h) dùng để cài đặt chế độ 12h (=1) hoặc
chế độ 24h (=0). Ở chế độ 12h, đọc bit 5 trên thanh ghi này sẽ cho thông tin về thời
gian: buổi sáng (=0), buổi chiều (=1). Ở chế độ 24h, bit 5 bằng 1 khi thời gian hiện tại
là 20h đến trước 24h.
Thanh ghi control (địa chỉ 07h) sử dụng điều khiển xung vuông phát ra từ chân
SQW của DS1307.
Trình tự ghi trên DS1307:

Hình 1.15. Ghi các thanh ghi/ô nhớ trên DS1307


Từ vi điều khiển, tạo “start”:
StartI2C();
IdleI2C(); //chờ bus rảnh (idle)
Ghi byte chứa địa chỉ thiết bị (slave address=1101000) và bit R/W(=0):
WriteI2C(0xd0);
IdleI2C();
Ghi byte chứa địa chỉ của thanh ghi đầu tiên cần ghi (word address (n)). Đây thực
chất là một con trỏ (pointer) cho phép từ thiết bị chủ có thể lựa chọn bắt đầu quá trình
ghi từ địa chỉ nào trong không gian từ 0x00 đến 0xff trên DS1307. Ví dụ: câu lệnh
dưới đây sẽ ghi vào word address giá trị 0x01:
WriteI2C(0x01);
IdleI2C();
Điều này đồng nghĩa với các lệnh ghi tiếp theo sẽ ghi vào các thanh ghi/ô nhớ có
địa chỉ 0x01 (thanh ghi phút), 0x02....
Ghi vào các thanh ghi/ô nhớ bắt đầu từ địa chỉ word address:
WriteI2C(0x06); //ghi vào thanh ghi địa chỉ 0x01,
//giá trị 0x06
IdleI2C();
WriteI2C(0x07); //ghi vào thanh ghi địa chỉ 0x02,
//giá trị 0x07
IdleI2C();
26
...
Tạo tín hiệu stop kết thúc quá trình ghi:
StopI2C();
Đoạn chương trình dưới đây sẽ đặt cho DS1307 thời gian:
giây:phút:giờ=05:06:07.
StartI2C();
IdleI2C();
WriteI2C(0xD0);
IdleI2C();
WriteI2C(0x00); //word address=0x00
IdleI2C();
WriteI2C(0x05); //ghi thanh ghi giây, địa chỉ 0x00
IdleI2C();
WriteI2C(0x06);
IdleI2C(); //ghi thanh ghi phút, địa chỉ 0x01
WriteI2C(0x07); //ghi thanh ghi giờ, địa chỉ 0x02
IdleI2C();
StopI2C();
Trình tự đọc giá trị của các thanh ghi trên DS1307 được mô tả trên hình 1.12.
Từ vi điều khiển, tạo “start”:
StartI2C();
IdleI2C(); //chờ bus rảnh (idle)

Hình 1.16. Đọc các thanh ghi trên DS1307


Ghi byte chứa địa chỉ thiết bị (slave address=1101000) và bit R/W(=0):
WriteI2C(0xd0);
IdleI2C();
Ghi word address (giả thiết word address=0x00):
WriteI2C(0x00);
IdleI2C();
Khởi tạo lại bus:
27
RestartI2C();
IdleI2C();
Ghi byte chứa địa chỉ thiết bị (slave address=1101000) và bit R/W(=1) để đọc:
WriteI2C(0xd1);
IdleI2C();
Đọc lần lượt các thanh ghi/ô nhớ bắt đầu từ địa chỉ 0x00:
s=ReadI2C(); //đọc thanh ghi có địa chỉ 0x00
IdleI2C();
AckI2C(); //tạo tín hiệu ACK
IdleI2C();

Tạo tín hiệu NotACK, stop, kết thúc quá trình đọc:
NotAckI2C();
IdleI2C();
StopI2C();
Chú ý: Thao tác (1)÷(3) không được mô tả trên hình 1.13, dùng để đặt con trỏ
cho lệnh đọc (ghi word address).
Đoạn chương trình dưới đây sẽ đọc các thanh ghi giây, phút, giờ và lưu vào các
biến s,m,h:
//(1)
StartI2C();
IdleI2C();
//(2)
WriteI2C(0xd0);
IdleI2C();
//(3)
WriteI2C(0x00);
IdleI2C();
//(4)
RestartI2C();
IdleI2C();
//(5)
WriteI2C(0xd1);
IdleI2C();
//(6)
s=ReadI2C();
IdleI2C();
AckI2C();
28
IdleI2C();
m=ReadI2C();
IdleI2C();
AckI2C();
IdleI2C();
h=ReadI2C();
IdleI2C();
//(7)
NotAckI2C();
IdleI2C();
StopI2C();
Giá trị đọc/ghi từ/đến DS1307 là các số biểu diễn dưới dạng mã BCD. Để thuận
tiện cho quá trình hiển thị trên LCD cần chuyển đổi từ số BCD sang số nguyên và
ngược lại:
Chuyển từ BCD sang số nguyên:
char bcd_int(int x)
{
return (((x>>4)&0x0f)*10)+(x&0x0f);
}

29
Chuyển từ số nguyên sang BCD:
int int_bcd(int x)
{
char
N[10]={0X00,0X01,0X02,0X3,0X4,0X05,0X06,0X07,0X08,0X09};
int a,b;
a=x/10;
b=x%10;
return ((N[a]<<4)&0xf0)+N[b] ;
}
Ví dụ 1. Cho sơ mạch điện như hình 1.17. Viết chương trình đọc các thanh ghi
giây (địa chỉ 0x00), phút, giờ và hiển thị trên LCD.

U3 DS1307 VCC
SCL 6 8
SCL VCC
SDA 5 3
SDA VBAT
1 Y1 1
X1
2 7 1
X2 SQW/OUT PIN
2 4 3V
GND
32.768KHz C7 2
VCC
104

Hình 1.17. Giao tiếp PIC18F4520 với DS1307


Dưới đây là chương trình tham khảo:
#include<p18f4520.h>
#include<delays.h>
#include<stdio.h>
#include<i2c.h>
#define lcd_data PORTD
#define RS PORTAbits.RA0
#define RW PORTAbits.RA1
#define E PORTAbits.RA2

unsigned int s,m,h;


char M[32];
void lcd_int(void);
void lcd_cmd(unsigned char cmd);
void lcd_write(unsigned char data);
void lcd_str(unsigned char *str);
30
char bcd_int(int x);
int int_bcd(int x);

//******cac ham cua LCD******


void lcd_int(void)
{
lcd_cmd(0x01);
lcd_cmd(0x38);
lcd_cmd(0x0c);
lcd_cmd(0x06);
lcd_cmd(0x01);
}
void lcd_cmd (unsigned char cmd)
{
RW=0;
RS=0;
E=1;
lcd_data=cmd;
E=0;
Delay1KTCYx(10);
}
void lcd_write(unsigned char data)
{
if(data=='\n')
{
lcd_cmd(0xc0);
Delay1KTCYx(10);
return;
}
RW=0;
RS=1;
E=1;
lcd_data=data;
E=0;
Delay1KTCYx(10);
}
void lcd_str(unsigned char*str)
{
while(*str)
{

31
lcd_write(*str);
str++;
}
Delay1KTCYx(10);
}
/******************************************************/
//Cac ham chuyen doi so nguyen, BCD
char bcd_int(int x)
{
return (((x>>4)&0x0f)*10)+(x&0x0f);
}
int int_bcd(int x)
{
char
N[10]={0X00,0X01,0X02,0X3,0X4,0X05,0X06,0X07,0X08,0X09};
int a,b;
a=x/10;
b=x%10;
return ((N[a]<<4)&0xf0)+N[b] ;
}
/******************************************************/
void main(void)
{
unsigned int a;
TRISA=0X00;
TRISB=0X0F;
TRISC=0X00;
TRISD=0X00;
TRISE=0X00;
ADCON1=0X0F;
OpenI2C(MASTER,SLEW_OFF); //master mode, clock=100Khz
lcd_int();
while(1)
{
StartI2C();
IdleI2C();

WriteI2C(0xd0);
IdleI2C();

32
WriteI2C(0x00);
IdleI2C();

RestartI2C();
IdleI2C();

WriteI2C(0xd1);
IdleI2C();

s=ReadI2C();
IdleI2C();
AckI2C();
IdleI2C();

m=ReadI2C();
IdleI2C();
AckI2C();
IdleI2C();

h=ReadI2C();
IdleI2C();

NotAckI2C();
IdleI2C();
StopI2C();

s=bcd_int(s);
m=bcd_int(m);
h=bcd_int(h);
lcd_cmd(0x80);
sprintf(&M[0],"TIME:
%d%d:%d%d:%d%d",h/10,h%10,m/10,m%10,s/10,s%10);
lcd_str(&M[0]);
}
}
Ví dụ 2. Đặt thời gian cho DS1307
Để thuận tiện cho quá trình đặt thời gian, chúng ta xây dựng hàm set_time dùng
để đặt giờ, phút, giây và ngày, tháng, năm. Lưu ý: Thanh ghi năm (year) chỉ gồm 8bit

33
nên chỉ có thể đặt 2 số hàng chục và hàng đơn vị của năm hiện tại. Ví dụ: năm 2015 có
thể ghi số 15 vào thanh ghi này.
void set_time(unsigned int s,unsigned int m,unsigned int
h,unsigned int dd,unsigned int mm,unsigned int yy )
{
s=int_bcd(s);
m=int_bcd(m);
h=int_bcd(h);
dd=int_bcd(dd);
mm=int_bcd(mm);
yy=int_bcd(yy);

StartI2C();
IdleI2C();

WriteI2C(0xD0);
IdleI2C();

WriteI2C(0x00);
IdleI2C();

WriteI2C(s);
IdleI2C();

WriteI2C(m);
IdleI2C();

WriteI2C(h);
IdleI2C();
StopI2C();
//bat dau lai qua trinh ghi
StartI2C();
IdleI2C();

WriteI2C(0xD0);
IdleI2C();

WriteI2C(0x04);//ghi tu thanh ghi date(dia chi 0x04)


IdleI2C();

34
WriteI2C(dd);
IdleI2C();

WriteI2C(mm);
IdleI2C();

WriteI2C(yy);
IdleI2C();
StopI2C();
}
Tương tự, chúng ta có thể xây dựng hàm đọc thời gian:
void get_time(void)
{
StartI2C();
IdleI2C();

WriteI2C(0xd0);
IdleI2C();

WriteI2C(0x00);
IdleI2C();

RestartI2C();
IdleI2C();

WriteI2C(0xd1);
IdleI2C();

s=ReadI2C();
IdleI2C();
AckI2C();
IdleI2C();

m=ReadI2C();
IdleI2C();
AckI2C();
IdleI2C();

h=ReadI2C();
IdleI2C();
35
NotAckI2C();
IdleI2C();
StopI2C();
//bat dau lai qua trinh doc
StartI2C();
IdleI2C();

WriteI2C(0xd0);
IdleI2C();

WriteI2C(0x04);//doc tu thanh ghi date (dia chi 0x04)


IdleI2C();

RestartI2C();
IdleI2C();

WriteI2C(0xd1);
IdleI2C();

dd=ReadI2C(); //doc ngay


IdleI2C();
AckI2C();
IdleI2C();

mm=ReadI2C(); //doc thang


IdleI2C();
AckI2C();
IdleI2C();

yy=ReadI2C(); //doc nam


IdleI2C();

NotAckI2C();
IdleI2C();
StopI2C();
}
Với các hàm đã xây dựng, chúng ta có thể tùy ý đặt/đọc thời gian thực, dưới đây
là chương trình minh họa:
void main(void)
36
{
//cac lenh khoi tao

//dat thoi gian:
//gio:phut:giay=06:06:06
//ngay/thang/nam=23/12/15
set_time(6,6,6,23,12,15);
while(1)
{
get_time();
s=bcd_int(s);
m=bcd_int(m);
h=bcd_int(h);
dd=bcd_int(dd);
mm=bcd_int(mm);
yy=bcd_int(yy);
//hien thi gio, phut, giay tren dong thu nhat
lcd_cmd(0x80);
sprintf(&M[0],"TIME:
%d%d:%d%d:%d%d",h/10,h%10,m/10,m%10,s/10,s%10);
lcd_str(&M[0]);
//hien thi ngay, thang, nam tren dong thu hai
lcd_cmd(0xc0);
sprintf(&M[0],"DATE:
%d%d/%d%d/20%d%d",dd/10,dd%10,mm/10,mm%10,((yy%1000)%100)/
10,((yy%1000)%100)%10);
lcd_str(&M[0]);
}
}
Chương trình chạy cho ra kết quả trên LCD như sau:

TIME: 06:06:06
DATE: 23/12/2015

1.4.2. Giao tiếp EEPROM 24C256


24C256 là vi mạch nhớ EEPROM của hãng Atmel được tích hợp giao thức I2C.
Vi mạch này có một số tính năng chính sau:
- Dung lượng 32768 x 8 bit (32 Kbyte)

37
- Có thể hoạt động ở dải điện áp rộng (2,7÷5,5V hoặc 1,8÷3,6V)
- Có thể hoạt động với xung clock ở 03 tần số: 1Mhz, 400Khz và 100Khz.
- Có chức năng bảo vệ dữ liệu bằng cả phần cứng và phần mềm. Dữ liệu được
ghi có thể lưu được trong 40 năm.

Hình 1.18. Sơ đồ khối và sơ đồ chân của vi mạch 24C256


Các khối chính trên 24C256:
- EEPROM: Gồm 32Kbyte x 8bit được chia thành 512 page (mỗi page gồm 64
byte). Từ vi mạch chủ có thể ghi/đọc từng byte hoặc ghi/đọc cả 64 byte trong cùng
một page.
- Khối so sánh địa chỉ (address comparator). Vi mạch 24C256 có 2 chân địa chỉ
(A0, A1) quy định địa chỉ của từng vi mạch khi chúng được ghép trên cùng bus. Mức
logic trên 2 chân này sẽ được so sánh với 2 bit trong byte địa chỉ mà 24C256 nhận
được từ vi mạch chủ. Nếu trùng khớp nghĩa là vi mạch chủ muốn đọc/ghi trên bộ nhớ
EEPROM tương ứng. Cách đặt địa chỉ bằng phần cứng này cho phép có tối đa 4 vi
mạch 24C256 cùng kết nối trên bus.
- Khối Start Stop Logic và khối Serial Control Logic: Tạo thành module I2C.
Ngoài ra 24C256 còn có chức năng bảo vệ ghi (Write Protection). Khi chân WP
được nối với GND hoặc để trống, từ vi mạch chủ có thể ghi vào các ô nhớ trong vi
mạch. Khi chân WP nối với Vcc, không cho phép ghi vào vi mạch.
38
Có 2 kiểu ghi dữ liệu vào 24C256, bao gồm: Ghi từng byte (byte write) và ghi
liên tục 64 byte (page write).
Để ghi từng byte vào các ô nhớ của 24C256, từ vi mạch chủ cần thực hiện các
bước sau (hình 1.19):

Hình 1.19: Thao tác byte write


Tạo tín hiệu “start”:
StartI2C();
IdleI2C();
Ghi địa chỉ của vi mạch (device address). Giả thiết rằng chân A0, A1 của 24C256
được nối GND, khi đó byte cần ghi sẽ là: 0b10100000.
WriteI2C(0b10100000);
IdleI2C();
Ghi địa chỉ của ô nhớ cần ghi dữ liệu vào. Giả thiết rằng từ vi mạch chủ cần ghi
vào ô nhớ có địa chỉ 0x0006, câu lệnh tương ứng sẽ là:
WriteI2C(0x00); //byte cao
IdleI2C();
WriteI2C(0x06); // byte thấp
IdleI2C();
Ghi byte dữ liệu. Giả thiết cần ghi số 0x41, câu lệnh tương ứng sẽ là:
WriteI2C(0x41);
IdleI2C();
Tạo tín hiệu stop:
StopI2C();
Thao các page write gần tương tự thao tác byte write, chỉ khác là sau khi ghi byte
dữ liệu (bước 4) thì sẽ tiếp tục ghi lần lượt 63 byte dữ liệu còn lại (hình 1.20).

39
Hình 1.20: Thao tác page write

Hình 1.21: Thao tác đọc từ 24C256


Để đọc các byte dữ liệu từ 24C256, từ vi mạch chỉ cần thực hiện các bước sau
(hình 1.21):
Tạo tín hiệu “start”:
StartI2C();
IdleI2C();
Ghi địa chỉ của vi mạch (device address). Giả thiết rằng chân A0, A1 của 24C256
được nối GND, khi đó byte cần ghi sẽ là: 0b10100000 (bit R/W=0).
WriteI2C(0b10100000);
IdleI2C();
Ghi địa chỉ của ô nhớ cần đọc dữ liệu ra. Giả thiết rằng từ vi mạch chủ cần đọc ô
nhớ có địa chỉ 0x0006, câu lệnh tương ứng sẽ là:
WriteI2C(0x00); //byte cao
IdleI2C();
WriteI2C(0x06); // byte thấp
IdleI2C();
Tạo tín hiệu restart
RestartI2C();
IdleI2C();
Ghi byte địa chỉ của vi mạch: (bit R/W=1).

40
WriteI2C(0b10100001);
IdleI2C();
Đọc byte dữ liệu từ ô nhớ tương ứng
a=ReadI2C(); //byte dữ liệu chứa trong biến a.
IdleI2C();
Tạo tín hiệu NotAck, stop:
NotAckI2C();
IdleI2C();
StopI2C();
Khi cần đọc dữ liệu tại ô nhớ có địa chỉ trùng với địa chỉ trong word address
(current address read) có thể bỏ qua bước 1,2,3 (hình 1.22). Khi cần đọc liên tiếp các
ô nhớ (Sequential Read) có thể thực hiện các thao tác đọc ngay sau bước 6 (

Hình 1.22: Thao tác current address read

Hình 1.22: Thao tác Sequential read


Ví dụ 3. Ghi vào ô nhớ có địa chỉ 0x0006 của vi mạch 24C256 byte dữ liệu 0x41
sau đó, đọc từ ô nhớ này ra và hiển thị trên LCD (vi mạch EEPROM được ghép nối
với PIC18F4520 như hình 1.17).
Dưới đây là chương trình tham khảo:
void main(void)
{
unsigned char a;

41
TRISA=0X00;
TRISB=0X0F;
TRISC=0X00;
TRISD=0X00;
TRISE=0X00;
ADCON1=0X0F;
OpenI2C(MASTER,SLEW_OFF);
lcd_int();
//ghi vao ROM (byte write)
//(1)
StartI2C();
IdleI2C();
//(2)
WriteI2C(0b10100000); //A0A1=00, R/W=0
IdleI2C();
//(3)
WriteI2C(0x00);
IdleI2C();

WriteI2C(0x06);
IdleI2C();
//(4)
WriteI2C(0x41); //ma ky tu ‘A’
IdleI2C();
//(5)
StopI2C();
while(1)
{
//doc tu ROM
//(1)
StartI2C();
IdleI2C();
//(2)
WriteI2C(0b10100000); //A0A1=00, R/W=0
IdleI2C();
//(3)
WriteI2C(0x00);
IdleI2C();

WriteI2C(0x06);

42
IdleI2C();
//(4)
RestartI2C();
IdleI2C();
//(5)
WriteI2C(0b10100001); //A0A1=00, R/W=1
IdleI2C();
//(6)Lưu ý, biến a là biến kiểu char
a=ReadI2C();
IdleI2C();
//(7)
NotAckI2C();
IdleI2C();
StopI2C();
//hien thi
lcd_cmd(0x80);
lcd_write(a);
}
}
Ví dụ 4. Ghi/đọc nhiều byte.
Để thuận tiện cho quá trình ghi/đọc EEPROM, chúng ta xây dựng các hàm:
- byte_write: Ghi từng byte
- page_write: ghi liên tiếp 64 byte
- byte_read: đọc
//Hàm ghi 1 byte vào một địa chỉ bất kỳ trên EEPROM
void byte_write(unsigned char add_high, unsigned char
add_low ,unsigned char data)
{
StartI2C();
IdleI2C();
WriteI2C(0b10100000);
IdleI2C();
WriteI2C(add_high);
IdleI2C();
WriteI2C(add_low);
IdleI2C();
WriteI2C(data);
IdleI2C();

43
StopI2C();
}
//Hàm ghi 1 page (64 byte) bắt đầu từ một địa chỉ bất kỳ trên EEPROM
void page_write(unsigned char add_high,unsigned char
add_low,unsigned char *data)
{
unsigned int i;
StartI2C();
IdleI2C();
WriteI2C(0b10100000);
IdleI2C();
WriteI2C(add_high);
IdleI2C();
WriteI2C(add_low);
IdleI2C();
for(i=0;i<=63;++i)
{
WriteI2C(*data);
IdleI2C();
data++;
}
StopI2C();
}
//Hàm đọc 1 byte từ một địa chỉ bất kỳ trên EEPROM
char byte_read(unsigned char add_high,unsigned char
add_low)
{
char a;
StartI2C();
IdleI2C();
WriteI2C(0b10100000);
IdleI2C();
WriteI2C(add_high);
IdleI2C();
WriteI2C(add_low);
IdleI2C();

RestartI2C();
IdleI2C();

44
WriteI2C(0b10100001);
IdleI2C();
a=ReadI2C();
IdleI2C();
NotAckI2C();
IdleI2C();

StopI2C();
return a;
}
Chương trình dưới đây sẽ ghi một mảng gồm 64 phần tử (từ 0 đến 63) vào
EEPROM và đọc ra 2 phần tử bất kỳ. (Khi chạy, trên LCD sẽ hiển thị số 16 (phần tử
có chỉ số bằng 16 trong mảng N) ở hàng trên và số 17 ở hàng dưới)
void main(void)
{
unsigned int a,b;
unsigned char
N[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,4
0,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59
,60,61,62,63,64,65,66};
TRISA=0X00;
TRISB=0X0F;
TRISC=0X00;
TRISD=0X00;
TRISE=0X00;
ADCON1=0X0F;
led1=led2=led3=1;
OpenI2C(MASTER,SLEW_OFF);
lcd_int();
//ghi vào page cuối cùng trong không gian 0000÷3FFF
page_write(0x3f,0xc0,&N[0]);
while(1)
{
//đọc ra byte thứ 17 được ghi vào
a=byte_read(0x3f,0xd0);
lcd_cmd(0x80);
sprintf(&M[0],"%d",a);
lcd_str(&M[0]);
45
//đọc ra byte thứ 18 được ghi vào
b=byte_read(0x3f,0xd1);
lcd_cmd(0xc0);
sprintf(&M[0],"%d",b);
lcd_str(&M[0]);
}
}

46

You might also like