I²C on the Pinephone
Tags: pinephone, rust, linux
Published: 2021-05-05 04:40:03

---
title: I²C on the Pinephone
published: true
description: A simple Pinephone project that uses the SSD1306 display to output the cell signal of the device 
tags: pinephone, rust, linux
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7c10xy6ekgxyn2zp7431.jpg
 
---

# Overview

The [Pinephone](https://www.pine64.org/pinephone/) is an open source smartphone that runs Linux. It's a really neat device and will hopefully be suitable as a daily driver smartphone in the near future, but for now it's very pleasant to hack on. Short surface level overview of the device [here](https://www.youtube.com/watch?v=fCKMxzz9cjs).

The below example is a little contrived, but hopefully sheds some light on connecting external peripherals to the Pinephone. I'm also fairly new to embedded stuff, D-bus, and still shaky on my Rust knowledge, so critique on a better way to implement all this is very welcome.

## I²C

I²C is a common method for interfacing with embedded peripherals. It's a fairly simple communication method. For a deeper dive checkout out the [Embedded Rust Discovery Book](https://docs.rust-embedded.org/discovery/14-i2c/index.html).

## Embedded rust

In order to understand how the driver works we need to understand a little bit about the embedded rust ecosystem. `embedded-hal` is a set of traits which can be used to create platform-agnostic drivers. The `SSD1306` targets `embedded-hal`, so any hal that implements the `embedded-hal` traits will be able to use the driver. In order to use `SSD1306` driver on our Pinephone we'll take advantage of the `linux-embeddedhal` crate.

# I2c on the pinephone
## Accessing the pogo pins

You'll need some way to connect your device to the Pinephone. The easiest method is to just purchase a [breakout board](https://pine64.com/product/pinephone-flex-break-out-board/?v=0446c16e2e66). But, there are [other ways](https://www.youtube.com/watch?v=lFsQpd0bLTY) to access these pins. I used the breakout board [from this repo](https://github.com/jnavarro7/pinephone_flex_breakout_board) and ordered them from OSH Park. There is some soldering required.

![Pinephone back](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15i6qlrjs7sxdp9f2z4y.jpg)
 

## Connect the display

Connecting the display is pretty straightforward. 
![](https://wiki.pine64.org/images/thumb/e/e1/Pinephone_pogo.png/400px-Pinephone_pogo.png)

One you've connected your breakout you just need to match up the pins (`GND` `VCC` `SCL` `SDA`) to the display. 

## I²C on Linux

:rotating_light: You may need to enable the proper kernel driver. I'm using Arch Linux on my pinephone, which appears to have the I²C kernel module already enabled. 

Make sure you have have the `i2cdetect` utility installed. On arch linux the utility is in the `i2c-tools` package. With that tool installed run `i2cdetect -l` to print out all the i2c devices:

```
i2c-3   unknown         mv64xxx_i2c adapter                     N/A
i2c-1   unknown         mv64xxx_i2c adapter                     N/A
i2c-4   unknown         i2c-csi                                 N/A
i2c-2   unknown         mv64xxx_i2c adapter                     N/A
i2c-0   unknown         DesignWare HDMI                         N/A
i2c-5   unknown         i2c-2-mux (chan_id 0)                   N/A
```

Each I²C device will have an address we use to interface with it. The `SSD1306` by default has an address of `3C`. In order to figure out which I²C device we need to communicate with, we can probe each of the interfaces. Here is my output after probing device 3

``` 
$ i2cdetect 3
 0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f  
00:                         -- -- -- -- -- -- -- --    
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --    
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
```

`/dev/i2c-3` appears to be the interface the SSD1306 display is connected to. With that knowledge we can start programming some Rust!

## Rust!

Create a new Rust project. Use [rustup](https://rustup.rs/) if you don't already have it installed. 

`cargo new i2c-test && cd i2c-test`

Modify `Cargo.toml` to add the dependencies we need:

```
# ...Omitted

[dependencies]  
ssd1306 = "0.5.2"  
linux-embedded-hal = "0.3.0"  
embedded-graphics = "0.6.2"
```

And then modify `src/main.rs`

```rust
use embedded_graphics::{  
   fonts::{Font6x8, Text},  
   pixelcolor::BinaryColor,  
   prelude::*,  
   style::TextStyleBuilder,  
};  
use ssd1306::{mode::GraphicsMode, prelude::\*, Builder, I2CDIBuilder};  
use core::fmt::Write;  
use linux_embedded_hal::I2cdev;  
  
fn main() {  
   let i2c = I2cdev::new("/dev/i2c-3").unwrap(); // Replace with the proper interface!
   let interface = I2CDIBuilder::new().init(i2c);  
   let mut disp: GraphicsMode<_, _> = Builder::new()  
       .size(DisplaySize128x64)  
       .connect(interface).into();  
   disp.init().unwrap();  
  
   let text_style = TextStyleBuilder::new(Font6x8)  
       .text_color(BinaryColor::On)  
       .build();  
  
   Text::new("Hello world!", Point::zero())  
       .intostyled(text_style)  
       .draw(&mut disp);  
  
   disp.flush().unwrap();  
  
}
```

Before we run this code let's take a step back and look at the permissions on the `i2c-#` devices.

```
$ ls -l /dev/ | grep i2c

crw-------  1 root  root    89,   0 May  4 12:09 i2c-0  
crw-------  1 root  root    89,   1 May  4 12:09 i2c-1  
crw-------  1 root  root    89,   2 May  4 12:09 i2c-2  
crw-------  1 root  root    89,   3 May  4 12:09 i2c-3  
crw-------  1 root  root    89,   4 May  4 12:09 i2c-4  
crw-------  1 root  root    89,   5 May  4 12:09 i2c-5
```

These devices are both owned by root and in the group root. If we try to run our rust code right now, the code will fail because our user lacks permission to read from the I²C device. A quick fix is to run:

`chown $(whoami) /dev/i2c-#`  (replace `#` with the I²C  # you found from the previous section).

Now we can run: `cargo run` and you should see the display light up with `Hello world!`.

![IMG_20210504_131234](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4glcmvkbl0h90fq9qv4b.jpg)

:pencil: There isn't anything Pinephone specific about this code. You should be able to run this exact program (provided that it references the right device) on something like a raspberry pi.

# Let's do something more elaborate

Wouldn't it be cool, although maybe a little unnecessary if we could display the cell signal on our new OLED screen? Let's do it!

## D-bus modem manager

[D-bus](https://en.wikipedia.org/wiki/D-Bus) is a messaging middleware for communicating between multiple processes. What we're interested in is the `ModemManager` D-bus interface. [Take a look at the modemmanager documenation](https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/) to get an idea of what you can do with this interface. [D-Feet](https://wiki.gnome.org/Apps/DFeet) is a great little app that can list all the different interfaces you can connect to and all the methods and properties they provide. 

## D-bus and Rust

### Generating our D-bus interface

To actually interact with D-bus we'll generate the rust code necessary using [dbus-codegen](https://crates.io/crates/dbus-codegen/). This is surprisingly easy once you have all the pieces. First install the utility:

`cargo install dbus-codegen`

And then run the utility. My command ended up looking something like this:

`dbus-codegen-rust -s -g -m None -d org.freedesktop.ModemManager1 -p /org/freedesktop/ModemManager1/Modem/0 > src/modemmanager.rs`

### Printing out the cell signal level

At this point your rust directory should look like this:

```
├── Cargo.lock  
├── Cargo.toml  
└── src  
    ├── main.rs  
    └── modemmanager.rs
```

First, let's update `Cargo.toml` with the `dbus` dependency

```
[dependencies]
dbus = "0.9.2"
# Extra deps omitted!
```

Now, we can get the signal level with the following code:

```rust
mod modemmanager;
//...
// Omitted extra packages!
//...
use dbus::{blocking::Connection, arg};

fn main() -> Result<(), Box<dyn std::error::Error>> {
	// Notice that we used `new_system`
	// This connects to the `system` dbus
    let conn = Connection::new_system()?;

    // Second, create a wrapper struct around the connection that makes it easy
    // to send method calls to a specific destination and path.
    let proxy = conn.with_proxy("org.freedesktop.ModemManager1", "/org/freedesktop/ModemManager1/Modem/0", Duration::from_millis(5000));

	// Import our generated interface!
    use modemmanager::OrgFreedesktopDBusProperties;
	
	// Use d-feet to make sure that this is the same on your system.
	// Look for the `SignalQuality` property
    let signal_quality:  Box<dyn arg::RefArg> = proxy.get("org.freedesktop.ModemManager1.Modem", "SignalQuality")?;
    
    // Cast the signal quality to an i64, yes this has some code smell, but we're just hacking this :)
    // in here
    let signal: i64 = signal_quality
        .as_iter()
        .unwrap()
        .next()
        .unwrap()
        .as_i64()
        .unwrap();

	// ...
	// I2C code from the previous omitted
	// ...
    Ok(())
}

```

![IMG_20210504_173254](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsmckggc52nmnq3egib4.jpg)
 

# What next?

Look at the methods generated by `dbus-codegen`. Instead of just running this method and closing we could listen for the `PropertiesChanged` signal to update the display. Or look at the existing [embedded hal drivers](https://crates.io/keywords/embedded-hal-driver) and pick up some new hardware to play with. Any of the sensors listed there should work just as easily as the SSD1306 display. If you have something which doesn't have a driver in Rust yet, well [writing embedded drivers in rust isn't that hard](https://hboeving.dev/blog/rust-2c-driver-p1/). Give it a try.

The Pinephone may still be a little rough to use as a daily driver, but it has a lot of potential and hackability. I hope more people invest in this device and we start seeing some more interesting projects.

# Misc Sources

1. https://wiki.pine64.org/index.php/PinePhone#Pogo_pins
2. https://docs.rust-embedded.org/book/portability/index.html
3. https://github.com/diwic/dbus-rs/blob/master/dbus/examples/argument_guide.md
4. https://github.com/diwic/dbus-rs
5. https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/