You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32中如何将可变长度正弦波采样数组存入Flash?

STM32F103C8T6 数字特雷门琴正弦波存储与CPU优化方案

存储方案优化

针对Flash存储、多维数组语法限制问题,推荐以下两种落地方式:

1. 编译期预生成单维const数组+索引映射

C语言多维数组的语法限制可以通过单维数组+计算索引绕过,同时const修饰的数组会被编译器分配到Flash,不占用RAM:

  • 用Python/Shell脚本预计算所有需要的正弦波采样数据(覆盖特雷门琴的频率范围),直接生成C头文件中的单维数组
  • 通过宏定义或简单计算模拟多维访问,可读性远优于旋转矩阵

示例脚本(Python):

import math

# 配置参数
FS = 48000  # DAC采样率
MIN_FREQ = 200
MAX_FREQ = 1200
FREQ_STEP = 10
SAMPLES_PER_CYCLE = 16  # 每个正弦周期固定采样点数

samples = []
freq_sample_counts = []

for freq in range(MIN_FREQ, MAX_FREQ + 1, FREQ_STEP):
    cycle_samples = []
    # 计算单个周期的采样值(转换为12位DAC整数范围0-4095)
    for i in range(SAMPLES_PER_CYCLE):
        sin_val = math.sin(2 * math.pi * i / SAMPLES_PER_CYCLE)
        dac_val = int((sin_val + 1) * 2047.5)
        cycle_samples.append(dac_val)
    samples.extend(cycle_samples)
    freq_sample_counts.append(SAMPLES_PER_CYCLE)

# 生成C头文件
with open("sine_data.h", "w") as f:
    f.write("#ifndef SINE_DATA_H\n#define SINE_DATA_H\n")
    f.write(f"#define FREQ_COUNT {len(freq_sample_counts)}\n")
    f.write("const uint16_t sine_samples[] __attribute__((section(\".rodata\"))) = {\n")
    f.write("    " + ", ".join(map(str, samples)) + "\n};\n")
    f.write("const uint16_t freq_sample_counts[] = {\n")
    f.write("    " + ", ".join(map(str, freq_sample_counts)) + "\n};\n")
    f.write("#endif\n")

C代码中模拟多维访问:

#include "sine_data.h"
// 通过频率索引和采样点索引直接定位数据
#define GET_SAMPLE(freq_idx, sample_idx) sine_samples[(freq_idx)*SAMPLES_PER_CYCLE + (sample_idx)]

2. 采样精度与长度优化

  • 若DAC精度允许,将16位采样值压缩为8位,直接减半Flash占用
  • 放弃固定3000点的统一长度,改为按正弦周期采样:每个频率仅存储1~2个完整周期的采样点,循环播放即可生成连续波形,大幅减少总数据量

CPU占用优化

高采样率下CPU开销大的核心原因是手动喂DAC数据,推荐通过硬件卸载解决:

1. DMA+定时器自动驱动DAC

STM32F1的DAC支持DMA触发,配置后完全无需CPU干预:

  • 用定时器(如TIM6)生成采样率触发信号
  • 将Flash中正弦数组的地址配置给DMA,设置循环传输模式
  • DMA会自动按定时器频率从Flash读取采样值,写入DAC数据寄存器

示例配置片段:

// 初始化DMA(以DAC通道1为例)
hdma_dac1_ch1.Instance = DMA1_Channel3;
hdma_dac1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_dac1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_dac1_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_dac1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_dac1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_dac1_ch1.Init.Mode = DMA_CIRCULAR;
hdma_dac1_ch1.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&hdma_dac1_ch1);

__HAL_LINKDMA(&hdac, DMA_Handle1, hdma_dac1_ch1);

// 启动DMA传输(以第0个频率为例)
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t)sine_samples, freq_sample_counts[0], DAC_ALIGN_12B_R);
// 启动定时器触发DMA
HAL_TIM_Base_Start(&htim6);

2. 频率切换优化

切换特雷门琴频率时,无需停止DMA,仅修改DMA的内存起始地址和传输长度即可:

void switch_freq(uint8_t freq_idx) {
    HAL_DMA_Stop(&hdma_dac1_ch1);
    // 计算目标频率的采样数据起始地址
    uint32_t start_addr = (uint32_t)&sine_samples[0];
    for(uint8_t i=0; i<freq_idx; i++){
        start_addr += freq_sample_counts[i] * sizeof(uint16_t);
    }
    // 更新DMA参数
    hdma_dac1_ch1.Instance->CMAR = start_addr;
    hdma_dac1_ch1.Instance->CNDTR = freq_sample_counts[freq_idx];
    // 重启DMA
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, start_addr, freq_sample_counts[freq_idx], DAC_ALIGN_12B_R);
}

3. 消除浮点运算

全程使用整数采样值,预计算阶段完成所有浮点计算,运行时仅做数组索引和DMA参数修改,彻底避免CPU浮点开销。

内容的提问来源于stack exchange,提问作者hennep

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 02:20:04