Python and libusb-1.0 via ctypes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I don't know if this is an issue with Linux, Python, libusb-1.0, or
what. I have my suspicions that it is an issue with Python,
particularly with threading.

First I need to outline an asynchronous transfer as it pertains to
libusb-1.0, as described here.

We can view asynchronous I/O as a 5 step process:

Allocation: allocate a libusb_transfer
Filling: populate the libusb_transfer instance with information about
the transfer you wish to perform
Submission: ask libusb to submit the transfer
Completion handling: examine transfer results in the libusb_transfer structure
Deallocation: clean up resources

I allocate my transfer as part of my class' initializer, named self.transfer.

Note that a fully synchronous transfer is basically the same set of
operations, as written here:

    def drv_send(self, data, size):
        if not self.Connected():
            return

        self.drv_locked = True
        buffer = ''.join(chr(c) for c in data[:size])
        out_buffer = cast(buffer, POINTER(c_ubyte))
        print "out", [buffer]
        libusb_fill_interrupt_transfer(self.transfer, self.handle,
LIBUSB_ENDPOINT_OUT + 1, out_buffer, size, self.cb_send_transfer,
None, 0) # Step 2
        lib.libusb_submit_transfer(self.transfer) # Step 3
        while self.drv_locked:
            r = lib.libusb_handle_events(None) # Step 4
            if r < 0:
                if r == LIBUSB_ERROR_INTERRUPTED:
                    continue
                lib.libusb_cancel_transfer(transfer)
                while self.drv_locked:
                    if lib.libusb_handle_events(None) < 0:
                        break

Here's a semi-asynchronous example, to make this email simpler.
Otherwise there would be a separate execution thread and method
dealing with completion handling. The issue I'm having is present in
both this semi-async version and a fully asynchronous version, so I
won't show the full version.

    def drv_send(self, data, size):
        if not self.Connected():
            return

        def f():
            self.drv_locked = True
            buffer = ''.join(chr(c) for c in data[:size])
            out_buffer = cast(buffer, POINTER(c_ubyte))
            print "out", [buffer]
            libusb_fill_interrupt_transfer(self.transfer, self.handle,
LIBUSB_ENDPOINT_OUT + 1, out_buffer, size, self.cb_send_transfer,
None, 0) # Step 2
            lib.libusb_submit_transfer(self.transfer) # Step 3
            while self.drv_locked:
                r = lib.libusb_handle_events(None) # Step 4
                if r < 0:
                    if r == LIBUSB_ERROR_INTERRUPTED:
                        continue
                    lib.libusb_cancel_transfer(transfer)
                    while self.drv_locked:
                        if lib.libusb_handle_events(None) < 0:
                            break

            self.count += 1

        self.command_queue.put(f)

Here's where the queue is depopulated. I've ran this both inside its
own thread (part of gobject.idle_add) and as part of a gobject timeout
event with the same result.

    def command_worker(self):
        if self.drv_locked: # or time.time() - self.command_time <
self.command_rate:
            return True
        try:
            cmd = self.command_queue.get_nowait()
        except Queue.Empty:
            return True
        cmd()
        self.command_time = time.time()
        return True

Ok, now to describe the issue.

All of this code is meant for my LCD controller, which is meant to
operate multiple, concurrent LCDs. Some LCDs are serial based, but in
the case of picoLCD256x64 it's USB based, non-ftdi (non-usbserial).
You deal directly with the USB controller. Everything works fine if I
keep it synchronous. The LCD is fully initialized, cleared, and then
filled with the expected pixels (This is a graphic LCD, as opposed to
a character display). However, as you've probably guessed, the
asynchronous version fails to control the LCD. All but one of the
internal chips are cleared, and only a portion of that chip's pixels
are filled. Since my LCD controller is meant to control multiple,
concurrent displays, I found that a synchronous implementation
wouldn't work. The program's also pygtk-based, so that in itself would
require an asynchronous implementation in order to not block the UI.

Now I've successfully written an asynchronous C program to control the
display. It however doesn't utilize GTK or Gobject. It's a very basic
example. I've attached that along with the Python implementation.

Anyone have a clue what might be causing this? Since there are threads
involved in the async and semi-async implementation, Python's GIL
could be responsible. In fact, before altering its behavior, the fully
async version was really slow. The following fixed that problem.

sys.setcheckinterval(0)

0 meaning run the thread at every 0'th virtual execution. Its default
value is 100.

Another possible culprit could be Python's ctypes, which I'm using to
access libusb-1.0. However, seeing that the synchronous version works
as it should, this possibility looks less likely.

Any help would be greatly appreciated. If I haven't made the issue
clear, I've also asked this question over at Stackoverflow here:
http://stackoverflow.com/questions/1060305/usb-sync-vs-async-vs-semi-async-partially-answered-now

- Scott
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <libusb-1.0/libusb.h>

#include "font_6x8.h"

#define VENDOR_ID 0x04d8
#define PRODUCT_ID 0xc002

#define OUT_REPORT_LED_STATE            0x81
#define OUT_REPORT_LCD_BACKLIGHT        0x91
#define OUT_REPORT_LCD_CONTRAST         0x92

#define OUT_REPORT_CMD                  0x94
#define OUT_REPORT_DATA                 0x95
#define OUT_REPORT_CMD_DATA             0x96

#define SCREEN_H                        64
#define SCREEN_W                        256

typedef struct Command {
    void (*cb) (unsigned char *data, int size);
    unsigned char data[64];
    int size;
} Command;

struct Command queue[1024];

int queueEnd = 0;
int queueStart = 0;

unsigned char locked = 0;

static unsigned char pLG_framebuffer[256*64];

/* used to display white text on black background or inverse */
unsigned char inverted = 0;

static struct libusb_device_handle *devh = NULL;
static struct libusb_transfer *lcd_transfer = NULL;

static int do_exit = 0;

static pthread_t poll_thread;
static pthread_t command_thread;
static pthread_cond_t exit_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t exit_cond_lock = PTHREAD_MUTEX_INITIALIZER;

static void queue_push(void (*cb), unsigned char *data, int size) {
    int i;
    if (queueEnd >= 1024)
        return;
    queue[queueEnd].cb = cb;
    queue[queueEnd].size = size;
    for(i = 0; i < size; i++) {
        queue[queueEnd].data[i] = data[i];
    }
    printf("Data0: %x, %x, %x\n", queue[queueEnd].data[0], queue[queueEnd].data[1], queue[queueEnd].data[2]);
    queueEnd++;
}

struct Command * queue_pop(void) {
    if( queueStart >= queueEnd )
        return NULL;
    return &queue[queueStart++];
}

static void request_exit(int code) {
    do_exit = code;
    pthread_cond_signal(&exit_cond);
}

static void *command_thread_main(void *arg) {
    struct Command *cmd;
    while (!do_exit) {
        if(locked) continue;
        locked = 1;
        cmd = queue_pop();
        if(cmd != NULL) {
            cmd->cb(cmd->data, cmd->size);
            printf("Data1: %x, %x, %x, %d\n", cmd->data[0], cmd->data[1], cmd->data[2], cmd->size);
        }
    }
    return NULL;
}

static void *poll_thread_main(void *arg) {
    int r = 0;
    while ( !do_exit) {
        struct timeval tv = { 1, 0};
        r = libusb_handle_events_timeout(NULL, &tv);
        if( r < 0 ) {
            request_exit(2);
            break;
        }
    }
    return NULL;
}

void cb_lcd(struct libusb_transfer *transfer) {
    if(transfer->status != LIBUSB_TRANSFER_COMPLETED) {
        fprintf(stderr, "transfer not completed!\n");
    }
    printf("cb_lcd: length=%d, actual_length=%d\n", transfer->length, transfer->actual_length);
    locked = 0;
}

void drv_pLG_real_send(unsigned char *data, int size) {
    libusb_fill_interrupt_transfer(lcd_transfer, devh, LIBUSB_ENDPOINT_OUT + 1, data, 
	size, cb_lcd, NULL, 0);
    libusb_submit_transfer(lcd_transfer);
}

void drv_pLG_send(unsigned char *data, int size) {
    queue_push(drv_pLG_real_send, data, size);
}

static void drv_pLG_update_img() {
    unsigned char cmd3[64] = { OUT_REPORT_CMD_DATA };   /* send command + data */
    unsigned char cmd4[64] = { OUT_REPORT_DATA };       /* send data only */

    int index, bit, x, y;
    unsigned char cs, line;
    unsigned char pixel;

    for (cs = 0; cs < 4; cs++) {
        unsigned char chipsel = (cs << 2);      //chipselect
        for (line = 0; line < 8; line++) {

            cmd3[0] = OUT_REPORT_CMD_DATA;
            cmd3[1] = chipsel;
            cmd3[2] = 0x02;
            cmd3[3] = 0x00;
            cmd3[4] = 0x00;
            cmd3[5] = 0xb8 | line;
            cmd3[6] = 0x00;
            cmd3[7] = 0x00;
            cmd3[8] = 0x40;
            cmd3[9] = 0x00;
            cmd3[10] = 0x00;
            cmd3[11] = 32;


            cmd4[0] = OUT_REPORT_DATA;
            cmd4[1] = chipsel | 0x01;
            cmd4[2] = 0x00;
            cmd4[3] = 0x00;
            cmd4[4] = 32;

            for (index = 0; index < 32; index++) {
                pixel = 0x00;

                for (bit = 0; bit < 8; bit++) {
                    x = cs * 64 + index;
                    y = (line * 8 + bit + 0) % SCREEN_H;

                    if (pLG_framebuffer[y * 256 + x] ^ inverted)
                        pixel |= (1 << bit);
                }
                cmd3[12 + index] = pixel;
            }

            for (index = 32; index < 64; index++) {
                pixel = 0x00;

                for (bit = 0; bit < 8; bit++) {
                    x = cs * 64 + index;
                    y = (line * 8 + bit + 0) % SCREEN_H;
                    if (pLG_framebuffer[y * 256 + x] ^ inverted)
                        pixel |= (1 << bit);
                }

                cmd4[5 + (index - 32)] = pixel;
            }

            drv_pLG_send(cmd3, 44);
            drv_pLG_send(cmd4, 38);
        }
    }

}

void drv_pLG_clear(void)
{
    unsigned char cmd[3] = { 0x93, 0x01, 0x00 };        /* init display */
    unsigned char cmd2[9] = { OUT_REPORT_CMD }; /* init display */
    unsigned char cmd3[64] = { OUT_REPORT_CMD_DATA };   /* clear screen */
    unsigned char cmd4[64] = { OUT_REPORT_CMD_DATA };   /* clear screen */

    int init, index;
    unsigned char cs, line;

    drv_pLG_send(cmd, 3);

    for (init = 0; init < 4; init++) {
        unsigned char cs = ((init << 2) & 0xFF);

        cmd2[0] = OUT_REPORT_CMD;
        cmd2[1] = cs;
        cmd2[2] = 0x02;
        cmd2[3] = 0x00;
        cmd2[4] = 0x64;
        cmd2[5] = 0x3F;
        cmd2[6] = 0x00;
        cmd2[7] = 0x64;
        cmd2[8] = 0xC0;

        drv_pLG_send(cmd2, 9);
    }


    for (cs = 0; cs < 4; cs++) {
        unsigned char chipsel = (cs << 2);      //chipselect
        for (line = 0; line < 8; line++) {
            cmd3[0] = OUT_REPORT_CMD_DATA;
            cmd3[1] = chipsel;
            cmd3[2] = 0x02;
            cmd3[3] = 0x00;
            cmd3[4] = 0x00;
            cmd3[5] = 0xb8 | line;
            cmd3[6] = 0x00;
            cmd3[7] = 0x00;
            cmd3[8] = 0x40;
            cmd3[9] = 0x00;
            cmd3[10] = 0x00;
            cmd3[11] = 32;

            unsigned char temp = 0;

            for (index = 0; index < 32; index++) {
                cmd3[12 + index] = temp;
            }

            drv_pLG_send(cmd3, 64);

            cmd4[0] = OUT_REPORT_DATA;
            cmd4[1] = chipsel | 0x01;
            cmd4[2] = 0x00;
            cmd4[3] = 0x00;
            cmd4[4] = 32;

            for (index = 32; index < 64; index++) {
                temp = 0x00;
                cmd4[5 + (index - 32)] = temp;
            }
            drv_pLG_send(cmd4, 64);
        }
    }
}


static int find_device(void)
{
    devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);
    return devh ? 0 : -EIO;
}


static void sighandler(int signum)
{
    request_exit(1);
}

static void fill_pixels(void)
{
    int i, r, c, row, col;
    char *text = "picoLCDGraphics ";
    char ch;
    memset(pLG_framebuffer, 0, sizeof(pLG_framebuffer));
    for( i = 0; i < 256/6*8; i++) {
        r = i / 42 * 8;
        c = i % 42 * 6;
        ch = text[i % strlen(text)];
        for( row = 0; row < 8; row++ ) {
            for( col = 0; col < 6; col++ ) {
                if (Font_6x8[(int)ch][row] & (1 << (5-col)) )
			pLG_framebuffer[(r + row) * 256 + c + col] = 1;
            }
        }
    }
}

int main(void)
{
    struct sigaction sigact;
    int r = 1;

    r = libusb_init(NULL);
    if( r < 0) {
        fprintf(stderr, "failed to initialize libusb\n");
        exit(1);
    }

    r = find_device();
    if(r < 0) {
        fprintf(stderr, "Could not find device\n");
        goto out;
    }

    r = libusb_claim_interface(devh, 0);
    if (r < 0) {
        fprintf(stderr, "usb_claim_interface error %d\n", r);
        goto out;
    }

    printf("claimed interface\n");

    lcd_transfer = libusb_alloc_transfer(0);

    if(!lcd_transfer) {
        fprintf(stderr, "Unable to allocate transfer\n");
        goto out;
    }

    drv_pLG_clear();
    fill_pixels();
    drv_pLG_update_img();

    sigact.sa_handler = sighandler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGINT, &sigact, NULL);
    sigaction(SIGTERM, &sigact, NULL);
    sigaction(SIGQUIT, &sigact, NULL);


    r = pthread_create(&poll_thread, NULL, poll_thread_main, NULL);

    if (r)
        goto out_deinit;

    r = pthread_create(&command_thread, NULL, command_thread_main, NULL);

    if (r)
        goto out_deinit;

    while (!do_exit) {
        pthread_mutex_lock(&exit_cond_lock);
        pthread_cond_wait(&exit_cond, &exit_cond_lock);
        pthread_mutex_unlock(&exit_cond_lock);
    }

    pthread_join(poll_thread, NULL);

    pthread_join(command_thread, NULL);

    if (lcd_transfer) {
        r = libusb_cancel_transfer(lcd_transfer);
        if (r < 0)
            goto out_deinit;
    }

    while (lcd_transfer)
        if (libusb_handle_events(NULL) < 0)
            break;
    
    
out_deinit:
    libusb_free_transfer(lcd_transfer);
out_release:
    libusb_release_interface(devh, 0);
out:
    libusb_close(devh);
    libusb_exit(NULL);
    return r >= 0 ? r : -r;    
}

Attachment: USB4.py
Description: Binary data


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux