文章目录
-
- 一、项目概述与硬件选型
-
- [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, ®_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条自定义关键词;硬件连接简单,成本可控;代码结构清晰,易于二次开发。
在实际应用中,可以本系统为基础扩展更多功能,如增加语音合成实现语音反馈、添加显示屏实现交互界面、接入物联网平台实现远程控制等。期望读者通过本项目能够深入理解嵌入式语音识别技术的工作原理,为开发更复杂的智能语音交互设备奠定坚实基础。