PIC24 Tutorial – Part 5 – IO Pins

For this tutorial you’ll need the following:

  • A PIC24 hardware platform – such as our picoTRONICS24 or nanoTRONICS24 microcontroller development boards
  • Microchip’s FREE MPLAB X IDE – make sure you’ve got the latest version – they are updating it all the time
  • Microchip’s FREE C30 or XC16 C Compiler – also make sure you have the latest version
  •  We also assume you have working knowledge of C/C++ – if you don’t have this there a many good C/C++ tutorials availible on the internet.

This tutorial has been designed to work seamlessly with our picoTRONICS24 series development boards. Click here to download the example project for this tutorial.

IO Pins – Background and Basics

All processors have some kind of IO, without this they wouldn’t be very useful. For example it would be very hard to get your processor to execute your program without IO pins to deliver it to the CPU. Generally though, inputs and outputs (IOs) refer to pins on the processor, or microcontroller in our case, that can be used during the programs run-time operation.

IO operations on PIC24 microcontrollers are carried out through the devices pins. These are commonly called IO pins. IO pins have several modes of operation on the PIC24’s (and most PIC devices for that matter), depending on whether the IO pins are used in digital or analogue mode. Users must take into account how they intend to use the IO pins when assigning them, because not all pins can be configured in the same way. Certain pins can only be configured as digital IO pins, while others may be configured as analogue or digital. Then there are pins with fixed functions, such as the chips power supply (VCC and GND pins).

You should read the datasheet carefully before applying anything more than the VCC voltage to the IO pins. Even though PIC24’s are powered from 3.3V, luckily most PIC24 series devices have 5V tolerant IO pins, meaning they can be interfaced to older 5V devices. As we said though, best to check the datasheet first, or risk releasing the magic smoke!

The final thing to consider is the special peripheral functions (such as UART and SPI), unlike most microcontrollers, on PIC24 devices these peripherals aren’t fixed to specific pins. The PIC24 devices support a feature called Peripheral Pin Select (PPS), which allows peripherals to be assigned to different pins, in software. This feature is extremely powerful and one we really enjoy using. We’ll discuss this very useful feature further in a future tutorial.

Before beginning your design and configuring the pins, you must first identify the functions you require (eg analogue pins, digital pins) and assign your external hardware to these pins. For example, if you want to measure a voltage using the PIC24’s ADC, then you’ll to attach this voltage to a pin that supports an analog input. If you want to drive a digital signal then you’ll need to to assign that device to a digital pin and set it to an output. To select the correct pins, you can use the helpful pin diagram from the Microchip datasheet for your chip. An example of the one for the PIC24FJ64GB004, used on our nanoTRONICS24 and picoTRONICS24 microcontroller development boards, is shown below.

pic24fj64gb004 pin layout

You’ll notice that there are several banks of pins, with different prefixes RA (eg. RA0 & RA1) and RB (eg RB0 & RB1). These are the microcontrollers main input and output banks or pins. Depending on your particular chip, there may be further banks of pins, for example RC, RD, RE etc, up to RG. Banks of analog pins have the prefix AN, eg AN0 AN1, etc. You’ll also notice that there are pins with the prefix RP (eg RP0 & RP1 etc). These are the configurable pins for the peripheral modules that I mentioned before – referred to as peripheral pin select (PPS).

Configuring Your Pins

Analogue Or Digital

Before you use the pins on your device you need to configure them correctly. The register AD1PCFGL controls analogue and digital pin selection. As you will have noticed from the datasheet pin diagram, some of the pins have dual analogue and digital functions. For these pins you must specify whether they will be used as analogue or digital pins. This is done by setting the value of AD1PCFGL. Setting a particular pins bit, in the AD1PCFGL register to 1 configures that pin as digital and setting it 0 to analogue. For example if you want all the pins to be digital your assignment would look like this:

[code language=”cpp”]
// Turn off analogue functions on all pins
AD1PCFG = 0xFFFF;
[/code]

If you want them to all be analogue then it would look like this:

[code language=”cpp”]
// Turn on analogue functions on all pins
AD1PCFG = 0x0000;
[/code]

Inputs or Outputs

If a pin is configured as a digital pin, then the pin must be configured as an input or an output. The configuration of input / output is controlled by the TRISx register, where x is the pin bank identifier (eg TRISA controls PORTA). In the example below all PORTA pins are configured as outputs:

[code language=”cpp”]
// Configure all PORTC pins as outputs
TRISA.TRISA = 0x0000;
[/code]

In the example below, all PORTB pins are configured as inputs:

[code language=”cpp”]
// Configure all PORTC pins as outputs
TRISA.TRISA = 0xFFFF;
[/code]

In this final example PORTC 7 (RC7) is configured as an output and PORTC 6 (RC6) as an input:

[code language=”cpp”]
// Configure RC7 as an output
TRISCbits.TRISC7 = 0;
// Configure RC6 as an output
TRISCbits.TRISC6 = 0;
[/code]

Writing and Reading From Pins

Now you’ve configured your pins, you need to read and write from them to make use of them. To write to a pin, the pin needs to be configured as a digital pin (a pin configured as an analogue pin can not be used as an output), next the pin needs to be configured as an output. Finally to write to the pin, you use the pins associated LATx register (where x is the pin bank identifier again). Again this is best illustrated as an example. If you want to write a 1 to RC7, you can use the following code – this example uses the macro assignment for the individual pins:

[code language=”cpp”]
// Set RC7 high
LATCbits.LATC7 = 1;
[/code]

If you want to set a whole port, you can use an assignment like that shown below – which sets PORTC to the value of a pre-set variable:

[code language=”cpp”]
int temp = 0x00CC;
// Set PORTC to the value in the variable temp
LATC = temp;
[/code]

Inputs are just as simple. To read the value of a port, you use the PORTx register, where x is the pin bank identifier. For example to read a value from RC6 you can use the following code:

[code language=”cpp”]
int temp;
// Read the value on RC6 into the variable temp
temp = PORTCbits.RC6;
[/code]

If you want to read a whole port into a variable, you can use an assignment like that shown below:

[code language=”cpp”]
int temp;
// Set PORTC to the value in the variable temp
temp = PORTC;
[/code]

Example Code

The example code for this tutorial can be downloaded as an MPLAB X project here, it is designed to work seamlessly with our picoTRONICS24 series development boards.

The example project code for this project is broken up into two files. The first shown below is called HardwareProfile.h and contains the important user definitions for project.

[code language=”cpp”]
/*
* File: hardwareprofile.h
* Author: Modtronics Australia
* www.modtronicsaustralia.com
*
* Created on 18 June 2013, 2:55 PM
*/

#ifndef HARDWAREPROFILE_H
#define HARDWAREPROFILE_H

#ifdef __cplusplus
extern "C" {
#endif

/****************************************************************************/
/* Useful Macros */
#define BITS2WORD(sfrBitfield) ( *((unsigned int*) &sfrBitfield) )
// Convert a bitfield to a word (unsigned int).
#define BITS2BYTEL(sfrBitfield) ( ((unsigned char*) &sfrBitfield)[0] )
// Return the low byte (as a unsigned char) of a bitfield.
#define BITS2BYTEH(sfrBitfield) ( ((unsigned char*) &sfrBitfield)[1] )
// Return the high byte (as a unsigned char) of a bitfield.

/****************************************************************************/
/* User Configurable Definitions */
// If the following line is uncommented the uC will use the internal oscillator,
// comment the line out to use the external oscillator
#define USE_FRC_CLOCK
// Processor clock frequency selection
#define CLOCK_FREQ 32000000ULL // Use 32MHz clock – default for demo board
//#define CLOCK_FREQ 16000000ULL // Use 16MHz clock
//#define CLOCK_FREQ 8000000ULL // Use 8MHz clock
//#define CLOCK_FREQ 4000000ULL // Use 4MHz clock

#define FOSC CLOCK_FREQ
#define FCY (FOSC/2)

#ifdef __cplusplus
}
#endif

#endif /* HARDWAREPROFILE_H */
[/code]

The second file, main.c, is the main project file. This program turns on the LED on the picoTRONICS24 board and then loops waiting for the user to push the button. Once the user pushes the button the LED is turned off. The user can turn the LED on again by pushing the button a second time and so on. You might also notice that the button checking code contains some delay functions. These are used as part of a very simple “button debounce” function.

[code language=”cpp”]
/*
* File: main.c
* Author: Modtronics Australia
* www.modtronicsaustralia.com
*
* Created on 18 June 2013, 2:55 PM
*/

#include "HardwareProfile.h"
#include <stdio.h>
#include <stdlib.h>

#include <xc.h>
#include <libpic30.h>

// Microcontroller config words (fuses) – get these wrong and nothing will work correctly, if at all!!
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & ICS_PGx1 & FWDTEN_OFF & WINDIS_OFF & FWPSA_PR32 & WDTPS_PS8192)
_CONFIG2(IESO_OFF & FNOSC_FRCPLL & OSCIOFNC_OFF & POSCMOD_NONE & PLL96MHZ_ON & PLLDIV_DIV2 & FCKSM_CSECME & IOL1WAY_OFF)
_CONFIG3(WPFP_WPFP0 & SOSCSEL_IO & WUTSEL_FST & WPDIS_WPDIS & WPCFG_WPCFGDIS & WPEND_WPENDMEM)
_CONFIG4(DSWDTPS_DSWDTPS3 & DSWDTOSC_LPRC & RTCOSC_LPRC & DSBOREN_OFF & DSWDTEN_OFF)

/*
* Function Prototypes
*/
inline void clockSetup( void );

/*
*
*/
int main(int argc, char** argv) {

// Setup the microcontrollers clocking
clockSetup( );

// Turn off analogue functions on all pins
AD1PCFG = 0xFFFF;

// Enable the LED pin as an output
TRISBbits.TRISB9 = 0;

// Enable the switch as an input
TRISBbits.TRISB13 = 1;

// Turn the LED on
LATBbits.LATB9 = 1;

// Loop forever and toggle the LED everytime the button is pressed
while(1)
{
// Wait until the button has been pressed
if (PORTBbits.RB13 == 0)
{
// Invert LED
LATBbits.LATB9 = ~PORTBbits.RB9;
// Delay to await for the button position to stabalised
__delay_ms(400);
// Now wait for the user to release the button
while (PORTBbits.RB13 == 0);
// Delay for a bit more while the button returns to normal
__delay_ms(200);

}
}

return (EXIT_SUCCESS);
}

/*
* This function is responsible for setting up the microcontroller clocking
* and PLL
*/
inline void clockSetup( void )
{

unsigned int pllCounter;
OSCCONBITS OSCCONbitsCopy;

// Copy the current Clock Setup
OSCCONbitsCopy = OSCCONbits;
// Slow output clock down to 4Mhz
CLKDIVbits.CPDIV = 3;
// Enable the PLL – Note: Fuse bits don’t do this
CLKDIVbits.PLLEN = 1;
// Wait for the PLL to stabalise
for (pllCounter = 0; pllCounter < 600; pllCounter++);

// Check to see what clock setup is defined – either internal or external
#ifdef USE_FRC_CLOCK
// Setup the uC to use the internal FRCPLL mode
OSCCONbitsCopy.NOSC = 1;
OSCCONbitsCopy.OSWEN = 1;
#else
// Setup the uC to use the external crystal with the PLL
OSCCONbitsCopy.NOSC = 3;
OSCCONbitsCopy.OSWEN = 1;
#endif

// Switch over to the new clock setup
__builtin_write_OSCCONH( BITS2BYTEH( OSCCONbitsCopy ) );
__builtin_write_OSCCONL( BITS2BYTEL( OSCCONbitsCopy ) );
// Wait for this transfer to take place
while (OSCCONbits.COSC != OSCCONbits.NOSC);
// Setup the DIV bits for the FRC, this values means the config word needs to be: PLLDIV_DIV2
CLKDIVbits.RCDIV0 = 0;

// Setup the PLL divider for the correct clock frequency
if (CLOCK_FREQ == 32000000)
{
CLKDIVbits.CPDIV = 0;
}
else if (CLOCK_FREQ == 16000000)
{
CLKDIVbits.CPDIV = 1;
}
else if (CLOCK_FREQ == 8000000)
{
CLKDIVbits.CPDIV = 2;
}
else if (CLOCK_FREQ == 4000000)
{
CLKDIVbits.CPDIV = 3;
}

// Check that the PLL is enabled again and locked properly to the new setup
CLKDIVbits.PLLEN = 1;
// Note – don’t want to do this check if we are running in the MPLAB X simulator as it won’t work
#ifndef __MPLAB_SIM
while(_LOCK != 1);
#endif

// At this point the PIC24FJ64GB004 clock setup will be complete with the PLL
// enabled and ready for operation with USB2.0
}
[/code]

Comments are closed.