476 lines
13 KiB
C
476 lines
13 KiB
C
/**
|
|
* \file
|
|
*
|
|
* \brief SAM PWM Driver for SAMB11
|
|
*
|
|
* Copyright (C) 2015-2016 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
|
|
*
|
|
*/
|
|
/*
|
|
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
|
|
*/
|
|
#include "pwm.h"
|
|
#include "system_sam_b.h"
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_output_polarity(enum pwm_device_select device_select)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return LPMCU_MISC_REGS_PWM0_CTRL_OUTPUT_POLARITY;
|
|
case PWM1:
|
|
return LPMCU_MISC_REGS_PWM1_CTRL_OUTPUT_POLARITY;
|
|
case PWM2:
|
|
return LPMCU_MISC_REGS_PWM2_CTRL_OUTPUT_POLARITY;
|
|
case PWM3:
|
|
return LPMCU_MISC_REGS_PWM3_CTRL_OUTPUT_POLARITY;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_agcdata_fmt(enum pwm_device_select device_select)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return LPMCU_MISC_REGS_PWM0_CTRL_AGCDATA_FMT;
|
|
case PWM1:
|
|
return LPMCU_MISC_REGS_PWM1_CTRL_AGCDATA_FMT;
|
|
case PWM2:
|
|
return LPMCU_MISC_REGS_PWM2_CTRL_AGCDATA_FMT;
|
|
case PWM3:
|
|
return LPMCU_MISC_REGS_PWM3_CTRL_AGCDATA_FMT;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_sample_method(enum pwm_device_select device_select, bool value)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return (LPMCU_MISC_REGS_PWM0_CTRL_SAMPLE_METHOD &
|
|
((value) << LPMCU_MISC_REGS_PWM0_CTRL_SAMPLE_METHOD_Pos));
|
|
case PWM1:
|
|
return (LPMCU_MISC_REGS_PWM1_CTRL_SAMPLE_METHOD &
|
|
((value) << LPMCU_MISC_REGS_PWM1_CTRL_SAMPLE_METHOD_Pos));
|
|
case PWM2:
|
|
return (LPMCU_MISC_REGS_PWM2_CTRL_SAMPLE_METHOD &
|
|
((value) << LPMCU_MISC_REGS_PWM2_CTRL_SAMPLE_METHOD_Pos));
|
|
case PWM3:
|
|
return (LPMCU_MISC_REGS_PWM3_CTRL_SAMPLE_METHOD &
|
|
((value) << LPMCU_MISC_REGS_PWM3_CTRL_SAMPLE_METHOD_Pos));
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_period(enum pwm_device_select device_select, enum pwm_period value)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return LPMCU_MISC_REGS_PWM0_CTRL_PWM_PERIOD(value);
|
|
case PWM1:
|
|
return LPMCU_MISC_REGS_PWM1_CTRL_PWM_PERIOD(value);
|
|
case PWM2:
|
|
return LPMCU_MISC_REGS_PWM2_CTRL_PWM_PERIOD(value);
|
|
case PWM3:
|
|
return LPMCU_MISC_REGS_PWM3_CTRL_PWM_PERIOD(value);
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_agcdata_in(
|
|
enum pwm_device_select device_select, \
|
|
bool agcdata_format, \
|
|
uint8_t duty_cycle)
|
|
{
|
|
int32_t agcdata_in;
|
|
|
|
if (agcdata_format) {
|
|
agcdata_in = (uint16_t)((1024 * duty_cycle) / 100);
|
|
} else {
|
|
agcdata_in = (int16_t)((1024 * duty_cycle) / 100 - 512);
|
|
}
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return LPMCU_MISC_REGS_PWM0_CTRL_AGCDATA_IN(agcdata_in);
|
|
case PWM1:
|
|
return LPMCU_MISC_REGS_PWM1_CTRL_AGCDATA_IN(agcdata_in);
|
|
case PWM2:
|
|
return LPMCU_MISC_REGS_PWM2_CTRL_AGCDATA_IN(agcdata_in);
|
|
case PWM3:
|
|
return LPMCU_MISC_REGS_PWM3_CTRL_AGCDATA_IN(agcdata_in);
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal Get the register configuration values by PWM device
|
|
*/
|
|
static uint32_t _pwm_reg_clock_sel(enum pwm_device_select device_select, enum pwm_clock_select value)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
return LPMCU_MISC_REGS_PWM0_CTRL_CLOCK_SEL(value);
|
|
case PWM1:
|
|
return LPMCU_MISC_REGS_PWM1_CTRL_CLOCK_SEL(value);
|
|
case PWM2:
|
|
return LPMCU_MISC_REGS_PWM2_CTRL_CLOCK_SEL(value);
|
|
case PWM3:
|
|
return LPMCU_MISC_REGS_PWM3_CTRL_CLOCK_SEL(value);
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Initializes a pwm configuration structure to defaults.
|
|
*
|
|
* Initializes a given pwm configuration structure to a set of
|
|
* known default values. This function should be called on all new
|
|
* instances of these configuration structures before being modified by the
|
|
* user application.
|
|
*
|
|
* The default configuration is as follows:
|
|
* \li Not to inverse the polarity
|
|
* \li Sample method 0
|
|
* \li PWM period is 4
|
|
* \li Duty cycle is 50%
|
|
* \li Clock is 26MHz
|
|
* \li Output frequency is 25.4KHz
|
|
* \li Pinmux pad
|
|
*
|
|
* \param[out] config Configuration structure to initialize to default values
|
|
*/
|
|
void pwm_get_config_defaults(struct pwm_config *const config)
|
|
{
|
|
config->output_polarity = false;
|
|
config->agcdata_format = false;
|
|
config->sample_method = PWM_SAMPLE_METHOD_0;
|
|
config->period = PWM_PERIOD_4;
|
|
config->duty_cycle = 50;
|
|
config->clock_select = PWM_CLOCK_SELECT_26_0;
|
|
config->pin_number_pad = 0;
|
|
config->pinmux_sel_pad = 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Set the duty cycle of the PWM module.
|
|
*
|
|
* This function will set the duty cycle of the PWM module, based on the values
|
|
* of setting.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
* \param[in] duty_cycle This value specifies the duty cycle(%)
|
|
*/
|
|
void pwm_set_duty_cycle(enum pwm_device_select device_select, \
|
|
uint8_t duty_cycle)
|
|
{
|
|
bool agcdata_format;
|
|
uint32_t temp;
|
|
|
|
switch(device_select) {
|
|
case PWM0:
|
|
agcdata_format = LPMCU_MISC_REGS0->PWM0_CTRL.bit.AGCDATA_FMT;
|
|
temp = LPMCU_MISC_REGS0->PWM0_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM0_CTRL_AGCDATA_IN_Msk;
|
|
temp |= _pwm_reg_agcdata_in(device_select, agcdata_format, duty_cycle);
|
|
LPMCU_MISC_REGS0->PWM0_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM1:
|
|
agcdata_format = LPMCU_MISC_REGS0->PWM1_CTRL.bit.AGCDATA_FMT;
|
|
temp = LPMCU_MISC_REGS0->PWM1_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM1_CTRL_AGCDATA_IN_Msk;
|
|
temp |= _pwm_reg_agcdata_in(device_select, agcdata_format, duty_cycle);
|
|
LPMCU_MISC_REGS0->PWM1_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM2:
|
|
agcdata_format = LPMCU_MISC_REGS0->PWM2_CTRL.bit.AGCDATA_FMT;
|
|
temp = LPMCU_MISC_REGS0->PWM2_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM2_CTRL_AGCDATA_IN_Msk;
|
|
temp |= _pwm_reg_agcdata_in(device_select, agcdata_format, duty_cycle);
|
|
LPMCU_MISC_REGS0->PWM2_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM3:
|
|
agcdata_format = LPMCU_MISC_REGS0->PWM3_CTRL.bit.AGCDATA_FMT;
|
|
temp = LPMCU_MISC_REGS0->PWM3_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM3_CTRL_AGCDATA_IN_Msk;
|
|
temp |= _pwm_reg_agcdata_in(device_select, agcdata_format, duty_cycle);
|
|
LPMCU_MISC_REGS0->PWM3_CTRL.reg = temp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Set the period of the PWM module.
|
|
*
|
|
* This function will set the frequence of the PWM module, based on the values
|
|
* of setting period.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
* \param[in] period Programmable PWM update period
|
|
*/
|
|
void pwm_set_period(enum pwm_device_select device_select, \
|
|
enum pwm_period period)
|
|
{
|
|
uint32_t reg_value;
|
|
uint32_t temp;
|
|
|
|
if (period > PWM_PERIOD_8) {
|
|
reg_value = PWM_PERIOD_4;
|
|
}
|
|
reg_value = _pwm_reg_period(device_select, period);
|
|
|
|
switch(device_select) {
|
|
case PWM0:
|
|
temp = LPMCU_MISC_REGS0->PWM0_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM0_CTRL_PWM_PERIOD_Msk;
|
|
temp |= reg_value;
|
|
LPMCU_MISC_REGS0->PWM0_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM1:
|
|
temp = LPMCU_MISC_REGS0->PWM1_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM1_CTRL_PWM_PERIOD_Msk;
|
|
temp |= reg_value;
|
|
LPMCU_MISC_REGS0->PWM1_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM2:
|
|
temp = LPMCU_MISC_REGS0->PWM2_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM2_CTRL_PWM_PERIOD_Msk;
|
|
temp |= reg_value;
|
|
LPMCU_MISC_REGS0->PWM2_CTRL.reg = temp;
|
|
break;
|
|
|
|
case PWM3:
|
|
temp = LPMCU_MISC_REGS0->PWM3_CTRL.reg;
|
|
temp &= ~LPMCU_MISC_REGS_PWM3_CTRL_PWM_PERIOD_Msk;
|
|
temp |= reg_value;
|
|
LPMCU_MISC_REGS0->PWM3_CTRL.reg = temp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Initializes the PWM module
|
|
*
|
|
* This function will initialize the PWM module, based on the values
|
|
* of the config struct.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
* \param[in] config Pointer to the config struct
|
|
*
|
|
* \return The status of the configuration.
|
|
* \retval STATUS_ERR_UNSUPPORTED_DEV If unsupported device were provided
|
|
* \retval STATUS_OK If the configuration was written
|
|
*/
|
|
enum status_code pwm_init(enum pwm_device_select device_select, \
|
|
const struct pwm_config *const config)
|
|
{
|
|
uint32_t reg_value = 0;
|
|
|
|
if (device_select > PWM3) {
|
|
return STATUS_ERR_UNSUPPORTED_DEV;
|
|
}
|
|
|
|
if (config->output_polarity) {
|
|
reg_value |= _pwm_reg_output_polarity(device_select);
|
|
}
|
|
|
|
if (config->agcdata_format) {
|
|
reg_value |= _pwm_reg_agcdata_fmt(device_select);
|
|
}
|
|
|
|
reg_value |= _pwm_reg_sample_method(device_select, config->sample_method);
|
|
|
|
/* If period > 8 will be set to 4 as default. */
|
|
if (config->period > PWM_PERIOD_8) {
|
|
reg_value |= _pwm_reg_period(device_select, PWM_PERIOD_4);
|
|
} else {
|
|
reg_value |= _pwm_reg_period(device_select, config->period);
|
|
}
|
|
reg_value |= _pwm_reg_agcdata_in(device_select, config->agcdata_format, \
|
|
config->duty_cycle);
|
|
|
|
reg_value |= _pwm_reg_clock_sel(device_select, config->clock_select);
|
|
|
|
switch(device_select) {
|
|
case PWM0:
|
|
LPMCU_MISC_REGS0->PWM0_CTRL.reg = reg_value;
|
|
break;
|
|
|
|
case PWM1:
|
|
LPMCU_MISC_REGS0->PWM1_CTRL.reg = reg_value;
|
|
break;
|
|
|
|
case PWM2:
|
|
LPMCU_MISC_REGS0->PWM2_CTRL.reg = reg_value;
|
|
break;
|
|
|
|
case PWM3:
|
|
LPMCU_MISC_REGS0->PWM3_CTRL.reg = reg_value;
|
|
break;
|
|
}
|
|
|
|
struct gpio_config config_gpio;
|
|
gpio_get_config_defaults(&config_gpio);
|
|
config_gpio.direction = GPIO_PIN_DIR_OUTPUT;
|
|
gpio_pin_set_config(config->pin_number_pad, &config_gpio);
|
|
gpio_pinmux_cofiguration(config->pin_number_pad, \
|
|
(uint16_t)(config->pinmux_sel_pad));
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Enables the PWM module
|
|
*
|
|
* This function will enable the PWM module.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
*/
|
|
void pwm_enable(enum pwm_device_select device_select)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
system_clock_peripheral_enable(PERIPHERAL_PWM0);
|
|
LPMCU_MISC_REGS0->PWM0_CTRL.reg |= LPMCU_MISC_REGS_PWM0_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM1:
|
|
system_clock_peripheral_enable(PERIPHERAL_PWM1);
|
|
LPMCU_MISC_REGS0->PWM1_CTRL.reg |= LPMCU_MISC_REGS_PWM1_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM2:
|
|
system_clock_peripheral_enable(PERIPHERAL_PWM2);
|
|
LPMCU_MISC_REGS0->PWM2_CTRL.reg |= LPMCU_MISC_REGS_PWM2_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM3:
|
|
system_clock_peripheral_enable(PERIPHERAL_PWM3);
|
|
LPMCU_MISC_REGS0->PWM3_CTRL.reg |= LPMCU_MISC_REGS_PWM3_CTRL_PWM_EN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Disable the PWM module
|
|
*
|
|
* This function will disable the PWM module.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
*/
|
|
void pwm_disable(enum pwm_device_select device_select)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
system_clock_peripheral_disable(PERIPHERAL_PWM0);
|
|
LPMCU_MISC_REGS0->PWM0_CTRL.reg &= ~LPMCU_MISC_REGS_PWM0_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM1:
|
|
system_clock_peripheral_disable(PERIPHERAL_PWM1);
|
|
LPMCU_MISC_REGS0->PWM1_CTRL.reg &= ~LPMCU_MISC_REGS_PWM1_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM2:
|
|
system_clock_peripheral_disable(PERIPHERAL_PWM2);
|
|
LPMCU_MISC_REGS0->PWM2_CTRL.reg &= ~LPMCU_MISC_REGS_PWM2_CTRL_PWM_EN;
|
|
break;
|
|
|
|
case PWM3:
|
|
system_clock_peripheral_disable(PERIPHERAL_PWM3);
|
|
LPMCU_MISC_REGS0->PWM3_CTRL.reg &= ~LPMCU_MISC_REGS_PWM3_CTRL_PWM_EN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Reset the PWM module
|
|
*
|
|
* This function will reset the PWM module.
|
|
*
|
|
* \param[in] device_select PWM device
|
|
*/
|
|
void pwm_reset(enum pwm_device_select device_select)
|
|
{
|
|
switch (device_select) {
|
|
case PWM0:
|
|
system_peripheral_reset(PERIPHERAL_PWM0);
|
|
break;
|
|
|
|
case PWM1:
|
|
system_peripheral_reset(PERIPHERAL_PWM1);
|
|
break;
|
|
|
|
case PWM2:
|
|
system_peripheral_reset(PERIPHERAL_PWM2);
|
|
break;
|
|
|
|
case PWM3:
|
|
system_peripheral_reset(PERIPHERAL_PWM3);
|
|
break;
|
|
}
|
|
} |