Shop OBEX P1 Docs P2 Docs Learn Events
Trans-Lux display & Propeller — Parallax Forums

Trans-Lux display & Propeller

Hi All -

Back in 2008 I picked up some surplus Trans-Lux displays from a local electronics salvage store. I've driven one half of a display using an ATMega168, but that was really at the limit of what it could do :)

Some pictures here: https://goo.gl/photos/TfGjPaihxif2U6dF7

In 2009, I ordered a few Propeller Proto Boards (#32212) thinking that someday I would get around to working with these displays again. I'm thinking that day has arrived!

Driving these involves using clock and data lines to shift in 320 display bits, three more bits to select which row in the 5x7 characters light up, and then pulling an enable line low. This repeats for each of the 7 rows that make up the 5x7 characters. The input connector has two sets of clock/data/enable, one for the top two rows of characters and another for the bottom two rows. From the photos you can see I was using only one of these sets - only half of the display is lit.

I'm thinking that the Propeller is going to work for this and two cogs could drive both halves of the display. Each cog would just be a loop to read data from a shared address space and send it using clock/data/enable. Another cog could read the serial port to allow external input to manipulate the display data.

So, am I going down the right path here with my approach? If so, does anyone know offhand of any project doing something similar?

Thanks for your time,

Matt




Comments

  • Cluso99Cluso99 Posts: 18,069
    Matt,
    Welcome to the Prop forum :)

    You will probably use 3 cogs for this.
    Serial cog running the FullDuplexSerial object
    Lux driver cog
    Cog to handle the data transfer between the other 2 cogs

    The simplest way will be having a hub buffer holding all display characters. Then the lux driver cog will just keep displaying this set of characters. Get this part working first. Then you can expand to have the serial update the buffer. Once working you can use a hub flag to indicate when updates are required to save power as I am guessing the lux panel is intelligent in that it has latches for each character that do not need updating unless the content changes.

    Anyway, we are here to help. So just ask. Use the (code) and (/code) tags to post code. Note replace ( and ) with less-than and greater-than characters.
  • kwinnkwinn Posts: 8,697
    I think a pasm program in a single cog should be able to read 64 characters from the hub and output the bits to display them easily.
  • mlctrezmlctrez Posts: 9
    edited 2017-01-22 22:00
    Thanks for the help so far.

    I started just with a main loop to prove out some basics. Here's my code:
    /*
      TransLuxController.c
    */
    
    #include "simpletools.h" 
    
    int main()                
    {
    
      high(2); // enable bottom (high = off)
      low(3);  // data bottom
      low(4);  // clock bottom
      
      high(5); // enable top (high = off)
      low(6);  // data top
      low(7);  // clock top
      
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          for (int i = 0; i < 2; i++) {
            shift_out(3, 4, MSBFIRST, 8, 0b10011001);
          }
          shift_out(3, 4, MSBFIRST, 3, j);
          
          low(2);
          high(2);
        }
              
      }  
      
    }
    

    What I'm finding so far is that the speed of the built in shift_out library code is not fast enough.
    Is it possible to increase the rate data is shifted out? Or drop down to lower level code to achieve this?

    Cluso99 -
    I am guessing the lux panel is intelligent in that it has latches for each character that do not need updating unless the content changes.
    Unfortunately, the board is not that smart. The shift registers are shared for each of the 7 rows in the characters, so keeping the board lit involves constantly sending data to it even if the data for the characters have not changed.

    Thanks,
    Matt




  • I dropped down to some lower level code and got positive results with reducing flicker.
    #include "simpletools.h" 
    
    
    // v2 code
    int main()                
    {
      // set P00 to P07 to output
      DIRA |= 0b11111111;
      // set P02 and P05 high, and everything else to low
      OUTA |= 0b00100100;
      OUTA &= 0b00100100;
    
      unsigned int CLKL = 1 << 4;
      unsigned int DATL = 1 << 3;
      unsigned int ENAL = 1 << 2;
    
      // pauses are micro seconds  
      set_pause_dt(CLKFREQ/1000000);
    
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          for (int i = 0; i < 160; i++) {
    
            // data high
            OUTA |= DATL;
            // clock on
            OUTA |= CLKL;
            // clock off
            OUTA &= ~CLKL;
    
            // data low
            OUTA &= ~DATL;
            // clock on
            OUTA |= CLKL;
            // clock off
            OUTA &= ~CLKL;
               
          }
          
          if (j & 0x04) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;
          
          if (j & 0x02) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;      
    
          if (j & 0x01) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;
                              
          // enable low
          OUTA &= ~ENAL;
          pause(10);
          // enable high            
          OUTA |= ENAL;
        }
              
      }  
      
    }
    

  • You should check how the code is being compiled, too. CMM code is smaller but significantly slower than LMM.
  • Thanks for the tip. Now digging into if SimpleIDE supports this or if I need to switch to command line.
  • Much better refresh rates with lmm thanks for the tip!
    diff TransLuxController.side TransLuxController.side.orig 
    3c3
    < >memtype=lmm main ram
    ---
    > >memtype=cmm main ram compact
    

    I had to restart SimpleIDE to pickup the changes.

  • Adapting this to run in a cog is apparently not as straightforward as I thought. This works:
    /*
      TransLuxController.c
    */
    
    #include "simpletools.h" 
    
    void topdisplay();
    void bottomdisplay();
    
    
    // v3 code
    int main()                
    {
      // set P00 to P07 to output
      DIRA |= 0b11111111;
      // set P02 and P05 high, and everything else to low
      OUTA |= 0b00100100;
      OUTA &= 0b00100100;
    
      // pauses are micro seconds  
      set_pause_dt(CLKFREQ/1000000);
      //cog_run(topdisplay, 128);
      
      bottomdisplay();
      //int *cog = cog_run(bottomdisplay, 222);
      //pause(3000000);   
      //cog_end(cog);
    }
    
    void topdisplay() {
      
      unsigned int CLKU = 1 << 7;
      unsigned int DATU = 1 << 6;
      unsigned int ENAU = 1 << 5;
      
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          for (int i = 0; i < 160; i++) {
            // data high
            OUTA |= DATU;
            // clock on
            OUTA |= CLKU;
            // clock off
            OUTA &= ~CLKU;
    
            // data low
            OUTA &= ~DATU;
            // clock on
            OUTA |= CLKU;
            // clock off
            OUTA &= ~CLKU;
    
          }
          
          if (j & 0x04) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU;
          OUTA &= ~CLKU;
          
          if (j & 0x02) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU;
          OUTA &= ~CLKU;      
    
          if (j & 0x01) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU;
          OUTA &= ~CLKU;
                              
          // enable low
          OUTA &= ~ENAU;
          pause(20);
          // enable high            
          OUTA |= ENAU;
        }
              
      }    
    }  
    
    void bottomdisplay() {
      unsigned int CLKL = 1 << 4;
      unsigned int DATL = 1 << 3;
      unsigned int ENAL = 1 << 2;
    
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          for (int i = 0; i < 160; i++) {
    
            // data low
            OUTA &= ~DATL;
            // clock on
            OUTA |= CLKL;
            // clock off
            OUTA &= ~CLKL;
    
            // data high
            OUTA |= DATL;
            // clock on
            OUTA |= CLKL;
            // clock off
            OUTA &= ~CLKL;
    
          }
          
          if (j & 0x04) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;
          
          if (j & 0x02) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;      
    
          if (j & 0x01) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL;
          OUTA &= ~CLKL;
                              
          // enable low
          OUTA &= ~ENAL;
          pause(10);
          // enable high            
          OUTA |= ENAL;
        }
              
      }    
    }  
    
    
    but changing
      bottomdisplay();
      //int *cog = cog_run(bottomdisplay, 222);
      //pause(3000000);   
      //cog_end(cog);
    
    to
      //bottomdisplay();
      int *cog = cog_run(bottomdisplay, 222);
      pause(3000000);   
      cog_end(cog);
    

    results in no output on the display. I'm probably missing something else here.


  • Cluso99Cluso99 Posts: 18,069
    Spin is much slower than C or PASM.

    Probably easiest to write a Simple PASM program that just updates the display from a buffer of characters in hub.

    Not sure how C handles a PASM cog ???
  • @mlctrez,

    If I remember correctly you can like LMM or CMM also create COG code.

    But starting a cog requires that THIS startet cog sets the Pins it uses.

    So on start of your void bottomdisplay() you are missing:

    // set P00 to P07 to output
    DIRA |= 0b11111111;
    // set P02 and P05 high, and everything else to low
    OUTA |= 0b00100100;
    OUTA &= 0b00100100;

    it has to be done in the same cog.

    Enjoy,

    Mike

  • Thank you both for more suggestions.

    @Cluso99 - I'll see if the C approach works out and keep PASM in the back pocket. What I've found with the C code now is that I should be able to drive 4 displays wide with only a minor amount of flicker.

    @msrobots - Thanks. That makes sense. I suspected there was something like that that I missed.
  • Success! Thanks for all the help!

    I've attached a photo showing the display with randomized bits shifted in.
    This photo and a video have been added to the album linked in the first post of this discussion.

    Now to figure out how to get something readable displayed.
    2132 x 1599 - 333K
  • If you look at some of the drivers for things like the Nokia 5110 or small oled displays there are a bunch of them that have 5x7 fonts in the code. They usually have them stored as 5 bytes per character, bits arranged vertically - should be easy to re-purpose one of those for this.
  • Here's the latest code with character support.

    I would be grateful for any comments regarding how I'm using shared memory, particularly so if I'm doing it wrong. :)
    
    #include "simpletools.h" 
    
    void top_bank();
    void bot_bank();
    
    void charAt(int x, int y, int idx, int color);
    void dotAt(int x, int y, int rg);
    
    static volatile unsigned int top_buff[70];
    static volatile unsigned int bot_buff[70];
    static volatile unsigned int brightness = 2;
    
    static int font[] = {
            0x00, 0x00, 0x00, 0x00, 0x00,// (space)
            0x00, 0x00, 0x5F, 0x00, 0x00,// !
            0x00, 0x07, 0x00, 0x07, 0x00,// "
            0x14, 0x7F, 0x14, 0x7F, 0x14,// #
            0x24, 0x2A, 0x7F, 0x2A, 0x12,// $
            0x23, 0x13, 0x08, 0x64, 0x62,// %
            0x36, 0x49, 0x55, 0x22, 0x50,// &
            0x00, 0x05, 0x03, 0x00, 0x00,// '
            0x00, 0x1C, 0x22, 0x41, 0x00,// (
            0x00, 0x41, 0x22, 0x1C, 0x00,// )
            0x08, 0x2A, 0x1C, 0x2A, 0x08,// *
            0x08, 0x08, 0x3E, 0x08, 0x08,// +
            0x00, 0x50, 0x30, 0x00, 0x00,// ,
            0x08, 0x08, 0x08, 0x08, 0x08,// -
            0x00, 0x60, 0x60, 0x00, 0x00,// .
            0x20, 0x10, 0x08, 0x04, 0x02,// /
            0x3E, 0x51, 0x49, 0x45, 0x3E,// 0
            0x00, 0x42, 0x7F, 0x40, 0x00,// 1
            0x42, 0x61, 0x51, 0x49, 0x46,// 2
            0x21, 0x41, 0x45, 0x4B, 0x31,// 3
            0x18, 0x14, 0x12, 0x7F, 0x10,// 4
            0x27, 0x45, 0x45, 0x45, 0x39,// 5
            0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
            0x01, 0x71, 0x09, 0x05, 0x03,// 7
            0x36, 0x49, 0x49, 0x49, 0x36,// 8
            0x06, 0x49, 0x49, 0x29, 0x1E,// 9
            0x00, 0x36, 0x36, 0x00, 0x00,// :
            0x00, 0x56, 0x36, 0x00, 0x00,// ;
            0x00, 0x08, 0x14, 0x22, 0x41,// <
            0x14, 0x14, 0x14, 0x14, 0x14,// =
            0x41, 0x22, 0x14, 0x08, 0x00,// >
            0x02, 0x01, 0x51, 0x09, 0x06,// ?
            0x32, 0x49, 0x79, 0x41, 0x3E,// @
            0x7E, 0x11, 0x11, 0x11, 0x7E,// A
            0x7F, 0x49, 0x49, 0x49, 0x36,// B
            0x3E, 0x41, 0x41, 0x41, 0x22,// C
            0x7F, 0x41, 0x41, 0x22, 0x1C,// D
            0x7F, 0x49, 0x49, 0x49, 0x41,// E
            0x7F, 0x09, 0x09, 0x01, 0x01,// F
            0x3E, 0x41, 0x41, 0x51, 0x32,// G
            0x7F, 0x08, 0x08, 0x08, 0x7F,// H
            0x00, 0x41, 0x7F, 0x41, 0x00,// I
            0x20, 0x40, 0x41, 0x3F, 0x01,// J
            0x7F, 0x08, 0x14, 0x22, 0x41,// K
            0x7F, 0x40, 0x40, 0x40, 0x40,// L
            0x7F, 0x02, 0x04, 0x02, 0x7F,// M
            0x7F, 0x04, 0x08, 0x10, 0x7F,// N
            0x3E, 0x41, 0x41, 0x41, 0x3E,// O
            0x7F, 0x09, 0x09, 0x09, 0x06,// P
            0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
            0x7F, 0x09, 0x19, 0x29, 0x46,// R
            0x46, 0x49, 0x49, 0x49, 0x31,// S
            0x01, 0x01, 0x7F, 0x01, 0x01,// T
            0x3F, 0x40, 0x40, 0x40, 0x3F,// U
            0x1F, 0x20, 0x40, 0x20, 0x1F,// V
            0x7F, 0x20, 0x18, 0x20, 0x7F,// W
            0x63, 0x14, 0x08, 0x14, 0x63,// X
            0x03, 0x04, 0x78, 0x04, 0x03,// Y
            0x61, 0x51, 0x49, 0x45, 0x43,// Z
            0x00, 0x00, 0x7F, 0x41, 0x41,// [
            0x02, 0x04, 0x08, 0x10, 0x20,// "\"
            0x41, 0x41, 0x7F, 0x00, 0x00,// ]
            0x04, 0x02, 0x01, 0x02, 0x04,// ^
            0x40, 0x40, 0x40, 0x40, 0x40,// _
            0x00, 0x01, 0x02, 0x04, 0x00,// `
            0x20, 0x54, 0x54, 0x54, 0x78,// a
            0x7F, 0x48, 0x44, 0x44, 0x38,// b
            0x38, 0x44, 0x44, 0x44, 0x20,// c
            0x38, 0x44, 0x44, 0x48, 0x7F,// d
            0x38, 0x54, 0x54, 0x54, 0x18,// e
            0x08, 0x7E, 0x09, 0x01, 0x02,// f
            0x08, 0x14, 0x54, 0x54, 0x3C,// g
            0x7F, 0x08, 0x04, 0x04, 0x78,// h
            0x00, 0x44, 0x7D, 0x40, 0x00,// i
            0x20, 0x40, 0x44, 0x3D, 0x00,// j
            0x00, 0x7F, 0x10, 0x28, 0x44,// k
            0x00, 0x41, 0x7F, 0x40, 0x00,// l
            0x7C, 0x04, 0x18, 0x04, 0x78,// m
            0x7C, 0x08, 0x04, 0x04, 0x78,// n
            0x38, 0x44, 0x44, 0x44, 0x38,// o
            0x7C, 0x14, 0x14, 0x14, 0x08,// p
            0x08, 0x14, 0x14, 0x18, 0x7C,// q
            0x7C, 0x08, 0x04, 0x04, 0x08,// r
            0x48, 0x54, 0x54, 0x54, 0x20,// s
            0x04, 0x3F, 0x44, 0x40, 0x20,// t
            0x3C, 0x40, 0x40, 0x20, 0x7C,// u
            0x1C, 0x20, 0x40, 0x20, 0x1C,// v
            0x3C, 0x40, 0x30, 0x40, 0x3C,// w
            0x44, 0x28, 0x10, 0x28, 0x44,// x
            0x0C, 0x50, 0x50, 0x50, 0x3C,// y
            0x44, 0x64, 0x54, 0x4C, 0x44,// z
            0x00, 0x08, 0x36, 0x41, 0x00,// {
            0x00, 0x00, 0x7F, 0x00, 0x00,// |
            0x00, 0x41, 0x36, 0x08, 0x00,// }
            0x08, 0x08, 0x2A, 0x1C, 0x08,// ->
            0x08, 0x1C, 0x2A, 0x08, 0x08 // <-
    };
    
    // main cog code, responsible for starting the independent cogs that update each bank
    int main()                
    {
      // pauses are micro seconds  
      set_pause_dt(CLKFREQ/1000000);
    
      cog_run(top_bank, 128);
      cog_run(bot_bank, 128);
    
      // this produces a changing random pattern through all display dots 
      //while (1) {
      //  for (int j=0; j<10; j++) {
      //    for (int i=0; i < 7; i++) {
      //      top_buff[i*10+j] = (rand()<<30)|(rand()<<15)|rand();
      //      bot_buff[i*10+j] = (rand()<<30)|(rand()<<15)|rand();
      //    }
      //  }
      //  pause(100000);        
      //}
    
      // scroll all of the characters in the font array and
      // alternate the color of each character
      int cf = 0;
      int array_len = sizeof(font)/sizeof(int);
      while (1) {
        int cfi = cf;
        for (int y = 0; y < 4; y++) {
            for (int x = 0; x < 16; x++) {
                int clr = cfi % 3 + 1;
                charAt(x, y, cfi, clr);
                cfi++;
                if (cfi >= (array_len / 5)) {
                    cfi = 0;
                }
            }
        }
        pause(1000000);
        cf++;
        if (cf >= (array_len / 5)) {
            cf = 0;
        }
      }  
    
    }
    
    void charAt(int x, int y, int idx, int color) {
    
        for (int col = 0; col < 5; col++) {
            int ver = font[idx * 5 + col];
            for (int row = 0; row < 7; row++) {
                if ((ver & 0x01) > 0) {
                    dotAt(x * 5 + col, y * 7 + row, color);
                } else {
                    dotAt(x * 5 + col, y * 7 + row, 0b00);
                }
                ver = ver >> 1;
            }
        }
    }
    
    void dotAt(int x, int y, int rg) {
    
        int ay = (y > 13) ? y - 14 : y;
    
        int addToMod = ((ay > 6) ? (ay - 7) * 10 : 5 + (ay * 10)) + x / 16;
        int addrColShift = 30 - (x % 16 * 2);
    
        if (y > 13) {
            bot_buff[addToMod] &= ~(0b11 << addrColShift);
            if (rg > 0) {
                bot_buff[addToMod] |= (rg & 0b11) << addrColShift;
            }
        } else {
            top_buff[addToMod] &= ~(0b11 << addrColShift);
            if (rg > 0) {
                top_buff[addToMod] |= (rg & 0b11) << addrColShift;
            }
        }
    }
    
    
    void top_bank() {
      
      // set P05 to P07 to output
      DIRA |= 0b11100000;
      // set P05 high, and everything else to low
      OUTA |= 0b00100000;
      OUTA &= 0b00100000;
      
      unsigned int CLKU = 1 << 7;
      unsigned int DATU = 1 << 6;
      unsigned int ENAU = 1 << 5;
      
      unsigned int offset;
      unsigned int end;
      unsigned int datval;
      
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          offset = (j-1)*10;
          end = offset + 10;
          
          for (int addr = offset; addr < end; addr ++) {
            datval = top_buff[addr];
            
            for (int bit = 0; bit < 32; bit++) {
              if (datval & 0x80000000) { OUTA |= DATU; } else { OUTA &= ~DATU; }
              OUTA |= CLKU;
              OUTA &= ~CLKU;
              datval = datval << 1;
            }
          }        
          
          // shift out each of the row select bits
          if (j & 0x04) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU; OUTA &= ~CLKU;      
          if (j & 0x02) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU; OUTA &= ~CLKU;      
          if (j & 0x01) { OUTA |= DATU; } else { OUTA &= ~DATU; }       
          OUTA |= CLKU; OUTA &= ~CLKU;
                              
          // toggle enable
          OUTA &= ~ENAU;
          pause(brightness);
          OUTA |= ENAU;
        } 
      }    
    }  
    
    void bot_bank() {
      
      // set P00 to P07 to output
      DIRA |= 0b00011111;
      // set P02 and P05 high, and everything else to low
      OUTA |= 0b00000100;
      OUTA &= 0b00000100;
      
      unsigned int CLKL = 1 << 4;
      unsigned int DATL = 1 << 3;
      unsigned int ENAL = 1 << 2;
    
      unsigned int offset;
      unsigned int end;
      unsigned int datval;
      
      
      while(1) {
      
        for (int j = 1; j < 8; j++) {
          
          offset = (j-1) * 10;
          end = offset + 10;
          
          for (int addr = offset; addr < end; addr ++) {
            datval = bot_buff[addr];
            
            for (int bit = 0; bit < 32; bit++) {
              if (datval & 0x80000000) { OUTA |= DATL; } else { OUTA &= ~DATL; }
              OUTA |= CLKL;
              OUTA &= ~CLKL;
              datval = datval << 1;
            }
          }        
          
          // shift out each of the row select bits
          if (j & 0x04) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL; OUTA &= ~CLKL;
          if (j & 0x02) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL; OUTA &= ~CLKL;
          if (j & 0x01) { OUTA |= DATL; } else { OUTA &= ~DATL; }       
          OUTA |= CLKL; OUTA &= ~CLKL;
                              
          // toggle enable
          OUTA &= ~ENAL;
          pause(brightness);
          OUTA |= ENAL;
        } 
              
      }    
    }  
    
    
Sign In or Register to comment.