Project showcase
RF Signal Generator DDS 600MHz@1.5GHz Arduino Shield AD9910

RF Signal Generator DDS 600MHz@1.5GHz Arduino Shield AD9910 © TAPR-OHL

The way to create a high-precision generator from 100 kHz to 600 MHz based on DDS from Analog Devices - AD9910.

  • 500 views
  • 0 comments
  • 4 respects

Components and supplies

Apps and online services

About this project

This story began at the moment when we got the need of an RF signal source for our experiments with SDR radio. For us were important parameters such as: high stability of frequency, low level of phase noise and also the minimal number of harmonics and spurious. Naturally, when working with SDR radio, it was necessary the ability of quick and easy adjustment of the output frequency in a wide range, for example, from 100 kHz to 500 MHz.

For implementing that kind of task, the DDS method (Direct Digital Synthesis) is ideally suitable. There existing ready-made IC (Integrated Circuits) for DDS on the market, here are the most common ones among them from Analog Devices IC: AD9910, AD9912, AD9914 and AD9915. We opted for the AD9910 because it has all the qualities we needed and it has a fair price.

The original branded Evaluation Board costs more than 600 USD. Therefore we decided to first look for a Chinese clone of such a board and it was easily found and bought on eBay at a price of 70 USD:

When the Chinese copy came to us, it turned out that there is no any existing software for it, and the seller could not provide for us even a connection diagram! It was sad that even an Internet search did not gave an answer on how is it to connect the Chinese board to the microcontroller. It took us for 3 full working days to redraw the circuit of this board on paper. This task was complicated by the fact that the board was of black color and the printed conductors were very poorly visible on it.

After the circuit was composed and before the first start-up, we decided to measure and check the ratings of some elements that caused us a suspicion and it turned out that elements with incorrect ratings were installed in the PLL Loop Filter circuit. We had to recalculate them according to the datasheet for the AD9910 (p. 26) and replace them with the correct ones. Upon further inspection of the Chinese board, it turned out that there were short circuits between several AD9910 legs (made by the Chinese worker during the assembly of this board), and one leg was not soldered at all. Naturally, all these shortcomings had to be eliminated before the first start-up.

Next came the stage of connecting to the microcontroller. In order not to create unnecessary difficulties when programming as a controller for controlling the AD9910, we chose the Arduino Mega. But since the ATmega2560, which is installed on the Arduino Mega, has signal levels of 5 Volts, and the AD9910 has 3.3 Volts, it had to also connect an additional level converter for 32 channels.

After that it had to study the datasheet for the AD9910 and write a control program. And only after that it was possible to turn on this whole circuit and measure the output signal. It is better to do all this with a spectrum analyzer. When we turned on the spectrum analyzer, we were unpleasantly surprised at how terrible everything was:

There were a lot of harmonics and spurious on the screen, and their level reached -25 dBm! And this is despite the fact that according to the documentation by Analog Devices to AD9910 the level of harmonics should not exceed -60 dBm. This was already enough to understand that nothing good would come out of using this board as a heterodyne for an SDR receiver. But we decided on practice to make sure on how badly everything will be.

To receive the signal, we used a receiver based on the IC MC3362, and fed the RF signal from the DDS AD9910 to the heterodyne input of the MC3362, and the Yaesu VX-6R radio station was used as a transmitter. But despite the high-quality transmitter, we could not hear anything except noise and screeching.

Therefore, we set a goal to create a generator with the characteristics declared in Datasheet and Applications Notes to the AD9910 in the form of a Shield for Arduino, that is, with the ability of connection without extra wires and external peripheral circuits, such as level converters.

Upon creating such Shield, we strictly adhered to all the recommendations from the manufacturer, and did some things even better. Here is an incomplete list of the main technical solutions that allowed us to achieve a good result at the end:

  • In accordance with the recommendation from Analog Devices, we used a 4-layer board, not a 2-layer one, as the Chinese did to reduce the cost of production.
  • Analog Devices recommends making separate or separate the power lines of analog circuits and digital ones with FB (Ferrite Beads), but we did even better: each power circuit is stabilized with a separate LDO (Low-Dropout Regulator) stabilizer and separated with FB.
  • The power scheme itself was implemented in such a way that the device could be powered from both USB and an external 7 volt power source. IMPORTANT: not to use an external power source with a voltage above 7 volts, since the DDS is powered by linear stabilizers with a low level of ripples, but they can fail due to overheating if they are powered with a voltage above 7 volts. It is thanks to the correct wiring of the board, the presence on the board of high-quality stabilizers, tantalum capacitors and common capacitor with capacity of 1000 uF, that the DDS core could be overclocked to 1.5 GHz and this is with powered by USB!
  • We found out that one of the reasons for the appearance of a large number of harmonics on the Chinese board is the DDS clock generator. The Chinese used a generator with a TTL output of 3.3 Volts. Such generator produces a rectangular signal with an infinite number of harmonics. Therefore, we used a generator with a clipped sine TCXO, since it has a minimal spectrum. And besides, it is more stable compared to the TTL generator.
  • The next problem with the Chinese board was that when clocked from an external source, the input signal passed through an ordinary jumper, which is intended only for transmitting a low-frequency signal, but not for a signal with a frequency of 1 GHz! Therefore, for switching clock sources, we use feed-through capacitors that needs to be soldered to the appropriate place to connect the desired clock source.
  • We connect all clock sources (except crystal) through balun in order to eliminate possible common-mode interference.
  • We installed an output transformer on the +IOUT/-IOUT outputs of the AD9910 to suppress even harmonics and increase the output signal level by 3 dBm.
  • We used the output filter already on the 7-th order, it was calculated and modeled in the AWR Microwave Office program, and the design and layout of the PCB section for this filter were taken from Application Notes AN-837 from Analog Devices.

In total, at this moment, the device has the following view:

  • Form factor of Shield for Arduino Mega.
  • Two high-speed level converters are installed on the board, which (in addition to control via the SPI bus) allows controlling DDS via parallel interface. Thus enables usage of all functions without unnecessary wires and connecting external circuits.
  • The Shield is equipped with a 0.96” detachable OLED Display.
  • There are 3 buttons on the device for control and navigation in the menu.

The software part of the device allows to configure and save the following parameters in non-volatile EEPROM memory:

  • The output signal frequency from 100 kHz to 450 MHz (600 MHz during overclocking 1.5Ghz) with increment of 1 Hz.
  • The amplitude of the output signal from 0 to -84 dBm (or from +4 when setting the DAC Current HI).
  • Perform AM (Amplitude Modulation) with adjustment:
  • frequency modulation from 10 Hz to 100 kHz.
  • depths of modulation from 0% to 100%.
  • Produce FM (Frequency Modulation) with adjustment:
  • frequency modulation from 10 Hz to 100 kHz.
  • deviations from 0 Hz to 100 kHz.
  • Select the clock source (XO, TXCO or external) and its frequency:
  • Overclock (adjust the core frequency) from 1000 MHz to 1500 MHz.

We took measurements of two boards and compared their readings:

Code

AD9910.cpp C/C++
most important functions
/*****************************************************************************
  Initialization DDS
  * Config SPI, Reset DDS and ReConfig SPI
  * Enable/Disable internal PLL, mux, div, charge pump current, Set VCO
  
    Input Parametr:
  * PLL - 1 enable, if 0 disable
  * Divider - This input REF CLOCK divider by 2, if 1 - Divider by 2, if 0 - Divider OFF
  * 
  * Set Current Output - 0..255, 127 - Default equal to 0 dB 
*****************************************************************************/
void DDS_Init(bool PLL, bool Divider, uint32_t Ref_Clk)
 {
   
   DDS_GPIO_Init();
   
   // It is very important for DDS AD9910 to set the initial port states
   HAL_GPIO_WritePin(DDS_MASTER_RESET_GPIO_PORT, DDS_MASTER_RESET_PIN, GPIO_PIN_SET);   // RESET = 1
   HAL_GPIO_WritePin(DDS_MASTER_RESET_GPIO_PORT, DDS_MASTER_RESET_PIN, GPIO_PIN_RESET); // RESET = 0
   HAL_GPIO_WritePin(DDS_IO_UPDATE_GPIO_PORT, DDS_IO_UPDATE_PIN, GPIO_PIN_RESET);       // IO_UPDATE = 0   
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);               // CS = 1
   HAL_GPIO_WritePin(DDS_OSK_GPIO_PORT, DDS_OSK_PIN, GPIO_PIN_SET);                     // OSK = 1
   HAL_GPIO_WritePin(DDS_PROFILE_0_GPIO_PORT, DDS_PROFILE_0_PIN, GPIO_PIN_RESET);
   HAL_GPIO_WritePin(DDS_PROFILE_1_GPIO_PORT, DDS_PROFILE_1_PIN, GPIO_PIN_RESET);
   HAL_GPIO_WritePin(DDS_PROFILE_2_GPIO_PORT, DDS_PROFILE_2_PIN, GPIO_PIN_RESET);
   
   DDS_SPI_Init();
   
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
   strBuffer[0] = CFR1_addr;
   strBuffer[1] = 0;// RAM_enable;//RAM_Playback_Amplitude;// | RAM_enable;//0x00; 
   strBuffer[2] = 0;//Inverse_sinc_filter_enable;//0; //Continuous_Profile_0_1; //0;//0x80;//0x00;
   strBuffer[3] = 0; //OSK_enable | Select_auto_OSK;//0x00;
   strBuffer[4] = SDIO_input_only ;
   HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 5, 1000);
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
   
   DDS_UPDATE();
   
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
   strBuffer[0] = CFR2_addr;
   strBuffer[1] = Enable_amplitude_scale_from_single_tone_profiles;//1;//0x00;
   strBuffer[2] = 0;//SYNC_CLK_enable;// | Read_effective_FTW;
   strBuffer[3] = 0;//0x08;//PDCLK_enable;
   strBuffer[4] = Sync_timing_validation_disable;// | Parallel_data_port_enable;
   HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 5, 1000);
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
   
   DDS_UPDATE();

  switch (PLL)
  {
    case false:
      /******************* External Oscillator 60 - 1000Mhz (Overclock up to 1500Mhz) ***************/ 
      HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
      strBuffer[0] = CFR3_addr;
      strBuffer[1] = 0;//DRV0_REFCLK_OUT_High_output_current;//
      strBuffer[2] = 0;
      if (Divider) strBuffer[3] = REFCLK_input_divider_ResetB;
        else strBuffer[3] = REFCLK_input_divider_ResetB | REFCLK_input_divider_bypass;
      strBuffer[4] = 0; // SYSCLK= REF_CLK * N
      HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 5, 1000);
      HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
      DDS_UPDATE();
      //**************************
    break;
    case true:
      /******************* External Oscillator TCXO 3.2 - 60 MHz ***********************************************/ 
      HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
      strBuffer[0] = CFR3_addr;
      strBuffer[1] = VCO5;  // | DRV0_REFCLK_OUT_High_output_current;
      strBuffer[2] = Icp287uA;   
      strBuffer[3] = REFCLK_input_divider_ResetB | PLL_enable; // REFCLK_input_divider_bypass; //
      strBuffer[4]=((uint32_t)DDS_Core_Clock/Ref_Clk)*2; // multiplier for PLL
      #if DBG==1
      Serial.print(F("PLL Multiplier="));
      Serial.println(strBuffer[4]);
      #endif
      HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 5, 1000);
      HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
      DDS_UPDATE();
    /**********************/
    break;
  }
   
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
   strBuffer[0] = FSC_addr;
   strBuffer[1] = 0;
   strBuffer[2] = 0;
   strBuffer[3] = 0;
   if (DACCurrentIndex==0) strBuffer[4] = 0x7F; //DAC current Normal
    else strBuffer[4] = 0xFF;// Max carrent 255 = 31mA //DAC current HI
   HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 5, 1000);
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
   
   DDS_UPDATE();
   
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET);    
   strBuffer[0] = 0x0E;
   strBuffer[1] = 0x3F;
   strBuffer[2] = 0xFF;
   strBuffer[3] = 0x00;
   strBuffer[4] = 0x00;
   
   strBuffer[5] = 0x19;
   strBuffer[6] = 0x99;
   strBuffer[7] = 0x99;
   strBuffer[8] = 0x9A; // 100mhz
   HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 9, 1000);
   HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
   
   DDS_UPDATE(); 
}   

/*****************************************************************************************
   F_OUT - Set Frequency in HZ 
   Num_Prof - Single Tone Mode 0..7
   Amplitude_dB - amplitude in dB from 0 to -84 (only negative values)
*****************************************************************************************/
void DDS_Fout (uint32_t *F_OUT, int16_t Amplitude_dB, uint8_t Num_Prof)
{
   #if DBG==1
   Serial.print(F("*F_OUT="));
   Serial.println(*F_OUT); 
   Serial.print(F("DDS_Core_Clock="));
   Serial.println(DDS_Core_Clock); 
   float FoutByDDScore=(double)*F_OUT / (double)DDS_Core_Clock;
   Serial.print(F("FoutByDDScore="));
   Serial.println(FoutByDDScore, 6); 
   #endif
   
   FTW = ((uint32_t)(4294967296.0 *((float)*F_OUT / (float)DDS_Core_Clock)));
   jlob = & FTW;	

	 HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_RESET); 	 
	 strBuffer[0] = Num_Prof; // Single_Tone_Profile_0;

   //ASF  - Amplitude 14bit 0...16127
	 strBuffer[1] =  (uint16_t)powf(10,(Amplitude_dB+84.288)/20.0) >> 8;     
	 strBuffer[2] =  (uint16_t)powf(10,(Amplitude_dB+84.288)/20.0);         
	 strBuffer[3] = 0x00;
	 strBuffer[4] = 0x00;
	 
	 strBuffer[5] = *(((uint8_t*)jlob)+ 3);
	 strBuffer[6] = *(((uint8_t*)jlob)+ 2);
	 strBuffer[7] = *(((uint8_t*)jlob)+ 1);
	 strBuffer[8] = *(((uint8_t*)jlob));
		
   HAL_SPI_Transmit(&hspi1, (uint8_t*)strBuffer, 9, 1000);
	 HAL_GPIO_WritePin(DDS_SPI_CS_GPIO_PORT, DDS_SPI_CS_PIN, GPIO_PIN_SET);
	 
	 DDS_UPDATE(); 
	 
}	
AD9910 Full repository for Arduino Mega 2560
Just compile, upload and have fun.

Schematics

Block Diagram
Pcb shield dds9910 v1 1 1200x860 mv6fawkmzq

Comments

Similar projects you might like

Arduino Shield NCS314 NIXIE Tubes Clock IN-14

Project tutorial by Team GRA_AND_AFCH

  • 27,500 views
  • 9 comments
  • 80 respects

Signal Generator with Arduino Using DDS and Pico

Project tutorial by pokitMeter

  • 14,185 views
  • 1 comment
  • 11 respects

Real Theremin Using Open.Theremin Shield for Arduino

Project showcase by Urs Gaudenz

  • 19,278 views
  • 9 comments
  • 24 respects

Send and Receive Text Messages (SMS) with GSM SIM900 Shield

Project tutorial by Boian Mitov

  • 77,455 views
  • 6 comments
  • 40 respects
Add projectSign up / Login