AD9833输出波形
目标:现在 AD9833 芯片在 SPI 控制器 0、片选信号 0 的位置,让它生成一个 1kHz 的正弦波。
1.SPI 配置
模式
SPI 通信依赖主设备产生的同步时钟(SCLK),主从设备通过 SCLK 同步数据传输。在此通信案例中,主为 CPU ,从为 AD9833。CPU 发来的数据会在 SCLK 的“边沿”被 AD9833 采样。
具体决定数据采样的 SPI 参数为 CPOL 和 CPHA。
- CPOL(Clock Polarity,时钟极性):空闲时的电平
- CPHA(Clock Phase,时钟相位):数据采样的边沿。取值
0或1,决定主从设备在 SCLK 的 “第几个跳变沿” 采样数据:- CPHA=0:数据在 SCLK 的 “第一个边沿”(空闲电平→激活电平的跳变沿)被采样。
- CPHA=1:数据在 SCLK 的 “第二个边沿”(激活电平→空闲电平的跳变沿)被采样。
AD9833 明确说明是在 SCLK 的下降沿获取数据,并且 SCLK 空闲时候既可以高也可以低。
根据“图4 串行时序”知道,在 FSYNC 变低前后,SCLK 为高电平,然后 SCLK 的第一个边沿为下降沿。所以我们选择 SPI_MODE_2。
位数
AD9833 每次传输是 16 位数据字。寄存器写入(频率寄存器是 28 位,要分两次写 16 位)
有的 MCU(比如 8051,68HC11)SPI 外设 只能按 8 位字节发送,不能直接 16 位。
手册里也给出了 8051/68HC11 的例子AD9833_CN:
- 做法是:
- 连续写两个 8 位字节,FSYNC 在整个 16 位传输过程中保持低电平。
- 这样 AD9833 就能正确地把这两个字节拼成一个 16 位字。
顺序
在手册 第 20 页《与微处理器接口》(80C51 例子)中定义了顺序:
“80C51/80L51 以 LSB 优先 格式输出串行数据。
AD9833 首先接收 MSB(写入目标寄存器时,4 个 MSB 为控制信息 …)。
因此,80C51 的发送程序必须重新排列位顺序,使得首先输出 MSB。”
- AD9833 要求先传输 MSB,再传输 LSB(即 MSB first)AD9833_CN。
- 例如:写
0x2100,要先发0x21,再发0x00。
手册明确说了 8051 默认是 LSB first,但 AD9833 要求 MSB first,因此需要软件重新排字节顺序
✅ 所以,最明确的地方就是手册 第 20–21 页,厂家直接告诉你:
- AD9833 接口必须 MSB first。
- 如果 MCU 默认 LSB first(比如 8051),必须软件倒序。
此时,要输出正弦波,还涉及两个部分:控制寄存器和频率寄存器。我们依次来看。
2.控制寄存器
下表是手册里的定义(整理简化版):
| 位 (D15…D0) | 名称 | 功能说明 |
|---|---|---|
| D15:D14 | 必须=00 | 表示这是控制寄存器写操作(区分和频率/相位寄存器写入) |
| D13 (B28) | 频率寄存器写入方式 | 1=连续写入完整 28 位频率字(推荐),0=只写 14 位(MSB 或 LSB)。 |
| D12 (HLB) | 半字选择 | 当 B28=0 时有效;1=写高 14 位,0=写低 14 位。 |
| D11 (FSELECT) | 频率寄存器选择 | 0=FREQ0,1=FREQ1。决定当前输出用哪个频率寄存器。 |
| D10 (PSELECT) | 相位寄存器选择 | 0=PHASE0,1=PHASE1。 |
| D9 | 保留 | 必须写 0。 |
| D8 (RESET) | 复位控制 | 1=输出固定在中点电平(波形停止),0=正常输出。 |
| D7 (SLEEP1) | 时钟休眠 | 1=关闭 MCLK(节能,DAC 输出保持),0=正常。 |
| D6 (SLEEP12) | DAC 休眠 | 1=关闭 DAC(省电),0=正常。 |
| D5 (OPBITEN) | 输出选择开关 | 0=正常波形(正弦/三角),1=输出方波(DAC 数据 MSB 或 MSB/2)。 |
| D4 | 保留 | 必须写 0。 |
| D3 (DIV2) | 方波分频 | 当 OPBITEN=1 时有效;0=输出 MSB,1=输出 MSB/2。 |
| D2 | 保留 | 必须写 0。 |
| D1 (MODE) | 波形模式 | 0=正弦波,1=三角波(注意:OPBITEN=1 时无效)。 |
| D0 | 保留 | 必须写 0。 |
控制制寄存器设置
目标:正弦波输出、DAC 正常工作、使用 FREQ0。
根据表格:
- D15:D14 =
00(控制寄存器写操作) - B28 (D13) =
1→ 允许一次写完整 28 位频率字(推荐方式) - HLB (D12) =
X(B28=1 时忽略) - FSELECT (D11) =
0→ 使用 FREQ0 - PSELECT (D10) =
0→ 使用 PHASE0(默认相位) - RESET (D8) =
0→ 允许输出(初始化时可以先置 1,然后清 0) - SLEEP1/SLEEP12 (D7/D6) =
0→ 不休眠 - OPBITEN (D5) =
0→ 输出 DAC 波形(而不是方波) - DIV2 (D3) =
0 - MODE (D1) =
0→ 正弦波 - 其他保留位 =
0
👉 这个控制字最终就是:
D15:D0 = 0b0010000000000000 = 0x2000
3.频率寄存器
输出频率由公式决定:
$$
f_{OUT} = \frac{FREQREG \times f_{MCLK}}{2^{28}}
$$
已知:
- f_OUT = 1 kHz
- f_MCLK = 25 MHz(常用板载晶振)
解得:
$$
FREQREG = \frac{f_{OUT} \times 2^{28}}{f_{MCLK}}
= \frac{1000 \times 268435456}{25,000,000} \approx 10737 ; (0x29E1)
$$
即 FREQ0 = 0x29E1(28 位寄存器)。
因为是 28 位寄存器,要分两次写入:
- 先写 低 14 位(0x29E1 & 0x3FFF = 0x29E1)
- 再写 高 14 位(0x29E1 >> 14 = 0x0)
写入时,每个字要加上寄存器选择位(D15:D14):
- 写低 14 位 → 前缀
01,即 0x2000 | (0x29E1 & 0x3FFF) = 0x29E1 - 写高 14 位 → 前缀
01,数据=0 → 0x2000
4. 完整配置步骤
- 复位 AD9833
- 写控制寄存器,RESET=1(0x2100)。
- 写频率寄存器 FREQ0
- 写低字:0x29E1
- 写高字:0x2000
- 写控制寄存器,正常输出正弦波
- 0x2000(RESET=0, 正弦波, FREQ0, 正常工作)。
5.C 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
// AD9833 寄存器地址定义
#define AD9833_REG_CMD 0x0000
#define AD9833_REG_FREQ0 0x4000
#define AD9833_REG_FREQ1 0x8000
#define AD9833_REG_PHASE0 0xC000
#define AD9833_REG_PHASE1 0xE000
// AD9833 控制位定义
#define AD9833_B28 0x2000
#define AD9833_HLB 0x1000
#define AD9833_FSELECT 0x0800
#define AD9833_PSELECT 0x0400
#define AD9833_RESET 0x0100
#define AD9833_SLEEP1 0x0080
#define AD9833_SLEEP12 0x0040
#define AD9833_OPBITEN 0x0020
#define AD9833_DIV2 0x0008
#define AD9833_MODE 0x0002
// SPI 设备路径
#define SPI_DEVICE "/dev/spidev0.0"
// AD9833 主时钟频率 (根据实际硬件配置)
#define MCLK_FREQUENCY 25000000.0
// 全局变量
static int spi_fd = -1;
/**
* @brief 初始化 SPI 接口
* @return 成功返回0,失败返回-1
*/
int spi_init(void) {
uint8_t mode = SPI_MODE_2;
uint8_t bits = 8;
uint32_t speed = 1000000;
spi_fd = open(SPI_DEVICE, O_RDWR);
if (spi_fd < 0) {
perror("无法打开SPI设备");
return -1;
}
if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0) {
perror("无法设置SPI模式");
close(spi_fd);
return -1;
}
if (ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("无法设置SPI字长");
close(spi_fd);
return -1;
}
if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
perror("无法设置SPI速度");
close(spi_fd);
return -1;
}
return 0;
}
/**
* @brief 向AD9833写入16位数据
* @param data 要写入的16位数据
* @return 成功返回0,失败返回-1
*/
int ad9833_write(uint16_t data) {
uint8_t tx_buf[2];
uint8_t rx_buf[2];
struct spi_ioc_transfer tr;
tx_buf[0] = (data >> 8) & 0xFF;
tx_buf[1] = data & 0xFF;
memset(&tr, 0, sizeof(tr));
tr.tx_buf = (unsigned long)tx_buf;
tr.rx_buf = (unsigned long)rx_buf;
tr.len = 2;
tr.delay_usecs = 0;
tr.speed_hz = 1000000;
tr.bits_per_word = 8;
tr.cs_change = 0;
if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
perror("SPI传输失败");
return -1;
}
usleep(10);
return 0;
}
/**
* @brief 重置AD9833
*/
void ad9833_reset(void) {
ad9833_write(AD9833_REG_CMD | AD9833_RESET);
usleep(10000);
ad9833_write(AD9833_REG_CMD | AD9833_B28);
usleep(10000);
}
/**
* @brief 设置AD9833输出频率
* @param freq 所需输出频率(Hz)
* @param reg 频率寄存器(0或1)
*/
void ad9833_set_frequency(double freq, int reg) {
uint32_t freq_word;
uint16_t reg_addr;
freq_word = (uint32_t)((freq * 268435456.0) / MCLK_FREQUENCY);
reg_addr = (reg == 0) ? AD9833_REG_FREQ0 : AD9833_REG_FREQ1;
ad9833_write(reg_addr | (freq_word & 0x3FFF));
ad9833_write(reg_addr | ((freq_word >> 14) & 0x3FFF));
}
/**
* @brief 设置AD9833输出相位
* @param phase_deg 相位角度(度)
* @param reg 相位寄存器(0或1)
*/
void ad9833_set_phase(double phase_deg, int reg) {
uint16_t phase_word;
uint16_t reg_addr;
phase_word = (uint16_t)((phase_deg * 4096.0) / 360.0) & 0x0FFF;
reg_addr = (reg == 0) ? AD9833_REG_PHASE0 : AD9833_REG_PHASE1;
ad9833_write(reg_addr | phase_word);
}
/**
* @brief 配置AD9833输出模式
* @param mode 输出模式(0=正弦波, 1=三角波, 2=方波)
*/
void ad9833_set_output_mode(int mode) {
uint16_t config = AD9833_REG_CMD | AD9833_B28;
if (mode == 1) {
// 三角波输出
config |= AD9833_MODE;
} else if (mode == 2) {
// 方波输出 (符号位输出)
config |= AD9833_OPBITEN;
}
ad9833_write(config);
}
void print_usage(const char *prog_name) {
printf("用法: %s [选项]\n", prog_name);
printf("选项:\n");
printf(" -f, --frequency <频率> 设置输出频率 (Hz), 默认: 1000.0\n");
printf(" -w, --waveform <波形> 设置输出波形, 可选: sine, triangle, square, 默认: sine\n");
printf(" -h, --help 显示此帮助信息\n");
}
int main(int argc, char *argv[]) {
double output_freq = 1000.0;
char *waveform_str = "sine";
int waveform_mode = 0; // 0=sine, 1=triangle, 2=square
int c;
// 定义长选项
static struct option long_options[] = {
{"frequency", required_argument, 0, 'f'},
{"waveform", required_argument, 0, 'w'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
// 解析命令行参数
while ((c = getopt_long(argc, argv, "f:w:h", long_options, NULL)) != -1) {
switch (c) {
case 'f':
output_freq = atof(optarg);
break;
case 'w':
waveform_str = optarg;
if (strcmp(waveform_str, "sine") == 0) {
waveform_mode = 0;
} else if (strcmp(waveform_str, "triangle") == 0) {
waveform_mode = 1;
} else if (strcmp(waveform_str, "square") == 0) {
waveform_mode = 2;
} else {
fprintf(stderr, "错误: 无效的波形类型 '%s'. 默认使用正弦波.\n", waveform_str);
waveform_mode = 0;
}
break;
case 'h':
print_usage(argv[0]);
return EXIT_SUCCESS;
case '?':
// getopt_long 已经打印了错误信息
print_usage(argv[0]);
return EXIT_FAILURE;
default:
break;
}
}
printf("AD9833 任意波形发生器\n");
printf("配置参数: 频率=%.2f Hz, 波形=%s\n", output_freq, waveform_str);
if (spi_init() != 0) {
fprintf(stderr, "SPI初始化失败\n");
return EXIT_FAILURE;
}
printf("SPI接口初始化成功\n");
ad9833_reset();
printf("AD9833复位完成\n");
ad9833_set_frequency(output_freq, 0);
printf("设置频率: %.2f Hz\n", output_freq);
ad9833_set_phase(0.0, 0);
printf("设置相位: 0度\n");
ad9833_set_output_mode(waveform_mode);
printf("设置为 %s 输出模式\n", waveform_str);
// ad9833_write(AD9833_REG_CMD | AD9833_B28);
// printf("启用输出\n");
printf("按 Enter 键退出...\n");
getchar();
close(spi_fd);
printf("程序结束\n");
return EXIT_SUCCESS;
}

浙公网安备 33010602011771号