Difference between revisions of "NU32v2: Digital Input and Output"
Andrew Long (talk | contribs) m |
|||
(67 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
'''THIS PAGE REFERS TO A PRE-RELEASE VERSION OF THE NU32 PIC32 DEVELOPMENT BOARD. FOR INFORMATION, SAMPLE CODE, AND VIDEOS RELATED TO THE PRODUCTION VERSION (2016 AND LATER), AND TO THE CORRESPONDING BOOK "EMBEDDED COMPUTING AND MECHATRONICS WITH THE PIC32 MICROCONTROLLER," VISIT [[NU32|THE NU32 PAGE]].''' |
|||
Return to [[Microchip PICs#Using the NU32v2|Using the NU32v2]] |
|||
Digital inputs and outputs (DIO) are the simplest of interfaces between the PIC and other electronics, sensors, and actuators. The PIC32 has many DIO pins, each of which can take one of two states: high or low. |
|||
== |
== Overview == |
||
The PIC32 offers many digital I/O pins, arranged into "ports" A through G. The pins are labeled Rxy, where x is the port letter and y is the pin number. For example, pin 5 of port B is named RB5. Ports B and D are "full" 16-bit ports, with pins 0-15. (Port B can also be used for analog input.) Other ports have a smaller number of pins, not necessarily sequentially numbered; for example, port C has pins 1-4 and 12-15. All pins labeled Rxy can be used for input or output, except for RG2 and RG3, which are input only. For more details on the available pin numbers, see chapter 1 of the Data Sheet. |
|||
==Simple Example== |
|||
This example uses two pins as digital outputs and one pin as digital input. When the switch is pressed, one LED will turn on and the other will turn off. When the switch is not pressed, the opposite LED will turn on and the other one will turn off. |
|||
Each pin configured as an output can be configured to be a typical 0 or 3.3 V output (since our PIC is powered by 3.3 V), or to be "open drain." An open drain output can take one of two states: 0 V or "floating" (disconnected). An open drain output allows you to attach an external "pull-up" resistor from the output to a positive voltage you choose, e.g., 5 V. Then when the output is left floating by the PIC, the external pull-up resistor will pull the voltage up to 5 V. A reasonable resistance for the pull-up is 1 K to 10 K ohms. |
|||
/******************************************************************** |
|||
Super simple Digital Output and Digital Input. |
|||
An input pin will read low if the input voltage is close to zero, and it will read high if it is close to 3.3 V. Many input pins will tolerate voltages up to 5 V; see the figure showing the PIC pins near the beginning of the data sheet. Some input pins, those that can also be used for "change notification" (labeled CNy, y = 0..21), can be configured to have an internal pull-up resistor to 3.3 V. If configured this way, the input will read "high" if it is disconnected (left floating). Otherwise, if an input pin is not connected to anything, we can't be certain what the input will read. |
|||
********************************************************************/ |
|||
== Details == |
|||
#include "HardwareProfile.h" |
|||
The functions of the DIO pins are controlled by special function registers (SFRs). Each of these SFRs has 32 bits on the PIC32, but for many, not all 32 are relevant. Depending on whether these bits are set to 0 or 1, different actions or settings are performed: |
|||
// NOTE THAT BECAUSE WE USE THE BOOTLOADER, NO CONFIGURATION IS NECESSARY |
|||
// THE BOOTLOADER PROJECT ACTUALLY CONTROLS ALL OF OUR CONFIG BITS |
|||
* '''AD1PCFG''': This SFR determines which of the 16 pins with an analog input function (port B) is treated as an analog input or a DIO pin. The 16 most significant bits (high bits) AD1PCFG<31:16> are ignored, but AD1PCFG<15:0> determine which of the 16 pins is treated as an analog input. 0 indicates analog input, and 1 indicates DIO. So if the bit AD1PCFG<7> is 0, then the pin with the function AN7 is an analog input; if AD1PCFG<7> is 1, then it is a DIO. AD1PCFG resets to 0x0000, so all 16 pins of port B are treated as analog inputs by default. To make AN15 and AN1 analog inputs and the rest DIO, we can use the C command <tt>AD1PCFG = 0x7FFD</tt>. To make AN15 and AN1 analog inputs but leave the other settings unchanged, we can use the C command <tt>AD1PCFG &= 0x7FFD</tt>. Note we only have to specify 16 bits, or 4 hex digits, as the 16 most significant bits have no role. Instead of acting on the entire SFR, we could instead address an individual bit, using <tt>AD1PCFGbits.PCFG15 = 1</tt>, for example, to set the value of a single bit. |
|||
// Define constants for PINS D1 - D2 and C1 |
|||
* '''TRISx''': This SFR determines which of the DIO pins of port x are inputs and which are outputs. For example, <tt>TRISBbits.TRISB5 = 0</tt> makes RB5 an output (0 = Output), <tt>TRISBbits.TRISB5 = 1</tt> makes RB5 an input (1 = Input), <tt>TRISB &= FF00</tt> makes RB0..RB7 outputs while leaving the others unchanged, <tt>TRISB |= FF00</tt> makes RB8..RB15 inputs while leaving the others unchanged, and <tt>TRISB = FF00</tt> makes RB0..RB7 outputs and RB8..RB15 inputs. |
|||
#define PIN_D1 LATDbits.LATD1 // Digital Output |
|||
#define PIN_D2 LATDbits.LATD2 // Digital Output |
|||
* '''LATx''': A write to the latch chooses the output for pins configured as digital outputs. For example, <tt>LATB = 0x00FF</tt> makes pins RB0..RB7 high and pins RB8..RB15 low. An individual pin can be referenced using LATBbits.LATB13, for example. |
|||
#define PIN_C1 PORTCbits.RC1 // Digital Input |
|||
* '''PORTx''': <tt>val = PORTB</tt>, where val is an unsigned int, returns the current digital inputs for DIO pins configured as inputs. PORTBbits.RB6 returns the digital input for RB6. |
|||
int main(void) |
|||
{ |
|||
* '''ODCx''': <tt>ODCB = 0x00FF</tt> configures RB0..RB7 as open drain, and pins RB8..RB15 as typical buffered output. An individual pin can be referenced as ODCBbits.ODCB8, for example. |
|||
// Configure the proper PB frequency and the number of wait states |
|||
* '''CNPUE''': The relevant bits of this SFR are CNPUE<21:0>, and they apply only to the 22 change notification pins CN0..CN21. If CNPUEbits.CNPUE2 = 1, then the pin with CN2 function has the internal pull-up resistor enabled, and if CNPUEbits.CNPUE2 = 0, then it is disabled. The command <tt>CNPUE = 0xFFFFF</tt> enables the internal pull-up resistors for all CN pins. The default has the pull-ups disabled. |
|||
SYSTEMConfigPerformance(SYS_FREQ); |
|||
Finally, each SFR (e.g., TRISB, LATD, PORTA, ODCC, etc.) has three more registers associated with it: a SET register, a CLR register, and an INV register. Writing to these registers allows atomic (single cycle) updating of the SFR contents, which is faster than reading the current value of the SFR, modifying it, and writing back to the SFR. SET means that bits are set (made equal to 1), CLR means that bits are cleared (made equal to 0), and INV means that bits are inverted. So <tt>TRISBCLR = 0x0110</tt> will clear bits 4 and 8 and <tt>TRISBSET = 0x0110</tt> will set bits 4 and 8. All other bits (with a 0 on the right-hand side of the = sign) are unchanged. For example, <tt>LATBSET = 0x0001</tt> has the same effect as <tt>LATB |= 0x0001</tt>, but the former is faster. |
|||
// LATX sets the value (0 or 1) for each pin of port X |
|||
// LATXbits.LATXY sets the value (0 or 1) for pin Y on port X |
|||
Other SFRs and their functions are described in Chapter 12 of the Reference Manual. The C names of the SFRs, and the structures with their bit references, can be found in the file p32mx795f512l.h, which is under pic32-libs/include/proc. The SET, CLR, and INV registers are described in Section 2 of the Reference Manual. |
|||
// PORTX gets the value (0 or 1) for each pin of port X |
|||
// PORTXbits.RXY gets the value (0 or 1) for pin Y on port X |
|||
<!-- |
|||
// TRISX declares the pin as either digital output (0) or digital input (1) |
|||
A block diagram of a typical input/output port is shown below, taken from the Reference Manual. |
|||
// use &= for setting the inputs |
|||
// use |= for setting outputs |
|||
[[Image:pic-gpio-module.png|center]] |
|||
// Initialize Pins D1 and D2 output. |
|||
// Initialize pins D1 and D2 as high |
|||
// Need to set TRIS bits 1 and 2 to low for output |
|||
OK, this looks pretty complicated, so let's go through it systematically. First, there may be some symbols you are unfamiliar with in this figure. These are all digital logic. Consult the figure below: |
|||
LATD |= 0x0006; TRISD &= 0xFFF9; // initialize |
|||
<table border=1 cellpadding=5 align=center> |
|||
// Initialize Pin C1 as digital input |
|||
<tr> |
|||
// Need to set TRIS bit 1 to high for input |
|||
<td>'''Symbol'''</td> |
|||
// note that all pins are initialized as digital input |
|||
<td>'''Name'''</td> |
|||
// except for the analog and JTAG pins, but here's how you would do it |
|||
<td>'''Description'''</td> |
|||
TRISCbits.TRISC1 = 1; |
|||
</tr> |
|||
<tr> |
|||
// Set all analog pins to be digital I/O |
|||
<td>[[Image:pic-hysteresis-buffer.png]]</td> |
|||
// This line of code only needs to be used if your pins are Analog Input (B port) |
|||
<td>Schmitt trigger</td> |
|||
AD1PCFG = 0xFFFF; |
|||
<td>The output (on the left) is digital, high or low, and matches the input if it is high or low. Because the input may be noisy and take values in between clearly low and clearly high, the gate implements "hysteresis." This means that if the output is currently low, the input must go quite high before the output will switch to high, and if the output is high, the signal must go quite low before the output will switch.</td> |
|||
</tr> |
|||
while(1) |
|||
<tr> |
|||
{ |
|||
<td>[[Image:pic-tristate-buffer.png]]</td> |
|||
// Toggle LEDS based on digital input |
|||
<td>tri-state buffer</td> |
|||
if(PIN_C1) |
|||
<td>If the input coming in from the top is high, the output matches the input. If it is low, the output is high impedance (disconnected).</td> |
|||
{ |
|||
</tr> |
|||
PIN_D1 = 1; |
|||
<tr> |
|||
PIN_D2 = 0; |
|||
<td>[[Image:pic-multiplexer.png]]</td> |
|||
} |
|||
<td>multiplexer</td> |
|||
else |
|||
<td>Depending on the input coming in from the top, one or the other inputs is passed through to the output.</td> |
|||
{ |
|||
</tr> |
|||
PIN_D1 = 0; |
|||
<tr> |
|||
PIN_D2 = 1; |
|||
<td>[[Image:pic-dflipflop.png]]</td> |
|||
} |
|||
<td>D flip-flop</td> |
|||
} |
|||
<td>If the D flip-flop is enabled (bottom input), then the input at D is latched through to the output Q when the clock CK goes high. The output only changes on a rising clock edge, and does not change if the flip-flop is not enabled. </td> |
|||
} |
|||
</tr> |
|||
<tr> |
|||
<td>[[Image:pic-logic-or.png]]</td> |
|||
<td>logic OR</td> |
|||
<td>Outputs the logical OR of the two inputs.</td> |
|||
</tr> |
|||
<tr> |
|||
<td>[[Image:pic-invert-and.png]]</td> |
|||
<td>logic AND (with inverted input)</td> |
|||
<td>Outputs the logical AND of the two inputs (where one input is inverted, as indicated by the circle).</td> |
|||
</tr> |
|||
</table> |
|||
--> |
|||
== Library Functions == |
|||
The peripheral library offers a number of function calls to help you use the DIO pins. These functions have names such as <tt>PORTRead</tt>, <tt>PORTSetPinsDigitalIn</tt>, <tt>PORTSetPinsDigitalOut</tt>, etc. C source code can be found in pic32-libs/peripheral/ports/source/*.c, and .h header files can be found in pic32-libs/include/peripheral/ports.h. Documentation can be found in your MPLAB C32's doc directory, in Microchip-PIC32MX-Peripheral-Library.chm. The DIO functions are simple enough, however, that we will usually just set the appropriate SFRs manually, as described above and demonstrated in the samples below. |
|||
== Sample Code == |
|||
This example uses two of the PIC32 pins as digital outputs and one pin as digital input. In particular, it uses pins RG12 and RG13 as outputs, since these are connected to LEDs on the NU32v2 board (by the NU32v2 circuit schematic, the associated LED is on when the pin is low). It configures RG6 as an input for a button press. If RG6 is high, the LEDs will be off. If RG6 is low, the LEDs will be on. Let's assume RG6 is low when the button is pressed (as it is with the NU32v2 and your button in the breadboard). |
|||
<pre> |
|||
/* |
|||
* Simple digital input/output. |
|||
*/ |
|||
#include <plib.h> |
|||
// No need to configure the configuration bits controlling clock speed, etc.; |
|||
// these definitions are in the bootloader. |
|||
// Lines below could go into a separate .h file, since they're the same |
|||
// for all projects using the NU32v2 and the NU32v2 bootloader. |
|||
#define LED0 LATGbits.LATG12 |
|||
#define LED1 LATGbits.LATG13 |
|||
#define SW PORTGbits.RG6 |
|||
#define SYS_FREQ 80000000 // 80 MHz |
|||
int main(void) { |
|||
// Turn on the pre-fetch cache, choose the maximum possible peripheral |
|||
// bus frequency, and choose minimum flash wait states. Could be done in |
|||
// bootloader. See pic32-libs/include/peripheral/system.h. |
|||
SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL); |
|||
AD1PCFG = 0xFFFF; // sets all port B to DIO. Only needed if using port B. |
|||
// Initialize LEDs as output, switch as input. Could be done in bootloader. |
|||
// Note we don't technically have to set switch as an input, since it's the |
|||
// default, but let's do it in case someone inserts code before this line. |
|||
TRISGCLR = 0x3000; TRISGSET = 0x0040; |
|||
while(1) { |
|||
while(!SW) { // if button is pressed, turn LEDs on |
|||
LED0 = 0; LED1 = 0; |
|||
} |
|||
LED0 = 1; LED1 = 1; // when button is released turn LEDs off |
|||
} |
|||
return 0; |
|||
} |
|||
</pre> |
|||
Now, for fun, let's make a couple of small changes to the code. |
|||
<pre> |
|||
/* |
|||
* Simple digital input/output. |
|||
*/ |
|||
#include <plib.h> |
|||
#define LED0 LATGbits.LATG12 |
|||
#define LED1 LATGbits.LATG13 |
|||
#define SW PORTGbits.RG6 |
|||
#define SYS_FREQ 80000000 // 80 MHz |
|||
int main(void) { |
|||
int i; |
|||
SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL); |
|||
TRISGCLR = 0x3000; TRISGSET = 0x0040; |
|||
while(1) { |
|||
while(!SW) { |
|||
while(!SW) { |
|||
LED0 = 0; LED1 = 0; // LED is on |
|||
} |
|||
for(i=0; i<10000000; i++); // 10 million |
|||
} |
|||
LED0 = 1; LED1 = 1; // LED is off |
|||
} |
|||
return 0; |
|||
} |
|||
</pre> |
|||
We got rid of most of the comments and the unnecessary AD1PCFG command, defined a counter i, and added another while(!SW) loop inside the original while(!SW) loop. When the button is pressed, we enter this innermost while loop (with the LEDs on) until the button is released. Then we enter a for loop which does nothing but set i to 0 and then increment it 10 million times. This is simply a delay. After we exit the for loop, we turn the LEDs off. |
|||
Try running this. When I did it, I got a delay of about 1.5 seconds between the time I released the button until the LEDs turned off. Calculating the number of instructions that must have been calculated during each loop, I get (1.5 seconds)*(80 M machine instructions/sec)/(10 M loops) = 12 machine instructions/loop. ([[NU32v2: Digital I/O Assembly Code|You can see what we get when we disassemble the code and look at the assembly instructions.]]) |
|||
Now let's simply comment out the SYSTEMConfig line |
|||
<pre> |
|||
// SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL); |
|||
</pre> |
|||
and try running again. This time I got a delay of around 11 seconds! The SYSTEMConfig command, when called with the system clock frequency and SYS_CFG_ALL, does three things: it turns on the pre-fetch cache, it determines the minimum number of instructions the CPU must wait between requesting data or instructions from flash memory and then actually using it, and it sets the peripheral bus frequency to the maximum possible (which is the lesser of 80 MHz and the system clock frequency). We can also try these optimizations individually. Replace the SYSTEMConfig line with |
|||
<pre> |
|||
SYSTEMConfig(SYS_FREQ, SYS_CFG_PB_BUS); |
|||
</pre> |
|||
and |
|||
<pre> |
|||
SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES); |
|||
</pre> |
|||
and |
|||
<pre> |
|||
SYSTEMConfig(SYS_FREQ, SYS_CFG_PCACHE); |
|||
</pre> |
|||
and try the program again with each. If you got the same results I did, you got a delay of about 11 seconds, 4 seconds, and just under 2 seconds, respectively, for the three cases. This indicates that just configuring the peripheral bus does nothing for us. This makes sense: in our bootloader, we already set both the system clock and the peripheral bus clock to 80 MHz, which is the maximum possible frequency for both. On the other hand, configuring the wait states gave us nearly a three times performance improvement. This means that this optimization reduced (from the default) the number of cycles the CPU waits for information requested from the flash memory. Finally, configuring the pre-fetch cache alone essentially gave us the entire speed-up. The pre-fetch cache stores recent instructions fetched from flash (program) memory and keeps them ready for use without flash wait states, and when possible, "runs ahead" to get future instructions so they are ready for the CPU without the flash wait cycles. |
|||
== More Information == |
|||
More information on the I/O ports can be found in Chapter 12 of the Data Sheet and Chapter 12 of the Reference Manual. Some special function registers (SFRs) are listed in Chapter 4 of the Data Sheet. The SET, CLR, and INV registers are described in Section 2 of the Reference Manual. |
|||
You can learn more about the system optimizations and pre-fetch cache by looking at the source code in pic32-libs/include/peripheral/{pcache.h,system.h}, the peripheral library documentation, section 9 of the Data Sheet, and Section 4 of the Reference Manual. |
|||
To gain a better appreciation of what is happening when you program your PIC32, take a look at [[NU32v2: A Detailed Look at Programming the PIC32|NU32v2: A Detailed Look at Programming the PIC32]]. |
Latest revision as of 05:22, 16 January 2016
THIS PAGE REFERS TO A PRE-RELEASE VERSION OF THE NU32 PIC32 DEVELOPMENT BOARD. FOR INFORMATION, SAMPLE CODE, AND VIDEOS RELATED TO THE PRODUCTION VERSION (2016 AND LATER), AND TO THE CORRESPONDING BOOK "EMBEDDED COMPUTING AND MECHATRONICS WITH THE PIC32 MICROCONTROLLER," VISIT THE NU32 PAGE.
Digital inputs and outputs (DIO) are the simplest of interfaces between the PIC and other electronics, sensors, and actuators. The PIC32 has many DIO pins, each of which can take one of two states: high or low.
Overview
The PIC32 offers many digital I/O pins, arranged into "ports" A through G. The pins are labeled Rxy, where x is the port letter and y is the pin number. For example, pin 5 of port B is named RB5. Ports B and D are "full" 16-bit ports, with pins 0-15. (Port B can also be used for analog input.) Other ports have a smaller number of pins, not necessarily sequentially numbered; for example, port C has pins 1-4 and 12-15. All pins labeled Rxy can be used for input or output, except for RG2 and RG3, which are input only. For more details on the available pin numbers, see chapter 1 of the Data Sheet.
Each pin configured as an output can be configured to be a typical 0 or 3.3 V output (since our PIC is powered by 3.3 V), or to be "open drain." An open drain output can take one of two states: 0 V or "floating" (disconnected). An open drain output allows you to attach an external "pull-up" resistor from the output to a positive voltage you choose, e.g., 5 V. Then when the output is left floating by the PIC, the external pull-up resistor will pull the voltage up to 5 V. A reasonable resistance for the pull-up is 1 K to 10 K ohms.
An input pin will read low if the input voltage is close to zero, and it will read high if it is close to 3.3 V. Many input pins will tolerate voltages up to 5 V; see the figure showing the PIC pins near the beginning of the data sheet. Some input pins, those that can also be used for "change notification" (labeled CNy, y = 0..21), can be configured to have an internal pull-up resistor to 3.3 V. If configured this way, the input will read "high" if it is disconnected (left floating). Otherwise, if an input pin is not connected to anything, we can't be certain what the input will read.
Details
The functions of the DIO pins are controlled by special function registers (SFRs). Each of these SFRs has 32 bits on the PIC32, but for many, not all 32 are relevant. Depending on whether these bits are set to 0 or 1, different actions or settings are performed:
- AD1PCFG: This SFR determines which of the 16 pins with an analog input function (port B) is treated as an analog input or a DIO pin. The 16 most significant bits (high bits) AD1PCFG<31:16> are ignored, but AD1PCFG<15:0> determine which of the 16 pins is treated as an analog input. 0 indicates analog input, and 1 indicates DIO. So if the bit AD1PCFG<7> is 0, then the pin with the function AN7 is an analog input; if AD1PCFG<7> is 1, then it is a DIO. AD1PCFG resets to 0x0000, so all 16 pins of port B are treated as analog inputs by default. To make AN15 and AN1 analog inputs and the rest DIO, we can use the C command AD1PCFG = 0x7FFD. To make AN15 and AN1 analog inputs but leave the other settings unchanged, we can use the C command AD1PCFG &= 0x7FFD. Note we only have to specify 16 bits, or 4 hex digits, as the 16 most significant bits have no role. Instead of acting on the entire SFR, we could instead address an individual bit, using AD1PCFGbits.PCFG15 = 1, for example, to set the value of a single bit.
- TRISx: This SFR determines which of the DIO pins of port x are inputs and which are outputs. For example, TRISBbits.TRISB5 = 0 makes RB5 an output (0 = Output), TRISBbits.TRISB5 = 1 makes RB5 an input (1 = Input), TRISB &= FF00 makes RB0..RB7 outputs while leaving the others unchanged, TRISB |= FF00 makes RB8..RB15 inputs while leaving the others unchanged, and TRISB = FF00 makes RB0..RB7 outputs and RB8..RB15 inputs.
- LATx: A write to the latch chooses the output for pins configured as digital outputs. For example, LATB = 0x00FF makes pins RB0..RB7 high and pins RB8..RB15 low. An individual pin can be referenced using LATBbits.LATB13, for example.
- PORTx: val = PORTB, where val is an unsigned int, returns the current digital inputs for DIO pins configured as inputs. PORTBbits.RB6 returns the digital input for RB6.
- ODCx: ODCB = 0x00FF configures RB0..RB7 as open drain, and pins RB8..RB15 as typical buffered output. An individual pin can be referenced as ODCBbits.ODCB8, for example.
- CNPUE: The relevant bits of this SFR are CNPUE<21:0>, and they apply only to the 22 change notification pins CN0..CN21. If CNPUEbits.CNPUE2 = 1, then the pin with CN2 function has the internal pull-up resistor enabled, and if CNPUEbits.CNPUE2 = 0, then it is disabled. The command CNPUE = 0xFFFFF enables the internal pull-up resistors for all CN pins. The default has the pull-ups disabled.
Finally, each SFR (e.g., TRISB, LATD, PORTA, ODCC, etc.) has three more registers associated with it: a SET register, a CLR register, and an INV register. Writing to these registers allows atomic (single cycle) updating of the SFR contents, which is faster than reading the current value of the SFR, modifying it, and writing back to the SFR. SET means that bits are set (made equal to 1), CLR means that bits are cleared (made equal to 0), and INV means that bits are inverted. So TRISBCLR = 0x0110 will clear bits 4 and 8 and TRISBSET = 0x0110 will set bits 4 and 8. All other bits (with a 0 on the right-hand side of the = sign) are unchanged. For example, LATBSET = 0x0001 has the same effect as LATB |= 0x0001, but the former is faster.
Other SFRs and their functions are described in Chapter 12 of the Reference Manual. The C names of the SFRs, and the structures with their bit references, can be found in the file p32mx795f512l.h, which is under pic32-libs/include/proc. The SET, CLR, and INV registers are described in Section 2 of the Reference Manual.
Library Functions
The peripheral library offers a number of function calls to help you use the DIO pins. These functions have names such as PORTRead, PORTSetPinsDigitalIn, PORTSetPinsDigitalOut, etc. C source code can be found in pic32-libs/peripheral/ports/source/*.c, and .h header files can be found in pic32-libs/include/peripheral/ports.h. Documentation can be found in your MPLAB C32's doc directory, in Microchip-PIC32MX-Peripheral-Library.chm. The DIO functions are simple enough, however, that we will usually just set the appropriate SFRs manually, as described above and demonstrated in the samples below.
Sample Code
This example uses two of the PIC32 pins as digital outputs and one pin as digital input. In particular, it uses pins RG12 and RG13 as outputs, since these are connected to LEDs on the NU32v2 board (by the NU32v2 circuit schematic, the associated LED is on when the pin is low). It configures RG6 as an input for a button press. If RG6 is high, the LEDs will be off. If RG6 is low, the LEDs will be on. Let's assume RG6 is low when the button is pressed (as it is with the NU32v2 and your button in the breadboard).
/* * Simple digital input/output. */ #include <plib.h> // No need to configure the configuration bits controlling clock speed, etc.; // these definitions are in the bootloader. // Lines below could go into a separate .h file, since they're the same // for all projects using the NU32v2 and the NU32v2 bootloader. #define LED0 LATGbits.LATG12 #define LED1 LATGbits.LATG13 #define SW PORTGbits.RG6 #define SYS_FREQ 80000000 // 80 MHz int main(void) { // Turn on the pre-fetch cache, choose the maximum possible peripheral // bus frequency, and choose minimum flash wait states. Could be done in // bootloader. See pic32-libs/include/peripheral/system.h. SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL); AD1PCFG = 0xFFFF; // sets all port B to DIO. Only needed if using port B. // Initialize LEDs as output, switch as input. Could be done in bootloader. // Note we don't technically have to set switch as an input, since it's the // default, but let's do it in case someone inserts code before this line. TRISGCLR = 0x3000; TRISGSET = 0x0040; while(1) { while(!SW) { // if button is pressed, turn LEDs on LED0 = 0; LED1 = 0; } LED0 = 1; LED1 = 1; // when button is released turn LEDs off } return 0; }
Now, for fun, let's make a couple of small changes to the code.
/* * Simple digital input/output. */ #include <plib.h> #define LED0 LATGbits.LATG12 #define LED1 LATGbits.LATG13 #define SW PORTGbits.RG6 #define SYS_FREQ 80000000 // 80 MHz int main(void) { int i; SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL); TRISGCLR = 0x3000; TRISGSET = 0x0040; while(1) { while(!SW) { while(!SW) { LED0 = 0; LED1 = 0; // LED is on } for(i=0; i<10000000; i++); // 10 million } LED0 = 1; LED1 = 1; // LED is off } return 0; }
We got rid of most of the comments and the unnecessary AD1PCFG command, defined a counter i, and added another while(!SW) loop inside the original while(!SW) loop. When the button is pressed, we enter this innermost while loop (with the LEDs on) until the button is released. Then we enter a for loop which does nothing but set i to 0 and then increment it 10 million times. This is simply a delay. After we exit the for loop, we turn the LEDs off.
Try running this. When I did it, I got a delay of about 1.5 seconds between the time I released the button until the LEDs turned off. Calculating the number of instructions that must have been calculated during each loop, I get (1.5 seconds)*(80 M machine instructions/sec)/(10 M loops) = 12 machine instructions/loop. (You can see what we get when we disassemble the code and look at the assembly instructions.)
Now let's simply comment out the SYSTEMConfig line
// SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL);
and try running again. This time I got a delay of around 11 seconds! The SYSTEMConfig command, when called with the system clock frequency and SYS_CFG_ALL, does three things: it turns on the pre-fetch cache, it determines the minimum number of instructions the CPU must wait between requesting data or instructions from flash memory and then actually using it, and it sets the peripheral bus frequency to the maximum possible (which is the lesser of 80 MHz and the system clock frequency). We can also try these optimizations individually. Replace the SYSTEMConfig line with
SYSTEMConfig(SYS_FREQ, SYS_CFG_PB_BUS);
and
SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES);
and
SYSTEMConfig(SYS_FREQ, SYS_CFG_PCACHE);
and try the program again with each. If you got the same results I did, you got a delay of about 11 seconds, 4 seconds, and just under 2 seconds, respectively, for the three cases. This indicates that just configuring the peripheral bus does nothing for us. This makes sense: in our bootloader, we already set both the system clock and the peripheral bus clock to 80 MHz, which is the maximum possible frequency for both. On the other hand, configuring the wait states gave us nearly a three times performance improvement. This means that this optimization reduced (from the default) the number of cycles the CPU waits for information requested from the flash memory. Finally, configuring the pre-fetch cache alone essentially gave us the entire speed-up. The pre-fetch cache stores recent instructions fetched from flash (program) memory and keeps them ready for use without flash wait states, and when possible, "runs ahead" to get future instructions so they are ready for the CPU without the flash wait cycles.
More Information
More information on the I/O ports can be found in Chapter 12 of the Data Sheet and Chapter 12 of the Reference Manual. Some special function registers (SFRs) are listed in Chapter 4 of the Data Sheet. The SET, CLR, and INV registers are described in Section 2 of the Reference Manual.
You can learn more about the system optimizations and pre-fetch cache by looking at the source code in pic32-libs/include/peripheral/{pcache.h,system.h}, the peripheral library documentation, section 9 of the Data Sheet, and Section 4 of the Reference Manual.
To gain a better appreciation of what is happening when you program your PIC32, take a look at NU32v2: A Detailed Look at Programming the PIC32.