Hyper Pixel - Rotate

Hi,
Is it possible to configure the hyper pixel to operate in portrait orientation, please? I had a brief play with the config.txt values (rotate and the setup line at the bottom) but it didn’t go at all according to plan - I just ended up with a tiny desktop across the middle 1/3 of the display with lots of artefacting in the remaining display.
Thanks
Marc

You know what, I think I just cracked it.

Try:

display_rotate=1
framebuffer_width=480
framebuffer_height=800

The touchscreen will be totally wonky, though, and wont respond to any config.txt settings :(

1 Like

Thanks @gadgetoid, I just saw those lines at the same time! This always happens to me - post a question and then find the answer in a few minutes anyway!

Now to get an fbi session working to output images to the screen

1 Like

If you want touch to work properly in this orientation, this is the modified /usr/bin/hyperpixel-touch that works for me:

#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
import sys
import smbus
import signal

from evdev import uinput, UInput, AbsInfo, ecodes as e
import time

cap = {
        #e.EV_REL : [e.REL_WHEEL],
        e.EV_ABS : (
                (e.ABS_X, AbsInfo(value=0, min=0, max=480, fuzz=0, flat=0, resolution=1)),
                (e.ABS_Y, AbsInfo(value=0, min=0, max=800, fuzz=0, flat=0, resolution=1)),
                (e.ABS_MT_SLOT, AbsInfo(value=0, min=0, max=1, fuzz=0, flat=0, resolution=0)),
                (e.ABS_MT_TRACKING_ID, AbsInfo(value=0, min=0, max=65535, fuzz=0, flat=0, resolution=0)),
                (e.ABS_MT_TOUCH_MAJOR, AbsInfo(value=0, min=0, max=30, fuzz=0, flat=0, resolution=0)),
                (e.ABS_MT_PRESSURE, AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)),
                (e.ABS_MT_POSITION_X, AbsInfo(value=0, min=0, max=480, fuzz=0, flat=0, resolution=0)),
                (e.ABS_MT_POSITION_Y, AbsInfo(value=0, min=0, max=800, fuzz=0, flat=0, resolution=0)),
        ),
        e.EV_KEY : [
                #e.BTN_LEFT, e.BTN_RIGHT,
                e.BTN_TOUCH,
        ]
}

ui = UInput(cap, name="Touchscreen")

last_status_one = last_status_two = False
last_status_x1 = last_status_y1 = last_status_x2 = last_status_y2 = 0

def write_status(x1, y1, touch_one, x2, y2, touch_two):
    global last_status_one, last_status_two, last_status_x1, last_status_y1, last_status_x2, last_status_y2


    if touch_one:
        ui.write(e.EV_ABS, e.ABS_MT_SLOT, 0)

        if not last_status_one: # Contact one press
            #print("Report: Touch 1 Start")
            ui.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, 0)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_X, x1)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_Y, y1)
            ui.write(e.EV_KEY, e.BTN_TOUCH, 1)
            ui.write(e.EV_ABS, e.ABS_X, x1)
            ui.write(e.EV_ABS, e.ABS_Y, y1)

        elif not last_status_one or (x1, y1) != (last_status_x1, last_status_y1):
            if x1 != last_status_x1: ui.write(e.EV_ABS, e.ABS_X, x1)
            if y1 != last_status_y1: ui.write(e.EV_ABS, e.ABS_Y, y1)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_X, x1)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_Y, y1)

        last_status_x1 = x1
        last_status_y1 = y1

        last_status_one = True

        ui.write(e.EV_SYN, e.SYN_REPORT, 0)
        ui.syn()

    elif not touch_one and last_status_one: # Contact one release
        #print("Report: Touch 1 End")
        ui.write(e.EV_ABS, e.ABS_MT_SLOT, 0)
        ui.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1)
        ui.write(e.EV_KEY, e.BTN_TOUCH, 0)
        last_status_one = False

        ui.write(e.EV_SYN, e.SYN_REPORT, 0)
        ui.syn()


    if touch_two:
        ui.write(e.EV_ABS, e.ABS_MT_SLOT, 1)

        if not last_status_two: # Contact one press
            #print("Report: Touch 2 Start")
            ui.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, 1)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_X, x2)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_Y, y2)
            ui.write(e.EV_KEY, e.BTN_TOUCH, 1)
            ui.write(e.EV_ABS, e.ABS_X, x2)
            ui.write(e.EV_ABS, e.ABS_Y, y2)

        elif not last_status_two or (x2, y2) != (last_status_x2, last_status_y2):
            if x2 != last_status_x2: ui.write(e.EV_ABS, e.ABS_X, x2)
            if y2 != last_status_y2: ui.write(e.EV_ABS, e.ABS_Y, y2)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_X, x2)
            ui.write(e.EV_ABS, e.ABS_MT_POSITION_Y, y2)

        last_status_x2 = x2
        last_status_y2 = y2

        last_status_two = True

        ui.write(e.EV_SYN, e.SYN_REPORT, 0)
        ui.syn()

    elif not touch_two and last_status_two: # Contact one release
        #print("Report: Touch 2 End")
        ui.write(e.EV_ABS, e.ABS_MT_SLOT, 1)
        ui.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1)
        ui.write(e.EV_KEY, e.BTN_TOUCH, 0)
        last_status_two = False

        ui.write(e.EV_SYN, e.SYN_REPORT, 0)
        ui.syn()

INT = 27 # Shared with LCD_SPI_CLK, interrupt is unreadable when the panel configuration is being sent.

ADDR = 0x5c

DELAY = 0.00001

GPIO.setmode(GPIO.BCM)
bus = smbus.SMBus(3)

def setupPins():
    GPIO.setup(INT, GPIO.IN)

ioerr_count = 0

def read_byte(address, delay=0):
    return bus.read_byte_data(0x5c,address)

def is_touch_on(x,y):
    # Pick the nearest touch lines to the coordinates
    x_line = int(x/57.2)
    y_line = int(y/60.01)

    assert ( 0 <= x_line <= 13)
    assert ( 0 <= y_line <= 7)

    # Read ADC values for those lines
    x_adc = bus.read_word_data(ADDR, x_line*2+16)
    y_adc = bus.read_word_data(ADDR, y_line*2)

    if x_adc > 100 or y_adc > 100:
        return True
    else:
        return False


touch_one_start = None
touch_two_start = None
touch_one_end = 0
touch_two_end = 0
last_x1 = last_y1 = -1
last_x2 = last_y2 = -1

def touch_finished(x1,y1,x2,y2):
    global touch_one_start, touch_two_start, touch_one_end, touch_two_end

    touch_one_dur = 0
    touch_two_dur = 0

    if touch_one_start and (x1,y1) == (0,0):
        touch_one_dur = time.time()-touch_one_start

        touch_one_end = time.time()
        touch_one_start = None

    if touch_two_start and (x2,y2) == (0,0):
        touch_two_dur = time.time()-touch_two_start

        touch_two_end = time.time()
        touch_two_start = None

def smbus_read_touch():
    global ioerr_count, touch_one_start, touch_two_start
    global last_x1, last_y1, last_x2, last_y2

    try:
        starttime = time.time()

        data = bus.read_i2c_block_data(ADDR, 0x40, 8)

        x1 = data[0] | (data[4] << 8)
        y1 = data[1] | (data[5] << 8)
        x2 = data[2] | (data[6] << 8)
        y2 = data[3] | (data[7] << 8)

        endtime = time.time()

        if x2 and y2:

            if ( touch_two_start and (x2, y2) != (last_x2, last_y2) ) or is_touch_on(x2, y2):
                if not touch_two_start:
                    touch_two_start = time.time()

            else:
                if touch_two_start:
                    touch_finished(x1,y1,x2,y2)

            last_x2 = x2
            last_y2 = y2


        if x1 and y1:

            if ( touch_one_start and (x1, y1) != (last_x1, last_y1) ) or is_touch_on(x1, y1):
                if not touch_one_start:
                    touch_one_start = time.time()
            else:
                if touch_one_start:
                    touch_finished(x1,y1,x2,y2)

            last_x1 = x1
            last_y1 = y1


        if (x2==0 and y2==0 and touch_two_start) or (x1==0 and y1==0 and touch_one_start):
            touch_finished(x1,y1,x2,y2)

        write_status(y1, 800-x1, touch_one_start is not None, y2, 800-x2, touch_two_start is not None)


    except e:
        print("Probably IOerror {}".format(e))
        ioerr_count += 1

def smbus_config_touch():
    global bus
    bus.write_byte_data(0x5c,0x6e,0b00001110)

if __name__ == "__main__":
    setupPins()

    smbus_config_touch()

    while True:
        if GPIO.input(INT) or touch_one_start or touch_two_start:
            smbus_read_touch()

        time.sleep(0.003)
1 Like

Great stuff, thanks. I’ll give it a go tonight but for the time being I am just making a static display for my home automation to give me some sensor data, outputting pillow images via FBI

OK so it may be a hardware fault, but I’m going to try putting the defaults back. Seems like when the display gets warm the brightness and stability of the image goes to pot. I’ll report back when 1) it’s cooled down a bit, 2) I’ve reset defaults, and 3) tried with my other screen.

Maybe you can help me with this one too…
I have my Hyperpixel display mounted in landscape, but in the opposite rotation to the default. Could you point out to me where you changed the file to correctly align the touch values (unless it’s the obvious “cap = {…” addition, in which case I haven’t the foggiest.)?
Dankeschon!

If you’re rotating 180degrees then you need to mirror the touch input. You can leave basically everything as it is, just change:

write_status(800-x1, 480-y1, touch_one_start is not None, 800-x2, 480-y2, touch_two_start is not None)

The X and Y coordinates are inverted here, which is effectively a 180 degree rotation. So if you un-invert (am I making up words now?) them all it should work!

1 Like

I’ve ran out of languages I know how to say thank you in, so here’s something I made up instead:
Hnkotanyu.

Apologies for once again requesting your assistance, but it seems flipping them simply ‘mirrors’ the touch screen input to below the screen. I.E: Imaging the screen and then flip a copy of it over the bottom vertice to become the touch input. It’s the right way around (I presume), but just translated a screen width downwards on a 2D plane.

You can just pass the raw x1 and y1 values in.

If you change 800 - x1 to x1 - 800 you will, indeed, end up with a range from -800 to 0. I should have been clearer, but I wanted you to experiment ;P

2 Likes

Haha, I appreciate the experimentation, but I didn’t switch them like that. ;)
I switched both 800 and 450 around within both values; though it’s a little difficult to keep testing since my Hyperpixel is in my garden shed and I’m controlling it via VNC. The rain doesn’t agree with my hair though…
Gimme a while for the rain to die down and I’ll keep experimenting. :3

I don’t fancy your chances… you might have to just dig a tunnel to the garden shed! It’s bucketing it down out here.

1 Like

OK; I got rather wet in the process, but I’ve managed to document my results!

800 - 480 : 800 - 480 - Both X and Y are inverted,
480 - 800 : 800 - 480 - Box X and Y are inverted and the touch screen box is off the bottom left of the screen,
480 - 800 : 480 - 800 - Same as the prior test,
800 - 480 : 480 - 800 - Same as the first test.

o_0

Either the laws of physics are bending in my shed, or the rain is getting to me…
I’m going to go with the rain.

You missed the permutations without subtraction!

Note: Help I’m stuck in a warehouse because I didn’t… wait a minute, I think I packed my rain coat!

I would help, but I don’t have much warehouse expertise… I just brave it without a coat, but then again, I am only going 5 or so metres. Under trees.
And I didn’t even recognise that those negatives did anything! I’m so used to typing in plain English with this forum that I though it was just a comment!
P.S: You know, you’re pretty evil making me go out in this weather to test different variations! The things I do for scientific method…

1 Like

They don’t call me Evil Gee for no reason ;)

I’m planning to- at least try - to clean up the hyperpixel-touch binary and add support for rotation 90, 180 and 270 degree rotation.

1 Like

You know what my next Google review of Pimoroni will be? "Pimoroni Support Team: Evil swashbuckling geniuses that take pleasure in forcing people out in the cold rain to find solutions."
Well, I’m still trying! I’ll get it eventually!
I’ll let you know when I figure it out eventually; it may be a while. :)

See this is how we’ll find out that what I think is the answer doesn’t work at all because I’m a fool and haven’t actually checked!

Please tell me not… Well! I need to buy an umbrella.