Professional Documents
Culture Documents
242
1. Tổng quan về 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.
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…
Để 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.
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.
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.
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.
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…
Để 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
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
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
Tab Analog
Tab này cho phép ta thiết lập các thông số cho các bộ Capture/Comparator/PWM.
‐ 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.
‐ 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.
‐ 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
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.
#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 .
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( ) ;
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
+ 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 :
+ 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 .
*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 .
+ 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 .
+ 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 .
+ 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 .
+ 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 .
+ 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
+ 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 .
+ 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 :
+ #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)
+ 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;
+ 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 :
+ 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 :
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
6 / # USE :
+ 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)
#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 )
I / CÁC HÀM XỬ LÝ SỐ :
+ Bao gồm các hàm:
+ 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.
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
+ Để 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 .
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
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
+ 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 .
+ 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
+ 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 ;
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 .
+ 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 .
+ Đâ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 )
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).
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)
{
}
}
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 .
#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
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 .
#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ẽ …
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.
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 :
* 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.
Để 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ó.
- 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)
- 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.
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)
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.
Đặ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
#include "16f877a.h"
#include "def_877a.h"
int8 sec,min,hrs,day,month,yr,dow;
#include <stddef.h>
// 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)
Thư viện ds1307.c:
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();
}
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);
}
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;
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);
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.
Để 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
#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);
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