Русский
Русский
English
Статистика
Реклама

Пишем драйвер фреймбуфера для Raspberry Pi с LCD

Прочитав монументальную серию статей о подключении LCD экрана к роутеру мне захотелось сделать то же самое. Однако многообразие используемого стека (openwrt, stm32, usb) в сочетании с отсутствием полных исходников кода но может плохо искал несколько затруднило задачу. Я решил начать с малого написать свою реализацию framebuffer для raspberry и вывести графическую среду raspberry на LCD. Что из этого получилось, описываю далее.


Вообще-то имеются готовые драйверы для LCD (проект tbtft), но мы напишем свой, чтобы лучше понять как все устроено.


LCD


LCD 320x240 с контроллером ILI9341. Передача данных по 8 битной шине.


Запись данных в LCD осуществляется следующим образом (стр.28):


1 на RD и 1 на RESET после старта LCD держим все время. Перед передачей данных подаем 0 на CS, выставляем 8 бит данных на шине, устанавливаем 1 или 0 на RS (D/CX на графике) в зависимости от типа передачи данные / команда, сбрасываем WR в 0, затем устанавливаем в 1. После окончания передачи данных выставляем CS в 1.


Код передачи данных / команд
/* файл lcd.c */void LCD_write(u8 VAL){    LCD_CS_CLR;    DATAOUT(VAL);    LCD_WR_CLR;    LCD_WR_SET;    LCD_CS_SET;}/* передача команды */void LCD_WR_REG(u8 data){    LCD_RS_CLR;    LCD_write(data);}/* передача данных */void LCD_WR_DATA(u8 data){    LCD_RS_SET;    LCD_write(data);}/* запись значения в регистр */void LCD_WriteReg(u8 LCD_Reg, u8 LCD_RegValue){    LCD_WR_REG(LCD_Reg);    LCD_WR_DATA(LCD_RegValue);}/* передача 16 бит данных */void Lcd_WriteData_16Bit(u16 Data){    LCD_RS_SET;    LCD_CS_CLR;    DATAOUT((u8)(Data>>8));    LCD_WR_CLR;    LCD_WR_SET;    DATAOUT((u8)Data);    LCD_WR_CLR;    LCD_WR_SET;    LCD_CS_SET;}

Основной код управления LCD (для STM32), в основном взят отсюда и адаптирован для raspberry. Цвет каждого пикселя на LCD задается 16 битами в формате RGB565 (5 бит на красный цвет, 6 на зеленый, 5 на синий).


Код управления LCD
/* файл lcd.h */#define LCD_W 320#define LCD_H 240/* файл lcd.c *//* индикация того, что далее передаются данные для видеобуфера */void LCD_WriteRAM_Prepare(void){    LCD_WR_REG(0x2C);}/* задаем прямоугольник на экране, который будем отрисовывать */void LCD_SetWindows(u16 xStart, u16 yStart,u16 xEnd,u16 yEnd){    LCD_WR_REG(0x2A);    LCD_WR_DATA(xStart>>8);    LCD_WR_DATA(0x00FF&xStart);    LCD_WR_DATA(xEnd>>8);    LCD_WR_DATA(0x00FF&xEnd);    LCD_WR_REG(0x2B);    LCD_WR_DATA(yStart>>8);    LCD_WR_DATA(0x00FF&yStart);    LCD_WR_DATA(yEnd>>8);    LCD_WR_DATA(0x00FF&yEnd);    LCD_WriteRAM_Prepare();}/* ресет экрана */void LCD_RESET(void){    LCD_RST_CLR;    delay(100);    LCD_RST_SET;    delay(50);}/* инициализация экрана */void LCD_Init(void){    LCD_RESET();    LCD_WR_REG(0xCF);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0xC9);    LCD_WR_DATA(0X30);    LCD_WR_REG(0xED);    LCD_WR_DATA(0x64);    LCD_WR_DATA(0x03);    LCD_WR_DATA(0X12);    LCD_WR_DATA(0X81);    LCD_WR_REG(0xE8);    LCD_WR_DATA(0x85);    LCD_WR_DATA(0x10);    LCD_WR_DATA(0x7A);    LCD_WR_REG(0xCB);    LCD_WR_DATA(0x39);    LCD_WR_DATA(0x2C);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x34);    LCD_WR_DATA(0x02);    LCD_WR_REG(0xF7);    LCD_WR_DATA(0x20);    LCD_WR_REG(0xEA);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_REG(0xC0);        LCD_WR_DATA(0x1B);       LCD_WR_REG(0xC1);        LCD_WR_DATA(0x00);       LCD_WR_REG(0xC5);        LCD_WR_DATA(0x30);       LCD_WR_DATA(0x30);       LCD_WR_REG(0xC7);       LCD_WR_DATA(0XB7);    LCD_WR_REG(0x36);        LCD_WR_DATA(0x08);    LCD_WR_REG(0x3A);    LCD_WR_DATA(0x55);    LCD_WR_REG(0xB1);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x1A);    LCD_WR_REG(0xB6);        LCD_WR_DATA(0x0A);    LCD_WR_DATA(0xA2);    LCD_WR_REG(0xF2);        LCD_WR_DATA(0x00);    LCD_WR_REG(0x26);        LCD_WR_DATA(0x01);    LCD_WR_REG(0xE0);        LCD_WR_DATA(0x0F);    LCD_WR_DATA(0x2A);    LCD_WR_DATA(0x28);    LCD_WR_DATA(0x08);    LCD_WR_DATA(0x0E);    LCD_WR_DATA(0x08);    LCD_WR_DATA(0x54);    LCD_WR_DATA(0XA9);    LCD_WR_DATA(0x43);    LCD_WR_DATA(0x0A);    LCD_WR_DATA(0x0F);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_REG(0XE1);        LCD_WR_DATA(0x00);    LCD_WR_DATA(0x15);    LCD_WR_DATA(0x17);    LCD_WR_DATA(0x07);    LCD_WR_DATA(0x11);    LCD_WR_DATA(0x06);    LCD_WR_DATA(0x2B);    LCD_WR_DATA(0x56);    LCD_WR_DATA(0x3C);    LCD_WR_DATA(0x05);    LCD_WR_DATA(0x10);    LCD_WR_DATA(0x0F);    LCD_WR_DATA(0x3F);    LCD_WR_DATA(0x3F);    LCD_WR_DATA(0x0F);    LCD_WR_REG(0x2B);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x01);    LCD_WR_DATA(0x3f);    LCD_WR_REG(0x2A);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0x00);    LCD_WR_DATA(0xef);    LCD_WR_REG(0x11);     delay(120);    LCD_WR_REG(0x29);     LCD_WriteReg(0x36,(1<<3)|(1<<5)|(1<<6)); }/* заполняем экран одним цветом */void LCD_Clear(u16 Color){    unsigned int i;    LCD_SetWindows(0,0,LCD_W-1,LCD_H-1);    for(i=0;i<LCD_H*LCD_W;i++)    {        Lcd_WriteData_16Bit(Color);    }}/* рисуем картинку из raw файла (в нем подряд идут цвета пикселей в формате RGB565) */void LCD_draw_image(char *file){    int fd = open(file, O_RDWR);    if(fd < 0){        perror("Open file");        exit(1);    }    u16 buffer[128];    LCD_SetWindows(0,0,LCD_W-1,LCD_H-1);    while(1){        int nread = read(fd, buffer, 256);        if(nread == 0 || nread < 0)            break;        /* buffer[i] - 2 байта, поэтому пишем nread/2 раз */        for(int i=0; i < nread/2; i++){            Lcd_WriteData_16Bit(buffer[i]);        }    }    close(fd);}

Raspberry


Я использую raspberry pi 3 с установленным raspbian lite (версия ядра 4.14). GUI добавлено установкой пакетов lxde и xinit.


sudo apt-get install lxde xinit

Расположение GPIO



Подключение LCD к raspberry


  • LCD Data 0 -> GPIO 12
  • LCD Data 1 -> GPIO 13
  • ...
  • LCD Data 7 -> GPIO 19
  • LCD CS -> GPIO 20
  • LCD RS -> GPIO 21
  • LCD RST -> GPIO 22
  • LCD WR -> GPIO 23
  • LCD RD -> GRPIO 24
  • LCD 5V -> 5V
  • LCD GND -> Ground

Управление GPIO


В raspberry GPIO можно управлять через прямое обращение к памяти. Из мануала к BCM 2837 32 битные регистры GPFSEL0-5 используются для установки режима GPIO. На каждый GPIO пин отводится 3 бита. Пину 0 соответствуют биты 2-0 в GPFSEL0, пину 1 биты 5-3 и т.д. Каждый регистр управляет 10 GPIO. Биты 000 соответствуют режиму input, биты 001 режиму output. Установку режима можно описать следующим образом:


/* файл rpi_gpio.h *//* установка input режима */#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))/* установка output режима */#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))

Для пинов 0 31 в режиме output установка 1 делается через регистр GPSET0. Чтобы установить GPIO n в 1, в регистр нужно записать число, n-ый бит в котором равен 1. Например, для установки 1 в GPIO 10 и 11 в регистр GPSET0 необходимо записать число 0b11 << 10.


Аналогично, установка 0 осуществляется через регистр GPCLR0.


/* устанавливаем 1 на GPIO, например, 1 на GPIO 10 - GPIO_SET = 1<<10 */#define GPIO_SET *(gpio+7)/*  устанавливаем 0 на GPIO, например, 0 на GPIO 10 - GPIO_CLR = 1<<10 */#define GPIO_CLR *(gpio+10)

gpio содержит виртуальный адрес физического адреса 0x3F200000 (отображенного посредством mmap в виртуальную память процесса). *gpio позволяет обратиться к GPFSEL0. *(gpio+7) к GPSET0. *(gpio+10) к GPCLR0.


Код установки gpio
/* файл rpi_gpio.c */int setup_rpi_gpio(){    unsigned int gpio_base_addr = 0x3F200000;   /* open /dev/mem */   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {      printf("can't open /dev/mem \n");      return -1;   }   /* mmap GPIO */   gpio_map = mmap(      NULL,             //Any adddress in our space will do      BLOCK_SIZE,       //Map length      PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory      MAP_SHARED,       //Shared with other processes      mem_fd,           //File to map      gpio_base_addr    //Offset to GPIO peripheral   );   close(mem_fd); //No need to keep mem_fd open after mmap   if (gpio_map == MAP_FAILED) {      printf("mmap error %d\n", (int)gpio_map);//errno also set!      return -1;   }   // Always use volatile pointer!   gpio = (volatile uint32_t *)gpio_map;   return 0;}

Управление LCD c raspberry


Пинами LCD управляем следующим образом:
/* файл lcd.h */#define BIT_BASE 12#define CS   20#define RS   21#define RST  22#define WR   23#define RD   24#define LCD_CS_SET  GPIO_SET=(1<<CS)#define LCD_RS_SET  GPIO_SET=(1<<RS)#define LCD_RST_SET GPIO_SET=(1<<RST)#define LCD_WR_SET  GPIO_SET=(1<<WR)#define LCD_RD_SET  GPIO_SET=(1<<RD)#define LCD_CS_CLR  GPIO_CLR=(1<<CS)#define LCD_RS_CLR  GPIO_CLR=(1<<RS)#define LCD_RST_CLR GPIO_CLR=(1<<RST)#define LCD_WR_CLR  GPIO_CLR=(1<<WR)#define LCD_RD_CLR  GPIO_CLR=(1<<RD)#define DATAOUT(x) GPIO_SET=(x<<BIT_BASE);GPIO_CLR=(x<<BIT_BASE)^(0xFF<<BIT_BASE)

Проверка работы с LCD в user space


Перед тем как бросаться в пучину kernel, проверим работу с LCD в user space. Подготовим картинку image.jpg в формате raw 320x240. В output.raw содержатся подряд идущие 16 битные значения цвета каждого пикселя (RGB565):


mogrify -format bmp -resize 320 -crop 320x240 image.jpgffmpeg -vcodec bmp -i image.bmp -vcodec rawvideo -f rawvideo -pix_fmt rgb565 output.raw

Выведем output.raw на LCD:


/* файл main.c */int main(int argc , char *argv[]){    if( setup_rpi_gpio() ) {        printf("Cannot map GPIO memory, probably use <sudo>\n");        return -1;    }    for(int i = BIT_BASE; i <= RD; i++){        INP_GPIO(i);        OUT_GPIO(i);    }    //set BITS_BASE - RD to 1    GPIO_SET = 0xFFF<<12;    GPIO_SET = 1 << RD;    LCD_Init();    if(argc >= 2){        LCD_draw_image(argv[1]);    }}

gcc main.c rpi_gpio.c lcd.c -o mainsudo ./main output.raw


Подготовка окружения


Если все работает, самое время приступить к подготовке окружения для компиляции и запуска драйвера.


Заголовки ядра со скриптами сборки для текущей версии ядра в raspbian так просто не поставить, поэтому скачаем исходный код linux, скомпилируем и установим ядро, и будем использовать эти заголовки со скриптами для компиляции драйвера. Основной reference по этому процессу здесь. Версия сорцов ядра подобрана под мою версию raspbian.


git clone --depth=1 -b rpi-4.14.y https://github.com/raspberrypi/linux.gitcd linuxKERNEL=kernel7make bcm2709_defconfigmake -j4 zImage modules dtbssudo make modules_installsudo cp arch/arm/boot/dts/*.dtb /boot/sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/sudo cp arch/arm/boot/zImage /boot/$KERNEL.img

Компиляцию драйвера в дальнейшем выполняем командой make, поместив в директорию с драйвером вот такой Makefile:


Makefile
ifeq ($(KERNELRELEASE),)    KERNELDIR ?= /lib/modules/$(shell uname -r)/build        PWD := $(shell pwd)modules:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions.PHONY: modules modules_install cleanelse    # имя драйвера, если компилируем vfb.c, заменим на vfb.o        obj-m := lcd_drv_simple.oendif

Драйвер фреймбуфера


Теория фреймбуферов хорошо расписана здесь и здесь, поэтому повторяться не буду.


Начнем с виртуального фреймбуфера (vfb.c). Он выделяет область памяти, в которую пишет изображение, направленное в /dev/fbX (X номер устройства). Это изображение потом можно легко прочитать через cat /dev/fbX. Этот драйвер удобен для тестирования (в нашем случае того, что компиляция и установка драйвера проходит успешно).


Код берем отсюда. Далее


makesudo cp vfb.ko /lib/modules/$(uname -r)/extra/# просим систему обновить зависимостиsudo depmod# загружаем драйверsudo modprobe vfb_enable=1# устанавливаем размер экрана и глубину цвета (16 бит, режим RGB565)fbset -fb /dev/fb1 -g 320 240 320 240 16

Должно появиться новое framebuffer устройство (/dev/fb1). Запишем в него какое-нибудь изображение,


sudo apt-get install fbi# fbi требует запуска из полноценной консоли, если запускаем под ssh используем sudo и -T 1 для указания первой консоли sudo fbi -a -d /dev/fb1 -T 1 image.jpg

считаем его


cat /dev/fb1 > scrn.raw

и откроем в gimp как файл raw rgb565. Убедимся, что изображение есть.


Простой драйвер


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


Установку режима и уровня (1/0) пинов модифицируем следующим образом (просто прямой доступ к I/O памяти в ядре не работает):


/* файл lcd_drv_simple.c */static void inp_gpio(u32 g){    u32 *addr = gpio+g/10;    u32 val = readl(addr);    u32 tmp =  ~(7<<((g%10)*3));    val &= tmp;    writel(val,addr);}static void out_gpio(u32 g){    u32 *addr = gpio+g/10;    u32 val = readl(addr);    u32 tmp =  (1<<(((g)%10)*3));    val |= tmp;    writel(val,addr);}static void GPIO_SET(u32 val){    writel(val,gpio+7);}static void GPIO_CLR(u32 val){    writel(val,gpio+10);}

Адрес gpio получаем вызовом ioremap:


gpio = ioremap(PORT, RANGE);

Параметры драйвера описываются в структурах:
u32 *gpio;static unsigned PORT = 0x3F200000;static unsigned RANGE =  0x40;#define W 320#define H 240static struct fb_fix_screeninfo ili9341_fix  = {        .type        = FB_TYPE_PACKED_PIXELS,        .visual      = FB_VISUAL_TRUECOLOR,        .accel       = FB_ACCEL_NONE,        .line_length = W * 2,};static struct fb_var_screeninfo ili9341_var  = {        .xres        = W,        .yres        = H,        .xres_virtual    = W,        .yres_virtual    = H,        .width        = W,        .height        = H,        .bits_per_pixel = 16,        .red         = {11, 5, 0}, /* смещение 11 бит, 5 битов на красный цвет */        .green         = {5, 6, 0}, /* смещение 5 бит, 6 битов на зеленый цвет */        .blue         = {0, 5, 0}, /* смещение 0 бит, 5 битов на синий цвет */        .activate     = FB_ACTIVATE_NOW,        .vmode     = FB_VMODE_NONINTERLACED,};/* используем готовую реализацию операций с фреймбуфером */static struct fb_ops ili9341_fbops = {        .owner        = THIS_MODULE,        .fb_write     = fb_sys_write,        .fb_fillrect  = sys_fillrect,        .fb_copyarea  = sys_copyarea,        .fb_imageblit = sys_imageblit,        .fb_setcolreg   = ili9341_setcolreg,};/* ссылки на функции probe и remove */struct platform_driver ili9341_driver = {        .probe = ili9341_probe,        .remove = ili9341_remove,        .driver = { .name = "my_fb_driver" }};/* задаем функцию ili9341_update, обновляющую экран (частота обновления задается в параметре delay) */static struct fb_deferred_io ili9341_defio = {        .delay          = HZ / 25,        .deferred_io    = &ili9341_update,};

Основные функции:
static int  ili9341_probe(struct platform_device *dev){    int ret = 0;    struct ili9341 *item;    struct fb_info *info;    unsigned char  *videomemory;    printk("ili9341_probe\n");    /*выделяем память под вспомогательную структуру для хранения указателей */    item = kzalloc(sizeof(struct ili9341), GFP_KERNEL);    if (!item) {        printk(KERN_ALERT "unable to kzalloc for ili9341\n");        ret = -ENOMEM;        goto out;    }    /* заполняем ее */    item->dev = &dev->dev;    dev_set_drvdata(&dev->dev, item);    /* получаем ссылку на минимально инициализированный fb_info */    info = framebuffer_alloc(0, &dev->dev);    if (!info) {        ret = -ENOMEM;        printk(KERN_ALERT "unable to framebuffer_alloc\n");        goto out_item;    }    item->info = info;    /* заполняем структуру fb_info нашими данными */    info->par = item;    info->dev = &dev->dev;    info->fbops = &ili9341_fbops;    info->flags = FBINFO_FLAG_DEFAULT;    info->fix = ili9341_fix;    info->var = ili9341_var;    info->fix.smem_len = VIDEOMEM_SIZE; // размер буфера видеопамяти    info->pseudo_palette = &pseudo_palette;    /* выделяем память под видеобуфер, в который пишут приложения, использующие /dev/fbX */    videomemory=vmalloc(info->fix.smem_len);    if (!videomemory)    {        printk(KERN_ALERT "Can not allocate memory for framebuffer\n");        ret = -ENOMEM;        goto out_info;    }    /* прописываем его в структуре fb_info и сохраняем в нашей структуре ili9341 для дальнейшего использования */    info->fix.smem_start =(unsigned long)(videomemory);    info->screen_base = (char __iomem *)info->fix.smem_start;    item->videomem = videomemory;    /* заполняем информацию об отложенном обновлении экрана */    info->fbdefio = &ili9341_defio;    fb_deferred_io_init(info);    /* передаем заполненную структуру fb_info ядру */    ret = register_framebuffer(info);    if (ret < 0) {        printk(KERN_ALERT "unable to register_frambuffer\n");        goto out_pages;    }    if (ili9341_setup(item)) goto out_pages;    return ret;    out_pages:    kfree(videomemory);    out_info:    framebuffer_release(info);    out_item:    kfree(item);    out:    return ret;}int ili9341_setup(struct ili9341 *item){    int i;    /* отображаем адрес для работы с портами GPIO в gpio */    gpio = ioremap(PORT, RANGE);    if(gpio == NULL){        printk(KERN_ALERT "ioremap error\n");        return 1;    }    /* инициализируем LCD */    for(i = BIT_BASE; i <= RD; i++){        inp_gpio(i);        out_gpio(i);    }    GPIO_SET(0xFFF<<12);    GPIO_SET(1 << RD);    LCD_Init();    printk("ili9341_setup\n");    return 0;}static void ili9341_update(struct fb_info *info, struct list_head *pagelist){    /* получаем ссылку на нашу структуру с указателями */    struct ili9341 *item = (struct ili9341 *)info->par;    /* адрес видеопамяти */    u16 *videomemory = (u16 *)item->videomem;    int i, j, k;    /* заполняем весь экран */    LCD_SetWindows(0,0,LCD_W-1,LCD_H-1);        for(i = 0; i < LCD_W * LCD_H; i++){        /* читаем данные из видеопамяти попиксельно и записываем их в LCD */        Lcd_WriteData_16Bit(readw(videomemory));        videomemory++;    }}

Запускаем графическую оболочку на LCD


Проверим работу драйвера. Скомпилируем, установим и загрузим его


makesudo cp lcd_drv_simple.ko /lib/modules/$(uname -r)/extra/sudo depmodsudo modprobe lcd_drv_simple

Выведем случайное изображение:


cat /dev/urandom > /dev/fb1

Выведем на соответствующий /dev/fbX картинку или видео:


sudo fbi -a -d /dev/fb1 -T 1 image.jpgmplayer -vo fbdev:/dev/fb1 video.mp4

Запустим графическую оболочку на LCD. Если Desktop environment (DE) еще не установлено (например, серверный вариант raspbian), его можно поставить:


sudo apt-get install lxde

Создадим файл /etc/X11/xorg.conf:


Section "Device"    Identifier "FBDEV"    Driver "fbdev"    Option "fbdev" "/dev/fb1"EndSection

и добавим в /etc/rc.local:


/sbin/modprobe lcd_drv_simple

После перезагрузки на LCD должна появиться графическая оболочка.


Ускоряем работу драйвера


Предыдущий вариант драйвера прост, но не очень быстр. Полная перерисовка экрана заметна. Deferred_io хорошо тем, что ядро передает в функцию ili9341_update список измененных страниц видеопамяти, которые и нужно перерисовать на экране. Т.е. необходимо понять, какая область экрана соответствует заданным 4096 байтам (размер страницы памяти).


  • Первые 4096 байтов соответствуют полным 6 линиям и 128 пикселям 7ой линии, т.к. 4096 = 320*2*6 + 128*2 (2 байта на каждый пиксель)
  • Вторые 4096 байтов начинаются с 129 пикселя 7ой линии, требуют 384 байта для завершения линии (128*2 + 384 = 640), затем идут 5 полных линий и 256 пикселей в 6 линии (4096 = 384 + 640*5 + 512).

Аналогично продолжаем рассуждения дальше, получается, что каждые 5 страниц ситуация повторяется. Поэтому достаточно прописать 5 вариантов отрисовки страницы памяти на экране. Отдельно прописываем работу с последней страницей номер 37, т.к. она занимает 2048 байтов:


Код драйвера
/* файл lcd_drv_fast.c *//* далее используем атомарные операции, которые по факту не очень нужны, т.к. метод ili9341_touch на raspberry ни разу не вызывался (т.е. нет ситуации нескольких потоков выполнения, изменяющих toUpdate одновременно */ static void ili9341_update(struct fb_info *info, struct list_head *pagelist){    struct ili9341 *item = (struct ili9341 *)info->par;    struct page *page;    int i;        /* для измененных страниц вычитаем 1 из toUpdate атомарно, toUpdate для этих страниц принимает значение -2 */     list_for_each_entry(page, pagelist, lru)    {        atomic_dec(&item->videopages[page->index].toUpdate);    }    for (i=0; i<FP_PAGE_COUNT; i++)    {        /* для всех страниц увеличиваем toUpdate на 1. Если страница не измененена, то вычтем 1 обратно и получим -1. Если изменена, то также получим -1 после инкремента, но в этом случае еще и выполним отрисовку измененной страницы */        if(atomic_inc_and_test(&item->videopages[i].toUpdate)){            atomic_dec(&item->videopages[i].toUpdate);        }        else        {            draw(item, i);                  }    }}static void draw(struct ili9341 *item, int page){    int xs,ys,i;    /* рассчитываем адрес страницы в видеопамяти */    u16 *videomemory = (u16*)(item->videomem + PAGE_SIZE*page);    /* строка LCD, с которой начинается страница */    ys = (((unsigned long)(PAGE_SIZE*page)>>1)/W);    /* короткая страница памяти, обрабатываем отдельно */    if (page == 37){        // write PAGE_SIZE / 2;        //write 128 bytes        LCD_SetWindows(256, ys, LCD_W-1, ys);        for(i = 0; i < 128 / 2; i++){            Lcd_WriteData_16Bit(readw(videomemory));            videomemory++;        }        //write 3 lines        LCD_SetWindows(0, ys+1, LCD_W-1, ys+6);        for(i = 0; i < 640 * 3 / 2; i++){            Lcd_WriteData_16Bit(readw(videomemory));            videomemory++;        }    }    else{        switch (page % 5){        //xs = 0. write full six lines and 256 bytes        //640 * 6 + 256        case 0:            //write 6 lines            LCD_SetWindows(0,ys,LCD_W-1,ys + 5);            for(i = 0; i < 640 * 6 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 256 bytes            LCD_SetWindows(0, ys+6, 256/2-1, ys + 6); //7th line from x = 0 to x = 256/2            for(i = 0; i < 256 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            break;        //xs = 128 (256 bytes). write 384 bytes, 5 full lines and 512 bytes        //384 + 640 * 5 + 512        case 1:            //write 384 bytes            LCD_SetWindows(256/2, ys, LCD_W-1, ys);            for(i = 0; i < 384 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 5 lines            LCD_SetWindows(0, ys+1, LCD_W-1, ys+5);            for(i = 0; i < 640 * 5 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 512 bytes            LCD_SetWindows(0, ys+6, 512/2-1, ys+6);            for(i = 0; i < 512 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            break;        //xs = 256 (512 bytes). write 128 bytes, then 6 full lines and 128 bytes        //128 + 640*6 + 128        case 2:            //write 128 bytes            LCD_SetWindows(256, ys, LCD_W-1, ys);            for(i = 0; i < 128 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 6 lines            LCD_SetWindows(0, ys+1, LCD_W-1, ys+6);            for(i = 0; i < 640 * 6 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 128 bytes            LCD_SetWindows(0, ys+7, 128/2-1, ys+7);            for(i = 0; i < 128 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            break;        //xs = 64 (128 /2). write 512 bytes, then 5 lines and 384 bytes        //512 + 640*5 + 384        case 3:            //write 512 bytes            LCD_SetWindows(64, ys, LCD_W-1, ys);            for(i = 0; i < 512 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 5 lines            LCD_SetWindows(0, ys+1, LCD_W-1, ys+5);            for(i = 0; i < 640 * 5 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            //write 384 bytes            LCD_SetWindows(0, ys+6, 384/2-1, ys+6);            for(i = 0; i < 384 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            break;        //xs = 384/2. write 256 bytes, then 6 full lines        //256 + 640*6        case 4:            //write 256 bytes            LCD_SetWindows(384/2, ys, LCD_W-1, ys);            for(i = 0; i < 256 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            LCD_SetWindows(0, ys+1, LCD_W-1, ys+6);            for(i = 0; i < 640 * 6 / 2; i++){                Lcd_WriteData_16Bit(readw(videomemory));                videomemory++;            }            break;        default: break;        }    }}

Также небольшие изменения в структуре ili9341 и функции ili9341_probe:


struct videopage{    atomic_t                toUpdate;};struct ili9341 {    struct device *dev;    struct fb_info *info;    unsigned char *videomem;    /* здесь отмечаем изменения в страницах памяти */    struct videopage videopages[FP_PAGE_COUNT];};static int  ili9341_probe(struct platform_device *dev){    ...    /* инициализируем массив для отслеживания изменений страниц памяти */    for(i=0;i<FP_PAGE_COUNT;i++)    {        atomic_set(&item->videopages[i].toUpdate, -1);          }}

В структуре ili9341_fbops используем свои функции, которые работают как обертка над стандартными, при этом помечая измененные страницы с помощью функции ili9341_touch. Дело в том, что если ядро использует функции отрисовки, заданные структурой ili9341_fbops, измененные страницы памяти в ili9341_update не поступают и их нужно отдельно помечать. Фактически же, графическая система raspbian эти функции не использует.


Код
static struct fb_ops ili9341_fbops = {        .owner        = THIS_MODULE,        .fb_write     = ili9341_write,        .fb_fillrect  = ili9341_fillrect,        .fb_copyarea  = ili9341_copyarea,        .fb_imageblit = ili9341_imageblit,        .fb_setcolreg   = ili9341_setcolreg,};static ssize_t ili9341_write(struct fb_info *p, const char __user *buf, size_t count, loff_t *ppos){    ssize_t retval;    printk("ili9341_write\n");    retval=fb_sys_write(p, buf, count, ppos);    ili9341_touch(p, 0, 0, p->var.xres, p->var.yres);    return retval;}static void ili9341_fillrect(struct fb_info *p, const struct fb_fillrect *rect){    printk("ili9341_fillrect\n");    sys_fillrect(p, rect);    ili9341_touch(p, rect->dx, rect->dy, rect->width, rect->height);}static void ili9341_imageblit(struct fb_info *p, const struct fb_image *image){    printk("ili9341_imageblit\n");    sys_imageblit(p, image);    ili9341_touch(p, image->dx, image->dy, image->width, image->height);}static void ili9341_copyarea(struct fb_info *p, const struct fb_copyarea *area){    printk("ili9341_copyarea\n");    sys_copyarea(p, area);    ili9341_touch(p, area->dx, area->dy, area->width, area->height);}static void ili9341_touch(struct fb_info *info, int x, int y, int w, int h){    struct ili9341 *item = (struct ili9341 *)info->par;    int firstPage;    int lastPage;    int i;    printk("touch x %d, y %d, w %d, h %d",x,y,w,h);    firstPage=((y*W)+x)*BYTE_DEPTH/PAGE_SIZE-1;    lastPage=(((y+h)*W)+x+w)*BYTE_DEPTH/PAGE_SIZE+1;    if(firstPage<0)        firstPage=0;    if(lastPage>FP_PAGE_COUNT)        lastPage=FP_PAGE_COUNT;    for(i=firstPage;i<lastPage;i++)        atomic_dec(&item->videopages[i].toUpdate);    schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);}

Система с двумя экранами


Немного поэксперементируем. Подключим к raspberry два экрана. В качестве основного экрана используем экран / телевизор, подключенный к HDMI. В качестве второго экрана используем LCD.


Чтобы перетаскивание окошек между экранами было лучше видно, я "увеличил" размер экрана LCD, которые видит linux до 640x480. В ядре я регистрирую экран 640x480, однако на сам LCD пишу каждый второй пиксель в строке и пропускаю каждую вторую строку. Измененный код ili9341_update:


/* файл lcd_drv_simple_640_480.c */#define W 320*2#define H 240*2/* изменения в ili9341_update на примере простого драйвера */for(j = 0; j < H; j++){    if (j % 2 == 1){ //skip        videomemory += W;    }    else{        for(i = 0; i < W; i += 2){            Lcd_WriteData_16Bit(readw(videomemory));            videomemory += 2;                       }    }}

Для работы с двумя экранами глубина цвета на них должна быть одинаковой. Для этого добавляем в /boot/config.txt:


[all]framebuffer_depth=16

Ставим xinerama для перетаскивания окон между экранами:


sudo apt-get install libxinerama-dev

Заменяем конфигурационный файл /etc/X11/xorg.conf


xorg.conf
Section "Device"        Identifier      "LCD"        Driver          "fbdev"        Option          "fbdev" "/dev/fb1"        Option          "ShadowFB" "off"        Option          "SwapbuffersWait" "true"EndSectionSection "Device"        Identifier      "HDMI"        Driver          "fbdev"        Option          "fbdev" "/dev/fb0"        Option          "ShadowFB" "off"        Option          "SwapbuffersWait" "true"EndSectionSection "Monitor"        Identifier      "LCD-monitor"        Option          "RightOf" "HDMI-monitor"EndSectionSection "Monitor"        Identifier      "HDMI-monitor"        Option          "Primary" "true"        EndSectionSection "Screen"        Identifier      "screen0"        Device          "LCD"        Monitor         "LCD-monitor"EndSectionSection "Screen"        Identifier      "screen1"        Device          "HDMI"         Monitor         "HDMI-monitor"EndSectionSection "ServerLayout"        Identifier      "default"        Option          "Xinerama" "on"        Option          "Clone" "off"        Screen 0        "screen0" RightOf "screen1"        Screen 1        "screen1" EndSection

Результат:


Заключение


Надеюсь было интересно. Код на github.

Источник: habr.com
К списку статей
Опубликовано: 05.01.2021 00:04:18
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Настройка linux

Разработка на raspberry pi

Framebuffer

Linux kernel

Raspberry pi

Linux

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru