Professional Documents
Culture Documents
Mục lục
timer.h timer.c
#ifndef __TIMER_H_ #include "timer.h"
#define __TIMER_H_ int counter0;
int counter1;
#include "LED7\led7.h" void init_tim er0();
{
extern int counter0; IE |= 0x20;
void init_timer0(); TR0 = 1;
void init_timer1(); ..........................
............... }
void init_tim er1()
#endif {
..........................
}
main.c
void private_function()
#include "TIMER\timer.h" {
#include "LED7\led7.h" ..........................
#include <REG51F.H> }
void main()
{
init_main();
init_timer0();
....................
while1(){}
}
led7.h led7.c
#ifndef __LED7_H_ #include "led7.h"
#define __LED7_H_ unsigned char led7_buff[8];
Để có thể tổ chức tốt chương trình, thông thường mỗi module ta sẽ chia làm 2
file:
v File .h : Khai các hàm và extern các biến toàn cục.
v File .c : Hiện thực các hàm được khai báo trong file .h.
Khi muốn sử dụng module này, ta chỉ cần include file .h là có thể gọi được các
hàm được khai báo trong file .h.
Bước 1 : Click vào file c51v815.exe trong CD đi kèm, màn hình sau sẽ xuất hiện
Bước 3 : Click chọn I agree và chọn Next, màn hình sau sẽ xuất hiện.
Bước 4 : Bạn có thể để mặc định và chọn Next. Tuy nhiên bạn có thể thay đổi
Destinatioin Folder, là thư mục chứa 1 số thư viện hỗ trợ trong việc lập trình trên Keil C.
Màn hình sau xuất hiện để bạn điền 1 số thông tin cá nhân.
Bước 6 : Khi cài đặt xong, màn hình sau xuất hiện và bạn nhấn Finish để hoàn tất
quá trình cài đặt.
Bước 7 : Trang web sau hiển thị để giới thiệu về KeilC phiên bản này.
Các bước cấu hình cho Keil C để dịch chương trình ra file Hex sẽ được trình bày
trong bài thực hành tiếp theo.
Đây là chương trình dùng để nạp file Hex cho 89V51. Để cài đặt bạn làm theo các
bước sau :
Bước 1 : Double click vào file FlashMagic.exe, màn hình sau sẽ xuất hiện
Bước 4 : Bạn có thể để mặc định và chọn Next, hoặc thay đổi đường dẫn của thư mực cài
đặt nếu bạn muốn. Khi click Next màn hình sau sẽ xuất hiện.
Bước 8 : Khi cài đặt xong màn hình sau sẽ xuất hiện, bạn nhấn Finish để kết thúc cài đặt.
Bước 1 : Khởi động Flash Magic từ màn hình Desktop (hoặc vào Start\Program
Files\Flash Magic và chọn Flash Magic), màn hình sau sẽ xuất hiện
Bước 2 : Bạn chọn Baud Rate là 9600, Device là chip tương ứng mà bạn dùng.
Check chọn Verify after programming và Erase blocks used by Hex File.
Bước 3 : Chọn menu Option và chọn Advance Option, màn hình sau sẽ xuất hiện :
Bước 4 : Chọn qua tab Hardware Config và check chọn Assert DTR and RTS
while COM Port open. Nhấn OK để đóng cửa số này lại.
Bước 5 : Xem cổng COM đang là cổng nào. Bạn thu nhỏ Flash Magic lại, trên
màn hình Desktop, từ biểu tượng My Computer, click phải chuột và chọn Manage.
Bước 6 : Màn hình sau xuất hiện, bạn chọn Device Manager.
Bước 7 : Cắm dây cổng COM nếu bạn đang dùng USB to COM, mở rộng Ports
(COM &LPT), bạn sẽ biết được tên cổng COM đang dùng (trong ví dụ này là COM 9).
Bước 9 : Kích hoạt lại Flash Magic và chọn cổng COM tương ứng.
Bước 3 : Chọn đường dẫn và gõ tên project và khung File name, chọn Save,
khung cửa số sau đây xuất hiện.
Bước 4 : Chọn chip tương ứng với board của bạn, trong board của chúng tôi sử
dụng 89V51RB2. Browse tới NXP, và chọn P89V51RB2
Bước 5 : Sau khi nhấn OK, màn hình sau sẽ xuất hiện, bạn chọn No
Bước 7 : Cấu hình để tạo ra file Hex, bạn chọn menu Project, chọn Option for
Target
Bước 10 : Chép thư file REG51F.H (nếu cài mặc định sẽ nằm trong thư mục
C:\Keil\C51\INC\Philips hoặc bạn có thể copy trong CD đi kèm) là file define các thanh
ghi của 89V51 vào cùng cấp với file project.
Bước 11 : Bạn có thể chọn và nhấn Delete Source Group1 do Keil C tự tạo ra, và
tự tạo Group mới cho mình. Chọn Target, click phải chuột và chọn New Group, 1 group
mới được tạo ra, đổi tên thành Main cho gợi nhớ. Trong tất cả các bài demo, group này sẽ
chứa file main.c, là file chính để chạy chương trình.
Bước 11 : Tạo mới 1 file bằng cách chọn menu File rồi chọn New. Chọn tiếp
menu File rồi chọn Save, khung cửa sổ sau sẽ hiện lên, bạn nhập tên file vào và nhấn
Save.
Bước 12 : Click phải vào group Main, và chọn Add Files to Group Main
Bước 13 : Chỉ đường dẫn đển file main.c mà bạn vừa tạo, nhấn Add rồi nhấn tiếp
Close.
Bước 14 : Click vào dấu + của Main, bạn sẽ thấy file main.c đã được thêm vào
project
Các nút nhấn được nối với P3 (Port 3) của vi điều khiển theo trình tự nút 1 nối với
P3.0, nút 2 nối với P3.1, v.v…. Các nút nhấn tích cực 0.
Đây là kiểu kết nối phím đơn giản nhất, các giải pháp kết nối phím khác tốt hơn
sẽ được trình bày ở bài chống rung cho phím.
Các đèn led được nối với P1 (Port 1) của vi điều khiển, đèn 1 nối với P1.0, đèn 2
nối với P1.1, v,v…. Các đèn tích cực mức 1.
void main()
{
while(1)
{
P1 = ~P3;
}
}
Do P3 tích cực mức 0, nút 1 được nhấn giá trị của nó sẽ là 1111 1110 = 0xFE,
~P3 lúc đó sẽ là 0000 0001 = 0x01. P1 tích cực mức 1 nên P1 = 0x01 thì đèn 1 sẽ sáng.
Chọn menu Project và chọn Build Target hoặc nhấn F7 để dịch chương trình. Nếu
có lỗi, Keil C sẽ thông báo để bạn sửa, nếu không có lỗi file Hex sẽ được tạo ra cùng cấp
với file project.
Bạn dùng thêm tổ hợp phím Shift để có thể chọn được nhiều Pages, và nhấn
Erase. Khi màn hình sau hiện lên, bạn nhấn nút Reset trên board để tiếp tục (nên nhấn và
giữ trong khoảng 2 giây).
Bước 6 : Nhấn Browse để chọn đường dẫn tới file Hex cần nạp rồi nhấn Start.
Bước 7 : Khi thấy Status là Finished, bạn có thể nhấn nút Reset trên board để bắt
đầu chạy chương trình của bạn.
Để khắc phục lỗi này, bạn chọn vào menu Opition/ Advance Option , chọn qua
tab Hardware Config và check chọn Assert DTR and RTS while COM Port open. Nhấn
OK để đóng cửa số này lại.
Ở lỗi này, Flash Magic hiển thị thông báo yêu cầu bạn nhấn reset, nhưng khi nhấn
reset thì thông báo trên xuất hiện, bạn chỉnh lại tốc độ Baud là 9600.
Ở lỗi này, Flash Magic cũng chạy được tới phần chờ nhấn nút Reset, sau khi ấn
nút Reset thì thông báo này xuất hiện.
Để khắc phục lỗi này, trước khi nạp bạn nhấn đè nút Reset, mọi thao tác vẫn diễn
ra bình thường cho đến khi cửa sổ yêu cầu bạn nhấn nút Reset, bạn giữa thêm vài giây rồi
thả ra. Cách thứ 2 là rút nguồn của board ra, cho đến khi Flash Magic yêu cầu bạn nhấn
Reset thì cắm nguồn vào.
Nếu như đã kiểm tra qua hết các lỗi kia mà chương trình vẫn chưa nạp được, thì
nguyên nhân có thể là bạn cắm ngược chiều mạch nạp, phần mềm Flash Magic bị lỗi, IC
MAX 232 bị hư hoặc hư vi điều khiển.
P0 : Data P2 : Select
P0.0 : a P2.0 : Led 1
P0.1 : b P2.0 : Led 2
P0.2 : c P2.0 : Led 3
P0.3 : d P2.0 : Led 4
P0.4 : e P2.0 : Led 5
P0.5 : f P2.0 : Led 6
P0.6 : g P2.0 : Led 7
P0.7 : dot P2.0 : Led 8
Với kết nối phần cứng như vậy, để hiện thị số 5 thì dữ liệu xuất ra sẽ là 0x6D
(0110 1101). Tương tự, giá trị cho các số từ 0 đến 9 sẽ là :
unsigned char led7_data[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void main()
{
init_main();
while(1)
{
if((P3 & 0x01) == 0) // P3 = 1111 1110
{
P0 = led7_data[0];
P2 = 0x01;
}
else if((P3 & 0x02) == 0)// P3 = 1111 1101
{
P0 = led7_data[1];
P2 = 0x02;
}
else if((P3 & 0x04) == 0)// P3 = 1111 1011
{
P0 = led7_data[2];
P2 = 0x04;
}
else if((P3 & 0x08) == 0) // P3 = 1111 0111
{
P0 = led7_data[3];
P2 = 0x08;
}
else if((P3 & 0x10) == 0) // P3 = 1110 1111
{
P0 = led7_data[4];
P2 = 0x10;
}
else if((P3 & 0x20) == 0) // P3 = 1101 1111
{
P0 = led7_data[5];
P2 = 0x20;
}
else if((P3 & 0x40) == 0) // P3 = 1011 1111
{
P0 = led7_data[6];
P2 = 0x40;
}
else if((P3 & 0x80) == 0) // P3 = 0111 1111
{
P0 = led7_data[7];
P2 = 0x80;
}
else
{
P0 = 0x00;
P2 = 0x00;
}
Các bước dịch và nạp chương trình bạn xem lại ở bài trước. Code mẫu của bài thí
nghiệm này bạn có thể tham khảo ở thư mục Bài 3 đính kèm trong CD này.
Khi gọi sai tên 1 hàm, compiler của KeilC sẽ vẫn tạo ra file hex, bạn nên lưu ý
các warning để khắc phục lỗi.
Không gọi hàm đã được định nghĩa : Để 1 module có thể dùng lại, thông thường
ta hiện thực nhiều hàm liên quan đến nó, nhưng trong 1 ứng dụng không gọi hết tất cả các
hàm đó, sẽ có thông báo về trường hợp này. Tuy nhiên đây không phải là lỗi, file hex
được tạo ra vẫn sẽ chạy đúng. Thông báo dạng này như sau :
Như thông báo ở trên, hàm start_timer0() và start_timer1() không được gọi trong
bất kì hàm nào.
// reinitialize
TH0 = (-10000/256);
TL0 = (-10000%256);
void start_timer0();
void start_timer1();
void stop_timer0();
void stop_timer1();
// reinitialize
TH1 = (-1000/256);
TL1 = (-1000%256);
Phần hiện thực các bạn xem trong project Bai 4 để biết thêm chi tiết.
delay_ms(duration);
}
Chi tiết code của bài thí nghiệm này, bạn xem thêm trong thư mục Bài 5 trong CD
đi kèm.
Hình trên minh hoạ mức điện áp của 1 phím nhấn tích cực mức 0, ở trạng thái
bình thường, điện áp vi điều khiển nhận vào là 5V còn khi nhấn là 0V. Tuy nhiên, do độ
rung cơ học của phím, tại thời điểm vừa nhấn xuống, điện áp sẽ không ổn định trong 1
khoảng thời gian, trước khi ổn định ở mức 0V. Hiệu tượng này gọi là rung phím.
Mặc dù khoảng thời gian điện áp ở mức 0 trong giai đoạn rung phím là nhỏ nhưng
cũng đủ để vi điều khiển nhận được. Vì vậy khi ta xét nếu điện áp là 0 thì gọi hàm func()
thì hàm này sẽ được gọi rất nhiều lần, là điều mà ta không mong muốn. Để khác phục
hiện tượng rung phím, có 2 hướng giải quyết : dùng phần cứng và phần mềm.
Về giải pháp phần cứng : thay vì mắc đơn giản như kit thí nghiệm này (xem lại sơ
đồ ở Bài 2), ta có thể dùng thêm tụ điện để hạn chế việc thay đổi điện áp đột ngột, sơ đồ
nguyên lý như sau:
Ở sơ đồ trên, khi không nhấn là mức 1, khi nhấn là mức 0. Phím nhấn trên tích
cực mức 0. Mạch ở trên còn gọi là mạch RC.
Nếu nút nhấn có 2 cực (3 chân), ta có thể chọn giải pháp dùng mạch RS flip flop,
đây là mạch phần cứng chống rung tốt nhất, sơ đồ nguyên lý như sau:
Về giải pháp phần mềm : Ta sẽ định kì đọc tín hiệu từ nút nhấn, cho đến khi nào
chúng trùng nhau n lần thì mới xử lý. Hình dưới đây minh hoạ trong trường hợp 2 lần là 0
thì mới xác nhận là phím được nhấn và mới xử lý tác vụ mà ta mong muốn.
Khoảng thời gian giữa 2 lần đọc là khoảng 10ms, ta sẽ hiện thực hàm đọc này và
gọi nó trong timer. Giải thuật đơn giản để xử lý chống rung có thể hiện thực như sau:
previous_key = current_key;
current_key = Port_key;
If(previous_key == current_key)
effective_key = current_key;
Trong đó :
previous_key : biến lưu giá trị phím trước đó.
current_key : biến lưu giá trị phím hiện tại.
Port_key : Port của vi điều khiển kết nối với phím.
effective_key : giá trị phím hợp lệ (giá trị trong giai đoạn ổn định)
Để tăng tính chính xác, ta có thể dùng nhiều biến previous_key để lưu lại các giá
trị và so sánh nhiều lần. Đoạn code trên chỉ so sánh trùng nhau 2 lần.
Phím nhấn này tính cực mức 0, được kết nối khá đơn giản, nên ta sẽ dùng phần
mềm để chống rung. Ta sẽ dùng 3 biến để so sánh 2 lần trùng nhau, 2 lần liên tiếp cách
nhau 10ms.
Trong trường hợp nhấn đè 1 phím, ta sẽ dùng biến TimeOutForKeyPress để xác
định thời gian tích cực tiếp theo. Biến này sẽ quan trọng trong trường hợp ta viết 1 ứng
dụng chẳng hạn như soạn thảo văn bản. Nếu không có biến này để quản lý, nếu ta đè thì
trong 1s có tới 100 lần tích cực. Trong ví dụ của Bài 6, thời gian TimeOutForKeyPress =
100 tương ứng với 1s (100*10 = 1000ms = 1s).
void getKey()
{
KeyReg2 = KeyReg1;
KeyReg1 = KeyReg0;
KeyReg0 = KEY_PORT;// Cho phep nut nhan nao duoc tich cuc.
}
}
Để quét led, sơ đồ nguyên lý sẽ được mắc như trên. Port 2 gọi là port dữ liệu, dữ
liệu này sẽ được nối với tất cả các led. Port 1 dùng để chọn led, cực E của transitor được
nối với chân mass của led 7 đoạn.
Port 1 bằng 1 tại bit nào thì transitor tương ứng sẽ dẫn. Giả sử Port 1 = 0x80,
transitor T4 dẫn, dòng data sẽ đi qua led 7 đoạn nối với T4 xuống đất, và led này sẽ sáng,
các led còn lại thì không.
Khi số lượng led càng nhiều, ta phải tính toán để tìm ra số t0 hợp lý.Thông
thường, khoảng thời gian tối đa giữa led đầu tiên và led cuối cùng trong khi quét led phải
nhỏ hơn 1/50 giây (50Hz).
Để quét 8 led 7 đoạn trong bài thí nghiệm này, ta sẽ dùng 1 buffer có 8 phần tử,
rồi định kì xuất từng phần tử ra led 7 đoạn tương ứng. Việc thay đổi dữ liệu trong buffer
này sẽ do các hàm được cung cấp cho người dùng sử dụng cập nhật.
Ta sẽ dùng ngắt timer để định kì gọi hàm scan_led7(), hàm này có chức năng xuất
1 giá trị trong buffer ra led tưng ứng, sau mỗi lần gọi, index sẽ tăng lên 1, đến khi bằng 8
sẽ quay trở về 0.
P0 : Port để xuất dữ liệu cho led 7 đoạn, khởi tạo 0x00 tức là không có led nào
trong led 7 đoạn sáng.
P2 : Port để chọn led 7 đoạn nào trong 8 led sẽ nhận data từ Port 0, tức cực mức 1,
P2 = 0x01 (0000 0001)tức là led 7 đoạn ngoài cùng bên trái sẽ sáng, P2 = 0x20 (0000
0010) tức là led thứ 2 tính từ trái sẽ sáng.
position : vị trí bắt đầu hiển thị giá trị, chẳng hạn người dùng mún hiển thị số 123
từ vị trí thứ 2 tính từ trái thì phải thiết lập position là 1 (position = 0 là led ngoài cùng bên
trái). Biến position này người dùng phải dùng hàm set_positon thì mới thay đổi được (chi
tiết xem bên dưới).
led7_index : vị trí sẽ xuất data, biến này được dùng trong hàm scan_led, người
dùng không can thiệp vào biến này được.
is_valid_data : biến dùng trong hàm scan_led, biến này để tạm dừng việc quét led
trong khi đang thay đổi buffer hiển thị, người dùng cũng không can thiệp được vào biến
này.
led7_buffer[4] = 0x00;
led7_buffer[5] = 0x00;
led7_buffer[6] = 0x00;
led7_buffer[7] = 0x00;
}
Hàm này chỉ đơn giản là xoá buffer 8 phần tử, lúc đó toàn bộ các led sẽ tắt. Buffer
này dùng để chứa dữ liệu của 8 led 7 đoạn, dữ liệu của từng led sẽ được định kì đưa ra
led tương ứng.
if(i>=0)
{
led7_buffer[i] = led7_data[num % 10];
}
num = num / 10;
if(num == 0)
break;
}
position = i;
is_valid_data = 1; //enable scan led
}
Hàm này dùng để cắt từng chữ số của số num để bỏ vào buffer tương ứng. Vòng
lập for dùng để hiện thực tác vụ này, chữ số hàng đơn vị của num sẽ được bỏ vào buffer
vị trí position.
led7_index = 0;
}
}
Đây là hàm dùng để quét led, và sẽ được ngắt timer gọi. Mục đích của hàm này là
xuất dữ liệu ra led ở vị trí tiếp theo.
Port 2 dùng để chọn led, và để tránh hiện tượng bóng mờ khi quét led, bạn phải
gán nó về 0x00 trước.
led7_index là biến chỉ vị trí của led hiện tại chuẩn bị nhận dữ liệu.
Hàm scan_led7() được gọi trong ngắt timer0.
Với các hàm đã thiết kế ở trên, để xuất số 1234 tại vị trí thứ 2 tính từ trái sang ta
viết trong hàm main() như sau:
void main()
{
init_main();
init_timer0();
init_led7();
set_position(1);
put_Number(1234);
while(1){};
}
Hàm main() trong ví dụ của Bài 7 xuất các số 123 và 4321 tại các vị trí liên tiếp
nhau, thời gian giữa 2 lần xuất là 1s (1000ms), ta sẽ có cảm giác là dòng số này chạy qua
các led 7 đoạn.
while(1)
{
alphabet_index = (alphabet_index + 8) % 216;
update_display_led_matrix();
delay_ms(1000);
}
Để chạy 1 dòng chữ qua ma trận led, bạn sẽ có 1 buffer lưu toàn bộ dòng chữ đó.
Buffer này thường là 1 mảng các byte. Chương trình sẽ định kì cắt 1 phần trong buffer
này đổ dữ liệu vào buffer nhỏ hơn dùng để quét led. Chương trình quét led sẽ hiển thị ra
led ma trận.
Tại thời điểm T1, dữ liệu đổ vào buffer quét led là chữ “H”. Tại thời điểm T2, 1
phần chữ H và E được đổ vào buffer này, và tại thời điểm T3 là chữ “E”. Nếu khoảng
cách giữa các thời điểm nhỏ lại, bạn sẽ thấy hiệu ứng dòng chữu chạy qua ma trận led.
Để sinh ra được buffer chứa toàn bộ dòng chữ, bạn phải xử lý dữ liệu đã lưu sẵn
(tạm gọi là font chữ), ghép nối chúng sao cho đẹp mắt. Chẳng hạn muốn hiện chữ
“HELLO WORLD”,bạn phải làm như sau:
Ø Lấy font của chữ “H”, bỏ những cột trống ở đầu và cuối, phần còn lại bỏ vào
buffer.
Ø Lấy font của chữ “E”, bỏ những cột trống ở đầu và cuối,thêm 1 byte 0x00 vào
buffer (tạo 1 nét rời giữa H và E) rồi bỏ dữ liệu của E vào.
Ø Tương tự, hết chữ “O”, bạn thêm khoảng 3 byte 0x00.
Tuỳ vào tài nguyên của hệ thống, bạn định nghĩa ra độ dài tối đa của buffer này.
Trong quá trình sinh ra buffer, chương trình sẽ cập nhật độ dài hiện tại và sẽ dừng việc
ghép chữ nếu độ dài là quá mức cho phép.
length = strlen(strMsg);
data_buff[total_length++]=
alphabet_upcase_led_matrix[j];
}
}
else //blank character,add 3 bytes 0x00
{
if(total_length != 100)
data_buff[total_length++] = 0x00;
if(total_length != 100)
data_buff[total_length++] = 0x00;
if(total_length != 100)
data_buff[total_length++] = 0x00;
}
}
}
IE |= 0x04;
IT1 = 1;
}
Cấu tạo của encoder gồm 1 vòng tròn, trên đó xẻ nhiều rãnh (còn gọi là đĩa), và 1
mạch cảm biến để phát xung
Số rãnh trên đĩa gọi là độ phân giải hay số xung của encoder. Đối với encoder có
độ phân giải thấp (dưới 200 xung) thì đĩa này là đĩa thép. Đối với các encoder có độ phân
giải cao, cỡ 1000 xung, thì đĩa này được làm bằng đĩa từ, encoder như vậy còn gọi là
encoder quang.
Tín hiệu trả về của encoder có 3 kênh A, B và Z. Thông thường ta sử dụng 2 kênh
A và B là đủ thông tin cho quãng đường và chiều quay của encoder. Tín hiệu trả về trên 2
kênh này như sau :
Như vậy nếu ta đưa tín hiệu A vào chân ngắt ngoài và cấu hình là ngắt
cạnh lên, thì khi tín hiệu B ở mức cao là chiều quay thuận của encoder, khi
B ở mức thấp là chiều quay ngược lại.
Để xuất giá trị encoder ra led 7 đoạn, ta thêm 2 dòng lệnh sau trong
hàm phục vụ ngắt timer 0:
set_position(2);
put_number(pulse);
Hàm main() chỉ đơn giản là khởi tạo các thông số cần thiết và sau đó loop vô tận.
Tất cả mọi công việc sẽ do các hàm phục vụ ngắt quãng thực hiện. Đây là 1 mô hình lập
trình cơ bản của vi điều khiển trong các ứng dụng thực tế. Chi tiết code các bạn xem thêm
trong thư mục Bài 11.
Do mức điện áp của tín hiệu logic 1/0 ở cổng COM của máy tính khác với vi điều
khiển, nên MAX 232 có tác dụng chuẩn hoá mức điện áp giữa máy tính và điều khiển
trong quá trình truyền nhận dữ liệu. Nếu giao tiếp trực tiếp giữa 2 vi điều khiển, ta không
cần phải sử dụng MAX 232.
SM1 Cùng với SM0, xác định chế độ làm việc của cổng nối tiếp
SM0,
Chế độ UART UART 0 Baud Rate
SM1
CCLK/16 (chế độ mặc định sau khi
SCON.6 00 0: thanh ghi dịch
RESET)
01 1: UART 8-bit Thay đổi
10 2: UART 9-bit CCLK/32 hoặc CCLK/16
11 3: UART 9-bit Thay đổi
Cho phép truyền thông đa xử lý. Ở Mode 2 và 3, nếu SM2
được lập, RI sẽ không tích cực nếu bit dữ liệu nhận thứ 9 (RB8)
SCON.5 SM2
là ‘0’. Ở Mode 0, SM2 nên bằng ‘0’, ở mode 1, SM2 phải bằng
‘0’.
Cho phép thu ở cổng nối tiếp. Thiết lập bởi phần mềm để cho
SCON.4 REN phép thu. Xóa bởi phần mềm khi không cho phép thu,REN = 1
trong tất cả 4 chế độ tuyền nhận.
Bit dữ liệu thứ 9 được phát ở mode 2 và 3, lập/xóa bởi phần
SCON.3 TB8
mềm khi muốn.
Bit dữ liệu thứ 9 được nhận ở mode 2 và 3. Ở mode 1(SM2=0),
SCON.2 RB8 RB8 là bit dừng được nhận, ở mode 0, RB8 không được định
nghĩa.
Cờ ngắt phát(truyền nối tiếp), lập bởi phần cứng ở cuối bit thứ
8 ở mode 0, hay là ở bit dừng của các chế độ còn lại, phải được
SCON.1 TI
xóa bằng phần mềm. (Xem thêm bit INTL0 ở thanh ghi
SSTAT)
Cờ ngắt thu, lập bởi phần cứng ở cuối bit thứ 8 trong mode 0,
hay khoảng giữa bit dừng ở mode 1. với mode 2 và mode 3,
SCON.0 RI nếu SMOD0=0, nó được lập gần khoảng giữa bit dữ liệu thứ 9
(bit 8). Nếu SMOD0=1, nó được lập gần khỏang giữa bit dừng.
Phải được xóa bởi phần mềm.
8051
TxD(P3.1)
CLOCK Shift register
Data
RxD(P3.0)
· Chế độ 1: là chế độ truyền với mỗi frame có 10 bits (bit start – 8 bits data –
bit stop) ,tốc độ baud rate có thể thay đổi được tùy vào cách khởi tạo ban
đầu.Có thể nhân đôi tốc độ truyền bằng cách bật PCON.7 = 1.
· Chế độ 2: là chế độ truyền với mỗi frame có 11 bits (bit start – 8 bits data –
bit parity – bit stop),bit parity được đặt trong TB8 khi phát và được đặt
trong RB8 khi thu, tốc độ baud rate ở chế độ này không thay đổi được và
bằng 1/64 dao động của XTAL (1/32 dao động của XTAL nếu PCON.7 =
1).
· Chế độ 3: có chức năng tương tự như chế độ 2 nhưng tốc độ baud rate có
thể thay đổi được như ở chể độ 1.
Tốc độ Baud : Tốc độ (Baud) của cổng 8051 phải phù hợp với máy tính: 100,
150, 300, 600, 1200, 2400, 4800, 9600, 19200. Khuyến cáo dùng thạch anh có tần số
11.0592Mhz để có thể tạo ra tốc độ Baud với sai số thấp (gần như là 0).
void init_timer1()
{
TMOD &= ~(0x20);// setup mode for timer 1
TMOD |= 0x20;
}
Tốc độ truyền nhận UART được quy định như sau :
Nếu PCON.7 = 0 : TH1 = 256 - ((Crystal / 384) / Baud)
TH1 = (-48); // baudrate 600
TH1 = (-24); // baudrate 1200
TH1 = (-12); // baudrate 2400
TH1 = (-6); // baudrate 4800
TH1 = (-3); // baudrate 9600
v Hàm initUart() sẽ thiết lập chế độ hoạt động của port nối tiếp
void initUart()
{
//IE = 0xb0;
//SCON: SM0 SM1 SM2 REN TB8 RB8 TI RI
ES = 1; // enable UART
IP = 0x10; // set priority for serial port
SCON = 0x50; // Uart mode 1
}
trans_busy = 1;
SBUF = c;
while(trans_busy);
}
v Hàm uart_isr() là hàm phục vụ ngắt của truyền nhận nối tiếp :
Công việc còn lại là cấu hình cho cổng COM truyền nhận uart cho tương thích với
giao thức truyền nhận của board 89. Các thông số thông thường là Baud rate 9600, Data
bit 8bit, không có Parity (chọn none) và 1 Stop bit. Nhấn nút Connect để kết nối. Kể từ
đây nếu board 89 có gửi dữ liệu lên, dữ liệu này sẽ nằm trong phần Receive. Ngược lại,
muốn gửi dữ liệu xuống board 89 ta gõ vào textbox và ấn Send.
Chọn vào menu File, chọn New Project như hình trên, màn hình dưới đây sẽ hiện ra.
Bạn chọn loại project là Visual C#, Windows Application và đặt tên cho project
tại mục Name. Sau khi chọn xong nhấn OK. Project sẽ được load lên để bắt đầu thiết kế
giao diện. Nếu xài hệ điều hành Windows Vista, thông báo sau có thể xuất hiện và bạn
chọn Load Project Normally.
Để thiết kế giao diện, bạn chỉ cần kéo thả các đối tượng giao diện vào form và thay đổi
thuộc tính của nó. Trong ví dụ này dùng các đối tượng Label, ComboBox,
TextBox,SerialPort và Command Button.
Chọn control SerialPort, kéo thả vào form và sửa thuộc tính Name thành
serialPort, kết quả như hình dưới đây:
Điều chỉnh thuộc tính của các đối tượng như sau:
v Chỉnh thuộc tính Name của ComboxBox là cboPorts. ComboBox này dùng để list ra
các cổng COM mà máy hỗ trợ.
v Nút Connect có thuộc tính Name là btnConnect, Caption là Connect.
v Nút Send có thuộc tính Name là btnSend, Caption là Send.
v Textbox dùng để chứa dữ liệu truyền có tên là txtSend và textbox dùng để hiển thị dữ
liệu nhận tên txtReceive, thuộc tính Multiple lines chỉnh thành true.
Chọn tab sự
Chọn Form kiện
Màn hình thiết kế giao diện hiện ra, bạn nhấp đôi vào button Connect để tạo hàm
xử lý sự kiện Click cho nó. Hàm được tạo ra và bạn hiện thực code như sau :
Để gửi dữ liệu, ta tạo hàm xử lý cho sự kiện click nút Send bằng cách double
click vào nút đó trên form thiết kế giao diện, hàm này sẽ được tự động tạo ra như sau:
Hàm này có chức năng truyền 1 string gồm nhiều kí tự qua cổng UART, việc hiện
thực hàm này như sau :
Do khi dữ liệu được gửi đến máy tính sẽ được chia thành nhiều phần. Ví dụ 1
chuỗi 10byte gửi, máy tính có thể lần 1 nhận được 8 byte, lần 2 mới nhận 2 byte còn lại.
Vì lý do này, việc nhận dữ liệu sẽ bị xung đột với thread vẽ màn hình giao diện của
chương trình mà chúng ta đang viết. Để khác phục vấn đề này, C#.NET đưa ra khái niệm
hàm gọi bất đồng bộ, khi sự kiện nhận UART bị xung đột với thread màn hình, nó sẽ bị
delay lại và không bị mất.
Ta khai báo hàm gọi bất đồng bộ ở đầu chương trình như sau :
if (this.txtReceive.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
//process incoming data + return character
txtReceive.Text += text + Environment.NewLine ;
}
}
Chi tiết code bạn có thể xem thêm trong project RS232Demo đi kèm trong đĩa CD
này. Để chạy chương trình bạn nhấn F5 hoặc tổ hợp phím Ctrl và F5. Ở máy tính khác
không cài Visual 2005, bạn cần phải cài dotnetframework 2.0 rồi chép file
RS232Demo.exe trong thư mục bin\Debug. Bạn có thể tham khảo thêm trên mạng để tạo
ra file setup cho ứng dụng này để sinh ra file cài đặt giống như khi bạn mua đĩa CD và cài
đặt ứng dụng.
Ta sẽ lấy dữ liệu tại cạnh xuống của clock, chân của clock được nối vào chân ngắt
ngoài 1 của vi điều khiển (P3.3) như sau:
kb_Shield VCC VCC
R3 R4
7
J3 22K 22K
6
VCC 5 kb_Clock P3.3
kb_Vcc 4
3 kb_Ground
2
1 kb_Data P3.4
Khi 1 phím được nhấn xuống, mã make_code sẽ được gửi lên. Trong khoảng thời
gian phím đó được đè xuống thì mã make_code vẫn được định kì gửi lên. Khi thả phím ra
thì bàn phím gửi lên mã break_code và make_code.
Bảng mã make_code và break_code của các phím như sau:
}
Biến count_bit_input dùng để đếm số bit gửi về, khi count_bit_input = 11 ta sẽ
có được dữ liệu scan_code từ bàn phím truyền lên. Bạn sẽ phải xử lý để phân loại đây là
mã make_code hay break_code, có được nhấn kèm với phím shift hay caps lock hay
không để chuyển sang mã ascii cho kí tự được nhấn. Chi tiết code bạn xem thêm trong
Bai 13.
v Chu kỳ T, thời gian xung mức cao TH + thời gian xung mức thấp TL.
v Duty Cycle: tỉ lệ thời gian xung mức và thời gian xung mức thấp.
Như hình trên ta có Duty Cycle lần lượt là 0%, 25%, 50%, 75%, 100%.
Một số công thức :
Khi đó nếu TOn = 0 thì VoltOutput = 0 (V) còn TOn = TTotal thì VoltOutput = VoltInput .
Trong thực tế ta có IC chuyên dụng để dùng cho việc điều xung và có các vi điều
khiển có tích hợp sẵn PWM bên trong. Vi điều khiển 89V51 hỗ 5 kênh điều xung (P1.3 –
P1.7) được sử dụng khá linh hoạt cho việc điều xung, người dùng chỉ cần ghi giá trị thích
hợp vào các thanh ghi để có được tín hiện PWM mong muốn.
void initPWM()
{
CCAPM0 = 0x42; //set P1.3 pwm mode
CCAPM1 = 0x42; //set P1.4 pwm mode
CCAPM2 = 0x42; //set P1.5 pwm mode
CCAPM3 = 0x42; //set P1.6 pwm mode
CCAPM4 = 0x42; //set P1.7 pwm mode
CMOD = 0x00; // setup to devide frequency by 6
CCAP0H = 0xff;
CCAP1H = 0xff;
CCAP2H = 0xff;
CCAP3H = 0xff;
CCAP4H = 0xff;
CCON |= (1<<6); // set registry PCA
}
Sau khi khởi tạo, mức điện áp ở 5 chân điều xung là 0V. Muốn thay đối giá trị
điện áp ta chỉ cần thay đối nội dung trong thanh ghi CCAPnH, 0x00 tương ứng với 100%
duty cycle, 0xFF tương ứng với 0% duty cycle. Motor được nối với P1.3 tương ứng với
kênh điều xung 0. Motor được tích cực mức 0 nên giá trị trong thanh ghi CCAP0H càng
lớn thì motor quay càng chậm (duty cycle nhỏ thì motor quay nhanh).
LCD thường sử dụng 14 chân, chế độ 16 chân khi cần điều khiển đèn nền. Chức
năng của các chân như sau:
Hình trên mô tả kết nối LCD với chế độ 16 chân, 2 chân K và A dùng để kết nối
với đèn nền.
Chúng ta muốn hiển thị chữ “CE” ở giữa hàng đầu tiên, giả sử cửa sổ hiển thị
đang bắt đầu từ vị trí đầu tiên (hàng thứ nhất hiển thị dữ liệu của ô nhớ từ 0x00 đến 0x0f,
hàng thứ hai hiển thị dữ liệu của ô nhớ từ 0x40 đến 0x4f, đây là vị trí home). Giá trị của ô
nhớ 0x07 là 0x43 (ký tự C), của ô nhớ 0x08 là 0x45 (ký tự E).
Chúng ta muốn hiển thị chữ “®” ở giữ hàng thứ hai, giả sử cử sổ hiển thị đang ở
vị trí home. Trong bảng mẫu ký tự chúng ta thấy không có mẫu “®”. Lúc này chúng ta
phải định nghĩa mẫu “®” 5x8 điểm, gồm có 8 byte, sau đó lưu vào vị trí của mẫu ký tự
CGRAM thứ nhất. Lúc này giá trị của ô nhớ 0x47 là 0x00 hoặc 0x08 (vị trí của mẫu ký
tự CGRAM thứ nhất “®”).
Thời gian
Lệnh RS RW D7 D6 D5 D4 D3 D2 D1 D0
thực thi
Clear display
0 0 0 0 0 0 0 0 0 1 1.52ms
Return home
0 0 0 0 0 0 0 0 1 * 1.52ms
Entry mode set
0 0 0 0 0 0 0 1 I/D RL 37µs
Display on/off
0 0 0 0 0 0 1 D C B 37µs
control
Cursor/Display S/ R/
0 0 0 0 0 1 * * 37µs
shift C L
Function set
0 0 0 0 1 DL N F * * 37µs
Set CGRAM
0 0 0 1 CGRAM address 37µs
address
Set DDRAM
0 0 1 DDRAM address 37µs
address
Read BUSY flag
0 1 BF DDRAM address 0µs
(BF)
Write to
DDRAM or 1 0 D7 D6 D5 D4 D3 D2 D1 D0 43µs
CGRAM
Read from
DDRAM or 1 1 D7 D6 D5 D4 D3 D2 D1 D0 43µs
CGRAM
cho LCD ở chế độ này bao gồm 4bit cao gửi trước, sau đó sẽ đến 4bit thấp. Sơ đồ kết nối
ở 2 chế độ như sau:
Nếu muốn tiết kiệm chân, R/W có thể nối xuống GND. Ở chế độ 4bit thì 4 bit
thấp của LCD có thể nối xuống GND.
RS(CMD);
lcd_write_4bits(cmd);
lcd_write_4bits(cmd << 4);
}
Nếu x = 1 thì thực hiện lệnh LCD_PORT |=0x02, x=0 thì thực hiện LCD_PORT
&=0xFD.
void init_lcd() {
lcd_delay(15000); //1
RS(CMD); //2
lcd_write_4bits(0x03 << 4); //3
lcd_delay(4100); //4
lcd_write_4bits(0x03 << 4); //5
lcd_delay(100); //6
}
Ý nghĩa các lệnh trên như sau:
v Lệnh 1 : gọi hàm lcd_delay(15000) để delay 15ms.
v Lệnh 2 : kéo chân RS (nối với LCD_PORT tại bit 1) xuống 0. Lệnh này được
định nghĩa là 1 macro trong file lcd.h:
#define RS(x) ( (x) ? ( LCD_PORT |= 0x02 ) : ( LCD_PORT &= 0xFD ) )
CMD được define là 0 nên lệnh RS(CMD) sẽ có điều kiện (x) là false và sẽ thực
hiện phần thứ 2 của lệnh trên : LCD_PORT & 0xFD (kéo bit 1 xuống 0 : 1111 1101).
v Lệnh 3 : thực hiện trạng thái đầu tiên sau khi chờ 15ms, ghi D7 D6 D5 D4 =
0011. Các chân này được nối với 4 bit cao của vi điều khiển nên ta phải dịch trái giá trị
0x03 4 bit.
v Lệnh 4 : delay khoảng 41ms.
v Lệnh 5,6,7,8 : Thực hiện các trạng thái 2,3 và 4. Sau lệnh 7 thì LCD đã
chuyển sang chế độ 4 bit, và để gửi 1 byte, ta sẽ gửi 2 lần 4 bit cao trước rồi tới 4 bit thấp.
v Lệnh 9 : gọi hàm lcd_write_cmd để ghi 4 bit 2 lần, giá trị 0x28 tương ứng với
N = 1 (hiển thị trên 2 hàng của LCD) và B = 0 (font định dạng 5x7 điểm).
v Lệnh 10 : thực hiện lệnh display on (xem thêm trong bảng lệnh), D = 1.
v Lệnh 11 : thực hiện lệnh entry set mode, 0x06 tương ứng với chế độ dịch phải
tăng dần.
}
else {
addr = (row * 0x40) + col;
addr = 0x94 | (addr & 0x7F);
}
lcd_write_cmd(addr);
current_row = row;
current_col = col;
return TRUE;
}
RS(DAT); //RS = 0
lcd_write_4bits(dat);
lcd_write_4bits(dat << 4);
}
Từ những hàm cơ bản này, bạn có thể hiện thực thêm các hàm để xuất 1 string
hay 1 giá trị số ra màn hình LCD. Code chi tiết có thể xem thêm trong thư mục Bài 15.
32khz. Chân này cũng là chân Open drain nên cũng yêu cầu có điện trở kéo lên
nguồn ở bên ngoài. SQW/OUT sẽ hoạt động khi có nguồn cung cấp vào cho dù đó
là nguồn VCC hay là VBAT.
X1, X2 : Kết nối với thạch anh 32.768Khz. Mạch tạo xung bên trong được
thiết kế để hoạt động với thạch anh và tụ CL = 12.5 pF.
Out (Output control) : Bít này điều khiển mức logic xuất ra trên chân
SQW/OUT khi mà sóng vuông không được kích hoạt. Nếu SQWE = 0, thì mức
logic trên chân SQW/OUT là 1 nếu OUT = 1, và là 0 nếu OUT = 0.
SQWE (Square Wave Enabel) : Bít này khi được set lên mức 1 thì sẽ kích
hoạt mạch dao động xuất ra ngoài. Tần số của sóng vuông phụ thuộc vào giá trị ở
bít RS0 và RS1. Với sóng vuông xuất ra 1Hz thì thanh ghi clock sẽ cập nhập dữ
liệu khi có cạnh xuống của xung vuông.
RS (Rate select) : Những bit này điều khiển tần số của sóng vuông được
xuất ra trên chân SQW/OUT. Bảng sau liệt kê ra các tần số có thể được chọn bởi 2
bit RS này.
Dựa vào giản đồ trên ta hiện thực hàm để Start I2C như sau :
void start_I2C()
{
SCL = 1;
SDA = 1;
nop();nop();
SDA = 0;
SCL = 0;
nop();nop();
}
Tốc độ clock chuẩn của giao thức I2C là 100KHz. Khi truyền ở tốc độ cao có thể
hoạt động ở clock 1MHz. Tuy nhiên bạn nên delay vài uS để đảm bảo tính đúng đắn của
dữ liệu.
Giản đồ xung cho điều kiện Stop như sau
nop();
}
if (ACK_Bit == 1)
SDA = 0; // Send ACK
else
SDA = 1; // Send NO ACK
nop();nop();
return Data;
}
Đây là quá trình truyền dữ liệu từ master xuống slave. Khi master gửi xong 1
byte, slave sẽ gửi lại bit ACK. Quá trình giao tiếp như sau:
v Master gửi tín hiệu Start.
v Master gửi địa chỉ của DS1307 (1101 000) và bit R/W, trong trường hợp
này là 0. Byte đầu tiên mà master gửi xuống sau khi start là D0.
v Master gửi địa chỉ pointer dữ liệu cần ghi, chẳng hạn là 0x00 (register
pointer, word address)
v Master gửi các byte data cần ghi.
v Master gửi tín hiện stop.
Code hiện thực cho quá trình này như sau
void write_RTC(unsigned char *buff)
{
start_I2C();
write_I2C(0xD0);
write_I2C(0x00);
write_I2C(*(buff+0));
write_I2C(*(buff+1));
write_I2C(*(buff+2));
write_I2C(*(buff+3));
write_I2C(*(buff+4));
write_I2C(*(buff+5));
write_I2C(*(buff+6));
stop_I2C();
}
buff là 1 mảng có 7 phần tử, tương ứng với các giá trị giây, phút, giờ, thứ, ngày,
tháng và năm.
Đọc dữ liệu từ DS1307
Đây là quá trình truyền dữ liệu từ slave lên master. Như đã trình bày ở phần trước,
khi gửi nhận từng byte, sẽ có bit ACK đi kèm ngoại trừ byte cuối cùng trước khi stop.
Để có thể đọc chính xác giá trị mong muốn, thông thường ta phải ghi vào thanh
ghi địa con trỏ dữ liệu (register pointer). Quá trình này chính là trình truyền dữ liệu từ
master xuống slave nên R/W bit sẽ là 0. Sau khi ghi dữ liệu và register pointer xong, quá
trình đọc dữ liệu mới bắt đầu, và bit R/W sẽ là 1.
Từng bước đọc dữ liệu từ DS1307 như sau:
v Master gửi tín hiệu start.
v Master gửi địa chỉ DS1307 + R/W = 0 : 0xD0.
v Master gửi byte ghi vào register pointer : 0x00.
v Master gửi tín hiệu start
v Master gửi địa chỉ DS1307 + R/W = 1 : 0xD1.
v Master đọc các byte dữ liệu và gửi bit ACK. Byte cuối cùng trước khi stop,
master gửi bit NACK.
v Master gửi tín hiện stop.
Code hiện thực cho quá trình này như sau
void read_RTC(unsigned char * buff)
{
//send address to slave and reset pointer
start_I2C();
write_I2C(0xD0); //address + direction
write_I2C(0x00); //pointer data
*(buff+4)=read_I2C(ACK); // date
*(buff+5)=read_I2C(ACK); // month
*(buff+6)=read_I2C(NO_ACK); // year
stop_I2C();
}
Hàm main() dưới đây minh hoạ cho việc sử dụng các hàm trong module RTC
DS1307:
void main()
{
P1 = P3 = 0x00;
while(1)
{
read_RTC(&RTC_ARR[0]); //read 7 bytes RTC