Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 16 of 16

Thread: LED Bar Graph sketch conversion to the Teensy 3. Any bit bangers out there?

  1. #1
    Senior Member
    Join Date
    Nov 2012
    Posts
    412

    LED Bar Graph sketch conversion to the Teensy 3. Any bit bangers out there?

    LED Bar Graph sketch conversion to the Teensy 3.

    SeeedStudio came up with an unique bargraph using only two
    wires (data/ clock) and multiple color bar graphs that can be ganged together.
    These handy 10 position LED bar graph have a special driver on
    the back of the Grove LED Bar which controls the LED brightness and
    multicolor LED bar display. The unit runs at 3 VDC (for Teensy 3) or 5 VDC.

    http://www.seeedstudio.com/depot/-p-...?cPath=156_157

    SeeedStudio wrote a sketch using an Arduino and "port" shifted out a bit
    pattern, from the Arduino 8 bit port, to the bar graph.
    Since the Teensy 3 ARM is 32 bit microcontroller how would you modify the
    following sketch so it would work on the 32 bit Teensy 3?
    Any help is appreciated.

    Code:
    // SeeedStudio Grove LED Bar graph sketch
    
    /* Version 1.0 April, 2012
    Supply voltage  5V  
    Module maximum working voltage     5.5 V  
    Module minimum working voltage     3 V  
    Module maximum working current   222.6 mA  <-----<<<<<
    Module Minimum working current    5.37 mA  
    Chip full load working temperature  52 ℃  
    
    works on the SeeedStudio 328P <------<<<<
    
    * Copyright 2012 Joinj
    * http://www.seeedstudio.com
    */
    #define PORT_Data PORTB
    #define PORT_Clk  PORTB
    
    #define DATA_Pin 8   //DATA IN
    #define CLK_Pin 9   //CLK IN
    
    #define BIT_Data  0x01
    #define BIT_Clk   0x02
    
    #define CmdMode 0x0000  //Work on 8-bit mode
    #define ON 0x00ff   //8-bit 1 data
    #define SHUT 0x0000   //8-bit 0 data
    
    
    
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {
            if(data&0x8000)
            {
                PORT_Data |= BIT_Data;
               
            }
            else
            {
                PORT_Data &=~ BIT_Data;
               
            }
    
            PORT_Clk ^= BIT_Clk; 
            data <<= 1;
        }
    }
    
    //latch routine for MY9221 data exchange
    void latchData(void)
    {
        //PORT_Data &=~ BIT_Data;
        PORT_DATAx &=~ BIT_Data;
        delayMicroseconds(10);
        for(unsigned char i=0;i<8;i++)
        {
        PORT_Data ^= BIT_Data;  
            
        }
    } 
    
    //Initializing pins
    void setup()
    {
      pinMode(DATA_Pin,OUTPUT);  //Data pin
      pinMode(CLK_Pin,OUTPUT);  //CLK pin
    }
    
    //Send 12 road led brightness data
    void sendLED(unsigned int LEDstate)
    {
      unsigned char i;
      for(i=0;i<12;i++)
      {
        if(LEDstate&0x0001)
          send16bitData(ON);
        else 
          send16bitData(SHUT);
    //    if(i!=11)
          LEDstate=LEDstate>>1;
      }
    }
    
    //If you want turn on the first red led,you can do it like this: sendLED(0x0001);
    //The second led: sendLED(0x0002);
    void loop()
    {
      unsigned int i=0x0000;
      while(i<=0x03ff)
      {
        send16bitData(CmdMode);  //set first LED Bar mode
        sendLED(i);  //send first LED Bar data
        send16bitData(CmdMode);  //set second LED Bar mode,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        sendLED(i);  //send second LED Bar data,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        latchData();  //make it come into effect
        i=i*2+1;
       delay(100); 
      }
    }
    Update ...I was able to re-write the LED bar graph code snippet to work (see below), on the Arduino 328P, without using portB and by using alternative functions. Shiftout and digitalWrite made it possible to port the code to the Teensy 3. Since the Teensy 3 is running faster than the 16 MHz Arduino then it is only a matter of obtaining a digital logic analyzer and get the timing correct.


    Code:
    /* Version 1.0 April, 2012
    Supply voltage  5V  
    Module maximum working voltage     5.5 V  
    Module minimum working voltage     3 V  
    Module maximum working current   222.6 mA  <-----<<<<<
    Module Minimum working current    5.37 mA  
    Chip full load working temperature  52 ℃  
    
    works on the SeeedStudio 328P <------<<<<
    // not working yet on the T3
    // alt send16bitData_1(data) <-------<<<<< works arduino
    // alt latchData_1()         <-------<<<<< works arduino
    
    * Copyright 2012 Joinj
    * http://www.seeedstudio.com
    */
    //#define PORT_Data PORTB
    //#define PORT_Clk  PORTB
    
    #define DATA_Pin 2   //DATA IN
    #define CLK_Pin 3    //CLK IN
    
    #define BIT_Data  0x01
    #define BIT_Clk   0x02
    
    #define CmdMode 0x0000  //Work on 8-bit mode
    #define ON 0x00ff   //8-bit 1 data
    #define SHUT 0x0000   //8-bit 0 data
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {
            if(data&0x8000)
            {
                //PORT_Data |= BIT_Data; 
            }
            else
            {
                //PORT_Data &=~ BIT_Data; 
            }
    
            //PORT_Clk ^= BIT_Clk; 
            data <<= 1; 
        }
    }
    
    //Send 16_bit data
    void send16bitData_1(unsigned int data){ // <-------<<<<< Alternate
    // Do this for MSBFIRST serial
    // shift out highbyte
    
    shiftOut(DATA_Pin, CLK_Pin, MSBFIRST, (data >> 8));  
    // shift out lowbyte
    shiftOut(DATA_Pin, CLK_Pin, MSBFIRST, data);
    }
    
    // License GNU Lesser General Public License 2.1 original material copyright David A. Mellis, changes copyright Scott Penrose
    // New shiftOut, but backwards compatible, accepts
    //	shiftOut( dataPin, clockPin, bitOrder, val);
    //	shiftOut( dataPin, clockPin, bitOrder, val, bits);
    //	shiftOut (dataPin, clockPin, bitOrder, val, bits, delayMicroSeconds);
    void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, unsigned int val, uint8_t bits = 8, uint8_t del = 0)
    {
      uint8_t i;
      for (i = 0; i < bits; i++)  {
        if (bitOrder == MSBFIRST)
          digitalWrite(DATA_Pin, !!(val & (1 << i)));
        else    
          digitalWrite(DATA_Pin, !!(val & (1 << ((bits - 1 - i)))));
        digitalWrite(CLK_Pin, HIGH);
        delayMicroseconds(del);
        digitalWrite(CLK_Pin, LOW);            
      }
    }
    
    void send16bitData_2(unsigned int data){
    shiftOut(DATA_Pin, CLK_Pin, MSBFIRST, data, 16, 0);
    }
    
    
    
    //latch routine for MY9221 data exchange
    void latchData(void)
    {
        // PORT_Data &=~ BIT_Data;
        delayMicroseconds(10);
        for(unsigned char i=0;i<8;i++)
        {
            //PORT_Data ^= BIT_Data; // toggle
        }
    } 
    
    //latch routine for MY9221 data exchange <-------<<<<< Alternate
    void latchData_1(void)
    {
        //PORT_Data &=~ BIT_Data; // set 
        digitalWrite(DATA_Pin, HIGH);
        
        delayMicroseconds(10); // 10 at 16 Hhz
        for(unsigned char i=0;i<8;i++)
        {
            //PORT_Data ^= BIT_Data; // toggle
            int x = digitalRead(DATA_Pin);
            if (x){
            digitalWrite(DATA_Pin, LOW);
            }
            else
            {
            digitalWrite(DATA_Pin, HIGH);  
            }  
        }
    } 
    
    
    //Initializing pins
    void setup()
    {
      pinMode(DATA_Pin,OUTPUT);  //Data pin
      pinMode(CLK_Pin,OUTPUT);  //CLK pin
    }
    
    //Send 12 road led brightness data
    void sendLED(unsigned int LEDstate)
    {
      unsigned char i;
      for(i=0;i<12;i++)
      {
        if(LEDstate&0x0001)
          send16bitData(ON);
        else 
          send16bitData(SHUT);
    //    if(i!=11)
          LEDstate=LEDstate>>1;
      }
    }
    
    //If you want turn on the first red led,you can do it like this: sendLED(0x0001);
    //The second led: sendLED(0x0002);
    void loop()
    {
      unsigned int i=0x0000;
      while(i<=0x03ff)
      {
        send16bitData_1(CmdMode);  //set first LED Bar mode
        //sendLED(i);  //send first LED Bar data
        sendLED(0x0001); // 0-3ff 0x100 0x200 0x3ff
        //sendLED(B11111100); // need hex to addr last 2 bars
        //sendLED(0x0002);
        //send16bitData(CmdMode);  //set second LED Bar mode,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        //sendLED(i);  //send second LED Bar data,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        latchData_1();  //make it come into effect
        while(1){}
        
        i=i*2+1;
       delay(100); 
      }
    }
    Last edited by t3andy; 12-25-2012 at 08:40 PM.

  2. #2
    I'll take a stab at it. I'm not sure what's going on in the line where they've replaced "PORT_Data &=~ BIT_Data;" with "PORT_DATAx &=~ BIT_Data;". I'm not familiar with the keyword "PORT_DATAx", and it's not defined in the program, and a web search for it only turns up this post...so, er, I've just treated it as though it's the commented-out line that you need there.

    The fact that Teensy 3.0 is 32-bit shouldn't actually matter here, because there don't appear to be any implicit assumptions about 16-bit data. (The only place it comes up at all is in send16bitData(int), but there it uses unsigned ints and shifts to the left, so the extra bits don't actually make any difference.)

    EDIT: Removing misinformation about pin numbers and ports! The port-manipulation code on Teensy is actually AVR emulation code, which maintains the pin numberings from the Arduino. Redoing the pin numbers is not necessary here. However, some of the bit-flicking operations used in the original code don't appear to be supported yet (particularly XOR), so the original code won't run on Teensy 3.0 without some modification yet. Misinformation excised!

    However...as far as I can tell, they were using port manipulation solely for speed here--they're still transferring the data one bit at a time; and there shouldn't really be a need for that on Teensy 3.0. So instead of using different pins as I've indicated above, I've written a version which replaces it with calls to digitalWriteFast, which makes the code easier to read. The only difference is that I need to introduce variables to store the state that they're reading out of the port registers (when they use the ^= operator). That's where the clock_state and data_bit variables came from. See if this works for you:

    Code:
    // SeeedStudio Grove LED Bar graph sketch
    
    /* Version 1.0 April, 2012
    Supply voltage  5V
    Module maximum working voltage     5.5 V
    Module minimum working voltage     3 V
    Module maximum working current   222.6 mA  <-----<<<<<
    Module Minimum working current    5.37 mA
    Chip full load working temperature  52 ?
    
    Formerly for Seeed 328P, modified for Teensy 3.0
    
    * Copyright 2012 Joinj
    * http://www.seeedstudio.com
    */
    
    #define DATA_Pin 8  // Connect to DATA_IN
    #define CLK_Pin 9  // Connect to CLK_IN
    boolean clock_state = false;
    
    #define CmdMode 0x0000  //Work on 8-bit mode
    #define ON 0x00ff   //8-bit 1 data
    #define SHUT 0x0000   //8-bit 0 data
    
    
    inline void digitalWriteBoolean(int pin, boolean data) {
      digitalWriteFast(pin, data ? HIGH : LOW);
    }
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {
          digitalWriteBoolean(DATA_Pin, data&0x8000);
          clock_state = !clock_state;
          digitalWriteBoolean(CLK_Pin, clock_state);
          data <<= 1;
        }
    }
    
    //latch routine for MY9221 data exchange
    void latchData(void)
    {
      boolean data_bit = false;
      digitalWriteBoolean(DATA_Pin, data_bit);
      delayMicroseconds(10);
      for(unsigned char i=0;i<8;i++)
      {
        data_bit = !data_bit;
        digitalWriteBoolean(DATA_Pin, data_bit);
      }
    }
    
    //Initializing pins
    void setup()
    {
      pinMode(DATA_Pin,OUTPUT);  //Data pin
      pinMode(CLK_Pin,OUTPUT);  //CLK pin
      digitalWriteBoolean(CLK_Pin, clock_state);
    }
    
    //Send 12 road led brightness data
    void sendLED(unsigned int LEDstate)
    {
      unsigned char i;
      for(i=0;i<12;i++)
      {
        if(LEDstate&0x0001)
          send16bitData(ON);
        else
          send16bitData(SHUT);
    //    if(i!=11)
          LEDstate=LEDstate>>1;
      }
    }
    
    //If you want turn on the first red led,you can do it like this: sendLED(0x0001);
    //The second led: sendLED(0x0002);
    void loop()
    {
      unsigned int i=0x0000;
      while(i<=0x03ff)
      {
        send16bitData(CmdMode);  //set first LED Bar mode
        sendLED(i);  //send first LED Bar data
        send16bitData(CmdMode);  //set second LED Bar mode,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        sendLED(i);  //send second LED Bar data,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        latchData();  //make it come into effect
        i=i*2+1;
       delay(100);
      }
    }
    Hope this helps. This should be more or less equivalent, but I don't have the hardware to debug, so take this interpretation with a grain of salt. If there's someone out there with more experience *and* one of these bar graph modules out there, his/her input would probably be more helpful.
    -Nick
    Last edited by emertonom; 12-27-2012 at 01:12 AM. Reason: Removing misinformation about pin/port correspondence

  3. #3
    Oops, you updated while I was writing my reply! Congrats on getting it working!

  4. #4
    It looks to me like you've inverted the outputs of the LATCH function--in these lines:
    Code:
        //PORT_Data &=~ BIT_Data; // set 
        digitalWrite(DATA_Pin, HIGH);
    The original version you've commented out doesn't actually set the bit--it clears it. Since it does a bitwise AND with the negation of BIT_Data, that retains the values of all bits except the one indicated by BIT_Data, which is coerced to 0. So it should probably be digitalWrite(DATA_Pin, LOW) at the start of that function.

    Worth double-checking.
    -Nick

  5. #5
    Senior Member
    Join Date
    Nov 2012
    Posts
    412
    @emertonom - thanks Nick

    Oops, you updated while I was writing my reply! Congrats on getting it working! .
    Only on the 328P.

    Hope this helps. This should be more or less equivalent, but I don't have the hardware to debug
    I have only one LED bar graph.

    The code you provided looks fine and what I did was to drop back (without using your digitalWriteFast) and made sure it would work first on the 16 MHz 328P - it did not. My code seems to work on the 328P but not on the Teensy 3? Its seems SeeedStudio's "Joinj" used PortB as a very fast means to cycle or bit-bang the clk and data bits. Trying to make sense out of this MY9221 data sheet is very confusing. What I need to do is to acquire a digital logic analyzer and check the waveform and try to duplicate it on the Teensy 3. The timing on this LED bargraph must be very tight.

  6. #6
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,100
    Did you try the original code? Teensy 3.0 has an AVR emulation layer that it meant to automatically translate these direct AVR register writes into the equivalent 32 bit ARM register operations.

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Posts
    412
    Yes and it fails to compile on the Teensy 3.
    See full code posted (above)


    Code:
    #define PORT_Data PORTB
    #define PORT_Clk  PORTB
    
    PORT_Clk ^= BIT_Clk; <--------------- This line fails
    
    LED_Bar_Seeed_328P_R3.ino: In function 'void send16bitData(unsigned int)':
    LED_Bar_Seeed_328P_R3:51: error: no match for 'operator^=' in 'PORTB ^= 2' 
    
    
    
    
    Function ref.
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {
            if(data&0x8000)
            {
                PORT_Data |= BIT_Data; 
            }
            else
            {
                PORT_Data &=~ BIT_Data; 
            }
    
            PORT_Clk ^= BIT_Clk; 
            data <<= 1; 
        }
    }

  8. #8
    Ah! XOR isn't missing--XOR is an operator that the AVR emulation doesn't handle. Sorry for the confusion. I'm not sure how to rewrite the port-manipulation code to work on both platforms at the moment. Direction registers (DDRB, etc) also appear to be omitted, which may affect other code.
    Last edited by emertonom; 12-27-2012 at 01:07 AM. Reason: Mistaken diagnosis--XOR is present, just not for emulated PORT registers.

  9. #9
    Hmm. On closer inspection, I'm *really* confused about how the updated code you posted is working. It appears to send the command code using your modified functions, but then sends the bar graph data using "sendLED," which in turn calls "send16bitData"--which is the old version, not your altered version ("send16bitData_1"), and indeed has all its data-sending lines commented out. So there's definitely something weird going on there. Are you sure it's working as intended? Also, the function "shiftOut" appears to be backwards--its cases for MSBFIRST/else seem to be reversed.

    As far as the timing thing goes, it looks like there are two likely problems. The first is the MY9221's latch timing--in particular, the latch operation won't succeed unless the data clock has been held constant for at least 220 microseconds before the data input is cycled. On the Arduino, it's likely this just happened naturally during the course of the program, and so they didn't put in an explicit delay; you probably need to add a certain amount of delay on the Teensy. The second problem is that the maximum data clock timing on the MY9221 is 10MHz, and it's possible the loop is exceeding that.

    See if this code works on the Teensy 3.0:
    Code:
    // SeeedStudio Grove LED Bar graph sketch
    
    /* Version 1.0 April, 2012
    Supply voltage  5V
    Module maximum working voltage     5.5 V
    Module minimum working voltage     3 V
    Module maximum working current   222.6 mA  <-----<<<<<
    Module Minimum working current    5.37 mA
    Chip full load working temperature  52 ?
    
    works on the SeeedStudio 328P <------<<<<
    
    * Copyright 2012 Joinj
    * http://www.seeedstudio.com
    */
    
    #define DATA_Pin 8   //DATA IN
    #define CLK_Pin 9   //CLK IN
    boolean clock_state = false;
    
    #define CmdMode 0x0000  //Work on 8-bit mode
    #define ON 0x00ff   //8-bit 1 data
    #define SHUT 0x0000   //8-bit 0 data
    
    #define CLOCK_DELAY 100
    #define WAIT_FOR_CLOCK delayMicroseconds(CLOCK_DELAY)
    #define LATCH_DELAY 220
    #define WAIT_FOR_LATCH delayMicroseconds(LATCH_DELAY)
    
    
    inline void digitalWriteBoolean(int pin, boolean data) {
      digitalWriteFast(pin, data ? HIGH : LOW);
    }
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {
          digitalWriteBoolean(DATA_Pin, data&0x8000);
          clock_state = !clock_state;
          digitalWriteBoolean(CLK_Pin, clock_state);
          data <<= 1;
          WAIT_FOR_CLOCK;
        }
    }
    
    //latch routine for MY9221 data exchange
    void latchData(void)
    {
      boolean data_bit = false;
      digitalWriteBoolean(DATA_Pin, data_bit);
      WAIT_FOR_LATCH;
      for(unsigned char i=0;i<8;i++)
        {
          data_bit = !data_bit;
          digitalWriteBoolean(DATA_Pin, data_bit);
        }
      WAIT_FOR_LATCH;
    }
    
    //Initializing pins
    void setup()
    {
      pinMode(DATA_Pin,OUTPUT);  //Data pin
      pinMode(CLK_Pin,OUTPUT);  //CLK pin
      digitalWriteBoolean(CLK_Pin, clock_state);
    }
    
    //Send 12 road led brightness data
    void sendLED(unsigned int LEDstate)
    {
      unsigned char i;
      for(i=0;i<12;i++)
      {
        if(LEDstate&0x0001)
          send16bitData(ON);
        else
          send16bitData(SHUT);
    //    if(i!=11)
          LEDstate=LEDstate>>1;
      }
    }
    
    //If you want turn on the first red led,you can do it like this: sendLED(0x0001);
    //The second led: sendLED(0x0002);
    void loop()
    {
      unsigned int i=0x0000;
      while(i<=0x03ff)
      {
        send16bitData(CmdMode);  //set first LED Bar mode
        sendLED(i);  //send first LED Bar data
    //    send16bitData(CmdMode);  //set second LED Bar mode,if you do not use two LED Bar work together(connect one by one),you can delete this line.
    //    sendLED(i);  //send second LED Bar data,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        latchData();  //make it come into effect
        i=i*2+1;
       delay(100);
      }
    }
    That adds delays that limit the code to the data rate in the MY9221 spec. I'm not completely certain there's no other bug in my code, but it's a pretty straight translation of the original version, so I can't really see why else it would be failing.

    Also: sorry about the misinformation about the pins associated with the ports! I was going by the Teensy 3.0 schematic at http://www.pjrc.com/teensy/schematic.html , but I didn't know about the AVR emulation layer. Sorry about that!

    Trying to help, just...not very good at it yet!
    -Nick
    Last edited by emertonom; 12-27-2012 at 12:56 AM. Reason: Replacing tabs with spaces

  10. #10
    Senior Member
    Join Date
    Nov 2012
    Posts
    412
    Hmm. On closer inspection, I'm *really* confused about how the updated code you posted is working.
    To end your confusion, I forgot that sendLED was still pointing to the old code which I did not remove and should have. Good catch Nick.
    So, only the original code still works on a 328P but, of course, not the Teensy 3.

    Code:
    PORT_Clk = (PORT_Clk &~ BIT_Clk) | (~PORT_Clk & BIT_Clk);
    I tried that code patch and the compiler has this error.

    LED_Bar_Seeed_328P_R3.ino: In function 'void send16bitData(unsigned int)':
    LED_Bar_Seeed_328P_R3:52: error: no match for 'operator&' in 'PORTB & -0x00000000000000003'
    LED_Bar_Seeed_328P_R3:52: error: no match for 'operator~' in '~PORTB'

    See if this code works on the Teensy 3.0:
    I tried it on the Teensy 3 and also on the 328P (without "digitalWriteFast") and still no luck.

    Did you try the original code? Teensy 3.0 has an AVR emulation layer that it meant to automatically translate these direct AVR register writes into the equivalent 32 bit ARM register operations.
    What Paul said about AVR emulation was news to me.

    Trying to help, just...not very good at it yet!
    Any help is very appreciated ... Nick - thanks.

    The second problem is that the maximum data clock timing on the MY9221 is 10MHz, and it's possible the loop is exceeding that.
    What is the I/O bit rate on the Teensy 3?
    Last edited by t3andy; 12-27-2012 at 01:56 AM.

  11. #11
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,100
    Quote Originally Posted by t3andy View Post
    What is the I/O bit rate on the Teensy 3?
    It depends.

    If you run this code with Tools > CPU Speed set to 96 MHz...

    Code:
    void setup() {
      pinMode(2, OUTPUT);
      while (1) {
        digitalWriteFast(2, HIGH); 
        digitalWriteFast(2, LOW);
      }
    }
    
    void loop() {
    }
    The result is this waveform (the ringing is from a 3 inch wire between the Teensy 3.0 and my scope's probe + ground)

    Click image for larger version. 

Name:	scope_7.png 
Views:	252 
Size:	17.5 KB 
ID:	106

    While that's "only" about 10.67 MHz, notice the signal is high for a shorter time (approx 33 ns) than when it's low, because the while(1) loop takes a couple extra cycles.

    If you need the signal constant for 200 microseconds (as the post above says... but 220 us seems like a long time), the extremely short 33 ns pulses you'll get on Teensy 3.0 using direct I/O without any extra delay will be much too short.
    Last edited by PaulStoffregen; 12-27-2012 at 03:13 AM.

  12. #12
    Oh yeah, stupid error--clock delay doesn't need to be nearly so long! I don't know why 100us didn't sound ridiculous to me. The 220us, however, is verbatim from the data sheet:

    Internal-latch control cycle timing diagram
    The steps to trigger internal-latch function are shown below:
    1. After whole given serial data are shifted into shift register, keeping DCKI at a fixed level
    (no matter “high” or “low”) for more than 220us. (Tstart > 220us)
    2. Send 4 DI pulses (twH(DI)>70ns, twL(DI)> 230ns, Tstop*)
    3. Data is loaded into the latch register at 2nd falling edge of DI pulse
    (There's a diagram that goes along with it--the datasheet is at http://www.seeedstudio.com/wiki/imag...221_DS_1.0.pdf ). So yes, the delay before triggering the latch has to be >220us...but the data clock speed should be much, much higher; the data and clock pulses only need to be high for 50ns, and indeed the *minimum* clock rate is 0.07MHz, so the delays I put in there were running data too slowly. Third or fourth time's the charm, right? Try this one. I've just taken the WAIT stuff out of the data loop. As a point of note, changing digitalWriteFast to digitalWrite on the 328P probably won't make it work, because digitalWrite is still somewhat slow on those processors--that's why the original version used port commands. So I'm including a quick-and-dirty implementation of "digitalWriteFast" for Arduino which encapsulates the port manipulation stuff, and wrapping it in a #ifdef to prevent it from compiling on the Teensy; that way the sketch might work on both chips. It may still not be fast enough--indeed, it may not even be faster than digitalWrite, as I'm not the best bit-twiddler. But it's worth a shot. So yeah...willing to give it another go?

    Code:
    // SeeedStudio Grove LED Bar graph sketch
    
    /* Version 1.0 April, 2012
    Supply voltage  5V
    Module maximum working voltage     5.5 V
    Module minimum working voltage     3 V
    Module maximum working current   222.6 mA  <-----<<<<<
    Module Minimum working current    5.37 mA
    Chip full load working temperature  52 ?
    
    works on the SeeedStudio 328P <------<<<<
    
    * Copyright 2012 Joinj
    * http://www.seeedstudio.com
    */
    
    #define DATA_Pin 8   //DATA IN
    #define CLK_Pin 9   //CLK IN
    boolean clock_state = false;
    
    #define CmdMode 0x0000  //Work on 8-bit mode
    #define ON 0x00ff   //8-bit 1 data
    #define SHUT 0x0000   //8-bit 0 data
    
    #define LATCH_DELAY 220
    #define WAIT_FOR_LATCH delayMicroseconds(LATCH_DELAY)
    
    #ifndef _mk20dx128_h_
    volatile uint8_t* _pin_port[2] = {&PORTD, &PORTB};
    uint8_t _pin_mask[14] = {1,2,4,8,16,32,64,128,1,2,4,8,16,32};
    inline void digitalWriteFast(int pin, int data) {
      volatile uint8_t* port_reg = _pin_port[pin >> 3];
      uint8_t mask = _pin_mask[pin];
      *port_reg = ((*port_reg) & ~mask) | mask * !!data;
    }
    #endif // ndef _mk20dx128_h_
    
    inline void digitalWriteBoolean(int pin, boolean data) {
      digitalWriteFast(pin, data ? HIGH : LOW);
    }
    
    //Send 16_bit data
    void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++)
        {digitalWriteBoolean(DATA_Pin, data&0x8000);
          digitalWriteBoolean(CLK_Pin, clock_state);
          clock_state = !clock_state;
          data <<= 1;
        }
    }
    
    //latch routine for MY9221 data exchange
    void latchData(void)
    {
      boolean data_bit = false;
      digitalWriteBoolean(DATA_Pin, data_bit);
      WAIT_FOR_LATCH;
      for(unsigned char i=0;i<8;i++)
        {
          data_bit = !data_bit;
          digitalWriteBoolean(DATA_Pin, data_bit);
        }
      WAIT_FOR_LATCH;
    }
    
    //Initializing pins
    void setup()
    {
      pinMode(DATA_Pin,OUTPUT);  //Data pin
      pinMode(CLK_Pin,OUTPUT);  //CLK pin
      digitalWriteBoolean(CLK_Pin, clock_state);
      clock_state = !clock_state;
    }
    
    //Send 12 road led brightness data
    void sendLED(unsigned int LEDstate)
    {
      unsigned char i;
      for(i=0;i<12;i++)
      {
        if(LEDstate&0x0001)
          send16bitData(ON);
        else
          send16bitData(SHUT);
    //    if(i!=11)
          LEDstate=LEDstate>>1;
      }
    }
    
    //If you want turn on the first red led,you can do it like this: sendLED(0x0001);
    //The second led: sendLED(0x0002);
    void loop()
    {
      unsigned int i=0x0000;
      while(i<=0x03ff)
      {
        send16bitData(CmdMode);  //set first LED Bar mode
        sendLED(i);  //send first LED Bar data
    //    send16bitData(CmdMode);  //set second LED Bar mode,if you do not use two LED Bar work together(connect one by one),you can delete this line.
    //    sendLED(i);  //send second LED Bar data,if you do not use two LED Bar work together(connect one by one),you can delete this line.
        latchData();  //make it come into effect
        i=i*2+1;
        delay(100);
      }
    }
    Still plugging away at it...
    -Nick

  13. #13
    Senior Member
    Join Date
    Nov 2012
    Posts
    412
    I tried it both on the 328P and the Teensy 3 - neither seemed to flash the LED bar. BTW ... I used, on the T3, 96/48/24 for testing.
    I had to remove the code snippet below before it would even compile on the T3.

    Code:
    #ifndef _mk20dx128_h_
    volatile uint8_t* _pin_port[2] = {&PORTD, &PORTB};
    uint8_t _pin_mask[14] = {1,2,4,8,16,32,64,128,1,2,4,8,16,32};
    inline void digitalWriteFast(int pin, int data) {
      volatile uint8_t* port_reg = _pin_port[pin >> 3];
      uint8_t mask = _pin_mask[pin];
      *port_reg = ((*port_reg) & ~mask) | mask * !!data;
    }
    #endif // ndef _mk20dx128_h_
    Code:
    LED_Bar_T3_Nick_Test_r2.cpp.o: In function `digitalWriteBoolean(int, unsigned char)':
    C:\Teensy\arduino-1.0.2-teensy3-beta9-windows\arduino-1.0.2/LED_Bar_T3_Nick_Test_r2.ino:39: undefined reference to `digitalWriteFast(int, int)'
    collect2: ld returned 1 exit status



    A very noble effort for this tight timing widget.
    Last edited by t3andy; 12-28-2012 at 09:44 AM.

  14. #14
    Senior Member
    Join Date
    Nov 2012
    Posts
    412
    If you need the signal constant for 200 microseconds (as the post above says... but 220 us seems like a long time), the extremely short 33 ns pulses you'll get on Teensy 3.0 using direct I/O without any extra delay will be much too short. .
    Paul's comment on how slow this device is - is the key.

    @Nick

    This LED Bar works fine on the standard 328P Arduino but I believe the effort to port this very slow and
    tight timing LED drive module to the very fast Teensy 3 is a design / application mismatch. I can obtain 74HC595 shift register
    which only uses 3 GPIO at 50Mhz which will keep up with the Teensy 3 and not bog it down. Thanks for your effort.

    https://www.sparkfun.com/products/10680
    http://bildr.org/2011/08/74hc595-breakout-arduino/
    Last edited by t3andy; 12-29-2012 at 02:34 AM.

  15. #15
    Oops! Something weird is going on there--even if I make it #ifndef _avr_emulation_h_, I still get the error "cannot convert 'PORTDemulation' to 'volatile uint8_t*' in assignment" on that line, which is bizarre, because PORTD is defined as being of type PORTDemulation in a file whose first lines are #ifndef _avr_emulation_h_ #define _avr_emulation_h_. Sorry, I thought I'd checked it on both platforms, must have just assumed it worked on Teensy 3.0. Cutting those lines out to compile on Teensy 3.0 was the right step.

    Sorry it's not working! It should be--the 33ns pulses Paul was talking about are from a loop which is solely turn on followed by turn off; in these, it's turn on one, turn on the other, do a shift operation and an invert operation and an increment operation and a compare operation, then loop, then switch things, so there's a lot more going on, and it should be taking at least 50ns, which is supposed to be enough for the device. I don't know what the problem is at this point. Sorry to waste your time with testing my broken code.

    Hope you get your project sorted!
    -Nick

  16. #16
    Junior Member
    Join Date
    Apr 2020
    Posts
    1
    For info, I've been able to drive 8 of those chips (MY9221) at 6000 FPS on a Teensy 4 with this code:

    Code:
    #define DATA_Pin   5
    #define CLK_Pin  6
    
    boolean clock_state = false;
    
    uint16_t setupWord = 0        // Zeros
           + (1 << 10)            // hspd - Iout Tr/Tf select - 1 : Iout fast mode
           + (0 << 8)            // BS = Grayscale resolution select - 11 : 16-bit grayscale application
           + (0 << 5)             // gck = Internal oscillator freq. select - GCK = 000
           + (1 << 4)             // sep = Output waveform select - 1 : APDM output waveform
           + (0 << 3)             // Grayscale clock source select - 0 : internal oscillator (8.6MHz) (internal GCK source)
           + (0 << 2)             // Output polarity select - 0 : work as LED driver
           + (0 << 1)             // Counter reset select - 0 : free running mode
           + (0 << 0)             // One-shot select - 0 : frame cycle repeat mode
           ;
    
    static inline void delayCycles(uint32_t) __attribute__((always_inline, unused));
    static inline void delayCycles(uint32_t cycles)
    { // MIN return in 7 cycles NEAR 20 cycles it gives wait +/- 2 cycles - with sketch timing overhead
      uint32_t begin = ARM_DWT_CYCCNT-12; // Overhead Factor for execution
      while (ARM_DWT_CYCCNT - begin < cycles) ; // wait 
    }
    
    #define WAIT_FOR_CLOCK delayCycles(16)
    #define WAIT_FOR_LATCH delayCycles(600)
    
    inline void digitalWriteBoolean(int pin, boolean data) {
      digitalWriteFast(pin, data ? HIGH : LOW);
    }
    
    inline void send16bitData(unsigned int data)
    {
      for(unsigned char i=0;i<16;i++) {
        digitalWriteBoolean(DATA_Pin, data&0x8000);
        clock_state = !clock_state;
        digitalWriteBoolean(CLK_Pin, clock_state); WAIT_FOR_CLOCK;
        data <<= 1;
      }
    }
    
    //latch routine for MY9221 data exchange
    inline void latchData(void)
    {
      clock_state = false;
      digitalWriteBoolean(CLK_Pin, clock_state); WAIT_FOR_CLOCK;
      for (int i=0; i<8; i++)  {
        digitalWriteBoolean(DATA_Pin, false); WAIT_FOR_LATCH;
        digitalWriteBoolean(DATA_Pin, true); WAIT_FOR_LATCH;
      }
    }
    
    void setup() {
      pinMode(CLK_Pin, OUTPUT);
      pinMode(DATA_Pin, OUTPUT);
      digitalWriteBoolean(CLK_Pin, clock_state);
      delay(200);
    }
    
    void loop() {
      uint16_t value = 0;
    
      while (true) {
        for(int chipNumber=0; chipNumber<8; chipNumber++) {
          send16bitData(setupWord);  
          for (int i = 0; i < 12; i++) {
            send16bitData((i == value) ? 0x00FF : 0x0000);
          }
        }
        
        value++;
        if (value == 6400) value = 0;
        
        latchData();
      }
    }
    I minimized the delay between pin switches to the minimum before getting wrong interpretation of data. I'm driving it with the 3.3V output of teensy pins. Ideally I should use 5V. I might get a better signal and decrease the delays. I'm more a software guy, so I'll check what can be improved, but as I was looking at this post I though that might help someone.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •