python 3.7 on raspberry pi 3b+ and raspbian 9.4


I’ve been working again on the RPI 3B+, this time with Python. The current version of Raspbian, 9.4, comes stock with Python 3.5.4. I wanted the latest and greatest, so I decided to actually build Python 3.7 from source and install it locally to my account’s home directory. This leaves both versions of distro Python alone.

Note before you get started that it will take a good three hours to build and test Python 3.7 before you can even install it for use. The long build time is due to the extensive suite of tests the build process performs before it’s finished. You want all the tests to complete and to be successful.

You can follow my simple, dense directions, or you can go here: https://www.scivision.co/compile-install-python-beta-raspberry-pi/

Directions To Build And Install

  1. Install support tools to build python
    • sudo apt install libffi-dev libbz2-dev liblzma-dev libsqlite3-dev libncurses5-dev libgdbm-dev zlib1g-dev libreadline-dev libssl-dev tk-dev build-essential libncursesw5-dev libc6-dev openssl git
  2. Download the latest Python 3.x source from the GitHub source repository
  3. Untar the source into your account’s home directory from where it was downloaded
    • tar xvzf ~/Downloads/cpython-3.7.0.tar.gz
    • Note that the default download for Chromium is ~/Downloads unless you change it
    • Note that the current version as of the date of this post is 3.7.0
  4. Configure Python 3.7
    • cd ~/cpython-3.7.0
    • ./configure –prefix=$HOME/.local –enable-optimizations
    • Note the convention of installing into ~/.local
  5. Build CPython
    • make -j -l 4
    • make install
    • Note that make will take two to three hours. Just let it run.
    • Do NOT run as root; in other words, do NOT run with sudo
  6. Add CPython 3.7 to your path such that it is found first
    • Edit ~/.bashrc, and somewhere towards the bottom add:
      • export PATH=$HOME/.local/bin/:$PATH
  7. Open a new shell session and test that CPython 3.7 and pip3 are accessible
    • ‘which python3’ should produce /home/[login]/.local/bin/python3
    • ‘which pip3’ should produce /home/[login]/.local/bin/pip3

Once that’s all done, you can also check with ‘python3 –version’ or ‘pip3 –version’. CPython 3.7 should return ‘3.7.0’ and pip3 should return ‘pip 10.0.1…’ with additional information showing the filesystem location from where it was invoked, which should match ‘which pip3’.

Why Python, And Why Now?

First, this is a reaction to my less than pleasant experience with Julia 1.0. Not everything worked as claimed in Julia. I also discovered that the performance of Julia on a Raspberry Pi 3B+ with Raspbian is poor compared to Python. CPython 3.7 is blazing fast by comparison, especially starting up, and Python’s resource footprint seems a lot smaller than Julia’s.

Second, I have some programatic math work I want to do that Python does quite well compared to Google’s Go. Google’s Go is great for what I want it to do at the hardware level as I’ve documented elsewhere on this blog. But it doesn’t (yet) have some of the math support frameworks that Python has. Trying to make a single language do everything, especially some of the math work I want to perform, is a bit stupid when existing tools already exist and I know how to use them. All the important bits that I needed to run in Go I’ve already converted to Go, and I’m quite happy with. Python can pick up the rest.

Third, some of the example Raspberry Pi code for manipulating hardware is written in Python (2.7 to be exact). If it’s already running then it’s foolish not to use it.

Finally, it’s fun. I learned how to pull the latest Python and get it running on the latest Raspberry Pi, and while it took a while to get it build and have it self-test, the final results were worth it. Python 3.7 is very, very fast for a scripting language. And I like the fact it runs quite well on the Raspberry Pi, a feature it shares with Google Go.

golang on the raspberry pi, part 4

animate vt52 from William Beebe on Vimeo.

It’s been a little while since my last posts. Busy and all. I’ve been working on writing more code to work with the Adafruit 0.8″ 8×16 LED Matrix FeatherWing Display. It’s still being driven by the Gobot framework on the Raspberry Pi 3 B+. It’s an interesting device, especially the way it’s wired with the H16K33 driver chip. Let me explain with the following diagram.

Let’s say I wanted to draw a smiley face on the left 8×8 LED matrix display. The first thing you have to realize is that the 8×8 display has to be loaded column centric, top to bottom, left to right. Just about every other display in the world addresses characters by rows, left to right, top to bottom. The second thing you have to realize is that because this device is on the I2C bus it has to be sent data serially. That means no addressing the character data directly. Instead you have to load a buffer, then send that buffer across the I2C serial interface, all 16 bytes in this case. Why 16 bytes, you ask? That’s the third realization; the two 8×8 matrix blocks have their display data interleaved. The left most block is laid out in the evenly addressed bytes of the HT13K33 display driver chip, while the rightmost block is laid out in the oddly addressed bytes. Thus, if all you want to send is one 8×8 matrix’s worth of data, you need to send all 16 bytes in the local buffer (see Adafruit816LedMatrix.go below).

It’s been interesting figuring out how to work with this device. I ran into a few issues using Gobot, such that I don’t use much except the core abstractions to the I2C bus such as I2cOperations, preferring to write everything else that I need beyond that. But that’s OK; the more complex code is obviously being used by others, and I like being able to find and use just what I need. I also finally understood placing code into Go packages, in order to make as much of my code as reuseable as possible. There’s still more to learn, and thus to do.

All my code is finally available at https://github.com/wbeebe/rpi. It doesn’t suck as much as it did when I first started, especially after the long overdue drive to break up all the duplicate code into packages that was beginning to spread around. That tiny repository is where you’ll find the VT52 converted ROM data.

And finally, the video at the top is using VT52 terminal characters converted from a VT52 rom dump. The VT52 used an 8×8 character cell, larger than later terminals and the PC, which used a 5×7 character cell. If you build animate.go and run it, the command is simply ‘animate vt52’.

/*
Copyright (c) 2018 William H. Beebe, Jr.

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.
*/

package devices

import (
    "fmt"

    "gobot.io/x/gobot/drivers/i2c"
    "gobot.io/x/gobot/platforms/raspi"
)

// Constants
//
// These constants are origially from Adafruit's GitHub
// repository C++ files at
// https://github.com/adafruit/Adafruit_LED_Backpack/
// Some changes made by me for clarity.
//

// Commands to send the HT16K33
//
const (
    HT16K33_SYSTEM_SETUP byte = 0x20
    HT16K33_OSCILLATOR_ON byte = 0x01
    HT16K33_DISPLAY_SETUP byte = 0x80
    HT16K33_DISPLAY_ON byte = 0x01
    HT16K33_BLINK_OFF byte = 0x00
    HT16K33_BLINK_2HZ byte = 0x02
    HT16K33_BLINK_1HZ byte = 0x04
    HT16K33_BLINK_HALFHZ byte = 0x06
    HT16K33_CMD_BRIGHTNESS byte = 0xE0
)

type HT16K33Driver struct {
    name string
    address int
    connection i2c.Connection
}

func NewHT16K33Driver(addr int) *HT16K33Driver {
    driver := &HT16K33Driver {
        name: "HT16K33",
        address: addr,
    }

    return driver
}

func (driver *HT16K33Driver) Name() string { return driver.name }
func (driver *HT16K33Driver) SetName(newName string ) { driver.name = newName }
func (driver *HT16K33Driver) Connection() i2c.Connection { return driver.connection }

// Initializes and opens a connection to an HT16K33.
// Returns the i2c.Connection on sucess, err on failure.
//
func (driver *HT16K33Driver) Start() (err error) {
    adapter := raspi.NewAdaptor()
    adapter.Connect()
    bus := adapter.GetDefaultBus()

    // Check to see if the device actually is on the I2C buss.
    // If it is then use it, else return an error.
    //
    if device, err := adapter.GetConnection(driver.address, bus) ; err == nil {
        if _, err := device.ReadByte() ; err == nil {
            fmt.Printf(" Using device 0x%x / %d on bus %d\n", driver.address, driver.address, bus)
        } else {
            return fmt.Errorf(" Could not find device 0x%x / %d", driver.address, driver.address)
        }
    }

    driver.connection, _ = adapter.GetConnection(driver.address, bus)
    // Turn on chip's internal oscillator.
    driver.connection.WriteByte(HT16K33_SYSTEM_SETUP | HT16K33_OSCILLATOR_ON)
    // Turn on the display. YOU HAVE TO SEND THIS.
    driver.connection.WriteByte(HT16K33_DISPLAY_SETUP | HT16K33_DISPLAY_ON)
    // Set for maximum LED brightness.
    driver.connection.WriteByte(HT16K33_CMD_BRIGHTNESS | 0x0f)
    return nil
}

// Clear the device of all data, and in the process turn off
// any LEDs that might be on.
//
func (driver *HT16K33Driver) Clear() {
    if driver.connection != nil {
        buffer := make([]byte, 16)
        driver.connection.WriteBlockData(0, buffer)
    }
}

func (driver *HT16K33Driver) Close() {
    if driver.connection != nil { driver.connection.Close() }
}
/*
Copyright (c) 2018 William H. Beebe, Jr.

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.
*/

package devices

import (
    "gobot.io/x/gobot/drivers/i2c"
)

var buffer []byte = make([]byte, 16)
var altIndex []int = []int{0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15}

// Loads the buffer with data, in the pattern necessary for proper
// displaying. Works with the concept of blocks that matches the
// 8x8 LED arrays on the display. Block 0 is on the left, block 1
// on the right.
//
func LoadBuffer(bits []byte, block int) {
    block &= 0x01

    for i := 0; i < len(bits) ; i++ {
        buffer[altIndex[i + block * 8]] = bits[i]
    }
}

// A wrapper for WriteBlockData for displaying the buffer.
//
func DrawBuffer(device i2c.Connection) {
    device.WriteBlockData(0, buffer)
}

// Rotates the buffer contents from left to right.
//
func RotateBuffer() {
    end := buffer[altIndex[len(buffer) - 1]]

    for i := len(buffer) - 1 ; i > 0 ; i-- {
        buffer[altIndex[i]] = buffer[altIndex[i-1]]
    }

    buffer[0] = end
}
/*
Copyright (c) 2018 William H. Beebe, Jr.

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.
*/

package main

import (
    "fmt"
    "log"
    "sort"
    "time"
    "os"
    "os/signal"
    "syscall"

    "gobot.io/x/gobot/drivers/i2c"

    "github.com/wbeebe/rpi/devices"
)

const DEFAULT_ADDRESS int = 0x70

// An application for the Adafruit 0.8" 8x16 LED Matrix FeatherWing Display.
//

// Higher level functions.
//
// Simple display glyphs
//
var blockCircle []byte  = []byte {0x3c, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3c}
var blockSquare []byte  = []byte {0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF}
var blockDiamond []byte = []byte {0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18}
var blockCheck []byte   = []byte {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}
var blockX []byte       = []byte {0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81}
var blockFace []byte    = []byte {0x3C, 0x42, 0xA9, 0x89, 0x89, 0xA9, 0x42, 0x3C}
var blockFrown []byte   = []byte {0x3C, 0x42, 0xA5, 0x89, 0x89, 0xA5, 0x42, 0x3C}
var blockSmile []byte   = []byte {0x3C, 0x42, 0xA9, 0x85, 0x85, 0xA9, 0x42, 0x3C}
var blockFslash []byte  = []byte {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}
var blockBslash []byte  = []byte {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}

// So why define this twice? Because I needed a set to display in insertion order,
// and a map to individually address each glyph by string name.
//
var shapeSet []*[]byte =
   []*[]byte {&blockCircle, &blockSquare, &blockDiamond, &blockCheck, &blockFslash, &blockBslash, &blockX, &blockFace, &blockFrown, &blockSmile}

var shapeTable= map[string]*[]byte{
    "circle": &blockCircle,
    "square": &blockSquare,
    "diamond": &blockDiamond,
    "check": &blockCheck,
    "forwardSlash": &blockFslash,
    "backSlash": &blockBslash,
    "x": &blockX,
    "face": &blockFace,
    "frown": &blockFrown,
    "smile": &blockSmile,
}

func listGlyphNames() {
    var names = make([]string, len(shapeTable))
    index := 0
    for key, _ := range shapeTable {
        names[index] = key
        index++
    }
    sort.Strings(names)
    for _, name := range names {
        fmt.Printf(" %s\n", name)
    }
}

// Simple animation with smiley faces. Similar to what Adafruit shows on their site
// with these 8x16 displays.
//
func simpleAnimation(device i2c.Connection) {
    for {
        devices.LoadBuffer(*shapeTable["face"], 0)
        devices.LoadBuffer(*shapeTable["frown"], 1)
        devices.DrawBuffer(device)
        time.Sleep( 500 * time.Millisecond )
        devices.LoadBuffer(*shapeTable["frown"], 0)
        devices.LoadBuffer(*shapeTable["smile"], 1)
        devices.DrawBuffer(device)
        time.Sleep( 500 * time.Millisecond )
        devices.LoadBuffer(*shapeTable["smile"], 0)
        devices.LoadBuffer(*shapeTable["face"], 1)
        devices.DrawBuffer(device)
        time.Sleep( time.Second )
    }
}

// Scroll's two glyphs across the display.
//
func simpleScroll(device i2c.Connection, glyphName string) {
    devices.LoadBuffer(*shapeTable[glyphName], 0)
    devices.LoadBuffer(*shapeTable[glyphName], 1)

    for {
        devices.DrawBuffer(device)
        time.Sleep( 250 * time.Millisecond )
        devices.RotateBuffer()
    }
}

// Displays a simple triangle wave across the display.
//
func wave(device i2c.Connection, cycles int) {
    devices.LoadBuffer(blockBslash, 0)
    devices.LoadBuffer(blockFslash, 1)

    for c := 0 ; c < cycles ; c++ {
        for i := 0 ; i < 16 ; i++ {
            devices.DrawBuffer(device)
            time.Sleep( 30 * time.Millisecond)
            devices.RotateBuffer()
        }
    }
}

func shapes(device i2c.Connection) {
    for _, glyph := range shapeSet {
        devices.LoadBuffer(*glyph, 0)
        devices.LoadBuffer(*glyph, 1)
        devices.DrawBuffer(device)
        time.Sleep( 500 * time.Millisecond)
    }
}

func vt52(device i2c.Connection) {
    for _, char := range devices.VT52rom {
        devices.LoadBuffer(char, 0)
        devices.LoadBuffer(char, 1)
        devices.DrawBuffer(device)
        time.Sleep( 350 * time.Millisecond)
    }
}

func help() {
    helpText := []string {
        "\n Adafruit 8x16 Featherwing Display utility\n",
        " Command line actions:\n",
        " faces  - Displays a series of three smiley faces.",
        " shapes - Displays a series of simple glyphs.",
        " scroll - Scrolls a selected glyph from left to right.",
        "        - scroll by itself scrolls a smiley face.",
        "        - 'animate scroll list' lists all glyphs.",
        " vt52   - Displays all the old VT-52 ROM characters",
        "        - translated to work with the Adafruit display.",
        " wave   - Displays a scrolling triangle wave for 10 cycles.\n",
        " No command - this help\n",
    }

    for _, line := range helpText {
        fmt.Println(line)
    }
}

//
//
func main() {
    ht16k33 := devices.NewHT16K33Driver(DEFAULT_ADDRESS)

    // Hook the various system abort calls for us to use or ignore as we
    // see fit. In particular hook SIGINT, or CTRL+C for below.
    //
    signal_chan := make(chan os.Signal, 1)
    signal.Notify(signal_chan,
        syscall.SIGHUP,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGQUIT)

    err := ht16k33.Start()
    if err != nil {
        log.Fatal(err)
    }

    // We want to capture CTRL+C to first clear the display and then exit.
    // We don't want to leave the display lit on an abort.
    //
    go func() {
        for {
            signal := <-signal_chan
            switch signal {
            case syscall.SIGINT:
                // CTRL+C
                fmt.Println()
                ht16k33.Clear()
                ht16k33.Close()
                os.Exit(0)
            default:
            }
        }
    }()

    var action, argument string

    if len(os.Args) > 1 {
        action = os.Args[1]
    }
    if len(os.Args) > 2 {
        argument = os.Args[2]
    }

    switch action {
    case "faces":
        simpleAnimation(ht16k33.Connection())
    case "scroll":
        if len(argument) == 0 {
            argument = "smile"
        } else {
            if argument == "list" {
                fmt.Printf("\n Scrollable glyphs are named:\n\n")
                listGlyphNames()
                break
            }
            if _, exist := shapeTable[argument]; ! exist {
                fmt.Printf("\n Glyph %s does not exist.\n", argument)
                fmt.Printf(" Please use one of:\n\n")
                listGlyphNames()
                break
            }
        }

        simpleScroll(ht16k33.Connection(), argument)
    case "wave":
        wave(ht16k33.Connection(), 10)
    case "shapes":
        shapes(ht16k33.Connection())
    case "vt52":
        vt52(ht16k33.Connection())
    default:
        help()
    }

    ht16k33.Clear()
    ht16k33.Close()
}