JunMo Hong / EV-COG-AD3029LZ

Fork of stm-spirit1-rf-driver by ST

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers SimpleSpirit1.h Source File

SimpleSpirit1.h

00001 /*** Mbed Includes ***/
00002 #include "mbed.h"
00003 #include "mbed_debug.h"
00004 
00005 
00006 /*** Cube Includes ***/
00007 #include "SPIRIT_Radio.h"
00008 #include "SPIRIT_Management.h"
00009 #include "SPIRIT_Commands.h"
00010 #include "MCU_Interface.h"
00011 
00012 
00013 /*** Contiki Lib Includes ***/
00014 #include "spirit1.h"
00015 #include "spirit1-config.h"
00016 #include "spirit1-const.h"
00017 
00018 
00019 // betzw: enable beyond macro if you want debug messages also from IRQ handler
00020 // #define DEBUG_IRQ
00021 
00022 
00023 /*** Macros from Cube Implementation ***/
00024 #define CLEAR_TXBUF()           (spirit_tx_len = 0)
00025 #define IS_RXBUF_EMPTY()        (spirit_rx_len == 0)
00026 #define CLEAR_RXBUF()           do {                    \
00027                                     spirit_rx_len = 0;  \
00028                                     _spirit_rx_pos = 0; \
00029                                 } while(0)
00030 
00031 
00032 /*** Macros from Cube Implementation ***/
00033 /* transceiver state. */
00034 #define ON     0
00035 #define OFF    1
00036 
00037 
00038 /*** Macros for Spirit1 API ***/
00039 /* max payload */
00040 #define SPIRIT1_MAX_PAYLOAD     (MAX_PACKET_LEN)
00041 
00042 //180619 HJM : init 재시작을 위한 OnOff 변수
00043 //          : 15:22, 일단 안해보고 해보기로.. spi init을 두번해도 상관 없는 거면 뭐
00044 //static volatile bool isItDoItRFInit;
00045 //static volatile bool isItDoItSPIInit;
00046 
00047 
00048 
00049 
00050 /*** Missing Cube External Declarations ***/
00051 extern "C" void SpiritManagementSetFrequencyBase(uint32_t);
00052 
00053 
00054 /*** UnlockedSPI for Usage in IRQ context ***/
00055 class UnlockedSPI : public SPI 
00056 {
00057 public:
00058     UnlockedSPI(PinName mosi, PinName miso, PinName sclk) :
00059         SPI(mosi, miso, sclk) { }
00060     virtual ~UnlockedSPI() {}
00061     virtual void lock() { }
00062     virtual void unlock() { }
00063 };
00064 
00065 
00066 /*** A Simple Spirit1 Class ***/
00067 // NOTE: must be a singleton (due to mix of MBED/CUBE code)!!!
00068 // NOTE: implementation is IRQ-save but (intentionally) NOT thread-safe!!!
00069 /** Simple Spirit1 Class
00070  *
00071  * @Note Synchronization level: implementation is IRQ-save but (intentionally) NOT thread-safe!!!
00072  *
00073  * Example:
00074  * @code
00075  * #include "mbed.h"
00076  * #include "SimpleSpirit1.h"
00077  *
00078  * static char send_buf[] = "Hello World!";
00079  *
00080  * static SimpleSpirit1 &myspirit = SimpleSpirit1::CreateInstance(D11, D12, D3, D9, D10, D2);
00081  *
00082  * static volatile bool tx_done_flag = false;
00083  *
00084  * static void callback_func(int event)
00085  * {
00086  *   if (event == SimpleSpirit1::TX_DONE) {
00087  *     tx_done_flag = true;
00088  *   }
00089  * }
00090  *
00091  * int main()
00092  * {
00093  *   myspirit.attach_irq_callback(callback_func);
00094  *   myspirit.on();
00095  *
00096  *   while(1)
00097  *   {
00098  *     size_t curr_len = strlen((const char*)send_buf);
00099  *     myspirit.send(send_buf, curr_len);
00100  *
00101  *     while(!tx_done_flag);
00102  *     tx_done_flag = false;
00103  *   }
00104  * }
00105  * @endcode
00106  */
00107 class SimpleSpirit1 
00108 {
00109  protected:
00110     static SimpleSpirit1 *_singleton;
00111 
00112     /** Communication Interface Instance Variables **/
00113     UnlockedSPI _spi; // betzw - NOTE: Morpho/Zio pins are valid only for NUCLEO-F401RE
00114                       // mosi: PA_7 (D11)
00115                       // miso: PA_6 (D12)
00116                       // sclk: PB_3 (D3) or
00117                       //       PA_5 (D13) (only in case you unmount R4 & mount R7,
00118                       //                  (note: in this case you may not use LED1 on some platforms)
00119                       // bits: 8-bit
00120                       // mode: 0
00121                       // ordr: MSB
00122                       // freq: max 10MHz
00123     InterruptIn _irq; // PC_7 (D9) (falling)
00124     DigitalOut _chip_select; // PB_6 (D10) ('1' == chip unselected)
00125     DigitalOut _shut_down; // PA_10 (D2) ('1' == shut_down)
00126     DigitalOut _led; // PB_4 (D5) (optional)
00127 
00128     Callback<void(int)> _current_irq_callback;
00129     Timeout _rx_receiving_timeout;
00130 
00131     void rx_timeout_handler(void) 
00132     {
00133         set_ready_state();
00134         cmd_strobe(SPIRIT1_STROBE_RX);
00135 #ifdef DEBUG_IRQ
00136         debug("\r\n%s (%d)\r\n", __func__, __LINE__);
00137 #endif
00138     }
00139 
00140     void start_rx_timeout(void) 
00141     {
00142         _rx_receiving_timeout.attach_us(Callback<void()>(this, &SimpleSpirit1::rx_timeout_handler), 100 * 1000); // 100ms
00143     }
00144 
00145     void stop_rx_timeout(void) 
00146     {
00147         _rx_receiving_timeout.detach();
00148     }
00149 
00150     /** Static Variables from Cube Implementation **/
00151     /*
00152      * The buffers which hold incoming data.
00153      * The +1 because of the first byte,
00154      * which will contain the length of the packet.
00155      */
00156     volatile uint16_t spirit_tx_len;
00157     volatile bool _spirit_tx_started;
00158     volatile uint16_t spirit_rx_len;
00159     volatile uint16_t _spirit_rx_pos;
00160     volatile bool _spirit_rx_err;
00161     uint8_t spirit_rx_buf[MAX_PACKET_LEN];
00162     volatile bool _is_receiving;
00163     
00164     
00165 
00166     /** Status Variables from Cube Implementation **/
00167     unsigned int spirit_on;
00168     uint8_t last_rssi; //MGR
00169     uint8_t last_sqi;  //MGR
00170 
00171     /** Low Level Instance Variables **/
00172     unsigned int _nr_of_irq_disables;
00173 
00174     //HJM : SPI 셋팅 변수
00175     volatile uint32_t _uint32SpiClkSettingValue;
00176     
00177 
00178 
00179     /** Low Level Instance Methods **/
00180     void disable_spirit_irq(void) 
00181     {
00182         _irq.disable_irq();
00183         _nr_of_irq_disables++;
00184 #ifndef NDEBUG
00185         debug_if(_nr_of_irq_disables == 0, "\r\nassert failed in: %s (%d)\r\n", __func__, __LINE__);
00186 #endif
00187     }
00188 
00189     void enable_spirit_irq(void) 
00190     {
00191 #ifndef NDEBUG
00192         debug_if(_nr_of_irq_disables == 0, "\r\nassert failed in: %s (%d)\r\n", __func__, __LINE__);
00193 #endif
00194         if(--_nr_of_irq_disables == 0)
00195             _irq.enable_irq();
00196     }
00197 
00198     void chip_select() { _chip_select = 0; }
00199     void chip_unselect() { _chip_select = 1; }
00200 
00201     void enter_shutdown() {
00202         _shut_down = 1;
00203         wait_ms(5); // wait 5 milliseconds (to allow Spirit1 to shut down)
00204     }
00205 
00206     void exit_shutdown() {
00207         _shut_down = 0;
00208         wait_ms(10); // wait 10 milliseconds (to allow Spirit1 a proper boot-up sequence)
00209     }
00210 
00211     void cs_to_sclk_delay(void) 
00212     {
00213         wait_us(1); // heuristic value
00214     }
00215 
00216     /**
00217      * @brief      Write and read a buffer to/from the SPI peripheral device at the same time
00218      *             in 8-bit data mode using synchronous SPI communication.
00219      * @param[in]  pBufferToWrite pointer to the buffer of data to send.
00220      * @param[out] pBufferToRead pointer to the buffer to read data into.
00221      * @param[in]  NumBytes number of bytes to read and write.
00222      * @retval     0 if ok.
00223      * @retval     -1 if data format error.
00224      * @note       When using the SPI in Interrupt-mode, remember to disable interrupts
00225      *             before calling this function and to enable them again after.
00226      */
00227     void spi_write_read(uint8_t* pBufferToWrite, uint8_t* pBufferToRead, uint16_t NumBytes)
00228     {
00229         /* Read and write data at the same time. */
00230         for (int i = 0; i < NumBytes; i++) {
00231             pBufferToRead[i] = _spi.write(pBufferToWrite[i]);
00232         }
00233     }
00234 
00235     /** Radio Instance Methods **/
00236     void radio_set_xtal_freq(uint32_t freq) {
00237         SpiritRadioSetXtalFrequency(freq);
00238     }
00239 
00240     void radio_set_pa_level_dbm(uint8_t cIndex, float fPowerdBm) {
00241         SpiritRadioSetPALeveldBm(cIndex, fPowerdBm);
00242     }
00243 
00244     void radio_set_pa_level_max_index(uint8_t cIndex) {
00245         SpiritRadioSetPALevelMaxIndex(cIndex);
00246     }
00247 
00248     uint8_t radio_init(SRadioInit *init_struct) {
00249         return SpiritRadioInit(init_struct);
00250     }
00251 
00252     void radio_persistent_rx(SpiritFunctionalState xNewState) {
00253         SpiritRadioPersistenRx(xNewState);
00254     }
00255 
00256     void radio_afc_freeze_on_sync(SpiritFunctionalState xNewState) {
00257         SpiritRadioAFCFreezeOnSync(xNewState);
00258     }
00259 
00260     /** Packet System Instance Methods **/
00261     void pkt_basic_init(PktBasicInit* pxPktBasicInit) {
00262         SpiritPktBasicInit(pxPktBasicInit);
00263     }
00264 
00265     void pkt_basic_set_payload_length(uint16_t nPayloadLength) {
00266         SpiritPktBasicSetPayloadLength(nPayloadLength);
00267     }
00268 
00269     uint16_t pkt_basic_get_received_pkt_length(void) {
00270         return SpiritPktBasicGetReceivedPktLength();
00271     }
00272 
00273     /** IRQ Instance Methods **/
00274     void irq_de_init(SpiritIrqs* pxIrqInit) {
00275         SpiritIrqDeInit(pxIrqInit);
00276     }
00277 
00278     void irq_clear_status(void) {
00279         SpiritIrqClearStatus();
00280     }
00281 
00282     void irq_set_status(IrqList xIrq, SpiritFunctionalState xNewState) {
00283         SpiritIrq(xIrq, xNewState);
00284     }
00285 
00286     void irq_get_status(SpiritIrqs* pxIrqStatus) {
00287         SpiritIrqGetStatus(pxIrqStatus);
00288     }
00289 
00290     /** Management Instance Methods **/
00291     void mgmt_set_freq_base(uint32_t freq) {
00292         SpiritManagementSetFrequencyBase(freq);
00293     }
00294 
00295     void mgmt_refresh_status(void) {
00296         SpiritRefreshStatus();
00297     }
00298 
00299     /** Spirit GPIO Instance Methods **/
00300     void spirit_gpio_init(SGpioInit* pxGpioInitStruct) {
00301         SpiritGpioInit(pxGpioInitStruct);
00302     }
00303 
00304     /** Qi Instance Methods **/
00305     void qi_set_sqi_threshold(SqiThreshold xSqiThr) {
00306         SpiritQiSetSqiThreshold(xSqiThr);
00307     }
00308 
00309     void qi_sqi_check(SpiritFunctionalState xNewState) {
00310         SpiritQiSqiCheck(xNewState);
00311     }
00312 
00313     void qi_set_rssi_threshold_dbm(int nDbmValue) {
00314         SpiritQiSetRssiThresholddBm(nDbmValue);
00315     }
00316 
00317     float qi_get_rssi_dbm() {
00318         last_rssi = qi_get_rssi();
00319         return get_last_rssi_dbm();
00320     }
00321 
00322     uint8_t qi_get_rssi() {
00323         return SpiritQiGetRssi();
00324     }
00325 
00326     uint8_t qi_get_sqi() {
00327         return SpiritQiGetSqi();
00328     }
00329 
00330     /** Timer Instance Methods **/
00331     void timer_set_rx_timeout_stop_condition(RxTimeoutStopCondition xStopCondition) {
00332         SpiritTimerSetRxTimeoutStopCondition(xStopCondition);
00333     }
00334 
00335     void timer_set_rx_timeout_counter(uint8_t cCounter) {
00336         SpiritTimerSetRxTimeoutCounter(cCounter);
00337     }
00338 
00339     void timer_set_infinite_rx_timeout(void) {
00340         timer_set_rx_timeout_counter(0);
00341     }
00342 
00343     /** CSMA/CA Instance Methods **/
00344     void csma_ca_state(SpiritFunctionalState xNewState) {
00345         SpiritCsma(xNewState);
00346     }
00347 
00348     void csma_ca_init(CsmaInit* pxCsmaInit) {
00349         csma_ca_state(S_DISABLE); // Disabled at init
00350         SpiritCsmaInit(pxCsmaInit);
00351         SpiritCsmaSeedReloadMode(S_DISABLE); // always disable seed reload
00352     }
00353 
00354     /** Command Instance Methods**/
00355     void cmd_strobe(uint8_t cmd) {
00356         SpiritCmdStrobeCommand((SpiritCmd)cmd);
00357     }
00358 
00359     void cmd_strobe_flush_rx_fifo() {
00360         SpiritCmdStrobeCommand(CMD_FLUSHRXFIFO);
00361     }
00362 
00363     /** SPI Instance Methods **/
00364     StatusBytes spi_write_linear_fifo(uint8_t cNbBytes, uint8_t* pcBuffer) {
00365         return SdkEvalSpiWriteFifo(cNbBytes, pcBuffer);
00366     }
00367 
00368     StatusBytes spi_read_linear_fifo(uint8_t cNbBytes, uint8_t* pcBuffer) {
00369         return SdkEvalSpiReadFifo(cNbBytes, pcBuffer);
00370     }
00371 
00372     /** Linear FIFO Instance Methods **/
00373     uint8_t linear_fifo_read_num_elements_rx_fifo(void) {
00374         return SpiritLinearFifoReadNumElementsRxFifo();
00375     }
00376 
00377     uint8_t linear_fifo_read_num_elements_tx_fifo(void) {
00378         return SpiritLinearFifoReadNumElementsTxFifo();
00379     }
00380 
00381     void linear_fifo_set_almost_full_thr_rx(uint8_t cThrRxFifo) {
00382         SpiritLinearFifoSetAlmostFullThresholdRx(cThrRxFifo);
00383     }
00384 
00385     /** Calibration Instance Methods **/
00386     void calibration_rco(SpiritFunctionalState xNewState) {
00387         SpiritCalibrationRco(xNewState);
00388     }
00389 
00390     /** Internal Spirit Methods */
00391     int set_ready_state(void);
00392     uint8_t refresh_state(void);
00393 
00394     /** Friend Functions **/
00395     friend StatusBytes SdkEvalSpiWriteRegisters(uint8_t cRegAddress, uint8_t cNbBytes, uint8_t* pcBuffer);
00396     friend StatusBytes SdkEvalSpiReadRegisters(uint8_t cRegAddress, uint8_t cNbBytes, uint8_t* pcBuffer);
00397     friend StatusBytes SdkEvalSpiCommandStrobes(uint8_t cCommandCode);
00398     friend StatusBytes SdkEvalSpiWriteFifo(uint8_t cNbBytes, uint8_t* pcBuffer);
00399     friend StatusBytes SdkEvalSpiReadFifo(uint8_t cNbBytes, uint8_t* pcBuffer);
00400 
00401     /** Sdk Instance Methods **/
00402     StatusBytes SdkEvalSpiWriteRegisters(uint8_t cRegAddress, uint8_t cNbBytes, uint8_t* pcBuffer);
00403     StatusBytes SdkEvalSpiReadRegisters(uint8_t cRegAddress, uint8_t cNbBytes, uint8_t* pcBuffer);
00404     StatusBytes SdkEvalSpiCommandStrobes(uint8_t cCommandCode);
00405     StatusBytes SdkEvalSpiWriteFifo(uint8_t cNbBytes, uint8_t* pcBuffer);
00406     StatusBytes SdkEvalSpiReadFifo(uint8_t cNbBytes, uint8_t* pcBuffer);
00407 
00408     /** Helper Instance Methods **/
00409     void chip_sync_select() {
00410         disable_spirit_irq();
00411         chip_select();
00412         cs_to_sclk_delay();
00413     }
00414 
00415     void chip_sync_unselect() {
00416         chip_unselect();
00417         enable_spirit_irq();
00418     }
00419 
00420     /** Init Instance Method **/
00421     void init();
00422 
00423     /** Spirit Irq Callback */
00424     void IrqHandler();
00425 
00426 
00427 
00428     /** Constructor **/
00429     SimpleSpirit1(PinName mosi, PinName miso, PinName sclk,
00430           PinName irq, PinName cs, PinName sdn,
00431           PinName led, 
00432           uint32_t uint32SClkSettingValue);
00433 
00434     /** Destructor **/
00435     ~SimpleSpirit1(void); // should never be called!
00436 
00437 
00438 
00439 
00440 
00441 
00442 
00443 
00444 
00445 
00446 
00447 
00448 public:
00449     enum 
00450     {
00451         RX_DONE,
00452         TX_DONE,
00453         TX_ERR
00454     };
00455     
00456     
00457     
00458     
00459     
00460 
00461     /** Create singleton instance of 'SimpleSpirit1'
00462      *
00463      * @param mosi 'PinName' of mosi pin to use
00464      * @param miso 'PinName' of miso pin to use
00465      * @param sclk 'PinName' of clock pin to use
00466      * @param irq  'PinName' of interrupt pin to use
00467      * @param cs   'PinName' of chip-select pin pin to use
00468      * @param sdn  'PinName' of pin to use for device shutdown
00469      *
00470      * @returns     reference to singleton instance
00471      */
00472     static SimpleSpirit1& CreateInstance(
00473                                             PinName mosi, PinName miso, PinName sclk,
00474                                             PinName irq, PinName cs, PinName sdn,
00475                                             PinName led = NC, 
00476                                             uint32_t uint32SClkSettingValue = 6500000   //6.5Mhz, SPI CLK speed 초기값, 최고 속도                                    
00477                                         ) 
00478     {
00479         if(_singleton == NULL) 
00480         {
00481             _singleton = new SimpleSpirit1(mosi, miso, sclk,
00482                     irq, cs, sdn, led, uint32SClkSettingValue);
00483             printf("[INIT_REVISE] init start..\n");             
00484 //          isItDoItRFInit = false;
00485 //          isItDoItSPIInit = false;
00486             wait(0.1);
00487             _singleton->init();
00488             printf("[INIT_REVISE] init end..\n");       
00489         } 
00490         else 
00491         {
00492             error("SimpleSpirit1 singleton already created!\n");
00493         }
00494 
00495         return *_singleton;
00496     }
00497     
00498     
00499     
00500     
00501 
00502     /** Get singleton instance of 'SimpleSpirit1'
00503      *
00504      * @returns     reference to singleton instance
00505      */
00506     static SimpleSpirit1& Instance() 
00507     {
00508         if(_singleton == NULL) 
00509         {
00510             error("SimpleSpirit1 must be created before used!\n");
00511         }
00512 
00513         return *_singleton;
00514     }
00515     
00516     
00517     
00518     
00519     
00520     
00521     
00522     
00523     
00524     
00525     
00526     
00527 
00528     /** Attach a function to be called by the Spirit Irq handler when an event has occurred
00529      *
00530      *  @param func A void(int) callback, or 0 to set as none
00531      *
00532      *  @note  Function 'func' will be executed in interrupt context!
00533      *  @note  Function 'func' will be call with either 'RX_DONE', 'TX_DONE', or 'TX_ERR' as parameter
00534      *         to indicate which event has occurred.
00535      */
00536     void attach_irq_callback(Callback<void(int)> func) 
00537     {
00538         _current_irq_callback = func;
00539     }
00540 
00541     /** Switch Radio On
00542      */
00543     int on(void);
00544 
00545     /** Switch Radio Off
00546      */
00547     int off(void);
00548 
00549     /** Set Channel
00550      */
00551     void set_channel(uint8_t channel) {
00552         SpiritRadioSetChannel(channel);
00553     }
00554 
00555     /** Send a Buffer
00556      *
00557      * @param payload       pointer to buffer to be send
00558      * @param payload_len   length of payload buffer in bytes
00559      * @param use_csma_ca   should CSMA/CA be enabled for transmission
00560      *
00561      * @returns             zero in case of success, non-zero error code otherwise
00562      *
00563      * @note                the maximum payload size in bytes allowed is defined by macro 'SPIRIT1_MAX_PAYLOAD'
00564      */
00565     int send(const void *payload, unsigned int payload_len, bool use_csma_ca = true);
00566 
00567     /** Copy received data into buffer
00568      *
00569      * @param buf       pointer to buffer to be filled
00570      * @param bufsize   size of buffer
00571      *
00572      * @returns         number of bytes copied into the buffer
00573      *
00574      * @note            the buffer should be (at least) of size 'SPIRIT1_MAX_PAYLOAD' (in bytes).
00575      */
00576     int read(void *buf, unsigned int bufsize);
00577 
00578     /** Perform a Clear-Channel Assessment (CCA) to find out whether the medium is idle or not.
00579      *
00580      * @returns  1 if the medium is busy.
00581      */
00582     int channel_clear(void);
00583 
00584     /** Check if the radio driver has just received a packet
00585      */
00586     int get_pending_packet(void);
00587 
00588     /** Is radio currently receiving
00589      */
00590     bool is_receiving(void) {
00591         return _is_receiving;
00592     }
00593 
00594     /** Get latest value of RSSI (in dBm)
00595      */
00596     float get_last_rssi_dbm(void) {
00597         get_last_rssi_raw();
00598         return (-120.0+((float)(last_rssi-20))/2);
00599     }
00600 
00601     /** Get latest value of RSSI (as Spirit1 raw value)
00602      */
00603     uint8_t get_last_rssi_raw(void) {
00604         if(last_rssi == 0) {
00605             last_rssi = qi_get_rssi();
00606         }
00607         return last_rssi;
00608     }
00609 
00610     /** Get latest value of LQI (scaled to 8-bit)
00611      */
00612     uint8_t get_last_sqi(void) {
00613         const uint8_t max_sqi = 8 * ((SYNC_LENGTH>>1)+1);
00614         if(last_sqi == 0) {
00615             last_sqi = qi_get_sqi();
00616         }
00617         if(last_sqi > max_sqi) last_sqi = max_sqi;
00618 
00619         return (last_sqi * 255 / max_sqi);
00620     }
00621 
00622     /** Reset Board
00623      */
00624     void reset_board() 
00625     {
00626         printf("[INIT_REVISE]reset board ...\n");
00627         init();
00628         printf("[INIT_REVISE]reset board end.\n");
00629     }
00630     
00631     
00632     
00633     
00634     
00635     
00636     
00637     
00638     
00639     
00640     
00641     
00642     
00643     
00644     
00645 //*************************************************************************************
00646 //HJM SUDO code : SPI 클럭 셋팅 가능 함수
00647 //*************************************************************************************
00648     /** Set SPI Clock
00649      */
00650     void set_spi_clock(uint32_t ) 
00651     {
00652         
00653     }
00654     
00655     
00656     
00657     
00658     
00659 //*************************************************************************************    
00660     
00661     
00662     
00663     
00664 };
00665 
00666 
00667 
00668 
00669 /*
00670 HJM SUDO code : 고려 상황 정리
00671 1. 올바르지 않은 행동을 할 인스턴스가 생성 되었을 시는 어떻게 하나??
00672     -> 생성자를 사용했을 시 포인터를 반환 하도록 해서, 올바르지 않은 인스턴스가 만들어졌을 시에는 반환 객체 주소 값을 반환하지 않도록 하면 된다.
00673         => ex> 
00674             test *t;
00675             t = new test();
00676             if(NULL == t)
00677             {
00678                 printf("올바르지 않은 객체가 생성되었습니다!\n");
00679                 return;
00680             }
00681 
00682 
00683 
00684 
00685 
00686 */
00687 
00688 
00689