Files
bacnet_stack/ports/xplained/ASF/xmega/services/pwm/pwm.c
T
2019-10-08 23:47:53 -05:00

245 lines
6.5 KiB
C

/**
* \file
*
* \brief PWM service for XMEGA.
*
* Copyright (c) 2012 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \asf_license_stop
*
*/
#include <asf.h>
/**
* \brief Calculate TC settings from PWM frequency
*
* This function will find the correct TC settings (clock prescaler and
* period) which will give the wanted PWM frequency.
*
* \note Since we want to be able to run the PWM at all duty-cycles ranging
* from 0-100%, we require a period of at least 100 to achieve this. Thus, the
* highest possible PWM frequency is CPU frequency / 100.
*
* \param config Pointer to PWM configuration.
* \param freq_hz Wanted PWM frequency in Hz.
*/
void pwm_set_frequency(struct pwm_config *config, uint16_t freq_hz)
{
uint32_t cpu_hz = sysclk_get_cpu_hz();
uint16_t smallest_div;
uint16_t dividor;
/* Avoid division by zero. */
Assert(freq_hz != 0);
/* Calculate the smallest divider for the requested frequency
related to the CPU frequency */
smallest_div = cpu_hz / freq_hz / 0xFFFF;
if (smallest_div < 1) {
dividor = 1;
config->clk_sel = PWM_CLK_DIV1;
} else if (smallest_div < 2) {
dividor = 2;
config->clk_sel = PWM_CLK_DIV2;
} else if (smallest_div < 4) {
dividor = 4;
config->clk_sel = PWM_CLK_DIV4;
} else if (smallest_div < 8) {
dividor = 8;
config->clk_sel = PWM_CLK_DIV8;
} else if (smallest_div < 64) {
dividor = 64;
config->clk_sel = PWM_CLK_DIV64;
} else if (smallest_div < 256) {
dividor = 256;
config->clk_sel = PWM_CLK_DIV256;
} else {
dividor = 1024;
config->clk_sel = PWM_CLK_DIV1024;
}
/* Calculate the period from the just found divider */
config->period = cpu_hz / dividor / freq_hz;
/* Make sure our period is at least 100 ticks so we are able to provide
a full range (0-100% duty cycle */
if (config->period < 100) {
/* The period is too short. */
config->clk_sel = PWM_CLK_OFF;
config->period = 0;
Assert(false);
}
}
/**
* \brief Initialize PWM configuration struct and set correct I/O pin to output
*
* \param config Pointer to PWM configuration struct.
* \param tc \ref pwm_tc_t "TC" to use for this PWM.
* \param channel \ref pwm_channel_t "CC channel" to use for this PWM.
* \param freq_hz Frequency to use for this PWM.
*/
void pwm_init(struct pwm_config *config, enum pwm_tc_t tc,
enum pwm_channel_t channel, uint16_t freq_hz)
{
/* Number of channels for this TC */
uint8_t num_chan = 0;
UNUSED(num_chan);
/* Set TC and correct I/O pin to output */
switch (tc) {
#if defined(TCC0)
case PWM_TCC0:
config->tc = &TCC0;
PORTC.DIR |= (1 << (channel-1));
num_chan = 4;
break;
#endif
#if defined(TCC1)
case PWM_TCC1:
config->tc = &TCC1;
PORTC.DIR |= (1 << (channel+3));
num_chan = 2;
break;
#endif
#if defined(TCD0)
case PWM_TCD0:
config->tc = &TCD0;
PORTD.DIR |= (1 << (channel-1));
num_chan = 4;
break;
#endif
#if defined(TCD1)
case PWM_TCD1:
config->tc = &TCD1;
PORTD.DIR |= (1 << (channel+3));
num_chan = 2;
break;
#endif
#if defined(TCE0)
case PWM_TCE0:
config->tc = &TCE0;
PORTE.DIR |= (1 << (channel-1));
num_chan = 4;
break;
#endif
#if defined(TCE1)
case PWM_TCE1:
config->tc = &TCE1;
PORTE.DIR |= (1 << (channel+3));
num_chan = 2;
break;
#endif
#if defined(TCF0)
case PWM_TCF0:
config->tc = &TCF0;
PORTF.DIR |= (1 << (channel-1));
num_chan = 4;
break;
#endif
#if defined(TCF1)
case PWM_TCF1:
config->tc = &TCF1;
PORTF.DIR |= (1 << (channel+3));
num_chan = 2;
break;
#endif
default:
Assert(false);
break;
}
/* Make sure we are not given a channel number larger
than this TC can handle */
Assert(channel <= num_chan);
config->channel = channel;
/* Set the correct cc_mask */
switch (channel) {
case PWM_CH_A:
config->cc_mask = TC_CCAEN;
break;
case PWM_CH_B:
config->cc_mask = TC_CCBEN;
break;
case PWM_CH_C:
config->cc_mask = TC_CCCEN;
break;
case PWM_CH_D:
config->cc_mask = TC_CCDEN;
break;
default:
Assert(false);
break;
}
/* Enable peripheral clock for this TC */
tc_enable(config->tc);
/* Set this TC's waveform generator in single slope mode */
tc_set_wgm(config->tc, TC_WG_SS);
/* Default values (disable TC and set minimum period)*/
config->period = 0;
config->clk_sel = PWM_CLK_OFF;
tc_write_clock_source(config->tc, PWM_CLK_OFF);
/* Set the PWM frequency */
pwm_set_frequency(config, freq_hz);
}
/**
* \brief Start a PWM channel
*
* This function enables a channel with a given duty cycle.
*
* \param *config Pointer to the PWM configuration struct
* \param duty_cycle_scale Duty cycle as a value between 0 and 100.
*/
void pwm_start(struct pwm_config *config, uint8_t duty_cycle_scale)
{
/* Set given duty cycle */
pwm_set_duty_cycle_percent(config, duty_cycle_scale);
/* Set correct TC period */
tc_write_period(config->tc, config->period);
/* Enable CC channel for this TC */
tc_enable_cc_channels(config->tc, config->cc_mask);
/* Enable TC by setting correct clock prescaler */
tc_write_clock_source(config->tc, config->clk_sel);
}