Jump to content
roadrunner84

[Energia Library] Charlieplexing (and more) library

Recommended Posts

I was planning on writing a charlieplexing library, this is still an in-progress, but I'd like to share it with you people already.

I realised that the library is currently so generic that it can be used for other things than LED matrix charlieplexed driving, some future applications:

  • Charlieplexed display (matrix)
  • Charlieplexed keyboard
  • Multiplexed display or keyboard
  • Charlieplexed or multiplexed 1D display (string)

I tried buidling the code to be C++98/03 complaint, but it lacked the awesome Array class (more features than a plain C array but less heavy than the C++ Vector class). So there are a few options: use C++0x/11, use Boost::Array or write a lean and mean Array class. I plan on providing replacement classes for both Array and Bitset which are tuned more to the limitations of the MSP430, but I want to make this library portable, so I keep writing with the STL implementations until I'm done.

 

Code so far:

 

 

#include <bitset>
#include <stdint.h>
#include <iostream>
#include <array>
using namespace std;

class Pin{};

template <uint8_t& pout, uint8_t& pdir>
class MSP430Pin: public Pin
{
  public:
    MSP430Pin(const uint8_t bit):bit(bit){}
    ~MSP430Pin(){}
    void high(){pout |= bit; pdir |= bit;}
    void low(){pout &= ~bit; pdir |= bit;}
    void floating(){pdir &= ~bit;}
  private:
    const uint8_t bit;
};

template <size_t M, size_t N>
class Image
{
  public:
    // Shift one row/column in, returns the row/column that shifted out
    bitset<N> shiftLeft(bitset<N> in = 0);
    bitset<N> shiftRight(bitset<N> in = 0);
    bitset<M> shiftUp(bitset<M> in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    bitset<M> shiftDown(bitset<M> in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    // Rotates one pixel in, returning pixel shifted out. Wraps towards/from origin
    bool rotateLeft(bool in = 0);
    bool rotateRight(bool in = 0);
    bool rotateUp(bool in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    bool rotateDown(bool in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    // Omit the "in" parameter to use these as "clear..." methods
    bool writePixel(size_t x, size_t y, bool in = 0);
    bitset<N> writeColumn(size_t x, bitset<N> in = 0);
    bitset<M> writeRow(size_t y, bitset<M> in = 0);
    void writeImage(array<bitset<N>, M> in = 0);
  private:
    array<bitset<N>, M> image;
};

template <size_t N>
class Image <0, N>
{
  private:
    Image(){}
    ~Image(){}
};
class Image <M, 0>
{
  private:
    Image(){}
    ~Image(){}
};

template <size_t N>
class Charlieplex: public Image<N - 1, N>
{
  public:
    Charlieplex(const array<Pin, N> pins):pins(pins){}
    void tick();
  private:
    const array<Pin, N> pins;
};

template<size_t M, size_t N>
bitset<N> Image<M, N>::shiftLeft(bitset<N> in)
{
  bitset<N> out = image[0];
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1] = image[i];
  }
  image[M - 1] = in;
  return out;
}

template<size_t M, size_t N>
bitset<N> Image<M, N>::shiftRight(bitset<N> in)
{
  bitset<N> out = image[M - 1];
  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] = image[i - 1];
  }
  image[0] = in;
  return out;
}

template<size_t M, size_t N>
bitset<M> Image<M, N>::shiftDown(bitset<M> in)
{
  bitset<M> out = 0;
  for(size_t i = 0; i < M; ++i)
  {
    out[i] = image[i][0];
    image[i] >>= 1;
    image[i][N - 1] = in[i];
  }
  return out;
}

template<size_t M, size_t N>
bitset<M> Image<M, N>::shiftUp(bitset<M> in)
{
  bitset<M> out = 0;
  for(size_t i = 0; i < M; ++i)
  {
    out[i] = image[i][N - 1];
    image[i] <<= 1;
    image[i][0] = in[i];
  }
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateLeft(bool in)
{
  bool out = image[0][0];
  bitset<N> temp = image[0];
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1] = image[i];
  }
  temp >>= 1;
  temp[N - 1] = in;
  image[M - 1] = temp;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateRight(bool in)
{
  bool out = image[M - 1][N - 1];
  bitset<N> temp = image[M - 1];
  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] = image[i - 1];
  }
  temp <<= 1;
  temp[0] = in;
  image[0] = temp;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateUp(bool in)
{
  bool out = image[M - 1][N - 1];
  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] <<= 1;
    image[i][0] = image[i - 1][N - 1];
  }
  image[0] <<= 1;
  image[0][0] = in;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateDown(bool in)
{
  bool out = image[0][0];
  image[0] >>= 1;
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1][N - 1] = image[i][0];
    image[i] >>= 1;
  }
  image[M - 1][N - 1] = in;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::writePixel(size_t x, size_t y, bool in)
{
  bool out = image[x][y];
  image[x][y] = in;
  return out;
}

template<size_t M, size_t N>
bitset<N> Image<M, N>::writeColumn(size_t x, bitset<N> in)
{
  bitset<N> out = image[x];
  image[x] = in;
  return out;
}

template<size_t M, size_t N>
bitset<M> Image<M, N>::writeRow(size_t y, bitset<M> in)
{
  bitset<M> out = 0;
  for(int i = 0; i < N - 1; ++i)
  {
    out[i] = image[i][y];
    image[i][y] = in[i];
  }
  return out;
}

template<size_t M, size_t N>
void Image<M, N>::writeImage(array<bitset<N>, M> in)
{
  image = in;
}

 

 

Example usage:

int main()
{
  P1DIR = 0;
  MSP430Pin<P1OUT, P1DIR> P1_3{BIT3};
  MSP430Pin<P1OUT, P1DIR> P1_2{BIT2};
  Image<5, 4> display;
  Charlieplex<5> display_driver {array<Pin, 5>{P1_3, P1_2, P1_3, P1_3, P1_3}};
  display.shiftLeft();
  return 0;
}

Did a rework on the code; I separated Image and Charlieplex classes. Renamed setPixel() et al to writePixel(), added rotateUp() et al to emulate (LED) string like behaviour.

Share this post


Link to post
Share on other sites

I've been tinkering a bit more. I'm still far from done, but this code should work as is.

The MSP430Pin should become a part of the MSP430Port class, it would be a cleaner design.

I've been trying to solve the puzzle of accessing through port-wide access, but still working on a solution for this without using heap/free space memory. I think for an embedded system avoiding heap usage is essential because we can't just "restart" the application.

 

 

#include <bitset>
#include <stdint.h>
#include <iostream>
//#include <array>
#include <algorithm> // for array::operator==
using namespace std;

///// minimal implementation of std::array /////
template <typename T, size_t N>
class array;

template <typename T, size_t N>
bool operator==(const array<T, N>& lhs, const array<T, N>& rhs)
{
  return equal(lhs.value, lhs.value + N, rhs.value);
}

template <typename T, size_t N>
class array
{
  public:
    typedef T& reference;
    typedef const T& const_reference;
    typedef size_t size_type;
    reference operator[] (size_type n) {return value[n];}
    const_reference operator[] (size_type n) const {return value[n];}
    friend bool operator== <>(const array<T, N>& lhs, const array<T, N>& rhs);
  private:
    T value[N];
};
///// minimal implementation of std::array /////

class Pin
{
  public:
    virtual void high() const = 0;
    virtual void low() const = 0;
    virtual void floating() const = 0;
    //virtual void pullup() const = 0;
    //virtual void pulldown() const = 0;
};

class MSP430Pin;
 
class MSP430Port
{
  public:
    MSP430Port(READ_ONLY DEFXC& in, DEFXC& out, DEFXC& dir, DEFXC& ifg, DEFXC& ies, DEFXC& ie, DEFXC& sel, DEFXC& ren)
    : in(in), out(out), dir(dir), ifg(ifg), ies(ies), ie(ie), sel(sel), ren(ren) {}
  private:
    friend class MSP430Pin;
    READ_ONLY DEFXC& in;
    DEFXC& out;
    DEFXC& dir;
    DEFXC& ifg;
    DEFXC& ies;
    DEFXC& ie;
    DEFXC& sel;
    DEFXC& ren;
};

class MSP430Pin: public Pin
{
  public:
    MSP430Pin(const MSP430Port& port, const uint8_t pin): port(port), pin(pin){}
    void high() const {port.out |= pin; port.dir |= pin;}
    void low() const {port.out &= ~pin; port.dir |= pin;}
    void floating() const {port.dir &= ~pin;}
  private:
    const uint8_t pin;
    const MSP430Port& port;
};
 
template <size_t M, size_t N>
class Image;

template <size_t M, size_t N>
bool operator==(const Image<M, N>& lhs, const Image<M, N>& rhs)
{
  return lhs.image == rhs.image;
}

template <size_t M, size_t N>
class Image
{
  public:
    typedef size_t size_type;
    static const size_type width = M;
    static const size_type height = N;
    typedef bitset<M> row;
    typedef bitset<N> column;
    friend bool operator== <>(const Image<M, N>& lhs, const Image<M, N>& rhs);
    // Shift one row/column in, returns the row/column that shifted out
    column shiftLeft(column in = 0);
    column shiftRight(column in = 0);
    row shiftUp(row in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    row shiftDown(row in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    // Rotates one pixel in, returning pixel shifted out. Wraps towards/from origin
    bool rotateLeft(bool in = 0);
    bool rotateRight(bool in = 0);
    bool rotateUp(bool in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    bool rotateDown(bool in = 0); //Using cartesian coordinates, swap Up and Down for "picture" coordinates
    // Omit the "in" parameter to use these as "clear..." methods
    void writePixel(size_type x, size_type y, bool in = 0);
    void writeColumn(size_type x, column in = 0);
    void writeRow(size_type y, row in = 0);
    void writeImage(array<column, M> in = 0);
    // Same as above, but will return the value that was overwritten
    bool replacePixel(size_type x, size_type y, bool in = 0);
    column replaceColumn(size_type x, column in = 0);
    row replaceRow(size_type y, row in = 0);
    // Reference exposure to the image data, bitsets behave as columns
    column& operator[] (size_type x) {return image[x];}
    const column& operator[] (size_type x) const {return image[x];}
  private:
    array<column, M> image;
};
 
template <size_t N>
class Image <0, N>
{
  private:
    Image(){}
    ~Image(){}
};
template <size_t M>
class Image <M, 0>
{
  private:
    Image(){}
    ~Image(){}
};

template<size_t M, size_t N>
typename Image<M, N>::column Image<M, N>::shiftLeft(bitset<N> in)
{
  bitset<N> out = image[0];
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1] = image[i];
  }
  image[M - 1] = in;
  return out;
}

template<size_t M, size_t N>
typename Image<M, N>::column Image<M, N>::shiftRight(bitset<N> in)
{
  bitset<N> out = image[M - 1];
  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] = image[i - 1];
  }
  image[0] = in;
  return out;
}
 
typename Image<M, N>::row Image<M, N>::shiftDown(bitset<M> in)
{
  bitset<M> out = 0;
  for(size_t i = 0; i < M; ++i)
  {
    out[i] = image[i][0];
    image[i] >>= 1;
    image[i][N - 1] = in[i];
  }
  return out;
}

template<size_t M, size_t N>
typename Image<M, N>::row Image<M, N>::shiftUp(bitset<M> in)
{
  bitset<M> out = 0;
  for(size_t i = 0; i < M; ++i)
  {
    out[i] = image[i][N - 1];
    image[i] <<= 1;
    image[i][0] = in[i];
  }
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateLeft(bool in)
{
  bool out = image[0][0];
  bitset<N> temp = image[0];
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1] = image[i];
  }
  temp >>= 1;
  temp[N - 1] = in;
  image[M - 1] = temp;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateRight(bool in)
{
  bool out = image[M - 1][N - 1];
  bitset<N> temp = image[M - 1];

  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] = image[i - 1];
  }
  temp <<= 1;
  temp[0] = in;
  image[0] = temp;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateUp(bool in)
{
  bool out = image[M - 1][N - 1];
  for(size_t i = M - 1; i > 0; --i)
  {
    image[i] <<= 1;
    image[i][0] = image[i - 1][N - 1];
  }
  image[0] <<= 1;
  image[0][0] = in;
  return out;
}

template<size_t M, size_t N>
bool Image<M, N>::rotateDown(bool in)
{
  bool out = image[0][0];
  image[0] >>= 1;
  for(size_t i = 1; i < M; ++i)
  {
    image[i - 1][N - 1] = image[i][0];
    image[i] >>= 1;
  }
  image[M - 1][N - 1] = in;
  return out;
}

template<size_t M, size_t N>
void Image<M, N>::writePixel(size_t x, size_t y, bool in)
{
  image[x][y] = in;
}
 
template<size_t M, size_t N>
void Image<M, N>::writeColumn(size_t x, bitset<N> in)
{
  image[x] = in;
}

template<size_t M, size_t N>
void Image<M, N>::writeRow(size_t y, bitset<M> in)
{
  for(int i = 0; i != N; ++i)
  {
    image[i][y] = in[i];
  }
}

template<size_t M, size_t N>
void Image<M, N>::writeImage(array<bitset<N>, M> in)
{
  image = in;
}

template<size_t M, size_t N>
bool Image<M, N>::replacePixel(size_t x, size_t y, bool in)
{
  bool out = image[x][y];
  image[x][y] = in;
  return out;
}

template<size_t M, size_t N>
typename Image<M, N>::column Image<M, N>::replaceColumn(size_t x, bitset<N> in)
{
  bitset<N> out = image[x];
  image[x] = in;
  return out;
}

template<size_t M, size_t N>
typename Image<M, N>::row Image<M, N>::replaceRow(size_t y, bitset<M> in)
{
  bitset<M> out = 0;
  for(int i = 0; i != N; ++i)

  {
    out[i] = image[i][y];
    image[i][y] = in[i];
  }
  return out;
}

template <size_t N>
class Charlieplex
{
  public:
    typedef Image<N - 1, N> image_type;
    typedef typename image_type::size_type size_type;
    typedef array<Pin*, N> pinmap_type;
    Charlieplex(const pinmap_type pins, const image_type& canvas, bool monotonic = true)
      :pins(pins), image(&canvas), x(1), y(0), monotonic(monotonic){}
    Charlieplex& operator= (const image_type& canvas){image = &canvas; return *this;}
    void tick();
  private:
    const pinmap_type pins;
    const image_type* image;
    size_type x;
    size_type y;
    const bool monotonic;
};

template <size_t N>
void Charlieplex<N>::tick()
{
  const size_type old_x = x;
  const size_type old_y = y;
  do
  {
    if (++x == N - 1)
    {
      x = 0;
      if (++y == N) y = 0;
    }
  } while(!(monotonic || (*image)[x][y] || ((old_x == x) && (old_y == y)))); // stop iterating on monotonic setting or lit pixel or took whole loop
  pins[old_y]->floating();
  if ((*image)[x][y])
  {
    if (x >= y)

    {
      pins[old_x + 1]->floating();
      pins[x + 1]->high();
    }
    else
    {
      pins[old_x]->floating();
      pins[x]->high();
    }
    pins[y]->low();
  }
}

and here's an example usage

 

int main()
{
  MSP430Port P1(P1IN, P1OUT, P1DIR, P1IFG, P1IES, P1IE, P1SEL, P1REN);
  MSP430Pin P1_3(P1, BIT3);
  MSP430Pin P1_2(P1, BIT2);
  Image<2, 3> char_img;
  array<Pin*, 3> pinmap;
  pinmap[0] = &P1_2;
  pinmap[1] = &P1_2;
  pinmap[2] = &P1_3;
  char_img[1][1] = 1;
  Charlieplex<3> charlie(pinmap, char_img);
  charlie = char_img;
  charlie.tick();
  return 0;
}

I still want to move the lot of the Image member functions to a subclass, AnimatedImage or something like that.

Share this post


Link to post
Share on other sites

I realize it's been a while since you posted this, but could you explain your example with a bit more detail?  

If I have 6 LEDs charlieplexed on three pins (or 12 on four pins, or 20 on 5), what would I use to set it up?

Share this post


Link to post
Share on other sites

[disclaimer]What I tried to do is avoid generic programming (which Energia usually uses for ports and pins), as a result the interface is a but bloated.[/disclaimer]

 

First I declare the port and pins I'll be using, this is quite straightforward.

Then I declare an image that maps onto those pins, so for a 3-pin clarlieplex I'll have an image of 2x3 "pixels".

Then I declare an array that consists of all the pins used in this charlieplex driver.

 

Now, char_img can be accessed as a 2D array, so I can write 1 and 0 bits in it to toggle pixels. For example char_img[1][1] = 1; to set pixel (1,1) to high.

Now I actually declare a charlieplex driver, I pass an array of pins to it and an image to pass to the pins. I've done this to restrict users from passing no image, you must have an image associated with the driver. Also, this image must have the correct dimensions.

Instead of editing the image while in place in the driver, you can also "assign" an image to the driver, which will replace the image instantaneously instead of pixel per pixel. This is done by charlie = char_img;

Since the driver is fully agnostic (it does not know about timers, or even about the MSP430, it only knows about tri-state pins) it must be hooked into your timer, this is done by calling charlie.tick(); in a rhythm, preferably by calling it from the timer ISR.

 

Hopefully I shed some light on how to use this driver, I should note that this driver is more of an exercise in C++ and abstraction than it is a true Energia library, though it should be functional for this purpose.

Share this post


Link to post
Share on other sites

So if I understand you, the above example is a 3-pin charlieplexing... but you only declare two pins.  Am I missing something? I'm not quite following how the charlieplex class is mapping pins to LEDs, so that you get the Pins*(Pins-1) total outputs, e.g. 1->2, 2->3, 3->1, 2->1, 3->2, 1->3 = your 2x3 image

Share this post


Link to post
Share on other sites

Yeah, I was lazy and tied two pins of the charlieplex to the same I/O pin, sorry for that. You can see that I'm doing this in the lines

  pinmap[0] = &P1_2;
  pinmap[1] = &P1_2;
  pinmap[2] = &P1_3;

Since both charlieplex pin 0 and pin 1 are linked to P1_2. In reality you'd have to use different pins.

So the mapping: imagine your image is 2x3, that means it's X dimension (at least driver-wise) is 2 pixels, and your Y dimension is 3 pixels. The idea is that this maps to your pin array like this:

pixel (0, 0) is linked to pin 1 to 0

pixel (1, 0) is linked to pin 2 to 0

pixel (0, 1) is linked to pin 0 to 1

pixel (1, 1) is linked to pin 2 to 1

pixel (0, 2) is linked to pin 0 to 2

pixel (1, 2) is linked to pin 1 to 2

So the pattern is that your cathode is connected to the pin of your Y coordinate, while the anode is linked to the pin of your X coordinate. But for the X you should not count the "gaps" as pins; if your Y is active on pin 1, then the "second" pixel in the image on a certain row is sourced from pin 2, because 2 comes after 0 when we skip pin 1 (which is the Y pin).

Share this post


Link to post
Share on other sites

Thanks, I think I get it now.

The only other thing I can see is that everything but the Tick would be in an Energia "setup" function, and the Tick in the "loop"

 

I'm playing with a Tic Tac Toe (Noughts and Crosses) game design.

I'm considering using your library, but since the two players' placement will be inverse pins (e.g. Player One in the upper left corner would be Pin 0 to Pin 1, but Player Two is Pin 1 to Pin 0 for the same spot), i can probably get some significant simplifications rewriting much of it.  I do want to think about timing code -- i hadn't considered that putting all my pins on one port would be of benefit, I'd just switch lamps every few millis(), but a Timer interrupt might mean that I don't even need to call Tick within the loop, it would just update regularly.

Share this post


Link to post
Share on other sites

In such a case you have to hook the WDT routine to the tick(), which is not possible in Energia AFAIK.

 

A little basic counting tells us that need 3x3x2 = 18 LEDs (charlieplexing does not work with incandescent lamps, since they must be semiconductors for charlieplexing)

18 is more than 3x4=12, but less than 4x5=20, so you need at least 5 pins.

So now you have a few choices:

  • Use 5 pin charlieplexing with a odd mapping to the image (3x3x2 does not project nicely on 4x5)
  • Use 6 pin multiplexing which cleanly maps onto the image (3x3x2 maps nicely onto 3x3 with double polarity)
  • Use 9 pin driving which allows you to access each symbol as a single bit

Thus based on your interface one or the other approach is better fit. I suppose you also need buttons and maybe an indicator to tell which player's turn it is.

Share this post


Link to post
Share on other sites

Oh obviously on the charlieplexing of LEDs vs incandescent -- I used "lamps" instead of typing LED again... but I've seen the term "LED Lamps" so I didn't think of that being any confusion.

I was planning on 5-pin 'plexing, just to keep the wiring clean (Fritzing is fun -- got a PCB routed for if I get this working).  I won't be able to use your image routine, but the code is short enough that I can one-off a version for my purposes. I'll credit you if I publish it on these boards or stellarisiti (I'm using a Tiva C).

Alternatively, it might be worth a version where your "image" is replaced by a linear array, so that the LEDs used can be allocated to any purpose... and of course a somethingarray[5][4] is equivalent to somethinglist[20].

 

There's another thread on 43oh regarding interrupt handling on Energia... it might be all I need (there's a patch to the linker script).

Share this post


Link to post
Share on other sites

Yeah, I was planning on doing linear arrays as well, but never came to it.

 

Also, my pin abstraction clutters the library a lot, I might redo it one day using the Energia functions for pin access.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×