Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: LCD дисплей KS0076
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
Freeze Anti
Здравствуйте.

У меня такая проблема. Есть LCD дисплей с контроллером KS0076, подключенный к микроконтроллеру ATmega32.

Я пытаюсь его инициализировать и вместо нужной мне строки символов получаю какие-то иероглифы вперемежку с черными прямоугольничками...

Вот текст программы, написанной в среде WinAVR...

Код
#include <avr/io.h>
#include <util/delay.h>

#define RD_1 PORTB |= 64 //PORTB = PORTB | 0b1000000 - RD = 1
#define RS_1 PORTB |= 16 //PORTB = PORTB | 0b0010000 - RS = 1
#define E_1 PORTB |= 32 //PORTB = PORTB | 0b0100000 - E = 1
#define RD_0 PORTB &= 63 //PORTB = PORTB & 0b0111111 - RD = 0
#define RS_0 PORTB &= 111 //PORTB = PORTB & 0b1101111 - RS = 0
#define E_0 PORTB &= 95 //PORTB = PORTB & 0b1011111 - E = 0

//Объявляем массив с адресами во внутренней памяти ЖК-модуля,
//соответствующими первой позиции в каждой из 4-х строк дисплея
const unsigned char addLUT[2] = {0x80, 0xC0};
unsigned char LCD_Address, LCD_Line; //Переменные для хранения
                                       //адреса позиции и номера строки ЖК-дисплея
char Buffer[15]; //Буфер для хранения строки, выводимой на дисплей

//Функция записи в ЖК-модуль полубайта
void WriteNibble(unsigned char data)
{
    RD_0; //Режим записи
    E_1; //Активируем передачу
    PORTB = (data & 0x0F); //Передаем данные
    E_0; //Завершаем передачу
    RD_1; //Режим чтения
    _delay_loop_2(5000); //Задержка 5 мс, чтобы ЖК-модуль имел
                          //достаточно времени на обработку данных
}

//Функция записи в ЖК-модуль байта
void WriteByte(unsigned char data)
{
    RD_0; //Режим записи
    E_1; //Активизируем передачу
    PORTB = (data >> 4); //Передаем старший полубайт
    E_0; //Завершаем передачу
    E_1; //Активизируем передачу
    PORTB = (data & 0xF); //Передаем младший полубайт
    E_0; //Завершаем передачу
    RD_1; //Режим чтения
    _delay_loop_2(3000); //Задержка 3мс, чтобы ЖК-модуль имел
                          //достаточно времени на обработку данных
}

//Функция перехода к первой позиции строки с номером LineNum
void GoToLine(char LineNum)
{
    RS_0; //Передача команды
    LCD_Address = addLUT[LineNum - 1]; //Определяем адрес строки
    WriteByte(LCD_Address); //и передаем его в ЖК-модуль
    RS_1; //Конец передачи команд
    LCD_Address = 0; //Обнуляем адрес
    LCD_Line = LineNum; //Устанавливаем текущую строку
}

//Функция очистки дисплея
void ClearLCD(void)
{
    RS_0; //Передача команды
    WriteByte(0x01); //Команда очистки, курсор перемещается
                      //в исходную позицию
    _delay_loop_2(10000); //Задержка 10мс, чтобы ЖК-модуль имел
                           //достаточно времени на обработку команды
    RS_1; //Конец передачи команд
    GoToLine(1); //Переходим к строке 1
}

//Функция установки текущей позиции col в строке row дисплея
void SetLCDPosition(char row, char col)
{
    RS_0; //Передача команды
    LCD_Address = addLUT[row - 1] + col; //Определяем позицию дисплея
    WriteByte(LCD_Address); //Записываем в ЖК-модуль адрес
    RS_1; //Конец передачи команды
    LCD_Line = row; //Устанавливаем текущую строку
}

//Функция отображения в текущей позиции на дисплее символа
void ShowChar(unsigned char c)
{
    RS_1; //Передача данных
    WriteByte(c); //Записываем символ
    LCD_Address++; //Увеличиваем адрес на 1
    switch(LCD_Address) //Определяем, произошел ли
                         //переход на новую строку дисплея
    {
        case 20:
            GoToLine(2);
            break;
        case 40:
            GoToLine(1);
            break;
    }
}

//Функция вывода на дисплей строки
void ShowStr(unsigned char *s)
{
    while(*s != 0)
        ShowChar(*s++);
}

//Функция инициализации ЖК-модуля
void InitLCD(void)
{
    RD_1; //предустановка управляющих сигналов
    E_0;
    RS_0;
    _delay_loop_2(50000); //Задержка на 50мс, чтобы ЖК-модуль
                           //имел достаточно времени на инициализацию
    WriteByte(0x33);
    _delay_loop_2(1100);
    WriteByte(0x33);
    WriteByte(0x33);
    WriteByte(0x38); //Определение параметров развертки и ширины шины данных сейчас 0x38 если не получится - попробовать 0x28
    WriteByte(0x01);
    WriteByte(0x10); //Устанавливаем режим смещения курсора
    WriteByte(0x06); //вправо, без сдвига содержимого дисплея
    WriteByte(0x0C); //Включаем дисплей, прячем курсор
    for(char i = 0x40; i < 0x5F; i++) //Инициализируем память
                                         //знакогенератора
    {
        _delay_loop_2(10000);
        RS_0;
        WriteByte(i);
        _delay_loop_2(10000);
        ShowChar(0);
    }
    RS_1; //Переходим в режим передачи данных
    SetLCDPosition(1, 4); //Переходим в первую строку дисплея
}

void main(void)
{
    DDRB = 0xFF; //Выводы 0 - 6 порта B - выходы (передача данных и управляющие сигналы)
    InitLCD(); //Инициализируем ЖК-дисплей
    while(1)
    {
        char Test[] = {0x53, 0x65, 0x63, 0xBF, 0x6F, 0xB3, 0x61, 0xC7, 0x00};
        char String[] = {0x63, 0xBF, 0x70, 0x6F, 0xBA, 0x61, 0x00};
        ClearLCD();
        ShowStr(Test);
        SetLCDPosition(2, 1);
        ShowStr(String);
        _delay_loop_2(10000);
    }
}


Данные передаются по четырем линиям, подключенным к PORTB, к этому же порту подключены и выводы, отвечающие за управляющие сигналы R/W, RS и ES.

Объясните, пожалуйста, что я делаю не так?
mempfis_
Добрый день.
Может быть проблема связана с выбором страницы знакогенератора.
У меня в индикаторе Мэлт 2 страницы, которые имеют разный набор отображаемых символов.
Поэтому часто было такое, что индикатор отображал то буквы,то иероглифы smile.gif
После подключения нужной страницы знакогенератора все проблемы с отображением пропали.
Проверьте процедуру инициализации индикатора. Ниже привожу свои процедуры записи кода/данных а также инициалии дисплея и записи в него строковых констант при которой всё работает нормально:


// П/П записи в дисплей комманд или данных

void WriteCode(char CODE)
{
//Процедура выдачи байта в индикатор как команды (+ задержка 40 мкс)
LCD_CTRL_PORT &= (~(1<<A0_LCD)); //Установить A0=0 - выдача команд в индикатор

//записываем старший полубайт
LCD_DATA_PORT=CODE;
//строб защёлкивания старшего полубайта
LCD_CTRL_PORT |=(1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &=(~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)

//записываем младший полубайт
LCD_DATA_PORT=(CODE<<4);
//строб защёлкивания младшего полубайта
LCD_CTRL_PORT |= (1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &= (~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)


//задержка на выполнение команды
delay_us(40);

//Только здесь можно снова изменять состояние сигналов R/W и A0 индикатора!
LCD_CTRL_PORT &= (~(1<<A0_LCD)); //Установить A0=0 - выдача команд в индикатор
}

void WriteData(char DATA)
{
//Процедура выдачи байта в индикатор как данные (+ задержка 40 мкс)
LCD_CTRL_PORT = LCD_CTRL_PORT|((1<<A0_LCD)); //Установить A0=0 - выдача команд в индикатор
//записываем старший полубайт
LCD_DATA_PORT=DATA;
//строб защёлкивания старшего полубайта
LCD_CTRL_PORT = LCD_CTRL_PORT|(1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT = LCD_CTRL_PORT&(~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)

//записываем младший полубайт
LCD_DATA_PORT=(DATA<<4);
//строб защёлкивания младшего полубайта
LCD_CTRL_PORT = LCD_CTRL_PORT|(1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT = LCD_CTRL_PORT&(~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)

//задержка на выполнение команды
delay_us(40);

//Только здесь можно снова изменять состояние сигналов R/W и A0 индикатора!
LCD_CTRL_PORT = LCD_CTRL_PORT&(~(1<<A0_LCD)); //Установить A0=0 - выдача команд в индикатор
}

//константы для инициализации дисплея
//очистка индикатора 0b00000001
//delay 1.5 mS
#define DISPLEY_CLEAR 0b00000001

//перемещение курсора в левую позицию 0b00000010
//delay 40 mkS
#define CURSOR_HOME 0b00000010

//установака направления сдвига и разрешение сдвига дисплея 0b000001 I/D SH
//I/D direction
//SH shift enable
//delay 40 mkS
#define DISPLEY_SHIFT_INI 0b00000110

//установка параметров индикатора 0b00001DCB
//D=1 displey on
//C=1 cursor on
//B cursor_type 0-lyne, 1-block
//delay 40 mkS
#define DISPLEY_ON 0b00001111
#define DISPLEY_OFF 0b00001000

//сдвиг дисплея или курсора 0b0001 S/C R/L 00
//S/C displey or cursor
//R/L right left
//delay 40 mkS
#define SHIFT_DISPLEY_L 0b00011000
#define SHIFT_DISPLEY_R 0b00011001
#define SHIFT_CURSOR_L 0b00010000
#define SHIFT_CURSOR_R 0b00010001

//установка разрядности интерфейса и страницы знакогенератора 0b001 DL 10 P 0
//DL 0-4 bits, 1-8 bits
//P page 0 or 1
//delay 40 mkS
#define DATA_LINE4_PAGE0 0b00101000
#define DATA_LINE4_PAGE1 0b00101010

//установка нулевого адреса области DDRAM 0b10000000
//delay 40 mkS
#define START_DDRAM_ADDR 0b10000000

void Displey_ini(void)
{
wdr();
// запрет всех прерываний
cli();

static char temp_LCD_DATA_PORT;
//сохраняет текущее значение порта данных индикатора
temp_LCD_DATA_PORT=LCD_DATA_PORT;

// устанавливаем А0 и Е в 0
LCD_CTRL_PORT = LCD_CTRL_PORT&(~(1<<A0_LCD));
LCD_CTRL_PORT = LCD_CTRL_PORT&(~(1<<E_LCD));

//задержка на 30 ms
delay_ms(30);

//в следующих 4-х командах необходимо защёлкнуть в индикатор только старший полубайт
//записываем старший полубайт
LCD_DATA_PORT=0x30;
//строб защёлкивания старшего полубайта
LCD_CTRL_PORT |= (1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &= (~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)
//Задержка на 40 mkS
delay_us(40);
//строб защёлкивания старшего полубайта
LCD_CTRL_PORT |= (1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &= (~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)
//Задержка на 40 mkS
delay_us(40);
//строб защёлкивания старшего полубайта
LCD_CTRL_PORT |= (1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &= (~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)
//Задержка на 40 mkS
delay_us(40);

LCD_DATA_PORT=0x20;
//строб защёлкивания младшего полубайта
LCD_CTRL_PORT |= (1<<E_LCD); //Установить E=1 (строб записи в индикатор)
LCD_CTRL_PORT &= (~(1<<E_LCD)); //Установить E=0 (строб записи в индикатор)
//Задержка на 40 mkS
delay_us(40);

//в этих командах защёлкиваются оба полубайта
WriteCode(0x08); //Установка параметров индикатора
//Задержка на 40 mkS
delay_us(40);

WriteCode(DISPLEY_CLEAR); //Очистка индикатора
//задержка на 1,5 ms
delay_us(1500);

WriteCode(DISPLEY_SHIFT_INI); //Установка режима ввода данных в область DDRAM
//Задержка на 40 mkS
delay_us(40);

WriteCode(DISPLEY_ON); //Включение дисплея
//Задержка на 40 mkS
delay_us(40);

WriteCode(START_DDRAM_ADDR); //Установка адреса записи (и курсора) в нулевую позицию
//Задержка на 40 mkS
delay_us(40);

WriteCode(DATA_LINE4_PAGE1); // установка разрядности и страницы знакогенератораа
//Задержка на 40 mkS
delay_us(40);

WriteCode(CURSOR_HOME); // Установка курсора в нулевую позицию
//Задержка на 40 mkS
delay_us(40);

//возвращаем начальное значение порта данных индикатора
LCD_DATA_PORT=temp_LCD_DATA_PORT;

}

volatile char DispleyBuffer[32];

//процедура переноса данных из буфера дисплея в память данных дисплея
void WriteDispley(void)
{

wdr();
// запрет всех прерываний
cli();

static char temp_LCD_DATA_PORT;
//сохраняет текущее значение порта данных индикатора
temp_LCD_DATA_PORT=LCD_DATA_PORT;

//производим очистку дисплея
WriteCode(DISPLEY_CLEAR);
//Задержка на 1500 mkS
delay_us(1500);

//устанавливаем начальный адрес записи
WriteCode(START_DDRAM_ADDR);
//Задержка на 40 mkS
delay_us(40);

static char i;

for(i=0;i<32;i++)
{
if(i==16)
{
WriteCode(START_DDRAM_ADDR + 0x40);
//Задержка на 40 mkS
delay_us(40);
};
WriteData(DispleyBuffer[i]);
//Задержка на 40 mkS
delay_us(40);

};

// Установка курсора в нулевую позицию
WriteCode(CURSOR_HOME);
//Задержка на 40 mkS
delay_us(40);

//возвращаем начальное значение порта данных индикатора
LCD_DATA_PORT=temp_LCD_DATA_PORT;

}

//процедура записи курсора в любую позицию
void WriteCursorAnyPosition(char Position)
{
wdr();
// запрет всех прерываний
cli();

static char temp_LCD_DATA_PORT;
//сохраняет текущее значение порта данных индикатора
temp_LCD_DATA_PORT=LCD_DATA_PORT;
if(Position<16)
{
WriteCode(0x80+Position);
}
else
{
WriteCode(0x80+0x30+Position);
};

//возвращаем начальное значение порта данных индикатора
LCD_DATA_PORT=temp_LCD_DATA_PORT;

}



//процедура очистки DispleyBuffer[]
void ClearDispleyBuffer(void)
{

wdr();
// запрет всех прерываний
cli();

static char i;
for(i=0;i<32;i++)
{
DispleyBuffer[i]=' ';
};

}

//процедура записи строковых констант в DispleyBuffer[]
void WriteStringToBuffer(char flash *StringAddress)
{

wdr();
// запрет всех прерываний
cli();

static char i;
static flash char *string;
string=StringAddress;
for(i=0;i<32;i++)
{
DispleyBuffer[i]=*string;
string++;
};

}
rezident
Freeze Anti, ИМХО у вас неправильная инициализация (не та последовательность и нет требуемых тайм-аутов). См. в приложении рекомендации по программированию. Несколько лет назад по ним писал управляющую программу для символьных LCD на базе HD44780 и её клонов. От себя могу добавить, что во время инициализации 4-х разрядной шины нужно 4 раза посылать код 0x30, причем как для 8-ми разрядной шины, т.е. только старший нибл. И между посылками выдерживать требуемые даташитом паузы.
Freeze Anti
Я уже видел эту статью... (Гугл никто не отменял smile.gif )

Сейчас вообще сделал точно так, как было написано... Вместо нужного текста - иероглифы (забыл сказать, что еще кроме иероглифов - полностью заполненные знакоместа, то есть черные квадратики...) и текст быстро ползет влево... Понятно, что я его инициализирую не так, но где? Неделю уже над этим вопросом бьюсь...

К сожалению, сейчас под рукой нет файла исправленного... завтра с утра напишу, что поменял...

P.S. человек, который этот же экранчик программировал на ассемблере говорит, что посылать вот эти вот 0х30 необязательно...
zltigo
Цитата(mempfis_ @ Dec 10 2007, 11:39) *
Может быть ....

Moderator:
Настоятельно не рекомедуется постить обширные неформатированные и нечитабельные исходники. Как минимум пользуйтесь соответствущими тэгами или лучше пользуйтесь приложениями.

Цитата
Есть LCD дисплей...

Дежурная многократно рассмотренная и избитая "тема" sad.gif - просьба не плодить бездумно!
Сергей Борщ
Цитата(Freeze Anti @ Dec 11 2007, 17:15) *
P.S. человек, который этот же экранчик программировал на ассемблере говорит, что посылать вот эти вот 0х30 необязательно...
Если человек использовал дисплей только в 8-битном режиме, на очень маленьком расстоянии от процессора и без помех вокруг - да, может не посылать, понадеявшись на то, что дисплей стартует в 8-битном режиме. В 4-битном же режиме или при наличии помех - посылка обязательна. Они занимают не так много памяти кода, а результат - дисплей инициализируется всегда, вне зависимости от состояния, в котором он находился перед инициализацией.
Freeze Anti
Мои изменения в программе таковы:

В функции WriteNibble я убрал задержку на 5 мс

Функцию InitLCD я переписал следующим образом
Код
//Функция инициализации ЖК-модуля
void InitLCD(void)
{
    RD_1; //предустановка управляющих сигналов
    E_0;
    RS_0;
    _delay_loop_2(50000); //Задержка на 50мс, чтобы ЖК-модуль
                           //имел достаточно времени на инициализацию
    WriteNibble(0x30);
    _delay_loop_2(4100);
    WritrNibble(0x30);
    _delay_loop_2(100);
    WriteNibble(0x30);
    WriteNibble(0x28); //Определение параметров развертки и ширины шины данных сейчас 0x38 если не получится - попробовать 0x28
    _delay_loop_2(5000);
    WriteByte(0x01);
    WriteByte(0x10); //Устанавливаем режим смещения курсора
    WriteByte(0x06); //вправо, без сдвига содержимого дисплея
    WriteByte(0x0C); //Включаем дисплей, прячем курсор
    for(char i = 0x40; i < 0x5F; i++) //Инициализируем память
                                         //знакогенератора
    {
        _delay_loop_2(10000);
        RS_0;
        WriteByte(i);
        _delay_loop_2(10000);
        ShowChar(0);
    }
    RS_1; //Переходим в режим передачи данных
    SetLCDPosition(1, 4); //Переходим в первую строку дисплея
}


Вот... и все равно не работает... симптомы все те же... я не понимаю, что я делаю не так...
Сергей Борщ
Цитата(Freeze Anti @ Dec 12 2007, 07:32) *
что я делаю не так...
1) PORTB = (data & 0xF); - тем самым вы не только выставляете данные, но и сбрасываете управляющие сигналы. Нужно бы PORTB = (data & 0x0F) | (PORTB & 0xF0);
2) аргументом _delay_loop2 является не время в микросекундах. Не зная частоты вашего кварца трудно сказать, какую реально задержку вы получаете. Используйте _delay_ms и _delay_mks.
3) Вы не используете чтение из дисплея. Поэтому выкиньте дерганье ногой RD.
4) Все команды, кроме очистки дисплея, исполняются за 40 мкс, очистка дисплея - за 1.6мс. Т.е. задержку в WriteByte можно уменьшить, а после посылки 0x01 в функциях InitLCD() и ClearLCD() добавить по дополнительной задержке 1.6мс.
5) Команда 0x01 в ClearLCD уже устанавливает курсор на начало, после этого не нужно GoToLine(1);
6) В порядке пожелания:
Код
#define E_1 PORTB |= (1 << 5) //PORTB = PORTB | 0b0100000 - E = 1
#define E_0 PORTB &= ~(1 << 5) //PORTB = PORTB & 0b11011111 - E = 0
Так вы не запутаетесь в битах.
7) В SetLCDPosition() вы первым делом ставите RS в 0. Зачем же в InitLCD вы перед вызовом SetLCDPosition() ставите его в 1? Ваша программа сильно упростится и станет более читабельной, если вы добавите функцию
Код
void WriteCommand(uint8_t byte)
{
     RS_0;   // command mode
     WriteByte(byte);
     RS_1;   // data mode
}
Т.е. дисплей после этой функции готов принимать команды и не нужно делать RS_1 перед каждым выводом информации.
8) Команды лучше задать в символьном виде, тоже повысит читаемость:
Код
#define   LCD_CLEAR    0x01
#define   LCD_SET_CURSOR   0x80
#define   LCD_SET_CG_PTR    0x40

9) Вы не используете знакогенератор, поэтому зачем его чистить? Когда будете использовать, грузить его можно как строку:
Код
//Функция вывода на дисплей строки из флеш
void ShowStr_P(prog_char *s)
{

    char C = pgm_read_byte(s++);
    while(C != 0)
    {
        ShowChar(C);
        C = pgm_read_byte(s++);
    }
}
prog_char const CharGen[][8] =
{
   0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01, 0x00, // symbol 0 image, 8 bytes per symbol
   0x10, 0x18, 0x1C, 0x17, 0x1C, 0x18, 0x10, 0x00, // symbol 1 image, 8 bytes per symbol
   .........
};

void InitLCD(void)
{
.............
    WriteCommand(LCD_SET_CG_PTR | 0);   // write to CG RAM, starting at position 0
    ShowStr_P(CharGen);
..............
    WriteCommand(LCD_SET_CURSOR | 0);  //Переходим в первую строку дисплея
}

10)Непонятен комментарий "//Определение параметров развертки и ширины шины данных сейчас 0x38 если не получится - попробовать 0x28" У вас дисплей, судя по коду, подключен в 4-битном режиме, вам обязательно надо посылать 0x28 после 0x30, 0x30, 0x30.
Freeze Anti
Огромное вам спасибо. Ваши советы очень помогли. Впредь буду гораздо внимательнее.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.