I spent the last few days doing my first real programming for the Milk-V Duo. I have one of Sparkfun's very nice LCD-16397 16x2 SerLCD - RGB Text displays, and wanted to get it working on the Milk-V Duo.

So I grabbed some PIC code for the display that I wrote a while back and ported it. Basically I used the skeleton and rewrote everything - the MilkV APIs were very different from my original code. When I finished finding all the bugs, it would display things on the LCD, but it was glitchy and the display reset itself often. It seemed like maybe an i2c speed problem - as in the MilkV maybe sending too fast for the display to keep up. AFAIK the MilkV does i2c at Full Speed (400000 BPS) which this display should have no problem with (I could be wrong about that).

First thing I thought of, because of the display resets, was that I was powering the display off the 3V3(OUT) pin, and that might not be able to supply enough power, causing brownouts and resets. I added another 3V power supply just for the display, but it made no difference.

I added delays all through my code to at least slow that down some. That improved things a little - the display stopped resetting itself, but the glitchiness and printing in wrong locations continued, even as I increased the delays until it was printing very slowly.

I watched the output with my old Saleae Logic logic analyzer, but could find no reason for the errors.

I tried debugging my code by putting printf's in strategic locations and watching what it was sending as the LCD glitched. My code was working correctly, sending the correct numbers even as the LCD glitched. As I said, it acts like a speed problem, or maybe a library code bug.

So I was disgusted with that and decided to grab some bit-bang i2c code I had written for PIC and this display and ported that over. That took quite a while and would not work for the longest time as I searched for the problem. It turned out to be a silly mistake with the bus address and the write bit. The rest of the code was fine. Once I fixed that the code drove the display flawlessly.

Here's the C code. The i2c_read and i2c_read_nack functions are untested. They may be buggy or not work at all. The rest works pretty well, though I haven't done a ton of testing yet. Shred() could probably be much better - it's a quick and dirty solution that works fine for what I'm doing for now.

This code does the usual way of handling i2c data/clock pins, which is not to directly set the pins high or low, but to set both pins low, add pullup resistors on them, and set the pins high by making them inputs (releasing them) and set them low by making them outputs. You have to break from that method in one place to read the ack/nack. Pretty simple stuff once you see what's going on.

Found out later that the display board has pullups onboard. You can see in at least some of the pics that my resistors are still there, but they aren't connected to anything.

i2c_bitbang_display.c

/* Demo for Milk-V Duo with Sparkfun LCD-16397

  Duo pin 16    -> SDA on LCD
  Duo pin 17    -> SCL on LCD
  3.3v (pin 36) -> VCC on LCD
  GND (pin 38)  -> GND on LCD
*/

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <wiringx.h>
#include "i2c_bitbang_display.h"

// Sparkfun display default bus address
unsigned char i2c_address = 0x72;

int dat = 16;
int clk = 17;
int dely = 10;
int delyhalf = 5;
char string[] = "                ";

int main() {
  unsigned char red = 255;
  unsigned char green = 255;
  unsigned char blue = 0;
  int i;

  if(wiringXSetup("duo", NULL) == -1) {
    wiringXGC();
    return -1;
  }

  // shift address over to high bits and slide in write bit at 0
  i2c_address = i2c_address << 1;

  pinMode(dat,PINMODE_OUTPUT);
  pinMode(clk,PINMODE_OUTPUT);
  digitalWrite(dat, LOW);       //dat == 0 when set as output
  digitalWrite(clk, LOW);       //clk == 0 when set as output
  pinMode(dat,PINMODE_INPUT);   //dat pin high
  pinMode(clk,PINMODE_INPUT);   //clk pin high

  sleep(2);                 // wait for lcd stabilize

  i2c_start();
  i2c_write(0x7c);          //lcd escape code
  i2c_write(0x16);          //set lcd i2c 1000000 bps baud rate
  i2c_write(0x7c);          //lcd escape code
  i2c_write(0x8d);          //primary backlight brightness 44%
  rgb(red,green,blue);      //text color
  i2c_write(0x7c);          //lcd escape code
  i2c_write(0x2d);          //cls
  line1();                  //line1
  lcd_string("Milk-V Duo");
  i2c_stop();

  while(1){
    for(i=0;i<32767;i++){
      i2c_start();
      line2();
      lcd_string("Count: ");
      shred(i);
      lcd_string(string);
      i2c_stop();
    }
    i2c_start();
    line2();
    lcd_string("                ");
    i2c_stop();
  }
}

// shred() converts a 1 to 5 digit int into a 5 digit ascii string
// no leading zero suppression - add later maybe
void shred(int i)
{
  unsigned char temp;
  temp = i/10000;
  string[0] = temp + 0x30;
  i = i - (temp * 10000);

  temp = i/1000;
  string[1] = temp + 0x30;
  i = i - (temp * 1000);

  temp = i/100;
  string[2] = temp + 0x30;
  i = i - (temp * 100);

  temp = i/10;
  string[3] = temp + 0x30;
  i = i - (temp * 10);

  string[4] = i + 0x30;

  string[5] = '\0';
}

//****************************************************
//* Sparkfun LCD-16397 SerLCD RGB Text LCD functions *
//****************************************************
void lcd_string(char *senpoint)
{
  while(*senpoint != '\0')
  {
    i2c_write(*senpoint);
    senpoint++;
  }
}

void line1(void)
{
  i2c_write(0xfe);
  i2c_write(0x80);
}

void line2(void)
{
  i2c_write(0xfe);
  i2c_write(0xc0);
}

void cls(void)
{
  i2c_write(0x7c);
  i2c_write(0x2d);
}

void rgb(unsigned char red,unsigned char green,unsigned char blue)
{
  i2c_write(0x7c);
  i2c_write(0x2b);
  i2c_write(red);
  i2c_write(green);
  i2c_write(blue);
}

//*************************
//* bitbang I2C functions *
//*************************
unsigned char i2c_write(unsigned char x){
  unsigned char i;
  usleep(dely);
  for(i=0;i<8;i++){               //clock out data byte
    pinMode(dat,PINMODE_OUTPUT);  //set data bit low
    if(x &  0x80)                 //if output bit is high
      pinMode(dat,PINMODE_INPUT); //then set data bit high
    i2c_clock();                  //clock it out
    x <<= 1;                      //shift next bit into position
  }
  //get ack
  pinMode(dat,PINMODE_INPUT);     //set data high
  pinMode(clk,PINMODE_INPUT);     //set clock high
  usleep(delyhalf);               //wait half a clock pulse
  if(digitalRead(dat))            //sample the data bit
    return(1);                    //if high then nack error
  usleep(delyhalf);               //ack good, wait other half of clock pulse
  pinMode(clk,PINMODE_OUTPUT);    //set clock low
  usleep(dely);
  pinMode(dat,PINMODE_INPUT);     //set data high
  return(0);
}

void i2c_start(void){             //send start condition
  pinMode(dat,PINMODE_OUTPUT);    //set data low
  usleep(dely);
  pinMode(clk,PINMODE_OUTPUT);    //set clock low
  usleep(dely);
  i2c_write(i2c_address);
}

void i2c_stop(void){              //send stop condition
  pinMode(dat,PINMODE_OUTPUT);    //set data low
  usleep(dely);
  pinMode(clk,PINMODE_INPUT);    //set clock high
  usleep(dely);                   //stop delay
  pinMode(dat,PINMODE_INPUT);     //set data high
  usleep(dely);
}

void i2c_clock(void){
  pinMode(clk,PINMODE_INPUT);     //set clock high
  usleep(dely);
  pinMode(clk,PINMODE_OUTPUT);    //set clock low
  usleep(dely);
}

void i2c_restart(void){           //send start condition
  pinMode(dat,PINMODE_INPUT);     //release data
  usleep(dely);
  pinMode(clk,PINMODE_INPUT);
  usleep(dely);
  pinMode(dat,PINMODE_OUTPUT);
  usleep(dely);
  pinMode(clk,PINMODE_OUTPUT);
  usleep(dely);
}

unsigned char i2c_read(void){
  unsigned char i,tmp = 0;
  static unsigned char mbit = 0;
  usleep(dely);                   //10uS delay
  for(i=0;i<8;i++){
    pinMode(dat,PINMODE_INPUT);   //data pin high
    usleep(dely);                 //minimum clock low time
    pinMode(clk,PINMODE_INPUT);   //clk high
    usleep(delyhalf);             //1/2 min clock high time
    mbit = digitalRead(dat);      //read the data bit
    if(mbit)                      //store it in tmp
      tmp = tmp | 0x01;
    usleep(delyhalf);             //last 1/2 min clock high time
    if(i < 7)
      tmp <<= 1;                  //shift left for next bit
    pinMode(clk,PINMODE_OUTPUT);  //clk low
    usleep(dely);                 //minimum clock low time
  }
  pinMode(dat,PINMODE_OUTPUT);
  digitalWrite(dat, 0);           //clear data & send ACK
  usleep(dely);                   //data settle time
  i2c_clock();                    //pulse the clock
  pinMode(dat,PINMODE_INPUT);     //release ACK
  usleep(dely);                   //gap between next byte
  return(tmp);
}

unsigned char i2c_read_nack(void){
  unsigned char i,tmp = 0;
  static unsigned char mbit;
  usleep(dely);
  for(i=0;i<8;i++){               
    pinMode(dat,PINMODE_INPUT);   //data high
    usleep(dely);                 //minimum clock low time
    pinMode(clk,PINMODE_INPUT);   //clk high
    usleep(delyhalf);             //1/2 min clock high time
    mbit = digitalRead(dat);      //read the data bit
    if(mbit)                      //store it in tmp
      tmp = tmp | 0x01;
    usleep(delyhalf);             //last 1/2 min clock high time
    if(i < 7)
      tmp <<= 1;                  //shift left for next bit
    pinMode(dat,PINMODE_OUTPUT);  //clk low
    usleep(dely);                 //minimum clock low time
  }
  pinMode(dat,PINMODE_OUTPUT);
  digitalWrite(dat,LOW);          //clear data
  pinMode(dat,PINMODE_INPUT);     //send NACK
  usleep(dely);                   //data settle time
  i2c_clock();                    //pulse the clock
  return(tmp);
}

i2c_bitbang_display.h

void lcd_string(char *);
void line1(void);
void line2(void);
void cls(void);
void rgb(unsigned char,unsigned char,unsigned char);
void shred(int);
unsigned char i2c_read(void);
unsigned char i2c_read_nack(void);
unsigned char i2c_write(unsigned char);
void i2c_start(void);
void i2c_restart(void);
void i2c_stop(void);
void i2c_clock(void);

Next Post Previous Post