Bài tập: ADC - LCD

2death

Cố Vấn CLB
Staff member
<Bài tập này của lớp C3, tháng 03/2011, tuy nhiên có nhiều trao đổi có giá trị và có sửa bài tập, có thể làm code mẫu để tham khảo, do đó được chuyển sang mục "Tài liệu học tập PIC16F887>

1. Hardware:
Dùng biến trở 10k kết nối với chân AN3 để đọc mức điện áp.
Khi vặn biến trở thì sẽ thay đổi mức điện áp vào chân AN3.

2. Yêu cầu:
- Đọc giá trị ADC từ chân AN3 (giá trị này nằm trong phạm vi 0-1023)

- Tính toán ra giá trị điện áp (Volt) thực tế?
*** Điện áp thực tế nằm trong khoảng từ 0-5V.

- Giá trị điện áp này yêu cầu lấy tới 3 chữ số thập phân: 0.000
- Hiển thị kết quả lên LCD.
Ví dụ: "V_in = 1.352 V"

- Code phải được trình bày rõ ràng, sạch đẹp :D, các cấu hình (configure) và khởi tạo (init) cho port và adc, tính toán, hiển thị phải được viết thành chương trình con.
Tham khảo sample code + xem lại bài giảng tại đây


Lưu ý:
* Nộp code bài tập (chỉ nộp file main.c) vào mail clb.lopc3@gmail.com

* Hạn chót: 23h00, thứ 4, 09/03/2011

P/S:
Xem thêm lý thuyết, bài giảng + cách sửa lỗi khi build do dùng bản mới tại:
http://www.payitforward.edu.vn/forum/threads/9/
 
Anh chị cho em hỏi có thể xem các tập lệnh dùng trong chương trình ở đâu ạ, ngôn ngữ ở đây lai giữa assembly với C em không rõ lắm, mới tham gia lớp thôi. :(
 

2death

Cố Vấn CLB
Staff member
Anh chị cho em hỏi có thể xem các tập lệnh dùng trong chương trình ở đâu ạ, ngôn ngữ ở đây lai giữa assembly với C em không rõ lắm, mới tham gia lớp thôi. :(
Thật ra nó là lệnh C hết mà, có điều mới bắt đầu có lẽ em chưa quen cách lập trình cho vi điều khiển.

Em nên xem trước 3 bài giảng tại mục Tutorials/Hitech PIC trên website:
http://www.payitforward.edu.vn/wordpress/tutorials/hitech-pic/

Trong đó đã có hướng dẫn cách viết 1 chương trình gồm những phần nào, cấu hình ra sao, cách xuất nhập port, ...
 
(1)
// Result format: Right
// ADFM = 1 Right justified
// ADFM = 0 Left justified
ADFM = 1;
................
(2)
//Read ADC result
ADC_result = (ADRESH<<8)|ADRESL;

//V_in > 2.5V <=> ADC_result > 512
---------------------------------------------------------------------------
Cho em hỏi với đoạn code trên thì ADRESH:ADRESL là bao nhiêu trong trường hợp (1) và (2)?
Và sau khi ADRESH<<8 (dịch phải 8 bit) và đem OR với ADRESL thì kết quả sẽ ra như thế nào để đem đi so sánh với 512?
Anh/chị ví dụ bằng bit cho em có được không ạ?

Em xin cảm ơn!
 

2death

Cố Vấn CLB
Staff member
(1)
// Result format: Right
// ADFM = 1 Right justified
// ADFM = 0 Left justified
ADFM = 1;
................
(2)
//Read ADC result
ADC_result = (ADRESH<<8)|ADRESL;

//V_in > 2.5V <=> ADC_result > 512
---------------------------------------------------------------------------
Cho em hỏi với đoạn code trên thì ADRESH:ADRESL là bao nhiêu trong trường hợp (1) và (2)?
Và sau khi ADRESH<<8 (dịch phải 8 bit) và đem OR với ADRESL thì kết quả sẽ ra như thế nào để đem đi so sánh với 512?
Anh/chị ví dụ bằng bit cho em có được không ạ?

Em xin cảm ơn!
Giả sử kết quả chuyển đổi ADC 10-bit của em có giá trị là 1101001000 (số 840 thập phân).
- Chọn ADFM = 1 tức là dịch phải, thì giá trị trong 2 thanh ghi ADRESH:ADRESL là:
xxxxxx11 01001000

- Chọn ADFM = 0 thì kết quả là:
11010010 00xxxxxx

Dùng ADFM =1 hay 0 trong trường hợp nào?

- ADFM = 1: khi cần bộ ADC có độ phân giải 10-bit. Đọc kết quả chuyển đổi ADC bằng lệnh sau: ketqua=(ADRESH<<8)|ADRESL;
Sau khi thực hiện lệnh trên thì ta có ketqua chính là số 1101001000, tức là 840. Vậy là đem so sánh được với 512.

- ADFM=0: Khi cần bộ ADC có độ phân giải 8-bit, như vậy kết quả sau khi chuyển đổi chính là giá trị chứa trong thanh ghi ADRESH, khi này không cần quan tâm đến thanh ghi ADRESL.
ketqua=ADRESH;
 
Thưa anh!
Em nghĩ là ADFM khi chọn = 1 và = 0 là khác nhau chứ ạ? Em đã thử sửa lại ADFM trong sample code = 0 (thay vì là bằng 1) và em nhận thấy kết quả là khác nhau.
Ban đầu, em để biến trở quay hết về bên trái, lúc này led tắt. Sau đó, em quay dần núm biến trở sang phải được 1 lúc thì led sáng. Khi biến trở quay gần hêt sang phải thì led lại tắt.
Vì vậy, em nghĩ là lệnh ketqua=(ADRESH<<8)|ADRESL là khác nhau trong 2 trường hợp ADFM = 1 và = 0. Có phải là ketqua là một giá trị 16 bit không ạ?

Theo như ví dụ của anh và những gì em đọc trong Datasheet thì cái phần "xxxx" của anh được xem mặc định là bit 0 luôn, có phải không ạ? cho nên khi ADFM = 1 thì do các bit đầu = 0, phần còn lại ta sẽ so sánh với 512. Nếu ADFM = 0 thì ketqua có 6 bit đầu = 0, 10 bit sau là giá trị kết quả của ADC. như vậy ta đem so sánh với 2^14 có đúng ko anh?

Em đã thử lại với ADFM = 0 và so sánh ketqua với 2^14 nhưng kết quả không đúng. Anh có thể chỉ cho em biết là em sai ở chỗ nào không anh?
 

2death

Cố Vấn CLB
Staff member
Thưa anh!

Vì vậy, em nghĩ là lệnh ketqua=(ADRESH<<8)|ADRESL là khác nhau trong 2 trường hợp ADFM = 1 và = 0.
....
Nếu ADFM = 0 thì ketqua có 6 bit đầu = 0, 10 bit sau là giá trị kết quả của ADC. như vậy ta đem so sánh với 2^14 có đúng ko anh?
Đã chỉnh sửa phần trả lời ở trên, cảm ơn em.
 

2death

Cố Vấn CLB
Staff member
Sửa bài tập ADC-LCD

Hầu như mọi công việc quan trọng đã thực hiện trong sample code post trong bài ADC (mục tài liệu và chương trình học), để làm bài tập này ta cần thêm:

- Công thức đổi giá trị chuyển đổi ADC (10-bit) thành dạng điện áp chính xác tới 3 chữ số thập phân.

Tầm điện áp đọc về là 0-5V --> ta phải nhân với 1000 để lấy 3 chữ số thập phân --> tầm giá trị cần quy đổi là 0 - 5000.

Ví dụ: điện áp đọc về là 4.356V thì số được lưu trong biến kết quả phải là 4356 (kết quả tính toán phải là số nguyên nên ta phải nhân 1000 thì mới lấy được 3 chữ số thập phân)

Lưu ý đến khi hiển thị lên LCD thì ta thêm kí tự dấu "." để biểu diễn kết quả.

Do ADC là 10-bit nên kết quả "thô" từ 2 thanh ghi ADRESH và ADRESL nằm trong phạm vi 0-1023.

=> Công thức tính:
ketqua=(ADRESH<<8)|ADRESL;
ketqua=ketqua*5*1000/1023;

- 1 đoạn chương trình con là đổi kết quả chuyển đổi ADC thành dạng kí tự để hiển thị lên LCD.

Một số điều chưa tốt trong code bài tập các bạn gửi:
- Về kiểu: biến ketqua nằm trong phạm vi 0-1023 chỉ cần đặt biến là int, không cần tới double.
- Biến toàn cục: Thực ra bài này chưa cần đặt biến toàn cục, biến kết quả có thể đặt trong chương trình ngắt, trước if (...)

Một số vấn đề đã nhắc nhưng một số bạn quên chưa thực hiện:
- Trình bày code theo form.
- Các phần liên quan tới khởi tạo, ngắt, xử lý kết quả để hiển thị lên LCD, ... nên viết thành chương trình con, trong hàm main càng ít chữ càng tốt :)
- 1 dòng code không quá 80 kí tự kể cả chú thích (recommend là 67 kí tự - bằng với chiều dài các dòng chú thích **** hay ----- trong standard form.


Đây là code có thể tham khảo - tác giả Phan Cường (đã được 2death chỉnh sửa một số chỗ về mặt trình bày)
Code:
/****************************************************************
 *
 * PIC Training Course
 * 
 ****************************************************************/

/****************************************************************
 *
 *    Module        : main.c
 *    Description    : ADC_LCD
 *  Tool        : HI-TECH PIC
 *    Chip        : 16F887A
 *     History        : 09/03/2011
 *                
 *    Author        : Phan Cuong, CLB NCKH            
 *    Notes        :
 *                
 *
 ****************************************************************/
 

 /****************************************************************
 * IMPORT
 ****************************************************************/
#include <htc.h>
#include "lcd.h"

__CONFIG(XT & WDTDIS & PWRTEN & MCLREN & UNPROTECT & SWBOREN & 
        IESODIS & FCMDIS & LVPDIS & DEBUGDIS);
__CONFIG(BORV21);

#define _XTAL_FREQ 4000000


//----------------------------------------------------------------
// khoi tao port
void port_init()                            //config port
{
    ANSEL|=1<<3;                            //BAT BIT 3
    ANSELH=0;                                //RA3: INPUT
    TRISA3=1;                                //PORTB: OUTPUT
    TRISB=0x00;                                
    RB0=1;                                    //TAT LED
}


//----------------------------------------------------------------
// khoi tao ADC
void adc_init()                                // CONFIG ADC
{
    ADCS1=1;                                //CHON TAN SO LAY MAU
    ADCS0=0;
    
    VCFG1=0;                                //CHON Vref
    VCFG0=0;
    
    CHS3=0;                                    //CHON KENH AN3
    CHS2=0;
    CHS1=1;
    CHS0=1;
    
    ADFM=1;                                        
    ADON=1;
}


//----------------------------------------------------------------
// khoi tao ngat
void interrupt_init()                        //config chuong trinh NGAT
{
    ADIF=0;                                    //SET CO NGAT BANG 0
    ADIE=1;                                
    GIE=1;                                    
    PEIE=1;
}
//----------------------------------------------------------------
// chuyen so de hien thi
void adc_lcd(int i)
{
    char a, b, c, d;       //tach hang ngan, tram, chuc, don vi
    a = i/1000;
    b = (i - 1000*a)/100;
    c = (i - 1000*a - 100*b)/10;
    d = i - 1000*a - 100*b - 10*c;
    //HIEN THI LEN LCD
    lcd_putc(a + 0x30);
    lcd_puts(".");
    lcd_putc(b + 0x30);
    lcd_putc(c + 0x30);
    lcd_putc(d + 0x30);
    lcd_puts(" V");
}
//----------------------------------------------------------------    

void interrupt isr()                    //chuong trinh ngat
{
    int t1;
    if(ADIE && ADIF)
    {
            
        ADIF=0;
        t1=(ADRESH<<8 |ADRESL);                
        t1=t1*5000/1024;                //do chia nho nhat:5V/1024 
        adc_lcd(t1);
        if(t1>=512)                        //neu Vra3> 2.5V
            RB0=0;
        else
            RB0=1;
    }
}
/****************************************************************
 * MAIN
 ****************************************************************/

void main(void)
{
    port_init();
    adc_init();
    interrupt_init();
    lcd_init();
    
    while(1)
    {
        __delay_ms(100);
        lcd_clear();    
        GODONE=1;        
    };
}
/****************************************************************
 * END OF adc_main.c
 ****************************************************************/
 

khoale90

Trứng gà
Mọi người xem dùm em đoạn code sau. Sao em thay đổi biến trở mà giá trị điện áp hiển thị trên LCD cứ 0.000V hoài :( Em xin cám ơn :)
Code:
#include <htc.h>
#include "lcd.h"
__CONFIG(XT & WDTDIS & PWRTEN & MCLREN & UNPROTECT & SWBOREN &
IESODIS & FCMDIS & LVPDIS & DEBUGDIS); //1st config. Word

__CONFIG(BORV21); //2st config. Word

#define _XTAL_FREQ     4000000
void port_init()
{
ANSEL = 0x08;   // Chon kenh ADC AN3
ANSELH = 0;
TRISA3 = 1;      //RA3 phai la Input de doc ADC
}

//----------------------------------------------------------------
void adc_init()
{
// ADC conversion clock: = Fosc/8
//            ADCS <1:0> = 00    Fosc/2
//            ADCS <1:0> = 01    Fosc/8
//            ADCS <1:0> = 10    Fosc/32
//            ADCS <1:0> = 11    F_RC
ADCS1 = 0;
ADCS0 = 1;

// Voltage reference: Internal Vref
//            VCFG <1:0> = 00    Internal Vref
//            VCFG <1:0> = 11    External Vref
VCFG1 = 0;
VCFG0 = 0;

// Select Input chanel: AN3
//            CHS <3:0> = 0000    Select AN0
//            CHS <3:0> = 0001    Select AN1
//            CHS <3:0> = 0010    Select AN2
//             ...
//            CHS <3:0> = 1101    Select AN13
CHS3 = 0;
CHS2 = 0;
CHS1 = 1;
CHS0 = 1;

// Result format: Right
//                ADFM = 1 Right justified
//                ADFM = 0 Left justified
ADFM = 0;

// Turn on ADC Module
ADON = 1;

//delay to wait for adc module init.
__delay_ms (1);
}

//----------------------------------------------------------------
//interrupt initialization
void int_init()
{
GIE = 1;    //Global Interrupt Enable
PEIE = 1;    //Peripheral Interrupt Enable
ADIE = 1;    //ADC Interrupt Enable
ADIF = 0;    //Clear ADC Interrupt Flag
}
void interrupt isr()
	{
		int ADC_result;
		if (ADIE&&ADIF)
		{	
			ADIF = 0;
			ADC_result = (ADRESH<<8)|ADRESL;
			ADC_result = ADC_result*5*1000/1023;
		}	
			
	}
void hien_thi_lcd(ADC_result)
{
	char nghin,tram,chuc,donvi;
	nghin = ADC_result/1000;
	ADC_result = ADC_result % 1000;
	tram = ADC_result/100;
	ADC_result = ADC_result % 100;
	chuc = ADC_result/10;
	donvi = ADC_result % 10;                
	
	
	lcd_init();
	__delay_ms(100);
	lcd_clear();
	__delay_ms(100);
	while(1)
	{
	lcd_puts("Ket qua:");
	lcd_gotoxy(10,0);
	lcd_putc(nghin + 0x30);
	lcd_puts(".");
	lcd_putc(tram + 0x30);
	lcd_putc(chuc + 0x30);
	lcd_putc(donvi + 0x30);
	lcd_puts("V");
	};
}
void main(void)
{
port_init();
adc_init();
int_init();
hien_thi_lcd();
while(1)
{
__delay_us(50);    //delay between 2 AD conversions
GODONE = 1;        //Set GODONE bit to start conversion
};
}
 

2death

Cố Vấn CLB
Staff member
Mọi người xem dùm em đoạn code sau. Sao em thay đổi biến trở mà giá trị điện áp hiển thị trên LCD cứ 0.000V hoài :( Em xin cám ơn :)
Code:
// Result format: Right
//                ADFM = 1 Right justified
//                ADFM = 0 Left justified
ADFM = 0;

..
			ADC_result = (ADRESH<<8)|ADRESL;
			ADC_result = ADC_result*5*1000/1023;
Do em chọn kiểu định dạng kết quả là "Left justified", tức là dịch kết quả sang trái.

Sau lệnh này: ADC_result = (ADRESH<<8)|ADRESL; --> ADC_result (kết quả 16-bit) có dạng b9_b8_b7_...b1_b0_000000
Trong đó b9...b0 là 10 bit kết quả chuyển đổi ADC và 6 bit không dùng tới sẽ có giá trị = 0.

ADC_result được khai báo là kiểu int.

Sau lệnh:
ADC_result = ADC_result*5*1000/1023; --->có thể đã tràn bit

Khắc phục: ADFM = 1
Khi này kết quả có dạng là 000000_b9_b8_...b1_b0, nên phép tính nhân không bị tràn.
 

khoale90

Trứng gà
Em cám ơn chị nhìu :) Nhưng mà sau khi cho ADFM=1, chỉnh biến trở, LCD vẫn cứ 0.000V :( . Em nghĩ cái hàm void hien_thi_lcd(ADC_result) của em nó có vấn đề khi truyền biến ADC_result từ Ngắt về, em đoán vậy cũng không biết phải không nữa :(
 

Manhdd

Cố Vấn CLB
Staff member
Uhm. Hàm void hien_thi_lcd(ADC_result) khai báo chưa rõ vì chưa khai báo kiểu tham số ADC_result.
Nhưng điều quan trong là lời gọi hàm "hien_thi_lcd();" của bạn không truyền tham số và đặt ở trước hàm while. Do đó, hàm chỉ thực hiện 1 lần trước khi chuyển đổi ADC với tham số ADC_result "tự hiểu" bằng 0.
Để khắc phục điều này, bạn phải đặt hàm trong hàm interrupt isr() (hàm này được gọi mỗi khi ADC chuyển đổi xong).
Thân!
 

vungocbac

Trứng gà
em lam như code tham khảo nhung lại bao lỗi:
Error [499] ; 0. undefined symbol:
_lcd_putc(Bac.obj)

các bác chỉ em với
Hầu như mọi công việc quan trọng đã thực hiện trong sample code post trong bài ADC (mục tài liệu và chương trình học), để làm bài tập này ta cần thêm:

- Công thức đổi giá trị chuyển đổi ADC (10-bit) thành dạng điện áp chính xác tới 3 chữ số thập phân.

....

Code:
/****************************************************************
 *
 * PIC Training Course
 * 
 ****************************************************************/

/****************************************************************
 *
 *    Module        : main.c
 *    Description    : ADC_LCD
 *  Tool        : HI-TECH PIC
 *    Chip        : 16F887A
 *     History        : 09/03/2011
 *                
 *    Author        : Phan Cuong, CLB NCKH            
 *    Notes        :
 *                
 *
 ****************************************************************/
 

 /****************************************************************
 * IMPORT
 ****************************************************************/
#include <htc.h>
#include "lcd.h"

__CONFIG(XT & WDTDIS & PWRTEN & MCLREN & UNPROTECT & SWBOREN & 
        IESODIS & FCMDIS & LVPDIS & DEBUGDIS);
__CONFIG(BORV21);

#define _XTAL_FREQ 4000000


//----------------------------------------------------------------
// khoi tao port
void port_init()                            //config port
{
    ANSEL|=1<<3;                            //BAT BIT 3
    ANSELH=0;                                //RA3: INPUT
    TRISA3=1;                                //PORTB: OUTPUT
    TRISB=0x00;                                
    RB0=1;                                    //TAT LED
}


//----------------------------------------------------------------
// khoi tao ADC
void adc_init()                                // CONFIG ADC
{
    ADCS1=1;                                //CHON TAN SO LAY MAU
    ADCS0=0;
    
    VCFG1=0;                                //CHON Vref
    VCFG0=0;
    
    CHS3=0;                                    //CHON KENH AN3
    CHS2=0;
    CHS1=1;
    CHS0=1;
    
    ADFM=1;                                        
    ADON=1;
}


//----------------------------------------------------------------
// khoi tao ngat
void interrupt_init()                        //config chuong trinh NGAT
{
    ADIF=0;                                    //SET CO NGAT BANG 0
    ADIE=1;                                
    GIE=1;                                    
    PEIE=1;
}
//----------------------------------------------------------------
// chuyen so de hien thi
void adc_lcd(int i)
{
    char a, b, c, d;       //tach hang ngan, tram, chuc, don vi
    a = i/1000;
    b = (i - 1000*a)/100;
    c = (i - 1000*a - 100*b)/10;
    d = i - 1000*a - 100*b - 10*c;
    //HIEN THI LEN LCD
    lcd_putc(a + 0x30);
    lcd_puts(".");
    lcd_putc(b + 0x30);
    lcd_putc(c + 0x30);
    lcd_putc(d + 0x30);
    lcd_puts(" V");
}
//----------------------------------------------------------------    

void interrupt isr()                    //chuong trinh ngat
{
    int t1;
    if(ADIE && ADIF)
    {
            
        ADIF=0;
        t1=(ADRESH<<8 |ADRESL);                
        t1=t1*5000/1024;                //do chia nho nhat:5V/1024 
        adc_lcd(t1);
        if(t1>=512)                        //neu Vra3> 2.5V
            RB0=0;
        else
            RB0=1;
    }
}
/****************************************************************
 * MAIN
 ****************************************************************/

void main(void)
{
    port_init();
    adc_init();
    interrupt_init();
    lcd_init();
    
    while(1)
    {
        __delay_ms(100);
        lcd_clear();    
        GODONE=1;        
    };
}
/****************************************************************
 * END OF adc_main.c
 ****************************************************************/
 

phat1510

Thành Viên PIF
em lam như code tham khảo nhung lại bao lỗi:
Error [499] ; 0. undefined symbol:
_lcd_putc(Bac.obj)

các bác chỉ em với
Thay bằng config này xem bạn :
__CONFIG(FOSC_HS & WDTE_OFF & PWRTE_ON & MCLRE_ON & CP_OFF & BOREN_OFF & IESO_OFF & FCMEN_OFF & LVP_OFF & DEBUG_OFF);
__CONFIG(BOR4V_BOR21V);
 

vungocbac

Trứng gà
AH.
cái này bạn mở file lcd.h ra.
bạn xem cái hàm lcd_putc(" ") trong đó no định nghia sẵn là lcd_putc(" ") hay lcd_putch(" ")
vì 2 cách định nghĩa này người dùng hay mắc lắm.
tớ mới gặp hôm qua.hehe
 
Top