You are on page 1of 100

Bài 1: Hướng dẫn lập trình cho PIC bằng CCS ver3.

242
1. Tổng quan về CCS

1.1. Vì sao ta sử dụng CCS?

Sự ra đời của một loại vi điều khiển đi kèm với việc phát triển phần mềm ứng dụng cho việc
lập trình cho con vi điều khiển đó. Vi điều khiển chỉ hiểu và làm việc với hai con số 0 và 1.
Ban đầu để việc lập trình cho VĐK là làm việc với dãy các con số 0 và 1. Sau này khi kiến
trúc của Vi điều khiển ngày càng phức tạp, số luợng thanh ghi lệnh nhiều lên, việc lập
trình với dãy các số 0 và 1 không còn phù hợp nữa, đòi hỏi ra đời một ngôn ngữ mới thay
thế. Và ngôn ngữ lập trình Assembly. Ở đây ta không nói nhiều đến Assmebly. Sau này
khi lập trình cho Vi điều khiển một cách ngắn gọn và dễ hiểu hơn đã dẫn đến sự ra đời
củangôn ngữ C ra đời, nhu cầu dùng ngôn ngữ C đề thay cho ASM trong việc mô tả các lệnh
nhiều chương trình soạn thảo và biên dịch C cho Vi điều khiển : Keil C, HT‐PIC,
MikroC,CCS…
Tôi chọn CCS cho bài giới thiệu này vì CCS là một công cụ lập trình C mạnh cho Vi điều
khiển PIC. Những ưu và nhược điểm của CCS sẽ được đề cập đến trong các phần dưới
đây.

1.2. Giới thiệu về CCS

CCS là trình biên dịch lập trình ngôn ngữ C cho Vi điều khiển PIC của hãng Microchip.
Chương trình là sự tích hợp của 3 trình biên dich riêng biết cho 3 dòng PIC khác nhau đó
là:
‐ PCB cho dòng PIC 12‐bit opcodes
‐ PCM cho dòng PIC 14‐bit opcodes
‐ PCH cho dòng PIC 16 và 18‐bit
Tất cả 3 trình biên dich này đuợc tích hợp lại vào trong một chương trình bao gồm cả trình
soạn thảo và biên dịch là CCS, phiên bản mới nhất là PCWH Compiler Ver 3.227.

Giống như nhiều trình biên dich C khác cho PIC, CCS giúp cho người sử dụng nắm bắt
nhanh được vi điều khiển PIC và sử dụng PIC trong các dự án. Các chương trình diều
khiển sẽ được thực hiện nhanh chóng và đạt hiệu quả cao thông qua việc sử dụng ngôn
ngữ lạp trình cấp cao – Ngôn ngữ C.Tài liệu hướng dẫn sử dụng có rất nhiều, nhưng chi
tiết nhất chính là bản Help đi kèm theo phần mềm (tài liệu Tiếng Anh). Trong bản trợ giúp
nhà sản xuất đã mô tả rất nhiều về hằng, biến, chỉ thị tiền xủa lý, cấu trúc các câu lệnh
trong chương trình, các hàm tạo sẵn cho người sử dụng…

2. Tạo PROJECT đầu tiên trong CCS

Để tạo một Project trong CCS có nhiều cách, có thể dùng Project Wizard, Manual Creat,
hay đơn giản là tạo một Files mới và thêm vào đó các khai báo ban đầu cần thiết và “bắt
buộc”.
Dưới đây sẽ trình bày cách tạo một project hợp lệ theo cả 3 phương pháp. Một điều ta
cần chú ý khi tạo một Project đó là: khi tạo bắt cứ một Project nào mới thì ta nên tạo một
thư mục mới với tên liên quan đến Project ta định làm, rồi lưu các files vào đó. Khi lập
trình và biên dịch, CCS sẽ tạo ra rất nhiều files khác nhau, do đó nếu để chung các
Project trogn một thư mục sẽ rất mất thời gian trong việc tìm kiếm sau này. Đây cũng là
quy tắc chung khi ta làm việc với bất kỳ phần mềm nào, thiết kế mạch hay lập trình.
Việc đầu tiên bạn cần làm là khởi động máy tính và bật chương trình PIC C Compiler.

2.1. Tạo một PROJECT sử dụng PIC Wizard

Trước hết bạn khởi động chương trình làm việc PIC C Compiler. Từ giao diện chương
trình bạn di chuột chọn Project ‐> New ‐> PIC Wizard nhấn nút trái chuột chọn.

Sau khi nhấn chuột, một cửa sổ hiện ra yêu cầu ban nhập tên Files cần tạo. Bạn tạo một
thư mục mới, vào thư mục đó và lưu tên files cần tạo tại đây.
 Cửa sổ Save As

Như vậy là xong bước đầu tiên. Sau khi nhấn nút Save, một cửa sổ New Project hiện ra.
Trong của sổ này bao gồm rất nhiều Tab, mỗi Tab mô tả về một vài tính năng của con
PIC. Ta sẽ chọn tính năng sử dụng tại các Tab tương ứng.
Dưới đây sẽ trình bày ý nghĩa từng mục chọn trong mỗi Tab. Các mục chọn này chính là
đề cập đến các tính năng của một con PIC, tùy theo từng loại mà sẽ có các Tab tương
ứng. Đối với từng dự án khác nhau, khi ta cần sử dụng tính năng nào của con PIC thì ta
sẽ chọn mục đó. Tổng cộng có 13 Tab đẻ ta lưa chọn. Tôi giới thiệu những Tab chính
thường hay được sử dụng.

2.1.1. Tab General

Tab General cho phép ta lựa chọn loại PIC mà ta sử dụng và một số lựa chọn khác như
chọn tần số thạch anh dao động, thiết lập các bit CONFIG nhằm thiết lập chế độ hoạt
động cho PIC.
 Tab General

‐ Device: Liệt kê danh sách các loại PIC 12F, 16F, 18F… Ta sẽ chọn tên Vi điều khiển
PIC mà ta sử dụng trong dự án. Lấy ví dụ chọn PIC16F877A.
‐ Oscilator Frequency: Tần số thạch anh ta sử dụng, chọn 20 MHz (tùy từng loại)
‐ Fuses: Thiết lập các bit Config như: Chế độ dao động (HS, RC, Internal ), chế độ bảo
vệ Code, Brownout detected…
‐ Chọn kiểu con trỏ RAM là 16‐bit hay 8‐bit.

2.1.2. Tab Communications

Tab Communications liệt kê các giao tiếp nối tiếp mà một con PIC hỗ trợ, thường là
RS232 và I2C, cùng với các lựa chọn để thiết lập chế độ hoạt động cho từng loại giao
tiếp.

Giao tiếp RS232

Mỗi một Vi điều khiển PIC hỗ trợ một cổng truyền thông RS232 chuẩn. Tab này cho phép
ta lựa chọn chân Rx, Tx, tốc độ Baud, Data bit, Bit Parity…

Giao tiếp I2C

Để sử dụng I2C ta tích vào nút chọn Use I2C, khi đó ta có các lựa chọn: Chân SDA, SCL,
Tốc độ truyền (Fast ‐ Slow), chế độ Master hay Slave, địa chỉ cho Salve.
Tab Communications

2.1.3. Tab SPI and LCD

Tab này liệt kê cho người dùng các lựa chọn đối với giao tiếp nối tiếp SPI, chuẩn giao
tiếp tốc độ cao mà PIC hỗ trợ về phần cứng. Chú ý khi ta dùng I2C thì không thể dùng
SPI và ngược lại. Để có thể sử dụng cả hai giao tiếp này cùng một lúc thì buộc một trong
2 giao tiếp phải lập trình bằng phần mềm (giồng như khi dùng I2C cho các chip AT8051,
không có hỗ trợ phần cứng SSP).
Phần cấu hình cho LCD dành cho các chíp dòng 18F và 30F.
Tab SPI and LCD

2.1.4. Tab Timer

Liệt kê các bộ đếm/định thời mà các con PIC dòng Mid‐range có: Timer0, timer1, timer2,
WDT…
Trong các lựa chọn cấu hình cho các bộ đếm /định thời có: chọn nguồn xung đồng hồ
(trong/ngoài), khoảng thời gian xảy ra tràn…

Tab Timer

2.1.5. Tab Analog


Liệt kê các lựa chọn cho bộ chuyển đổi tương tự/số (ADC) của PIC. Tùy vào từng IC cụ
thể mà có các lựa chọn khác nhau, bao gồm:
‐ Lựa chọn cổng vào tương tự
‐ Chọn chân điện áp lấy mẫu (Vref)
‐ Chọn độ phân giải: 8‐bit = 0 ~ 255 hay 10‐bit = 0~1023
‐ Nguồn xung đồng hồ cho bộ ADC (trong hay ngoài), từ đó mà ta có được tốc độ lấy
mẫu, thường ta chọn là internal 2‐6 us.
‐ Khi không sử dụng bộ ADC ta chọn none

Tab Analog

2.1.6. Tab Other

Tab này cho phép ta thiết lập các thông số cho các bộ Capture/Comparator/PWM.

Capture ‐ Bắt giữ

‐ Chọn bắt giữ xung theo sườn dương (rising edge) hay sườn âm (falling edge) của
xung vào
‐ Chọn bắt giữ sau 1, 4 hay 16 xung (copy giá trị của TimerX vào thanh ghi lưu trữ
CCCPx sau 1, 4 hay 16 xung).

Compare ‐ So sánh

‐ Ta có các lựa chọn thực hiện lệnh khi xayư ra bằng nhau giữa 2 đối tượng so sánh là
giá trị của Timer1 với giá trị lưu trong thanh ghi để so sánh. Bao gồm:
o Thực hiện ngắt và thiết lập mức 0
o Thực hiện ngắt và thiết lập mức 1
o Thực hiện ngắt nhưng không thay đổi trạng thái của chân PIC.
o Đưa Timer1 về 0 nhưng không thay đổi trạng thái chân.

PWM ‐ Điều chế độ rộng xung

‐ Lựa chọn về tần số xung ra và duty cycle. Ta có thể lựa chọn sẵn hay tự chọn tần số,
tất nhiên tần số ra phải nằm trong một khoảng nhất định.

Comparator ‐ So sánh điện áp

‐ Lựa chọn mức điện áp so sánh Vref. Có rất nhiều mức điện áp để ta lựa chọn. Ngoài ra
ta còn có thể lựa chọn cho đầu vào của các bộ so sánh.
Tab Other

2.1.7. Tab Interrupts và Tab Driver

Tab Interrupts cho phép ta lựa chọn nguồn ngắt mà ta muốn sử dụng. Tùy vào từng loại
PIC mà số lượng nguồn ngắt khác nhau, bao gồm: ngắt ngoài 0(INT0), ngắt RS232, ngắt
Timer, ngắt I2C‐SPI, ngắt onchange PORTB.v.v…
Tab Drivers được dùng để lựa chọn những ngoại vi mà trình dịch đã hỗ trợ các hàm giao
tiếp. Đây là nhưng ngoại vi mà ta sẽ kết nối với PIC, trong các IC mà CCS hỗ trợ, đáng
chú ý là các loại EEPROM như 2404, 2416, 2432, 9346, 9356…Ngoài ra còn có IC RAM
PCF8570, IC thời gian thực DS1302, Keypad 3x4, LCD, ADC… Chi tiết ta có thể xem
trong thư mục Driver của chương trình: \...\PICC\Drivers

Tab Interrupts
Tab Driver

Sau các bước chọn trên, ta nhấn OK để kết thúc quả trình tạo một Project trong CCS, một
Files ten_project.c được tạo ra, chứa những khai báo cần thiết cho PIC trong một Files
ten_project.h.

Sau đây là ví dụ về cấu trúc 1 chương trình trong CCS :

#include < 16F877 .h >


#device PIC6f877 *=16 ADC=10
#use delay(clock=20000000)
 . . . . 
Int16 a,b;
 . . . .
Void xu_ly_ADC ( )
{...
 . . . 
}

#INT_TIMER1
Void xu_ly_ngat_timer ( )
{...
 . . .
}

Main ( )
{...
 . . .
}

+ Đầu tiên là các chỉ thị tiền xử lý : # . . . có nhiệm vụ báo cho CCS cần sử dụng những gì
trong chương trình C như dùng VXL gì , có dùng giao tiếp PC qua cổng COM không , có
dùng  ADC không , có dùng DELAY không  ,  có biên dịch kèm các file hay không . . . 
+ Các khai báo biến . 
+ Các hàm con do ta viết : xu_ly_ADC ()  , . . .
+ Các hàm phục vụ ngắt theo sau bởi 1 chỉ thị tiền xử lý cho biết dùng ngắt nào.
+ Chương trình chính .
+ Một chương trình C có thể được viết luôn tuồn trong  hàm main () , nếu chúng rất ngắn
và đơn giản. Nhưng khi chương trình bắt đầu dài ra , phức tạp lên 1 chút thì phải phân
chia trong các hàm con .

Các hàm này có thể là :

1/ Hàm không trả về trị.


Ví dụ :

Void xu_ly( )

z= x+y ;
}

Hàm trên chỉ thực hiện các lệnh trong thân hàm , khi gọi hàm này chỉ đơn giản viết :

Xu_ly( ) ;

2/ Hàm có trả về trị. 


Ví dụ :

int  xu_ly ( int a , int b)


{
 . . . . . .
Return (a+b) ;
}

Hàm trên sẽ trả về tổng  (a+b) . khi sử dụng , ví dụ tính tổng 2 biến e ,f , chương trình như
sau (trong hàm main() ) :

Main()
{
Int e ,f  ,g ;
e=7 ;
f= 4;
g = xu_ly(e ,f );   // giá trị g=28
}

+ Mỗi hàm con nên được viết để thực hiện 1 chức năng chuyên biệt nào đó  . Bên trong 1 
hàm con có thể gọi 1 hay nhiều hàm khác  . Cách thức hoạt động như  viết 1 chương trình
C trên máy tính . 
+ Nếu chương trình lớn hơn nữa có thể làm file c rất dài và do đó rất khó kiểm soát , nên
sẽ cần phân chia ra các file c . trong đó file chính chứa hàm main sẽ được biên dịch . Các
file c khác chứa các hàm phục vụ chuyên biệt như : cho LCD , . . .Trong file chính chỉ cần
thêm dòng #include < filex.c > là tất cả hàm cần dùng chứa trong file x sẽ được biên dịch
vào file hex chung.  Các ví dụ trong thư mục của CCS nếu có sử dụng LCD sẽ chèn 1
dòng #include <lcd.c> và do đó sẽ gọi được các hàm trong file này mà không cần phải
viết lại . điều này có nghĩa là ta có thể viết các file c chứa mã tổng quát có thể dùng chung
cho nhiều project , tức là tái sử dụng mã , thay vì phải viết lại chuyên biệt cho từng project
. Đây là cách làm chuyên nghiệp cho những project lớn .

Bài 2: Sử dụng Biến và Hàm, Cấu trúc lệnh, Chỉ thị tiền xử lý
trong CCS

I / KHAI BÁO VÀ SỬ DỤNG BIẾN , HẰNG , MẢNG : 

1 /Khai báo biến  , hằng  ,mảng : 

+ Các loại biến sau được hỗ trợ : 

int1  số  1 bit = true hay false ( 0 hay 1)


int8  số  nguyên 1 byte ( 8 bit)
int16  số nguyên 16 bit
int32  số nguyên 32 bit
char  ký tự 8 bit
float  số thực 32 bit
short  mặc định như  kiểu int1
byte  mặc định như kiểu int8
int  mặc định như kiểu int8
long  mặc định như kiểu int16

+ Thêm signed hoặc unsigned phía trước để chỉ đó là số có dấu hay không dấu .Khai báo như
trên mặc định là không dấu . 4 khai báo cuối không nên dùng vì dễ nhầm lẫn . Thay vào đó nên
dùng 4 khai báo đầu .

VD : 
Signed int8  a ; // số a là 8 bit dấu ( bit 7 là bit dấu ). 
Signed int16 b , c , d ; 
Signed int32 , . . .  

+ Phạm vi biến :  

 Int8 :0 , 255  signed int8 : -128 , 127


 Int16 : 0 ,2^15-1 signed int16 : -2^15 , 2^15-1
 Int32 : 0 , 2^32-1 signed int32 : -2^31 , 2^31-1

+ Khai báo hằng : 


VD : 
 Int8 const  a=231 ; 

+ Khai báo 1 mảng hằng số : 


VD :
Int8 const a[5] = { 3,5,6,8,6 } ;  //5 phần tử , chỉ số mảng bắt đầu từ 0 : a[0]=3 

+ Một mảng hằng số có kích thước tối đa tuỳ thuộc loại VĐK: 

*NếuVĐK là PIC 14 ( VD :16F877 )  : bạn chỉ được khai báo 1 mảng hằng số  có kích thước tối đa
là 256byte .

 Các khai báo sau là hợp lệ :


Int8 const a[5]={ . . .};  // sử dụng 5 byte , dấu . . . để bạn điền số vào  
Int8 const a[256]={ . . .};  // 256 phần tử x 1 byte = 256 byte  
Int16 const a[12] = { . . . };  // 12 x 2= 24 byte 
Int16 const a[128] = { . . . }; // 128 x 2= 256 byte 
không hợp lệ :

Int16 const a[200] = { . . . }; // 200 x 2 =400 byte

*Nếu VĐK là PIC 18 : khai báo mảng hằng số thoải mái , không giới hạn kích thước .

+ Lưu ý : nếu đánh không đủ số phần tử vào trong ngoặc kép như  đã khai báo , các phần tử còn
lại sẽ là 0 . Truy xuất giá trị vượt quá chỉ số mảng khai báo sẽ làm chương trình chạy vô tận .
VD :
int8 const a [7] = { 0 , 3,5 ,9 }   // các phần tử a[4] ,a[5],a[6] đều =0 

+ Mảng hằng số thường  dùng làm bảng tra (ví dụ bảng tra sin ) , viết dễ dàng và nhanh chóng ,
gọn hơn so với khi dùng ASM để viết .

+ Khai báo 1 biến mảng : kích thước tuỳ thuộc khai báo con trỏ trong #device  và loại VDK:

*PIC 14 : Nếu bạn khai báo con trỏ 8 bit : VD: # device *=8  : không gian bộ nhớ chỉ có 256 byte
cho tất cả các biến chương trình bất chấp VĐK của bạn có hơn 256 byte RAM (Vd : 368 , . . .)  và
biến mảng có kích thước tối đa tuỳ thuộc độ phân mảnh bộ nhớ , với 16F877 có 368 byte ram ,
thường thì kích thước không quá 60 byte ,có khi dưới 40 byte , nếu khai báo lớn hơn sẽ gặp lỗi vô
duyên : "not enough ram for all variable"  trong khi thực sự VDK còn rất nhiều RAM . Nếu khai
báo con trỏ 16 bit : VD : #device *=16 , không gian bộ nhớ là đầy đủ ( trừ đi 1 ít RAM do CCS
chiếm làm biến tạm ) .VD : với 16F877  bạn dùng đủ 368 byte RAM . Nhưng kích thước mảng
cũng không quá 60 byte .

* PIC 18 : kích thước mảng không giới hạn, xài hết RAM thì thôi . Với khai báo con trỏ 8 bit ,
bạn chỉ được xài tối đa 256 byte RAM , nếu khai báo con trỏ 16 bit , bạn xài trọn bộ nhớ RAM thực
sự . VD: Khai báo biến mảng : int16  a[125] ; // biến mảng 126 phần tử , kích thước 252 byte ram . 

2 / Cách sử dụng biến : 

+ Khi sử dụng các phép toán cần lưu ý : sự tràn số , tính toán với số âm , sự chuyển kiểu và ép
kiểu .

A ) Một vài ví dụ  về tràn số  , làm tròn :  


VD :  
Int8 a=275;  // a =275-256=19 
Int8 const a=275 //a=19 
Int8 a=40 , b=7 , c; 
C=a * b ;  //c=280-256=24 
C=a / b ;  //c=5 

+ Bạn có thể ép kiểu , thường là tiết kiệm ram , hay muốn tiết kiệm thời gian tính , . . .. VD : 
Int8 a =8 , b=200; 
Int16 c ; 
C=  ( int16) a * b ; 
// c= 1600 , a chuyển sang 16 bit , 16bit*8bit => b tự động chuyển sang 16 bit , kết quả là 16 bit
trong c , lưu ý biến a , b vẫn là 8 bit .  

+ 8bit * 8bit  =>  phép nhân là 8 bit , KQ là 8 bit


+ 16bit * 8 bit  =>  phép nhân là 16 bit , KQ là 16 bit
+ 32bit * 16 bit  =>  phép nhân là 32 bit , KQ là 32 bit
+ 16bit * 16 bit  =>  phép nhân là 16 bit , KQ là 16 bit
...v.v...

+ Có thể ép kiểu kết quả : VD : 16b*8b => 16bit , nếu gán vào biến 8 bit thì KQ sẽ cắt bỏ 8 bit
cao .

B )Phạm vi sử dụng  biến : 

+ Giống  như C trong lập trình C cho máy tính . Biến có thể được khai báo như toàn cục hay cục
bộ . Biến khai báo trong hàm sẽ là cục bộ và sẽ chỉ dùng được trong hàm đó , kể cả trong hàm
main() . Ngoài ra còn có thể khai báo ngay trong 1 khối lệnh , và cũng chỉ tồn tại trong khối lệnh đó
. Do vậy nếu dùng MPLAB để mô phỏng , thì khi nhảy vào hàm hay khối lệnh có chứa khai báo
biến đó thì biến đó mới có giá trị , có khi nhảy ra ngoài hàm thì biến đó sẽ là” out of scope” khi ta
quan sát chúng trong cửa sổ Watch.
+ Chi tiết về phạm vi biến xem tài liệu lập trình C trên máy tính .
+ CCS có hỗ trợ cả con trỏ , tuy nhiên ít dùng .
+ CCs không hỗ trợ lập trình hướng đối tượng như C++ . Tuy vậy CCS có hỗ trợ các biến cấu trúc .

3 / Các phép toán , sự thực thi và vấn đề tối ưu mã , chương trình: 


+ Trên đây là thời gian cần cho 1 phép toán . 
+ Khi chương trình của bạn nhỏ xíu và có thể kiểm soát được , và thời gian thực thi là không
quan trọng ,đồng thời có thể không cần mô phỏng thì bạn có thể dùng cả kiểu float nếu thấy tiện . 
+ Khi chương trình lớn , cần mô phỏng , và thời gian thực thi là quan trọng thì các điều sau đây
nên làm : 
+ Không xài biến kiểu float , vì khi mô phỏng không thấy được giá trị thực  của nó .Để khử số thập
phân kiểu float , hãy nhân hay chia cho 2^k . 
VD : số kiểu float : m có thể biểu diễn ở dạng : n / 2^8 , với m biết trước , n nguyên được tính
trước 
bằng cách : n= m* 2^8 , lấy được 2 chữ số sau dấu phẩy (2^8=256 ) . Do đó với 1 bảng tra sin 361 
phần tử từ 0->360 độ , nếu lấy chính xác tới 2 dấu phẩy thì các giá trị sin nhân thêm cho 2^8 , cắt
bỏ 
phần thập phân và lưu vào mảng hằng số int16  , sau đó khi truy xuất tới các giá trị này để sử dụng 
thì hãy chia cho 256 bằng cách dịch phải 8 bit . 

+ Các phép tính nhân chia cho 2^k rất nhanh vì  ta dùng phép toán dịch bit .
VD : 
Z=Y*2^5 ; thì thay bởi z =  y<<5 ;   nhanh gấp 20 lần . 
Z= y / 2^5; thay bởi z = y >>5 ; nhanh gấp 20 lần . 

Trong đó  phép dịch nguyên byte ( 8bit, 16 bit  ) là nhanh nhất . VD : z= y>>8  ; z=y <<16 ; 

+ Không dùng phép trừ mà dẫn đến kết quả có thể âm vì số âm sẽ không hiển thị được  khi mô
phỏng ( số hiển thị sẽ là dương và dĩ nhiên giá trị sẽ khác hẳn ) .Biến đổi sao cho  phép trừ luôn cho
kết quả dương thì mới hiển thị chính xác . 
VD : công thức điều chế sin PWM có dạng : z = T * (1 + ma *  y ) 
Trong đó  : ma <1 , y : giá trị hàm sin : -1< y < 1 . Biến đổi như sau : 
y= (y +1) – 1 = y’ -1

=> z = T* ( 1-ma ) + T * ma * y’ trong đó ( 1-ma ) >=1 . và  0< y’ <2


=> z = [ T * ( 256 – MA ) ]>>8  +  [T * MA * Y’ ] >> 15
Trong đó  MA = ma<<8  và  Y’ = y’ << 7 ;
=> chỉ cần lập bảng tra sin trong đó là các giá trị sin là số nguyên = ( y + 1) * 128 ;
II / CÁC CẤU TRÚC LỆNH : ( statement ) 

+ Gồm các lệnh như: while . . do , case ,  . . .

Lưu ý : các mục trong  [ ] là có thể có  hoặc không . 

+ while (expr) stmt : xét điều kiện trước rồi thực thi biểu thức sau . 
+ do stmt while (expr) : thực thi biểu thức rồi mới xét điều kiện sau . 
+ Return : dùng cho hàm có trả về trị , hoặc không trả về trị cũng được , khi đó chỉ cần dùng:
return ; ( nghĩa là thoát khỏi hàm tại đó ) . 
+ Break : ngắt ngang ( thoát khỏi ) vòng lặp while. _Continue : quay trở về đầu vòng lặp while .

III /CHỈ THỊ TIỀN XỬ LÝ : 

+ Xem chi tiết tất cả ở phần HELP , mục pre_processor . Ở đây sẽ giới thiệu 1 số chỉ thị thường
dùng nhất :

1 / #ASM và #ENDASM :  

+ Cho phép đặt 1 đoạn mã ASM giữa 2 chỉ thị này , Chỉ đặt trong hàm . CCS định nghĩa sẵn 1 biến
8 bit  _RETURN_  để bạn gán giá trị trả về cho hàm từ đoạn mã Assembly.
+ C đủ mạnh để thay thế Assmemly . Vì vậy nên hạn chế lồng mã Assembly vào vì thường gây
ra xáo trộn  dẫn đến sau khi biên dịch mã chạy sai  , trừ phi bạn nắm rõ Assembly và đọc hiểu
mã Assembly sinh ra thông qua mục C/Asm list .
+ Khi sử dụng các biến không ở bank hiện tại , CCS sinh thêm mã  chuyển bank tự động cho các
biến đó . Nếu sử dụng #ASM ASIS thì CCS không sinh thêm mã chuyển bank tự động , bạn phải tự
thêm vào trong mã ASM .

+ Lưu ý : mã Assembly theo đúng mã tập lệnh VDK , không phải mã kiểu MPLAB .
VD : 
int find_parity (int data)    

int count; 
#asm 
movlw   0x8 
movwf   count 
movlw   0 
loop: 
xorwf   data,w 
rrf     data,f 
decfsz  count,f 
goto    loop 
movwf   _return_ 
#endasm 

2 /  #INCLUDE : 

+ Cú pháp : #include <filename>  Hay  #include “ filename”


Filename : tên file cho thiết bị *.h , *.c . Nếu chỉ định file ở đường dẫn khác thì thêm đường dẫn
vào . Luôn phải có để khai báo chương trình viết cho VĐK nào , và luôn đặt ở dòng đầu tiên .
VD : 
#include  <16F877.H>   // chương trình sử dụng cho VĐK 16F877 
#include < C:\INCLUDES\COMLIB\MYRS232.C > 

3 / #BIT ,  #BYTE  ,  #LOCATE  và  # DEFINE: 

+ #BIT id = x . y 
Với id : tên biến  x : biến C ( 8,16,32,…bit) hay hằng số địa chỉ thanh ghi.
y : vị trí bit trong x
=> tạo biến 1 bit đặt ở  byte x vị trí bit y, tiện dùng kiểm tra hay gán trị cho bit thanh ghi . Điểm
khác biệt so với dùng biến 1 bit từ khai báo int1 là : int1 tốn 1 bit bộ  nhớ , đặt ở thanh ghi đa mục
đích nào đó do CCS tự chọn , còn #BIT thì không tốn thêm bộ nhớ do id chỉ là danh định đại diện
cho bit chỉ định ở biến x , thay đổi giá trị id ( 0 / 1 ) sẽ thay đổi giá trị bit tương ứng y ->  thay đổi
trị x.
VD: 
#bit TMR1Flag = 0xb.2   //bit cờ ngắt timer1 ở địa chỉ 0xb.2 (PIC16F877)

Khi đó TMR1Flag = 0   => xoá cờ ngắt timer1


Int16 a=35;  //a=00000000 00100011
#bit b= a.11   //b=0  , nếu b=a.0 thì b chỉ vị trí LSB ( bit thấp nhất , bên  trái)
Sau đó : b=1;  //a=00001000 00100011 = 2083

+ Lưu ý không dùng được  : if ( 0xb.2 ) mà phải khai báo như  trên rồi dùng : if(TMR1Flag)

+ #BYTE  id = x 
X: địa chỉ  id : tên biến C
Gán tên biến id cho địa chỉ (thanh ghi ) x ,  sau đó muốn gán hay kiểm tra địa chỉ x chỉ cần dùng id.
Không tốn thêm bộ nhớ  , tên  id  thường dùng tên gợi nhớ chức năng thanh ghi ở địa chỉ đó . Lưu
ý rằng giá trị thanh ghi có thể thay đổi bất kỳ lúc nào do hoạt động chương trình nên giá trị id cũng
tự thay đổi theo giá trị thanh ghi đó . Không nên dùng id cho thanh ghi đa mục đích như 1 cách
dùng biến int8 vì CCS có thể dùng các thanh ghi này bất kỳ lúc nào cho chương trình , nếu muốn
dùng riêng , hãy dùng #LOCATE.
VD: 
#byte port_b = 0xc6;  // 16F877 :0xc6 là địa chỉ portb 
Muốn port b có giá trị 120 thì : port_b=120;
#byte status = 0xc3; 

+ # LOCATE  id = x 


+ Làm việc như #byte nhưng có thêm chức năng bảo vệ không cho CCS sử dụng  địa chỉ đó vào
mục đích khác  . VD: # LOCATE  temp = 0xc20   // 0xc20 :thanh ghi đa mục đích  
Cách sau tương tự :
Int8 temp ; 
#locate temp = 0xc20 

+ Sử  dụng #LOCATE để gán biến cho 1 dãy địa chỉ kề nhau ( cặp thanh ghi ) sẽ tiện lợi hơn thay
vì phải dùng 2 biến với #byte .
VD : CCP1 có giá trị là cặp thanh ghi 0x15 ( byte thấp ) và 0x16 ( byte cao ) . Để gán trị cho
CCP1 :
Int16 CCP1;
#locate CCP1= 0x15   //  byte thấp  của CCP1 ở 0x15 , byte cao của CCP1 ở 0x16 
Gán trị cho CCP1 sẽ tự động gán vào cả 2 thanh ghi
CCP1 = 1133 ; // = 00000100 01101101    =>  0x15 = 00000100 , 0x16 = 01101101 

+ # DEFINE id   text 
Text : chuỗi hay  số . Dùng định nghĩa giá trị .
VD : #define  a   12345 

4 /  # DEVICE   : 

# DEVICE chip option 


chip : tên VĐK sử dụng , không dùng tham số này nếu đã khai báo tên chip ở #include .
option : toán  tử  tiêu chuẩn theo từng chip:
 * = 5  dùng pointer 5 bit ( tất cả PIC )
 * = 8 dùng pointer 8 bit ( PIC14 và PIC18 )
 * = 16 dùng pointer 16 bit ( PIC14 ,PIC 18)
 ADC = x sử dụng ADC x bit ( 8 , 10 , . . . bit tuỳ chip ) , khi dùng hàm read_adc( ) , sẽ trả
về giá trị x bit .

ICD = true : tạo mã tương thích debug phần cứng Microchip


HIGH_INTS = TRUE  : cho phép dùng ngắt ưu tiên cao

+ Khai báo pointer 8 bit , bạn sử dụng được tối đa 256 byte RAM cho tất cả biến chương trình .
+ Khai báo pointer 16 bit , bạn sử dụng được hết số RAM có của VDK .
+ Chỉ nên dùng duy nhất 1 khai báo #device cho cả pointer và ADC .
VD : #device  * = 16   ADC = 10 

5 / # ORG : 

# org start , end


# org segment
#org start , end  { }

Start , end: bắt đầu và kết thúc vùng ROM dành riêng cho hàm theo sau , hoặc để riêng không dùng.
VD : 
Org 0x30 , 0x1F 
Void xu_ly( ) 

} // hàm này bắt đầu  ở địa chỉ 0x30  

org 0x1E00 
anotherfunc( ) 

} //hàm này bắt đầu tuỳ ý ở 0x1E00 đến 0x1F00 
Org 0x30 , 0x1F { } 
// không có gì cả đặt trong vùng ROM này 

+ Thường thì không dùng ORG .

6 / # USE : 

# USE delay ( clock = speed ) 


Speed : giá trị OSC mà bạn dùng . VD: dùng thạch anh dao động 40Mhz thì :
#use delay( clock = 40000000)
+ Chỉ khi có chỉ thị này thì trong chương trình bạn mới được dùng hàm delay_us ( ) và
delay_ms( ) .

#USE fast_io ( port) 


Port : là tên port :từ  A-G ( tuỳ chip )
+ Dùng cái này thì trong chương trình khi dùng các lệnh io như  output_low() , . . . nó sẽ set chỉ với
1 lệnh , nhanh hơn so với khi không dùng chỉ thị này.
+ Trong hàm main( ) bạn phải dùng hàm set_tris_x( ) để chỉ rõ chân vào ra thì chỉ thị trên mới
có hiệu lực , không thì chương trình sẽ chạy sai .
+ Không cần  dùng nếu không có yêu cầu gì đặc biệt .
VD : # use fast_io( A )  

#USE  I2C  ( options ) 


+ Thiết lập giao tiếp I2C.
Option bao gồm các thông số sau, cách nhau bởi dấu phẩy :
Master   : chip ở chế độ master

Slave    : chip ở chế độ slave


SCL = pin  : chỉ định chân SCL
SDA = pin  : chỉ định chân SDA
ADDRESS =x  : chỉ định địa chỉ chế độ slave
FAST   : chỉ định FAST I2C
SLOW   : chỉ định SLOW I2C
RESTART_WDT : restart WDT trong khi chờ I2C_READ( )
FORCE_HW  : sử dụng chúc năng phần cứng I2C ( nếu chip hỗ trợ )
NOFLOAT_HIGH : không cho phép tín hiệu ở float high ( ??? ) , tín hiệu được lái từ thấp lên  cao.
SMBUS  : bus dùng không phải bus I2C , nhưng là cái gì đó tương tự .
VD : 
 #use I2C ( master , sda=pin_B0 , scl = pin_B1 ) 
#use I2C (slave , sda= pin_C4 , scl= pin_C3 , address = 0xa00 , FORCE_HW ) 
#USE RS232 ( options ) 

+ Thiết lập giao tiếp RS232 cho chip ( có hiệu lực sau khi nạp chương trình cho chip , không
phải giao tiếp RS232 đang sử dụng để nạp chip ) .
Option bao gồm :
BAUD  = x  : thiết lập tốc độ baud rate : 19200 , 38400 , 9600 , . . .
PARITY = x  : x= N ,E hay O , với N : không dùng bit chẵn lẻ .
XMIT = pin  : set chân transmit ( chuyển data)
RCV = pin  : set chân receive ( nhận data )
+ Các thông số trên hay dùng nhất , các tham số khác sẽ bổ sung sau.
VD : 
#use rs232(baud=19200,parity=n,xmit=pin_C6,rcv=pin_C7) 

7 /Một số  chỉ thị tiền xử lý khác : 


#CASE : cho phép phân biệt chữ hoa / thường trong tên biến , dành cho những ai quen lập trình C .

#OPT n :với n=0 – 9 : chỉ định cấp độ tối ưu mã , không cần dùng thì mặc định là 9 ( very tối ưu ) .

#PRIORITY ints  :  với ints là danh sách các ngắt theo thứ tự ưu tiên thực hiện khi có nhiều ngắt
xảy
ra đồng thời , ngắt đứng đầu sẽ là ngắt ưu tiên nhất , dùng ngắt nào đưa ngắt đó vô . Chỉ cần dùng
nếu dùng hơn 1 ngắt . Xem cụ thể phần ngắt .
VD : #priority int_CCP1 , int_timer1 // ngắt CCP1 ưu tiên nhất 

MỘT SỐ VẤN ĐỀ QUAN TRỌNG KHÁC – xem chi tiết trong phần HELP : 
+ Biểu thức : xem HELP->Expressions , trong đó : biểu thị số trong C:
123 : số decimal  0x3 , 0xB1 : số hex  0b100110 : số binary
‘a’ : ký tự
“abcd” : chuỗi , ký tự null được thêm phía sau
_Các toán tử C : xem Operators
>= , < = , = =  ,  !=  ( không bằng )
&& : and  || : or  ! : not ( đảo của bit , không phải đảo của byte )

>>n  : dịch trái n bit   << n : dịch phải n bit


++ , - - ,  += , - = ,   .  .  .
Bài 3: Các Hàm Xử Lý Số, Xử Lý Bit, Delay trong CCS

I / CÁC HÀM XỬ LÝ SỐ : 
+ Bao gồm các hàm:

Sin() cos() tan()  Asin()  acos()  atan() 


Abs() : lấy trị tuyệt đối
Ceil( ) :làm tròn theo hướng tăng
Floor ( ) : làm tròn theo hướng giảm
Exp ( ) : tính e^x
Log ( ) :tính log
Log10 ( ) : log10
Pow ( ) : tính luỹ thừa
Sqrt ( ) :căn thức

+ Các hàm này chạy rất chậm  trên các VDK không có bộ nhân phần cứng ( PIC 14 ,12 ) vì chủ yếu tính toán
với số thực và trả về cũng số thực ( 32 bit ) và bằng phần mềm .VD:  hàm sin mất 3.5 ms ( thạch anh =
20Mhz )để cho KQ . Do đó nếu không đòi hỏi tốc độ thì dùng các hàm này cho đơn giản  , như là dùng hàm
sin thì khỏi phải lập bảng tra.
+ Xem chi tiết trên HELP CCS.

II / CÁC HÀM XỬ LÝ BIT VÀ CÁC PHÉP TOÁN : 


+ Bao gồm các hàmsau :
Shift_right()  shift_left() 
Rotate_right()  rotate_left() 
Bit_clear()  bit_set()  bit_test()  Swap() 
Make8()  make16()  make32() 

1 / Shift_right ( address , byte , value ) 


     Shift_left   ( address , byte , value ) 
+ Dịch phải (trái ) 1 bit vào 1 mảng hay 1 cấu trúc . Địa chỉ có thể là địa chỉ mảng hay địa chỉ trỏ tới
cấu trúc ( kiểu như  &data) . Bit 0 byte thấp nhất là LSB .

2 / Rotate_right ()  ,  rotate_left () 


+ Nói chung 4 hàm này ít sử dụng .

3 / Bit_clear ( var , bit )


       it_set ( var , bit )  
+ Bit_clear ( ) dùng xóa ( set = 0 ) bit được chỉ định bởi vị trí bit trong biến var .
+ Bit_set ( ) dùng set=1 bit được chỉ định bởi vị trí bit trong biến var .
+ var : biến 8 , 16 , 32 bit bất kỳ .
+ bit : vị trí clear ( set )  : từ  0-7 ( biến 8 bit) , 0-15 ( biến 16 bit ) , 0-31 (biến 32 bit ) .
+ Hàm không trả về trị .
VD : 
Int x; 
X=11 ;  //x=1011 
Bit_clear ( x ,1 ) ; // x= 1001b = 9 

4 / Bit_test ( var , bit ) : 


+ Dùng kiểm tra vị trí bit trong biến var .
+ Hàm trả về 0 hay 1 là giá  trị bit đó trong var .
+ var : biến 8, 16 ,32 bit .
+ bit : vị trí bit trong var .
+ Giả sử bạn có biến x 32 bit đếm từ 0 lên và muốn kiểm tra xem nó có lớn hơn 4096 không ( 4096= 2^12
=1000000000000b) :
If ( x >= 4096) . . .  // phép kiểm tra này mất ~5 us
Trong 1 vòng lặp , việc kiểm tra thường xuyên như vậy sẽ làm mất 1 thời gian đáng kể . Để tối ưu , chỉ cần
dùng  :  if ( bit_test ( x, 12 )  Ỉ chỉ mất ~ 0.4 us .  ( 20 Mhz thạch anh ) .
+ Kiểm tra đếm lên tới những giá trị đặc biệt ( 2^ i) thì dùng hàm này rất  tiện lợi.

5 / Swap ( var ) : 
+ var : biến 1 byte
+ Hàm này tráo vị trí 4 bit trên với 4 bit dưới của var , tương đương  var =( var>>4 ) | ( var << 4 )
+ Hàm không trả về trị .
VD : 
X= 5 ;  //x=00000101b 
Swap ( x) ; //x = 01010000b = 80 

6 / make8  ( var  , offset ) : 


+Hàm này trích 1 byte từ biến var .
+ var : biến 8,16,32  bit . offset là vị trí của byte cần trích ( 0,1,2,3) .
+ Hàm trả về giá trị byte cần trích .
VD : 
Int16 x = 1453 ;  // x=0x5AD  
Y = Make(x, 1) ;   //Y= 5 = 0x05  

7 / make16 ( varhigh , varlow ) : 


+Trả về giá trị 16 bit kết hợp từ 2 biến 8 bit varhigh và varlow . Byte cao là varhigh , thấp là varlow .

8 / make32 ( var1 , var2 , var3 , var4 ) :  


+ Trả về giá trị 32 bit kết hợp từ  các giá trị 8 bit hay 16 bit từ  var1 tới var4 . Trong đó var2 đến var4 có thể
có hoặc không . Giá trị var1 sẽ là MSB , kế tiếp là var2 , . . .Nếu tổng số bit kết hợp ít hơn 32 bit thì 0 được
thêm vào MSB cho đủ 32 bit .
VD:
Int a=0x01 , b=0x02 , c=0x03 , d=0x04 ;  // các giá trị hex 
Int32 e ; 
e = make32 (  a , b , c , d );   // e = 0x01020304 
e = make32 ( a , b , c , 5 ) ;   // e = 0x01020305 
e = make32 ( a, b, 8 );   // e = 0x00010208 
e = make32 ( a ,0x1237 ) ;    // e = 0x00011237 

III / CÁC HÀM DELAY :

+ Để sử dụng các hàm delay , cần có khai báo tiền xử lý ở đầu file , VD : sử dụng  OSC 20 Mhz , bạn cần khai
báo : #use delay ( clock = 20000000 )
+ Hàm delay không sử dụng bất kỳ timer nào . Chúng thực ra là 1 nhóm lệnh ASM để khi thực thi từ đầu tới
cuối thì xong khoảng thời gian mà bạn quy định . Tuỳ thời gian delay yêu cầu dài ngắn mà CCS sinh mã phù
hợp . có khi là vài lệnh NOP cho thời gian rất nhỏ . Hay 1 vòng lặp NOP . Hoặc gọi tới 1 hàm phức tạp trong
trường hợp delay dài . Các lệnh nói chung là vớ vẩn sao cho đủ thời gian quy định là được . Nếu trong trong
thời gian delay lại xảy ra ngắt thì thời gian thực thi ngắt không tính vào thời gian delay , xong ngắt nó quay
về chạy tiếp các dòng mã cho tới khi xong hàm delay . Do đó thời gian delay sẽ không đúng .
+ Có 3 hàm phục vụ :

1 / delay_cycles (count ) 
+ Count : hằng số từ 0 – 255 , là số chu kỳ lệnh .1 chu kỳ lệnh bằng 4 chu kỳ máy .
+ Hàm không trả về trị . Hàm dùng delay 1 số chu kỳ lệnh cho trước .
VD : delay_cycles ( 25 ) ; // với OSC = 20 Mhz , hàm này delay 5 us 

2 / delay_us ( time )  
+ Time : là biến số thì = 0 – 255 , time là 1 hằng số thì = 0 -65535 .
+ Hàm không trả về trị .
+ Hàm này cho phép delay khoảng thời gian dài hơn theo đơn vị us .
+ Quan sát trong C / asm list bạn sẽ thấy với time dài ngắn khác nhau , CSS sinh mã khác nhau .

3 / delay_ms (time ) 
+ Time = 0-255 nếu là biến số hay = 0-65535 nếu là hằng số .
+ Hàm không trả về trị .
+ Hàm này cho phép delay dài hơn nữa .
VD : 
Int a = 215; 
Delay_us ( a ) ; // delay 215 us 
Delay_us ( 4356 ) ; // delay 4356 us 
Delay_ms ( 2500 ) ; // delay 2 . 5 s 
Bài 4: Chuyển Đổi ADC - Các Hàm Vào/Ra trong CCS

I /XỬ LÝ ADC : 
+ PIC có nhiều chân phục vụ  xử lý ADC với nhiều cách thức khác nhau . Để dùng ADC , bạn
phải có khai báo #DEVICE cho biết dùng ADC mấy bit ( tuỳ chip hỗ trợ , thường là 8 hay 10 bit
hoặc hơn) . Bạn cần lưu ý là: 1 VDK hỗ trợ ADC 10 bit thì giá trị vào luôn là 10 bit , nhưng chia
cho 4 thì còn 8 bit . Do đó 1 biến trở chiết áp cấp cho ngõ vào ADC mà bạn chọn chế độ 10 bit thì
sẽ rất nhạy so với chế độ 8 bit ( vì 2 bit cuối có thay đổi cũng không ảnh hưởng giá trị 8 bit cao và
do đó kết quả 8 bit ADC ít thay đổi ) , nếu chương trình có chế độ kiểm tra ADC để cập nhật tính
toán , hay dùng ngắt ADC ,  thì nó sẽ chạy hoài thôi . Dùng ADC 8 bit sẽ hạn chế điều này . Do đó
mà CCS cung cấp chọn lựa ADC 8 hay 10 bit tùy mục đích sử dụng .

Cấu hình bộ ADC : 


+ Thông dụng nhất khi dùng ADC là sử dụng 1 biến trở , điều chỉnh bởi 1 nút vặn , qua đó thu được
1 điện áp nhỏ hơn điện áp tham chiếu ( Vref – áp max ) , đưa vào chân biến đổi ADC , kết quả cho
1 giá trị số ADC 8 bit ( 0-255 ) hay ADC 10 bit (0-1023 ) . Thường thì áp Vref lấy bằng Vdd
( 5V ).
+ Trên các PIC có ngõ AVdd và AVss ( PIC 18 ) , thường thì bạn luôn nối AVdd tới Vdd , AVss
tới Vss để đảm bảo họat động cho lập trình qua ICD 2 .

Các hàm sau phục vụ ADC : 


1 /   Setup_ADC ( mode  ) : 
+ Không trả về trị . Dùng xác định cách thức hoạt động bộ biến đổi ADC . Tham số mode tuỳ
thuộc file thiết bị *.h có tên tương ứng tên chip bạn đang dùng , nằm trong thư mục DEVICES của
CCS . Muốn biết có bao nhiêu tham số có thể dùng cho chip đó , bạn mở file tương ứng đọc , tìm
tới chỗ các định nghĩa cho chức năng ADC dùng cho chip đó tương ứng với hàm này . Sau đây là
các  giá trị mode của 16F877 , ( 1 số  khác có thể không có hoặc có thêm như  16F877A có thêm 1
số thứ là ADC_CLOCK_DIV_2/4/8/16/32/64 . . .) :

ADC_OFF  : tắt hoạt động ADC ( tiết kiệm điện , dành chân  cho hoạt động khác ) .
ADC_CLOCK_INTERNAL  : thời gian lấy mẫu bằng xung clock IC  ( mất 2-6 us ) thường là
chung cho các chip .
ADC_CLOCK_DIV_2  : thời gian lấy mẫu bằng xung clock / 2  ( mất 0.4 us trên thạch anh
20MHz )
ADC_CLOCK_DIV_8  : thời gian lấy mẫu bằng xung clock / 8  ( 1.6 us )
ADC_CLOCK_DIV_32  : thời gian lấy mẫu bằng xung clock / 32 ( 6.4 us )

2 / Setup_ADC_ports ( value ) 
+ Xác định chân lấy tín hiệu analog và điện thế chuẩn sử dụng . Tùy thuộc bố trí chân trên chip ,
số chân  và chân nào dùng cho ADC  và số chức  năng ADC mỗi chip mà value có thể có những giá
trị khác nhau. Xem file tương ứng trong thư mục DEVICES để biết số chức năng tương ứng chip
đó . Để tương thích chương trình viết cho phiên bản cũ , 1 số tham số  có 2 tên khác nhau ( nhưng
cùng chức năng do định nghĩa cùng địa chỉ ) , ở đây dùng  phiên bản 3.227 .Lưu ý : Vref : áp
chuẩn ,  Vdd : áp nguồn .

Sau đây là các giá trị cho value ( chỉ dùng 1 trong các giá trị )  của 16F877 :
ALL_ANALOGS  .........: dùng tất cả chân sau làm analog  : A0  A1  A2  A3  A5   E0  E1  E2
(Vref=Vdd)
NO_ANALOG  ..............................................................: không dùng analog , các chân đó sẽ là chân
I /O .
AN0_AN1_AN2_AN4_AN5_AN6_AN7_VSS_VREF .:  A0 A1 A2 A5 E0 E1 E2 VRefh=A3
AN0_AN1_AN2_AN3_AN4     .....................................:  A0 A1 A2 A3 A5
( tên thì giống nhau cho tất cả thiết bị nhưng 16F877 chỉ có portA có 5 chân nên A0 , A1 , A2 , A5
được dùng , A6 , A7 không có )
AN0_AN1_AN3      .......................................................:  A0 A1 A3  , Vref = Vdd
AN0_AN1_VSS_VREF    ..............................................:  A0 A1 VRefh = A3
AN0_AN1_AN4_AN5_AN6_AN7_VREF_VREF  ......:  A0 A1 A5 E0 E1 E2
VRefh=A3 , VRefl=A2 .
AN0_AN1_AN2_AN3_AN4_AN5    ............................:  A0 A1 A2 A3 A5 E0
AN0_AN1_AN2_AN4_AN5_VSS_VREF  .................. :  A0 A1 A2 A5 E0 VRefh=A3
AN0_AN1_AN4_AN5_VREF_VREF   .........................:  A0 A1 A5 E0 VRefh=A3 VRefl=A2
AN0_AN1_AN4_VREF_VREF   .................................. :  A0 A1 A5 VRefh=A3 VRefl=A2
AN0_AN1_VREF_VREF     ...........................................:  A0 A1 VRefh=A3 VRefl=A2
AN0   ..............................................................................:  A0
AN0_VREF_VREF    ..................................................... :  A0 VRefh=A3 VRefl=A2

VD :  setup_adc_ports (AN0_AN1_AN3 ) ;  // A0 , A1 , A3 nhận  analog , áp nguồn +5V cấp cho


IC sẽ là điện áp chuẩn .

3 / Set_ADC_channel ( channel ) : 
+ Chọn chân để đọc vào giá trị analog bằng lệnh Read_ADC ( ) . Giá trị channel tuỳ số chân
chức năng  ADC mỗi chip .Với 16F877 , channel có giá trị từ  0 -7 :
   0-chân A0, 1-chân A1, 2-chân A2,  3-chân A3, 4-chân A5, 5-chân E0, 6-chân E1, 7-chân E2  
+Hàm không trả về trị . Nên delay 10 us sau hàm này rồi mới dùng hàm read_ADC ( ) để bảo
đảm kết quả đúng . Hàm chỉ hoạt động với A /D phần cứng trên chip.

4 / Read_ADC ( mode ) : 
+ Dùng đọc  giá trị ADC từ thanh ghi (/ cặp thanh ghi ) chứa kết quả biến đổi ADC . Lưu ý hàm
này sẽ hỏi vòng cờ cho tới khi cờ này báo đã hoàn thành biến đổi ADC ( sẽ mất vài us ) thì xong
hàm .
+ Nếu giá trị ADC là 8 bit như khai báo trong chỉ thị #DEVICE , giá trị trả về của hàm là 8
bit , ngược lại là 16 bit nếu khai báo #DEVICE sử dụng ADC 10 bit trở lên .
+ Khi dùng hàm này , nó sẽ lấy ADC từ chân bạn chọn trong hàm Set_ADC_channel( ) trước
đó . Nghĩa là mỗi lần chỉ đọc 1 kênh  Muốn đổi sang đọc chân nào , dùng hàm set_ADC_channel( )
lấy chân đó . Nếu không có đổi chân , dùng read_ADC( ) bao nhiêu lần cũng được .
+ mode có thể có hoặc không , gồm có :
ADC_START_AND_READ  :  giá trị mặc  định
ADC_START_ONLY  :  bắt đầu chuyển đổi và trả về
ADC_READ_ONLY   :  đọc kết quả chuyển đổi lần cuối

#DEVCE  8 bit  10 bit  11 bit  16 bit 


ADC=8  0-255  0-255  00-255  00-255 
ADC=10  x  0-1023  x  x 
ADC=11  x  x  0-2047  x 
ADC=16  0-65280  0-65472  0-65504  0-65535 
+ 16F877 chỉ hỗ trợ ADC 8 và 10 bit . 
VD : 
setup_adc(  ADC_CLOCK_INTERNAL  ); 
setup_adc_ports( ALL_ANALOG ); 
set_adc_channel(1); 
while ( input(PIN_B0) )  

   delay_ms( 5000 ); 
   value = read_adc(); 
   printf("A/D value = %2x\n\r", value); 

read_adc(ADC_START_ONLY); 
sleep(); 
value=read_adc(ADC_READ_ONLY); 

+ Lưu ý : trên PIC 18 , cấu trúc ADC tương đối phức tạp , đa năng hơn  như là cho phép lấy 2
mẫu cùng lúc , . . . cũng sử dụng với các hàm trên , có nhiều thông số trong file *.h , sẽ đề cập sau . 

5 / _ Ví dụ : 
+ Chương trình sau lấy ADC 8 bit , đọc và xuất ra dãy led ở port B , và xuất ra màn hình máy tính . 
+ Kết nối chân trên 16F877 : RA0 là chân lấy Analog vào , áp chuẩn là nguồn +5V , mass=0 V
#include <16F877.h > 
#use delay( clock=20000000 ) 
#device *= 16  ADC = 8    // sử dụng ADC 8 bit , giá trị ADC vào từ 0-255 
#use rs232(baud=19200,parity=n,xmit=pin_C6,rcv=pin_C7) 
Int8 adc ;
Main( ) 

Setup_ADC ( ADC_internal ) ; 
Setup_ADC_ports (AN0); 
Set_ADC_channel ( 0 ) ; 
Delay_us (10 );  // delay 10 us 
While (true ) 
{ adc = read_adc ( ) ; 
 Output_B ( adc ) ;  // xuat ra port B gia tri bien adc 
 Printf( “ gia tri adc la : %u “ , adc ) ; // in ra man hinh  


// giá trị biến adc từ  0-255 , dùng chương trình Serial port Monitor trong mục Tools của CCS để 
giám sát giá trị . Nhớ thiết lập tốc độ là 19200 như khai báo trên . 

II / CÁC HÀM VÀO RA TRONG C : 


+ Bao gồm các hàm sau : 
Output_low()  Output_high() 
Output_float()  Output_bit() 
Input()   Ouput_X() 
Input_X()  port_b_pullups() 
Set_tris_X() 

1 / Output_low ( pin ) , Output_high (pin ) : 


+ Dùng thiết lập mức 0 ( low, 0V ) hay mứ c 1 ( high , 5V ) cho chân IC , pin chỉ vị trí chân . 
+ Hàm này sẽ đặt pin làm ngõ ra , xem mã asm để biết cụ thể . 
+ Hàm này dài 2-4 chu kỳ máy . Cũng có thể xuất xung dùng set_tris_X() và #use fast_io. 
VD : chương trình sau xuất xung vuông chu kỳ 500ms , duty =50% ra chân B0 ,nối B0 với 1 led
sẽ làm nhấp nháy led . 
#include <16F877.h> 
#use delay( clock=20000000) 
Main() 
{ while(1) 
 { output_high(pin_B0) ; 
  Delay_ms(250) ;  // delay 250ms 
  Output_low (pin_B0); 
Delay_ms (250 ); 
 } 

2 / Output_bit ( pin , value ) : 


+ pin : tên chân value : giá trị 0 hay 1 

+ Hàm này cũng xuất giá trị 0 / 1 trên pin , tương tự 2 hàm trên . Thường dùng nó khi giá trị ra
tuỳ thuộc giá trị biến 1 bit nào đó , hay muốn xuất  đảo của giá  trị ngõ ra trước đó .
VD : Chương trình xuất xung vuông chu kỳ 500ms ,duty =50%
 int1 x;  // Khai báo x, mặc định = 0
Main()  //Trong hàm main :
{ while (1 ) 
{ output_bit( pin_B0 , !x ) ; 
  Delay_ms(250 ); 
 } 

3 / Output_float ( pin ) : 
+ Hàm này set pin như ngõ vào , cho phép pin ở mức cao như 1 cực thu hở.

4 / Input ( pin ) : 
+ Hàm này trả về giá trị 0 hay 1 là trạng  thái của chân IC . Giá trị là 1 bit
5 / Output_X ( value ) : 
+ X là tên port có trên chip . Value là giá trị 1 byte .
+ Hàm này xuất giá trị 1 byte ra port . Tất cả chân của port đó đếu là ngõ ra .
VD : 
Output_B ( 212 ) ;  // xuất giá trị 11010100 ra port B

6 / Input_X ( ) : 
+ X : là tên port ( a, b ,c ,d e ) .
+ Hàm này trả về giá trị 8 bit là giá trị đang hiện hữu của port đó .VD : m=input_E(); 

7 / Port_B_pullups ( value ) : 
+ Hàm này thiết lập ngõ vào port B pullup ( điện trở kéo lên) . Value =1 sẽ kích hoạt tính năng
này và value =0 sẽ ngừng .
+ Chỉ các chip có port B có tính năng này mới dùng hàm này .

8 / Set_tris_X ( value ) : 
+ Hàm này định nghĩa chân IO cho 1 port là ngõ vào hay ngõ ra. Chỉ được dùng với #use fast_IO .
Sử dụng #byte để tạo biến chỉ đến port và thao tác trên biến này chính là thao tác trên port .
+ Value là  giá trị 8 bit . Mỗi bit đại diện 1 chân và bit=0 sẽ set chân đó là ngõ vào, bit= 1 set
chân đó là ngõ ra .
VD : chương trình sau cho phép thao tác trên portB 1 cách dễ dàng:
#include < 16F877.h > 
#use delay(clock=20000000) 
#use Fast_IO( B ) 
#byte portB = 0x6  // 16F877 có port b ở địa chỉ 6h
#bit B0 = portB. 0  // biến B0 chỉ đến chân B0
#bit B1=portB.1  // biến B1 chỉ đến chân B1

#bit B2=portB.2  // biến B2 chỉ đến chân B2


#bit B3=portB.3  // biến B3 chỉ đến chân B3
#bit B4=portB.4  // biến B4 chỉ đến chân B4
#bit B5=portB.5  // biến B5 chỉ đến chân B5
#bit B6=portB.6  // biến B6 chỉ đến chân B6
#bit B7=portB.7  // biến B7 chỉ đến chân B7
Main() 
{  set_tris_B ( 126 ) ;  //portB=01111110 b 
// B0 là ngõ vào , thường làm ngắt ngoài 
//B1 . . . B6 là ngõ ra , Vd làm 6 ngõ ra điều chế PWM  
    //B7 là ngõ vào , Vd là nhận tín hiệu cho phép chẳng hạn 
 if ( B7 )  //nếu ngõ vào chân B7 là 1 thì xuất 3 cặp xung đối nghịch 
  {  B1 = 1 ; 
  B2 = 0 ; 
  B3 = 1 ; 
  B4 = 0 ; 
  B5 = 1 ; 
  B6 = 0 ; 
  } 
 Else  B1=B2=B3=B4=B5=B6= 0; 

+ Lưu ý : 
+ Set_tris_B (0 ) :  port B =00000000 : tất cả chân portB là ngõ ra
+ set_tris_B ( 1 ) :  portB = 00000001 : chỉ B0 là ngõ vào , còn lại là ngõ ra
+ set_tris_B ( 255 ) : portB=11111111: tất cả chân portB là ngõ vào. Bạn nên dùng giá trị ở dạng
nhị phân cho dễ . VD : set_tris_B ( 00110001b ) ; 

+ Đến đây là bạn có thể viết nhiều chương trình thú vị rồi đó. Vd  như là dùng ADC để điều
chỉnh tốc độ nhấp nháy của dãy đèn  led , truyền giá trị 8 bit từ  chip này sang chip khác  , . . .
+ VD: Chương trình sau dùng ADC qua chân A0  để điều chỉnh tốc độ nhấp nháy dãy đèn led nối
vào portB, có thể dùng fast_io hay hàm output_B () để xuất giá trị đều được . chương trình dùng
hàm. Nếu ngõ vào chân C0 =0 thì tiếp tục nhận ADC và xuất ra portB, C0=1 thì không xuất
#include <16F877.h> 
#device *=16 ADC= 8 
#use delay( clock =20000000) 
Int8 ADC_delay  ; 

Void hieu_chinh (  ) 


{  ADC_delay = read_adc ( 0 ) ; 
 Output_B ( 0) ;  //portB=00000000 
 Delay_ms ( ADC_delay ); 
 Output_B ( 255 ) ;  // portB= 11111111 
 Delay_ms ( ADC_delay ); 
}

Main() 

setup_adc_ports(AN0_AN1_AN3);  // A0 ,  A1 và  A3 là chân analog , ta chỉ cần dùng A0 lấy
tín hiệu 
setup_adc(adc_clock_internal); 
set_adc_channel ( 0 );  // chọn đọc ADC từ chân A0
while(1) 
{  hieu_chinh ( ) ; 
 If ( input ( pin_C0 ) 
  {  output_B (0 ); 
  Break ;  // thoát khỏi vòng lặp while nhỏ
  } 

}
Bài 5: Truyền Thông Nối Tiếp RS232 - Xử Lý Chuỗi Trong
CCS

+ Phần này sẽ giúp bạn viết chương trình có sử dụng giao tiếp với máy tính (PC) . Điều này rất cần
thiết khi bạn muốn VĐK khi hoạt động có thể truyền dữ liệu cho PC xử lý , hoặc nhận giá trị từ PC
để xử lý và điều khiển ( dùng PC điều khiển động cơ , nhiệt độ , hay biến PC thành dụng cụ đo các
đại lượng điện, Oscilocope , . . .) .

+ Viết chương trình lập trình cho VĐK để giao tiếp máy tính là công việc rất phức tạp khi viết
bằng ASM , rất khó hiểu đối với những người mới bắt đầu lập trình . Đặc biệt là khi viết cho những
con VĐK không hỗ trợ từ phần cứng ( 8951 thì phải (?) ) . Thật may là phần lớn PIC hiện nay đều
hỗ trợ phần này nên việc lập trình có dễ dàng hơn . Nhưng nếu chương trình của bạn yêu cầu truyền
hay nhận nhiều loại dữ liệu ( số  8 , 16 ,32 bit , dương , âm , chuỗi , . . .) thì việc viết chương trình
xử lý và phân loại chúng  là điều “ kinh dị “ .

+ Nếu bạn đã lập trình ASM cho vấn đề này rồi thì bạn sẽ thấy sao dễ dàng quá vậy khi giải
quyết vấn đề này với C  khi dùng CCS . Rất đơn giản ! CCS cung cấp rất nhiều hàm phục vụ cho
giao tiếp qua cổng COM  và vô số hàm xử lý chuỗi . Chương này sẽ giải quyết điều đó . 
+ Một yếu tố quan trọng là khi nào thì VĐK biết PC truyền data => có thể lập trình bắt tay bằng
phần mềm hay đơn giản là dùng ngắt . Các ví dụ về ngắt , xem phần ngắt .

I / TRUYỀN THÔNG VỚI PC QUA CỔNG COM : 


+ Để sử dụng giao thức này , phải có 2 khai báo như  ví dụ  sau : 
#use delay (clock = 40000000 )  //  nếu VDK đang dùng OSC 40Mhz 
#use rs232 (baud=19200 , parity=n , xmit=pin_C6 , rcv=pin_C7 ) 
 // baud= 19200 , không chẵn lẻ , chân truyền C6 , chân nhận C7 

+Các hàm liên quan : 


Printf ( ) 
Getc ( )  putc ( ) 
Getch ( )  putchar ( ) 
Getchar ( )  fputc ( ) 
Fgetc ( )  puts ( ) 
Gets ( )  fputs ( ) 
Fgets ( )   
Kbhit ( ) 
Assert ( )   => mới trên CCS 3.222 
Perror ( )   => mới trên CCS 3.222 
Set_uart_speed ( ) 
Setup_uart ( ) 

+ Tất cả các hàm trên đòi hỏi phải khai báo chỉ thị tiền xử lý #use RS232 ( . . . . .) . Chi tiết chỉ
thị này xem phần Chỉ thị tiền xử lý . 
+ Hàm perror ( ) đòi hỏi thêm #include<errno.h > . Hàm assert() đòi hỏi thêm #include<assert.h> . 

1 / printf ( string ) 
     Printf ( cstring , values . . . ) 
+ Dùng xuất chuỗi theo chuẩn RS232 ra PC . 
+ string là 1 chuỗi hằng hay 1 mảng ký tự  ( kết thúc bởi ký tự null ) . 
+ value là danh sách các biến , cách nhau bởi dấu phẩy . 
+ Bạn phải khai báo dạng format của value theo kiểu %wt .Trong đó w có thể có hoặc không,
có giá trị từ  1-9 chỉ rõ có bao nhiêu ký tự được xuất ra ( mặc định  không có thì có bao nhiêu ra bấy
nhiêu ), hoặc 01-09 sẽ chèn thêm 0 cho đủ ký tự  hoặc 1.1-1.9 cho trường hợp số thực . còn t là
kiểu giá trị . 
+ t có thể là : 
C :  1 ký tự 
S : chuỗi hoặc ký tự  
U : số 8 bit không dấu 
x : số 8 bit kiểu hex ( ký tự viết thường ,VD : 1ef ) 
X : số 8 bit kiểu hex ( ký tự viết hoa ,VD : 1EF ) 
D : số 8 bit có dấu 
e : số thực có luỹ thừa VD : e12
f : số thực 
Lx : số hex 16 /32 bit ( ký tự viết thường ) 
LX : hex 16 /32 bit ( ký tự viết hoa ) 
Lu : số thập phân không dấu 
Ld : số thập phân có dấu  
% : ký hiệu % 
VD : 
Specifier  Value=0x12  Value=0xfe 
%03u  018  254 
%u  18  254 
%2u  18  * 
%5  18  254 
%d  18  -2 
%x  12  Fe 
%X  12  FE 
%4X  0012  00FE 
 * Result is undefined - Assume garbage. 
VD : 
Int k =6 ; 
Printf ( “ hello “ ); 
Printf ( “ %u “ , k ); 

2 / KBHIT ( ) : 
+ Thường thì chúng ta dùng RC6 và RC7 cho RX và TX trong giao tiếp cổng COM , VDK PIC
trang bị phần cứng phục vụ việc này với thanh ghi gởi và nhận và các bit bào hiệu tương ứng . Do
đó khi dùng RS232 hỗ trợ từ phần cứng thì KHBIT ( ) trả về TRUE nếu 1 ký tự đã được nhận
( trong bộ đệm phần cứng ) và sẵn sàng cho việc đọc , và trả về 0 nếu chưa sẵn sàng . 
+ Hàm này có thể dùng hỏi vòng xem khi nào có data nhận từ RS232 để đọc .

Bài 6: Giao Tiếp SPI


I /  GIAO TIẾP SPI

+ Đây là giao tiếp dễ dùng nhất , đơn giản nhất , tốc độ cao nhất trong nhóm . hoạt động theo cơ
chế hand-shaking, bắt tay . Giả sử có 2 VDK , thì 1 là master , 1 là slave . Khi master truyền 1 byte
cho slave , nó phát 8 xung clock qua đường clock - SCK nối tới slave , đồng thời truyền 8 bit data
từ chân SDO tới chân SDI của slave. Không kiểm tra chẵn lẻ , lỗi . Do đó Ví dụ  nếu đang truyền
được 3 bit mà master reset hay hở dây clock thì data bị mất , slave sẽ không nhận đủ 8 bit và do đó
nếu tiếp tục nhận nó sẽ lấy 5 bit ở byte kế tiếp đưa vào thanh ghi nhận để đủ 8 bit ( và để  kích
ngắt ) . Từ đó trở đi là mọi giá trị nhận là sai bét trừ phi chấm dứt và sau đó thiết lập lại giao tiếp
này ( ở cả hai ) .

+ Giao tiếp này cần  ít nhất 2 dây trở lên . Nếu 1 VDK chỉ  cần gởi data thì chỉ cần dây clock và
SDO .VDK nhận  sẽ dùng SDI và dây clock . Dây clock là nối chung .
+ Nếu có gởi và nhận ở cả 2 VDK thì : dây clock chung , master có SDO nối tới SDI của slave ,
SDO của slave nối tới SDI của master .
+ Nếu master cần truyền data cho nhiều slave trở lên  thì  SDO master nối tới các SDI của slave .
+ Chân  SS là slave select .
+ SPI hoạt động từ phần cứng , vì nó có sẵn thanh ghi gởi và nhận , nhận đủ giá trị thì có cờ
ngắt phục vụ .
+ Danh sách các hàm :
1 / Setup_spi (mode ) 
     Setup_spi2 (mode ) 
+ Dùng thiết lập giao tiếp SPI . Hàm thứ 2 dùng với VDK có 2 bộ SPI .
+ Tham số mode :là các hằng số sau , có thể OR giữa các nhóm bởi dấu |
  I SPI_MASTER , SPI_SLAVE , SPI_SS_DISABLED 
  II SPI_L_TO_H , SPI_H_TO_L 
  III SPI_CLK_DIV_4 , SPI_CLK_DIV_16 , SPI_CLK_DIV_64 , SPI_CLK_T2 
+ Nhóm I xác định VDK là master hay slave ,slave select
+ Nhóm II xác định clock cạnh lên hay xuống .
+ Nhóm III xác định tần số  xung clock , SPI_CLK_DIV_4 ngĩa là tần số = FOSC / 4 , tương ứng 1
chu kỳ lệnh / xung .
+ Hàm không trả về trị .
+ Ngoài ra ,tuỳ VDK mà có thêm 1 số tham số khác , xem file * .h .

2 / Spi_read ( data ) 
     Spi_read2 ( data ) 
+ data có thể có thêm và là số 8 bit . Hàm thứ 2 cho bộ SPI thứ 2 .
+ Hàm trả về giá  trị 8 bit: value = spi_read ( ) 
+ Hàm trả về giá trị đọc bởi SPI . Nếu value phù hợp SPI_read ( ) thì data sẽ được phát xung
ngoài và data nhận được sẽ được trả về . Nếu không có data sẵn sàng , spi_read ( ) sẽ đợi data .
+ Hàm chỉ dùng cho SPI hardware ( SPI phần cứng  ) .

3 / Spi_write ( value ) 
     Spi_write2 ( value ) 

+ Hàm không trả về trị . value là giá trị 8 bit .


+ Hàm này gửi value ( 1 byte ) tới SPI , đồng thời tạo 8 xung clock .
+ Hàm chỉ dùng cho SPI hardware ( SPI phần cứng  ) .

4 / Spi_data_is_in ( ) 
     Spi_data_is_in2 ( ) 
+ Hàm trả về TRUE ( 1 ) nếu data nhận được đầy đủ ( 8 bit ) từ SPI , trả về false nếu chưa nhận đủ .
+ Hàm này dùng kiểm tra xem giá  trị nhận về SPI đã đủ 1 byte chưa để dùng hàm spi_read ( )
đọc data vào biến .

Giao tiếp SPI song công giữa 2 PIC: PIC Master ở trên truyền dữ liệu từ PortB (công tắc trên) qua
PIC Slave ở dưới để hiển thị ra PortD (LED dưới) , PIC Slave cũng lấy dữ liệu từ PortB (công tắc
dưới) của mình, truyền qua PIC Master để hiển thị ra PortD (LED trên).

Chương trình chạy mô phỏng trên ISIS - Proteus:

mô phỏng SPI trên Proteus

Mã nguồn:
Code Master:

#include "16f877a.h"
#include "def_877a.h"
#device *=16 ADC=8
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,NOLVP,
NOCPD, NOWRT
#use delay(clock=20000000)
#use rs232(baud=38400,parity=N,xmit=PIN_C6,rcv=PIN_C7)
#use fast_io(B)
#use fast_io(D)
#use fast_io(A)
#define REG_Write 0x80

#INT_SSP
void spi()
{
      PORTD=spi_read(PORTB);
      delay_ms(10);
}
void main()
{
   port_b_pullups(TRUE);
   setup_spi(spi_master|spi_l_to_h|spi_clk_div_16);
   enable_interrupts(INT_SSP);
   enable_interrupts(GLOBAL);
   SET_TRIS_B(0xff);
   SET_TRIS_D(0x00);
   SET_TRIS_A(0x00);
   while(1)
   {
   delay_ms(100);
      output_low(PIN_A5);//Chân C2 dùng Select chip.
      delay_ms(10);//Tao tre de Slave chuan bi.
      spi_write(PORTB);
      output_high(PIN_A5);
   }
}

Code Slave:
#include "16f877a.h"
#include "def_877a.h"
#device *=16 ADC=8
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,NOLVP,
NOCPD, NOWRT
#use delay(clock=20000000)
#use rs232(baud=38400,parity=N,xmit=PIN_C6,rcv=PIN_C7)
#use fast_io(B)
#use fast_io(D)

#INT_SSP
void spi()
{
      PORTD=spi_read(PORTB);//Vua nhan vua truyen.
      delay_ms(10);
}

void main()
{
   port_b_pullups(TRUE);
   setup_spi(spi_slave|spi_l_to_h|spi_clk_div_16);
   enable_interrupts(INT_SSP);
   enable_interrupts(GLOBAL);
   TRISB=0xff;
   TRISD=0x00;
   while(1)
   {
   }
}

Bài 7: Các Ngắt Trong PIC


I /  CƠ CHẾ HOẠT ĐỘNG CỦA NGẮT : 
1 / Ngắt 1 cấp : 
+ Trên PIC 14 , 12 ,10 ,tất cả các ngắt chỉ có 1 cấp ưu tiên . Nghĩa là ngắt nào đang được phục vụ
thì không thể bị ngắt bởi 1 ngắt khác xảy ra . Cơ chế sinh mã cho ngắt của CCS như sau : nhảy đến
địa chỉ ngắt , thường là 004h , sao lưu thanh ghi W,  STATUS , PCLATCH , FSR,  và nhiều thứ vớ
vẫn khác,   sau đó nó mới hỏi vòng xem cờ ngắt nào xảy ra  thì nhảy đến hàm phục vụ ngắt đó .
thực hiện xong thì phục hồi tất cả thanh ghi trên , rồi mới “RETFIE” – thoát ngắt . Số chu kỳ thực
thi từ chỗ ngắt đến khi nhảy vào hàm ngắt cỡ 20 chu kỳ lệnh !, nhảy ra cũng cỡ đó .
+ Điều gì xảy ra nếu chương trình dùng nhiều ngắt và khi có ngắt thì có 2 ngắt trở lên xảy ra
đồng thời ? Nghĩa là : 2 ngắt xảy ra cùng lúc , hay khi ngắt A kích hoạt và CCS đang lưu các thanh
ghi ( chưa tới hỏi vòng cờ ngắt ) thì ngắt B xảy ra , dĩ nhiên ngắt B không thể kích vector ngắt nhảy
tới 004h vì bit cho phép ngắt toàn cục ( GIE ) bị khóa tự động khi có ngắt , chỉ có cờ ngắt B bật mà
thôi. Sau khi lưu các thanh ghi , chương trình  kiểm tra cờ ngắt , rõ ràng là nếu bit nào được kiểm
tra trước thì  phục vụ trước , dù nó xảy ra sau . Để tránh phục vụ không đúng chỗ , bạn dùng
#priority để xác định ưu tiên ngắt ( xem phần chỉ thị tiền xử lý ) . Ngắt ưu  tiên nhất sẽ luôn được
hỏi vòng trước .Sau khi xác định cờ ngắt cần phục vụ , nó sẽ thực thi hàm ngắt tương ứng .Xong thì
xoá cờ ngắt đó và thoát ngắt . Phục vụ ngắt nào xong thì chỉ xoá cờ ngắt đó .Nếu A ưu tiên hơn B
thì sau khi làm A , chương trình  xoá cờ ngắt A , nhưng cờ B không xoá ( vì đâu có phục vụ ) , nên
khi thoát ra ngắt A , nó sẽ lại ngắt tiếp ( vì cờ B đã bật ), lại hỏi vòng cờ ngắt từ đầu : nếu cờ A
chưa bật thì xét B, lúc này B bật nên phục vụ B , xong thì xoá cờ B và thoát ngắt .
+ Môt chương trình dùng nhiều ngắt phải lưu ý điều này , tránh trường hợp : ngắt xảy ra liên tục
(tràn
ngắt ) , 1 ngắt bị đáp ứng trễ  , ngắt không đúng , . . .

2 / Ngắt 2 cấp : 
+ Chỉ có trên PIC 18 ( và dsPIC ) . Có 2 khái niệm : ngắt ưu tiên thấp (low priority) và ngắt ưu
tiên cao ( high priority ) . 2 vector thực thi ngắt tương ứng thường là 0008h (high) và 0018h
( low ) . Một  ngắt thấp đang được phục vụ sẽ bị ngưng và phục vụ ngắt cao ở 0008h nếu ngắt cao
xảy ra . Ngược lại , ngắt cao đang xảy ra thì không bao giờ bị ngắt bởi ngắt thấp .
+ Nếu viết hàm ngắt bình thường , không đòi hỏi ưu tiên gì thì CCS sinh mã để tất cả hàm ngắt
đều là ngắt ưu tiên cao . Quy trình thực hiện ngắt sẽ như ngắt 1 cấp trên . #priority vẫn được dùng .
Số chu kỳ thực thi từ 0008h đến khi nhảy vào thực thi hàm ngắt khoảng 30 chu kỳ , xong hàm ngắt
tới khi kết thúc ngắt cũng mất khoảng 30 chu kỳ lệnh .
+ Để sử dụng ngắt 2 cấp ,  khai báo #device phải có high_ints=true . Và hàm ngắt nào muốn ưu
tiên cao thì thêm FAST hay HIGH theo sau chỉ thị tiền xử lý hàm đó .
Lưu ý : khi dùng FAST thì không nên dùng HIGH cho các ngắt khác thì mới có ý nghĩa và chỉ
có duy nhất 1 ngắt được ưu tiên FAST , nhưng  có thể có nhiều ngắt đặt ở mức HIGH .
VD :
#int_timer1  FAST 
 Void xu_ly ( ) 
  { . . . 
 } 

 #int_timer2  HIGH 
 Void dinh_thi () 
  { . . . 
 } 
 #int_timer5 HIGH  
 Void vong_lap() 
  { . . . 
 } 

+ Cơ chế sinh mã như sau : có ngắt thấp thì nhảy tới 0018h , sao lưu W, STATUS , FSR0/1/2 ,. . .
rồi mới hỏi vòng cờ ngắt thấp . chạy xong hàm ngắt  thì phục hồi tất cả và “RETFIE 0 “ .
+ Riêng ngắt cao đánh dấu FAST không sinh mã sao lưu gì cả mà nhảy thẳng vào hàm ngắt
chạy luôn . PIC 18 và dsPIC có cơ chế lưu siêu tốc là FAST STACK REGISTER ( xem
datasheet ) . Khi xảy ra ngắt bất kỳ,  W, S , BSR tự động lưu vào thanh ghi trên , PC counter lưu
vào stack . xong ngắt thì pop ra . Vấn đề ở chỗ : khi ngắt thấp xảy ra , FAST STACK REGISTER
tự động lưu W ,S , BSR , PC -> stack . Trong khi thực hiện hàm phục vụ ngắt thì trường hợp W, S ,
BSR thay đổi là có thể ( vì vậy mới sao lưu chứ ) . nhưng nếu xảy ra ngắt cao vào thời điểm đó ?
FAST STACK REGISTER sẽ bị ghi đè => mất data . Do đó , cơ chế sinh mã của CCS cần phải
luôn đúng , nghĩa là : luôn tự sao lưu riêng W ,S , BSR, và các thanh ghi FSR nữa , khi thực  thi
ngắt thấp . Còn ngắt cao FAST khi chạy xong sẽ “RETFIE 1 “ – tự động phục hồi W, S , BSR từ
FAST STACK REGISTER . Có 2 trường hợp : 1 là chỉ có ngắt cao , thì không có vấn đề gì  . 2 là
ngắt cao ngắt 1 ngắt thấp đang chạy . Phân tích sẽ thấy rằng cho dù bị ngắt trong khi đang sao
lưu ,hay chưa kịp sao lưu , hay đã sao lưu vào các biến riêng rồi , cuối cùng chương trình cũng quay
ra đúng địa chỉ ban đầu với các thanh ghi W, S , BSR như cũ .
+ Tuân thủ nguyên tắc ngắt cao thực thi tức thời nên CCS chỉ cho 1 ngắt cao FAST duy nhất bất
kỳ hoạt động ,  nên không sinh mã hỏi vòng , sao lưu thêm gì cả .
+ Nếu bạn muốn có nhiều ngắt ưu tiên cao , thì dùng HIGH , chương trình sao lưu bình thường
như với ngắt thấp , nhưng khi đó ngắt đánh dấu FAST cũng mất tác dụng , CCS xem như là HIGH
và xử lý bình thường .

_Như vậy dùng FAST hay HIGH đều có ý nghĩa riêng của nhà  lập trình .

II / KHAI BÁO NGẮT :


_Mỗi dòng VDK có số lượng nguồn ngắt ngắt khác nhau : PIC 14 có 14 ngắt , PIC 18 có 35 ngắt .
_Muốn biết CCS hỗ trợ những ngắt nào cho VDK của bạn , mở file *.h tương ứng , ở cuối file là
danh sách các ngắt mà CCS hỗ trợ nó . Cách khác là vào CCS -> View -> Valid interrupts , chọn
VDK muốn xem , nó sẽ hiển thị danh sách ngắt có thể có cho VDK đó .
_Sau đây là danh sách 1 số  ngắt với chức năng tương ứng :
#INT_GLOBAL : ngắt chung , nghĩa là khi có ngắt xảy ra , hàm theo sau chỉ thị này được thực
thi , bạn sẽ không được khai báo thêm chỉ thị ngắt nào khác khi sử dụng chỉ thị này . CCS không
sinh
bất kỳ mã lưu nào , hàm ngắt bắt đầu ngay tại vector ngắt . Nếu bật nhiều cờ cho phép ngắt , có thể
bạn sẽ phải hỏi vòng để xác định ngắt nào . Dùng chỉ thị này tương đương viết hàm ngắt 1 cách thủ
công  mà thôi , như là viết hàm ngắt với ASM vậy .

#INT_AD  : chuyển đổi A /D đã hoàn tất , thường  thì không nên dùng
#INT_ADOF  : I don’t know
#INT_BUSCOL : xung đột bus
#INT_BUTTON : nút nhấn ( không biết hoạt động thế nào )
#INT_CCP1  : có Capture hay compare trên CCP1
#INT_CCP2  : có Capture hay compare trên CCP2
#INT_COMP  : kiểm tra bằng nhau trên Comparator
#INT_EEPROM : hoàn thành ghi EEPROM
#INT_EXT  : ngắt ngoài
#INT_EXT1  : ngắt ngoài 1
#INT_EXT2  : ngắt ngoài 2
#INT_I2C  : có hoạt động I 2C
#INT_LCD  : có hoạt động LCD
#INT_LOWVOLT : phát hiện áp thấp
#INT_PSP  : có data vào cổng Parallel slave
#INT_RB  : bất kỳ thay đổi  nào trên chân B4 đến B7
#INT_RC  : bất kỳ thay đổi  nào trên chân C4 đến C7
#INT_RDA  : data nhận từ RS 232 sẵn sàng
#INT_RTCC  : tràn Timer 0
#INT_SSP  : có hoạt động SPI hay I 2C
#INT_TBE  : bộ đệm chuyển RS 232 trống
#INT_TIMER0 : một tên khác của #INT_RTCC
#INT_TIMER1 : tràn Timer 1
#INT_TIMER2 : tràn Timer 2
#INT_TIMER3 : tràn Timer 3
#INT_TIMER5 : tràn Timer 5
#INT_OSCF               : lỗi OSC
#INT_PWMTB          : ngắt cuả PWM time base
#INT_IC3DR             : ngắt đổi hướng ( direct ) của IC 3
#INT_IC2QEI            : ngắt của QEI
#INT_IC1                   : ngắt IC 1

+ Hàm đi kèm phục vụ ngắt không cần tham số vì không có tác dụng .
+ Sử dụng NOCLEAR sau #int_xxx để CCS không xoá cờ ngắt của hàm đó .

+ Để cho phép ngắt đó hoạt động phải dùng lệnh enable_interrupts ( int_xxxx) và enable_interrupts
(global ) .
+ Khoá FAST theo sau #int_xxxx để cho ngắt đó là ưu tiên cao , chỉ được 1 ngắt thôi , chỉ có ở
PIC 18 và dsPIC .
VD : #int_timer0  FAST  NOCLEAR 

III / CÁC HÀM THIẾT LẬP HOẠT ĐỘNG NGẮT : 


1 /  enable_interrupts ( level ) 
+ level là tên các ngắt đã cho ở trên hay là GLOBAL để cho phép ngắt ở cấp toàn cục .
+ Mọi ngắt của VDK đều có 1 bit cờ ngắt , 1 bit cho phép ngắt . Khi có ngắt thì bit cờ ngắt bị set
=1, nhưng ngắt có họat động được hay không tuỳ thuộc  bit  cho phép ngắt . enable_interrupts
(int_xxx ) sẽ bật bit cho phép ngắt . Nhưng tất cả các ngắt đều không thể thực thi nếu bit cho phép
ngắt toàn cục = 0, enable_interrupts( global ) sẽ bật bit này .
VD : để cho phép ngắt timer0 và timer1  hoạt động:
enable_interrupts (int_timer0); 
enable_interrupts (int_timer1 ) ; 
enable_interrupts ( global );  // chỉ cần dùng 1 lần trừ phi muốn có thay đổi đặc biệt

2 / disable_interrupts ( level ) 
+ level giống như trên .
+ Hàm này vô hiệu 1 ngắt bằng cách set bit cho phép ngắt = 0 .
+ disable_interrupts ( global ) set bit cho phép ngắt toàn cục =0 , cấm tất cả các ngắt .
+ Không  dùng hàm này trong hàm phục vụ ngắt vì không có tác dụng , cờ ngắt luôn  bị xoá tự
động .

3 / clear_interupt ( level ) 
+ level không có GLOBAL .
+ Hàm này xoá cờ ngắt của ngắt được chỉ định bởi level .

4 / ext_int_edge ( source , edge ) 


+ Hàm này thiết lập nguồn ngắt ngoài EXTx là cạnh lên hay cạnh xuống .
+ source : nguồn ngắt . Trên PIC 18 có 3 nguồn ngắt trên 3 chân EXT0, EXT1, EXT2 ứng
với source =0,1, 2 . Các PIC khác chỉ có 1 nguồn EXT nên source = 0 .
+ edge : chọn cạnh kích ngắt , edge = L_TO_H nếu chọn  cạnh lên ( từ mức thấp  chuyển lên
mức cao ) hay H_TO_L nếu chọn cạnh xuống .

IV / CÁC CHƯƠNG TRÌNH VD VỀ NGẮT :


  1 /  #INT_RB : 
+ Sau đây là 1 chương trình điển hình về sử dụng ngắt khi có sự thay đổi trên chân B4-B7 .
+ Mô tả : mỗi khi nhấn nút bất kỳ trên B4-B7 , sẽ kích ngắt RB , hàm phục vụ ngắt có tên
RB_LED được thực thi , hàm này đơn giản là xuất ra LED ở vị trí tương ứng nhưng trên portD từ
D4 – D7 .
+ VDK là 16F877 .

#include < 16F877.h > 


#device  PIC16F877 *=16 
#use delay (clock = 20000000 )  //thêm khai báo này nếu ctrình có dùng hàm delay,OSC=20 Mhz 
#byte portb = 0x06      //tạo tên danh định portb thay thế địa chỉ portB là 06h 
#byte portd = 0x08      //tạo tên danh định portd thay thế địa chỉ portD là 08h 

#INT_RB 
Void RB_LED ( )      // hàm phục vụ ngắt 

   portd=portb; 

void main ( ) 
{  set_tris_b ( 0xF0 ) ;    // portB = 11110000 , B4-B7 là ngõ vào , B0-B3 là ngõ ra 
    set_tris_d ( 0x00 ) ;    // portD = 00000000 , D0-D7 đều là ngõ ra 
    enable_interrupts ( INT_RB ) ;  // cho phép ngắt RB 
    enable_interrupts ( GLOBAL ) ;  // cho phép ngắt toàn cục 
// do chương trình không làm gì khác ngoài việc chờ ngắt nên vòng while này trống không  
while( true )  
  { //có thể thêm mã xử lý ở đây . . . 
  } 
} //main

Ngày nay, thiết bị hiển thị LCD (Liquid Crystal Display) được sử dụng trong rất nhiều các
ứng dụng của VĐK. LCD có rất nhiều ưu điểm so với các dạng hiển thị khác: Nó có khả
năng hiển thị kí tự đa dạng, trực quan (chữ, số và kí tự đồ họa), dễ dàng đưa vào mạch
ứng dụng theo nhiều giao thức giao tiếp khác nhau, tốn rất ít tài nguyên hệ thống và giá
thành rẽ … 

Tổng Quát Về LCD HD44780 


1> Hình dáng và kích thước: 

        Có rất nhiều loại LCD với nhiều hình dáng và kích thước khác nhau, trên hình 1 là
loại LCD thông 
dụng.

Hình 1 : Hình dáng của loại LCD thông dụng 

Khi sản xuất LCD, nhà sản xuất đã tích hợp chíp điều khiển (HD44780) bên trong lớp vỏ
và chỉ đưa các chân giao tiếp cần thiết. Các chân này được đánh số thứ tự và đặt tên như
hình 2 : 

Hình 2 : Sơ đồ chân của LCD

2> Chức năng các chân : 


Châ Ký Mô tả
n hiệu
1 Vss Chân nối đất cho LCD, khi thiết kế mạch ta nối chân này
với GND của mạch điều khiển
2 VDD Chân cấp nguồn cho LCD, khi thiết kế mạch ta nối chân
này với VCC=5V của mạch điều khiển
3 VEE Điều chỉnh độ tương phản của LCD.
4 RS Chân chọn thanh ghi (Register select). Nối chân RS với
logic “0” (GND) hoặc logic “1” (VCC) để chọn thanh ghi.
+ Logic “0”: Bus DB0-DB7 sẽ nối với thanh ghi lệnh IR của
LCD (ở chế độ “ghi” - write) hoặc nối với bộ đếm địa chỉ của
LCD (ở chế độ “đọc” - read)
+ Logic “1”: Bus DB0-DB7 sẽ nối với thanh ghi dữ liệu DR
bên trong LCD.
5 R/W Chân chọn chế độ đọc/ghi (Read/Write). Nối chân R/W với
logic “0” để LCD hoạt động ở chế độ ghi, hoặc nối với logic
“1” để LCD ở chế độ đọc.
6 E Chân cho phép (Enable). Sau khi các tín hiệu được đặt lên
bus DB0-DB7, các lệnh chỉ được chấp nhận khi có 1 xung
cho phép của chân E.
+ Ở chế độ ghi: Dữ liệu ở bus sẽ được LCD chuyển
vào(chấp nhận) thanh ghi bên trong nó khi phát hiện một
xung (high-to-low transition) của tín hiệu chân E.
+ Ở chế độ đọc: Dữ liệu sẽ được LCD xuất ra DB0-DB7 khi
phát hiện cạnh lên (low-to-high transition) ở chân E và
được LCD giữ ở bus đến khi nào chân E xuống mức thấp.
7- DB0 - Tám đường của bus dữ liệu dùng để trao đổi thông tin với
14 DB7 MPU. Có 2 chế độ sử dụng 8 đường bus này :
+ Chế độ 8 bit : Dữ liệu được truyền trên cả 8 đường, với
bit MSB là bit DB7.
+ Chế độ 4 bit : Dữ liệu được truyền trên 4 đường từ DB4
tới DB7, bit MSB là DB7
15 - Nguồn dương cho đèn nền

16 - GND cho đèn nền

Bảng 1 : Chức năng các chân của LCD 

* Ghi chú : Ở chế độ “đọc”, nghĩa là MPU sẽ đọc thông tin từ LCD thông qua các chân
DBx. 
Còn khi ở chế độ “ghi”, nghĩa là MPU xuất thông tin điều khiển cho LCD thông qua các
chân DBx. 

3> Sơ đồ khối của HD44780: 

Để hiểu rõ hơn chức năng các chân và hoạt động của chúng, ta tìm hiểu sơ qua chíp
HD44780 thông qua các khối cơ bản của nó. 

Hình 3 : Sơ đồ khối của HD44780 

a> Các thanh ghi : 


        Chíp HD44780 có 2 thanh ghi 8 bit quan trọng : Thanh ghi lệnh IR (Instructor
Register) và thanh ghi dữ liệu DR (Data Register) 

- Thanh ghi IR : Để điều khiển LCD, người dùng phải “ra lệnh” thông qua tám đường bus
DB0-DB7. Mỗi lệnh được nhà sản xuất LCD đánh địa chỉ rõ ràng. Người dùng chỉ việc
cung cấp địa chỉ lệnh bằng cách nạp vào thanh ghi IR. Nghĩa là, khi ta nạp vào thanh ghi
IR một chuỗi 8 bit, chíp HD44780 sẽ tra bảng mã lệnh tại địa chỉ mà IR cung cấp và thực
hiện lệnh đó. 
VD : Lệnh “hiển thị màn hình” có địa chỉ lệnh là 00001100 (DB7…DB0) 

         Lệnh “hiển thị màn hình và con trỏ” có mã lệnh là 00001110 

- Thanh ghi DR : Thanh ghi DR dùng để chứa dữ liệu 8 bit để ghi vào vùng RAM DDRAM
hoặc CGRAM 
(ở chế độ ghi) hoặc dùng để chứa dữ liệu từ 2 vùng RAM này gởi ra cho MPU (ở chế độ
đọc). Nghĩa là, khi MPU ghi thông tin vào DR, mạch nội bên trong chíp sẽ tự động ghi
thông tin này vào DDRAM hoặc CGRAM. Hoặc khi thông tin về địa chỉ được ghi vào IR,
dữ liệu ở địa chỉ này trong vùng RAM nội của HD44780 sẽ được chuyển ra DR để truyền
cho MPU. 
=> Bằng cách điều khiển chân RS và R/W chúng ta có thể chuyển qua lại giữ 2 thanh ghi
này khi giao tiếp với MPU. Bảng sau đây tóm tắt lại các thiết lập đối với hai chân RS và
R/W theo mục đích giao tiếp. 

RS R/W Chức năng


0 0 Ghi vào thanh ghi IR để ra lệnh cho LCD
0 1 Đọc cờ bận ở DB7 và giá trị của bộ đếm địa chỉ ở DB0-DB6
1 0 Ghi vào thanh ghi DR
1 1 Đọc dữ liệu từ DR

Bảng 2 : Chức năng chân RS và R/W theo mục đích sử dụng

b> Cờ báo bận BF: (Busy Flag) 


Khi thực hiện các hoạt động bên trong chíp, mạch nội bên trong cần một khoảng thời gian
để hoàn tất. Khi 
đang thực thi các hoạt động bên trong chip như thế, LCD bỏ qua mọi giao tiếp với bên
ngoài và bật cờ BF (thông qua chân DB7 khi có thiết lập RS=0, R/W=1) lên để báo cho
MPU biết nó đang “bận”. Dĩ nhiên, khi xong việc, nó sẽ đặt cờ BF lại mức 0. 
c> Bộ đếm địa chỉ AC : (Address Counter) 
Như trong sơ đồ khối, thanh ghi IR không trực tiếp kết nối với vùng RAM (DDRAM và
CGRAM) mà thông qua bộ đếm địa chỉ AC.  Bộ đếm này lại nối với 2 vùng RAM theo kiểu
rẽ nhánh. Khi một địa chỉ lệnh được nạp vào thanh ghi IR, thông tin được nối trực tiếp cho
2 vùng RAM nhưng việc chọn lựa vùng RAM tương tác đã được bao hàm trong mã lệnh. 
Sau khi ghi vào (đọc từ) RAM, bộ đếm AC tự động tăng lên (giảm đi) 1 đơn vị và nội dung
của AC được  xuất ra cho MPU thông qua DB0-DB6 khi có thiết lập RS=0 và R/W=1 (xem
bảng tóm tắt RS - R/W). 
Lưu ý: Thời gian cập nhật AC không được tính vào thời gian thực thi lệnh mà được cập
nhật sau khi cờ BF lên mức cao (not busy), cho nên khi lập trình hiển thị, bạn phải delay
một khoảng tADD khoảng 4uS-5uS (ngay sau khi BF=1) trước khi nạp dữ liệu mới. Xem
thêm hình bên dưới. 
Hình 4 : Giản đồ xung cập nhật AC 

d> Vùng RAM hiển thị DDRAM : (Display Data RAM) 


Đây là vùng RAM dùng để hiển thị, nghĩa là ứng với một địa chỉ của RAM là một ô kí tự
trên màn hình và khi bạn ghi vào vùng RAM này một mã 8 bit, LCD sẽ hiển thị tại vị trí
tương ứng trên màn hình một kí tự có mã 8 bit mà bạn đã cung cấp. Hình sau đây sẽ trình
bày rõ hơn mối liên hệ này : 

Hình 4 : Mối liên hệ giữa địa chỉ của DDRAM và vị trí hiển thị của LCD 

Vùng RAM này có 80x8 bit nhớ, nghĩa là chứa được 80 kí tự mã 8 bit. Những vùng RAM
còn lại không dùng cho hiển thị có thể dùng như vùng RAM đa mục đích. 
Lưu ý là để truy cập vào DDRAM, ta phải cung cấp địa chỉ cho AC theo mã HEX 
e> Vùng ROM chứa kí tự CGROM: Character Generator ROM 
Vùng ROM này dùng để chứa các mẫu kí tự loại 5x8 hoặc 5x10 điểm ảnh/kí tự, và định
địa chỉ bằng 8 bit. Tuy nhiên, nó chỉ có 208 mẫu kí tự 5x8 và 32 mẫu kí tự kiểu 5x10 (tổng
cộng là 240 thay vì 2^8 = 256 mẫu kí tự). Người dùng không thể thay đổi vùng ROM này.
Hình 5 : Mối liên hệ giữa địa chỉ của ROM và dữ liệu tạo mẫu kí tự. 

Như vậy, để có thể ghi vào vị trí thứ x trên màn hình một kí tự y nào đó, người dùng phải
ghi vào vùng DDRAM tại địa chỉ x (xem bảng mối liên hệ giữa DDRAM và vị trí hiển thị)
một chuỗi mã kí tự 8 bit trên CGROM. Chú ý là trong bảng mã kí tự trong CGROM  ở hình
bên dưới có mã ROM A00.
 Ví dụ : Ghi vào DDRAM tại địa chỉ “01” một chuỗi 8 bit “01100010” thì trên LCD tại ô thứ
2 từ trái sang (dòng trên) sẽ hiển thị kí tự “b”.
Bảng 3 : Bảng mã kí tự (ROM code A00) 

f> Vùng RAM chứa kí tự đồ họa CGRAM : (Character Generator RAM) 


Như trên bảng mã kí tự, nhà sản xuất dành vùng có địa chỉ byte cao là 0000 để người
dùng có thể tạo các mẫu kí tự đồ họa riêng. Tuy nhiên dung lượng vùng này rất hạn chế:
Ta chỉ có thể tạo 8 kí tự loại 5x8 điểm ảnh, hoặc 4 kí tự loại 5x10 điểm ảnh. 
Để ghi vào CGRAM, hãy xem hình 6 bên dưới. 
Hình 6 : Mối liên hệ giữa địa chỉ của CGRAM, dữ liệu của CGRAM, và mã kí tự. 

4> Tập lệnh của LCD : 


Trước khi tìm hiểu tập lệnh của LCD, sau đây là một vài chú ý khi giao tiếp với LCD : 
* Tuy trong sơ đồ khối của LCD có nhiều khối khác nhau, nhưng khi lập trình điều khiển
LCD ta chỉ có thể tác động trực tiếp được vào 2 thanh ghi DR và IR thông qua các chân
DBx, và ta phải thiết lập chân RS, R/W phù hợp để chuyển qua lại giữ 2 thanh ghi này.
(xem bảng 2) 
* Với mỗi lệnh, LCD cần một khoảng thời gian để hoàn tất, thời gian này có thể khá lâu
đối với tốc độ của MPU, nên ta cần kiểm tra cờ BF hoặc đợi (delay) cho LCD thực thi
xong lệnh hiện hành mới có thể ra lệnh tiếp theo. 
* Địa chỉ của RAM (AC) sẽ tự động tăng (giảm) 1 đơn vị, mỗi khi có lệnh ghi vào RAM.
(Điều này giúp chương trình gọn hơn) 
* Các lệnh của LCD có thể chia thành 4 nhóm như sau : 
•  Các lệnh về kiểu hiển thị. VD : Kiểu hiển thị (1 hàng / 2 hàng), chiều dài dữ liệu (8 bit / 4
bit), … 
•  Chỉ định địa chỉ RAM nội. 
•  Nhóm lệnh truyền dữ liệu trong RAM nội. 
•  Các lệnh còn lại .
Bảng 4 : Tập lệnh của LCD

Tên Hoạt động


lệnh
Clear Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
Display
                  DBx =    0        0       0        0       0        0       0        1

Lệnh Clear Display (xóa hiển thị) sẽ ghi một khoảng trống-blank
(mã hiện kí tự 20H) vào tất cả ô nhớ trong DDRAM, sau đó trả bộ
đếm địa AC=0, trả lại kiểu hiển thị gốc nếu nó bị thay đổi. Nghĩa
là : Tắt hiển thị, con trỏ dời về góc trái (hàng đầu tiên), chế độ
tăng AC.
Return Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
home
                  DBx =    0       0        0        0       0        0        1       *

Lệnh Return home trả bộ đếm địa chỉ AC về 0, trả lại kiểu hiển thị
gốc nếu nó bị thay đổi. Nội dung của DDRAM không thay đổi.
Entry Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
mode
set                   DBx =   0         0       0        0       0        1     [I/D]   [S]

I/D : Tăng (I/D=1) hoặc giảm (I/D=0) bộ đếm địa chỉ hiển thị AC 1
đơn vị mỗi khi có hành động ghi hoặc đọc vùng DDRAM. Vị trí
con trỏ cũng di chuyển theo sự tăng giảm này.

S : Khi S=1 toàn bộ nội dung hiển thị bị dịch sang phải (I/D=0)
hoặc sang trái (I/D=1) mỗi khi có hành động ghi vùng DDRAM.
Khi S=0: không dịch nội dung hiển thị. Nội dung hiển thị không
dịch khi đọc DDRAM hoặc đọc/ghi vùng CGRAM.
Display Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
on/off
control                   DBx =    0       0        0       0        1      [D]    [C]    [B]

D: Hiển thị màn hình khi D=1 và ngược lại. Khi tắt hiển thị, nội
dung DDRAM không thay đổi.
C: Hiển thị con trỏ khi C=1 và ngược lại.

B: Nhấp nháy kí tự tại vị trí con trỏ khi B=1 và ngược lại.
Chu kì nhấp nháy khoảng 409,6ms khi mạch dao  động nội LCD
là 250kHz.
Cursor Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
or                   DBx =    0       0        0        1    [S/C] [R/L]   *        *
display Lệnh Cursor or display shift dịch chuyển con trỏ hay dữ liệu hiển
shift thị sang trái mà không cần hành động ghi/đọc dữ liệu. Khi hiển
thị kiểu 2 dòng, con trỏ sẽ nhảy xuống dòng dưới khi dịch qua vị
trí thứ 40 của hàng đầu tiên. Dữ liệu hàng đầu và hàng 2 dịch
cùng một lúc. Chi tiết sử dụng xem bảng bên dưới:
S/C R/L Hoạt động
0 0 Dịch vị trí con trỏ sang trái (Nghĩa là giảm AC một đơn
vị).
0 1 Dịch vị trí con trỏ sang phải (Tăng AC lên 1 đơn vị).
1 0 Dịch toàn bộ nội dung hiển thị sang trái, con trỏ cũng
dịch theo.
1 1 Dịch toàn bộ nội dung hiển thị sang phải, con trỏ cũng
dịch theo.
Functio Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
n                   DBx =    0       0        1     [DL]   [N]    [F]     *        *
set DL: Khi DL=1, LCD giao tiếp với MPU bằng giao thức 8 bit (từ bit
DB7 đến DB0). Ngược lại, giao thức giao tiếp là 4 bit (từ bit DB7
đến bit DB0). Khi chọn giao thức 4 bit, dữ liệu được truyền/nhận
2 lần liên tiếp. với 4 bit cao gởi/nhận trước, 4 bit thấp gởi/nhận
sau.
N : Thiết lập số hàng hiển thị. Khi N=0 : hiển thị 1 hàng, N=1:
hiển thị 2 hàng.
F : Thiết lập kiểu kí tự. Khi F=0: kiểu kí tự 5x8 điểm ảnh, F=1:
kiểu kí tự 5x10 điểm ảnh.
Set Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
CGRAM                   DBx =   0       1   [ACG][ACG][ACG][ACG][ACG][ACG]
address Lệnh này ghi vào AC địa chỉ của CGRAM. Kí hiệu [ACG] chỉ 1 bit
của chuỗi dữ liệu 6 bit. Ngay sau lệnh này là lệnh đọc/ghi dữ liệu
từ CGRAM tại địa chỉ đã được chỉ định.
Set Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
DDRAM                   DBx =    1    [AD]  [AD]  [AD]  [AD]  [AD] [AD]  [AD]
address Lệnh này ghi vào AC địa chỉ của DDRAM, dùng khi cần thiết lập
tọa độ hiển thị
mong muốn. Ngay sau lệnh này là lệnh  đọc/ghi dữ liệu từ
DDRAM tại  địa chỉ  đã được chỉ định.
Khi ở chế độ hiển thị 1 hàng: địa chỉ có thể từ 00H đến 4FH. Khi
ở chế độ hiển thị 2 hàng, địa chỉ từ 00h đến 27H cho hàng thứ
nhất, và từ 40h đến 67h cho hàng thứ 2.
Read Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
BF                   DBx =[BF] [AC]  [AC]   [AC]  [AC]  [AC]  [AC]  [AC]
and (RS=0,R/W=1)
address
Chân Như(Vcc-GND)
cấp nguồn đã đề cập trước đây,, khi
Min:-0.3V cờ BF bật, LCD đang làm việc và
Max+7V
Các chân ngõlệnh
vàotiếp theo (nếu có) sẽ bị bỏ qua nếu cờ BF chưa về mức
(DBx,E,
thấp. Cho nên, khi Min:-0.3V
lập trình , Max:(Vcc+0.3V)
điều khiển, phải kiểm tra cờ BF trước
…)
khi ghi dữ liệu vào LCD.
Nhiệt độ hoạt động Min:-30C , Max:+75C
Khi đọc cờ BF, giá trị của AC cũng được xuất ra các bit [AC]. Nó
Nhiệt độ bảolà địa chỉ của Min:-55C , Max:+125C
quản
CG hay DDRAM là tùy thuộc vào lệnh trước đó.
Write Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
data to                   DBx =                     [Write data]                                  
CG or (RS=1, R/W=0)
DDRAM Khi thiết lập RS=1, R/W=0, dữ liệu cần ghi được đưa vào các
chân DBx từ mạch
ngoài sẽ được LCD chuyển vào trong LCD tại địa chỉ được xác
định từ lệnh ghi địa chỉ trước đó (lệnh ghi địa chỉ cũng xác định
luôn vùng RAM cần ghi)
Sau khi ghi, bộ đếm địa chỉ AC tự động tăng/giảm 1 tùy theo thiết
lập Entry mode.
Read Mã lệnh :  DBx = DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
data                   DBx =                       [Read data]                                
from (RS=1, R/W=1)
CG or Khi thiết lập RS=1, R/W=1,dữ liệu từ CG/DDRAM được chuyển
DDRAM ra MPU thông qua các chân DBx (địa chỉ và vùng RAM đã được
xác định bằng lệnh ghi địa chỉ trước đó).
Sau khi đọc, AC tự động tăng/giảm 1 tùy theo thiết lập Entry
mode, tuy nhiên nội dung hiển thị không bị dịch bất chấp chế độ
Entry mode.

5> Giao tiếp giữa LCD và MPU : 


a> Đặc tính điện của các chân giao tiếp : 
LCD sẽ bị hỏng nghiêm trọng, hoặc hoạt động sai lệch nếu bạn vi phạm khoảng đặc tính
điện sau đây: 

                                  Bảng 6 : Maximun Rating

Đặc tính điện làm việc điển hình: (Đo trong điều kiện hoạt động Vcc = 4.5V đến 5.5V, T =
-30 đến +75C) 
Chân cấp nguồn Vcc-GND 2.7V đến 5.5V

Điện áp vào mức cao VIH 2.2V đến Vcc

Điện áp vào mức thấp VIL -0.3V đến 0.6V

Điện áp ra mức cao (DB0-DB7) Min 2.4V    (khi IOH = -0.205mA)

Điện áp ra mức thấp (DB0-DB7) Max 0.4V   (khi IOL = 1.2mA)


Dòng điện ngõ vào (input leakage -1uA đến 1uA    (khi VIN = 0 đến
current) ILI Vcc)
Dòng điện cấp nguồn ICC 350uA(typ.) đến 600uA
190kHz đến 350kHz (điển hình là
Tần số dao động nội fOSC
270kHz)

Bảng 7: Miền làm việc bình thường 

b> Sơ đồ nối mạch điển hình: 


- Sơ đồ mạch kết nối giữa mô đun LCD và VĐK 89S52 (8 bit). 
- Sơ đồ mạch kết nối giữa môđun LCD và VĐK (4 bit). 
c> Bus Timing: 
6> Khởi tạo LCD: 
Khởi tạo là việc thiết lập các thông số làm việc ban đầu. Đối với LCD, khởi tạo giúp ta thiết
lập các giao thức làm việc giữa LCD và MPU. Việc khởi tạo chỉ được thực hiện 1 lần duy
nhất ở đầu chương trình điều khiển LCD và bao gồm các thiết lập sau : 
•  Display clear : Xóa/không xóa toàn bộ nội dung hiển thị trước đó. 
•  Function set : Kiểu giao tiếp 8bit/4bit, số hàng hiển thị 1hàng/2hàng, kiểu kí tự
5x8/5x10. 
•  Display on/off control: Hiển thị/tắt màn hình, hiển thị/tắt con trỏ, nhấp nháy/không nhấp
nháy. 
•  Entry mode set : các thiết lập kiểu nhập kí tự như: Dịch/không dịch, tự tăng/giảm
(Increment). 
a> Mạch khởi tạo bên trong chíp HD44780: 
Mỗi khi được cấp nguồn, mạch khởi tạo bên trong LCD sẽ tự động khởi tạo cho nó. Và
trong thời gian khởi tạo này cờ BF bật lên 1, đến khi việc khởi tạo hoàn tất cờ BF còn giữ
trong khoảng 10ms sau khi Vcc đạt đến 4.5V (vì 2.7V thì LCD đã hoạt động). Mạch khởi
tạo nội sẽ thiết lập các thông số làm việc của LCD như sau: 
•  Display clear : Xóa toàn bộ nội dung hiển thị trước đó. 
•  Function set: DL=1 : 8bit; N=0 : 1 hàng; F=0 : 5x8 
•  Display on/off control: D=0 : Display off; C=0 : Cursor off; B=0 : Blinking off. 
•  Entry mode set: I/D =1 : Tăng; S=0 : Không dịch. 
Như vậy sau khi mở nguồn, bạn sẽ thấy màn hình LCD giống như chưa mở nguồn do
toàn bộ hiển thị tắt. Do đó, ta phải khởi tạo LCD bằng lệnh. 
b> Khởi tạo bằng lệnh: (chuỗi lệnh) 
Việc khởi tạo bằng lệnh phải tuân theo lưu đồ sau của nhà sản xuất :
Như đã đề cập ở trên, chế độ giao tiếp mặc định của LCD là 8bit (tự khởi tạo lúc mới bật
điện lên). Và khi kết nối mạch theo giao thức 4bit, 4 bit thấp từ DB0-DB3 không được kết
nối đến LCD, nên lệnh khởi tạo ban đầu (lệnh chọn giao thức giao tiếp – function set
0010****) phải giao tiếp theo chế độ 8 bit (chỉ gởi 4 bit cao một lần, bỏ qua 4 bit thấp). Từ
lệnh sau trở đi, phải gởi/nhận lệnh theo 2 nibble. 
Lưu ý là sau khi thiết lập function set, bạn không thể thay đổi function set ngoại trừ thay
đổi giao thức giao tiếp (4bit/8bit). 
Chương trình thực hiện giao tiếp I2C giữa PIC 16F877A và IC DS1307 để cài đặt thời gian, đọc
thời gian từ DS1307, hiển thị lên LCD, truyền qua RS232.
Với LCD, chương trình sẽ đọc dữ liệu DS1307 và cập nhật LCD liên tục, còn khi truyền lên máy
tính sẽ dựa vào ngắt RB0 đưa vào từ xung 1Hz của DS1307, tức là mỗi 1s sẽ truyền dữ liệu 1 lần.
Trong chương trình có sử dụng các thư viện: lcd_lib_4bit.c, ds1307.c.

Sơ đồ nguyên lý mô phỏng trên Proteus:

Truyền lên hyper terminal:


Mã nguồn chương trình chính:

#include "16f877a.h"
#include "def_877a.h"

#device *=16 ADC=10


#use delay(clock=20000000)
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP,
NOCPD, NOWRT
#use i2c(Master, sda = PIN_C4, scl=PIN_C3)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#include "lcd_lib_4bit.c"
#include "ds1307.c"

#define Slave_add 0x68


#define Read 1
#define Write 0

void send(int8 a);

int8 sec,min,hrs,day,month,yr,dow;

//ngat o chan RB0: Truyen len cong RS232


#int_EXT
void EXT_isr(void) //moi 1s truyen len may tinh 1 lan
{
   ds1307_get_date(day,month,yr,dow); 
   ds1307_get_time(hrs,min,sec);
   send(hrs);
   putc(45);
   send(min);
   putc(45);
   send(sec);
   putc(10);
   return;
}
void main()
{
   enable_interrupts(INT_EXT);//cho phep ngat RB0
   ext_int_edge(0,H_TO_L);//dat suon ngat
   enable_interrupts(GLOBAL);//cho phep ngat toan cuc
  
   LCD_init(); //Khoi tao LCD.
   delay_ms(10);
   ds1307_init();// khoi tao DS1307, tao xung 1Hz o chan 7 DS1307.
   // Set date : 12-4-2012
   // Set time : thu 5 - 12 gio, 59 phút 10 giây
   ds1307_set_date_time(12,4,12,5,12,59,10);
  
   while(1) 
   { 
      ds1307_get_date(day,month,yr,dow); 
      ds1307_get_time(hrs,min,sec);
      
      //Truyen len LCD
      //o day chi hien gio, phut, giay. cac thong tin khac thuc hien tuong tu.
      LCD_PutCmd(0x80);
      LCD_PutChar(hrs/10+48);
      LCD_PutChar(hrs%10+48);
      LCD_PutChar(45);
      LCD_PutChar(min/10+48);
      LCD_PutChar(min%10+48);
      LCD_PutChar(45);
      LCD_PutChar(sec/10+48);
      LCD_PutChar(sec%10+48);
   }
}
void send(int8 a)
{
    if(a<10)
     {
         putc(a+48);         
     }
     if(a>9&&a<100)
     {
          unsigned char c=a/10;
          unsigned char d=a%10;
          putc(c+48);
          putc(d+48);       
     }
     if(a>99)
     {
          unsigned char t=a/100;
          unsigned char c=a/10-10*t;
          unsigned char d=a%10;
          putc(t+48);
          putc(c+48);
          putc(d+48);
     }
}
Thư viện lcd_lib_4bit.c:

#include <stddef.h>

#define LCD_RS          PIN_D2


//#define LCD_RW          PIN_A1
#define LCD_EN          PIN_D3

#define LCD_D4          PIN_D4


#define LCD_D5          PIN_D5
#define LCD_D6          PIN_D6
#define LCD_D7          PIN_D7

// misc display defines-


#define Line_1          0x80
#define Line_2          0xC0
#define Clear_Scr       0x01

// prototype statements
#separate void LCD_Init ( void );// ham khoi tao LCD
#separate void LCD_SetPosition ( unsigned int cX );//Thiet lap vi tri con tro
#separate void LCD_PutChar ( unsigned int cX );// Ham viet1kitu/1chuoi len LCD
#separate void LCD_PutCmd ( unsigned int cX) ;// Ham gui lenh len LCD
#separate void LCD_PulseEnable ( void );// Xung kich hoat
#separate void LCD_SetData ( unsigned int cX );// Dat du lieu len chan Data
// D/n Cong
#use standard_io (C)
#use standard_io (D)

//khoi tao LCD**********************************************


#separate void LCD_Init ( void )
  {
    LCD_SetData ( 0x00 );
    delay_ms(200);       /* wait enough time after Vdd rise >> 15ms */
    output_low ( LCD_RS );// che do gui lenh
    LCD_SetData ( 0x03 );   /* init with specific nibbles to start 4-bit mode */
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_SetData ( 0x02 );   /* set 4-bit interface */
    LCD_PulseEnable();      /* send dual nibbles hereafter, MSN first */
    LCD_PutCmd ( 0x2C );    /* function set (all lines, 5x7 characters) */
    LCD_PutCmd ( 0x0C );    /* display ON, cursor off, no blink */
    LCD_PutCmd ( 0x06 );    /* entry mode set, increment & scroll left */
    LCD_PutCmd ( 0x01 );    /* clear display */
  }

#separate void LCD_SetPosition ( unsigned int cX )


  {
    /* this subroutine works specifically for 4-bit Port A */
    LCD_SetData ( swap ( cX ) | 0x08 );
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );
    LCD_PulseEnable();
  }

#separate void LCD_PutChar ( unsigned int cX )


  {
    /* this subroutine works specifically for 4-bit Port A */
        output_high ( LCD_RS );
        LCD_PutCmd( cX );
        output_low ( LCD_RS );
  }

#separate void LCD_PutCmd ( unsigned int cX )


  {
    /* this subroutine works specifically for 4-bit Port A */
    LCD_SetData ( swap ( cX ) );     /* send high nibble */
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     /* send low nibble */
    LCD_PulseEnable();
  }
#separate void LCD_PulseEnable ( void )
  {
    output_high ( LCD_EN );
    delay_us ( 3 );         // was 10
    output_low ( LCD_EN );
    delay_ms ( 3 );         // was 5
  }

#separate void LCD_SetData ( unsigned int cX )


  {
    output_bit ( LCD_D4, cX & 0x01 );
    output_bit ( LCD_D5, cX & 0x02 );
    output_bit ( LCD_D6, cX & 0x04 );
    output_bit ( LCD_D7, cX & 0x08 );
  }

Thư viện ds1307.c:

BYTE bin2bcd(BYTE binary_value);


BYTE bcd2bin(BYTE bcd_value);

void ds1307_init(void) 

   BYTE initsec = 0;
   BYTE initmin=0;
   BYTE inithr=0;                  
   BYTE initdow=0;
   BYTE initday=0;                   
   BYTE initmth=0;
   BYTE inityear=0;
   i2c_start(); 
   i2c_write(0xD0);      // WR to RTC 
   i2c_write(0x00);      // REG 0 
   i2c_start(); 
   i2c_write(0xD1);      // RD from RTC 
   initsec  = bcd2bin(i2c_read() & 0x7f); 
   initmin  = bcd2bin(i2c_read() & 0x7f); 
   inithr   = bcd2bin(i2c_read() & 0x3f); 
   initdow  = bcd2bin(i2c_read() & 0x7f);   // REG 3 
   initday  = bcd2bin(i2c_read() & 0x3f);   // REG 4 
   initmth  = bcd2bin(i2c_read() & 0x1f);   // REG 5 
   inityear = bcd2bin(i2c_read(0));         // REG 6 
   i2c_stop(); 
   delay_us(3);
   i2c_start(); 
   i2c_write(0xD0);      // WR to RTC 
   i2c_write(0x00);      // REG 0 
   i2c_write(bin2bcd(initsec));      // Start oscillator with current "seconds value 
   i2c_write(bin2bcd(initmin));      // REG 1 
   i2c_write(bin2bcd(inithr));       // REG 2 
   i2c_write(bin2bcd(initdow));      // REG 3 
   i2c_write(bin2bcd(initday));      // REG 4 
   i2c_write(bin2bcd(initmth));      // REG 5 
   i2c_write(bin2bcd(inityear));     // REG 6 
   i2c_start(); 
   i2c_write(0xD0);      // WR to RTC 
   i2c_write(0x07);      // Control Register 
   i2c_write(0x90);      //  squarewave output pin 1Hz
   i2c_stop(); 

void ds1307_set_date_time(BYTE day, BYTE mth, BYTE year, BYTE dow, BYTE hr, BYTE min,
BYTE sec)
{
  sec &= 0x7F;
  hr &= 0x3F;

  i2c_start();
  i2c_write(0xD0);              // I2C write address
  i2c_write(0x00);              // Start at REG 0 - Seconds
  i2c_write(bin2bcd(sec));      // REG 0
  i2c_write(bin2bcd(min));      // REG 1
  i2c_write(bin2bcd(hr));       // REG 2
  i2c_write(bin2bcd(dow));      // REG 3
  i2c_write(bin2bcd(day));      // REG 4
  i2c_write(bin2bcd(mth));      // REG 5
  i2c_write(bin2bcd(year));     // REG 6
  i2c_write(0x90);              // REG 7 - 1Hz squarewave output pin
  i2c_stop();
}

void ds1307_get_date(BYTE &day, BYTE &mth, BYTE &year, BYTE &dow)


{
  i2c_start();
  i2c_write(0xD0);
  i2c_write(0x03);              // Start at REG 3 - Day of week
  i2c_start();
  i2c_write(0xD1);
  dow  = bcd2bin(i2c_read() & 0x7f);   // REG 3
  day  = bcd2bin(i2c_read() & 0x3f);   // REG 4
  mth  = bcd2bin(i2c_read() & 0x1f);   // REG 5
  year = bcd2bin(i2c_read(0));         // REG 6
  i2c_stop();
}

void ds1307_get_time(BYTE &hr, BYTE &min, BYTE &sec)


{
  i2c_start();
  i2c_write(0xD0);
  i2c_write(0x00);                     // Start at REG 0 - Seconds
  i2c_start();
  i2c_write(0xD1);
  sec = bcd2bin(i2c_read() & 0x7f);
  min = bcd2bin(i2c_read() & 0x7f);
  hr  = bcd2bin(i2c_read(0) & 0x3f);
  i2c_stop();

BYTE bin2bcd(BYTE binary_value)


{
  BYTE temp;
  BYTE retval;

  temp = binary_value;
  retval = 0;

  while(1)
 {
    // Get the tens digit by doing multiple subtraction
    // of 10 from the binary value.
    if(temp >= 10)
  {
      temp -= 10;
      retval += 0x10;
  }
    else // Get the ones digit by adding the remainder.
  {
      retval += temp;
      break;
  }
 }

  return(retval);
}

// Input range - 00 to 99.


BYTE bcd2bin(BYTE bcd_value)
{
  BYTE temp;

  temp = bcd_value;
  // Shifting upper digit right by 1 is same as multiplying by 8.
  temp >>= 1;
  // Isolate the bits for the upper digit.
  temp &= 0x78;

  // Now return: (Tens * 8) + (Tens * 2) + Ones

  return(temp + (temp >> 2) + (bcd_value & 0x0f));



Chương trình thực hiện đo nhiệt độ, điện áp, hiển thị kết quả lên màn hình LCD 16x2, và truyền giá
trị lên máy tính.
Mô phỏng trên Proteus:

mô phỏng trên Proteus

Sơ đồ nguyên lý:

Sơ đồ nguyên lý

Mã nguồn:

#include "16f877a.h"
#include "def_877a.h"
#device *=16 ADC=10
#use delay(clock=20000000)
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP,
NOCPD, NOWRT

#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#include "lcd_lib_4bit.c"
//======================================================================
=
void send(int8 a);
void HIENTHI_LCD(int8 t);
void HIENTHI_LCD_float(int8 t);
int8 count=0;
int8 i,value,vl[10];
//======================================================================
=
#INT_RDA //ngat khi nhan du lieu.
Receive_isr()
{
   char c;
   c=getc();
   switch(c)
   {
      case '0':   //xoa man hinh LCD
   {
               LCD_putcmd(0x01);
   }
      break;
          //o day co the su dung ma thap phan! (ngoai tru cac ky tu dieu khien)
      case '-':   //lui con tro hien thi LCD 1 don vi.
   {
               LCD_putcmd(0x10);
   }
      break;
      case '1':   //truyen len may tinh: gia tri do duoc.
   {
         Set_ADC_channel(0);  //kenh 0 chan so2
         delay_us(10);
         for(i=0;i<10;i++)
         {
               vl[i]=read_adc();
               delay_us(10);
         }
         value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/20.48;
         send(value);
   }
      break;
      case '2':   //truyen len may tinh: gia tri do duoc.
   {
         Set_ADC_channel(1);  //kenh 1 chan so3
         delay_us(10);
         if(read_adc()==0)
         {
               for(i=0;i<10;i++)
               {
                     vl[i]=read_adc();
                     delay_us(10);
               }
               value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
         }
         else
         {
               value=read_adc()+6;
               for(i=0;i<10;i++)
               {
                     vl[i]=read_adc()+6;
                     delay_us(10);
               }
               value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
         }
         send(value);
   }
      break;
      case '9':   //thoat khoi ham ngat, cho ADC làm viec.
   {
            count=0;   //ket thuc viec truyen ky tu len LCD
            LCD_putcmd(0x01);
   }
      break;
      default : //truyen ky tu xuong LCD ^^.
   {
               count++;
               if(count==1) LCD_putcmd(0x01);
               if(count==16) LCD_putcmd(0xC0);
               if(count==32)
               {
                  LCD_putcmd(0x01);
                  count=0;
               }
               LCD_putchar(c);
   }  
  }
}
//======================================================================
=
void main()
{
   //trisD=0x0C; //D0,D1 LA CONG VAO, D2-D7 LA CONG RA.
 
   enable_interrupts(int_rda); //cho phep ngat noi tiep nhan.
   enable_interrupts(GLOBAL);

 
   LCD_init(); //Khoi tao LCD.
   LCD_putcmd(0xC0);
   Printf(LCD_putchar,"Khoi tao...");
   delay_ms(250);

   setup_adc_ports(AN0_AN1_AN3);   //Khoi tao che do cho bo ADC.


   setup_adc(ADC_CLOCK_INTERNAL);
   delay_us(10);

   while(1)
   {
      //do nhiet do
   {
         Set_ADC_channel(0);  //kenh 0 chan so2
         delay_us(10);
         value=read_adc();
         value=value/2.048;
    
         LCD_putcmd(0x80);
         LCD_putchar("Nhiet do: ");
         LCD_putcmd(0x89);
         HIENTHI_LCD(value);
         LCD_putcmd(0x8C);
         LCD_putchar("oC");
         delay_ms(0.1);
   }
      //do diep ap
   {
         Set_ADC_channel(1);  //kenh 1 chan so3
         delay_us(10);
         if(read_adc()==0)
         {
               for(i=0;i<10;i++)
               {
                     vl[i]=read_adc();
                     delay_us(10);
               }
               value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
         }
         else
         {
               value=read_adc()+6;
               for(i=0;i<10;i++)
               {
                     vl[i]=read_adc()+6;
                     delay_us(10);
               }
               value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
         }

         LCD_PutCmd(0xC0);
         LCD_putchar("DIEN AP : ");
         LCD_putcmd(0xC9);
         //HIENTHI_LCD(value);
         HIENTHI_LCD_float(value);
         LCD_putcmd(0xCD);
         LCD_putchar("V ");
   }
   }
}
//======================================================================
=
void HIENTHI_LCD(int8 t)
{
     unsigned char v;
     if(t<10)
          LCD_putchar(t+48);
     else
     if(t<100)
     {
          LCD_putchar(t/10+48);
          LCD_putchar(t%10+48);
     }
     else
     {
         v=t/10;
         LCD_putchar(v/10+48);
         LCD_putchar(v%10+48);
         LCD_putchar(t%10+48);
     }
}
void send(int8 a)
{
    if(a<10)
     {
         putc(a+48);      
     }
     if(a>9&&a<100)
     {
          unsigned char c=a/10;
          unsigned char d=a%10;
          putc(c+48);
          putc(d+48);    
     }
     if(a>99)
     {
          unsigned char t=a/100;
          unsigned char c=a/10-10*t;
          unsigned char d=a%10;
          putc(t+48);
          putc(c+48);
          putc(d+48);
     }
}
void HIENTHI_LCD_float(int8 t)
{
     int8 v;
     if(t<10)
     {
          LCD_putchar(48);
          LCD_putchar(46);
          LCD_putchar(t+48);
          LCD_putchar(32);
     }
     else
     if(t<100)
     {
          LCD_putchar(t/10+48);
          LCD_putchar(46);
          LCD_putchar(t%10+48);
          LCD_putchar(32);
     }
     else
     {
         v=t/10;
         LCD_putchar(v/10+48);
         LCD_putchar(v%10+48);
         LCD_putchar(46);
         LCD_putchar(t%10+48);
     }
}
/*
void send_float(float a)
{
    if(a<10)
     {
         putc(a+48);      
     }
     if(a>9&&a<100)
     {
          unsigned char c=a/10;
          unsigned char d=a%10;
          putc(c+48);
          putc(d+48);    
     }
     if(a>99)
     {
          unsigned char t=a/100;
          unsigned char c=a/10-10*t;
          unsigned char d=a%10;
          putc(t+48);
          putc(c+48);
          putc(d+48);
     }
}*/
//======================================================================
=
Trong Vi điều khiển PIC có nhiều nguồn ngắt. Để biết cụ thể ta có thể vào mục View >> Valid
Interrupts . Khi đó một của sổ sẽ hiện ra liệt kê đầy đủ các nguồn ngắt của từng con PIC.

 Các nguồn ngắt trong PIC

Để viết một hàm phục vụ ngắt ta chỉ việc thêm khai báo #INT_tên_ngắt vào 
trước hàm phục vụ cho ngắt đó. Khi đó trình dich sẽ hiểu đó là địa chỉ hàm cho 
ngắt, khi có ngắt tương ứng xảy ra thì nó sẽ nhảy đến vị trí đó . Lấy ví dụ khi ta muốn xử lý ngắt
ngoài, hàm sẽ được viết như sau:

#INT_EXT 
Ext_isr() 

// Nhập mã tại đây 
}

Dưới đây là chương trình nháy led theo nhiều kiểu khác nhau, sử dụng 1 phím bấm nối với chân
ngắt ngoài RB0 để chọn kiểu nháy. Có 8 kiểu nháy LED khác nhau, Khi đến kiểu nháy thứ 8, nếu ta
nhấn thì sẽ trở về chế độ ban đẩu. Ban đầu biến mode = 0 và tất cả các LED đều tắt Mỗi khi nhấn
phím bấm, biến mode sẽ tăng lên 1 đơn vị. Giá trị biến mode tương ứng với chương trình nháy
được thực hiện. Khi mode = 9 thì sẽ được gán về mode = 0. Các kiểu nháy khác nhau là do ta bật
tắt các LED trên cổng D theo các cách khác nhau. Lấy ví dụ khi ta muôn các LED nháy xen kẽ
nhau ta chỉ việc gửi ra cổng D giá trị AAh (10101010) và 55h (01010101).
 Nháy LED nhiều chế độ dùng Ngắt

Mã nguồn chương trình: 

#include <16F877A.h> 
#include <def_877a.h> 
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT, 
NOLVP, NOCPD, NOWRT 
#use delay(clock=20000000) 

int8 mode,i; 
byte temp; 

#INT_EXT
EXT_ISR() {

mode++;
if (mode==9) mode = 0;
}
// End of INT

void program1();
void program2();
void program3();
void program4();
void program5();
void program6();
void program7();
void program8();

void main() {

   trisd = 0x00;
   trisb = 0xFF;
   portd=0xff;
   enable_interrupts(int_EXT);
   ext_int_edge(H_TO_L);  // Chọn ngắt theo sườn âm
   enable_interrupts(GLOBAL);
   mode = 0;
while (1) {
   switch(mode) {
      case 1: program1(); break;
      case 2: program2(); break;
      case 3: program3(); break;
      case 4: program4(); break;
      case 5: program5(); break;
      case 6: program6(); break;
      case 7: program7(); break;
      case 8: program8(); break;
   }
}
}

void program1() {

 PortD = 0x00;
   delay_ms(250);
   Portd = 0xFF;
   delay_ms(250);
}
void program2() { // LED sáng chạy từ trái qua phải
   temp = 0xFF;
   for (i=0;i<=8;i++) {
      portd = temp;
      delay_ms(250);
      temp >>= 1;
   }
}
void program3() { // LED sáng chạy từ phải qua trái
    temp = 0xFF;
   for (i=0;i<=8;i++) {
      portd = temp;
      delay_ms(250);
      temp <<= 1;
   }
}
void program4() {
   portd = 0xAA;
   delay_ms(500);
   portd = 0x55;
   delay_ms(500);
}
void program5() {
   Portd = 0x7E;   delay_ms(150);
   Portd = 0xBD;   delay_ms(250);
   Portd = 0xDB;   delay_ms(150);
   Portd = 0xE7;   delay_ms(150);
   Portd = 0xDB;   delay_ms(150);
   Portd = 0xBD;   delay_ms(150);
   Portd = 0x7E;   delay_ms(150);
}
void program6() {
  temp = 0xFF;
   for (i=0;i<=8;i++) {
      portd = temp;
      delay_ms(250);

 temp = temp >> 1;


   }
}
void program7() {

   Portd = 0xFE;   delay_ms(150);


   Portd = 0xFD;   delay_ms(150);
   Portd = 0xFB;   delay_ms(150);
   Portd = 0xF7;   delay_ms(150);
   Portd = 0xEF;   delay_ms(150);
   PortD = 0xDF;   delay_ms(150);
   Portd = 0xBF;   delay_ms(150);
   Portd = 0x7F;   delay_ms(150);
}

void program8() {
   Portd = 0x7F;   delay_ms(150);
   Portd = 0xBF;   delay_ms(150);
   PortD = 0xDF;   delay_ms(150);
   Portd = 0xEF;   delay_ms(150);
   Portd = 0xF7;   delay_ms(150);
   Portd = 0xFB;   delay_ms(150);
   Portd = 0xFD;   delay_ms(150);
   Portd = 0xFE;   delay_ms(150);
}
1. /////////////////////////////////////////////////////////////////////////   
2. ////                           MMCSD.c                               ////   
3. ////                                                                 ////   
4. ////    This is a low-level driver for MMC and SD cards.             ////   
5. ////                                                                 ////   
6. //// --User Functions--                                              ////   
7. ////                                                                 ////   
8. //// mmcsd_init(): Initializes the media.                            ////   
9. ////                                                                 ////   
10. //// mmcsd_read_byte(a, p)                                           ////   
11. ////  Reads a byte from the MMC/SD card at location a, saves to      ////   
12. ////  pointer p.  Returns 0 if OK, non-zero if error.                ////   
13. ////                                                                 ////   
14. //// mmcsd_read_data(a, n, p)                                        ////   
15. ////  Reads n bytes of data from the MMC/SD card starting at address ////   
16. ////  a, saves result to pointer p.  Returns 0 if OK, non-zero if    ////   
17. ////  error.                                                         ////   
18. ////                                                                 ////   
19. //// mmcsd_flush_buffer()                                            ////   
20. ////  The two user write functions (mmcsd_write_byte() and           ////   
21. ////  mmcsd_write_data()) maintain a buffer to speed up the writing  ////   
22. ////  process.  Whenever a read or write is performed, the write     ////   
23. ////  buffer is loaded with the specified page and only the          ////   
24. ////  contents of this buffer is changed.  If any future writes      ////   
25. ////  cross a page boundary then the buffer in RAM is written        ////   
26. ////  to the MMC/SD and then the next page is loaded into the        ////   
27. ////  buffer.  mmcsd_flush_buffer() forces the contents in RAM       ////   
28. ////  to the MMC/SD card.  Returns 0 if OK, non-zero if errror.      ////   
29. ////                                                                 ////   
30. //// mmcsd_write_byte(a, d)                                          ////   
31. ////  Writes data byte d to the MMC/SD address a.  Intelligently     ////   
32. ////  manages a write buffer, therefore you may need to call         ////   
33. ////  mmcsd_flush_buffer() to flush the buffer.                      ////   
34. ////                                                                 ////   
35. //// mmcsd_write_data(a, n, p)                                       ////   
36. ////  Writes n bytes of data from pointer p to the MMC/SD card       ////   
37. ////  starting at address a.  This function intelligently manages    ////   
38. ////  a write buffer, therefore if you may need to call              ////   
39. ////  mmcsd_flush_buffer() to flush any buffered characters.         ////   
40. ////  returns 0 if OK, non-zero if error.                            ////   
41. ////                                                                 ////   
42. //// mmcsd_read_block(a, s, p)                                       ////   
43. ////  Reads an entire page from the SD/MMC.  Keep in mind that the   ////   
44. ////  start of the read has to be aligned to a block                 ////   
45. ////  (Address % 512 = 0).  Therefore s must be evenly divisible by  ////   
46. ////  512.  At the application level it is much more effecient to    ////   
47. ////  to use mmcsd_read_data() or mmcsd_read_byte().  Returns 0      ////   
48. ////  if successful, non-zero if error.                              ////   
49. ////                                                                 ////   
50. //// mmcsd_write_block(a, s, p):                                     ////   
51. ////  Writes an entire page to the SD/MMC.  This will write an       ////   
52. ////  entire page to the SD/MMC, so the address and size must be     ////   
53. ////  evenly  divisble by 512.  At the application level it is much  ////   
54. ////  more effecient to use mmcsd_write_data() or mmcsd_write_byte().////   
55. ////  Returns 0 if successful, non-zero if error.                    ////   
56. ////                                                                 ////   
57. //// mmcsd_print_cid(): Displays all data in the Card Identification ////   
58. ////                     Register. Note this only works on SD cards. ////   
59. ////                                                                 ////   
60. //// mmcsd_print_csd(): Displays all data in the Card Specific Data  ////   
61. ////                     Register. Note this only works on SD cards. ////   
62. ////                                                                 ////   
63. ////                                                                 ////   
64. //// --Non-User Functions--                                          ////   
65. ////                                                                 ////   
66. //// mmcsd_go_idle_state(): Sends the GO_IDLE_STATE command to the   ////   
67. ////                        SD/MMC.                                  ////   
68. //// mmcsd_send_op_cond(): Sends the SEND_OP_COND command to the     ////   
69. ////                        SD. Note this command only works on SD.  ////   
70. //// mmcsd_send_if_cond(): Sends the SEND_IF_COND command to the     ////   
71. ////                        SD. Note this command only works on SD.  ////   
72. //// mmcsd_sd_status(): Sends the SD_STATUS command to the SD. Note  ////   
73. ////                     This command only works on SD cards.        ////   
74. //// mmcsd_send_status(): Sends the SEND_STATUS command to the       ////   
75. ////                       SD/MMC.                                   ////   
76. //// mmcsd_set_blocklen(): Sends the SET_BLOCKLEN command along with ////   
77. ////                        the desired block length.                ////   
78. //// mmcsd_app_cmd(): Sends the APP_CMD command to the SD. This only ////   
79. ////                   works on SD cards and is used just before any ////   
80. ////                   SD-only command (e.g. send_op_cond()).        ////   
81. //// mmcsd_read_ocr(): Sends the READ_OCR command to the SD/MMC.     ////   
82. //// mmcsd_crc_on_off(): Sends the CRC_ON_OFF command to the SD/MMC  ////   
83. ////                      along with a bit to turn the CRC on/off.   ////   
84. //// mmcsd_send_cmd(): Sends a command and argument to the SD/MMC.   ////   
85. //// mmcsd_get_r1(): Waits for an R1 response from the SD/MMC and    ////   
86. ////                  then saves the response to a buffer.           ////   
87. //// mmcsd_get_r2(): Waits for an R2 response from the SD/MMC and    ////   
88. ////                  then saves the response to a buffer.           ////   
89. //// mmcsd_get_r3(): Waits for an R3 response from the SD/MMC and    ////   
90. ////                  then saves the response to a buffer.           ////   
91. //// mmcsd_get_r7(): Waits for an R7 response from the SD/MMC and    ////   
92. ////                  then saves the response to a buffer.           ////   
93. //// mmcsd_wait_for_token(): Waits for a specified token from the    ////   
94. ////                          SD/MMC.                                ////   
95. //// mmcsd_crc7(): Generates a CRC7 using a pointer to some data,    ////   
96. ////                and how many bytes long the data is.             ////   
97. //// mmcsd_crc16(): Generates a CRC16 using a pointer to some data,  ////   
98. ////                and how many bytes long the data is.             ////   
99. ////                                                                 ////   
100. /////////////////////////////////////////////////////////////////////////   
101. ////        (C) Copyright 2007 Custom Computer Services              ////   
102. //// This source code may only be used by licensed users of the CCS  ////   
103. //// C compiler.  This source code may only be distributed to other  ////   
104. //// licensed users of the CCS C compiler.  No other use,            ////   
105. //// reproduction or distribution is permitted without written       ////   
106. //// permission.  Derivative programs created using this software    ////   
107. //// in object code form are not restricted in any way.              ////   
108. /////////////////////////////////////////////////////////////////////////   
109.    
110. #ifndef MMCSD_C   
111. #define MMCSD_C   
112.    
113. /////////////////////   
114. ////             ////   
115. //// User Config ////   
116. ////             ////   
117. /////////////////////   
118.    
119. #ifndef MMCSD_PIN_SCL   
120.  #define MMCSD_PIN_SCL     PIN_C3 //o   
121.  #define MMCSD_PIN_SDI     PIN_C4 //i   
122.  #define MMCSD_PIN_SDO     PIN_C5 //o   
123.  #define MMCSD_PIN_SELECT  PIN_C2 //o   
124. #endif   
125.    
126. #use spi(MASTER, DI=MMCSD_PIN_SDI, DO=MMCSD_PIN_SDO, CLK=MMC
SD_PIN_SCL, BITS=8, MSB_FIRST, IDLE=1, stream=mmcsd_spi)   
127.    
128. ////////////////////////   
129. ////                ////   
130. //// Useful Defines ////   
131. ////                ////   
132. ////////////////////////   
133.    
134. enum MMCSD_err   
135.    {MMCSD_GOODEC = 0,   
136.    MMCSD_IDLE = 0x01,   
137.    MMCSD_ERASE_RESET = 0x02,   
138.    MMCSD_ILLEGAL_CMD = 0x04,   
139.    MMCSD_CRC_ERR = 0x08,   
140.    MMCSD_ERASE_SEQ_ERR = 0x10,   
141.    MMCSD_ADDR_ERR = 0x20,   
142.    MMCSD_PARAM_ERR = 0x40,   
143.    RESP_TIMEOUT = 0x80};   
144.    
145. #define GO_IDLE_STATE 0   
146. #define SEND_OP_COND 1   
147. #define SEND_IF_COND 8   
148. #define SEND_CSD 9   
149. #define SEND_CID 10   
150. #define SD_STATUS 13   
151. #define SEND_STATUS 13   
152. #define SET_BLOCKLEN 16   
153. #define READ_SINGLE_BLOCK 17   
154. #define WRITE_BLOCK 24   
155. #define SD_SEND_OP_COND 41   
156. #define APP_CMD 55   
157. #define READ_OCR 58   
158. #define CRC_ON_OFF 59   
159.    
160. #define IDLE_TOKEN 0x01   
161. #define DATA_START_TOKEN 0xFE   
162.    
163. #define MMCSD_MAX_BLOCK_SIZE 64   
164.    
165. ////////////////////////   
166. ///                  ///   
167. /// Global Variables ///   
168. ///                  ///   
169. ////////////////////////   
170.    
171. int g_mmcsd_buffer[MMCSD_MAX_BLOCK_SIZE];   
172.    
173. int1 g_CRC_enabled;   
174. int1 g_MMCSDBufferChanged;   
175.    
176. int32 g_mmcsdBufferAddress;   
177.    
178. enum _card_type{SD, MMC} g_card_type;   
179.    
180. /////////////////////////////   
181. ////                     ////   
182. //// Function Prototypes ////   
183. ////                     ////   
184. /////////////////////////////   
185.    
186. MMCSD_err mmcsd_init();   
187. MMCSD_err mmcsd_read_data(int32 address, int16 size, int* ptr);   
188. MMCSD_err mmcsd_read_block(int32 address, int16 size, int* ptr);   
189. MMCSD_err mmcsd_write_data(int32 address, int16 size, int* ptr);   
190. MMCSD_err mmcsd_write_block(int32 address, int16 size, int* ptr);   
191. MMCSD_err mmcsd_go_idle_state(void);   
192. MMCSD_err mmcsd_send_op_cond(void);   
193. MMCSD_err mmcsd_send_if_cond(int r7[]);   
194. MMCSD_err mmcsd_print_csd();   
195. MMCSD_err mmcsd_print_cid();   
196. MMCSD_err mmcsd_sd_status(int r2[]);   
197. MMCSD_err mmcsd_send_status(int r2[]);   
198. MMCSD_err mmcsd_set_blocklen(int32 blocklen);   
199. MMCSD_err mmcsd_read_single_block(int32 address);   
200. MMCSD_err mmcsd_write_single_block(int32 address);   
201. MMCSD_err mmcsd_sd_send_op_cond(void);   
202. MMCSD_err mmcsd_app_cmd(void);   
203. MMCSD_err mmcsd_read_ocr(int* r1);   
204. MMCSD_err mmcsd_crc_on_off(int1 crc_enabled);   
205. MMCSD_err mmcsd_send_cmd(int cmd, int32 arg);   
206. MMCSD_err mmcsd_get_r1(void);   
207. MMCSD_err mmcsd_get_r2(int r2[]);   
208. MMCSD_err mmcsd_get_r3(int r3[]);   
209. MMCSD_err mmcsd_get_r7(int r7[]);   
210. MMCSD_err mmcsd_wait_for_token(int token);   
211. unsigned int8 mmcsd_crc7(char *data, unsigned int8 length);   
212. unsigned int16 mmcsd_crc16(char *data, unsigned int8 length);   
213. void mmcsd_select();   
214. void mmcsd_deselect();   
215.    
216. /// Fast Functions ! ///   
217.    
218. MMCSD_err mmcsd_load_buffer(void);   
219. MMCSD_err mmcsd_flush_buffer(void);   
220. MMCSD_err mmcsd_move_buffer(int32 new_addr);   
221. MMCSD_err mmcsd_read_byte(int32 addr, char* data);   
222. MMCSD_err mmcsd_write_byte(int32 addr, char data);   
223.    
224. //////////////////////////////////   
225. ////                          ////   
226. //// Function Implementations ////   
227. ////                          ////   
228. //////////////////////////////////   
229.    
230. MMCSD_err mmcsd_init()   
231. {   
232.    int   
233.       i,   
234.       r1;   
235.    
236.    g_CRC_enabled = TRUE;   
237.    g_mmcsdBufferAddress = 0;   
238.    
239.    output_drive(MMCSD_PIN_SCL);   
240.    output_drive(MMCSD_PIN_SDO);   
241.    output_drive(MMCSD_PIN_SELECT);   
242.    output_float(MMCSD_PIN_SDI);   
243.    
244.    mmcsd_deselect();   
245.    delay_ms(15);   
246.       
247.    /* begin initialization */   
248.    i = 0;   
249.    do   
250.    {   
251.       delay_ms(1);   
252.       mmcsd_select();   
253.       r1=mmcsd_go_idle_state();   
254.       mmcsd_deselect();   
255.       i++;   
256.       if(i == 0xFF)   
257.       {   
258.          mmcsd_deselect();   
259.          return r1;   
260.       }   
261.    } while(!bit_test(r1, 0));   
262.    
263.    i = 0;   
264.    do   
265.    {   
266.       delay_ms(1);   
267.       mmcsd_select();   
268.       r1=mmcsd_send_op_cond();   
269.       mmcsd_deselect();   
270.       i++;   
271.       if(i == 0xFF)   
272.       {   
273.          mmcsd_deselect();   
274.          return r1;   
275.       }   
276.    } while(r1 & MMCSD_IDLE);   
277.    
278.    /* figure out if we have an SD or MMC */   
279.    mmcsd_select();   
280.    r1=mmcsd_app_cmd();   
281.    r1=mmcsd_sd_send_op_cond();   
282.    mmcsd_deselect();   
283.    
284.    /* an mmc will return an 0x04 here */   
285.    if(r1 == 0x04)   
286.       g_card_type = MMC;   
287.    else   
288.       g_card_type = SD;   
289.    
290.    /* set block length to 512 bytes */   
291.    mmcsd_select();   
292.    r1 = mmcsd_set_blocklen(MMCSD_MAX_BLOCK_SIZE);   
293.    if(r1 != MMCSD_GOODEC)   
294.    {   
295.       mmcsd_deselect();   
296.       return r1;   
297.    }   
298.    mmcsd_deselect();   
299.    
300.    /* turn CRCs off to speed up reading/writing */   
301.    mmcsd_select();   
302.    r1 = mmcsd_crc_on_off(0);   
303.    if(r1 != MMCSD_GOODEC)   
304.    {   
305.       mmcsd_deselect();   
306.       return r1;   
307.    }   
308.    mmcsd_deselect();   
309.    
310.    r1 = mmcsd_load_buffer();   
311.    
312.    return r1;   
313. }   
314.    
315. MMCSD_err mmcsd_read_data(int32 address, int16 size, int* ptr)   
316. {   
317.    MMCSD_err r1;   
318.    int16 i;  // counter for loops   
319.    
320.    for(i = 0; i < size; i++)   
321.    {   
322.       r1 = mmcsd_read_byte(address++, ptr++);   
323.       if(r1 != MMCSD_GOODEC)   
324.          return r1;   
325.    }   
326.       
327.    return MMCSD_GOODEC;   
328. }   
329.    
330. MMCSD_err mmcsd_read_block(int32 address, int16 size, int* ptr)   
331. {     
332.    MMCSD_err ec;   
333.    int16 i; // counter for loops   
334.    
335.    // send command   
336.    mmcsd_select();   
337.    ec = mmcsd_read_single_block(address);   
338.    if(ec != MMCSD_GOODEC)   
339.    {   
340.       mmcsd_deselect();   
341.       return ec;   
342.    }   
343.       
344.    // wait for the data start token   
345.    ec = mmcsd_wait_for_token(DATA_START_TOKEN);   
346.    if(ec != MMCSD_GOODEC)   
347.    {   
348.       mmcsd_deselect();   
349.       return ec;   
350.    }   
351.       
352.    // read in the data   
353.    for(i = 0; i < size; i += 1)   
354.       ptr[i] = spi_xfer(mmcsd_spi, 0xFF);   
355.    
356.    if(g_CRC_enabled)   
357.    {   
358.       /* check the crc */   
359.       if(make16(spi_xfer(mmcsd_spi, 0xFF), spi_xfer(mmcsd_spi, 0xFF)) != mmcsd_
crc16(g_mmcsd_buffer, MMCSD_MAX_BLOCK_SIZE))   
360.       {   
361.          mmcsd_deselect();   
362.          return MMCSD_CRC_ERR;   
363.       }   
364.    }   
365.    else   
366.    {   
367.       /* have the card transmit the CRC, but ignore it */   
368.       spi_xfer(mmcsd_spi, 0xFF);   
369.       spi_xfer(mmcsd_spi, 0xFF);   
370.    }   
371.    mmcsd_deselect();   
372.    
373.    return MMCSD_GOODEC;   
374. }   
375.    
376. MMCSD_err mmcsd_write_data(int32 address, int16 size, int* ptr)   
377. {   
378.    MMCSD_err ec;   
379.    int16 i;  // counter for loops   
380.      
381.    for(i = 0; i < size; i++)   
382.    {   
383.       ec = mmcsd_write_byte(address++, *ptr++);   
384.       if(ec != MMCSD_GOODEC)   
385.          return ec;   
386.    }   
387.       
388.    return MMCSD_GOODEC;   
389. }   
390.    
391. MMCSD_err mmcsd_write_block(int32 address, int16 size, int* ptr)   
392. {    
393.    MMCSD_err ec;   
394.    int16 i;   
395.    
396.    // send command   
397.    mmcsd_select();   
398.    ec = mmcsd_write_single_block(address);   
399.    if(ec != MMCSD_GOODEC)   
400.    {   
401.       mmcsd_deselect();   
402.       return ec;   
403.    }   
404.       
405.    // send a data start token   
406.    spi_xfer(mmcsd_spi, DATA_START_TOKEN);   
407.       
408.    // send all the data   
409.    for(i = 0; i < size; i += 1)   
410.       spi_xfer(mmcsd_spi, ptr[i]);   
411.    
412.    // if the CRC is enabled we have to calculate it, otherwise just send an 0xFFFF   
413.    if(g_CRC_enabled)   
414.       spi_xfer(mmcsd_spi, mmcsd_crc16(ptr, size));   
415.    else   
416.    {   
417.       spi_xfer(mmcsd_spi, 0xFF);   
418.       spi_xfer(mmcsd_spi, 0xFF);   
419.    }   
420.       
421.    // get the error code back from the card; "data accepted" is 0bXXX00101   
422.    ec = mmcsd_get_r1();   
423.    if(ec & 0x0A)   
424.    {   
425.       mmcsd_deselect();   
426.       return ec;   
427.    }   
428.       
429.    // wait for the line to go back high, this indicates that the write is complete   
430.    while(spi_xfer(mmcsd_spi, 0xFF) == 0);   
431.    mmcsd_deselect();   
432.    
433.    return MMCSD_GOODEC;   
434. }   
435.    
436. MMCSD_err mmcsd_go_idle_state(void)   
437. {   
438.    mmcsd_send_cmd(GO_IDLE_STATE, 0);   
439.       
440.    return mmcsd_get_r1();   
441. }   
442.    
443. MMCSD_err mmcsd_send_op_cond(void)   
444. {   
445.    mmcsd_send_cmd(SEND_OP_COND, 0);   
446.       
447.    return mmcsd_get_r1();   
448. }   
449.    
450. MMCSD_err mmcsd_send_if_cond(int r7[])   
451. {   
452.    mmcsd_send_cmd(SEND_IF_COND, 0x45A);   
453.    
454.    return mmcsd_get_r7(r7);   
455. }   
456.    
457. MMCSD_err mmcsd_print_csd()   
458. {     
459.    int   
460.       buf[16],   
461.       i,   
462.       r1;   
463.    
464.    // MMCs don't support this command   
465.    if(g_card_type == MMC)   
466.       return MMCSD_PARAM_ERR;   
467.    
468.    mmcsd_select();      
469.    mmcsd_send_cmd(SEND_CSD, 0);   
470.    r1 = mmcsd_get_r1();   
471.    if(r1 != MMCSD_GOODEC)   
472.    {   
473.       mmcsd_deselect();   
474.       return r1;   
475.    }   
476.       
477.    r1 = mmcsd_wait_for_token(DATA_START_TOKEN);   
478.    if(r1 != MMCSD_GOODEC)   
479.    {   
480.       mmcsd_deselect();   
481.       return r1;   
482.    }   
483.    
484.    for(i = 0; i < 16; i++)   
485.       buf[i] = spi_xfer(mmcsd_spi, 0xFF);   
486.    mmcsd_deselect();   
487.    
488.    printf("\r\nCSD_STRUCTURE: %X", (buf[0] & 0x0C) >> 2);   
489.    printf("\r\nTAAC: %X", buf[1]);   
490.    printf("\r\nNSAC: %X", buf[2]);   
491.    printf("\r\nTRAN_SPEED: %X", buf[3]);   
492.    printf("\r\nCCC: %lX", (make16(buf[4], buf[5]) & 0xFFF0) >> 4);   
493.    printf("\r\nREAD_BL_LEN: %X", buf[5] & 0x0F);      
494.    printf("\r\nREAD_BL_PARTIAL: %X", (buf[6] & 0x80) >> 7);   
495.    printf("\r\nWRITE_BLK_MISALIGN: %X", (buf[6] & 0x40) >> 6);   
496.    printf("\r\nREAD_BLK_MISALIGN: %X", (buf[6] & 0x20) >> 5);   
497.    printf("\r\nDSR_IMP: %X", (buf[6] & 0x10) >> 4);   
498.    printf("\r\nC_SIZE: %lX", (((buf[6] & 0x03) << 10) | (buf[7] << 2) | ((buf[8] & 0x
C0) >> 6)));   
499.    printf("\r\nVDD_R_CURR_MIN: %X", (buf[8] & 0x38) >> 3);   
500.    printf("\r\nVDD_R_CURR_MAX: %X", buf[8] & 0x07);   
501.    printf("\r\nVDD_W_CURR_MIN: %X", (buf[9] & 0xE0) >> 5);   
502.    printf("\r\nVDD_W_CURR_MAX: %X", (buf[9] & 0x1C) >> 2);   
503.    printf("\r\nC_SIZE_MULT: %X", ((buf[9] & 0x03) << 1) | ((buf[10] & 0x80) >> 7
));   
504.    printf("\r\nERASE_BLK_EN: %X", (buf[10] & 0x40) >> 6);   
505.    printf("\r\nSECTOR_SIZE: %X", ((buf[10] & 0x3F) << 1) | ((buf[11] & 0x80) >> 
7));   
506.    printf("\r\nWP_GRP_SIZE: %X", buf[11] & 0x7F);   
507.    printf("\r\nWP_GRP_ENABLE: %X", (buf[12] & 0x80) >> 7);   
508.    printf("\r\nR2W_FACTOR: %X", (buf[12] & 0x1C) >> 2);   
509.    printf("\r\nWRITE_BL_LEN: %X", ((buf[12] & 0x03) << 2) | ((buf[13] & 0xC0) 
>> 6));   
510.    printf("\r\nWRITE_BL_PARTIAL: %X", (buf[13] & 0x20) >> 5);   
511.    printf("\r\nFILE_FORMAT_GRP: %X", (buf[14] & 0x80) >> 7);   
512.    printf("\r\nCOPY: %X", (buf[14] & 0x40) >> 6);   
513.    printf("\r\nPERM_WRITE_PROTECT: %X", (buf[14] & 0x20) >> 5);   
514.    printf("\r\nTMP_WRITE_PROTECT: %X", (buf[14] & 0x10) >> 4);   
515.    printf("\r\nFILE_FORMAT: %X", (buf[14] & 0x0C) >> 2);   
516.    printf("\r\nCRC: %X", buf[15]);   
517.    
518.    return r1;   
519. }   
520.    
521. MMCSD_err mmcsd_print_cid()   
522. {   
523.    int   
524.       buf[16],   
525.       i,   
526.       r1;   
527.    
528.    // MMCs don't support this command   
529.    if(g_card_type == MMC)   
530.       return MMCSD_PARAM_ERR;   
531.       
532.    mmcsd_select();   
533.    mmcsd_send_cmd(SEND_CID, 0);   
534.    r1 = mmcsd_get_r1();   
535.    if(r1 != MMCSD_GOODEC)   
536.    {   
537.       mmcsd_deselect();   
538.       return r1;   
539.    }   
540.    r1 = mmcsd_wait_for_token(DATA_START_TOKEN);   
541.    if(r1 != MMCSD_GOODEC)   
542.    {   
543.       mmcsd_deselect();   
544.       return r1;   
545.    }   
546.       
547.    for(i = 0; i < 16; i++)   
548.       buf[i] = spi_xfer(mmcsd_spi, 0xFF);   
549.    mmcsd_deselect();   
550.       
551.    printf("\r\nManufacturer ID: %X", buf[0]);   
552.    printf("\r\nOEM/Application ID: %c%c", buf[1], buf[2]);   
553.    printf("\r\nOEM/Application ID: %c%c%c%c%c", buf[3], buf[4], buf[5], buf[6], b
uf[7]);   
554.    printf("\r\nProduct Revision: %X", buf[8]);   
555.    printf("\r\nSerial Number: %X%X%X%X", buf[9], buf[10], buf[11], buf[12]);   
556.    printf("\r\nManufacturer Date Code: %X%X", buf[13] & 0x0F, buf[14]);   
557.    printf("\r\nCRC-7 Checksum: %X", buf[15]);   
558.    
559.    return r1;   
560. }   
561.    
562. MMCSD_err mmcsd_sd_status(int r2[])   
563. {   
564.    int i;   
565.    
566.    mmcsd_select();   
567.    mmcsd_send_cmd(APP_CMD, 0);   
568.    r2[0]=mmcsd_get_r1();   
569.    mmcsd_deselect();   
570.    
571.    mmcsd_select();   
572.    mmcsd_send_cmd(SD_STATUS, 0);   
573.    
574.    for(i = 0; i < 64; i++)   
575.       spi_xfer(mmcsd_spi, 0xFF);         
576.    
577.    mmcsd_deselect();   
578.    
579.    return mmcsd_get_r2(r2);   
580. }   
581.    
582. MMCSD_err mmcsd_send_status(int r2[])   
583. {   
584.    mmcsd_send_cmd(SEND_STATUS, 0);      
585.       
586.    return mmcsd_get_r2(r2);   
587. }   
588.    
589. MMCSD_err mmcsd_set_blocklen(int32 blocklen)   
590. {   
591.    mmcsd_send_cmd(SET_BLOCKLEN, blocklen);   
592.       
593.    return mmcsd_get_r1();   
594. }   
595.    
596. MMCSD_err mmcsd_read_single_block(int32 address)   
597. {   
598.    mmcsd_send_cmd(READ_SINGLE_BLOCK, address);   
599.       
600.    return mmcsd_get_r1();   
601. }   
602.    
603. MMCSD_err mmcsd_write_single_block(int32 address)   
604. {   
605.    mmcsd_send_cmd(WRITE_BLOCK, address);   
606.      
607.    return mmcsd_get_r1();   
608. }   
609.    
610. MMCSD_err mmcsd_sd_send_op_cond(void)   
611. {   
612.    mmcsd_send_cmd(SD_SEND_OP_COND, 0);   
613.       
614.    return mmcsd_get_r1();   
615. }   
616.    
617. MMCSD_err mmcsd_app_cmd(void)   
618. {   
619.    mmcsd_send_cmd(APP_CMD, 0);   
620.       
621.    return mmcsd_get_r1();   
622. }   
623.    
624. MMCSD_err mmcsd_read_ocr(int r3[])   
625. {   
626.    mmcsd_send_cmd(READ_OCR, 0);   
627.       
628.    return mmcsd_get_r3(r3);   
629. }   
630.    
631. MMCSD_err mmcsd_crc_on_off(int1 crc_enabled)   
632. {   
633.    mmcsd_send_cmd(CRC_ON_OFF, crc_enabled);   
634.       
635.    g_CRC_enabled = crc_enabled;   
636.       
637.    return mmcsd_get_r1();   
638. }   
639.    
640. MMCSD_err mmcsd_send_cmd(int cmd, int32 arg)   
641. {      
642.    int packet[6]; // the entire command, argument, and crc in one variable   
643.      
644.    // construct the packet   
645.    // every command on an SD card is or'ed with 0x40   
646.    packet[0] = cmd | 0x40;   
647.    packet[1] = make8(arg, 3);   
648.    packet[2] = make8(arg, 2);   
649.    packet[3] = make8(arg, 1);   
650.    packet[4] = make8(arg, 0);   
651.    
652.    // calculate the crc if needed   
653.    if(g_CRC_enabled)   
654.       packet[5] = mmcsd_crc7(packet, 5);   
655.    else   
656.       packet[5] = 0xFF;   
657.    
658.    // transfer the command and argument, with an extra 0xFF hacked in there   
659.    spi_xfer(mmcsd_spi, packet[0]);   
660.    spi_xfer(mmcsd_spi, packet[1]);   
661.    spi_xfer(mmcsd_spi, packet[2]);   
662.    spi_xfer(mmcsd_spi, packet[3]);   
663.    spi_xfer(mmcsd_spi, packet[4]);   
664.    spi_xfer(mmcsd_spi, packet[5]);   
665.    
666.    return MMCSD_GOODEC;   
667. }   
668.    
669. MMCSD_err mmcsd_get_r1(void)   
670. {   
671.    int   
672.       response = 0,  // place to hold the response coming back from the SPI line   
673.       timeout = 0xFF; // maximum amount loops to wait for idle before getting impatie
nt and leaving the function with an error code   
674.        
675.    // loop until timeout == 0   
676.    while(timeout)   
677.    {   
678.       // read what's on the SPI line   
679.       //  the SD/MMC requires that you leave the line high when you're waiting for dat
a from it   
680.       response = spi_xfer(mmcsd_spi, 0xFF);   
681.          
682.       // check to see if we got a response   
683.       if(response != 0xFF)   
684.       {      
685.          // fill in the response that we got and leave the function   
686.          return response;   
687.       }   
688.    
689.       // wait for a little bit longer   
690.       timeout--;   
691.    }   
692.       
693.    // for some reason, we didn't get a response back from the card   
694.    //  return the proper error codes   
695.    return RESP_TIMEOUT;   
696. }   
697.    
698. MMCSD_err mmcsd_get_r2(int r2[])   
699. {   
700.    r2[1] = mmcsd_get_r1();   
701.       
702.    r2[0] = spi_xfer(mmcsd_spi, 0xFF);   
703.       
704.    return 0;   
705. }   
706.    
707. MMCSD_err mmcsd_get_r3(int r3[])   
708. {   
709.    return mmcsd_get_r7(r3);   
710. }   
711.    
712. MMCSD_err mmcsd_get_r7(int r7[])   
713. {   
714.    int i;   // counter for loop   
715.       
716.    // the top byte of r7 is r1   
717.    r7[4]=mmcsd_get_r1();   
718.       
719.    // fill in the other 4 bytes   
720.    for(i = 0; i < 4; i++)   
721.       r7[3 - i] = spi_xfer(mmcsd_spi, 0xFF);   
722.    
723.    return r7[4];   
724. }   
725.    
726. MMCSD_err mmcsd_wait_for_token(int token)   
727. {   
728.    MMCSD_err r1;   
729.       
730.    // get a token   
731.    r1 = mmcsd_get_r1();   
732.       
733.    // check to see if the token we recieved was the one that we were looking for   
734.    if(r1 == token)   
735.       return MMCSD_GOODEC;   
736.       
737.    // if that wasn't right, return the error   
738.    return r1;      
739. }   
740.    
741. unsigned int8 mmcsd_crc7(char *data, unsigned int8 length)   
742. {   
743.    unsigned int8 i, ibit, c, crc;   
744.        
745.    crc = 0x00;                                                                // Set initial value   
746.    
747.    for (i = 0; i < length; i++, data++)   
748.    {   
749.       c = *data;   
750.    
751.       for (ibit = 0; ibit < 8; ibit++)   
752.       {   
753.          crc = crc << 1;   
754.          if ((c ^ crc) & 0x80) crc = crc ^ 0x09;                              // ^ is XOR   
755.          c = c << 1;   
756.       }   
757.    
758.        crc = crc & 0x7F;   
759.    }   
760.    
761.    shift_left(&crc, 1, 1);                                                    // MMC card stores the result 
in the top 7 bits so shift them left 1   
762.                                                                               // Should shift in a 1 not a 0 as one 
of the cards I have won't work otherwise   
763.    return crc;   
764. }   
765.    
766. unsigned int16 mmcsd_crc16(char *data, unsigned int8 length)   
767. {   
768.    unsigned int8 i, ibit, c;   
769.    
770.    unsigned int16 crc;   
771.    
772.    crc = 0x0000;                                                                // Set initial value   
773.    
774.    for (i = 0; i < length; i++, data++)   
775.    {   
776.       c = *data;   
777.    
778.       for (ibit = 0; ibit < 8; ibit++)   
779.       {   
780.          crc = crc << 1;   
781.          if ((c ^ crc) & 0x8000) crc = crc ^ 0x1021;                              // ^ is XOR   
782.          c = c << 1;   
783.       }   
784.    
785.        crc = crc & 0x7FFF;   
786.    }   
787.    
788.    shift_left(&crc, 2, 1);                                                    // MMC card stores the result 
in the top 7 bits so shift them left 1   
789.                                                                               // Should shift in a 1 not a 0 as one 
of the cards I have won't work otherwise   
790.    return crc;   
791. }   
792.    
793. void mmcsd_select()   
794. {   
795.    output_low(MMCSD_PIN_SELECT);   
796. }   
797.    
798. void mmcsd_deselect()   
799. {   
800.    spi_xfer(mmcsd_spi, 0xFF);   
801.    output_high(MMCSD_PIN_SELECT);   
802. }   
803.    
804. MMCSD_err mmcsd_load_buffer(void)   
805. {   
806.    g_MMCSDBufferChanged = FALSE;   
807.    return(mmcsd_read_block(g_mmcsdBufferAddress, MMCSD_MAX_BLOCK_SI
ZE, g_mmcsd_buffer));   
808. }   
809.    
810. MMCSD_err mmcsd_flush_buffer(void)   
811. {   
812.    if (g_MMCSDBufferChanged)   
813.    {   
814.       g_MMCSDBufferChanged = FALSE;   
815.       return(mmcsd_write_block(g_mmcsdBufferAddress, MMCSD_MAX_BLOCK_
SIZE, g_mmcsd_buffer));   
816.    }   
817.    return(0);  //ok   
818. }   
819.    
820. MMCSD_err mmcsd_move_buffer(int32 new_addr)   
821. {   
822.    MMCSD_err ec = MMCSD_GOODEC;   
823.    int32   
824.       //cur_block,   
825.       new_block;   
826.       
827.    // make sure we're still on the same block   
828.    //cur_block = g_mmcsdBufferAddress - (g_mmcsdBufferAddress % MMCSD_M
AX_BLOCK_SIZE);   
829.    new_block = new_addr - (new_addr % MMCSD_MAX_BLOCK_SIZE);   
830.       
831.    //if(cur_block != new_block)   
832.    if(g_mmcsdBufferAddress != new_block)   
833.    {   
834.       // dump the old buffer   
835.       if (g_MMCSDBufferChanged)   
836.       {   
837.          ec = mmcsd_flush_buffer();   
838.          if(ec != MMCSD_GOODEC)   
839.             return ec;   
840.          g_MMCSDBufferChanged = FALSE;   
841.       }   
842.             
843.       // figure out the best place for a block   
844.       g_mmcsdBufferAddress = new_block;   
845.    
846.       // load up a new buffer   
847.       ec = mmcsd_load_buffer();   
848.    }   
849.       
850.    return ec;   
851. }   
852.    
853. MMCSD_err mmcsd_read_byte(int32 addr, char* data)   
854. {   
855.    MMCSD_err ec;   
856.       
857.    ec = mmcsd_move_buffer(addr);   
858.    if(ec != MMCSD_GOODEC)   
859.    {   
860.      return ec;   
861.    }   
862.     
863.    *data = g_mmcsd_buffer[addr % MMCSD_MAX_BLOCK_SIZE];   
864.    
865.    return MMCSD_GOODEC;   
866. }   
867.    
868. MMCSD_err mmcsd_write_byte(int32 addr, char data)   
869. {     
870.    MMCSD_err ec;   
871.    ec = mmcsd_move_buffer(addr);   
872.    if(ec != MMCSD_GOODEC)   
873.      return ec;   
874.       
875.    g_mmcsd_buffer[addr % MMCSD_MAX_BLOCK_SIZE] = data;   
876.       
877.    g_MMCSDBufferChanged = TRUE;   
878.    
879.    return MMCSD_GOODEC;   
880. }   
881.    
882. #endif   

You might also like