Thursday, December 30, 2010

AVR32 development on Ubuntu 10.10

I got to play with Atmel's nice EVK1101 board recently so here are some notes on setting up your Ubuntu PC for AVR32 microcontroller development.

Toolchain

Install a few dependencies:

$ sudo apt-get install build-essential flex bison libxerces-c28

The AVR32 tools depend on libboost1.34 and libboost 1.38 which aren't packaged for Ubuntu 10.10 but were available in Karmic. You can just grab and install the appropriate packages manually.  Here are instructions for the 1.34 part and for 1.38 it's similar:

$ wget http://mirror.pnl.gov/ubuntu//pool/main/b/boost1.38/libboost-thread1.38.0_1.38.0-6ubuntu6_i386.deb
$ wget http://mirror.pnl.gov/ubuntu//pool/main/b/boost1.38/libboost-filesystem1.38.0_1.38.0-6ubuntu6_i386.deb
$ wget http://mirror.pnl.gov/ubuntu//pool/main/b/boost1.38/libboost-system1.38.0_1.38.0-6ubuntu6_i386.deb

$ sudo dpkg -i libboost-filesystem1.38.0_1.38.0-6ubuntu6_i386.deb libboost-system1.38.0_1.38.0-6ubuntu6_i386.deb libboost-thread1.38.0_1.38.0-6ubuntu6_i386.deb


The AV32 toolchain can be downloaded from the Atmel GNU Toolchain page.  You have to fill out a registration form to be able to download.  Make sure that you pick the right toolchain (32-bit or 64-bit) for your PC.  Unpack the archive "sudo dpkg -i" all of the packages.

Reference Code

Atmel provides a large reference code library with support for most of their development boards and facilities to add your own board.  The included projects can be built with the GNU toolchain for either standalone use or with FreeRTOS.

The AVR Freaks Wiki has a good tutorial on getting started.

DFU

The AVR32 comes with Atmel's USB bootloader and dfu-programmer handles the host side.  You can install it on Ubuntu:

$ sudo apt-get install dfu-programmer


To program the application Flash, use the ihex (.hex) format output from the build system. You'll need to erase the Flash, write to it, and then start the application. For example,

$ dfu-programmer at32uc3b1256 erase
$ dfu-programmer at32uc3b1256 flash --suppress-bootloader-mem myapp.hex
$ dfu-programmer at32uc3b1256 start


If you need the latest version of dfu-programmer, grab the source and extract it, also install libusb-dev:

$ sudo apt-get install libusb-dev
$ cd dfu-programmer-0.5.4/
$ ./configure
$ make
$ sudo make install


JTAG

If you see errors about the Flash memory being locked, try erasing the AVR the first time you use it:

avr32program chiperase


To program a 256K device for example, you can:

avr32program program -finternal@0x80000000,256Kb -e -v myapp.elf


You can restore the DFU bootloader with JTAG as well.

Friday, October 8, 2010

BMW 740iL seats retrofit in my 1973 BMW: wiring

I installed front seats from an E38 BMW 740iL in my 1973 BMW Bavaria.  This car of course did not have power (or heated, for that matter) seats so here are some notes on how to connect them.






The basic parts needed are:
  • two fuse holders and fuses (one 30A and one 15A).
  • a relay and relay holder (recommended).
  • some wire: there are several colors involved but the power wiring could just be red and brown.  The heater circuit uses white/blue and white/yellow but I suppose white and yellow would look OK.  The power wiring for the seat power is very thick (like 10ga) and the rest of the wiring is thinner, I think that 14ga or 16ga should do.
  • wiring "pigtail" for each seat's wiring connector from the donor car.  These seats use a very complicated AMP connector so it's important that you have it since it's nearly impossible to make your own.  On the plus side, it's a very nice connector.
  • two of BMW part 61318352259, "seat heating switch". This would certainly be a good thing to buy used (I found mine on eBay).  I would get the pigtails for the small connector on these switches as well.  Here's a list of donor cars.
  • some typical wiring splice connectors.
Seat Power


The seat power is connected to the battery at all times through a single 30A fuse which is shared between the two seats.  I suppose that this could be divided into two 15A circuits but I don't know the headroom that the designers considered so it's probably safer to wire it up like BMW did.  There are potentially three sets of circuits to power on each seat:
  1. The main set of motors (the thick red and brown wires are +12V and ground respectively and there is also a thinner brown wire that I believe is a also a ground).  All seats have this.
  2. The lumbar support motor (red/yellow is +12V)
  3. The thigh support motor (red/white is +12V)
These are found on pages 40 through 42 on the BMW E38 wiring diagram but the internals of the seat wiring are not particularly important.  Some of these seats have a memory function which is shown on page 39 and needs an additional fuse.


From what I understand, the regular ("comfort") seats have 16 adjustments and just use the first power supply.  There are "sport" seats with 18 adjustments that have the other two as well.  There is also a bizarre "active" (massaging) seat that I don't know anything about.

Seat Heaters


The BMW E38 wiring diagram shows the seat heater circuit on page 23.  Each seat has a heater switch which consists of an on/off button (with backlight) and a temperature knob.  The switch has six numbered pins corresponding to:
  1. interior lights system (connect it to the dash lights dimmer circuit, ie: the one that lights up the in-dash clock)
  2. seat harness white/blue
  3. power supply: switched ignition power through a 15A fuse (common between both seats)
  4. ground
  5. seat harness: white/yellow
  6. not connected
The power supply pin is supposed to be connected straight to the car's ignition or "run" circuit however on older cars that circuit isn't designed to have many accessories on it.  I therefore connected it through a relay which switches power to the heater circuit (via the 15A fuse as per the diagram) when the ignition is on but then allows the seat heaters to draw current directly from the battery.


The seat heaters have a very nice mechanical safety system built in.  It consists of a clear tube that contains two springs on either side with a significant air gap between them.  Presumably as the seat heats up the springs expand and eventually they will make contact, cutting power to the circuit until they've cooled enough to break contact.


Additional / Unused Wires


There are additional wires on the seat harness that are not connected to the seat motors or heaters.  These, I believe, are for the car's SRS system (airbags) and functions such as the seatbelt warning and they're probably connected to an "occupant switch" and the seat belt buckle.  My Bavaria used to have the USA-mandated "fasten seatbelts" switch so I could even connect some of this up but it doesn't really interest me.  In addition the built-in seat buckle has a mechanism to tighten the belt during a crash and that seems to be connected to the car via CAN or some other bus (it uses a twisted pair).  I recommend removing that whole assembly since it won't work in the E3.


Seat Mounting


BMW mounted the E3 seats on "perches" up through 1973 and then switched to mounting seats almost directly to the floor in the 1974 models.  As a result it's difficult to install later-style seats in a 1973 or earlier E3.  The E38 seats have nearly identical rail-to-rail widths to the stock E3 seats and their rails are only a little longer.  I first tried simply bolting the E38 seats to the E3 mounting "perches" but found that the seats sat too high so I had the seat "perches" removed and John at Nameless Performance made brackets to bolt the E38 seats straight to the floor in the 1974 and above style.  I then re-routed my carpets to cover things up.  The E38 seats wound up at a perfect driving position, matching the originals, with plenty of adjustment left.


To remove the "perches", take out the carpet pieces and drill out the spot welds and pull them out.  I had someone do this for me but you can do it yourself if you know what you're doing (when it comes to metal, I typically don't).  Please contact Nameless Performance if you'd like to try E38 seats in your E3 as they can sell you some brackets.

Tuesday, September 14, 2010

A quick guide to minicom as a terminal for your embedded Linux system

minicom is a great little terminal application for talking to embedded Linux systems (or pretty much anything else that uses a serial port). It can be tricky to set up at first but, once you're done with that, it's very fast and easy to use. Here's how I set it up:

install and configure

On Ubuntu, you'll need to install the minicom package:


$ sudo apt-get install minicom


By default, minicom will try to do all sorts of stuff for supporting modems. This wastes time and will send various characters down to the connected system's shell, so I recommend disabling all of it. You can do that via the menus but you can also write the following to ~/.minirc.dfl once before starting minicom:


# Machine-generated file - use setup menu in minicom to change parameters.
pu port /dev/ttyS0
pu minit
pu mreset
pu mdialpre
pu mdialsuf
pu mdialpre2
pu mdialsuf2
pu mdialpre3
pu mdialsuf3
pu mconnect
pu mnocon1
pu mnocon2
pu mnocon3
pu mnocon4
pu mhangup
pu mdialcan
pu rtscts No


Note that I typically use /dev/ttyS0 (the first serial port) and you may wish to change that.  If you prefer to run minicom's setup manually, try:


$ sudo minicom -s


...and then save the configuration by selecting "Save setup as dfl" from the menu.

running minicom

I like to have a little launcher icon for minicom which starts the terminal with certain settings. To make one, right-click on a Gnome panel, select "Add to panel..." and then create an "Application launcher". The command that I recommend for the launcher is:


gnome-terminal --title="minicom" -e "minicom -o -w -c on"


That starts minicom in a new gnome-terminal window (titled "minicom"), with additional modem stuff disabled (-o), line wrapping enabled (-w), and color output enabled (-c on). Alternately you can just run:


minicom -o -w -c on


from your shell. minicom accepts commands after you type control+a. Its menu-driven configuration interface is activated by pressing control+a and then "o". For example, to set the baud rate, press control+a and then "o" and navigate to "Serial port setup". To exit minicom, type control+a and then "x". Keep in mind that most Linux systems use 115200 8N1 (115200 baud, 8-bit, no parity) for their serial terminal. Note that, after typing control+a, you will see a red bar on the bottom of the minicom screen with information about the current serial port configuration.

useful features / issues

You can clear the minicom screen at any time by typing control+c. minicom has a handy logging feature that enables you to capture shell output to a file. To activate it, type control+a "l" (lower-case "L") and then enter the name of the destination file (log). After that, type control+a and then "l" again to start or pause logging.

minicom is not particularly graceful with multiple instances of the application. You can use "lsof" to see if someone has the serial port open, for example:


$ lsof /dev/ttyS0


...and stop or kill the offending processes if needed. Also keep in mind that, if you use a USB serial port dongle, the kernel may assign a new device between plugging/unplugging the dongle. Be sure to tell minicom about that (ex: /dev/ttyUSB1 vs. /dev/ttyUSB0). I prefer to use the physical RS232 port on my ThinkPad's docking station rather than dealing with USB dongles.

Wednesday, September 1, 2010

working with a local git tree and OpenEmbedded

OpenEmbedded works pretty well as a system-level build system but sometimes one needs to develop something locally (for example, a whole application or a set of changes) before committing to a tree that OpenEmedded pulls from.   OpenEmbedded won't really pull local files, and it's uncomfortable to not have revision control either way.

I use a local git tree to achieve this the work flow, which someone else might find useful.  I originally came across this approach here but want to elaborate a bit.

Setup

First, create a git tree for your project. For example:

$ mkdir myapp.git
$ cd myapp.git
$ git init --bare


This sets up the git management stuff that OpenEmbedded will use. Now clone it:

$ cd ..
$ git clone myapp.git myapp
$ cd myapp


...and either start developing there or grab your code from somewhere else.  For example, we can add a file (or several) and push to a new branch, "master".

$ cp /path/to/something/main.c ./
$ git add main.c
$ git commit -a -m "initial commit"

$ git push origin master

Edit (or create) an OpenEmbedded recipe for your app and tell it to fetch from the local git. You need the full path to 'myapp.git', for example '/home/andrey/myapp.git'. In the recipe this looks like:

SRC_URI = "git:///home/andrey/myapp.git;protocol=file;branch=master"


Note the three "/" after "git:".

Change / Build / Test

Now, the workflow is:

  1. make some changes to the source code
  2. commit and push:

    $ git commit -a -m "my change"
    $ git push origin master

  3. in OpenEmbedded, clean and rebuild:

    $ bitbake -c distclean myapp && bitbake myapp

  4. deploy and test your app
  5. repeat as needed

When you're happy with a set of changes, commit them to some upstream (or otherwise not local) tree or send a patch to someone.  The advantage is that you are able to work locally and you have revision control for all of your changes, no matter how minor.  In addition, your application builds within OpenEmebdded and has access to dependencies that it may need.  You do have to suffer some time waiting for bitbake to do its thing, but that can be quite fast for small applications or libraries.  You can then switch the "SRC_URI" for your recipe to pull from your remote repository and switch back to the local one when you need to make and test some more quick changes.

Monday, August 16, 2010

A 2000+ mile road trip in the Bavaria

With the 1973 Bavaria converted to fuel injection just in time, we set off early on Friday the 16th for a week-long road trip to the Pacific North West and returned safely last night.  The car worked great this time and there were no major issues and the car hardly lost any oil and returned acceptable fuel economy (21 to 24mpg, without an O2 sensor for the time being).

Our first proper stop was Portland, OR where we enjoyed some great beer (including a sampler at the Deschutes location in town) and amazing donuts at Voodoo Donut.  If you haven't been yet and are in town, I highly recommend it and promise that it's worth the wait in line.  The downtown location is usually crowded but there's a second location that's closer to I-5 and has parking.  They've also opened one in Eugene but I haven't stopped in yet.

This trip was, thankfully, the last one for my terrible exhaust system which consisted of beat up and poorly-fitting Stahl headers and a questionable vintage Monza brand aftermarket exhaust.  Our next stop was at my friends Jason and Mira's place near Vancouver, WA where Jason and John (who started Nameless Performance) helped me improve my fuel line routing and install their new stainless steel exhaust system.  That consists of ported factory intake manifolds, stainless downpipes, and stainless exhaust all the way back with a modern muffler and resonator design.  The fit and finish on the parts is absolutely excellent, no more rubbing or wobbling around, and no more rubber donut hangers!  I highly recommend Nameless' exhaust system for your Bavaria or E9 coupe needs, please contact them for details if you're thinking of upgrading or replacing your exhaust system.

We continued North to the beautiful city of Seattle where we explored (mostly on foot) and met up with our friends Eric and Seth.  Seattle has excellent public transit but it was really helpful to have the car as we were able to see more of the surrounding area, including Alki beach, in just a few days.  If the weather is cooperating (which it definitely did that week), I recommend taking a long walk through the Pike Place market, the Belltown area, up and over the Queen Anne area (which has great views of the city) and then back down and over the bridge to the Fremont district which has weird public art that includes a statue of Lenin and a massive troll under a bridge.  We had more excellent beer at Brouwers on 35th there.

After touring Seattle, we drove the Olympic peninsula, which contains what is basically the most northwest point in the lower 48 states.  There's not much going on out there but it's beautiful and the drive is a lot of fun, especially past Lake Crescent, Port Angeles, and the Puget Sound itself.  I'll need to come back and spend more time in just that part of WA state some day.

We put over 2000 miles on the car in that short week and, while it performed great, I was constantly reminded of how bad the front seats are and that I removed the broken AC system a while back, the trip down I-5 back home in 95 degree heat was especially uncomfortable.  It's time to shop for new seats (or have the current ones reupholstered) and now that other issues are dealt with I might spend some time installing new AC components.

Wednesday, August 4, 2010

Bosch L-jet and BMW Bavaria notes

My friend George recently helped me convert my 1973 BMW Bavaria to L-jetronic fuel injection, replacing my troublesome carburetors.  Here are some notes that might be helpful if you're contemplating a similar project for your E3 or E9:

  • It took us one weekend to do the whole conversion, from taking off the carburetors to test-driving the fuel-injected Bavaria and adjusting the timing.  We could possibly have done it all in one day if we prepared a few parts ahead of time and weren't missing some little pieces.  Overall, this conversion isn't that difficult if you have the parts.
  • I recommend using the latest version of L-jet (called Lambda L-jet and found on the 1981 528i USA models and a few other cars) over any earlier version.  The main reasons are:
    • Simpler and more robust wiring harnesses, coolant sensors are all up front in the thermostat housing rather than below the intake manifold at the "Joules Verne" device.
    • Integrated transistor ignition module (in my car, I replaced my Crane ignition with the 528i parts, which do approximately the same thing).
    • Much safer and cleaner fuel rail design.  The later fuel rail is a once-piece solid pipe with barb fittings for the injector hoses and fuel pressure regulator located up front.  Earlier rails have multiple hoses and components as well as a fuel pressure regulator located in a rather inconvenient spot.
    • Simpler and cleaner idle air bypass parts and fewer emissions things to plug up.
    • The O2 sensor of course enables the system to run more efficiently and cleaner since it provides feedback to the ECU.  This system's O2 sensor circuit is pretty basic, there's no heater for the sensor for example, and the replacement interval is about 30,000 miles.  On the other hand, the sensor is relatively cheap and it's still better than running open-loop.
  • Be prepared to "rebuild" the fuel rail before installing it into the car.  You will need some 5/16" fuel injection grade fuel line, which you'll also use for the rubber parts of the fuel supply line.  You will need to replace all of the injector seals, otherwise the system will have vacuum leaks.  Namely you need parts 1, 3, 5, and 7 on this parts diagram and a good set of snap-ring pliers.  We used modern fuel injection hose clamps over the barb fittings in place of the BMW "cup" connectors.  There's a detailed guide about this.
  • You will need a new thermostat housing with more threaded holes for sensors unless your car already has one.  The 528i L-jet needs a temperature sensor (in addition to the one-wire sender for your instrument cluster) and thermal-time switch.  There is also a heat-actuated vacuum valve for the fuel tank evaporation via the charcoal canister and you should consider installing it.  The thermostat housing must be installed after the L-jet base manifolds are in place as there isn't enough clearance to do it the other way around.  Keep in mind that there's a front and rear base manifold, the cylinder numbers are written on them (with 1 being the front of the engine as usual).
  • We used the 528i throttle linkage rod on my car, however it was too tall when used with the Bavaria linkage pivot located at the motor mount bracket.  We shortened and re-threaded mine to work.  You can do the same or you may need the corresponding 528i pivot parts.
  • The L-jet throttle has coolant hoses running underneath it.  We don't believe that these serve any useful purpose and chose to skip them as they're not needed for making the car run properly.  To bypass that mess, simply connect the small fitting on the metal coolant bypass pipe (located toward the middle of the engine) to your block's heater fitting (located near cylinder 6) with the same kind of hose that you'd use on your carburetor chokes.  I've heard two theories for why the throttle coolant lines are there:
    • They may be part of an emissions effort: by heating the intake air, the car is forced to run leaner.  This of course is the opposite of what you want, it doesn't help to have a "hot air" intake system.
    • They may be part of an effort to prevent icing on the throttle plate in some kind of insanely cold environment.  That said, some systems (ex: Motronic on the same engine) don't have this provision so it's anyone's guess as to how well that worked out.
  • When installing the intake manifold, install the two triangle-shaped supporting brackets along with the base intake manifold.  Don't wait to install them later when you fit the "log" and C-shape runners, it's a very tight fit otherwise.
  • The E12 (or other parts car's) wiring harness winds up as part of the fuel injection and ignition harness.  You'll see:
    • Red wires with an eyelet terminal which should be bolted to your starter in the same spot that the battery cable connects.  That's your power source.
    • There's a black/yellow wire near the starter which should be connected to your solenoid terminal, it's used for the cold-start valve circuit, I believe.
    • There's a black plug with two rows of pins.  This went to the car's fuse box but on our cars the only thing we need to know is it requires ignition switched +12V at pin 2.  Simply connect your carburetor choke wires (either green/yellow or green/white) to pin 2 on that connector and you're done with that plug.  You can remove and discard the choke relay that sits on top of the brake booster bracket (I chose to install my L-jet combo relay there instead).
    • There's an eyelet connector near the alternator, it hooks up to the alternator output just like the battery does.  There's also a D+ wire there for the original car's instrument cluster (alternator warning light) which can be left unconnected if your car's wiring is already doing the job.
    • There are two eyelet terminals near the ignition coil and they connect to the coil as expected.  The ECU connector also has a gray terminal which is the "pulse" wire and needs to be connected (with a long wire that you add) to the negative side of the ignition coil.  The 528i transistor ignition harness contains its own resistor pack, you'll need to remove your ballast resistor and replace your coil with the kind used on the E12.
    • The ECU itself can be placed behind the glove box but it's a tight fit and we haven't found a good way to secure it yet.  Drill a hole in the firewall and insert the ECU harness plug through it with a grommet (the E12 uses two grommets but the E3 only needs one).  A nice trick we found is to remove the casing/shroud on the connector and reinstall it over the harness is placed through the firewall.
    • Don't forget to connect the grounds (eyelet connectors) in the injector harness, one will be near cylinders 5 and 6 and another up front near 1.  They can be bolted to the intake manifold studs or to the triangle-shaped "log" support brackets.  There's also a brown eyelet up front near the thermostat housing, that's also a ground.
  • The E12 distributor replaces your original distributor and connects to the ignition control module.  There's not much to be said about it aside from it needing only a cap and rotor for maintenance.  The cap is wider than the E3 version but the same spark plug wires fit.  You may want to replace your spark plugs with ones better suited for fuel injection, check what's recommended for the E12 528i.
  • You will need to run power for your fuel pumps.  On the E12, this comes from the combo relay which winds up near your radiator overflow tank.  You could also install your own relay off the E3's electric fuel pump fuse (and corresponding connector, located right behind the fuse box) if you wish.  I ran the wire on the driver side following the harness under the carpet at the rocker panel.  This requires taking out the driver's door threshold plate and peeling back the carpet and then taking out the rear seat as the harness curves up and under the parcel shelf.  In the trunk, it comes down the driver side wheel well and around under the tail lights.  Some fuel pump wiring tips in the trunk:
    • ground the pumps near where they sit, for example I grounded the in-tank pump at the trunk ground point for the other wires (tail lights, etc).  I grounded the second pump to the car body near where I mounted it via a self-tapping screw.  Never run power and ground wires together through a grommet, it's dangerous and there's no need, the whole car's body is ground.
    • run the external pump's power through the trunk (and a grommet) to the pump, near where the fuel line exits.
    • don't forget to grab connector pigtails for the in-tank pump and fuel level sender from a parts car so that you can solder your wiring to them.  Otherwise you need some very odd connectors and that can be cumbersome and messy.
  • You will need the right valve cover for a coupe of reasons:
    • The oil filler cap on the E3 is in the front but it needs to be in the back to get around the AFM and air cleaner assembly.
    • The 528i L-jet air bypass valve mounts to the air cleaner.
  • I couldn't find an E12 valve cover but we noticed that early Motronic covers are the same part except that the air bypass mounting holes aren't drilled and tapped.  George was able to drill and tap them so that worked out fine.  Unfortunately these later valve covers have their front bolt offset from where the original E3 bolt hole is.  You must therefore replace your upper front timing cover (and gaskets) as well.  We didn't do that on my car for now, instead we sealed up the hole (to prevent vacuum leaks) and inserted a short bolt for now.

Saturday, May 8, 2010

Ubuntu 10.04 + older ThinkPad = black screen pain

I decided to do a clean install of Ubuntu 10.04 LTS on my ThinkPad X40 (still my favorite laptop, ever). For this release there seems to be even less testing than usual (9.10 was pretty painful as far as good old Intel 855 graphics were concerned) and this time there aren't even decent release notes.  Please be aware that when you install 10.04 on your laptop with older Intel graphics, you'll be treated to a black screen without any way to drop to a virtual console. To solve this:

  1. Power the machine off (hold down the power button until it shuts off).
  2. Boot and hold down Shift until you see the Grub bootloader screen.  On there, edit the entry for the default kernel and add "i915.modeset=1" to the list of options (ie: after "quiet splash") and then press control+x to boot
  3. The system should boot up with working graphics now.  With sudo, edit /etc/default/grub and change GRUB_CMDLINE_LINUX_DEFAULT to "quiet splash i915.modeset=1" to have modeset permanently enabled.

    Wednesday, February 10, 2010

    polling key state on a Linux keyboard with EVIOCGKEY

    Here's another quick Linux input snippet.  Sometimes you just want to check if a key is pressed down rather than using the event-driven interface from evdev.  That can be accomplished by issuing EVIOCGKEY, which asks the device for a bitmask of key states.

    int is_key_pressed(int fd, int key)
    {
            char key_b[KEY_MAX/8 + 1];
    
            memset(key_b, 0, sizeof(key_b));
            ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b);
    
            return !!(key_b[key/8] & (1<<(key % 8)));
    }
    

    ...where 'key' can be one of the key codes specified in <linux/input.h>. For example KEY_F1 corresponds to key code 59.  'fd' is a file handle for a keyboard device.

    Linux input event (evdev) snippet: find a keyboard by name

    Here's a (hopefully useful) function for opening a keyboard (input event device) by name on Linux. For example, you want to open a device named "My Keyboard" but you don't know it's at /dev/input/event2. This function will open "/dev/input" and ask for the device name from each "eventN" device. It will return the file descriptor for the first device whose name matches (and the descriptor will be left open) or -1 if none matched.

    #include <linux/input.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <dirent.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    int get_kb_fd(char *input_name)
    {
            int fd;
            struct dirent *dp;
            char name[32];
            char dev[32];
            DIR *dir;
    
            dir = opendir("/dev/input");
            if (!dir)
                    return 1;
    
            while ((dp = readdir(dir)) != NULL) {
                    if (dp->d_name && !strncmp(dp->d_name, "event", 5)) {
                            snprintf(dev, sizeof(dev), "/dev/input/%s", dp->d_name);
                            fd = open(dev, O_RDONLY);
                            if (fd == -1)
                                    continue;
                            if (ioctl(fd, EVIOCGNAME(32), name) < 0) {
                                    close(fd);
                                    continue;
                            }
    
                            if (!strcmp(name, input_name)) {
                                    closedir(dir);
                                    return fd;
                            } else { /* not what we wanted, check another */
                                    close(fd);
                                    fd = -1;
                            }
                    }
            }
    
            closedir(dir);
            return -1;
    }