Jump to content
davebranton

LED matrix timer display - brush your teeth for two minutes kids!

Recommended Posts

Earlier this year I happened to be in Singapore in transit, and took the opportunity to visit Sim Lim Tower - a high-rise mall full of electronic components shops! Amazing place, and amongst other things (including super-cheap kits for the kids to build!) I picked up three 5x7 LED matrix panels for a dollar each (!!). They didn't have any part numbers that I could see, but after experimentation they turned out to be row-anode devices and once I figured out the pinout I decided to build a project out of them.

 

As any of us who have children know, it's a daily battle to get kids to brush their teeth for long enough, and as any of us who have children also know, time spent on projects goes down better if there's a kid angle to it.

 

So I decided to make a tooth brush timer, that will display a countdown from two minutes - interspersed with inspirational text and probably amusing patterns and other things to keep the kids entertained during their twice-daily two-minute ordeal. Poor things.

 

Being given to doing things the hard way, I decided to design two boards - one for the LED panels, and one for the controller. I'll get to the controller board in a later post because it's quite complicated (since the board fab place makes 10 identical boards I decided to try to make it as multi-purpose as possible). The LED boards though are simpler, they just use a 4094 shift register to handle the columns (or rows, I decided to use my three matrices abutted end to end rather than side to side, giving me a display that's five LEDs high and twenty-one across. I found a nice 5x5 bitmap font on dafont.com, and decided that I had enough space for what I needed to display.

 

Because if all the LEDs are on the 4094 will be over it's specified current limit, it drives the columns through seven PNP transistors. This meant quite a bit of routing, which is what led me to use a board fab house rather than attempt it all on veroboard.

 

So, onto the code (I have attached the eagle files if anyone is interested). I wanted to use the USI module to drive the 4094's. Looking at the datasheet for a 4094, it clocks in it's data on a rising edge of the clock. So the USI module will need to be configured thusly (I'm running the CPU at 12Mhz).

// Reset USI logic to allow configuration
USICTL0 |= USISWRST;

// Setup serial output for a 4094 shift register
USICTL0 = USIPE6 // SDO enabled
	  + USIPE5 // CLK enabled
	  + USIMST // master mode
	  + USIOE // output enable
	  + USISWRST // leave in reset
	  ;

// clock phase = 1, interrupts enabled
SICTL1 = USICKPH // phase = 1
	 + USIIE // interrupt enabled
	 ;

USICKCTL = USIDIV_4 // divide by 16, giving 500khz clock
	+ USISSEL_2 // SMCLCK as clock source
			;

// release SPI for operation
USICTL0 &= ~USISWRST;

Now, the tricky part of this project is that I want to have greyscale (redscale!) on my LEDs. To do this I will have to not only row-scan the matrix, but also PWM each LED individually. And I want to do all this in the USI interrupt handler, and have a framebuffer in RAM that I can just write to at any time without having to worry about getting the LEDs to update. So first thing is the framebuffer, I'm just using one LED matrix at the moment so it's only a 5x7 array:

unsigned char framebuffer[5][8]; // greyscale framebuffer

So, for those unfamiliar with how to drive these types of LED matrices, here's a bullet-pointed flow-chart.

  1. Start at the first row.
  2. Clock bits into the 4094 shift register, corresponding to which LEDs in the current row are on. Because I'm using PNP transistors as high-side switches, a zero in the 4094 outputs will correspond to an LED being on. 
  3. Once the bits are clocked in, set the 'strobe' input into the 4094 to high. This will latch the outputs of the 4094, causing them all to change at once to whatever you just clocked in.
  4. Now that the high-side transistors are all either on or off, according to which LED in the current row you want to be on, it's time to turn that row on using the low-side switch for that row. Of course there could be quite a bit of current flowing out through the row pin if all the LEDs in that row are on, so it's not safe to just hook it up to a GPIO pin. A FET is instead used as a low-side switch.
  5. Now the row is lit up. Move to the next row and goto 2. If we're at the last row, then instead goto 1 (which means start over).

The above method, if run fast enough, will cause all the LEDs to appear illuminated in whatever pattern you desire. But it won't allow brightness control of individual LEDs. To get that we need another variable, that I'll call PWM.

 

PWM will start at zero, and will be incremented at step 1 of the above bullet-pointed flowchart. When it reaches 16, it will be reset back to zero. So it's a rolling counter that will count up each time a frame is output to the matrix, and will roll over at 16. Using this, and the greyscale framebuffer above, we can modify the flowchart so that instead of just clocking in whichever LEDs are on into the 4094, we clock in whichever LEDs have a brightness value that is greater than the current PWM counter.

 

Thus, when the framebuffer entry is zero for a particular LED, that LED is never illuminated because it's brightness value (zero) is never greater than the PWM value (zero to fifteen). And when the framebuffer entry is sixteen, it's always greater than the PWM counter, and thus that LED is illuminated every frame. In between values illumiate the LED for varying numbers of frames, which - if you run the whole process fast enough - will cause them to appear dimmer.

 

The heart of this code is the following loop, which I would love some advice on how to optimise:

output = 0;
for(bit = 8; bit ; bit--)
{
	output <<= 1;
	output |= (framebuffer[row][bit] > pwm) ? 0 : 1; // on if over pwm
}

This loop calculates what to output to the 4094 shift register for each row. It's quite alot of work for an interrupt handler, and it needs to be three times more work once I add the additional two LED panels. I'm sure the compiler in CCS does a good job of figuring out the loop and optimising it for me, but I should take a look at the assembly output and determine if I can do any better.

 

Anyway, to kickstart the process, the main program calls NextRow(), and then sets up a timer and fiddles with the framebuffer at intervals. So, here's NextRow and the USI interrupt handler:

unsigned char pwm = 0;
unsigned char row = 0;

void NextRow()
{
	unsigned char bit;
	unsigned char output;
	// strobe low ready for next thingy
	P1OUT &= ~BIT3;
	// next row's data - work out if each pixel should be on
	output = 0;
	for(bit = 8; bit ; bit--)
	{
		output <<= 1;
		output |= (framebuffer[row][bit] > pwm) ? 0 : 1; // on if over pwm
	}
	USISRL = output;
	// 8 bits out, we'll come back to _usi once these bits have been clocked out.
	USICNT = 8;
}


#pragma vector = USI_VECTOR
interrupt void _usi()
{
	USICTL1 &= ~USIIFG; // reset interrupt flag
	// turn off rows
	P2OUT = 0;
	// latch data
	P1OUT |= BIT3; // Strobe high
	// turn on rows
	P2OUT = (1 << row);
	// next row
	row++;
	if (row == 5)
	{
		pwm++;
		pwm &= 0x0F; // loop pwm back to zero when it hits sixteen
		row= 0;   // start at row zero again.
	}
	NextRow();
}

I've left alot of stuff out here, setting up the clock module and the GPIO pins etc etc. Also there's code in the project that makes a pretty (ish) pattern, and the displays some numbers. The code and eagles files are attached, and assuming I can use the video feature of BBcode correctly, here's a video!

 

http://www.youtube.com/watch?v=gvPZ8LyGYGY

 

--Edit---

 

Well that youtube embed didn't work. Here's a link. How do you embed videos anyway?

post-31167-0-60607600-1371596581_thumb.png

LED control.zip

Edited by bluehash
Fixed youtube video.

Share this post


Link to post
Share on other sites

Thanks abecedarian, I'll do that next time.

 

Ok, so I took a look at the assembly version of the loop I mentioned above, and I'm not all that impressed with the compiler's output. I'm probably expecting too much, because I've seen it do pretty clever stuff in the past.

output = 0;
for(bit = 8; bit ; bit--)
{
	output <<= 1;
	output |= (framebuffer[row][bit] > pwm) ? 0 : 1; // on if over pwm
}

And here's the assembly, annotated to help with my own understanding of it. This is a release build using CCS with optimisations at maximum speed.

	MOV.B     &line+0,r14           ; [] 
        RLA.W     r14                   ; [] 
        RLA.W     r14                   ; [] 
        RLA.W     r14                   ; [] 
        ADD.W     #framebuffer+8,r14    ; [] 
		// r14 = (framebuffer + 8) + (line * 8)
		
        MOV.W     #0,r15                ; [] |81| 
        MOV.W     #8,r12                ; [] 
		// r15 = 0, r12 = 8
		// r12 is being used as the loop counter
		
        MOV.B     #0,r13                ; [] |84| 
		// r13 = 0
        CMP.B     @r14,&pwm+0           ; [] |84| 
		// set flags as if (*r14) - pwm . 
		
		// Jump if LO to $C$L2, JLO jumps if carry is not set
        JLO       $C$L2                 ; [] |84| 

		// r13 = 1
        MOV.B     #1,r13                ; [] |84| 
		
C2L2:
        RLA.B     r15                   ; [] |84| 
        OR.B      r13,r15               ; [] |84| 
		// r15 <<= 1
		// OR r13 into r15

        SUB.W     #1,r14                ; [] |82| 
		// move pointer
		// r14--
        SUB.W     #1,r12                ; [] |82| 
		// loop counter
		// r13--, loop if nonzero
        JNE       $C$L1                 ; [] |82| 

So looking at this code reveals that it uses an unnecessary register (r13), which it loads with either zero or one, depending on the value of the carry flag. If carry is not set, it leaves r13 as zero, otherwise it loads it with one. It then OR's r13 into r15, having shifted r15 one bit to the left.

 

Sound like the instruction  RLC.B to you? Sure does to me. All the code between the MOV.W #8,r12 and the SUB.W #1,r14 can be replaced with the following two instructions.

CMP.B     @r14,&pwm+0
RLC.B     r15

And moreover, since pwm doesn't change inside the loop, it can kept in a register during the loop thus saving an additional memory read during the instruction. I suspect that this will make the whole operation at least twice as fast - although I haven't tried it out yet.

 

If that's still not enough, I think I might unroll the whole loop - since each iteration will only be three instructions long there will be plenty of program space.

 

All of the above leaves me with a single question - how do I get my assembly into the code? I've written a single assembly function once, and I placed it in a separate file. I can do that again, but I'll need that file to have access to global variables declared in a C file. If anyone here is able to offer advice on how to achieve that I would be grateful.

 

Actually I have another question too, in case anyone is still reading. What is the interrupt priority on the MSP430? I presume it doesn't nest interrupts, and so most of the time the priority is whichever happens first, but if two did happen to come along at once, which would be serviced first?

Share this post


Link to post
Share on other sites

@@igor , that's not a bad idea, I probably will

 

Also, I wonder if anyone knows of any nice big smackable buttons that would be suitable for something like this. Kid proof, waterproof, and nice and big and satisfying. The kind you often find on slot machines I guess.

Share this post


Link to post
Share on other sites

One place could look for buttons is children's toys if have a decent second hand store around.

Seem to be a lot of children's toys are now plastic boxes that flash lights or go beep.

Expect some of them must have some decent innards.

Share this post


Link to post
Share on other sites

@@igor , that's not a bad idea, I probably will

 

Also, I wonder if anyone knows of any nice big smackable buttons that would be suitable for something like this. Kid proof, waterproof, and nice and big and satisfying. The kind you often find on slot machines I guess.

Sent from my Galaxy Note II with Tapatalk

 

I got an assortment of (not terribly big but big enough) colored arcade-style chassis mount buttons from Seeed studio in a recent order (was trying out their misc parts & tools store since I'd only ever uses them for PCB fabrication before)

Share this post


Link to post
Share on other sites

Two great suggestions, thanks. I have the PCBs on order from seeed as it happens, and I have some other boards in the pipeline so maybe I'll look into that. I couldn't find the ones you mentioned spirilis, but I did find this:

 

http://www.seeedstudio.com/depot/the-nevergoingtomiss-glaringdevileye-huge-red-push-button-p-378.html?cPath=85

 

Costs as much as 10 custom PCBs!! We do have a fantastic thrift shop round these parts that I'll hit up first to see what I can find.

 

Anyway, I wrote the code I mentioned earlier up in assembly, and unrolled the loops, and got the inter-byte time down from 25us to 8us. Then I upped the size of the framebuffer so that it covers all three panels, soldered up the other two boards, and it all works very nicely with no perceptible flicker.

 

Next step is the control board. This is the one I have on order from seeed, which should be here in a week or so. This is a much more complex board that my first one, as it supports a few different peripherals, and a couple of power supply options. I'll put a picture of it up when I get it, but I mentioned it in this post here:

 

http://forum.43oh.com/topic/1374-get-your-booster-packs-sponsored-10/?p=36212

 

For this project, I'll be boosting a couple of AAs to 3.3v, and at this point I've no idea how well the part I've chosen to do this is going to work. I did try this once with a different boost controller, and saw spikes up to 4v as the inductor discharged. I've made provision for a LC filter to help smooth these out, but I'll have to see if it's going to be required.

 

The idea is that the project is asleep most of the time, waiting for the button to be pressed. While asleep the boost regulator is off, and the micro is powered by the AAs through the inductor and the diode (so that'll be battery voltage minus the drop of the diode, which I think is about 100mv at low currents). When it wakes up, it'll turn on the boost controller and then switch to a higher clock frequency. It remains to be seen what the power supply will look like when the regulator turns on, maybe there'll be horrible spikes that'll kill the micro? Who knows.

Share this post


Link to post
Share on other sites

A small suggestion, perhaps for a future version: an accelerometer in the brush to measure whether the brush is actually moving, and if it's not the timer stops until motion is detected again, and maybe resets the timer if motion stops for more than a few seconds?

 

Those of us with kids know they will try to find short-cuts for everything they do, and will put even more effort into doing so when they are being trusted to do things without supervision. And when they're caught doing it, they don't comprehend the fact  "you spent 20 minutes getting around something you could've finished with in 2 minutes, so you've wasted time, and with time spent arguing, instead of 15 minutes on the Wii, you have 5".

Share this post


Link to post
Share on other sites

Those of us with kids know they will try to find short-cuts for everything they do, and will put even more effort into doing so when they are being trusted to do things without supervision. And when they're caught doing it, they don't comprehend the fact  "you spent 20 minutes getting around something you could've finished with in 2 minutes, so you've wasted time, and with time spent arguing, instead of 15 minutes on the Wii, you have 5".

Man - that one is absolutely true!  Still trying to beat that one into my kids!

Share this post


Link to post
Share on other sites

A small suggestion, perhaps for a future version: an accelerometer in the brush to measure whether the brush is actually moving, and if it's not the timer stops until motion is detected again, and maybe resets the timer if motion stops for more than a few seconds?

 

Those of us with kids know they will try to find short-cuts for everything they do, and will put even more effort into doing so when they are being trusted to do things without supervision. And when they're caught doing it, they don't comprehend the fact  "you spent 20 minutes getting around something you could've finished with in 2 minutes, so you've wasted time, and with time spent arguing, instead of 15 minutes on the Wii, you have 5".

 

I like the accelerometer idea.  I wonder if one could make it not just detect motion, but provide feedback on how well one is brushing?

Evaluate whether one is using appropriate motion (circling, rather than up/down or back forth), 

If add a magnetometer for orientation, might be able to assess coverage (e.g. did one spend adequate time on each area of the mouth).

 

 

As far as time spent, maybe show them this chart?

 

 

is_it_worth_the_time.png

 

 

On the other hand, given the time invested in some projects, vs. the return on them, maybe not what one wants to show one's children/significant other/etc.

Share this post


Link to post
Share on other sites

Also, I wonder if anyone knows of any nice big smackable buttons that would be suitable for something like this. Kid proof, waterproof, and nice and big and satisfying. The kind you often find on slot machines I guess.

Maybe search ebay for "arcade push button"?

I just did and there are several on there. Round ones starting around $1 and lighted rectangular starting around $3, plus shipping on some of them.

The rectangular ones are quite similar to the ones used on slot and lottery ticket machines.

Share this post


Link to post
Share on other sites

 

I like the accelerometer idea. I wonder if one could make it not just detect motion, but provide feedback on how well one is brushing?

Evaluate whether one is using appropriate motion (circling, rather than up/down or back forth),

If add a magnetometer for orientation, might be able to assess coverage (e.g. did one spend adequate time on each area of the mouth).

Might be a bit math intensive to determine if the brush is being moved correctly. If you could get it to determine the motions are correct, you could have a little panel with LED's that progress to show what they should be doing- top-left-inside, top-front-inside, top-right-inside, and so on, giving them 10 seconds on each part before moving to the next. Combine that with stopping the timer if the brush isn't moving, and resetting the timer if the brush doesn't move for a long time, and you might be half-way there to clean teeth. ;-)

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

×