With some free samples of PIC18F4550, the most popular micro-controller for hobbyist to build custom USB gadgets, several type of sensors, a PS2 keyboard and a Game Boy camera unit, I decided to build a custom USB device that would process input from these peripherals and display them on my computer. This article shows the final product that I came up with and shares some of my findings.
USB firmware for the PIC18F4550
Unlike UART which requires just a few lines of code to configure the baudrate, USB is a very complicated protocol and it would take some serious efforts just to get your computer recognize the PIC as a USB device.
The first step is to write the firmware for this PIC which would allow it to perform USB enumeration upon connected and necessary communication before it is usable by the host. The next step would be to write an application running on the host that communicates with the USB device and displays the relevant information. Fortunately, I do not have to develop the firmware code and the Windows software from scratch, as a working setup with code sample is provided in this website which includes:
- A firmware for the PIC18F4550 which reports itself as a generic USB Human Interface Device (HID)
- A .NET application written in C# that performs basic communication (e.g. toggling LEDs) with the PIC
The source code for the Windows application is developed in C# using Visual Studio and consists of 2 projects:
- usbGenericHidCommunications_3_0_0_0: the library for low-level USB communications
- WFF Generic HID Demo 3: the application communicating with the USB device using the above library
As a generic USB HID device, the PIC will wait for requests to be sent from the host and replies with a data packet in response to the command. Since each data packet is 64 bytes and the host cannot send more than 1 request per millisecond, the maximum throughput is 64KB/sec. Although that is well below even the maximum speed for USB 1.1 (12MBit/sec), it is more than enough for my purpose.
I got the sample code working upon first try:
Interfacing additional peripherals
To make it a more useful USB device, I connected the following peripherals to the PIC and modify the Windows software accordingly:
- DHT11: temperature and humidity sensor using proprietary 1-wire protocol
- DS1621: I2C digital thermometer by Maxim
- LM35: analog temperature sensor by Texas Instruments.
- DS1307: I2C real-time clock by Maxim
- HCSR04: ultrasonic distance sensor
- A3144: hall-effect sensor
- A PIR sensor for motion detector
Using this AVR source code to interface a PS2 keyboard and this for the Game Boy camera and adapted them for use with the PIC, together with a Nokia 5110 LCD for displaying of status, it took me 1 week to complete my final circuit:
The LCD screen at a closer look, with the third line showing the temperature detected by the 3 sensors and the fifth line showing the distance detected by the ultrasonic sensor and the humidity detected by DHT11:
Extra C# code is added to the Windows software to cater for the added devices, for example, to query the temperature of the DS1621:
The PIC firmware is also modified to look out for the command 0x83 sent by the host and reply with the status of the DS1621. This change is done inside the processUsbCommands function:
The final Windows application to show the added peripheral status looks like the following:
Interfacing the PS2 keyboard
This is a bit challenging due to the need to watch out for the PS2 clock to detect when a key is pressed and read the key’s scan code while at the same time processing any possible commands sent from the host. This is solved by using interrupt – connecting the CLK line of the keyboard to the INT0 pin and process the keyboard input in the highPriorityISRCode() function:
The key is then stored in a buffer, to be retrieved the next time the host queries the list of pressed keys. A text box is added to the host software to show which key has been pressed.
Interfacing the Game Boy camera
Codes are written to take a picture using the Game Boy camera and transfer it back to the host, via multiple data packets of 64 bytes each, to be displayed in a PictureBox in the .NET application. It takes around 5 seconds for a 16KB image to be taken and sent back to the host. So much for a USB connection!
The following is a picture of a calculator taken using this method. If you can see it, the calculator screen is showing 12345678.
Another picture showing my hand:
My clock and a plastic bottle:
Bearing in mind the limited capabilities of this camera, the following problems were noted:
- The last 5 lines of the image does not contain data. This is because the actual resolution of the camera in my case is 128×123 only, despite the camera indicating a resolution of 128×128 during the communication protocol.
- In some cases the image appears brighter than normal, possibly because of an increase due to the exposure time due to the waiting time between host commands.
- Most importantly, the camera sensor experiences interference causing “dead” pixels which will create periodic horizontal lines as part of the bitmap.
Issue (3) is also encountered if the image data taken using the same code is transferred via UART or written to an SD card, instead of via USB. My post on this forum yields a few replies suggesting that the analog-to-digital converter of the PIC experiences interferences when the camera is capturing photo affecting the ground reference causing the change in pixel intensity. Despite trying various different methods such as making the ground connection large, changing the reference voltage for the ADC, or even correcting the intensity in software, I could not solve the issue and have to leave with the interferences in the captured photos.
However, needless to say, I have learned a lot about the USB protocol and acquire more experience interfacing various peripherals to the PIC during the project, The modified source code for both the firmware and the Windows application can be downloaded here.