Simple USB keyboard emulation with PIC18F2550 in Android based CarPC
Hello dear participants of Habrahabr.
Despite the fact that Habr is a portal focused on programmers, I noticed that lately there have been many articles on programming microcontrollers and creating devices based on them. I decided to share one of my development. In the past, I wrote a lot for MK, I even worked as a software and circuitry developer in one of the companies, and before that I worked on an AFM program for Z80 and i8080. Now, in my adult life, I mainly write in PHP / MySQL for my own Internet projects and MK has not returned to programming for a very long time. I cannot call myself a full-fledged programmer, because I couldn’t master, for example, OOP, but I write a little in C as needed.
Some time ago, I had the task of creating a USB keyboard emulator for a CarPC project. It should have been used in a Becker BE2580 radio, installed on German-made cars of the 2000s. The emulator was supposed to interrogate the regular buttons of the radio and generate clicks on a virtual USB keyboard connected to the CarPC motherboard based on Android. What came of it, under the cut.
Briefly about CarPC itself: initially I came up with the seditious idea of sticking a new-fangled Android radio with a removable front panel-tablet into the car, which Chinese manufacturers have recently launched. However, comrades in the auto community dissuaded me from this idea, urging me not to violate the classic look of the car interior and the design conceived by the manufacturer. As a result, a motherboard from the Iconbit Toucan Nano media player was built into the radio, I installed it in place of the tape drive. At the same time, he noted how quickly progress is developing: he decided to check the cassette for the sake of interest, but not a single cassette was found at home.
Toucan Nano is an ideal candidate for use in CarPC, at a price of about $ 100 (in Russia), it has a TV output (composite), a component (subtractive with a dedicated clock signal), however, unfortunately, there is no RGB that was needed for output images on the display in the radio itself. There is HDMI, USB host (2 ports). Support for the PL2303 chip (USB2COM converter, i.e. RS232) turned out to be compiled into the kernel as well - which made it possible to connect an external GPS receiver, as well as support for WiFi whistles on the Ralink chip (I used Dlink DWA-140). The board was installed in the radio, the signal from the TV output was fed to the RGB inputs of the head unit’s monitor using a self-assembled TV -> RGB converter based on TDA8362. In the radio, a video signal switch is provided, thus, all the native functionality of the radio was saved.

Red color shows what is placed on the second floor (under the board). The Toucan Nano comes with a remote control paired with a radio whistle. An interesting Fly Mouse technology is implemented in the remote control: the mouse responds to the accelerometer built into the remote control, so just tilting the remote control moves the mouse around the device’s screen, it’s something like a pointer. However, it seemed uncomfortable and silly to use this in a car: it was a radio, after all, not a computer! Therefore, it was decided to build a radio keyboard keyboard emulator that would connect to a USB port and behave like a standard PC keyboard, while interrogating the radio buttons. In fact, this solution is universal and can be used with any OS: it is no secret that many CarPC systems are built on Intel platforms.
It so happened that at that time I had no experience working with PIC microcontrollers, in my youth I was a program for Atmel, but this experience was no longer relevant today, because 10 years have passed and no one remembers my beloved AT89c2051 now, probably. Programs were written strictly on the AFM at that time, because on C it would just be more difficult, 2051 has only 2K ROM. In those years, PIC also existed, but we refused to use it, because development tools were too expensive, and for 2051 I then developed a ROM emulator myself based on i51 with an external ROM that had the same instruction set as Atmel 89c2051. Now, realizing that I would have to go this way almost anew, I gathered my courage and sat down for literature. I think that experience will therefore be useful to readers because I paint everything in steps, in development.
First, I began to study the Internet on the topic of microcontrollers with USB support and immediately came across a device from the glorious company PIC, it was 18F2550. The declared functions were very good: support for USB 2.0, 1K RAM, a fully configurable USB port, in fact, 2550 can be anyUSB device. Plus all standard PIC features. This chip can be purchased in the DIP28 package, which is convenient for mounting and desoldering, and the price is very low. In ancient times, microcontrollers were sewn with a programmer, but now bootloaders have become popular. The meaning of the bootloader is that it is enough to write it to the microcontroller only once, after which you can access it by any other means (in our case via the USB port), and update the firmware of the device without removing it from the circuit. Ready whales with pre-sewn microcontrollers and ready-made software for uploading firmware via a bootloader are sold, but I did not want to look for such a whale (or rather, wait until it was sent). So I just went to the nearest radio store and bought a clean PIC18F2550.
Next, it was necessary to assemble a programmer for filling the bootloader. Having searched the internet, I found this:
products.foxdelta.com/progparallel.htm
The scheme is attached there. Simplified it, of course, removing unnecessary details, because soldering was too lazy. In fact, the pins of the microcontroller are connected directly to the LPT. Software used WinPic800. About the bootloader itself, a separate story. It is loaded from the address 0x000, respectively, the program itself should start from the address 0x800, which is indicated in the compiler settings. I found several different bootloaders, they pour firmware in different modes (for example, HID), respectively, are intended for different IDEs. The USB PIC driver for the flashing computer can also be used differently, because PIC "turns" into different USB devices. I constantly came across bootloaders for 18F4550, and its config is different and the bootloader from it on my MK did not work. As a result, I settled on the bootloader and bootloader firmware based on the PIC-ovsky demo board MCHPUSB. The bootloader itself had to be slightly modified for my circuitry (because I placed the bootloader button on another pin of the microcontroller). In general, there turned out to be a lot of all sorts of different parameters (for example, the type of a quartz resonator, a frequency multiplier, and a lot of such nonsense). If these parameters are incorrectly indicated, the device is sewn and even blinks an LED according to the program, but then refuses to work as a USB device, giving my “favorite” error “USB device is not recognized”, because frequencies are wrong. I spent a couple of days with this problem until I finally found a workable configuration. As a result, my bootloader is designed for 4Mhz quartz. The LED is soldered to RA5, the button for activating the firmware mode for the bootloader is to RA3 (and from it 1kOhm plus), 26 feet to the ground (for working in LVP mode). In order for the MK to enter bootloader mode while holding the reset, press the indicated button on RA3. For some reason, I failed to reflash the LVP bit (disabling the low-voltage firmware mode), okay. In theory, it is needed to protect the bootloader from being overwritten. The multiplier is tuned to a maximum frequency of 48 Mhz. Unfortunately, if the microcontroller is used in USB mode, the frequency cannot be raised higher, because USB frequencies are fixed, and the multiplier has a multiplicity, but this turned out to be not necessary.
As a result, the bootloader corrected, installed the drivers on the computer from Microchip. As a basis of the program, I immediately took a free example from Bradley A. Minch, which outputs the characters 'f', 'o', 'o', 'b', 'a', 'r' to the virtual keyboard sequentially. The source code of this program can be found here:
code.google.com/p/picusb/source/browse/lab2_pickit2/lab2.asm?spec=svn69&r=69 Having
compiled the program, after setting up a bunch of PIC configuration registers, I made sure that it began to print on a computer in a loop the string foobar! Hurrah! In reality, all this did not happen in one day, but I omit the details. And here I was apprehended for a second by the fear that all this miracle might not work for Iconbit, which might simply ignore almost a week of my work without giving reasons - and I will never know why. But, having connected the device to the Android motherboard, I saw that it worked: immediately a search window opened in which the familiar foobar letters ran. Android reacts to pressing arbitrary keys by opening a search box.
Further, it was required to modify the program to my needs, in particular, write a code that will read data from the radio itself. How it would work, I had no idea at that time. I thought I would have to connect to the matrix of the radio and read from it. Typically, such devices use dynamic scanning of the matrix, when a unit “runs” along the X axis, and information is read along the Y axis. That is, say, 16 keys can be processed with 8 pins (4 + 4). To simultaneously read such a matrix, you will also need 8 pins, but configured for input. However, digging around on the radio board, I found that caring Germans had allocated a separate processor to process the entire keyboard, only the encoder handles went directly to the central percent. As a result, I found a way out of the keyboard of the radio, on which pulses appear when you press any keys on the radio. The pulse repetition rate turned out to be quite high and for a start I just stupidly connected them to a converter based on PL2303, i.e. I just applied to the RS-232 port and started experimenting with the port settings. As a result, it turned out that meaningful bytes can be seen by setting the port to as much as 480 kbps. For me, it’s still a mystery why the Germans needed to transmit keyboard data at such a high speed.
In order to write a program for PIC, I had to analyze in detail the nature of the pulses of the keyboard processor of the radio. For this, I bought an oscilloscope. In my youth, of course, I had all these devices, but all that remained with my parents, and I had already moved to Moscow for 5 years. Therefore, I decided to slowly build a fleet of devices: after all, I return to amateur radio from time to time.
As a result, it turned out that the processor before each packet of information (which consists of 2 bytes) produces a long pulse, which serves as a “pilot” warning of the beginning of the transmission of the information block. It was surprising that the length of this block (as well as the intervals between bytes) change arbitrarily even when you press the same button. Another problem was that the PIC could not programmatically reliably read data at that speed. After all, he must also have time to service the USB bus! If a key is pressed at the wrong time, it simply skips it. As I wrote above, I raised the core frequency to the maximum with the help of multipliers, however, even with this setting, the processor worked at the limit and sometimes missed bits, as a result, the probability of correctly determining the buttons was about 70%, which is unacceptable in the car. Here I had already gathered my mind wisely and thought that, probably, there is a second signal (strobe). And so it turned out. But initially I did not find it because the pulses for some reason are constantly present, and it would be logical if they appeared when the buttons were pressed. Apparently, the radio processor polls the keyboard in a continuous cycle and gives an impulse when transmitting any bit, even if the state of the keys does not change. That is, it simply sends zeros if there is no activity. In general, the processor gates every transmitted bit; as a result, they can be read absolutely clearly. I rewrote the program on PIC, after which everything began to work stably and beautifully. When a button is pressed, one code is generated, when released, another. Thus, it was possible to implement the button holding mode, but I did not do this, because
Here I will give excerpts from my program, those parts that I wrote. You can see the original program at the link above. In the program, I have, of course, a table comparing the read key codes and USB codes that must be transferred to the head device. I’ll immediately notice that I usually write comments in English (so as not to waste time switching the case). If someone is interested in the full version of the program, I can send it without any problems. The full text is not given here for reasons of copyright. I can also send a bootloader and a program for its firmware, a program for uploading firmware.
Recoding buttons.
Configs:
Some of the configs are redefined by the programmer itself when filling the bootloader.
Transferring the key code to the USB interface:
Subroutines for reading radio buttons. To maximize performance, the
cycle is not used. Every beat counts.
CAR - the pin to which the signal from the radio keyboard processor is connected. SYN - strobe signal from the radio keyboard processor.
In the process of debugging, I encountered a number of problems. In some cases, the microcontroller hung and the USB device simply disappeared. The USB exchange protocol is very difficult and to delve into all its nuances was not easy. Through trial and error, I found the problem areas and added a software watchdog there, which simply restarted the processor in case of a hang in these places. As a result, I achieved a stable, trouble-free operation of the program without freezes with a fidelity of determining key pressures of 99%. The USB port in Toucan itself does not work very well, especially since I used a USB splitter. Sometimes even “serious” devices like USB mouse whistle disappear until the next reboot. What can I say about my homemade. However, I managed to achieve stability at the level of factory USB devices.
Next, you had to connect the encoder. I decided to send the data from the encoder to the emulation of the up and down buttons, and pressing the encoder knob on the Enter key. Well, pressing processes the processor, i.e. it is already working. As for the encoder, it is not difficult to read it. If you connect its pins to two bits, it gives the sequence 0, 1, 3, 2 (in binary code 00 01 11 10). We change the two with the triple, it turns out 0 1 2 3. Next, subtract the current result from the previous one, if the difference is 1 or -3, then rotation in one direction, if -1 or 3, in the other. I saw that reading the encoder is implemented by some kind of table, in general, nothing needs to be done. Here is the procedure:
Now PowerAMP is clearly controlled: the encoder knob and enter! With a pen we run through the folders with the music, we enter them into the button and start the composition. Dream!
I add that the codes generated by the virtual keyboard are not ASCII codes or even Android key codes (which also differ from ASCII codes). These are separate USB keyboard codes. You can find them in this table:
www.win.tue.nl/~aeb/linux/kbd/scancodes-14.html
Accordingly, in Android there is a special config that is responsible for processing specials. keys. Of course, I had to start navigation, and use the Home button and launch the player. For this purpose, you need to edit the keylayout file, a description of these files is here:
source.android.com/tech/input/key-layout-files.html
Here is my config:
key 1 BACK # ESC (“AC” button)
key 59 MENU # F1 (“BC” button)
#key 60 CAMERA # F2 (“CALL” button)
key 61 NOTIFICATION # F3 (“repeat” key on deck)
key 62 HOME # F4 (“main” button)
key 63 MEDIA_PREVIOUS # F5 (“4” button)
key 64 HEADSETHOOK # PLAY / PAUSE F6 (“audio” button)
#key 65 INFO # F7 (“navi” button)
key 66 VOLUME_UP # F8 ??
key 67 VOLUME_DOWN # F9 ??
key 200 HEADSETHOOK # hardware key # MEDIA_STOP not working
key 68 MEDIA_NEXT # F10 (“6” button)
key 87 POWER # F11 ('map' key on deck)
#key 88 EMAIL # F12 ("tel" button)
Of course, edit this File can only be on rooted devices.
It so happened that some keys do not work after installing the latest update on the Toucan Nano. Why - I still could not find out. For example, the most important HOME button has stopped working. Therefore, I wrote a simple batch file that starts with my system startup. Here it is:
#! / System / bin / sh
stty -F / dev / ttyUSB0 ispeed 4800
chmod 0777 / dev / ttyUSB0
sleep 20
am broadcast -a info.mapcam.droid.SERVICE_START # mapcam.info
while true
do
s = $ ( getevent -v0 -c1) # read one event from all input devices
# -v0 so that it does not spill a bunch of unnecessary garbage
s = $ (echo $ s | awk '{print $ 4}') # select the key code
case $ s in # execute desired team
0007003f) am start -a android.intent.action.MAIN -c android.intent.category.HOME -n com.maxmpz.audioplayer / .StartupActivity # AUDIO
# am start -a android.intent.action.MAIN -c android. intent.category.HOME -n com.maxmpz.audioplayer / .PlayListActivity # AUDIO
sleep 1
;;
00070040) am start -n ru.yandex.yandexmaps / .MapActivity # NAVI
sleep 1
;;
0007003d) am start -a android.intent.action.MAIN -c android.intent.category.HOME
sleep 1
;;
00070045) am start -a android.intent.action.MAIN -n com.speedsoftware.rootexplorer / .RootExplorer # TEL
sleep 1
;;
0007003b) am startservice -a "org.broeuschmeul.android.gps.usb.provider.nmea.intent.action.START_GPS_PROVIDER"
sleep 5
am broadcast -a info.mapcam.droid.SERVICE_START
# am start -n info.mapcam.droid / .SpeedometrActivity # CALL
sleep 1
;;
esac
done
This emulator has been successfully used on the machine for almost a year now. The plans are to make mouse emulation so that it can be moved around the screen with buttons. Unfortunately, I have not yet mastered integrating the touchscreen into the system. Unfortunately, a number of programs (for example, Rambler Maps) are not controlled by buttons and require a touchscreen. Therefore, all the same, for such cases in the car you have to carry a USB mouse.
About the Android project itself, I can somehow tell separately if it will be interesting.

Despite the fact that Habr is a portal focused on programmers, I noticed that lately there have been many articles on programming microcontrollers and creating devices based on them. I decided to share one of my development. In the past, I wrote a lot for MK, I even worked as a software and circuitry developer in one of the companies, and before that I worked on an AFM program for Z80 and i8080. Now, in my adult life, I mainly write in PHP / MySQL for my own Internet projects and MK has not returned to programming for a very long time. I cannot call myself a full-fledged programmer, because I couldn’t master, for example, OOP, but I write a little in C as needed.
Some time ago, I had the task of creating a USB keyboard emulator for a CarPC project. It should have been used in a Becker BE2580 radio, installed on German-made cars of the 2000s. The emulator was supposed to interrogate the regular buttons of the radio and generate clicks on a virtual USB keyboard connected to the CarPC motherboard based on Android. What came of it, under the cut.
Briefly about CarPC itself: initially I came up with the seditious idea of sticking a new-fangled Android radio with a removable front panel-tablet into the car, which Chinese manufacturers have recently launched. However, comrades in the auto community dissuaded me from this idea, urging me not to violate the classic look of the car interior and the design conceived by the manufacturer. As a result, a motherboard from the Iconbit Toucan Nano media player was built into the radio, I installed it in place of the tape drive. At the same time, he noted how quickly progress is developing: he decided to check the cassette for the sake of interest, but not a single cassette was found at home.
Toucan Nano is an ideal candidate for use in CarPC, at a price of about $ 100 (in Russia), it has a TV output (composite), a component (subtractive with a dedicated clock signal), however, unfortunately, there is no RGB that was needed for output images on the display in the radio itself. There is HDMI, USB host (2 ports). Support for the PL2303 chip (USB2COM converter, i.e. RS232) turned out to be compiled into the kernel as well - which made it possible to connect an external GPS receiver, as well as support for WiFi whistles on the Ralink chip (I used Dlink DWA-140). The board was installed in the radio, the signal from the TV output was fed to the RGB inputs of the head unit’s monitor using a self-assembled TV -> RGB converter based on TDA8362. In the radio, a video signal switch is provided, thus, all the native functionality of the radio was saved.

Red color shows what is placed on the second floor (under the board). The Toucan Nano comes with a remote control paired with a radio whistle. An interesting Fly Mouse technology is implemented in the remote control: the mouse responds to the accelerometer built into the remote control, so just tilting the remote control moves the mouse around the device’s screen, it’s something like a pointer. However, it seemed uncomfortable and silly to use this in a car: it was a radio, after all, not a computer! Therefore, it was decided to build a radio keyboard keyboard emulator that would connect to a USB port and behave like a standard PC keyboard, while interrogating the radio buttons. In fact, this solution is universal and can be used with any OS: it is no secret that many CarPC systems are built on Intel platforms.
It so happened that at that time I had no experience working with PIC microcontrollers, in my youth I was a program for Atmel, but this experience was no longer relevant today, because 10 years have passed and no one remembers my beloved AT89c2051 now, probably. Programs were written strictly on the AFM at that time, because on C it would just be more difficult, 2051 has only 2K ROM. In those years, PIC also existed, but we refused to use it, because development tools were too expensive, and for 2051 I then developed a ROM emulator myself based on i51 with an external ROM that had the same instruction set as Atmel 89c2051. Now, realizing that I would have to go this way almost anew, I gathered my courage and sat down for literature. I think that experience will therefore be useful to readers because I paint everything in steps, in development.
First, I began to study the Internet on the topic of microcontrollers with USB support and immediately came across a device from the glorious company PIC, it was 18F2550. The declared functions were very good: support for USB 2.0, 1K RAM, a fully configurable USB port, in fact, 2550 can be anyUSB device. Plus all standard PIC features. This chip can be purchased in the DIP28 package, which is convenient for mounting and desoldering, and the price is very low. In ancient times, microcontrollers were sewn with a programmer, but now bootloaders have become popular. The meaning of the bootloader is that it is enough to write it to the microcontroller only once, after which you can access it by any other means (in our case via the USB port), and update the firmware of the device without removing it from the circuit. Ready whales with pre-sewn microcontrollers and ready-made software for uploading firmware via a bootloader are sold, but I did not want to look for such a whale (or rather, wait until it was sent). So I just went to the nearest radio store and bought a clean PIC18F2550.
Next, it was necessary to assemble a programmer for filling the bootloader. Having searched the internet, I found this:
products.foxdelta.com/progparallel.htm
The scheme is attached there. Simplified it, of course, removing unnecessary details, because soldering was too lazy. In fact, the pins of the microcontroller are connected directly to the LPT. Software used WinPic800. About the bootloader itself, a separate story. It is loaded from the address 0x000, respectively, the program itself should start from the address 0x800, which is indicated in the compiler settings. I found several different bootloaders, they pour firmware in different modes (for example, HID), respectively, are intended for different IDEs. The USB PIC driver for the flashing computer can also be used differently, because PIC "turns" into different USB devices. I constantly came across bootloaders for 18F4550, and its config is different and the bootloader from it on my MK did not work. As a result, I settled on the bootloader and bootloader firmware based on the PIC-ovsky demo board MCHPUSB. The bootloader itself had to be slightly modified for my circuitry (because I placed the bootloader button on another pin of the microcontroller). In general, there turned out to be a lot of all sorts of different parameters (for example, the type of a quartz resonator, a frequency multiplier, and a lot of such nonsense). If these parameters are incorrectly indicated, the device is sewn and even blinks an LED according to the program, but then refuses to work as a USB device, giving my “favorite” error “USB device is not recognized”, because frequencies are wrong. I spent a couple of days with this problem until I finally found a workable configuration. As a result, my bootloader is designed for 4Mhz quartz. The LED is soldered to RA5, the button for activating the firmware mode for the bootloader is to RA3 (and from it 1kOhm plus), 26 feet to the ground (for working in LVP mode). In order for the MK to enter bootloader mode while holding the reset, press the indicated button on RA3. For some reason, I failed to reflash the LVP bit (disabling the low-voltage firmware mode), okay. In theory, it is needed to protect the bootloader from being overwritten. The multiplier is tuned to a maximum frequency of 48 Mhz. Unfortunately, if the microcontroller is used in USB mode, the frequency cannot be raised higher, because USB frequencies are fixed, and the multiplier has a multiplicity, but this turned out to be not necessary.
As a result, the bootloader corrected, installed the drivers on the computer from Microchip. As a basis of the program, I immediately took a free example from Bradley A. Minch, which outputs the characters 'f', 'o', 'o', 'b', 'a', 'r' to the virtual keyboard sequentially. The source code of this program can be found here:
code.google.com/p/picusb/source/browse/lab2_pickit2/lab2.asm?spec=svn69&r=69 Having
compiled the program, after setting up a bunch of PIC configuration registers, I made sure that it began to print on a computer in a loop the string foobar! Hurrah! In reality, all this did not happen in one day, but I omit the details. And here I was apprehended for a second by the fear that all this miracle might not work for Iconbit, which might simply ignore almost a week of my work without giving reasons - and I will never know why. But, having connected the device to the Android motherboard, I saw that it worked: immediately a search window opened in which the familiar foobar letters ran. Android reacts to pressing arbitrary keys by opening a search box.
Further, it was required to modify the program to my needs, in particular, write a code that will read data from the radio itself. How it would work, I had no idea at that time. I thought I would have to connect to the matrix of the radio and read from it. Typically, such devices use dynamic scanning of the matrix, when a unit “runs” along the X axis, and information is read along the Y axis. That is, say, 16 keys can be processed with 8 pins (4 + 4). To simultaneously read such a matrix, you will also need 8 pins, but configured for input. However, digging around on the radio board, I found that caring Germans had allocated a separate processor to process the entire keyboard, only the encoder handles went directly to the central percent. As a result, I found a way out of the keyboard of the radio, on which pulses appear when you press any keys on the radio. The pulse repetition rate turned out to be quite high and for a start I just stupidly connected them to a converter based on PL2303, i.e. I just applied to the RS-232 port and started experimenting with the port settings. As a result, it turned out that meaningful bytes can be seen by setting the port to as much as 480 kbps. For me, it’s still a mystery why the Germans needed to transmit keyboard data at such a high speed.
In order to write a program for PIC, I had to analyze in detail the nature of the pulses of the keyboard processor of the radio. For this, I bought an oscilloscope. In my youth, of course, I had all these devices, but all that remained with my parents, and I had already moved to Moscow for 5 years. Therefore, I decided to slowly build a fleet of devices: after all, I return to amateur radio from time to time.
As a result, it turned out that the processor before each packet of information (which consists of 2 bytes) produces a long pulse, which serves as a “pilot” warning of the beginning of the transmission of the information block. It was surprising that the length of this block (as well as the intervals between bytes) change arbitrarily even when you press the same button. Another problem was that the PIC could not programmatically reliably read data at that speed. After all, he must also have time to service the USB bus! If a key is pressed at the wrong time, it simply skips it. As I wrote above, I raised the core frequency to the maximum with the help of multipliers, however, even with this setting, the processor worked at the limit and sometimes missed bits, as a result, the probability of correctly determining the buttons was about 70%, which is unacceptable in the car. Here I had already gathered my mind wisely and thought that, probably, there is a second signal (strobe). And so it turned out. But initially I did not find it because the pulses for some reason are constantly present, and it would be logical if they appeared when the buttons were pressed. Apparently, the radio processor polls the keyboard in a continuous cycle and gives an impulse when transmitting any bit, even if the state of the keys does not change. That is, it simply sends zeros if there is no activity. In general, the processor gates every transmitted bit; as a result, they can be read absolutely clearly. I rewrote the program on PIC, after which everything began to work stably and beautifully. When a button is pressed, one code is generated, when released, another. Thus, it was possible to implement the button holding mode, but I did not do this, because
Here I will give excerpts from my program, those parts that I wrote. You can see the original program at the link above. In the program, I have, of course, a table comparing the read key codes and USB codes that must be transferred to the head device. I’ll immediately notice that I usually write comments in English (so as not to waste time switching the case). If someone is interested in the full version of the program, I can send it without any problems. The full text is not given here for reasons of copyright. I can also send a bootloader and a program for its firmware, a program for uploading firmware.
Recoding buttons.
unsigned char recode(long n) {
if (n==0x3550) return 'Q'; // <> REVERSE
if (n==0x1580) return 'U'; // up - 1
if (n==0x6aa0) return 'X'; // next - 2
if (n==0x2540) return 'D'; // down - 3
if (n==0x4a80) return 'Z'; // prev - 4
if (n==0x1570) return 'L'; // left - 5
if (n==0x0560) return 'R'; // right - 6
if (n==0x4520) return 'H'; // home
if (n==0x0ac0) return 'A'; // player
if (n==0x6500) return 'T'; // TEL ??
if (n==0x5530) return 'N'; // navi
if (n==0x7510) return 'S'; // settings
if (n==0x1b28) return 'B'; // back
if (n==0x4500) return 'E'; // enter
if (n==0x28e0) return 'M'; // (MAP) ??
if (n==0x1320) return 'O'; // notification area - REPEAT
if (n==0x0330) return 'F'; // search/find - CALL
return 0;
}
int remap(int n) {
// launcher
if (n=='S') return 0x3a; // F1 settings/menu
if (n=='F') return 0x3b; // F2
if (n=='O') return 0x3c; // F3 notification area
// programs
if (n=='H') return 0x3D; // F4 home
if (n=='A') return 0x3f; // F6 audio/player
if (n=='N') return 0x40; // F7 navi
if (n=='M') return 0x44; // F11 map
if (n=='T') return 0x45; // F12 tel
// player controls
if (n=='Z') return 0x3e; // F5 prev song
if (n=='X') return 0x43; // F10 next song
//cursor
if (n=='L') return 0x50; // left
if (n=='R') return 0x4f; // right
if (n=='U') return 0x52; // up
if (n=='D') return 0x51; // down
if (n=='E') return 0x28; // enter
if (n=='B') return 0x29; // ESC back
return 0x0;
}
Configs:
#pragma config DEBUG = OFF
// 1L 30000
#pragma config PLLDIV = 1 // PLL prescaler
//#pragma config CPUDIV = OSC3_PLL4 // sysclock postscaler - working
#pragma config CPUDIV = OSC1_PLL2 // sysclock postscaler
#pragma config USBDIV = 2 // usb clock comes from PLL div 2
// 1H 30001
// #pragma config FOSC = XTPLL_XT // low speed mode
#pragma config FOSC = HSPLL_HS // highspeed mode
#pragma config FCMEN = OFF // fail safe clock
#pragma config IESO = OFF // int/ext oscillator
// 2L 30002
#pragma config PWRT = OFF // power up timer
#pragma config BOR = OFF // brown out
#pragma config BORV = 0
#pragma config VREGEN = ON // usb voltage regulator
// 2H 30003
#pragma config WDT = OFF // watchdog
#pragma config WDTPS = 32768
// 3H 30005
#pragma config CCP2MX = ON // ccp2 mux
#pragma config PBADEN = OFF // portb a/d enable
#pragma config LPT1OSC = ON // low power timer 1 oscillator
#pragma config MCLRE = ON // MCLR pin enable
// 4L 30006
#pragma config STVREN = ON // stack full
#pragma config LVP = ON
#pragma config XINST = OFF // extended instructions
// 30008
//#pragma config ICPRT = OFF
#pragma config CP0 = OFF
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF
// 30009
#pragma config CPB = OFF
#pragma config CPD = OFF
// 3000a
#pragma config WRT0 = OFF
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF
#pragma config WRT3 = OFF
// 3000b
#pragma config WRTB = OFF
#pragma config WRTC = OFF
#pragma config WRTD = OFF
// 3000c
#pragma config EBTR0 = OFF
#pragma config EBTR1 = OFF
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF
// 3000d
#pragma config EBTRB = OFF
//#define SHOW_ENUM_STATUS
#define LED PORTAbits.RA5
#define CAR PORTAbits.RA0 // keypad data line
#define EN1 PORTAbits.RA1
#define EN2 PORTAbits.RA2
#define SYN PORTAbits.RA3 // keypad strobe line
Some of the configs are redefined by the programmer itself when filling the bootloader.
Transferring the key code to the USB interface:
void SendKeyBuffer(void) {
unsigned char n;
for (n = 0; n<8; n++)
BD1I.address[n] = Key_buffer[n]; // copy Key_buffer to EP1 IN buffer
BD1I.bytecount = 0x08; // set the EP1 IN byte count to 8
BD1I.status = ((BD1I.status&0x40)^0x40)|0x88; // toggle the DATA01 bit of the EP1 IN status register and set the UOWN and DTS bits
}
int sendKey1(unsigned char c) {
int n;
long timeout=0;
for (n = 0; n<8; n++) Key_buffer[n] = 0x00; // clear key buffer
// ?? hang place 2
while ((BD1I.status&0x80)) {
// wait to see if UOWN bit of EP1 IN status register is clear, (i.e., PIC owns EP1 IN buffer)
timeout++;
if (timeout>250000) {
return 0;
}
ServiceUSB();
}
*(Key_buffer+2)=c;
SendKeyBuffer();
ServiceUSB();
return 1;
}
int sendKey(unsigned char c) {
if (sendKey1(c)) return (sendKey1(0x00));
return 0;
}
Subroutines for reading radio buttons. To maximize performance, the
cycle is not used. Every beat counts.
char readByte() {
unsigned char b = 0;
// unsigned char i = 0;
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
while (SYN==0);
b = (b << 1) | CAR;
while (SYN==1);
return b;
}
int countZero() {
unsigned short i=1;
while (CAR==0 && i++);
return i/4;
}
int readCar() {
int i;
int d = 0;
unsigned char k;
unsigned char r;
unsigned long vl;
//#define analyzer
//#define dbg // debug mode
if (CAR==0) return 1;
#ifdef analyzer
for (i=0;i<110;i++) {
carbuff[i]=readByte();
carbuff[i+110]=ssd; // strobe data
}
for (i=0;i<221;i++) {
printHex(carbuff[i]);
}
crlf();
crlf();
return 1;
#endif // end analyzer
while (CAR==1); // short intro 1
while (CAR==0); // short intro 0
while (CAR==1); // long intro 1
vl = readByte() * 0x100 + readByte();
k = recode(vl);
#ifdef dbg
printDword(vl); // dbg
crlf(); // dbg
#endif
if (k!=0) {
#ifdef dbg
printChar(k); // dbg
crlf(); // dbg
#endif
#ifndef dbg
r=remap(k); // send actual keys we need
return sendKey(r);
#endif
}
}
CAR - the pin to which the signal from the radio keyboard processor is connected. SYN - strobe signal from the radio keyboard processor.
In the process of debugging, I encountered a number of problems. In some cases, the microcontroller hung and the USB device simply disappeared. The USB exchange protocol is very difficult and to delve into all its nuances was not easy. Through trial and error, I found the problem areas and added a software watchdog there, which simply restarted the processor in case of a hang in these places. As a result, I achieved a stable, trouble-free operation of the program without freezes with a fidelity of determining key pressures of 99%. The USB port in Toucan itself does not work very well, especially since I used a USB splitter. Sometimes even “serious” devices like USB mouse whistle disappear until the next reboot. What can I say about my homemade. However, I managed to achieve stability at the level of factory USB devices.
Next, you had to connect the encoder. I decided to send the data from the encoder to the emulation of the up and down buttons, and pressing the encoder knob on the Enter key. Well, pressing processes the processor, i.e. it is already working. As for the encoder, it is not difficult to read it. If you connect its pins to two bits, it gives the sequence 0, 1, 3, 2 (in binary code 00 01 11 10). We change the two with the triple, it turns out 0 1 2 3. Next, subtract the current result from the previous one, if the difference is 1 or -3, then rotation in one direction, if -1 or 3, in the other. I saw that reading the encoder is implemented by some kind of table, in general, nothing needs to be done. Here is the procedure:
int readEncoder() {
int k;
int enc;
int subb;
int res = 1;
static int enc1 = 0;
static int init = 0;
if (!init) { // prevent encoder key command sent on startup
init = 1;
enc1 = enc = EN1 + EN2 * 2;
if (enc==3 || enc==2) enc=enc ^ 1;
}
enc = EN1 + EN2 * 2;
if (enc==3 || enc==2) enc=enc ^ 1;
if (enc != enc1) {
subb = enc - enc1;
if (subb==1 || subb==-3) k='U';
if (subb==-1 || subb==3) k='D';
res = sendKey(remap(k)); // send up or down key command
}
enc1 = enc;
return res;
}
Now PowerAMP is clearly controlled: the encoder knob and enter! With a pen we run through the folders with the music, we enter them into the button and start the composition. Dream!
I add that the codes generated by the virtual keyboard are not ASCII codes or even Android key codes (which also differ from ASCII codes). These are separate USB keyboard codes. You can find them in this table:
www.win.tue.nl/~aeb/linux/kbd/scancodes-14.html
Accordingly, in Android there is a special config that is responsible for processing specials. keys. Of course, I had to start navigation, and use the Home button and launch the player. For this purpose, you need to edit the keylayout file, a description of these files is here:
source.android.com/tech/input/key-layout-files.html
Here is my config:
key 1 BACK # ESC (“AC” button)
key 59 MENU # F1 (“BC” button)
#key 60 CAMERA # F2 (“CALL” button)
key 61 NOTIFICATION # F3 (“repeat” key on deck)
key 62 HOME # F4 (“main” button)
key 63 MEDIA_PREVIOUS # F5 (“4” button)
key 64 HEADSETHOOK # PLAY / PAUSE F6 (“audio” button)
#key 65 INFO # F7 (“navi” button)
key 66 VOLUME_UP # F8 ??
key 67 VOLUME_DOWN # F9 ??
key 200 HEADSETHOOK # hardware key # MEDIA_STOP not working
key 68 MEDIA_NEXT # F10 (“6” button)
key 87 POWER # F11 ('map' key on deck)
#key 88 EMAIL # F12 ("tel" button)
Of course, edit this File can only be on rooted devices.
It so happened that some keys do not work after installing the latest update on the Toucan Nano. Why - I still could not find out. For example, the most important HOME button has stopped working. Therefore, I wrote a simple batch file that starts with my system startup. Here it is:
#! / System / bin / sh
stty -F / dev / ttyUSB0 ispeed 4800
chmod 0777 / dev / ttyUSB0
sleep 20
am broadcast -a info.mapcam.droid.SERVICE_START # mapcam.info
while true
do
s = $ ( getevent -v0 -c1) # read one event from all input devices
# -v0 so that it does not spill a bunch of unnecessary garbage
s = $ (echo $ s | awk '{print $ 4}') # select the key code
case $ s in # execute desired team
0007003f) am start -a android.intent.action.MAIN -c android.intent.category.HOME -n com.maxmpz.audioplayer / .StartupActivity # AUDIO
# am start -a android.intent.action.MAIN -c android. intent.category.HOME -n com.maxmpz.audioplayer / .PlayListActivity # AUDIO
sleep 1
;;
00070040) am start -n ru.yandex.yandexmaps / .MapActivity # NAVI
sleep 1
;;
0007003d) am start -a android.intent.action.MAIN -c android.intent.category.HOME
sleep 1
;;
00070045) am start -a android.intent.action.MAIN -n com.speedsoftware.rootexplorer / .RootExplorer # TEL
sleep 1
;;
0007003b) am startservice -a "org.broeuschmeul.android.gps.usb.provider.nmea.intent.action.START_GPS_PROVIDER"
sleep 5
am broadcast -a info.mapcam.droid.SERVICE_START
# am start -n info.mapcam.droid / .SpeedometrActivity # CALL
sleep 1
;;
esac
done
This emulator has been successfully used on the machine for almost a year now. The plans are to make mouse emulation so that it can be moved around the screen with buttons. Unfortunately, I have not yet mastered integrating the touchscreen into the system. Unfortunately, a number of programs (for example, Rambler Maps) are not controlled by buttons and require a touchscreen. Therefore, all the same, for such cases in the car you have to carry a USB mouse.
About the Android project itself, I can somehow tell separately if it will be interesting.
