After not looking at electronics for quite a while, and PICs for even longer, I started a project with the PIC16F72 board and soon found out why those savage old chips were being sold from China for cheap. I ran out of memory right quick. Could have probably done it if I switched to assembly language, but I'm lazy. It's easier to work in C.

So I dug through some boxes here and found some PIC18F27K42 chips. They're pin compatible with the old 72 and are a very modern MCU. The 18F27K42 has 64 times as much memory as the old chip and corresponding increases in peripherals and speed (64MHz internal clock).

I removed the chip from the board and reflowed the new one on with my Yaogong 898D hot air rework station. Bought that thing some years back and had not used it on a project yet. It worked great! Way better than expected for the price.

Then I removed the crystal & caps and rewired most of the back of the board to move PortB and PortC over two pins to make room for PortA RA6 and RA7 pins, which used to be the crystal input on the 16F72.

All went well, and I set up to blink some LEDs and make sure things worked properly. Oh! Nice! An 833 page datasheet to dig through! The 18F27K42 is a pretty serious MCU - lots to learn.

And that's where things went... not so great. My much loved old Sourceboost BoostC compiler would not integrate with MPLAB X anymore, and it turned out that Sourceboost seems to be out of business. No support for modern PICs.

Ok then... Upgrade to the latest v5.35 MPLAB X and install the Microchip XC8 compiler. Another 455 page manual to dig through, and have to learn tons of new ways of doing things. XC8 is very picky about many things that BoostC didn't care about, so porting often involves "fixing" what was perfectly good working code. Changing compilers is a bit of a chore.

But now I can stop using crappy MS Windows and move to nice comfy Linux. Woohoo! I was only running the clunky thing because the BoostC compiler would not run in Linux. MPLABX and XC8 both run good in Linux. Thank you Microchip! So nice! I don't like Microsoft software.

I ported a couple of mid-size (for me) programs to XC8 for practice. It was pretty daunting at first, with long lists of errors to grind through, but I'd fix one error and change all the other instances of that error in the code and huge swathes of errors would disappear from the list. I quickly learned what changes needed to be made and got some LEDs blinking.

Then I moved on to bit-banging I2C code for an LCD with PCF8574T backpack, and then added a DS3231 RTC and made some clock code. I'll probably eventually rewrite to use the onboard I2C module - it would tighten up the code some. But for now my bit-bang code works just fine.

The project I'm working on is an auto watering control for my grow tent (growing cannabis is legal here), so the next steps will be some control menus and time setting code, adding some buttons and getting interrupt service routines written to handle buttons, display refresh and RTC. Then I'll connect a relay board to run the pump(s). I guess I should also write some code to get temperature from the DS3231 and display it. Perhaps some temperature alarms? So much work to do...

clock.c

(Scroll down for .h file)

#include "clock.h"

#define dat   TRISCbits.TRISC4
#define clk   TRISCbits.TRISC3

void main()
{
  char string[16];
  unsigned char sec,sec10,min,min10,hour,hour10,ampm,dayofweek,date1,date10,mon,mon10,year1,year10;
  unsigned char seconds,minutes,hours,date,month,year;

  TRISA = 0;                  //porta (LED) all outs
  ANSELA = 0;                 //porta all digital
  TRISC = 0b00011000;         //LATC all outs except i2c
  LATC = 0;                   //LATC all zeros
  ANSELC = 0;                 //portc all digital
  __delay_ms(5);

  lcd_init();
  setclock();

  while(1){
    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;
    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("AM");
    else
      lcd_string("PM");
    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();
    __delay_ms(255);
  }
}

void setclock(){
  start();
  i2c_write(0xd0);        //send ID code & write bit
  i2c_write(0x00);        //register address
  i2c_write(0x00);        //$00 set seconds
  i2c_write(0x10);        //$01 set minutes
  i2c_write(0b01101001);  //$02 set hours
  i2c_write(0x04);        //$03 set day
  i2c_write(0b00011000);  //$04 set date
  i2c_write(0b00000011);  //$05 set month
  i2c_write(0x20);        //$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(100);
  for(i=0;i<8;i++){
    dat = 1;              //sda high
    __delay_us(100);      //minimum clock low time
    clk = 1;              //clk high
    __delay_us(50);       //1/2 min clock high time
    mbit = PORTCbits.RC4; //read the data bit
    if(mbit)              //store it in tmp
      tmp = tmp | 0x01;
    __delay_us(50);       //last 1/2 min clock high time
    if(i < 7)
      tmp <<= 1;          //shift left for next bit
    clk = 0;              //lower clock
    __delay_us(100);      //minimum clock low time
  }
  PORTC = 0;
  dat = 0;                //send ACK
  __delay_us(100);        //data settle time
  i2c_clock();            //pulse the clock
  dat = 1;                //release ACK
  __delay_us(100);        //gap between next byte
  return(tmp);
}

unsigned char i2c_read_nack(void){
  unsigned char i,tmp = 0;
  static __bit mbit;
  __delay_us(100);
  for(i=0;i<8;i++){
    dat = 1;            //SDA = input
    __delay_us(100);       //minimum clock low time
    clk = 1;            //release clock
    __delay_us(100);       //1/2 min clock high time
    mbit = PORTCbits.RC4;     //read the data bit
    if(mbit)            //store it in tmp
      tmp = tmp | 0x01;
    __delay_us(100);      //last 1/2 min clock high time
    if(i < 7)
      tmp <<= 1;          //shift left for next bit
    clk = 0;            //lower clock
    __delay_us(100);       //minimum clock low time
  }
  PORTCbits.RC4 = 0;
  dat = 1;                //send NACK
  __delay_us(100);           //data settle time
  i2c_clock();            //pulse the clock
  return(tmp);
}

unsigned char i2c_write(unsigned char x){
  unsigned char i;
  __delay_us(100);
  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(50);     //wait half a clock pulse
  if(PORTCbits.RC4)   //sample data bit
    return(1);        //nack error
  __delay_us(50);     //ack good, wait other half of clock pulse
  clk = 0;            //set clock low
  __delay_us(100);
  dat = 1;            //set data
  return(0);
}

void start(void){     //send start condition
  dat = 0;            //set data low
  __delay_us(100);
  clk = 0;
  __delay_us(100);
}

void restart(void){     //send start condition
  dat = 1;              //release data
  __delay_us(50);
  clk = 1;
  __delay_us(50);
  dat = 0;
  __delay_us(50);
  clk = 0;
  __delay_us(50);
}

void stop(void){      //send stop condition
  dat = 0;            //set data low
  __delay_us(100);
  clk = 1;            //set clock high
  __delay_us(100);       //stop delay
  dat = 1;            //set data high
  __delay_us(100);
}

void i2c_clock(void){
  clk = 1;            //set clock high
  __delay_us(100);
  clk = 0;            //set clock low
  __delay_us(100);
}

//* 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 | 0b00001100; //set E, RW = write, RS = command
  i2c_write(temp);          //send upper nybble
  temp = temp & 0b11111000; //clear E to latch data and resend
  i2c_write(temp);

    temp = letter;
    temp = temp & 0x0f;       //clear upper nybble
  temp = temp << 4;         //shift left 4 bits
  temp = temp | 0b00001100; //send lower nybble
  i2c_write(temp);
  temp = temp & 0b11111000;
  i2c_write(temp);
}

void lcd_char(unsigned char letter)
{
    unsigned char temp;
    temp = letter;
    temp = temp & 0xf0;       //clear lower nybble
  temp = temp | 0b00001101; //set E, RW = write, RS = data
  i2c_write(temp);          //send upper nybble
  temp = temp & 0b11111001; //clear E to latch data and resend
  i2c_write(temp);

    temp = letter;
    temp = temp & 0x0f;       //clear upper nybble
  temp = temp << 4;         //shift left 4 bits
  temp = temp | 0b00001101; //send lower nybble
  i2c_write(temp);
  temp = temp & 0b11111001;
  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
}

clock.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 = ON      // Multi-vector enable bit (Multi-vector enabled, Vector table 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 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 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);

Next Post Previous Post