Reading the BNO08x IMU (from Golang)

We’re very fond of having an IMU on the robot. In the past we’ve used an IMU with a rate gyro, which tells you how fast the unit is rotating at any given instant. You then have to integrate the rate to get the actual angle. The problem with rate gyros is that any error in the rate gets integrated too, which means that your compass gradually drifts. This is a pain to deal with!

When we were looking for an IMU this time, Lance spotted Adafruit’s BNO08x breakout board; the chip it contains is actual magic. It has a rate gyro inside, but it runs all the integration and error cancelling onboard.

Even better than that, the BNO08x has an “easy mode” called UART-RVC, which stands for “Remote Vacuum Cleaner” mode. If you’re building something that looks like a remote vacuum cleaner (i.e. ground dwelling robot), you can put it in RVC mode by pulling a particular pin high and then there’s no configuration or callibration required. Simply connect the board to the Pi’s UART and it will send a 3-axis heading and accelerometer reading 100 times a second.

We’re reading the packets from the UART from Golang using the `”” library. After enabling the Pi’s GPIO UART using the device overlay, we open the serial port and wrap it with a buffered reader for efficiency and to allow peaking ahead:

mode := &serial.Mode{
	BaudRate: 115200,
s, err := serial.Open("/dev/ttyAMA0", mode)
if err != nil {
br := bufio.NewReader(s)

The packets from the chip always start with two bytes 0xAA 0xAA, so we wait for those; peaking ahead 2 bytes and then discarding 1 byte if we don’t see the correct bytes:

for {
	buf, err := br.Peek(2)
	if err != nil {
	if bytes.Equal(buf, []byte{0xAA, 0xAA}) {
	_, _ = br.Discard(1)
fmt.Printf("\nGot packet start.")

Then we loop, reading the 19-byte packets. We check each packet to make sure it starts with the header bytes and ends with the correct checksum…

const packetLen = 19
buf := make([]byte, packetLen)
var i int
for {
	_, err := io.ReadAtLeast(br, buf, packetLen)
	if err != nil {
	if !bytes.Equal(buf[:2], []byte{0xaa, 0xaa}) {
		fmt.Println("Lost sync.")
		goto resync
	//fmt.Printf("Packet: %x\n", buf)
	var checksum uint8
	for _, b := range buf[2 : packetLen-1] {
		checksum += b
	if buf[18] != checksum {
		fmt.Printf("  BAD CHECKSUM %x != %x\n", buf[18], checksum)
		goto resync
	var report IMUReport
	report.Index = buf[2]
	report.Yaw = int16(binary.LittleEndian.Uint16(buf[3:5]))
	report.Pitch = int16(binary.LittleEndian.Uint16(buf[5:7]))
	report.Roll = int16(binary.LittleEndian.Uint16(buf[7:9]))
	report.XAccel = int16(binary.LittleEndian.Uint16(buf[9:11]))
	report.YAccel = int16(binary.LittleEndian.Uint16(buf[11:13]))
	report.ZAccel = int16(binary.LittleEndian.Uint16(buf[13:15]))
	if i%10 == 0 {
		fmt.Printf("Report: %s\n", report.String())

It took me days to bring up the last Gyro we used, this one took about 30 minutes and its accuracy/resistance to drift looks great. With it sat on my desk, it detects a 0.2 degree rotation of the desk surface when I lean forward to type; magic!

$ go run ./cmd/imutests/
Got packet start.
Report: 1f Y: -32.18 P:  -0.94 R:  18.85 X:  -3.24 Y:  -0.16 Z:   9.48
Report: 29 Y: -32.18 P:  -0.94 R:  18.82 X:  -3.24 Y:  -0.16 Z:   9.51
Report: 33 Y: -32.18 P:  -0.93 R:  18.82 X:  -3.24 Y:  -0.16 Z:   9.51
Report: 3d Y: -32.18 P:  -0.93 R:  18.82 X:  -3.24 Y:  -0.16 Z:   9.44
Report: 47 Y: -32.18 P:  -0.93 R:  18.82 X:  -3.20 Y:  -0.16 Z:   9.55

The complete code for the test program is on github.

Leave a Reply

Your email address will not be published. Required fields are marked *