Signal Generator in a PIC16F1829–Part 2

PIC16F1829_schematic_waveform

Previously I covered using Excel to create/chart waveform equations and Visual Basic (VB) code to convert the equations into C coded look-up tables (that post is here).  The output of the VB program is copy/pasted into firmware for a Microchip PIC16F1829.    Using the PWM output from the microcontroller, an RC filter, and an op-amp buffer I was able to generate test waveforms for a recent software design.

The PIC16F1829 is one of my go-to, low-end, Microchip parts.  It has a useful set of built-in peripherals such as UART, SPI, I2C, PWM (10-bit), 5 timers, ADC (10-bit), DAC (5-bit), comparators, voltage reference, plenty of flash memory, and 256 bytes of EEPROM.  It also has as a  wide operating voltage (1.8-5.5V) and an internal oscillator (up to 32MHz).  This makes it really useful for one-off small designs, including battery powered projects.

For this project I just needed a quick signal generator that created waveforms that are more complex than what you can easily generate out of a desktop function generator.   Using a microcontroller to accomplish this provided some real benefits.  I could send this with the oscilloscope to our client and know that they would see the same signals as me when they tested the software.  This would make tech support easier if there was a problem later on.   The actual signal they were measuring was not something that we could duplicate in our lab.

Looking back at the last post on this topic you can see that my VB code created C formatted look up tables for four different equations.  Here is the code that was generated.

/******************************************************************************/
/* User Global Variable and Constant Declaration                              */
/******************************************************************************/
const struct sig1 {
	unsigned char Size;
	unsigned char PWM[100];
	} sig1 =
		{ 100, {
		3, 6, 8, 11, 14, 17, 20, 23, 25, 28,
		31, 34, 37, 40, 42, 45, 48, 51, 53, 56,
		59, 62, 64, 67, 70, 73, 75, 78, 81, 83,
		86, 89, 91, 94, 97, 99, 102, 104, 107, 110,
		112, 115, 117, 120, 122, 125, 127, 130, 132, 134,
		137, 139, 142, 144, 146, 149, 151, 153, 155, 158,
		160, 162, 164, 166, 169, 171, 173, 175, 177, 179,
		181, 183, 185, 187, 189, 191, 193, 194, 196, 198,
		200, 201, 203, 205, 207, 208, 210, 211, 213, 215,
		216, 218, 219, 220, 222, 223, 225, 226, 227, 229
		} };

const struct sig2 {
	unsigned char Size;
	unsigned char PWM[100];
	} sig2 =
		{ 100, {
		86, 158, 134, 108, 89, 75, 64, 56, 50, 45,
		41, 38, 35, 32, 30, 28, 27, 25, 24, 23,
		22, 21, 20, 19, 18, 18, 17, 16, 16, 15,
		15, 14, 14, 14, 13, 13, 13, 12, 12, 12,
		11, 11, 11, 11, 10, 10, 10, 10, 10, 9,
		9, 9, 9, 9, 9, 9, 8, 8, 8, 8,
		8, 8, 8, 8, 8, 7, 7, 7, 7, 7,
		7, 7, 7, 7, 7, 7, 7, 7, 6, 6,
		6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
		6, 6, 6, 6, 6, 6, 6, 6, 6, 6
		} };

const struct sig3 {
	unsigned char Size;
	unsigned char PWM[100];
	} sig3 =
		{ 100, {
		60, 70, 80, 89, 99, 108, 116, 124, 132, 139,
		145, 151, 156, 160, 164, 167, 169, 170, 170, 170,
		169, 167, 164, 161, 157, 152, 147, 141, 135, 128,
		121, 114, 107, 99, 91, 83, 75, 68, 60, 53,
		46, 39, 33, 27, 22, 18, 14, 11, 8, 6,
		5, 5, 6, 7, 9, 12, 15, 20, 25, 31,
		37, 44, 51, 59, 68, 76, 86, 95, 104, 114,
		124, 133, 143, 152, 161, 170, 179, 187, 194, 201,
		208, 213, 219, 223, 227, 229, 232, 233, 233, 233,
		232, 230, 228, 224, 220, 215, 210, 204, 198, 191
		} };

const struct sig4 {
	unsigned char Size;
	unsigned char PWM[100];
	} sig4 =
		{ 100, {
		43, 32, 22, 14, 9, 6, 7, 9, 15, 23,
		33, 44, 57, 70, 83, 95, 106, 116, 124, 130,
		134, 136, 137, 136, 135, 133, 132, 131, 131, 133,
		136, 141, 148, 157, 168, 179, 192, 205, 217, 229,
		240, 249, 255, 255, 255, 255, 254, 247, 238, 227,
		215, 202, 189, 177, 165, 155, 146, 140, 136, 133,
		133, 134, 136, 139, 143, 146, 149, 150, 150, 148,
		145, 140, 133, 125, 116, 106, 96, 87, 79, 72,
		67, 65, 66, 69, 75, 83, 94, 107, 121, 136,
		151, 166, 180, 193, 204, 213, 220, 224, 227, 227
		} };

 

In this design I used one analog (ADC) input and one pulse-width modulated (PWM) output with the firmware written/debugged using  Microchip’s MPLABX development environment, the XC8 (version 1.35), and a PICKit3.    There are some additional digital I/O’s being used and functions like button debouncing that I’ve left out for brevity.

Additionally, while both the ADC and PWM on the PIC16F1829 are 10-bit I only used 8-bits of resolution for both.  As you can see form the schematic the analog input is from a potentiometer and I use it to determine how often I change the PWM output.  Here are the functions I call to configure the oscillator, ports, and PWM registers…

/******************************************************************************/
/* Configure_Oscillator:                                                      
   Configures the device to run with 8MHz using the internal oscillator
   and no phase-lock-loop.  Instruction time will be 4X slower than clock (2MHz-500ns).
   Sets WDT timout period.                                                    */
/******************************************************************************/
void Configure_Oscillator(void)
{
    OSCCONbits.IRCF=0xe;           // Set internal osc. for 8MHz
    OSCCONbits.SPLLEN=0x00;        // Disable 4X PLL
    OSCCONbits.SCS=0x0;            // Config word selects internal osc.
    WDTCONbits.WDTPS=0xd;          // set watch dog time for ~8s reset
}
/******************************************************************************/
/* Configure_Ports:                                                           
   Configures i/o direction and power on states.
*/
/******************************************************************************/
void Configure_Ports_ADC(void)
{
    OPTION_REGbits.nWPUEN = 0;      // enable individual weak pull-ups

    // Set up port A
    ANSELA = 0x04;                  // RA2 is an analog input
    LATA = 0x00;                    // all outputs low
    TRISA= 0b00001111;              // Set i/o direction, 0 = output '1' = input
    WPUAbits.WPUA = 0x00;           // No port A pins are pulled-up

    // Using 10-bit right justified
    FVRCONbits.FVREN = 0x00;        // disable fixed voltage reference
    ADCON1bits.ADFM = 0x01;         // right justified
    ADCON1bits.ADCS = 0x04;         // conversion clock use FOSC/4
    ADCON0bits.CHS = 0x02;
    ADCON1bits.ADPREF = 0x00;       // positive reference is VCC
    ADCON0bits.ADON = 1;            // turn on ADC module

    // Set up port B
    ANSELB = 0;                     // all pins are digital inputs
    TRISB= 0b11111111;              // RB4-7 inputs RB0-3 don't exist
    WPUB = 0xff;                    // enable all port B pullups

    // Set up port C
    ANSELC = 0;                     // first, make all pins are digital inputs
    TRIGGER = 1;                    // Trigger = 1, PWM and LED pins = 0
    CCP1PIN = 0;                    //
    LED = 0;
    TRISC= 0b00000100;              // RC2 is an input
    WPUCbits.WPUC2 = 1;             // DIPSW0 is pulled up
}
/******************************************************************************/
/* Configure_PWM:
   Configures registers to generate PWM signals                               */
/******************************************************************************/
void Configure_PWM(void)
{
    // setup ECCP modules, PWM1 ands PWM2.
    CCP1CONbits.CCP1M = 0x0c;       // PWM mode
    CCP1CONbits.P1M = 0x00;         // single output mode
    CCP1AS = 0x00;                  // auto shutdown is disabled
    PWM1CON = 0x00;                 // no startup delay on PWM signal

    CCPR1L = 0x00;                  // set PWM dutycycle to 0%

    // set up PWM timebase-frequency
    CCPTMRS = 0x00;                 // all PWM modules use timer 2

    PR2 = 0xff;                     // load PWM period value
    T2CONbits.T2OUTPS = 0x00;       // postscaler = 0
    T2CONbits.T2CKPS = 0x00;        // prescaler = 1 -> 7.6KHz PWM
    T2CONbits.TMR2ON = 0x01;        // turn on timer

}

When the microcontroller is running it monitors a push-button.  When pressed it reads a DIP switch to determine which signal to generate with the PWM pin.  It also reads the potentiometer to determine how often the PWM output needs to be updated.   It will loop through the appropriate look-up table to grab the correct value and change the output voltage.  Here is that code.

/******************************************************************************/
/* Output_Signal:
 Outputs a signal based on the DIP switch and potentiometer settings
 /******************************************************************************/
void Output_Signal(void)
{
    LED = 1;
    TRIGGER = 0;
    unsigned char i;
    unsigned char j;

    // read the DIP switch to see what signal to generate
    unsigned char DIPSwitchState = 0;
    if (DIPSW0 == 0)
        DIPSwitchState = 0x01;
    if (DIPSW1 == 0)
        DIPSwitchState = 0x02;
    if (DIPSW2 == 0)
        DIPSwitchState = 0x03;
    if (DIPSW3 == 0)
        DIPSwitchState = 0x04;

    // read POT to find out the time base of the PWM signal

    unsigned char PWM_delay = Read_Pot();

    PIR1bits.TMR1IF = 1;

    switch(DIPSwitchState)
    {
        case 0x01:
            for(i=0;i<sig1.Size;i++)
            {
                CLRWDT();
                for(j=0;j<PWM_delay;j++)
                {
                    while(PIR1bits.TMR1IF==0);
                    PIR1bits.TMR1IF = 0;
                    Reset_Timer1(TMR1_500uS);
                }
                CCPR1L = sig1.PWM[i];
            }
        break;

        case 0x02:
            for(i=0;i<sig2.Size;i++)
            {
                CLRWDT();
                for(j=0;j<PWM_delay;j++)
                {
                    while(PIR1bits.TMR1IF==0);
                    PIR1bits.TMR1IF = 0;
                    Reset_Timer1(TMR1_500uS);
                }
                CCPR1L = sig2.PWM[i];
            }
        break;

        case 0x03:
            for(i=0;i<sig3.Size;i++)
            {
                CLRWDT();
                for(j=0;j<PWM_delay;j++)
                {
                    while(PIR1bits.TMR1IF==0);
                    PIR1bits.TMR1IF = 0;
                    Reset_Timer1(TMR1_500uS);
                }
                CCPR1L = sig3.PWM[i];
            }
        break;

        case 0x04:
            for(i=0;i<sig4.Size;i++)
            {
                CLRWDT();
                for(j=0;j<PWM_delay;j++)
                {
                    while(PIR1bits.TMR1IF==0);
                    PIR1bits.TMR1IF = 0;
                    Reset_Timer1(TMR1_500uS);
                }
                CCPR1L = sig4.PWM[i];
            }
        break;
    }

    CCPR1L = 0;
    LED = 0;
    TRIGGER = 1;
    Reset_Timer1(TMR1_500uS);
}

The analog value is read in the Read_Pot() function, shown below…

/******************************************************************************/
/* Read_Pot:
 Read voltage from potentiometer and returns it as an unsigned char.
 /******************************************************************************/
unsigned char Read_Pot(void)
{
    unsigned char result = 0;
    unsigned int result_int = 0;

    ADCON0bits.ADGO = 1;
    while(ADCON0bits.ADGO == 1);
    result_int = ADRES;
    result = result_int>>2;
    if(result==0)
        result+=1;

    return result;
}

The end result is some funky waveforms that I could generate to test my software, whose purpose was to automate some oscilloscope measurements.

Here is test signal 3 (from the sig3 lookup table).

signal_generator_sig3

 

Here is test signal 4 (from the sig4 lookup table).

sig_4

Speak Your Mind

*