MakingThings
  • Main Page
  • Related Pages
  • Modules
  • Data Structures
  • Files
  • File List
  • Globals

pwm.c

Go to the documentation of this file.
00001 /*********************************************************************************
00002 
00003  Copyright 2006-2008 MakingThings
00004 
00005  Licensed under the Apache License, 
00006  Version 2.0 (the "License"); you may not use this file except in compliance 
00007  with the License. You may obtain a copy of the License at
00008 
00009  http://www.apache.org/licenses/LICENSE-2.0 
00010  
00011  Unless required by applicable law or agreed to in writing, software distributed
00012  under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
00013  CONDITIONS OF ANY KIND, either express or implied. See the License for
00014  the specific language governing permissions and limitations under the License.
00015 
00016 *********************************************************************************/
00017 
00018 /** \file pwm.c 
00019   PWM - Pulse Width Modulation.
00020   Library of functions for the Make Application Board's PwmOut Subsystem.
00021 */
00022 
00023 /* Library includes. */
00024 #include <string.h>
00025 #include <stdio.h>
00026 
00027 /* Scheduler includes. */
00028 #include "FreeRTOS.h"
00029 #include "task.h"
00030 
00031 /* Hardware specific headers. */
00032 #include "Board.h"
00033 #include "AT91SAM7X256.h"
00034 
00035 #include "io.h"
00036 #include "config.h"
00037 #include "pwm.h"
00038 
00039 int Pwm_GetChannelIo( int channel );
00040 
00041 #define PWM_DUTY_MAX 1024
00042 
00043 #define PWM_COUNT 4
00044 
00045 #define PWM_CHANNEL_0_IO IO_PB19
00046 #define PWM_CHANNEL_1_IO IO_PB20
00047 #define PWM_CHANNEL_2_IO IO_PB21
00048 #define PWM_CHANNEL_3_IO IO_PB22
00049 
00050 static int Pwm_Init( void );
00051 static int Pwm_Deinit( void );
00052 
00053 struct Pwm_
00054 {
00055   int users;
00056   int channels;
00057   int duty[ PWM_COUNT ];
00058 } Pwm;
00059 
00060 /** \defgroup Pwm PWM (Pulse Width Modulation)
00061   The PWM subsystem provides control of the 4 PWM outputs on the SAM7X.
00062 
00063   The Make Controller has 4 PWM lines.  These can each be configured separately and can control
00064   up to 2 output lines directly, the 2 lines running either parallel or inverted.  For a very simple
00065   start, just see Pwm_Set( ) and Pwm_Get( ) as these will start driving your PWMs immediately with
00066   very little hassle.  
00067 
00068   \section padjust Period adjustment of the PWM unit
00069   Configuring and setting the clock for the PWM system can be quite a complicated matter.  Here are 
00070   some of the relevant issues.
00071   
00072   Each of the 4 PWM channels is fed in a clock, as determined by its clock source:
00073    - Clock source 0-10 represent the Master clock divided by 2 to the power of the Clock Source Value.  eg. a clock 
00074    source value of 5 = a clock rate of MasterClock/(2^5)
00075    - A value of 11 sets the Clock source to be generated by clock Divider A (Default)
00076    - A value of 12 sets the Clock source to be generated by clock Divider B
00077   
00078   If either Clock Divider A or Clock Divider B is used, you can adjust their values individually to
00079   allow the clock period to precisely match your needs.  Each clock divider has two values, a \b Mux value 
00080   and a \b Divider value.  
00081   
00082   The mux works just like the clock source values, and chooses Master clock divided by 2 to 
00083   the power of the Clock Source Value, eg. a DividerXMux value of 5 == a clock rate of MasterClock/(2^5).
00084   The Divider value sets a linear divider, which is fed the clock value as selected by the Mux, and returns that 
00085   clock value divided by the divider value.  This output value is what is fed out of the divider unit.  A output 
00086   formula: 
00087   \code output = MCLK / ( (2^DividerMux) * DividerValue ) \endcode
00088   
00089   The PWM subsystem of the Controller Board can be used independently from the \ref PwmOut 
00090   library, since the \ref PwmOut library relies on the core PWM.
00091 
00092   \ingroup Core
00093   @{
00094 */
00095 
00096 /** 
00097   Set the duty of a PWM device.
00098   @param index An integer specifying which PWM device (0-3).
00099   @param duty The duty - (0 - 1023).
00100   @return 0 on success.
00101 */
00102 int Pwm_Set( int index, int duty )
00103 {
00104   if ( index < 0 || index >= PWM_COUNT )
00105     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00106 
00107   // Set the duty
00108   Pwm.duty[ index ] = duty;
00109   AT91C_BASE_PWMC->PWMC_CH[ index ].PWMC_CUPDR = duty;
00110 
00111   return CONTROLLER_OK;
00112 }
00113 
00114 /** 
00115   Read the current duty of a PWM device.
00116   @param index An integer specifying which PWM device (0-3).
00117   @return The duty - (0 - 1023).
00118 */
00119 int Pwm_Get( int index )
00120 {
00121   if ( index < 0 || index >= PWM_COUNT )
00122     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00123 
00124   return Pwm.duty[ index ];
00125 }
00126 
00127 int Pwm_Start( int channel )
00128 {
00129   int status;
00130 
00131   if ( channel < 0 || channel >= PWM_COUNT )
00132     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00133 
00134   // Make sure the channel isn't already being used
00135   int c = 1 << channel;
00136   if ( c & Pwm.channels )
00137     return CONTROLLER_ERROR_CANT_LOCK;
00138 
00139   // lock the correct select line 
00140   int io = Pwm_GetChannelIo( channel );
00141   // Try to lock the pin
00142   status = Io_Start( io, true );
00143   if ( status != CONTROLLER_OK )
00144     return status;
00145 
00146   // mark the channel as being used
00147   Pwm.channels |= c;
00148 
00149   // Disable the PIO for the IO Line
00150   Io_SetPio( io, false );
00151 
00152   Io_SetPeripheralA( io );
00153 
00154   if ( Pwm.users++ == 0 )
00155   {   
00156     status = Pwm_Init();
00157   }
00158 
00159   // Enable the current channel
00160   AT91C_BASE_PWMC->PWMC_ENA = 1 << channel;
00161 
00162   return CONTROLLER_OK;
00163 }
00164 
00165 int Pwm_Stop( int channel )
00166 {
00167   if ( Pwm.users <= 0 )
00168     return CONTROLLER_ERROR_TOO_MANY_STOPS;
00169 
00170   if ( channel < 0 || channel >= PWM_COUNT )
00171     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00172 
00173   int c = 1 << channel;
00174   if ( !( c & Pwm.channels ) )
00175     return CONTROLLER_ERROR_NOT_LOCKED;
00176 
00177   // Get the IO
00178   int io = Pwm_GetChannelIo( channel );
00179 
00180   // Set the pin to OFF
00181   Io_SetValue( io, false );
00182   Io_SetPio( io, true );
00183   
00184   // release the pin
00185   Io_Stop( io );
00186 
00187   Pwm.channels &= ~c;
00188 
00189   // Disable the PWM's
00190   AT91C_BASE_PWMC->PWMC_DIS = 1 << channel;
00191 
00192   if ( --Pwm.users == 0 )
00193     Pwm_Deinit();
00194 
00195   return CONTROLLER_OK;
00196 }
00197 
00198 int Pwm_Init()
00199 {
00200   // Configure PMC by enabling PWM clock
00201   AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_PWMC;
00202 
00203   // Initially they're stopped
00204   AT91C_BASE_PWMC->PWMC_DIS = AT91C_PWMC_CHID0 | AT91C_PWMC_CHID1 | AT91C_PWMC_CHID2 | AT91C_PWMC_CHID3;
00205   
00206   // Set the Clock A divider
00207   AT91C_BASE_PWMC->PWMC_MR = (( 4 << 8 ) | 0x08 );  // MCK selection or'ed with Divider
00208 
00209   // Set the Clock
00210   int i;
00211   for ( i = 0; i < 4; i++ )
00212   {
00213     AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ i ];
00214 
00215     pwm->PWMC_CMR =
00216       // AT91C_PWMC_CPRE_MCK |  // Divider Clock ? 
00217          AT91C_PWMC_CPRE_MCKA |    //Divider Clock A
00218       // AT91C_PWMC_CPRE_MCKB;  //Divider Clock B
00219       // AT91C_PWMC_CPD;  // Channel Update Period 
00220          AT91C_PWMC_CPOL; // Channel Polarity Invert
00221       // AT91C_PWMC_CALG ; // Channel Alignment Center
00222 
00223     // Set the Period register (sample size bit fied )
00224     pwm->PWMC_CPRDR = PWM_DUTY_MAX;
00225   
00226     // Set the duty cycle register (output value)
00227     pwm->PWMC_CDTYR = 0;
00228 
00229     // Initialise the Update register write only
00230     pwm->PWMC_CUPDR = 0 ;
00231   }
00232 
00233   return CONTROLLER_OK;
00234 }
00235 
00236 int Pwm_Deinit()
00237 {
00238   // Deconfigure PMC by disabling the PWM clock
00239   AT91C_BASE_PMC->PMC_PCDR = 1 << AT91C_ID_PWMC;
00240 
00241   return CONTROLLER_OK;
00242 }
00243 
00244 int Pwm_GetChannelIo( int channel )
00245 {  
00246   int io;
00247   switch ( channel )
00248   {
00249     case 0:
00250       io = PWM_CHANNEL_0_IO;
00251       break;
00252     case 1:
00253       io = PWM_CHANNEL_1_IO;
00254       break;
00255     case 2:
00256       io = PWM_CHANNEL_2_IO;
00257       break;
00258     case 3:
00259       io = PWM_CHANNEL_3_IO;
00260       break;
00261     default:
00262       io = 0;
00263       break;
00264   }
00265   return io;
00266 }
00267 
00268 /**
00269   Adjust the clock period on Divider A.
00270   See AT91SAM7X manual for more information p.421
00271 
00272   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00273   @param val An int between 0 and 4096.
00274   @return 0 on success.
00275 */
00276 int Pwm_SetDividerA(int val)
00277 { 
00278   if( val < 0 || val > ( 1 << 12 ) )
00279     return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE;
00280 
00281   // First disable PWM controller for all 4 channels.
00282   AT91C_BASE_PWMC->PWMC_DIS = 0x0000000f;
00283 
00284   // Now Set the new Clock values
00285   AT91C_BASE_PWMC->PWMC_MR = (AT91C_BASE_PWMC->PWMC_MR & 0xffff0000) | val;
00286 
00287   // Re-enable the active channels
00288   AT91C_BASE_PWMC->PWMC_ENA = Pwm.channels;
00289   
00290   return CONTROLLER_OK;
00291 }
00292 
00293 /**
00294   Read the clock period on Divider A.
00295 
00296   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00297   @return The clock period on Divider A.
00298 */
00299 int Pwm_GetDividerA()
00300 {
00301   return (AT91C_BASE_PWMC->PWMC_MR & 0x0000ffff);
00302 }
00303 
00304 /**
00305   Adjust the clock period on Divider B.
00306   See AT91SAM7X manual for more information p.421
00307 
00308   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00309   @param val An int between 0 and 4096.
00310   @return 0 on success.
00311 */
00312 int Pwm_SetDividerB(int val)
00313 { 
00314   if( val < 0 || val > ( 1 << 12 ) )
00315     return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE;
00316 
00317   //New function which adjusts the clock period on Divider A.
00318   //See AT91SAM7X manual for more information p.421
00319 
00320   // First disable PWM controller for all 4 channels.
00321   AT91C_BASE_PWMC->PWMC_DIS = 0x0000000f;
00322 
00323   // Now Set the new Clock values
00324   AT91C_BASE_PWMC->PWMC_MR = (AT91C_BASE_PWMC->PWMC_MR & 0x0000ffff) | ( val << 16 );
00325 
00326   // Re-enable the active channels
00327   AT91C_BASE_PWMC->PWMC_ENA = Pwm.channels;
00328   
00329   return CONTROLLER_OK;
00330 }
00331 
00332 /**
00333   Read the clock period on Divider B.
00334 
00335   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00336   @return The clock period on Divider B.
00337 */
00338 int Pwm_GetDividerB()
00339 {
00340   return (AT91C_BASE_PWMC->PWMC_MR >> 16);
00341 }
00342 
00343 /**
00344   Set the clock divider for a particular channel.
00345   For values 0-10, the Master_Clock is divided by (2^val).  For example,
00346   for 4 the resulting period will be Master Clock / 16, as 2 ^ 4 == 16.
00347 
00348   When val is 11, Clock Divider A is used.  When val is 12, Clock Divider B
00349   is used.  Values other than 0 - 12 are not valid.
00350 
00351   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00352   @param channel The PWM channel (0-3) you'd like to configure.
00353   @param val The new clock divider.
00354   @return 0 on success.
00355 */
00356 int Pwm_SetClockSource(int channel, int val)
00357 {
00358   if ( channel < 0 || channel >= PWM_COUNT )
00359     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00360 
00361   if ( val < 0 || val > 12 )
00362     return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE;
00363 
00364   AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ channel ];
00365   int c = 1 << channel;
00366 
00367   // Disable the Channel
00368   AT91C_BASE_PWMC->PWMC_DIS = c;
00369 
00370   // Set the Channel Divider Value
00371   pwm->PWMC_CMR = (pwm->PWMC_CMR & 0x00000700) | val;
00372   
00373   // Re-enable the Channel
00374   AT91C_BASE_PWMC->PWMC_ENA = c;
00375 
00376   return CONTROLLER_OK;
00377 }
00378 
00379 /**
00380   Read the clock source for a particular channel.
00381   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00382   @param channel The PWM channel (0-3) whose channel you'd like to read.
00383   @return The clock source.
00384   @see Pwm_SetClockSource( )
00385 */
00386 int Pwm_GetClockSource(int channel)
00387 {
00388   if ( channel < 0 || channel >= PWM_COUNT )
00389     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00390 
00391   return (AT91C_BASE_PWMC->PWMC_CH[ channel ].PWMC_CMR & 0x0000000f);
00392 }
00393 
00394 /**
00395   Set the wave form properties of the PWM contoller for a given channel.
00396   The waveform properties are controlled by bits 0, 1, and 2.
00397    - bit 0 sets whether the PWModule is Left aligned (0) or Center aligned (1)
00398    - bit 1 sets whether the PWMoudle's polarity starts out low (0) or high (1)
00399    - bit 2 is not supported at this time.
00400 
00401   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00402   @param channel the PWM channel (0-3) that you want to configure
00403   @param val The mask of values as described above.
00404   @return 0 on success.
00405 */
00406 int Pwm_SetWaveformProperties(int channel, int val)
00407 {
00408   if ( channel < 0 || channel >= PWM_COUNT )
00409     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00410 
00411   if ( val < 0 || val > 3 )
00412     return CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE;
00413 
00414   AT91S_PWMC_CH *pwm = &AT91C_BASE_PWMC->PWMC_CH[ channel ];
00415   int c = 1 << channel;
00416 
00417   // Disable the Channel
00418   AT91C_BASE_PWMC->PWMC_DIS = c;
00419 
00420   // Set the Channel Divider Value
00421   pwm->PWMC_CMR = (pwm->PWMC_CMR & 0x0000040f) | ( val << 8 );
00422   
00423   // Re-enable the Channel
00424   AT91C_BASE_PWMC->PWMC_ENA = c;
00425 
00426   return CONTROLLER_OK;
00427 }
00428 
00429 /**
00430   Read the waveform configuration of a specified PWM channel
00431 
00432   Contributed by TheStigg - http://www.makingthings.com/author/thestigg
00433   @param channel The PWM channel (0-3) to read from.
00434   @return A bitmask describing the waveform configuration.
00435   @see Pwm_SetWaveformProperties( )
00436 */
00437 int Pwm_GetWaveformProperties(int channel)
00438 {
00439   if ( channel < 0 || channel >= PWM_COUNT )
00440     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00441   
00442   return ( (AT91C_BASE_PWMC->PWMC_CH[ channel ].PWMC_CMR >> 8) & 0x00000003 );
00443 }
00444 
00445 /** @}
00446 */

The Make Controller Kit is an open source project maintained by MakingThings.
MakingThings code is released under the Apache 2.0 license.
Bug tracker, development wiki and status can be found at http://dev.makingthings.com.
This document was last updated on 18 May 2009.