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