After getting my clock code to the stage it was at in Part 2, I started to refine the code and add features. That code was pretty quick and dirty. It worked, but still needed lots of work.
I started looking at actual i2c timings. I had built my clock code by just picking a random number (100uS) that would be very conservative and probably work. It did work, but was very slow, so I looked up actual minimums - around 4 to 6uS! Quite a far cry from the 100uS I was using. So I changed to 20uS. Much faster, and some room for still faster speeds later. Works well.
I wrote an ISR and set up Timer0 for a 122Hz interrupt to check for button presses on RA0 and RA1. Each has a counter variable that gets incremented each time through the ISR that the button is still pressed, and after 15 good readings a button good flag gets set. Pretty good way to debounce buttons. Needs a bit of fine tuning still, but the code works acceptably for now.
I'm still thinking of cutting down Timer0's frequency. Interrupts at 122Hz is more often than necessary. I could probably cut that in half and it would still work well.
The same timer also handles timing for turning off the display backlight after some seconds. In the code below the variable 'backlight_count' counts up to 2500 interrupts for about a 20 second delay (for testing). Later I'll increase that to 3660 for a 30 second delay. Pressing button1 resets that counter to zero, turning on the display backlight until the counter finishes again.
Then I looked at my crappy quick and dirty way of reading the RTC and refreshing the LCD screen every time through my main loop. I was refreshing far too often, so I took the 1Hz squarewave output from pin 3 of the DS3231, added a 4.7K pullup resistor to it and connected it to pin RB0 of the MCU. I set up interrupt-on-change for RB0 and enabled the interrupt for that. So now once a second that signal goes high and an interrupt is triggered. The code in the ISR sets the refresh flag, causing the main loop code to call the display refresh function. Once per second is perfect for a clock, obviously.
I added time setting code, so I don't have to reprogram the chip to change the time. The code is a bit primitive and needs fine tuning still, but works acceptably for now.
I'm going to be changing to a 20x4 LCD soon. That will allow watering time and duration to be displayed without changing screens.
Lots of work to do still. I'm pretty lazy about getting it done, but I sit down and work at it often enough that it'll eventually get somewhere.
lcd-i2c-rtc.c
NOTE: Do not use this code. Yes, it works (barely). But I've found a lot of problems in it as I expanded its functionality, and a lot of it has been rewritten. I spent an entire day chasing some crazy strangeness in my code, but instead found what I think is a serious bug in sprintf that was causing the strangeness, not to mention that sprintf is a horribly bloated piece of code. I've removed all sprintf calls and went a different way. That made program file size a lot smaller. Stay tuned for newer, better versions.
(Scroll down for .h file)
#include "lcd-i2c-rtc.h"
#define dat TRISCbits.TRISC4
#define clk TRISCbits.TRISC3
unsigned char sec,sec10,min,min10,hour,hour10,ampm,dayofweek,date1,date10,mon,mon10,year1,year10;
unsigned char seconds,minutes,hours,date,month,year;
char string[16];
unsigned char backlight = 0x08; //backlight flag (bit3) enabled
int backlight_count = 0;
unsigned char button0_count = 0; //button0 debounce counter
__bit button0_good = 0; //button0 verified press flag
unsigned char button1_count = 0; //button1 debounce counter
__bit button1_good = 0; //button1 verified press flag
__bit refresh = 0; //display refresh flag
void __interrupt() bleh()
{
if(PIR3bits.TMR0IF && PIE3bits.TMR0IE){ //button0 pressed
if(PORTAbits.RA0 == 0){ //if button0 pressed
button0_count++; //then increment button0 debounce counter
if(button0_count > 15) //if 15 good button0 lows
button0_good = 1; //then set button0 good press flag
}
if(PORTAbits.RA1 == 0){ //if button1 pressed
button1_count++; //then increment button1 debounce counter
if(button1_count > 15) //if 15 good button1 lows
button1_good = 1; //then set button1 good press flag
}
PIR3bits.TMR0IF = 0; //clear timer0 interrupt flag
}
if(IOCBFbits.IOCBF0 && PIE0bits.IOCIE){ //1Hz rising edge from RTC
refresh = 1; //set display refresh flag
IOCBFbits.IOCBF0 = 0; //clear interrupt on change flag
}
if(backlight_count < 2500)
backlight_count++;
else{
backlight_count = 0;
backlight = 0x00;
}
}
void main()
{
int count;
unsigned char setblink = 0;
INTCON0bits.IPEN = 0; //priority ints disabled
T0CON1 = 0x41; //timer0 fosc/4, prescale 1:2
T0CON0 = 0x90; //timer0 on, 16 bit, no postscale
PIE3bits.TMR0IE = 1; //TMR0 interrupt enabled
IOCBPbits.IOCBP0 = 1; //interrupt on change for RB0 enabled
PIE0bits.IOCIE = 1; //interrupt on change enabled
TRISA = 0x03; //porta all outs except bit 0 & 1
ANSELA = 0; //porta all digital
WPUA = 0x03; //porta weak pullups enabled on 0 & 1
TRISB = 0x01; //portb R0 is input
ANSELB = 0; //portb all digital
TRISC = 0b00011000; //portc all outs except i2c
LATC = 0; //portc all zeros
ANSELC = 0; //portc all digital
__delay_ms(5);
lcd_init();
INTCON0bits.GIE = 1; //global ints enabled
while(1){
if(refresh){
display_refresh();
refresh = 0;
}
if(button0_good){
setclock();
button0_good = 0;
button0_count = 0;
}
if(button1_good){
backlight_count = 0;
backlight = 0x08;
button1_good = 0;
button1_count = 0;
}
}
}
void display_refresh(){
start(); //read ds3231 rtc
i2c_write(0xd0);
i2c_write(0x00);
restart();
i2c_write(0xd1); //setup for read
seconds = i2c_read();
minutes = i2c_read();
hours = i2c_read();
dayofweek = i2c_read();
date = i2c_read();
month = i2c_read();
year = i2c_read_nack();
stop();
sec = seconds & 0x0f; //prep data
sec10 = (seconds & 0x70) >> 4;
min = minutes & 0x0f;
min10 = (minutes & 0x70) >> 4;
hour = hours & 0x0f;
hour10 = (hours & 0x10) >> 4;
ampm = (hours & 0x20) >> 5;
date1 = date & 0x0f;
date10 = (date & 0x30) >> 4;
mon = month & 0x0f;
mon10 = (month & 0x10) >> 4;
year1 = year & 0x0f;
year10 = (year & 0xf0) >> 4;
start(); //display data
i2c_write(0x4e); //send lcd slave address & write bit
lcd_cmd(0x80); //line 1
sprintf(string,"%01u",hour10);
lcd_string(string);
sprintf(string,"%01u:",hour);
lcd_string(string);
sprintf(string,"%01u",min10);
lcd_string(string);
sprintf(string,"%01u:",min);
lcd_string(string);
sprintf(string,"%01u",sec10);
lcd_string(string);
sprintf(string,"%01u ",sec);
lcd_string(string);
if(ampm)
lcd_string("PM");
else
lcd_string("AM");
lcd_cmd(0xc0); //line 2
sprintf(string,"%01u",mon10);
lcd_string(string);
sprintf(string,"%01u:",mon);
lcd_string(string);
sprintf(string,"%01u",date10);
lcd_string(string);
sprintf(string,"%01u:",date1);
lcd_string(string);
sprintf(string,"20%01u",year10);
lcd_string(string);
sprintf(string,"%01u",year1);
lcd_string(string);
stop();
}
void setclock(){
unsigned char digit = 0;
static __bit blinkflag;
static __bit done;
static __bit other;
blinkflag = 0;
done = 1;
backlight = 0x08; //backlight on
while(done){
start(); //display data
i2c_write(0x4e); //send lcd slave address & write bit
lcd_cmd(0x80); //line 1
if((digit == 1) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",hour10);
lcd_string(string);
}
if((digit == 1) && blinkflag)
lcd_string(" :");
else{
sprintf(string,"%01u:",hour);
lcd_string(string);
}
if((digit == 2) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",min10);
lcd_string(string);
}
if((digit == 2) && blinkflag)
lcd_string(" :");
else{
sprintf(string,"%01u:",min);
lcd_string(string);
}
if((digit == 3) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",sec10);
lcd_string(string);
}
if((digit == 3) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u ",sec);
lcd_string(string);
}
if((digit == 4) && blinkflag)
lcd_string(" ");
else{
if(ampm)
lcd_string("PM");
else
lcd_string("AM");
}
lcd_cmd(0xc0); //line 2
if((digit == 5) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",mon10);
lcd_string(string);
}
if((digit == 5) && blinkflag)
lcd_string(" :");
else{
sprintf(string,"%01u:",mon);
lcd_string(string);
}
if((digit == 6) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",date10);
lcd_string(string);
}
if((digit == 6) && blinkflag)
lcd_string(" :");
else{
sprintf(string,"%01u:",date1);
lcd_string(string);
}
if((digit == 7) && blinkflag)
lcd_string("20 ");
else{
sprintf(string,"20%01u",year10);
lcd_string(string);
}
if((digit == 7) && blinkflag)
lcd_string(" ");
else{
sprintf(string,"%01u",year1);
lcd_string(string);
}
stop();
if(button0_good){
++digit;
button0_count = 0;
button0_good = 0;
}
if(button1_good){
switch(digit){
case 1:
hour++;
if(hour > 9){
hour = 0;
hour10 = 1;
}
if(hour10 == 1 && hour > 2){
hour = 1;
hour10 = 0;
}
break;
case 2:
min++;
if(min > 9){
min = 0;
min10++;
if(min10 > 5)
min10 = 0;
}
if(min10 > 5 && min > 9){
min = 0;
min10 = 0;
}
break;
case 3:
sec = 0;
sec10 = 0;
break;
case 4:
ampm = !ampm;
break;
case 5:
mon++;
if(mon > 9){
mon = 0;
mon10 = 1;
}
if(mon10 == 1 && mon > 2){
mon = 1;
mon10 = 0;
}
break;
case 6:
date1++;
if(date10 == 3 && date1 > 1){
date1 = 1;
date10 = 0;
}
else if(date1 > 9){
date1 = 1;
date10++;
}
break;
case 7:
year1++;
if(year1 > 9){
year1 = 0;
year10++;
if(year10 > 9)
year10 = 0;
}
if(year10 == 9 && year1 > 9){
year1 = 0;
year10 = 0;
}
}
button1_good = 0;
button1_count = 0;
}
if(digit == 8){
__delay_ms(50);
done = 0;
}
if(blinkflag == 0)
blinkflag = 1;
else
blinkflag = 0;
backlight_count = 0;
__delay_ms(200);
}
seconds = sec10; //prep new data for clock setting
seconds <<= 4;
seconds = seconds | sec;
minutes = min10;
minutes <<= 4;
minutes = minutes | min;
hours = hour10; //high digit
hours <<= 4; //shift it into place
digit = ampm; //move am/pm bit into place
digit <<= 5;
hours = hours | 0x40; //12 hour time bit
hours = hours | digit; //am/pm bit
hours = hours | hour; //low digit
month = mon10;
month <<= 4;
month = month | mon;
date = date10;
date <<= 4;
date = date | date1;
year = year10;
year <<= 4;
year = year | year1;
start(); //set the clock
i2c_write(0xd0); //send ID code & write bit
i2c_write(0x00); //register address
i2c_write(seconds); //$00 set seconds
i2c_write(minutes); //$01 set minutes
i2c_write(hours); //$02 set hours
i2c_write(dayofweek); //$03 set day
i2c_write(date); //$04 set date
i2c_write(month); //$05 set month
i2c_write(year); //$06 set year
restart();
i2c_write(0xd0); //ID code & write bit
i2c_write(0x0e); //register address
i2c_write(0x40); //enable 1Hz square wave
stop();
}
//* I2C functions *
//*****************
unsigned char i2c_read(void){
unsigned char i,tmp = 0;
static __bit mbit = 0;
__delay_us(20);
for(i=0;i<8;i++){
dat = 1; //sda high
__delay_us(20); //minimum clock low time
clk = 1; //clk high
__delay_us(10); //1/2 min clock high time
mbit = PORTCbits.RC4; //read the data bit
if(mbit) //store it in tmp
tmp = tmp | 0x01;
__delay_us(10); //last 1/2 min clock high time
if(i < 7)
tmp <<= 1; //shift left for next bit
clk = 0; //lower clock
__delay_us(20); //minimum clock low time
}
PORTC = 0;
dat = 0; //send ACK
__delay_us(20); //data settle time
i2c_clock(); //pulse the clock
dat = 1; //release ACK
__delay_us(20); //gap between next byte
return(tmp);
}
unsigned char i2c_read_nack(void){
unsigned char i,tmp = 0;
static __bit mbit;
__delay_us(20);
for(i=0;i<8;i++){
dat = 1; //SDA = input
__delay_us(20); //minimum clock low time
clk = 1; //release clock
__delay_us(20); //1/2 min clock high time
mbit = PORTCbits.RC4; //read the data bit
if(mbit) //store it in tmp
tmp = tmp | 0x01;
__delay_us(20); //last 1/2 min clock high time
if(i < 7)
tmp <<= 1; //shift left for next bit
clk = 0; //lower clock
__delay_us(20); //minimum clock low time
}
PORTCbits.RC4 = 0;
dat = 1; //send NACK
__delay_us(20); //data settle time
i2c_clock(); //pulse the clock
return(tmp);
}
unsigned char i2c_write(unsigned char x){
unsigned char i;
__delay_us(20);
for(i=0;i<8;i++){
dat = 0; //set data bit low
if(x & 0x80) //if output bit is high
dat = 1; //then set data bit high
i2c_clock(); //clock it out
x <<= 1; //shift next bit into position
}
//get ack
dat = 1; //set data high (input)
PORTCbits.RC4 = 0; //clear data bit
clk = 1; //set clock high
__delay_us(10); //wait half a clock pulse
if(PORTCbits.RC4) //sample data bit
return(1); //nack error
__delay_us(10); //ack good, wait other half of clock pulse
clk = 0; //set clock low
__delay_us(20);
dat = 1; //set data
return(0);
}
void start(void){ //send start condition
dat = 0; //set data low
__delay_us(20);
clk = 0;
__delay_us(20);
}
void restart(void){ //send start condition
dat = 1; //release data
__delay_us(20);
clk = 1;
__delay_us(20);
dat = 0;
__delay_us(20);
clk = 0;
__delay_us(20);
}
void stop(void){ //send stop condition
dat = 0; //set data low
__delay_us(20);
clk = 1; //set clock high
__delay_us(20); //stop delay
dat = 1; //set data high
__delay_us(20);
}
void i2c_clock(void){
clk = 1; //set clock high
__delay_us(20);
clk = 0; //set clock low
__delay_us(20);
}
//* LCD functions *
//*****************
void lcd_cls(void){
start();
lcd_cmd(0x80); //line 1
lcd_string(" ");
lcd_cmd(0xc0); //line 2
lcd_string(" ");
stop();
}
void lcd_string(char *senpoint)
{
while(*senpoint != '\0')
{
lcd_char(*senpoint);
senpoint++;
}
}
void lcd_cmd(unsigned char letter)
{
unsigned char temp;
temp = letter;
temp = temp & 0xf0; //clear lower nybble
temp = temp | 0b00000100; //set E, RW = write, RS = command
temp = temp | backlight; //set backlight on or off
i2c_write(temp); //send upper nybble
temp = temp & 0b11110000; //clear E to latch data and resend
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
temp = letter;
temp = temp & 0x0f; //clear upper nybble
temp = temp << 4; //shift left 4 bits
temp = temp | 0b00000100; //send lower nybble
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
temp = temp & 0b11110000;
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
}
void lcd_char(unsigned char letter)
{
unsigned char temp;
temp = letter;
temp = temp & 0xf0; //clear lower nybble
temp = temp | 0b00000101; //set E, RW = write, RS = data
temp = temp | backlight; //set backlight on or off
i2c_write(temp); //send upper nybble
temp = temp & 0b11110001; //clear E to latch data and resend
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
temp = letter;
temp = temp & 0x0f; //clear upper nybble
temp = temp << 4; //shift left 4 bits
temp = temp | 0b00000101; //send lower nybble
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
temp = temp & 0b11110001;
temp = temp | backlight; //set backlight on or off
i2c_write(temp);
}
void lcd_nybble(unsigned char nyb)
{
nyb = nyb << 4; //shift left 4 bits
nyb = nyb | 0b00001100; //send lower nybble
i2c_write(nyb);
nyb = nyb & 0b11111000;
i2c_write(nyb);
}
void lcd_init(void)
{
start();
i2c_write(0x4e); //send lcd slave address & write bit
__delay_ms(50);
lcd_nybble(0x03);
__delay_ms(5);
lcd_nybble(0x03);
__delay_us(160);
lcd_nybble(0x03);
__delay_us(160);
lcd_nybble(0x02);
__delay_us(160);
lcd_cmd(0x28); //set 4-bit mode and 2 lines
__delay_us(160);
lcd_cmd(0x10); //cursor move & shift left
__delay_us(160);
lcd_cmd(0x06); //entry mode = increment
__delay_us(160);
lcd_cmd(0b00001100); //display on - cursor & blink off
__delay_us(160);
lcd_cmd(0x01); //clear display
__delay_us(160);
stop();
}
void blink(void){
for(int x=0;x<10;x++){
PORTAbits.RA0 = 0;
__delay_ms(35);
PORTAbits.RA0 = 1; //ra0 high
__delay_ms(20);
}
PORTAbits.RA0 = 0; //turn LED off at end
}
lcd-i2c-rtc.h
// CONFIG1L
#pragma config FEXTOSC = ECH // External Oscillator Selection (EC (external clock) above 8 MHz; PFM set to high power)
#pragma config RSTOSC = HFINTOSC_64MHZ// Reset Oscillator Selection (HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1)
// CONFIG1H
#pragma config CLKOUTEN = OFF // Clock out Enable bit (CLKOUT function is disabled)
#pragma config PR1WAY = ON // PRLOCKED One-Way Set Enable bit (PRLOCK bit can be cleared and set only once)
#pragma config CSWEN = ON // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor enabled)
// CONFIG2L
#pragma config MCLRE = EXTMCLR // MCLR Enable bit (If LVP = 0, MCLR pin is MCLR; If LVP = 1, RE3 pin function is MCLR )
#pragma config PWRTS = PWRT_OFF // Power-up timer selection bits (PWRT is disabled)
#pragma config MVECEN = OFF // Multi-vector enable bit (Multi-vector disabled, Vector table not used for interrupts)
#pragma config IVT1WAY = ON // IVTLOCK bit One-way set enable bit (IVTLOCK bit can be cleared and set only once)
#pragma config LPBOREN = OFF // Low Power BOR Enable bit (ULPBOR disabled)
#pragma config BOREN = SBORDIS // Brown-out Reset Enable bits (Brown-out Reset enabled , SBOREN bit is ignored)
// CONFIG2H
#pragma config BORV = VBOR_2P45 // Brown-out Reset Voltage Selection bits (Brown-out Reset Voltage (VBOR) set to 2.45V)
#pragma config ZCD = OFF // ZCD Disable bit (ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON)
#pragma config PPS1WAY = ON // PPSLOCK bit One-Way Set Enable bit (PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config DEBUG = OFF // Debugger Enable bit (Background debugger disabled)
#pragma config XINST = OFF // Extended Instruction Set Enable bit (Extended Instruction Set and Indexed Addressing Mode disabled)
// CONFIG3L
#pragma config WDTCPS = WDTCPS_31// WDT Period selection bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = OFF // WDT operating mode (WDT Disabled; SWDTEN is ignored)
// CONFIG3H
#pragma config WDTCWS = WDTCWS_7// WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC // WDT input clock selector (Software Control)
// CONFIG4L
#pragma config BBSIZE = BBSIZE_512// Boot Block Size selection bits (Boot Block size is 512 words)
#pragma config BBEN = OFF // Boot Block enable bit (Boot block disabled)
#pragma config SAFEN = OFF // Storage Area Flash enable bit (SAF disabled)
#pragma config WRTAPP = OFF // Application Block write protection bit (Application Block not write protected)
// CONFIG4H
#pragma config WRTB = OFF // Configuration Register Write Protection bit (Configuration registers (300000-30000Bh) not write-protected)
#pragma config WRTC = OFF // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF // Data EEPROM Write Protection bit (Data EEPROM not write-protected)
#pragma config WRTSAF = OFF // SAF Write protection bit (SAF not Write Protected)
#pragma config LVP = OFF // Low Voltage Programming Enable bit (HV on MCLR/VPP must be used for programming)
// CONFIG5L
#pragma config CP = OFF // PFM and Data EEPROM Code Protection bit (PFM and Data EEPROM code protection disabled)
// CONFIG5H
//-------------------------------------------
#include <xc.h>
#include <stdio.h>
#define _XTAL_FREQ 64000000
void __interrupt() bleh(void);
void start(void);
void restart(void);
void stop(void);
void ack(void);
void nack(void);
void i2c_clock(void);
unsigned char i2c_write(unsigned char);
unsigned char i2c_read(void);
unsigned char i2c_read_nack(void);
void display_refresh(void);
void setclock(void);
void lcd_cls(void);
void lcd_cmd(unsigned char);
void lcd_char(unsigned char);
void lcd_init(void);
void lcd_string(char *);
void lcd_nybble(unsigned char);void setclock(void);