Главная » Программирование » PIC - микроконтроллеры » PIC и значения АЦП на ЖКИ
PIC и значения АЦП на ЖКИ

PIC и значения АЦП на ЖКИ

PIC и ЖКИ, часть 2

Я уже рассказывал, как подключать, инициализировать, выводить отдельные символы и строки на дисплей на HD44780. Пора рассмотреть как выводить числовую информацию на примере значений, полученных с АЦП.

Допустим, нужно вывести число 5. Для этого вспомним таблицу кодировки:

Кодировка HD44780

Кодировка HD44780

т.е. нужно вывести на дисплей символ, расположенный в памяти по адресу 0b00110101 (35h или 53). Поскольку символы чисел 0..9 располагаются в таблице по порядку и занимают места 30h..3Ah (48..58), можно вывести любое число 0..9 на дисплей просто прибавив к нему 48 и выполнив команду отображения символа по получившемуся адресу.

unsigned char digit = 0;
DSPL_PUTS_CUR("Число 5",1,1);
digit = 5 + 48;
DSPL_WD(digit);

Соответственно, если нужно вывести на дисплей произвольное число в диапазоне 0..9, то:

void print_digit( unsigned char digit )
{
unsigned char digit = 0;
DSPL_PUTS_CUR("Число x: ",1,1);
digit = digit + 48;
DSPL_WD(digit);
}

Как видно, пока число из одной цифры — проблем нет. Теперь подумаем, что будет при выводе двузначного числа… Потребуется разобраться где десятки, а где единицы. А если число трехзначное? А если вообще неизвестно какое число — может вещественное, т.е. с десятичной точкой? В обычных компьютерах при написании программ на си используется функция printf — форматированный вывод значений. printf выводит все необходимое на стандартное устройство вывода — это дисплей. Но в микроконтроллерах нет дисплея, да и вообще стандартного устройства вывода нет! Поэтому используется функция, которая полностью аналогична функции printf, но в качестве вывода использует виртуальный буфер. Встречайте:

Функция sprinf

Я не буду подробно описывать работу с функцией sprintf, т.к. это тема для отдельно статьи или даже цикла статей, тем более, что в рунете имеется куча материалов по указанной функции. Лучше рассмотрим конкретные примеры по работе с данной функцией. В качестве источника чисел будет АЦП, которым будет измеряться три величины:

  • с переменного резистора
  • с RC-цепочки (PWM)
  • с внутреннего ИОН (узнаем напряжение питания)
sprintf - как искать
Искать лучше именно printf, поскольку про нее написано значительно больше, к тому же странички посвященные sprintf зачастую просто ссылаются на странички посвященные printf.

Прототип функции в stdio.h, синтаксис функции sprintf:

int sprintf(char *buf, const char *format, arg-list)

вывод производится в массив, указанный аргументом buf. Возвращаемая величина равна количеству символов, действительно занесенных в buf. В случае ошибки вернет отрицательную величину.

Организация опроса АЦП

Ранее в примерах опрашивался один канал АЦП, который выбирался при настройке АЦП. Теперь необходимо завести переменную, в которой будет храниться опрашиваемый канал, и, в соответствии со значением этой переменной, писать считанное значение АЦП в соответствующий буфер (в моем случае 16-бит переменная).

Заведем глобальные переменные:

/*#######################################################
 * Глобальные переменные
 */
...
// Переменные для АЦП
unsigned char adc_channel = 0; // Канал АЦП (используются в программе 0, 1, 15)
unsigned short adc_Data0 = 0; // Значение АЦП канала 0
unsigned short adc_Data1 = 0; // Значение АЦП канала 1
unsigned short adc_Data15 = 0; // Значение АЦП канала 15

Обработчик прерывания АЦП:

/*##################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с ВЫСОКИМ ПРИОРИТЕТОМ
 */
void interrupt high_isr (void)
{
// ------------- Прерывание АЦП --------------
if (PIR1bits.ADIF && PIE1bits.ADIE) // Обработали прерывание от AD (АЦП)
 {
 PIR1bits.ADIF = 0; // Сброс прерывания АЦП 

 if (adc_channel == 0)
 {
 adc_Data0 = ADRES;
 adc_channel = 1;
 } else
 if (adc_channel == 1)
 {
 adc_Data1 = ADRES;
 adc_channel = 15;
 } else
 {
 adc_Data15 = ADRES;
 adc_channel = 0;
 }
 ADCON0bits.CHS = adc_channel;
 } // if (PIR1bits.ADIF && PIE1bits.ADIE)
} // high_isr END

Осталось отобразить значения «сырых» данных АЦП:

 sprintf(buffer, "%d", adc_Data0);
 DSPL_PUTS_CUR(buffer,1,1);

 sprintf(buffer, "%d", adc_Data1);
 DSPL_PUTS_CUR(buffer,1,6);

 sprintf(buffer, "%d", adc_Data15);
 DSPL_PUTS_CUR(buffer,1,12);

Напоминаю, что buffer это массив, который определен в файле display_lcd.h. И так, по этой программе у меня выводится три цифровых значения в верхнюю строчку дисплея:

напряжение на движке переменного резистора, напряжение V0, измеренное значение Vref. Нажатие на кнопку корректирует контрастность дисплея и очищает дисплей.

Проект программы:

ЖКИ + АЦП + sprintf (1)
ЖКИ + АЦП + sprintf (1)
project_display_lcd_print_1.zip
238.8 KiB
94 Downloads
Детали

После запуска программы приветствие и вывод значений АЦП на дисплей:

Цифры наложились поверх написанного текста

Цифры наложились поверх написанного текста

Чтобы очистить дисплей от мусора, нажать кнопку:

Жмак на кнопку - очистили дисплей

Жмак на кнопку — очистили дисплей

Поскольку значения АЦП выводятся в цикле, они появляются сразу же после очистки.

В крайнем правом положении переменного резистора значение АЦП максимально и равно 1023:

Переменник вправо - максимальное значение АЦП

Переменник вправо — максимальное значение АЦП

В крайнем левом положении переменного резистора значение АЦП минимально и должно быть равно 1023… но:

Переменник влево - получилась фигня

Переменник влево — получилась фигня

Произошло вот что. Строки кода:

 sprintf(buffer, "%d", adc_Data0);
 DSPL_PUTS_CUR(buffer,1,1);

действительно выводят 0 на дисплей, но те цифры, которые выводились пока я крутил резистор влево никто не стирал, поэтому в моем случае остались цифры 283. Жмак на кнопку — лишнее стерлось:

Все то же самое, но после жмак на кнопку - очистка дисплея

Все то же самое, но после жмак на кнопку — очистка дисплея

Стирай!
Таким образом, при выводе на дисплей необходимо следить за своевременным стиранием лишних символов с дисплея.
Не мерцай!
Однако, если неправильно организовать стирание, например с большой частотой записывать новые данные затирая перед этим старые, то можно получить различные нежелательные эффекты, такие как мерцание и т.п.

Частично поможет сама же функция sprintf. Можно указать, сколько знакомест зарезервировано под числовое значение и указать выравнивание (влево или вправо), в этом случае sprintf сама записывает пробелы вместо ненужных цифр. В коде ниже я зарезервировал 4 знакоместа под каждое число с выравниванием вправо:

 sprintf(buffer, "%4d", adc_Data0);
 DSPL_PUTS_CUR(buffer,1,1);

 sprintf(buffer, "%4d", adc_Data1);
 DSPL_PUTS_CUR(buffer,1,6);

 sprintf(buffer, "%4d", adc_Data15);
 DSPL_PUTS_CUR(buffer,1,12);

Аналогично, но с выравниванием влево:

 sprintf(buffer, "%-4d", adc_Data0);
 DSPL_PUTS_CUR(buffer,1,1);

 sprintf(buffer, "%-4d", adc_Data1);
 DSPL_PUTS_CUR(buffer,1,6);

 sprintf(buffer, "%-4d", adc_Data15);
 DSPL_PUTS_CUR(buffer,1,12);
Знаки-знаки...

Имеется одна тонкость: АЦП нашего МК выдает данные в беззнаковом формате, а спецификатор d работает со знаковым целым. Это означает, что если в качестве значения будет передано, например, число 35000, то на дисплее будет выведено число -30536, т.е. отрицательное число! Хотите проверить? Нет проблем:

 sprintf(buffer, "%5d", 35000);
 DSPL_PUTS_CUR(buffer,1,1);

 //sprintf(buffer, "%4d", adc_Data1);
 //DSPL_PUTS_CUR(buffer,1,6);

 sprintf(buffer, "%4d", adc_Data15);
 DSPL_PUTS_CUR(buffer,1,12);

Это происходит потому, что в знаковых числах за знак отвечает самый старший бит. Если старший бит = 0 — число положительное, если старший бит = 1 — число отрицательное. Число 35000 в двоичном виде даст 0b1000 1000 1011 1000, т.е. имеет единичку в старшем разряде. Для целых чисел нужно применять спецификатор u.

Скачать финальный проект этой статьи:

ЖКИ + АЦП + sprintf (2)
ЖКИ + АЦП + sprintf (2)
project_display_lcd_print_2.zip
248.1 KiB
93 Downloads
Детали

В проекте для начального стирания не требуется нажимать на кнопку. Кнопка только регулирует напряжение V0 для изменения контраста дисплея.

Традиционно — все вопросы, пожелания, критика отправляются в комментарии 🙂

Продолжение следует…

Метки:: , , , , , ,

6 Отзывы Ваш отзыв

  1. Egor #

    У меня возникла такая проблема. Можете подсказать?
    Символы по адресам xxxx xx00 — xxxx xx11 воспринимаются как xxxx xx01
    Что это может быть?

    • Может сопли при распайке дисплея или МК?

      • Egor #

        Нет, проверял.
        Да и команды управления курсором работают

      • Вот ссылка на проект
        https://yadi.sk/d/sgMhzpaqiN8Pp

        • Ух! Ничего себе, я думал школьник про лабу спрашивает, а там целый STM32 =)
          У меня этот проект и открыть кроме как notepad++ нечем.
          ======
          В целом, лично у меня с этими дисплеями чаще всего случались косяки при обработке флага BUSY. Я бы от этого и плясал: 1 — в функции void WriteData(uint8_t data) строку WaitBusyFlag(); заменил бы на простой delay. 2. В функции void WriteTxt(uint8_t* text) используется уже упомянутая функция WriteData, в которой последней строчкой идет функция WaitBusyFlag. И по окончании WriteData опять вызывается WaitBusyFlag — это странно…

          • Спасибо. Буду смотреть

Ваш отзыв