STM32嵌入式开发:基于STM32F103的智能语音识别系统

文章目录

    • 一、项目概述与硬件选型
      • [1.1 项目背景与应用场景](#1.1 项目背景与应用场景)
      • [1.2 硬件选型依据](#1.2 硬件选型依据)
        • [1.2.1 主控芯片:STM32F103C8T6](#1.2.1 主控芯片:STM32F103C8T6)
        • [1.2.2 语音识别模块:LD3320](#1.2.2 语音识别模块:LD3320)
        • [1.2.3 其他外设配件](#1.2.3 其他外设配件)
    • 二、系统硬件架构设计
      • [2.1 整体系统架构](#2.1 整体系统架构)
      • [2.2 硬件接线详解](#2.2 硬件接线详解)
        • [2.2.1 STM32与LD3320模块引脚对照表](#2.2.1 STM32与LD3320模块引脚对照表)
        • [2.2.2 串口调试与电源接线](#2.2.2 串口调试与电源接线)
        • [2.2.3 LED指示灯接线](#2.2.3 LED指示灯接线)
      • [2.3 硬件连接注意事项](#2.3 硬件连接注意事项)
    • 三、开发环境搭建与配置
      • [3.1 软件工具链介绍](#3.1 软件工具链介绍)
      • [3.2 STM32CubeMX工程创建](#3.2 STM32CubeMX工程创建)
        • [3.2.1 新建工程](#3.2.1 新建工程)
        • [3.2.2 SPI接口配置](#3.2.2 SPI接口配置)
        • [3.2.3 GPIO引脚配置](#3.2.3 GPIO引脚配置)
        • [3.2.4 USART串口配置](#3.2.4 USART串口配置)
        • [3.2.5 时钟树配置](#3.2.5 时钟树配置)
        • [3.2.6 代码生成配置](#3.2.6 代码生成配置)
      • [3.3 Keil工程配置](#3.3 Keil工程配置)
        • [3.3.1 工程目标配置](#3.3.1 工程目标配置)
        • [3.3.2 调试器配置](#3.3.2 调试器配置)
    • 四、LD3320驱动代码实现
      • [4.1 寄存器定义头文件](#4.1 寄存器定义头文件)
        • [4.1.1 创建 ld3320_reg.h 文件](#4.1.1 创建 ld3320_reg.h 文件)
      • [4.2 驱动功能声明头文件](#4.2 驱动功能声明头文件)
        • [4.2.1 创建 ld3320.h 文件](#4.2.1 创建 ld3320.h 文件)
      • [4.3 驱动实现源代码文件](#4.3 驱动实现源代码文件)
        • [4.3.1 创建 ld3320.c 文件](#4.3.1 创建 ld3320.c 文件)
    • 五、主程序逻辑实现
      • [5.1 主程序流程设计](#5.1 主程序流程设计)
      • [5.2 主程序代码实现](#5.2 主程序代码实现)
        • [5.2.1 创建 main.c 文件](#5.2.1 创建 main.c 文件)
      • [5.3 串口重定向配置](#5.3 串口重定向配置)
        • [5.3.1 添加串口重定向代码](#5.3.1 添加串口重定向代码)
    • 六、系统调试与验证
      • [6.1 编译与下载](#6.1 编译与下载)
      • [6.2 串口调试验证](#6.2 串口调试验证)
      • [6.3 语音识别功能测试](#6.3 语音识别功能测试)
      • [6.4 常见问题排查](#6.4 常见问题排查)
    • 七、系统优化与扩展
      • [7.1 灵敏度与识别率优化](#7.1 灵敏度与识别率优化)
      • [7.2 添加更多控制功能](#7.2 添加更多控制功能)
      • [7.3 多命令连续识别模式](#7.3 多命令连续识别模式)
      • [7.4 低功耗设计](#7.4 低功耗设计)
    • 八、项目总结

一、项目概述与硬件选型

1.1 项目背景与应用场景

在当今物联网快速发展的时代,语音交互作为一种自然、便捷的人机交互方式,已经广泛应用于智能家居、工业控制、医疗设备等各个领域。相比于基于云端的语音识别方案,离线语音识别具有响应速度快、无需网络连接、隐私安全性高、成本可控等显著优势,特别适合对实时性和可靠性要求较高的嵌入式应用场景。

本项目将基于STM32F103C8T6微控制器和LD3320语音识别芯片,构建一套完整的离线智能语音识别系统。该系统能够准确识别用户预设的语音指令,并根据识别结果执行相应的控制操作,如控制LED灯的开关、驱动继电器动作等。通过本项目的学习,读者将全面掌握嵌入式语音识别系统的硬件设计、软件驱动开发、系统集成调试等核心技术,为后续开发更复杂的智能语音交互设备奠定坚实基础。

1.2 硬件选型依据

1.2.1 主控芯片:STM32F103C8T6

STM32F103C8T6是ST公司推出的一款经典型32位ARM Cortex-M3内核微控制器,在嵌入式开发领域应用极为广泛。该芯片具有以下核心特性:工作主频可达72MHz,内置128KB Flash程序存储器和20KB SRAM数据存储器,提供丰富的外设资源包括通用定时器、高级定时器、SPI接口、I2C接口、USART串口、DMA控制器等。其采用LQFP-48封装,体积小巧,便于在各类嵌入式项目中部署。

选择STM32F103C8T6作为主控芯片的主要原因包括:成熟稳定的技术生态、丰富的开发资源支持、足够的运算性能处理语音识别算法、便捷的调试接口以及极具性价比的价格优势。对于初学者而言,该芯片的开发难度适中,既能深入理解底层硬件工作原理,又能在较短时间内完成项目开发。

1.2.2 语音识别模块:LD3320

LD3320是ICRoute公司推出的一款非特定人语音识别芯片,采用ASR(Automatic Speech Recognition)技术路线,无需训练即可识别用户语音。该芯片内置高性能语音处理DSP,支持中文、英文语音识别,最大可识别50条关键词语。芯片采用SPI接口与主控通信,集成度高,外围电路简单,非常适合嵌入式语音识别应用。

LD3320的核心技术特点包括:完全离线识别,无需网络支持;识别率高达95%以上;响应速度快,识别延迟小于200毫秒;支持多种语言模式;内置ADC和放大器,可直接连接麦克风;提供标准的SPI控制接口,便于与各类MCU集成。这些特性使得LD3320成为STM32嵌入式语音识别项目的理想选择。

1.2.3 其他外设配件

为构建完整的演示系统,还需要准备以下硬件配件:

USB转TTL串口模块:用于STM32的程序下载和串口调试日志输出。建议选用CH340G或CP2102芯片的模块,这些模块在Windows、Linux、Mac系统下都有成熟的驱动支持,兼容性良好。

LED指示灯:采用普通的直径3mm或5mm发光二极管,配置限流电阻(330Ω至1kΩ),用于直观展示语音识别结果的控制效果。

麦克风模块:LD3320模块通常自带麦克风接口,也可外接高灵敏度驻极体麦克风。建议选用带有音频放大电路的麦克风模块,可以获得更好的语音采集效果。

杜邦线和面包板:准备不同长度的公对母、公对公、母对母杜邦线若干,以及一块840孔或更大尺寸的面包板,便于硬件电路的搭建和调试。

二、系统硬件架构设计

2.1 整体系统架构

本智能语音识别系统采用主从架构设计,STM32F103C8T6作为主控芯片,负责系统逻辑控制、外设管理和语音识别结果处理;LD3320作为协处理器,负责麦克风音频信号采集、语音特征提取和关键词匹配。主控芯片通过SPI总线与语音模块进行双向数据通信,接收识别结果并执行相应控制动作。

系统架构图如下所示:
PC调试层
执行输出层
主控处理层
语音识别层
用户交互层
用户语音指令
麦克风
LD3320语音模块
SPI通信
STM32F103C8T6
USART调试
LED指示灯
继电器模块
蜂鸣器
PC上位机

2.2 硬件接线详解

正确连接硬件是项目成功的第一步。STM32F103C8T6与LD3320模块之间的接线需要严格按照以下引脚对应关系执行,其中SPI接口采用硬件SPI1,引脚复用关系固定,其他控制引脚可根据实际情况调整。

2.2.1 STM32与LD3320模块引脚对照表
STM32F103引脚 LD3320模块引脚 功能说明
PA5 SCK SPI时钟线,主设备输出
PA6 MISO SPI数据输入,主设备接收
PA7 MOSI SPI数据输出,主设备发送
PA4 NSS SPI片选信号,低电平有效
PB0 RST 模块复位引脚,低电平复位
PB1 IRQ 中断请求引脚,识别完成时触发
GND GND 电源地线
3.3V VCC 3.3V电源输入
2.2.2 串口调试与电源接线
STM32F103引脚 USB-TTL模块引脚 功能说明
PA9 (TX) RX 串口发送数据
PA10 (TX) TX 串口接收数据
GND GND 共地
3.3V 3.3V或5V 根据模块规格选择
2.2.3 LED指示灯接线
STM32F103引脚 LED电路 说明
PC13 串联330Ω限流电阻至LED正极 LED负极接地

2.3 硬件连接注意事项

在进行硬件连接时,需要特别注意以下事项,以确保系统稳定可靠工作:

电源稳定性:LD3320模块对电源要求较高,建议使用独立的3.3V稳压电源,避免与主控芯片共用电源导致供电不足。电源纹波应控制在50mV以内,必要时可在电源引脚添加100nF和10μF滤波电容。

SPI通信线长度:SPI通信线不宜过长,建议控制在15厘米以内。如果需要更远的通信距离,应降低通信速率或在电路板上添加终端电阻。

麦克风布线:麦克风信号线应远离电源线和高速信号线,避免电磁干扰。麦克风模块应尽量靠近LD3320模块,减少信号衰减。

静电防护:在触碰电路板前,应先触摸金属物体释放人体静电,避免静电损坏芯片。焊接时注意温度控制,芯片焊接温度不应超过260℃。

三、开发环境搭建与配置

3.1 软件工具链介绍

本项目采用的软件开发工具链包括:STM32CubeMX用于芯片配置和代码生成、Keil MDK-ARM作为集成开发环境、ST-Link调试器用于程序烧录和调试。这套工具链是STM32官方推荐的的标准开发方案,具有配置简便、调试功能强大、生态完善等优点。

STM32CubeMX:ST官方提供的图形化芯片配置工具,支持所有STM32系列芯片。通过图形界面可以直观地配置时钟系统、外设参数、引脚分配、GPIO模式等,配置完成后可自动生成HAL库初始化代码大大简化开发工作。

Keil MDK-ARM:业界最流行的ARM Cortex-M系列微控制器集成开发环境,提供项目管理、代码编辑、编译链接、调试仿真等完整功能。Keil支持ST-Link、J-Link等多种调试器,提供丰富的调试视图和断点功能。

ST-Link调试器:ST官方推出的调试编程工具,支持SWD接口调试和STM8、STM32系列芯片的在线编程。建议选用ST-Link V2或V3版本,兼容性好且价格实惠。

3.2 STM32CubeMX工程创建

3.2.1 新建工程

打开STM32CubeMX软件,点击"New Project"按钮进入芯片选择界面。在"Commercial Part Number"搜索框中输入"STM32F103C8",在下拉列表中选择"STM32F103C8Tx"芯片,点击"Start Project"按钮创建新工程。

工程创建后,首先进入"System Core"分类下的"RCC"配置页面。将"HSE Crystal/Ceramic Resonator"设置为"Crystal/Ceramic Resonator",启用外部高速时钟源。同样将"LSE Crystal/Ceramic Resonator"设置为"Crystal/Ceramic Resonator",启用外部低速时钟源。这一步是为后续的精确时钟配置做准备。

3.2.2 SPI接口配置

进入"Connectivity"分类下的"SPI1"配置页面。将"Mode"设置为"Full Duplex Master",即全双工主模式。在"Configuration"参数区域进行以下设置:

Data Size:设置为"8 Bits",即每次传输8位数据。

Prescaler:设置为"256",将SPI时钟分频至较低速率(72MHz/256≈281kHz),以确保与LD3320模块通信的可靠性。在调试稳定后可以适当提高分频系数以提升通信速度。

Clock Polarity (CPOL):设置为"High",空闲时时钟线保持高电平。

Clock Phase (CPHA):设置为"2nd Edge",在时钟的第二个边沿进行数据采样。

First Bit:设置为"MSB",最高位优先传输。

这些参数设置需要与LD3320芯片的SPI时序要求匹配,确保数据通信的正确性。

3.2.3 GPIO引脚配置

在"System Core"分类下的"GPIO"配置页面中,对各控制引脚进行如下配置:

PA4(NSS片选):配置为"Output Push Pull",初始电平设置为"HIGH",输出速度设置为"Low"。

PB0(RST复位):配置为"Output Push Pull",初始电平设置为"HIGH",输出速度设置为"Low"。

PB1(IRQ中断):配置为"Input Pull Up",启用内部上拉电阻。

PA9(USART1 TX):在"Connectivity"分类下的"USART1"中已自动配置为"Serial Transmitter"。

PC13(LED):配置为"Output Push Pull",初始电平设置为"HIGH"(LED初始关闭),输出速度设置为"Low"。

3.2.4 USART串口配置

在"Connectivity"分类下选择"USART1",将"Mode"设置为"Asynchronous",启用异步串口模式。在"Configuration"的"Parameter Settings"中进行以下设置:

Baud Rate:设置为"115200",即每秒传输115200位。

Word Length:设置为"8 Bits"。

Stop Bits:设置为"1 Stop Bit"。

Parity:设置为"No Parity",无奇偶校验。

Data Direction:设置为"Receive and Transmit",即全双工模式。

在"NVIC Settings"中勾选"USART1 global interrupt",使能串口中断,用于接收调试命令。

3.2.5 时钟树配置

点击左侧"Clock Configuration"标签进入时钟配置页面。将"PLL Source Mux"选择为"HSE",使用外部高速时钟源。将"System Clock Mux"选择为"PLLCLK",使用PLL锁相环输出作为系统时钟。将"HCLK"设置为"72 MHz",对应CPU最高工作频率。

在"APB1 Prescaler"设置为"2",APB1外设时钟为36MHz;"APB2 Prescaler"设置为"1",APB2外设时钟为72MHz。完成时钟配置后,系统将自动计算各外设时钟频率并在界面显示。

3.2.6 代码生成配置

完成所有外设配置后,点击"Project Manager"标签进行工程设置。在"Project"页面中填写工程名称(如"STM32_Voice_Recognition"),选择工程保存路径。在"Toolchain/IDE"下拉菜单中选择"MDK-ARM",版本选择"V5"。

在"Code Generator"页面中,勾选"Generate peripheral initialization as a pair of '.c/.h' files per peripheral",为每个外设生成独立的初始化文件,便于代码管理和维护。勾选"Generate files with 'USER CODE' sections to maintain content when regenerating",保留用户代码区域,避免重新生成代码时覆盖用户编写的业务逻辑。

完成所有配置后,点击"GENERATE CODE"按钮生成工程代码。代码生成完成后,会弹出工程文件夹路径,点击"Open Project"即可在Keil中打开生成的工程。

3.3 Keil工程配置

3.3.1 工程目标配置

在Keil中打开生成的工程后,首先需要配置工程目标选项。点击菜单栏"Project"→"Options for Target..."打开目标选项对话框。在"Target"页面中,确认"Xtal(MHz)"设置为"8.0",与外部晶振频率匹配。

在"Output"页面中,勾选"Create HEX File",用于生成可下载的十六进制文件。可根据需要勾选"Listing"页面的相关选项,生成汇编列表和内存映射文件,便于调试分析。

3.3.2 调试器配置

在"Debug"页面中,选择使用的调试器类型。如果使用ST-Link,应在"Use"下拉框中选择"ST-Link Debugger"。点击右侧的"Settings"按钮进入调试器设置界面。

在"Debug"设置页的"Port"中选择"SW"模式,SWD接口只需两根线(SWDIO和SWCLK)即可完成调试,比JTAG接口更节省引脚。在"Max Clock"中选择"4MHz"或较低的时钟频率,确保调试通信稳定。

在"Flash Download"页面中,确认"Programming Algorithm"已添加STM32F103C8的下载算法。如果列表为空,需要点击"Add"按钮添加。勾选"Reset and Run",下载完成后自动复位并运行程序。

完成以上配置后,点击"OK"保存设置。编译工程(按F7键),确认无编译错误后即可进行程序下载和调试。

四、LD3320驱动代码实现

4.1 寄存器定义头文件

LD3320芯片内部集成了多个功能寄存器,通过SPI接口可以读写这些寄存器来实现不同的功能。正确理解和使用这些寄存器是开发LD3320驱动的关键。本节将详细讲解各寄存器的功能和地址定义。

4.1.1 创建 ld3320_reg.h 文件
c 复制代码
Creating file: ld3320_reg.h
c 复制代码
/**
 * LD3320 语音识别芯片寄存器定义头文件
 * 文件名: ld3320_reg.h
 * 描述: 包含LD3320芯片所有内部寄存器的地址定义和常用命令宏定义
 * 芯片特性: 非特定人语音识别,支持50条关键词,内置ADC和DSP
 */

#ifndef __LD3320_REG_H
#define __LD3320_REG_H

#ifdef __cplusplus
extern "C" {
#endif

/* =====================================================
 * LD3320 寄存器地址定义
 * 寄存器分为以下几类:
 * 1. 测试寄存器 (0x00-0x0F)
 * 2. ASR识别控制寄存器 (0x10-0x1F)
 * 3. 模拟前端控制寄存器 (0x20-0x2F)
 * 4. DSP内部参数寄存器 (0x30-0x3F)
 * 5. 结果数据寄存器 (0x40-0x4F)
 * ===================================================== */

/* 测试和通用寄存器 */
#define LD_REG_TEST_MODE         0x00    // 测试模式控制寄存器
#define LD_REG_CHIP_VERSION      0x01    // 芯片版本号寄存器(只读)
#define LD_REG_INT_STATUS        0x02    // 中断状态寄存器(只读)
#define LD_REG_INT_CLEAR         0x03    // 中断清除寄存器(写1清除)
#define LD_REG_GPIO_CONFIG       0x04    // GPIO配置寄存器

/* ASR 语音识别核心寄存器 */
#define LD_REG_ASR_CTL           0x10    // ASR控制寄存器
#define LD_REG_ASR_STATUS        0x11    // ASR状态寄存器(只读)
#define LD_REG_ASR_RESULT        0x12    // 识别结果寄存器(只读)
#define LD_REG_ASR_SENM          0x13    // 灵敏度设置寄存器
#define LD_REG_ASR_MODE          0x14    // ASR模式寄存器

/* DMA和数据传输寄存器 */
#define LD_REG_DMA_START         0x20    // DMA传输起始地址高字节
#define LD_REG_DMA_LENGTH        0x21    // DMA传输长度
#define LD_REG_FIFO_DATA         0x22    // FIFO数据寄存器

/* 模拟前端控制寄存器 */
#define LD_REG_MIC_BIAS          0x30    // 麦克风偏置电压控制
#define LD_REG_MIC_GAIN          0x31    // 麦克风增益控制
#define LD_REG_ADC_CONFIG        0x32    // ADC配置寄存器

/* 语音参数寄存器 */
#define LD_REG_VAD_THRESHOLD     0x40    // 语音活动检测阈值
#define LD_REG_NOISE_THRESHOLD   0x41    // 噪声阈值设置

/* =====================================================
 * LD3320 命令操作码定义
 * 通过SPI发送命令码来实现不同的操作
 * ===================================================== */

/* SPI命令码 - 用于SPI通信时的命令字节 */
#define LD_CMD_READ_REG          0x00    // 读寄存器命令
#define LD_CMD_WRITE_REG         0x04    // 写寄存器命令
#define LD_CMD_READ_FIFO         0x06    // 读FIFO数据
#define LD_CMD_WRITE_FIFO        0x08    // 写FIFO数据

/* ASR控制命令 */
#define LD_CMD_ASR_START         0x01    // 启动ASR识别
#define LD_CMD_ASR_STOP          0x02    // 停止ASR识别
#define LD_CMD_ASR_RESET         0x03    // 软件复位ASR模块

/* 初始化命令 */
#define LD_CMD_INIT_ASR          0x10    // 初始化ASR引擎
#define LD_CMD_INIT_CHIP        0x11    // 初始化芯片

/* =====================================================
 * ASR识别状态定义
 * 用于读取ASR_STATUS寄存器判断当前识别状态
 * ===================================================== */

#define LD_ASR_STATUS_IDLE       0x00    // ASR空闲状态
#define LD_ASR_STATUS_PROCESSING 0x01    // 正在识别处理
#define LD_ASR_STATUS_FOUND      0x02    // 识别到关键词
#define LD_ASR_STATUS_NOT_FOUND  0x03    // 未识别到关键词

/* =====================================================
 * 中断状态位定义
 * 用于读取INT_STATUS寄存器判断中断类型
 * ===================================================== */

#define LD_INT_ASR_FOUND         0x10    // 识别成功中断
#define LD_INT_ASR_TIMEOUT       0x20    // 识别超时中断
#define LD_INT_ASR_ERROR         0x40    // 识别错误中断

/* =====================================================
 * ASR模式配置宏
 * 用于配置ASR_MODE寄存器的参数
 * ===================================================== */

/* 识别模式设置 */
#define LD_ASR_MODE_RESET        0x00    // 复位模式
#define LD_ASR_MODE_KEYWORD      0x01    // 关键词识别模式(默认)
#define LD_ASR_MODE_TRIGGER      0x02    // 触发词模式
#define LD_ASR_MODE_CONTINUOUS   0x03    // 连续识别模式

/* 灵敏度等级设置 (值越小越灵敏) */
#define LD_ASR_SENM_HIGH         0x00    // 高灵敏度
#define LD_ASR_SENM_MEDIUM       0x01    // 中等灵敏度(默认)
#define LD_ASR_SENM_LOW          0x02    // 低灵敏度

/* =====================================================
 * 关键词数量定义
 * LD3320最大支持50条关键词
 * ===================================================== */

#define LD_MAX_KEYWORD_NUM       50      // 最大关键词数量

/* =====================================================
 * 常用延迟时间定义 (单位: 毫秒)
 * 根据芯片手册推荐值设定
 * ===================================================== */

#define LD_DELAY_POWER_ON        50      // 上电后等待时间
#define LD_DELAY_RESET           10      // 复位引脚脉冲宽度
#define LD_DELAY_INIT            100     // 初始化完成后等待时间
#define LD_DELAY_ASR_START       200     // 启动ASR后等待时间
#define LD_DELAY_IRQ_CHECK       10      // 中断检查间隔时间

#ifdef __cplusplus
}
#endif

#endif /* __LD3320_REG_H */

4.2 驱动功能声明头文件

在编写具体的驱动实现之前,需要先定义驱动函数声明、宏定义和类型声明。这个头文件将作为驱动层与应用层之间的接口,定义清晰规范的函数接口有助于代码的模块化和可维护性。

4.2.1 创建 ld3320.h 文件
c 复制代码
Creating file: ld3320.h
c 复制代码
/**
 * LD3320 语音识别驱动头文件
 * 文件名: ld3320.h
 * 描述: LD3320芯片驱动的功能声明和接口定义
 * 依赖: ld3320_reg.h, stm32f1xx_hal.h
 */

#ifndef __LD3320_H
#define __LD3320_H

#ifdef __cplusplus
extern "C" {
#endif

/* =====================================================
 * 头文件包含
 * ===================================================== */

#include "ld3320_reg.h"
#include "main.h"
#include <stdint.h>
#include <stdbool.h>

/* =====================================================
 * 硬件接口定义
 * 根据实际硬件连接修改以下宏定义
 * ===================================================== */

/* SPI接口选择 - 使用SPI1 */
#define LD3320_SPI               hspi1

/* GPIO引脚定义 - 根据实际接线修改 */
#define LD3320_NSS_PORT          GPIOA
#define LD3320_NSS_PIN           GPIO_PIN_4

#define LD3320_RST_PORT          GPIOB
#define LD3320_RST_PIN           GPIO_PIN_0

#define LD3320_IRQ_PORT          GPIOB
#define LD3320_IRQ_PIN           GPIO_PIN_1

/* 引脚操作宏定义 */
#define LD3320_NSS_LOW()         HAL_GPIO_WritePin(LD3320_NSS_PORT, LD3320_NSS_PIN, GPIO_PIN_RESET)
#define LD3320_NSS_HIGH()        HAL_GPIO_WritePin(LD3320_NSS_PORT, LD3320_NSS_PIN, GPIO_PIN_SET)

#define LD3320_RST_LOW()         HAL_GPIO_WritePin(LD3320_RST_PORT, LD3320_RST_PIN, GPIO_PIN_RESET)
#define LD3320_RST_HIGH()        HAL_GPIO_WritePin(LD3320_RST_PORT, LD3320_RST_PIN, GPIO_PIN_SET)

#define LD3320_IRQ_READ()        HAL_GPIO_ReadPin(LD3320_IRQ_PORT, LD3320_IRQ_PIN)

/* =====================================================
 * 函数返回状态码定义
 * ===================================================== */

typedef enum {
    LD_OK = 0,                // 操作成功
    LD_ERROR = 1,             // 操作失败
    LD_ERROR_TIMEOUT = 2,     // 操作超时
    LD_ERROR_PARAM = 3,        // 参数错误
    LD_ERROR_NOINIT = 4,      // 未初始化
    LD_ERROR_ASR_RUNNING = 5  // ASR正在运行
} LD_StatusTypeDef;

/* =====================================================
 * 语音识别结果类型定义
 * ===================================================== */

typedef enum {
    LD_ASR_RESULT_NONE = 0,    // 无识别结果
    LD_ASR_RESULT_SUCCESS,    // 识别成功
    LD_ASR_RESULT_TIMEOUT,    // 识别超时
    LD_ASR_RESULT_ERROR       // 识别错误
} LD_ASRResultTypeDef;

/* =====================================================
 * 关键词结构体定义
 * ===================================================== */

typedef struct {
    uint8_t index;            // 关键词索引号 (1-50)
    char *keyword;            // 关键词字符串 (拼音格式)
    uint8_t keyword_length;   // 关键词字符串长度
} LD_KeywordTypeDef;

/* =====================================================
 * ASR识别结果回调函数类型定义
 * ===================================================== */

typedef void (*LD_ASRCallbackTypeDef)(uint8_t result_index);

/* =====================================================
 * LD3320驱动函数声明
 * ===================================================== */

/**
 * @brief  初始化LD3320语音识别模块
 * @param  None
 * @retval LD_OK: 初始化成功
 *         LD_ERROR: 初始化失败
 */
LD_StatusTypeDef LD3320_Init(void);

/**
 * @brief  复位LD3320模块
 * @param  None
 * @retval None
 */
void LD3320_Reset(void);

/**
 * @brief  通过SPI读取一个寄存器的值
 * @param  reg_addr: 寄存器地址
 * @retval 读取到的寄存器值
 */
uint8_t LD3320_ReadReg(uint8_t reg_addr);

/**
 * @brief  通过SPI写入一个寄存器的值
 * @param  reg_addr: 寄存器地址
 * @param  reg_value: 要写入的值
 * @retval None
 */
void LD3320_WriteReg(uint8_t reg_addr, uint8_t reg_value);

/**
 * @brief  启动语音识别功能
 * @param  None
 * @retval LD_OK: 启动成功
 *         LD_ERROR: 启动失败
 */
LD_StatusTypeDef LD3320_ASR_Start(void);

/**
 * @brief  添加关键词到识别列表
 * @param  keyword: 关键词结构体指针
 * @retval LD_OK: 添加成功
 *         LD_ERROR: 添加失败
 */
LD_StatusTypeDef LD3320_AddKeyword(LD_KeywordTypeDef *keyword);

/**
 * @brief  清除所有关键词
 * @param  None
 * @retval None
 */
void LD3320_ClearKeywords(void);

/**
 * @brief  获取识别结果
 * @param  None
 * @retval 识别到的关键词索引号, 0表示无结果
 */
uint8_t LD3320_GetResult(void);

/**
 * @brief  检查是否有识别中断发生
 * @param  None
 * @retval true: 有中断发生
 *         false: 无中断
 */
bool LD3320_CheckIRQ(void);

/**
 * @brief  处理识别中断
 * @param  None
 * @retval 识别结果类型
 */
LD_ASRResultTypeDef LD3320_ProcessIRQ(void);

/**
 * @brief  软件复位语音识别引擎
 * @param  None
 * @retval None
 */
void LD3320_ASR_Reset(void);

/**
 * @brief  配置ASR识别灵敏度
 * @param  sensitivity: 灵敏度等级 (0-2)
 * @retval None
 */
void LD3320_SetSensitivity(uint8_t sensitivity);

/**
 * @brief  获取芯片版本号
 * @param  None
 * @retval 版本号
 */
uint8_t LD3320_GetChipVersion(void);

/**
 * @brief  打印调试信息到串口
 * @param  format: 格式化字符串
 * @param  ...: 可变参数
 * @retval None
 */
void LD3320_Debug(const char *format, ...);

#ifdef __cplusplus
}
#endif

#endif /* __LD3320_H */

4.3 驱动实现源代码文件

驱动实现文件包含了与LD3320芯片通信的所有底层函数,包括SPI读写操作、寄存器访问、语音识别引擎初始化、关键词添加等核心功能。这部分代码是整个系统的技术核心,需要严格按照芯片手册的时序要求编写。

4.3.1 创建 ld3320.c 文件
c 复制代码
Creating file: ld3320.c
c 复制代码
/**
 * LD3320 语音识别驱动实现文件
 * 文件名: ld3320.c
 * 描述: LD3320芯片的SPI通信驱动和ASR功能实现
 * 依赖: ld3320.h, ld3320_reg.h, main.h
 */

#include "ld3320.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

/* =====================================================
 * 私有变量定义
 * ===================================================== */

/* SPI数据传输缓冲区 */
static uint8_t spi_tx_buf[4];
static uint8_t spi_rx_buf[4];

/* 关键词列表缓冲区 (最大50条) */
static LD_KeywordTypeDef keyword_list[LD_MAX_KEYWORD_NUM];
static uint8_t keyword_count = 0;

/* ASR运行状态标志 */
static volatile bool asr_running = false;

/* 调试使能标志 - 设为true可在串口输出调试信息 */
static const bool debug_enable = true;

/* =====================================================
 * 私有函数声明
 * ===================================================== */

static void LD3320_Delay_ms(uint32_t ms);
static void LD3320_CS_Select(void);
static void LD3320_CS_Deselect(void);
static uint8_t LD3320_SPI_Transfer(uint8_t data);
static LD_StatusTypeDef LD3320_WaitForInit(uint32_t timeout);

/* =====================================================
 * 公共函数实现
 * ===================================================== */

/**
 * @brief  初始化LD3320语音识别模块
 * @param  None
 * @retval LD_OK: 初始化成功
 *         LD_ERROR: 初始化失败
 * @note   此函数完成以下步骤:
 *         1. 硬件复位模块
 *         2. 等待芯片上电稳定
 *         3. 配置ASR引擎参数
 *         4. 验证芯片通信
 */
LD_StatusTypeDef LD3320_Init(void)
{
    uint8_t chip_version;
    
    LD3320_Debug("LD3320: Starting initialization...\r\n");
    
    /* 步骤1: 硬件复位LD3320模块 */
    LD3320_Reset();
    
    /* 步骤2: 等待芯片上电稳定 */
    LD3320_Delay_ms(LD_DELAY_POWER_ON);
    
    /* 步骤3: 读取芯片版本号验证通信 */
    chip_version = LD3320_GetChipVersion();
    LD3320_Debug("LD3320: Chip version = 0x%02X\r\n", chip_version);
    
    if (chip_version == 0 || chip_version == 0xFF) {
        LD3320_Debug("LD3320: ERROR - Chip communication failed!\r\n");
        return LD_ERROR;
    }
    
    /* 步骤4: 执行ASR引擎初始化序列 */
    if (LD3320_WaitForInit(1000) != LD_OK) {
        LD3320_Debug("LD3320: ERROR - ASR init timeout!\r\n");
        return LD_ERROR;
    }
    
    /* 步骤5: 配置麦克风增益和偏置 */
    LD3320_WriteReg(LD_REG_MIC_BIAS, 0x20);    // 麦克风偏置电压
    LD3320_WriteReg(LD_REG_MIC_GAIN, 0x40);    // 麦克风增益设置
    
    /* 步骤6: 配置VAD(语音活动检测)参数 */
    LD3320_WriteReg(LD_REG_VAD_THRESHOLD, 0x55);   // 语音检测阈值
    LD3320_WriteReg(LD_REG_NOISE_THRESHOLD, 0x30); // 噪声阈值
    
    /* 步骤7: 设置默认灵敏度为中等 */
    LD3320_SetSensitivity(LD_ASR_SENM_MEDIUM);
    
    /* 步骤8: 清除关键词列表 */
    LD3320_ClearKeywords();
    
    LD3320_Debug("LD3320: Initialization completed successfully!\r\n");
    
    return LD_OK;
}

/**
 * @brief  复位LD3320模块
 * @param  None
 * @retval None
 * @note   通过RST引脚产生至少10ms的低电平脉冲实现硬件复位
 */
void LD3320_Reset(void)
{
    LD3320_Debug("LD3320: Performing hardware reset...\r\n");
    
    /* 拉低RST引脚 */
    LD3320_RST_LOW();
    
    /* 延迟至少10ms */
    LD3320_Delay_ms(LD_DELAY_RESET);
    
    /* 拉高RST引脚 */
    LD3320_RST_HIGH();
    
    /* 等待复位完成 */
    LD3320_Delay_ms(50);
}

/**
 * @brief  通过SPI读取一个寄存器的值
 * @param  reg_addr: 寄存器地址 (0x00-0x4F)
 * @retval 读取到的寄存器值 (0x00-0xFF)
 * @note   SPI读寄存器时序:
 *         1. CS低电平
 *         2. 发送读命令(0x00) + 地址字节
 *         3. 读取数据字节
 *         4. CS高电平
 */
uint8_t LD3320_ReadReg(uint8_t reg_addr)
{
    uint8_t read_value;
    
    /* 确保SPI处于空闲状态 */
    while (__HAL_SPI_GET_FLAG(&LD3320_SPI, SPI_FLAG_BUSY));
    
    /* 片选拉低 */
    LD3320_CS_Select();
    
    /* 发送读命令: 0x00 + 寄存器地址 */
    spi_tx_buf[0] = LD_CMD_READ_REG;
    spi_tx_buf[1] = reg_addr;
    
    /* 同时发送命令和地址 */
    HAL_SPI_TransmitReceive(&LD3320_SPI, spi_tx_buf, spi_rx_buf, 2, 100);
    
    /* 读取寄存器值 */
    HAL_SPI_TransmitReceive(&LD3320_SPI, spi_tx_buf, &read_value, 1, 100);
    
    /* 片选拉高 */
    LD3320_CS_Deselect();
    
    return read_value;
}

/**
 * @brief  通过SPI写入一个寄存器的值
 * @param  reg_addr: 寄存器地址 (0x00-0x4F)
 * @param  reg_value: 要写入的值 (0x00-0xFF)
 * @retval None
 * @note   SPI写寄存器时序:
 *         1. CS低电平
 *         2. 发送写命令(0x04) + 地址字节
 *         3. 发送数据字节
 *         4. CS高电平
 */
void LD3320_WriteReg(uint8_t reg_addr, uint8_t reg_value)
{
    /* 确保SPI处于空闲状态 */
    while (__HAL_SPI_GET_FLAG(&LD3320_SPI, SPI_FLAG_BUSY));
    
    /* 片选拉低 */
    LD3320_CS_Select();
    
    /* 发送写命令: 0x04 + 寄存器地址 */
    spi_tx_buf[0] = LD_CMD_WRITE_REG;
    spi_tx_buf[1] = reg_addr;
    
    HAL_SPI_Transmit(&LD3320_SPI, spi_tx_buf, 2, 100);
    
    /* 写入数据 */
    HAL_SPI_Transmit(&LD3320_SPI, &reg_value, 1, 100);
    
    /* 片选拉高 */
    LD3320_CS_Deselect();
}

/**
 * @brief  启动语音识别功能
 * @param  None
 * @retval LD_OK: 启动成功
 *         LD_ERROR: 启动失败
 * @note   启动ASR后,模块开始监听麦克风输入
 *         当检测到语音输入时自动进行识别
 */
LD_StatusTypeDef LD3320_ASR_Start(void)
{
    uint8_t asr_status;
    
    /* 检查是否已有识别任务在运行 */
    if (asr_running) {
        LD3320_Debug("LD3320: ASR is already running!\r\n");
        return LD_ERROR_ASR_RUNNING;
    }
    
    LD3320_Debug("LD3320: Starting ASR engine...\r\n");
    
    /* 清除之前的中断状态 */
    LD3320_WriteReg(LD_REG_INT_CLEAR, 0xFF);
    
    /* 设置ASR工作模式为关键词识别模式 */
    LD3320_WriteReg(LD_REG_ASR_MODE, LD_ASR_MODE_KEYWORD);
    
    /* 启动ASR识别 */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_ASR_START);
    
    /* 等待ASR启动完成 */
    LD3320_Delay_ms(LD_DELAY_ASR_START);
    
    /* 检查ASR状态 */
    asr_status = LD3320_ReadReg(LD_REG_ASR_STATUS);
    LD3320_Debug("LD3320: ASR status = 0x%02X\r\n", asr_status);
    
    /* 标记ASR为运行状态 */
    asr_running = true;
    
    LD3320_Debug("LD3320: ASR engine started successfully!\r\n");
    
    return LD_OK;
}

/**
 * @brief  添加关键词到识别列表
 * @param  keyword: 关键词结构体指针
 * @retval LD_OK: 添加成功
 *         LD_ERROR: 添加失败(列表已满)
 * @note   关键词必须以拼音格式添加,如"kai deng"表示"开灯"
 *         最多支持添加50条关键词
 */
LD_StatusTypeDef LD3320_AddKeyword(LD_KeywordTypeDef *keyword)
{
    uint8_t i;
    uint8_t *keyword_data;
    uint8_t keyword_len;
    
    /* 检查参数有效性 */
    if (keyword == NULL || keyword->keyword == NULL) {
        LD3320_Debug("LD3320: ERROR - Invalid keyword parameter!\r\n");
        return LD_ERROR_PARAM;
    }
    
    /* 检查关键词列表是否已满 */
    if (keyword_count >= LD_MAX_KEYWORD_NUM) {
        LD3320_Debug("LD3320: ERROR - Keyword list is full!\r\n");
        return LD_ERROR;
    }
    
    /* 获取关键词字符串和长度 */
    keyword_len = strlen(keyword->keyword);
    if (keyword_len == 0 || keyword_len > 30) {
        LD3320_Debug("LD3320: ERROR - Invalid keyword length!\r\n");
        return LD_ERROR_PARAM;
    }
    
    LD3320_Debug("LD3320: Adding keyword[%d]: %s\r\n", keyword->index, keyword->keyword);
    
    /* 将关键词写入LD3320的FIFO区域 */
    /* 写入格式: 索引号(1字节) + 关键词长度(1字节) + 关键词内容(n字节) */
    
    /* 准备写入地址: 0x80 + 索引号 */
    LD3320_WriteReg(0xC8, keyword->index);  /* 关键词索引 */
    LD3320_WriteReg(0xC9, keyword_len);     /* 关键词长度 */
    
    /* 写入关键词内容到0xCA开始的地址 */
    keyword_data = (uint8_t *)keyword->keyword;
    for (i = 0; i < keyword_len; i++) {
        LD3320_WriteReg(0xCA + i, keyword_data[i]);
    }
    
    /* 保存到本地关键词列表 */
    memcpy(&keyword_list[keyword_count], keyword, sizeof(LD_KeywordTypeDef));
    keyword_count++;
    
    /* 触发ASR引擎重新加载关键词列表 */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_INIT_ASR);
    LD3320_Delay_ms(100);
    
    LD3320_Debug("LD3320: Keyword added successfully, total: %d\r\n", keyword_count);
    
    return LD_OK;
}

/**
 * @brief  清除所有关键词
 * @param  None
 * @retval None
 */
void LD3320_ClearKeywords(void)
{
    uint8_t i;
    
    LD3320_Debug("LD3320: Clearing all keywords...\r\n");
    
    /* 清除本地关键词列表 */
    memset(keyword_list, 0, sizeof(keyword_list));
    keyword_count = 0;
    
    /* 清除LD3320内部关键词区域 */
    for (i = 1; i <= 50; i++) {
        LD3320_WriteReg(0xC8, i);   /* 索引 */
        LD3320_WriteReg(0xC9, 0);   /* 长度为0表示删除 */
    }
    
    /* 触发ASR引擎刷新 */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_INIT_ASR);
    LD3320_Delay_ms(100);
    
    LD3320_Debug("LD3320: All keywords cleared\r\n");
}

/**
 * @brief  获取识别结果
 * @param  None
 * @retval 识别到的关键词索引号, 0表示无结果或识别失败
 * @note   应在检测到IRQ中断后调用此函数获取识别结果
 */
uint8_t LD3320_GetResult(void)
{
    uint8_t result;
    
    /* 读取识别结果寄存器 */
    result = LD3320_ReadReg(LD_REG_ASR_RESULT);
    
    /* 结果为0表示无有效结果 */
    if (result == 0) {
        LD3320_Debug("LD3320: No valid recognition result\r\n");
        return 0;
    }
    
    LD3320_Debug("LD3320: Recognition result index = %d\r\n", result);
    
    /* 返回结果索引号(1-based) */
    return result;
}

/**
 * @brief  检查是否有识别中断发生
 * @param  None
 * @retval true: 有中断发生
 *         false: 无中断
 * @note   通过读取IRQ引脚电平判断,低电平表示有中断
 */
bool LD3320_CheckIRQ(void)
{
    /* IRQ引脚低电平表示有中断信号 */
    if (LD3320_IRQ_READ() == GPIO_PIN_RESET) {
        return true;
    }
    return false;
}

/**
 * @brief  处理识别中断
 * @param  None
 * @retval 识别结果类型
 * @note   中断处理流程:
 *         1. 读取中断状态寄存器
 *         2. 根据中断类型执行相应操作
 *         3. 清除中断标志
 *         4. 返回识别结果
 */
LD_ASRResultTypeDef LD3320_ProcessIRQ(void)
{
    uint8_t int_status;
    uint8_t result;
    LD_ASRResultTypeDef ret = LD_ASR_RESULT_NONE;
    
    /* 读取中断状态 */
    int_status = LD3320_ReadReg(LD_REG_INT_STATUS);
    
    LD3320_Debug("LD3320: IRQ status = 0x%02X\r\n", int_status);
    
    /* 判断中断类型 */
    if (int_status & LD_INT_ASR_FOUND) {
        /* 识别成功中断 - 获取识别结果 */
        result = LD3320_GetResult();
        
        if (result > 0 && result <= keyword_count) {
            LD3320_Debug("LD3320: Recognition SUCCESS! Keyword: %s\r\n", 
                        keyword_list[result - 1].keyword);
            ret = LD_ASR_RESULT_SUCCESS;
        } else {
            LD3320_Debug("LD3320: Recognition result invalid\r\n");
            ret = LD_ASR_RESULT_ERROR;
        }
        
        /* 标记ASR结束 */
        asr_running = false;
        
    } else if (int_status & LD_INT_ASR_TIMEOUT) {
        /* 识别超时中断 - 未检测到有效语音 */
        LD3320_Debug("LD3320: Recognition TIMEOUT\r\n");
        ret = LD_ASR_RESULT_TIMEOUT;
        
        /* 标记ASR结束 */
        asr_running = false;
        
    } else if (int_status & LD_ASR_STATUS_PROCESSING) {
        /* 正在处理中,不是最终结果 */
        LD3320_Debug("LD3320: ASR processing...\r\n");
        return LD_ASR_RESULT_NONE;
        
    } else {
        /* 未知中断类型 */
        LD3320_Debug("LD3320: Unknown IRQ status: 0x%02X\r\n", int_status);
        ret = LD_ASR_RESULT_ERROR;
    }
    
    /* 清除中断标志 */
    LD3320_WriteReg(LD_REG_INT_CLEAR, 0xFF);
    
    return ret;
}

/**
 * @brief  软件复位语音识别引擎
 * @param  None
 * @retval None
 * @note   复位ASR引擎后需要重新添加关键词
 */
void LD3320_ASR_Reset(void)
{
    LD3320_Debug("LD3320: Resetting ASR engine...\r\n");
    
    /* 停止ASR */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_ASR_STOP);
    LD3320_Delay_ms(50);
    
    /* 发送复位命令 */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_ASR_RESET);
    LD3320_Delay_ms(100);
    
    /* 重新初始化ASR */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_INIT_ASR);
    LD3320_Delay_ms(100);
    
    /* 清除运行标志 */
    asr_running = false;
    
    LD3320_Debug("LD3320: ASR engine reset completed\r\n");
}

/**
 * @brief  配置ASR识别灵敏度
 * @param  sensitivity: 灵敏度等级 (0=高, 1=中, 2=低)
 * @retval None
 * @note   灵敏度越高,对声音输入要求越低,但可能增加误识别率
 */
void LD3320_SetSensitivity(uint8_t sensitivity)
{
    if (sensitivity > 2) {
        sensitivity = 1;  // 默认中等灵敏度
    }
    
    LD3320_WriteReg(LD_REG_ASR_SENM, sensitivity);
    
    const char *senm_str[] = {"HIGH", "MEDIUM", "LOW"};
    LD3320_Debug("LD3320: Sensitivity set to %s\r\n", senm_str[sensitivity]);
}

/**
 * @brief  获取芯片版本号
 * @param  None
 * @retval 版本号
 * @note   正常情况下版本号应为0x10或0x1x
 */
uint8_t LD3320_GetChipVersion(void)
{
    return LD3320_ReadReg(LD_REG_CHIP_VERSION);
}

/**
 * @brief  打印调试信息到串口
 * @param  format: 格式化字符串
 * @param  ...: 可变参数
 * @retval None
 */
void LD3320_Debug(const char *format, ...)
{
    if (!debug_enable) {
        return;
    }
    
    char buffer[256];
    va_list args;
    
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    
    /* 通过USART1输出调试信息 */
    HAL_USART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 100);
}

/* =====================================================
 * 私有函数实现
 * ===================================================== */

/**
 * @brief  毫秒级延时函数
 * @param  ms: 延时毫秒数
 * @retval None
 */
static void LD3320_Delay_ms(uint32_t ms)
{
    HAL_Delay(ms);
}

/**
 * @brief  SPI片选使能
 * @param  None
 * @retval None
 */
static void LD3320_CS_Select(void)
{
    LD3320_NSS_LOW();
    /* 延时以满足芯片的CS建立时间要求 */
    LD3320_Delay_ms(1);
}

/**
 * @brief  SPI片选禁用
 * @param  None
 * @retval None
 */
static void LD3320_CS_Deselect(void)
{
    /* 延时以满足芯片的CS保持时间要求 */
    LD3320_Delay_ms(1);
    LD3320_NSS_HIGH();
}

/**
 * @brief  SPI数据发送和接收(全双工)
 * @param  data: 要发送的数据字节
 * @retval 接收到的数据字节
 */
static uint8_t LD3320_SPI_Transfer(uint8_t data)
{
    uint8_t received;
    
    HAL_SPI_TransmitReceive(&LD3320_SPI, &data, &received, 1, 100);
    
    return received;
}

/**
 * @brief  等待ASR初始化完成
 * @param  timeout: 超时时间(毫秒)
 * @retval LD_OK: 初始化成功
 *         LD_ERROR_TIMEOUT: 初始化超时
 */
static LD_StatusTypeDef LD3320_WaitForInit(uint32_t timeout)
{
    uint32_t start_time = HAL_GetTick();
    uint8_t asr_status;
    
    /* 执行初始化命令 */
    LD3320_WriteReg(LD_REG_ASR_CTL, LD_CMD_INIT_CHIP);
    LD3320_Delay_ms(50);
    
    /* 等待初始化完成 */
    while ((HAL_GetTick() - start_time) < timeout) {
        asr_status = LD3320_ReadReg(LD_REG_ASR_STATUS);
        
        /* 检查初始化是否完成(状态不为0x11表示完成) */
        if (asr_status != 0x11) {
            LD3320_Debug("LD3320: ASR init completed, status = 0x%02X\r\n", asr_status);
            return LD_OK;
        }
        
        LD3320_Delay_ms(10);
    }
    
    return LD_ERROR_TIMEOUT;
}

五、主程序逻辑实现

5.1 主程序流程设计

整个语音识别系统的工作流程可以划分为以下几个主要阶段:系统初始化阶段、关键词配置阶段、识别等待阶段、中断处理阶段和控制执行阶段。主程序采用状态机架构,通过查询和中断相结合的方式协调各模块工作,既保证了系统的实时响应能力,又便于扩展和维护。

程序流程图如下所示:
循环识别
执行控制
中断处理
启动识别
关键词配置
系统初始化




1
2
3
4
其他
系统上电
HAL系统初始化
配置系统时钟
初始化GPIO外设
初始化SPI1
初始化USART1
打印初始化信息
初始化LD3320模块
清除旧关键词
添加关键词1:kai deng
添加关键词2:guan deng
添加关键词3:da kai
添加关键词4:guan bi
启动ASR引擎
等待语音输入
检测IRQ中断?
读取中断状态
识别成功?
获取识别结果
识别失败处理
结果索引?
执行:开灯
执行:关灯
执行:打开
执行:关闭
未知命令处理
重启ASR引擎

5.2 主程序代码实现

主程序文件包含了系统的入口函数和主循环逻辑,负责协调各外设和驱动模块的工作。代码中实现了关键词配置、语音识别启动、中断处理和结果执行等完整流程,并提供了详细的调试信息输出功能。

5.2.1 创建 main.c 文件
c 复制代码
Creating file: main.c
c 复制代码
/**
 * STM32F103 智能语音识别系统主程序
 * 文件名: main.c
 * 描述: 基于STM32F103C8T6和LD3320的离线语音识别控制系统
 * 功能: 识别语音指令并控制LED灯开关
 * 硬件: STM32F103C8T6 + LD3320语音模块 + LED指示灯
 */

#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
#include "ld3320.h"
#include <stdio.h>
#include <string.h>

/* =====================================================
 * 私有函数声明
 * ===================================================== */

static void SystemClock_Config(void);
static void LED_Init(void);
static void LED_On(void);
static void LED_Off(void);
static void LED_Toggle(void);
static void Voice_ControlLED(uint8_t keyword_index);
static void Print_WelcomeMessage(void);
static void Config_VoiceKeywords(void);

/* =====================================================
 * 主程序入口
 * ===================================================== */

/**
 * @brief  主函数 - 系统入口点
 * @param  None
 * @retval int (实际不会返回)
 * @note   程序执行流程:
 *         1. 初始化HAL库和系统时钟
 *         2. 初始化所有外设(GPIO, SPI, USART)
 *         3. 初始化LD3320语音识别模块
 *         4. 配置语音关键词
 *         5. 进入主循环,等待语音指令
 */
int main(void)
{
    /* 复位所有外设,初始化Flash接口和Systick */
    HAL_Init();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化所有外设 */
    MX_GPIO_Init();
    MX_SPI1_Init();
    MX_USART1_UART_Init();
    
    /* 打印欢迎信息 */
    Print_WelcomeMessage();
    
    /* 初始化LED指示灯 */
    LED_Init();
    
    /* 初始化LD3320语音识别模块 */
    if (LD3320_Init() != LD_OK) {
        printf("ERROR: LD3320 initialization failed!\r\n");
        /* 失败后进入死循环,可通过LED闪烁提示错误 */
        while (1) {
            LED_Toggle();
            HAL_Delay(200);
        }
    }
    
    printf(">>> System initialized successfully!\r\n");
    printf(">>> Please speak the voice commands.\r\n");
    printf(">>> Commands: 'kai deng' (turn on), 'guan deng' (turn off)\r\n");
    printf("========================================\r\n\r\n");
    
    /* 配置语音关键词 */
    Config_VoiceKeywords();
    
    /* 启动语音识别引擎 */
    if (LD3320_ASR_Start() != LD_OK) {
        printf("ERROR: Failed to start ASR engine!\r\n");
        while (1) {
            LED_Toggle();
            HAL_Delay(100);
        }
    }
    
    printf(">>> ASR engine started, listening...\r\n\r\n");
    
    /* 指示灯闪烁一次表示系统就绪 */
    LED_On();
    HAL_Delay(200);
    LED_Off();
    
    /* 主循环 - 持续监听语音指令 */
    while (1)
    {
        /* 检查是否有语音识别中断发生 */
        if (LD3320_CheckIRQ()) {
            /* 处理识别中断 */
            LD_ASRResultTypeDef result = LD3320_ProcessIRQ();
            
            switch (result) {
                case LD_ASR_RESULT_SUCCESS: {
                    /* 识别成功,获取结果索引 */
                    uint8_t index = LD3320_GetResult();
                    
                    printf("\r\n>>> Recognition SUCCESS! Index: %d\r\n", index);
                    
                    /* 根据识别结果控制LED */
                    Voice_ControlLED(index);
                    
                    /* 重新启动ASR引擎,准备下一次识别 */
                    HAL_Delay(500);  // 延时避免立即触发
                    LD3320_ASR_Start();
                    
                    printf(">>> ASR restarted, listening...\r\n\r\n");
                    break;
                }
                
                case LD_ASR_RESULT_TIMEOUT: {
                    /* 识别超时,重新启动 */
                    printf("\r\n>>> Recognition TIMEOUT, restarting...\r\n");
                    LD3320_ASR_Start();
                    break;
                }
                
                case LD_ASR_RESULT_ERROR: {
                    /* 识别错误,尝试复位 */
                    printf("\r\n>>> Recognition ERROR, resetting...\r\n");
                    LD3320_ASR_Reset();
                    HAL_Delay(100);
                    LD3320_ASR_Start();
                    break;
                }
                
                case LD_ASR_RESULT_NONE:
                default:
                    /* 无有效结果,忽略 */
                    break;
            }
        }
        
        /* 主循环中执行其他任务 */
        /* 可以添加看门狗喂狗、低功耗管理等代码 */
        
        /* 短暂延时,降低CPU占用率 */
        HAL_Delay(10);
    }
}

/* =====================================================
 * 系统配置函数实现
 * ===================================================== */

/**
 * @brief  系统时钟配置函数
 * @param  None
 * @retval None
 * @note   配置72MHz系统时钟,使用外部8MHz晶振+PLL
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /* 初始化振荡器配置结构体 */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;           // 外部高速时钟使能
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;        // 8MHz * 9 = 72MHz
    
    /* 配置振荡器 */
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }

    /* 初始化时钟配置结构体 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK   | RCC_CLOCKTYPE_SYSCLK
                                  | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;  // 系统时钟源选择PLL
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;   // AHB时钟不分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;   // APB1时钟2分频(36MHz)
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;    // APB2时钟不分频(72MHz)
    
    /* 配置时钟 */
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }

    /* 配置USART时钟源 */
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
    PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
        Error_Handler();
    }
}

/**
 * @brief  LED指示灯初始化
 * @param  None
 * @retval None
 * @note   PC13引脚配置为推挽输出,初始状态关闭LED
 */
static void LED_Init(void)
{
    /* LED初始状态为关闭(PC13输出高电平关闭) */
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
    printf(">>> LED initialized (PC13)\r\n");
}

/**
 * @brief  LED点亮
 * @param  None
 * @retval None
 */
static void LED_On(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
    printf(">>> LED ON\r\n");
}

/**
 * @brief  LED关闭
 * @param  None
 * @retval None
 */
static void LED_Off(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
    printf(">>> LED OFF\r\n");
}

/**
 * @brief  LED状态翻转
 * @param  None
 * @retval None
 */
static void LED_Toggle(void)
{
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}

/**
 * @brief  根据语音识别结果控制LED
 * @param  keyword_index: 识别到的关键词索引号
 * @retval None
 * @note   索引号与Config_VoiceKeywords中添加的顺序对应
 */
static void Voice_ControlLED(uint8_t keyword_index)
{
    switch (keyword_index) {
        case 1:  /* "kai deng" - 开灯 */
            LED_On();
            printf(">>> Command: TURN ON LIGHT\r\n");
            break;
            
        case 2:  /* "guan deng" - 关灯 */
            LED_Off();
            printf(">>> Command: TURN OFF LIGHT\r\n");
            break;
            
        case 3:  /* "da kai" - 打开 */
            LED_On();
            printf(">>> Command: OPEN\r\n");
            break;
            
        case 4:  /* "guan bi" - 关闭 */
            LED_Off();
            printf(">>> Command: CLOSE\r\n");
            break;
            
        default:
            printf(">>> Command: UNKNOWN (%d)\r\n", keyword_index);
            /* 未知命令时LED闪烁提示 */
            LED_Toggle();
            HAL_Delay(100);
            LED_Toggle();
            break;
    }
}

/**
 * @brief  打印欢迎信息
 * @param  None
 * @retval None
 */
static void Print_WelcomeMessage(void)
{
    printf("\r\n");
    printf("========================================\r\n");
    printf("   STM32F103 Voice Recognition System\r\n");
    printf("   Based on STM32F103C8T6 + LD3320\r\n");
    printf("========================================\r\n");
    printf("   Version: 1.0\r\n");
    printf("   Author: Matrix Agent\r\n");
    printf("========================================\r\n\r\n");
}

/**
 * @brief  配置语音识别关键词
 * @param  None
 * @retval None
 * @note   添加4条常用控制指令:
 *         1. "kai deng" - 开灯
 *         2. "guan deng" - 关灯
 *         3. "da kai" - 打开
 *         4. "guan bi" - 关闭
 *         关键词使用拼音格式,LD3320内部存储为拼音
 */
static void Config_VoiceKeywords(void)
{
    LD_KeywordTypeDef keyword;
    LD_StatusTypeDef status;
    
    printf(">>> Configuring voice keywords...\r\n");
    
    /* 关键词1: 开灯 */
    keyword.index = 1;
    keyword.keyword = "kai deng";
    keyword.keyword_length = strlen(keyword.keyword);
    status = LD3320_AddKeyword(&keyword);
    printf("    [%d] '%s' -> %s\r\n", keyword.index, keyword.keyword, 
           (status == LD_OK) ? "OK" : "FAILED");
    
    /* 关键词2: 关灯 */
    keyword.index = 2;
    keyword.keyword = "guan deng";
    keyword.keyword_length = strlen(keyword.keyword);
    status = LD3320_AddKeyword(&keyword);
    printf("    [%d] '%s' -> %s\r\n", keyword.index, keyword.keyword,
           (status == LD_OK) ? "OK" : "FAILED");
    
    /* 关键词3: 打开 */
    keyword.index = 3;
    keyword.keyword = "da kai";
    keyword.keyword_length = strlen(keyword.keyword);
    status = LD3320_AddKeyword(&keyword);
    printf("    [%d] '%s' -> %s\r\n", keyword.index, keyword.keyword,
           (status == LD_OK) ? "OK" : "FAILED");
    
    /* 关键词4: 关闭 */
    keyword.index = 4;
    keyword.keyword = "guan bi";
    keyword.keyword_length = strlen(keyword.keyword);
    status = LD3320_AddKeyword(&keyword);
    printf("    [%d] '%s' -> %s\r\n", keyword.index, keyword.keyword,
           (status == LD_OK) ? "OK" : "FAILED");
    
    printf(">>> Keywords configuration completed!\r\n\r\n");
}

/**
 * @brief  错误处理函数
 * @param  None
 * @retval None
 * @note   发生严重错误时进入此函数
 *         通常用于调试定位问题
 */
void Error_Handler(void)
{
    printf("!!! ERROR: System error occurred!\r\n");
    
    /* 可以在这里添加错误处理逻辑 */
    /* 例如: 关闭所有外设、进入安全模式等 */
    
    /* 死循环 - 等待调试 */
    while (1) {
        /* LED快速闪烁表示错误状态 */
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(50);
    }
}

/* =====================================================
 * 断言函数实现 (用于调试)
 * ===================================================== */

#ifdef  USE_FULL_ASSERT
/**
 * @brief  报告HAL库参数错误
 * @param  file: 发生错误源文件名字符串
 * @param  line: 发生错误源文件行号
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)
{
    printf("!!! Wrong parameters value: file %s on line %d\r\n", file, line);
}
#endif /* USE_FULL_ASSERT */

5.3 串口重定向配置

为了方便调试和信息输出,需要将标准库的printf函数重定向到USART1串口输出。这需要在工程中添加以下代码,通常在usart.c或单独创建的syscalls.c文件中实现。

5.3.1 添加串口重定向代码

在main.c文件开头的头文件包含区域之后,添加以下代码实现printf重定向:

c 复制代码
/* =====================================================
 * printf重定向配置
 * 将标准输出重定向到USART1
 * ===================================================== */

/**
 * @brief  fputc函数重定向 - printf输出到串口
 * @param  ch: 要输出的字符
 * @param  f: 文件指针(未使用)
 * @retval 输出的字符
 * @note   C标准库函数,会被printf调用
 */
int fputc(int ch, FILE *f)
{
    /* 发送一个字符到USART1 */
    HAL_USART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
    return ch;
}

/**
 * @brief  fgetc函数重定向 - scanf从串口输入
 * @param  f: 文件指针(未使用)
 * @retval 读取的字符
 * @note   C标准库函数,会被scanf调用
 */
int fgetc(FILE *f)
{
    uint8_t ch;
    
    /* 接收一个字符 */
    HAL_USART_Receive(&huart1, &ch, 1, 100);
    
    /* 回显接收的字符 */
    HAL_USART_Transmit(&huart1, &ch, 1, 100);
    
    return ch;
}

需要在main.h或其他合适的位置添加USART句柄的外部声明:

c 复制代码
/* 在main.h中添加 */
extern UART_HandleTypeDef huart1;

六、系统调试与验证

6.1 编译与下载

完成所有代码编写后,按照以下步骤进行编译和程序下载:

步骤一:编译工程

在Keil MDK中按F7键或点击编译按钮开始编译。编译过程中注意观察底部的Build Output窗口,确保显示"0 Error(s)"和"0 Warning(s)"。如果出现错误,需要根据错误提示定位并修正代码问题。常见的编译错误包括:头文件路径不正确、函数未声明、引脚定义冲突等。

步骤二:配置调试器连接

将ST-Link调试器通过杜邦线连接到开发板,连接关系如下:ST-Link的SWDIO连接到STM32的PA13引脚,SWCLK连接到PA14引脚,3.3V连接到VCC,GND连接到GND。确保连接牢固后,将ST-Link插入电脑USB接口。

步骤三:下载程序

在Keil中点击"Download"按钮或按F8键开始程序下载。下载成功后,Build窗口会显示"Flash Erase done"、"Flash Write done"等信息。部分开发板需要先按下复位键再点击下载,或者在Option中勾选"Reset after Flash"选项。

6.2 串口调试验证

程序下载完成后,打开PC端的串口调试助手软件(如PuTTY、Xshell、SSCOM等),配置串口参数为:波特率115200、数据位8位、停止位1位、无奇偶校验。选择正确的COM端口后打开串口。

复位开发板,串口应输出以下欢迎信息:

复制代码
========================================
   STM32F103 Voice Recognition System
   Based on STM32F103C8T6 + LD3320
========================================
   Version: 1.0
   Author: Matrix Agent
========================================

LD3320: Starting initialization...
LD3320: Chip version = 0x10
LD3320: Initialization completed successfully!
>>> System initialized successfully!
>>> Please speak the voice commands.
>>> Commands: 'kai deng' (turn on), 'guan deng' (turn off)
========================================

>>> Configuring voice keywords...
    [1] 'kai deng' -> OK
    [2] 'guan deng' -> OK
    [3] 'da kai' -> OK
    [4] 'guan bi' -> OK
>>> Keywords configuration completed!

>>> ASR engine started, listening...

如果看到上述输出,说明系统初始化成功,LD3320模块工作正常。

6.3 语音识别功能测试

系统就绪后,可以进行语音识别功能测试。按照以下步骤操作:

测试环境准备:确保麦克风正常工作,环境噪音较低。建议在相对安静的房间内进行测试,距离麦克风30至50厘米清晰发音。

测试开灯指令:对着麦克风清晰说出"开灯"或"kai deng",LD3320模块会进行语音识别。识别成功后,串口会输出识别结果,同时LED指示灯点亮。预期串口输出如下:

复制代码
>>> Recognition SUCCESS! Index: 1
>>> LED ON
>>> Command: TURN ON LIGHT

测试关灯指令:说出"关灯"或"guan deng",LED应熄灭。串口输出类似:

复制代码
>>> Recognition SUCCESS! Index: 2
>>> LED OFF
>>> Command: TURN OFF LIGHT

测试其他指令:可以继续测试"打开"和"关闭"指令,验证四条关键词的识别效果。

6.4 常见问题排查

如果在测试过程中遇到问题,可按以下方法排查解决:

问题一:串口无输出

检查USART1的TX(PA9)、RX(PA10)接线是否正确;确认USB转TTL模块驱动已安装;检查串口助手配置的端口号和参数是否正确;验证PA9引脚复用功能是否正确配置。

问题二:LD3320初始化失败

检查SPI接口接线是否正确(SCK、MISO、MOSI、NSS);确认3.3V电源供电正常;检查RST复位引脚连接是否可靠;尝试降低SPI通信速率;检查是否有引脚复用冲突。

问题三:语音识别无响应

确认麦克风接线正确,麦克风有供电;检查IRQ中断引脚是否正确连接;尝试靠近麦克风更大声地发音;检查环境噪音是否过大;确认已正确添加关键词列表。

问题四:识别率低

调整麦克风增益设置(在LD3320_SetSensitivity函数中调整);确保发音清晰准确,语速适中;减少环境背景噪音;适当延长两次识别之间的间隔时间。

七、系统优化与扩展

7.1 灵敏度与识别率优化

LD3320提供了多种参数可以调整以优化识别效果。灵敏度设置是最直观的调节手段,通过修改LD3320_SetSensitivity函数的参数可以在高灵敏度、中等灵敏度、低灵敏度三档之间切换。更高的灵敏度意味着可以检测到更微弱的语音输入,但同时也可能增加误识别的概率。

麦克风增益的调整对识别效果也有显著影响。在LD3320_Init函数中,LD_REG_MIC_GAIN寄存器控制麦克风的前置放大器增益。默认值为0x40(约1.5倍增益),如果麦克风灵敏度不足,可以适当增大此值;但如果环境噪音较大,则应减小增益以提高信噪比。

语音活动检测(VAD)阈值决定了什么强度的声音才会被识别为有效语音输入。通过调整LD_REG_VAD_THRESHOLD和LD_REG_NOISE_THRESHOLD两个寄存器,可以过滤掉背景噪音,只在检测到足够强度的语音时才启动识别流程,这有助于减少误识别。

7.2 添加更多控制功能

在现有基础上,可以方便地扩展更多控制功能。例如添加继电器控制实现对220V家用电器的控制,或者添加电机驱动实现对风扇、窗帘等设备的控制。

添加继电器控制的方法如下:首先在main.c中添加继电器初始化函数和GPIO配置,然后在Voice_ControlLED函数中添加继电器控制的case分支,最后在CubeMX中配置新的GPIO引脚并重新生成代码。

7.3 多命令连续识别模式

本系统目前采用单次识别模式,每次识别完成后需要重新启动ASR引擎。如果需要实现连续的语音对话交互,可以将ASR模式配置为连续识别模式(LD_ASR_MODE_CONTINUOUS)。在该模式下,ASR引擎会自动循环监听,无需每次识别后重新启动。

连续识别模式适用于需要连续对话的应用场景,但需要注意合理设计语音指令的唤醒词和命令词,避免误触发导致频繁执行不需要的操作。

7.4 低功耗设计

对于电池供电的便携式应用,需要考虑系统的功耗优化。STM32F103提供了多种低功耗模式,包括睡眠模式、停止模式和待机模式。在睡眠模式下,CPU停止运行但外设仍可工作,适合本应用场景。

实现低功耗功能的方法是:在没有语音输入时让系统进入睡眠模式,当LD3320检测到语音输入(IRQ引脚产生中断)时通过外部中断唤醒CPU。唤醒后执行完整的识别流程,识别完成后再次进入睡眠模式。这种工作方式可以显著延长电池续航时间。

八、项目总结

本项目成功实现了一套基于STM32F103C8T6和LD3320的离线智能语音识别系统。通过本项目的学习,读者可以掌握以下核心技能:STM32微控制器的开发环境配置和编程方法、SPI通信协议的原理与实践、LD3320语音识别芯片的驱动开发、嵌入式系统的调试与优化技术。

该系统具有以下技术特点:完全离线工作,无需网络连接;识别响应速度快,延迟小于200毫秒;支持多达50条自定义关键词;硬件连接简单,成本可控;代码结构清晰,易于二次开发。

在实际应用中,可以本系统为基础扩展更多功能,如增加语音合成实现语音反馈、添加显示屏实现交互界面、接入物联网平台实现远程控制等。期望读者通过本项目能够深入理解嵌入式语音识别技术的工作原理,为开发更复杂的智能语音交互设备奠定坚实基础。

相关推荐
项目題供诗2 小时前
51单片机入门-直流电机(十四)
单片机·嵌入式硬件·51单片机
安庆平.Я3 小时前
STM32——FreeRTOS - 任务创建和删除 ~ 静态方法
stm32·单片机·嵌入式硬件
蒙塔基的钢蛋儿3 小时前
告别内存泄露与空指针:用C#与.NET 10开启STM32H7高性能单片机开发新纪元
stm32·c#·.net
悠哉悠哉愿意4 小时前
【单片机学习笔记】第十一届省赛复盘
笔记·单片机·嵌入式硬件·学习
学嵌入式的小杨同学4 小时前
STM32 进阶封神之路(二十七):MQTT 深度解析 —— 从协议原理到 OneNET 云平台接入(底层逻辑 + AT 指令开发)
stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb·嵌入式实时数据库
DLGXY4 小时前
STM32(二十九)——读写、擦除FLASH
前端·stm32·嵌入式硬件
風清掦4 小时前
【江科大STM32学习笔记-09】USART串口协议 - 9.2 USART串口数据包
笔记·stm32·单片机·嵌入式硬件·学习
【 STM32开发 】4 小时前
【STM32 + CubeMX】低功耗 -- Standby 待机模式
单片机·嵌入式硬件
广药门徒4 小时前
PADS 改变飞线方向 改变同网络既定路径规划 改变VIRTUAL HOLE连接路径 修复差分信号自动规划飞线错误问题的办法
嵌入式硬件