/********************************************************************************* Copyright 2006-2008 MakingThings 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. *********************************************************************************/ /** \file pwm.c PWM - Pulse Width Modulation. Library of functions for the Make Application Board's PwmOut Subsystem. */ /* Library includes. */ #include #include /* Scheduler includes. */ #include "FreeRTOS.h" #include "task.h" /* Hardware specific headers. */ #include "Board.h" #include "AT91SAM7X256.h" #include "io.h" #include "config.h" #include "pwm.h" int Pwm_GetChannelIo( int channel ); #define PWM_DUTY_MAX 1024 #define PWM_COUNT 4 #define PWM_CHANNEL_0_IO IO_PB19 #define PWM_CHANNEL_1_IO IO_PB20 #define PWM_CHANNEL_2_IO IO_PB21 #define PWM_CHANNEL_3_IO IO_PB22 static int Pwm_Init( void ); static int Pwm_Deinit( void ); struct Pwm_ { int users; int channels; int duty[ PWM_COUNT ]; } Pwm; /** \defgroup Pwm PWM (Pulse Width Modulation) The PWM subsystem provides control of the 4 PWM outputs on the SAM7X. The Make Controller has 4 PWM lines. These can each be configured separately and can control up to 2 output lines directly, the 2 lines running either parallel or inverted. For a very simple start, just see Pwm_Set( ) and Pwm_Get( ) as these will start driving your PWMs immediately with very little hassle. \section padjust Period adjustment of the PWM unit Configuring and setting the clock for the PWM system can be quite a complicated matter. Here are some of the relevant issues. Each of the 4 PWM channels is fed in a clock, as determined by its clock source: - Clock source 0-10 represent the Master clock divided by 2 to the power of the Clock Source Value. eg. a clock source value of 5 = a clock rate of MasterClock/(2^5) - A value of 11 sets the Clock source to be generated by clock Divider A (Default) - A value of 12 sets the Clock source to be generated by clock Divider B If either Clock Divider A or Clock Divider B is used, you can adjust their values individually to allow the clock period to precisely match your needs. Each clock divider has two values, a \b Mux value and a \b Divider value. The mux works just like the clock source values, and chooses Master clock divided by 2 to the power of the Clock Source Value, eg. a DividerXMux value of 5 == a clock rate of MasterClock/(2^5). The Divider value sets a linear divider, which is fed the clock value as selected by the Mux, and returns that clock value divided by the divider value. This output value is what is fed out of the divider unit. A output formula: \code output = MCLK / ( (2^DividerMux) * DividerValue ) \endcode The PWM subsystem of the Controller Board can be used independently from the \ref PwmOut library, since the \ref PwmOut library relies on the core PWM. \ingroup Core @{ */ /** Set the duty of a PWM device. @param index An integer specifying which PWM device (0-3). @param duty The duty - (0 - 1023). @return 0 on success. */ int Pwm_Set( int index, int duty ) { if ( index < 0 || index >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; // Set the duty Pwm.duty[ index ] = duty; AT91C_BASE_PWMC->PWMC_CH[ index ].PWMC_CUPDR = duty; return CONTROLLER_OK; } /** Read the current duty of a PWM device. @param index An integer specifying which PWM device (0-3). @return The duty - (0 - 1023). */ int Pwm_Get( int index ) { if ( index < 0 || index >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; return Pwm.duty[ index ]; } int Pwm_Start( int channel ) { int status; if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; // Make sure the channel isn't already being used int c = 1 << channel; if ( c & Pwm.channels ) return CONTROLLER_ERROR_CANT_LOCK; // lock the correct select line int io = Pwm_GetChannelIo( channel ); // Try to lock the pin status = Io_Start( io, true ); if ( status != CONTROLLER_OK ) return status; // mark the channel as being used Pwm.channels |= c; // Disable the PIO for the IO Line Io_SetPio( io, false ); Io_SetPeripheralA( io ); if ( Pwm.users++ == 0 ) { status = Pwm_Init(); } // Enable the current channel AT91C_BASE_PWMC->PWMC_ENA = 1 << channel; return CONTROLLER_OK; } int Pwm_Stop( int channel ) { if ( Pwm.users <= 0 ) return CONTROLLER_ERROR_TOO_MANY_STOPS; if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; int c = 1 << channel; if ( !( c & Pwm.channels ) ) return CONTROLLER_ERROR_NOT_LOCKED; // Get the IO int io = Pwm_GetChannelIo( channel ); // Set the pin to OFF Io_SetValue( io, false ); Io_SetPio( io, true ); // release the pin Io_Stop( io ); Pwm.channels &= ~c; // Disable the PWM's AT91C_BASE_PWMC->PWMC_DIS = 1 << channel; if ( --Pwm.users == 0 ) Pwm_Deinit(); return CONTROLLER_OK; } int Pwm_Init() { // Configure PMC by enabling PWM clock AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_PWMC; // Initially they're stopped AT91C_BASE_PWMC->PWMC_DIS = AT91C_PWMC_CHID0 | AT91C_PWMC_CHID1 | AT91C_PWMC_CHID2 | AT91C_PWMC_CHID3; // Set the Clock A divider AT91C_BASE_PWMC->PWMC_MR = (( 4 << 8 ) | 0x08 ); // MCK selection or'ed with Divider // Set the Clock int i; for ( i = 0; i < 4; i++ ) { AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ i ]; pwm->PWMC_CMR = // AT91C_PWMC_CPRE_MCK | // Divider Clock ? AT91C_PWMC_CPRE_MCKA | //Divider Clock A // AT91C_PWMC_CPRE_MCKB; //Divider Clock B // AT91C_PWMC_CPD; // Channel Update Period AT91C_PWMC_CPOL; // Channel Polarity Invert // AT91C_PWMC_CALG ; // Channel Alignment Center // Set the Period register (sample size bit fied ) pwm->PWMC_CPRDR = PWM_DUTY_MAX; // Set the duty cycle register (output value) pwm->PWMC_CDTYR = 0; // Initialise the Update register write only pwm->PWMC_CUPDR = 0 ; } return CONTROLLER_OK; } int Pwm_Deinit() { // Deconfigure PMC by disabling the PWM clock AT91C_BASE_PMC->PMC_PCDR = 1 << AT91C_ID_PWMC; return CONTROLLER_OK; } int Pwm_GetChannelIo( int channel ) { int io; switch ( channel ) { case 0: io = PWM_CHANNEL_0_IO; break; case 1: io = PWM_CHANNEL_1_IO; break; case 2: io = PWM_CHANNEL_2_IO; break; case 3: io = PWM_CHANNEL_3_IO; break; default: io = 0; break; } return io; } /** Adjust the clock period on Divider A. See AT91SAM7X manual for more information p.421 Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param val An int between 0 and 4096. @return 0 on success. */ int Pwm_SetDividerA(int val) { if( val < 0 || val > ( 1 << 12 ) ) return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE; // First disable PWM controller for all 4 channels. AT91C_BASE_PWMC->PWMC_DIS = 0x0000000f; // Now Set the new Clock values AT91C_BASE_PWMC->PWMC_MR = (AT91C_BASE_PWMC->PWMC_MR & 0xffff0000) | val; // Re-enable the active channels AT91C_BASE_PWMC->PWMC_ENA = Pwm.channels; return CONTROLLER_OK; } /** Read the clock period on Divider A. Contributed by TheStigg - http://www.makingthings.com/author/thestigg @return The clock period on Divider A. */ int Pwm_GetDividerA() { return (AT91C_BASE_PWMC->PWMC_MR & 0x0000ffff); } /** Adjust the clock period on Divider B. See AT91SAM7X manual for more information p.421 Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param val An int between 0 and 4096. @return 0 on success. */ int Pwm_SetDividerB(int val) { if( val < 0 || val > ( 1 << 12 ) ) return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE; //New function which adjusts the clock period on Divider A. //See AT91SAM7X manual for more information p.421 // First disable PWM controller for all 4 channels. AT91C_BASE_PWMC->PWMC_DIS = 0x0000000f; // Now Set the new Clock values AT91C_BASE_PWMC->PWMC_MR = (AT91C_BASE_PWMC->PWMC_MR & 0x0000ffff) | ( val << 16 ); // Re-enable the active channels AT91C_BASE_PWMC->PWMC_ENA = Pwm.channels; return CONTROLLER_OK; } /** Read the clock period on Divider B. Contributed by TheStigg - http://www.makingthings.com/author/thestigg @return The clock period on Divider B. */ int Pwm_GetDividerB() { return (AT91C_BASE_PWMC->PWMC_MR >> 16); } /** Set the clock divider for a particular channel. For values 0-10, the Master_Clock is divided by (2^val). For example, for 4 the resulting period will be Master Clock / 16, as 2 ^ 4 == 16. When val is 11, Clock Divider A is used. When val is 12, Clock Divider B is used. Values other than 0 - 12 are not valid. Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param channel The PWM channel (0-3) you'd like to configure. @param val The new clock divider. @return 0 on success. */ int Pwm_SetClockSource(int channel, int val) { if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; if ( val < 0 || val > 12 ) return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE; AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ channel ]; int c = 1 << channel; // Disable the Channel AT91C_BASE_PWMC->PWMC_DIS = c; // Set the Channel Divider Value pwm->PWMC_CMR = (pwm->PWMC_CMR & 0x00000700) | val; // Re-enable the Channel AT91C_BASE_PWMC->PWMC_ENA = c; return CONTROLLER_OK; } /** Read the clock source for a particular channel. Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param channel The PWM channel (0-3) whose channel you'd like to read. @return The clock source. @see Pwm_SetClockSource( ) */ int Pwm_GetClockSource(int channel) { if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; return (AT91C_BASE_PWMC->PWMC_CH[ channel ].PWMC_CMR & 0x0000000f); } /** Set the wave form properties of the PWM contoller for a given channel. The waveform properties are controlled by bits 0, 1, and 2. - bit 0 sets whether the PWModule is Left aligned (0) or Center aligned (1) - bit 1 sets whether the PWMoudle's polarity starts out low (0) or high (1) - bit 2 is not supported at this time. Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param channel the PWM channel (0-3) that you want to configure @param val The mask of values as described above. @return 0 on success. */ int Pwm_SetWaveformProperties(int channel, int val) { if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; if ( val < 0 || val > 3 ) return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE; AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ channel ]; int c = 1 << channel; // Disable the Channel AT91C_BASE_PWMC->PWMC_DIS = c; // Set the Channel Divider Value pwm->PWMC_CMR = (pwm->PWMC_CMR & 0x0000040f) | ( val << 8 ); // Re-enable the Channel AT91C_BASE_PWMC->PWMC_ENA = c; return CONTROLLER_OK; } /** Read the waveform configuration of a specified PWM channel Contributed by TheStigg - http://www.makingthings.com/author/thestigg @param channel The PWM channel (0-3) to read from. @return A bitmask describing the waveform configuration. @see Pwm_SetWaveformProperties( ) */ int Pwm_GetWaveformProperties(int channel) { if ( channel < 0 || channel >= PWM_COUNT ) return CONTROLLER_ERROR_ILLEGAL_INDEX; return ( (AT91C_BASE_PWMC->PWMC_CH[ channel ].PWMC_CMR >> 8) & 0x00000003 ); } /** @} */