KIẾN TRÚC
Microchip PIC được xây dựng trên nền tảng kiến trúc Harvard, thuộc loại RISC (Reduced Instruction Set Computer), mang lại nhiều cải tiến trong hiệu suất xử lý và giảm chi phí sản xuất Đặc điểm nổi bật của kiến trúc Harvard giúp nâng cao hiệu quả cho PIC, đặc biệt khi so sánh với kiến trúc Von Neumann, một trong những kiểu RISC phổ biến khác.
Kiến trúc Princeton, hay còn gọi là kiến trúc Von Neumann, tổ chức bộ nhớ bằng cách kết hợp bộ nhớ chương trình và bộ nhớ dữ liệu thành một vùng nhớ chung, sử dụng một Bus dữ liệu duy nhất Trong giai đoạn đầu của máy tính, độ tin cậy của bộ nhớ còn thấp, dễ phát sinh lỗi hệ thống, vì vậy kiến trúc này trở nên phổ biến do dễ thiết kế, nâng cao độ tin cậy và thuận tiện trong việc thay thế bộ nhớ bị lỗi Tuy nhiên, kiến trúc này cũng có nhược điểm như hạn chế băng thông, thực hiện nhiều lần lấy dữ liệu cho một lệnh và không hỗ trợ thao tác song song Mặc dù hiện nay giá thành bộ nhớ đã giảm và độ tin cậy được cải thiện, nhưng hầu hết vi điều khiển vẫn được xây dựng dựa trên kiến trúc này do sự phổ biến ban đầu của nó.
Hình 1.1: Kiến trúc bộ nhớ kiểu Von Neumann
Kiến trúc Harvard sở hữu không gian nhớ riêng biệt cho bộ nhớ dữ liệu và bộ nhớ chương trình, mang lại lợi thế hiệu suất cao nhờ vào việc sử dụng hai bus dữ liệu độc lập Điều này cho phép CPU truy xuất dữ liệu từ bộ nhớ chương trình trong khi vẫn thực hiện các thao tác đọc ghi trên bộ nhớ dữ liệu, tối ưu hóa quá trình xử lý thông tin.
Một ưu điểm nổi bật của kiến trúc Harvard là khả năng cho phép độ rộng Bus bộ nhớ chương trình và Bus dữ liệu khác nhau Mặc dù không phải tất cả các loại vi điều khiển theo kiến trúc Harvard đều sở hữu lợi thế này, nhưng vi điều khiển PIC thì có.
Bus có độ rộng khác nhau, dẫn đến độ rộng Bus bộ nhớ chương trình có thể lớn hơn bộ nhớ dữ liệu Đối với PIC 8-bit, Bus dữ liệu luôn là 8-bit, trong khi Bus bộ nhớ chương trình có thể rộng hơn tùy thuộc vào mục đích sử dụng của loại PIC Có ba loại PIC 8-bit được phân chia theo độ rộng Bus bộ nhớ chương trình: 12-bit, 14-bit và 16-bit Bus bộ nhớ chương trình rộng hơn cho phép truyền tải nhiều dữ liệu từ bộ nhớ chương trình trong một chu kỳ máy.
Hình 1.2: Kiến trúc bộ nhớ kiểu Von Neumann
INSTRUCTION PIPELINING
Việc lấy lệnh và dữ liệu từ bộ nhớ chương trình được tối ưu hóa để nâng cao hiệu suất Kỹ thuật Instruction Pipelining cho phép thực hiện đồng thời quá trình lấy và thực thi lệnh từ bộ nhớ, giúp cải thiện hiệu quả hoạt động của hệ thống.
Hình 1.3: Quy trình thực thi các lệnh
PIC có hai bus riêng biệt cho việc truy xuất bộ nhớ chương trình và dữ liệu, cho phép chúng hoạt động đồng thời Tuy nhiên, khi thực hiện một lệnh, cần xác định vị trí của dữ liệu cần thiết để thực thi cả hai thao tác trong cùng một chu kỳ lệnh, vì thông tin địa chỉ của dữ liệu nằm trong lệnh đó Vấn đề này được giải quyết bằng phương pháp Instruction Pipelining.
Trong chu kỳ lệnh đầu tiên khi CPU hoạt động, lệnh được lấy vào Pipeline Bus bộ nhớ chương trình được truy xuất, Bus dữ liệu không hoạt động
Hình 1.4: Thực hiện lệnh trong chu kỳ đầu tiên
Trong chu kỳ lệnh thứ hai, lệnh đã được lấy trước đó sẽ được thực thi, điều này có nghĩa là Bus dữ liệu sẽ trao đổi dữ liệu với CPU trong quá trình thực hiện lệnh Đồng thời, cả hai Bus sẽ hoạt động song song.
Hình 1.5: Thực hiện lệnh trong chu kỳ thứ hai
Trong chu kỳ lệnh thứ 3, lệnh thứ 2 được thực thi trong khi lệnh tiếp theo được lấy Mọi thứ đã rõ ràng, ngoại trừ chu kỳ lệnh đầu tiên, mỗi chu kỳ sẽ thực hiện một lệnh qua hai bước: lấy lệnh và thực thi Khi một lệnh được thực thi, thông tin về địa chỉ bộ nhớ dữ liệu đã được lấy từ lệnh trước đó.
Hình 1.6:Thực hiện lệnh trong chu kỳ thứ ba
Trong chu kỳ thứ 4,5, lệnh sau luôn thực thi sau khi lệnh trước hoàn thành Tuy nhiên, nếu bộ đếm chương trình bị thay đổi bởi một lệnh, lệnh tiếp theo có thể không được thực hiện Khi lệnh Call được thực thi, chương trình sẽ chuyển đến vùng nhớ khác, và lệnh sau lệnh Call chỉ được thực hiện khi chương trình quay trở lại từ hàm SUB1 Mặc dù vậy, lệnh kế tiếp lệnh Call vẫn được đưa vào Pipeline.
Hình 1.7: Thực hiện lệnh trong chu kỳ thứ 4
Cần loại bỏ lệnh sau Call khỏi Pipeline và thay thế bằng một lệnh khác Đối với bất kỳ lệnh nào thay đổi bộ đếm chương trình, sẽ mất 2 chu kỳ máy để thực hiện.
Hình 1.8: Quy trình thực hiện lệnh
Hình 1.9: Thực hiện lệnh tổng quát
KÍCH THƯỚC TỪ LỆNH
Vi điều khiển PIC sử dụng kiến trúc Harvard, cho phép độ rộng của Bus bộ nhớ chương trình không nhất thiết phải bằng với độ rộng của Bus bộ nhớ dữ liệu Ví dụ, vi điều khiển 8 bit sẽ có Bus rộng 8 bit Trong khi đó, các vi điều khiển áp dụng kiến trúc Von Neumann thường có độ rộng Bus giống nhau cho cả bộ nhớ chương trình và bộ nhớ dữ liệu.
Nhiều lệnh yêu cầu 2 byte để cung cấp đủ thông tin cho việc thực thi Chẳng hạn, lệnh LDAA (Load Data to Accumulator A) cần ít nhất 2 byte dữ liệu, với 1 byte cho hằng số 8 bit và byte còn lại cho lệnh Điều này dẫn đến việc bộ nhớ bị truy xuất hai lần khi thực hiện lệnh, gây ảnh hưởng đến tốc độ truy xuất Hơn nữa, cấu trúc lệnh 1, 2 hay 3 trong kiểu kiến trúc này làm cho việc thiết kế vi điều khiển với bộ nhớ vừa phải trở nên khó khăn hơn.
Quy trình thực thi lệnh LDAA trên PIC16F sử dụng kiến trúc bộ nhớ 14 bit với độ rộng bus bộ nhớ chương trình, phù hợp cho các ứng dụng vừa phải Lệnh dài cho phép chứa nhiều thông tin, giúp thực thi công việc chỉ với một từ lệnh và chỉ cần truy xuất bộ nhớ một lần Thiết kế này tối ưu hóa kích thước bộ nhớ so với các loại bộ nhớ khác.
Hình 1.11: Thực thi một công việc chỉ với một từ lệnh
THIẾT KẾ PHẦN CỨNG CHO PIC
Các chân nguồn Vdd cần được cấp nguồn đầy đủ và không được bỏ trống Khi chip có nhiều tính năng, số lượng transistor tăng lên, dẫn đến độ dài đường cấp nguồn trong diesilicon cũng tăng, gây ra điện trở cao và mất cân đối điện áp trên các vùng của chip Hơn nữa, khi đường nối dài, cảm kháng ký sinh cũng tăng, khiến áp cung cấp cho các vùng bị sụt tức thời khi chip hoạt động ở tần số cao Những hiệu ứng này làm giảm tính ổn định của chip, vì vậy chip thường được thiết kế với nhiều đầu cấp nguồn để giảm thiểu các tác động tiêu cực này.
Để đảm bảo hiệu suất tối ưu cho mạch điện, cần đặt các tụ decoupling 0.1uF gần các chân cấp nguồn trên PCB, càng gần càng tốt Mặc dù dòng tiêu thụ trung bình có thể nhỏ, nhưng khi hoạt động ở tần số cao, dòng điện tức thời mà chip yêu cầu để nạp các cổng của MOSFET là rất lớn Do sự thay đổi dòng điện nhanh (di/dt) cao, ảnh hưởng của điện cảm trên đường mạch cũng trở nên đáng kể.
Các ổn áp tuyến tính không đủ khả năng cung cấp dòng điện cho chip, do đó cần sử dụng tụ điện để tích điện tạm thời trong chu kỳ chip không hoạt động và xả dòng trong chu kỳ hoạt động Tụ điện cần có ESR thấp để đảm bảo khả năng xả dòng lớn, với tụ gốm 0.1uF là lựa chọn phù hợp Để giảm điện cảm và điện trở của đường mạch, có thể mắc song song thêm một tụ 0.01uF gần chân nguồn của chip Khi lắp các tụ decoupling, sẽ phát sinh nhiễu tần số cao do quá trình nạp và xả, cùng với nhiễu từ các nguồn khác, điều này cần được triệt tiêu nhưng là một vấn đề phức tạp.
Sử dụng LDO tốt, dòng tĩnh thấp, nếu phải xài 7805, với một loại chỉ cần có 1 con 0.1uF sát ngay chân output để tránh bị dao động
Tăng kích thước đường nguồn để giảm điện cảm Đặt rải rác các tụ 10uF(low ESR) trên các đường cấp nguồn
Nếu phải dùng các tải cảm như relay, motor, và dùng chung nguồn, nên đi 2 đường mạch Vss – Vdd riêng
Sử dụng diode Schottky (như 1N5817, 1N5822) thay cho các loại diode nắn dòng thông thường (như 1N4001, 1N4007) để giảm thiểu dòng cảm ứng trong các tải cảm Ngoài ra, có thể sử dụng các linh kiện như tranzorbs và sidactor để xử lý dòng cảm ứng Khi thiết kế PCB, cần đặt thạch anh gần với PIC, đặc biệt là khoảng cách từ chân OSCI đến chân thạch anh phải được giữ càng ngắn càng tốt, vì dao động vào chân này càng xa sẽ gây ra nhiễu và làm mất ổn định cho PIC.
1.4.2 Chuẩn ICSP(In-Circurt Serial Programming) Đây là chuẩn nạp trực tiếp cho vi điều khiển PIC kể cả khi PIC được hàn trên board, gồm có 6 chân như theo thứ tự:
Hình 1.12: Vị trí các chân trong chuẩn ICSP
Để nạp PIC, chỉ cần 5 chân từ 1 đến 5, tuy nhiên có thêm chân số 6 để bảo vệ khi cắm ngược Trong trường hợp đó, chân MCLR/Vpp sẽ kết nối với chân NC Khi nạp chip, điện áp chân MCLR/Vpp sẽ tăng từ 8-13V, nếu chân này nối vào một I/O bất kỳ có thể gây hỏng hóc Do đó, nên chọn loại Bus có quy định chiều cắm cho chuẩn ICSP để đảm bảo an toàn.
1.4.3 Các tính năng đặc biệt, các bit cấu hình của PIC16F887
PIC16F887 được trang bị nhiều tính năng nhằm nâng cao độ tin cậy của hệ thống và giảm chi phí bằng cách loại bỏ các linh kiện bên ngoài Bên cạnh đó, nó còn tích hợp các tính năng bảo vệ mã và tiết kiệm năng lượng, mang lại hiệu suất tối ưu cho các ứng dụng.
Oscillator Start-up Timer (OST)
Low-voltage In-Circurt Serial Progrmamming
Một số tính năng của PIC luôn sẵn có, trong khi những tính năng khác cần được cấu hình trong quá trình nạp chương trình Việc cấu hình liên quan đến việc bật/tắt các bit trong thanh ghi CONFIG1 và CONFIG2, nằm trong bộ nhớ chương trình với độ rộng 14-bits và được ghi bằng bộ nạp Những tính năng này là cần thiết để PIC hoạt động, vì vậy trước khi lập trình, cần xác định các tính năng cần thiết cho PIC Có hai phương pháp để cấu hình: sử dụng từ khóa chỉ dẫn của Hi-tech C để tích hợp các bit vào mã nguồn (file*.hex) và sử dụng MPLAB để cấu hình Phần tiếp theo sẽ trình bày về các tính năng của PIC và cách cấu hình bằng Hi-Tech C, trong khi phần hướng dẫn sử dụng MPLAB sẽ được trình bày sau.
Before configuring the setting bits for the PIC, it is essential to consult the PIC 16F887 datasheet, specifically section 14.0 on special CPU features, as well as the file pic16f887.h located in the include directory of the Hi-Tech C compiler (for instance, in version 9.65 Pro, this default directory can be found at C:\Program Files\Hi-Tech\Software\PICC\PRO\9.65\include\pic16f887.h).
Khi lập trình bằng Hi-Tech C và cần đặt trước các bit cấu hình, bạn sử dụng từ khóa CONFIG (chú ý có hai dấu gạch dưới) để trình biên dịch nhận diện và áp dụng các tùy chọn vào mã nguồn.
CONFIG (OPTION & OPTION2 & OPTION3 &…); CONFIG (OPTION & OPTION2 & OPTION3 &…);…
Các OPTION1, OPTION2, … là các định nghĩa có trong file pic16f887.h của Hi-tech C, được phân cách bằng dấu '&' Câu lệnh CONFIG đầu tiên dùng để cấu hình thanh ghi CONFIG1, trong khi CONFIG thứ hai dùng để cấu hình thanh ghi CONFIG2.
Một chương trình mẫu khi sử dụng các bit cấu hình đặt trong chương trình:
/////////////////////Cấu hình thanh ghi CONFIG1
XT: Dao động thạch anh bên ngoài, 4Mhz
WDTDIS: Tắt chức năng watch-dog
PWRTEN: Mở tính năng Power-up Timer
MCLREN: Mở tính năng master clear từ chân MCLR
UNPROTECH: Không bảo vệ mã
DUNPROTECH: Không bảo vệ dữ liệu trong EEPROM
BORDIS: Không sử dụng Brown-out reset
IESODIS: Không sử dụng tính năng Internal External Switchoer FCMDIS: Không sử dụng tính năng Fail-Safe Clock Monitor LVPDIS:Không sử dụng tính năng LVP
/////////////////////Cấu hình thanh ghi CONFIG2
BORV40: Nếu Brown-out Reset được cho phép, thì điện áp Vbor = 4V
Cho phép PIC được ghi lên Flash (không xuất hiện WP0,WP1, WP2 nghĩa là cho phép tất cả)
Thanh ghi cấu hình CONFIG1:
Khi bit Debug bị xóa (0), chip hoạt động ở chế độ gỡ rối, cho phép truyền thông tin bộ nhớ về thiết bị gỡ rối qua chân RB6/CLK và RB7/DAT Ngược lại, khi bit này được bật (1), chip sẽ không thực hiện tính năng gỡ rối và hai chân này sẽ hoạt động như I/O thông thường Bit Debug có thể được điều chỉnh bởi MPLAB khi chọn chế độ nạp hoặc gỡ rối, vì vậy người dùng không cần quá lo lắng về trạng thái của bit này trong quá trình làm việc.
Trong Hi-tech C định nghĩa Debug:
#define DEBUGEN 0x1FFF // Enable ICD2 debugging
#define DEBUGDIS 0x3FFF // Disable ICD2 debugging
LVP (Low Voltage Programming) là tính năng lập trình điện áp thấp, cho phép nạp chip với điện áp hoạt động 5V khi được kích hoạt Khi sử dụng tính năng này, chân RB3/PGM phải được kết nối với Vdd, điều này đồng nghĩa với việc không thể sử dụng RB3 như một I/O Chân MCPR/Vpp cũng cần được cấp điện áp VDD Đáng lưu ý, tính năng lập trình điện áp cao luôn khả dụng, cho phép nạp chip qua cổng ICSP khi điện áp chân MCLR/Vpp đạt khoảng 13V (hoặc 8V tùy loại PIC) Tuy nhiên, tính năng này thường ít được sử dụng và thường được tắt để tận dụng chân RB3 như I/O.
Trong Hi-Tech C định nghĩa như sau:
FCMEN (Fali safe Clock Monitor Enable) là tính năng giúp vi điều khiển PIC duy trì hoạt động ngay cả khi nguồn dao động bên ngoài gặp sự cố Tính năng FSCM có khả năng tương thích với nhiều loại nguồn dao động bên ngoài như LP, XT, HS và EC.
RC và RCIO (tham khảo phần dao động) Nguyên tắc hoạt động, xem mạch mô tả
Hình 1.13: Sơ đồ khối FSCM trong PIC16F887
FSCM phát hiện sự sai khác dao động bằng cách so sánh dao động bên ngoài với dao động mẫu từ nguồn LFINTOSC được chia 64 Bên trong FSCM có một SR Latch, mỗi xung clock bên ngoài sẽ làm ngõ ra Q=1, và sau khoảng 2ms, dao động mẫu sẽ xóa ngõ ra Q=0 Nếu dao động bên ngoài bị hỏng, sự cố sẽ được phát hiện ngay nếu kéo dài hơn một nửa chu kỳ của dao động mẫu.
DỤNG CỤ THÍ NGHIỆM
Kít thí nghiệm + cáp USB
CƠ SỞ LÝ THUYẾT
2.2.1 Thanh ghi qui định tín hiệu xử lý ở chân vi điều khiển
Hình 2.1: Các chân có thể xử lý tín hiệu số
Hình 2.2: Các chân có thể xử lý tín hiệu tương tự
Những chân có kí hiệu ANx là những chân vừa có thể xử lý tín hiệu số vừa có thể xử lý tín hiệu tương tự
Do đó khi làm việc với những chân này ta cần chú ý đến hai thanh ghi:
Thanh ghi này bao gồm 14 bit từ ANS0 đến ANS13, xác định chế độ hoạt động của các chân từ AN0 đến AN13, cho phép chúng hoạt động như tín hiệu số hoặc tín hiệu analog.
ANSx=0: Cho phép chân ANx xử lý tín hiệu số
ANSx=1: Cho phép chân ANx xử lý tín hiệu tương tự
2.2.2 Chức năng của thanh ghi TRIS
Trong việc xử lý tín hiệu số, các chân có thể hoạt động như ngõ ra (để điều khiển LED, kích transistor, hoặc IC) hoặc ngõ vào (để đọc trạng thái nút nhấn, encoder, hoặc tín hiệu từ cảm biến số) Để thiết lập các chân này làm ngõ ra hoặc ngõ vào, cần chú ý đến thanh ghi TRISx (với x là A, B, C, D, E).
TRISxy=0:Quy định bit thứ y của PORTx là ngõ ra (0= Output)
TRISxy=1:Quy định bit thứ y của PORTx là ngõ vào (1=Input)
(Trong đó:x=A,B,C,D,E; y=0-7) Chú ý: PORTE chỉ có 4 bit thấp: TRISE0, TRISE1, TRISE2, TRISE3
2.2.3 Chức năng của thanh ghi PORT
Trong xử lý tín hiệu số, ngõ ra có thể ở mức cao với điện áp VH hoặc ở mức thấp với điện áp thấp hơn.
V L ) sẽ do bit Rxy của thanh ghi PORTx quy định
Rxy=0: Quy định chân thứ y của PORTx là mức thấp(V L )
Rxy=1: Quy định chân thứ y của PORTx là mức cao(V H )
Tóm lại ta có bảng tóm tắt sau:
ANSx TRISxy Rxy Kết quả
0 1 1 Ngõ vào, tác động mức thấp
1 1 x Xử lý tín hiệu tương tự
Hình 2.3: Ứng dụng xử lý tín hiệu số
2.2.4 Những thanh ghi đặc biệt chỉ có riêng ở PORTB
2.2.4.1 Thanh ghi hỗ trợ điện trở treo bên trong Để tránh trạng thái thả nổi (tín hiệu điện áp ở chân đó không rõ ràng) khi khởi tạo PORTB là ngõ vào số, PIC16f887 tích hợp thêm vào cho PORTB các điện trở kéo lên (pull-up), để sử dụng các điện trở này ta chú ý đến thanh ghi WPUB
WPUBy=0: Không cho phép điện trở kéo lên ở chân thứ y của PORTB
WPUBy=1: Cho phép điện trở kéo lên ở chân thứ y của PORTB
Khi sử dụng điện trở kéo lên ngoài việc sử dụng thanh ghi WPUB còn phải khởi tạo bit: ̅̅̅̅̅̅̅̅
Khi sử dụng PORTB như ngõ vào số, cần khởi tạo điện trở kéo lên, trong khi các PORT khác không hỗ trợ điện trở treo trong Nếu cần thiết, người dùng có thể lắp thêm điện trở bên ngoài để đảm bảo hoạt động ổn định.
2.2.4.2 Ngắt ngoài ở chân RB0 Để xử lý được các tín hiệu tác động tức thời (cạnh lên hay cạnh xuống), chân RB0 có hỗ trợ xử lý ngắt (interrupt) kí hiệu ở chân là INT, khởi tạo ngắt ngoài ở chân RB0 ta cần chú ý đến các bit INTE, INTF
INTE (Interrupt enable): bit cho phép ngắt ở PORTB
Cờ ngắt INTF (Interrupt flag) là một bit tự động được thiết lập bằng 1 khi có sự kiện ngắt xảy ra tại chân RB0, bao gồm cả cạnh lên và cạnh xuống Để lập trình hiệu quả, chúng ta cần xóa bit này sau khi xử lý ngắt.
GIE (Global interrupt): bit cho phép ngắt toàn cục
INTEDG (interrupt edge select bit): bit chọn cạnh tác động để sinh ra sự kiện ngắt ở RB0
INTEDG=1: Xảy ra ngắt khi có tín hiệu cạnh lên
INTEDG=0: Xảy ra ngắt khi có tín hiệu cạnh xuống
Hình 2.4: Sơ đồ ngắt INT ở chân RB0
Các bước khởi tạo ngắt INT:
Bước 1: Khởi tạo chân RB0 là ngõ vào số, điện trở treo
Bước 2: Khởi tạo ngắt INT
INTE=1;//Cho phép ngắt hoạt động INTF=0;//Xóa cờ ngắt thì ngắt lần tiếp theo mới có thể xảy ra
INTEDG= ; //Chọn cạnh tác động ngắt
GIE=1; //Cho phép ngắt toàn cục
PORTB, từ RB0 đến RB7, không chỉ hỗ trợ ngắt INT ở chân RB0 mà còn có khả năng ngắt on-change Ngắt on-change sẽ được kích hoạt khi tín hiệu logic tại các chân của PORTB thay đổi trạng thái.
Các thanh ghi và các bit điều khiển ngắt on-change
Sơ đồ ngắt on-change:
Hình 2.5: Sơ đồ ngắt on-change ở PORTB
IOCBX=0: Không cho phép ngắt on-change ở chân thứ X của
IOCBX=1: Cho phép ngắt on-change ở chân thứ X của PORTB
Các bit khởi tạo khác:
RBIF: Cờ ngắt on-change ở PORTB, cần phải xóa bít này trong lập trình
RBIE: Bit cho phép ngắt on-change của PORTB
GIE: Bit cho phép ngắt toàn cục
Các bước khởi tạo ngắt on-change:
Bước 1: Khởi tạo PORTB là ngõ vào số, có điện trở treo
Bước 2: Khởi tạo ngắt on-change ở PORTB
IOCB=0xFF; //khởi tạo toàn bộ PORTB ngắt on- change (có thể khởi tạo một hay cả PORTB)
RBIE=1; //Cho phép ngắt xảy ra
RBIF=0; //Xóa cờ ngắt GIE=1; //Cho phép ngắt toàn cục
Khi sử dụng ngắt on-change, việc chỉ xóa cờ ngắt (RBIF=0) là không đủ để kích hoạt lần ngắt tiếp theo Cần phải thực hiện thêm thao tác đọc hoặc viết vào thanh ghi PORTB để đảm bảo ngắt được thực hiện đúng cách.
Ví dụ: unsigned char bien; bien = PORTB; //đọc thanh ghi PORTB hoặc PORTB =5; //viết vào thanh ghi PORTB
2.2.4.4 Bảng so sánh giữa ngắt INT và ngắt on-change
Ngắt ở chân INT(RB0) Ngắt on-change
Chỉ có duy nhất ở chân RB0 Xảy ra trên cả PORTB Để xảy ra ngắt thì tín hiệu logic là cạnh lên hoặc cạnh xuống
Chỉ cần tín hiệu logic thay đổi là xảy ra ngắt, không phân biệt cạnh lên hay cạnh xuống
Để thực hiện lần ngắt tiếp theo, cần phải xóa cờ ngắt INTF và thực hiện việc đọc hoặc ghi vào thanh ghi PORTB.
BÀI TẬP THỰC HÀNH
Bài 1: Viết chương trình điều khiển led theo yêu cầu sau:
Nhấn (không giữ) nút nhấn nối với chân RB0: led RE1 và led RE2 chớp tắt xen kẽ trong thời gian T=0.2(s)
Nhấn (không giữ) nút nhấn nối với chân RB1: led RE1 và led RE2 cùng chớp tắt trong thời gian T=0.5(s)
Nhấn (không giữ) nút nhấn nối với chân RB2: led RE1 sáng và led RE2 tắt trong thời gian T=0.1(s), led RE1 tat và LED2 sáng T=0.7(s)
Sử dụng định thời bằng hàm _delay(n), thạch anh Fosc = 4 Mhz
* Bước 1: Tạo một project mới với tên 01_01_MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành các dấu …
The article discusses the configuration settings for a program, including disabling various features such as watchdog timer and low-voltage detection It introduces a delay function and declares subroutines for handling different input/output operations Additionally, it defines a variable to track the number of received signals and outlines the main function of the program.
//Disable analog ở các chân RE1,RE2,RB0,RB1,RB2
ANS6=ANS7=ANS12=ANS10=ANS8= …….;
//Khởi tạo các chân RE1,RE2 là ngõ ra, ban đầu led tắt
//Khởi tạo chân RB0,RB1,RB2 là ngõ vào, tác động mức thấp
TRISB0=TRISB1=TRISB2= …….; RB0=RB1=RB2= …….;
//Khởi tạo điện trở kéo lên ở các chân RB0,RB1,RB2
//Xác định trạng thái các nút nhấn if(!RB0)so_lan_nhan=0; //( -2 -) else if(!RB1)so_lan_nhan=1; //( -3 -) else if(!RB2)so_lan_nhan=2; //( -4 -) if (so_lan_nhan==0)
RB_0(); //Chạy chương trình con RB_0 else if(so_lan_nhan==1)
RB_1(); //Chạy chương trình con RB_1 else if(so_lan_nhan==2)
RB_2(); //Chạy chương trình con RB_2
//( -5 -) void delay(unsigned char counter) //Chương trình con làm tăng thời gian delay
{ unsigned char value=0; while(counter>value)
_delay(100000); //trễ 1ms với Fosc = 4MHz
RE1^=1;RE2= …….RE1; // led RE1 và led RE2 chớp tắt xen kẽ delay(2); //delay 0.2s
RE1^=1;RE2 …….RE1; // led RE1 và led RE2 cùng chớp tắt delay(5); //delay 0.5s
RE1= …….;RE2= …….; // led RE1 sáng và led RE2 tắt delay(1); //delay 0.1s
RE1= …….;RE2= …….; // led RE1 tat và LED2 sáng delay(7); //delay 0.7s
*Bước 3: Biên dịch chương trình, nạp xuống kít thí nghiệm, tiến hành nhấn các nút nhấn và quan sát 2 led
*Bước 4: Thay đổi chương trình như sau:
Thêm vào dòng: ( -1 -) đoạn code sau:
IOCB0=IOCB1=IOCB2=1; //Cho phép ngắt onchange
RBIE=1; //Cho phép ngắt onchange toàn PORTB
PEIE=1; //Cho phép ngắt ngoại vi
GIE=1; //Cho phép ngắt toàn cục
Thêm vào dòng: ( -5 -) đoạn code sau: void interrupt isr() //Chương trình con xử lý tất cả ngắt
{ if(RBIE&&RBIF) //Chương trình con cho ngắt on-change
{ if(!RB0) so_lan_nhan=0; //nhấn RB0 else if(!RB1) so_lan_nhan=1; //nhấn RB1 else if(!RB2) so_lan_nhan=2; //nhấn RB2
*Bước 5: Biên dịch chương trình, nạp xuống kít thí nghiệm, tiến hành nhấn các nút nhấn và quan sát 2 led
*Bước 6: Nhận xét sự khác nhau về tốc độ đáp ứng khi nhấn nút nhấn trước và sau khi sửa code, giải thích, rút ra kết luận:
Bài 2: Viết chương trình điều khiển led theo yêu cầu sau:
Nhấn (không giữ) nút nhấn nối với chân RB0 lần (2n+1): 8 led dịch từ trái qua phải
Nhấn (không giữ) nút nhấn nối với chân RB0 lần (2n): 8 led dịch từ phải qua trái n = 0,1,3,4,5…k
Sử dụng định thời bằng hàm _delay(n); thạch anh Fosc = 4 Mhz
* Bước 1: Tạo một project mới với tên 01_02_MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu …
CONFIG(INTIO&WDTDIS&MCLREN&BORDIS&LVPDIS); char count=0; void display(char number); void main()
//Disable analog các chân RE1,RE2,RB0,RB3,RB4,RB5
ANS6=ANS7=ANS9=ANS11=ANS12=ANS13=…….;
//Khởi tạo RE1, RE2 là ngõ ra, trạng thái ban đầu led tắt
//Khởi tạo RB0 là ngõ vào, tác động mức thấp
//Cho phép điện trở kéo lên ở chân RB0;
//Khởi tạo ngắt ngoài ở chân RB0
{ while(count==1) //Chương trình dịch từ trái qua phải
} while(count==2) //Chương trình dịch từ phải qua trái
} void display(char number) //Chương trình con hiển thị led
RE1=RE2=…….; //Tắt led RE1, RE2
//Khởi tạo chân RB3,RB4,RB5 là tổng trở cao
RB3=RB4=RB5=…….; switch(number)
RE2=…….; //RE2 sáng break; case 2:
RE1=…….; //RE1 sáng break; case 3: //led D6 sáng
TRISB3=0;RB3=…….;TRISB4=…….;RB4=0; break; case 4: //led D7 sáng
TRISB3=…….;RB3=0;TRISB4=…….;RB4=1; break; case 5: //led D8 sáng
TRISB4=…….;RB4=1;TRISB5=…….;RB5=0; break; case 6: //led D9 sáng
TRISB4=…….;RB4=0;TRISB5=…….;RB5=1; break; case 7: //led D10 sáng
TRISB5=0;RB5=…….;TRISB3=0;RB3=…….; break; case 8: //led D11 sáng
TRISB5=0;RB5=…….;TRISB3=0;RB3=…….; break;
* Bước 3: Biên dịch chương trình, nạp xuống kít thí nghiệm, tiến hành nhấn nút nhấn và quan sát các led
* Bước 4:Trả lời câu hỏi: Đề ra các phương pháp xử lý khi ngõ vào tác động mức cao:
Bài 3 Viết chương trình theo yêu cầu sau, 16 led kết nối với PORTD, PORTC (tác động mức cao), 8 nút nhấn kết nối với PORTB: (Tạo một project mới với tên 01_03_MSSV)
Nhấn nút RB0:16 led dịch từ phải qua trái
Nhấn nút RB1:16 led dịch từ trái qua phải
Nhấn nút RB2:16 led chớp tắt xen kẽ
Nhấn nút RB3:16 led sáng dần từ trái qua phải
Nhấn nút RB4:16 led sáng dần từ phải qua trái
Nhấn nút RB5:16 led sáng dần từ trong ra ngoài
Nhấn nút RB6:16 led sáng dần từ ngoài vào trong
Nhấn nút RB7:16 led cùng chớp tắt
Bài 4 Viết chương trình đọc giá trị phím và hiển thị giá trị lên led 7 đoạn theo sơ đồ phần cứng sau: (Tạo một project mới với tên 01_04_MSSV) (Phím từ 0-9, led hiển thị số tương ứng,phím‘*’ thể hiện chữ ‘S’, phím ‘#’ thể hiện chữ ‘H’, lúc không nhấn thể hiện chữ ‘U’)
Bài 5 Viết chương trình đếm số lần nhấn nút (RB0) và hiển thị từ
000 đến 255 lên 3 led bảy đoạn theo sơ đồ phần cứng sau: (Tạo một project mới với tên 01_05_MSSV)
BÀI TẬP TỰ GIẢI
Sau khi hoàn thành chương về mô-đun ADC, sinh viên sẽ có khả năng giải thích khái niệm và chức năng của điện áp tham chiếu, thiết lập điện áp tham chiếu cho khối ADC của vi điều khiển, và liệt kê các bước đo ADC cho một hoặc nhiều kênh Sinh viên cũng sẽ biết cách thiết lập công thức tính ADC 8-bit và 10-bit ở chế độ định dạng canh trái và canh phải, cũng như tính toán giá trị tín hiệu tương tự dựa trên giá trị của thanh ghi ADRESL và ADRESH Cuối cùng, sinh viên có thể thiết lập và khởi tạo một bài tập liên quan đến LCD, điều chỉnh file lcd.h để phù hợp với cấu hình phần cứng bên ngoài.
Kít thí nghiệm + cáp USB
3.2.1 Tín hiệu tương tự và tín hiệu số
Hình 3.1: Đồ thị biễu diễn tín hiệu tương tự và tín hiệu số
DỤNG CỤ THÍ NGHIỆM
Kít thí nghiệm + cáp USB
CƠ SỞ LÝ THUYẾT
3.2.1 Tín hiệu tương tự và tín hiệu số
Hình 3.1: Đồ thị biễu diễn tín hiệu tương tự và tín hiệu số
Trong thực tế, các tín hiệu cần xử lý xung quanh chúng ta chủ yếu là tín hiệu tương tự như vận tốc, nhiệt độ, độ ẩm, cường độ ánh sáng và áp suất Tuy nhiên, vi xử lý chỉ có khả năng làm việc với tín hiệu số, tức là chỉ có hai trạng thái 0 và 1 Do đó, để có thể xử lý các tín hiệu tương tự, vi điều khiển cần được trang bị bộ chuyển đổi tín hiệu tương tự sang số (ADC - Analog to Digital Converter).
Hình 3.2: Bộ ADC được tích hợp bên trong vi điều khiển
Hình 3.3: Đồ thị của bộ chuyển đổi ADC 8-bit
Độ phân giải của bộ chuyển đổi ADC ảnh hưởng trực tiếp đến chất lượng chuyển đổi, với bộ chuyển đổi 8-bit có khả năng tạo ra 256 giá trị điện áp từ VREF- đến VREF+ Nếu độ phân giải là n bit, thì số giá trị có thể đạt được là 2^n Do đó, độ phân giải càng cao, kết quả chuyển đổi càng chính xác.
Điện áp tham chiếu (VREF+) là mức điện áp được sử dụng để so sánh với tín hiệu điện áp tương tự cần đo Để đảm bảo độ chính xác, VREF+ nên được chọn bằng với mức điện áp lớn nhất cần đo, tránh việc chọn mức điện áp nhỏ hơn hoặc lớn hơn.
3.2.2 Bộ ADC của vi điều khiển PIC16F887
3.2.2.1 Các chân vi điều khiển có khả năng xử lý tín hiệu analog
Hình 3.4: Các chân có thể làm việc với tín hiệu tương tự
Hình 3.5: Sơ đồ khối bộ ADC trong vi điều khiển PIC16F887
3.2.2.2 Các thanh ghi điều khiển hoạt động chuyển đổi của bộ ADC
ADON: Bit cho phép bộ ADC hoạt động
ADON=1: Cho phép bộ ADC hoạt động
ADON=0: Không cho phép hoạt động
Bit GO/ ̅̅̅̅̅̅̅̅ chỉ trạng thái chuyển đổi của bộ ADC, tự động trở về 0 khi quá trình chuyển đổi hoàn tất Để thực hiện lần chuyển đổi tiếp theo, bit này cần được đặt lại thành 1 trong lập trình.
CHS: Dùng để chọn kênh cần chuyển đổi
ADCS: Bit lựa chọn tần số chuyển đổi
VCFG1: Dùng để chọn điện áp tham chiếu VREF-
VCFG1=1: khi đó VREF-= điện áp ở chân số 4 (VREF-)
VCFG1=0: khi đó V REF- = V ss
VCFG0: Dùng để chọn điện áp tham chiếu VREF+
VCFG0=1: khi đó VREF+= điện áp ở chân số 5 (VREF+)
VCFG0=0: khi đó VREF+= VDD
ADFM: Bit dùng để lựa chọn kiểu định dạng kết quả chuyển đổi
Sau khi chuyển đổi hoàn tất, kết quả sẽ được lưu theo một trong hai kiểu
Thanh ghi ADRESH và ADRESL trong vi điều khiển PIC16F887 được sử dụng để lưu trữ kết quả của bộ ADC sau khi quá trình chuyển đổi hoàn tất Với độ phân giải 10-bit, bộ ADC cần hai byte để lưu trữ kết quả, có thể theo hai cách: canh trái (ADFM=0) hoặc canh phải (ADFM=1) Khi sử dụng định dạng canh trái (ADFM=0), kết quả có thể được đọc theo cách cụ thể.
Độ phân giải 10-bit:Kết quả = ADRESH*4+ADRESL >> 6.(1)
Độ phân giải 8-bit:Kết quả = ADRESH.(2)
Đối với định dạng kết quả bên phải: ADFM=1 thì ta có thể đọc kết quả như sau:
Độ phân giải 10-bi:Kết quả = ADRESH*256+ADRESL.(3)
Độ phân giải 8-bit:Kết quả = ADRESH*64+ADRESL>>2.(4)
Rõ ràng ta thấy biểu thức (1) và (4) gây khó khăn trong việc lập trình và thời gian cần tín toán lâu hơn, do đó ta rút ra kết luận:
Khi cần đọc ADC 8-bit thì cần định dạng kết quả bên trái(ADFM=0)
Khi cần đọc ADC 10-bit thì cần định dạng kết quả bên phải (ADFM=1)
Khối ADC cũng có thể tạo ra sự kiện ngắt (ngắt trong), sự kiện ngắt xảy ra khi bộ ADC thực hiện xong một chu kỳ chuyển đổi
Hình 3.6: Sơ đồ khởi tạo ngắt ADC
ADIE: Bit cho phép bộ chuyển đổi ADC
ADIF là cờ ngắt của bộ chuyển đổi ADC, tự động được đặt thành 1 khi quá trình chuyển đổi hoàn tất Để thực hiện lần chuyển đổi tiếp theo, chúng ta cần phải xóa bit này thông qua phần mềm lập trình.
PEIE: Bit cho phép ngắt ngoại vi
GIE: Bit cho phép ngắt toàn cục
3.2.2.4 Các bước khởi tạo bộ chuyển đổi ADC
Bước 1: Chọn tín hiệu xử lý
Khởi tạo chân là ngõ vào:TRISxy=1; //x:A,B,E, y:0-7
Khởi tạo chân xử lý tín hiệu tương tự: ANSx=1;
Bước 2: Khởi tạo khối ADC
Chọn tần số chuyển đổi
Chọn điện áp tham chiếu
Chọn định dạng kết quả
Bước 3: Khởi tạo ngắt ADC (có thể bỏ qua bước này nếu không sử dụng ngắt):
Cho phép ngắt ADC: ADIE=1;
Cho phép ngắt ngoại vi: PEIE=1;
Cho phép ngắt toàn cục: GIE=1;
Bước 4: Chờ thời gian khởi tạo
Bước 5: Bắt đầu cho phép chuyển đổi
Bước 6: Chờ cho bộ ADC chuyển đổi hoàn tất bằng các dấu hiệu sau:
Bit GODONE tự động xuống 0, ta có thể sử dụng code sau để thực hiện việc chờ: while(GODONE);
Ngắt ADC xảy ra (Nếu bước 3 được thực hiện)
ADC 8-bit: kết quả = ADRESH //canh trái
ADC 10-bit:kết quả = ADRESH*64+ADRESL //canh phải
Bước 8: Xóa cờ ngắt cho lần chuyển đổi tiếp theo (Nếu bước 3 được thực hiện)
3.2.3 Làm việc với LCD 16x2 Để vi điều khiển PIC16F887 giao tiếp được với LCD đòi hỏi trong chương trình cần phải có những dòng lệnh phù hợp lcd, thường những yêu cầu lệnh này được quy định bởi chip xử lý bên trong lcd, do đó để chương trình ngắn gọn và đơn giản, ta thường xây dựng file lcd.c và lcd.h là những file chứa sẵn những chương trình con có những câu lệnh giao tiếp với lcd, ta chỉ cần khai báo hai file lcd.c và lcd.h thì có thể dễ dàng giao tiếp với lcd bằng những câu lệnh bên trong file đó Các bước khởi tạo và làm việc với LCD:
Bước 1: Kiểm tra phần cứng, phải phù hợp với những khai báo trong file LCD.h
Bước 2: Copy 2 file LCD.c và LCD.h vào thư mục của project đang lập trình
Bước 3: Add hai file trên vào Header file và Source file
− Bước 4: Tối thiểu cần phải khai báo 2 dòng lệnh sau (tô đen) trong chương trình khi có liên quan tới file LCD:
#include “lcd.h” //khai báo thư viện hàm lcd.h void main()
{ lcd_init(); //lệnh khởi tạo lcd
Các lệnh sử dụng cho màn hình LCD có thể tham khảo trong file lcd.c Ví dụ, lệnh lcd_gotoxy(x,y) di chuyển con trỏ đến vị trí trên màn hình, với y là hàng dọc (y=[0,1]) và x là hàng ngang (x=[0,15]) Lệnh lcd_putc('kí tự cần in') cho phép in một ký tự tại vị trí con trỏ, trong khi lệnh lcd_puts("chuỗi cần in") dùng để in một chuỗi tại vị trí con trỏ, lưu ý rằng chuỗi phải nằm giữa hai dấu " " Cuối cùng, lệnh lcd_putc('\f') sẽ xóa màn hình LCD và đưa con trỏ về vị trí ban đầu.
* Ngoài ra còn có thể sử dụng hàm printf nhưng cần khai báo như sau:
#include //khai báo thư viện cho hàm printf
#include “lcd.h” //khai báo thư viện hàm lcd.h void main()
{ lcd_init(); //lệnh khởi tạo lcd while(1);
The provided code snippet defines a function `putch` that takes a character parameter It initializes an integer variable `bien2` with a value of 6, a float variable `bien1` with a value of 66667, and an unsigned integer variable `bien3` with a value of 0 The function then uses `printf` to display various strings on the screen, including "In ra man hinh" and "In ra \n man hinh," demonstrating how to print text and format output Additionally, it shows how to print the value of `bien2` using formatted output, displaying "In gia tri:6" and "In gia tri: 6" with specific formatting.
6 printf(“In gia tri:%03d ”, bien2); //Hiển thị chuỗi In gia tri:006 printf(“In gia tri:%5.3d ”, bien2); //Hiển thị chuỗi In gia tri:
The article demonstrates various ways to use the `printf` function in C to display formatted output For example, `printf("In gia tri:%f ", bien1);` outputs a floating-point value, while `printf("In gia tri:%3d ", bien2);` shows an integer with a minimum width of three characters Additionally, `printf("In gia tri:%03d ", bien2);` formats the integer to be zero-padded, resulting in `006` The use of `printf("In gia tri:%5.3d ", bien2);` further illustrates formatting with a specified width and precision Floating-point values can also be formatted, as seen in `printf("In gia tri:%3.2f ", bien1);`, which rounds the output to two decimal places Lastly, the article includes examples of printing signed integers and long integers, such as `printf("In gia tri:%d ", bien3);` and `printf("In gia tri:%ld ", bien3);`, showcasing the versatility of the `printf` function for different data types.
Khi lập trình với LCD, cần lưu ý rằng nếu phần cứng không có LCD hoặc kết nối LCD không đúng với thư viện LCD.H, chương trình sẽ dừng lại ngay tại lệnh lcd_init();.
BÀI TẬP THỰC HÀNH
Bài 1: Viết chương trình đọc ADC 8 bit ở chân AN3 và thực hiện theo yêu cầu sau:
Hiển thị giá trị thanh ghi ANSEL ở hàng (0,0) của LCD
Hiển thị giá trị điện áp chân AN3 trên hàng (0,1) của màn hình LCD với định thời sử dụng hàm delay Điện áp tham chiếu được thiết lập trong điều kiện Fosc = 4Mhz, và giao tiếp với LCD được thực hiện thông qua thư viện LCD.h.
* Bước 1: Tạo một project mới với tên 02_01_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu
#include //thư viện cho hàm printf();
CONFIG(INTIO&WDTDIS& MCLREN& BORDIS& LVPDIS);
{ lcd_init(); //Khởi tạo LCD
ANS3=…….; //Enable analog ở chân RA3
TRISA3=…….;RA3=…….; //Khởi tạo RA3 là ngõ vào
VCFG0=VCFG1=…….; //Chọn điện áp tham chiếu trong
CHS3=CHS2=…….;CHS1=CHS0=…….;//Chọn kênh đo là AN3 ADFM=…….; // dữ liệu canh trái
ADCS0CS1=…….; //Tần số chuyển đổi
ADON=…….; //Enable module ADC hoạt động while(1)
GODONE=…….;//Cho phép ADC bắt đầu chuyển đổi while(GODONE) …….//Chờ bộ ADC chuyển đổi xong lcd_gotoxy(0,0); printf("\fADRESH:%d",ADRESH); lcd_gotoxy(0,1); printf("Dien ap:%3.2f",ADRESH*5.0/255.0);
} void putch(char c) //Hỗ trợ cho hàm printf in ra LCD
* Bước 3: Biên dịch chương trình, nạp xuống kít thí nghiệm, jum header
3 ở vị trí POT, dùng vít vặn biến trở và quan sát kết quả trên LCD
* Bước 4: Thiết lập công thức quan hệ giữa điện áp và giá trị thanh ghi
Bài 2: Viết chương trình đọc ADC 10 bit ở hai kênh AN3, AN12 và thực hiện theo yêu cầu sau:
Hiển thị giá trị điện áp tại chân AN3 ở hàng (0,0) của LCD
Giá trị điện áp tại chân AN12 được hiển thị ở hàng (0,1) của LCD Để đảm bảo độ chính xác, sử dụng hàm delay để định thời Điện áp tham chiếu trong được thiết lập với tần số Fosc là 4MHz, và giao tiếp với LCD được thực hiện thông qua thư viện LCD.h.
* Bước 1: Tạo một project mới với tên 02_02_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu
CONFIG(INTIO&WDTDIS & MCLREN& BORDIS& LVPDIS);
{ unsigned char old_ADRESH; lcd_init();
//Enable analog ở chân RA3 và chân RB0
//Khởi tạo RA3,RB0 là ngõ vào
VCFG0=VCFG1= …….;//Chọn điện áp tham chiếu trong
ADFM= …….; //Định dạng kết quả bên phải (ADC 10 bit)
ADCS0CS1=0; //Tần số chuyển đổi
ADON= …….; //Enable module ADC while(1)
GODONE= …….; while(GODONE); lcd_gotoxy(0,0); printf("\fAN3 la:%d",ADRESH*256+ADRESL);
GODONE= …….; while(GODONE); lcd_gotoxy(0,1); printf("AN12 la:%d",ADRESH*256+ADRESL);
*Bước 3: Jum header 3 ở vị trí BUTTON
Nhấn giữ nút nhấn SW2 và quan sát kết quả trên LCD ở vị trí (0,0) tức là kết quả đo kênh AN3
Nhấn giữ nút nhấn SW3và quan sát kết quả trên LCD ở vị trí (0,0) tức là kết quả đo kênh AN3
Nhấn giữ cả hai nút SW2,SW3 và quan sát kết quả trên LCD ở vị trí (0,0) tức là kết quả đo kênh AN3
Dựa vào mạch điện nguyên lý, có thể chứng minh ba kết quả quan sát được thông qua các thông số điện trở trên mạch Việc này yêu cầu không sử dụng các thông số đọc được từ màn hình LCD, mà chỉ dựa vào các giá trị điện trở để xác nhận tính chính xác của các kết quả.
Khi thực hiện bước 4, hãy quan sát kết quả hiển thị ở hàng (0,1) của màn hình LCD, cụ thể là giá trị đo được từ kênh AN12 So sánh kết quả này khi nhấn nút RB0 và khi không nhấn nút để đưa ra nhận xét chính xác về sự thay đổi giá trị đo.
BÀI TẬP TỰ GIẢI
Bài 3: Viết chương trình sử dụng khối ADC của vi điều khiển PIC16F887 đo nhiệt độ của 5 phòng và hiển thị lên LCD, sử dụng LM35 theo yêu cầu sau: (Tạo một project mới với tên 02_03_ MSSV)
Khi nhấn (không giữ) RB0 thì LCD hiển thị giá trị giá trị nhiệt độ của phòng 1
Khi nhấn (không giữ) RB1thì LCD hiển thị giá trị giá trị nhiệt độ của phòng 2
Khi nhấn (không giữ) RB2 thì LCD hiển thị giá trị giá trị nhiệt độ của phòng 3
Khi nhấn (không giữ) RB3 thì LCD hiển thị giá trị giá trị nhiệt độ của phòng 4
Khi nhấn (không giữ) RB4 thì LCD hiển thị giá trị giá trị nhiệt độ của phòng 5
Tần số hoạt động của Fosc là 4MHz, với phần cứng LCD được kết nối đến PORTD của vi điều khiển theo sơ đồ chân của thư viện LCD.H Hệ thống sử dụng ADC 8 bit với điện áp tham chiếu bên trong.
Bài 4: Viết chương trình đọc giá trị điện áp ở chân AN0 và hiển thị lên LCD 16x2 theo yêu cầu sau: (Tạo một project mới với tên 02_04_ MSSV)
Hiển thị giá trị hai thanh ghi ADRESH và ADRESL ở vị trí (0,0) của LCD Hiển thị giá trị điện áp đo được ở vị trí (0,1) của LCD
Tần số hoạt động của Fosc là 4MHz, với phần cứng LCD được kết nối qua PORTD của vi điều khiển theo sơ đồ chân của thư viện LCD.H Hệ thống sử dụng điện áp tham chiếu bên ngoài và ADC 10 bit để thực hiện các phép đo.
Bài 5: Viết chương trình dùng vi điều khiển PIC16F887 thực hiện chức năng như một máy tính theo sơ đồ phần cứng sau: (Tạo một project mới với tên 02_05_ MSSV).
DỤNG CỤ THÍ NGHIỆM
Kít thí nghiệm + cáp USB
CƠ SỞ LÝ THUYẾT
Timer là một khối hoạt động độc lập với CPU, giúp giảm thời gian xử lý và tăng tốc độ hoạt động của vi điều khiển Ứng dụng chính của timer là định thời trong khoảng thời gian ngắn và đếm số lượng xung clock bên ngoài, phục vụ cho các ứng dụng như đếm sản phẩm và đọc số xung encoder Vi điều khiển PIC16f887 tích hợp 3 timer: timer 0 (8-bit), timer 1 (16-bit) và timer 2 (8-bit).
Hình 4.1: Sơ đồ khối các timer trong PIC16F887
4.2.1 Nguyên tắc hoạt động của timer
Nguyên tắc hoạt động của timer là tăng giá trị của thanh ghi đếm khi nhận xung clock đã qua bộ chia Khi thanh ghi đạt giá trị tối đa, nếu tiếp tục nhận xung clock, timer sẽ xảy ra sự kiện tràn (overflow), dẫn đến ngắt.
Ví dụ: Thanh ghi TMR0 là thanh ghi 8-bit chứa giá trị đếm của timer 0:
Hình 4.2: Quá trình hoạt động của một timer 0
Timer 0 có những đặc tính sau:
Thanh ghi 8-bit timer/counter (TMR0)
Bộ chia prescaler 8-bit (dùng chung với Watchdog timer)
Hoạt động với xung clock bên trong hoặc bên ngoài
Hoạt động với việc lựa chọn cạnh của xung clock bên ngoài
Hình 4.3: sơ đồ khối timer 0 và watchdog timer trong PIC16F887 4.2.2.1 Các thanh ghi khởi tạo timer 0
T0CS: Bit lựa chọn chế độ hoạt động của timer 0
T0CS=1: Timer 0 hoạt động với chế độ đếm counter.(clock được cấp từ chân T0CKI)
T0CS=0: Timer 1 hoạt động với chế độ định thời timer.(clock được cấp từ thạch anh hoạt động của vi điều khiển)
T0SE: Bit chọn cạnh tác động khi timer hoạt động với chế độ đếm counter
T0SE=1: Thanh ghi TMR0 tăng khi có sự kiện cạnh xuống ở chân T0CKI
T0SE=0: Thanh ghi TMR0 tăng khi có sự kiện cạnh lên ở chân T0CKI
PSA: Dùng để lựa chọn bộ chia Prescaler thuộc về của timer 0 hay của Watchdog
PSA=1:Bộ chia prescaler thuộc về Watchdog
PSA=0: Bộ chia prescaler thuộc về Timer 0
PS: Chọn tỉ lệ cho bộ chia prescaler
4.2.2.2 Công thức định thời timer 0
Hình 4.4: Thời gian hoạt động của timer0 (8-bit)
Từ sơ đồ trên ta thấy thời gian định thời của timer 0:
Do đó để thay đổi thời gian định thời T ta có thể thay đổi một trong các thông số sau:
TMR0’ là giá trị khởi đầu của thanh ghi TMR0 trong timer 0, dùng để bắt đầu quá trình đếm Nếu trong lập trình không chỉ định giá trị ban đầu cho thanh ghi TMR0, thì timer sẽ bắt đầu đếm từ 0 (TMR0’=0).
Prescaler: Tỉ lệ bộ chia của timer 0
Fosc: Tần số hoạt động của vi điều khiển.(Ít khi thay đổi thông số này)
Trong quá trình xác định thời gian định thời T hợp lý, cần lưu ý rằng sẽ có sai số xảy ra Do đó, việc lựa chọn Prescaler và TMR0’ là rất quan trọng để đảm bảo sai số được giảm thiểu đến mức tối thiểu.
Timer 0 cũng có thể xảy ra ngắt, ngắt timer 0 xảy ra khi timer 0 tràn (tức là thanh ghi TMR0%5 rồi sau đó trở về giá trị ban đầu)
Hình 4.5: Sơ đồ ngắt timer 0
T0IE: Bit cho phép xảy ra ngắt timer 0, để ngắt timer 0 xảy ra thì bit này phải bằng 1
T0IF: Cờ ngắt timer 0, khi ngắt xảy ra bít này tự động bằng 1, chúng ta cần phải xóa (=0) bít này trong lập trình
GIE: GIE bit cho phép ngắt toàn cục, để ngắt timer 0 xảy ra thì bit này phải bằng 1
4.2.2.4 Các bước khởi tạo timer 0
Khởi tạo timer 0 hoạt động với chế độ định thời timer
T0CS=0; //Clock cấp cho timer 0 là Fosc (tần số hoạt động của vi điều khiển)
PSA=0; //Bộ chia prescaler được sử dụng cho timer 0
PS= ; //Chọn tỉ lệ bộ chia Nếu PSA=1 thì có thể bỏ qua dòng này
//Khởi tạo ngắt nếu có sử dụng
Khởi tạo timer 0 hoạt động với chế độ đếm counter:
//Khởi tạo chân T0CKI là ngõ vào
T0CS=1; // Clock cấp cho timer 0 từ chân T0CKI
PSA=0; //Bộ chia prescaler được sử dụng cho timer 0
PS= ; //Chọn tỉ lệ bộ chia.Nếu PSA=1thì có thể bỏ qua dòng này
//Khởi tạo ngắt nếu có sử dụng
Timer 1 có hai thanh ghi chứa giá trị đếm, do đó có thể đếm lên đến 65535(2 ^16 -1) mới xảy sự kiện tràn timer, timer 1 thích hợp nhất cho việc đếm xung encoder tốc độ cao, cũng giống như timer 0, timer 1 cũng hoạt động với hai chế độ: chế độ định thời và chế độ đếm counter
Hình 4.6 Sơ đồ khối timer 1 4.2.3.1 Công thức định thời timer 1
T thời gian định thời timer 1
TMR1L, TMR1H: thanh ghi chứa giá trị đếm của timer 1
F OSC: tần số hoạt động của vi điều khiển
Prescaler: tỉ lệ bộ chia
4.2.3.2 Thanh ghi điều khiển timer1
TMR1ON: Bit cho phép timer1 hoạt động
1: Cho phép timer 1 (điều kiện cần cho timer 1 hoạt động, chưa đủ)
TMR1CS: Bit lựa chọn clock cho timer 1
1:Clock từ bên ngoài (từ chân T1CKI)
0:Clock bên trong (FOSC/4) ̅̅̅̅̅̅̅̅̅̅̅: Bit điều khiển lựa chọn đồng bộ clock vào từ bên ngoài của timer 1
1: Clock vào từ bên ngoài không được đồng bộ
0: Clock vào từ bên ngoài được đồng bộ
Không cần quan tâm bit này, timer 1 sử dụng clock bên trong
T1OSCEN: Bit điều khiển cho phép dao động LP
1: Dao động LP được cho phép cho clock timer 1
T1CKPS: Bit lựa chọn tỉ lệ bộ chia Prescale
TMR1GE: Bit cho phép cổng timer 1
Nếu TMR1ON=0: bit này không cần quan tâm
1: Sự đếm lên của timer1 sẽ được điểu khiển bởi cổng Timer 1
0: Timer 1 có thể đếm lên mà không cần quan tâm đến trạng thái của cổng timer 1
T1GINV: Bit đảo cổng timer1
1: Cổng timer1 tác động mức cao (Timer1 hoạt động khi cổng ở mức cao)
0: Cổng timer 1 tác động mức thấp (Timer 1 hoạt động khi cổng ở mức thấp)
Không giống với timer 0, chúng ta cần khởi tạo một số bit thì timer
1 mới có thể hoạt động được Từ sơ đồ trên ta có thể thấy việc bật/tắt timer 1có hai dạng:
Cho phép timer 1 hoạt động mà không cần tác động từ bên ngoài: TMR1ON=1;
Cho phép timer 1 hoạt động khi có tín hiệu logic ở chân ̅̅̅̅̅: //Cho phép điện trở treo
//Khởi tạo chân ̅̅̅̅̅ là ngõ vào
T1GINV= ;//Tùy theo mức logic ở chân ̅̅̅̅̅ là mức cao hay thấp T1GSS=1;
Timer 1 cũng có thể tạo ra sự kiện ngắt khi xảy ra tràn timer, các bit khởi tạo ngắt của timer 1:
TMR1IF: Cờ ngắt, bit này tự động bằng 1 khi có sự kiện tràn timer
TMR1IE:Bit cho phép ngắt tràn timer
PEIE :Bit cho phép ngắt ngoại vi
GIE :Bit cho phép ngắt toàn cục
Hình 4.7: Sơ đồ ngắt timer 1 4.2.3.5 Khởi tạo timer 1 hoạt động với chế độ đếm counter
//Khởi tạo chân T1CKI là ngõ vào số ……
//Khởi tạo ngắt nếu có
Timer 1 có khả năng hoạt động trong chế độ timer với thạch anh ngoài, độc lập với tần số của vi điều khiển Thạch anh ngoài có thể được kết nối với hai chân OSC1 và OSC2, và được khởi tạo tương tự như các chế độ khác.
4.2.3.6 Khởi tạo timer 1 hoạt động với chế độ định thời timer
//Khởi tạo ngắt nếu có
Khác với timer 0 và timer 1, timer 2 chỉ hoạt động ở chế độ định thời Tuy nhiên, timer 2 có hai bộ chia và một giá trị đặt, giúp thời gian định thời linh hoạt hơn và giảm thiểu sai số trong tính toán.
Hình 4.8: Sơ đồ khối timer 2
Khi xung clock tần số Fosc/4 đi qua bộ chia Prescaler vào thanh ghi TMR2, thanh ghi TMR2 sẽ tăng lên Khi giá trị của thanh ghi TMR2 đạt bằng giá trị của thanh ghi PR2, một xung clock sẽ đi qua bộ chia Postscaler, và thanh ghi TMR2 sẽ được reset về giá trị ban đầu Nếu tỉ lệ bộ chia Postscaler được chọn là 1:1, sự kiện ngắt timer 2 sẽ xảy ra.
4.2.4.1 Công thức tính định thời timer 2
T :Thời gian định thời timer 2
PR2 :giá trị thanh ghi đặt
TMR2’ : giá trị bắt đầu đếm của thanh ghi TMR0
Prescaler : Tỉ lệ bộ chia prescaler
Postscaler : Tỉ lệ bộ chia Postscaler
Fosc :Tần số hoạt động của vi điều khiển
4.2.4.2 Thanh ghi điều khiển timer 2
T2CKPS: Bit lựa chọn tỉ lệ bộ chia Prescaler
TMR2ON: Bit cho phép timer 2 hoạt động
0:Không cho phép timer2 hoạt động
1:Cho phép timer 2 hoạt động
TOUTPS: Bit lựa chọn tỉ lệ bộ chia Postscaler
4.2.4.3 Khởi tạo timer 2 hoạt động với chế độ định thời timer:
//Tính toán lựa chọn tỉ lệ bộ chia Prescaler, Postscaler //Chọn tỉ lệ bộ chia Prescaler
//Chọn tỉ lệ bộ chia Postscaler
//Đặt giá trị lớn nhất cho giá trị thanh ghi đếm TMR2 PR2=……;
//Cho phép timer 2 họat động
//Khởi tạo ngắt timer 2 nếu có sử dụng:
Bài 1: Viết chương trình đọc số lần nhấn nút tại chân T0CKI (RA4) và hiển thị kết quả lên LCD
* Bước 1: Tạo một project mới với tên 03_01_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu
CONFIG(INTIO&WDTDIS& MCLREN &BORDIS& LVPDIS);
TRISA4=…….; RA4=…….; //Khởi tạo chân T0CKI là ngõ vào T0CS =…….; //Chọn clock cấp cho Timer0 là từ chân T0CKI T0SE =…….; //Chọn cạnh tác động là cạnh xuống
PSA =…….; //Xung clock không qua bộ chia ( -1 -)
{ lcd_gotoxy(0,0); printf("\fSo lan nhan nut la:\r\n %d",TMR0);
* Bước 3: Nhấn nút nhấn kết nối với chân T0CKI và quan sát kết quả trên LCD
*Bước 5:Thêm vào dòng ( -2 -) đoạn code sau:
PSA=0; //Chọn bộ chia thuộc về timer
PS2=0; //Chọn tỉ lệ chia
* Bước 6: Quan sát kết quả khi nhấn nút nhấn
* Bước 7: So sánh hai kết quả trước và sau khi sửa code, giải thích? Nêu chức năng của bộ chia trong timer 0:
Bài 2: Viết chương trình chớp tắt led RE1, định thời bằng timer 0, tần số Fosc/4
* Bước 1: Tạo một project mới với tên 03_02_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu
CONFIG(INTIO&WDTDIS& MCLREN &BORDIS &LVPDIS); void main()
ANS6=…….;TRISE1=…….; //Khởi tạo chân RE1 tín hiệu số T0CS=…….; //Chọn xung clock cấp cho timer 0 là từ Fosc/4 PSA=…….; //Xung clock sẽ đi qua bộ chia
PS2=…….; PS1=…….; PS0=…….; //Tỉ lệ bộ chia là 128
//Khởi tạo ngắt cho timer 0
T0IE=…….; //Cho phép ngắt timer 0
GIE=…….; //Cho phép ngắt toàn cục while(1); //Lặp vô tận
{ if(T0IE&T0IF) //Chương trình con ngắt cho timer0
TMR0=0;//Khởi tạo giá trị đếm ban đầu cho timer0( 1 ) T0IF=0; //Reset cờ ngắt
* Bước 3: Biên dịch chương trình, nạp xuống kít thí nghiệm, quan sát trạng thái của led
* Bước 4: Tăng giá trị TMR0 ở dòng ( -1 -) từ thấp đến cao (0-
Sau khi biên dịch chương trình và nạp xuống kit thí nghiệm, cần chờ cho đến khi mắt không còn thấy đèn LED RE1 chớp tắt Lúc này, hãy tính tần số tương ứng với giá trị thanh ghi TMR0 vừa tìm được.
* Bước 5: Sử dụng oscillocope đo tần số vừa tìm được và so sánh với kết quả tính toán, nhận xét
Bài 3: Viết chương trình hiển thị đồng hồ lên LCD, sử dụng timer 1 định thời, thạch anh ngoài 32.768KHz
* Bước 1: Tạo một project mới với tên 03_03_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính và hoàn thành vào dấu
CONFIG(INTIO&WDTDIS &MCLREN &BORDIS &LVPDIS);
#include "lcd.h" unsigned char hh,mm,ss;//Khai báo biến chứa giờ, phút, giây void main()
//Khởi tạo các chân ngõ vào thạch anh 32.768KHz
T1OSCEN= …….; //Cho phép timer 1 hoạt động clock ngoài TMR1CS= …….; //Clock cấp cho timer 1 là clock ngoài
T1CKPS1=T1CKPS0=…….; //Tỉ lệ bộ chia 1:8( -1 -) T1SYNC= …….; //Qua khối đồng bộ
//Cho phép timer hoạt động không cần tác động bên ngoài
TMR1IE= …….; //Khởi tạo ngắt timer1
TMR1IF= …….; //Reset cờ ngắt timer 1
PEIE= …….; //Cho phép ngắt ngoại vi
GIE= …….; //Cho phép ngắt toàn cục while(1); //Lặp vô tận
} void putch(char c) //Chương trình con cho hàm printf()
{ if(TMR1IE&&TMR1IF) //Ngắt tràn timer 1
{ ss=0; mm++; if(mm=`) { mm=0; hh++;
} } lcd_gotoxy(0,0); printf("\fBay Gio La:\r\n %02d:%02d:%02d",hh,mm,ss); //Khởi tạo giá trị đếm ban đầu cho timer1
* Bước 3: Nạp chương trình xuống kit thí nghiệm, jumper J1(XTAL)
Quan sát LCD, và so sánh kết quả với đồng hồ thực, nhận xét:
Dựa vào công thức định thời của timer 1, chứng minh những thông số ở các dòng (1),(2),(3):
Bài 4: Viết chương trình sử dụng timer 2 điều khiển led RE1 chớp tắt với chu kì T=0.05s, sử dụng thạch anh ngoài 20MHz
* Bước 1: Tạo một project mới với tên 03_04_ MSSV
* Bước 2: Nhập chương trình sau vào máy tính
CONFIG(HS&WDTDIS &MCLREN& &BORDIS &LVPDIS); void main()
//Khởi tạo chân RE1 là ngõ ra số
T2CKPS1=…….;T2CKPS0=…….; //chọn tỉ lệ bộ chia
//Chọn tỉ lệ bộ chia postscaler hợp lý ( -1 -)
TOUTPS3=0;TOUTPS2=0;TOUTPS1=…….;TOUTPS0=…….; PR2=…….; //Giá trị thanh ghi PR2 ( -2 -) TMR2=…….; //Giá trị bắt đầu đếm của timer 2 ( -3 -) //Cho phép timer 2 hoạt động
TMR2IE=…….; //Cho phép ngắt tràn timer 2
TMR2IF=…….; //Reset cờ ngắt
PEIE=…….; //Cho phép ngắt ngoại vi
GIE=…….; //Cho phép ngắt toàn cục while(1);
{ if(TMR2IE&&TMR2IF)
TMR2IF=…….; //Xóa cờ ngắt Timer2 //Khởi tạo giá trị bắt đầu đếm timer2( -4 -)
* Bước 3: Tính toán các thông số ở dòng (1),(2),(3),(4)
* Bước 4: Hoàn thành vào dấu và biên dịch chương trình, nạp xuống kít thí nghiệm, quan sát led.
BÀI TẬP TỰ GIẢI
Bài 5: Viết chương trình hiển thị tốc độ động cơ lên LCD theo sơ đồ phần cứng sau: (Tạo một project mới với tên 03_05_ MSSV)