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

analogin.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 // MakingThings - Make Controller Board - 2006
00019 
00020 /** \file analogin.c  
00021   Functions for reading the analog inputs on the MAKE Application Board.
00022 */
00023 
00024 /* Library includes. */
00025 #include <string.h>
00026 #include <stdio.h>
00027 
00028 /* Scheduler includes. */
00029 #include "FreeRTOS.h"
00030 #include "task.h"
00031 #include "semphr.h"
00032 
00033 /* Hardware specific headers. */
00034 #include "Board.h"
00035 #include "AT91SAM7X256.h"
00036 
00037 #include "config.h"
00038 
00039 #include "io.h"
00040 
00041 #include "analogin.h"
00042 #include "analogin_internal.h"
00043 
00044 #define ANALOGIN_0_IO IO_PB27
00045 #define ANALOGIN_1_IO IO_PB28
00046 #define ANALOGIN_2_IO IO_PB29
00047 #define ANALOGIN_3_IO IO_PB30
00048 
00049 #define AUTOSENDSAVE 0xDF
00050 
00051 static int AnalogIn_Start( int index );
00052 static int AnalogIn_Stop( int index );
00053 
00054 static int AnalogIn_Init( void );
00055 static int AnalogIn_Deinit( void );
00056 
00057 static int AnalogIn_GetIo( int index );
00058 
00059 extern void ( AnalogInIsr_Wrapper )( void );
00060 
00061 struct AnalogIn_* AnalogIn;
00062 
00063 /** \defgroup AnalogIn Analog Inputs
00064   The AnalogIn subsystem converts 0-3.3V signals to 10-bit digital values.
00065   The analog to digital converters read incoming signals from 0 - 3.3V.  They are rated as 5V tolerant, 
00066   and indeed can momentarily withstand higher voltages, but will not return meaningful values for anything 
00067   above 3.3V.\n
00068   
00069   Converting the 0 - 1023 reading of the AnalogIn channel into a voltage is performed as follows:
00070    \verbatim v = 3.3 * ( a / 1023.0 ) \endverbatim
00071     where a is the AnalogIn value
00072 
00073   This is of course a floating point operation (slowish) using a division (slowish).  Fixed point 
00074   versions may be more suitable for some applications.  Where reduced accuracy is acceptable, the following 
00075   can be used to get the input as a percentage of 3.3V:
00076   \verbatim  p = ( 100 * a ) / 1023 \endverbatim
00077 
00078   Initializing the controller's AnalogIn system is pretty involved (see AnalogIn_Init() in AnalogIn->c).  There are a lot of 
00079   different options - different converter speeds, DMA access, etc.  We've chosen something pretty simple here.
00080   More ambitious users may wish to alter the implementation.
00081 
00082   \todo Provide multi-channel conversion routines
00083 
00084   \ingroup Core
00085   @{
00086 */
00087 
00088 /**
00089   Sets whether the specified channel is active.
00090   This initializes the AnalogIn system and gets a lock on its IO lines.  It only needs to be called once.
00091   @param index An integer specifying the channel (0 - 7).
00092   @param state An integer specifying the active state - 1 (active) or 0 (inactive).
00093   @return Zero on success.
00094   
00095   
00096   \par Example
00097   \code
00098   if( AnalogIn_SetActive( 0, 1 )  == CONTROLLER_OK ) // set analogin 0 to active
00099     // then continue processing
00100   else
00101     // some error occurred
00102   \endcode
00103 */
00104 int AnalogIn_SetActive( int index, int state )
00105 {
00106   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00107     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00108 
00109   if ( state )
00110   {
00111     if( AnalogIn == NULL )
00112     {
00113       AnalogIn = MallocWait( sizeof( struct AnalogIn_ ), 100 );
00114       AnalogIn->users = 0;
00115       int i;
00116       for( i = 0; i < ANALOGIN_CHANNELS; i++ )
00117         AnalogIn->channelUsers[ i ] = 0;
00118       #ifdef OSC
00119       AnalogIn_AutoSendInit( );
00120       #endif
00121     }
00122 
00123     return AnalogIn_Start( index );
00124   }
00125   else
00126   {
00127     // TODO: check if any channels are still active and if not, Free( ) the AnalogIn struct
00128     return AnalogIn_Stop( index );
00129   }
00130 }
00131 
00132 /**
00133   Returns the active state of a channel.
00134   @param index An integer specifying the ANALOGIN channel (0 - 7).
00135   @return State - 1/non-zero (active) or 0 (inactive).
00136   
00137   \par Example
00138   \code
00139   int active = AnalogIn_GetActive( 0 ) // check whether analogin 0 is active
00140   \endcode
00141 */
00142 int AnalogIn_GetActive( int index )
00143 {
00144   if( AnalogIn == NULL )
00145     return 0;
00146 
00147   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00148     return false;
00149   return AnalogIn->channelUsers[ index ] > 0;
00150 }
00151 
00152 /** 
00153   Read the value of an analog input.
00154   @param index An integer specifying which input (0-7).
00155   @return The value as an integer (0 - 1023).
00156   
00157   \par Example
00158   \code
00159   int analogin1 = AnalogIn_GetValue( 1 );
00160   \endcode
00161 */
00162 int AnalogIn_GetValue( int index )
00163 {
00164   AnalogIn_SetActive( index, 1 );
00165   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00166     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00167 
00168   if ( AnalogIn->channelUsers[ index ] < 1 )
00169   {
00170     int status = AnalogIn_Start( index );
00171     if ( status != CONTROLLER_OK )
00172       return status;
00173   }
00174 
00175   int value;
00176 
00177   if ( !xSemaphoreTake( AnalogIn->semaphore, 1000 ) )
00178     return -1;
00179 
00180   /* Third Step: Select the active channel */
00181   int mask = 1 << index; 
00182   AT91C_BASE_ADC->ADC_CHDR = ~mask;
00183   AT91C_BASE_ADC->ADC_CHER = mask;
00184   
00185   /* Fourth Step: Start the conversion */
00186   AT91C_BASE_ADC->ADC_CR = AT91C_ADC_START;
00187 
00188   /* Busy wait */
00189   // while ( !( AT91C_BASE_ADC->ADC_CHSR  & ( 1 << index ) ) );
00190 
00191   if ( !xSemaphoreTake( AnalogIn->doneSemaphore, 1000 ) )
00192     return -1;
00193 
00194   value = AT91C_BASE_ADC->ADC_LCDR & 0xFFFF;
00195 
00196   xSemaphoreGive( AnalogIn->semaphore );
00197 
00198   return value;
00199 }
00200 
00201 /** 
00202   Read the value of several of the analog inputs.
00203   Due to the current ISR handling, this isn't too much quicker than making
00204   calls to each of the channels individually.
00205   @param mask A bit mask specifying which channels to read.
00206   @param values A pointer to an int array to be filled with the values.
00207   @return 0 on success, otherwise non-zero.
00208   
00209   \par Example
00210   \code
00211   int mask = 0xFF;
00212   int samples[8];
00213   AnalogIn_GetValueMulti( mask, samples ); // now samples is filled with all the analogin values
00214   \endcode
00215 */
00216 int AnalogIn_GetValueMulti( int mask, int values[] )
00217 {
00218   //AnalogIn_SetActive( 1 );
00219   if ( mask < 0 || mask > 255 ) // check the value is a valid 8-bit mask
00220     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00221 
00222   int i; // Is this the best way to make sure everything is started up properly?
00223   for( i = 0; i < 8; i++ )
00224   {
00225     if( mask >> i & 1 )
00226     {
00227       if ( AnalogIn->channelUsers[ i ] < 1 )
00228       {
00229         int status = AnalogIn_Start( i );
00230         if ( status != CONTROLLER_OK )
00231         return status;
00232       }
00233     }
00234   }
00235 
00236   if ( !xSemaphoreTake( AnalogIn->semaphore, 1000 ) )
00237     return -1;
00238 
00239   /* Third Step: Select the active channels */
00240   AT91C_BASE_ADC->ADC_CHDR = ~mask;
00241   AT91C_BASE_ADC->ADC_CHER = mask;
00242   
00243   /* Fourth Step: Start the conversion */
00244   AT91C_BASE_ADC->ADC_CR = AT91C_ADC_START;
00245 
00246   if ( !xSemaphoreTake( AnalogIn->doneSemaphore, 1000 ) )
00247     return -1;
00248 
00249   //Figure out which of the channels we want to read
00250   volatile uint* reg = &AT91C_BASE_ADC->ADC_CDR0; // the address of the first ADC result register
00251   for( i = 0; i < 8; i++ )
00252   {
00253     if( mask >> i & 1 )
00254       values[ i ] = *reg++ & 0xFFFF;
00255   }
00256 
00257   xSemaphoreGive( AnalogIn->semaphore );
00258 
00259   return CONTROLLER_OK;
00260 }
00261 
00262 /** 
00263   Read the value of an analog input without the use of any OS services.
00264   Note that this is not thread safe and shouldn't be used if another 
00265   part of the code might be using it or the thread safe versions.
00266   @param index An integer specifying which input (0-7).
00267   @return The value as an integer (0 - 1023).
00268   
00269   \par Example
00270   \code
00271   int analogin1 = AnalogIn_GetValueWait( 1 );
00272   \endcode
00273 */
00274 int AnalogIn_GetValueWait( int index )
00275 {
00276   AnalogIn_SetActive( index, 1 );
00277   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00278     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00279 
00280   if ( AnalogIn->channelUsers[ index ] < 1 )
00281   {
00282     int status = AnalogIn_Start( index );
00283     if ( status != CONTROLLER_OK )
00284       return status;
00285   }
00286 
00287   int value = 0;
00288 
00289   // Third Step: Select the active channel
00290   int mask = 1 << index; 
00291   AT91C_BASE_ADC->ADC_CHDR = ~mask;
00292   AT91C_BASE_ADC->ADC_CHER = mask;
00293   
00294   AT91C_BASE_ADC->ADC_IDR = AT91C_ADC_DRDY; 
00295 
00296   // Fourth Step: Start the conversion
00297   AT91C_BASE_ADC->ADC_CR = AT91C_ADC_START;
00298 
00299   value++;
00300   value++;
00301 
00302   // Busy wait
00303   while ( !( AT91C_BASE_ADC->ADC_SR & AT91C_ADC_DRDY ) )
00304     value++;
00305 
00306   AT91C_BASE_ADC->ADC_IDR = AT91C_ADC_DRDY; 
00307 
00308   value = AT91C_BASE_ADC->ADC_LCDR & 0xFFFF;
00309 
00310   return value;
00311 }
00312 
00313 /** @}
00314 */
00315 
00316 #ifdef OSC
00317 void AnalogIn_AutoSendInit( )
00318 {
00319   int autosend;
00320   Eeprom_Read( EEPROM_ANALOGIN_AUTOSEND, (uchar*)&autosend, 4 );
00321   if( !((autosend >> 16) & 0xFF) == AUTOSENDSAVE )
00322     AnalogIn->autosend = AUTOSENDSAVE << 16;
00323   else
00324     AnalogIn->autosend = autosend;
00325 }
00326 #endif
00327 
00328 int AnalogIn_Start( int index )
00329 {
00330   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00331     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00332   if ( AnalogIn->channelUsers[ index ]++ == 0 )
00333   {
00334     int status;
00335 
00336     // The lower four channel pins are shared with other subsystems, so no locking
00337     if ( index < 4 )
00338     {
00339       int io = AnalogIn_GetIo( index );
00340       status = Io_Start( io, false );
00341       if ( status != CONTROLLER_OK )
00342       {
00343         AnalogIn->channelUsers[ index ]--;
00344         return status;
00345       }
00346 
00347       Io_SetPullup( io, false );
00348     }
00349 
00350     if ( AnalogIn->users++ == 0 )
00351     {
00352       AnalogIn_Init();  
00353     }
00354   }
00355   
00356   return CONTROLLER_OK;
00357 }
00358 
00359 int AnalogIn_Stop( int index )
00360 {
00361   if ( index < 0 || index >= ANALOGIN_CHANNELS )
00362     return CONTROLLER_ERROR_ILLEGAL_INDEX;
00363   if ( AnalogIn->channelUsers[ index ] <= 0 )
00364     return CONTROLLER_ERROR_TOO_MANY_STOPS;
00365   if ( --AnalogIn->channelUsers[ index ] == 0 )
00366   {
00367     if ( index < 4 )
00368     {
00369       int io = AnalogIn_GetIo( index );
00370       Io_Stop( io );
00371     }
00372     if ( --AnalogIn->users == 0 )
00373     {
00374       AnalogIn_Deinit();
00375     }
00376   }
00377   return CONTROLLER_OK;
00378 }
00379 
00380 int AnalogIn_Init()
00381 {
00382   // Enable the peripheral clock
00383   AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_ADC;
00384 
00385   // Set up
00386   AT91C_BASE_ADC->ADC_MR =
00387        AT91C_ADC_TRGEN_DIS | // Hardware Trigger Disabled
00388     // AT91C_ADC_TRGEN_EN  | // Hardware Trigger Disabled
00389     //   AT91C_ADC_TRGSEL_ | // Hardware Trigger Disabled
00390     // AT91C_ADC_TRGSEL_TIOA0  | // Trigger Selection Don't Care
00391        AT91C_ADC_LOWRES_10_BIT | // 10 bit conversion
00392     // AT91C_ADC_LOWRES_8_BIT | // 8 bit conversion
00393        AT91C_ADC_SLEEP_NORMAL_MODE | // SLEEP
00394     // AT91C_ADC_SLEEP_MODE | // SLEEP
00395        ( ( 9 << 8 ) & AT91C_ADC_PRESCAL ) | // Prescale rate (8 bits)
00396        ( ( 127 << 16 ) & AT91C_ADC_STARTUP ) | // Startup rate
00397        ( ( 127 << 24 ) & AT91C_ADC_SHTIM ); // Sample and Hold Time
00398 
00399   //TODO: Will need to fine-tune these timings.
00400 
00401   // Do the OS stuff
00402 
00403   vSemaphoreCreateBinary( AnalogIn->semaphore );
00404 
00405   // Create the sempahore that will be used to wake the calling process up 
00406   vSemaphoreCreateBinary( AnalogIn->doneSemaphore );
00407   xSemaphoreTake( AnalogIn->doneSemaphore, 0 );
00408 
00409   // Initialize the interrupts
00410   // WAS AT91F_AIC_ConfigureIt( AT91C_ID_ADC, 3, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ( void (*)( void ) ) AnalogInIsr_Wrapper );
00411   // Which is defined at the bottom of the AT91SAM7X256.h file
00412   unsigned int mask ;                         
00413                                       
00414   mask = 0x1 << AT91C_ID_ADC;   
00415                         
00416   /* Disable the interrupt on the interrupt controller */         
00417   AT91C_BASE_AIC->AIC_IDCR = mask ;                   
00418   /* Save the interrupt handler routine pointer and the interrupt priority */ 
00419   AT91C_BASE_AIC->AIC_SVR[ AT91C_ID_ADC ] = (unsigned int)AnalogInIsr_Wrapper;      
00420   /* Store the Source Mode Register */                  
00421   AT91C_BASE_AIC->AIC_SMR[ AT91C_ID_ADC ] = AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL | 4  ;       
00422   /* Clear the interrupt on the interrupt controller */         
00423   AT91C_BASE_AIC->AIC_ICCR = mask ;         
00424 
00425   AT91C_BASE_ADC->ADC_IER = AT91C_ADC_DRDY; 
00426 
00427   AT91C_BASE_AIC->AIC_IECR = mask;
00428 
00429   return CONTROLLER_OK;
00430 }
00431 
00432 int AnalogIn_Deinit()
00433 {
00434   return CONTROLLER_OK;
00435 }
00436 
00437 int AnalogIn_GetIo( int index )
00438 {
00439   int io = -1;
00440   switch ( index )
00441   {
00442     case 0:
00443       io = ANALOGIN_0_IO;
00444       break;
00445     case 1:
00446       io = ANALOGIN_1_IO;
00447       break;
00448     case 2:
00449       io = ANALOGIN_2_IO;
00450       break;
00451     case 3:
00452       io = ANALOGIN_3_IO;
00453       break;
00454   }
00455   return io;
00456 }
00457 
00458 #ifdef OSC
00459 
00460 /** 
00461   Read whether a particular channel is enabled to check for and send new values automatically.
00462   @param index An integer specifying which input (0-7).
00463   @return True if enabled, false if disabled.
00464 */
00465 bool AnalogIn_GetAutoSend( int index )
00466 {
00467   AnalogIn_SetActive( index, 1 );
00468   return (AnalogIn->autosend >> index) & 0x01;
00469 }
00470 
00471 /** 
00472   Set whether a particular channel is enabled to check for and send new values automatically.
00473   @param index An integer specifying which input (0-7).
00474   @param onoff A boolean specifying whether to turn atuosending on or off.
00475 */
00476 void AnalogIn_SetAutoSend( int index, bool onoff )
00477 {
00478   AnalogIn_SetActive( index, 1 );
00479   if( ((AnalogIn->autosend >> index) & 0x01) != onoff )
00480   {
00481     int mask = (1 << index);
00482     if( onoff )
00483       AnalogIn->autosend |= mask;
00484     else
00485       AnalogIn->autosend &= ~mask;
00486     
00487     Eeprom_Write( EEPROM_ANALOGIN_AUTOSEND, (uchar*)&AnalogIn->autosend, 4 );
00488   }
00489 }
00490 
00491 /** \defgroup AnalogInOSC Analog In - OSC
00492   Read the Application Board's Analog Inputs via OSC.
00493   \ingroup OSC
00494   
00495   \section devices Devices
00496   There are 8 Analog Inputs on the Make Application Board, numbered 0 - 7.
00497   
00498   \section properties Properties
00499   The Analog Ins have three properties:
00500   - value
00501   - active
00502   - autosend
00503 
00504   \par Value
00505   The \b value property corresponds to the incoming signal of an Analog In.
00506   The range of values you can expect to get back are from 0 - 1023.
00507   Because you can only ever \em read the value of an input, you'll never
00508   want to include an argument at the end of your OSC message to read the value.\n
00509   To read the sixth Analog In, send the message
00510   \verbatim /analogin/5/value \endverbatim
00511   The board will then respond by sending back an OSC message with the Analog In value.
00512   
00513   \par Autosend
00514   The \b autosend property corresponds to whether an analogin will automatically send a message
00515   when its incoming value changes.
00516   To tell the Controller to automatically send messages from analogin 4, send the message
00517   \verbatim /analogin/5/autosend 1 \endverbatim
00518   To have the Controller stop sending messages from analogin 4, send the message
00519   \verbatim /analogin/5/autosend 0 \endverbatim
00520   All autosend messages send at the same interval.  You can set this interval, in 
00521   milliseconds, by sending the message
00522   \verbatim /system/autosend-interval 10 \endverbatim
00523   so that messages will be sent every 10 milliseconds.  This can be anywhere from 1 to 5000 milliseconds.
00524   \par
00525   You also need to select whether the board should send to you over USB or Ethernet.  Send
00526   \verbatim /system/autosend-usb 1 \endverbatim
00527   to send via USB, and 
00528   \verbatim /system/autosend-udp 1 \endverbatim
00529   to send via Ethernet.  Via Ethernet, the board will send messages to the last address it received a message from.
00530   
00531   \par Active
00532   The \b active property corresponds to the active state of an Analog In.
00533   If an Analog In is set to be active, no other tasks will be able to
00534   read from it as an Analog In.  If you're not seeing appropriate
00535   responses to your messages to the Analog In, check the whether it's 
00536   locked by sending a message like
00537   \verbatim /analogin/0/active \endverbatim
00538   \par
00539   You can set the active flag by sending
00540   \verbatim /analogin/0/active 1 \endverbatim
00541 */
00542 
00543 #include "osc.h"
00544 #include "string.h"
00545 #include "stdio.h"
00546 
00547 // Need a list of property names
00548 // MUST end in zero
00549 static char* AnalogInOsc_Name = "analogin";
00550 static char* AnalogInOsc_PropertyNames[] = { "active", "value", "autosend", 0 }; // must have a trailing 0
00551 
00552 int AnalogInOsc_PropertySet( int index, int property, int value );
00553 int AnalogInOsc_PropertyGet( int index, int property );
00554 
00555 // Returns the name of the subsystem
00556 const char* AnalogInOsc_GetName( )
00557 {
00558   return AnalogInOsc_Name;
00559 }
00560 
00561 // Now getting a message.  This is actually a part message, with the first
00562 // part (the subsystem) already parsed off.
00563 int AnalogInOsc_ReceiveMessage( int channel, char* message, int length )
00564 {
00565   int status = Osc_IndexIntReceiverHelper( channel, message, length, 
00566                                      ANALOGIN_CHANNELS, AnalogInOsc_Name,
00567                                      AnalogInOsc_PropertySet, AnalogInOsc_PropertyGet, 
00568                                      AnalogInOsc_PropertyNames );
00569   if ( status != CONTROLLER_OK )
00570     return Osc_SendError( channel, AnalogInOsc_Name, status );
00571   return CONTROLLER_OK;
00572 
00573 }
00574 
00575 // Set the index LED, property with the value
00576 int AnalogInOsc_PropertySet( int index, int property, int value )
00577 {
00578   switch ( property )
00579   {
00580     case 0: 
00581       AnalogIn_SetActive( index, value );
00582       break;
00583     case 2: // autosend 
00584       AnalogIn_SetAutoSend( index, value );
00585       break;    
00586   }
00587   return CONTROLLER_OK;
00588 }
00589 
00590 // Get the index LED, property
00591 int AnalogInOsc_PropertyGet( int index, int property )
00592 {
00593   int value = 0;
00594   switch ( property )
00595   {
00596     case 0:
00597       value = AnalogIn_GetActive( index );
00598       break;
00599     case 1:
00600       value = AnalogIn_GetValue( index );
00601       break;
00602     case 2: // autosend
00603       value = AnalogIn_GetAutoSend( index );
00604       break;
00605   }
00606   
00607   return value;
00608 }
00609 
00610 int AnalogInOsc_Async( int channel )
00611 {
00612   int newMsgs = 0;
00613   char address[ OSC_SCRATCH_SIZE ];
00614   int i;
00615   int value;
00616   for( i = 0; i < ANALOGIN_CHANNELS; i ++ )
00617   {
00618     if( !AnalogIn_GetAutoSend( i ) )
00619       continue;
00620     value = AnalogIn_GetValue( i );
00621     if( value != AnalogIn->lastValues[i] )
00622     {
00623       AnalogIn->lastValues[i] = value;
00624       snprintf( address, OSC_SCRATCH_SIZE, "/%s/%d/value", AnalogInOsc_Name, i );
00625       Osc_CreateMessage( channel, address, ",i", value );
00626       newMsgs++;
00627     }
00628   }
00629 
00630   return newMsgs;
00631 }
00632 
00633 #endif // OSC

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.