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);