Это второй по счету урок о mikroPascal PRO for AVR. И в нем мы рассмотрим АЦП, подключение LCD дисплея с контроллером HD44780 и немного о передаче данных по UART.
Сначала рассмотрим АЦП
В предыдущем уроке я упомянул о конфигурации портов на выход. Тогда это нужно было, что бы зажигать светодиоды, подключенные к порту. По логике, для работы с АЦП порт на выход конфигурировать нельзя, но разработчики mikroPascal решили иначе.
Давайте напишем простенькую программку, которая будет считывать значение АЦП с C0 порта C.
program ADC_UART;
begin
DDRB:=0xFF; //Порты на выход
DDRC:=0xFF; //
ADC_Init(); //Инициализация АЦП
While TRUE do begin //Старт цикла
PORTB:=ADC_Read(0); //Отправляем в порт результат
end; //измерения АЦП
end.
Из прошлого урока вы знаете, что DDRB:=0xFF это конфигурация порта B на выход. Тоже самое значит и DDRC:=0xFF. Далее идет строчка ADC_Init() – это и есть инициализация AЦП. Ну и как понятно из названия, в бесконечном цикле я поместил функцию чтения значения АЦП (в скобках указывается канал, с которого надо считать, так в ATmega8 целых 6 каналов в DIP корпусе и 8-мь в TQFP). Вот что получилось:
Но для снятия показаний такого вольтметра нужно переводить числа из двоичной системы счисления в десятичную, что хоть и не сложно, но отнимает какое-то количество времени. По этому, следующая наша программа будет выводить результат измерения на 7-ми сегментный индикатор. Для начала возьмем индикатор с одним разрядом, а позже попытаемся подключить 4-х разрядную махину.
Теперь приступим к написанию кода. У меня в итоге получилось следующее:
program ADC_UART;
var read_adc:integer; //Объявляем глобальную переменную
const //Блок констант
d_0:byte=63;
d_1:byte=6;
d_2:byte=91;
d_3:byte=79;
d_4:byte=102;
d_5:byte=109;
begin //Основное тело программы
DDRD:=0xFF; //Порт на выход
ADC_Init(); //Инициализация АЦП
While TRUE do begin //Бесконечный цикл
read_adc:=ADC_Read(0); //Получаем значение с АЦП
read_adc:=read_adc*0.0049; //Переводим в читабельный формат
case read_adc of //Подбираем, что отбразить
0:PORTD:=d_0;
1:PORTD:=d_1;
2:PORTD:=d_2;
3:PORTD:=d_3;
4:PORTD:=d_4;
5:PORTD:=d_5;
end;
end;
end.
Тут добавились многие знакомые функции паскаля (для тех, кто в нем работал конечно): объявление переменных, констант, оператор «case». Ну а теперь о коде. Так как мы выводим информацию на один индикатор, то смысла в точности нет. Значит можно просто обрезать все значения, кроме единиц, что я и сделал. Но, стандартно в mikroPascal нет никаких функций для работы с 7-ми сегментным индикатором, по этому мне пришлось импровизировать, использовав утилитку под названием «Seven Segment Editor» (о ней я упоминал в предыдущей статье).
Сгенерировав коды чисел 0-5 для индикатора я записал, объявил их как константы и в последствии через оператор case определял, какое число нужно отобразить. А теперь о конвертации числа, считанного АЦП, в читабельный формат. Как правило функция чтения АЦП возвращает значение в двоичном коде, и для операций над ним его нужно конвертировать в десятичную систему счисления. Так как по умолчанию при инициализации АЦП в mikroPascal среда автоматически устанавливает источник опорного напряжения по питанию (5 вольт), то для получения нормального числа нужна вот такая простенькая формула:
Uin(дес.)=Uin(дв.)*0,0049
Где Uin(дес.)-результат преобразования в виде десятичного числа, Uin(дв.)-число в двоичном коде, полученное с АЦП (В данном случае точность АЦП на частотах до 200КГц = 10 бит, до 1МГц = 8 бит и на 2МГц = 6 бит. Думаю дальше продолжать эту линейку смысла нет (погрешность в 0,5 вольта мало кого устроит)).
Закончив с программой для одноразрядного индикатора, попытаемся сделать то же самое для индикатора, в котором 4 разряда.
Так как нам нужно динамически отображать информацию, я создал специальную функцию, которая этим занимается. Ниже приведен ее код:
procedure Disp(input:real); //Объявляем процедуру, отвечающюю за отображение
var i,count,pos_p:integer; //Блок объявления локальных переменных
temp_i:array [0..3] of integer;
temp_a_s:array [0..3] of string [6];
temp_s:string [6];
begin
FloatToStr(input,temp_s); //Переводим переменную со знач. АЦП в string
pos_p:=strchr(temp_s,’.’); //Если есть знак «.» то находим и заменяем его….
if pos_p <> 0xFFFF then temp_s[pos_p]:=’p’;
for i:=0 to 3 do begin //Старт цикла for
temp_a_s[i][0]:=temp_s[i]; //Копируем посимвольно переменную, содер. знач. АЦП
temp_i[i]:=StrToInt(temp_a_s[i]); //То же самое, лишь ещё переводим в массив integer
case i of //Выбираем на какой анод индикатора подать напряжение
0:PORTB:=0x1;
1:PORTB:=0x2;
2:PORTB:=0x4;
3:PORTB:=0x8;
end;
case temp_i[i] of //Выбираем что отобразить на индикаторе
0:PORTD:=d0;
1:PORTD:=d1;
2:PORTD:=d2;
3:PORTD:=d3;
4:PORTD:=d4;
5:PORTD:=d5;
6:PORTD:=d6;
7:PORTD:=d7;
8:PORTD:=d8;
9:PORTD:=d9;
64:PORTD:=dp;
end;
delay_ms(10); //Задержка в 10-100 мс. устанавливает частоту мерцания
end; //индикатора
end;
В основном теле программы значение, считанное с АЦП передается в процедуру. Далее, оно преобразуется из float в string и обрабатывается. Далее число посимвольно записывается в массив типа integer, из которого собственно и формируется на LED индикатор. На создание этой процедуры у меня ушло достаточно большое количество времени (большая часть – изучение особенностей mikroPascal, относительно string). Вот код всей программы (как это не парадоксально, но объявление процедуры и констант больше, чем само тело программыJ ).
program ADC_UART;
var read_adc_i:real;
const d0:byte=192; //Здесь мы объявляем константы. Это символы
d1:byte=249; //которые буду отображаться на дисплее.
d2:byte=164;
d3:byte=176;
d4:byte=153;
d5:byte=146;
d6:byte=130;
d7:byte=216;
d8:byte=128;
d9:byte=144;
dp:byte=127;
procedure Disp(input:real); //Объявляем процедуру, отвечающую за отображение
var i,count,pos_p:integer; //Блок объявления локальных переменных
temp_i:array [0..3] of integer;
temp_a_s:array [0..3] of string [6];
temp_s:string [6];
begin
FloatToStr(input,temp_s); //Переводим переменную со знач. АЦП в string
pos_p:=strchr(temp_s,’.’); //Если есть знак «.» то находим и заменяем его….
if pos_p <> 0xFFFF then temp_s[pos_p]:=’p’;
for i:=0 to 3 do begin //Старт цикла for
temp_a_s[i][0]:=temp_s[i]; //Копируем посимвольно переменную, содер. знач. АЦП
temp_i[i]:=StrToInt(temp_a_s[i]); //То же самое, лишь ещё переводим в массив integer
case i of //Выбираем на какой анод индикатора подать напряжение
0:PORTB:=0x1;
1:PORTB:=0x2;
2:PORTB:=0x4;
3:PORTB:=0x8;
end;
case temp_i[i] of //Выбираем что отобразить на индикаторе
0:PORTD:=d0;
1:PORTD:=d1;
2:PORTD:=d2;
3:PORTD:=d3;
4:PORTD:=d4;
5:PORTD:=d5;
6:PORTD:=d6;
7:PORTD:=d7;
8:PORTD:=d8;
9:PORTD:=d9;
64:PORTD:=dp;
end;
delay_ms(10); //Задержка в 10-100 мс. устанавливает частоту мерцания
end; //индикатора
end;
begin //Основное тело программы 🙂
DDRB:=0xFF; //Порт B и D на выход
DDRD:=0xFF;
ADC_Init(); //Инициализируем АЦП
While true do begin //Сарт цикла
read_adc_i:=ADC_Read(0); //Читаем значение АЦП
read_adc_i:=read_adc_i*0.0049; //Переводим в удобную для восприятия форму
Disp(read_adc_i); //Отправляем в процедуру для отображения
end;
end. //Конец армагеддона 🙂
Как видите, получился не такой уж и маленький код. А всего-то нужно было вывести информацию на индикатор.
Ну побыстрее нужно закончить с индикаторами и перейти к LCD. Тут в mikroPascal уже есть библиотеки и нам не придется изобретать велосипед.
В этот раз, благодаря вышеупомянутой библиотеке , код получился не большим.
Собственно вот и он:
program ADC_LCD;
var LCD_RS : sbit at PORTD2_bit; //Пишем,куда подключили дисплей
var LCD_EN : sbit at PORTD3_bit;
var LCD_D4 : sbit at PORTD4_bit;
var LCD_D5 : sbit at PORTD5_bit;
var LCD_D6 : sbit at PORTD6_bit;
var LCD_D7 : sbit at PORTD7_bit;
var LCD_RS_Direction : sbit at DDD2_bit;
var LCD_EN_Direction : sbit at DDD3_bit;
var LCD_D4_Direction : sbit at DDD4_bit;
var LCD_D5_Direction : sbit at DDD5_bit;
var LCD_D6_Direction : sbit at DDD6_bit;
var LCD_D7_Direction : sbit at DDD7_bit;
var read_adc:string [6]; //Объявляем глобальную переменную
read_adc_temp:real;
begin
DDRD:=0xFF; //Порт на выход
ADC_Init(); //Инициализация АЦП
LCD_Init(); //Инициализация LCD
LCD_Cmd(_LCD_CLEAR); //Очищаем дисплей
LCD_Cmd(_LCD_CURSOR_OFF); //Отключаем курсор
While true do begin //Старт цикла
read_adc_temp:=ADC_Read(0)*0.0049; //Считываем значение с АЦП
FloatToStr(read_adc_temp,read_adc); //
lcd_out(1,1,’U(in)= ‘); //Выводим на LCD «U(in)= »
lcd_out(1,8,read_adc); //Выводим то, что считали с АЦП
end;
end.
Вот и с выводом на LCD покончено. Осталось лишь одно – отправить значение измеренного напряжения по UART и цели, которые я ставил перед этим уроком, достигнуты!
program ADC_LCD;
var read_adc:string [4]; //Объявляем глобальные переменные
read_adc_temp:integer;
begin
ADC_Init(); //Инициализация АЦП
UART1_Init(9600); /Инициализация UART (9600 бод)
While true do begin //
read_adc_temp:=ADC_Read(0)*0.0049; //»Читаем» АЦП
IntToStr(read_adc_temp,read_adc); //переводим, то что считали в string
Uart_Write_Text(read_adc); //Отправляем в UART
delay_ms(100); //Задержка 100 мс
end;
end.
Как видите, код очень маленький и простой. Если добавить переходник USB-UART то можно сделать USB вольтметр J. И в догонку пару слов об этой программе: можно было обойтись и без преобразования в string, а просто передать integer, но мне показалось, что так удобнее.
Кстати, вот и стандартный терминал, который входит в состав mikroPascal. Я выбрал режим отображения данных в виде hex кодов (каждый символ), но так же можно выбрать режим ANCII, DEC и BIN. Кстати, для связи виртуально МК с виртуальным терминалом пришлось использовать виртуальный COM порт (в общем я сходил в другую реальность:) ).
Ниже вы можете посмотреть видеоурок в этой статье.
Прикрепленные файлы:
- mikroPascal_Part2.rar (381 Кб)