12 to 8 bit conversion

Discussion in 'Embedded Systems and Microcontrollers' started by Shagas, Sep 1, 2013.

  1. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Hello

    I am trying to send data from my atmega32 to these
    TLC5947 chips from TI.

    Basically these are 24 channel PWM led drivers .
    The pwm is 12 bit on each channel.
    They recieve data serially and work like SIPO shift registers , where you have to raise and lower a latch pin after you've transfered all the data to all the channels.

    I've managed to make it work by manually sending data (bitbanging??) through the i/o port and it works fine. That is if I send binary data.


    I don't know how I can convert decimal values into binary and then the data bit by bit .
    I could probably do it but it would be very inefficient.
    Problem:

    What if I want to send the data using the AVR SPI ?
    The issue is that the SPI sends data in 8 bit chunks and the TLC driver has 12 bit registers for each one of the 24 channels .

    Let's say my data is stored in a 24 element long array where the numbers range from 0 to 4096 in decimal (12bits). How would I have my SPI peripheral take that data in chunks of 8 bits and send it?

    I would even be satisfied with an 8 bit pwm accuracy but the problem is that the communication is serial and I HAVE to shift all 12 bits to get to the next channel so I can't just send 8 bits and skip to the next channel etc ( this is probably obvious but thought I'd say it anyway)

    Would something like this be more efficient to do manually with software(bitbanging?)rather than coverting the data to the SPI?

    I really can't think of a way to convert a 12x 24 string of bits into chunks of 8 . Anyone have experience with this?

    Thanks in advance
     
  2. mitko89

    Member

    Sep 20, 2012
    123
    19
    I'm not quite sure using spi will be of any help if your SPI is able to work in 16 bit mode. To be honest, I am not sure if you shift more then 12 bits what happens with the bits after, do they overwrite the old input. If not just control the enable single and feed the shift registers with 2 consecutive 8bit transfers and then enable, if not, I don't see any need to use the SPI.
     
  3. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    The problem is that there is no "overwrite" .
    You just shift in bits so they move along . You can't select what channel you want to shift bits to .
    Everytime you want to update a single bit in any of the channels then you have to re-shift 12X24 = 288 bits into the whole system .

    If I could convert my values into a continuous string of bits and then have the SPI shift the string in 8 bit chunks then that would work ... but I don't know how to do that or if that is even possible.

    So is there any way that I can convert my Pwm_intensity[24]; array into a continuous string of 288 bits and then send those bits in chunks of 8 ? Using SPI or without .
     
  4. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Hmmmmm I just got a good Idea .

    I could send 8 bits using the SPI and then manually shift in four zeros giving me 12 bits with an 8 bit precision. (who the hell would need 12 bit precision for driving led's anyway ? :/ . Not like i'm making a HD television or something) .

    Seriously ... why did they make it a 12 bit PWM when they know that these things will be driven by 8 bit micros ?
     
  5. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    This seems pretty easy to me, but if you say it's difficult, maybe I don't fully understand the problem. I say you just assemble the data into 36 bytes, then send them sequentially to the SPI port. Is that so hard?

    If the problem is making 8 bit bytes out of 12 bit words, then it's not much of a problem. If you had a C array of 24 words, where the data is in the low 12 bits of the word, then it's something like this (I'm assuming the most significant bit goes first):

    Code ( (Unknown Language)):
    1.  
    2.     for (i = 0; i < 24; i += 2)
    3.     {
    4.         wait_for_spi();              // Check SPI port and make sure it's clear
    5.         spi_out = (char) (data[i] >> 4);
    6.         wait_for_spi();
    7.         spi_out = (char) (((data[i] << 4) & 0xF0) + ((data[i+1] >> 8) & 0xF));
    8.         wait_for_spi();
    9.         spi_out = (char) (data[i+1] >> 8);
    10.     }
    11. [/i][/i]


    Note that it's un-tested code! But I believe the key point is that you have to send 3 SPI bytes for every 2 data words. Somehow that's necessary, whatever else you do.
     
    Shagas likes this.
  6. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    All you need to do is send 12x24 = 288 bits serially.
    You can use SPI by sending 36 bytes with no loss of resolution. If you only care about 8-bit resolution, yes, just pack 8-bits into the most significant portion and pad the least significant 4-bits with zeros.

    Just pay attention to how you pack 24 8-bit values into 24 12-bit values ending up with 36 bytes.

    If you wish to retain 12-bit resolution then you pack 24 12-bit values into 36 8-bit values, that is, data values for every two channels are stored into 3 bytes.
     
  7. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    That would work fine and it's what I'm trying to find out the past 20 minutes . It's 'difficult' for me because I don't know how I can convert a 24 channel 12 bit (in each channel) array into a 36 channel 8 bit array.
    If I did that , then as you said , I could just send the data in 8 bit chunks through SPI.
    That would work because we are sending the data in a serial fashion and we don't care in what size chunks we are sending it .

    I'm trying to read and understand your code now.
     
  8. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Thats what I'm trying to figure out how to do.
    I think John.P is trying to explain how to do that in his code .I'm still mulling it over
     
  9. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    The first spi_out takes the top 8 bits of the first word, the second spi_out takes the low 4 bits of the first word and the top 4 bits of the second word, then the final spi_out takes the low 8 bits of the second word. You repeat that 12 times and the job is done. Note that although there are 24 words, the loop counts by 2's, so there are only 12 passes not 24.

    Sorry, I made mistake there. That final write to the SPI port shouldn't use shifted data; it should just be the low 8 bits of the word:

    Code ( (Unknown Language)):
    1.  
    2.         spi_out = (char) (data[i+1]);
    3.     }
    4.  
     
    Last edited: Sep 1, 2013
  10. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Yes that's what I thought , but i'm having trouble understanding the syntax .

    are
    Code ( (Unknown Language)):
    1. spi_out = (char)
    2.  
    3. and
    4.  
    5.  (data[i] >> 4);[/i]


    2 separate commands? Isn't there supposed to be a semicolon inbetween?
    I'm slightly confused as to what data the "char" is holding at that moment .
     
  11. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    I don't know what language you are using so I will simply outline the procedure.

    1) Create 24 integer values, assumed to be 16-bit unsigned integers, say, x[1]..x[24]
    2) Create an array of 36 bytes (or char), say, c[1]..c[36]
    3) Take the first two values x[1] and x[2]
    4) Pack two 12-bit values into three 8-bit values c[1], c[2], c[3] as follows:

    Code ( (Unknown Language)):
    1.  
    2. c[1] = x[1] >> 4;
    3. c[2] = (x[1] << 4) + (x[2] >> 8);
    4. c[3] = x[2];
    5.  
    This is just and example. Replace the numeric indices with variables.

    Note that data is packed MSB to the left. You may have to bit-reverse the data depending on how the SPI data is transmitted. The TLC5947 expects the MSB to be transmitted first.

    Edit: Cross posting with John. We're both showing you the same thing.
     
  12. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Thanks Mr chips , I'll use your outline to write up the code myself because I don't like to copy-paste ready code.

    I'm using C but i've gotten into micros and C language only 2 months ago so I sometimes don't understand the full syntax.

    The MSB orientation is not important as I can change the order of transfer in the SPI register.
     
  13. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    I used indices starting with [1] for clarity.
    C-language begins with [0]. Modify to suit.

    Code ( (Unknown Language)):
    1.  
    2. c[i] = x[j] >> 4;
    3. c[i+1] = (x[j] << 4) + (x[j+1] >> 8);
    4. c[i+2] = x[j+1];
    5. [/i]
     
  14. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74


    Thanks alot !
    I did it on paper to confirm the result .

    Just one question :

    If I say:

    Code ( (Unknown Language)):
    1.  char Chips = 1010 1111 0000 ;
    Then since Chips has a length of a byte will

    Chips = 1111 0000 and ignore the first four digits?
     
  15. techristian

    New Member

    Aug 27, 2013
    26
    2
    You may ant to round off before TRUNCATING .

    Dan
     
    Shagas likes this.
  16. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224


    The (char) is called a "cast". It indicates that the result of a calculation is being forced into a particular data type--in this case, a 16-bit (or longer?) word is being made to appear as an 8-bit quantity, which means it will truncate the word to 8 bits. In general if you put a 16-bit word into an 8-bit register like the SPI port, you get the lowest 8 bits anyway, but there's no harm in making that explicit.

    Note that when you use a cast, it is the designation of the data type rather than the data itself that appears in parentheses. So:

    the_byte = (char) the_word;

    not

    the_byte = char (the_word):
     
  17. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Great , thanks John P . This is one thing that I was looking for .
    So if I cast a 16 bit word into a char it will truncate the word to 8 bits starting from the LSB and counting to the left?
    Can I truncate a larger word into a smaller starting from the MSB using this syntax?
     
  18. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,365
    No. You have to shift to the right first before typecasting.

    this_byte = (char) (this_word >> 8);

    Another way of doing this is with unions but let's leave that off the table for now.
     
  19. Shagas

    Thread Starter Active Member

    May 13, 2013
    802
    74
    Thanks Mr.Chips
    I learned alot in this thread.
     
Loading...