Sunday, 12 April 2015

Printing On the Panel

This is actually pretty simple, given the work to date. It could be a lot faster, but for simple scrolling messages then this should be plenty fast enough.

To plot out bitmaps we take three steps:
  • Generate a main bitmap
  • Break down the four grid regions of each panel (one per HT1632C)
  • Render each grid region bitmap

Doing this in reverse (bottom up)

A single HT1632C Renderer

We can define a single handler for a 16 * 8 LED grid, controlled by a single HT1632C chip, as:
#define DISPLAY_W (16)
#define DISPLAY_H (8)
#define DISPLAY_SZ (DISPLAY_W*DISPLAY_H)

class Grid
{

    public:
        Grid();
        virtual ~Grid();
...

    private:
        char _pixels_green[DISPLAY_SZ];
        char _pixels_red[DISPLAY_SZ];
        DisplayInterface* _interface;
        int _display_chip;
};
Where _pixels_green[] and _pixels_red[] are simply bitmaps for the LED states. We can set a single pixel value with
void Grid::SetPixel(int x, int y, char value_g, char value_r)
{
int offset;

    if ((x >= DISPLAY_W) || (y>= DISPLAY_H))
    {
        printf("Display data set is out of range\n");
    return;
    }
    offset = (y * DISPLAY_W) + x;
    _pixels_green[offset] = value_g;
    _pixels_red[offset] = value_r;
}


and the writeout for green looks something like:
    reg = 0;
    dcursor = 0;
    for (int x = 0 ; x < DISPLAY_W; x++)
    {
        for (int y=0; y < DISPLAY_H; y++)
        {
        int offset = (y * DISPLAY_W) + x;

            if (_pixels_green[offset] != 0)
            {
                reg |=1;
            }
            else
            {
                reg &=0xfe;
            }
            if ((dcursor+1) %4 == 0)
            {
               _interface->Command(_display_chip, 0x5, dcursor/4, reg);
            }
            else
            {
                reg = reg <<1;
            }
            dcursor++;
        }
    }

This loads the registers for each column from the incoming bitmap, writing out the register value every four pixels. X=Y=0 is in the top left hand corner of the display in this case. Other than this the class is simply getters and setters for the chip select on this panel (_display_chip) and the appropriate hardware interface controller, as well as grid clear operations (just memset the pixel arrays).

The red LED writeout operates using the same logic, only 32 registers higher.

A Four Grid (Panel) Renderer

This is  actually very simple - we create a master "bitmap" class for our main 32 * 16 display and it aggregates the grid class above:

#define BITMAP_WIDTH (32)
#define BITMAP_HEIGHT (16)
#define BITMAP_SZ (BITMAP_WIDTH*BITMAP_HEIGHT)

class BitMapper
{

    public:
        BitMapper();
        virtual ~BitMapper();
...
    private:
        char _pixels_green[BITMAP_SZ];
        char _pixels_red[BITMAP_SZ];

        Grid _grid;
        DisplayInterface _display;
};

 This has an almost identical setup logic for pixels as the Grid code from earlier. So now we can load and write out a single bitmap to the underlying panels with logic like:

    _grid.SetPanel(0);
    _grid.ClearPixels();
    for (x=0; x <16; x++)
    {
        for (y=0; y < 8; y++)
        {
        int offset = (y * BITMAP_WIDTH) + x;
            _grid.SetPixel(x, y, _pixels_green[offset], _pixels_red[offset]);
        }
    }
    _grid.Writeout();

    _grid.SetPanel(1);
    _grid.ClearPixels();
   for (x=16; x <32; x++)
        for (y=0; y < 8; y++)
        {
        int offset = (y * BITMAP_WIDTH) + x;

            _grid.SetPixel(x-16, y, _pixels_green[offset], _pixels_red[offset]);
        }

    _grid.Writeout();

and so on for for all four panels
i.e. we're simply looping over sections of the source bitmap, and setting the appropriate sub-panel select and pixel value.



The top level renderer

This is also fairly trivial - we create a pixel map for rendering characters and simply drop this into the BitMapper data, then invoke a write and flush it to the display. There's a discussion of bitmap fonts here and I'll just grab the Font Image here as an example of font grid comprising of 10*10 characters.

To get this into our source code we can just use The Gimp and load up the image, then autocrop it (to ensure the characters are on a grid from 0,0) and export as "C Source Code".

This produces the image data as a simple C-Style array:

/*
 * GIMP RGB C-Source image dump (rexpaint_cp437_10x10.c)
 * From http://www.gridsagegames.com/blog/2014/09/font-creation/
*/

#define GIMP_IMAGE_WIDTH (160)
#define GIMP_IMAGE_HEIGHT (160)
#define GIMP_IMAGE_BYTES_PER_PIXEL (3) /* 3:RGB, 4:RGBA */
#define GIMP_IMAGE_PIXEL_DATA ((unsigned char*) GIMP_IMAGE_pixel_data)

static const unsigned char GIMP_IMAGE_pixel_data[160 * 160 * 3 + 1] =
("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377"
...


In this code we have defines for the image width, height and pixel depth, as well as the image data array so we can reference a single pixel with code such as:
void GimpImport::SampleMap(int x, int y, char* red, char* green)
{
int index;

    index = y*GIMP_IMAGE_WIDTH*GIMP_IMAGE_BYTES_PER_PIXEL;
    index += x*GIMP_IMAGE_BYTES_PER_PIXEL;

   *red = GIMP_IMAGE_pixel_data[index++];
   *green = GIMP_IMAGE_pixel_data[index++];
return;
}

And we can copy single character glyphs out of the source bitmap and into a destination bitmap:

void GimpImport::RenderGlyph(int which, int x, int y)
{
int xin, yin;


    xin = ((which*_glyph_w) % GIMP_IMAGE_WIDTH);
    yin = (which/_glyph_per_line)* _glyph_h;

    for (int ln =0; ln < _glyph_h; ln++)
    {
        for (int xg =0; xg < _glyph_w; xg++)
        {
        char g,r;
            SampleMap(xin+xg, yin, &r, &g);
            PutPixel(x+xg, y, r, g);
        }
        y++;
        yin++;
    }
}

By combining this with a couple of simple strings we can then simply dump to the output panel:

Section of Character Map

Scrolling date text


Tuesday, 7 April 2015

CI20 and an LED Panel

The Application


So, wired up to the 4 GPIO pins from last time is a Sure DE-DP14111 32*16 LED Dot matrix Display. It's a smaller version of this display here, and although the LED's are smaller the physical HW is still very similar to the "3216 Bicolor LED 3mm Dot Matrix Display Information Board" from that page.

Hardware Interface

The interface to this board is a 16 pin IDC cable and two power rails. However the connector is fairly sparsely populated, and the power rails show as continuous.

Although the board is labelled as requiring a 5V supply I'm actually using a (separate) 3V3 supply to the Sure board, with a common ground to the CI20 and driving the inputs directly from the CI20 expansion header. This "works for me"(tm) with a dimmed display, however for a full 5V system I'd want to build a 3v3 to 5V driver board up, which I can skip for now.

The board really has 6 pins it requires:
  • Ground
  • Power - Nominally 5V, but I'm using 3v3 to match the CI20 expansion header levels
  • Two chip select  controls
  • Two Data line controls
The chip selects are worth taking a second to describe.

The front of the board consists of 8 LED panels, each of which contains an 8x8 LED Array, with 64 (bicolour) Red/Green LED's.

Logically speaking the display is actually broken up into four different areas; a pair of 8x8 panels (128 LEDs) is driven by a single HT1632C driver chip.

The two chip select inputs on the header are used to pick which of the HT1632C chips the data lines are communicating with, by controlling the chip select line of the HT1632C devices.

The chip select controls are a clock and a data line; these actually feed a 74HC164 shift register. Each of the device CS lines is fed to a separate delay stage. So when the chip select  line is pulsed and clocked then the shift register moves the "active low"  along the outputs through CS1 to CS2 to CS3 etc.

Using this mechanism the CS can be selected individually by pulsing the CS_IN line low, and clocking along to the specific chip, or multiple devices can be selected by holding the input low and clocking. In addition the boards can be chained together, since the final CS (CS4) is passed through to the output header.

This does make selecting a device slightly more complex however. A simple piece of code to do this, using the GPIO routines from the previous post, can be:
int DisplayInterface::ClockSelect(int val)
{
    _pins[SELECT_ACTIV].SetData(val);
    _pins[SELECT_CLOCK].SetData(1);
    usleep(USLEEP_DELAY);
    _pins[SELECT_CLOCK].SetData(0);

    usleep(USLEEP_DELAY);
return 0;
}

int DisplayInterface::SelectSegment(int which)
{
    for (int i=0 ; i < 4; i++)
    {// Clock all CS lines to high
        ClockSelect(1);
    }
    ClockSelect(0);
    while (which-- > 0)
    {
        ClockSelect(1);
    }
return 0;
}
i.e. we have two pins SELECT_ACTIV (the CS In, pin 1) and SELECT_CLOCK (CS Clock, pin 2). The ClockSelect() method takes the CS value in and makes a clock transition (low to high, then back to low). The usleep() means we can see the display fill as it happens by tuning the delay.


The SelectSegment() simply takes a chip offset then clocks the CS lines through so that all are high, then pushes in a low pulse and clocks this along to the target device.

This gives us a very simple "pick one" solution for the CS mechanism.

The Data

The data control of the device is likewise a serial bus, with a clock and data input. These fan out to all four of the HT1632C devices, and the chip select determines which one is active.

So a single bit write across the data interface would be of the form
int DisplayInterface::ClockWrite(int val)
{
    _pins[WRITE_CLOCK].SetData(0);
    _pins[WRITE_DATA].SetData(val);
usleep(USLEEP_DELAY);
    _pins[WRITE_CLOCK].SetData(1);
usleep(USLEEP_DELAY);
return 0;
}
Where WRITE_CLOCK and WRITE_DATA are pins 5 and 7 of the input header.

To simplify the write I'm only going to deal with simple command sequences here, where we enable the CS for a device, write a single command and then de-select it. There are continuous write modes, but for my application (simple, largely static, display) then I don't care about frequent updates, so I won't bother with that for now.

There are actually two kinds of data we can send to the devices, one set is simple and one is not.

Configuration

The "not simple" part is the control sequences which set up the HT1632C devices. These configure the output stages, system oscillator, etc. However the good news is that the data sheet contains some simple "good" configurations we can use.

The configuration sequences are 12 bits each, and are shown in the Command Summary section of the user guide; Figure 2-14 (and on p21 of the HT1632C data sheet); the first three bits indicate the Command ID, and the following bits are configuration information.

For our application we can just use the following canned configuration strings

uint32_t sysen = 0x802;
uint32_t ledon = 0x806;
uint32_t nmos = 0x840;
uint32_t rcmaster = 0x830;
uint32_t pwm10 = 0x962;

These are:
  • SYS_EN - Turn the oscillator on
  • LED On - Turn on the LED Duty cycle generator
  • COM Option - N-MOS open drain output and 8 COM option
  • RC Master mode - set clock from on chip oscillator
  • PWM Duty - 10/16 Duty cycle

Setting them is fairly simple: we can push out a 12 bit configuration packet with the function:

int DisplayInterface::Issue12(uint32_t data)
{
int pos = 11;

     while (pos >= 0)
    {
    uint32_t out;
        out = data >> pos;
        ClockWrite(out &0x01);
        pos--;
    }
return 0;
}


Which simply shifts the correct bits into the output position and uses ClockWrite() to issue it to the board.

We can therefore configure a single chip with:

int DisplayInterface::Enable(int which)
{
uint32_t sysen = 0x802;
uint32_t ledon = 0x806;
uint32_t nmos = 0x840;
uint32_t rcmaster = 0x830;
uint32_t pwm10 = 0x962;

    SelectSegment(which);
    Issue12(sysen);
    SelectSegment(which);
    Issue12(ledon);
    SelectSegment(which);
    Issue12(rcmaster);
    SelectSegment(which);
    Issue12(nmos);
    SelectSegment(which);
    Issue12(pwm10);
return 0;
}

Data

Data is much simpler. All data sequences are a three bit ID (101 - write) , and then a 7 bit address and 4 bits of data

The output LEDs are simply memory mapped; each 4 bit data register maps to four LED status settings. The first 32 registers (0x00-0x1F) control the green LEDs - (4 * 32 = 128 LED's from each panel pair) and the next 32 (0x20-0x3F) control the corresponding red LEDs.

So we can write a data sequence with:
int DisplayInterface::Command(int which, unsigned char id, unsigned char addr, unsigned char data)
{
uint32_t wire;
int pos = 13;

    SelectSegment(which);

    wire = id & 0x07;
    wire = wire << 7;
    wire |= addr&0x7F;
    wire = wire << 4;
    wire |= data&0x0F;
    //printf("Write Command 0x%x\n", wire);
    while (pos >= 0)
    {
    uint32_t out;
        out = wire >> pos;
        ClockWrite(out &0x01);
        pos--;
    }
r
eturn 0;
}

(although we form up the id we don't need to do this, since it's always 0x5 for write)

And therefore using the DisplayInterface class we've defined to date we can do something like this:

DisplayInterface display;
...
    display.Enable(0);
    display.Enable(1);
    display.Enable(2);
    display.Enable(3);

    for (int j=0; j < 100; j++ )
    {

        for (int i =0 ; i < 64; i++)
        {
            display.Command(0, 0x5, i, 0x0f);
            display.Command(1, 0x5, i, 0x0f);
            display.Command(2, 0x5, i, 0x0f);
            display.Command(3, 0x5, i, 0x0f);
        }

        for (int i =0 ; i < 64; i++)
        {
            display.Command(0, 0x5, i, 0x00);
            display.Command(1, 0x5, i, 0x00);
            display.Command(2, 0x5, i, 0x00);
            display.Command(3, 0x5, i, 0x00);
        }
    }

Which turns all the LED's on and off again on all the panels in a (more or less) parallel sequence:



Sunday, 5 April 2015

Yet More CI20: How to manipulate the GPIO

The GPIO in this case is pretty typical - it's a set of standard Linux interfaces under /sys/class/gpio/.

So the access is a simple three stage process:
  • Export the GPIO pin, to provide sysfs hooks
  • Turn the GPIO into an input or output using "direction"
  • Manipulate or read tthe value using "value"
The sample CI20 resource provide a blinking LED tutorial which goes over the basics of evaluating the PIO from the shell and using sysfs to set the value.

For this case I'll build a simple little C++ program to manipulate the system files using standard POSIX file calls, and which covers the first four GPIO pins on the expansion header (1,2,3 & 4).

This is actually fairly trivial: first I define a simple structure which relates a GPIO number to the sysfs number we use for export and access. I also throw in a PD reference for the CI20 here, but don't really need to use it


typedef struct{
    int gpio_number;
    int pd_ref;
    int sys_ref;
}portinfo;


static const portinfo ports[]={
    {1, 28, 124},
    {2, 26, 122},
    {3, 27, 123},
    {4, 29, 125},
};

Next up we create a simple "pin" class, which  hides the underlying sysfs access from the outside, and simply provides us with basic IO hooks

class GpioPin
{
public:
    GpioPin();
    int Open(int which);
    void Close();

    int SetOutput();
...
    int SetData(int val);
...
};

Following this then we'll fill in some of the simpler functions: there's a bit of vestigial stuff from the testing phases in the actual code (Toggle, etc) but the key functions we'll use here are those above:

  GpioPin::Open() Looks through the ports list, to try and find which pin we're accessing then opens the relevant sysfs interface.

 GpioPin::SetOutput() sets the pin as an output (which for the tests I'm doing is all I'm using for the moment)

 GpioPin::SetData() pushes a data value to the port pin.

 GpioPin::Close() closes any open sysfs file handles


So to use this class the simplest case to toggle a pin high then low is:

GpioPin pin;
  pin.Open(1);
  pin.SetOutput();
  pin.SetData(1);
...
  pin.SetData(0);
  pin.Close();
...

And a complete example to toggle the first four pins is attached below. Compile it with "mips-linux-gnu-g++ -Wall gpio_handler.cpp" and run it on the CI20.

Scoping the output on the header gets us:


There's some ringing on the data lines, but that's poor grounding hygiene on my part. Otherwise we're ticking along fairly well.

The full code is:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_STR (4096)

typedef struct{
    int gpio_number;
    int pd_ref;
    int sys_ref;
}portinfo;

static const portinfo ports[]={
    {1, 28, 124},
    {2, 26, 122},
    {3, 27, 123},
    {4, 29, 125},
};

class GpioPin
{
public:
    GpioPin();
    int Open(int which);
    void Close();
    int SetOutput();
    int SetData(int val);

private:
   const portinfo* _portinfo;
   int _fd_data;
   int _fd_direction;
   int _create_port_sysfs();
   int _do_open();
};

int GpioPin::_do_open()
{
char gpiodirection[MAX_STR];
char gpioval[MAX_STR];
int which;
 
    which = _portinfo->sys_ref;

    snprintf(gpiodirection, MAX_STR, "/sys/class/gpio/gpio%d/direction", which);
    snprintf(gpioval, MAX_STR, "/sys/class/gpio/gpio%d/value", which);

    _fd_data = open(gpioval, O_WRONLY);
    _fd_direction = open(gpiodirection, O_WRONLY);

    if ((_fd_data <0) || (_fd_direction <0))
    {
        printf("Can't open the port access files\n");
    }

return 0;
}

int GpioPin::_create_port_sysfs()
{
char gpiosel[MAX_STR];
char gpiodir[MAX_STR];
DIR * dirref;
int which;

    which = _portinfo->sys_ref;
    snprintf(gpiodir, MAX_STR, "/sys/class/gpio/gpio%d", which);
    dirref = opendir(gpiodir);
    if (dirref == NULL)
    {
    const char* exp_dir="/sys/class/gpio/export";
    int fd;
     
        fd = open(exp_dir, O_WRONLY);
        if (fd <0)
        {
            perror("Cannot access given GPIO");
        return -1;
        }

        snprintf(gpiosel, MAX_STR, "%d",which);
        write(fd, gpiosel, strlen(gpiosel));
        close(fd);

        dirref = opendir(gpiodir);
    }

    if (dirref == NULL)
    {
        printf("Can't get at Directory %s?\n", gpiodir);
    return -1;
    }
    closedir(dirref);
return 0;
}

GpioPin::GpioPin()
{
}

int GpioPin::Open(int which)
{
    _portinfo = NULL; //@todo: close if open

    for (unsigned int i=0; i< (sizeof(ports)/sizeof(portinfo)); i++)
    {
    const portinfo* inf;
        inf = &ports[i];
        if (inf->gpio_number == which)
        {
            printf("Found port pin %d on sysfs index %d\n", which,  inf->sys_ref);
         _portinfo=inf;
        break;
        }
    }

    if (_portinfo == NULL)
    {
        printf("Cannot find the specific port pin %d \n", which);
    return -1;
    }
    if (_create_port_sysfs() < 0)
    {
        printf("Cannot open the sysfs hooks for port pin %d \n", which);
    return -1;
    }
return _do_open();
}


int GpioPin::SetOutput()
{
    if (_fd_direction >0)
        return write(_fd_direction, "out", 3);

    printf("Cannot set the output");
return -1;
}

int GpioPin::SetData(int val)
{
const char* c;
    if (val>0)
    {
        c = "1";
    }
    else
    {
        c = "0";
    }
    if (_fd_data >0)
        return write(_fd_data, c, 1);

return -1;
}

void GpioPin::Close()
{
    if (_portinfo == NULL)
        return;
    if (_fd_data >0)
    {
        close(_fd_data);
    }
    if (_fd_direction >0)
    {
        close(_fd_direction);
    }
}

int main(int argc, char *argv[])
{
GpioPin pin[4];

    pin[0].Open(1);
    pin[1].Open(2);
    pin[2].Open(3);
    pin[3].Open(4);

    pin[0].SetOutput();
    pin[1].SetOutput();
    pin[2].SetOutput();
    pin[3].SetOutput();

    for (int i=0; i< 100; i++)
    {
        pin[0].SetData(1);
        pin[1].SetData(1);
        pin[2].SetData(1);
        pin[3].SetData(1);

        pin[0].SetData(0);
        pin[1].SetData(0);
        pin[2].SetData(0);
        pin[3].SetData(0);
    }

    pin[0].Close();
    pin[1].Close();
    pin[2].Close();
    pin[3].Close();

return 0;
}

Simple, no?