Пользователь
0,0
рейтинг
16 декабря 2013 в 01:21

Администрирование → Создание собственных драйверов под Linux



Многие мои друзья и знакомые крутят пальцем у виска или задаются вопросом: не жмёт ли мне череп, когда узнают, что я пишу драйвера под Linux. Слово “драйвер” окутано каким-то почти мистическим смыслом, и постичь Дао его написания способны лишь избранные гуру.
К счастью это не так. Не знаю, как обстоят дела с написанием драйверов под другие операционные системы, в т.ч. и наиболее популярные, но под linux, вне зависимости от аппаратной архитектуры драйвера пишутся очень просто. Для написания драйвера необходимы базовые знания языка си, представление о работе ОС линукс (базовые), понимание того, что мы хотим получить, желание чтения документации и исходных кодов, ну и усидчивость. Всё.
Вы хотите посмотреть как написать драйвер для своего устройства? Тогда ныряйте под кат!

Лёгкость написания драйверов объясняется тем, что исходный код linux великолепно задокументирован и доступен в исходниках, в сети множество примеров и куча литературы. А для создания драйвера вам нужен только любимый дистрибутив и исходники ядра, ну и компилятор под нужную вам архитектуру. Свой первый, не учебный, драйвер я писал для процессора PowerPC 8360, он взаимодействовал с микросхемой ПЛИС и осуществлял сброс настроек по умолчанию. Как это ни странно звучит, я его сделал, немного переписав драйвер SPI для этой архитектуры. Я это говорю к тому, что единая стандартизация написания программ в ядре Linux позволяет проводить такие штуки.
Настольная книга разработчика драйверов под Linux — это “Linux Device Drivers”. Книжка является исчерпывающим руководством по разработке и если что-то не хватает в книге, то это точно можно найти самостоятельно в исходниках ядра. Разумеется, данный пост не претендует на то, чтобы заменить эту замечательную книгу. Более того, написан он по мотивам прочтения оных трудов, но всё же не является кратким пересказом данной книги. Автор этих строк ставит собой цель заинтересовать читателя простотой и изяществом ОС Linux, и понизить порог вхождения людей в разработку драйверов. Поверьте — это очень просто!

То, для чего будем писать драйвер



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

Мы будем писать драйвер для дисплея семейства HD44780 подключённого через LPT-порт к компьютеру. Знаю, что порт древний, а типу дисплеев лет не меньше, чем мне, но всё же — это красивый и простой пример написания драйверов под linux, не углубляясь в аппаратные подробности. И тем более, что переделать этот драйвер для этого экрана для других архитектур будет делом замены одной строчки!

Итак, нам понадобится LPT-порт; шнурок для старого принтера и опционально разъём CENTRONICS на 36 контактов, хотя можно просто распатронить кабель; разумеется экран семейства HD44780, самое лучшее — это минимум 4х40 символов, хотя подойдут даже самые маленькие; ну и переменный резистор на 10 кОм, для регулировки контраста. Поскольку LPT-порта у меня нет уже давным-давно, я прикупил первую попавшуюся плату на PCI (можно и PCI-E). Причём плата попалась на столько китайская, что я даже не смог найти никакой документации по напаянному чипу на ней. Ничего, будем ещё и реверсить её :)))).


Аппаратные исходники

Будем считать, что достать эти компоненты не составило труда, и ваши руки горят от нетерпения всё сделать.

Предупреждение!!!
Внимательно прочитать!





Хочу обозначить, что мы работаем в самых тяжёлых условиях, с железом, да ещё и будем работать на уровне ядра. Живём, так сказать, внутри пациента. Любая ошибка в программе и ядро падает. При чём оно может даже не успеть помахать вам kernel_panic-ом, просто внезапно всё перестаёт работать. Обратите внимание, что вы запустили драйвер, выгрузили его, и вдруг начались неявные глюки — всё, ваш путь — перезагрузка. Если система жива, то командой reboot. Если уже нет, то аппаратный reset. Хотя при тяжёлых случаях иногда система может сама себя перезагрузить.

Плюс, если вы не знаете что делаете (пишете в порт, обращаетесь к областям памяти и т.п.), то лучше этого не делать. Ибо всё это в лучшем случае может сбить настройки (записанное неверное значение в порт 70h раньше сбрасывало BIOS в х86) или даже попортить оборудование (например посадив ножку GPIO на землю). Поэтому чётко понимайте что вы делаете, как на аппаратном, так на программном уровне!

Приступаем



Для счастливых обладателей встроенного lpt-порта в своём компьютере скажу, что в вашем случае порт живёт по стандартному адресу 378h. Меня можно среди ночи разбудить и я скажу эту цифру, столько с ней связанно. Но ныне нам дали вражескую плату, и нам предстоит её победить!
Вставляем плату в слот PCI или PCI-E, загружаемся и смотрим что у нас происходит на шине PCI следующей командой:

lspci  -v
…
05:01.0 Serial controller: Device 4348:5053 (rev 10) (prog-if 02 [16550])
	Subsystem: Device 4348:5053
	Flags: medium devsel, IRQ 18
	I/O ports at c030 [size=8]
	I/O ports at c020 [size=8]
	Kernel driver in use: serial


Как видим линух нашёл что-то на шине PCI, и даже попытался дать ему какой-то левый, явно неправильный драйвер. Но нас более всего интересует адреса, на которых сидит данный порт. Это строки: I/O ports, а именно цифры c030h и c020h.
Нам теперь нужно найти какие же адреса за что отвечают. Для этого к порту вывода подключим светодиоды с резисторами примерно по такой схеме (резисторы примерно 310 Ом, или больше или меньше, не важно)


Схема проверки

Поскольку у меня уже есть заготовка светодиодов для теста такого рода, то я вставлял провода прямо в разъем CENTRONICS и у меня не влезло все 8 штук проводов, поэтому вставил только 6 (первые 4 и последние два).


Собранный тестер порта

И набросаем небольшую программку для проверки.

Для работы с портом я взял пример отсюда www.faqs.org/docs/Linux-mini/IO-Port-Programming.html#s9. Единственное, что надо исправить строку:

#include <asm/io.h>


на

#include <sys/io.h>


Делаем нормальные задержки в секунду (вместо usleep(100000);
, пишем sleep(1);) и делаем последовательно три вывода в порт:

outb(0, BASEPORT);
sleep(1);
outb(0xAA, BASEPORT);
sleep(1);
outb(0xFF, BASEPORT);
sleep(1);


Дефайн #define BASEPORT меняем на порт 0xc030. Компилируем, запускаем из под рута: светодиоды не горят. Меняем на другую цифру — 0xc020 — але оп:


Нуль


На выводах AAh


И, разумеется через секунду FFh

Результат налицо. Т.е. с нумерацией портов мы угадали, можно приступать к дровам. LPT-порт данной платы живёт по адресу. 0Xc020!

Хочу обратить внимание программистов-жестянщиков на магическое число 0xAA — это последовательность единиц и нулей. Вот так: 10101010b. Это очень удобно для всяких отладок. А вообще всякий кто работает с железом должен легко в уме переводить двоичное, десятичное, шестнадцатеричное туда-сюда-обратно.

Таки дисплей



Барабанная дробь, настало время собрать железку воедино! Для этого нам осталось всё спаять вместе. Всё собираем по следующей схеме.


Знаю, что схема не по ГОСТу, но это и не журнал «Радио»

Для питания экрана я распатронил провод USB, и сейчас у меня к экрану идут два провода. Есть хитрый вариант, просто до него никак не доходят руки — это пустить питание USB прямо в проводе LPT, отпаяв один из многочисленных земляных проводов. Как дойдут руки — обязательно сделаю. Допишу только эту статью :)))).
В результате, после подачи питания, мы должны получить что-то вроде этого. Подрегулируйте яркость резистором, чтобы появились две полоски. Они свидетельствуют нам о том, что дисплей не проинициализирован.


Девайс в сборе

Фух, с аппаратной частью мы закончили, пришла пора переходить к программе.

Приступаем злобным опытам с экраном в программе



В качестве готовой либы для работы с дисплеем использовал код от Arduino взятый отсюда. Я тоже не люблю эту платформу, но код годный для дисплеев, подключаемых по I2C. Если последнее слово вам ни о чём не говорит, не пугайтесь. Суть такова, что тот же самый дисплей подключён по двум проводам (как следует из названия шины), а далее там стоит регистр, который снова преобразует переданный байт в параллельную шину, прям как у нашего LPT-порта. В результате выкинув часть работы с I2C, получим годную либу.
Для чистоты переписал на сях, добавил парочку нужных функций, переименовал некоторые конфликтные, сделал ещё какие-то изменения (не помню, много воды утекло) и получил конфетку.
К слову сказать код использовался на STM32, так же с дисплеями подключающимися по I2C. А теперь мы его портируем уже на х86 да под linux. Вот она — великая сила переносимости кода! Потратив всего 10 минут, чтобы код адаптировать уже под новую платформу.
Работа с портом осуществляется в одной единственной функции — это непереносимый узел, и в зависимости от платформы способ вывода следует менять. Тут был вывод по I2C:

 void expanderWrite(unsigned char _data){                                        
	outb(((_data) | _backlightval), BASEPORT); //x86 instruction!!!!!
}


Обратите внимание на эту функцию. Заменив её, код можно запустить в AVR, STM32, MIPS и т. д.
Уже знакомый нам вывод в порт. Дефайн порта определяем в хедере lcd.h. Всё, теперь можно попробовать сделать вывод. Программа стала ещё проще и нагляднее.
Для вывода на экран используется функция-обёртка print_to_string

void print_to_string (unsigned char col, unsigned char row, unsigned char c[], unsigned char len);


Первый параметр номер столбца (от нуля до 19), второй номер строки (от нуля до трёх), третий — указатель на символьную строку и последний — длина строки. Строка не проверяется на окончание “\0”, длину надо контролировать самостоятельно! В результате попробуем дисплейчик:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>
#include "lcd.h"

#define LCD_STRINGS	4 //строк
#define LCD_COLUMNS 	20 //столбцов


int main()
{
	LCD_init(0, LCD_COLUMNS, LCD_STRINGS);//инициализация экрана
	print_to_string (0, 0, "XXXXXXXXXXXXXXXXXXXX"			, 20);
	print_to_string (0, 1, "YYYYYYYYYYYYYYYYYYYY"			, 20);
	print_to_string (0, 2, "ZZZZZZZZZZZZZZZZZZZZ"			, 20);
	print_to_string (0, 3, "MMMMMMMMMMMMMMMMMMMM"			, 20);
	exit(0);
}


В результате на экране должно появится что-то типа этого:


Наш первый дебют!

Не пугайтесь этой порнографии. Этот экран был списан за битые пиксели, точнее там просто погиб контроллер от статики (их там несколько). Для работы он не годится, а для всяких подобных опытов — самое оно. Такие хреновые экраны делают китайцы!

На этом можно было бы остановиться, т.к. с данным примером можно выводить любые символы, загружать шрифты и т.п. Но, не в качестве драйвера. Хотя можно переделать в демон или как-то ещё, насколько позволит фантазия.
Поэтому, для тех кому стало скучно читать дальше, полную версию этого примера выкладываю тут.
Наша глобальная цель — драйвер устройства, поэтому к нему и приступим.

Инструментарий для сборки драйвера



Чтобы приступить к написанию драйвера, надо поставить необходимый инструментарий. Как подготовить инструментарий для Ubuntu и Debian хорошо рассказывается вот в этой статье blog.markloiseau.com/2012/04/hello-world-loadable-kernel-module-tutorial
Лишь кратко скажу, что если вам не нужно собирать debian-пакеты, а вы пишете модуль только для себя, то достаточно будет установить необходимые компоненты следующей командой:

sudo apt-get install build-essential linux-headers-$(uname -r)


Всячески рекомендую так же скачать исходные тексты своего ядра. Есть два варианта это сделать. Первый, и очевидный, но возможно слегка геморройный — это узнать версию своего ядра командой:

uname - a


и скачать его с www.kernel.org. Я же лентяй лазать по ссылкам и искать версию ядра, доверю это дело автоматике.

#переходим в режим суперпользователя
sudo -s
cd /usr/src/
apt-get source linux-image-$(uname -r)
#после чего вы получите архив вашего ядра.
#в моём случае это был файл linux_3.8.0-9.18.tar.gz он же и распаковался в /usr/src/linux-3.8.0


Усё, запомните этот путь или добавьте ссылку на него, например /usr/src/linux, но не рекомендую это делать, т.к. ядра имеют тенденцию к обновлению. Тут вопрос спорный быть или не быть, решайте сами.
Теперь вы имеете у себя настоящее сокровище: кладезь актуальный документации и чудовищное количество примеров исходного кода и разумеется необходимый плацдарм для сборки модуля.
Настоятельнейше рекомендую лазать в папочку Documents, drivers и прочие, просматривать исходные тексты — это невероятно полезно!

Собстна драйвер



Если вы думаете, что мы сейчас просто поправим мейкфайл и получим готовый драйвер, то вы глубоко заблуждаетесь. Всё, теперь мы находимся в другом мире: не в пространстве пользователя, в пространстве ядра. И тут действуют совсем другие правила.
Чтобы осознать всю бедовость ситуации, то вы должны понимать, что ядро — это одна большая-пребольшая программа. А это означает, что глобальные переменные, функции и т.п. могут быть доступны в других местах ядра! Поэтому ВСЕ глобальные переменные (их следует избегать по максимуму) должны быть объявлены как static!
Мы не будем пересобирать ядро, встраивая в него драйвер, и каждый раз перезапускаться, пробуя его. Это конечно забавно, но очень долго. Вместо этого, мы сделаем модуль ядра, который мы будем загружать и выгружать прямо во время работы.
В нашем будущем модуле, по сравнении с программой для пространства пользователя, изменения будут не очень значительные. Во первых, перенесём все сишные файлы в один, чтобы не иметь проблем с компиляцией (ну или инклудим сишники в друг друга). Хедер оставляем, но чётко определяем все параметры функций (если никаких параметров, то надо прописать void). Вспоминаем, что мы в пространстве ядра, и все библиотеки у нас другие. Меняем все хидеры на ядерные:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>  /* everything... */

#include <asm/io.h>
#include <linux/unistd.h>
#include <linux/delay.h>	/* udelay */
#include <asm/uaccess.h>
#include <linux/miscdevice.h>


Обратите внимание, что даже unistd.h и io.h стали ядерными.

В качестве первичной основы драйвера возьмём модуль hello world. Поправим функцию init:

static int __init hello_init(void)
{
    	LCD_init(0, LCD_COLUMNS, LCD_STRINGS);
	print_to_string (0, 1, "Hello Habrahabr     "			, 20);
	printk(KERN_INFO "Lpt module init\n");
	return 0;    // Non-zero return means that the module couldn't be loaded.
}


Здесь всё практически без изменений. Меняется у нас функция работы с железом (самая платформозависимая), т.к. будет работать уже с ядерной функцией вывода в порт:

void expanderWrite(unsigned char _data){                                        
	//outb(((_data) | _backlightval), BASEPORT); //x86 instruction rootfs!!!!!
	outb_p(((_data) | _backlightval),BASEPORT); //x86 instruction kernel!!!!!
}


И, как это ни странно, функция задержек. Вообще задержки — отдельная песня, которой можно посвятить целую статью. У нас же должно быть всё быстро, и как можно меньше занимать ядро на всякую ерунду. Но всё же:

static inline int delayMicroseconds(int value)
{
	//usleep(value); //rootfs
	if (value > 1000)
		msleep(value/1000);
	udelay(value%1000);
	return 0; //kernel
}


Объяснение такой махинации простое: udelay внутри ядра не может принимать значения больше тысячи. Чтобы избежать казусов, необходима такая заглушка.
После этого собираем примерно таким мейкфайлом:

TARGET = lptlcd
obj-m	:= $(TARGET).o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)
CC = gcc
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions


И загружаем модуль в систему следующей командой:

sudo insmod lptlcd.ko


Если мы всё сделали правильно, то нам не будет выведено ошибок, а экран верно проинициализируется и dmesg покажет последней строкой сообщение вашего модуля:

[10036.950566] Lpt module init

Выгрузить модуль можно командой

sudo rmmod lptlcd


dmesg скажет:

[10077.176714] Cleaning up module

Вообще, чтобы не жать постоянно dmesg существует скрипт существует решение, которое мне подсказал камрад Jtu:

while true; do sudo dmesg -c; sleep 1; done


Или, если вы используете дистрибутив отличный от Ubuntu, то следует исполнять это под root, и убрать sudo.

Результат на экране:


Привет вам!

Если вы всё это увидели — поздравляю, это ваш первый рабочий бесполезный модуль ядра, который выводит надпись на экран! Это конечно занимательно, но надо таки превращать наши эксперименты в нормальный драйвер, с которым можно работать. А для этого всё же придётся рассказать о матчасти, хоть немного.

Пару слов о матчасти



Как ни крути, но без знания некой матчасти далеко не уйдёшь.Есть символьные, блочные и сетевые устройства. Также у каждого устройства есть старший и младший номер устройства. Лучше, чем в книге “The Linux Kernel Module Programming Guide” сказать нельзя, по этому приведу цитату из книги в спойлере:
Старший и младший номер устройства
Старший и младший номер устройства
Давайте взглянем на некоторые файлы устройств. Ниже перечислены те из них, которые представляют первые три раздела на первичном жестком диске:
# ls -l /dev/hda[1-3]
brw-rw----  1 root  disk  3, 1 Jul  5  2000 /dev/hda1
brw-rw----  1 root  disk  3, 2 Jul  5  2000 /dev/hda2
brw-rw----  1 root  disk  3, 3 Jul  5  2000 /dev/hda3



Обратили внимание на столбец с числами, разделенными запятой? Первое число называют «Старшим номером» устройства. Второе — «Младшим номером». Старший номер говорит о том, какой драйвер используется для обслуживания аппаратного обеспечения. Каждый драйвер имеет свой уникальный старший номер. Все файлы устройств с одинаковым старшим номером управляются одним и тем же драйвером. Все из выше перечисленных файлов устройств имеют старший номер, равный 3, потому что все они управляются одним и тем же драйвером.
Младший номер используется драйвером, для различения аппаратных средств, которыми он управляет. Возвращаясь к примеру выше, заметим, что хотя все три устройства обслуживаются одним и тем же драйвером, тем не менее каждое из них имеет уникальный младший номер, поэтому драйвер «видит» их как различные аппаратные устройства.
Устройства подразделяются на две большие группы — блочные и символьные. Основное различие блочных и символьных устройств состоит в том, что обмен данными с блочным устройством производится порциями байт — блоками. Они имеют внутренний буфер, благодаря чему повышается скорость обмена. В большинстве Unix-систем размер одного блока равен 1 килобайту или другому числу, являющемуся степенью числа 2. Символьные же устройства — это лишь каналы передачи информации, по которым данные следуют последовательно, байт за байтом. Большинство устройств относятся к классу символьных, поскольку они не ограничены размером блока и не нуждаются в буферизации. Если первый символ в списке, полученном командой ls-l /dev, 'b', тогда это блочное устройство, если 'c', тогда — символьное. Устройства, которые были приведены в примере выше — блочные. Ниже приводится список некоторых символьных устройств (последовательные порты):
crw-rw----  1 root  dial 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r-----  1 root  dial 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw----  1 root  dial 4, 66 Jul  5  2000 /dev/ttyS2
crw-rw----  1 root  dial 4, 67 Jul  5  2000 /dev/ttyS3



Если вам интересно узнать, как назначаются старшие номера устройств, загляните в файл /usr/src/linux/documentation/devices.txt.
Все файлы устройств создаются в процессе установки системы с помощью утилиты mknod. Чтобы создать новое устройство, например с именем «coffee», со старшим номером 12 и младшим номером 2, нужно выполнить команду mknod /dev/coffee c 12 2. Вас никто не обязывает размещать файлы устройств в каталоге /dev, тем не менее, делается это в соответствии с принятыми соглашениями. Однако, при разработке драйвера устройства, на период отладки, размещать файл устройства в своем домашнем каталоге — наверное не такая уж и плохая идея. Единственное — не забудьте исправить место для размещения файла устройства после того, как отладка будет закончена.
Еще несколько замечаний, которые явно не касаются обсуждаемой темы, но которые мне хотелось бы сделать. Когда происходит обращение к файлу устройства, ядро использует старший номер файла, для определения драйвера, который должен обработать это обращение. Это означает, что ядро в действительности не использует и даже ничего не знает о младшем номере. Единственный, кто обеспокоен этим — это сам драйвер. Он использует младший номер, чтобы отличить разные физические устройства.
Между прочим, когда я говорю «устройства», я подразумеваю нечто более абстрактное чем, скажем, PCI плата, которую вы можете подержать в руке. Взгляните на эти два файла устройств:
% ls -l /dev/fd0 /dev/fd0u1680
brwxrwxrwx   1 root  floppy   2,  0 Jul  5  2000 /dev/fd0
brw-rw----   1 root  floppy   2, 44 Jul  5  2000 /dev/fd0u1680



К настоящему моменту вы можете сказать об этих файлах устройств, что оба они — блочные устройства, что обслуживаются одним и тем же драйвером (старший номер 2). Вы можете даже заявить, что они оба представляют ваш дисковод для гибких дисков, несмотря на то, что у вас стоит только один дисковод. Но почему два файла? А дело вот в чем, один из них представляет дисковод для дискет, емкостью 1.44 Мб. Другой — тот же самый дисковод, но для дискет емкостью 1.68 Мб, и соответствует тому, что некоторые люди называют «суперотформатированным» диском («superformatted» disk). Такие дискеты могут хранить больший объем данных, чем стандартно-отформатированная дискета. Вот тот случай, когда два файла устройства, с различным младшими номерами, фактически представляют одно и то же физическое устройство. Так что, слово «устройство», в нашем обсуждении, может означать нечто более абстрактное.


Создаём файл устройства



Мы будем использовать файл-драйвер символьного устройства. Чтобы не заниматься пересказом книги Linux Device Driver, а так же упростить себе жизнь, то я взял готовый пример регистрации символьного драйвера из этой статьи. В данной статье не занимаются поиском свободного фиксированного минора, а используется динамический минор. Для нас — это самое то.

Итак, модуль теперь будет выглядеть следующем образом: работа с экраном, которую мы реализовали выше остаётся без изменений, а вот остальную часть переписываем с нуля.
Определяем структуру file_operations

static const struct file_operations lptlcd_fops = {
	.owner  = THIS_MODULE,
	.read   = dev_read,
	.write  = dev_write,
};


Где dev_read и dev_write — это указатели на функции (регистрация обратного вызова), которые будут обрабатывать соответственно чтение и запись из файла устройства. Здесь ещё можно, да наверное и нужно добавить указатели .open и .release для проверки однократного открытия и закрытия файла устройства, но мне пока лениво.

Для регистрации драйвера заводим вот такую структуру

static struct miscdevice lptlcd_dev = {
	MISC_DYNAMIC_MINOR,    
	"lptlcd",
	&lptlcd_fops
};


где MISC_DYNAMIC_MINOR — макрос для динамического минора, строка «lptlcd» — это название устройства, как оно будет выглядеть в папке /dev/ (в нашем случае будет /dev/lptlcd), &lptlcd_fops — указатель на структуру file_operations.

Код регистрации драйвера тоже весьма прост

static int __init dev_init( void ) {
	int ret;
//регистрируем файл устройство
	ret = misc_register( &lptlcd_dev );
//проблемы с регистрацией ругаемся
	if( ret ) printk( KERN_ERR "=== Unable to register misc device\n" );
//Инициализируем экран
	LCD_init(0, LCD_COLUMNS, LCD_STRINGS);
	print_to_string (0, 0, "lptlcd init     "			, 16);
	return ret;
}


Дерегистрация устройства проста:

static void __exit dev_exit( void ) {
	misc_deregister( &lptlcd_dev );
}


Ну и не забываем макросы установки колбеков, лицензии, автора и версии

module_init( dev_init );

module_exit( dev_exit );

MODULE_LICENSE("GPL");
MODULE_AUTHOR( "Dolin Sergey <dlinyj@gmail.com>" );
MODULE_VERSION( "0.1" );


Магия начинается в функции dev_read и dev_write. Начну с первой

static char *info_str = "lcdlpt device driver\nAuthor Dolin Sergey aka dlinyj dliny@gmail.com\n";         // buffer!

static ssize_t dev_read( struct file * file, char * buf,
						size_t count, loff_t *ppos ) {
	int len = strlen( info_str );
	if( count < len ) return -EINVAL;
	if( *ppos != 0 ) {
		return 0;
	}
	if( copy_to_user( buf, info_str, len ) ) return -EINVAL;
	*ppos = len;
	return len;
}



Делает она следующее: если мы произведём чтение файла устройства, например так:

cat /dev/lptlcd


То нам на экран будет выведена строка за указателем info_str.
Код настолько очевидный, что в комментариях, надеюсь, не нуждается. Единственное, что функция copy_to_user наравне с функцией copy_from_user используется копирования данных между адресными пространствами пользователя и ядра.

Другая функции dev_write, она-то и пихает данные в экран.

static int str_pos = 0; //номер строки
static int col_pos = 0; //номер столбца

static ssize_t dev_write( struct file *file, const char *buf, size_t count, loff_t *ppos ) {
	int i;
//Начинаем копировать байты.
	for (i=0; i<count;i++) { 
//переводим курсор в текущую позицию
		setCursor(col_pos, str_pos);
//если позиция у нас нулевая по обоим координатам, то очищаем экран 
		if ((col_pos==0) && (str_pos==0)) clear(); 
//если не перевод каретки, то выводим на экран
		if (buf[i] != '\n') { 
			write_l(buf[i]);
			col_pos++;
			}
//если перевод каретки, то делаем позицию курсора максимальной
		else {col_pos=LCD_COLUMNS;}
//при максимальной позиции курсора переходим на следующую строку
		if (col_pos == LCD_COLUMNS) {
			col_pos=0;
			str_pos++;
//если исчерпали лимит строк, то идём в нулевую строку
			if (str_pos == LCD_STRINGS)	{
				str_pos=0;
			}
		}
	}
	return count;
}


На мой взгляд, не добавить и не убрать. Надеюсь тут всё понятно.
Компилируем, добавляем модуль в ядро и смотрим, что у нас появился файл:

$ ls /dev/lptlcd
/dev/ttylptlcd


Пробуем прочитать из него и записать что-то:

$ cat /dev/lptlcd
lcdlpt device driver
Author Dolin Sergey aka dlinyj dliny@gmail.com
echo -ne "Trolo Pysh\nPysh" > /dev/lptlcd


И увидим вот это:


Я водитель НЛО

Всё, мы теперь имеем полностью рабочий драйвер вместе с отвечающим взаимностью устройством! Поздравляю, можно бежать в лабаз за шампанским, и потом в юзерспейсе под волшебные пузырьки писать программы, работающие с этим девайсом.
Но всё же меня лично гложут какие-то сомнения, вот что-то не так…

Хочется БОЛЬШЕГО!!!



Итак, сейчас мы написали вполне рабочий драйвер, который уже можно использовать в повседневной практике. Выводить на него любой текст, даже текст из файла, использовать в своих программах. Но есть ощущение незавершённости, несовершенства.
Если вы возьмёте и почитаете любую документацию на экран, например статью, которую написал DIHALT о инициализации дисплея для AVR, то будете удивлены богатством возможностей. Например:
1. Можно загрузить текст в память, а затем просто двигать видимую область, экономя такты на загрузку новых данных (как помним, у нас могучие задержки).
2. Можно выводить текст в произвольную позицию.
3. Можно очищать экран.
4. И конечно, можно загружать свои шрифты. Не стоит обольщаться по этому поводу, доступно всего 8 символов. Например, задача реализация русского меню с этим экраном без русского шрифта, с латинскими буквами и частично загруженными русскими (8 символов), превращается во вполне занимательный ребус для целого отдела (было, плавали). Так что это юзается для каких-то специфических символов.

Хороший пример применения возможности загрузки шрифтов я нашёл на кофейном автомате на работе, в котором как раз установлен аналогичный экран, только русифицированный.


Экран на кофейном автомате

Вот тут отлично видно использование символов:
— два символа на сахар, пустой кружок (не догадался снять) и полный;
— пять символов бегущей строки (в символе 5 столбцов).

Итого 2+5 — остаётся всего один запасной символ, который так же, вероятно, используется. Не очень-то разбежишься.

В общем фишек у дисплея полно, так много, что мне уже лениво читать в документации. Но, как мы видим, всего этого мы делать пока не можем. И в этот чудесный момент настало время достать мой рояль из кустов, который уже давным-давно там стоит.
Прежде, чем я приступил к реализации данной идеи (даже до того, как начал покупать детали), я погуглил, а делал ли кто-то подобное. И, о чудо(!) таковой драйвер уже существует. Его написал Michael McLellan и обитает драйвер тут.
Честно скажу, данный драйвер на меня произвёл смешанное впечатление. Скажем так, он стал ценным пособием, как НЕ надо писать драйвера — переписать его под другую схему включения будет нетривиальной задачей, поскольку идёт тупая запись байтов, а каких, куда и зачем — непонятно. Переносимость отсутствует как класс. А куча define-ов для разных ядер выносят моск. Плюс, изначально драйвер задумывался под полную схему включения (8 бит на порт данных, см. схему lcd-mod.sourceforge.net/wiring.php ), а я сразу был нацелен на 4-х битную шину. Она хоть и медленнее (примерно в два раза, т.к. байт посылается двумя посылками), но во-первых, будет меньше паять, а во-вторых — не потребуется отыскивать другие порты. Ну и в третьих, у меня уже был отличный и логичный код для 4-х битной шины. В результате я решил таки написать свой драйвер сам и с нуля, попутно рассказывая как это сделать.

Ладно, к чёрту лирику. В том драйвере есть бесценная штука — это обработка управляющих или ESC-последовательностей. Если потрудится и качнуть этот “рояль”, то можно там найти всякие ридми и хау-ту. Так же на офсайте есть фак. Чтобы вам не пришлось лазать по этим всем данным, я решил собрать всё в одну кучу и на русском языке. Во первых, пару слов об этих последовательностях, вы все уже с ними сталкивались, когда операторе printf добавляли перевод строки в виде ‘\n’ — это и есть простейшая ESC-последовательность, которая интерпретируется, как символ 0Ah в ASCII. Такая же петрушка лежит в управлении данным дисплеем.

Итак, управляющие последовательности, для нашего экрана:

  • \033 = Отправка ESC-последовательности, с которой начинаются команды
  • [A = Переместить курсор на одну строку вверх
  • [B = Переместить курсор на одну строку вниз
  • [C = Сдвинуть курсор на одну позицию вправо
  • [D = Сдвинуть курсор на одну позицию влево
  • [H = Переместить курсор в левый верхний угол — домой (позиция 0,0)
  • [J = Очистить всё, НЕ возвращает курсор домой!
  • [K = Стирает до конца строки, НЕ возвращает курсор домой!
  • [M = Новая карта символов (ДЗ — объяснить зачем!)
  • [Y = Позиция Y (см. FAQ ниже)
  • [X = Позиция X (см. FAQ ниже)
  • [R = CGRAM Выбор ячейки памяти
  • [V = Прокрутка включена
  • [W = Прокрутка выключена
  • [b = Подсветка включена-выключена (у нас работать не будет).


Другие полезные команды, работают без префикса \033!

  • \r = Возврат каретки (возвращают курсор в позицию 0 на текущей линии!)
  • \n = Новая линия
  • \t = Табуляция (по умолчанию 3 символа)


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

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

Кто ещё не понял, что это и зачем, то более подробно у него описано тут lcd-mod.sourceforge.net/faq.php. Чтобы не мучаться, я вольно перевёл его и заботливо положил в спойлер с картинками

FAQ
Q. Хорошо, я установил модуль, как теперь я могу его использовать?
A. Экран будет вести себя почти так же, как vt52-терминал ru.wikipedia.org/wiki/VT52, отличие состоит только в использовании пользовательских шрифтов. Если вы хотите просто вывести текст на экран, вам следует послать его в формате ASCII на устройство. Для начала новой строки, следует отправить символ подачи строки — ‘\r’ и символ перевода каретки ‘\n’ (см. википедию ). Например, дав команду:

echo -en "Line One\r\nLine Two" > /dev/lptlcd



Перевод каретки

на экран будет выведено “Line One” и “Line Two” на двух строках экрана. От себя замечу, что опции команды “echo” означают:
-n — не завершать строку символом перевода каретки ‘\n’
-e — включается поддержка интерпретации управляющих последовательностей

Q. Когда я вывожу новую строку на экран, курсор не перемещается в начало строки?
A. Вы должны так же послать на экран символ перевода на новую строку и перевод каретки. Например:
echo -en "line1\r\nline2" > /dev/lptlcd

(см. предыдущий пример)
Q. Почему, когда я пишу более, чем n линий на мой n-строчный эран, я вижу только n-1 линий, а последняя линия пустая?
A. Это потому, что вы используете команду “echo”. Данная комманда, без опций добавляет символ перевода каретки ‘\n’ в конце строки. Необходимо использовать аргумент -n, как я говорил выше.

Q. Как я могу передвигать курсор по кругу?
A. Вы можете установить курсор где хотите с помощью специальной управляющей последовательности: ESC-Y[Y-координата+037][X-координата+037]. Например:
echo -en "\033[Y\037\037Hello" > /dev/lptlcd



Hello в позиции 0:0

Курсор будет перемещён в нулевую строку, нулевой столбец и будет выведено на экран слово “Hello”;
echo -en "\033[Y\040\040Hello" > /dev/lptlcd 

будет выведено тоже самое, но в позицию 1, 1;


Hello в позиции 1:1 (без очистки экрана)

echo -en "\033[Y\041\041Hello" > /dev/lptlcd

аналогично, но напечатано будет в позицию 2, 2


Hello в позиции 2:2 (без очистки экрана, с предыдущими сообщениями)

Число “037” может немного запутать особенно, если вы не привыкли использовать восьмиричную арифметику, в будущих версиях я думаю о том, чтобы отбросить весь протокол vt52 и заменить его в более понятную реализацию (как автор жестоко ошибается :) )..

Q. Как я могу использовать мои собственные шрифты?
A. Контроллер экрана HD4480 поддерживат до восьми символов определённых пользователем и наш модуль ядра поддерживает это, но это может быть довольной сложной операцией. Я собираюсь написать красивую GUI делающее это для вас, когда я вернусь к ней (или когда вы сможете написать об этом мне). Готов поспорить, что это предложение прочтут сущие единицы. Трололо, проверка на внимательность.
Вы можете установить один символ, отправив в устройство следующую команду: Esc-R[позиция шрифта][8 байт, определяющих битовую маску символа]. Символ определяет только последние пять бит во всём байте, поскольку в символе используется всего 5 столбцов. Я уже говорил об этом выше. Например:
echo -en "\033[R1\037\037\037\037\037\037\037\037" > /dev/lptlcd 

Устанавливает символпо адресу 1, представляющую собой полностью закрашенный блок. Такой же, как я выше приводил у кофейного автомата в полосе готовности.


Символ полностью закрашенного блока

echo -en "\033[R2\037\000\037\000\037\000\037\000" > /dev/lptlcd

Устанавливает символ по адресу 0 в виде горизонтальных полос.


Символ горизонтальных полос

Чтобы вывести все «левые» символы, надо ввести следующую команду:
echo -en "\000\001\002\003\004\005\006\007" > /dev/lptlcd



Вся порнография, что у нас есть сейчас в памяти.


В общем, как видно, требуется переписать функцию dev_write. И она теперь стала выглядеть так:

static ssize_t dev_write( struct file *file, const char *buf, size_t count, loff_t *ppos ) {
	int i;
	for (i=0; i<count;i++) 
		handleInput(buf[i]);
	return count;
}


А всю грязную работу по разбору полётов на себя взяла функция handleInput. Код данной функции монструозен, поэтому я его приводить тут не буду. Оставлю вам на домашнее задание разобраться с ним и найти бекдор. Кто найдёт пасхальное яйцо в коде, отмечайтесь в комментах, тому конфетка.
Для затравки видос бекдора (он реализован в драйвере)


ASCII-ART Move! Запускается пасхалкой

Те, кто покажут мне аналогичный видос на своих экранах получат от меня подарок!

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

while true; do     echo -en "\033[H`date +\"%d.%m.%Y\"` \r\n`date +%r`" > /dev/lptlcd ;    sleep 1s; done



И они таки ходят!

Разумеется, написал — поделись с народом! Исходные коды доступны по ссылке (всё никак не соберусь юзать гитхаб для обмена кодом, позор).

Чего хотелось бы сделать



Просто так экранчик на столе мало понятно зачем нужен, это правда. Тем более в век планшетов и прочей техники. Когда всё можно перенаправить на ваш смартфон. Другое дело, что скоро грядёт Новый Год, и вспоминается один замечательный новогодний девайс — ёлочка на процессоре lpc2104


Embedded Artists Cristmas Tree

Не стоит гуглить этот девайс, все упоминания о нём убраны даже с официального сайта, а редкие упоминания о нём сложно найти даже на просторах интернета.
Я даже снял видео работы этой ёлочки


Как работает эмбеддеровская ёлочка

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

Теперь внимание: идея! Сейчас ничего не мешает заплатить на месяц 500 рублей (а если скинемся, сумма будет совсем мелкой), и арендовать сервак на виртуалке. Туда поставить программулину, которая будет рассылать сообщения. Либо jabber либо какие-то другие способы (да хоть nc и telnet всё сделать). А на компе поставить приёмник этих сообщений. И на Новый Год все мы будем иметь поздравления.
Кто готов помочь это реализовать, и кто будет делать себе такие экраны? Если нас наберётся хотя бы три человека, то будет весьма и весьма весело! Вливайтесь в тусовку разработчиков :)))). Выслушаю ЛЮБЫЕ идеи!

Итог



Буду краток. Данным постом я ставил цель не сделать пересказ книги Linux Device Drivers, а просто показать, что написание дров это тривиальная задача. Я умышленно ушёл от USB, т.к. там достаточно много теории, подводных камней и прочего геморроя. Но вы вполне можете переписать данный модуль для USB-LPT-шнурков, типа таких



Делается относительно просто: перехватывается протокол обмена с usb и реализуется в вашей программе. Поверьте — это просто. Будет интересно как — я вам расскажу!

А данный драйвер, поменяв всего ОДНУ(!!!) строку вывода в порт можно переписать для применения, например на Raspberry Pi на порт GPIO. Точно, что написать я вам не скажу, но всё решается чтением даташита на используемый проц и занимает всего несколько минут.

Благодарности:


1. Хочу высказать главную благодарность своему учителю Боронину Сергею Сергеевичу sboronin за обучению программированию под linux и прекрасному курсу разработки драйверов под linux. Благодаря его курсам я весьма успешно работаю разработчиком.
2. Камраду Ariman с его замечательной серией статей про разработку дисплея для роутера (первая, вторая, третья часть (а ведь обещалась четвёртая...)). Эта серия, хоть и весьма специфическая, прекрасно иллюстрирует создание устройства и написание для него соответствующих драйверов. И я неоднократно обращался к ней, как к источнику бесценной информации, как в разработке драйверов, так и в разработке модулей для OpenWRT.
3. Разумеется автору книги “Linux Device Driver” Greg Kroah-Hartman. Эта книга просто библия, и в ядре часто можно встретить драйвера, которые базируются на его примерах. Например, в драйверах USB встретил такой:

/drivers/usb/misc/idmouse.c

Derived from the USB Skeleton driver 1.1,
Copyright © 2003 Greg Kroah-Hartman (greg@kroah.com)


И так во многих драйверах. Так, что пользуйтесь книгой — это библия.
4. Michael McLellan автору аналогичного драйвера. Как он говорит в своём драйвере:

* LCD driver for HD44780 compatible displays connected to the parallel port,
* because real men use device files.


К сожалению у меня нет его координат, кроме ссылки на linkedin. Почта из его программ уже не работает. Так, что как ему лично сказать спасибо, я не знаю… Если кто ему напишет, я буду очень признателен.

Ссылки

1. Исходники моей программы для rootfs работающая с дисплеем качнуть
2. Исходники моего модуля ядра, который мы разбирали в этом посте качнуть
3. «Рояль в кустах»
4. dmilvdv.narod.ru/translate.html Переводы статей. Настольная книга — Linux Device Drivers, Third Edition".
5. Отличная библиотека статей по программированию на русском от IBM обитает тут. Конкретно по разработке модулей ядра
6. Описание дисплея HD44780 (для AVR, но в целом оно универсально) easyelectronics.ru/avr-uchebnyj-kurs-podklyuchenie-k-avr-lcd-displeya-hd44780.html
7. Программы для дисплея (модуль ядра и спектральный анализатор для этого модуля) от Michael McLellan linux.downloadatoz.com/developer-michael-mclellan.html

P.S. Не стреляйте в пианиста, он играет как умеет. Такую громадную статью при моей врождённой неграмотности написать нормально нельзя. Обязательно присылайте мне замечания, правки, и дополнения, буду очень признателен!!!
P.P.S. Ничто не греет душу так, как оставленный комментарий.
P.P.P.S. В тексте тоже есть пасхалка, кто найдёт — тому конфетка ;)))

UPD Вот я создал группу, для желающих участвовать меседжере :)
Для участников, желающих, прошу присоединяться :)))
Сергей @dlinyj
карма
453,5
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Администрирование

Комментарии (104)

  • +43
    Хабр — торт :)
  • +8
    Хабр — торт.
    Кто там минусовал за Длинного за картинку с Боромиром и «нельзя просто так написать статью за 5 часов»? Стыдитесь.
    Не соглашусь только, что I2C назван так за использование двух проводников. Двойка там — это на самом деле не «два», а «квадрат», а само I2C — это Inter-Integrated Circuit, но это несущественная мелочь.
    В остальном — преклоняюсь. Сам разбил бы статью на десяток коротких заметок длиной в полгода, и еще не факт, что удалось бы закончить.
    • +17
      Кто там минусовал за Длинного за картинку с Боромиром и «нельзя просто так написать статью за 5 часов»? Стыдитесь.

      Спасибо. Эту статью, если учитывать создание девайса и написание программы, я писал 3 недели. И всё делалось только ради поста. Так что, за 5 часов такие статьи не пишутся.

      Про i2c, для понимания сути работы — не имеет значения. Править текст не буду, а те кому нужно прочтут твой комментарий. Спасибо.
      • +1
        А у меня тут два вопроса
        Все же с чего лучше начать i2c или spi?

        И второй — Кто то игрался с e-ink? Зашел к китайцам, там их вагон и тележка, что выбрать непонятно.
        P.S. Хотя думаю самое дешевое на поигратся.
        • +6
          spi много проще чем iic
          • +1
            А отсюда можно понять, насколько SPI простой сам по себе.
          • +1
            Я бы i2c советовал бы…
        • +3
          I2C — шинная архитектура с адресацией, имеет смысл для низких скоростей когда на одной шине больше одного устройства, да даже два — сомнительное преимущество применения I2C.

          SPI — считай синхронный UART с выведенными наружу выводами регистра. Да собственно, отличия от UART тоже есть и довольно существенные.
          Очень подходит для высоких скоростей, организация шины возможна только адресация ложится на плечи ВАШЕГО протокола передачи, стандарта на который попросту не существует. Ну и организация такой шины — циклический регистр сдвига который нужно прокачать ВЕСЬ чтобы услышать ответ первого устройства, либо иметь N отдельных входов для N устройств на шине.
          • +1
            Поиграться i2c — очень даже. Есть даже приложения в линухе, которыми можно обмениватся байтиками по этой шине. Мне она нравится больше из-за простоты. SPI — слабо стандартизован, но весьма топорен и надёжен. В общем, изучать лучше сразу оба :)))
            • +2
              Просто думаю на i2c тут датчики разные вешать, думаю сразу можно и дисплей.
              Сейчас вот заинтересовался e ink.
        • 0
          А что там у китайцев с елинком? Что-то ничего найти не могу, можно ссылку?
          • 0
            Ну я сам хотел бы ссылки :) Хочется экранчик небольшой. Но в основном продают запчасти для ebook.
            • 0
              Ааа, ну дык, всё импатентами закрыто. Низзя, а если можно, то стоит просто конских денег…
              • 0
                ну есть типа для арду
                www.seeedstudio.com/depot/204-eink-display-shield-p-1374.html

                но цена в 50$ как то совсем не алё!

                Хотя опять же, я сильно тему не изучал.
                • 0
                  Ну, это ещё гуманно. У китайцев можно найти от 30 долларов.

                  Как-то смотрел девкит от eink… Вот там ценник был абсолютно не любительский, около 3 тысяч евро.
                  • 0
                    А где и как искать?

                    На ебееях, dx-х и прочих неприличных словах что-то как-то ничего не гуглица…
                    • 0
                      Например, сходу нашел на www.digikey.com. По китайцам надо искать, но у кого-то, вроде, видел.
  • +14
    > while true; do sudo dmesg -c; sleep 1; done

    tail -f /var/log/kern.log
    • +2
      Плюсую, люто и бешено — ваш способ более расово верный!
      • +8
        dmesg -w
        • +1
          dlinyj@dlinyj ~ $ dmesg -w
          dmesg: invalid option — 'w'
          • +3
            У вас слишком древнее ядро просто.

            man dmesg
                   -w, --follow
                          Wait for new messages. This feature is supported on systems with
                          readable /dev/kmsg only (since kernel 3.5.0).
            
            • +1
              Ну-ну… Неделю назад систему поставил.
              uname -a
              Linux dlinyj 3.11.0-12-generic #19-Ubuntu SMP Wed Oct 9 16:12:00 UTC 2013 i686 i686 i686 GNU/Linux
              • +3
                Значит у вас слишком древний util-linux в системе.

                ftp.kernel.org/pub/linux/utils/util-linux/v2.22/v2.22-ReleaseNotes
                dmesg(1):
                  - reads kernel messages from /dev/kmsg on kernel 3.5
                  - supports new option --follow to wait for new messages  (kernel 3.5 required)
                  - supports new option --reltime to print human readable deltas
                
                • +1
                  Эт да…

                  dlinyj@dlinyj ~ $ dmesg -V
                  dmesg from util-linux 2.20.1
                  


                  Но всё это говорит лишь о том, что мой вариант самый универсальный. Ибо часто приходится иметь дело со старыми ядрами, и древними утилитами.
    • –1
      man tailf
    • 0
      watch dmesg -c // запускать dmesg -c каждые 2 секунды
      • 0
        > dmesg -c

        Для него рут нужен:

        :~$ dmesg -c
        dmesg: klogctl failed: Operation not permitted

        Да и не особо мне нравится идея буфер очищать.
  • +3
    Надо будет проделать с подобным LCD все то же самое, только без собственных драйверов и через USB в рамках статей про FT232H.
    Получится в разы проще, но и в разы менее хардкорно.
    • +2
      Я тоже об этом думал, но я б написал драйвер дисплея для FT232H. Тем более, что на сайте ftdi лежат исходники драйверов. Их остаётся только немного поправить под свою задачу.

      Не, если хотите работать с дисплеем только со своей программой, то да. Так-то получается универсальная консоль.
      • +2
        Тоже вариант, только лучше тогда сразу на основе libftdi, чтобы с лицензиями проблем не было.
    • +2
      без драйвера всеравно не выйдет, только это будет драйвер другого уровня — который будет использовать готовый драйвер от микросхемы для реализации функции вывода в порт, остальное останется неизменным — так или иначе работу с индикатором надо будет реализовывать.
  • +3
    Спасибо вам огромное. Я то думал что написать драйвер под линукс нериально сложно и простой смертный на такое не способен. Однако хотел посмотреть как это делается, а все руки не доходили, а тут такой пост просто чудо.
    • +8
      Старался именно для таких как вы :)
  • +5
    Как человек, драйвера не пищуший, но иногда читающий, хочу сказать, что самые большие драконы скрываются в высокопроизводительных подсистемах — блочные устройства, сеть и графика. Остальному чуть легче жить.
  • +5
    Два символа: [b
    И 2 совета:
    1. Поставь в свою IDE плагин для проверки орфографии в комментариях и строках
    2. Не оставляй комментариев на русском, пожалей наших англоязычных товарищей

    • +1
      С меня конфетка ;) Куда высылать?
      А слабо продемонстрировать работу? Даю подсказку, можно завести в консоли, заменив print_to_string на printf :)

      1. Надо почитать как для geany это сделать.
      2. Учту. Я думаю заморочится и перевести статью на английский.
      • +3
        Вышли тому, кто продемонстрирует на LCD, а то в консоли — это не спортивно.
        Мой LCD сейчас на работе, а те, что доступны в «удаленной лаборатории» моего ПТУ, подключены к Stellaris Launchpad и Infineon XE167FM EasyKit, запуск Linux на которых — еще больший хардкор, а через мой драйвер дисплея это писать еще менее спортивно, чем в консоль.
  • +4
    Идея с сервером классная, если что, я с вами :)
    p.s.
    Похоже, что этого парня ждут приключения:
    • +3
      Забыл про пасхалку в тексте:
      Заголовок
      Трололо, проверка на внимательность.
      • 0
        Куда высылать конфетку?
    • +1
      О, круто, круто. Я уж думал никто не соберётся…
  • +2
    Красивая статья, спасибо.
    Пожалуйста, добавьте название структуры file_operations в определение её объекта:
    static const struct lptlcd_fops = { .owner = THIS_MODULE, .read = dev_read, .write = dev_write, };
    • +2
      Ох, выпало. Спасибо. На будущее — присылайте такое личным сообщением. Я сейчас поправлю, а ваш комментарий станет не актуальным.
  • +1
    Спасибо) Вспомнил, как писал для этого контроллера мини-библиотеку (на C под 8051) и потом портировал её на AVR для домашнего проектика.

    Кстати, встречал дисплеи с русскими буквами (естественно, только теми, которые нельзя заменить соответствующими латинскими). Естественно, необходимо писать тривиальный табличный маппер.

    Стоит упомянуть, что дисплею на этом контроллере необходима относительная хитрая инициализация. Правда, в разных источниках описывается чуточку различная процедура:
    один вариант
    Несколько слов о процессе инициализации ЖКИ-модуля. Производитель контроллера рекомендует выполнять следующую последовательность действий для инициализации. Выдержать паузу не менее 15 мс между установлением рабочего напряжения питания (> 4,5 В) и выполнением каких-либо операций с контроллером. Первой операцией выполнить команду, выбирающую разрядность шины (это должна быть команда $30 независимо от того, какой разрядности интерфейс вы собираетесь использовать в дальнейшем), причем перед выполнением этой операции не проверять значение флага BF. Далее опять выдержать паузу не менее 4,1 мс и повторить команду выбора разрядности шины, причем перед подачей команды вновь не производить проверку флага BF. Следующим шагом необходимо вновь выдержать паузу, на этот раз 100 мкс, и в третий раз повторить команду установления разрядности шины, вновь без проверки BF. Эти три операции являются инициализирующими и призваны вывести контроллер в исходный режим работы (то есть перевести в режим работы с 8-ми разрядной шиной) из любого состояния. Следом за ними нормальным порядком (без выдерживания пауз, но с проверкой флага BF) выполняется инициализация режимов работы с выдачей инициализирующей последовательности, аналогичной указанной в таблице 7 (содержащей в том числе команду выбора необходимой разрядности шины).
    Отсюда: www.gaw.ru/html.cgi/txt/lcd/chips/hd44780/start.htm

    Ещё вариант: joshuagalloway.com/lcd.html
    • +1
      Кстати, встречал дисплеи с русскими буквами (естественно, только теми, которые нельзя заменить соответствующими латинскими). Естественно, необходимо писать тривиальный табличный маппер.


      Ну вообще, в статье есть пример экрана с русскими буквами — это дисплей кофейного автомата. А мапер и в этом экране используется.
      А на счёт пауз и прочего, всё реализовано в либе, я даже об этом как-то и не думал.
      • +1
        Про кофеавтоматы вспоминается Lavazza BLUE Zenith, которые стояли на работе.

        У них глючило всё: монетоприемник (не признавал часть монет и банкнот с первого раза), диспенсеры (выдывал по 2-3 ложечки, но скоро заканчивались; выдавал второй стакан в заполненный первый), управляющий софт глючил (иногда наливал американо при любом выборе, повисал).
  • +4
    Там играет звук, но сейчас я динамик отпаял, потому что он меня достал
    • +3
      Так и есть :))))
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Хорошо, но это сокрее исключение, ну и нет её в наличии наверняка. На офсайте ничего нет точно.
  • +1
    Черт, аж руки зачесались :) Спасибо за отличную статью, несмотря на некоторые грамматические и орфографические недочеты, она великолепна!
    • +1
      Спасибо! А недочёты можете прислать личкой, с удовольствием поправлю.
  • +3
    У дисплея не контроллер погиб, а резинки поотклеивались. Скорей всего влага попадала. Разбери его, протри спиртом контакты, собери и будет как новенький.
    • +3
      У дисплея не контроллер погиб, а резинки поотклеивались.


      Я тоже так думал, но артефакты «играют» когда пальцем бляху сзади щупаешь. Это раз, а во вторых погиб он после вооооот такой искры статики с пальца, я это видел. Статика резику не отклеивает.
  • +3
    Просто какое-то колдунство!
  • +3
    на Assembly этим летом было
    www.youtube.com/watch?feature=player_embedded&v=nxFad7Rxw7Q
    • +1
      Как круто, спасибо за ссылку! Тут какой-то быстрый экран, у меня даже на видео видно как всё тормозит. Это максимальная скорость.

      Перезагрузка шрифтов на лету, великолепно!
      • +2
        Ещё вот первое, из 2003, LCD megademo.
        www.youtube.com/watch?v=4wjj0Xcu2F8
      • +1
        Обычный, просто сделано не на делаях, а на ожидании busy флага. Он реально очень быстр так то. И может цепляться как память практически.
        • +1
          Да аппаратно там понятно как можно сделать, проблема в том, что сама ЖК матрица лагает. Погляди как у меня долго перерисовывается каждый символ на видео. Скорость максимальная, дальше просто фарш…
    • +2
      Там ещё с экраном было забавное в категории Wild, демо на информаторе хельсинского метро. Экран потом около главного входа почетно стоял.
      www.youtube.com/watch?v=XCO1I3zSwps
  • +3
    «while true; do sudo dmesg -c; sleep 1; done»
    тогда уж watch -n 1 dmesg -c
    ещё и подсвечивать изменения умеет
    • +1
      попробуйте, у меня просто всё стирает.
    • +2
      тогда dmesg без -с нужен
  • +1
    Объяснение такой махинации простое: udelay внутри ядра не может принимать значения больше тысячи

    Используйте же тогда mdelay(), конечно если важны точные значения интервалов времени. Ограниченный диапазон значений udelay в том числе из-за особенностей реализации задержек, где не используется вся разрядная сетка для счетчика. Точно деталей не знаю.
  • +1
    А можно на него вывести tty? Чтобы пользоваться, как внешним текстовым экраном только для консоли. Такое вообще бывает в Linux?
    • 0
      По идее он таковым и является. Но слишком мало символов для этого.
  • 0
    О как! Только ночью сегодня подумал, что мне охренеть как нужны USB-часы показывающие системное время ОС, а тут такая хорошая поделка.
    • 0
      Давайте в нашу тусовку :))))
      • +1
        Я вот тут подумал, может быть все же можно будет раскрыть вопрос про usb?
        Я малость долек от этой темы (нет с com и lpt я справлюсь) но вот usb для меня темный лес.
        • 0
          В смысле раскрыть вопрос?
          • 0
            >Я умышленно ушёл от USB, т.к. там достаточно много теории, подводных камней и прочего геморроя. Но вы вполне можете переписать данный модуль для USB-LPT-шнурков, типа таких

            ^_^
            • 0
              Ну когда-нить я напишу про дрова для USB… А пока можно выкатить статью, как сделать это на AVR…
      • 0
        Вообще, я хотел на 7-сегментных индикаторах сделать, и LPT порт у меня есть родной, на материнке. Так что это вроде как даже проще будет.
  • +1
    А я забавы ради изучал программирование под AVR, и подцепил к attiny2313 экранчик, реализовал переселку текста на МК по uart, а МК уже передает на экран, так что я тоже с компа могу на экранчик выводить новогоднее поздравление, вы только скажите как его забрать из интернета!
    • 0
      Можно, кстати без проблем перенести этот код в AVR. Просто при получении байта по UART, содержимое регистра UDR передавать в функцию handleInput (как раз в сути своей будет функция dev_write). Переписать задержки и сделать вывод ваш порт в функции:

      void expanderWrite(unsigned char _data){                                        
          //outb(((_data) | _backlightval), BASEPORT); //x86 instruction rootfs!!!!!
          //outb_p(((_data) | _backlightval),BASEPORT); //x86 instruction kernel!!!!!
          //вот здесь надо делать вывод в ваш порт
      }
      
      

      И сделать функцию задержек:

      int delayMicroseconds(int value)
      {
        /usleep(value); 
         return 0; 
      }
      


      В gcc по моему есть delay. И далее так же можно тупо выводить данные перенаправляя вывод в СОМ-порт и будут рабоать ESC-последовательности.
    • 0
      А для вступления в тусовку, с сообщениями — надо вступить в группу :) см. ссылку внизу поста.
  • +1
    Серега, молодец! Большой поклон за годный материал.
    • 0
      А помнишь мы не так давно обсуждали как писать дрова, и ты мне говорил, мол всё просто, а я ныл, что это гемморой :)))))
      • +1
        Ну да, и мясо ели с пивом вприкуску :) Я вроде тогда рассказывал как раз как я катал драйвер вот для такого же дисплея + подвязанной к нему через диоды матричной клавы. И для 1-вайр. И все это было уже > 10 лет назад, свистело и пердело на старенькой 486-ой матери, и стояла там еще неведомой редкости SSD-шка (как их щяс модно называть) DiskOnChip IDE на аж целых 16 мегабайт на которой гнездился линукс фром скретч :) А девайс так у меня и не удался тогда и как проект загнулся, т.к. я не смог побороть «магические» перезагрузки, которые походу возникали от хренового БП… Тогда, в 16 лет не смог осилить ту проблему :(
        Блин, аж ностальгия пробила, вот были времена!
  • +1
    У меня вот такая штуковина есть: LCD2USB, давно уже собирал, подключается по usb.

    image
    • 0
      Ахренительно 40х4. Думал таковых не бывает в природе. Ну осталось прослойку протокола написать между нашим и вашим :))))
      • 0
        Они на двух HD44780. Софтовая часть из-за этого становится менее приятной)
        • 0
          Ммм… А как идёт выбо какой куда выводить?
          • +1
            Если рассматривать конкретное устройство выше — то у него есть ещё внешний контроллер (какая-то AVR), которая с точки зрения софта на компе — единый дисплей.

            Если смотреть на железо — там два почти независимых контроллера с 4хбитной шиной. И контроллер, использующий дисплей, выводит нужные части на разные HD44780. Т. е. общие d0-3, rs, rw, vcc, gnd, contrast и раздельные e1 и e2.
            • 0
              Т. е. общие d0-3, rs, rw, vcc, gnd, contrast и раздельные e1 и e2.


              Собственно это меня и интересовало. Цена ещё у них кусается. За такие бабки проще с графическим разобраться. Тем более, что я прикидывал, что если взять экран 40х4, то он будет эквивалентен графическому:
              40*6х4*9=240х36 (по пикселю добавил на пробел между символами).
          • +1
            У 40x4 дисплеев есть два дополнительных вывода E1 и E2, подавая лог. уровень на один из них, можно выводить символы на первую половину (20x4) или на вторую.
            • 0
              Ну я уже понял, пояснили. Спасибо!
      • +1
        Есть еще старенький 20x8, там еще веселее — 4 отдельных чипа. Филипс — LM324.
        • 0
          Да его не достать…
  • 0
    Простите мое невежество.

    static const struct file_operations lptlcd_fops = {
        .owner  = THIS_MODULE,
        .read   = dev_read,
        .write  = dev_write,
    };
    


    Что обозначают точки перед полями структуры? Для чего они?
    • +2
      Это синтаксис инициализации структур из ansi c 99. Так можно более наглядно и удобно инициализировать поля структур (в произвольном порядке, например).
  • 0
    Решил присоединиться к празднику жизни. Сегодня-завтра опубликую пост.

    Возник вопрос при реализации (в описании vt100/vt52 не нашел): [X и [Y отличаются порядком аргументов?
    • 0
      Псот лучше всего будет опубликовать с сегодня на завтра в ночь :))))

      На счёт ESC-последовательностей, честно признаюсь, что описания не читал. Но согласно переведённому факу, как я понял мы сразу указываем обе координаты:

      echo -en "\033[Y\037\038Hello" > /dev/lptlcd

      Выведет в нулевую позицию по Y и в первую по X
      • +1
        Этот вариант я увидел. У вас, кстати, опечатка: \038. Сам мало восьмиричной системой пользуюсь, так же ошибаюсь. Шестнадцатиричная как-то привычнее.

        Реализовал в итоге так:
        — ESC-Y[Y-координата+037][X-координата+037]
        — ESC-X[X-координата+037][Y-координата+037]
        • 0
          Да… Опечатка, точняк. Нулевая позиция по Y будет 037+0=037, а первая по X 037+1=040

          А шестнадцатиричная — да…
      • +1
  • +1
    Как же просто работать с таким дисплеем по i2c на raspberry + wiringPi. По сути воткнул — и работает.
    • 0
      Верю :). ESC-последовательности так же работают? :))))
      • 0
        Ну, все функции, которые вы указали для ESC-последовательностей уже реализованы в wiringPi. Если уж так надо, то никто не мешает линейно парсить входную строку на наличие нужных кодов и вызывать соответствующие функции.
        • 0
          Вы мой код не смотрели? Я так и делаю. И поверьте, что i2c, что параллельный порт — никакой разницы.
        • 0
          Кстати, для справки, данный дисплей, который я юзаю в этом проекте изначально был i2c, потом платка была снята.
  • 0
    Сергей, хорошая статья. Рад, что Вы с таким увлечением занялись разработкой модулей ядра!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.