Raspberry Pi and drawing with 8 bit page – part two

Let’s move on. This time we will extract NJU6450 drivers and implement a line drawing.

part one
part three

– source @ GitHub

Decoupling NJU6450

During extraction of communication driver, I found out that it has two parameters in cmd and data functions. It is an enabled pin. We need to add it to interface and SSD1306 driver.
And we didn’t wire a reset pin. Now we need to hook it properly.
What we need to change?
First, wire RST to G5. Next, add:

'RST': 5,

to pins array.
Finally, we need to add:

RPi.GPIO.output(self.pins['RST'], 1)
time.sleep(0.025)
RPi.GPIO.output(self.pins['RST'], 0)
time.sleep(0.025)
RPi.GPIO.output(self.pins['RST'], 1)
time.sleep(0.025)

at the top of the reset function.
The whole process went rather smooth and it was a full success.

I took code from the previous post and switched SSD1306 for NJU6450:

from driver.nju6450.gpio import GPIO
from driver.nju6450.nju6450 import NJU6450
import random


def hole(x, y):
    o.draw_pixel(x+1, y)
    o.draw_pixel(x+2, y)
    o.draw_pixel(x+3, y)
    o.draw_pixel(x+1, y + 4)
    o.draw_pixel(x+2, y + 4)
    o.draw_pixel(x+3, y + 4)
    o.draw_pixel(x, y + 1)
    o.draw_pixel(x+4, y + 1)
    o.draw_pixel(x, y + 2)
    o.draw_pixel(x+4, y + 2)
    o.draw_pixel(x, y + 3)
    o.draw_pixel(x+4, y + 3)

drv = GPIO()
o = NJU6450(122, 32, drv)

o.init()
o.auto_flush = False
for _ in range(0, 50):
    hole(random.randint(2,115), random.randint(2,25))
hole(10, 10)
hole(15, 13)
hole(18, 23)
hole(40, 10)
o.flush(True)

And it worked:

Getting width and height

We have no width and height getters, they can be useful so let’s add them. But if we want them in both drivers we need to write that code two times. And this is wrong!
Time to do another refactor and extract common parts to another class.
What things? Width and height and getters and setters for them. We could move them to Chip and change both init functions in drivers:
Chip interface:

    def __init__(self, width, height):
        self.width = width
        self.height = height

Inits in NJU and SSD:

    def __init__(self, width, height, driver):
        Page.__init__(self)
        Chip.__init__(self, width, height)

        self.driver = driver
        self.options = {
            'auto_flush': True,
        }

We could move whole __init__ to Chip because it is still a code duplication but we would have a problem with inheritance.
We can now add the getter for dimensions and disable setters. Why? Because it is strange to change size during initialized display and this can do very bad things to our code.

class Chip(metaclass=abc.ABCMeta):
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @property
    def width(self):
        """get width"""
        return self._width

    @property
    def height(self):
        """get height"""
        return self._height
    ...

Now when I look at it, seems that we can also move the driver variable to Chip class.
Refactoring done, we can work on drawing the line.

The line

First, let’s create one test script to be usable on both screens. And during this, move auto_flush option to the constructor and to the driver class.
Base test script:

from driver.nju6450.gpio import GPIO
from driver.nju6450.nju6450 import NJU6450
from driver.ssd1306.spi import SPI
from driver.ssd1306.ssd1306 import SSD1306
import random

def hole(o, x, y):
    o.draw_pixel(x+1, y)
    o.draw_pixel(x+2, y)
    o.draw_pixel(x+3, y)
    o.draw_pixel(x+1, y + 4)
    o.draw_pixel(x+2, y + 4)
    o.draw_pixel(x+3, y + 4)
    o.draw_pixel(x, y + 1)
    o.draw_pixel(x+4, y + 1)
    o.draw_pixel(x, y + 2)
    o.draw_pixel(x+4, y + 2)
    o.draw_pixel(x, y + 3)
    o.draw_pixel(x+4, y + 3)

def draw_points(o):
    for _ in range(0, 50):
        hole(o, random.randint(2,o.width - 10), random.randint(2,o.height-10))
    o.flush(True)

lcd_oled = SSD1306(128, 64, SPI())
lcd_oled.init()
lcd_oled.auto_flush = False

lcd_nju = NJU6450(122, 32, GPIO())
lcd_nju.init()
lcd_nju.auto_flush = False

draw_points(lcd_oled)
draw_points(lcd_nju)

This shows that united interface works:) We have same drawing functions but different devices.

Drawing a line is something we have already written for TFT screen. Let’s use this algorithm with two changes. First, we will adapt it to draw horizontal and vertical straight lines, and we will change sub-lines drawing to writing pixels to buffer.

    def _calculate_steps(self, length, step, required_length):
        """calculate lineparts - helper"""
        steps = [length for _ in range(0, step)]
        if step * length < required_length: for idx in range(0, required_length - step * length): steps[idx] += 1 return steps def draw_line(self, x1, y1, x2, y2): """draw diagonal line""" width = abs(x2 - x1) height = abs(y2 - y1) if x1 == x2: steps = [height-2] horizontal = False offset_x = offset_y = 0 elif y1 == y2: steps = [width-1] horizontal = True offset_x = offset_y = 0 elif width > height:
            if x2 < x1: x1, x2 = x2, x1 y1, y2 = y2, y1 offset_y = 1 if y2 > y1 else -1
            offset_x = 1 if x2 > x1 else -1
            horizontal = True
            step = height
            length = width / step
            steps = self._calculate_steps(length, step, width)

        else:
            if y2 < y1: x1, x2 = x2, x1 y1, y2 = y2, y1 offset_y = 1 if y2 > y1 else -1
            offset_x = 1 if x2 > x1 else -1
            horizontal = False
            step = width
            length = height / step
            steps = self._calculate_steps(length, step, height)
        dy = 0
        dx = 0
        for idx, step in enumerate(steps):
            if horizontal:
                for appendix in range(int(step)+1):
                    self.draw_pixel(
                        int(x1 + dx + appendix),
                        int(y1 + (idx * offset_y))
                    )
                dx += step * offset_x
            else:
                for appendix in range(int(step)+1):
                    self.draw_pixel(
                        int(x1 + (idx * offset_x)),
                        int(y1 + dy + appendix)
                    )
                dy += step * offset_y

Time to see if this works, we need to update the test script with the following code:

def draw_net(o):
    s = 0
    while s < o.width-1:
        o.draw_line(s, 0, s, o.height-1)
        s += 10
    s = 0
    while s < o.height-1:
        o.draw_line(0, s, o.width-1, s)
        s += 10

lcd_oled.draw_line(0, 0, lcd_oled.width-1, lcd_oled.height-1)
lcd_oled.draw_line(0, lcd_oled.height-1, lcd_oled.width-1, 0)

draw_net(lcd_oled)
draw_net(lcd_nju)

lcd_oled.draw_line(0, 0, lcd_oled.width-1, 0)
lcd_oled.draw_line(0, lcd_oled.height-1, lcd_oled.width-1, lcd_oled.height-1)
#draw_points(lcd_oled)
#draw_points(lcd_nju)

lcd_nju.draw_line(0, 0, lcd_nju.width-1, lcd_nju.height-1)
lcd_nju.draw_line(0, lcd_nju.height-1, lcd_nju.width-1, 0)

lcd_oled.flush(True)
lcd_nju.flush(True)

The effect is stunning 😛

Summary

We made it, with the slight upgrade to the interface we support second chip: NJU6450.
And the code for drawing lines from TFT with small tweaks work with our small LCDs.

Advertisements

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s