beepberry-rp2040/README.md

24 KiB

title layout
Beepy Firmware default

Beepy Firmware

User Guide

In the Beepy device, separate from the Raspberry Pi running Linux, there is an RP2040 microcontroller chip. This chip controls basic hardware input and output functions, including keyboard and touchpad input. The firmware discussed here runs directly on the RP2040. It cooperates with the Beepy keyboard driver beepy-kbd to provide key input and other functionality to Linux running on the Raspberry Pi.

See keyboard driver reference beepy-kbd for more information on keymaps.

Flashing firmware directly

If you're setting up a new Beepy device, it's recommended to flash the firmware directly from the latest firmware release.

  1. Download the latest firmware image

  2. Turn the power switch off. With the device facing up, slide the power switch in the bottom-left hand corner to the left.

Diagram showing direction of power switch

  1. Connect the Beepy to your computer via USB-C.

  2. Locate the "End Call" key. It is the rightmost key on the top row of four function keys.

  3. While holding the "End Call" key, slide the power switch back on to enter firmware flash mode. In firmware flash mode, the LED will light up, and the Beepy will present itself as a USB mass storage device on your computer.

  4. Copy the firmware image onto the presented drive just like a normal file. When copying is complete, Beepy will automatically flash and reboot with the new firmware.

If you're setting up a new device, you can proceed with the rest of the steps in the quick start guide.

Firmware update utility

Once you have an up-to-date firmware image installed, and your Linux system configured, you can update firmware directly on the device using firmware distribution packages. When a new firmware image is released, there will be an update available for this package, beepy-fw. Running sudo apt-get upgrade will upgrade this firmware package. Due to the way that package updates work, firmware updates must be applied using an updater utility.

The package comes with two primary files, a copy of the new firmware, and the firmware updater utility update-beepy-fw. Copies of new firmware are installed into the directory /usr/lib/beepy-firmware. After updating the firmware package, apply the firmware update by running sudo beepy-firmware.

For example, if you have firmware 3.0 installed, and update the beepy-fw package to version 3.1, running the firmware updater utility will look like this:

$ sudo update-beepy-fw

Beepy firmware updater
Installed firmware: 3.0
Newer firmware in /usr/lib/beepy-firmware:
[ 0] 3.1: beepy_3.1.hex
Enter number of newer firmware to install: 

In this case, you would want to type 0, then press Enter to install version 3.1:

Enter number of newer firmware to install: 0
Installing /usr/lib/beepy-firmware/beepy_3.1.hex...
Update applied. Please wait until system powers back on in 30 seconds

After a successful firmware install, the system will shut down and apply the firmware update. Please wait until the update is installed and for the system to reboot automatically. There will be a delay of 30 seconds between the shutdown of the Raspberry Pi and reboot to apply the firmware.

If the firmware cannot be written, the update will fail and new firmware will not be applied. But if you do get into a state with a broken firmware, you can always follow the steps listed above in the section Flashing firmware directly to reset or update the firmware.

If your firmware version is up to date, running beepy-firmware will report that there are no newer versions available to install:

Beepy firmware updater
Installed firmware: 3.1
Newer firmware in /usr/lib/beepy-firmware:
    (None found)

Working with the keyboard driver

When new functionality is added to the Beepy keyboard driver, there may be a corresponding update to the firmware as well. However, the keyboard driver will automatically check the installed firmware version. If there is a hard incompatibility, the installation of the keyboard driver will fail:

error: driver requires firmware version 3.1
    (firmware has version 3.0)
update the beepy-fw package and run
    /sbin/update-beepy-fw
or manually install the firmware release from
    https://github.com/ardangelo/beepberry-rp2040/releases/latest

In this case, update the beepy-fw driver and run sudo update-beepy-fw (see Firmware update utility), or manually flash the updated firmware release (see Flashing firmware directly).

Developer Reference

This section is intended for developers to reference for working directly with the firmware over I2C, at a lower level than the keyboard driver. For changing firmware settings from Linux through control files, see the keyboard driver reference beepy-kbd.

This firmware is based off of the i2c_puppet firmware for keyboard interaction and communication. It adds several Beepy-specific features and improvements, including sticky modifier keys, real-time-clock support, self-update capability, deep sleep mode, and touchpad tuning.

Original readme for i2c_puppet including wiring and USB reference:

https://github.com/solderparty/i2c_puppet/blob/main/README.md

Building from source

The code depends on the Raspberry Pi Pico SDK, which is added as a submodule. You can either perform a recursive submodule init, or rather follow these steps in the root of the repository:

cd 3rdparty/pico-sdk
git submodule update --init
cd 3rdparty/pico-flashloader
git submodule update --init
cd 3rdparty/pico-extras
git submodule update --init

Run cmake to build the firmware:

mkdir build
cd build
cmake -DPICO_BOARD=beepy ..
make

In the build directory, you will find the files i2c_puppet.uf2 and app/firmware.hex. The primary firmware file is i2c_puppet.uf2, that can be flashed directly over USB. app/firmware.hex is an Intel HEX encoded firmware file that can be applied on-device after converting line format to Unix and prepending a firmware header:

cp app/firmware.hex beepy.hex
dos2unix beepy.hex
sed -i '1s;^;+Beepy dev build\n;' beepy.hex
cat beepy.hex | sudo tee /sys/firmware/beepy/update_fw

See keyboard driver reference beepy-kbd for more information on using /sys/firmware/beepy/update_fw.

Key values

Firmware has been updated to use BB10-style sticky modifier keys. It has a corresponding kernel module that has been updated to read modifier fields over I2C.

Holding a modifier key (shift, physical alt, Symbol) while typing an alpha keys will apply the modifier to all alpha keys until the modifier is released.

One press and release of the modifier will enter sticky mode, applying the modifier to the next alpha key only. If the same modifier key is pressed and released again in sticky mode, it will be canceled.

Call is mapped to Control. The Berry button is mapped to KEY_PROPS. Clicking the touchpad button is mapped to KEY_COMPOSE. Back is mapped to Escape. End Call is not sent as a key, but holding it will still trigger the power-off routine. Symbol is mapped to AltGr (Right Alt).

Physical alt does not send an actual Alt key, but remaps the output scancodes to the range 135 to 161 in QWERTY order. This should be combined with a keymap for proper symbol output. This allows symbols to be customized without rebuilding the firmware, as well as proper use of the actual Alt key.

See keyboard driver reference beepy-kbd for more information on keymaps.

Power draw readings

Approximate power draw readings, obtained using a USB-C power meter, in amps.

.000 Power switch off

.50 Pi booting
.25 Pi idle, wireless connected, keyboard backlight full
.20 Pi idle, wireless connected, keyboard backlight off
.09 Pi shut down from command line (Pi pin still powered)
.025 Pi pin unpowered, no deep sleep
.005 Pi pin unpowered, deep sleep

.027 Backlight brightness off
.030 Backlight brightness dim
.053 Backlight brightness half
.079 Backlight brightness full

.030 Touchpad disabled
.031 Touchpad enabled

Register reference

The device uses I2C slave interface to communicate, the address can be configured in app/config/conf_app.h, the default is 0x1F.

You can read the values of all the registers, the number of returned bytes depends on the register. It's also possible to write to the registers, to do that, apply the write mask 0x80 to the register ID (for example, the backlight register 0x05 becomes 0x85).

Some registers are read-only or write-only. For read-only registers, writes are discarded. For write-only registers, arbitrary byte is returned.

0x01 REG_ID_VER

Read-only, 1 byte.

The first nibble contains the major version and the second nibble contains the minor version of the firmware.

0x02 REG_ID_CFG

Read-write, 1 byte.

Bitmap of various settings that can be changed to customize the behavior of the firmware.

See REG_CF2 for additional settings.

  • Bit 7 CFG_USE_MODS: deprecated, unused
  • Bit 6 CFG_REPORT_MODS: deprecated, unused
  • Bit 5 CFG_PANIC_INT: unused
  • Bit 4 CFG_KEY_INT: Interrupt when a key is pressed
  • Bit 3 CFG_NUMLOCK_INT: Interrupt when numlock is pressed
  • Bit 2 CFG_CAPSLOCK_INT: Interrupt when capslock is pressed
  • Bit 1 CFG_OVERFLOW_INT: Innterrupt when event queue overflows
  • Bit 0 CFG_OVERFLOW_ON: Overwrite oldest event when overflow occurs

Defaut value: CFG_OVERFLOW_INT | CFG_KEY_INT

0x03 REG_ID_INT

Read-write, 1 byte.

On interrupt, this contains the cause.

  • Bit 7 Unused
  • Bit 6 INT_TOUCH Generated by trackpad motion
  • Bit 5 INT_GPIO Generated by input GPIO changing level
  • Bit 4 INT_PANIC Unused
  • Bit 3 INT_KEY Generated by a key press
  • Bit 2 INT_NUMLOCK Generated by Num Lock
  • Bit 1 INT_CAPSLOCK Generated by Caps Lock
  • Bit 0 INT_OVERFLOW Generated by FIFO overflow

After reading this register, write 0x00 to reset it.

For INT_GPIO, check REG_GIN to see which GPIO triggered the interrupt. The GPIO interrupt must first be enabled in REG_GIC.

0x04 REG_ID_KEY

Read-only, 1 byte.

Contains FIFO status and modifier key status.

  • Bit 7 Unused
  • Bit 6 KEY_NUMLOCK Num Lock enabled?
  • Bit 5 KEY_CAPSLOCK Caps Lock enabled?
  • Bits 0-4 KEY_COUNT Unread FIFO event count

0x05 REG_ID_BKL

Read-write, 1 byte.

Set keyboard backlight brightness from 0x00 for off to 0xff for full brightness. Full brightness draws approximately 25% more power on an idle Raspberry Pi (see Power draw readings), so a lower setting is recommended.

0x06 REG_ID_DEB

Unimplemented, 1 byte.

Keyboard debounce setting.

0x07 REG_ID_FRQ

Unimplemented, 1 byte.

Keyboard poll frequency.

0x08 REG_ID_RST

Read-write, 1 byte.

Access will cause RP2040 to reset.

0x09 REG_ID_FIF

Read-only, 2 bytes.

Return topmost event in FIFO. First byte contains key scancode. Second byte contains key state:

  • 0 KEY_STATE_IDLE
  • 1 KEY_STATE_PRESSED
  • 2 KEY_STATE_HOLD
  • 3 KEY_STATE_RELEASED
  • 4 KEY_STATE_LONG_HOLD

0x0A REG_ID_BK2

Unimplemented, 1 byte.

Secondary backlight control.

0x0B REG_ID_DIR

Read-write, 1 byte.

Cnotrols direction of the GPIO expander pins, each bit corresponding to one pin.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

Bit set to 1 configures pin as input, bit set to 0 configures pin as output.

Default value: 0xFF (all pins configured as input)

0x0C REG_ID_PUE

Read-write, 1 byte.

If a GPIO pin is configured as an input using REG_DIR, an optional pull-up/pull-down can be enabled. If pin is configured as output, its bit in this register has no effect.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

Bit set to 1 enables input pull for that pin, bit set to 0 disables input pull.

The direction of the pull is set in REG_PUD.

Default value: 0 (all pulls disabled)

0x0D REG_ID_PUD

Read-write, 1 byte.

If a GPIO pin is configured as an input using REG_DIR, an optional pull-up/pull-down can be enabled. If pin is configured as output, its bit in this register has no effect.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

Bit set to 1 sets input pull to pull-up for that pin, bit set to 0 sets input pull to pull-down.

Default value: 0xFF (all pulls set to pull-up, if enabled in REG_PUE and set to input in REG_DIR)

0x0E REG_ID_GIO

Read-write, 1 byte.

Contains the values of the GPIO Expander pins, each bit corresponding to one pin.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

If a pin is configured as an output in REG_DIR, writing to this register will change the value of that pin.

Reading from this register returns the value for both input and output pins.

0x0F REG_ID_GIC

Read-write, 1 byte.

If a GPIO pin is configured as an input using REG_DIR, an optional interrupt on value change can be enabled. If pin is configured as output, its bit in this register has no effect.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

Bit set to 1 triggers interrupt on value change, bit set to 0 disables interrupt.

On interrupt, GPIO that triggered the interrupt can be determined by reading REG_GIN. Additionally, the INT_GPIO bit will be set in REG_INT.

Default value: 0x00

0x10 REG_ID_GIN

Read-only, 1 byte.

On interrupt, this register contains which GPIO pin caused the interrupt, each bit corresponding to one pin.

The assignment of pin to MCU depends on the board, see boards/beepy.h for the assignments.

After reading, reset it to 0x00.

0x11 REG_ID_HLD

Read-write, 1 byte.

Sets time threshold for "press and hold" key state, in units of 10ms.

When a key is held for longer than this time, a key hold event is generated and enqueued into the FIFO event queue.

Default value: 30 (300ms)

0x12 REG_ID_ADR

Read-write, 1 byte.

Device's primary I2C bus address. On write, applies address change immediately. The next communication must be performed on the new address. Not saved after a reset.

Default value: 0x1F

0x13 REG_ID_IND

Read-write, 1 byte.

Sets time for which the INT/IRQ pin is held LOW on interrupt, in units of 1ms.

Default value: 1 (1ms)

0x14 REG_ID_CF2

Read-write, 1 byte.

Bitmap of various settings that can be changed to customize the behavior of the firmware.

See REG_CFG for additional settings.

  • 7 Unused
  • 6 Unused
  • 5 Unused
  • 4 Unused
  • 3 CF2_AUTO_OFF When driver state unloaded set to unloaded, wait for REG_ID_SHUTDOWN_GRACE seconds, then enter deep sleep
  • 2 CF2_USB_MOUSE_ON Send trackpad events over USB
  • 1 CF2_USB_KEYB_ON Send keyboard events over USB
  • 0 CF2_TOUCH_INT Generate interrupt for trackpad event. Should only be enabled when ready to accept touch input, otherwise touch events will accumulate and be sent all at once when interrupts are activated

Default value: 0 (cleared)

0x15 REG_ID_TOX

Read-only, 1 byte.

Trackpad X-axis position delta since the last time this register was read. Signed, in range[-128, 127]. Resets to 0 on read.

Recommended to read value when touch event received, or overflow may occur.

0x16 REG_ID_TOY

Read-only, 1 byte.

Trackpad Y-axis position delta since the last time this register was read. Signed, in range[-128, 127]. Resets to 0 on read.

Recommended to read value when touch event received, or overflow may occur.

0x17 REG_ID_ADC

Read-only, 2 bytes.

Raw battery level from ADC. 16-bit result:

(read(REG_ID_ADC)[1] << 8) | read(REG_ID_ADC)[0]

0x20 REG_ID_LED

Read-write, 1 byte.

Write the LED color registers before this register.

  • 0x00 LED off
  • 0x01 LED on
  • 0x02 LED flashes
  • 0x03 LED flashes until key is pressed

Mode 3, flash until key pressed, will overlay on top of an existing LED setting. For example, the following write sequence will set the LED to be solid red, but with a blue flash. Then, when a key is pressed, it will return to a solid red.

Set LED to solid red
REG_ID_LED_R <- 0xff
REG_ID_LED_G <- 0x00
REG_ID_LED_B <- 0x00
REG_ID_LED   <- 0x01

Set LED to flash blue until key pressed
REG_ID_LED_R <- 0x00
REG_ID_LED_G <- 0x00
REG_ID_LED_B <- 0xff
REG_ID_LED   <- 0x03

0x21 REG_ID_LED_R

Read-write, 1 byte.

Set LED red values unsigned in range [0, 255].

Color settings are applied after REG_LED is written.

0x22 REG_ID_LED_G

Read-write, 1 byte.

Set LED green values unsigned in range [0, 255].

Color settings are applied after REG_LED is written.

0x23 REG_ID_LED_B

Read-write, 1 byte.

Set LED blue values unsigned in range [0, 255].

Color settings are applied after REG_LED is written.

0x24 REG_ID_REWAKE_MINS

Read-write, 1 byte.

Write to shut down the Pi, then power-on in that many minutes. Useful for polling services in conjunction with REG_ID_STARTUP_REASON, such as with the beepy-poll service.

0x25 REG_ID_SHUTDOWN_GRACE

Read-write, 1 byte.

Due to the Beepy hardware design, there is no way to reliably determine the power state of the Pi. To avoid powering off the Pi while it is still running, this register is set to the number of seconds to wait between a shut down signal and Pi power off. This helps ensure that the Pi has time to process the power-off command and to shut down cleanly.

Used for shutdown followed by rewake and entering deep sleep mode.

Default: 30 (30s)

0x26 REG_ID_RTC_SEC

Read-write, 1 byte.

Read to get the seconds value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x27 REG_ID_RTC_MIN

Read-write, 1 byte.

Read to get the minutes value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x28 REG_ID_RTC_HOUR

Read-write, 1 byte.

Read to get the hour value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x29 REG_ID_RTC_MDAY

Read-write, 1 byte.

Read to get the day-of-month value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x2A REG_ID_RTC_MON

Read-write, 1 byte.

Read to get the month value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x2B REG_ID_RTC_YEAR

Read-write, 1 byte.

Year value is expressed in years since 1900.

Read to get the year value from the real-time clock. Write is committed after a write to REG_ID_RTC_COMMIT.

0x2C REG_ID_RTC_COMMIT

Write-only, 1 byte.

Write 1 to commit the values written to RTC registers to the real-time clock. Due to the Beepy hardware design, RTC settings are lost on power off of the RP2040 via power switch, or when entering deep sleep.

The keyboard driver beepy-kbd will update the RP2040 RTC with network time settings when available.

0x2D REG_ID_DRIVER_STATE

Read-write, 1 byte.

Write 1 to indicate that the keyboard driver is loaded and that shutdown commands will be read and processed. The keyboard driver implementation will set this value to 1 when the driver is loaded and 0 when unloaded.

In most cases, the driver is loaded on boot and unloaded during shutdown. For substantial power savings, the default-enabled CF2_AUTO_OFF setting will trigger when 0 is written to this register. After a 30 second wait to allow for the driver to potentially be reloaded, the RP2040 will send a shutdown signal to the Pi, wait for REG_ID_SHUTDOWN_GRACE seconds, then power off the Pi and enter deep sleep.

Default: 0

0x2E REG_ID_STARTUP_REASON

Read-only, 1 byte.

Contains the reason why the Pi was booted. Useful for polling services in conjunction with REG_ID_REWAKE_MINS.

  • 0 RP2040 initialized and booted Pi
  • 1 Power button held to turn Pi back on
  • 2 Rewake triggered from REG_ID_REWAKE_MINS
  • 3 During rewake polling, 0 was written to REG_ID_REWAKE_MINS. This allows the beepy-poll service to cancel the poll and proceeded with a full boot

0x30 REG_ID_UPDATE_DATA

Read-write, 1 byte.

RP2040 firmware is loaded in two stages. The first stage is a modified version of pico-flashloader. It allows updates to be flashed to the second stage firmware while booted. The second stage is the actual Beepy firmware.

Reading REG_ID_UPDATE_DATA will return an update status code

  • 0 UPDATE_OFF Update not in progress
  • 1 UPDATE_RECV In the process of receiving an update
  • 2 UPDATE_FAILED General update failure
  • 3 UPDATE_FAILED_LINE_OVERFLOW Firmware line overflowed buffer
  • 4 UPDATE_FAILED_FLASH_EMPTY Firmware flash request was empty
  • 5 UPDATE_FAILED_FLASH_OVERFLOW Firmware overflows allowed update region
  • 6 UPDATE_FAILED_BAD_LINE Failed to parse line in Intel HEX format
  • 7 UPDATE_FAILED_BAD_CHECKSUM Failed checksum

Firmware updates are flashed by writing byte-by-byte to REG_UPDATE_DATA:

  • Header line beginning with + e.g. +Beepy
  • Followed by the contents of an image in Intel HEX format

By default, REG_UPDATE_DATA will be set to UPDATE_OFF. After writing, REG_UPDATE_DATA will be set to UPDATE_RECV if more data is expected.

If the update completes successfully:

  • REG_UPDATE_DATA will be set to UPDATE_OFF
  • Shutdown signal will be sent to the Pi
  • Delay to allow the Pi to cleanly shut down before poweroff (configurable with REG_SHUTDOWN_GRACE)
  • Firmware is flashed and the system is reset

Please wait until the system reboots on its own before removing power.

If the update failed, REG_UPDATE_DATA will contain an error code and the firmware will not be modified.

The header line +... will reset the update process, so an interrupted or failed update can be retried by restarting the firmware write.

0x40 REG_ID_TOUCHPAD_REG

Read-write, 1 byte.

To send or recieve data from the touchpad firmware, write the desired touchpad register number. Touchpad registers can be found in the ADBS A320 datasheet. Then, read or write REG_ID_TOUCHPAD_VAL.

0x41 REG_ID_TOUCHPAD_VAL

Read-write, 1 byte.

To send or recieve data from the touchpad firmware, write the desired touchpad register number to REG_ID_TOUCHPAD_REG. Then, read or write this register.

0x42 REG_ID_TOUCHPAD_MIN_SQUAL

Read-write, 1 byte.

Reject touchpad input if surface quality as reported by touchpad sensor is lower than this threshold.

Default: 16

0x43 REG_ID_TOUCHPAD_LED

Read-write, 1 byte.

Touchpad LED power setting. "High" is recommended for reliable input.

  • 0x0 power medium
  • 0x3 power high
  • 0x5 power low

Default: 0x3 power high