When we tilt our Smartphone from Portrait to Landscape... How does it know that we're tilting our phone?
That's because of the Accelerometer inside our Smartphone! To understand how it works, let's snoop the raw Accelerometer Data from PINE64 PinePhone with Apache NuttX RTOS (Real-Time Operating System).
Build and Boot NuttX RTOSFollow these steps to boot NuttX RTOS on PinePhone with a microSD Card (it won't touch the Internal eMMC Storage)...
(1) Install the Build Prerequisites
(2) Download the ARM64 Toolchain for AArch64 Bare-Metal Target: aarch64-none-elf
(Skip the section for Beta Releases)
(3) Download and build NuttX RTOS for PinePhone...
git clone https://github.com/apache/nuttx nuttx
git clone https://github.com/apache/nuttx-apps apps
cd nuttx
tools/configure.sh pinephone:sensor
make
cp nuttx.bin Image
rm -f Image.gz
gzip Image
(4) This produces the NuttX Image File Image.gz
. We'll copy it to PinePhone in a while.
(5) Download the PinePhone Jumpdrive Image: pine64-pinephone.img.xz
(6) Write the downloaded image to a microSD Card with Balena Etcher or GNOME Disks.
(7) Remember Image.gz
from earlier? Copy and overwrite the file on the microSD Card.
(8) Insert the microSD Card into PinePhone and flip Privacy Switch 6 (Headphone) to Off.
(9) Connect the PinePhone Serial Console Cable to our computer and power up PinePhone.
(10) On our computer, use screen
or putty
to connect to the PinePhone Serial Console at 115.2 kbps (assuming "usbserial-1410" is the USB Serial Port)...
screen /dev/tty.usbserial-1410 115200
(11) At the prompt, enter: ls /dev
NuttShell (NSH) NuttX-12.0.3
nsh> ls /dev
/dev:
console
imu0
null
ram0
ram2
ttyS0
userleds
zero
NuttX says that the PinePhone Accelerometer InvenSense MPU-6050 is now accessible at /dev/imu0
. (That's the Inertial Measurement Unit)
Let's read the Raw Accelerometer Data from PinePhone's Accelerometer.
Hold our PinePhone upright in the Portrait Orientation. Enter this command...
hexdump /dev/imu0 count=14
This dumps 14 bytes of raw data from the Accelerometer...
/dev/imu0 at 00000000:
0000: 10 21 00 05 01 6a f7 9e ff d8 00 13 ff fd
Now rotate our PinePhone 90 degrees anti-clockwise, into the Landscape Orientation. Enter the same command...
hexdump /dev/imu0 count=14
We'll see a different bunch of 14 bytes of raw data...
/dev/imu0 at 00000000:
0000: 00 19 f0 48 01 12 f8 80 ff d6 00 0f ff fe
But what do the numbers mean?
Decode the Accelerometer DataNuttX Kernel defines the MPU-6050 Accelerometer Data Format as...
/* MPU-6050 Accelerometer Data Format
* (14 bytes, big-endian) */
struct sensor_data_s {
int16_t x_accel; /* Accelerometer X */
int16_t y_accel; /* Accelerometer Y */
int16_t z_accel; /* Accelerometer Z */
int16_t temp; /* Temperature */
int16_t x_gyro; /* Gyroscope X */
int16_t y_gyro; /* Gyroscope Y */
int16_t z_gyro; /* Gyroscope Z */
};
When we decode the data from above, we get...
Portrait Orientation:
- Accelerometer X = 4129 (0x1021) ⬆️
- Accelerometer Y = 5 (0x0005)
- Accelerometer Z = 362 (0x016A)
- Temperature = -2146 (0xF79E)
- Gyroscope X = -40 (0xFFD8)
- Gyroscope Y = 19 (0x0013)
- Gyroscope Z = -3 (0xFFFD)
Landscape Orientation:
- Accelerometer X = 25 (0x0019)
- Accelerometer Y = -4024 (0xF048) ⬇️
- Accelerometer Z = 274 (0x0112)
- Temperature = -1920 (0xF880)
- Gyroscope X = -42 (0xFFD6)
- Gyroscope Y = 15 (0x000F)
- Gyroscope Z = -2 (0xFFFE)
Aha the Accelerometer X and Y values changed significantly!
Yep the Accelerometer measures the acceleration in Three Axes...
- X Axis: Points towards the Top Edge of PinePhone
- Y Axis: Points towards the Left Edge of PinePhone
- Z Axis: Points towards the Back Cover of PinePhone
The Accelerometer has detected a change in acceleration: From the X Axis to the Y Axis, after we rotated our phone.
But we didn't "accelerate" the phone horizontally or vertically?
It's because of Gravity! The Earth's surface exerts a normal force upwards on our phone. Which is measured by the Accelerometer as we rotate our phone.
And that's how our phone knows when we tilt it from Portrait to Landscape!
Read the AccelerometerTo wrap up, let's write a program to read and interpret the Accelerometer Data.
We begin by declaring the MPU-6050 Accelerometer Data Format: hello_main.c
/* MPU-6050 Accelerometer Data Format
* (14 bytes, big-endian)
* Based on NuttX Kernel: https://github.com/apache/nuttx/blob/master/drivers/sensors/mpu60x0.c#L218-L233 */
struct sensor_data_s
{
int16_t x_accel; /* Accelerometer X */
int16_t y_accel; /* Accelerometer Y */
int16_t z_accel; /* Accelerometer Z */
int16_t temp; /* Temperature */
int16_t x_gyro; /* Gyroscope X */
int16_t y_gyro; /* Gyroscope Y */
int16_t z_gyro; /* Gyroscope Z */
};
Next we open the Accelerometer at /dev/imu0
...
/* Open the MPU-6050 Accelerometer for reading */
int fd = open("/dev/imu0", O_RDONLY);
assert(fd > 0); /* Check that it exists */
We read 14 bytes of data from the Accelerometer...
/* Accelerometer Data will have 14 bytes */
struct sensor_data_s data;
assert(sizeof(data) == 14); /* We expect to read 14 bytes */
/* Read the Accelerometer Data (14 bytes) */
int bytes_read = read(fd, &data, sizeof(data));
assert(bytes_read == sizeof(data)); /* We expect 14 bytes read */
But the Accelerometer Data is in Big-Endian Format. We convert to Little-Endian Format for PinePhone...
/* Flip the bytes from Big-Endian to Little-Endian for PinePhone.
* ntohs() is explained here: https://developer.ibm.com/articles/au-endianc/ */
int16_t x_accel = ntohs(data.x_accel);
int16_t y_accel = ntohs(data.y_accel);
int16_t z_accel = ntohs(data.z_accel);
int16_t temp = ntohs(data.temp);
int16_t x_gyro = ntohs(data.x_gyro);
int16_t y_gyro = ntohs(data.y_gyro);
int16_t z_gyro = ntohs(data.z_gyro);
Finally we print the converted data and close the Accelerometer...
/* Print the Accelerometer Data */
printf("Accelerometer X: %d\n", x_accel);
printf("Accelerometer Y: %d\n", y_accel);
printf("Accelerometer Z: %d\n", z_accel);
printf("Temperature: %d\n", temp);
printf("Gyroscope X: %d\n", x_gyro);
printf("Gyroscope Y: %d\n", y_gyro);
printf("Gyroscope Z: %d\n", z_gyro);
/* Close the Accelerometer */
close(fd);
To run this on PinePhone, look for this Source File on our computer...
apps/examples/hello/hello_main.c
Overwrite the contents of that file by this: hello_main.c
Rebuild NuttX RTOS for PinePhone...
make
cp nuttx.bin Image
rm -f Image.gz
gzip Image
Copy Image.gz
to the PinePhone microSD Card according to the earlier instructions. Boot the microSD Card on PinePhone and enter: hello
When PinePhone is in the Portrait Orientation, we'll see something like...
nsh> hello
Accelerometer X: 4137
Accelerometer Y: -97
Accelerometer Z: 208
Temperature: -1500
Gyroscope X: -29
Gyroscope Y: 66
Gyroscope Z: 12
When we rotate PinePhone to the Landscape Orientation, the Accelerometer X and Y values change significantly...
nsh> hello
Accelerometer X: -143
Accelerometer Y: -4007
Accelerometer Z: -443
Yep our program has successfully read the PinePhone Accelerometer!
(Many Thanks to Filipe Cavalcanti for inspiring this tutorial!)
Further ReadingTo learn more about NuttX RTOS for PinePhone...
MPU-6050 works fine on Microcontrollers too...
MPU-6050 is actually a Combined Accelerometer and Gyroscope...
Can we compute the Tilt Angle? It gets complicated in 3D Space, check out the Trigonometric Formula...
- "How to use a Three-Axis Accelerometer for Tilt Sensing"
- "Tilt Sensing Using a Three-Axis Accelerometer"
How is the MPU-6050 Driver started in the NuttX Kernel?
At startup, NuttX Kernel starts the MPU-6050 Driver in the PinePhone Bringup Function pinephone_bringup and registers the driver at /dev/imu0: pinephone_bringup.c
/* To register the IMU Driver for MPU-6050 Accelerometer at Port TWI1... */
/* Init the Power Management Integrated Circuit */
ret = pinephone_pmic_init();
/* Wait 15 milliseconds for power supply and power-on init */
up_mdelay(15);
/* Allocate the IMU Driver struct */
mpu_config = kmm_zalloc(sizeof(struct mpu_config_s));
mpu_config->i2c = i2c1;
mpu_config->addr = 0x68;
/* Register the IMU Driver at /dev/imu0 */
mpu60x0_register("/dev/imu0", mpu_config);
(mpu60x0_register is defined in the MPU-6050 Driver)
(pinephone_pmic_init comes from the driver for PinePhone's Power Management Integrated Circuit)
What's i2c1
?
i2c1
is the Second I2C Port on the Allwinner A64 SoC, connected to the MPU-6050 Accelerometer. (PinePhone Schematic, Page 12)
The I2C Ports on Allwinner A64 are named TWI0, TWI1,... So i2c1
is actually Port TWI1 on Allwinner A64: pinephone_bringup.c
/* Initialize TWI1 as I2C Bus 1 */
i2c1 = a64_i2cbus_initialize(i2c1_bus);
/* Register I2C Driver for I2C Bus 1 at TWI1 */
ret = i2c_register(i2c1, i2c1_bus);
(TWI means "Two Wire Interface")
(a64_i2cbus_initialize is defined in the Allwinner A64 I2C Driver)
What's this PMIC?
At startup, NuttX Kernel initialises PinePhone's Power Management Integrated Circuit (PMIC), before starting the MPU-6050 Driver: pinephone_bringup.c
/* To register the IMU Driver for MPU-6050 Accelerometer at Port TWI1... */
/* Init the Power Management Integrated Circuit */
ret = pinephone_pmic_init();
/* Omitted: Start the IMU Driver at /dev/imu0 */
(pinephone_pmic_init comes from the PMIC Driver)
That's because the MPU-6050 Accelerometer is powered by PinePhone's PMIC: X-Powers AXP803. (PinePhone Schematic, Page 3, DLDO1)
The DLDO1 Power Supply on X-Powers AXP803 provides power to the I2C Sensors on PinePhone, including the MPU-6050 Accelerometer.
Appendix: MPU-6050 OrientationHow did we figure out that X Axis points towards the Top Edge of PinePhone? And Y Axis points towards the Left Edge?
According to the PinePhone Component List, the MPU-6050 Accelerometer is labelled U1202.
From the PinePhone Mainboard Bottom Placement, U1202 is at Top Left of the diagram. Which means that the Accelerometer is at Top Left of PinePhone. (When viewed from the front)
U1202 has a Tiny Circle oriented towards the Lower Left edge of PinePhone.
When we refer to InvenSense MPU-6050 Datasheet (Page 21), we conclude that...
- X Axis: Points towards the Top Edge of PinePhone
- Y Axis: Points towards the Left Edge of PinePhone
- Z Axis: Points towards the Back Cover of PinePhone
Comments