[pic16f88] Этап 10. Цифровой вольтметр

Дмитрий Филатов
Рассмотрим модуль АЦП микроконтроллера pic16f88. Особое внимание уделим разнице между левым и правым выравниванием 10-битного результата аналого-цифрового преобразования. Эта схема - каркас вольтметра. Для превращения её в полноценный измерительный прибор необходимо добавить делитель напряжения, защитный стабилитрон и немного изменить код.

Информация
Условия компиляции и прошивки аналогичны 8 этапу.

Радиокомпоненты и оборудование
- микроконтроллер pic16f88
- регистр сдвига 74HC595
- символьный жидкокристаллический индикатор Winstar WH1602A-YGH-CTK (1602H REV.0)
- источник стабилизированного питания напряжением +5 В

Электрическая принципиальная схемаОт предыдущей схема отличается только наличием щупа напряжения, заведённого на 3 ножку МК.

Исходный код прошивки на языке C
Файл pic.c, компилятор SDCC

#include <pic16f88.h>

__code char __at _CONFIG1 conf1 = _CP_OFF & _CCP1_RB0 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_IO;
__code char __at _CONFIG2 conf2 = _IESO_OFF & _FCMEN_OFF;

#define RS_SHCP RA1 //Сдвоенные сигналы: тактовый и тип данных
#define DS RA6
#define E_STCP RA0 //Сдвоенные сигналы: стробирующий и защёлка

void __delay_us(unsigned char t){
while(t--){
__asm
NOP
NOP
NOP
NOP
NOP
__endasm;
}
}

void __delay_ms(unsigned char m){
while(m--){
__delay_us(1000);
}
}


void lcd_write(unsigned char c, unsigned char sig_rs){
int i;
E_STCP = 0; //Здесь Е нас не интересует, может быть любым
for(i=7;i>=0;i--){
DS = (c >> i) & 1;
RS_SHCP = 0; //Здесь важна SHCP, RS может быть любым
RS_SHCP = 1; //-//-
}
E_STCP = 1; //Защёлкнули данные и выставили высокий уровень стробирующего сигнала. Интересуют оба.
RS_SHCP = sig_rs; //Теперь можем пренебрегать SH. Выставили нужный уровень сигнала RS
__delay_us(200); //Теперь имеем сигналы DS (DB7-DB0), для создания которых отработали SH и ST, и сигнал RS
E_STCP = 0; //Начинаем выполнять операцию записи в ЖКИ, STCP не интересует
/*
До тех пор, пока мы снова не защёлкнем данные, на выходах регистра байт не изменится.
Мы можем делать всё, что угодно, но на ножках будет стабильный сигнал.
Операция записи в ЖКИ начинается только при спадающем фронте, поэтому с Е тоже можем
делать всё, что угодно, пока она не была переведена в единицу и затем в ноль.
Все сигналы надо успеть установить до Е = 0, но SH мы можем играться только после
защёлкивания данных. Поэтому в функции представлен единственно верный порядок операций
*/
}

//Рекурсивная функция преобразования типа int в тип char *
void __intToStr(int n, char* c, int l)
{
int i, j;
char t;
if (n == 0){
c[l++] = '\0';
for (i = 0, j = l-2; i<j; i++, j--) {
t = c[i];
c[i] = c[j];
c[j] = t;
}
return;
}
c[l++] = n % 10 + '0';
__intToStr(n/10, c, l);
}

void print_str(unsigned char *c){
unsigned char k;
while (k=*c++) {
lcd_write(k, 1);
}
}

void lcd_init(){
__delay_ms(15);

lcd_write(0b00111000, 0); //Шина 8 бит, 2 строки
__delay_ms(40);

lcd_write(0b00000001, 0); //Очистка экрана
__delay_ms(2);

lcd_write(0b00000110, 0); //Инкремент
__delay_ms(40);

lcd_write(0b00001100, 0); //Включили дисплей
__delay_ms(40);

lcd_write(0b00000001, 0); //Очистили дисплей
__delay_ms(2);
}

main(){
OSCTUNE = 0b00000000; // Калиброванная частота
OSCCON = 0b01100100; // 4MHz
CCP1CON = 0b00000000; // Отключить модуль CCP
ANSEL = 0b00010000; // AN4 - аналоговый вход
ADCON1 = 0b10000000; // Опорное напряжение - от источника питания, правое выравнивание
ADCON0 = 0b01100000; // Канал AN4, делитель /8, модуль выключен
CMCON = 0b00000111; // Отключить компаратор
TRISA = 0b00000000; // PORTA все как output
PORTA = 0b00000000; // Очистка PORTA (RA5:RA0)

lcd_init();

ADON = 1; //Включили модуль АЦП
__delay_ms(20); //Выждали необходимое время Taq

while(1){
unsigned int x;
char s[5]; //Для правого выравнивания: 4 цифры и символ завершения строки. Для левого должно быть char s[6]
GO_DONE = 1;
while(GO_DONE);
x = (ADRESH<<8) + ADRESL;
x = (x * 49)/10;
__intToStr(x,s,0); //Преобразование числового значения в строку
lcd_write(0b0000001, 0); //Очистка дисплея
print_str(s); //Вывод строки на дисплей
print_str(" mV");
__delay_ms(4);
}

/*
Сниппеты
Так надо писать в одном и том же знакоместе:

lcd_write(0b11000011, 0);
lcd_write('t', 1);
__delay_ms(2000); //Задержка для наглядности

lcd_write(0b11000011, 0);
lcd_write('e', 1);
__delay_ms(2000);

lcd_write(0b11000011, 0);
lcd_write('s', 1);
__delay_ms(2000);

lcd_write(0b11000011, 0);
lcd_write('t', 1);

//Опрос состояния кнопки
while(1){
if(RA4 == 1){
lcd_write(0b10000000, 0);
lcd_write('A', 1);
RA4 = 0;
}
}
*/
}

Необходимые пояснения
10-битный результат аналого-цифрового преобразования содержится в двух спаренных 8-битных регистрах ADRESH и ADRESL. Примем, что все 10 бит находятся в состоянии логической единицы. Тогда 16-битная переменная x = (ADRESH<<8) + ADRESL будет содержать одно из двух значений: 0b0000001111111111 при правом выравнивании и 0b1111111111000000 при левом выравнивании. Выравнивание задаётся битом ADFM регистра ADCON1. Если мы переведём эти двоичные числа в привычную нам десятичную систему, получим значения 1023 при правом выравнивании и 65472 при левом. Таким образом, для пересчёта 10-битного результата аналого-цифрового преобразования в напряжение, в вольтах, при использовании в качестве опорного напряжения питания +5 В от стабилизированного источника, необходимо применить формулы V = (x / 1023) * 5 и V = (x / 65472) * 5 при правом и левом выравнивании соответственно. Поэтому, левое выравнивание применяется, когда необходима высокая точность получаемых результатов. Например, если нужно измерить напряжение 2.386 вольт, результат правого выравнивания будет 488 (2.3851 В, а 489 - уже 2.3900 В), а левого 31244 (2.3860 В). В особо точной технике (например, химических измерительных приборах) точность в четвёртом знаке очень даже играет роль (в pH-метре детектируются милливольты).

Для отображения измеренного напряжения на дисплее я написал функцию преобразования числа в строку __intToStr(). Это рекурсивная функция, в которой используется операция взятия остатка от деления на 10. Исходное число читается с конца, по одной цифре. К считанной цифре прибавляется код нуля для конвертирования цифры в символ цифры (в char). Символы цифр накапливаются в массиве char *, но в обратном порядке. Когда число прочитано полностью, к строке прибавляется символ завершения строки, и вся срока переворачивается (осуществляется реверс). Затем полученная строка передаётся в функцию print_str(), описанную ещё на шестом этапе. На дисплее отображается значение напряжения в милливольтах.
2013-12-05