Skip navigation

Embedded

2 Posts authored by: Jan Cumps Top Member

Our Raspberry Pi friends will know the Building a Low Power Compact LCD Display project that shabaz published a while ago. He designed a £5 LCD module and the source code for Raspberry Pi.

Let's have a look at the I2C traffic.

 

 

What data is flowing according to the code?

Our code tries to send 9 bytes.

  • 1:           the 7-bit I2C address + read/write bit
  • 2 - 6:     config and commands
  • 7 - 9:     data for the segments and dots

 

#define LCD_ADDR 0x38
// ...

// Commands (these can be 'continued' by setting the MSB
#define DEVICE_ADDR 0x62
#define BLINK_OFF 0x70
#define BANK_CMD 0x78


// BANK_CMD bit values
#define WR_BANK_A 0x00
#define WR_BANK_B 0x02
#define DISP_BANK_A 0x00
#define DISP_BANK_B 0x01

// ...

 unsigned char mode, device, bank, blinkmode;


  mode=0xc9; // Set static mode, display enabled, continuation enabled
  device=DEVICE_ADDR | 0x80; // Select the device, continuation enabled
  bank=BANK_CMD | 0x80 | WR_BANK_A | DISP_BANK_A; 
  blinkmode=BLINK_OFF | 0x80;
  buf[0]=mode;
  buf[1]=device;
  buf[2]=blinkmode;
  buf[3]=bank;
  buf[4]=0x00; // pointer
  buf[5]=progval->byte0;
  buf[6]=progval->byte1;
  buf[7]=progval->byte2;

 

What's really flowing to the LCD Controller

These two captures are the result of sending ' . . ' and '  0.0' to the display:

blankdotblankdotblank.png

blanknodotzerodotzero.png

Everything looks as expected. On the odd lines from 3 to 17 we find exactly what's defined in the code.

Line 3 in the screen captures, with value 0xc9, is exactly what's assigned to buf[0]in the code.

  mode=0xc9; // Set static mode, display enabled, continuation enabled
  //...
  buf[0]=mode;

 

The only thing that may need some more clarification is the I2C address, on line 1.

We've set it to 0x38, but in the transfers there is 0x70.

That's part of the I2C protocol.

The address isn't 8 bit, but 7 bit.

That 7 bit value is appended with a read/write bit. 1 if we're reading, 0 if we're writing.

Because we're writing to the LCD, we append the address with a 0.

The value 0x38 left shifted and with the last bit set to 0 equals 0x70.

 

address.png

Our Raspberry Pi friends will know the Building a Low Power Compact LCD Display project that shabaz published a while ago. He designed a £5 LCD module and the source code for Raspberry Pi.

 

I have ported that code to the Texas Instruments Hercules family. The hardware is exactly the same as in the original article.

lcd-complete-cc-r.jpg

Take care to read shabaz' blog first. It's a good read and this blog builds upon the information.

 

Porting from Pi to Hercules

The Pi code is linux based. It uses the linux i2c file device to talk to the LCD driver.

Our Hercules isn't a linux controller. Our work will be to port from the file type i2c access to a hardware driver based API.

 

The API is documented in the HALCoGen configuration program.

That help set also has an example that we can use to understand how a typical i2c conversation looks like: example_i2cMaster_TX_RX.

The first part of that example is a Master Send scenario with a loop. Exactly the same as what shabaz uses in his Pi test.

That means we don't have to find out what API functions to call when. We can just replace the each linux-based set of calls in the Pi code with the ones from the example.

 

I have only ported lcd3-test.c.

Shabaz uses i2c abstraction utility functions in his design. HALCoGen already has an abstraction API, so I'm using that.

You will see that the changes are very few.

 

header declarations:

 

Pi

#include <stdio.h>
#include "i2cfunc.h"

Hercules

#include <stdio.h>
#include "i2c.h"

 

constants and variables:

 

Pi

// we will use I2C2 which is enumerated as 1 on the BBB
#define I2CBUS 1

// set to 1 to print out the intermediate calculations
#define DBG_PRINT 0
// set to 1 to use the example values in the datasheet
// rather than the values retrieved via i2c
#define ALG_TEST 0

#define LCD_ADDR 0x38
// Four possible values for DEVICE_ADDRESS
// R1   R2   R3   R4   DEVICE_ADDRESS
// Fit       FIT       0x60
//      Fit  Fit       0x61
// Fit            Fit  0x62
//      Fit       Fit  0x63

// Commands (these can be 'continued' by setting the MSB
#define DEVICE_ADDR 0x60
#define BLINK_OFF 0x70
#define BANK_CMD 0x78

// BANK_CMD bit values
#define WR_BANK_A 0x00
#define WR_BANK_B 0x02
#define DISP_BANK_A 0x00
#define DISP_BANK_B 0x01

typedef struct char_prog_s
{
  unsigned char byte0;
  unsigned char byte1;
  unsigned char byte2;
} char_prog_t;

typedef struct ascii_s
{
  char_prog_t char0;
  char_prog_t char1;
  char_prog_t char2;
} ascii_t;

const char bit_table[]=
{
  0x38, 0x00, 0x86,    0x00, 0xbe, 0x00,    0x86, 0x00, 0x38,    /* 0 */
  0x08, 0x00, 0x02,    0x00, 0x0a, 0x00,    0x02, 0x00, 0x08,    /* 1 */
  0x30, 0x00, 0x46,    0x00, 0x76, 0x00,    0x46, 0x00, 0x30,    /* 2 */
  0x18, 0x00, 0x46,    0x00, 0x5e, 0x00,    0x46, 0x00, 0x18,    /* 3 */
  0x08, 0x00, 0xc2,    0x00, 0xca, 0x00,    0xc2, 0x00, 0x08,    /* 4 */
  0x18, 0x00, 0xc4,    0x00, 0xdc, 0x00,    0xc4, 0x00, 0x18,    /* 5 */
  0x38, 0x00, 0xc4,    0x00, 0xfc, 0x00,    0xc4, 0x00, 0x38,    /* 6 */
  0x08, 0x00, 0x06,    0x00, 0x0e, 0x00,    0x06, 0x00, 0x08,    /* 7 */
  0x38, 0x00, 0xc6,    0x00, 0xfe, 0x00,    0xc6, 0x00, 0x38,    /* 8 */
  0x18, 0x00, 0xc6,    0x00, 0xde, 0x00,    0xc6, 0x00, 0x18     /* 9 */
};

unsigned char buf[10];
int handle;

 

Hercules

// set to 1 to use the example values in the datasheet
// rather than the values retrieved via i2c
#define ALG_TEST 0


#define LCD_ADDR 0x38
// Four possible values for DEVICE_ADDRESS
// R1   R2   R3   R4   DEVICE_ADDRESS
// Fit       FIT       0x60
//      Fit  Fit       0x61
// Fit            Fit  0x62
//      Fit       Fit  0x63

// Commands (these can be 'continued' by setting the MSB
// jc20161120 R1 and R4 mounted on my board
#define DEVICE_ADDR 0x62
#define BLINK_OFF 0x70
#define BANK_CMD 0x78

// BANK_CMD bit values
#define WR_BANK_A 0x00
#define WR_BANK_B 0x02
#define DISP_BANK_A 0x00
#define DISP_BANK_B 0x01

typedef struct char_prog_s {
  unsigned char byte0;
  unsigned char byte1;
  unsigned char byte2;
} char_prog_t;

typedef struct ascii_s {
  char_prog_t char0;
  char_prog_t char1;
  char_prog_t char2;
} ascii_t;

const char bit_table[] = { 0x38, 0x00, 0x86, 0x00, 0xbe, 0x00, 0x86, 0x00, 0x38, //0
    0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x02, 0x00, 0x08,     //1
    0x30, 0x00, 0x46, 0x00, 0x76, 0x00, 0x46, 0x00, 0x30,     //2
    0x18, 0x00, 0x46, 0x00, 0x5e, 0x00, 0x46, 0x00, 0x18,     //3
    0x08, 0x00, 0xc2, 0x00, 0xca, 0x00, 0xc2, 0x00, 0x08,     //4
    0x18, 0x00, 0xc4, 0x00, 0xdc, 0x00, 0xc4, 0x00, 0x18,     //5
    0x38, 0x00, 0xc4, 0x00, 0xfc, 0x00, 0xc4, 0x00, 0x38,     //6
    0x08, 0x00, 0x06, 0x00, 0x0e, 0x00, 0x06, 0x00, 0x08,     //7
    0x38, 0x00, 0xc6, 0x00, 0xfe, 0x00, 0xc6, 0x00, 0x38,     //8
    0x18, 0x00, 0xc6, 0x00, 0xde, 0x00, 0xc6, 0x00, 0x18      //9
    };

unsigned char buf[10];

 

The char_prog() function is not changed at all, so I'm skipping that.

 

print_line():

 

Pi

void
print_line(char* line)
{
  // ...
  buf[5]=byte0;
  buf[6]=byte1;
  buf[7]=byte2;
  i2c_write(handle, buf, 8);
}

 

Hercules

void print_line(char* line) {
  // ...
  int delay;
  // ...
  buf[5] = byte0;
  buf[6] = byte1;
  buf[7] = byte2;

  /* Configure Data count */
  i2cSetCount(i2cREG1, 8); // buffer values
  /* Set mode as Master */
  i2cSetMode(i2cREG1, I2C_MASTER);

  /* Set Stop after programmed Count */
  i2cSetStop(i2cREG1);

  /* Transmit Start Condition */
  i2cSetStart(i2cREG1);
  /* Tranmit DATA_COUNT number of data in Polling mode */
  i2cSend(i2cREG1, 8, buf);

  /* Wait until Bus Busy is cleared */
  while (i2cIsBusBusy(i2cREG1) == true)
    ;

  /* Wait until Stop is detected */
  while (i2cIsStopDetected(i2cREG1) == 0)
    ;

  /* Clear the Stop condition */
  i2cClearSCD(i2cREG1);

  for(delay=0;delay<1000000;delay++); // todo: I will have to check in the specs how long I have to wait before I can submit the next conversation.
}

 

The part that shabaz has put in the main() function, I've extracted into a function called lcd3_test().

This because the main() in a non-linux environment has a different nature than bare metal, and I wanted to keep the flow as close to shabaz' as possible.

 

Pi

int
main(void)
{
  int i;
  double v;
  unsigned char text[4];
  char_prog_t *progval;
  unsigned char mode, device, bank, blinkmode;

  mode=0xc9; // Set static mode, display enabled, continuation enabled
  device=DEVICE_ADDR | 0x80; // Select the device, continuation enabled
  bank=BANK_CMD | 0x80 | WR_BANK_A | DISP_BANK_A; 
  blinkmode=BLINK_OFF | 0x80;
  buf[0]=mode;
  buf[1]=device;
  buf[2]=blinkmode;
  buf[3]=bank;
  buf[4]=0x00; // pointer

  handle=i2c_open(I2CBUS, 0x38);

  buf[5]=0xf1; // data
  buf[6]=0x80; // data
  buf[7]=0x02; // data

  progval=char_prog(0, '5');
  buf[5]=progval->byte0;
  buf[6]=progval->byte1;
  buf[7]=progval->byte2;

  print_line(" . .");
  for (v=0.0; v<99.9; v=v+0.1)
  {
    sprintf(text, "%4.1f", v);
    print_line(text);
    delay_ms(40);
  }
  delay_ms(500);
  print_line("   ");

  i2c_close(handle);
  
  return(0);
}

 

Hercules

int lcd3_test(void) {

  double v;
  char text[4];
  char_prog_t *progval;
  unsigned char mode, device, bank, blinkmode;

  i2cInit();

  mode = 0xc9; // Set static mode, display enabled, continuation enabled
  device = DEVICE_ADDR | 0x80; // Select the device, continuation enabled
  bank = BANK_CMD | 0x80 | WR_BANK_A | DISP_BANK_A;
  blinkmode = BLINK_OFF | 0x80;
  buf[0] = mode;
  buf[1] = device;
  buf[2] = blinkmode;
  buf[3] = bank;
  buf[4] = 0x00; // pointer

  buf[5] = 0xf1; // data
  buf[6] = 0x80; // data
  buf[7] = 0x02; // data

  progval = char_prog(0, '5');
  buf[5] = progval->byte0;
  buf[6] = progval->byte1;
  buf[7] = progval->byte2;

  /* Configure address of Slave to talk to */
  i2cSetSlaveAdd(i2cREG1, LCD_ADDR);

  /* Set direction to Transmitter */
  /* Note: Optional - It is done in Init */
  i2cSetDirection(i2cREG1, I2C_TRANSMITTER);

  print_line(" . .");

  for (v = 0.0; v < 99.9; v = v + 0.1) {
    sprintf(text, "%4.1f", v);
    print_line(text);
  }

  print_line("   ");

  return (0);
}

 

and for completeness, the Hercules main() function:

int main(void)
{
  lcd3_test();
  while(1) {
  }
  return 0;
}

 

Configure I2C

True to the Hercules way of working, we set up the i2c driver in HALCoGen.

  • enable the driver

  • set multiplexing to enable i2c functionality

 

  • set the parameters

Connections

We need 4 connections: ground, 3V3, SDA and SCL.

The location of these pins on the LaunchPad depends on witch one you have. Check the schematic file for the right pins.

I'm using an RM46 here.

 

 

For that board, all available pins are on Proto Board Header 11

 

signalLCD boardLaunchXL2-RM46
GND1J11-2
I2C SDA2J11-9
I2C SCL3J11-8
3V34 and 5J11-3

 

 

If you prefer, you can also use BoosterPack position 1 for the connections:

 

CCS project file is attached to this blog. It contains all sources and the HALCoGen configuration for the RM46.

This is an easy-to-replicate exercise. Both for hardware and software.

Spend £5 and check shabaz' instructions for the components, PCB design and build steps.

Then give it a go on your Pi and Hercules.

Have fun!

Filter Blog

By date: By tag: