[Программирование микроконтроллеров] STM32 и LCD2004A без I2C интерфейса
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно начал изучать STM32 контроллеры и понадобилось взаимодействие с LCD дисплеем. Из дисплеев нашел у себя только 2004A, причем без I2C интерфейса. О нем и пойдет речь в этой статье.
Для начала необходимо подключить дисплей к контроллеру. Подключаем по схеме:
PB0 — PB7 — выводы контроллера.
Назначение выводов дисплея :
SPL
Номер вывода
Сигнал
Назначение сигнала
1
GND
Земля (общий провод)
2
VCC
Питание + 5 В
3
VEE
Управление контрастностью дисплея. Подключается средний вывод делителя напряжения. Обычно это подстроечный резистор 10-20 кОм, но я распаял на плате дисплея резисторы.
4
RS
Выбор регистра: 0 – регистр команд; 1 – регистр данных.
5
R/W
Направление передачи данных:
0 – запись;
1 – чтение.
Как правило чтение из дисплея не используется, поэтому сажаем вывод на землю.
6
EN
Строб операции шины. При спадающем фронте данные, находящиеся на шине данных «защелкиваются» в регистр.
7
DB0
Младшие биты восьми битного режима. При четырех битном интерфейсе не используются и обычно сажаются на землю.
8
DB1
9
DB2
10
DB3
11
DB4
Старшие биты восьми битного режима или биты данных четырех битного интерфейса.
12
DB5
13
DB6
14
DB7
15
A
Анод питания подсветки (+)
16
K
Катод питания подсветки (-). Ток должен быть ограничен.
Итак, дисплей подключили. Самое время научить микроконтроллер работать с ним. Я решил создать свою библиотеку для того, чтобы можно было ее использовать в разных проектах. Она состоит из двух файлов — lcd_20x4.h и lcd_20x4.c
Начнем с заголовочного файла.
#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_
#include "stm32f1xx.h"
#include "delay.h"
В начале подключаем файл библиотеки CMSIS stm32f1xx.h так как у меня камень STM32F103C8T6. Следующим включением подключаем файл delay.h — это моя библиотека для работы с задержками на основе системного таймера. Здесь ее описывать не буду, вот ее код:
Файл delay.h
SPL
#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_
#include "stm32f1xx.h"
#define F_CPU 72000000UL
#define US F_CPU/1000000
#define MS F_CPU/1000
#define SYSTICK_MAX_VALUE 16777215
#define US_MAX_VALUE SYSTICK_MAX_VALUE/(US)
#define MS_MAX_VALUE SYSTICK_MAX_VALUE/(MS)
void delay_us(uint32_t us); // до 233 мкс
void delay_ms(uint32_t ms); // до 233 мс
void delay_s(uint32_t s);
#endif /* DELAY_DELAY_H_ */
Файл delay.с
SPL
#include "delay.h"
/* Функции задержек на микросекунды и миллисекунды*/
void delay_us(uint32_t us){ // до 233 016 мкс
if (us > US_MAX_VALUE || us == 0)
return;
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // запретить прерывания по достижении 0
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; // ставим тактирование от процессора
SysTick->LOAD = (US * us-1); // устанавливаем в регистр число от которого считать
SysTick->VAL = 0; // обнуляем текущее значение регистра SYST_CVR
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // запускаем счетчик
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); // ждем установку флага COUNFLAG в регистре SYST_CSR
SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk; // скидываем бит COUNTFLAG
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // выключаем счетчик
}
void delay_ms(uint32_t ms){ // до 233 мс
if(ms > MS_MAX_VALUE || ms ==0)
return;
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
SysTick->LOAD = (MS * ms);
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void delay_s(uint32_t s){
for(int i=0; i<s*5;i++) delay_ms(200);
}
Дисплей 2004A основан на контроллере фирмы HITACHI HD44780. Поэтому заглянем в даташит на данный контроллер. В таблице 6 есть система команд, а так же тайминги выполнения этих команд.
Перепишем нужные команды в макроопределения в заголовочном файле:
// display commands
#define CLEAR_DISPLAY 0x1
#define RETURN_HOME 0x2
#define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift
#define DISPLAY_ON 0xC // non cursor
#define DISPLAY_OFF 0x8
#define CURSOR_SHIFT_LEFT 0x10
#define CURSOR_SHIFT_RIGHT 0x14
#define DISPLAY_SHIFT_LEFT 0x18
#define DISPLAY_SHIFT_RIGHT 0x1C
#define DATA_BUS_4BIT_PAGE0 0x28
#define DATA_BUS_4BIT_PAGE1 0x2A
#define DATA_BUS_8BIT_PAGE0 0x38
#define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS
#define SET_DDRAM_ADDRESS 0x80
Теперь необходимо настроить выводы контроллера для работы с дисплеем. Определяем положение битов в порте ODR контроллера. Следует обратить внимание на PIN_D4. У меня там прописан 10-й бит вместо 4. На моем контроллере не работает 4-й вывод. Не знаю с чем это связано, но в регистре ODR этот бит всегда единица, даже до начала инициализации тактирования контроллера. Не знаю с чем это связано, возможно камень не оригинальный.
// положение битов в порте ODR
#define PIN_RS 0x1
#define PIN_EN 0x2
#define PIN_D7 0x80
#define PIN_D6 0x40
#define PIN_D5 0x20
#define PIN_D4 0x400
Далее настраиваем управляющие регистры для выводов. Я решил это сделать в виде макросов препроцессора:
#define LCD_PORT GPIOB
#define LCD_ODR LCD_PORT->ODR
#define LCD_PIN_RS() LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \
LCD_PORT->CRL |= GPIO_CRL_MODE0; // PB0 выход тяни-толкай, частота 50 Мгц
#define LCD_PIN_EN() LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\
LCD_PORT->CRL |= GPIO_CRL_MODE1; // PB1
#define LCD_PIN_D7() LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\
LCD_PORT->CRL |= GPIO_CRL_MODE7; // PB7
#define LCD_PIN_D6() LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\
LCD_PORT->CRL |= GPIO_CRL_MODE6; // PB6
#define LCD_PIN_D5() LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\
LCD_PORT->CRL |= GPIO_CRL_MODE5; // PB5
#define LCD_PIN_D4() LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\
LCD_PORT->CRH |= GPIO_CRH_MODE10; // PB10
#define LCD_PIN_MASK (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011 маска пинов для экрана
В завершении заголовочного файла определяем функции работы с дисплеем:
void portInit(void); // инициализация ножек порта под экран
void sendByte(char byte, int isData);
void lcdInit(void); // инициализация дисплея
void sendStr(char *str, int row ); // вывод строки
#endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */
С заголовочным файлом закончили. Теперь напишем реализации функций в файле lcd_20x4.c
Первым делом нужно настроить выводы для работы с дисплеем. Это делает функция void portInit(void):
void portInit(void){
//----------------------включаем тактирование порта----------------------------------------------------
if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
else return;
//--------------------- инициализация пинов для LCD-----------------------------------------------------
LCD_PIN_RS();// макроопределения в заголовочном файле
LCD_PIN_EN();
LCD_PIN_D7();
LCD_PIN_D6();
LCD_PIN_D5();
LCD_PIN_D4();
lcdInit(); // функция инициализации дисплея
return ;
}
Что касается функции lcdInit() — это функция инициализации дисплея. Напишем и ее. Она основана на блок-схеме инициализации дисплея из даташита:
//--------------------- инициализация дисплея-----------------------------------------------------------
void lcdInit(void){
delay_ms(15); // ждем пока стабилизируется питание
sendByte(0x33, 0); // шлем в одном байте два 0011
delay_us(100);
sendByte(0x32, 0); // шлем в одном байте 00110010
delay_us(40);
sendByte(DATA_BUS_4BIT_PAGE0, 0); // включаем режим 4 бит
delay_us(40);
sendByte(DISPLAY_OFF, 0); // выключаем дисплей
delay_us(40);
sendByte(CLEAR_DISPLAY, 0); // очищаем дисплей
delay_ms(2);
sendByte(ENTRY_MODE_SET, 0); //ставим режим смещение курсора экран не смещается
delay_us(40);
sendByte(DISPLAY_ON, 0);// включаем дисплей и убираем курсор
delay_us(40);
return ;
}
Функция инициализации использует функцию void sendByte(char byte, int isData). Напишем ее реализацию. Она основана на временной диаграмме из даташита:
void sendByte(char byte, int isData){
//обнуляем все пины дисплея
LCD_ODR &= ~LCD_PIN_MASK;
if(isData == 1) LCD_ODR |= PIN_RS; // если данные поднимаем RS
else LCD_ODR &= ~(PIN_RS); // иначе скидываем RS
LCD_ODR |= PIN_EN; // поднимаем пин E
// ставим старшую тетраду на порт
if(byte & 0x80) LCD_ODR |= PIN_D7;
if(byte & 0x40) LCD_ODR |= PIN_D6;
if(byte & 0x20) LCD_ODR |= PIN_D5;
if(byte & 0x10) LCD_ODR |= PIN_D4;
LCD_ODR &= ~PIN_EN; // сбрасываем пин Е
LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);//обнуляем все пины дисплея кроме RS
LCD_ODR |= PIN_EN;// поднимаем пин E
// ставим младшую тетраду на порт
if(byte & 0x8) LCD_ODR |= PIN_D7;
if(byte & 0x4) LCD_ODR |= PIN_D6;
if(byte & 0x2) LCD_ODR |= PIN_D5;
if(byte & 0x1) LCD_ODR |= PIN_D4;
LCD_ODR &= ~(PIN_EN);// сбрасываем пин Е
delay_us(40);
return;
}
Теперь мы умеем отсылать байт на дисплей по 4-битной шине. Этим байтом может быть как команда так и символ. Определяется передачей в функцию переменной isData. Пришло время научиться передавать строки.
Дисплей 2004A состоит из 4 строк по 20 символов, что отражается в названии. Дабы не усложнять функцию я не буду реализовывать обрезку строк до 20 символов. В функцию будем отправлять строку символов и строку в которой ее вывести.
Для отображения символа на экране нужно записать его в память DDRAM. Адресация DDRAM соответствует таблице:
void sendStr(char *str, int row ){
char start_address;
switch (row) {
case 1:
start_address = 0x0; // 1 строка
break;
case 2:
start_address = 0x40; // 2 строка
break;
case 3:
start_address = 0x14; // 3 строка
break;
case 4:
start_address = 0x54; // 4 строка
break;
}
sendByte((start_address |= SET_DDRAM_ADDRESS), 0); // ставим курсор на начало нужной строки в DDRAM
delay_ms(4);
while(*str != '\0'){// пока не встретили конец строки
sendByte(*str, 1);
str++;
}// while
}
Вот и все, библиотека для дисплея готова. Теперь настало время ее использовать. В функции main() пишем:
portInit();// инициализация портов под дисплей
sendStr(" HELLO, HABR", 1);
sendStr(" powered by", 2);
sendStr(" STM32F103C8T6", 3);
sendStr("Nibiru", 4);
И получаем результат:
В заключение приведу полный листинг файлов:
lcd_20x4.h
SPL
#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_
#include "stm32f1xx.h"
#include "delay.h"
// display commands
#define CLEAR_DISPLAY 0x1
#define RETURN_HOME 0x2
#define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift
#define DISPLAY_ON 0xC // non cursor
#define DISPLAY_OFF 0x8
#define CURSOR_SHIFT_LEFT 0x10
#define CURSOR_SHIFT_RIGHT 0x14
#define DISPLAY_SHIFT_LEFT 0x18
#define DISPLAY_SHIFT_RIGHT 0x1C
#define DATA_BUS_4BIT_PAGE0 0x28
#define DATA_BUS_4BIT_PAGE1 0x2A
#define DATA_BUS_8BIT_PAGE0 0x38
#define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS
#define SET_DDRAM_ADDRESS 0x80
// положение битов в порте ODR
#define PIN_RS 0x1
#define PIN_EN 0x2
#define PIN_D7 0x80
#define PIN_D6 0x40
#define PIN_D5 0x20
#define PIN_D4 0x400
#define LCD_PORT GPIOB
#define LCD_ODR LCD_PORT->ODR
#define LCD_PIN_RS() LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \
LCD_PORT->CRL |= GPIO_CRL_MODE0; // PB0 выход тяни-толкай, частота 50 Мгц
#define LCD_PIN_EN() LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\
LCD_PORT->CRL |= GPIO_CRL_MODE1; // PB1
#define LCD_PIN_D7() LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\
LCD_PORT->CRL |= GPIO_CRL_MODE7; // PB7
#define LCD_PIN_D6() LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\
LCD_PORT->CRL |= GPIO_CRL_MODE6; // PB6
#define LCD_PIN_D5() LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\
LCD_PORT->CRL |= GPIO_CRL_MODE5; // PB5
#define LCD_PIN_D4() LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\
LCD_PORT->CRH |= GPIO_CRH_MODE10; // PB10
#define LCD_PIN_MASK (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011 маска пинов для экрана
void portInit(void); // инициализация ножек порта под экран
void sendByte(char byte, int isData);
void lcdInit(void); // инициализация дисплея
void sendStr(char *str, int row ); // вывод строки
#endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */
lcd_20x4.c
SPL
#include "lcd_20x4.h"
// посылка байта в порт LCD
void sendByte(char byte, int isData){
//обнуляем все пины дисплея
LCD_ODR &= ~LCD_PIN_MASK;
if(isData == 1) LCD_ODR |= PIN_RS; // если данные ставмим RS
else LCD_ODR &= ~(PIN_RS); // иначе скидываем RS
// ставим старшую тетраду на порт
if(byte & 0x80) LCD_ODR |= PIN_D7;
if(byte & 0x40) LCD_ODR |= PIN_D6;
if(byte & 0x20) LCD_ODR |= PIN_D5;
if(byte & 0x10) LCD_ODR |= PIN_D4;
// поднимаем пин E
LCD_ODR |= PIN_EN;
LCD_ODR &= ~PIN_EN; // сбрасываем пин Е
//обнуляем все пины дисплея кроме RS
LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);
// ставим младшую тетраду на порт
if(byte & 0x8) LCD_ODR |= PIN_D7;
if(byte & 0x4) LCD_ODR |= PIN_D6;
if(byte & 0x2) LCD_ODR |= PIN_D5;
if(byte & 0x1) LCD_ODR |= PIN_D4;
// поднимаем пин E
LCD_ODR |= PIN_EN;
//delay_us(10);
// сбрасываем пин Е
LCD_ODR &= ~(PIN_EN);
delay_us(40);
return;
}
// функция тактирует порт под дисплей и задает пины на выход тяни толкай и частоту 50 Мгц
void portInit(void){
//----------------------включаем тактирование порта----------------------------------------------------
if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
else return;
//--------------------- инициализация пинов для LCD-----------------------------------------------------
LCD_PIN_RS();
LCD_PIN_EN();
LCD_PIN_D7();
LCD_PIN_D6();
LCD_PIN_D5();
LCD_PIN_D4();
lcdInit();
return ;
}
//--------------------- инициализация дисплея-----------------------------------------------------------
void lcdInit(void){
delay_ms(15); // ждем пока стабилизируется питание
sendByte(0x33, 0); // шлем в одном байте два 0011
delay_us(100);
sendByte(0x32, 0); // шлем в одном байте 00110010
delay_us(40);
sendByte(DATA_BUS_4BIT_PAGE0, 0); // включаем режим 4 бит
delay_us(40);
sendByte(DISPLAY_OFF, 0); // выключаем дисплей
delay_us(40);
sendByte(CLEAR_DISPLAY, 0); // очищаем дисплей
delay_ms(2);
sendByte(ENTRY_MODE_SET, 0); //ставим режим смещение курсора экран не смещается
delay_us(40);
sendByte(DISPLAY_ON, 0);// включаем дисплей и убираем курсор
delay_us(40);
return ;
}
void sendStr(char *str, int row ){
char start_address;
switch (row) {
case 1:
start_address = 0x0; // 1 строка
break;
case 2:
start_address = 0x40; // 2 строка
break;
case 3:
start_address = 0x14; // 3 строка
break;
case 4:
start_address = 0x54; // 4 строка
break;
}
sendByte((start_address |= SET_DDRAM_ADDRESS), 0); // ставим курсор на начало нужной строки в DDRAM
delay_ms(4);
while(*str != '\0'){
sendByte(*str, 1);
str++;
//delay_ms(100);
}// while
}
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование микроконтроллеров, Производство и разработка электроники, DIY или Сделай сам, Звук] MIDI2USB – музыка нас связала
- [Программирование микроконтроллеров] Передача аналогового тв сигнала с помощью STM32
- [Программирование микроконтроллеров] Разработка измерительного прибора ИРИС
- [Беспроводные технологии, Программирование микроконтроллеров] Как подключить АЦП HX711 к NRF52832
- [C, JavaScript, Интернет вещей, Программирование микроконтроллеров, Разработка для интернета вещей] Термостат на ThingJS (beta)
- [Системное программирование, Программирование микроконтроллеров, Компьютерное железо] Моделирование прошивки в среде ModelSim с использованием моделей на языке SystemC
- [Гаджеты, Информационная безопасность, Программирование микроконтроллеров, Умный дом] Исследователь в рамках теста заразил умную кофеварку вымогателем и запустил на ней майнинг криптовалюты
- [3D-принтеры, DIY или Сделай сам, Программирование микроконтроллеров] Как управлять CNC-роутером, не привлекая внимания…
- [Интернет вещей, Программирование микроконтроллеров, Разработка для интернета вещей, Разработка систем связи] MQTTv5.0: Обзор новых функций. Часть 2
- [DIY или Сделай сам, Космонавтика, Научно-популярное, Программирование микроконтроллеров] Per aspera ad astra, или как я строил ракету. Часть 2. Собираем альтиметр на STM32 и BMP280
Теги для поиска: #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_stm32f103, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:51
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно начал изучать STM32 контроллеры и понадобилось взаимодействие с LCD дисплеем. Из дисплеев нашел у себя только 2004A, причем без I2C интерфейса. О нем и пойдет речь в этой статье. Для начала необходимо подключить дисплей к контроллеру. Подключаем по схеме: PB0 — PB7 — выводы контроллера. Назначение выводов дисплея :SPLНомер вывода
Сигнал Назначение сигнала 1 GND Земля (общий провод) 2 VCC Питание + 5 В 3 VEE Управление контрастностью дисплея. Подключается средний вывод делителя напряжения. Обычно это подстроечный резистор 10-20 кОм, но я распаял на плате дисплея резисторы. 4 RS Выбор регистра: 0 – регистр команд; 1 – регистр данных. 5 R/W Направление передачи данных: 0 – запись; 1 – чтение. Как правило чтение из дисплея не используется, поэтому сажаем вывод на землю. 6 EN Строб операции шины. При спадающем фронте данные, находящиеся на шине данных «защелкиваются» в регистр. 7 DB0 Младшие биты восьми битного режима. При четырех битном интерфейсе не используются и обычно сажаются на землю. 8 DB1 9 DB2 10 DB3 11 DB4 Старшие биты восьми битного режима или биты данных четырех битного интерфейса. 12 DB5 13 DB6 14 DB7 15 A Анод питания подсветки (+) 16 K Катод питания подсветки (-). Ток должен быть ограничен. Итак, дисплей подключили. Самое время научить микроконтроллер работать с ним. Я решил создать свою библиотеку для того, чтобы можно было ее использовать в разных проектах. Она состоит из двух файлов — lcd_20x4.h и lcd_20x4.c Начнем с заголовочного файла. #ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_ #include "stm32f1xx.h" #include "delay.h" В начале подключаем файл библиотеки CMSIS stm32f1xx.h так как у меня камень STM32F103C8T6. Следующим включением подключаем файл delay.h — это моя библиотека для работы с задержками на основе системного таймера. Здесь ее описывать не буду, вот ее код: Файл delay.hSPL#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_ #include "stm32f1xx.h" #define F_CPU 72000000UL #define US F_CPU/1000000 #define MS F_CPU/1000 #define SYSTICK_MAX_VALUE 16777215 #define US_MAX_VALUE SYSTICK_MAX_VALUE/(US) #define MS_MAX_VALUE SYSTICK_MAX_VALUE/(MS) void delay_us(uint32_t us); // до 233 мкс void delay_ms(uint32_t ms); // до 233 мс void delay_s(uint32_t s); #endif /* DELAY_DELAY_H_ */ Файл delay.сSPL#include "delay.h"
/* Функции задержек на микросекунды и миллисекунды*/ void delay_us(uint32_t us){ // до 233 016 мкс if (us > US_MAX_VALUE || us == 0) return; SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // запретить прерывания по достижении 0 SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; // ставим тактирование от процессора SysTick->LOAD = (US * us-1); // устанавливаем в регистр число от которого считать SysTick->VAL = 0; // обнуляем текущее значение регистра SYST_CVR SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // запускаем счетчик while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); // ждем установку флага COUNFLAG в регистре SYST_CSR SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk; // скидываем бит COUNTFLAG SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // выключаем счетчик } void delay_ms(uint32_t ms){ // до 233 мс if(ms > MS_MAX_VALUE || ms ==0) return; SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; SysTick->LOAD = (MS * ms); SysTick->VAL = 0; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk; SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; } void delay_s(uint32_t s){ for(int i=0; i<s*5;i++) delay_ms(200); } Дисплей 2004A основан на контроллере фирмы HITACHI HD44780. Поэтому заглянем в даташит на данный контроллер. В таблице 6 есть система команд, а так же тайминги выполнения этих команд. Перепишем нужные команды в макроопределения в заголовочном файле: // display commands
#define CLEAR_DISPLAY 0x1 #define RETURN_HOME 0x2 #define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift #define DISPLAY_ON 0xC // non cursor #define DISPLAY_OFF 0x8 #define CURSOR_SHIFT_LEFT 0x10 #define CURSOR_SHIFT_RIGHT 0x14 #define DISPLAY_SHIFT_LEFT 0x18 #define DISPLAY_SHIFT_RIGHT 0x1C #define DATA_BUS_4BIT_PAGE0 0x28 #define DATA_BUS_4BIT_PAGE1 0x2A #define DATA_BUS_8BIT_PAGE0 0x38 #define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS #define SET_DDRAM_ADDRESS 0x80 Теперь необходимо настроить выводы контроллера для работы с дисплеем. Определяем положение битов в порте ODR контроллера. Следует обратить внимание на PIN_D4. У меня там прописан 10-й бит вместо 4. На моем контроллере не работает 4-й вывод. Не знаю с чем это связано, но в регистре ODR этот бит всегда единица, даже до начала инициализации тактирования контроллера. Не знаю с чем это связано, возможно камень не оригинальный. // положение битов в порте ODR
#define PIN_RS 0x1 #define PIN_EN 0x2 #define PIN_D7 0x80 #define PIN_D6 0x40 #define PIN_D5 0x20 #define PIN_D4 0x400 Далее настраиваем управляющие регистры для выводов. Я решил это сделать в виде макросов препроцессора: #define LCD_PORT GPIOB
#define LCD_ODR LCD_PORT->ODR #define LCD_PIN_RS() LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \ LCD_PORT->CRL |= GPIO_CRL_MODE0; // PB0 выход тяни-толкай, частота 50 Мгц #define LCD_PIN_EN() LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\ LCD_PORT->CRL |= GPIO_CRL_MODE1; // PB1 #define LCD_PIN_D7() LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\ LCD_PORT->CRL |= GPIO_CRL_MODE7; // PB7 #define LCD_PIN_D6() LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\ LCD_PORT->CRL |= GPIO_CRL_MODE6; // PB6 #define LCD_PIN_D5() LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\ LCD_PORT->CRL |= GPIO_CRL_MODE5; // PB5 #define LCD_PIN_D4() LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\ LCD_PORT->CRH |= GPIO_CRH_MODE10; // PB10 #define LCD_PIN_MASK (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011 маска пинов для экрана В завершении заголовочного файла определяем функции работы с дисплеем: void portInit(void); // инициализация ножек порта под экран
void sendByte(char byte, int isData); void lcdInit(void); // инициализация дисплея void sendStr(char *str, int row ); // вывод строки #endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */ С заголовочным файлом закончили. Теперь напишем реализации функций в файле lcd_20x4.c Первым делом нужно настроить выводы для работы с дисплеем. Это делает функция void portInit(void): void portInit(void){
//----------------------включаем тактирование порта---------------------------------------------------- if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; else return; //--------------------- инициализация пинов для LCD----------------------------------------------------- LCD_PIN_RS();// макроопределения в заголовочном файле LCD_PIN_EN(); LCD_PIN_D7(); LCD_PIN_D6(); LCD_PIN_D5(); LCD_PIN_D4(); lcdInit(); // функция инициализации дисплея return ; } Что касается функции lcdInit() — это функция инициализации дисплея. Напишем и ее. Она основана на блок-схеме инициализации дисплея из даташита: //--------------------- инициализация дисплея-----------------------------------------------------------
void lcdInit(void){ delay_ms(15); // ждем пока стабилизируется питание sendByte(0x33, 0); // шлем в одном байте два 0011 delay_us(100); sendByte(0x32, 0); // шлем в одном байте 00110010 delay_us(40); sendByte(DATA_BUS_4BIT_PAGE0, 0); // включаем режим 4 бит delay_us(40); sendByte(DISPLAY_OFF, 0); // выключаем дисплей delay_us(40); sendByte(CLEAR_DISPLAY, 0); // очищаем дисплей delay_ms(2); sendByte(ENTRY_MODE_SET, 0); //ставим режим смещение курсора экран не смещается delay_us(40); sendByte(DISPLAY_ON, 0);// включаем дисплей и убираем курсор delay_us(40); return ; } Функция инициализации использует функцию void sendByte(char byte, int isData). Напишем ее реализацию. Она основана на временной диаграмме из даташита: void sendByte(char byte, int isData){
//обнуляем все пины дисплея LCD_ODR &= ~LCD_PIN_MASK; if(isData == 1) LCD_ODR |= PIN_RS; // если данные поднимаем RS else LCD_ODR &= ~(PIN_RS); // иначе скидываем RS LCD_ODR |= PIN_EN; // поднимаем пин E // ставим старшую тетраду на порт if(byte & 0x80) LCD_ODR |= PIN_D7; if(byte & 0x40) LCD_ODR |= PIN_D6; if(byte & 0x20) LCD_ODR |= PIN_D5; if(byte & 0x10) LCD_ODR |= PIN_D4; LCD_ODR &= ~PIN_EN; // сбрасываем пин Е LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);//обнуляем все пины дисплея кроме RS LCD_ODR |= PIN_EN;// поднимаем пин E // ставим младшую тетраду на порт if(byte & 0x8) LCD_ODR |= PIN_D7; if(byte & 0x4) LCD_ODR |= PIN_D6; if(byte & 0x2) LCD_ODR |= PIN_D5; if(byte & 0x1) LCD_ODR |= PIN_D4; LCD_ODR &= ~(PIN_EN);// сбрасываем пин Е delay_us(40); return; } Теперь мы умеем отсылать байт на дисплей по 4-битной шине. Этим байтом может быть как команда так и символ. Определяется передачей в функцию переменной isData. Пришло время научиться передавать строки. Дисплей 2004A состоит из 4 строк по 20 символов, что отражается в названии. Дабы не усложнять функцию я не буду реализовывать обрезку строк до 20 символов. В функцию будем отправлять строку символов и строку в которой ее вывести. Для отображения символа на экране нужно записать его в память DDRAM. Адресация DDRAM соответствует таблице: void sendStr(char *str, int row ){
char start_address; switch (row) { case 1: start_address = 0x0; // 1 строка break; case 2: start_address = 0x40; // 2 строка break; case 3: start_address = 0x14; // 3 строка break; case 4: start_address = 0x54; // 4 строка break; } sendByte((start_address |= SET_DDRAM_ADDRESS), 0); // ставим курсор на начало нужной строки в DDRAM delay_ms(4); while(*str != '\0'){// пока не встретили конец строки sendByte(*str, 1); str++; }// while } Вот и все, библиотека для дисплея готова. Теперь настало время ее использовать. В функции main() пишем: portInit();// инициализация портов под дисплей
sendStr(" HELLO, HABR", 1); sendStr(" powered by", 2); sendStr(" STM32F103C8T6", 3); sendStr("Nibiru", 4); И получаем результат: В заключение приведу полный листинг файлов: lcd_20x4.hSPL#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_ #include "stm32f1xx.h" #include "delay.h" // display commands #define CLEAR_DISPLAY 0x1 #define RETURN_HOME 0x2 #define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift #define DISPLAY_ON 0xC // non cursor #define DISPLAY_OFF 0x8 #define CURSOR_SHIFT_LEFT 0x10 #define CURSOR_SHIFT_RIGHT 0x14 #define DISPLAY_SHIFT_LEFT 0x18 #define DISPLAY_SHIFT_RIGHT 0x1C #define DATA_BUS_4BIT_PAGE0 0x28 #define DATA_BUS_4BIT_PAGE1 0x2A #define DATA_BUS_8BIT_PAGE0 0x38 #define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS #define SET_DDRAM_ADDRESS 0x80 // положение битов в порте ODR #define PIN_RS 0x1 #define PIN_EN 0x2 #define PIN_D7 0x80 #define PIN_D6 0x40 #define PIN_D5 0x20 #define PIN_D4 0x400 #define LCD_PORT GPIOB #define LCD_ODR LCD_PORT->ODR #define LCD_PIN_RS() LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \ LCD_PORT->CRL |= GPIO_CRL_MODE0; // PB0 выход тяни-толкай, частота 50 Мгц #define LCD_PIN_EN() LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\ LCD_PORT->CRL |= GPIO_CRL_MODE1; // PB1 #define LCD_PIN_D7() LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\ LCD_PORT->CRL |= GPIO_CRL_MODE7; // PB7 #define LCD_PIN_D6() LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\ LCD_PORT->CRL |= GPIO_CRL_MODE6; // PB6 #define LCD_PIN_D5() LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\ LCD_PORT->CRL |= GPIO_CRL_MODE5; // PB5 #define LCD_PIN_D4() LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\ LCD_PORT->CRH |= GPIO_CRH_MODE10; // PB10 #define LCD_PIN_MASK (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011 маска пинов для экрана void portInit(void); // инициализация ножек порта под экран void sendByte(char byte, int isData); void lcdInit(void); // инициализация дисплея void sendStr(char *str, int row ); // вывод строки #endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */ lcd_20x4.cSPL#include "lcd_20x4.h"
// посылка байта в порт LCD void sendByte(char byte, int isData){ //обнуляем все пины дисплея LCD_ODR &= ~LCD_PIN_MASK; if(isData == 1) LCD_ODR |= PIN_RS; // если данные ставмим RS else LCD_ODR &= ~(PIN_RS); // иначе скидываем RS // ставим старшую тетраду на порт if(byte & 0x80) LCD_ODR |= PIN_D7; if(byte & 0x40) LCD_ODR |= PIN_D6; if(byte & 0x20) LCD_ODR |= PIN_D5; if(byte & 0x10) LCD_ODR |= PIN_D4; // поднимаем пин E LCD_ODR |= PIN_EN; LCD_ODR &= ~PIN_EN; // сбрасываем пин Е //обнуляем все пины дисплея кроме RS LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS); // ставим младшую тетраду на порт if(byte & 0x8) LCD_ODR |= PIN_D7; if(byte & 0x4) LCD_ODR |= PIN_D6; if(byte & 0x2) LCD_ODR |= PIN_D5; if(byte & 0x1) LCD_ODR |= PIN_D4; // поднимаем пин E LCD_ODR |= PIN_EN; //delay_us(10); // сбрасываем пин Е LCD_ODR &= ~(PIN_EN); delay_us(40); return; } // функция тактирует порт под дисплей и задает пины на выход тяни толкай и частоту 50 Мгц void portInit(void){ //----------------------включаем тактирование порта---------------------------------------------------- if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; else return; //--------------------- инициализация пинов для LCD----------------------------------------------------- LCD_PIN_RS(); LCD_PIN_EN(); LCD_PIN_D7(); LCD_PIN_D6(); LCD_PIN_D5(); LCD_PIN_D4(); lcdInit(); return ; } //--------------------- инициализация дисплея----------------------------------------------------------- void lcdInit(void){ delay_ms(15); // ждем пока стабилизируется питание sendByte(0x33, 0); // шлем в одном байте два 0011 delay_us(100); sendByte(0x32, 0); // шлем в одном байте 00110010 delay_us(40); sendByte(DATA_BUS_4BIT_PAGE0, 0); // включаем режим 4 бит delay_us(40); sendByte(DISPLAY_OFF, 0); // выключаем дисплей delay_us(40); sendByte(CLEAR_DISPLAY, 0); // очищаем дисплей delay_ms(2); sendByte(ENTRY_MODE_SET, 0); //ставим режим смещение курсора экран не смещается delay_us(40); sendByte(DISPLAY_ON, 0);// включаем дисплей и убираем курсор delay_us(40); return ; } void sendStr(char *str, int row ){ char start_address; switch (row) { case 1: start_address = 0x0; // 1 строка break; case 2: start_address = 0x40; // 2 строка break; case 3: start_address = 0x14; // 3 строка break; case 4: start_address = 0x54; // 4 строка break; } sendByte((start_address |= SET_DDRAM_ADDRESS), 0); // ставим курсор на начало нужной строки в DDRAM delay_ms(4); while(*str != '\0'){ sendByte(*str, 1); str++; //delay_ms(100); }// while } =========== Источник: habr.com =========== Похожие новости:
Программирование микроконтроллеров ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:51
Часовой пояс: UTC + 5