使用CMSIS开发STM32麦克风采样应用

使用CMSIS开发STM32

使用 CMSIS(Cortex Microcontroller Software Interface Standard)开发 STM32 是一种低耦合、硬件抽象层更清晰的方式,适合对性能和代码移植性有较高要求的项目。相比 HAL 库,CMSIS 更接近底层,但比直接操作寄存器更标准化。


一、什么是 CMSIS?

CMSIS 是 ARM 推出的标准化软件接口,包括以下几个部分:

  1. CMSIS-Core:定义了 Cortex-M 核心寄存器、异常向量表、系统启动代码等。
  2. CMSIS-DSP:提供数字信号处理库(滤波器、FFT 等)。
  3. CMSIS-RTOS:定义 RTOS 的接口,如 CMSIS-RTOS2(兼容 RTX、FreeRTOS)。
  4. CMSIS-Driver:标准化外设驱动接口(用于与中间件配合)。
  5. CMSIS-Pack:管理芯片包和设备支持文件。

二、开发环境准备

推荐使用以下环境:

  • Keil MDK(集成了 CMSIS,支持 STM32)
  • STM32CubeIDE(虽然默认是 HAL,但可以集成 CMSIS)
  • ARM-GCC + VS Code(手动配置)
  • PlatformIO + Clion(支持 STM32,可自动配置)

三、开发步骤(使用 CMSIS-Core 开发 STM32)

以下以 STM32F103 为例说明基本流程:

1. 创建工程并引入 CMSIS
  • 使用 Clion,PlatformIO 新建工程,选择芯片型号,勾选 CMSIS框架。

  • 环境会自动创建,包含:

    • core_cm3.h(或对应内核)
    • 启动文件:startup_stm32f10x.s
    • 系统文件:system_stm32f10x.c/h
2. 编写主程序

CMSIS 提供对寄存器的结构体封装,可以使用类似以下方式配置 GPIO 和控制:

新建一个文件 src/main.c

#include "stm32f1xx.h"

int main(void) {
    // 1. 启用 GPIOC 时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // 2. 配置 PC13 为推挽输出
    GPIOC->CRH &= ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13); // 清除
    GPIOC->CRH |= (0x2 << GPIO_CRH_MODE13_Pos);        // 输出 2MHz 推挽

    while (1) {
        GPIOC->ODR ^= (1 << 13); // 反转 PC13(LED)
        for (volatile int i = 0; i < 500000; ++i); // 延时
    }
}
3. 使用 system_stm32f10x.c 初始化系统时钟

CMSIS 提供了 SystemInit() 函数,建议在 main() 前调用(通常在启动文件中已调用)。


四、CMSIS vs HAL 对比

特性 CMSIS HAL
层级 贴近底层 高层抽象
易用性 一般 易用性强
可移植性 高(ARM Cortex-M 通用) 限于 STM32
性能开销 更小(无多余封装) 较大(函数调用多)
学习曲线 陡峭 平缓

五、推荐学习资源

  • ARM CMSIS 官方文档
  • 《STM32 嵌入式系统开发实战指南》
  • GitHub 上的裸机项目模板,如 libopencm3

STM32F103C6T6 是一款常见的小容量 STM32 芯片,非常适合用于入门音频采集实验。
接下来,使用它实现 基于 CMSIS、无 HAL 库、使用 DMA+ADC+TIM 采样音频输入(如麦克风)


✅ 芯片特点(STM32F103C6T6)

项目 参数
Flash / RAM 32KB / 10KB
ADC通道 10 个(12-bit)
DMA通道 7 个(DMA1)
定时器 TIM1, TIM2, TIM3, TIM4
最大主频 72 MHz
外设资源有限 适合轻量采集用途

硬件连接

  • 麦克风模块输出(如 MAX9814 模拟输出)→ PA1(ADC1_IN1)
  • VCC/GND 接 3.3V 电源(注意音频输入不能超出 3.3V)

项目结构(CMSIS + 标准寄存器)

你只需要 4 个核心文件,手动搭建即可(适合 Keil / GCC / VS Code):

Project/
├── main.c                // 主程序
├── system_stm32f10x.c    // CMSIS 系统时钟设置
├── startup_stm32f103x6.s // 启动文件(适用于 C6T6)
└── stm32f10x.h           // 头文件(CMSIS 和设备寄存器定义)

✅ 可用 STM32CubeMX 生成 .ssystem_*.c 文件,再手动移除 HAL 相关代码。
使用PlatformIO, 只需要配置 platformio.ini 文件,添加 main.c实现。


main.c 示例(完整 DMA 采样音频)

#include "stm32f10x.h"

#define SAMPLE_RATE 8000
#define BUFFER_SIZE 512

volatile uint16_t adc_buffer[BUFFER_SIZE];

void ADC_DMA_Init(void) {
    // 启用时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN;
    RCC->AHBENR  |= RCC_AHBENR_DMA1EN;

    // DMA1 Channel1 设置
    DMA1_Channel1->CPAR  = (uint32_t)&ADC1->DR;
    DMA1_Channel1->CMAR  = (uint32_t)adc_buffer;
    DMA1_Channel1->CNDTR = BUFFER_SIZE;
    DMA1_Channel1->CCR   = DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_PSIZE_0 | DMA_CCR_MSIZE_0 | DMA_CCR_EN;

    // ADC 设置
    ADC1->SQR1 = 0;            // 1个规则通道
    ADC1->SQR3 = 1;            // IN1(PA1)
    ADC1->CR2  = ADC_CR2_DMA | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTTRIG | ADC_CR2_ADON;

    // 稍等后开启
    for (volatile int i = 0; i < 10000; ++i);
    ADC1->CR2 |= ADC_CR2_ADON;
}

void TIM2_Init(void) {
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    // TIM2 设置 8kHz 触发
    TIM2->PSC = 0;
    TIM2->ARR = (72000000 / SAMPLE_RATE) - 1;
    TIM2->CR2 = TIM_CR2_MMS_1; // TRGO = Update Event
    TIM2->CR1 = TIM_CR1_CEN;
}

int main(void) {
    ADC_DMA_Init();
    TIM2_Init();

    while (1) {
        // 在此处处理 adc_buffer[] 里的音频采样数据
    }
}

链接脚本 / 启动文件建议

启动文件(Keil 默认自带 startup_stm32f103x6.s

如果你用的是 Keil,可选择如下:

Device: STM32F103C6
Startup: startup_stm32f103x6.s

system_stm32f10x.c 示例(修改 SystemInit() 使用 72MHz)

void SystemInit(void) {
    // 选择 HSE + PLL 得到 72MHz 系统时钟
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY));

    RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9; // HSE x9 = 72MHz
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR & RCC_CR_PLLRDY));

    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}

文件支持

主要文件包含:

  • main.c
  • system_stm32f10x.c
  • startup_stm32f103x6.s
  • stm32f10x.h
  • Makefile 或 Keil 工程模板

增加双缓冲(Ping-Pong)机制 可以避免在音频采样时出现 DMA 覆盖还未处理的数据的问题,确保数据完整性,是音频应用中非常重要的一步。


目标

在采样过程中将缓冲区一分为二,交替处理:

  • DMA 仍然使用循环模式
  • 主循环检测 DMA 传输位置,判断何时处理哪一半的数据
  • 处理期间另一半仍在填充,不会丢数据

原理图

adc_buffer[BUFFER_SIZE]
↓       ↓
[0........BUFFER_SIZE/2-1] ← 半区 A(正在处理)
[BUFFER_SIZE/2....END]     ← 半区 B(正在采样)
        ↑                 ↑
    DMA CNDTR      +主循环判断

循环交替进行

实现步骤(适用于 STM32F103C6 + CMSIS)

1. 修改缓冲区大小为 2 倍半区

#define HALF_BUFFER_SIZE 256
#define BUFFER_SIZE (2 * HALF_BUFFER_SIZE)

volatile uint16_t adc_buffer[BUFFER_SIZE];

2. 增加 DMA 中断(Transfer Half & Full Complete)

void DMA1_Channel1_IRQHandler(void) {
    if (DMA1->ISR & DMA_ISR_HTIF1) {
        DMA1->IFCR = DMA_IFCR_CHTIF1;  // 清除中断标志
        // ✅ 半缓冲区完成(adc_buffer[0] ~ adc_buffer[255])
        process_audio_data(&adc_buffer[0], HALF_BUFFER_SIZE);
    }

    if (DMA1->ISR & DMA_ISR_TCIF1) {
        DMA1->IFCR = DMA_IFCR_CTCIF1;
        // ✅ 整缓冲区完成(adc_buffer[256] ~ adc_buffer[511])
        process_audio_data(&adc_buffer[HALF_BUFFER_SIZE], HALF_BUFFER_SIZE);
    }
}

3. 初始化 DMA 开启中断

void ADC_DMA_Init(void) {
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    DMA1_Channel1->CPAR  = (uint32_t)&ADC1->DR;
    DMA1_Channel1->CMAR  = (uint32_t)adc_buffer;
    DMA1_Channel1->CNDTR = BUFFER_SIZE;

    DMA1_Channel1->CCR = DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_PSIZE_0 |
                         DMA_CCR_MSIZE_0 | DMA_CCR_TCIE | DMA_CCR_HTIE |
                         DMA_CCR_EN;

    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

4. 示例处理函数(音频数据处理)

void process_audio_data(uint16_t *data, uint16_t len) {
    // 可选:滤波、峰值检测、FFT、串口输出等
    uint32_t sum = 0;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }
    uint16_t avg = sum / len;
    // 例如:输出平均电平
}

✅ 总结

特性 实现方式
双缓冲 adc_buffer 分为两半
触发机制 DMA1 HT / TC 中断触发数据处理
实时性与稳定性 避免主循环中判断 CNDTR,响应更快
可扩展 可用于串口上传、FFT 等实时处理

上面具备了稳定的 ADC 采样和双缓冲机制,现在我们可以在回调处理函数中 增加数字信号处理(如滤波和 FFT),让 STM32F103C6T6 实现简单的语音分析、频谱分析或前端滤波处理。


概念简介

✅ 常见信号处理任务

功能 作用
FIR/IIR 滤波 去噪、平滑、通带选择
FFT 频谱分析、语音识别特征提取
RMS/均值 音量检测

✅ 工具选择

  • 使用 CMSIS-DSP 库(ARM 官方优化)
  • 支持 Cortex-M3(STM32F103)✔️
  • process_audio_data() 中对采样数据处理

一、启用 CMSIS-DSP 库

添加以下文件:

  1. DSP 文件夹中的 arm_math.h + 源码(如 arm_cfft_f32.c
  2. 在编译器中定义宏:
#define ARM_MATH_CM3
  1. 链接 arm_cortexM3_math.lib 或编译 .c 源码

你可以从 [CMSIS-DSP GitHub](https://github.com/AR M-software/CMSIS_5/tree/develop/CMSIS/DSP) 下载。


️ 二、低通滤波器示例(FIR)

初始化低通 FIR 滤波器(采样率 8kHz,截止 1kHz)

#include "arm_math.h"

#define BLOCK_SIZE 256
#define NUM_TAPS   32

float32_t firStateF32[BLOCK_SIZE + NUM_TAPS - 1];
float32_t firCoeffs32[NUM_TAPS];  // 使用工具生成的系数
arm_fir_instance_f32 S;

void fir_filter_init(void) {
    // 举例:生成一个低通系数数组(可用 MATLAB / Python 设计)
    for (int i = 0; i < NUM_TAPS; i++) {
        firCoeffs32[i] = 0.03125f; // 简单平均低通(不理想,建议换更精细系数)
    }

    arm_fir_init_f32(&S, NUM_TAPS, firCoeffs32, firStateF32, BLOCK_SIZE);
}

在回调中使用:

void process_audio_data(uint16_t *data, uint16_t len) {
    float32_t input[BLOCK_SIZE];
    float32_t output[BLOCK_SIZE];

    // 转换为 float32
    for (int i = 0; i < len; i++) {
        input[i] = (float32_t)data[i];
    }

    // FIR 滤波
    arm_fir_f32(&S, input, output, len);

    // 可做后续处理,如峰值检测、FFT
}

⚡ 三、FFT 示例(频谱分析)

初始化 FFT(256 点)

arm_rfft_fast_instance_f32 fft_instance;

void fft_init(void) {
    arm_rfft_fast_init_f32(&fft_instance, 256);
}

在回调中进行频谱计算

float32_t input[256];
float32_t fft_out[256]; // 输出为幅度谱

void process_audio_data(uint16_t *data, uint16_t len) {
    for (int i = 0; i < len; i++) {
        input[i] = (float32_t)data[i] - 2048.0f; // 居中
    }

    arm_rfft_fast_f32(&fft_instance, input, input, 0); // FFT 就地
    arm_cmplx_mag_f32(input, fft_out, len / 2);         // 计算复数模(频谱幅度)

    // 此处 fft_out[i] 为第 i 个频率点的能量
}

采样率 8kHz,256 点 FFT ⇒ 分辨率 = 8000/256 ≈ 31.25 Hz


四、处理流程整合

int main(void) {
    SystemInit();
    fir_filter_init();  // 可选
    fft_init();         // 可选
    ADC_DMA_Init();
    TIM2_Init();

    while (1) {
        // 所有处理交由 DMA 中断完成
    }
}

使用 OLED 显示频谱图


系统组成结构图

[ 麦克风 ] → [ ADC + DMA ] → [ 双缓冲 Ping-Pong ]
                                   ↓
                             [ FFT + 频谱分析 ]
                                   ↓
                            [ OLED 显示频谱 ]

OLED 显示频谱(SSD1306 I2C)

1. OLED 连接引脚(以 I2C 为例)

  • SCL → PB6
  • SDA → PB7
  • 使用软件 I2C 或硬件 I2C(推荐用软件 I2C,自主可控)

2. OLED 驱动库

  • 使用轻量级 ssd1306 驱动库(开源、兼容 STM32)
  • 可选:u8g2Adafruit GFX(更复杂)

FFT 显示示意图(128 像素宽度)

  • 256 点 FFT → 128 个频段(取前半)
  • 每个频段的幅值转成高度(0~63 像素)
  • OLED 分辨率:128×64,x 为频率,y 为能量
Y ↑
64│    ▁▁▂▄▅▆█
   │   ████▇▇▅
 0└────────────────→ X(频率 0 ~ 4kHz)

✅ OLED 频谱绘制逻辑(代码示例)

extern uint8_t SSD1306_Buffer[1024];

void display_fft_on_oled(float32_t *fft_out, uint16_t bins) {
    ssd1306_Fill(Black);

    for (int i = 0; i < 128; i++) {
        uint16_t bin = i * (bins / 128); // 取样频率映射
        float val = fft_out[bin] / 500.0f; // 调整缩放比例
        if (val > 63) val = 63;

        for (int y = 0; y < (uint8_t)val; y++) {
            ssd1306_DrawPixel(i, 63 - y, White);
        }
    }

    ssd1306_UpdateScreen();
}

文件结构

Project/
├── main.c
├── adc_dma.c / .h           // 采样控制
├── fft_process.c / .h       // FFT + CMSIS-DSP
├── oled_display.c / .h      // SSD1306 驱动
├── i2c_soft.c / .h          // 软件 I2C 支持
├── arm_math.c / .h          // CMSIS DSP
└── ...

⚡ 性能建议(STM32F103C6T6 资源较小)

项目 推荐设置
FFT 点数 256 或 128 点
OLED 更新频率 每 50ms 更新一次即可
数据类型 float32(CMSIS-DSP),如需省资源可用 q15_t

可选增强功能

  • ✅ 动态频谱滚动(用 OLED 横向滚动)
  • ✅ 音量柱显示(电平检测)
  • ✅ 简易语音触发或节奏分析(峰值/过零率)
  • ✅ 串口输出 FFT 值 → 在 PC 上可视化

✅ 示例效果图(在 OLED 上看到)

音频频谱动态柱状图(实时刷新)
[▁▁▃▄▆███▆▅▄▃▂]

你可能感兴趣的:(stm32)