PIC_Mid_C_1

download PIC_Mid_C_1

of 21

description

pic codes

Transcript of PIC_Mid_C_1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 1

    Introduction to PIC Programming

    Programming Mid-Range PICs in C

    by David Meiklejohn, Gooligum Electronics

    Lesson 1: Basic Digital I/O

    The Baseline PIC C Programming tutorial series demonstrated how to program baseline PICs (such as the

    12F509 and 16F506) in C, to perform the tasks covered in the Baseline PIC Assembler lessons: from

    flashing LEDs and reading switches, through to implementing a simple light meter with smoothed output on

    a multiplexed 7-segment LED display. The baseline C programming series used the free CCS PCB

    compiler bundled with MPLAB1, and Microchips XC8 compiler (running in Free mode). We saw in that

    series that, although these C compilers were perfectly adequate for the simplest tasks, they faltered when it

    came to the more complex applications involving arrays, reflecting the difficulty of implementing a C

    compiler for the baseline PIC architecture.

    This tutorial series revisits this material, along with other topics covered in the Mid-range PIC Assembler

    lessons, using mid-range devices such as the 12F629 and 16F684. It will become apparent that the mid-

    range architecture is much more suitable for C programming than the baseline architecture, especially for

    applications which need to access more than one bank of data memory. But we will also see that, although it

    is often easier to program in C, in the sense that programs are shorter and more easily expressed, assembler

    remains the most effective way to make the most of the limited resources on these small devices, even for

    mid-range PICs. Nevertheless, we will see that C is a very practical alternative for most applications.

    Microchips XC8 compiler supports all mid-range PICs, and can be operated in Free mode (with all

    optimisations disabled) for free making it a good choice for use in these lessons.

    This lesson covers basic digital I/O: flashing LEDs, responding to and debouncing switches, as covered in

    lessons 1 to 3 of the mid-range assembler tutorial series. You may need to refer to those lessons while

    working through this one.

    In summary, this lesson covers:

    Introduction to the Microchip XC8 compiler

    Digital input and output

    Programmed delays

    Switch debouncing

    Using internal (weak) pull-ups

    These tutorials assume a working knowledge of the C language; they do not attempt to teach C.

    1 CCS PCB is bundled with MPLAB 8 only, and only supports baseline PICs, so cannot be used with mid-range PICs.

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 2

    Introducing the XC8 Compiler

    Up until version 8.10, MPLAB was bundled with HI-TECHs PICC-Lite compiler, which supported all the baseline (12-bit) PICs available at that time, including those used in this tutorial series, with no restrictions.

    It also supports a small number of the mid-range (14-bit) PICs although, for most of the mid-range devices

    it supported, PICC-Lite limited the amount of data and program memory that could be used, to provide an

    incentive to buy the full compiler. Microchip have since acquired HI-TECH Software, and no longer supply

    or support PICC-Lite. As such, PICC-Lite will not be covered in these tutorials.

    XC8 supports the whole 8-bit PIC10/12/16/18 series in a single edition, with different licence keys unlocking

    different levels of code optimisation Free (free, but no optimisation), Standard and PRO (most

    expensive and highest optimisation).

    Microchip XC compilers are also available for the PIC24, dsPIC and PIC32 families.

    XC8s Free mode supports all 8-bit (including baseline and mid-range) PICs, with no memory restrictions. However, in this mode, most compiler optimisation is turned off, making the generated code around twice

    the size of that generated by PICC-Lite.

    This gives those developing for baseline and mid-range PICs easy access to a free compiler supporting a

    much wider range of devices than PICC-Lite does, without memory usage restrictions, albeit at the cost of

    much larger generated code. And XC8 will continue to be maintained, supporting new baseline and mid-

    range devices over time.

    But if you are using Windows and developing code for a supported mid-range PIC, it is quite valid to

    continue to use PICC-Lite (if you are able to locate a copy by downloading MPLAB 8.10 from the archives

    on www.microchip.com, for example), since it will generate much more efficient code. It can be installed

    alongside XC8. But to repeat PICC-Lite wont be described in these lessons.

    Regardless of which version of MPLAB you are using, the XC8 installer (for Windows, Linux or Mac) has

    to be downloaded separately from www.microchip.com.

    When you run the XC8 installer, you will be asked to enter a license activation key. Unless you have

    purchased the commercial version, you should leave this blank. You can then choose whether to run the

    compiler in Free mode, or activate an evaluation license. Well be using Free mode in these lessons, but its ok to use the evaluation license (for 60 days) if you choose to.

    See baseline assembler lesson 1 for more detail on installing and using MPLAB 8 or MPLAB X.

    Data Types

    One of the problems with implementing ANSI-standard C on microcontrollers is that there is often a need to

    work with individual bits, while the smallest data-type included in the ANSI standard is char, which is

    normally considered to be a single byte, or 8 bits. Another problem is the length of a standard integer

    (int) is not defined, being implementation-dependent. Whether an int is 16 or 32 bits is an issue on larger systems, but it makes a much more significant difference to code portability on microcontrollers.

    Similarly, the sizes of float, double, and the effect of the modifiers short and long is not defined by the standard.

    So various compilers use different sizes for the standard data types, and for microcontroller

    implementations it is common to add a single-bit type as well generally specific to that compiler.

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 3

    Here are the data types and sizes supported by XC8 and, for comparison, the size of the same data types in

    CCS PCB:

    Youll see that very few of these line up; the only point of agreement is that char is 8 bits!

    XC8 defines a single bit type, unique to XC8.

    The standard int type is 16 bits in XC8, but 8 bits in CCS PCB.

    But by far the greatest difference is in the definition of

    short: in XC8, it is a synonym for int, with short, int and short int all being 16-bit quantities, whereas in CCS PCB, short is a single-bit type.

    Finally, note that double floating-point variables in XC8 can be either 24 or 32 bits; this is set by a compiler option. 32

    bits may be a higher level of precision than is needed in most

    applications for small applications, so XC8s ability to work with 24-bit floating point numbers can be useful.

    To make it easier to create portable code, XC8 provides the stdint.h header file, which defines the C99 standard types such as uint8_t and int16_t.

    Example 1: Turning on an LED

    We saw in mid-range assembler lesson 1 how to turn on a single LED, and leave it on; the (very simple)

    circuit is shown below:

    The LED, with a current-limiting resistor, is

    connected to the GP1 pin of a PIC12F629.

    The pushbutton acts as a reset switch, and the 10

    k pull-up resistor holds MCLR high while the switch is open

    2.

    If you are using the Gooligum training board,

    plug your PIC12F629 into the top section of the

    14-pin IC socket the section marked 12F3.

    Close jumpers JP3 and JP12 to bring the 10 k resistor into the circuit and to connect the LED to

    GP1, and ensure that every other jumper is

    disconnected.

    If you have the Microchip Low Pin Count Demo

    Board, refer back to baseline assembler lesson 1

    to see how to build this circuit.

    2 This external pull-up resistor wasnt needed in the baseline PIC examples, because the baseline PICs, and indeed most

    mid-range PICs, include an internal weak pull-up (see example 6, later in this lesson) on MCLR which is automatically

    enabled whenever the device is configured for external reset.

    3 Note that, although the PIC12F629 comes in an 8-pin package, it will not work in the 8-pin 10F socket. You must

    install it in the 12F section of the 14-pin socket.

    Type XC8 CCS PCB

    bit 1 -

    char 8 8

    short 16 1

    int 16 8

    short long 24 -

    long 32 16

    float 24 or 32 32

    double 24 or 32 -

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 4

    To turn on the LED, we loaded the TRISIO register with 111101b, so that only GP1 is set as an output, and

    then set bit 1 of GPIO, setting GP1 high, turning the LED on.

    At the start of the program, the PICs configuration was set, and the OSCCAL register was loaded with the factory calibration value.

    Finally, the end of the program consisted of an infinite loop (goto $), to leave the LED turned on.

    Here are the key parts of the assembler code from mid-range assembler lesson 1:

    ; ext reset, no code or data protect, no brownout detect,

    ; no watchdog, power-up timer, 4 Mhz int clock

    __CONFIG _MCLRE_ON & _CP_OFF & _CPD_OFF & _BODEN_OFF & _WDT_OFF &

    _PWRTE_ON & _INTRC_OSC_NOCLKOUT

    ;***** RESET VECTOR *****************************************************

    RESET CODE 0x0000 ; processor reset vector

    ; calibrate internal RC oscillator

    call 0x03FF ; retrieve factory calibration value

    banksel OSCCAL ; (stored at 0x3FF as a retlw k)

    movwf OSCCAL ; then update OSCCAL

    ;***** MAIN PROGRAM *****************************************************

    ;***** Initialisation

    start

    ; configure port

    movlw ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 5

    So we might use something like:

    /************************************************************************

    * *

    * Filename: MC_L1-Turn_on_LED-HTC.c *

    * Date: 8/6/12 *

    * File Version: 1.2 *

    * *

    * Author: David Meiklejohn *

    * Company: Gooligum Electronics *

    * *

    *************************************************************************

    * *

    * Architecture: Mid-range PIC *

    * Processor: 12F629 *

    * Compiler: MPLAB XC8 v1.00 (Free mode) *

    * *

    *************************************************************************

    * *

    * Files required: none *

    * *

    *************************************************************************

    * *

    * Description: Lesson 1, example 1 *

    * *

    * Turns on LED. LED remains on until power is removed. *

    * *

    *************************************************************************

    * *

    * Pin assignments: *

    * GP1 = indicator LED *

    * *

    ************************************************************************/

    Note that, as we did our previous assembler code, the processor architecture and device are specified in the

    comment block. This is important for the XC8 compiler, as there is no way to specify the device in the code;

    i.e. there is no equivalent to the MPASM list p= or processor directives. Instead, the processor is specified in the IDE (MPLAB), or as a command-line option.

    Most of the symbols relevant to specific processors are defined in header files. But instead of including a

    specific file, as we would do in assembler, it is normal to include a single catch-all file: xc.h (or htc.h). This file identifies the processor being used, and then calls other header files as appropriate. So

    our next line, which should be at the start of every XC8 program, is:

    #include

    The processor can be configured with a series of configuration pragmas, such as:

    // ext reset, no code protect, no brownout detect, no watchdog,

    // power-up timer enabled, int RC clock

    #pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF, WDTE = OFF

    #pragma config PWRTE = OFF, FOSC = INTRCIO

    Or, you can use the __CONFIG macro, in a very similar way to the __CONFIG directive in MPASM:

    __CONFIG(MCLRE_ON & CP_OFF & CPD_OFF & BOREN_OFF & WDTE_OFF &

    PWRTE_OFF & FOSC_INTRCIO);

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 6

    The symbols are the same in both, but note that the pragma uses = (with optional spaces) between each setting, such as MCLRE, and its value, such as ON, while the macro uses _ (with no spaces)4. To see

    which symbols to use for a given PIC, you need to consult the pic_chipinfo.html file, in the docs directory within the compiler install directory.

    As with most C compilers, the entry point for user code is a function called main().

    So an XC8 program will look like:

    void main()

    {

    ; // user code goes here

    }

    Declaring main() as void isnt strictly necessary, since any value returned by main() is only relevant

    when the program is being run by an operating system which can act on that return value, but of course there

    is no operating system here. Similarly it would be more correct to declare main() as taking no parameters (i.e. main(void)), since there is no operating system to pass any parameters to the program. How you

    declare main() is really a question of personal style.

    At the start of our assembler programs, weve always loaded the OSCCAL register with the factory calibration value (although it is only necessary when using the internal RC oscillator). There is no need to

    do so when using XC8; the default start-up code, which runs before main(), loads OSCCAL for us.

    XC8 makes the PICs special function registers, such as TRISIO, available as variables.

    To load the TRISIO register with 111101b, it is simply a matter of:

    TRISIO = 0b111101; // configure GP1 (only) as an output

    Alternatively this could be expressed as:

    TRISIO = ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 7

    Finally, we need to loop forever. There are a number of C constructs that could be used for this, but one

    thats as good as any is:

    for (;;)

    { // loop forever

    ;

    }

    Complete program

    Here is the complete code to turn on an LED on GP1:

    /************************************************************************

    * *

    * Description: Lesson 1, example 1 *

    * *

    * Turns on LED. LED remains on until power is removed. *

    * *

    *************************************************************************

    * *

    * Pin assignments: *

    * GP1 = indicator LED *

    * *

    ************************************************************************/

    #include

    /***** CONFIGURATION *****/

    // ext reset, no code protect, no brownout detect, no watchdog,

    // power-up timer enabled, int RC clock

    __CONFIG(MCLRE_ON & CP_OFF & CPD_OFF & BOREN_OFF & WDTE_OFF &

    PWRTE_OFF & FOSC_INTRCIO);

    /***** MAIN PROGRAM *****/

    void main()

    {

    //*** Initialisation

    // configure port

    TRISIO = ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 8

    (see baseline assembler lesson 1). The other Project menu item or toolbar button, Rebuild, is equivalent to the MPASM Build All, recompiling all your source files, regardless of whether they have changed.

    Building an XC8 project in MPLAB X is exactly the same as for a MPASM assembler project: click on the

    Build or Clean and Build toolbar button, or select the equivalent items in the Run menu, to compile and link your code. When it builds without errors and you are ready to program your code into your PIC,

    select the Run Run Main Project menu item, click on the Make and Program Device toolbar button, or simply press F6.

    Example 2: Flashing an LED (20% duty cycle)

    In mid-range assembler lesson 1, we used the same circuit as above, but made the LED flash by toggling the

    GP1 output. The delay was created by an in-line busy-wait loop. Mid-range assembler lesson 2 showed

    how to move the delay loop into a subroutine, and to generalise it, so that the delay is passed as a parameter

    to the routine, in W. This was demonstrated by a program which flashed the LED at 1 Hz, with a duty cycle

    of 20%, by turning it on for 200 ms and then off for 800 ms, before repeating.

    Here is the main loop from the assembler code from that lesson:

    main_loop

    ; turn on LED

    banksel GPIO

    movlw 1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 9

    The compiler also provides two macros: __delay_us() and __delay_ms(), which use the _delay(n) function create delays specified in s and ms respectively. To do so, they reference the

    symbol _XTAL_FREQ, which you must define as the processor oscillator frequency, in Hertz.

    Since our PIC12F629 is running at 4 MHz, we have:

    #define _XTAL_FREQ 4000000 // oscillator frequency for _delay()

    Then, to generate a 200 ms delay, we can write:

    __delay_ms(200); // stay on for 200 ms

    Complete program

    Putting these delay macros into the main loop, we have:

    /************************************************************************

    * Description: Lesson 1, example 2 *

    * *

    * Flashes an LED at approx 1 Hz, with 20% duty cycle *

    * LED continues to flash until power is removed. *

    * *

    *************************************************************************

    * Pin assignments: *

    * GP1 = flashing LED *

    * *

    ************************************************************************/

    #include

    #define _XTAL_FREQ 4000000 // oscillator frequency for _delay()

    //***** CONFIGURATION *****/

    // ext reset, no code protect, no brownout detect, no watchdog,

    // power-up timer enabled, int RC clock

    __CONFIG(MCLRE_ON & CP_OFF & CPD_OFF & BOREN_OFF & WDTE_OFF &

    PWRTE_OFF & FOSC_INTRCIO);

    /***** MAIN PROGRAM *****/

    void main()

    {

    //*** Initialisation

    // configure port

    TRISIO = 0b111101; // configure GP1 (only) as an output

    //*** Main loop

    for (;;)

    {

    GPIO = 0b000010; // turn on LED on GP1 (bit 1)

    __delay_ms(200); // stay on for 200 ms

    GPIO = 0; // turn off LED (clearing GPIO clears GP1)

    __delay_ms(800); // stay off for 800 ms

    } // repeat forever

    }

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 10

    Example 3: Flashing an LED (50% duty cycle)

    The LED flashing example in mid-range assembler lesson 1 used an XOR operation to flip the GP1 bit

    every 500 ms, creating a 1 Hz flash with a 50% duty cycle.

    The read-modify-write problem revisited

    As discussed in that lesson, any operation which reads an output (or part-output) port, modifies the value

    read, and then writes it back, can lead to unexpected results. This is because, when a port is read, it is the

    value at the pins that is read, not necessarily the value that was written to the output latches. And thats a

    problem if, for example, you have written a 1 to an output pin, which, because it is being externally loaded (or, more usually, it hasnt finished going high yet, because of a capacitive load on the pin), it reads back as a 0. When the operation completes, that output bit would be written back as a 0, and the output pin sent

    low instead of high not what it is supposed to be.

    This can happen with any instruction which reads the current value of a register when updating it. That

    includes logic operations such as XOR, but also arithmetic operations (add, subtract), rotate instructions, and

    increment and decrement operations. And crucially, it also includes the bit set and clear instructions.

    You may think that the instruction bsf GPIO,1 will only affect GP1, but in fact that instruction reads the whole of GPIO, sets bit 1, and then writes the whole of GPIO back again.

    Consider the sequence:

    bsf GPIO,1

    bsf GPIO,2

    Assuming that GP1 and GP2 are both initially low, the first instruction will attempt to raise the GP1 pin

    high. However, the first instruction writes to GPIO at the end of the instruction cycle, while the second

    instruction reads the port pins toward the start of the following instruction cycle. That doesnt leave much time for GP1 to be pulled high, against whatever capacitance is loading the pin. If it hasnt gone high

    enough by the time the second bsf instruction reads the pins, it will read as a 0, and it will then be written back as a 0 when the second bsf writes to GPIO. The potential result is that, instead of both GP1 and GP2 being set high, as you would expect, it is possible that only GP2 will be set high, while the

    GP1 pin remains low, and the GP1 bit holds a 0.

    This problem is sometimes avoided by placing nop instructions between successive read-modify-write operations, but as weve seen in the assembler lessons, a more robust solution is to use a shadow register.

    So why revisit this topic, in a lesson on C programming?

    When you use a statement like GPIObits.GP1 = 1 in XC8, the compiler translates those statements into corresponding bit set or clear instructions, which may lead to read-modify-write problems.

    There was no problem with using these types of statements in the examples above, where only a single pin is

    being used and there are lengthy delays between changes.

    But you should be aware that a sequence such as:

    GPIObits.GP1 = 1;

    GPIObits.GP2 = 1;

    may in fact result in GP1 being cleared and only GP2 being set high.

    To avoid such problems, shadow variables can be used in C programs, in much the same way that they are

    used in assembly language.

    Note: Any C statements which directly modify individual port bits may be subject to read-modify-

    write considerations.

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 11

    Here is the main code from the program presented in mid-range assembler lesson 2:

    ; configure port

    movlw ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 12

    The variable declaration could be placed within the main() function, which is what you should do for any

    variable that is only accessed within main(). However, a variable such as a shadow register may need to be

    accessed by other functions. For example, its quite common to place all of your initialisation code into a function called init(), which might initialise the shadow register variables as well as the ports, and your

    main() code may also need to access them. It is often best to define such variables as global (or external)

    variables toward the start of your code, before any functions, so that they can be accessed throughout your

    program.

    But remember that, to make your code more maintainable and to minimise data memory use, you should

    declare any variable which is only used by one function, as a local variable within that function.

    Well see examples of that later, but in this example well define sGPIO as a global variable.

    Flipping the shadow copy of GP1 and updating GPIO, can then be done by:

    sGPIO ^= 1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 13

    //*** Main loop

    for (;;)

    {

    // toggle LED on GP1

    sGPIO ^= 1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 14

    Example 4: Reading Digital Inputs

    Mid-range assembler lesson 3 introduced digital

    inputs, using a pushbutton switch in the simple

    circuit shown on the right.

    Its the same circuit as in the earlier examples (you can leave your board configured the same

    way as before), but now well use the pushbutton

    to drive a digital input (GP3), instead of as a

    reset switch.

    The 10 k resistor normally holds the GP3 input high, until the pushbutton is pressed, pulling the

    input low.

    Note: if you are using a PICkit 2 programmer,

    you must enable 3-State on Release from Reset, as described in mid-range assembler

    lesson 3, to allow the pushbutton to pull GP3

    low when pressed.

    As an initial example, the pushbutton input was copied to the LED output, so that the LED was on, whenever

    the pushbutton is pressed.

    In pseudo-code, the operation is:

    do forever

    if button down

    turn on LED

    else

    turn off LED

    end

    The assembler code we used to implement this, using a shadow register, was:

    ; configure port

    movlw ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 15

    XC8 implementation

    To copy a value from one bit to another, e.g. GP3 to GP1, using XC8, can be done as simply as:

    GPIObits.GP1 = GPIObits.GP3; // copy GP3 to GP1

    But that wont do quite what we want; given that GP3 goes low when the button is pressed, simply copying GP3 to GP1 would lead to the LED being on when the button is up, and on when it is pressed the opposite of the required behaviour.

    We can address that by inverting the logic:

    GPIObits.GP1 = !GPIObits.GP3; // copy !GP3 to GP1

    or

    GPIObits.GP1 = GPIObits.GP3 ? 0 : 1; // copy !GP3 to GP1

    This works well in practice, but to allow a valid comparison with the assembly source above, which uses a

    shadow register, we should not use statements which modify individual bits in GPIO. Instead we should

    write an entire byte to GPIO at once.

    For example, we could write:

    if (GPIObits.GP3 == 0) // if button pressed

    GPIO = 0b000010; // turn on LED

    else

    GPIO = 0; // else turn off LED

    However, this can be written much more concisely using Cs conditional expression:

    GPIO = GPIObits.GP3 ? 0 : 0b000010; // if GP3 high, clear GP1, else set GP1

    It may seem a little obscure, but this is exactly the type of situation the conditional expression is intended for.

    Complete program

    Here is the complete XC8 code to turn on a LED when a pushbutton is pressed:

    /************************************************************************

    * *

    * Description: Lesson 1, example 4 *

    * *

    * Demonstrates reading a switch *

    * *

    * Turns on LED when pushbutton is pressed *

    * *

    *************************************************************************

    * *

    * Pin assignments: *

    * GP1 = indicator LED *

    * GP3 = pushbutton switch (active low) *

    * *

    ************************************************************************/

    #include

    /***** CONFIGURATION *****/

    // int reset, no code protect, no brownout detect, no watchdog,

    // power-up timer enabled, int RC clock

    __CONFIG(MCLRE_OFF & CP_OFF & CPD_OFF & BOREN_OFF & WDTE_OFF &

    PWRTE_OFF & FOSC_INTRCIO);

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 16

    /***** MAIN PROGRAM *****/

    void main()

    {

    //*** Initialisation

    // configure port

    TRISIO = ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 17

    The algorithm was expressed in pseudo-code as:

    count = 0

    while count < max_samples

    delay sample_time

    if input = required_state

    count = count + 1

    else

    count = 0

    end

    It was implemented in assembler as follows:

    ; wait for button press, debounce by counting:

    db_dn movlw .13 ; max count = 10ms/768us = 13

    movwf db_cnt

    clrf dc1

    dn_dly incfsz dc1,f ; delay 256x3 = 768 us.

    goto dn_dly

    btfsc GPIO,GP3 ; if button up (GP3 high),

    goto db_dn ; restart count

    decfsz db_cnt,f ; else repeat until max count reached

    goto dn_dly

    This code waits for the button to be pressed (GP3 being pulled low), by sampling GP3 every 768 s and

    waiting until it has been low for 13 times in succession approximately 10 ms in total.

    XC8 implementation

    To implement the counting debounce algorithm (above) using XC8, the pseudo-code can be translated

    almost directly into C:

    db_cnt = 0;

    while (db_cnt < 10)

    {

    __delay_ms(1);

    if (GPIObits.GP3 == 0)

    db_cnt++;

    else

    db_cnt = 0;

    }

    where the debounce counter variable has been declared as:

    uint8_t db_cnt; // debounce counter

    Note that, because this variable is only used locally (other functions would never need to access it), it should

    be declared within main().

    Whether you modify this code to make it shorter is largely a question of personal style. Compressed C code,

    using a lot of clever tricks can be difficult to follow.

    But note that the while loop above is equivalent to the following for loop:

    for (db_cnt = 0; db_cnt < 10;)

    {

    __delay_ms(1);

    if (GPIObits.GP3 == 0)

    db_cnt++;

    else

    db_cnt = 0;

    }

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 18

    That suggests restructuring the code into a traditional for loop, as follows:

    for (db_cnt = 0; db_cnt

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 19

    /***** MAIN PROGRAM *****/

    void main()

    {

    uint8_t db_cnt; // debounce counter

    //*** Initialisation

    // configure port

    GPIO = 0; // start with LED off

    sGPIO = 0; // update shadow

    TRISIO = ~(1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 20

    The Gooligum training board already has a pushbutton switch connected to GP2 as shown, but you should

    ensure that jumper JP7 is not closed, so that there is no external pull-up in place.

    If you are using the Microchip demo board, you will need to supply your own pushbutton and connect it

    between GP2 (pin 9 of the 14-pin header) and ground (pin 14 on the header).

    To enable the weak pull-ups, clear the GPPU bit in the OPTION register.

    In the example assembler program from mid-range lesson 3, this was done by:

    bcf OPTION_REG,NOT_GPPU ; enable weak pull-ups (global)

    Unlike the baseline PICs, the weak pull-ups on mid-range devices individually selectable; to enable a pull-

    up, the corresponding bit in the WPU register must be set.

    By default (after a power-on reset) every bit in WPU is set, so to enable a pull-up on only a single pin, the

    remaining bits in WPU must be cleared.

    This was done, in the assembler example in mid-range lesson 3, by:

    movlw 1

  • Gooligum Electronics 2012 www.gooligum.com.au

    Mid-range PIC C, Lesson 1: Basic Digital I/O Page 21

    Comparisons

    Here is the resource usage summary for the toggle a LED using weak pull-ups programs:

    Toggle_LED+WPU

    Although the difference is less pronounced than in the simpler, earlier examples, the C source code continues

    to be less than half the length of the assembly version, while the (unoptimised) code generated by the XC8

    compiler continues to be more than twice the size of the hand-written assembly language version.

    Summary

    Overall, we have seen that basic digital I/O operations can be expressed succinctly using C, leading to

    significantly shorter source code than assembly language, as illustrated by the comparisons we have done in

    this lesson: the C code is typically only half, or less, as long as the corresponding assembler source code.

    It could be argued that, because the C code is significantly shorter than corresponding assembler code, with

    the program structure more readily apparent, C programs are more easily understood, faster to write, and

    simpler to debug, than assembler.

    So why use assembler? One argument is that, because assembler is closer to the hardware, the developer

    benefits from having a greater understanding of exactly what the hardware is doing; there are no unexpected

    or undocumented side effects, no opportunities to be bitten by bugs in built-in or library functions. But that

    argument applies more to other compilers than it does to XC8, which exposes all the PICs registers as variables, and the programmer has to directly modify the register contents in much the same way as would be

    done in assembler.

    However, C compilers usually generate code which occupies more program memory and uses more data

    memory than for corresponding hand-written assembler programs7.

    Since the C compilers consistently use more resources than assembler (for equivalent programs), there comes

    a point, as programs grow, that a C program will not fit into a particular PIC, while the same program would

    have fit if it had been written in assembler. In that case, the choice is to write in assembler, or use a more

    expensive PIC. For a one-off project, a more expensive chip probably makes sense, whereas for volume

    production, using resources efficiently by writing in assembler may be the right choice.

    In the next lesson well see how to use XC8 to configure and access Timer0.

    7 Its a little unfair to draw this conclusion, based on a compiler (XC8) running with optimisation disabled (in Free

    mode). However, this statement remains true when the PICC-Lite compiler (sadly, no longer available), with full

    optimisation enabled, is used to implement these examples.

    Assembler / Compiler

    Source code

    (lines)

    Program memory

    (words)

    Data memory

    (bytes)

    12F629 12F509 12F629 12F509 12F629 12F509

    Microchip MPASM 47 43 40 36 3 3

    XC8 (Free mode) 23 22 97 94 3 3

    Introduction to PIC ProgrammingProgramming Mid-Range PICs in CLesson 1: Basic Digital I/OIntroducing the XC8 CompilerData Types

    Example 1: Turning on an LEDXC8 implementationComplete programBuilding the project

    Example 2: Flashing an LED (20% duty cycle)XC8 implementationComplete program

    Example 3: Flashing an LED (50% duty cycle)The read-modify-write problem revisitedXC8 implementationComplete program

    Comparisons

    Example 4: Reading Digital InputsXC8 implementationComplete program

    Comparisons

    Example 5: Switch DebouncingXC8 implementationComplete program

    Example 6: Internal (Weak) Pull-upsXC8 implementationComparisons

    Summary