2558 字
13 分钟
小孜同学整理的AD9833移植stm32f407使用手册~

AD9833应用手册#

AD9833功能#

  • 可输出正弦波,三角波,方波。
  • 稳定频率范围0-2MHz,低频调频精度1Hz(误差<5%)。
  • 幅值恒定输出500 - 600mVpp,输出正负交流电。

使用方法#

硬件连接#

  1. 三线SPI

    STM32作为SPI的主机,AD9833作为从机。

    • NSS --->FSYNC
    • SCK
    • SDATA
  2. VCC和GND可直接连接STM32。

软件设置#

STM32CubeMX配置#

image-20251129151414477

  • Mode:image-20251129104005388 这里有八种Mode,大致分为三类:全双工,半双工,单向收发。这里STM32作为主机,AD9833作为从机,我们只需要向从机单向发送数据,所以选择Transmit only Master(只由主机发送)。

    ​ SPI通信详细知识参考:SPI协议详解(图文并茂+超详细)-CSDN博客

  • Hardware NSS Signal

    image-20251129104756755

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

  • Frame Format(帧格式) image-20251129105010958

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

  • Data Size

    image-20251129111324805

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

    image-20251129111556088

  • First Bit

    image-20251129112005061

    image-20251129112100493

    ​ 因为D15,D14决定写入哪个寄存器,所以选***MSB(Most Significant Bit)***即最高有效位先传输。为输入数据指明方向。

  • Prescaler (for Baud Rate) image-20251129112725510 image-20251129112751341 这里的预分频系数选择16,对应波特率为2.625M(小于图十中的AD9833最大传输速率40M),太大了不稳定,太小了太慢,保险起见留有余量只要设置小于25M即可。

  • CPOL和CPHA image-20251129113801218 image-20251129113917727 这里从时序图看到AD9833的时钟一开始拉高,并且在第一个时钟下降沿开始读取数据,所以CPOL选择High,CPHA选择1 Edge。 SPI通信详细知识参考:SPI协议详解(图文并茂+超详细)-CSDN博客

  • CRC Calculation(CRC 校验计算image-20251129114239570AD9833 本身不支持 SPI 的 CRC 校验,因此需将设为Disabled。

  • NSS Signal Type

    image-20251129115631793

    上面选择了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)#

image-20251129122841931 image-20251129123247878 控制寄存器内有16bits,即SPI传输一次可改写其全部内容(HAL_SPI_Transmit第三个参数为2,即一次传输两个字节)。这16bits的功能大概分成四类

  1. 帧头(D15,D14) image-20251129124453556 D15和D14位设为0,告知AD9833我们这16bits写入的对象是控制寄存器。

  2. 规定数据写入格式(D13,D12) image-20251129124850633 因为我们不需要粗调细调分开来的功能,所以选择一次输入1 word(16bits),将D13设定为1,此时D12无用,不用管他默认为0。

  3. 设定波形输出状态(D1,D3,D5,D11,D10)

    • D1,D3,D5三位共同决定输出波形为正弦波,三角波或者方波 image-20251129125459677
    • AD9833共有两个独立的频率寄存器FREQ0和FREQ1。D11决定输出哪个寄存器内的值。为0输出FREQ0,为1输出FREQ1。
    • AD9833共有两个独立的相位寄存器PHASE0和PHASE1。D10决定输出哪个寄存器内的值。为0输出PHASE0,为1输出PHASE1。
  4. 设定AD9833工作状态(D6,D7,D8)

    • D8为RESET位,上电时RESET一次(设置为1,后面在AD9833_CtrlSet函数中默认设置为0),复位硬件,不改变频率寄存器和相位寄存器的值。即AD9833有掉电存储功能

      image-20251129133033079 image-20251129134302173 image-20251129134345071

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

  5. 预留位(D0,D2,D4,D9) 为了兼容性、扩展性和容错性有几个预留位,我们不用管他们,全部都默认设置为0

频率寄存器(28 bits)#

​ AD9833一次只能接受16bits,所以要发送两次即32bits发送,每次都要2bits帧头,除去共4bits帧头,剩下28bits写入频率寄存器当作频率大小。如图所示,配置FREQ0。 image-20251129140143115

​ 1. 帧头(D14,D15)

​ AD9833有两个频率寄存器,D15|D14分别为0|1,1|0时写入FREQ0和FREQ1。

  1. 数据位(其他28bits) 因为AD9833是先写入LSB,再写入MSB,所以发送函数也要按顺序编写image-20251129140303339

相位寄存器(16 bits)#

​ 相位寄存器和频率寄存器数量相同,不同的是位数和帧头数量(3个)。 image-20251129140715241

频率计算公式#

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

image-20251129142857811

0e29c9878dfba50b63691f0bd95bc6d4 image-20251129142945058

使用心得#

  1. 分模块功能编写程序。在AD9833.c里分成AD9833_Write,AD9833_CtrlSet和AD9833_FreqSet,之所以没有AD9833_PhaseSet是因为AD9833只有一个输出口,改不改相位没影响。
  2. 在使用发送数据函数的时候要注意参数格式对不对。HAL_SPI_Transmit函数里一次只能发送8bits,就不能直接填写16bits的TxData。
  3. 基本只用FREQ0就够了。

遇到的问题#

  1. 无法输出正常频率的方波。改DIV2的值不起作用。只能输出频率比设定频率小一半的方波。
  2. 输出的幅度不太稳定,有时不是0.6Vpp,变成0.4Vpp。
  3. STLINK烧程序不太好使,动不动就要拔了重连。😑

参考#

STM笔记_AD9833的详解及其f407驱动_ad9833驱动程序详解-CSDN博客

小孜同学整理的AD9833移植stm32f407使用手册~
https://cyanyumu.github.io/posts/学习记录/ad9833应用手册/
作者
CyanYuMu
发布于
2025-11-09
许可协议
CC BY-NC-SA 4.0