Skip to content

Commit 189f9fd

Browse files
authored
PIC18 PWM Driver (#21)
2 parents b911cef + 6897a2d commit 189f9fd

File tree

5 files changed

+256
-131
lines changed

5 files changed

+256
-131
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
doc/_build
22

33
unit_tests/low_pass_filter_test
4+
.DS_Store

doc/pwm_driver.rst

Lines changed: 141 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,164 @@
1-
PIC18 Software PWM Driver
2-
*************************
1+
PIC18 PWM Driver
2+
****************
33

4-
The driver provides PWM output functionality for PIC18F26K83 on one pin, with timer only. Pulse-width/duty-cycle can be adjusted when output is enabled. Timer 2 is used by this driver.
4+
The driver provides PWM output functionality for the PIC18F26K83 using the CCP (Capture/Compare/PWM) modules. It supports up to four PWM channels, each with configurable pin mapping. Timer 2 is utilized by this driver to manage PWM periods.
55

66
Integration
77
===========
88

9-
ROCKETLIB_PWM_PIN Macro
10-
-----------------------
11-
Set ``ROCKETLIB_PWM_PIN`` macro to the pin register name for output, e.g. ``LATC6``
9+
Pin Configuration
10+
-----------------
11+
.. c:macro:: CONCAT(a, b, c)
1212
13-
ROCKETLIB_PWM_INVERT_POLARITY Macro
14-
-----------------------------------
15-
Set ``ROCKETLIB_PWM_INVERT_POLARITY`` macro to inverse output polarity
13+
Concatenates three tokens to dynamically form register names.
1614

17-
Interrupt
18-
---------
19-
``void timer2_handle_interrupt(void)`` have to be called when having a timer 2 interrupt
15+
:param a: First part of the token.
16+
:param b: Second part of the token.
17+
:param c: Third part of the token.
2018

21-
SoftPWM Controller Functions
22-
============================
19+
.. c:macro:: CCPR_L(module)
20+
21+
Accesses the CCPR Low register for the specified module.
22+
23+
:param module: CCP module number (1-4).
24+
25+
.. c:macro:: CCPR_H(module)
26+
27+
Accesses the CCPR High register for the specified module.
28+
29+
:param module: CCP module number (1-4).
30+
31+
.. c:macro:: CCP_CON(module)
32+
33+
Accesses the CCPxCON control register for the specified module.
34+
35+
:param module: CCP module number (1-4).
36+
37+
.. c:macro:: GET_TRIS_REG(port)
38+
39+
Retrieves the TRIS register for the specified port.
40+
41+
:param port: Port letter (A, B, C).
42+
43+
.. c:macro:: GET_PPS_REG(port, pin)
44+
45+
Retrieves the PPS register address for the specified port and pin.
46+
47+
:param port: Port letter (A, B, C).
48+
:param pin: Pin number (0-7).
49+
50+
.. c:macro:: SET_TRIS_OUTPUT(port, pin)
51+
52+
Sets the specified pin as output by modifying the TRIS register.
53+
54+
:param port: Port letter (A, B, C).
55+
:param pin: Pin number (0-7).
56+
57+
.. c:macro:: ASSIGN_PPS(port, pin, ccp_module)
58+
59+
Assigns the CCP module to the specified PPS register to map the peripheral to the desired pin.
60+
61+
:param port: Port letter (A, B, C).
62+
:param pin: Pin number (0-7).
63+
:param ccp_module: CCP module number (1-4).
64+
65+
CCP Mode Configuration
66+
----------------------
67+
.. c:macro:: CONFIGURE_CCP_MODE(ccp_module, ccp_con)
68+
69+
Configure the CCP module for PWM mode.
70+
71+
:param ccp_module: CCP module number (1-4)
72+
:param ccp_con: CCPxCON register to configure
73+
74+
Output Pin Configuration
75+
------------------------
76+
.. c:macro:: SET_PWM_OUTPUT_PIN(ccp_module, output_pin)
77+
78+
Set the TRIS register for the output pin.
79+
80+
:param ccp_module: CCP module number (1-4)
81+
:param output_pin: Output pin number
82+
83+
Duty Cycle Configuration
84+
------------------------
85+
.. c:macro:: WRITE_DUTY_CYCLE(ccp_module, duty_cycle)
86+
87+
Write the 10-bit duty cycle value to the appropriate CCPRxH:CCPRxL register pair.
88+
89+
:param ccp_module: CCP module number (1-4)
90+
:param duty_cycle: 10-bit duty cycle value (0-1023)
91+
92+
PWM Controller Functions
93+
========================
94+
95+
PWM Pin Configuration Structure
96+
-------------------------------
97+
.. c:type:: pwm_pin_config_t
98+
99+
Structure that holds the configuration details for a PWM pin.
100+
101+
:param port: Port letter (A, B, C).
102+
:param pin: Pin number (0-7).
103+
:param pps_reg: PPS register value for this pin.
23104

24105
Initialization
25106
--------------
26-
.. c:function:: void pwm_init(uint16_t period)
107+
.. c:function:: w_status_t pwm_init(uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period)
27108
28-
Initialize Software PWM
109+
Initializes PWM for the specified CCP module with the given pin configuration and PWM period.
29110
30-
:param uint16_t period: period in increment of 10 us
111+
:param ccp_module: CCP module number (1-4).
112+
:param pin_config: PWM pin configuration structure containing port, pin, and PPS register values.
113+
:param pwm_period: PWM period value.
114+
:return: W_SUCCESS on successful initialization, otherwise an error code.
31115
32-
Enable
33-
------
34-
.. c:function:: void pwm_enable(void)
116+
This function configures Timer 2, sets the PWM period, and enables the PWM output for the specified CCP module.
35117
36-
Enable PWM Output
118+
PWM Operation
119+
=============
37120
38-
Disable
39-
-------
40-
.. c:function:: void pwm_disable(void)
121+
.. c:function:: w_status_t pwm_update_duty_cycle(uint8_t ccp_module, uint16_t duty_cycle)
41122
42-
Disable PWM Output
123+
Updates the duty cycle of the specified CCP module to the new value.
43124
44-
Set Duty Cycle
45-
--------------
46-
.. c:function:: bool pwm_set_duty_cycle(uint16_t duty_cycle)
125+
:param ccp_module: CCP module number (1-4).
126+
:param duty_cycle: New duty cycle value (0-1023).
127+
:return: W_SUCCESS if successful, W_INVALID_PARAM if parameters are out of range.
128+
129+
The duty cycle is a 10-bit value that determines the percentage of time the signal stays high. The lower 8 bits are written to CCPRxL, and the upper 2 bits are written to CCPRxH.
130+
131+
Timer Configuration
132+
-------------------
133+
.. c:macro:: CONFIGURE_TIMER2(pwm_period)
134+
135+
Configures Timer 2 to manage PWM periods. The prescaler and postscaler are set to 1:1.
136+
137+
:param pwm_period: PWM period value to load into the PR2 register.
138+
139+
Helper Functions
140+
================
141+
142+
PPS Configuration
143+
-----------------
144+
.. c:function:: static w_status_t configure_pps(uint8_t ccp_module, pwm_pin_config_t pin_config)
47145
48-
Set duty cycle
146+
Configures Peripheral Pin Select (PPS) for the specified pin and CCP module. This function is essential for routing the PWM signal to the correct output pin.
49147
50-
:param uint16_t duty_cycle: Duty cycle in per-mille(1/1000)
51-
:return: success or not
52-
:retval true: success
53-
:retval false: failed
148+
:param ccp_module: CCP module number (1-4).
149+
:param pin_config: Structure containing port, pin, and PPS register values.
150+
:return: W_SUCCESS if successful, W_INVALID_PARAM if the module number is out of range.
54151
55-
Set Pulse Width
56-
---------------
57-
.. c:function:: bool pwm_set_pulse_width(uint16_t pulse_width)
152+
Error Handling
153+
==============
58154
59-
Set pulse width
155+
Error Codes
156+
-----------
157+
- **W_SUCCESS**: Operation completed successfully.
158+
- **W_INVALID_PARAM**: Parameter value is out of range. Typically returned if an invalid CCP module number or duty cycle value is provided.
60159
61-
:param uint16_t pulse_width: pulse width in increment of 10 us
62-
:return: success or not
63-
:retval true: success
64-
:retval false: failed
160+
Practical Considerations
161+
========================
162+
- **Timer Usage**: Ensure Timer 2 is not shared with other peripherals to avoid conflicts in PWM generation.
163+
- **Prescaler and Postscaler**: The prescaler and postscaler are currently set to 1:1 for simplicity. These can be adjusted to modify the frequency of the PWM signal.
164+
- **Pin Mapping**: Correct pin mapping is critical for proper operation. Incorrect configuration can result in no output or conflicts with other peripherals.

include/pwm.h

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,57 @@
11
#ifndef ROCKETLIB_PWM_H
22
#define ROCKETLIB_PWM_H
33

4+
#include "common.h"
45
#include <stdint.h>
56

6-
/* PWM Initialization
7-
period: Period in 10us
8-
Note only 10ms period work for now(period=1000)
9-
*/
10-
void pwm_init(uint16_t period);
7+
// Structure to hold the configuration details for a PWM pin
8+
typedef struct {
9+
uint8_t port; // Port letter (A, B, C)
10+
uint8_t pin; // Pin number (0-7)
11+
uint8_t pps_reg; // PPS register value for this pin
12+
} pwm_pin_config_t;
1113

12-
/* Set PWM Duty Cycle
13-
duty_cycle: Duty cycle in per-mille(1/1000)
14-
*/
15-
void pwm_set_duty_cycle(uint16_t duty_cycle);
14+
// Macro to concatenate tokens for register naming
15+
// This macro concatenates three tokens together, used for constructing register names dynamically
16+
#define CONCAT(a, b, c) a##b##c
1617

17-
void pwm_enable(void);
18+
// Macro to get the CCPR Low register based on module number
19+
// This macro forms the register name for the low byte of the Compare/Capture/PWM register
20+
#define CCPR_L(module) CONCAT(CCPR, module, L)
1821

19-
void pwm_disable(void);
22+
// Macro to get the CCPR High register based on module number
23+
// This macro forms the register name for the high byte of the Compare/Capture/PWM register
24+
#define CCPR_H(module) CONCAT(CCPR, module, H)
2025

21-
#endif
26+
// Macro to get the CCPxCON register based on module number
27+
// This macro forms the register name for the control register of the specified CCP module
28+
#define CCP_CON(module) CONCAT(CCP, module, CON)
29+
30+
// Macro to get the TRIS register based on port letter
31+
// TRIS registers control the direction of pins (input or output)
32+
// This macro dynamically constructs the TRIS register name for a given port
33+
#define GET_TRIS_REG(port) CONCAT(TRIS, port, A)
34+
35+
// Macro to get the PPS register address based on port and pin
36+
// PPS (Peripheral Pin Select) allows mapping of peripherals to different pins
37+
// This macro forms the PPS register name for a given port and pin
38+
#define GET_PPS_REG(port, pin) CONCAT(R, port, pin##PPS)
39+
40+
// Macro to set the TRIS register for a specific pin
41+
// This macro configures the specified pin as an output by clearing the corresponding bit in the
42+
// TRIS register
43+
#define SET_TRIS_OUTPUT(port, pin) (GET_TRIS_REG(port) &= ~(1 << (pin)))
44+
45+
// Macro to assign the CCP module to the PPS register
46+
// This macro assigns the CCP module to a specific pin by writing to the PPS register
47+
#define ASSIGN_PPS(port, pin, ccp_module) (*GET_PPS_REG(port, pin) = (ccp_module))
48+
49+
// Function prototypes
50+
w_status_t pwm_init(
51+
uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period
52+
); // Initializes the PWM for a specific CCP module
53+
w_status_t pwm_update_duty_cycle(
54+
uint8_t ccp_module, uint16_t duty_cycle
55+
); // Updates the duty cycle of the specified CCP module
56+
57+
#endif /* ROCKETLIB_PWM_H */

pic18f26k83/pwm.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include "pwm.h"
2+
#include <xc.h>
3+
4+
// Helper function to configure PPS registers using macros
5+
static w_status_t configure_pps(uint8_t ccp_module, pwm_pin_config_t pin_config) {
6+
volatile uint8_t *pps_reg;
7+
8+
// Ensure the CCP module number is within valid range (1-4)
9+
if (ccp_module < 1 || ccp_module > 4) {
10+
return W_INVALID_PARAM; // Return error if the module number is out of range
11+
}
12+
13+
// Set the pin as output to drive PWM signal
14+
// This macro modifies the TRIS register to set the specified pin as an output
15+
SET_TRIS_OUTPUT(pin_config.port, pin_config.pin);
16+
17+
// Assign the CCP module to the corresponding PPS register
18+
// This macro sets up the peripheral pin select to link the CCP module to the desired pin
19+
ASSIGN_PPS(pin_config.port, pin_config.pin, ccp_module);
20+
21+
return W_SUCCESS; // Return success status after configuring PPS
22+
}
23+
24+
// Initialize PWM for a specific CCP module
25+
w_status_t pwm_init(uint8_t ccp_module, pwm_pin_config_t pin_config, uint16_t pwm_period) {
26+
// Configure PPS registers to map CCP module to the selected pin
27+
w_status_t status = configure_pps(ccp_module, pin_config);
28+
if (status != W_SUCCESS) {
29+
return status; // Return error status if PPS configuration fails
30+
}
31+
32+
// Obtain the address of the CCPxCON register using macro
33+
volatile uint8_t *ccp_con = &CCP_CON(ccp_module);
34+
*ccp_con = 0x8C; // Enable CCP module in PWM mode (PWM mode selection)
35+
36+
// Set PWM period using Timer2
37+
PR2 = pwm_period & 0xFF; // Load lower 8 bits of PWM period into PR2 register
38+
TMR2 = 0; // Reset Timer2 count to 0
39+
T2CONbits.T2CKPS = 0; // Set Timer2 prescaler to 1:1 (no prescaling)
40+
T2CONbits.TOUTPS = 0; // Set Timer2 postscaler to 1:1 (no postscaling)
41+
T2CONbits.TMR2ON = 1; // Start Timer2 to begin PWM operation
42+
43+
// Wait for Timer2 to reach the period value before starting PWM
44+
while (!PIR1bits.TMR2IF) {} // Wait until Timer2 overflow flag is set
45+
PIR1bits.TMR2IF = 0; // Clear Timer2 interrupt flag to continue
46+
47+
return W_SUCCESS; // Return success status after PWM initialization
48+
}
49+
50+
// Update the duty cycle of a specific CCP module
51+
w_status_t pwm_update_duty_cycle(uint8_t ccp_module, uint16_t duty_cycle) {
52+
// Validate CCP module and duty cycle range
53+
if (ccp_module < 1 || ccp_module > 4 || duty_cycle > 1023) {
54+
return W_INVALID_PARAM; // Return error if module number or duty cycle is out of range
55+
}
56+
57+
// Update the lower 8 bits of the duty cycle
58+
// This sets the low byte of the duty cycle for the PWM signal
59+
CCPR_L(ccp_module) = duty_cycle & 0xFF;
60+
61+
// Update the upper 2 bits of the duty cycle for 10-bit resolution
62+
// This sets the high bits of the duty cycle to achieve 10-bit PWM precision
63+
CCPR_H(ccp_module) = (duty_cycle >> 8) & 0x03;
64+
65+
return W_SUCCESS; // Return success status after updating duty cycle
66+
}

0 commit comments

Comments
 (0)