Playing WAV files on STM32 for BMW E30 gong

The main goal of the project was to minimize power consumption by replacing the existing gong module with a modern microprocessor.

The main microprocessor for the gong is the STM32L452RET6.

The microprocessor sleeps most of the time consuming only 5 μA. When the level changes on one of its interrupt lines, a WAKEUP interrupt awakens the microprocessor. The program detects which interrupt line awakened the system and plays a corresponding WAV file on its DAC. When it stops playing, the microprocessor goes back into sleeping.

WAV files are only a few seconds each, and they are stored in the microprocessor flash. The program uses a timer to transfer bytes to the DAC according to the bit rate, usually, 16 bit/44.1kHz or 16 bit/48kHz. As the STM32 DAC is only 12-bit, some conversion is done. First, bytes are read into a memory buffer. Then, they are transferred to the DAC using a DMA channel. The program waits until it receives a transfer complete interrupt, and send a new portion of the music file to the buffer.

The DAC signal is amplified by the amplifier chip NJM2135M (MC34119).

The software uses the Real-Time Operating System Zephyr to organize multiple threads and simplify writing drivers.

The gong schema.

I used a simpler and cheaper octocouple before, and the power consumption was similar.

BMW E30 gong schema

The audio amplifier subschema.
BMW E30 gong amplifier schema

The gong PCB

BMW E30 gong pcb

BMW E30 gong pcb

Software: BMW E30 gong software for Zephyr RTOS and STM32

6-button digital clock for BMW E30

The clock is engineered to be installed into the existing 6-button BMW E30 digital clock enclosure. The digits and letters are drawn by five red LTP-305HR 5×7 dot matrix displays covered by an orange filter. There are two PCBs: the first is for the power supply and processor, the second is for the displays, buttons, and display chip. The time functions are processed by the RTC module RV-3032-C7, which is temperature-compensated and very accurate. When the power is off, the RTC module gets power from the supercapacitor, which can supply enough power for a few months. The light sensor VEML7700 provides information about the current light level. The display dims at night. The optocoupler provides isolation from other car electronics.

The software is written using RTOS Zephyr for the processor STM32L452RE. There are several threads that read and update the current time, check the light level and adjust the display brightness, process interrupts fired by controlling buttons. A watchdog process restarts the processor in case of a serious error.



The processor is programmed by connecting it to an STM32 Nucleo board.

clock programming

The display PCB.


The processor PCB.


The power module.


The supercapacitor charger module.


Software: BMW E30 clock software for Zephyr RTOS and STM32

LED Matrix Font for HT1632C and Zephyr RTOS

Now, when the HT1632C display driver for Zephyr RTOS is ready, we want to write characters into the display buffer and send the buffer to the LED matrix.

LED matrix

The LED indicator is the LTP-305HR 5×7 dot matrix display. It has an LED for a dot ., that is located at the column 0. So, we will start writing characters at the column 1 of the display buffer.

The HT1632C supports 32 ROWs – in our case they will be vertical columns, and 8 COMs – they will be horizontal rows.

The center of coordinates 0x0 starts at the left top.

In our case, we will use 5×7 fixed width fonts. Each character will be represented as an array of 5 uint8_t 8-bit numbers(bytes). The array will start from the leftest column. The top row will start from the most significant(the leftest) bit of a byte.

Here is the letter M displayed on the LED matrix. When a bit is set to 1, it turns on the LED at that position; when it is set to 0, it turns off the LED. The 8th bit doesn’t matter as it’s not displayed at all – we will set it to 0.

led matrix m

The C array for the letter M looks like this:
{Column 1, Column 2, Column 3, Column 4, Column 5}

{0b11111110, 0b01000000, 0b00110000, 0b01000000, 0b11111110}, // M

Font File

All characters for the 5×7 font are included in the file font_5x7.h

They are located according to the ASCII order starting from the character SPACE ‘ ‘, which has the decimal number 32.

#ifndef FONT_5x7_H
#define FONT_5x7_H

// The width of each font character
#define APP_FONT_WIDTH 5

const uint8_t font[][APP_FONT_WIDTH] = {
    {0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000}, // 
    {0b00000000, 0b00000000, 0b11111010, 0b00000000, 0b00000000}, // !
    {0b00000000, 0b11100000, 0b00000000, 0b11100000, 0b00000000}, // "
    {0b00101000, 0b11111110, 0b00101000, 0b11111110, 0b00101000}, // #
    {0b00101000, 0b01010100, 0b11111110, 0b01010100, 0b00101000}, // $
    {0b11000100, 0b11001000, 0b00010000, 0b00100110, 0b01000110}, // %
    {0b01101100, 0b10010010, 0b10101010, 0b01000100, 0b00001010}, // &
    {0b00000000, 0b00000000, 0b11100000, 0b00000000, 0b00000000}, // '
    {0b00000000, 0b00111000, 0b01000100, 0b10000010, 0b00000000}, // (
    {0b00000000, 0b10000010, 0b01000100, 0b00111000, 0b00000000}, // )
    {0b00101000, 0b00010000, 0b01111100, 0b00010000, 0b00101000}, // *
    {0b00010000, 0b00010000, 0b01111100, 0b00010000, 0b00010000}, // +
    {0b00000000, 0b00001010, 0b00001100, 0b00000000, 0b00000000}, // ,
    {0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000}, // -
    {0b00000000, 0b00000110, 0b00000110, 0b00000000, 0b00000000}, // .
    {0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b01000000}, // /
    {0b01111100, 0b10000010, 0b10000010, 0b10000010, 0b01111100}, // 0
    {0b00000000, 0b01000010, 0b11111110, 0b00000010, 0b00000000}, // 1
    {0b01000010, 0b10000110, 0b10001010, 0b10010010, 0b01100010}, // 2
    {0b01000100, 0b10000010, 0b10010010, 0b10010010, 0b01101100}, // 3
    {0b00001000, 0b00011000, 0b00101000, 0b01001000, 0b11111110}, // 4
    {0b11110100, 0b10010010, 0b10010010, 0b10010010, 0b10001100}, // 5
    {0b01111100, 0b10010010, 0b10010010, 0b10010010, 0b01001100}, // 6
    {0b10000000, 0b10001110, 0b10010000, 0b10100000, 0b11000000}, // 7
    {0b01101100, 0b10010010, 0b10010010, 0b10010010, 0b01101100}, // 8
    {0b01100100, 0b10010010, 0b10010010, 0b10010010, 0b01111100}, // 9
    {0b00000000, 0b01101100, 0b01101100, 0b00000000, 0b00000000}, // :
    {0b00000000, 0b01101010, 0b01101100, 0b00000000, 0b00000000}, // ;
    {0b00010000, 0b00101000, 0b01000100, 0b10000010, 0b00000000}, // <
    {0b00101000, 0b00101000, 0b00101000, 0b00101000, 0b00101000}, // =
    {0b10000010, 0b01000100, 0b00101000, 0b00010000, 0b00000000}, // >
    {0b01000000, 0b10000000, 0b10001010, 0b10010000, 0b01100000}, // ?
    {0b01111100, 0b10000010, 0b10011000, 0b10100100, 0b01111000}, // @
    {0b01111110, 0b10010000, 0b10010000, 0b10010000, 0b01111110}, // A
    {0b11111110, 0b10010010, 0b10010010, 0b10010010, 0b01101100}, // B
    {0b01111100, 0b10000010, 0b10000010, 0b10000010, 0b01000100}, // C
    {0b11111110, 0b10000010, 0b10000010, 0b10000010, 0b01111100}, // D
    {0b11111110, 0b10010010, 0b10010010, 0b10010010, 0b10000010}, // E
    {0b11111110, 0b10010000, 0b10010000, 0b10010000, 0b10000000}, // F
    {0b01111100, 0b10000010, 0b10010010, 0b10010010, 0b01011110}, // G
    {0b11111110, 0b00010000, 0b00010000, 0b00010000, 0b11111110}, // H
    {0b00000000, 0b10000010, 0b11111110, 0b10000010, 0b00000000}, // I
    {0b00000100, 0b10000010, 0b10000010, 0b10000010, 0b11111100}, // J
    {0b11111110, 0b00010000, 0b00101000, 0b01000100, 0b10000010}, // K
    {0b11111110, 0b00000010, 0b00000010, 0b00000010, 0b00000010}, // L
    {0b11111110, 0b01000000, 0b00110000, 0b01000000, 0b11111110}, // M
    {0b11111110, 0b00100000, 0b00010000, 0b00001000, 0b11111110}, // N
    {0b01111100, 0b10000010, 0b10000010, 0b10000010, 0b01111100}, // O
    {0b11111110, 0b10010000, 0b10010000, 0b10010000, 0b01100000}, // P
    {0b01111100, 0b10000010, 0b10001010, 0b10000100, 0b01111010}, // Q
    {0b11111110, 0b10010000, 0b10011000, 0b10010100, 0b01100010}, // R
    {0b01100100, 0b10010010, 0b10010010, 0b10010010, 0b01001100}, // S
    {0b10000000, 0b10000000, 0b11111110, 0b10000000, 0b10000000}, // T
    {0b11111100, 0b00000010, 0b00000010, 0b00000010, 0b11111100}, // U
    {0b11111000, 0b00000100, 0b00000010, 0b00000100, 0b11111000}, // V
    {0b11111100, 0b00000010, 0b00111100, 0b00000010, 0b11111100}, // W
    {0b11000110, 0b00101000, 0b00010000, 0b00101000, 0b11000110}, // X
    {0b11100000, 0b00010000, 0b00001110, 0b00010000, 0b11100000}, // Y
    {0b10000110, 0b10001010, 0b10010010, 0b10100010, 0b11000010}, // Z
    {0b00000000, 0b11111110, 0b10000010, 0b00000000, 0b00000000}, // [
    {0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00000100}, /* \ */
    {0b00000000, 0b10000010, 0b11111110, 0b00000000, 0b00000000}, // ]
    {0b00100000, 0b01000000, 0b10000000, 0b01000000, 0b00100000}, // ^
    {0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b00000010}, // _
    {0b00000000, 0b10000000, 0b01000000, 0b00000000, 0b00000000}, // `
    {0b00000100, 0b00101010, 0b00101010, 0b00101010, 0b00011110}, // a
    {0b11111110, 0b00010010, 0b00100010, 0b00100010, 0b00011100}, // b
    {0b00011100, 0b00100010, 0b00100010, 0b00100010, 0b00000100}, // c
    {0b00011100, 0b00100010, 0b00100010, 0b00010010, 0b11111110}, // d
    {0b00011100, 0b00101010, 0b00101010, 0b00101010, 0b00011000}, // e
    {0b00000000, 0b00010000, 0b01111110, 0b10010000, 0b01000000}, // f
    {0b00010000, 0b00101010, 0b00101010, 0b00101010, 0b00111100}, // g
    {0b11111110, 0b00010000, 0b00100000, 0b00100000, 0b00011110}, // h
    {0b00000000, 0b00100010, 0b10111110, 0b00000010, 0b00000000}, // i
    {0b00000100, 0b00000010, 0b00100010, 0b10111100, 0b00000000}, // j
    {0b11111110, 0b00001000, 0b00010100, 0b00100010, 0b00000000}, // k
    {0b00000000, 0b10000010, 0b11111110, 0b00000010, 0b00000000}, // l
    {0b00111110, 0b00100000, 0b00011110, 0b00100000, 0b00011110}, // m
    {0b00111110, 0b00010000, 0b00100000, 0b00100000, 0b00011110}, // n
    {0b00011100, 0b00100010, 0b00100010, 0b00100010, 0b00011100}, // o
    {0b00111110, 0b00101000, 0b00101000, 0b00101000, 0b00010000}, // p
    {0b00010000, 0b00101000, 0b00101000, 0b00101000, 0b00111110}, // q
    {0b00111110, 0b00010000, 0b00100000, 0b00100000, 0b00010000}, // r
    {0b00010010, 0b00101010, 0b00101010, 0b00101010, 0b00100100}, // s
    {0b00000000, 0b00100000, 0b11111100, 0b00100010, 0b00000100}, // t
    {0b00111100, 0b00000010, 0b00000010, 0b00000100, 0b00111110}, // u
    {0b00111000, 0b00000100, 0b00000010, 0b00000100, 0b00111000}, // v
    {0b00111100, 0b00000010, 0b00001100, 0b00000010, 0b00111100}, // w
    {0b00100010, 0b00010100, 0b00001000, 0b00010100, 0b00100010}, // x
    {0b00110000, 0b00001010, 0b00001010, 0b00001010, 0b00111100}, // y
    {0b00100010, 0b00100110, 0b00101010, 0b00110010, 0b00100010}, // z
    {0b00010000, 0b00010000, 0b01101100, 0b10000010, 0b10000010}, // {
    {0b00000000, 0b00000000, 0b11111110, 0b00000000, 0b00000000}, // |
    {0b10000010, 0b10000010, 0b01101100, 0b00010000, 0b00010000}, // }
    {0b01000000, 0b10000000, 0b01000000, 0b01000000, 0b10000000}, // ~
    {0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000}, // Delete

#endif /* FONT_5x7_H */

When we draw a character, we can subtract the value of SPACE ‘ ‘ from its decimal number and find its location in the font array. Then, we write 5 bytes of the font to the buffer.

 * Draws a character at the position X
 * @param uint8_t c The character to display
 * @param uint8_t x The position at the display from which to display
void draw_char(uint8_t buf[], const uint8_t font[][APP_FONT_WIDTH], 
               uint8_t c, uint8_t x) 
    // Convert the character to an index
    c = c & 0x7F;
    if (c < ' ') {
        c = 0;
    } else {
        c -= ' ';

    for (int i = x, j = 0; j < APP_FONT_WIDTH; i++, j++) {
        buf[i] = font[c][j];

We can display a character starting from the position 1 like this:

draw_char(buf, font, 'M', 1);

When all required characters are set in the display buffer, the display buffer is sent to the HT1632C controller at once.

display_write(display_dev, 0, 0, &buf_desc, buf);

The code is available on

Writing HT1632 driver for RTOS Zephyr and Nucleo STM32

Zephyr Real-Time Operating System

When you have a powerful microcontroller, there is no need to limit yourself by the capabilities of Arduino. Most Arduino programs run in a loop that checks buttons, does some calculations, connect to the Internet, etc sequentially. Coding gets complicated when you want to click on a button and process the click quickly while another function is slowly connecting and parsing a webpage.

A Real-Time operating system allows to code multiple functions and delegate the task of running them simultaneously to the OS.

Zephyr Project is a very promising project. But some sensors, displays, hardware chips don’t have drivers to interact with them yet.

An experienced programmer usually learns by reading code of sample programs. Zephyr provides many samples.

STM32 Nucleo boards

ST provides $15 evaluation boards for its family of the STM32 ARM processors called Nucleo.

I compared the list of available Nucleo boards with the list of the Nucleo boards supported by Zephyr.

I wanted a low-power microcontroller with the largest RAM and flash, and in the LQFP64 package (64 pins, 10×10 mm), which is hot-air solderable. I selected ST Nucleo L452RE for the project.

How the Holtek HT1632C works

The Holtek HT1632C is a popular display controller that can drive matrices of LEDs (one chip up to 32×8 or 24×16). The Holtek HT1632D is a newer version that can come in a smaller LQFP48 7×7 mm package that has fewer pins for rows.

Here is a typical electrical schema. The HT1632C controls the 5×7 LED matrices (with 1 additional row for a dot) with the common cathodes.

Here is how it works. To turn on the 1st top LED in the 2nd column, the HT1632C sets 1 (HIGH) on ROW1 and opens COM0, so the current flows via the current-limiting resistor to the LED and then via COM0 to the ground. The LED turns on.

The LEDs in the matrices are addressed starting from the leftest top LED.

In order to enable the 1st top LED in the 2nd column, user code must write 1 into the HT1632C RAM address matching the ROW1 and COM0 combination. According to the HT1632C controller datasheet, the address is 02H when the controller is configured as 32 ROW x 8 COM or the address is 04H when the controller is configured as 24 ROW x 16 COM.

display LTP-305

The HT1632C communication protocol

The HT1632C uses 4 pins to communicate with a microcontroller.

  • CS (chip select)
  • WR (write)
  • RD (read)
  • DATA

This driver doesn’t read from the display controller. It only writes. So, we don’t use the RD pin.

  • The CS pin is active LOW. When the microcontroller wants to communicate with the HT1632C, it sets the CS pin to the ground (LOW).
  • The microcontroller sets the WR pin to LOW.
  • It sends a bit (0 or 1) by setting the DATA pin to LOW (0) or HIGH (1).
  • It sets the WR pin to HIGH. The HT1632C reads the DATA bit on the rising WR edge.
  • The microcontroller repeats the WR LOW, DATA bit, WR HIGH sequence until all bits are sent.
  • When it finishes, it sets the CS pin HIGH.

There are two modes of operation: the command mode and the write data mode.

The command mode starts with the 3 bits 100 and then the command itself.

HT1632C command mode

The write data mode starts with the 3 bits 101, the RAM address, the 4 bits of data.

This driver writes the whole array of data starting from the address 00H using the Successive Address Writing Mode. It starts with the 3 bits 101, the RAM address 00H, then all 32 8-bit words or 24 16-bit words.

It’s the task of the application code to set individual bits in the matrix array. Then it sends the whole matrix array to the driver.

HT1632C write data mode

Here is how the command SYS EN (Turn on system oscillator) looks like on the screen of my oscilloscope.
SYS EN 100-0000-0001-0

HT1632C command SYS EN

Zephyr Application Structure

The structure of my application is based on the Zephyr Example Application.

It creates an application with a sensor driver.

I changed the string ‘example-application’ in the files to my own application name.

HT1632C Zephyr Driver Installation

Download the source from and follow instructions there.

Zephyr driver configuration files

Available configuration options are defined in the file dts/bindings/display/holtek,ht1632c.yaml.
It follows the Device Tree specifications.

The GPIOS have the phandle-array type that describes the GPIO identifier, pin, and flags. For example, the flag GPIO_OUTPUT_HIGH sets the default output as HIGH, the flag GPIO_ACTIVE_LOW means when you set the pin ACTIVE, the signal goes LOW (the CS pin is active low).

description:  Holtek HT1632C display controller

compatible: "holtek,ht1632c"

include: base.yaml

      type: phandle-array
      required: true
      description: GPIO to which the CS pin of HT1632C is connected.

      type: phandle-array
      required: true
      description: GPIO to which the WR pin of HT1632C is connected.

      type: phandle-array
      required: true
      description: GPIO to which the DATA pin of HT1632C is connected.

      type: int
      required: true
      default: 0x00
      description: |
        0x00: N-MOS  opendrain output and 8 common option
        0x01: N-MOS  opendrain  output  and  16 common option
        0x10: P-MOS  opendrain output and 8 common option
        0x11: P-MOS  opendrain  output  and  16 common option

The test application is located in the directory app.
I created the overlay configuration file for my Nucleo L452RE board: app/boards/nucleo_l452re.overlay.

I defined the GPIOs that are connected to the HT1632C controller on the Nucleo L452RE board: CS to PB3, WR to PB5, DATA to PB4. The commons-options configures the HT1632C controller as 32×8 N-MOS. This configuration doesn’t need external transistors, the current will flow from ROWN -> resistor -> LEDs -> COMN.

/ {
    ht1632c {
        compatible = "holtek,ht1632c";
        label = "HT1632C";
        cs-gpios = <&gpiob 3 0>;
        wr-gpios = <&gpiob 5 0>;
        data-gpios = <&gpiob 4 0>;
        commons-options = <0x00>;

Hardware delays

The HT1632C datasheet provides the table with electrical parameters.

tCLK is 500 ns – it’s the pulse width of each WR signal
tsu is minimum 100 ns, I set it to 250 ns – it’s the delay between the setting the DATA pin and the WR pin going HIGH.

The function ht1632c_ns_to_sys_clock_hw_cycles converts nanoseconds to the processor clock cycles that must pass during those nanoseconds.

 * @brief Converts nanoseconds to the number of the processor system cycles
 * @param uint32_t ns Nanoseconds
static inline uint32_t ht1632c_ns_to_sys_clock_hw_cycles(uint32_t ns)
    return ((uint64_t)sys_clock_hw_cycles_per_sec() * (ns) / NSEC_PER_SEC + 1);

That’s what I get for the Nucleo L452RE with the 80MHz clock.

Delay tCS 33
Delay tCLK 41
Delay tSU 21
Delay tH 21
Delay tSU1 25
Delay tH1 17

The function ht1632c_delay then delays execution counting cycles.

 * @brief Delays the execution waiting for processor cycles
 * @param dev Pointer to device data
 * @param uint32_t cycles_to_wait How many processor cycles to wait
static void ht1632c_delay(uint32_t cycles_to_wait)
    uint32_t start = k_cycle_get_32();

    // Wait until the given number of cycles have passed
    while (k_cycle_get_32() - start < cycles_to_wait) {

Here is the function that writes a command to the HT1632C controller.

 * @brief Writes a command to HT1632C
 * @param dev Pointer to device data
 * @param uint8_t command Command without the first 3 bits 100
static void ht1632c_write_command(struct ht1632c_data *data, 
        uint8_t command)
    //CS down
    ht1632c_set_cs_pin(data, true);
    //100 - command mode
    ht1632c_write_bits(data, HT1632C_COMMAND_HEADER, BIT(2));
    //the command itself
    ht1632c_write_bits(data, command, BIT(7));
    //one extra bit
    ht1632c_write_bits(data, 0, BIT(0));

    //set the DATA pin low waiting for the next command
    ht1632c_set_data_pin(data, false);

    //CS UP
    ht1632c_set_cs_pin(data, false);

Sample program

The sample program writes the letter M, changes the brightness, and then makes the HT1632C sleep when CONFIG_PM=y and CONFIG_PM_DEVICE=y in app/prj.conf.

It's interesting to see how the second row of the letter M is half-lighted in the photo. That's because the HT1632C constantly switches ROWs and COMs ON and OFF. The human eye can't catch it, but a photo camera can.

ht1632c sample program output

Useful links

How to Build Drivers for Zephyr

BMW E30 E36 White Flashlight Li-ion Battery 82119413147

The BMW E30, E32, E34, and E36 have space in the glove box for the white flashlight (torch) 63171375457 or 82119413147. The later BMW models provide the black flashlight 63316962052, which is not compatible because the flashlight contacts are male.

The original electric circuit is extremely simple. There are two 1.2V NiMH coin cells, a resistor to constantly charge them, an E10 2.4V bulb, and a switch.
You can read an article about Teardown and Repair of the Simply Designed BMW E36 Glovebox Flashlight.

bmw e30 e32 e34 e36 flashlight 63171375457

All white flashlights that you can get now have dead batteries. You can replace the batteries with similar NiMH batteries, and they should work for a few years.

My project replaces the original circuit with a more sophisticated circuit that has a charger chip, Li-ion 4.2V coin cells, and an LED bulb.

Electrical Circuit

flashlight charger ltc4079

The charging chip is LTC4079.

The LTC®4079 is a low quiescent current, high voltage linear charger for most battery chemistry types including Li-Ion/Polymer, LiFePO4, Lead-Acid or NiMH battery stacks up to 60V. The maximum charge current is adjustable from 10mA to 250mA with an external resistor. The battery charge voltage is set using an external resistor divider.

The chip is tiny, but it’s possible to solder the QFN chip using hot air.

Two ⌀24.5mm RJD2450ST1 200 mAh rechargeable coin cells are used.

Charging Current – Standard: 95 mA
Charging Voltage – Maximum: 4.2 V

The RJD2450ST1 has contacts for soldered wires. But it requires cutting two small slots in the plastic circle in the middle of the flashlight. The RJD2450 without contacts can be used if there is a battery spot welder for the original contacts.

The resistor divider R2=1.54MΩ and R4=249kΩ provides the 8.4V charging voltage.
The resistor divider R1=1.2MΩ and R3=130kΩ disables charging when the battery voltage goes down to less than 12.17V. VIN(REG) = 1.190 × (1 + 1200000 ÷ 130000).
The resistor R5=12kΩ sets the charging current to 25mA. RPROG = 297.5 ÷ 0.025. The maximum charging current for the coin cells is 95mA. When I tested this amperage by using a 3kΩ resistor, the LTC4079 was very hot as the PCB is too tiny to dissipate all heat.
The timer capacitor C2=0.1µF is set to disable charging after approximately 5½ hours. CTIMER = 5.5 × 18.2.
The 10k NTC thermistor is NHQM103B375T5.


I used a 4-layer 1.2mm-thick PCB.

flashlight charger pcb

flashlight charger pcb 3d LTC4079

LED bulb

The flashlight uses E10 bulbs.

The default polarity of the BMW E30 flashlight bulb is to have – (negative or ground) at the tip of the bulb and to have + (positive) at the screw body. Most LED E10 bulbs have the opposite polarity: + at the tip and – at the body.

It’s relatively easy to change the wires, but the original connectors around the coin cells must be isolated well.

I checked a 1W LED light. It was really hot when it was on. The body of the flashlight is plastic and there is no airflow, so the plastic can melt.

I selected this LED: 0.5W, Screw E10, Nopolar, 12V Warm White(4300K). It can be connected to – and + both ways. It doesn’t get warm. It also has the same height as the original bulb: 22mm. It even has a lens.

e10 led


The flashlight is pretty bright and works for more than 10 hours on one charge.


3D-printed BMW E30, E32, E34, E36 connector 61130007441 61130007442

Many 2-pole (2-pin) and 3-pole (3-pin) electrical connectors in the BMW E30 and other classic BMWs (E32, E34, E36) use 2.5mm female contacts.

There are BMW outer plastic housings for 2 and 3 poles. But they require watertight rubber housing inserts for each contact terminal.

BMW offers the sockets and wires with the housing inserts already molded around them: 61130007441 and 61130007442. You have to solder them to your existing wires and use shrink wraps to isolate them. They are pretty expensive: $8-9.

If you want to go the DIY way, rubber contact housing inserts can be 3D-printed and regular inexpensive contact sockets can be inserted in them and sealed with a glue.

Contact Socket

The contact socket is made by TE Connectivity (formerly AMP).
At the time of writing, the silver sockets cost $0.27 each for quantities over 10. The gold sockets are of course 10 times more expensive.


TE Internal #: T2030002008-000
TE Internal Description: CEF-0.75
Contact Type : Socket
Contact Mating Area Plating Material : Silver
Wire Contact Termination Area Plating Material : Silver
Wire Size (AWG): 18
Wire Size (mm²): .75
Contact Current Rating (Max) (A): 16

This one has a slightly bigger hole for a wire: T2030002010-000

TE Internal #: T2030002010-000
TE Internal Description: CEF-1.0
Contact Type : Socket
Contact Mating Area Plating Material : Silver
Wire Contact Termination Area Plating Material : Silver
Wire Size (AWG): 18
Wire Size (mm²): 1.0
Contact Current Rating (Max) (A): 16

Check the inner hole diameters yourself: T2030002005-000 – Ø1.15mm, T2030002008-000 – Ø1.30mm, T2030002010-000 – Ø1.45mm.

There are also smaller and gold-plated sockets in the series.
There are similar contacts from other companies: Digikey heavy-duty sockets

TE AMP T2030002010-000 CEF

3D-printed housing

The material is TPU (Thermoplastic polyurethane), which is flexible.

Only the upper part is cut to accommodate insertion of the socket.
The bottom part is a solid circle that wraps around the male part providing good seal. If it were cut and glued, the adhesive bond could easily break under stress of insertion.

Download the 3D model


Sculpteo 3D printing

I used the 3D-printing service provided by
The material was TPU (flexible), Multijet Fusion (Plastic).
The price was $20 for 20 contacts plus shipping, $1 per contact.

It’s cheaper to attach more contacts to the existing design in a 3D-program if you need more contacts than to print multiple designs.

scuplteo multijet tpu

Here is the result.

3dprinted contacts

Contact installation

The 3D-insert crevices were cleaned of the excessive material dust using a knife and picks.

The wires were inserted into the sockets T2030002008-000 and they were crimped. The contact holes for wires were tight for the headlight wire gauge. The wiring diagram showed 1 mm² (16 AWG), though.

The sockets are pretty cheap to have an assortment and match different wire gauges perfectly. The biggest socket that will fit into the 3D-printed insert is T2030002015-000.

T2030002008-000 crimped

The sockets were inserted into the 3D-printed housing inserts.

Contact housing inserted

The open parts of the housing inserts were glued together using the shoe cement “Petronio”. After the glue settled a little bit, the sides were pressed together. Probably, any flexible glue should be good enough. If you wish to use Super Glue, it should be used on both sides ignoring the glue instructions.

contact glue

A day passed waiting until the glue hardened.


The contacts were installed successfully.


BMW E30, E32, E34, E36 connector plastic housings

The 3D-printed housing insert described in the article is compatible only with the straight contact terminal plastic housings.

BMW E30 housing 61131378401 61131378402 61131378403 61131378410

The other housings require angled contact terminals: 61131378416 (black), 61131378417 (white), 61131378418 (gray), 61131378419 (yellow).

Custom bracket to install 6-speed transmission in BMW E30

This transmission bracket (cross member) can be used for installation of a 6-speed transmission in a BMW E30. It should work with the Getrag 420G, ZF GS6-37DZ and ZF GS6-37BZ.

Weld all parts together and paint them.


The 6-speed transmission bracket for the E30 dimensions.

Use 4 square-headed M8X18 bolts (23711130250) to connect the bracket to the chassis.

BMW E30 325i auto to manual 6-speed ZF GS6-37DZ transmission swap

A typical E30 325i automatic transmission swap to manual with the Getrag 260

A typical manual transmission swap uses the 5-speed Getrag 260 transmission from a manual E30.
Here is the list of the parts that are required for a swap:

  • Transmission input shaft seal
  • Engine rear main seal
  • Transmission
  • Transmission oil
  • Transmission crossmember 23711176574
  • Transmission mounts 23711175939 x2
  • Transmission mount nuts 07119905515 x4
  • Washer 33311108205 x2
  • Washer 07119904115 x2
  • Shifter assembly
  • Selector rod | Shift link w/washers and clips
  • Shift handle
  • Shift knob and emblem
  • Nylon cup and bushing
  • Shift lever insulating rubber boot
  • Shift lever leather boot
  • Lock pin for support arm
  • Bushing mounting nut
  • Transmission to block bolts (set, you can reuse the bolts that came off your auto)
  • Giubo bolts
  • Giubo
  • Manual driveshaft
  • Center bearing assembly
  • Reverse light switch
  • M/T reverse lamps wiring harness BMW 23141220263
  • Clutch cruise control switch and wiring
  • Flywheel
  • Flywheel spacer plate
  • Flywheel bolts x8 (need new)
  • Throwout bearing
  • Clutch/Pressure Plate assembly
  • Clutch fork
  • Clutch fork bearing
  • Clutch master cylinder
  • Clutch master fluid line connector
  • Clutch slave cylinder
  • Clutch hardline
  • Firewall grommet
  • Clutch soft line
  • Brake pedal 35211154729
  • Clutch pedal 35311156838
  • Clutch and brake pedal rubber pads 35211108634
  • Hex bolt for the pedals M10x175 07119912721

The most expensive parts are the Getrag 260, manual driveshaft, shifter and selector rod, flywheel, and clutch. It’s very difficult to find a Getrag 260 that doesn’t need a rebuild. BMW sells a refurbished one for $3,500.

If you have to change so many parts, isn’t it better to install a newer 6-speed transmission with its driveshaft, shifter, clutch, differential, and linking parts?

GS6-37DZ transmission

A newer one can be the European diesel transmission GS6-37DZ. This swap was pioneered by Wanganstyle.

This article describes only the Project Research stage. I’m trying to collect as much information as possible. I don’t know what will work and what won’t.

The GS6-37 is a 6-speed manual transmission manufactured by ZF Friedrichshafen AG and Getrag. It is designed for longitudinal engine applications and is rated to handle up to 370 newton metres (273 lbf⋅ft) of torque.

Gear ratios

Gear ratios for Getrag 260
1 2 3 4 5 R
3.83 2.20 1.40 1.00 0.81 3.71
Gear ratios for diesel GS6-37DZ
1 2 3 4 5 6 R
5.14 2.83 1.79 1.26 1.00 0.83 4.64
Gear ratios for gasoline GS6-37BZ
1 2 3 4 5 6 R
4.350 2.496 1.665 1.234 1.000 0.851 3.926


Which differential ratio is the best?

  • The acceleration in the 2, 3, 4 gears should be similar to the Getrag 260 + a 4.10 or 3.73 differential.
  • The 6th gear should be good for low-RPM freeway cruising.
  • The 1st gear should be usable.

The Getrag 260 with a 4.10 differential compared to the GS6-37DZ with a 3.15 differential:

Differential ratios 4.1 3.15

The Getrag 260 with a 3.73 differential compared to the GS6-37DZ with a 3.07 differential:

Differential ratios 3.73 3.07

It’s possible to find Z3 3.07 and 3.15 medium 188mm differentials. They have Torsen LSDs and require little maintenance for regular driving. They are more modern and have few miles as the regular Z3 was a weekend car. The Z3M had a 3.23 clutch-type LSD differential.

Getrag 260 and GS6-37DZ bellhousing comparison

Looking at the different variants of the GS6-37DZ transmission, only the earlier variants GS6-37DZ — THES (23007562730) and GS6-37DZ — TJEM (23007565194) can be used.

Compatible transmissions were installed with the M47 engine:


  • BMW E87 (118d, 120d)
  • BMW 320Cd E46
  • BMW E90/E91 (318d (M47N2), 320d (M47N2))
  • BMW E60/E61 (520d)
  • BMW E60 LCI/E61 LCI (520d (M47N2))


  • BMW 120d E87
  • BMW E46 (320Cd/td, 320d (M47N))
  • BMW E90/E91 (320d M47N2)
  • BMW E60/E61 (520d)

The newer versions of the GS6-37DZ (TJEF, TJEJ) that came with the engine N47 have the starter protrusion at the bottom, not at the top. They are not compatible.


The main advantage of the GS6-37DZ (D – diesel) over its sibling GS6-37BZ (B – benzin, petrol, gasoline) is the slant(tilt) degree of 20°. The slant degree of the M20 engine in the 325i is the same. The engine doesn’t stand vertically to save on the height. The GS6-37BZ has the slant degree of 30° – it’s compatible with the M30, M50, etc.

Getrag 260 and GS6-37DZ bellhousing comparison

As you can see, the GS6-37DZ has more bellhousing holes than the Getrag 260: 2 at the bottom and 1 pin at the top.

The transmission will be mated with the BMW M20 engine. The top pin interferes with the starter. But the M20 starter won’t probably work with the GS6-37DZ flywheel, so it’ll be replaced anyway.

BMW M20 engine rear

The bottom part of the M20 engine can be disconnected. It’s called the Bowl reinforcement (11141286342). An additional hole can be drilled and tapped in it, but there is no casting for it in the back of the reinforcement. Maybe, a nut can be used.


Transmission drive shaft differences

The driveshaft in the Getrag 260 has 10 splines, the driveshaft of the GS6-37DZ has 22 splines. So, a clutch disk should have 22 splines too.
The driveshaft in the Getrag 260 is longer and its pilot bearing is inserted into the crankshaft of the engine.
The driveshaft in the GS6-37DZ is shorter and its pilot bearing is in the flywheel.

Rear transmission mount brace (cross member)

The GS6-37DZ is mounted differently. The top (22316760303) E90 Transmission supporting bracket is available. Then, the mounting style and dimensions should be similar to the 6-speed Getrag 420G, for which some companies create braces.

GS6-37DZ rear mounting brace

A custom bracket for a 6-speed transmission in a BMW E30

Drive Shaft

The automatic E36 M3 driveshaft

The GS6-37DZ is 635 mm long. It is 120 mm longer than the Getrag 260. The automatic transmission ZF HP4-22 is 685 mm long.
The length of the drive shaft for the GS6-37DZ must be close to 1367 mm.

gs6-37bz dimensions

The E36 M3 automatic drive shaft (26112228212) should be compatible with its length of 1346 mm. It’s about 20 mm short.

E36 M3 auto drive shaft 26112228212

Also, the European E36 automatic drive shaft (26111227441) should be compatible too with its length of 1345 mm.

It seems that many 4-bolt E36 drive shafts have the same rear half. It’s only the front half that has different lengths. Both u-joints are located in the rear half, and they wear out. The front half is just a pipe with connectors.

Some people indicate that the manual Z4 front part(26117514468) can be used with the rear E36 part to provide the perfect length.

The M3 giubo (26112226527) is 96mm in diameter, 35 mm wide, and it is connected using M12 bolts.

The drive shaft center support mount must be from the E30 (26121226723). Probably, it should be mounted backwards.

The front section of the E46 320d driveshaft + the rear section of the E30 rear driveshaft

The rear section of all E30 driveshafts is the same, so a good automatic E30 driveshaft can be used.

The front section must come from a European e46 320d produced from 03/2003 (26117523929). The 6-speed E46 330i and M3 driveshafts are not compatible.

e46 320d 26117523929 + e30 drive shaft

e46 320d drive shaft 7523929


The shifter carrier and selector rod for this conversion must be custom.

Shifter carrier and Selector rod

Zionsville BMW E36 6 Speed Conversion Shift Arm Support Bracket.
Zionsville shifter carrier

A longer shifter carrier can be shortened and welded.
gs6-37 Shifting carrier welded

Here is a shifter carrier and selector rod for the E36 + GS6-37BZ. It should be compatible, too.
E36 GS6-37BZ

Flywheel and Clutch

The LuK dual-mass flywheel.
LuK DMF053 twin-mass flywheel

The original M20 flywheel.
M20 flywheel

These items should work:

There are aftermarket flywheel and clutch kits.
Clutch Masters 2003-2006 : 3.0L E46 (6-Speed)

Clutch Slave Cylinder

The master clutch cylinder is from the E30. The slave clutch cylinder is from the E36 325i(21521159045). There should be other compatible clutch cylinders.


The starter from the E46(12412354693) is compatible with the E46 flywheel.

The Bosch SR0448N from the E46 has the threaded holes for the bolts, no E30-style nuts will be needed.

The E30 starter(12411726504) specifications:
Rated Power [kW]: 1,4
Voltage [V]: 12
Number of Teeth: 9
Rotation Direction: Clockwise rotation
Number of mounting bores: 2
Flange Ø [mm]: 76
Number of Thread Bores 0
Plug Type ID: M6
Clamp: M8
Length 3 [mm]: 25
Length [mm]: 184.21

The E46 starter(12412354693) specifications:
Rated Power [kW]: 1,4
Voltage [V]: 12
Number of Teeth: 9
Rotation Direction: Clockwise rotation
Number of mounting bores: 3
Flange Ø [mm]: 76
Number of Thread Bores: 2
Plug Type ID: M6
Clamp: M8
Length 3 [mm]: 23
Length [mm]: 173.21

Reverse Switch

The E30 reverse switch(23141354071) has a correct connector.

Lube and Oil

The transmission splines must be lubed.

Rebuilding the GS6-37DZ transmission

The transmission bellhousing can be opened using this crude technology.
It seems that the bellhousings of the GS6-37DZ and GS6-37BZ can be exchanged to get the gasoline engine gear ratios.

gs6-37bz bellhousing open

In addition to the new seals and new bolts, these bearings are required for a rebuild:

  • 2 bearings for the input and main shafts NSK 35TM10-A-1CG28U01 (BMW 23111224806) 35x80x20mm
  • 1 bearing for the main shaft by ZF. 1833728 IC 32272-MB90A IC 0735298254 ZF 1833728 DAF, 5001847341 DAF 42535016 IVECO 5001847341 IVECO 8869673 IVECO, 8869673EB12-6460 IVECO 32272-MB90A NISSAN 322754716R RVI, 50 01 847 341 RVI 23×40,40x21mm
  • 1 bearing for the lay shaft SKF BA2B 633910 C (BMW 23111224808) 29.8x67x31mm
  • 1 bearing INA 712 0613 10 (INA F-223773) 27x44x23.2mm

NSK 35TM10-A-1CG28U01, SKF BA2B 633910 C(23111224808)

NSK 35TM10-A-1CG28U01 (BMW 23111224806)

SKF BA2B 633910 C (BMW 23111224808)

bearing INA 712 0613 10 (INA F-223773)

Schematics of the GS6-37DZ

gs6-37dz schematics

Qt5 GUI on Intel Edison

If you want to create a graphical user interface (GUI) for the Intel Edison, it’s better to use the popular Qt cross-platform framework.
Then you will be able to develop and debug a program on your powerful desktop computer and then deploy it to your Edison and test it there.

My hardware and Linux kernel configuration

I used my shield that has a SSD1322 based display connected to the SPI bus and 4 push buttons connected to GPIOs.
The display is controlled through the Linux framebuffer kernel module fbtft.
The buttons are controlled through the gpio-keys module.

QT5 Yocto Client package installation

My Edison source packages are located in the folder ~/edison.

I increased the size of my root Edison partition in the file ~/edison/poky/meta-intel-edison/meta-intel-edison-distro/recipes-core/images/


I downloaded a new meta package meta-qt5.
At the time of writing, the Intel Edison packages used the openembedded branch called “dizzy”.

cd ~/edison/poky
git clone -b dizzy

I modified the configuration file ~/edison/build_edison/conf/auto.conf
I added some Qt packages and enabled some configuration options. I may select something else in the future.

The Intel Edison has no GPU. So, the Qt OpenGL based modules don’t work. Among them are Qt Quick and Qt Declarative and the packages that depend on these two.

DISTRO = "poky-edison"
MACHINE = "edison"
DISTRO_FEATURES_append = " alsa bluetooth x11"
PACKAGE_CLASSES = "package_ipk"

IMAGE_INSTALL_append = " tinyb"
IMAGE_INSTALL_append = " tinyb-dev"
#IMAGE_INSTALL_append = " openzwave"
IMAGE_INSTALL_append = " bacnet-stack"
IMAGE_INSTALL_append = " libmodbus"
IMAGE_INSTALL_append = " mc"
IMAGE_INSTALL_append = " qtbase qtbase-fonts \
    qtbase-plugins \
    qtbase-tools \
IMAGE_INSTALL_append_core2-32 = " libft4222"
IMAGE_INSTALL_append_edison = " libft4222"

PACKAGECONFIG_DISTRO_append_pn-qtbase = " linuxfb icu alsa pulseaudio sql-sqlite"

I modified the file ~/edison/build_edison/conf/bblayers.conf and added the meta-qt5 layer at the end of BBLAYERS.



  ${TOPDIR}/../poky/meta \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-arduino \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-edison-bsp \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-edison-distro \
  ${TOPDIR}/../poky/meta-intel-iot-devkit \
  ${TOPDIR}/../poky/meta-intel-iot-middleware \
  ${TOPDIR}/../poky/meta-java \
  ${TOPDIR}/../poky/meta-oic \
  ${TOPDIR}/../poky/meta-openembedded/meta-filesystems \
  ${TOPDIR}/../poky/meta-openembedded/meta-networking \
  ${TOPDIR}/../poky/meta-openembedded/meta-oe \
  ${TOPDIR}/../poky/meta-openembedded/meta-python \
  ${TOPDIR}/../poky/meta-openembedded/meta-ruby \
  ${TOPDIR}/../poky/meta-openembedded/meta-webserver \
  ${TOPDIR}/../poky/meta-yocto \
  ${TOPDIR}/../poky/meta-yocto-bsp \
  ${TOPDIR}/../poky/meta-qt5 \
  ${TOPDIR}/../poky/meta \
  ${TOPDIR}/../poky/meta-yocto \

Then the usual Edison image compilation stage.
You must insert yourself into the group “dialout” on Linux Ubuntu to have permissions to run the script “”.

cd ~/edison/poky/
source oe-init-build-env ../build_edison/
bitbake edison-image u-boot
../poky/meta-intel-edison/utils/flash/ .
Qt5 Host cross-platform SDK compilation

I modified the file ~/edison/poky/meta-qt5/recipes-qt/packagegroups/
and deleted all packages from the list RDEPENDS_${PN} that depend on Qt Quick and Qt Declarative. My list may change in the future.

RDEPENDS_${PN} += " \
    packagegroup-core-standalone-sdk-target \
    libsqlite3-dev \
    qtbase-dev \
    qtbase-fonts \
    qtbase-mkspecs \
    qtbase-plugins \
    qtbase-staticdev \
    qtconnectivity-dev \
    qtconnectivity-mkspecs \
    qtimageformats-dev \
    qtimageformats-plugins \
    qtserialport-dev \
    qtserialport-mkspecs \
    qtsvg-dev \
    qtsvg-mkspecs \
    qtsvg-plugins \
    qtsystems-dev \
    qtsystems-mkspecs \
    qttools-dev \
    qttools-mkspecs \
    qttools-staticdev \
    qttools-tools \
    qtxmlpatterns-dev \
    qtxmlpatterns-mkspecs \

I compiled the SDK.

cd ~/edison/poky/
source oe-init-build-env ../build_edison/
bitbake meta-toolchain-qt5

I ran the new SDK installer in ~/edison/build_edison/tmp/deploy/sdk


It installed the SDK into the default folder /opt/poky-edison/1.7.3
I opened the directory /opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux
and created symlinks. Without them, my system compiler and linker in /usr/bin are called instead.

ln -s i586-poky-linux-g++ g++
ln -s i586-poky-linux-cpp cpp
ln -s i586-poky-linux-ld ld
ln -s i586-poky-linux-gdb gdb
Qt Creator IDE installation

I installed Qt Creator from, as the version of Qt in the Yocto package is 5.3.2.
But, probably, the one from the Linux distribution should work as well.
I installed it in the directory ~/Qt.

I modified the script ~/Qt/Tools/QtCreator/bin/, so it will load the environment variables for the SDK. I put the new line at the very top of the script.

source /opt/poky-edison/1.7.3/environment-setup-core2-32-poky-linux
#! /bin/sh

On my Edison I created the user “farit” and added him to the group “video” that has permissions to write to the framebuffer and read input buttons. His home directory on the Edison is “/home/farit”.

usermod -a -G video farit

I modified the menu link on my desktop to run the script ~/Qt/Tools/QtCreator/bin/ to open Qt Creator.

In Qt Creator, I opened the menu Tools -> Options -> Devices and added my Edison device as “Generic Linux Device”.
I named it “edison” and provided the name and password combination for the user “farit”. Then tested the connection.

I opened the menu Tools > Options > Build & Run.
I opened the tab “Qt Versions”, clicked on the button “Add” and browsed to select qmake


I opened the tab “Compilers”, clicked on the button “Add”, selected “GCC” and browsed to select gcc


I opened the tab “Debuggers”, clicked on the button “Add” and browsed to select gdb. I named it GDB.


I opened the tab “Kits”, clicked on the button “Add” and filled in the form.
I selected the values for Qt, Compiler, Debugger that I just defined in the previous steps.

Name: edison
Device type: Generic Linux Device
Device: edison
Sysroot: /opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux
Compiler: GCC
Debugger: GDB
Qt Version: Qt 5.3.2

Sample Qt program

I clicked on “New Project” and selected Applications -> Qt Widgets Application.
I named it “testik”.

In, I added the path “/home/farit”, where Qt Creator will copy the executable on the Edison.

# Project created by QtCreator 2016-06-02T14:32:35

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = testik

SOURCES += main.cpp\

HEADERS  += mainwindow.h

FORMS    += mainwindow.ui

target.path = /home/farit
INSTALLS += target

In mainwindow.h, I only added the prototype for the function “keyPressEvent”.


#include <QMainWindow>

namespace Ui {
class MainWindow;

class MainWindow : public QMainWindow

    explicit MainWindow(QWidget *parent = 0);

    void keyPressEvent(QKeyEvent *);

    Ui::MainWindow *ui;

#endif // MAINWINDOW_H

main.cpp is the default one

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
    QApplication a(argc, argv);
    MainWindow w;;

    return a.exec();

In mainwindow.cpp, I implemented the function “keyPressEvent”.

#include <QKeyEvent>
#include <QDebug>
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    ui(new Ui::MainWindow)

    delete ui;

void MainWindow::keyPressEvent(QKeyEvent *event)
    if(event->key() == Qt::Key_Up)
        if (event->isAutoRepeat()) {
             ui->myLabel->setText("You auto pressed Up");
        else {
            ui->myLabel->setText("You pressed Up");
    else if(event->key() == Qt::Key_Down)
        ui->myLabel->setText("You pressed Down");

In mainwindow.ui, I created a label “myLabel”.
In the MainWindow properties, I clicked on the “palette” button and made the background transparent by setting the opacity to 0 for “Window”. I changed “WindowText” to white by selecting the color #FFFFFF.

In the “Projects” tab in the “Run” section, I added the arguments for the executable, so it can write to the default framebuffer /dev/fb0 and read input from /dev/input/event0.

-platform linuxfb -plugin EvdevKeyboard
Qt test program running
Qt test program running

Connecting Buttons to Intel Edison

The Intel Edison has multiple GPIO pins, which can be connected to push buttons.

I use the gpio-keys Linux driver. Each button emits an event, which can be read in the same way as a keyboard event.


Here is the schematic of my shield with the push buttons. The buttons are SW2, SW3, SW4, SW5.

Shiled with push buttons for Intel Edison
Shield with Push Buttons for Intel Edison

GPIO pullup code

As you can see, one side of a button is connected to the ground and another to a GPIO pin. I could use external pullup resistors, but the Intel Edison has built-in pullup resistors, which I activated instead. The current kernel code doesn’t have a function for setting a pullup resitor, so I had to write my own.

I added the function lnw_gpio_set_pull_alt into the Linux kernel file drivers/gpio/gpio-langwell.c

void lnw_gpio_set_pull_alt(unsigned gpio, int value, int pullup_value)
	struct lnw_gpio *lnw;
	u32 flis_offset;
	u32 flis_value;
	unsigned long flags;

	/* use this trick to get memio */
	lnw = irq_get_chip_data(gpio_to_irq(gpio));
	if (!lnw) {
		pr_err("langwell_gpio: can not find pin %d\n", gpio);
	if (gpio < lnw->chip.base || gpio >= lnw->chip.base + lnw->chip.ngpio) {
		dev_err(lnw->, "langwell_gpio: wrong pin %d to config alt\n", gpio);
	gpio -= lnw->chip.base;

	if (lnw->type != TANGIER_GPIO) {

	flis_offset = lnw->get_flis_offset(gpio);
	if (WARN(flis_offset == -EINVAL, "invalid pin %d\n", gpio))
		return -EINVAL;
	if (is_merr_i2c_flis(flis_offset))

	spin_lock_irqsave(&lnw->lock, flags);
	flis_value = get_flis_value(flis_offset);
	if (value) {
		flis_value |= PULLUP_ENABLE;
		flis_value &= ~PULLDOWN_ENABLE;
	} else {
		flis_value |= PULLDOWN_ENABLE;
		flis_value &= ~PULLUP_ENABLE;
	//flis_value |= PUPD_VAL_50K;
    flis_value |= pullup_value;

	set_flis_value(flis_value, flis_offset);
	spin_unlock_irqrestore(&lnw->lock, flags);

Also added its declaration and the constants for the pullup values into the Linux kernel file include/linux/lnw_gpio.h

#define PUPD_VAL_2K	(0 << 4)
#define PUPD_VAL_20K	(1 << 4)
#define PUPD_VAL_50K	(2 << 4)
#define PUPD_VAL_910	(3 << 4)
void lnw_gpio_set_pull_alt(unsigned gpio, int value, int pullup_value);

I added the definitions for my 4 push buttons into the Linux kernel file arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c
The buttons are for GPIO12, GPIO13, GPIO182, GPIO183.
Each button configured as a general purpose pin by using the multiplexor code: lnw_gpio_set_alt(gb[i].gpio, LNW_GPIO);
The pullup value is set to 50K using the previously written function: lnw_gpio_set_pull_alt(gb[i].gpio, 1, PUPD_VAL_50K);

 * platform_gpio_keys.c: gpio_keys platform data initilization file
 * (C) Copyright 2008 Intel Corporation
 * Author:
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.

#include <linux/input.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/platform_device.h>
#include <asm/intel-mid.h>
#include "platform_gpio_keys.h"
#include <linux/lnw_gpio.h>

 * we will search these buttons in SFI GPIO table (by name)
 * and register them dynamically. Please add all possible
 * buttons here, we will shrink them if no GPIO found.
static struct gpio_keys_button gpio_button[] = {
                .code = KEY_POWER,
                .gpio = -1, /* GPIO number */
                .active_low = 1,
                .desc = "power_btn",/*Button description*/
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 3000,
                .code = KEY_UP,
                .gpio = 182,
                .active_low = 1,
                .desc = "up_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
                .code = KEY_DOWN,
                .gpio = 12,
                .active_low = 1,
                .desc = "down_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
                .code = KEY_ESC,
                .gpio = 13,
                .active_low = 1,
                .desc = "back_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
                .code = KEY_ENTER,
                .gpio = 183,
                .active_low = 1,
                .desc = "down_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,

static struct gpio_keys_platform_data gpio_keys = {
	.buttons	= gpio_button,
	.rep		= 1,
	.nbuttons	= -1, /* will fill it after search */

static struct platform_device pb_device = {
	.name		= DEVICE_NAME,
	.id		= -1,
	.dev		= {
		.platform_data	= &gpio_keys,

 * Shrink the non-existent buttons, register the gpio button
 * device if there is some
static int __init pb_keys_init(void)
	struct gpio_keys_button *gb = gpio_button;
	int i, num, good = 0;

	num = sizeof(gpio_button) / sizeof(struct gpio_keys_button);
	for (i = 0; i < num; i++) {
		pr_info("info[%2d]: name = %s, gpio = %d\n",
			 i, gb[i].desc, gb[i].gpio);
		if (gb[i].gpio == -1)

                if (gb[i].gpio > 0) {
                    lnw_gpio_set_alt(gb[i].gpio, LNW_GPIO);
                    lnw_gpio_set_pull_alt(gb[i].gpio, 1, PUPD_VAL_50K);

		if (i != good)
			gb[good] = gb[i];

	if (good) {
		gpio_keys.nbuttons = good;
		return platform_device_register(&pb_device);
	return 0;
Testing Buttons

When the new Linux kernel was recompiled and flashed to the Intel Edison, I could verify the buttons.

I checked the files in the directory for the GPIO12: /sys/kernel/debug/gpio_debug/gpio12
current_pinmux was mode0
current_pullmode was pullup
current_pullstrength was 50k

I ran the command and tried to push on the buttons. I received some symbols. It meant that the buttons worked.

cat /dev/input/event0

I copied the file evtest.c from and compiled it on my Edison.

gcc evtest.c -o evtest

I ran the program evtest and got expected results.

# ./evtest /dev/input/event0
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "gpio-keys"
Supported events:
  Event type 0 (Sync)
  Event type 1 (Key)
    Event code 1 (Esc)
    Event code 28 (Enter)
    Event code 103 (Up)
    Event code 108 (Down)
  Event type 20 (Repeat)
Testing ... (interrupt to exit)
Event: time 1465107735.152126, type 1 (Key), code 108 (Down), value 1
Event: time 1465107735.152126, -------------- Report Sync ------------
Event: time 1465107735.344576, type 1 (Key), code 108 (Down), value 0
Event: time 1465107735.344576, -------------- Report Sync ------------
Event: time 1465107743.087594, type 1 (Key), code 103 (Up), value 1
Event: time 1465107743.087594, -------------- Report Sync ------------
Event: time 1465107743.332727, type 1 (Key), code 103 (Up), value 0
Event: time 1465107743.332727, -------------- Report Sync ------------
Event: time 1465107745.988003, type 1 (Key), code 28 (Enter), value 1
Event: time 1465107745.988003, -------------- Report Sync ------------
Event: time 1465107746.187692, type 1 (Key), code 28 (Enter), value 0
Event: time 1465107746.187692, -------------- Report Sync ------------
Event: time 1465107748.336925, type 1 (Key), code 1 (Esc), value 1
Event: time 1465107748.336925, -------------- Report Sync ------------
Event: time 1465107748.552085, type 1 (Key), code 1 (Esc), value 0
Event: time 1465107748.552085, -------------- Report Sync ------------

I may change the event key codes for the buttons. I can get the codes from the same file

Accessing /dev/input/event0 as a regular user

By default, the device /dev/input/event0 is accessible only by root.

I created the file /etc/udev/rules.d/input.rules
I chose the group “video” as I will use a user who should be able to access both the video device and the input device.

KERNEL=="event*", NAME="input/%k", MODE="660", GROUP="video"

I also created the file with the same contents poky/meta/recipes-core/systemd/systemd/input.rules
and added it into the recipe poky/meta/recipes-core/systemd/

SRC_URI = "git://;branch=master;protocol=git \
           file://binfmt-install.patch \
           file://systemd-pam-configure-check-uclibc.patch \
           file://systemd-pam-fix-execvpe.patch \
           file://systemd-pam-fix-fallocate.patch \
           file://systemd-pam-fix-mkostemp.patch \
           file://optional_secure_getenv.patch \
           file://uclibc-sysinfo_h.patch \
           file://uclibc-get-physmem.patch \
           file://0001-add-support-for-executing-scripts-under-etc-rcS.d.patch \
           file://0001-missing.h-add-fake-__NR_memfd_create-for-MIPS.patch \
           file://0001-Make-root-s-home-directory-configurable.patch \
           file://0001-systemd-user-avoid-using-system-auth.patch \
           file://0001-journal-Fix-navigating-backwards-missing-entries.patch \
           file://0001-tmpfiles-make-resolv.conf-entry-conditional-on-resol.patch \
           file://0001-build-sys-do-not-install-tmpfiles-and-sysusers-files.patch \
           file://0001-build-sys-configure-the-list-of-system-users-files-a.patch \
           file://touchscreen.rules \
           file://input.rules \
           file://00-create-volatile.conf \
           file://init \
           file://run-ptest \