0

I am writing a basic LED program to light LEDs on a custom board using an STM32L0xx chip and the TI TLC59116F, and I'm having some trouble interpreting the data sheet.

I used STM32CubeMX to set up the initial pins and init I2C, and have come up with the following based on some examples I found.

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "led.h"

/* Private variables ---------------------------------------------------------*/

void TLC59116_Init(void)
{
    // Set default register values
    TLC59116_WriteReg(TLC59116_MODE1, TLC59116_MODE1_DEFAULT);
    TLC59116_WriteReg(TLC59116_MODE2, TLC59116_MODE2_DEFAULT);

    // Set all PWM values to 0x00 (OFF)
    uint8_t aTXBuffer[] = { TLC59116_PWM0_AUTOINCR, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF,
            TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF,
            TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF,
            TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF,
            TLC59116_LEDOUT_OFF, TLC59116_LEDOUT_OFF };

    TLC59116_WriteStream(sizeof(aTXBuffer), aTXBuffer);

    // Set all LEDs to PWM Control
    TLC59116_WriteReg(TLC59116_LEDOUT0, TLC59116_LEDOUT_ON);
    TLC59116_WriteReg(TLC59116_LEDOUT1, TLC59116_LEDOUT_ON);
    TLC59116_WriteReg(TLC59116_LEDOUT2, TLC59116_LEDOUT_ON);
    TLC59116_WriteReg(TLC59116_LEDOUT3, TLC59116_LEDOUT_ON);

}

static void TLC59116_WriteReg(uint8_t reg, uint8_t val)
{
    uint8_t aTXBuffer[] = { reg, val };
    while(HAL_I2C_Master_Transmit(&hi2c1, TLC59116_ADDR, aTXBuffer, sizeof(aTXBuffer), 100) != HAL_OK)
        {
            if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
            {
                Error_Handler();
            }
        }
}

static void TLC59116_WriteStream(uint8_t len, uint8_t *pData)
{
    if (len == 0)
    {
        Error_Handler();
    }
    while(HAL_I2C_Master_Transmit(&hi2c1, TLC59116_ADDR, pData, len, 100) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
        {
            Error_Handler();
        }
    }
}

void TLC59116_LED(uint8_t led, uint8_t state)
{
    if ((led < 0) || (led > 15)) {
        Error_Handler();
    } else {
        TLC59116_WriteReg(led + TLC59116_PWM0, state);
    }
}

void TLC59116_SLEEP(void)
{
    TLC59116_WriteReg(TLC59116_MODE1, TLC59116_MODE1_SLEEP);
}

void TLC59116_WAKE(void)
{
    TLC59116_WriteReg(TLC59116_MODE1, TLC59116_MODE1_DEFAULT);
    HAL_Delay(5);
}

Then in main.c

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USB_PCD_Init();
  TLC59116_Init();
  TLC59116_LED(TLC59116_PWM5, 0x01); // Sets LED 5 to ON 

}

Edit: Here is a more complete code example with HAL error checking and such.

TyrantUT
  • 29
  • 1
  • 5
  • Sub addressing is an I2C term, not specific to the TI part. Sub addressing refers to selecting a specific register within the TI part. When accessing any I2C device, you first send the device/slave address, then a sub address, and lastly a data value when writing a register value. See page 26 in the datasheet, first row illustrates writing to a single register with auto increment off. – abonneville Sep 06 '19 at 01:23
  • Stick with HAL for learning, and optimize later using LL, or bite the bullet and stick with LL. In the LL example you provided above, LL_I2C_TransmitData8(...) only sends a single 8-bit value over the I2C bus, not the entire aTxBuffer. – abonneville Sep 06 '19 at 01:26
  • I completely overlooked the 8... jeesh. I’ll look through the DS and come up with a modified example. Thank you. – TyrantUT Sep 06 '19 at 03:44
  • I added a bit more info, and switched over to HAL. – TyrantUT Sep 06 '19 at 20:03
  • Much better. The TLC59116_WriteReg() looks correct, however, the TLC59116_WriteStream() will not work. You will need to add a size parameter as well to the method call, and use the pointer and size directly in the HAL_I2C_Master_Transmit(). As is, the TLC59116_WriteStream() sends bad data on unto the bus. When using the auto increment feature, be sure to follow page 22 of the datasheet. Also, keep in mind the TLC59116_ADDR you have defined should be for writing only (0xC0). If/when you need to read register contents a new definition will need to be defined (0xC1). – abonneville Sep 07 '19 at 22:13
  • Awesome! I realized I forgot to update the function in this example with the len argument. I kept it in the function call but forgot it in the function itself. In that regard, the auto increment bits are sent in the control register, but in my code, i'm not really sure what that control register is. The datasheet is super confusing and incomplete it seems. The payload example for writing to a single register should have useful information. It shows slave address, control, and blank. Not where the register address or values should go, or anything useful. Is this standard for TI components? – TyrantUT Sep 08 '19 at 03:20
  • When using HAL, the slave address = TLC59116_ADDR, control = aTxBuffer[0], and data/blank starts at aTxBuffer[1] when using auto increment. This format is I2C standard, although the use of the term control register is unusual. The following application report provides a baisc intro, see section 3.1: http://www.ti.com/lit/an/slva704/slva704.pdf Just be aware, HAL takes care of the protocol, you just need to load the values correctly – abonneville Sep 08 '19 at 17:19
  • I updated the example below to include streaming – abonneville Sep 08 '19 at 17:42
  • Awesome, thank you. That makes sense. The auto increment options are 101 for AI2,AI1,AI0, then the next 5 bits are the register addresses, but since I'm sending more than 2 bytes in total - it auto increments to the next register address in the stream byte by byte - keeping the auto increment going until the last register is accessed (1 0001), then rolls over to 0 0010. So I'm assuming since only the last 5 bits are being read as the register address, each byte is being broken down by the last 5 bits - and ignoring the first 3 (which would have been the auto increment bits)? – TyrantUT Sep 09 '19 at 18:08
  • Correct, within the 8-bit control register: upper 3-bits specify how to auto increment, the lower 5-bits are the unique register address. The data bytes are a full 8-bits. – abonneville Sep 09 '19 at 18:29
  • Wouldn't the TLC59116_PWM0_AUTOINCR be 0xA2 though? Auto Increment would be (1010 0000) and PWM0 would be (0000 0010), making it 1010 0010) Calculating the autoinc with base register as `uint8_t res = (AUTOINC_ADDR & ~0xf) | (PWM_ADDR & 0xf);` Where AUTOINC_ADDR = 0xA0, and PWM_ADDR (in this example) is 0x02) – TyrantUT Sep 09 '19 at 19:02
  • Also, would I then have to set bits 5-7 in MODE1 to 1 to enable auto increment? I'm confused on that description. 5 is Where Bit 0 is set to 1, 6 is where bit 1 is set to 1, and 7 is whether register auto-increment is enabled or not. my TLC59116_MODE1_DEFAULT is set to 0x00, OSC on everything else off. Should it be 0x07 (0000 0111) instead? – TyrantUT Sep 09 '19 at 19:10
  • I understand - you had the auto inc bits for all registers. I had them for brightness only. So if I were to set the auto increment bits to 100 (all registers) instead of 101 (brightness registers) then change the LEDOUT state control LDRx bits to full on rather than PWM then, instead of sending a PWM value of 0x00-0xff, I could send 0x01 (on) or 0x00 (off)? – TyrantUT Sep 09 '19 at 20:55
  • Yes, that would be ideal for on/off control. PWM would be used if you want to achieve variable brightness (or dimming) between off and fully on. – abonneville Sep 10 '19 at 01:13

1 Answers1

1

Assuming you have STM32CubeMX generate the correct initialization for your I2C bus, then you can assess the TLC59116 using the polling method you referenced:

HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

Value for setting DevAddress is defined on page 23 of the datasheet, you will have to review your schematic and set bit 0 to 0 when writing a register. Assuming A[3:0] = 0x0 on the board, DevAddress would become:

  • 0xC0 for writing (writeAddr) to TLC59116
  • 0xC1 for reading (readAddr) from TLC59116

Next, you need to setup the payload which contains the register address and register value to be set. For example (see page 18 of the datasheet):

uint8_t aTxBuffer[2] = {};
aTxBuffer[0] = 0x14; /* LEDOUT0 register */
aTxBuffer[1] = 0x05; /* Turn on LED 0 and LED 1 */

HAL_I2C_Master_Transmit(hi2c1, writeAddr, aTxBuffer, sizeof(aTxBuffer), 100);

However, be sure to review MODE1 register before implementing the above. You need to clear the OSC bit or you will never light an LED.

uint8_t aTxBuffer[2] = {};
aTxBuffer[0] = 0x00; /* MODE1 register */
aTxBuffer[1] = 0x00; /* Example only, review each bit */

HAL_I2C_Master_Transmit(hi2c1, writeAddr, aTxBuffer, sizeof(aTxBuffer), 100);

The above illustrates writing to a single register, to stream data into multiple register locations:

void TLC59116_WriteStream(uint8_t len, uint8_t *pData)
{
    if (len == 0)
    {
        ErrorHandler();
    }
    HAL_I2C_Master_Transmit(&h12c1, TLC59116_ADDR, pData, len, 100);
}

Where TLC59116_ADDR = 0xC0 and pData points to the base address of your message to be sent to TI device.

To set all 16 PWM registers to a 0x00 or off:

uint8_t aTXBuffer[] = { TLC59116_PWM0_AUTOINCR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
TLC59116_WriteStream(sizeof(aTXBuffer), aTXBuffer);

Where TLC59116_PWM0_AUTOINCR = 0x82;

abonneville
  • 334
  • 2
  • 5
  • Awesome! Thank you. I have been considering scrapping HAL and using LL instead. This is what I came up with in LL, in main post. – TyrantUT Sep 05 '19 at 20:28
  • Hi @TyrantUT if this or any answer has solved your question please consider [accepting it](https://meta.stackexchange.com/q/5234/179419) by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this. – abonneville Sep 20 '19 at 15:25