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,时钟相位):数据采样的边沿。取值01,决定主从设备在 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. 完整配置步骤

  1. 复位 AD9833
    • 写控制寄存器,RESET=1(0x2100)。
  2. 写频率寄存器 FREQ0
    • 写低字:0x29E1
    • 写高字:0x2000
  3. 写控制寄存器,正常输出正弦波
    • 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;
}
posted @ 2025-09-07 15:08  上山砍大树  阅读(151)  评论(0)    收藏  举报