mikroPascal for AVR. Урок 2. АЦП, UART и отображение

Это второй по счету урок о 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 порт (в общем я сходил в другую реальность:) ).

Ниже вы можете посмотреть видеоурок в этой статье.