Это второй по счету урок о 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 Кб)
Комментарии (32) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
Автору Спасибо за труды!!! Буду следить...
[Автор]
[Автор]
[Автор]
И, если влезет OneWire.
[Автор]
[Автор]
Хотелось бы так же увидеть уроки по ШИМ, например плавное включение и гашение светодиода. И примеры работы с таймерами.
Написав программу для мигания светодиодом как залить ее на микропроцессор? У меня есть программа AVRDudeProg но ей нужен файл расширения hex, а при сохранении проекта на микропаскале создается файл mpas? С помощью того, что лежит в Tools создается что-то не то. Как быть?
[Автор]
А вот мой вопрос касается функции FloatToStr. Как ограничить количество знаков после запятой? Правило Delphi не работает.
s: string;
n: real;
begin
n := 10/3;
s := FloatTostrF(n,ffFixed,8,2); //8 - количество знаков до запятой, 2 - количество знаков после запятой
end;
Вопрос возник при выводе на LCD дисплей вывода результата измерения значения АЦП, стандартная функция FloatToStr в mikroPascal выводит 5-ть знаков после запятой, а мне надо обрезать показания до двух знаков. Вот и возник вопрос. Еще раз спасибо.
var LCD_RS : sbit at PORTB4_bit; //Ïèøåì,êóäà ïîäêëþ÷èëè äèñïëåé
var LCD_EN : sbit at PORTB5_bit;
var LCD_D4 : sbit at PORTB0_bit;
var LCD_D5 : sbit at PORTB1_bit;
var LCD_D6 : sbit at PORTB2_bit;
var LCD_D7 : sbit at PORTB3_bit;
var LCD_RS_Direction : sbit at DDB4_bit;
var LCD_EN_Direction : sbit at DDB5_bit;
var LCD_D4_Direction : sbit at DDB0_bit;
var LCD_D5_Direction : sbit at DDB1_bit;
var LCD_D6_Direction : sbit at DDB2_bit;
var LCD_D7_Direction : sbit at DDB3_bit;
var
read_adc: array[17] of char;
read_adc_temp: real;
begin
DDRB:=0xFF;
ADC_Init();
LCD_Init();
LCD_Cmd(_LCD_CLEAR);
LCD_Cmd(_LCD_CURSOR_OFF);
While true do begin
read_adc_temp:=(ADC_Read(0)*0.0049)*2;
FloatToStr(read_adc_temp,read_adc);
read_adc[5]:=0;
lcd_out(1,6,'V ');
lcd_out(1,1,read_adc);
end;
end.
Изменил переменную read_adc как array of char, я получил возможность регулировать длину выводимого сообщения при помощи задания длины по средством read_adc[5]:=0;, где [5] это общее количество выводимых знаков включая запятую. Теперь если напряжение меньше 10 вольт, то на экране мы видим 5.123V, а если больше, то 10.12V.
Может кому то и понадобится.
С возрастом восприятие новой информации становится затруднительным процессом, а так как с молодых годов занимался Ассемблером и Паскалем, то эти языки роднее для меня. Честно сказать контроллеры от Atmel не недолюбливаю, больше работаю с ST, Motorola. А AVR сейчас поднял просто из-за наличия старых AT90S4433, решил сделать Вольт-Амперметры для лабораторных блоков питания.
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=248;
d8:byte=128;
d9:byte=144;
d10:byte=064; //0.
d11:byte=121; //1.
d12:byte=036; //2.
d13:byte=048; //3.
d14:byte=025; //4.
d15:byte=018; //5.
d16:byte=002; //6.
d17:byte=120; //7.
d18:byte=000; //8.
d19:byte=016; //9.
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);
pos_p:=strchr(temp_s,'.'); //Ищем "."
if pos_p <> 0xFFFF then
begin
temp_s[(pos_p-1)]:= temp_s[(pos_p-1)] + 10;
temp_s[pos_p]:= temp_s[(pos_p+1)];
temp_s[(pos_p + 1)]:= temp_s[(pos_p + 2)];
end;
for i:=0 to 2 do begin
temp_a_s[i][0]:=temp_s[i];
temp_i[i]:=StrToInt(temp_a_s[i]);
case i of
0:PORTB:=0x1;
1:PORTB:=0x2;
2:PORTB:=0x4;
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;
10:PORTD:=d10;
11:PORTD:=d11;
12:PORTD:=d12;
13:PORTD:=d13;
14:PORTD:=d14;
15:PORTD:=d15;
16:PORTD:=d16;
17:PORTD:=d17;
18:PORTD:=d18;
19:PORTD:=d19;
end;
delay_ms(10);
end;
end;
begin //Основное тело программы
DDRB:=0xFF;
DDRD:=0xFF;
ADC_Init();
While true do begin
read_adc_i:=ADC_Read(0);
read_adc_i:=(read_adc_i*0.0049)*4;
Disp(read_adc_i);
end;
end.
Первое изменение это добавлены еще 10-ть символов, а именно цифра с точкой. Далее заменил
if pos_p 0xFFFF then temp_s[pos_p]:='p';
на
begin
temp_s[(pos_p-1)]:= temp_s[(pos_p-1)] + 10;
temp_s[pos_p]:= temp_s[(pos_p+1)];
temp_s[(pos_p + 1)]:= temp_s[(pos_p + 2)];
end;
Ну и естественно я уменьшил цикл for до трех разрядов.
Еще раз Большое Спасибо Автору.
[Автор]
[Автор]
Еще раз говорю Вам, большое спасибо, ваши статьи написаны весьма простым языком.
[Автор]
Для интересующихся, это архив с проектом работы на 3 сегмента и симуляция в Proteus.