Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: frdm_kl05z_gpio_test
Fork of mbed-src by
targets/hal/TARGET_NORDIC/TARGET_NRF51822/pwmout_api.c
- Committer:
- mbed_official
- Date:
- 2014-07-08
- Revision:
- 250:a49055e7a707
- Parent:
- 229:9bd26d142f33
- Child:
- 251:de9a1e4ffd79
File content as of revision 250:a49055e7a707:
/* mbed Microcontroller Library
* Copyright (c) 2013 Nordic Semiconductor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mbed_assert.h"
#include "pwmout_api.h"
#include "cmsis.h"
#include "pinmap.h"
#include "mbed_error.h"
#define NO_PWMS 3
#define TIMER_PRECISION 4 //4us ticks
#define TIMER_PRESCALER 6 //4us ticks = 16Mhz/(2**6)
static const PinMap PinMap_PWM[] = {
{p0, PWM_1, 1},
{p1, PWM_1, 1},
{p2, PWM_1, 1},
{p3, PWM_1, 1},
{p4, PWM_1, 1},
{p5, PWM_1, 1},
{p6, PWM_1, 1},
{p7, PWM_1, 1},
{p8, PWM_1, 1},
{p9, PWM_1, 1},
{p10, PWM_1, 1},
{p11, PWM_1, 1},
{p12, PWM_1, 1},
{p13, PWM_1, 1},
{p14, PWM_1, 1},
{p15, PWM_1, 1},
{p16, PWM_1, 1},
{p17, PWM_1, 1},
{p18, PWM_1, 1},
{p19, PWM_1, 1},
{p20, PWM_1, 1},
{p21, PWM_1, 1},
{p22, PWM_1, 1},
{p23, PWM_1, 1},
{p24, PWM_1, 1},
{p25, PWM_1, 1},
{p28, PWM_1, 1},
{p29, PWM_1, 1},
{p30, PWM_1, 1},
{NC, NC, 0}
};
static NRF_TIMER_Type *Timers[1] = {
NRF_TIMER2
};
uint16_t PERIOD = 20000/TIMER_PRECISION;//20ms
uint8_t PWM_taken[NO_PWMS] = {0,0,0};
uint16_t PULSE_WIDTH[NO_PWMS] = {1,1,1};//set to 1 instead of 0
uint16_t ACTUAL_PULSE[NO_PWMS] = {0,0,0};
/** @brief Function for handling timer 2 peripheral interrupts.
*/
#ifdef __cplusplus
extern "C" {
#endif
void TIMER2_IRQHandler(void)
{
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->CC[3] = PERIOD;
if(PWM_taken[0]){
NRF_TIMER2->CC[0] = PULSE_WIDTH[0];
}
if(PWM_taken[1]){
NRF_TIMER2->CC[1] = PULSE_WIDTH[1];
}
if(PWM_taken[2]){
NRF_TIMER2->CC[2] = PULSE_WIDTH[2];
}
NRF_TIMER2->TASKS_START = 1;
}
#ifdef __cplusplus
}
#endif
/** @brief Function for initializing the Timer peripherals.
*/
void timer_init(uint8_t pwmChoice)
{
NRF_TIMER_Type *timer = Timers[0];
timer->TASKS_STOP = 0;
if(pwmChoice == 0){
timer->POWER = 0;
timer->POWER = 1;
timer->MODE = TIMER_MODE_MODE_Timer;
timer->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
timer->PRESCALER = TIMER_PRESCALER;
timer->CC[3] = PERIOD;
}
timer->CC[pwmChoice] = PULSE_WIDTH[pwmChoice];
//high priority application interrupt
NVIC_SetPriority(TIMER2_IRQn, 1);
NVIC_EnableIRQ(TIMER2_IRQn);
timer->TASKS_START = 0x01;
}
/** @brief Function for initializing the GPIO Tasks/Events peripheral.
*/
void gpiote_init(PinName pin,uint8_t channel_number)
{
// Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
| (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
| (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
| (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos)
| (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
NRF_GPIO->OUTCLR = (1UL << pin);
// Configure GPIOTE channel 0 to toggle the PWM pin state
// @note Only one GPIOTE task can be connected to an output pin.
/* Configure channel to Pin31, not connected to the pin, and configure as a tasks that will set it to proper level */
NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
(31UL << GPIOTE_CONFIG_PSEL_Pos) |
(GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
/* Launch the task to take the GPIOTE channel output to the desired level */
NRF_GPIOTE->TASKS_OUT[channel_number] = 1;
/* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly.
If it does not, the channel output inheritance sets the proper level. */
NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos);// ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);//
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
}
/** @brief Function for initializing the Programmable Peripheral Interconnect peripheral.
*/
static void ppi_init(uint8_t pwm)
{
//using ppi channels 0-7 (only 0-7 are available)
uint8_t channel_number = 2*pwm;
NRF_TIMER_Type *timer = Timers[0];
// Configure PPI channel 0 to toggle ADVERTISING_LED_PIN_NO on every TIMER1 COMPARE[0] match
NRF_PPI->CH[channel_number].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
NRF_PPI->CH[channel_number+1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[pwm];
NRF_PPI->CH[channel_number].EEP = (uint32_t)&timer->EVENTS_COMPARE[pwm];
NRF_PPI->CH[channel_number+1].EEP = (uint32_t)&timer->EVENTS_COMPARE[3];
// Enable PPI channels.
NRF_PPI->CHEN |= (1 << channel_number)
| (1 << (channel_number+1));
}
void setModulation(pwmout_t* obj,uint8_t toggle,uint8_t high)
{
if(high){
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
if(toggle){
NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
}
else{
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos);
}
}
else{
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos);
if(toggle){
NRF_GPIOTE->CONFIG[obj->pwm] |= (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
}
else{
NRF_GPIOTE->CONFIG[obj->pwm] &= ~((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
NRF_GPIOTE->CONFIG[obj->pwm] |= ((uint32_t)GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos);
}
}
}
void pwmout_init(pwmout_t* obj, PinName pin) {
// determine the channel
uint8_t pwmOutSuccess = 0;
PWMName pwm = (PWMName)pinmap_peripheral(pin, PinMap_PWM);
MBED_ASSERT(pwm != (PWMName)NC);
if(PWM_taken[(uint8_t)pwm]){
for(uint8_t i = 1; !pwmOutSuccess && (i<NO_PWMS) ;i++){
if(!PWM_taken[i]){
pwm = (PWMName)i;
PWM_taken[i] = 1;
pwmOutSuccess = 1;
}
}
}
else{
pwmOutSuccess = 1;
PWM_taken[(uint8_t)pwm] = 1;
}
if(!pwmOutSuccess){
error("PwmOut pin mapping failed. All available PWM channels are in use.");
}
obj->pwm = pwm;
obj->pin = pin;
gpiote_init(pin,(uint8_t)pwm);
ppi_init((uint8_t)pwm);
if(pwm == 0){
NRF_POWER->TASKS_CONSTLAT = 1;
}
timer_init((uint8_t)pwm);
//default to 20ms: standard for servos, and fine for e.g. brightness control
pwmout_period_ms(obj, 20);
pwmout_write (obj, 0);
}
void pwmout_free(pwmout_t* obj) {
// [TODO]
}
void pwmout_write(pwmout_t* obj, float value) {
uint16_t oldPulseWidth;
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
if (value < 0.0f) {
value = 0.0;
} else if (value > 1.0f) {
value = 1.0;
}
oldPulseWidth = ACTUAL_PULSE[obj->pwm];
ACTUAL_PULSE[obj->pwm] = PULSE_WIDTH[obj->pwm] = value* PERIOD;
if(PULSE_WIDTH[obj->pwm] == 0){
PULSE_WIDTH[obj->pwm] = 1;
setModulation(obj,0,0);
}
else if(PULSE_WIDTH[obj->pwm] == PERIOD){
PULSE_WIDTH[obj->pwm] = PERIOD-1;
setModulation(obj,0,1);
}
else if( (oldPulseWidth == 0) || (oldPulseWidth == PERIOD) ){
setModulation(obj,1,oldPulseWidth == PERIOD);
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
}
float pwmout_read(pwmout_t* obj) {
return ((float)PULSE_WIDTH[obj->pwm]/(float)PERIOD);
}
void pwmout_period(pwmout_t* obj, float seconds) {
pwmout_period_us(obj, seconds * 1000000.0f);
}
void pwmout_period_ms(pwmout_t* obj, int ms) {
pwmout_period_us(obj, ms * 1000);
}
// Set the PWM period, keeping the duty cycle the same.
void pwmout_period_us(pwmout_t* obj, int us) {
uint32_t periodInTicks = us/TIMER_PRECISION;
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
if(periodInTicks>((1<<16) -1))
{
PERIOD = (1<<16 )-1;//131ms
}
else if(periodInTicks<5){
PERIOD = 5;
}
else{
PERIOD =periodInTicks;
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
}
void pwmout_pulsewidth(pwmout_t* obj, float seconds) {
pwmout_pulsewidth_us(obj, seconds * 1000000.0f);
}
void pwmout_pulsewidth_ms(pwmout_t* obj, int ms) {
pwmout_pulsewidth_us(obj, ms * 1000);
}
void pwmout_pulsewidth_us(pwmout_t* obj, int us) {
uint32_t pulseInTicks = us/TIMER_PRECISION;
uint16_t oldPulseWidth = ACTUAL_PULSE[obj->pwm];
NRF_TIMER2->EVENTS_COMPARE[3] = 0;
NRF_TIMER2->TASKS_STOP = 1;
ACTUAL_PULSE[obj->pwm] = PULSE_WIDTH[obj->pwm] = pulseInTicks;
if(PULSE_WIDTH[obj->pwm] == 0){
PULSE_WIDTH[obj->pwm] = 1;
setModulation(obj,0,0);
}
else if(PULSE_WIDTH[obj->pwm] == PERIOD){
PULSE_WIDTH[obj->pwm] = PERIOD-1;
setModulation(obj,0,1);
}
else if( (oldPulseWidth == 0) || (oldPulseWidth == PERIOD) ){
setModulation(obj,1,oldPulseWidth == PERIOD);
}
NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE3_Msk;
NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE3_CLEAR_Msk | TIMER_SHORTS_COMPARE3_STOP_Msk;
NRF_TIMER2->TASKS_START = 1;
}
