mirror of
https://github.com/google/pebble.git
synced 2025-03-23 04:02:19 +00:00
576 lines
32 KiB
Markdown
576 lines
32 KiB
Markdown
|
---
|
|||
|
# Copyright 2025 Google LLC
|
|||
|
#
|
|||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
# you may not use this file except in compliance with the License.
|
|||
|
# You may obtain a copy of the License at
|
|||
|
#
|
|||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
#
|
|||
|
# Unless required by applicable law or agreed to in writing, software
|
|||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
# See the License for the specific language governing permissions and
|
|||
|
# limitations under the License.
|
|||
|
|
|||
|
title: Pebble Emulator 1/2 - QEMU for Pebble
|
|||
|
author: Ron Marianetti
|
|||
|
tags:
|
|||
|
- Down the Rabbit Hole
|
|||
|
date: 2015-01-30
|
|||
|
---
|
|||
|
|
|||
|
This is another in a series of technical articles provided by the members of the
|
|||
|
Pebble software engineering team. This article describes some recent work done
|
|||
|
at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short
|
|||
|
for Quick EMUlator, is a generic, open source machine emulator and virtualizer).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
> This post is part 1 of 2, and covers the development of the Pebble QEMU
|
|||
|
> emulator itself. Read
|
|||
|
> [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1]
|
|||
|
> for more information on PebbleKit JS emulation and
|
|||
|
> embedding the emulator in CloudPebble.
|
|||
|
|
|||
|
An early implementation of this emulator was used internally at Pebble over a
|
|||
|
year ago and was mentioned in a previous article published in April, 2014,
|
|||
|
[*How Pebble Converted 135,070 Customized Watchfaces for Pebble OS v2.0.*][4]
|
|||
|
However, it had languished due to lack of attention, and somewhere along the
|
|||
|
line changes made to the firmware had broke it altogether.
|
|||
|
|
|||
|
An emulator has many benefits, not only for outside developers but also for the
|
|||
|
internal team:
|
|||
|
|
|||
|
* New software can be loaded, executed, and tested much faster on the emulator
|
|||
|
than on actual hardware.
|
|||
|
* It greatly facilitates automated testing, due to the faster speed of
|
|||
|
execution, convenience, and ease of scripting.
|
|||
|
* For internal use, the emulator can be built to run firmware images that
|
|||
|
wouldn’t fit into the limited memory of the real hardware, enabling engineers
|
|||
|
to develop and test new features with extra debugging features included.
|
|||
|
* Some tasks, like testing localization changes, can be easily performed in
|
|||
|
parallel by launching a different emulator instance for each language.
|
|||
|
|
|||
|
[The QEMU open source project][2] was written by Fabrice Bellard. QEMU is a very
|
|||
|
flexible and powerful code base capable of emulating a wide variety CPUs, one of
|
|||
|
which is the ARM Cortex M3 that is inside the STM32F2xx microcontroller used in
|
|||
|
the Pebble. Building upon the core QEMU project as a base, Andre Beckus created
|
|||
|
a [fork][3] that adds in support for the hardware peripherals of the STM32F1xx
|
|||
|
series of microcontrollers. Pebble further built upon Andre Beckus’ fork for its
|
|||
|
emulator and added in support for the STM32F2xx series of microcontrollers as
|
|||
|
well as the specific hardware components in the Pebble that are outside the
|
|||
|
microcontroller, like the display and buttons. The result is a full-featured
|
|||
|
Pebble emulator capable of executing Pebble firmware images and native 3rd party
|
|||
|
applications.
|
|||
|
|
|||
|
## Interacting With the Emulator
|
|||
|
|
|||
|
When you launch the Pebble emulator on a host machine you are presented with a
|
|||
|
window displaying the contents of the Pebble display. You can interact with it
|
|||
|
using the arrow keys from the keyboard, which act as the 4 buttons on the Pebble
|
|||
|
(“back”, “up”, “down”, and “select”). You can also load in and run, unmodified,
|
|||
|
any third-party watchface or watchapp written for the Pebble.
|
|||
|
|
|||
|
A huge part of the value of the Emulator is in the additional features it
|
|||
|
provides for development and testing purposes, like being able to interact with
|
|||
|
the Pebble through a terminal and debug it using gdb. These capabilities are not
|
|||
|
even possible with a standard shipping Pebble. Before the emulator, the only way
|
|||
|
engineers at Pebble could accomplish this was to use custom “big boards”, which
|
|||
|
are specially built boards that include the standard Pebble components with the
|
|||
|
addition of a USB port and associated circuitry.
|
|||
|
|
|||
|
The emulator exposes three socket connections to the outside world. The first,
|
|||
|
the gdb socket, is built into the base QEMU framework itself and allows one to
|
|||
|
connect and debug the emulated CPU using gdb. The second, the console socket, is
|
|||
|
specific to Pebble emulation and is a simple terminal window into which you can
|
|||
|
see print output and issue commands to the Pebble OS. The third, the qemu
|
|||
|
channel, is also specific to Pebble emulation and is used for sending Bluetooth
|
|||
|
traffic and various other hardware sensor information such as the battery level,
|
|||
|
compass direction, and accelerometer readings.
|
|||
|
|
|||
|
## The GDB Socket
|
|||
|
|
|||
|
Built into QEMU is a gdb remote server that listens by default on port 1234 for
|
|||
|
connections from gdb. This remote server implements most of the basic debugging
|
|||
|
primitives required by gdb including inspecting memory, setting breakpoints,
|
|||
|
single-stepping, etc.
|
|||
|
|
|||
|
A critical aspect to debugging firmware running on the Pebble is the ability to
|
|||
|
see what each of the threads in the operating system is doing at any time. For
|
|||
|
this, gdb provides the “info threads” and related commands like “thread apply
|
|||
|
all backtrace”, etc. In order to gather the information about the threads
|
|||
|
though, the gdb remote server needs to understand where the thread information
|
|||
|
is stored in memory by the remote target, which of course is operating system
|
|||
|
specific.
|
|||
|
|
|||
|
The built-in gdb remote server implemented by QEMU does not have this knowledge
|
|||
|
because it is specific to the Pebble. Internally, Pebble uses FreeRTOS for
|
|||
|
managing threads, mutexes, semaphores, etc. Since the location of this
|
|||
|
information in memory could easily change in different versions of the Pebble
|
|||
|
firmware, it did not seem appropriate to incorporate it into QEMU. Instead, we
|
|||
|
took the approach of creating a proxy process that sits between gdb and the gdb
|
|||
|
remote server implemented by QEMU. This proxy forwards most generic requests
|
|||
|
from gdb unmodified to QEMU and returns the responses from QEMU back to gdb. If
|
|||
|
however it sees a request from gdb that is thread related, it does the handling
|
|||
|
in the proxy itself. Interpreting the thread command generally involves issuing
|
|||
|
a series of primitive memory read requests to QEMU to gather the information
|
|||
|
stored by FreeRTOS. This design ensures that the QEMU code base is isolated from
|
|||
|
the operating system dependencies of the Pebble firmware that could change from
|
|||
|
version to version.
|
|||
|
|
|||
|
## The Console Socket
|
|||
|
|
|||
|
Console input and output has always been built into the Pebble OS but was
|
|||
|
traditionally only accessible when using one of the engineering “big boards”.
|
|||
|
There are function calls in Pebble OS for sending output to the console and a
|
|||
|
simple interpreter and executor that parses input commands. This console output
|
|||
|
and the available commands are invaluable tools when developing and debugging
|
|||
|
the Pebble firmware.
|
|||
|
|
|||
|
Routing this console output to a TCP socket leverages a very powerful and
|
|||
|
flexible feature of QEMU, which is the ability to create up to four virtual
|
|||
|
serial devices. Each serial device can be associated with a file, pipe,
|
|||
|
communication port, TCP socket (identified by port number), etc. This capability
|
|||
|
is exposed through the `-serial` command line option of QEMU. When we launch
|
|||
|
QEMU to emulate a Pebble, we create one of these serial devices for console use
|
|||
|
and assign it to a socket. On the emulated Pebble side, the firmware is simply
|
|||
|
accessing a UART peripheral built into the STM32F2xx and has no knowledge of the
|
|||
|
external socket connection. In QEMU, the code that emulates that UART device is
|
|||
|
given a reference to that virtual serial device so that it can ferry data
|
|||
|
between the two.
|
|||
|
|
|||
|
## The QEMU Channel Socket
|
|||
|
|
|||
|
The third socket is the Pebble QEMU channel (PQ channel). This channel is used
|
|||
|
for a number of purposes that are specific to the Pebble running in the
|
|||
|
emulator. For example, we send data through this channel to give the Pebble
|
|||
|
custom sensor readings like accelerometer, compass, battery level, etc. We also
|
|||
|
use this channel to send and receive Bluetooth traffic.
|
|||
|
|
|||
|
We have decided to take the approach of creating custom builds of the firmware
|
|||
|
for use in the emulator, which frees us from having to make the emulator run the
|
|||
|
exact same firmware image that runs on an actual hardware. Of course it is
|
|||
|
advantageous to minimize the differences as much as possible. When it comes to
|
|||
|
each of the hardware features that needs to be emulated, a judgment call thus
|
|||
|
has to be made on where to draw the line – try to emulate the hardware exactly
|
|||
|
or replace some of the low level code in the firmware. Some of the areas in the
|
|||
|
firmware that we have decided to modify for the emulator are the areas that
|
|||
|
handle Bluetooth traffic, accelerometer readings, and compass readings, for
|
|||
|
example.
|
|||
|
|
|||
|
There is some custom code built into the firmware when it is built for QEMU to
|
|||
|
support the PQ channel. It consists of two components: a fairly generic
|
|||
|
STM32F2xx UART device driver (called “qemu_serial”) coupled with some logic to
|
|||
|
parse out “Pebble QEMU Protocol” (PQP) packets that are sent over this channel
|
|||
|
and pass them onto the appropriate handler. Every PQP packet is framed with a
|
|||
|
PQP header containing a packet length and protocol identifier field, which is
|
|||
|
used to identify the type of data in the packet and the handler for it.
|
|||
|
|
|||
|
## Bluetooth Traffic
|
|||
|
|
|||
|
In the Pebble OS, there is a communication protocol used which is called, not
|
|||
|
surprisingly, “Pebble Protocol”. When running on a real Pebble, this protocol
|
|||
|
sits on top of Bluetooth serial and is used for communication between the phone
|
|||
|
and the Pebble. For example, this protocol is used to install apps onto the
|
|||
|
Pebble, get the list of installed apps, set the time and date on the watch, etc.
|
|||
|
|
|||
|
The pebble SDK comes with a command line tool called simply “pebble” which also
|
|||
|
allows you to leverage this protocol and install watch apps directly from a host
|
|||
|
computer. When using the pebble tool, the tool is actually sending Pebble
|
|||
|
protocol packets to your phone over a WebSocket and the Pebble app on the phone
|
|||
|
then forwards the packets to the Pebble over Bluetooth. On the Pebble side, the
|
|||
|
Bluetooth stack collects the packet data from the radio and passes it up to a
|
|||
|
layer of code in Pebble OS that interprets the Pebble Protocol formatted packet
|
|||
|
and processes it.
|
|||
|
|
|||
|
Rather than try and emulate the Bluetooth radio in the emulator, we have instead
|
|||
|
decided to replace the entire Bluetooth stack with custom code when building a
|
|||
|
firmware image for QEMU. As long as this code can pass Pebble protocol packets
|
|||
|
up, the rest of the firmware is none the wiser whether the data is coming from
|
|||
|
the Bluetooth radio or not. We chose this approach after noticing that most
|
|||
|
other emulators (for Android, iOS, etc.) have chosen not to try and emulate
|
|||
|
Bluetooth using the radio on the host machine. Apparently, this is very
|
|||
|
difficult to get right and fraught with problems.
|
|||
|
|
|||
|
To support the emulator, we have also modified the pebble tool in the SDK to
|
|||
|
have a command line option for talking to the emulator. When this mode is used,
|
|||
|
the pebble tool sends the Pebble Protocol packets to a TCP socket connected to
|
|||
|
the PQ channel socket of the emulator. Before sending the Pebble Protocol packet
|
|||
|
to this socket, it is framed with a PQP header with a protocol ID that
|
|||
|
identifies it as a Pebble protocol packet.
|
|||
|
|
|||
|
## Accelerometer Data
|
|||
|
|
|||
|
Data for the accelerometer on the Pebble is also sent through the PQ channel. A
|
|||
|
unique PQP protocol ID is used to identify it as accelerometer data. The pebble
|
|||
|
tool in the SDK includes a command for sending either a fixed reading or a
|
|||
|
series of accelerometer readings from a file to the emulator.
|
|||
|
|
|||
|
An accelerometer PQP packet includes a series of one or more accelerometer
|
|||
|
reading. Each reading consists of three 16-bit integers specifying the amount of
|
|||
|
force in each of the X, Y and Z-axes.
|
|||
|
|
|||
|
On actual hardware, the accelerometer is accessed over the i2c bus in the
|
|||
|
STM32F2XX. A series of i2c transactions are issued to set the accelerometer mode
|
|||
|
and settings and when enough samples are ready in its FIFO, it interrupts the
|
|||
|
CPU so that the CPU can issue more i2c transactions to read the samples out.
|
|||
|
|
|||
|
The current state of the emulator does not yet have the STM32F2XX i2c peripheral
|
|||
|
fully implemented and does not have any i2c devices, like the accelerometer,
|
|||
|
emulated either. This is one area that could definitely be revisited in future
|
|||
|
versions. For now, we have taken the approach of stubbing out device drivers
|
|||
|
that use i2c when building the firmware for QEMU and plugging in custom device
|
|||
|
drivers.
|
|||
|
|
|||
|
Control gets transferred to the custom accelerometer device driver for QEMU
|
|||
|
whenever a PQP packet arrives with accelerometer data in it. From there, the
|
|||
|
driver extracts the samples from the packet and saves them to its internal
|
|||
|
memory. Periodically, the driver sends an event to the system informing it that
|
|||
|
accelerometer data is available. When the system gets around to processing that
|
|||
|
event, it calls back into the driver to read the samples.
|
|||
|
|
|||
|
When an application subscribes to the accelerometer, it expects to be woken up
|
|||
|
periodically with the next set of accelerometer samples. For example, with the
|
|||
|
default sampling rate of 25Hz and a sample size of 25, it should be woken up
|
|||
|
once per second and sent 25 samples each time. The QEMU accelerometer driver
|
|||
|
still maintains this regular wakeup and feed of samples, but if no new data has
|
|||
|
been received from the PQ channel, it simply repeats the last sample -
|
|||
|
simulating that the accelerometer reading has not changed.
|
|||
|
|
|||
|
## Compass Data
|
|||
|
|
|||
|
Data for the compass on the Pebble is also sent through the PQ channel with a
|
|||
|
unique PQP protocol ID. The pebble tool in the SDK includes a command for
|
|||
|
sending compass orientation (in degrees) and calibration status.
|
|||
|
|
|||
|
On the Pebble side, we have a custom handler for PQP compass packets that simply
|
|||
|
extracts the heading and calibration status from the packet and sends an event
|
|||
|
to the system with this information – which is exactly how the real compass
|
|||
|
driver hooks into the system.
|
|||
|
|
|||
|
## Battery Level
|
|||
|
|
|||
|
Data for the battery level on the Pebble is also sent through the PQ channel
|
|||
|
with a unique PQP protocol ID. The pebble tool in the SDK includes a command for
|
|||
|
setting the battery percent and the plugged-in status.
|
|||
|
|
|||
|
On the Pebble side, we have a custom handler for PQP battery packets inside a
|
|||
|
dedicated QEMU battery driver. When a PQP battery packet arrives, the driver
|
|||
|
looks up the battery voltage corresponding to the requested percent and saves
|
|||
|
it. This driver provides calls for fetching the voltage, percent and plugged-in
|
|||
|
status.
|
|||
|
|
|||
|
On real hardware, the battery voltage is read using the analog to digital
|
|||
|
converter (ADC) peripheral in the STM32F2xx. For now, the emulator takes the
|
|||
|
approach of stubbing out the entire battery driver but in the future, it might
|
|||
|
be worth considering emulating the ADC peripheral better and then just feed the
|
|||
|
PQP packet data into the emulated ADC peripheral.
|
|||
|
|
|||
|
## Taps
|
|||
|
|
|||
|
On the real hardware, the accelerometer has built-in tap detection logic that
|
|||
|
runs even when the FIFO based sample-collecting mode is turned off. This tap
|
|||
|
detection logic is used to support features like the tap to turn on the
|
|||
|
backlight, etc.
|
|||
|
|
|||
|
Data for the tap detection on the Pebble is also sent through the PQ channel
|
|||
|
with a unique PQP protocol ID. The pebble tool in the SDK includes a command for
|
|||
|
sending taps and giving the direction (+/-) and axis (X, Y, or Z).
|
|||
|
|
|||
|
On the Pebble side, we have a custom handler for PQP tap packets that simply
|
|||
|
sends an event to the system with the tap direction and axis – which is exactly
|
|||
|
what the hardware tap detection driver normally does.
|
|||
|
|
|||
|
## Button Presses
|
|||
|
|
|||
|
Button presses can be sent to the emulated Pebble through two different
|
|||
|
mechanisms. QEMU itself monitors key presses on the host system and we hook into
|
|||
|
a QEMU keyboard callback method and assert the pins on the emulated STM32F2xx
|
|||
|
that would be asserted in real hardware depending on what key is pressed.
|
|||
|
|
|||
|
An alternate button pressing mechanism is exposed through the PQ channel and is
|
|||
|
provided primarily for test automation purposes. But here, in contrast to how we
|
|||
|
process things like accelerometer data, no special logic needs to be executed in
|
|||
|
the firmware to handle these button PQP packets. Instead, they are processed
|
|||
|
entirely on the QEMU side and it directly asserts the appropriate pin(s) on the
|
|||
|
emulated STM32F2xx.
|
|||
|
|
|||
|
A module in QEMU called “pebble_control” handles this logic. This module
|
|||
|
essentially sits between the PQ channel serial device created by QEMU and the
|
|||
|
UART that we use to send PQP packets to the emulated Pebble. It is always
|
|||
|
monitoring the traffic on the PQ channel. For some incoming packets, like button
|
|||
|
packets, the pebble_control module intercepts and handles them directly rather
|
|||
|
than passing them onto the UART of the emulated Pebble. To register a button
|
|||
|
state, it just needs to assert the correct pins on the STM32F2xx depending on
|
|||
|
which buttons are pressed. The button PQP packet includes the state of each
|
|||
|
button (pressed or not) so that the pebble_control module can accurately reflect
|
|||
|
that state among all the button pins.
|
|||
|
|
|||
|
## Device Emulation
|
|||
|
|
|||
|
So far, we have mainly just talked about how one interacts with the emulator and
|
|||
|
sends sensor data to it from the outside world. What exactly is QEMU doing to
|
|||
|
emulate the hardware?
|
|||
|
|
|||
|
## QEMU Devices
|
|||
|
|
|||
|
QEMU is structured such that each emulated hardware device (CPU, UART, flash
|
|||
|
ROM, timer, ADC, RTC, etc.) is mirrored by a separate QEMU device. Typically,
|
|||
|
each QEMU device is compiled into its own module and the vast majority of these
|
|||
|
modules have no external entry points other than one that registers the device
|
|||
|
with a name and a pointer to an initialization method.
|
|||
|
|
|||
|
There are far too many intricacies of QEMU to describe here, but suffice it to
|
|||
|
say that the interface to each device in QEMU is standardized and generically
|
|||
|
defined. You can map an address range to a device and also assign a device one
|
|||
|
or more interrupt callbacks. Where a device would normally assert an interrupt
|
|||
|
in the real hardware, it simply calls an interrupt callback in QEMU. When the
|
|||
|
emulated CPU performs a memory access, QEMU looks up to see if that address maps
|
|||
|
to any of the registered devices and dispatches control to a handler method for
|
|||
|
that device with the intended address (and data if it is a write).
|
|||
|
|
|||
|
The basic mode of communication between devices in QEMU is through a method
|
|||
|
called `qemu_set_irq()`. Contrary to its name, this method is not only used for
|
|||
|
generating interrupts. Rather, it is a general-purpose method that calls a
|
|||
|
registered callback. One example of how this is used in the Pebble emulation is
|
|||
|
that we provide a callback to the pin on the emulated STM32F2xx that gets
|
|||
|
asserted when the vibration is turned on. The callback we provide is a pointer
|
|||
|
to a method in the display device. This enables the display device to change how
|
|||
|
it renders the Pebble window in QEMU based on the vibration control. The IRQ
|
|||
|
callback mechanism is a powerful abstraction because an emulated device does not
|
|||
|
need to know who or what it is connected to, it simply needs to call the
|
|||
|
registered callback using `qemu_set_irq()`.
|
|||
|
|
|||
|
## Pebble Structure
|
|||
|
|
|||
|
We create multiple devices to emulate a Pebble. First of course there is the
|
|||
|
CPU, which is an ARM v7m variant (already implemented in the base code of QEMU).
|
|||
|
|
|||
|
The STM32F2XX is a microcontroller containing an ARM v7m CPU and a rather
|
|||
|
extensive set of peripherals including an interrupt controller, memory
|
|||
|
protection unit (MPU), power control, clock control, real time clock, DMA
|
|||
|
controller, multiple UARTS and timers, I2C bus, SPI bus, etc. Each of these
|
|||
|
peripherals is emulated in a separate QEMU device. Many of these peripherals in
|
|||
|
the STM32F2xx behave similarly with those in the STM32F1xx series of
|
|||
|
microcontroller implemented in Andre Beckus’ fork of QEMU and so could be
|
|||
|
extended and enhanced where necessary from his implementations.
|
|||
|
|
|||
|
Outside of the STM32F2xx, we have the Pebble specific devices including the
|
|||
|
display, storage flash, and buttons and each of these is emulated as a separate
|
|||
|
QEMU device as well. For the storage flash used in the Pebble, which sits on the
|
|||
|
SPI bus, we could use a standard flash device already implemented in QEMU. The
|
|||
|
display and button handling logic however are custom to the Pebble.
|
|||
|
|
|||
|
## Device Specifics
|
|||
|
|
|||
|
Getting the Pebble emulation up and running required adding some new QEMU
|
|||
|
devices for peripherals in the STM32F2XX that are not in the STM32F1XX and
|
|||
|
extending and enhancing some of the existing devices to emulate features that
|
|||
|
were not yet fully implemented.
|
|||
|
|
|||
|
To give a flavor for the changes that were required, here is a sampling: the DMA
|
|||
|
device was more fully implemented to support 8 streams of DMA with their
|
|||
|
associated IRQs; a bug in the NOR flash device was addressed where it was
|
|||
|
allowing one to change 0’s to 1’s (an actual device can only change 1’s to 0’s
|
|||
|
when programming); many of the devices were raising an assert for registers that
|
|||
|
were not implemented yet and support for these registers needed to be added in;
|
|||
|
the PWR device for the STM32F2xx was added in; wake up timer support was not
|
|||
|
implemented in the RTC; many devices were not resetting all of their registers
|
|||
|
correctly in response to a hardware reset, etc.
|
|||
|
|
|||
|
## Real Time Clock
|
|||
|
|
|||
|
One of the more challenging devices to emulate correctly was the real time clock
|
|||
|
(RTC) on the STM32F2xx. Our first implementation, for example, suffered from the
|
|||
|
problem that the emulated Pebble did not keep accurate time, quite a glaring
|
|||
|
issue for a watch!
|
|||
|
|
|||
|
The first attempt at emulating the RTC device relied on registering a QEMU timer
|
|||
|
callback that would fire at a regular interval. The QEMU framework provides this
|
|||
|
timer support and will attempt call your register callback after the requested
|
|||
|
time elapses. In response to this callback, we would increment the seconds
|
|||
|
register in the RTC by one, see if any alarm interrupts should fire, etc.
|
|||
|
|
|||
|
The problem with this approach is that there is no guarantee that the QEMU timer
|
|||
|
callback method will be called at the exact requested time and, depending on the
|
|||
|
load on the host machine, we were falling behind sometimes multiple minutes in
|
|||
|
an hour.
|
|||
|
|
|||
|
To address this shortcoming, we modified the timer callback to instead fetch the
|
|||
|
current host time, convert it to target time, and then modify the registers in
|
|||
|
the RTC to match that newly computed target time. In case we are advancing the
|
|||
|
target time by more than one in any specific timer callback, we also need to
|
|||
|
check if any alarm interrupts were set to fire in that interval. We also update
|
|||
|
the RTC time based on the host time (and process alarms) whenever a read request
|
|||
|
comes in from the target for any of the time or calendar registers.
|
|||
|
|
|||
|
Although conceptually simple, there were a number of details that needed to be
|
|||
|
worked out for this approach. For one, we have to maintain an accurate mapping
|
|||
|
from host time to target time. Anytime the target modifies the RTC registers, we
|
|||
|
also have to update the mapping from host to target time. The other complication
|
|||
|
is that the Pebble does not use the RTC in the “classic” sense. Rather than
|
|||
|
incrementing the seconds register once per second, the Pebble OS actually runs
|
|||
|
that RTC at 1024 increments per second. It does this so that it can use the RTC
|
|||
|
to measure time to millisecond resolution. The emulation of the RTC thus needs
|
|||
|
to also honor the prescaler register settings of the RTC to get the correct
|
|||
|
ratio of host time to target time.
|
|||
|
|
|||
|
A further complication arose in the Pebble firmware itself. When the Pebble OS
|
|||
|
has nothing to do, it will put the CPU into stop mode to save power. Going into
|
|||
|
stop mode turns off the clock and an RTC alarm is set to wake up the CPU after
|
|||
|
the desired amount of time has elapsed. On real hardware, if we set the alarm to
|
|||
|
fire in *N* milliseconds, we are guaranteed that when we wake up, the RTC will
|
|||
|
read that it is now *N* milliseconds later. When running in emulation however,
|
|||
|
quite often the emulation will fall slightly behind and by the time the emulated
|
|||
|
target processes the alarm interrupt, the RTC registers will show that more than
|
|||
|
*N* milliseconds have elapsed (especially since the RTC time on the target is
|
|||
|
tied to the host time). Addressing this required modifying some of the glue
|
|||
|
logic we have around FreeRTOS in order to fix up the tick count and wait time
|
|||
|
related variables for this possibility.
|
|||
|
|
|||
|
## UART Performance
|
|||
|
|
|||
|
We rely heavily on the PQ channel to communicate with the emulated pebble. We
|
|||
|
use it to send 3rd party apps to the emulated pebble from the host machine for
|
|||
|
example and even for sending firmware updates to the emulated Pebble.
|
|||
|
|
|||
|
The first implementation of the UART device though was giving us only about
|
|||
|
1Kbyte/second throughput from the host machine to the emulated Pebble. It turns
|
|||
|
out that the emulated UART device was telling QEMU to send it only 1 character
|
|||
|
at a time from the TCP socket. Once it got that character, it would make it
|
|||
|
available to the emulated target, and once the target read it out, it would tell
|
|||
|
QEMU it had space for one more character from the socket, etc.
|
|||
|
|
|||
|
To address this, the QEMU UART device implementation was modified to tell QEMU
|
|||
|
to send it a batch of bytes from the socket at a time, then those bytes were
|
|||
|
dispatched to the target one at a time as it requested them. This simple change
|
|||
|
gave us about a 100x improvement in throughput.
|
|||
|
|
|||
|
## CPU Emulation Issues
|
|||
|
|
|||
|
Running the Pebble OS in QEMU ended up stressing some aspects of the ARM that
|
|||
|
were not exercised as completely before, and exposed a few holes in the CPU
|
|||
|
emulation logic that are interesting to note.
|
|||
|
|
|||
|
The ARM processor has two operating modes (handler mode and thread mode) and two
|
|||
|
different stack pointers it can use (MSP and PSP). In handler mode, it always
|
|||
|
uses the MSP and in thread mode, it can use either one, depending on the setting
|
|||
|
of a bit in one of the control registers. It turns out there was a bug in the
|
|||
|
ARM emulation such that the control bit was being used to determine which stack
|
|||
|
pointer to use even when in handler mode. The interesting thing about this is
|
|||
|
that because of the way the Pebble OS runs, this bug would only show up when we
|
|||
|
tried to launch a 3rd party app for the first time.
|
|||
|
|
|||
|
Another, more subtle bug had to do with interrupt masking. The ARM has a BASEPRI
|
|||
|
register, which can be used to mask all interrupts below a certain priority,
|
|||
|
where the priority can be between 1 and 255. This feature was not implemented in
|
|||
|
QEMU, so even when the Pebble OS was setting BASEPRI to mask off some of the
|
|||
|
interrupts, that setting was being ignored and any interrupt could still fire.
|
|||
|
This led to some intermittent and fairly hard to reproduce crashes in the
|
|||
|
emulated Pebble whose source remained a mystery for quite a while.
|
|||
|
|
|||
|
We discovered an issue that if the emulated CPU was executing a tight infinite
|
|||
|
loop, that we could not connect using gdb. It turns out that although QEMU
|
|||
|
creates multiple threads on the host machine, only one is allowed to effectively
|
|||
|
run at a time. This makes coding in QEMU easier because you don’t have to worry
|
|||
|
about thread concurrency issues, but it also resulted in the gdb thread not
|
|||
|
getting a chance to run at all if the thread emulating the CPU had no reason to
|
|||
|
exit (to read a register from a peripheral device for example). To address this,
|
|||
|
we modified the CPU emulation logic to break out at least once every few hundred
|
|||
|
instructions to see if any other thread had work to do.
|
|||
|
|
|||
|
As briefly mentioned earlier, the ARM has a stop mode that is used for power
|
|||
|
savings and the Pebble OS uses this mode extensively. Going into stop mode turns
|
|||
|
off the clock on the CPU and most peripherals until an alarm interrupt fires.
|
|||
|
There was a flaw in the initial implementation of stop mode in the emulator that
|
|||
|
resulted in any and all interrupts being able to wake up the CPU. The emulated
|
|||
|
Pebble ran just fine and the user would not notice any problems. However, QEMU
|
|||
|
ended up using 100% of a CPU on the host machine at all times. When this bug was
|
|||
|
addressed, the typical CPU load of QEMU went down to about 10%. Addressing this
|
|||
|
bug was critical for us in order to efficiently host enough QEMU instances on a
|
|||
|
server farm for use by CloudPebble.
|
|||
|
|
|||
|
Another challenge in the CPU emulation was figuring out how to correctly
|
|||
|
implement standby mode for the STM32F2xx. In standby mode, all peripherals are
|
|||
|
powered off and the Pebble sets up the CPU to only wake up when the WKUP pin on
|
|||
|
the STM32F2xx is asserted. This mode is entered when the user chooses “Shut
|
|||
|
Down” from the Pebble settings menu or automatically when the battery gets
|
|||
|
critically low. In most cases, wake up sources for the CPU (like the RTC
|
|||
|
peripheral, UARTs, timers, etc.) generate an interrupt which first goes to the
|
|||
|
interrupt controller (NVIC) which is outside the core CPU. The NVIC then figures
|
|||
|
out the priority of the interrupt and only wakes the CPU if that priority of
|
|||
|
interrupt is not masked off. The WKUP pin functionality is unique in that it is
|
|||
|
not a normal interrupt, cannot be masked, and instead of resulting in the
|
|||
|
execution of an interrupt service routine, it results in a reset of the CPU.
|
|||
|
Figuring out how to hook this up correctly into QEMU required quite a bit of
|
|||
|
learning and investigation into the core interrupt handling machinery in QEMU
|
|||
|
and understanding difference between that and the WKUP functionality.
|
|||
|
|
|||
|
## Window Dressing
|
|||
|
|
|||
|
A few of the many fun things about working on the emulator had to do with
|
|||
|
finding creative ways to represent things such as the brightness of the
|
|||
|
backlight and the status of the vibration output.
|
|||
|
|
|||
|
In the Pebble hardware, a timer peripheral is used to generate a PWM (Pulse
|
|||
|
Width Modulated) output signal whose duty cycle controls the brightness of the
|
|||
|
backlight. This allows the Pebble to gradually ramp up and down the brightness
|
|||
|
of the backlight. In QEMU, the STM32F2xx timer device was enhanced to support
|
|||
|
registering a QEMU IRQ handler that it would call whenever the PWM output value
|
|||
|
was changed. In QEMU, when you call an interrupt handler callback using
|
|||
|
`qemu_set_irq()`, you pass in an integer argument, allowing one to pass a scalar
|
|||
|
value if need be. A “brightness” interrupt handler callback was added to the
|
|||
|
Pebble display device and this callback was registered with the timer
|
|||
|
peripheral. Once hooked up, this enabled the display to be informed whenever the
|
|||
|
PWM output value changed. The display device implementation was then modified to
|
|||
|
adjust the brightness (via the RGB color) of the pixels rendered to the display
|
|||
|
based on the scalar value last sent to its brightness callback.
|
|||
|
|
|||
|
Whenever the Pebble firmware decides to turn on the vibration, it asserts one of
|
|||
|
the GPIO (General Purpose IO) pins on the STM32F2xx. To implement this feature,
|
|||
|
a “vibration” callback was added to the display device implementation and this
|
|||
|
callback was registered with the GPIO device pin that gets asserted by the
|
|||
|
firmware in order to vibrate. To implement the vibrate, the display device
|
|||
|
repeatedly re-renders the contents of the Pebble screen to the window offset
|
|||
|
plus or minus 2 pixels, giving the illusion of a vibrating window.
|
|||
|
|
|||
|
## Future Work
|
|||
|
|
|||
|
The Pebble emulator has already proven to be a great productivity enhancer in
|
|||
|
its current state, and has a lot of potential for future enhancements and
|
|||
|
improvements.
|
|||
|
|
|||
|
It would be interesting for example to investigate how well we can emulate I2C
|
|||
|
and some of the I2C devices used in the Pebble. Doing this could allow us to use
|
|||
|
more of the native device drivers in the firmware for things like the
|
|||
|
accelerometer rather than having to plug in special QEMU based ones.
|
|||
|
|
|||
|
Another area for investigation revolves around Bluetooth. Currently, we replace
|
|||
|
the entire Bluetooth stack, but another option worth investigating is to run the
|
|||
|
native Bluetooth stack as-is and just emulate the Bluetooth chip used on the
|
|||
|
Pebble. This chip communicates over a serial bus to the STM32F2xx and accepts
|
|||
|
low-level HCI (Host Controller Interface) commands.
|
|||
|
|
|||
|
There is a lot of potential to improve the UI on the QEMU side. Currently, we
|
|||
|
show only a bare window with the contents of the Pebble display. It would be a
|
|||
|
great improvement for example to show a status area around this window with
|
|||
|
clickable buttons. The status area could display helpful debugging information.
|
|||
|
|
|||
|
There is a lot of potential for enhanced debugging and profiling tools. Imagine
|
|||
|
a way to get a trace of the last *N* instructions that were executed before a
|
|||
|
crash, or improved ways to profile execution and see where performance can be
|
|||
|
improved or power savings realized.
|
|||
|
|
|||
|
## More Information
|
|||
|
|
|||
|
Continue reading [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1]
|
|||
|
for more information on PebbleKit JS emulation and embedding the emulator in
|
|||
|
CloudPebble.
|
|||
|
|
|||
|
|
|||
|
[1]: /blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation/
|
|||
|
[2]: http://wiki.qemu.org/Main_Page
|
|||
|
[3]: http://beckus.github.io/qemu_stm32/
|
|||
|
[4]: http://appdevelopermagazine.com/1313/2014/4/14/How-Pebble-Converted-135,070-Customized-Watchfaces-For-Pebble-OS-v2.0/
|