Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
evgenij-byvshev
7 лет назад

Программирование микроконтроллеров. Часть 4-2

Делаем вольтметр

  И снова здравствуйте! В прошлой части этого курса я показал, как можно подключить LCD 16-и символьный 2-х строчный индикатор и инициализировать его. В этой части я покажу как выводить на него информацию.


Сейчас можно попробовать вывести что-нибудь на дисплей.
В главной функции main() напишем следующее:

LCD_ini(); //Инициализируем АЦП
    
    send_byte('G',1);
    send_byte('o',1);
    send_byte('l',1);
    send_byte('o',1);
    send_byte('s',1);



После компиляции программы попробуем запустить проект в Proteus:




Как видно – все прекрасно работает, но символы будут выводиться на одной строке последовательно друг за другом.
Для того чтобы можно было выводить информацию на экране дисплея в произвольном месте нужно написать еще одну функцию:

void set_pos(unsigned char x, unsigned y)
{
    char adress;
    adress=(0x40*y+x)|0b10000000;
    send_byte(adress, 0);
}



У этой функции два входных аргумента — позиция по горизонтали и позиция по вертикали.
В переменной address типа char находится вычисленное значение в зависимости от входных аргументов функции.
Значения входных аргументов мы будем начинать от нуля, а не от единицы. Поэтому чтобы вычислить адрес по y в памяти DDRAM, нам достаточно умножить значение y на 0x40, так как именно с данного адреса начинается 2 строка, а у нас она будет выглядеть как 1. Затем прибавляем x, тем самым получим смещение по горизонтали в данной памяти. И ещё по операции "ИЛИ" вычисленное значение мы сложим с двоичным числом 0b10000000. То есть мы передаём единицу для того, чтобы контроллер дисплея "понял" что мы даём именно такую команду — передача адреса памяти DDRAM, чтобы контроллер дисплея установил указатель именно туда, какую позицию мы даём ему в оставшихся семи младших битах — DB6-DB0.


Чтобы проверить как это работает нужно написать следующий код:

LCD_ini(); //Инициализируем АЦП
    
    set_pos(5,0);  //Печатаем в первой строке
    send_byte('H',1);
    send_byte('e',1);
    send_byte('l',1);
    send_byte('l',1);
    send_byte('o',1);
    send_byte(',',1);

    set_pos(5,1);  //Печатаем во второй строке
    send_byte('G',1);
    send_byte('o',1);
    send_byte('l',1);
    send_byte('o',1);
    send_byte('s',1);
    send_byte('!',1);



Результат работы в программе Proteus:




Таким способом можно выводить на экран любой символ на экран дисплея по одному. Для того чтобы вывести на экран сразу целую строку нужно написать две функции.
В первой функции просто вызывается другая функция с единичкой, чтобы каждый раз не писать единички в которой будут передаваться только данные:

void send_char(unsigned char c)
{
    send_byte(c,1);
}



Во второй функции в цикле попеременно будут перебираться символы и выводиться на экран:

void str_lcd (char str1[])
{
    wchar_t n;
    for(n=0;str1[n]!='\0';n++)
    send_char(str1[n]);
}



И напишем код для проверки:

LCD_ini(); //Инициализируем АЦП
    
set_pos(3,0);  //Печатаем в первой строке
str_lcd("Hello,");

set_pos(8,1);  //Печатаем во второй строке
str_lcd("Golos!");



Результат работы в Proteus:




С выводом информации мы разобрались, теперь нужно вывести информацию, полученную с АЦП предварительно ее обработав для понятного нам вида. Для этого нужно в теле главного цикла добавить следующий код:

V = (float) u*0.0048828; // Переводим в вольты
sprintf(string, "V = %1.2f", V); // форматируем
set_pos(0,1);  //Выставляем курсор
str_lcd(string); //Выводим данные на дисплей



V = (float) u*0.0048828; Здесь мы преобразуем полученное число в вольты. Так как у нас опорное напряжение 5В, а значение регистра 1024, то мы 5/1024=0.0048828 Это коэффициент напряжения. Ну или минимальная величина напряжения при минимальном значении регистра ADCW. То есть если в регистре будет значение 1, то величина напряжения будет равна 0.0048828 В. Поэтому мы в строке, данные ADCW перемножаем с 0.0048828. Слово float в скобке нужно для того чтобы преобразовать переменную u из целочисленной в вещественную с плавающей точкой.


sprintf(string, "V = %1.2f", V); Здесь мы заносим значение напряжения в массив string с форматированием. Сначала мы впишем V = . После ставится знак процента. Он говорит о том сколько знаков будет выведено. 1.2f говорит о том, что мы хотим вывести один знак до запятой и 2 знака после, а буква f говорит, что мы имеем дело со значением вещественным с плавающей точкой.


А в остальных двух строках все ясно из комментариев
set_pos(0,1); //Выставляем курсор
str_lcd(string); //Выводим данные на дисплей


Результат моделирования схемы в Proteus:



Полный текст программы:

#define F_CPU 8000000UL //Рабочая частота МК (8МГц)
#include <avr/io.h>
#include <stdio.h> //Нужна для работы с текстом
#include <stdlib.h> //заголовочный файл стандартной библиотеки языка Си
#include <util/delay.h> //для _delay_ms()
#define e1 PORTB|=0b00001000 //установка линии E в 1
#define e0 PORTB&=0b11110111 //установка линии E в 0
#define rs1 PORTB|=0b00000100 //установка линии RS в 1 (данные)
#define rs0 PORTB&=0b11111011 //установка линии RS в 0 (команда)

char string[10]; //Массив для временного хранения форматированного текста


//Инициализируем порты
void port_ini(void)
{
    DDRD=0xff;
    PORTD=0x00;
    DDRB=0xFF;
    PORTB=0x00;
}

//Отправляем половину байта
void send_half_byte(unsigned char c)
{
    c<<=4;
    e1; //включаем линию Е
    _delay_us(50);
    PORTB&=0b00001111; //стираем информацию на входах DB4-DB7, остальное не трогаем
    PORTB|=c;
    e0; //выключаем линию Е
    _delay_us(50);
}

void send_byte(unsigned char c, unsigned char mode)
{
    if (mode==0) rs0;
    else         rs1;
    unsigned char hc=0;
    hc=c>>4;
    send_half_byte(hc); send_half_byte(c);
}

//Передаем только данные
void send_char(unsigned char c)
{
    send_byte(c,1);
}

//Задаем позицию
void set_pos(unsigned char x, unsigned y)
{
    char adress;
    adress=(0x40*y+x)|0b10000000;
    send_byte(adress, 0);
}

//Выводим строку
void str_lcd (char str1[])
{
    wchar_t n;
    for(n=0;str1[n]!='\0';n++)
    send_char(str1[n]);
}

//Инициализация LCD
void LCD_ini(void)
{
    _delay_ms(15); //Ждем 15 мс
    send_half_byte(0b00000011);
    _delay_ms(4);
    send_half_byte(0b00000011);
    _delay_us(100);
    send_half_byte(0b00000011);
    _delay_ms(1);
    send_half_byte(0b00000010);
    _delay_ms(1);
    send_byte(0b00101000, 0); //4бит-режим и 2 линии
    _delay_ms(1);
    send_byte(0b00001100, 0); //включаем изображение на дисплее, курсоры никакие не включаем
    _delay_ms(1);
    send_byte(0b00000110, 0); //курсор (хоть он у нас и невидимый) будет двигаться влево
    _delay_ms(1);
}

//Инициализируем АЦП
void ADC_ini(){
    ADCSRA |= (1<<ADEN) // Разрешение использования АЦП
    |(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);//Делитель 128
    ADMUX |= (0<<REFS1)|(0<<REFS0); //Подключен внешний опорный ион, внутренний ион выключен
    ADMUX |= (0 << MUX0)|(0 << MUX1)|(0 << MUX2)|(0 << MUX3); // вход PC0
}

//Получаем данные с АЦП
float ADC_convert (void)
{
    ADCSRA |= (1<<ADSC); //Начинаем преобразование
    while((ADCSRA & (1<<ADSC))); //проверим закончилось ли аналого-цифровое преобразование
    return (unsigned int) ADC;
}

//Главная функция
int main(void)
{
   port_ini(); //Инициализируем порты
   ADC_ini(); //Инициализируем АЦП
   LCD_ini(); //Инициализируем АЦП
    
    set_pos(2,0);  //Устанавливаем курсор
    str_lcd("Hello, Golos");


    unsigned int  u;
    float V;  // Переменная для выводимого значения. float так как у нас точность до 2 знаков.


    while (1) 
    {
        
        u = ADC_convert(); //Вызывем преобразование
        V = (float) u*0.0048828; // Переводим в вольты
        sprintf(string, "V = %1.2f", V); // форматируем
        set_pos(0,1);  //Выставляем курсор
        str_lcd(string); //Выводим данные на дисплей

        //проверяем считанное значение
        if (u > 128)
        PORTD = 0b00000001;
        else
        PORTD = 0b00000000;
        if (u > 256) 
        PORTD = 0b00000011;
        if (u > 384)
        PORTD = 0b00000111;
        if (u > 512)
        PORTD = 0b00001111;
        if (u > 640)
        PORTD = 0b00011111;
        if (u > 768) 
        PORTD = 0b00111111;
        if (u > 896) 
        PORTD = 0b01111111;
        if (u > 1020)
        PORTD = 0b11111111;
        _delay_ms(30);
    }
}



Если есть вопросы и предложения по этому уроку, пишите комментарии буду рад ответить на ваши вопросы.


Часть 1
Часть 2-1 Часть 2-2
Часть 3-1 Часть 3-2
Часть 4-1


Мой блог в ЖЖ: http://evgenij-byvshev.livejournal.com

23
258.445 GOLOS
На Golos с February 2017
Комментарии (0)
Сортировать по:
Сначала старые