Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Tail light display

Status
Not open for further replies.

dimulec

New Member
I am new here. So don't scream if I do something wrong (I did read the rules :) ).

Just wanted to post some of my projects. Here is the first one. I use it on my cars for about 2 years now. Basically, this is a 3-rd brake light which takes 5 inputs from (brake, turns, reverse and accessory power) and acts upon them. When the car is parked it displays advertisement (like my wife's real estate ads). The controller is detacable and resembles a USB stich. So you can connect it to a USB port for script uploading.

See the attached files for the schematics/board/firmware/etc...

I know as a new guy I can't post links to my web site. But hopefully a youtube link would be fine.

YouTube - Car tail light project
YouTube - Car tail light project
 

Attachments

  • ctl_controller_schematic_eagle.zip
    48.5 KB · Views: 165
  • ctl_display_schematic_eagle.zip
    32.3 KB · Views: 143
  • ctl_controller_brd_eagle.zip
    10.8 KB · Views: 153
  • ctl_controller_gerbers_eagle.zip
    8.1 KB · Views: 164
  • ctl_display_brd_eagle.zip
    37.9 KB · Views: 144
  • ctl_display_gerbers_eagle.zip
    53.2 KB · Views: 141
  • ctl_firmware.zip
    4 MB · Views: 154
  • cemgr.zip
    488.4 KB · Views: 168
dimulec,

Nicely done. Would you happen to have schematic in something other than Eagle?

Regards, Mike
 
dimulec,

Nicely done. Would you happen to have schematic in something other than Eagle?

Regards, Mike

Here are the exported PNG images if that is what you mean. I also exported BOMs since it was not clear what ICs are in the display module.
 

Attachments

  • ctl_png_sch_and_BOM.zip
    153.3 KB · Views: 141
Thank you.

Is the display 5 rows high and 35 columns long?

Are you lighting 5 LEDs in a single column at a time and doing this 35 times to refresh the entire display?

Are you using discrete LEDs or 5x7 modules?

Sorry for all the questions.

Mike
 
Last edited:
Thank you.

Is the display 5 rows high and 35 columns long?

Are you lighting 5 LEDs in a single column at a time and doing this 35 times to refresh the entire display?

Are you using discrete LEDs or 5x7 modules?

Sorry for all the questions.

Mike

5 rows by 35 cols discrete LEDs. Duty cycle 1/5 - one row at a time. Current pulse is about 100-120ma so they are pretty bright. There are 5 counters - 1 for each 5rows by 7cols block. Controller loads all 5 in parallel and then enables one row. Don't remember the exact frequency, but the loading take very little time.
 
Really? What is the part number of U0.1, U1.1, U2.1, U3.1, and U4.1 ? It looks like some sort of ripple counter so it's not obvious how you might store seven bits of data in it...
 
Last edited:
Really? What is the part number of U0.1, U1.1, U2.1, U3.1, and U4.1 ? It looks like some sort of ripple counter so it's not obvious how you might store seven bits of data in it...

CD4024 - 7 bit counter. Selected because of the Vcc range 3-18V. They are powered directly from the car's battery voltage.
 
Oh my goodness, I think I just figured out what you're doing. Please tell me if I'm correct.

At the beginning of each row update interval you pulse the counter "reset" pins to clear the counter outputs. Then you turn off the current row to blank the display. Then you pulse the "clock" line on each of the five counters 0 to 127 times to put the correct bit pattern onto the seven outputs. Then you turn on the new row.

If you're doin' it this way you could easily be using up to about 1300 instruction cycles just to refresh 35 bits of data for a single row, yes, no?

Wait a minute...
Controller loads all 5 in parallel and then enables one row
Maybe lots fewer cycles then I thought. Let me see if I can find this in your code. Only 42 files (grin). Maybe in the user.c file?

Regards, Mike
 
Last edited:
Oh my goodness, I think I just figured out what you're doing. Please tell me if I'm correct.

At the beginning of each row update interval you pulse the counter "reset" pins to clear the counter outputs. Then you turn off the current row to blank the display. Then you pulse the "clock" line on each of the five counters 0 to 127 times to put the correct bit pattern onto the seven outputs. Then you turn on the new row.

If you're doin' it this way you could easily be using up to about 1300 instruction cycles just to refresh 35 bits of data for a single row, yes, no?

Not exactly. I spend at most 127 pulses to fill in all the counters since they are filled in in parallel. Counters are used to minimize the number of wires running from the controller to the display board.
 
Found it. Thanks.

My calculations were way off. This code looks like it could take up to about 2500 cycles and it's not isochronous. So, depending on row data, you may not get same amount of display "on" and "off" time for each row which could produce perceivable brightness variations.

Code:
        while(display0|display1|display2|display3|display4) {
            if(display0) {
                LATCbits.LATC0 = 1;
                display0--;
            }
            if(display1) {
                LATCbits.LATC1 = 1;
                display1--;
            }
            if(display2) {
                LATCbits.LATC2 = 1;
                display2--;
            }
            if(display3) {
                LATCbits.LATC6 = 1;
                display3--;
            }
            if(display4) {
                LATCbits.LATC7 = 1;
                display4--;
            }
            LATC = 0;
        }
Bravo! You've done an excellent job. Very cohesive, comprehensive, and elegant. I would do it differently (wouldn't everbody, grin) but it wouldn't necessarily be better then the way you did it.

Cheerful regards, Mike
 
Found it. Thanks.

My calculations were way off. This code looks like it could take up to about 2500 cycles and it's not isochronous. So, depending on row data, you may not get same amount of display "on" and "off" time for each row which could produce perceivable brightness variations.

Bravo! You've done an excellent job. Very cohesive, comprehensive, and elegant. I would do it differently (wouldn't everbody, grin) but it wouldn't necessarily be better then the way you did it.

Cheerful regards, Mike

Thanks. I know everyone would do it differently. I did not really care about the amount of cpu cycles. First 3 or 4 prototypes had a different algorithm. But this one worked better.

I have other light projects. Oldest one is for motorcycle. It uses RS-232 connectivity. Another one is for bicycles. I don't know if I can post a link to my web site. but the videos (with the web site link) are here:

YouTube - ‪Motorcycle tail light project‬‎
YouTube - ‪Bicycle tail light‬‎

My daughter is on the bike :) And also her voice is on the second video.
 
Its amazing you didn't sell more! Thank you, though, for "giving away" the project. Sigh, another addition to my (sizeable and backlogged) project list! LOL...
 
..... I did not really care about the amount of cpu cycles ......

You sound like a true HLL programmer (LOL). Seriously though, you really should be concerned about performance. While your project turned out incredibly nice, I suspect the hardware and software performance could be improved dramatically. Would you mind an unsolicited example?

This example (below) retains your 1/5th (20%) duty cycle but I'm using MIC5821 high current 8-bit serial-to-parallel sinking driver ICs instead of your 4042/2003 IC combination. If the MIC5821 is too pricey then you could use the relatively inexpensive 74HC595/ULN2803 combination instead.

I connected the PIC PWM signal to the driver IC <latch> and <oe> pins and I'm using a PWM period equal to the row scan rate. Now I have complete fade-to-black "no overhead" PWM brightness control and the ability to re-task the row driver lines temporarily at the beginning of the row update interrupt when PWM is hi (display off) for use as <clk> and <dat> lines to load the driver IC shift registers in parallel by writing 8 special SR bytes and clock pulses onto the buss in about 26 cycles. I still need a "data bender" to build the special SR array for the next interrupt but that takes less then 100 cycles. Please note that the <clk> bit within each srdata() array byte is preset to '0'. The display ISR driver then might look like this (BoostC);

Code:
/********************************************************************
 *  interrupt service routine, approx 7% 'overhead' (8-MHz clock)   *
 *  (C) 2010, Michael McLaren, Micro Application Consultants        *
 ********************************************************************/

void interrupt()                // 1-msec Timer 2 interrupts
{ pir1.TMR2IF = 0;              // clear Timer 2 interrupt flag
 /*                                                                 *
  *  retask the row driver lines while PWM is hi (display off) as   *
  *  one <CLK> line and five <DAT> lines to load all five MIC5821   *
  *  shift registers in parallel (5 chan SPI). Then write new row   *
  *  select bit pattern to PORTB before PWM goes lo (display on)    *
  *  to resume PORTB row driver duties.  26 instruction cycles.     *
  *                                                                 */
  portb = srdata[7]; clk = 1;   // clock out the b7 SR data bits
  portb = srdata[6]; clk = 1;   // clock out the b6 SR data bits
  portb = srdata[5]; clk = 1;   // clock out the b5 SR data bits
  portb = srdata[4]; clk = 1;   // clock out the b4 SR data bits
  portb = srdata[3]; clk = 1;   // clock out the b3 SR data bits
  portb = srdata[2]; clk = 1;   // clock out the b2 SR data bits
  portb = srdata[1]; clk = 1;   // clock out the b1 SR data bits
  portb = srdata[0]; clk = 1;   // clock out the b0 SR data bits
  portb = rowsel;               // select new row

  if(rowsel.4)                  // if last row
  { rowndx = 64;                // reset row index = &disp[0][0]
    rowsel = 1;                 // reset row select bit for RB0
  }
  else                          // not last row so
  { rowndx += 5;                // bump row index
    rowsel <<= 1;               // bump row select bit
  }
 /*                                                                 *
  *  build new srdata[] array for next row update interrupt         *
  *                                                                 */
  srdata[0] = 0;                // clear srdata[] array
  srdata[1] = 0;                //
  srdata[2] = 0;                //
  srdata[3] = 0;                //
  srdata[4] = 0;                //
  srdata[5] = 0;                //
  srdata[6] = 0;                //
  srdata[7] = 0;                //
 /*                                                                 *
  *  example bended data in srdata[] array for a row 0 update       *
  *                                                                 *
  *  disp[0][0]  0x2C  00101100  ' bend onto b1 bits for RB1        *
  *  disp[0][1]  0x37  00110111  ' bend onto b2 bits for RB2        *
  *  disp[0][2]  0x42  01000010  ' bend onto b3 bits for RB3        *
  *  disp[0][3]  0x77  01110111  ' bend onto b4 bits for RB4        *
  *  disp[0][4]  0x85  10000101  ' bend onto b5 bits for RB5        *
  *                                                                 *
  *   srdata[0]  0x34  00110100  ' shift register b0 bits           *
  *   srdata[1]  0x1C  00011100  ' shift register b1 bits           *
  *   srdata[2]  0x36  00110110  ' shift register b2 bits           *
  *   srdata[3]  0x02  00000010  ' shift register b3 bits           *
  *   srdata[4]  0x14  00010100  ' shift register b4 bits           *
  *   srdata[5]  0x16  00010110  ' shift register b5 bits           *
  *   srdata[6]  0x18  00011000  ' shift register b6 bits           *
  *   srdata[7]  0x20  00100000  ' shift register b7 bits           *
  *                                                                 */
  fsr = rowndx;                 // bender for disp[row][0] byte
  if(indf.0) srdata[0] |= 2;    // (2 is mask for RB1 bit)
  if(indf.1) srdata[1] |= 2;    //
  if(indf.2) srdata[2] |= 2;    //
  if(indf.3) srdata[3] |= 2;    //
  if(indf.4) srdata[4] |= 2;    //
  if(indf.5) srdata[5] |= 2;    //
  if(indf.6) srdata[5] |= 2;    //
  if(indf.7) srdata[7] |= 2;    //
  fsr++;                        // bender for disp[row][1] byte
  if(indf.0) srdata[0] |= 4;    // (4 is mask for RB2 bit)
  if(indf.1) srdata[1] |= 4;    //
  if(indf.2) srdata[2] |= 4;    //
  if(indf.3) srdata[3] |= 4;    //
  if(indf.4) srdata[4] |= 4;    //
  if(indf.5) srdata[5] |= 4;    //
  if(indf.6) srdata[5] |= 4;    //
  if(indf.7) srdata[7] |= 4;    //
  fsr++;                        // bender for disp[row][2] byte
  if(indf.0) srdata[0] |= 8;    // (8 is mask for RB3 bit)
  if(indf.1) srdata[1] |= 8;    //
  if(indf.2) srdata[2] |= 8;    //
  if(indf.3) srdata[3] |= 8;    //
  if(indf.4) srdata[4] |= 8;    //
  if(indf.5) srdata[5] |= 8;    //
  if(indf.6) srdata[5] |= 8;    //
  if(indf.7) srdata[7] |= 8;    //
  fsr++;                        // bender for disp[row][3] byte
  if(indf.0) srdata[0] |= 16;   // (16 is mask for RB4 bit)
  if(indf.1) srdata[1] |= 16;   //
  if(indf.2) srdata[2] |= 16;   //
  if(indf.3) srdata[3] |= 16;   //
  if(indf.4) srdata[4] |= 16;   //
  if(indf.5) srdata[5] |= 16;   //
  if(indf.6) srdata[5] |= 16;   //
  if(indf.7) srdata[7] |= 16;   //
  fsr++;                        // bender for disp[row][4] byte
  if(indf.0) srdata[0] |= 32;   // (32 is mask for RB5 bit)
  if(indf.1) srdata[1] |= 32;   //
  if(indf.2) srdata[2] |= 32;   //
  if(indf.3) srdata[3] |= 32;   //
  if(indf.4) srdata[4] |= 32;   //
  if(indf.5) srdata[5] |= 32;   //
  if(indf.6) srdata[5] |= 32;   //
  if(indf.7) srdata[7] |= 32;   //
}
You'd still need to add your general purpose animation and process timers but the basic ISR only uses about 150 cycles and the display refresh rate with 1-msec interrupts is a whopping 200 Hz. If you're synchronizing animation to the frame rate, as you should to avoid unwanted visual artifacts, then you can change the display array up to 200 times per second (once every 5 msec display frame).

I would use a 64 step gamma correction table to linearize brightness across the PWM duty cycle range, which should in turn produce very smooth fading effects. Note that brightness is inversely proportional to PWM duty cycle. The display brightness function might look something like this (PR2 period register = 125-1);

Code:
void brightness(u08 level)      // parameter range 0..63
{ ccpr1l = 125 - gamma[level];  // level 63 =  3% duty cycle, 97% bright
}                               // level 0 = 100% duty cycle,  0% bright
Anyway, I hope you don't mind another design perspective.

Cheerful regards, Mike
 

Attachments

  • 7x40 Example.PNG
    7x40 Example.PNG
    24.3 KB · Views: 181
Last edited:
You sound like a true HLL programmer (LOL). Seriously though, you really should be concerned about performance.

Nice, I will save your message for the future references :) The problem here - I am an HLL programmer. MCC design is my hobby. I got a nice education as a hardware engineer but for the last 24 years was working as a HLL programmer. Well, I was using assembly languages too (at first) - PDP-11, 8080, 8085, 8086, etc. Btw, that motorcycle light I mentioned above has assembly firmware. I hope this does not mean I can't be here :)
 
Status
Not open for further replies.

Latest threads

Back
Top