Using LCD HD44780 on Raspberry Pi – part 3: upgrades and tests

Before we add new drivers lets do some more cleaning, upgrading and add nose tests.

First we will clean driver class and remove manual 4/8 bit function usage.

Next we will add abstraction class to our driver to force required functions implementation.

Finally we will add Nose framework for tests. And add few simple tests.

This time I will attach ziped source code

Add 4/8 bit mode switch to driver

Few function in our code double their functionality. I have 4bit and 8bit functions in mind. So we have cmd and cmd4. And we are manually using them. Its not very friendly.

Driver should know in what mode is and act accordingly.

We will remove cmd4 and similar. Function write in driver will detect mode and do its job accordingly.

First change init(self) in CharLCD to

   def init(self):
        '''Inits lcd display, send commands
        '''
        self.driver.init()

        self.driver.cmd(3)
        time.sleep(0.05)
        self.driver.cmd(3)
        time.sleep(0.05)
        self.driver.cmd(3)
        time.sleep(0.05)

        self.driver.cmd(2)
        self.driver.set_mode(4)
        self.driver.cmd(0x28)
        self.driver.cmd(0x08)
        self.driver.cmd(0x01)
        self.driver.cmd(0x06)
        self.driver.cmd(0x0D)

We are using only cmd.

Next change function write in driver to:

  
    def write(self, c):
        """main write function"""
        if self.mode == 4:
            self._write4(c)
        else:
            self._write8(c)

Add

self.mode = 8

to __init__ and add functions:

   
    def _write8(self, c):
        """write 8 bits"""
        for i in self.DATA_PINS:
            t = c & 0x01
            GPIO.output(self.PINS[i], t)
            c >>= 1
        self.send()

    def _write4(self, c):
        """write 4/4 bits"""
        data = (c >> 4)
        for i in self.DATA_PINS:
            t = data & 0x01
            GPIO.output(self.PINS[i], t)
            data >>= 1
        self.send()

        data = (c & 0x0F)
        for i in self.DATA_PINS:
            t = data & 0x01
            GPIO.output(self.PINS[i], t)
            data >>= 1
        self.send()

Last function we need is function to set current mode:

    def set_mode(self, mode):
        self.mode = mode

 

Driver interface

Our driver is required to have some functions. So we need to force their existence.

We will create a base class that will be extended by our drivers. In this class we will put all such functions with raise exceptions. This will force us to redefine them in driver. If we forget about any, error will refresh our memory.

In drivers directory create base.py and copy:

#!/usr/bin/python
# -*- coding: utf-8 -*-

__author__ = 'kosci'


class BaseDriver(object):
    def __init__(self):
        raise NotImplementedError

    def init(self):
        raise NotImplementedError

    def cmd(self):
        """write command"""
        raise NotImplementedError

    def shutdown(self):
        """shutdown procedure"""
        raise NotImplementedError

    def send(self):
        """send ack command"""
        raise NotImplementedError

    def write(self):
        """write data to lcd"""
        raise NotImplementedError

    def char(self):
        """write char to lcd"""
        raise NotImplementedError

    def set_mode(self):
        """sets driver mode"""
        raise NotImplementedError

 

Next change Gpio driver and add BaseDriver inheritance,

from base import BaseDriver

class Gpio(BaseDriver)

Nose tests and TDD

What is TDD ? Its a way of writing software from tests. First we write test, run it, fix errors / add logic, rerun test. Next refactor code run test and so :). Definition is on wikipedia so feel free to read it. I’m sorry but I wont dig into definitions and descriptions. I will try to focus on usage

What is Nose ? Nose is unittest expander. Tutorials are all over web. But I recommend reading this. Once again I will focus on usage. But few tips may be useful:

  •  file should begin with test_
  •  functions should begin with test_
  • test classes begin with Test
  • nose will try to find and run tests automatically
  • on auto only files with 644 mode are launched – be careful with samba share

For now lets write few tests for existing code. Create a test folder in charlcd package. In this folder create a file test_lcd.py.

For now we have a very little to test. We will create few simple test to check if our CharLCD initialize properly and if we can read width and height.

#!/usr/bin/python
# -*- coding: utf-8 -*-

__author__ = 'kosci'

import nose
from nose.tools import assert_equal
from charlcd import lcd


class TestLcd:
  def test_init(self):
    l = lcd.CharLCD(20, 4, None)
    assert_equal(l.get_width(), 20)
    assert_equal(l.get_height(), 4)

We are creating class without driver! I don’t know if there is a way to test our drivers. So for now lets stay like this.

From project root launch test by typing nosetests -v. If you see Ran 0 tests check file attributes. I’m working on samba share so I had to create small script that will change file mode and launch nose.

chmod 644 charlcd/test/test_lcd.py
nosetests -v

And the result is:

pi@raspberrypi ~/workspace/lcds $ ./tests
test_lcd.TestLcd.test_init ... ok

----------------------------------------------------------------------
Ran 1 test in 0.173s

OK

We can test functions stream_char and stream_string. They are new functions added while testing rf433 communication. A stream_char is taking char as parameter and displaying it on screen next to previous. When screen is full it start from beginning. A stream_string takes string and call stream_char in loop. Simple and effective.

Lets check if adding char will move internal pointer to next position. Add following test:

def test_stream(self):
 l = lcd.CharLCD(8, 2, None)
 l.stream_char("t")

Run nose and… it will crash! Our driver (None) has no attribute char. Things got complicated. Its time to write a new driver, Null one.

Add file null.py into drivers package and copy:

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'kosci'

from base import BaseDriver


class Null(BaseDriver):
 def __init__(self):
   pass

 def init(self):
   pass

 def cmd(self, c):
   return c

 def shutdown(self):
   pass

 def send(self):
   pass

 def write(self, c):
   return c

 def char(self, c):
   return ord(c)

 def set_mode(self, mode):
   return mode

Our class wont do much. But its ok. We just need a mock. Now in test file add

from charlcd.drivers.null import Null

And change

l = lcd.CharLCD(8, 2, None) into
l = lcd.CharLCD(20, 4, Null())

Now run tests and they should pass. But stream test is not testing anything! Lets create true test. Replace test_stream with:

def test_stream(self):
 l = lcd.CharLCD(8, 2, Null())
 assert_equal(l.c_w, 0)
 assert_equal(l.c_h, 0)
 
 l.stream_char("t")
 assert_equal(l.c_w, 1)
 assert_equal(l.c_h, 0)

And now we are testing something! When we print char pointer should increase.

But what about line breaking? Lets test it !

def test_stream_should_break_line(self):
 l = lcd.CharLCD(2, 2, Null())
 l.stream_char("t")
 l.stream_char("t")
 
 assert_equal(l.c_w, 0)
 assert_equal(l.c_h, 1)

When we put two chars on 2*2 screen pointer should be on line 1 position 0. But when we reach end it should restart.

def test_stream_should_return_top(self):
 l = lcd.CharLCD(2, 2, Null())
 l.stream_string("tt")
 l.stream_string("tt")

 assert_equal(l.c_w, 0)
 assert_equal(l.c_h, 0)

Running tests show that everything (we test) works:

pi@raspberrypi ~/workspace/lcds $ ./tests
test_lcd.TestLcd.test_init ... ok
test_lcd.TestLcd.test_stream ... ok
test_lcd.TestLcd.test_stream_should_break_line ... ok
test_lcd.TestLcd.test_stream_should_return_top ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.223s

OK

Lets stop for now. We have a basic tests, refactored code and driver interface.

See you in next part.

 

Advertisements

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