II. Chuyển đổi ADC trên AVR.
2. Sử dụng ADC Chuyển đổi đơn kênh.
Khái niệm đơn kênh được hiểu là đại lượng cần chuyển đổi là các điện áp đặt trực tiếp trên các chân analog của chip, giá trịđiện áp này được so sánh với 0V của chip, hay nói một cách khác, điện áp cần chuyển đổi và chip AVR có “mass chung”. Chúng ta sẽ minh họa cách sử dụng ADC trên AVR ở chếđộđơn kênh bằng ví dụđọc và hiển thị giá trị ADC trên các LED 7 đoạn. Như minh họa trong hình 4, chúng ta sẽ
dùng 4 LED để hiển thị 4 chữ số của kết quả, do chúng ta đều biết ADC trên AVR có
độ phân giải 10 bit nên kết quả chuyển đổi tối đa là 1023, 4 LED là đủ để hiển thị kết quả này. 4 chip 7447 được dùng đểđiều khiển 4 LED, chúng ta cần 16 đường để xuất dữ liệu hiển thị lên 4 LED vì thế PORTB và PORTC sẽ được dùng cho mục đích này. 4 bit cao của PORTC(PC4:7) chứa chữ số hàng nghìn của kết quả, 4 bit thấp PC0:3 chứa chữ số hàng trăm, 4 bit cao của PORTB(PB4:7) dùng xuất chữ số hàng chục và 4 bit PB0:3 dành cho chữ số hàng đơn vị. Đại lượng cần chuyển đổi là điện áp trên chân ADC0 (kênh 0 của ADC, chân 0 trong PORTA chip ATmega32), điện áp được tạo ra bằng một biến trở RV1. Thay đổi giá trị biến trở, điện áp rơi trên ADC0 thay đổi và
được cập nhật trực tiếp trên các LED. Giá trị hiển thị trên LED không phải là giá trị điện áp mà là giá trị tương đối sau khi chuyển đổi. Trong ví dụ này, tôi sẽ trình bày dạng tổng quát, việc đọc ADC và hiển thị LED được viết trong các chương trình con tương ứng. Bằng cách này, các bạn có thể dễ dàng sửa đổi và mở rộng ví dụ sau này.
List 1 trình bày đoạn code minh họa đọc ADC đơn kênh và hiển thị kết quả trên LED 7 đoạn.
Tôi tạm thời chia đoạn chương trình thành 4 phần, phần 1 là các định nghĩa (dòng 4
chương trình con hiển thị môt giá trị 4 chữ số lên 4 LED 7 đoạn (từ dòng 17 đến 30) và phần 4 là chương trình chính. Chúng ta sẽ tìm hiểu theo từng phần.
- Phần 1: ba dòng 4, 5 và 6 chúng ta định nghĩa 3 biến đại diện tên của 3 mode
điện áp tham chiếu có thể dùng cho ADC. Xem lại bảng 2 chúng ta biết rằng điện áp tham chiếu được chọn thông qua 2 bit REFS trong thanh ghi ADMUX, có 3 loại điện áp có thểđược chọn. Biến AREF_MODE tương ứng với trường hợp chúng ta muốn lấy điện áp trên chân AREF làm điện áp tham chiếu, đối chiếu bảng 2 chúng ta cần set 2 bit REFS bằng 0, và dòng 4 “ #define AREF_MODE 0” thực hiện việc này. Tương tự, biến INT_MODE đại diện cho trường hợp điện áp tham chiếu nội 2.56V và
được định nghĩa cho phép set 1 bit REFS lên 1 “#define INT_MODE
(1<<REFS1)|(1<<REFS0)”. Biến AVCC_MODE đại diện trường hợp điện áp tham chiếu lấy từ chân AVCC. Cuối cùng, biến ADC_VREF_TYPE được định nghĩa là biến chọn mode mà chúng ta thực sự muốn dùng cho ADC, trong ví dụ này tôi chọn
điện áp tham chiếu lấy từ chân AVCC vì thế tôi định nghĩa “#define
ADC_VREF_TYPE AVCC_MODE”. Bit ADC_VREF_TYPE sẽ được gán cho thanh ghi ADMUX khi khởi động ADC trong chương trình chính.
- Phần 2-chương trình con đọc ADC đơn kênh “uint16_t read_adc(unsigned char adc_channel)”: tên chương trình là read_adc và adc_channel là tham số cần truyền cho chương trình con, tham số này là chỉ số kênh muốn đọc (từ kênh 0 đến kênh 7). Giá trị trả về là một số nguyên không dấu 16 bit (kiểu unsigned int của C), tuy nhiên trong ví dụ này tôi dùng kiểu dữ liệu uint16_t thay cho unsigned int, uint16_t là một cách định nghĩa kiểu dữ liệu nguyên không dấu 16 bit của riêng thư
viện gcc-avr. Dòng đầu tiên của đoạn chương trình con (dòng 11) là khai báo kênh muốn đọc bằng cách ghép giá trị kênh cho thanh ghi ADMUX “ADMUX
|=adc_channel;”. Xem lại cấu trúc thanh ghi ADMUX, trong thanh ghi này, ngoài các bit chọn nguồn điện áp tham chiếu REFS thì 5 bit thấp MUX4:0 cho phép chọn kênh ADC cần đọc. Tham khảo them bảng 3 chúng ta thấy rằng 8 giá trịđầu tiên của các bit MUX4:0 (từ 00000 đến 00111 nhị phân) tương ứng với 8 kênh đơn ADC0:7. Chính sự
sắp xếp này cho phép chúng ta ghép trực tiếp giá trị kênh muốn đọc vào thanh ghi ADMUX thông qua dòng lệnh ADMUX |=adc_channel. Chúng ta dùng phép OR “|” vì chúng ta chỉ muốn thay đổi giá trị cá bit MUX mà không muốn làm ảnh hưởng đến giá trị các bit khác trong thanh ghi ADMUX khi chọn kênh. Một chú ý quan trọng là giá trị của tham số adc_channel chỉ trong khoảng từ 0 đến 7 tương ứng với 8 chế độ đọc đơn kênh ADC trong bảng 3. Sau khi kênh đã được chọn, dòng 12 set bit ADCS trong thanh ghi ADCSRA để bắt đầu quá trình chuyển đổi “ADCSRA|=(1<<ADSC);”. Nhưđã đề cập trong khi khảo sát chức năng của bit ADIF trong thanh ghi ADCSRA, sau khi quá trình chuyển đổi kết thúc bit ADIF sẽ được tự động set lên 1, vì thế dòng code 13 được dùng để chờ cho bit này lên 1, tức chờ cho quá trình chuyển đổi kết thúc. Câu lệnh “loop_until_bit_is_set(ADCSRA,ADIF);” được hiểu là lặp cho đến khi bit ADIF trong thanh ghi ADCSRA được set lên 1, lệnh “loop_until_bit_is_set” này
được định nghĩa sẵn trong thư viện gcc-avr. Nếu quá trình chuyển đỗi đã kết thúc, kết quả chuyển đổi sẽđược chứa trong 2 thanh ghi ADCL và ADCH, 2 thanh ghi này
được tựđộng gọp thành thanh ghi 16 bit ADCW (ADC WORD), dòng 14 “return ADCW” trả về kết quả chuyển đổi.
- Phần 3-chương trình con hiển thị số có 4 chữ số lên 4 LED 7 đoạn “void LED7_out(uint16_t val)” : val là số cần hiển thị, chúng ta khai báo 4 biến tạm “dvi, chuc, tram, nghin” đại diện cho các chữ sốđơn vị, chục, trăm và nghìn ở dòng 18.
Đồng thời, một biến tạm temp_val được dùng để lưu giá trị tạm thời của số val như
trong dòng 19 “temp_val=val;”, cách làm này nhằm tránh thay đổi giá trị của bản thân val trong quá trình thao tác. Các dòng code từ 21 đến 26 thực hiện quá trình tách số
val ra thành 4 các chữ số hàng đơn vị, chục, trăm và nghìn. Đây chỉ là phương pháp
đại số thông thường nên tôi sẽ không giải thích thêm cho đoạn này. Hai dòng 28 và 29 xuất giá trị ra 4 LED 7 đoạn. Bốn LED 7 đoạn được điều khiển bởi các IC chuyển mã 7447, giá trị input choc các IC 7447 là các số BCD 4 bit. Vì thế, để xuất 4 chữ số ra 4 LED thông qua 7447 chúng ta cần 4x4=16 bit, trong ví dụ này tôi dùng PORTB và PORTC cho nhiệm vụ này. Bốn bit cao của PORTC sẽ chứa chữ số hàng nghìn, bốn bit thấp chứa chữ số hàng trăm, bốn bit cao của PORTB chứa chữ số hàng chục và bốn bit thấp PORTB chứa sốđơn vị. Dòng code 28 “PORTB=(chuc<<4)+dvi;” xuất 2 chữ
số chục và đơn vị ra PORTB, trong đó hàm “chuc<<4” nghĩa là dịch chữ số hàng chục sang trái 4 vị trí đểđưa chữ số này lên 4 bit cao của PORTB, sau đó cộng chữ sốđơn vị vào 4 bit thấp và cuối cùng là xuất ra PORTB. Tương tự chúng ta có thể xuất 2 chữ
số hàng nghìn và hàng trăm ra PORTC thông qua dòng code 29 “PORTC=(nghin<<4)+tram”.
- Phần 4-chương trình chính: do hầu hết các nhiệm vụđã được thực hiện trong các đoạn chương trình con nên chương trình chính trong ví dụ này khá đơn giản. Hai dòng code 32 và 33 set các thông số cho ADC, dòng 32
“ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS0);” set các bit trong thanh ghi điều khiển ADCSRA, ADC được cho phép hoạt động bởi bit ADEN, các bit ADPS2:0 để
chọn prescaler xung clock (xem lại phần mô tả thanh ghi ADCSRA), trong ví dụ này tôi chọn prescaler = 32 (bạn có thể chọn giá trị khác). Dòng 33
“ADMUX=ADC_VREF_TYPE;” cho phép chọn điện áp tham chiếu bằng cách gán biến ADC_VREF_TYPE mà chúng ta đã định nghĩa trong dòng code 7 cho thanh ghi ADMUX. Bạn cần chú ý là sau khi thực hiện 2 dòng code này, ADC chỉ mới ở tư thế
“sẵn sàng” nhưng vẫn chưa hoạt động, ADC sẽ hoạt động khi chúng ta gọi chương trình con đọc adc. Trong vòng lặp while của chương trình chính chúng ta lần lượt đọc giá trị ADC ở kênh 0 bằng cách gọi chương trình con “read_adc(0)” ở dòng lệnh 39 “ADC_val=read_adc(0);” sau đó hiển thị ra LED 7 đoạn ở dòng 40
“LED7_out(ADC_val);” và cuối cùng là delay 1 khoảng thời gian nhỏ (100ms) trước khi lặp lại quá trình đọc và hiển thị.
Mô phỏng ví dụ: Tạo 1 project bằng Programmer Notepad và type đoạn code trên vào file source (xem phần tạo Project với WinAVR). Biên dịch và chạy mô phỏng với mạch điện trong hình 4. Điều chỉnh giá trị biến trở RV1 để thay đổi giá trịđiện áp input của ADC kênh 0 và xem giá trị hiển thị trên các LED 7 đoạn. Hãy thay đổi giá trị biến ADC_VREF_TYPE trong dòng code 7 sang các mode khác như INT_MODE,
biên dịch và mô phỏng lại chương trình, quan sát và so sánh sự khác nhau giữa các mode điện áp tham chiếu. Bạn sẽ dễ dàng nhận thấy rằng khi chọn điện áp tham chiếu nội 2.56V, khi tăng biến trở đến khoảng giữa thì kết quả chuyển đổi sẽ là 1023(giá trị
lớn nhất của số 10 bit) và nếu tiếp tục tăng biến trở giá trị này sẽ không thay đổi. Điều này có nghĩa là nếu điện áp input lớn hơn điện áp tham chiếu thì kết quả chuyển đổi sẽ
là 1023.
Phần chuyển đổi ADC ở chếđộ so sánh sẽ được trình bày trong 1 dịp khác ở phần