Делаем вольтметр
И снова здравствуйте! В прошлой части этого курса я показал, как можно подключить 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