GLCD2USB driver from Till Harbaum

git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@987 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
This commit is contained in:
michael
2009-02-28 06:19:46 +00:00
parent 64522a267d
commit 915154c4eb
8 changed files with 629 additions and 14 deletions
+1
View File
@@ -75,6 +75,7 @@ drv_Cwlinux.c \
drv_D4D.c \
drv_EA232graphic.c \
drv_G15.c \
drv_GLCD2USB.c \
drv_HD44780.c \
drv_Image.c \
drv_IRLCD.c \
+2
View File
@@ -274,6 +274,7 @@ drv_Cwlinux.c \
drv_D4D.c \
drv_EA232graphic.c \
drv_G15.c \
drv_GLCD2USB.c \
drv_HD44780.c \
drv_Image.c \
drv_IRLCD.c \
@@ -463,6 +464,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_D4D.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_EA232graphic.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_G15.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_GLCD2USB.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_HD44780.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_IRLCD.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_Image.Po@am__quote@
+3
View File
@@ -449,6 +449,9 @@
/* GD library */
#undef WITH_GD
/* GLCD2USB driver */
#undef WITH_GLCD2USB
/* HD44780 driver */
#undef WITH_HD44780
Vendored
+28 -6
View File
@@ -1343,11 +1343,12 @@ Optional Packages:
(try 'all,\!<driver>' if your shell complains...)
possible drivers are:
BeckmannEgle, BWCT, CrystalFontz, Curses, Cwlinux, D4D,
G15, HD44780, IRLCD, LCD2USB LCDLinux, LCDTerm, LPH7508,
LUIse, M50530, MatrixOrbital, MilfordInstruments, Noritake,
NULL, PNG, PPM, Pertelian, PHAnderson, picoLCD, picoLCDGraphic,
RouterBoard, Sample, serdisplib, SimpleLCD, st2205, T6963, Trefon,
ULA200, USBLCD, USBHUB, WincorNixdorf, X11
G15, GLCD2USB, HD44780, IRLCD, LCD2USB, LCDLinux, LCDTerm,
LPH7508, LUIse, M50530, MatrixOrbital, MilfordInstruments,
Noritake, NULL, PNG, PPM, Pertelian, PHAnderson, picoLCD,
picoLCDGraphic, RouterBoard, Sample, serdisplib, SimpleLCD,
st2205, T6963, Trefon, ULA200, USBLCD, USBHUB, WincorNixdorf
X11
--with-plugins=<list> choose which plugins to compile.
type --with-plugins=list for a list
of avaible plugins
@@ -4447,7 +4448,7 @@ echo "${ECHO_T}Please note that some screen refreshs may fail" >&6; }
echo $ECHO_N "checking for ncurses version... $ECHO_C" >&6; }
ncurses_version=unknown
cat > conftest.$ac_ext <<EOF
#line 4450 "configure"
#line 4451 "configure"
#include "confdefs.h"
#ifdef RENAMED_NCURSES
#include <curses.h>
@@ -7694,6 +7695,7 @@ for driver in $drivers; do
D4D="yes"
EA232graphic="yes"
G15="yes"
GLCD2USB="yes"
HD44780="yes"
IRLCD="yes"
LCD2USB="yes"
@@ -7750,6 +7752,9 @@ for driver in $drivers; do
G15)
G15=$val
;;
GLCD2USB)
GLCD2USB=$val
;;
HD44780)
HD44780=$val
;;
@@ -7987,6 +7992,23 @@ echo "$as_me: WARNING: usb.h not found: G15 driver disabled" >&2;}
fi
fi
if test "$GLCD2USB" = "yes"; then
if test "$has_usb" = "true"; then
GRAPHIC="yes"
KEYPAD="yes"
DRIVERS="$DRIVERS drv_GLCD2USB.o"
LIBUSB="yes"
cat >>confdefs.h <<\_ACEOF
#define WITH_GLCD2USB 1
_ACEOF
else
{ echo "$as_me:$LINENO: WARNING: usb.h not found: GLCD2USB driver disabled" >&5
echo "$as_me: WARNING: usb.h not found: GLCD2USB driver disabled" >&2;}
fi
fi
if test "$HD44780" = "yes"; then
TEXT="yes"
PARPORT="yes"
+22 -5
View File
@@ -33,11 +33,12 @@ AC_ARG_WITH(
[ (try 'all,\!<driver>' if your shell complains...)]
[ possible drivers are:]
[ BeckmannEgle, BWCT, CrystalFontz, Curses, Cwlinux, D4D,]
[ G15, HD44780, IRLCD, LCD2USB LCDLinux, LCDTerm, LPH7508,]
[ LUIse, M50530, MatrixOrbital, MilfordInstruments, Noritake,]
[ NULL, PNG, PPM, Pertelian, PHAnderson, picoLCD, picoLCDGraphic,]
[ RouterBoard, Sample, serdisplib, SimpleLCD, st2205, T6963, Trefon,]
[ ULA200, USBLCD, USBHUB, WincorNixdorf, X11],
[ G15, GLCD2USB, HD44780, IRLCD, LCD2USB, LCDLinux, LCDTerm,]
[ LPH7508, LUIse, M50530, MatrixOrbital, MilfordInstruments,]
[ Noritake, NULL, PNG, PPM, Pertelian, PHAnderson, picoLCD,]
[ picoLCDGraphic, RouterBoard, Sample, serdisplib, SimpleLCD,]
[ st2205, T6963, Trefon, ULA200, USBLCD, USBHUB, WincorNixdorf]
[ X11],
drivers=$withval,
drivers=all
)
@@ -66,6 +67,7 @@ for driver in $drivers; do
D4D="yes"
EA232graphic="yes"
G15="yes"
GLCD2USB="yes"
HD44780="yes"
IRLCD="yes"
LCD2USB="yes"
@@ -122,6 +124,9 @@ for driver in $drivers; do
G15)
G15=$val
;;
GLCD2USB)
GLCD2USB=$val
;;
HD44780)
HD44780=$val
;;
@@ -321,6 +326,18 @@ if test "$G15" = "yes"; then
fi
fi
if test "$GLCD2USB" = "yes"; then
if test "$has_usb" = "true"; then
GRAPHIC="yes"
KEYPAD="yes"
DRIVERS="$DRIVERS drv_GLCD2USB.o"
LIBUSB="yes"
AC_DEFINE(WITH_GLCD2USB,1,[GLCD2USB driver])
else
AC_MSG_WARN(usb.h not found: GLCD2USB driver disabled)
fi
fi
if test "$HD44780" = "yes"; then
TEXT="yes"
PARPORT="yes"
+4
View File
@@ -55,6 +55,7 @@ extern DRIVER drv_Cwlinux;
extern DRIVER drv_D4D;
extern DRIVER drv_EA232graphic;
extern DRIVER drv_G15;
extern DRIVER drv_GLCD2USB;
extern DRIVER drv_HD44780;
extern DRIVER drv_Image;
extern DRIVER drv_IRLCD;
@@ -117,6 +118,9 @@ DRIVER *Driver[] = {
#ifdef WITH_G15
&drv_G15,
#endif
#ifdef WITH_GLCD2USB
&drv_GLCD2USB,
#endif
#ifdef WITH_HD44780
&drv_HD44780,
#endif
+562
View File
@@ -0,0 +1,562 @@
/* $Id$
* $URL$
*
* GLCD2USB driver for LCD4Linux
* (see http://www.harbaum.org/till/glcd2usb for hardware)
*
* Copyright (C) 2007 Till Harbaum <till@harbaum.org>
*
* This file is part of LCD4Linux.
*
* LCD4Linux is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* LCD4Linux is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/*
*
* exported fuctions:
*
* struct DRIVER drv_GLCD2USB
*
*/
/*
* Options:
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/time.h>
#include <usb.h>
#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "plugin.h"
#include "widget.h"
#include "widget_text.h"
#include "widget_icon.h"
#include "widget_bar.h"
#include "widget_keypad.h"
#include "drv.h"
#include "drv_generic_graphic.h"
#include "drv_generic_keypad.h"
#define usbDevice usb_dev_handle /* use libusb's device structure */
#include "usbcalls.h"
#include "glcd2usb.h"
/* ------------------------------------------------------------------------- */
#define USBRQ_HID_GET_REPORT 0x01
#define USBRQ_HID_SET_REPORT 0x09
usbDevice_t *dev = NULL;
/* USB message buffer */
static union {
unsigned char bytes[132];
display_info_t display_info;
} buffer;
/* ------------------------------------------------------------------------- */
#define IDENT_VENDOR_NUM 0x0403
#define IDENT_VENDOR_STRING "www.harbaum.org/till/glcd2usb"
#define IDENT_PRODUCT_NUM 0xc634
#define IDENT_PRODUCT_STRING "GLCD2USB"
static char Name[] = IDENT_PRODUCT_STRING;
/* ------------------------------------------------------------------------- */
static int usbGetString(usb_dev_handle * dev, int index, char *buf, int buflen)
{
char buffer[256];
int rval, i;
if ((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
(USB_DT_STRING << 8) + index, 0x0409, buffer, sizeof(buffer), 1000)) < 0)
return rval;
/* not a string */
if (buffer[1] != USB_DT_STRING)
return 0;
/* string would have been longer than buffer is */
if ((unsigned char) buffer[0] < rval)
rval = (unsigned char) buffer[0];
/* 16 bit unicode -> 8 bit ascii */
rval /= 2;
/* lossy conversion to ISO Latin1 */
for (i = 1; i < rval; i++) {
if (i > buflen) /* destination buffer overflow */
break;
buf[i - 1] = buffer[2 * i];
if (buffer[2 * i + 1] != 0) /* outside of ISO Latin1 range */
buf[i - 1] = '?';
}
/* terminate string */
buf[i - 1] = 0;
return i - 1;
}
/* ------------------------------------------------------------------------- */
int usbOpenDevice(usbDevice_t ** device, int vendor, char *vendorName, int product, char *productName)
{
struct usb_bus *bus;
struct usb_device *dev;
usb_dev_handle *handle = NULL;
int errorCode = USB_ERROR_NOTFOUND;
static int didUsbInit = 0;
if (!didUsbInit) {
usb_init();
didUsbInit = 1;
}
usb_find_busses();
usb_find_devices();
for (bus = usb_get_busses(); bus; bus = bus->next) {
for (dev = bus->devices; dev; dev = dev->next) {
if (dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product) {
char string[256];
int len;
handle = usb_open(dev); /* we need to open the device in order to query strings */
if (!handle) {
errorCode = USB_ERROR_ACCESS;
error("%s Warning: cannot open USB device: %s", Name, usb_strerror());
continue;
}
if (vendorName == NULL && productName == NULL) { /* name does not matter */
break;
}
/* now check whether the names match: */
len = usbGetString(handle, dev->descriptor.iManufacturer, string, sizeof(string));
if (len < 0) {
errorCode = USB_ERROR_IO;
error("%s: Cannot query manufacturer for device: %s", Name, usb_strerror());
} else {
errorCode = USB_ERROR_NOTFOUND;
if (strcmp(string, vendorName) == 0) {
len = usbGetString(handle, dev->descriptor.iProduct, string, sizeof(string));
if (len < 0) {
errorCode = USB_ERROR_IO;
fprintf(stderr, "Warning: cannot query product for device: %s\n", usb_strerror());
} else {
errorCode = USB_ERROR_NOTFOUND;
if (strcmp(string, productName) == 0)
break;
}
}
}
usb_close(handle);
handle = NULL;
}
}
if (handle)
break;
}
if (handle != NULL) {
int rval, retries = 3;
if (usb_set_configuration(handle, 1)) {
fprintf(stderr, "Warning: could not set configuration: %s\n", usb_strerror());
}
/* now try to claim the interface and detach the kernel HID driver on
* linux and other operating systems which support the call.
*/
while ((rval = usb_claim_interface(handle, 0)) != 0 && retries-- > 0) {
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
if (usb_detach_kernel_driver_np(handle, 0) < 0) {
fprintf(stderr, "Warning: could not detach kernel HID driver: %s\n", usb_strerror());
}
#endif
}
#ifndef __APPLE__
if (rval != 0)
fprintf(stderr, "Warning: could not claim interface\n");
#endif
/* Continue anyway, even if we could not claim the interface. Control transfers
* should still work.
*/
errorCode = 0;
*device = handle;
}
return errorCode;
}
/* ------------------------------------------------------------------------- */
void usbCloseDevice(usbDevice_t * device)
{
if (device != NULL)
usb_close(device);
}
/* ------------------------------------------------------------------------- */
int usbSetReport(usbDevice_t * device, int reportType, unsigned char *buffer, int len)
{
int bytesSent;
/* the write command needs some tweaking regarding allowed report lengths */
if (buffer[0] == GLCD2USB_RID_WRITE) {
int i = 0, allowed_lengths[] = { 4 + 4, 8 + 4, 16 + 4, 32 + 4, 64 + 4, 128 + 4 };
if (len > 128 + 4)
error("%s: %d bytes usb report is too long \n", Name, len);
while (allowed_lengths[i] != (128 + 4) && allowed_lengths[i] < len)
i++;
len = allowed_lengths[i];
buffer[0] = GLCD2USB_RID_WRITE + i;
}
bytesSent = usb_control_msg(device, USB_TYPE_CLASS | USB_RECIP_INTERFACE |
USB_ENDPOINT_OUT, USBRQ_HID_SET_REPORT,
reportType << 8 | buffer[0], 0, (char *) buffer, len, 1000);
if (bytesSent != len) {
if (bytesSent < 0)
error("%s: Error sending message: %s", Name, usb_strerror());
return USB_ERROR_IO;
}
return 0;
}
/* ------------------------------------------------------------------------- */
int usbGetReport(usbDevice_t * device, int reportType, int reportNumber, unsigned char *buffer, int *len)
{
*len = usb_control_msg(device, USB_TYPE_CLASS | USB_RECIP_INTERFACE |
USB_ENDPOINT_IN, USBRQ_HID_GET_REPORT,
reportType << 8 | reportNumber, 0, (char *) buffer, *len, 1000);
if (*len < 0) {
error("%s: Error sending message: %s", Name, usb_strerror());
return USB_ERROR_IO;
}
return 0;
}
char *usbErrorMessage(int errCode)
{
static char buffer[80];
switch (errCode) {
case USB_ERROR_ACCESS:
return "Access to device denied";
case USB_ERROR_NOTFOUND:
return "The specified device was not found";
case USB_ERROR_BUSY:
return "The device is used by another application";
case USB_ERROR_IO:
return "Communication error with device";
default:
sprintf(buffer, "Unknown USB error %d", errCode);
return buffer;
}
return NULL; /* not reached */
}
static char *video_buffer = NULL;
static char *dirty_buffer = NULL;
static void drv_GLCD2USB_blit(const int row, const int col, const int height, const int width)
{
int r, c, err, i, j;
/* update offscreen buffer */
for (r = row; r < row + height; r++) {
for (c = col; c < col + width; c++) {
int x, y, bit;
/* these assignments are display layout dependent */
x = c;
y = r / 8;
bit = r % 8;
i = video_buffer[x + DCOLS * y];
if (drv_generic_graphic_black(r, c))
video_buffer[x + DCOLS * y] |= 1 << bit;
else
video_buffer[x + DCOLS * y] &= ~(1 << bit);
if (video_buffer[x + DCOLS * y] != i)
dirty_buffer[x + DCOLS * y] |= 1 << bit;
}
}
#if 0
/* display what's in the buffer (for debugging) */
for (r = 0; r < DROWS; r++) {
for (c = 0; c < DCOLS; c++) {
if (video_buffer[c + DCOLS * (r / 8)] & (1 << (r % 8)))
putchar('#');
else
putchar(' ');
}
putchar('\n');
}
#endif
/* short gaps of unchanged bytes in fact increase the communication */
/* overhead. so we eliminate them here */
for (j = -1, i = 0; i < DROWS * DCOLS / 8; i++) {
if (dirty_buffer[i] && j >= 0 && i - j <= 4) {
/* found a clean gap <= 4 bytes: mark it dirty */
for (r = j; r < i; r++)
dirty_buffer[r] = 1;
}
/* if this is dirty, drop the saved position */
if (dirty_buffer[i])
j = -1;
/* save position of this clean entry if there's no position saved yet */
if (!dirty_buffer[i] && j < 0)
j = i;
}
/* and do the actual data transmission */
buffer.bytes[0] = 0;
for (i = 0; i < DROWS * DCOLS / 8; i++) {
if (dirty_buffer[i]) {
/* starting a new run? */
if (!buffer.bytes[0]) {
buffer.bytes[0] = GLCD2USB_RID_WRITE;
buffer.bytes[1] = i % 256; // offset
buffer.bytes[2] = i / 256;
buffer.bytes[3] = 0; // length
}
buffer.bytes[4 + buffer.bytes[3]++] = video_buffer[i];
}
/* this part of the buffer is not dirty or we are at end */
/* of buffer or the buffer is fill: send data then */
if ((!dirty_buffer[i]) || (i == DROWS * DCOLS / 8 - 1) || (buffer.bytes[3] == 128)) {
/* is there data to be sent in the buffer? */
if (buffer.bytes[0] && buffer.bytes[3]) {
if ((err = usbSetReport(dev, USB_HID_REPORT_TYPE_FEATURE, buffer.bytes, buffer.bytes[3] + 4)) != 0)
error("%s: Error sending display contents: %s", Name, usbErrorMessage(err));
buffer.bytes[0] = 0;
}
}
/* this entry isn't dirty anymore */
dirty_buffer[i] = 0;
}
}
static int drv_GLCD2USB_start(const char *section)
{
char *s;
int err = 0, len;
if (sscanf(s = cfg_get(section, "font", "6x8"), "%dx%d", &XRES, &YRES) != 2 || XRES < 1 || YRES < 1) {
error("%s: bad %s.Font '%s' from %s", Name, section, s, cfg_source());
free(s);
return -1;
}
free(s);
if ((err = usbOpenDevice(&dev, IDENT_VENDOR_NUM, IDENT_VENDOR_STRING,
IDENT_PRODUCT_NUM, IDENT_PRODUCT_STRING)) != 0) {
error("%s: opening GLCD2USB device: %s", Name, usbErrorMessage(err));
return -1;
}
info("%s: Found device", Name);
/* query display parameters */
memset(&buffer, 0, sizeof(buffer));
len = sizeof(display_info_t);
if ((err = usbGetReport(dev, USB_HID_REPORT_TYPE_FEATURE, GLCD2USB_RID_GET_INFO, buffer.bytes, &len)) != 0) {
error("%s: query display parameters: %s", Name, usbErrorMessage(err));
usbCloseDevice(dev);
return -1;
}
if (len < (int) sizeof(buffer.display_info)) {
error("%s: Not enough bytes in display info report (%d instead of %d)",
Name, len, (int) sizeof(buffer.display_info));
usbCloseDevice(dev);
return -1;
}
info("%s: display name = %s", Name, buffer.display_info.name);
info("%s: display resolution = %d * %d", Name, buffer.display_info.width, buffer.display_info.height);
info("%s: display flags: %x", Name, buffer.display_info.flags);
/* TODO: check for supported features */
/* save display size */
DCOLS = buffer.display_info.width;
DROWS = buffer.display_info.height;
/* allocate a offscreen buffer */
video_buffer = malloc(DCOLS * DROWS / 8);
dirty_buffer = malloc(DCOLS * DROWS / 8);
memset(video_buffer, 0, DCOLS * DROWS / 8);
memset(dirty_buffer, 0, DCOLS * DROWS / 8);
/* get access to display */
buffer.bytes[0] = GLCD2USB_RID_SET_ALLOC;
buffer.bytes[1] = 1; /* 1=alloc, 0=free */
if ((err = usbSetReport(dev, USB_HID_REPORT_TYPE_FEATURE, buffer.bytes, 2)) != 0) {
error("%s: Error allocating display: %s", Name, usbErrorMessage(err));
usbCloseDevice(dev);
return -1;
}
return 0;
}
/****************************************/
/*** plugins ***/
/****************************************/
/* none at the moment... */
/****************************************/
/*** widget callbacks ***/
/****************************************/
/* using drv_generic_graphic_draw(W) */
/* using drv_generic_graphic_icon_draw(W) */
/* using drv_generic_graphic_bar_draw(W) */
/****************************************/
/*** exported functions ***/
/****************************************/
/* list models */
int drv_GLCD2USB_list(void)
{
printf("generic");
return 0;
}
static int drv_GLCD2USB_keypad(const int num)
{
int val = 0;
#if 0
/* check for key press event */
if (num & 0x80)
val = WIDGET_KEY_PRESSED;
else
val = WIDGET_KEY_RELEASED;
if ((num & 0x7f) == 0)
val += WIDGET_KEY_UP;
if ((num & 0x7f) == 1)
val += WIDGET_KEY_DOWN;
#endif
return val;
}
/* initialize driver & display */
int drv_GLCD2USB_init(const char *section, const __attribute__ ((unused))
int quiet)
{
int ret;
info("%s: %s", Name, "$Rev$");
/* real worker functions */
drv_generic_graphic_real_blit = drv_GLCD2USB_blit;
drv_generic_keypad_real_press = drv_GLCD2USB_keypad;
/* start display */
if ((ret = drv_GLCD2USB_start(section)) != 0)
return ret;
/* initialize generic graphic driver */
if ((ret = drv_generic_graphic_init(section, Name)) != 0)
return ret;
/* register plugins */
/* none at the moment... */
return 0;
}
/* close driver & display */
int drv_GLCD2USB_quit(const __attribute__ ((unused))
int quiet)
{
int err;
info("%s: shutting down.", Name);
drv_generic_graphic_quit();
/* release access to display */
buffer.bytes[0] = GLCD2USB_RID_SET_ALLOC;
buffer.bytes[1] = 0; /* 1=alloc, 0=free */
if ((err = usbSetReport(dev, USB_HID_REPORT_TYPE_FEATURE, buffer.bytes, 2)) != 0) {
error("%s Error freeing display: %s", Name, usbErrorMessage(err));
}
/* clean up */
if (dev != NULL)
usbCloseDevice(dev);
if (video_buffer != NULL) {
free(video_buffer);
free(dirty_buffer);
}
return (0);
}
DRIVER drv_GLCD2USB = {
.name = Name,
.list = drv_GLCD2USB_list,
.init = drv_GLCD2USB_init,
.quit = drv_GLCD2USB_quit,
};
+7 -3
View File
@@ -55,6 +55,9 @@ Display LCD2USB {
Icons 1
}
Display GLCD2USB {
Driver 'GLCD2USB'
}
Display LCD2041 {
Driver 'MatrixOrbital'
@@ -1237,15 +1240,16 @@ Layout Debug {
#Display 'LPH7508-serdisplib'
#Display 'LPH7508'
#Display 'ctinclud'
Display 'picoLCD'
#Display 'picoLCD'
Display 'GLCD2USB'
#Layout 'Default'
#Layout 'TestLayer'
Layout 'TestLayer'
#Layout 'TestImage'
#Layout 'L8x2'
#Layout 'L16x1'
#Layout 'L16x2'
Layout 'L20x2'
#Layout 'L20x2'
#Layout 'L40x2'
#Layout 'Test'
#Layout 'Test2'