at91lib/peripherals/pwmc/pwmc2.c

590 lines
24 KiB
C

/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2008, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: 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
* 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.
* ----------------------------------------------------------------------------
*/
//------------------------------------------------------------------------------
// Headers
//------------------------------------------------------------------------------
#include "pwmc2.h"
#include <board.h>
#include <utility/assert.h>
#include <utility/trace.h>
//------------------------------------------------------------------------------
// Local functions
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
/// Finds a prescaler/divisor couple to generate the desired frequency from
/// MCK.
/// Returns the value to enter in PWMC_MR or 0 if the configuration cannot be
/// met.
/// \param frequency Desired frequency in Hz.
/// \param mck Master clock frequency in Hz.
//------------------------------------------------------------------------------
static unsigned short FindClockConfiguration(
unsigned int frequency,
unsigned int mck)
{
unsigned int divisors[11] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};
unsigned char divisor = 0;
unsigned int prescaler;
SANITY_CHECK(frequency < mck);
// Find prescaler and divisor values
prescaler = (mck / divisors[divisor]) / frequency;
while ((prescaler > 255) && (divisor < 11)) {
divisor++;
prescaler = (mck / divisors[divisor]) / frequency;
}
// Return result
if (divisor < 11) {
TRACE_DEBUG("Found divisor=%u and prescaler=%u for freq=%uHz\n\r",
divisors[divisor], prescaler, frequency);
return prescaler | (divisor << 8);
}
else {
return 0;
}
}
//------------------------------------------------------------------------------
// Global functions
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
/// Configures PWM a channel with the given parameters, basic configure function.
/// The PWM controller must have been clocked in the PMC prior to calling this
/// function.
/// Beware: this function disables the channel. It waits until disable is effective.
/// \param channel Channel number.
/// \param prescaler Channel prescaler.
/// \param alignment Channel alignment.
/// \param polarity Channel polarity.
//------------------------------------------------------------------------------
void PWMC_ConfigureChannel(
unsigned char channel,
unsigned int prescaler,
unsigned int alignment,
unsigned int polarity)
{
SANITY_CHECK(prescaler < AT91C_PWMC_CPRE_MCKB);
SANITY_CHECK((alignment & ~AT91C_PWMC_CALG) == 0);
SANITY_CHECK((polarity & ~AT91C_PWMC_CPOL) == 0);
// Disable channel (effective at the end of the current period)
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) != 0) {
AT91C_BASE_PWMC->PWMC_DIS = 1 << channel;
while ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) != 0);
}
// Configure channel
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CMR = prescaler | alignment | polarity;
}
//------------------------------------------------------------------------------
/// Configures PWM a channel with the given parameters, extend configure function.
/// The PWM controller must have been clocked in the PMC prior to calling this
/// function.
/// Beware: this function disables the channel. It waits until disable is effective.
/// \param channel Channel number.
/// \param prescaler Channel prescaler.
/// \param alignment Channel alignment.
/// \param polarity Channel polarity.
/// \param countEventSelect Channel counter event selection.
/// \param DTEnable Channel dead time generator enable.
/// \param DTHInverte Channel Dead-Time PWMHx output Inverted.
/// \param DTLInverte Channel Dead-Time PWMHx output Inverted.
//------------------------------------------------------------------------------
void PWMC_ConfigureChannelExt(
unsigned char channel,
unsigned int prescaler,
unsigned int alignment,
unsigned int polarity,
unsigned int countEventSelect,
unsigned int DTEnable,
unsigned int DTHInverte,
unsigned int DTLInverte)
{
SANITY_CHECK(prescaler < AT91C_PWMC_CPRE_MCKB);
SANITY_CHECK((alignment & ~AT91C_PWMC_CALG) == 0);
SANITY_CHECK((polarity & ~AT91C_PWMC_CPOL) == 0);
SANITY_CHECK((countEventSelect & ~AT91C_PWMC_CES) == 0);
SANITY_CHECK((DTEnable & ~AT91C_PWMC_DTE) == 0);
SANITY_CHECK((DTHInverte & ~AT91C_PWMC_DTHI) == 0);
SANITY_CHECK((DTLInverte & ~AT91C_PWMC_DTLI) == 0);
// Disable channel (effective at the end of the current period)
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) != 0) {
AT91C_BASE_PWMC->PWMC_DIS = 1 << channel;
while ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) != 0);
}
// Configure channel
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CMR = prescaler | alignment | polarity |
countEventSelect | DTEnable | DTHInverte | DTLInverte;
}
//------------------------------------------------------------------------------
/// Configures PWM clocks A & B to run at the given frequencies. This function
/// finds the best MCK divisor and prescaler values automatically.
/// \param clka Desired clock A frequency (0 if not used).
/// \param clkb Desired clock B frequency (0 if not used).
/// \param mck Master clock frequency.
//------------------------------------------------------------------------------
void PWMC_ConfigureClocks(unsigned int clka, unsigned int clkb, unsigned int mck)
{
unsigned int mode = 0;
unsigned int result;
// Clock A
if (clka != 0) {
result = FindClockConfiguration(clka, mck);
ASSERT(result != 0, "-F- Could not generate the desired PWM frequency (%uHz)\n\r", clka);
mode |= result;
}
// Clock B
if (clkb != 0) {
result = FindClockConfiguration(clkb, mck);
ASSERT(result != 0, "-F- Could not generate the desired PWM frequency (%uHz)\n\r", clkb);
mode |= (result << 16);
}
// Configure clocks
TRACE_DEBUG("Setting PWMC_MR = 0x%08X\n\r", mode);
AT91C_BASE_PWMC->PWMC_MR = mode;
}
//------------------------------------------------------------------------------
/// Sets the period value used by a PWM channel. This function writes directly
/// to the CPRD register if the channel is disabled; otherwise, it uses the
/// update register CUPD.
/// \param channel Channel number.
/// \param period Period value.
//------------------------------------------------------------------------------
void PWMC_SetPeriod(unsigned char channel, unsigned short period)
{
// If channel is disabled, write to CPRD
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) == 0) {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CPRDR = period;
}
// Otherwise use update register
else {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CPRDUPDR = period;
}
}
//------------------------------------------------------------------------------
/// Sets the duty cycle used by a PWM channel. This function writes directly to
/// the CDTY register if the channel is disabled; otherwise it uses the
/// update register CUPD.
/// Note that the duty cycle must always be inferior or equal to the channel
/// period.
/// \param channel Channel number.
/// \param duty Duty cycle value.
//------------------------------------------------------------------------------
void PWMC_SetDutyCycle(unsigned char channel, unsigned short duty)
{
SANITY_CHECK(duty <= AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CPRDR);
// SAM7S errata
#if defined(at91sam7s16) || defined(at91sam7s161) || defined(at91sam7s32) \
|| defined(at91sam7s321) || defined(at91sam7s64) || defined(at91sam7s128) \
|| defined(at91sam7s256) || defined(at91sam7s512)
ASSERT(duty > 0, "-F- Duty cycle value 0 is not permitted on SAM7S chips.\n\r");
ASSERT((duty > 1) || (AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CMR & AT91C_PWMC_CALG),
"-F- Duty cycle value 1 is not permitted in left-aligned mode on SAM7S chips.\n\r");
#endif
// If channel is disabled, write to CDTY
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) == 0) {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CDTYR = duty;
}
// Otherwise use update register
else {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CDTYUPDR = duty;
}
}
//------------------------------------------------------------------------------
/// Sets the dead time used by a PWM channel. This function writes directly to
/// the DTR register if the channel is disabled; otherwise it uses the
/// update register DTUPDR.
/// Note that the dead time must always be inferior or equal to the channel
/// period.
/// \param channel Channel number.
/// \param timeH Dead time value for PWMHx output.
/// \param timeL Dead time value for PWMLx output.
//------------------------------------------------------------------------------
void PWMC_SetDeadTime(unsigned char channel, unsigned short timeH, unsigned short timeL)
{
SANITY_CHECK(timeH <= AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CPRDR);
SANITY_CHECK(timeL <= AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_CPRDR);
// If channel is disabled, write to DTR
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) == 0) {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_DTR = timeH | (timeL << 16);
}
// Otherwise use update register
else {
AT91C_BASE_PWMC->PWMC_CH[channel].PWMC_DTUPDR = timeH | (timeL << 16);
}
}
//------------------------------------------------------------------------------
/// Configures Syncronous channel with the given parameters.
/// Beware: At this time, the channels should be disabled.
/// \param channels Bitwise OR of Syncronous channels.
/// \param updateMode Syncronous channel update mode.
/// \param requestMode PDC transfer request mode.
/// \param requestComparisonSelect PDC transfer request comparison selection.
//------------------------------------------------------------------------------
void PWMC_ConfigureSyncChannel(
unsigned int channels,
unsigned int updateMode,
unsigned int requestMode,
unsigned int requestComparisonSelect)
{
AT91C_BASE_PWMC->PWMC_SYNC = channels | updateMode | requestMode
| requestComparisonSelect;
}
//------------------------------------------------------------------------------
/// Sets the update period of the synchronous channels.
/// This function writes directly to
/// the SCUP register if the channel #0 is disabled; otherwise it uses the
/// update register SCUPUPD.
/// \param period update period.
//------------------------------------------------------------------------------
void PWMC_SetSyncChannelUpdatePeriod(unsigned char period)
{
// If channel is disabled, write to SCUP
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << 0)) == 0) {
AT91C_BASE_PWMC->PWMC_SCUP = period;
}
// Otherwise use update register
else {
AT91C_BASE_PWMC->PWMC_SCUPUPD = period;
}
}
//------------------------------------------------------------------------------
/// Sets synchronous channels update unlock.
/// Note: If the UPDM field is set to 0, writing the UPDULOCK bit to 1
/// triggers the update of the period value, the duty-cycle and
/// the dead-time values of synchronous channels at the beginning
/// of the next PWM period. If the field UPDM is set to 1 or 2,
/// writing the UPDULOCK bit to 1 triggers only the update of
/// the period value and of the dead-time values of synchronous channels.
/// This bit is automatically reset when the update is done.
//------------------------------------------------------------------------------
void PWMC_SetSyncChannelUpdateUnlock(void)
{
AT91C_BASE_PWMC->PWMC_UPCR = AT91C_PWMC_UPDULOCK;
}
//------------------------------------------------------------------------------
/// Enables the given PWM channel. This does NOT enable the corresponding pin;
/// this must be done in the user code.
/// \param channel Channel number.
//------------------------------------------------------------------------------
void PWMC_EnableChannel(unsigned char channel)
{
AT91C_BASE_PWMC->PWMC_ENA = 1 << channel;
}
//------------------------------------------------------------------------------
/// Disables the given PWM channel.
/// Beware, channel will be effectively disabled at the end of the current period.
/// Application can check channel is disabled using the following wait loop:
/// while ((AT91C_BASE_PWMC->PWMC_SR & (1 << channel)) != 0);
/// \param channel Channel number.
//------------------------------------------------------------------------------
void PWMC_DisableChannel(unsigned char channel)
{
AT91C_BASE_PWMC->PWMC_DIS = 1 << channel;
}
//------------------------------------------------------------------------------
/// Enables the period interrupt for the given PWM channel.
/// \param channel Channel number.
//------------------------------------------------------------------------------
void PWMC_EnableChannelIt(unsigned char channel)
{
AT91C_BASE_PWMC->PWMC_IER1 = 1 << channel;
}
//------------------------------------------------------------------------------
/// Disables the period interrupt for the given PWM channel.
/// \param channel Channel number.
//------------------------------------------------------------------------------
void PWMC_DisableChannelIt(unsigned char channel)
{
AT91C_BASE_PWMC->PWMC_IDR1 = 1 << channel;
}
//-----------------------------------------------------------------------------
/// Enables the selected interrupts sources on a PWMC peripheral.
/// \param sources1 Bitwise OR of selected interrupt sources of PWMC_IER1.
/// \param sources2 Bitwise OR of selected interrupt sources of PWMC_IER2.
//-----------------------------------------------------------------------------
void PWMC_EnableIt(unsigned int sources1, unsigned int sources2)
{
AT91C_BASE_PWMC->PWMC_IER1 = sources1;
AT91C_BASE_PWMC->PWMC_IER2 = sources2;
}
//-----------------------------------------------------------------------------
/// Disables the selected interrupts sources on a PWMC peripheral.
/// \param sources1 Bitwise OR of selected interrupt sources of PWMC_IDR1.
/// \param sources2 Bitwise OR of selected interrupt sources of PWMC_IDR2.
//-----------------------------------------------------------------------------
void PWMC_DisableIt(unsigned int sources1, unsigned int sources2)
{
AT91C_BASE_PWMC->PWMC_IDR1 = sources1;
AT91C_BASE_PWMC->PWMC_IDR2 = sources2;
}
//------------------------------------------------------------------------------
/// Sends the contents of buffer through a PWMC peripheral, using the PDC to
/// take care of the transfer.
/// Note: Duty cycle of syncronous channels can update by PDC
/// when the field UPDM (Update Mode) in the PWM_SCM register is set to 2.
/// \param pwmc Pointer to an AT91S_PWMC instance.
/// \param buffer Data buffer to send.
/// \param length Length of the data buffer.
//------------------------------------------------------------------------------
unsigned char PWMC_WriteBuffer(AT91S_PWMC *pwmc,
void *buffer,
unsigned int length)
{
// Check if first bank is free
if (pwmc->PWMC_TCR == 0) {
pwmc->PWMC_TPR = (unsigned int) buffer;
pwmc->PWMC_TCR = length;
pwmc->PWMC_PTCR = AT91C_PDC_TXTEN;
return 1;
}
// Check if second bank is free
else if (pwmc->PWMC_TNCR == 0) {
pwmc->PWMC_TNPR = (unsigned int) buffer;
pwmc->PWMC_TNCR = length;
return 1;
}
// No free banks
return 0;
}
//-----------------------------------------------------------------------------
/// Set PWM output override value
/// \param value Bitwise OR of output override value.
//-----------------------------------------------------------------------------
void PWMC_SetOverrideValue(unsigned int value)
{
AT91C_BASE_PWMC->PWMC_OOV = value;
}
//-----------------------------------------------------------------------------
/// Enalbe override output.
/// \param value Bitwise OR of output selection.
/// \param sync 0: enable the output asyncronously, 1: enable it syncronously
//-----------------------------------------------------------------------------
void PWMC_EnableOverrideOutput(unsigned int value, unsigned int sync)
{
if (sync) {
AT91C_BASE_PWMC->PWMC_OSSUPD = value;
} else {
AT91C_BASE_PWMC->PWMC_OSS = value;
}
}
//-----------------------------------------------------------------------------
/// Disalbe override output.
/// \param value Bitwise OR of output selection.
/// \param sync 0: enable the output asyncronously, 1: enable it syncronously
//-----------------------------------------------------------------------------
void PWMC_DisableOverrideOutput(unsigned int value, unsigned int sync)
{
if (sync) {
AT91C_BASE_PWMC->PWMC_OSCUPD = value;
} else {
AT91C_BASE_PWMC->PWMC_OSC = value;
}
}
//-----------------------------------------------------------------------------
/// Set PWM fault mode.
/// \param mode Bitwise OR of fault mode.
//-----------------------------------------------------------------------------
void PWMC_SetFaultMode(unsigned int mode)
{
AT91C_BASE_PWMC->PWMC_FMR = mode;
}
//-----------------------------------------------------------------------------
/// PWM fault clear.
/// \param fault Bitwise OR of fault to clear.
//-----------------------------------------------------------------------------
void PWMC_FaultClear(unsigned int fault)
{
AT91C_BASE_PWMC->PWMC_FCR = fault;
}
//-----------------------------------------------------------------------------
/// Set PWM fault protection value.
/// \param value Bitwise OR of fault protection value.
//-----------------------------------------------------------------------------
void PWMC_SetFaultProtectionValue(unsigned int value)
{
AT91C_BASE_PWMC->PWMC_FPV = value;
}
//-----------------------------------------------------------------------------
/// Enable PWM fault protection.
/// \param value Bitwise OR of FPEx[y].
//-----------------------------------------------------------------------------
void PWMC_EnableFaultProtection(unsigned int value)
{
AT91C_BASE_PWMC->PWMC_FPER1 = value;
}
//-----------------------------------------------------------------------------
/// Configure comparison unit.
/// \param x comparison x index
/// \param value comparison x value.
/// \param mode comparison x mode
//-----------------------------------------------------------------------------
void PWMC_ConfigureComparisonUnit(unsigned int x, unsigned int value, unsigned int mode)
{
// If channel is disabled, write to CMPxM & CMPxV
if ((AT91C_BASE_PWMC->PWMC_SR & (1 << 0)) == 0) {
if (x == 0) {
AT91C_BASE_PWMC->PWMC_CMP0M = mode;
AT91C_BASE_PWMC->PWMC_CMP0V = value;
} else if (x == 1) {
AT91C_BASE_PWMC->PWMC_CMP1M = mode;
AT91C_BASE_PWMC->PWMC_CMP1V = value;
} else if (x == 2) {
AT91C_BASE_PWMC->PWMC_CMP2M = mode;
AT91C_BASE_PWMC->PWMC_CMP2V = value;
} else if (x == 3) {
AT91C_BASE_PWMC->PWMC_CMP3M = mode;
AT91C_BASE_PWMC->PWMC_CMP3V = value;
} else if (x == 4) {
AT91C_BASE_PWMC->PWMC_CMP4M = mode;
AT91C_BASE_PWMC->PWMC_CMP4V = value;
} else if (x == 5) {
AT91C_BASE_PWMC->PWMC_CMP5M = mode;
AT91C_BASE_PWMC->PWMC_CMP5V = value;
} else if (x == 6) {
AT91C_BASE_PWMC->PWMC_CMP6M = mode;
AT91C_BASE_PWMC->PWMC_CMP6V = value;
} else if (x == 7) {
AT91C_BASE_PWMC->PWMC_CMP7M = mode;
AT91C_BASE_PWMC->PWMC_CMP7V = value;
}
}
// Otherwise use update register
else {
if (x == 0) {
AT91C_BASE_PWMC->PWMC_CMP0MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP0VUPD = value;
} else if (x == 1) {
AT91C_BASE_PWMC->PWMC_CMP1MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP1VUPD = value;
} else if (x == 2) {
AT91C_BASE_PWMC->PWMC_CMP2MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP2VUPD = value;
} else if (x == 3) {
AT91C_BASE_PWMC->PWMC_CMP3MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP3VUPD = value;
} else if (x == 4) {
AT91C_BASE_PWMC->PWMC_CMP4MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP4VUPD = value;
} else if (x == 5) {
AT91C_BASE_PWMC->PWMC_CMP5MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP5VUPD = value;
} else if (x == 6) {
AT91C_BASE_PWMC->PWMC_CMP6MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP6VUPD = value;
} else if (x == 7) {
AT91C_BASE_PWMC->PWMC_CMP7MUPD = mode;
AT91C_BASE_PWMC->PWMC_CMP7VUPD = value;
}
}
}
//-----------------------------------------------------------------------------
/// Configure event line mode.
/// \param x Line x
/// \param mode Bitwise OR of line mode selection
//-----------------------------------------------------------------------------
void PWMC_ConfigureEventLineMode(unsigned int x, unsigned int mode)
{
if (x == 0) {
AT91C_BASE_PWMC->PWMC_EL0MR = mode;
} else if (x == 1) {
AT91C_BASE_PWMC->PWMC_EL1MR = mode;
} else if (x == 2) {
AT91C_BASE_PWMC->PWMC_EL2MR = mode;
} else if (x == 3) {
AT91C_BASE_PWMC->PWMC_EL3MR = mode;
} else if (x == 4) {
AT91C_BASE_PWMC->PWMC_EL4MR = mode;
} else if (x == 5) {
AT91C_BASE_PWMC->PWMC_EL5MR = mode;
} else if (x == 6) {
AT91C_BASE_PWMC->PWMC_EL6MR = mode;
} else if (x == 7) {
AT91C_BASE_PWMC->PWMC_EL7MR = mode;
}
}