/****************************************************************************/
/* Application note AN009                                                   */
/* CC1000 interface library                                                 */
/*                                                                          */
/* File:      cc1000avr.c                                                   */
/* Revision:  2.1                                                           */
/*                                                                          */
/* Microcontroller:                                                         */
/*          Atmel AVRmega8L                                                 */
/* Written for the IAR AVR compiler                                         */
/*                                                                          */
/* Author:  Karl H. Torvmark, Field Applications Engineer, Chipcon          */
/*                                                                          */
/* Contact: Chipcon AS +47 22 95 85 44                                      */
/*          wireless@chipcon.com                                            */
/*                                                                          */
/* Changes:                                                                 */
/*      2.1 : First AVR version                                             */
/****************************************************************************/

/****************************************************************************/
/* This library contains functions for configuring the CC1000. These        */
/* routines use bit-banging to program the CC1000.                          */
/* Routines to read and write the calibration values in the CC1000 are      */
/* provided, they aree useful in frequency-agile and frequency hopping      */
/* applications. See application note AN009 for more information.           */
/* The routines in this file will have to be adapted depending on the MCU   */
/* and compiler used.                                                       */
/****************************************************************************/

/*                                                                           *
 * Revision history:                                                         *
 *
 * Revision 2.5  2003/05/08 10:51:52  tos
 * Corrected LOCK monitor in Calibrate.
 *
 * Revision 2.4  2003/05/08 10:05:26  tos
 * Corrections according to Errata Note 01: reset freq.synth if unable
 * to lock PLL.
 *
 * Revision 2.3  2003/04/28 08:21:14  tos
 * Corrected inconsistent monitoring of CC1000: [calibration complete] +
 * [lock].
 *
 *
 *                                                                           *
 ****************************************************************************/

#include "cc1000.h"

/****************************************************************************/
/*  This routine sends new configuration data to the CC1000                 */
/*  Based on bit bashing (general I/O pin use)                              */
/****************************************************************************/

void ConfigureCC1000(char Count, short Configuration[])
{
  char BitCounter;
  uint8_t WordCounter;
  short Data;
 
  CC1000_PORT |= (1<<PALE); //PALE = 1
  
  for (WordCounter = 0; WordCounter<Count; WordCounter++)
  {
    Data = Configuration[WordCounter];
    CC1000_PORT &= ~(1<<PALE); // PALE = 0
    
    /* Send address bits */
    for (BitCounter = 0; BitCounter < 7; BitCounter++) {
      CC1000_PORT |= (1<<PCLK); // PCLK = 1
      if ((Data & 0x8000) == 0)
        CC1000_PORT &= ~(1<<PDATA); // PDATA = 0
      else
        CC1000_PORT |= (1<<PDATA); // PDATA = 1
      
      Data = Data<<1;
      CC1000_PORT &= ~(1<<PCLK); //PCLK = 0;
    }
    
    /* Send read/write bit */
    /* Ignore bit in data, always use 1 */
    
    CC1000_PORT |= (1<<PCLK); //PCLK = 1
    CC1000_PORT |= (1<<PDATA);  //PDATA = 1
    CC1000_PORT &= ~(1<<PCLK); //PCLK = 0
    Data = Data<<1;
    CC1000_PORT |= (1<<PCLK); //PCLK = 1
    CC1000_PORT |= (1<<PALE); //PALE = 1
    
    /* Send data bits */
    
    for (BitCounter = 0; BitCounter<8; BitCounter++)
    {
      CC1000_PORT |= (1<<PCLK); //PCLK = 1
      if ((Data & 0x8000)==0) {
        CC1000_PORT &= ~(1<<PDATA); // PDATA = 0
      }
      else {
        CC1000_PORT |= (1<<PDATA); // PDATA = 1
      }
      Data = Data<<1;
      CC1000_PORT &= ~(1<<PCLK); //PCLK = 0
     }
  CC1000_PORT |= (1<<PCLK); //PCLK = 1
  }   /* Finished with word */   
}

/****************************************************************************/
/*  This routine writes to a single CC1000 register                         */
/****************************************************************************/

void WriteToCC1000Register(unsigned char addr, unsigned char data)
{
  short val;

  val = addr<<9 | data;
  ConfigureCC1000(1,&val);
}

/****************************************************************************/
/*  This routine writes to a single CC1000 register, with data and address  */
/*  given in the same variable                                              */
/****************************************************************************/

void WriteToCC1000RegisterWord(short addranddata)
{
  ConfigureCC1000(1,&addranddata);
}

/****************************************************************************/
/*  This routine reads from a single CC1000 register                        */
/****************************************************************************/

char ReadFromCC1000Register(char addr)
{
  char BitCounter;
  char Data;
  char Debug;
    
  CC1000_PORT |= (1<<PALE); //PALE = 1
  
  Data = addr<<1;
  CC1000_PORT &= ~(1<<PALE);
    
  /* Send address bits */
  for (BitCounter = 0; BitCounter<7; BitCounter++)
  {
    CC1000_PORT |= (1<<PCLK); // PCLK = 1
    if ((Data & 0x80)==0) {
      CC1000_PORT &= ~(1<<PDATA); // PDATA = 0
    }
    else {
      CC1000_PORT |= (1<<PDATA); // PDATA = 1
    }
    Data = Data<<1;
    CC1000_PORT &= ~(1<<PCLK); //PCLK = 0;
  }
  
  /* Send read/write bit */
  /* Ignore bit in data, always use 0 */
  
  CC1000_PORT |= (1<<PCLK); //PCLK = 1
  CC1000_PORT &= ~(1<<PDATA); //PDATA = 0
  CC1000_PORT &= ~(1<<PCLK); //PCLK = 0
 
 
  CC1000_PORT |= (1<<PCLK); //PCLK = 1
  CC1000_PORT |= (1<<PALE); //PALE = 1
    
  /* Receive data bits */
  
  CC1000_PORT |= (1<<PDATA); //PDATA = 1
   
  CC1000_PDIR &= ~(1<<PDATA); /* Set up PDATA as an input */
    
  for (BitCounter = 0; BitCounter<8; BitCounter++)
  {
    CC1000_PORT &= ~(1<<PCLK); //PCLK = 0
    Data = Data<<1;
    Debug = (1<<PDATA);
    if ((PIND & Debug)==0) {
      Data&=0xFE;
    } else {
      Data |= 0x01;
    }
    CC1000_PORT |= (1<<PCLK); //PCLK = 1
  }
  
  
  CC1000_PDIR |= (1<<PDATA); /* Set up PDATA as an output again */
  
  return Data;
}
  
/****************************************************************************/
/*  This routine resets the CC1000, clearing all registers.                 */
/****************************************************************************/  

void ResetCC1000(void)
{
  char MainValue;
  
  MainValue = ReadFromCC1000Register(CC1000_MAIN);
  WriteToCC1000Register(CC1000_MAIN, MainValue & 0xFE);         // Reset CC1000
  WriteToCC1000Register(CC1000_MAIN, MainValue | 0x01);         // Bring CC1000 out of reset
}


/****************************************************************************/
/*  This routine calibrates the CC1000                                      */
/*  Returns 0 if calibration fails, non-zero otherwise. Checks the LOCK     */
/*  to check for success.                                                   */
/****************************************************************************/

char CalibrateCC1000(void)
{  
  int TimeOutCounter;

  WriteToCC1000Register(CC1000_PA_POW, 0x00); // Turn off PA to avoid spurs

  WriteToCC1000Register(CC1000_CAL, 0xA6); // Start calibration

  // Wait for calibration complete
  for(TimeOutCounter = CAL_TIMEOUT; ((ReadFromCC1000Register(CC1000_CAL)&0x08)==0)&&(TimeOutCounter>0); TimeOutCounter--);

  // Wait for lock
  for(TimeOutCounter = LOCK_TIMEOUT; ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==0)&&(TimeOutCounter>0); TimeOutCounter--);
  
  WriteToCC1000Register(CC1000_CAL, 0x26); /* End calibration */
  WriteToCC1000Register(CC1000_PA_POW, PA_VALUE); /* Restore PA setting */

  return ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==1);
}

/****************************************************************************/
/*  This routine puts the CC1000 into RX mode (from TX). When switching to  */
/*  RX from PD, use WakeupC1000ToRX first                                   */
/****************************************************************************/


char SetupCC1000RX(char RXCurrent)
{
  int i;
  char lock_status;

  WriteToCC1000Register(CC1000_MAIN, 0x11);    // Switch into RX, switch to freq. reg A
  WriteToCC1000Register(CC1000_PLL, RX_PLL);   // Use RX refdiv setting
  WriteToCC1000Register(CC1000_CURRENT, RXCurrent); // Program VCO current for RX

  // Wait 250us before monitoring LOCK
  for (i = 0; i<0x1000; i++);

  // Wait for lock
  for(i = LOCK_TIMEOUT; ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==0)&&(i>0); i--);

  // If PLL in lock
  if ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==0x01){
    // Indicate PLL in LOCK
    lock_status = LOCK_OK;
  // Else (PLL out of LOCK)
  }else{
    // If recalibration ok
    if(CalibrateCC1000()){
      // Indicate PLL in LOCK
      lock_status = LOCK_RECAL_OK;
    // Else (recalibration failed)
    }else{
      // Reset frequency syncthesizer (ref.: Errata Note 01)
      ResetFreqSynth();
      // Indicate PLL out of LOCK
      lock_status = LOCK_NOK;
    }
  }

  // Return LOCK status to application
  return (lock_status);
}

/****************************************************************************/
/*  This routine puts the CC1000 into TX mode (from RX). When switching to  */
/*  TX from PD, use WakeupCC1000ToTX first                                  */
/****************************************************************************/

char SetupCC1000TX(char TXCurrent)
{
  int i;
  char lock_status;

  WriteToCC1000Register(CC1000_PA_POW, 0x00);   // Turn off PA to avoid frequency splatter

  WriteToCC1000Register(CC1000_MAIN, 0xE1);    // Switch into TX, switch to freq. reg B
  WriteToCC1000Register(CC1000_PLL, TX_PLL);   // Use TX refdiv setting
  WriteToCC1000Register(CC1000_CURRENT, TXCurrent); // Program VCO current for TX

  // Wait 250us before monitoring LOCK
  for (i = 0; i<0x1000; i++);

  // Wait for lock
  for(i = LOCK_TIMEOUT; ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==0)&&(i>0); i--);

  // If PLL in lock
  if ((ReadFromCC1000Register(CC1000_LOCK)&0x01)==0x01){
    // Indicate PLL in LOCK
    lock_status = LOCK_OK;
  // Else (PLL out of LOCK)
  }else{
    // If recalibration ok
    if(CalibrateCC1000()){
      // Indicate PLL in LOCK
      lock_status = LOCK_RECAL_OK;
    // Else (recalibration failed)
    }else{
      // Reset frequency syncthesizer (ref.: Errata Note 01)
      ResetFreqSynth();
      // Indicate PLL out of LOCK
      lock_status = LOCK_NOK;
    }
  }

  // Increase output power
  WriteToCC1000Register(CC1000_PA_POW, PA_VALUE); // Restore PA setting
  
  // Return LOCK status to application
  return (lock_status);
}

/****************************************************************************/
/*  This routine puts the CC1000 into power down mode. Use WakeUpCC1000ToRX */
/*  followed by SetupCC1000RX or WakeupCC1000ToTX followed by SetupCC1000TX */
/*  to wake up from power down                                              */
/****************************************************************************/

void SetupCC1000PD(void)
{
  WriteToCC1000Register(CC1000_MAIN, 0x3F);    // Put CC1000 into power-down
  WriteToCC1000Register(CC1000_PA_POW, 0x00);  // Turn off PA to minimise current draw
}

/****************************************************************************/
/*  This routine wakes the CC1000 up from PD mode to RX mode, call          */
/*  SetupCC1000RX after this routine is finished.                           */
/****************************************************************************/

void WakeUpCC1000ToRX(char RXCurrent)
{
  int i;
  
  
  WriteToCC1000Register(CC1000_MAIN, 0x3B);  // Turn on xtal oscillator core
  WriteToCC1000Register(CC1000_CURRENT, RXCurrent); // Program VCO current for RX 
  WriteToCC1000Register(CC1000_PLL, RX_PLL); // Use RX refdiv setting
  
  // Insert wait routine here, must wait for xtal oscillator to stabilise, 
  // typically takes 2-5ms.
  for (i = 0; i<0x7FFE; i++);
  
  WriteToCC1000Register(CC1000_MAIN, 0x39);  // Turn on bias generator
  // Wait for 250us, insert wait loop here
  WriteToCC1000Register(CC1000_MAIN, 0x31);  // Turn on frequency synthesiser
}

/****************************************************************************/
/*  This routine wakes the CC1000 up from PD mode to TX mode, call          */
/*  SetupCC1000TX after this routine is finished.                           */
/****************************************************************************/

void WakeUpCC1000ToTX(char TXCurrent)
{
  int i;
  
  
  WriteToCC1000Register(CC1000_MAIN, 0xFB);  // Turn on xtal oscillator core
  WriteToCC1000Register(CC1000_CURRENT, TXCurrent); // Program VCO current for TX
  WriteToCC1000Register(CC1000_PLL, TX_PLL); // Use TX refdiv setting
  
  // Insert wait routine here, must wait for xtal oscillator to stabilise, 
  // typically takes 2-5ms. 
  for (i = 0; i<0x7FFE; i++);
  
  WriteToCC1000Register(CC1000_MAIN, 0xF9);  // Turn on bias generator
  // Wait for 250us, insert wait loop here
  WriteToCC1000Register(CC1000_PA_POW, PA_VALUE); // Turn on PA
  WriteToCC1000Register(CC1000_MAIN, 0xF1);  // Turn on frequency synthesiser
}

/****************************************************************************/
/*  This routine locks the averaging filter of the CC1000                   */
/****************************************************************************/

void AverageManualLockCC1000(void)
{
  WriteToCC1000Register(CC1000_MODEM1, 0x19);
}

/****************************************************************************/
/*  This routine unlocks the averaging filter of the CC1000                 */
/****************************************************************************/

void AverageFreeRunCC1000(void)
{
  WriteToCC1000Register(CC1000_MODEM1, 0x09);
}

/****************************************************************************/
/*  This routine sets up the averaging filter of the CC1000 for automatic   */
/*  lock. This can be used in polled receivers.                             */
/****************************************************************************/

void AverageAutoLockCC1000(void)
{
  WriteToCC1000Register(CC1000_MODEM1, 0x01);
}

/****************************************************************************/
/*  This routine reads the current calibration values from the CC1000       */
/****************************************************************************/

void ReadCurrentCalibration(char *val1, char *val2)
{
  *val1=ReadFromCC1000Register(CC1000_TEST0);
  *val2=ReadFromCC1000Register(CC1000_TEST2);
}

/****************************************************************************/
/*  This routine overrides the current calibration of the CC1000            */
/****************************************************************************/

void OverrideCurrentCalibration(char val1, char val2)
{
  WriteToCC1000Register(CC1000_TEST5,(val1 & 0x0F)|0x10);
  WriteToCC1000Register(CC1000_TEST6,(val2 & 0x1F)|0x20);
}

/****************************************************************************/
/*  This routine stops override of the CC1000 calibration values            */
/****************************************************************************/

void StopOverridingCalibration(void)
{
  WriteToCC1000Register(CC1000_TEST5, 0x00);
  WriteToCC1000Register(CC1000_TEST6, 0x00);
}
  

/****************************************************************************/
/*  This CC1000 frequency synthesizer                                       */
/****************************************************************************/
void ResetFreqSynth(void)
{
  char modem1_value;
  modem1_value = ReadFromCC1000Register(CC1000_MODEM1)&~0x01;
  WriteToCC1000Register(CC1000_MODEM1, modem1_value);
  WriteToCC1000Register(CC1000_MODEM1, modem1_value|0x01);
}
