0% found this document useful (0 votes)
264 views

Code Examples For dsPIC30F4011

Uploaded by

Minh Vu
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
264 views

Code Examples For dsPIC30F4011

Uploaded by

Minh Vu
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 28

//

// Simple Timer 1 interrupt example for dsPIC30F4011


// Written by Ted Burke - Last updated 12-4-2012
//

#include <libpic30.h>
#include <p30f4011.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

// Function prototype for timer 1 ISR


void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void);

int main()
{
// Configure RD0 and RD1 as outputs
TRISD = 0b1100;

// Configure Timer 1.
// PR1 and TCKPS are set to call interrupt every 500ms.
// Period = PR1 * prescaler * Tcy = 58594 * 256 * 33.33ns = 500ms
T1CON = 0; // Clear Timer 1 configuration
T1CONbits.TCKPS = 3; // Set timer 1 prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
PR1 = 58594; // Set Timer 1 period (max value is 65535)
_T1IP = 1; // Set Timer 1 interrupt priority
_T1IF = 0; // Clear Timer 1 interrupt flag
_T1IE = 1; // Enable Timer 1 interrupt
T1CONbits.TON = 1; // Turn on Timer 1

// Main loop just flashes LED on RD0 at approx 2Hz.


// Meanwhile, Timer 1 ISR flashes LED on RD1 at 1Hz.
while(1)
{
_LATD0 = 1;
__delay32(7500000);
_LATD0 = 0;
__delay32(7500000);
}

return 0;
}

// Timer 1 interrupt service routine toggles LED on RD1


void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
{
// Clear Timer 1 interrupt flag
_T1IF = 0;

// Toggle LED on RD1


_LATD1 = 1 - _LATD1;
}

//
// dsPIC30F4011 example - Control speed of DC motor
// Written by Ted Burke, Last updated 29-9-2015
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

int main(void)
{
// Make RD0 and RD1 digital outputs
TRISD = 0b1100;

// Configure PWM
// PWM period = PTPER * prescale * Tcy = 9470 * 64 * 33.33ns = 20ms
_PMOD1 = 0; // PWM channel 1 mode: 0 for complementary, 1 for independent
_PEN1H = 1; // PWM1H pin enable: 1 to enable, 0 to disable
_PTCKPS = 3; // PWM prescaler setting: 0=1:1, 1=1:4, 2=1:16, 3=1:64
PTPER = 9470; // Set PWM time base period to 20ms (15-bit value)
PDC1 = 0; // 0% duty cycle on channel 1 (16-bit value)
_PTEN = 1; // Enable PWM time base to start generating pulses

// Control motor
while(1)
{
// Forward slow for 2 seconds
PDC1 = 0.25 * 2 * PTPER; // 25% duty cycle
LATD = 0b01; // forwards
__delay32(60000000);

// Forward fast for 2 seconds


PDC1 = 0.75 * 2 * PTPER; // 75% duty cycle
LATD = 0b01; // forwards
__delay32(60000000);
// Stop for 2 seconds
LATD = 0b00; // stop
__delay32(60000000);
}
}

Digital i/o

This program uses one digital output (RD0) and one digital input (RD1). When RD1 is
high, an LED on RD0 blinks fast; when RD1 is low, the LED blinks slow.

The function __delay32() (defined in “libpic30.h”) creates a delay of the specified


number of instruction cycles. In this example, the dsPIC is running at 30 MIPS (million
instructions per second) so, for example, “__delay32(15000000)” creates a delay of half
a second. The number “32” in the function name refers to the fact the that specified
number of instruction cycles is a 32 bit unsigned value, allowing very long delays to be
generated with a single call.
The same build script as for the simple example above should be used.

1 //
2 // dsPIC30F4011 example - blink an LED fast or slow
3 // Written by Ted Burke, Last updated 6-3-2013
4 //
5
6 #include <xc.h>
7 #include <libpic30.h>
8
// Configuration settings
9
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
10
_FWDT(WDT_OFF); // Watchdog timer off
11
_FBORPOR(MCLR_DIS); // Disable reset pin
12
13 int main(void)
14 {
15 // Make RD0 a digital output
16 _TRISD0 = 0;
17
18 // Make RD1 a digital input
19 _TRISD1 = 1;
20
21 // Blink LED on RD0
22 while(1)
{
23
24
25 // Toggle RD0 (turn LED on or off)
26 _LATD0 = 1 - _LATD0;
27
28 // Either delay for 0.1 or 0.5 seconds
29 if (_RD1 == 0) __delay32(3000000); // 0.1 second delay
30 else __delay32(15000000); // 0.5 second delay
31 }
32 }

Analog input

1 //
2 // dsPIC30F4011 example - read an analog input
3 // Written by Ted Burke, Last updated 6-3-2013
4 //
5
6 #include <xc.h>
7 #include <libpic30.h>
8
// Configuration settings
9
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
10
_FWDT(WDT_OFF); // Watchdog timer off
11
_FBORPOR(MCLR_DIS); // Disable reset pin
12
13 unsigned int read_analog_channel(int channel);
14
15 int main(void)
16 {
17 int voltage;
18
19 // Make RD0 a digital output
20 _TRISD0 = 0;
21
22 // Configure AN0-AN8 as analog inputs
23 ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
24 ADCON1bits.ADON = 1; // Turn ADC ON
25
26 // Blink LED on RD0
27 while(1)
28 {
29 // Toggle RD0 (turn LED on or off)
_LATD0 = 1 - _LATD0;
30
31
// Length of delay depends on AN0 pin voltage
32
33
34
35 voltage = read_analog_channel(0);
36 __delay32(voltage * 30000L);
37 }
38 }
39
40 // This function reads a single sample from the specified
41 // analog input. It should take less than 5us when the
42 // microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
43
// returned is between 0 and 1023 inclusive.
44
unsigned int read_analog_channel(int channel)
45
{
46 ADCHS = channel; // Select the requested channel
47 ADCON1bits.SAMP = 1; // Start sampling
48 __delay32(30); // 1us delay @ 30 MIPS
49 ADCON1bits.SAMP = 0; // Start Converting
50 while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
51 return ADCBUF0;
52 }

Serial output

1 TO BE WRITTEN

Serial input

1 TO BE WRITTEN

Using a timer

1 TO BE WRITTEN

Using a timer interrupt

1 TO BE WRITTEN

Pulse Width Modulation

1 TO BE WRITTEN

dsPIC30F4011 Super Example


This is my dsPIC30F4011 “super example”. When I’m writing a program for this handy
microcontroller, I often use this template code as my starting point and trim out
whatever I don’t need.

This example includes the following features (further details below):

 Digital output
 Analog input
 PWM output
 Serial output via UART
 Serial input via UART
 Timer 1 interrupt
1 //
2 // dsPIC30F4011 Super Example
3 // Written by Ted Burke
4 // Last updated 27-8-2012
5 //
6 // Featuring digital i/o, analog input,
// PWM output, UART Tx/Rx, Timer 1 ISR.
7
//
8
9
#include <xc.h>
10
#include <stdio.h>
11 #include <libpic30.h>
12
13 // Configuration settings
14 _FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
15 _FWDT(WDT_OFF); // Watchdog timer off
16 _FBORPOR(MCLR_DIS); // Disable reset pin
17
18 // Function prototypes
19 void setup();
20 unsigned int read_analog_channel(int n);
21
22 // Function prototype for Timer 1 interrupt service routine
23 void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void);
24
25 int main()
26 {
27 int voltage;
char c;
28
29
// Set up digital i/o, analog input, PWM, UART and interrupt
30
setup();
31
32 while(1)
33
34 {
35 // Read a voltage from AN0 and print it via serial port
36 voltage = read_analog_channel(0);
37 printf("Voltage = %d\r\n", voltage);
38
39 // Toggle LED on RD0
40 _LATD0 = 1 - _LATD0;
41
// Check if any characters were received via UART
42
if (U1STAbits.URXDA == 1)
43
{
44
c = U1RXREG;
45 if (c == '2') _LATD2 = 1 - _LATD2;
46 if (c == '3') _LATD3 = 1 - _LATD3;
47 }
48
49 __delay32(6000000); // 200ms delay
50 }
51
52 return 0;
53 }
54
55 // Timer 1 interrupt service routine
56 void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
57 {
58 // Clear Timer 1 interrupt flag
59 IFS0bits.T1IF = 0;
60
61 // Toggle LED on RD1
_LATD1 = 1 - _LATD1;
62
}
63
64
// This function sets up digital i/o, analog input,
65 // PWM, UART and timer interrupt.
66 void setup()
67 {
68 // Configure all 4 port D pins as digital outputs
69 TRISD = 0;
70
71 // Configure AN0-AN8 as analog inputs
72 ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
73 ADCON1bits.ADON = 1; // Turn ADC ON
74
75 // Configure PWM for free running mode
76 //
77 // PWM period = Tcy * prescale * PTPER = 0.33ns * 64 * PTPER
78 // PWM pulse width = (Tcy/2) * prescale * PDCx
79
80
81
82
83
84 //
85 PWMCON1 = 0x00FF; // Enable all PWM pairs in complementary mode
86 PTCONbits.PTCKPS = 3; // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
87 PTPER = 9375; // 20ms PWM period (15-bit period value)
88 PDC1 = 1406; // 1.5ms pulse width on PWM channel 1
89 PDC2 = 1406; // 1.5ms pulse width on PWM channel 2
90 PDC3 = 1406; // 1.5ms pulse width on PWM channel 3
PTMR = 0; // Clear 15-bit PWM timer counter
91
PTCONbits.PTEN = 1; // Enable PWM time base
92
93
// Setup UART
94
U1BRG = 48; // 38400 baud @ 30 MIPS
95 U1MODEbits.UARTEN = 1; // Enable UART
96
97 // Configure Timer 1
98 // In this example, I'm setting PR1 and TCKPS for 8Hz
99 PR1 = 14648; // Set the Timer 1 period (max 65535)
100 TMR1 = 0; // Reset Timer 1 counter
101 IEC0bits.T1IE = 1; // Enable Timer 1 interrupt
102 T1CONbits.TCKPS = 3; // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
103 T1CONbits.TON = 1; // Turn on Timer 1
104 }
105
106 // This function reads a single sample from the specified
107 // analog input. It should take less than 5us when the
108 // microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
109
// returned is between 0 and 1023 inclusive.
110
unsigned int read_analog_channel(int channel)
111
{
112 ADCHS = channel; // Select the requested channel
113 ADCON1bits.SAMP = 1; // Start sampling
114 __delay32(30); // 1us delay @ 30 MIPS
115 ADCON1bits.SAMP = 0; // Start Converting
116 while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
117 return ADCBUF0;
118 }
This code is written for Microchip’s XC16 compiler (xc16-gcc.exe). You can compile it in
MPLAB X, but personally I’ve been having a lot of reliability problems with the IDE, so
I’m currently just using a plain text editor (Notepad++) to edit my programs and a
simple makefile to manage the build process with XC16. I use the (very useful) PICkit2
helper application to transfer my compiled hex file to the dsPIC chip. To be honest, I’m
finding this way of working way more convenient than using MPLAB X, which takes
forever to start on my laptop and really just doesn’t run all that well. By comparison,
Notepad++ / XC16 / PICkit2 all run really smoothly and aren’t giving me any reliability
problems. I’m not using a debugger at all – I just use printf a lot!

//
// This dsPIC30F4011 example program generates two
// square waves with a variable phase shift between
// them. Both waveforms 10kHz, 50% duty cycle.
//
// Written by Ted Burke - last updated 20-8-2012
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

int main()
{
// Configure all four port D pins (RD0, RD1, RD2, RD3)
// as digital outputs
TRISD = 0b1111111111110000;

// Set OC channel 1 pulse start and stop times


OC1R = 0;
OC1RS = 1500;

// Set OC channel 2 pulse start and stop times


OC2R = 750;
OC2RS = 2250;

// Set output compare mode for continuous pulses


OC1CONbits.OCM = 0b101;
OC2CONbits.OCM = 0b101;

// Configure timer 2 (default timer for output compare)


PR2 = 3000; // 0.1ms period
T2CONbits.TON = 1; // Enable timer 2

// Flash an LED on RD0 indefinitely at 1Hz


while(1)
{
_LATD2 = 0; // LED off
__delay32(30000000); // 500ms delay
_LATD2 = 1; // LED on
__delay32(30000000); // 500ms delay
}

return 0;
}
I compiled it using Microchip’s new XC16 C compiler. Here’s a screenshot of the
generated waveform, which I checked using the incredibly useful PICkit2 helper
application.

I’m using two output compare channels (OC1 on pin 23 and OC2 on pin 18) to generate
the waveforms. In this example, both output compare channels are driven by Timer 2
(which is the default setting). I set the period register PR2 to 3000 to set the frequency
to 10kHz (the chip is running at 30 MIPS, so 3000 instruction cycles is 0.1ms). This
means that Timer 2 counts from 0 up to 3000 over and over again. The start and stop
times for the pulses on each output compare channel are then controlled using the four
registers OC1R, OC1RS, OC2R and OC2RS. To modify the phase shift between the two
waveforms, just specify different values for OC2R and OC2RS which control the start
and stop times respectively of each pulse on output compare channel 2.

Finally, I set an LED flashing on RD3 (pin 22) just so that I could see that the circuit was
running correctly. Of course, this is not needed for the square wave generation to work
correctly, but it’s always useful to be absolutely sure the program is actually running
when you’re debugging these things! In due course, I’ll add in an analog read into the
while loop to allow the phase shift between the waveforms to be varied using a
potentiometer connected to an analog input channel.

//
// 10Hz sampling example for dsPIC30F4011
// Written by Ted Burke
// Last updated 15-1-2013
//
// This program is an example of 10Hz sampling using Timer 1 on
// the dsPIC30F4011. It doesn't currently do anything very useful.
// the analog voltage on pin AN0 is sampled every 100ms and stored
// in a data buffer. Once ten samples have been recorded to the
// buffer, the samples are added together and multiplied by a
// scaling factor to set the flashing rate on an LED on RD1.
// The flashing rate is updated every ten samples. Another LED on
// RD0 toggles each time a sample is recorded. The 10Hz sampling
// is triggered by an interrupt on Timer 1.
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

// Function prototypes
unsigned int read_analog_channel(int n);

// Function prototype for Timer 1 interrupt service routine


void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void);

// This counter variable will be used to identify


// every tenth interrupt
int counter = 0;

// This data buffer stores 10 voltage readings


int voltage[10];

// This controls the flashing rate of LED on RD0


long flash_delay = 0;

int main()
{
// Configure RD0 and RD1 as digital outputs
TRISD = 0b1100;

// Configure AN0-AN8 as analog inputs


ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON

// Configure Timer 1 - set PR1 and TCKPS values for 10Hz


// Timer 1 period = PR1*prescaler*Tcy = 11719*256*33.33ns = 100ms
PR1 = 11719; // Set the Timer 1 period (max 65535)
TMR1 = 0; // Reset Timer 1 counter
IEC0bits.T1IE = 1; // Enable Timer 1 interrupt
T1CONbits.TCKPS = 3; // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
T1CONbits.TON = 1; // Turn on Timer 1

// Now, just let the interrupt service routine do the work


while(1)
{
__delay32(flash_delay);

// Toggle LED on RD1


_LATD1 = 1 - _LATD1;
}

return 0;
}

// Timer 1 interrupt service routine


void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
{
// Clear Timer 1 interrupt flag
IFS0bits.T1IF = 0;

// Read a voltage from AN0


voltage[counter] = read_analog_channel(0);

// Increment counter and check if 10 samples


// have been collected
counter = counter + 1;
if (counter == 10)
{
//
// DO SOMETHING WITH THE 10 SAMPLES HERE!!!
//
// For the time being, I'm just using the
// average of the 10 samples to set the flashing
// rate of the LED on RD0.
//
flash_delay = 0;
while(counter > 0) flash_delay += voltage[--counter];
flash_delay = 1000 * flash_delay;

// Reset counter
counter = 0;
}

// Toggle LED on RD0


_LATD0 = 1 - _LATD0;
}

// This function reads a single sample from the specified


// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}
I compiled this program with Microchip’s XC16 C compiler. I don’t use MPLAB, so I
used the following build script to create a hex file, which I then downloaded to the
dsPIC30F4011 using the PICkit2 software application.
//
// dsPIC30F4011 phase difference measurement example
//
// Written by Ted Burke
// Last updated 4-3-2013
//
// Measure the phase difference between two 50Hz sine waves.
// The leading sine wave is assumed to be connected to AN0.
// The lagging sine wave is assumed to be connected to AN1.
// Both sine waves are assumed to vary between 0V and 5V.
// The phase difference is measured as the time delay
// between the time each signal crosses a threshold (2.5V).
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

// Function prototype for analog input function


unsigned int read_analog_channel(int channel);

int main()
{
// Configure AN0-AN8 as analog inputs
ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON

// Setup UART
U1BRG = 48; // 38400 baud @ 30 MIPS
U1MODEbits.UARTEN = 1; // Enable UART

// Configure Timer 1
PR1 = 65535; // Set the Timer 1 period (max 65535)
TMR1 = 0; // Reset Timer 1 counter
T1CONbits.TCKPS = 2; // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
T1CONbits.TON = 0; // Disable Timer 1

while(1)
{
// Reset timer to zero
TMR1 = 0;

// Wait until AN0 is below voltage threshold


while (read_analog_channel(0) >= 512);

// Wait for AN0 to rise above voltage threshold


while (read_analog_channel(0) < 512);

// Start timer to measure phase difference


T1CONbits.TON = 1;

// Wait for AN1 to rise above voltage threshold


while (read_analog_channel(1) < 512);

// Stop timer
T1CONbits.TON = 0;

// Print time delay. Note 64:1 timer prescaling.


// Also note that 30000 clock cycles is 1ms at 30 MIPS.
printf("Phase diff = %4.2f ms\n", 64.0 * TMR1 / 30000.0);
}

return 0;
}

// This function reads a single sample from the specified


// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}
//
// dsPIC30F4011 servo control using output compare
// Written by Ted Burke - last updated 26-2-2014
//
// This dsPIC30F4011 example program controls a
// servo using the output compare module for PWM.
// The duty cycle varies between 1ms and 2ms as
// the analog voltage on AN0 varies between 0 and Vcc.
//
// Analog input is on AN0 (pin 2)
// PWM output is on OC1 (pin 23)
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

unsigned int read_analog_channel(int channel);

int main()
{
// Configure RD0 and RD1 as outputs
TRISD = 0b1100;

// Configure AN0-AN8 as analog inputs


ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON

// Configure Timer 2 (default timer for output compare)


T2CONbits.TCKPS = 0b10; // Timer 2 prescaler 1:64
PR2 = 9375; // Timer 2 period (20ms)
T2CONbits.TON = 1; // Enable Timer 2

// Configure Output Compare channel 1 (OC1)


OC1CONbits.OCM = 0b101; // continuous pulse mode
OC1R = 0; // pulse start time
OC1RS = min; // pulse stop time

// Set max and min OC1RS values


int min = 469; // OC1RS value for 1ms duty cycle (469 x 64 x Tcy = 1ms)
int max = 937; // OC1RS value for 2ms duty cycle (937 x 64 x Tcy = 2ms)
int range = max - min;

// Now just cycle through the angle positions


while(1)
{
// Read voltage from pin AN0 as a value between 0 and 1023
v = read_analog_channel(0);

// update servo angle


OC1RS = min + (range * v / 1023.0);

__delay32(3000000); // 100ms delay


}

return 0;
}

// This function reads a single sample from the specified


// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}
The PWM period depends on three things:

1.) Tcy: the instruction cycle. When the chip is running at 30 MIPS (million instructions
per second), Tcy = 33.33ns
2.) _PTCKPS: the PWM prescaler setting. _PTCKPS is actually a pair of bits in the
PTCON register.
3.) PTPER: the PWM period register.

The prescaler ratio depends on _PTCKPS as follows:

– When _PTCKPS = 0, the prescaler ratio is 1:1


– When _PTCKPS = 1, the prescaler ratio is 4:1
– When _PTCKPS = 2, the prescaler ratio is 16:1
– When _PTCKPS = 3, the prescaler ratio is 64:1

So, the formula for the PWM period, Tpwm is:

Tpwm = PTPER * prescaler_ratio * Tcy

The PWM pulse width (and hence duty cycle) for each channel depends on three things.
I’ll use channel 1 as an example here. The three things are:

1.) Tcy: the instruction cycle


2.) _PTCKPS: the prescaler setting
3.) PDC1: the duty cycle register for PWM channel 1

The formula for the pulse width is:

pulse_width = (PDC1 * prescaler_ratio * Tcy) / 2

Note that there is an extra factor of 2 there which gives a little extra resolution when
you’re specifying the pulse width.

Here’s an example…

Supppose you want to set the PWM period to 20ms and the pulse width to 2ms.

1 //
2 // dsPIC30F4011 example - Simple PWM
3 // Written by Ted Burke, Last updated 1-10-2015
4 //
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1 #include <xc.h>
8 #include <libpic30.h>
1
9 // Configuration settings
2 _FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
0 _FWDT(WDT_OFF); // Watchdog timer off
2 _FBORPOR(MCLR_DIS); // Disable reset pin
1
2 int main(void)
2 {
2 // Configure PWM
// PWM period = PTPER * prescale * Tcy = 9470 * 64 * 33.33ns = 20ms
3
// PWM pulse width = PDC1 * prescale * Tcy / 2 = 1894 * 64 * 33.33ns / 2 = 2ms
2
_PMOD1 = 0; // PWM channel 1 mode: 0 for complementary, 1 for independent
4
_PEN1H = 1; // PWM1H pin enable: 1 to enable, 0 to disable
2 _PTCKPS = 3; // PWM prescaler setting: 0=1:1, 1=1:4, 2=1:16, 3=1:64
5 PTPER = 9470; // Set PWM time base period to 20ms
2 PDC1 = 1894; // 2ms pulse width on channel 1
6 _PTEN = 1; // Enable PWM time base to start generating pulses
2
7 // Now do nothing while pulses are generated by PWM module
2 while(1) {}
8 }
//
// MPPT example for dsPIC30F4011 using perturb and observe
// Written by Ted Burke - last updated 23-1-2016
//
// Notes:
//
// 1. Voltage sensing is on AN0 (pin 2)
// 2. Current sensing is on AN1 (pin 3)
// 3. PWM output is on PWM1H (pin 37)
// 4. PWM period is 1ms - I just gessed this.
// 5. P&O control loop period is 100ms approx - also a guess.
// 6. The duty cycle step size is currently 1% (i.e. 0.01) but that's just a guess.
// 7. The initial duty cycle is currently 25% - this should be set as desired.
// 8. The code won't compile until values are inserted for volts_per_du and
amps_per_du.
// 9. This code is completely untested - I haven't even tried compiling it!
// 10. I enabled the UART at 38400 baud so that debugging info can be printed.
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

void main()
{
// voltage, current, power, duty cycle
const float volts_per_du=???, amps_per_du=???; // FILL IN VALUES HERE!!!
int v_du, i_du; // voltage and current in digital units
float v, i; // voltage in volts and current in amps
float p_old, p_new; // power in watt on previous and current cycles
float duty_cycle = 0.25; // set this to desired initial duty cycle

// duty cycle direction: 1 for increasing, -1 for decreasing


int direction = 1;

// duty cycle increases or decreases by this amount each cycle


float step = 0.01;

// Set up PWM output


//
// PWM period = Tcy * prescale * PTPER = 0.33ns * 64 * PTPER
// PWM pulse width = (Tcy/2) * prescale * PDC1
//
PWMCON1 = 0x00FF; // Enable all PWM pairs in complementary mode
PTCONbits.PTCKPS = 1; // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
PTPER = 7500; // 1ms PWM period (15-bit period value)
PDC1 = 0; // controls duty cycle: PDC1=0 -> 0%, PDC1=15000 -> 100%
PTCONbits.PTEN = 1; // Enable PWM time base

// Configure AN0-AN8 as analog inputs


ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON

// Setup UART
U1BRG = 48; // 38400 baud @ 30 MIPS
U1MODEbits.UARTEN = 1; // Enable UART

// Delay to let initial duty cycle take effect


__delay32(3000000); // 100ms - just a guess!

while(1)
{
// Read voltage and current from analog inputs
// Both values are in digital units (du), meaning
// that they are 10-bit unsigned integers (i.e.
// whole numbers between 0 and 1023).
v_du = read_analog_channel(0);
i_du = read_analog_channel(1);

// Convert the measured voltage and current to


// untis of volts and amps.
v = volts_per_du * v_du;
i = amps_per_du * i_du;

// Calculate power
p_new = v * i;

// Decide whether to increase or decrease duty cycle.


// If power now is greater than power on previous cycle,
// then keep going in the same direction. If power now
// is less than on previous cycle, change direction.
if (p_new < p_old)
{
direction = -direction;
}

// Remember this power value for comparison on the next cycle


p_old = p_new;

// Increase or decrease the duty cycle


duty_cycle = duty_cycle + (direction * step);
PDC1 = duty_cycle * 2 * PTPER;

// To assist debugging, print current values over serial link


printf("v=%.2f i=%.2f p=%.2f dc=%.2f\n", v, i, p_new, duty_cycle);

// This delay can be modified to control the rate


// at which the duty cycle is updated.
// I've set it to 100ms, but that's a complete guess.
__delay32(3000000);
}
}

// This function reads a single sample from the specified


// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}
The program is written for Microchip’s C30 compiler. Ok, here’s the complete code:

1 //
2 // This is a dsPIC30F4011 program to generate a PWM signal
3 // with a sinusoidally varying duty cycle. The frequency of
4 // the PWM signal is 10kHz and the period of the sinusoidal
5 // variation in the duty cycle is 50Hz.
6 //
// written by Ted Burke - last updated 2-5-2011
7
//
8
9
#include <libpic30.h>
10
#include <p30f4011.h>
11 #include <math.h>
12
13 // Configuration settings
14 _FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
15 _FWDT(WDT_OFF); // Watchdog timer off
16 _FBORPOR(MCLR_DIS); // Disable reset pin
17
18 // Function prototypes
19 void setup();
20 unsigned int read_analog_channel(int n);
21
22 #define PI 3.1415926536
23
24 int n = 0;
25 double dc_min=0.0, dc_max=1.0;
26
27 int main()
28 {
// Set up digital i/o, PWM, and Timer 1 interrupt
29
setup();
30
31
//
32 // It doesn't really matter what we put here. It's really
33 // just something to keep the main function busy while the
34 // real action takes place in the background. I'm making
35 // it flash an LED on RD0, just so that I can see that the
36 // circuit is running.
37 //
38 // The sinusoidal variation of the PWM output will actually
39 // be controlled by the Timer 1 interrupt service routine
40 // which is below, just after the end of the main function.
//
41 while(1)
42 {
43 _LATD0 = 1;
44 __delay32(15000000);
45 _LATD0 = 0;
46 __delay32(15000000);
}
47
48
return 0;
49
}
50
51
// Timer 1 interrupt service routine (runs automatically every 200us)
52 void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
53 {
54 double dc, sine_term;
55
56 // Clear Timer 1 interrupt flag
57 _T1IF = 0;
58
59 // Vary PWM duty cycle sinusoidally (in 100 steps)
60 n = n + 1;
61 if (n == 100) n = 0;
62
63 // Calculate duty cycle
64 sine_term = (1 + sin(2 * PI * n / 100.0)); // varies between 0 and 2
65 dc = sine_term * dc_min + (2 - sine_term) * dc_max; // varies between 2*dc_min an
66
67 // Update the duty cycle register
68 PDC1 = dc * PTPER;
}
69
70
// This function sets up digital i/o, PWM, and Timer 1 interrupt
71
void setup()
72 {
73 // Configure all four port D pins (RD0, RD1, RD2, RD3)
74 // as digital outputs
75 LATD = 0;
76 TRISD = 0b1111111111110000;
77
78 // Configure PWM for 10kHz frequency
79 //
80 // PWM period = Tcy * prescale * PTPER = 0.333ns * 64 * PTPER
81 // PWM pulse width = (Tcy/2) * prescale * PDC
82 //
83 PWMCON1 = 0x00FF; // Enable all PWM pairs in complementary mode
84 PTCON = 0;
85 _PTCKPS = 0; // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
86
87
88
89
90
91
92 PTPER = 3000; // 100us PWM period (15-bit period value)
93 PDC1 = PTPER; // 50% duty cycle on PWM channel 1
94 PDC2 = PTPER; // 50% duty cycle on PWM channel 2
95 PDC3 = PTPER; // 50% duty cycle on PWM channel 3
96 PTMR = 0; // Clear 15-bit PWM timer counter
97 _PTEN = 1; // Enable PWM time base
98
99 // Configure Timer 1
100 // In this example, I'm setting PR1 and TCKPS for 5kHz.
101 // In other words, the Timer 1 interrupt service routine
// will run 5000 times a second (i.e. every 200us).
102
T1CON = 0; // Clear the Timer 1 configuration
103
TMR1 = 0; // Reset Timer 1 counter
104
PR1 = 6000; // Set the Timer 1 period (max 65535)
105 T1CONbits.TCS = 0; // Select internal clock (Fosc/4)
106 T1CONbits.TCKPS = 0; // Prescaler (0=1:1, 1=1:8, 2=1:64, 3=1:256)
107 _T1IP = 1; // Set the Timer 1 interrupt priority
108 _T1IF = 0; // Clear the Timer 1 interrupt flag
109 _T1IE = 1; // Enable Timer 1 interrupt
110 T1CONbits.TON = 1; // Turn on Timer 1
111 }
Now to explain the code.

There are three functions in this program:

 The main function actually doesn’t do very much. It calls the setup function to
configure the dsPIC and then just bides its time flashing an LED on RD0 (pin
23) once every two seconds.
 The setup function configures the four port D pins as digital outputs, enables
all three PWM channels in complementary mode with a frequency of 10kHz,
enables Timer 1 with a frequency of 5kHz, and enables the Timer 1 interrupt
so that its interrupt service routine, _T1Interrupt, will be called every
200µs.
 The Timer 1 interrupt service routine, _T1Interrupt, is where most of the
action really happens. This function is called automatically every 200µs. It
updates the duty cycle of PWM channel 1. The sinusoidal variation repeats
every 100 updates, which produces a frequency of 50Hz.
Of course, it’s difficult to observe the sinusoidal variation directly because it’s happening
so fast (going through 50 cycles per second). In order to make it more obvious what’s
happening, all you need to do is slow down the frequency of the Timer 1 interrupt
service routine. To see the sinusoidal variation happening at a slower rate, connect an
LED to PWM1H (pin 37) and replace lines 104-106 with the following ones:

PR1 = 9375; // Set the Timer 1 period to 20 ms


T1CONbits.TCS = 0; // Select internal clock (Fosc/4)
T1CONbits.TCKPS = 2; // Set prescaler to 1:64
If you do that, you should see the LED brightness pulsating smoothly at 0.5Hz.

//
// dsPIC30F4011 Push-pull PWM sinewave with deadtime
// Written by Ted Burke
// Last updated 12-10-2012
//
// This example generates sinusoidally modulated PWM
// waveforms on both output compare channels of a
// dsPIC30F4011. The two modulating sine waves are
// both at 25Hz frequency, but are 180 degrees out
// of phase with each other. The PWM frequency is
// 100kHz (seems high to me!). These PWM waveforms
// are intended to drive a pair of MOSFETs in a
// push-pull circuit (e.g. in a power inverter),
// so a short deadtime is included between the end
// of one channel's pulse and the beginning of the
// other channel's pulse.
//

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

// Configuration settings
_FOSC(CSW_FSCM_OFF & XT_PLL8); // Fosc=80MHz (10MHz crystal, 8xPLL)
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin

int main()
{
// Variables
long Fcy = 20000000; // Fcy = Fosc/4
double Tsin = 0.04; // Sine wave period in seconds (40ms -> 25Hz)
int deadtime = 10; // Deadtime in instruction cycles
int Tpwm = 200; // PWM period in instruction cycles
double dc = 0.5; // duty cycle of OC1, initially 50%

// Make all port D pins outputs


TRISD = 0;

// Set OC channel 1 & 2 start and stop times


OC1R = 0;
OC1RS = dc * (Tpwm - 2 * deadtime);
OC2R = OC1RS + deadtime;
OC2RS = Tpwm - deadtime;

// Set output compare mode for continuous pulses


OC1CONbits.OCM = 0b101;
OC2CONbits.OCM = 0b101;

// Configure timer 2 (default timer for output compare)


PR2 = Tpwm; // 100kHz PWM frequency
T2CONbits.TON = 1; // Enable timer 2

// Move through sine wave with 100 steps


int n = 0, N=100;

// Now vary the duty cycle sinusoidally


while(1)
{
// delay for Tsin/N seconds
__delay32(Tsin * Fcy / N);

// Update duty cycle


n = n + 1;
if (n >= N) n = 0;
dc = 0.5 * (1 + sin(n*6.2831853/N));
if (dc < 0) dc = 0;

// Set pulse start and stop times for both OC channels.


// It seems that OCxRS needs to be at least OCxR + 2.
OC1R = 0;
OC1RS = 2 + dc * (Tpwm - 2 * deadtime);
OC2R = OC1RS + deadtime;
OC2RS = 2 + 2 + Tpwm - deadtime;
}

return 0;
}

You might also like