AD9833应用手册
AD9833功能
- 可输出正弦波,三角波,方波。
- 稳定频率范围0-2MHz,低频调频精度1Hz(误差<5%)。
- 幅值恒定输出500 - 600mVpp,输出正负交流电。
使用方法
硬件连接
-
三线SPI
STM32作为SPI的主机,AD9833作为从机。
- NSS --->FSYNC
- SCK
- SDATA
-
VCC和GND可直接连接STM32。
软件设置
STM32CubeMX配置

-
Mode:
这里有八种Mode,大致分为三类:全双工,半双工,单向收发。这里STM32作为主机,AD9833作为从机,我们只需要向从机单向发送数据,所以选择Transmit only Master(只由主机发送)。 SPI通信详细知识参考:SPI协议详解(图文并茂+超详细)-CSDN博客
-
Hardware NSS Signal

勾选此项意味着使用SPI硬件片选引脚,此引脚为固定引脚。若后续有引脚使用冲突,可随意定义GPIO引脚输出高低电平模拟片选,这里选择Disable即可。我这里为测试SPI通信,遂勾选此项。
-
Frame Format(帧格式)

这里选Motorola 格式是因为他是SPI 的标准格式,配置帧长度较灵活(可配置为 8/16/32 位等),TI固定16位,通用性较差。
-
Data Size

虽然AD9833一次传输16bits,但是HAL库的SPI传输函数一次只支持传送8bits(const uint8_t格式),所以这里选择8bits。

-
First Bit


因为D15,D14决定写入哪个寄存器,所以选***MSB(Most Significant Bit)***即最高有效位先传输。为输入数据指明方向。
-
Prescaler (for Baud Rate)
这里的预分频系数选择16,对应波特率为2.625M(小于图十中的AD9833最大传输速率40M),太大了不稳定,太小了太慢,保险起见留有余量只要设置小于25M即可。 -
CPOL和CPHA
这里从时序图看到AD9833的时钟一开始拉高,并且在第一个时钟下降沿开始读取数据,所以CPOL选择High,CPHA选择1 Edge。
SPI通信详细知识参考:SPI协议详解(图文并茂+超详细)-CSDN博客 -
CRC Calculation(CRC 校验计算)
AD9833 本身不支持 SPI 的 CRC 校验,因此需将设为Disabled。 -
NSS Signal Type

上面选择了Hardware NSS Signal的话,这里自动配置,不用管他。
keil编译烧录
#include "AD9833.h"
#include "spi.h"
/*
*******************************************************
向AD9833发送一个16bit的数据
*******************************************************
*/
void AD9833_Write(unsigned short TxData) //TxData是2字节
{
unsigned char data[2] ; //一个char一个字节,数组为2个字节
data[0] = (unsigned char)((TxData>>8) &0xff); //data[0]存储高位
data[1] = (unsigned char)(TxData&0xff); //data[1]存储低位
HAL_SPI_Transmit (&hspi2 , data, 2, 0x02) ; //用HAL库的SPI发送函数发送数据
}
/*
*******************************************************
Reset: 0为有输出,1为没输出,此位只控制有无输出,不复位寄存器
SleeppMode: 3为关闭内部DAC和时钟,0为不关闭
optionbit|modebit: 00正弦01三角10方波11保留
*******************************************************
*/
void AD9833_CtrlSet(unsigned char Reset,unsigned char SleeppMode,unsigned char optionbit,unsigned char modebit)
{
unsigned short regtemp = 0; //对输出模式的一些选择
regtemp = regtemp|(((unsigned short)Reset&0x01)<<8); //以下就是把每个位对应到相应的寄存器上,DIV2默认为0
regtemp = regtemp|((SleeppMode&0x03)<<6);
regtemp = regtemp|((optionbit&0x01)<<5);
regtemp = regtemp|((modebit&0x01)<<1);
AD9833_Write(regtemp); //写入数据,不需要先声明将要写入,可以复用指令,只要前两位是00
}
/*
*******************************************************
设置频率,设置完后暂不输出,用CtrlSet函数设置输出
频率值:0.1Hz-12.5MHz(最大值为25M晶振时钟的一半)
单位:Hz;例如,输出1M,则输入1000000
*******************************************************
*/
void AD9833_FreqSet(double Freq){ // 核心:先计算28位频率控制字 uint32_t freq_data = (uint32_t)(Freq * 268435456.0 / 25000000.0); // 25000000是AD9833参考时钟(硬件晶振为25MHz时,若实际是12.5M则改为12500000)
// 拆分28位为两个14位(LSB和MSB) uint16_t frequence_LSB = (uint16_t)(freq_data & 0x3FFF); // 低14位 uint16_t frequence_MSB = (uint16_t)((freq_data >> 14) & 0x3FFF); // 高14位
// 标记为写入FREQ0寄存器(bit15-14=01) frequence_LSB |= 0x4000; frequence_MSB |= 0x4000;
// 先写入控制字:连续写FREQ0的LSB和MSB(bit15-12=0010,bit8=0(不复位)) AD9833_Write(0x2000); // 原代码0x2100错误:bit8=1是Reset,会关闭输出 // 按顺序写入LSB、MSB AD9833_Write(frequence_LSB); AD9833_Write(frequence_MSB);}#include "main.h"
void AD9833_Write(unsigned short TxData);
void AD9833_CtrlSet(unsigned char Reset,unsigned char SleeppMode,unsigned char optionbit,unsigned char modebit);
void AD9833_FreqSet(double Freq);/* USER CODE BEGIN Header *//** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *//* USER CODE END Header *//* Includes ------------------------------------------------------------------*/#include "main.h"#include "spi.h"#include "gpio.h"
/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include "AD9833.h"/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*//* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*//* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/void SystemClock_Config(void);/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/** * @brief The application entry point. * @retval int */int main(void){
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */ SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI2_Init(); /* USER CODE BEGIN 2 */ //置RESET位为1,复位AD9833硬件(不清除寄存器) AD9833_Write(0x0100); //0001 0000 0000--D8是RESET位 //短暂延时,确保复位完成(1ms足够) HAL_Delay(1); AD9833_FreqSet(10000);//方波频率为设置的一半,其他两个相同 AD9833_CtrlSet(0,0,0,0);// 00正弦|01三角|10方波|11保留
/* USER CODE END 2 */
/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_Delay(100);//减少功耗,降低 MCU 占用率:若无延时,MCU 会以最高主频疯狂执行空循环 /* USER CODE END WHILE */
/* USER CODE BEGIN 3 */ } /* USER CODE END 3 */}
/** * @brief System Clock Configuration * @retval None */void SystemClock_Config(void){ RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 168; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); }
/** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); }}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/** * @brief This function is executed in case of error occurrence. * @retval None */void Error_Handler(void){ /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */}
#ifdef USE_FULL_ASSERT/** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */void assert_failed(uint8_t *file, uint32_t line){ /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */}#endif /* USE_FULL_ASSERT */软件驱动编写
AD9833有三种功能寄存器,控制寄存器、频率寄存器、相位寄存器。AD9833每次接收16bits,然后我们根据要改写的功能(频率或相位)改写16bits里的内容。
控制寄存器(16 bits)
控制寄存器内有16bits,即SPI传输一次可改写其全部内容(HAL_SPI_Transmit第三个参数为2,即一次传输两个字节)。这16bits的功能大概分成四类。
-
帧头(D15,D14)
D15和D14位设为0,告知AD9833我们这16bits写入的对象是控制寄存器。 -
规定数据写入格式(D13,D12)
因为我们不需要粗调细调分开来的功能,所以选择一次输入1 word(16bits),将D13设定为1,此时D12无用,不用管他默认为0。 -
设定波形输出状态(D1,D3,D5,D11,D10)
- D1,D3,D5三位共同决定输出波形为正弦波,三角波或者方波

- AD9833共有两个独立的频率寄存器FREQ0和FREQ1。D11决定输出哪个寄存器内的值。为0输出FREQ0,为1输出FREQ1。
- AD9833共有两个独立的相位寄存器PHASE0和PHASE1。D10决定输出哪个寄存器内的值。为0输出PHASE0,为1输出PHASE1。
- D1,D3,D5三位共同决定输出波形为正弦波,三角波或者方波
-
设定AD9833工作状态(D6,D7,D8)
-
D8为RESET位,上电时RESET一次(设置为1,后面在AD9833_CtrlSet函数中默认设置为0),复位硬件,不改变频率寄存器和相位寄存器的值。即AD9833有掉电存储功能。

-
将D6,D7都设定为0,让AD9833一直保持工作状态,输出波形。

-
-
预留位(D0,D2,D4,D9) 为了兼容性、扩展性和容错性有几个预留位,我们不用管他们,全部都默认设置为0。
频率寄存器(28 bits)
AD9833一次只能接受16bits,所以要发送两次即32bits发送,每次都要2bits帧头,除去共4bits帧头,剩下28bits写入频率寄存器当作频率大小。如图所示,配置FREQ0。

1. 帧头(D14,D15)
AD9833有两个频率寄存器,D15|D14分别为0|1,1|0时写入FREQ0和FREQ1。
- 数据位(其他28bits)
因为AD9833是先写入LSB,再写入MSB,所以发送函数也要按顺序编写。

相位寄存器(16 bits)
相位寄存器和频率寄存器数量相同,不同的是位数和帧头数量(3个)。

频率计算公式
这里硬件晶振是25MHz,所以AD9833输出波形频率理论上限是12.5MHz(奈奎斯特采样定律)。可输出频率上限由晶振大小决定。


使用心得
- 分模块功能编写程序。在AD9833.c里分成AD9833_Write,AD9833_CtrlSet和AD9833_FreqSet,之所以没有AD9833_PhaseSet是因为AD9833只有一个输出口,改不改相位没影响。
- 在使用发送数据函数的时候要注意参数格式对不对。HAL_SPI_Transmit函数里一次只能发送8bits,就不能直接填写16bits的TxData。
- 基本只用FREQ0就够了。
遇到的问题
- 无法输出正常频率的方波。改DIV2的值不起作用。只能输出频率比设定频率小一半的方波。
- 输出的幅度不太稳定,有时不是0.6Vpp,变成0.4Vpp。
- STLINK烧程序不太好使,动不动就要拔了重连。😑