1 /* mbed Microcontroller Library
2 * Copyright (c) 2013 Nordic Semiconductor
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 #include "mbed_assert.h"
17 #include "pwmout_api.h"
20 #include "mbed_error.h"
23 #define TIMER_PRECISION 4 //4us ticks
24 #define TIMER_PRESCALER 6 //4us ticks = 16Mhz/(2**6)
25 static const PinMap PinMap_PWM
[] = {
58 static NRF_TIMER_Type
*Timers
[1] = {
62 uint16_t PERIOD
= 20000 / TIMER_PRECISION
; //20ms
63 uint8_t PWM_taken
[NO_PWMS
] = {0, 0, 0};
64 uint16_t PULSE_WIDTH
[NO_PWMS
] = {1, 1, 1}; //set to 1 instead of 0
65 uint16_t ACTUAL_PULSE
[NO_PWMS
] = {0, 0, 0};
68 /** @brief Function for handling timer 2 peripheral interrupts.
73 void TIMER2_IRQHandler(void)
75 NRF_TIMER2
->EVENTS_COMPARE
[3] = 0;
76 NRF_TIMER2
->CC
[3] = PERIOD
;
79 NRF_TIMER2
->CC
[0] = PULSE_WIDTH
[0];
82 NRF_TIMER2
->CC
[1] = PULSE_WIDTH
[1];
85 NRF_TIMER2
->CC
[2] = PULSE_WIDTH
[2];
88 NRF_TIMER2
->TASKS_START
= 1;
94 /** @brief Function for initializing the Timer peripherals.
96 void timer_init(uint8_t pwmChoice
)
98 NRF_TIMER_Type
*timer
= Timers
[0];
99 timer
->TASKS_STOP
= 0;
101 if (pwmChoice
== 0) {
104 timer
->MODE
= TIMER_MODE_MODE_Timer
;
105 timer
->BITMODE
= TIMER_BITMODE_BITMODE_16Bit
<< TIMER_BITMODE_BITMODE_Pos
;
106 timer
->PRESCALER
= TIMER_PRESCALER
;
107 timer
->CC
[3] = PERIOD
;
110 timer
->CC
[pwmChoice
] = PULSE_WIDTH
[pwmChoice
];
112 //high priority application interrupt
113 NVIC_SetPriority(TIMER2_IRQn
, 1);
114 NVIC_EnableIRQ(TIMER2_IRQn
);
116 timer
->TASKS_START
= 0x01;
119 /** @brief Function for initializing the GPIO Tasks/Events peripheral.
121 void gpiote_init(PinName pin
, uint8_t channel_number
)
123 // Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
124 NRF_GPIO
->PIN_CNF
[pin
] = (GPIO_PIN_CNF_SENSE_Disabled
<< GPIO_PIN_CNF_SENSE_Pos
)
125 | (GPIO_PIN_CNF_DRIVE_S0S1
<< GPIO_PIN_CNF_DRIVE_Pos
)
126 | (GPIO_PIN_CNF_PULL_Disabled
<< GPIO_PIN_CNF_PULL_Pos
)
127 | (GPIO_PIN_CNF_INPUT_Connect
<< GPIO_PIN_CNF_INPUT_Pos
)
128 | (GPIO_PIN_CNF_DIR_Output
<< GPIO_PIN_CNF_DIR_Pos
);
129 NRF_GPIO
->OUTCLR
= (1UL << pin
);
130 // Configure GPIOTE channel 0 to toggle the PWM pin state
131 // @note Only one GPIOTE task can be connected to an output pin.
132 /* Configure channel to Pin31, not connected to the pin, and configure as a tasks that will set it to proper level */
133 NRF_GPIOTE
->CONFIG
[channel_number
] = (GPIOTE_CONFIG_MODE_Task
<< GPIOTE_CONFIG_MODE_Pos
) |
134 (31UL << GPIOTE_CONFIG_PSEL_Pos
) |
135 (GPIOTE_CONFIG_POLARITY_HiToLo
<< GPIOTE_CONFIG_POLARITY_Pos
);
136 /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
140 /* Launch the task to take the GPIOTE channel output to the desired level */
141 NRF_GPIOTE
->TASKS_OUT
[channel_number
] = 1;
143 /* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly.
144 If it does not, the channel output inheritance sets the proper level. */
145 NRF_GPIOTE
->CONFIG
[channel_number
] = (GPIOTE_CONFIG_MODE_Task
<< GPIOTE_CONFIG_MODE_Pos
) |
146 ((uint32_t)pin
<< GPIOTE_CONFIG_PSEL_Pos
) |
147 ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle
<< GPIOTE_CONFIG_POLARITY_Pos
) |
148 ((uint32_t)GPIOTE_CONFIG_OUTINIT_Low
<< GPIOTE_CONFIG_OUTINIT_Pos
); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High <<
149 // GPIOTE_CONFIG_OUTINIT_Pos);//
151 /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
157 /** @brief Function for initializing the Programmable Peripheral Interconnect peripheral.
159 static void ppi_init(uint8_t pwm
)
161 //using ppi channels 0-7 (only 0-7 are available)
162 uint8_t channel_number
= 2 * pwm
;
163 NRF_TIMER_Type
*timer
= Timers
[0];
165 // Configure PPI channel 0 to toggle ADVERTISING_LED_PIN_NO on every TIMER1 COMPARE[0] match
166 NRF_PPI
->CH
[channel_number
].TEP
= (uint32_t)&NRF_GPIOTE
->TASKS_OUT
[pwm
];
167 NRF_PPI
->CH
[channel_number
+ 1].TEP
= (uint32_t)&NRF_GPIOTE
->TASKS_OUT
[pwm
];
168 NRF_PPI
->CH
[channel_number
].EEP
= (uint32_t)&timer
->EVENTS_COMPARE
[pwm
];
169 NRF_PPI
->CH
[channel_number
+ 1].EEP
= (uint32_t)&timer
->EVENTS_COMPARE
[3];
171 // Enable PPI channels.
172 NRF_PPI
->CHEN
|= (1 << channel_number
) |
173 (1 << (channel_number
+ 1));
176 void setModulation(pwmout_t
*obj
, uint8_t toggle
, uint8_t high
)
179 NRF_GPIOTE
->CONFIG
[obj
->pwm
] |= ((uint32_t)GPIOTE_CONFIG_OUTINIT_High
<< GPIOTE_CONFIG_OUTINIT_Pos
);
181 NRF_GPIOTE
->CONFIG
[obj
->pwm
] |= (GPIOTE_CONFIG_MODE_Task
<< GPIOTE_CONFIG_MODE_Pos
) |
182 ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle
<< GPIOTE_CONFIG_POLARITY_Pos
);
184 NRF_GPIOTE
->CONFIG
[obj
->pwm
] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle
<< GPIOTE_CONFIG_POLARITY_Pos
);
185 NRF_GPIOTE
->CONFIG
[obj
->pwm
] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_LoToHi
<< GPIOTE_CONFIG_POLARITY_Pos
);
188 NRF_GPIOTE
->CONFIG
[obj
->pwm
] &= ~((uint32_t)GPIOTE_CONFIG_OUTINIT_High
<< GPIOTE_CONFIG_OUTINIT_Pos
);
191 NRF_GPIOTE
->CONFIG
[obj
->pwm
] |= (GPIOTE_CONFIG_MODE_Task
<< GPIOTE_CONFIG_MODE_Pos
) |
192 ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle
<< GPIOTE_CONFIG_POLARITY_Pos
);
194 NRF_GPIOTE
->CONFIG
[obj
->pwm
] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle
<< GPIOTE_CONFIG_POLARITY_Pos
);
195 NRF_GPIOTE
->CONFIG
[obj
->pwm
] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_HiToLo
<< GPIOTE_CONFIG_POLARITY_Pos
);
200 void pwmout_init(pwmout_t
*obj
, PinName pin
)
202 // determine the channel
203 uint8_t pwmOutSuccess
= 0;
204 PWMName pwm
= (PWMName
)pinmap_peripheral(pin
, PinMap_PWM
);
206 MBED_ASSERT(pwm
!= (PWMName
)NC
);
208 if (PWM_taken
[(uint8_t)pwm
]) {
209 for (uint8_t i
= 1; !pwmOutSuccess
&& (i
<NO_PWMS
); i
++) {
218 PWM_taken
[(uint8_t)pwm
] = 1;
221 if (!pwmOutSuccess
) {
222 error("PwmOut pin mapping failed. All available PWM channels are in use.");
228 gpiote_init(pin
, (uint8_t)pwm
);
229 ppi_init((uint8_t)pwm
);
232 NRF_POWER
->TASKS_CONSTLAT
= 1;
235 timer_init((uint8_t)pwm
);
237 //default to 20ms: standard for servos, and fine for e.g. brightness control
238 pwmout_period_ms(obj
, 20);
239 pwmout_write (obj
, 0);
242 void pwmout_free(pwmout_t
*obj
)
244 MBED_ASSERT(obj
->pwm
!= (PWMName
)NC
);
245 PWM_taken
[obj
->pwm
] = 0;
246 pwmout_write(obj
, 0);
249 void pwmout_write(pwmout_t
*obj
, float value
)
251 uint16_t oldPulseWidth
;
253 NRF_TIMER2
->EVENTS_COMPARE
[3] = 0;
254 NRF_TIMER2
->TASKS_STOP
= 1;
258 } else if (value
> 1.0f
) {
262 oldPulseWidth
= ACTUAL_PULSE
[obj
->pwm
];
263 ACTUAL_PULSE
[obj
->pwm
] = PULSE_WIDTH
[obj
->pwm
] = value
* PERIOD
;
265 if (PULSE_WIDTH
[obj
->pwm
] == 0) {
266 PULSE_WIDTH
[obj
->pwm
] = 1;
267 setModulation(obj
, 0, 0);
268 } else if (PULSE_WIDTH
[obj
->pwm
] == PERIOD
) {
269 PULSE_WIDTH
[obj
->pwm
] = PERIOD
- 1;
270 setModulation(obj
, 0, 1);
271 } else if ((oldPulseWidth
== 0) || (oldPulseWidth
== PERIOD
)) {
272 setModulation(obj
, 1, oldPulseWidth
== PERIOD
);
275 NRF_TIMER2
->INTENSET
= TIMER_INTENSET_COMPARE3_Msk
;
276 NRF_TIMER2
->SHORTS
= TIMER_SHORTS_COMPARE3_CLEAR_Msk
| TIMER_SHORTS_COMPARE3_STOP_Msk
;
277 NRF_TIMER2
->TASKS_START
= 1;
280 float pwmout_read(pwmout_t
*obj
)
282 return ((float)PULSE_WIDTH
[obj
->pwm
] / (float)PERIOD
);
285 void pwmout_period(pwmout_t
*obj
, float seconds
)
287 pwmout_period_us(obj
, seconds
* 1000000.0f
);
290 void pwmout_period_ms(pwmout_t
*obj
, int ms
)
292 pwmout_period_us(obj
, ms
* 1000);
295 // Set the PWM period, keeping the duty cycle the same.
296 void pwmout_period_us(pwmout_t
*obj
, int us
)
298 uint32_t periodInTicks
= us
/ TIMER_PRECISION
;
300 NRF_TIMER2
->EVENTS_COMPARE
[3] = 0;
301 NRF_TIMER2
->TASKS_STOP
= 1;
303 if (periodInTicks
>((1 << 16) - 1)) {
304 PERIOD
= (1 << 16) - 1; //131ms
305 } else if (periodInTicks
<5) {
308 PERIOD
= periodInTicks
;
310 NRF_TIMER2
->INTENSET
= TIMER_INTENSET_COMPARE3_Msk
;
311 NRF_TIMER2
->SHORTS
= TIMER_SHORTS_COMPARE3_CLEAR_Msk
| TIMER_SHORTS_COMPARE3_STOP_Msk
;
312 NRF_TIMER2
->TASKS_START
= 1;
315 void pwmout_pulsewidth(pwmout_t
*obj
, float seconds
)
317 pwmout_pulsewidth_us(obj
, seconds
* 1000000.0f
);
320 void pwmout_pulsewidth_ms(pwmout_t
*obj
, int ms
)
322 pwmout_pulsewidth_us(obj
, ms
* 1000);
325 void pwmout_pulsewidth_us(pwmout_t
*obj
, int us
)
327 uint32_t pulseInTicks
= us
/ TIMER_PRECISION
;
328 uint16_t oldPulseWidth
= ACTUAL_PULSE
[obj
->pwm
];
330 NRF_TIMER2
->EVENTS_COMPARE
[3] = 0;
331 NRF_TIMER2
->TASKS_STOP
= 1;
333 ACTUAL_PULSE
[obj
->pwm
] = PULSE_WIDTH
[obj
->pwm
] = pulseInTicks
;
335 if (PULSE_WIDTH
[obj
->pwm
] == 0) {
336 PULSE_WIDTH
[obj
->pwm
] = 1;
337 setModulation(obj
, 0, 0);
338 } else if (PULSE_WIDTH
[obj
->pwm
] == PERIOD
) {
339 PULSE_WIDTH
[obj
->pwm
] = PERIOD
- 1;
340 setModulation(obj
, 0, 1);
341 } else if ((oldPulseWidth
== 0) || (oldPulseWidth
== PERIOD
)) {
342 setModulation(obj
, 1, oldPulseWidth
== PERIOD
);
344 NRF_TIMER2
->INTENSET
= TIMER_INTENSET_COMPARE3_Msk
;
345 NRF_TIMER2
->SHORTS
= TIMER_SHORTS_COMPARE3_CLEAR_Msk
| TIMER_SHORTS_COMPARE3_STOP_Msk
;
346 NRF_TIMER2
->TASKS_START
= 1;