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!

 

Related Blog
Building a Low Power Compact LCD Display - Port to Hercules Safety Microcontroller
Building a Low Power Compact LCD Display - I2C Traffic
Building a Low Power Compact LCD Display - Port to SimpleLink TI-RTOS