Showing posts with label XC8. Show all posts
Showing posts with label XC8. Show all posts

Wednesday, January 18, 2017

NEOs & PIC 12F1840 Proof of Concept

I was looking to add some NEOs to another project and wanted to see if a PIC could be programmed to drive them. Thus the birth of this POC. I took some inspiration from an Adafruit tutorial on writing your own driver for a PIC. This tutorial focused on the RGB version of the NEO (no separate white LED) The PIC used was a PIC 18F4550 clocked at 48Mhz in order to be fast enough for the timing. It's not quite fast enough to technically keep within the specs for the NEOs but it was learned in this tutorial that only the positive pulse width is relevant (within reason) and the author was able to keep that within the variance spec.
This experiment is to see if I can do the same for a much smaller PIC: 12F1840 driven at it's max clock rate of 32Mhz and using the RGBW version of the NEO: An Adafruit NEOPixel stick (8 NEOs with RGBW LEDs). I was able to do it as can be seen in the video:

The PIC is configured to use internal PLL for generating an internal 32 Mhz clock which, with a few coding optimizations is just fast enough to get within specs of the NEOs for that critical positive pulse width. In addition I coded this to use 32 bit format for the RGBW version of the NEO.

So now I can use this same technique to incorporate NEOs into a much smaller (8 pin device) PIC.
The schematic for this POC:

The entire project was coded in XC8 (free version) with one function using in-line assembler to get the speed needed to meet timing requirements for the NEOs.

The zipped MPLABX project package is here.

Sunday, December 25, 2016

PWM experiment on a 10w LED

This is an experiment to illustrate control element dissipation with the use of a PIC 12F1840 operating as a touch switch and PWM to drive a 12v 10 watt LED using a single MosFET (BSS806N) as the driver.

Wednesday, April 27, 2016

Simple Cap Sense Example for PIC 12F1840 in XC8

My wife wanted a simple touch switch battery powered sconce light... sounded like a great fit for a PIC 12F1840... small 8 pin microcontroller with a built in CSM (Cap Sense Module) also sometimes referred to as a CPS module. Attempting to research this was incredibly frustrating! There is just no SIMPLE example out there in either assembler or C! Everywhere I looked was only examples of using the mTouch library, which although a nice library is just far too much over-kill for my needs. Other research from forums suggested that the peripheral is just too complex and tweeky to be of much use. So off on my own... I dug into the manuals and put this simple example of using the CSM module on a PIC12F1840 (and probably the 12F1822 as well) written in XC8 free edition. Here it is in operation.

The PIC12F1840 CSM module is simply an R/C oscillator feeding a frequency counter! You select one of the four available pins as the capacitor input pin for the oscillator, setup timer 0 as the frequency counter time base, and timer 1 as the counter to count the R/C oscillator. The time base (Timer 0) is set to interrupt the PIC at specific intervals and it also gates the R/C pulses going to Timer 1. That interval should be long enough to capture a large count in Timer 1 from the R/C oscillator. The system clock is configured for internal 4MHz and Timer 0 is configured for a prescaling of 256. This gives a count of around 33,000 in Timer 1 (16 bit counter) or about half way through. So for every time interval of Timer 0 interrupting the PIC, Timer 1 will have a count of about 33,000 in it. Now in the interrupt routine you just read Timer 1 into a global variable, reset Timer 1 to zero, and re-enable interrupts for the next read. In the main body of code you just loop and watch the Timer 1 value stored in the global variable (touch in my case) and act on that value when it changes. Simple! If there is any change in the R/C oscillator's cap sense pin (like a finger, or other larger object), that will change the capacitance on that pin and shift the oscillator down ... Timer 1 will reflect that with a smaller count. Now just have your code watch for the change to a smaller count and you have a touch switch.

Here are the XC8 files out of the MPLABX IDE:

/***********************************/
/* Simple PIC 12F1840 Touch Switch Example */
/* main.c - Tim Stoddard, 2016  */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */

#include "system.h"        /* System funct/params, like osc/peripheral config */
#include "user.h"          /* User funct/params, such as InitApp */

/***********************************/
/* User Global Variable Declaration */
/***********************************/

/* i.e. uint8_t <variable_name>; */

/***********************************/
/* Main Program  */
/***********************************/
void main(void)
{
    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

   /* Flash the LAMP a few times */
    for(i=0;i<5;i++){
        LAMP=1;
        __delay_ms(50);
        LAMP=0;
        __delay_ms(200);
    }
    /* grab the latest count from Timer 1 (touch), subtract a threshold amount,
        and save it (touch_cal) */
    touch_cal=touch-1000;

    /* MAIN LOOP */
    while(1)
    {
        if(touch<touch_cal){// sensor is touched
            LAMP=1;// turn it on
        }else{// otherwise ...
            LAMP=0;// turn it off
        }
    }

}


/***********************************/
/* user.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>         /* For uint8_t definition */
#include <stdbool.h>        /* For true/false definition */

#include "user.h"
/***********************************/
/* User Functions */
/***********************************/

void InitApp(void)
{
    /* Setup analog functionality and port direction */
       TRISA = 0b00111011; // RA2 is output
       ANSA4=1;
    /* Initialize peripherals */
       CM1CON1=0;
       CPSCON0bits.CPSRM=0;// 1=variable voltage ref, 0=fixed
       CPSCON0bits.CPSRNG=0b11;// high current
       CPSCON0bits.CPSON=1;
       CPSCON1bits.CPSCH=0b11;//CPS3 selected
       //Timer 0 is the time base for CPS
       TMR0CS=0;//Timer 0 used as time base for CPS select Fosc/4
       OPTION_REGbits.PS=0b111;
       OPTION_REGbits.PSA=0;
       // Timer 1 is the freq counter for CPS
       T1CON=0b11000101; // TMR1 capacitive sensing osc, prescaler 1/1, dedicated osc disabled,no synch,timer1 enabled
       T1GSEL=0b01;// set timer 1 gate for TMR0 overflow
    /* Enable interrupts */
       TMR0IE=1;// enable TMR0 interrupts
       PEIE=1;//enable Peripheral interrupts
       GIE=1;// enable global interrupts
}


/***********************************/
/*interrupts.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>         /* For uint8_t definition */
#include <stdbool.h>        /* For true/false definition */
#include "user.h"
/***********************************/
/* Interrupt Routines */
/***********************************/

#ifndef _PIC12

void interrupt isr(void)
{
 #if 1

    GIE=0;// disable all interrupts

    if(T0IF) // Timer 0 interrupt?
    {
        TMR1ON=0;// turn off timer 1
        touch=TMR1L+(unsigned int)(TMR1H << 8);// read timer 1
        TMR1H=0;// clear timer 1
        TMR1L=0;
        TMR1ON=1;// enable timer 1
        T0IF=0;
    }else{
        /* Unhandled interrupts */
    }
    GIE=1;// enable all interrupts

#endif

}
#endif


/***********************************/
/* user.h */
/***********************************/
#define _XTAL_FREQ  4000000

#define LAMP RA2

void InitApp(void);         /* I/O and Peripheral Initialization */

/* declare variables */
unsigned int touch;
unsigned int touch_cal;
unsigned int i;


/***********************************/
/* configuration_bits.c */
/***********************************/

#if defined(__XC)
    #include <xc.h>         /* XC8 General Include File */
#elif defined(HI_TECH_C)
    #include <htc.h>        /* HiTech General Include File */
#endif


// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

/***********************************/
/* system.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */

#include "system.h"

void ConfigureOscillator(void)
{
    while (!HFIOFS) continue;// wait for stable osc

#if 0

    OSCCAL=_READ_OSCCAL_DATA(); /* _READ_OSCCAL_DATA macro unloads cal memory */

#endif
}

/***********************************/
/* system.h */
/***********************************/

/* TODO Define system operating frequency */

/* Microcontroller MIPs (FCY) */
#define SYS_FREQ        4000000L
#define FCY             SYS_FREQ/4

/***********************************/
/* System Function Prototypes */
/***********************************/

void ConfigureOscillator(void); /* Handles clock switching/osc initialization */

Sunday, January 31, 2016

URSA: Heartbeat-Delay-PWM code

Now to some coding! I'm using Microchip's free IDE (MPLAB X) and the free version of their C compiler XC8

As a first step I always like to code the basic 'blinking LED' in a new PIC project. I use interrupts instead of delays because in addition to having a visual indication that the PIC is alive, I also use the same interrupt routine to handle any delays I may need down the road. XC8 does have built-in delay functions however, XC8 delay functions actually inserts NOP instructions into the code and you can't pass a variable into the XC8 delay function. You need to know all your static only delays ahead of time! Not very robust or efficient, so I use an interrupt routine to handle all delays instead of the built in XC8 functions. In addition to this basic 'heartbeat' I also configured two CCP peripherals as PWM channels for the control of the two drive motors. 

First we configure the PIC 16F1829 fuses:
/* configuration_bits.c */
#include <xc.h>         /* XC8 General Include File */
#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)

#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

Now the system config:
/* system.h */
#define SYS_FREQ        1600000L
#define FCY             SYS_FREQ/4

/******************************************************************************/
/* System Function Prototypes                                                 */
/******************************************************************************/


void ConfigureOscillator(void); /* Handles clock switching/osc initialization */

...and

/* system.c */
#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */
#include "system.h"

void ConfigureOscillator(void)
{
    OSCCON=0b01111010;
    while (!HFIOFS) continue;// wait for stable osc

}

The code below contains the configuration of both the CCP and Timer 2 for use as a 1ms interrupt timer.

/* user.c */
#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>     /* For uint8_t definition */
#include <stdbool.h>    /* For true/false definition */
#include "user.h"

void InitApp(void)
{
    /* Heartbeat config */
    TRISAbits.TRISA5 = 0; // Heartbeat LED
    TMR2=0;// clear TMR2
    T2CON = 0b00000110;//post 0, timer on, pre 16
    PR2=0xF9; // sets up timer2 for 1ms interrupts
 
    /* PWM Left config */
    TRISCbits.TRISC6 = 0; // Left PWM output
    T4CON = 0b00000110;//post 0, timer on, pre 16
    PR4 = 0xF9; // 1000 Hz PWM freq
    CCP4CON = 0x3C;// PWM mode
    CCPR4H = 0;
    CCPR4L = 0x3E; // Duty cycle count 1% = 0x9 to 100% = 0xF9
    CCPTMRS = 0x40; // use Timer 4
 
    /* PWM Right config */
    TRISAbits.TRISA2 = 0; // Right PWM output
    T6CON = 0b00000110;//post 0, timer on, pre 16
    PR6 = 0xF9; // 1000 Hz PWM freq
    CCP3CON = 0x1C; // PWM mode
    CCPR3H = 0;
    CCPR3L = 0x7C; // Duty cycle count 1% = 0x9 to 100% = 0xF9
    CCPTMRS = 0x20; // use Timer 6
 
    /* Enable interrupts */
    PIR1bits.TMR2IF=0; // clear TMR2 interrupt flag
    PIE1bits.TMR2IE=1;//enable TMR2 interrupts
    INTCONbits.PEIE=1;//enable Peripheral interrupts
    INTCONbits.GIE=1;// enable global interrupts

}
void msDelay(unsigned int d){
    // load up the msec delay variable
    ms_delay=d;
    // loop execution here until msec delay completes via 1 ms interrupts
    while(ms_delay)continue;
}

We will be manipulating the CCPR3L and CCPR4L registers to vary the duty cycles for the two motors. For now however, they are hard-coded to generate a 25% duty cycle on the Left motor and 50% on the right motor as a test. Note the msDelay function I wrote to execute any delays needed. It simply sets a global variable with the amount of msecs needed and the 1ms interrupt routine decrements that variable until it reaches zero! The delay routine just loops checking for that variable to reach zero.

The O'scope shot below shows the two outputs from the PIC with the top trace being the Left and the bottom trace being the Right. 


The interrupt routine:
/* interrupts.c */
#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>         /* For uint8_t definition */
#include <stdbool.h>        /* For true/false definition */
#include "user.h"

void interrupt isr(void)
{
    INTCONbits.GIE=0;// disable all interrupts

    /* Determine which flag generated the interrupt */
    if(PIR1bits.TMR2IF)// 1ms interrupt via Timer 2
    {
        if(hb_count++>500)// if counted 500ms
        {
            LED = ~LED; // toggle the 'heartbeat' LED
            hb_count=0; // and reset the count to 0
        }
        if (ms_delay){ // if  msec delay variable is not zero
            ms_delay--; // decrement the delay
        }
        PIR1bits.TMR2IF=0; // clear Timer 2 interrupt flag
    }
    else
    {
        /* Unhandled interrupts */
    }
    INTCONbits.GIE=1;// enable global interrupts


}

General definitions:
/* user.h */
#define _XTAL_FREQ 16000000                  // Fosc  frequency for _delay()  library
#define LED LATAbits.LATA5

/******************************************************************************/
/* User Function Prototypes                                                   */
/******************************************************************************/
void msDelay(unsigned int d);
void InitApp(void);         /* I/O and Peripheral Initialization */

/* global variables */
unsigned int hb_count=0;// Heartbeat counter

unsigned long ms_delay=0;

The main routine is a bit bare since not much is going on yet!
/* main.c */
#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */
#include "system.h"        /* System funct/params, like osc/peripheral config */
#include "user.h"          /* User funct/params, such as InitApp */

void main(void)
{
    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

    while(1)
    {

    }


}