文章目录
-
- 一、项目概述
- 二、硬件接线
- 三、开发环境准备
- 四、Mermaid系统流程图
- 五、工程创建与代码实现
-
- [1. 创建工程目录结构](#1. 创建工程目录结构)
- [2. 文件1:sys.h(系统宏定义)](#2. 文件1:sys.h(系统宏定义))
- [3. 文件2:spi.h(SPI引脚定义)](#3. 文件2:spi.h(SPI引脚定义))
- [4. 文件3:spi.c(SPI驱动实现)](#4. 文件3:spi.c(SPI驱动实现))
- [5. 文件4:ld3320.h(LD3320驱动头文件)](#5. 文件4:ld3320.h(LD3320驱动头文件))
- [6. 文件5:ld3320.c(LD3320核心驱动)](#6. 文件5:ld3320.c(LD3320核心驱动))
- [7. 文件6:usart.h(串口头文件)](#7. 文件6:usart.h(串口头文件))
- [8. 文件7:usart.c(串口驱动)](#8. 文件7:usart.c(串口驱动))
- [9. 文件10:main.c(主函数)](#9. 文件10:main.c(主函数))
- 六、工程配置步骤
- 七、程序烧录与测试
- 八、常见问题解决
- 九、项目扩展
一、项目概述
本项目基于STM32F103C8T6 单片机+LD3320语音识别模块实现非特定人语音识别功能,无需训练、无需外接Flash,可直接识别预设的中文语音指令,实现语音控制LED、继电器、蜂鸣器等外设,适合零基础小白入门智能语音控制项目。
核心硬件
- 主控:STM32F103C8T6最小系统板
- 语音模块:LD3320(集成ASR语音识别,支持50条指令)
- 外设:LED灯、杜邦线、USB转TTL模块、5V电源
- 辅助:面包板
核心功能
- 识别预设语音指令(开灯、关灯、打开风扇、关闭风扇等)
- 串口打印识别结果
- 根据识别结果控制外设输出
- 模块状态实时反馈
二、硬件接线
LD3320模块与STM32F103采用SPI通信,接线严格按照下表操作,避免接错烧毁模块:
| STM32F103引脚 | LD3320模块引脚 | 功能说明 |
|---|---|---|
| 3.3V | VCC | 模块供电(必须3.3V,严禁接5V) |
| GND | GND | 公共地 |
| PA4 | CS | SPI片选 |
| PA5 | SCK | SPI时钟 |
| PA7 | MOSI | SPI主机发送 |
| PA6 | MISO | SPI主机接收 |
| PB0 | RST | 模块复位 |
| PB1 | IRQ | 模块中断(识别成功触发) |
外设接线
- LED正极 → PA0
- LED负极 → GND(串联220Ω电阻)
三、开发环境准备
- 软件:Keil5 MDK-ARM(安装STM32F103芯片包)
- 固件库:STM32F10x标准库(3.5版本)
- 烧录工具:FlyMcu/ST-Link
- 串口调试助手:XCOM
四、Mermaid系统流程图
否
是
开灯指令
关灯指令
其他指令
系统上电初始化
STM32初始化
GPIO/SPI/串口
LD3320模块复位
LD3320初始化
写入识别指令
开启语音识别模式
检测到IRQ中断?
读取识别结果
串口打印识别指令
指令匹配
PA0输出高电平 点亮LED
PA0输出低电平 熄灭LED
执行对应外设操作
清除中断标志
五、工程创建与代码实现
本项目基于STM32标准库开发,所有代码文件直接复制到工程对应目录即可使用。
1. 创建工程目录结构
STM32_LD3320
├─ User
│ ├─ main.c // 主函数
│ ├─ ld3320.c // LD3320驱动代码
│ ├─ ld3320.h // LD3320头文件
│ ├─ spi.c // SPI驱动
│ ├─ spi.h // SPI头文件
│ └─ sys.h // 系统配置
├─ STM32F10x_StdPeriph_Driver // 标准库文件
└─ System
└─ system_stm32f10x.c // 系统时钟
2. 文件1:sys.h(系统宏定义)
c
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
// 位带操作
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr)
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12)
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
#define GPIOC_ODR_Addr (GPIOC_BASE+12)
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n)
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#endif
3. 文件2:spi.h(SPI引脚定义)
c
#ifndef __SPI_H
#define __SPI_H
#include "sys.h"
// SPI引脚定义
#define LD3320_SPI_CS PAout(4)
#define LD3320_SPI_SCK PAout(5)
#define LD3320_SPI_MOSI PAout(7)
#define LD3320_SPI_MISO PAin(6)
void SPI1_Init(void);
u8 SPI1_ReadWriteByte(u8 TxData);
#endif
4. 文件3:spi.c(SPI驱动实现)
c
#include "spi.h"
#include "sys.h"
// SPI1初始化 主机模式
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
// PA4/5/7 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// PA6 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// SPI配置
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
// SPI读写一个字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
u8 retry = 0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
{
retry++;
if(retry > 200) return 0;
}
SPI_I2S_SendData(SPI1, TxData);
retry = 0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
{
retry++;
if(retry > 200) return 0;
}
return SPI_I2S_ReceiveData(SPI1);
}
5. 文件4:ld3320.h(LD3320驱动头文件)
c
#ifndef __LD3320_H
#define __LD3320_H
#include "sys.h"
#include "spi.h"
// LD3320控制引脚
#define LD3320_RST_LOW PBout(0) = 0
#define LD3320_RST_HIGH PBout(0) = 1
#define LD3320_IRQ PBin(1)
// 函数声明
void LD3320_Init(void);
void LD3320_WriteReg(u8 addr, u8 data);
u8 LD3320_ReadReg(u8 addr);
void LD3320_Reset(void);
void LD3320_Clear(void);
u8 LD3320_ReadResult(void);
void LD3320_AddCommand(u8 num, char *str);
void LD3320_Start(void);
#endif
6. 文件5:ld3320.c(LD3320核心驱动)
c
#include "ld3320.h"
#include "delay.h"
#include "usart.h"
// LD3320写入寄存器
void LD3320_WriteReg(u8 addr, u8 data)
{
LD3320_SPI_CS = 0;
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(addr);
SPI1_ReadWriteByte(data);
LD3320_SPI_CS = 1;
delay_ms(1);
}
// LD3320读取寄存器
u8 LD3320_ReadReg(u8 addr)
{
u8 temp;
LD3320_SPI_CS = 0;
SPI1_ReadWriteByte(0x01);
SPI1_ReadWriteByte(addr);
temp = SPI1_ReadWriteByte(0xFF);
LD3320_SPI_CS = 1;
return temp;
}
// LD3320硬件复位
void LD3320_Reset(void)
{
LD3320_RST_LOW;
delay_ms(100);
LD3320_RST_HIGH;
delay_ms(100);
}
// 清除LD3320状态
void LD3320_Clear(void)
{
LD3320_WriteReg(0x42, 0x55);
delay_ms(20);
}
// 初始化LD3320
void LD3320_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// PB0 复位输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// PB1 中断输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 外部中断配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
LD3320_Reset();
SPI1_Init();
LD3320_WriteReg(0x06, 0x01);
delay_ms(30);
LD3320_WriteReg(0x06, 0x00);
delay_ms(30);
LD3320_WriteReg(0x17, 0x35);
LD3320_WriteReg(0x18, 0xFF);
LD3320_WriteReg(0x19, 0xFF);
LD3320_WriteReg(0x1A, 0xFF);
LD3320_WriteReg(0x1B, 0xFF);
LD3320_WriteReg(0x1C, 0xFF);
LD3320_WriteReg(0x1E, 0x03);
LD3320_WriteReg(0x1F, 0x1F);
LD3320_WriteReg(0x20, 0x03);
LD3320_WriteReg(0x21, 0x1F);
LD3320_WriteReg(0x22, 0x03);
LD3320_WriteReg(0x23, 0x1F);
LD3320_WriteReg(0x24, 0x03);
LD3320_WriteReg(0x25, 0x1F);
LD3320_WriteReg(0x26, 0x03);
LD3320_WriteReg(0x27, 0x1F);
LD3320_WriteReg(0x28, 0x03);
LD3320_WriteReg(0x29, 0x1F);
LD3320_WriteReg(0x2A, 0x03);
LD3320_WriteReg(0x2B, 0x1F);
LD3320_WriteReg(0x3E, 0x03);
LD3320_WriteReg(0x3F, 0x1F);
LD3320_WriteReg(0x41, 0x00);
LD3320_WriteReg(0x42, 0x00);
}
// 添加语音指令
void LD3320_AddCommand(u8 num, char *str)
{
u8 i;
LD3320_WriteReg(0x40, num);
for(i = 0; str[i] != '\0'; i++)
{
LD3320_WriteReg(0x41, str[i]);
}
LD3320_WriteReg(0x41, 0x00);
delay_ms(20);
}
// 启动识别
void LD3320_Start(void)
{
LD3320_WriteReg(0x29, 0x36);
LD3320_WriteReg(0x2A, 0x01);
LD3320_WriteReg(0x2B, 0x1F);
LD3320_WriteReg(0x3E, 0x03);
LD3320_WriteReg(0x3F, 0x1F);
LD3320_WriteReg(0x41, 0x00);
LD3320_WriteReg(0x42, 0x00);
LD3320_WriteReg(0x37, 0x01);
delay_ms(10);
}
// 读取识别结果
u8 LD3320_ReadResult(void)
{
u8 res;
res = LD3320_ReadReg(0x4B);
return res;
}
// 中断服务函数
void EXTI1_IRQHandler(void)
{
u8 num;
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
num = LD3320_ReadResult();
printf("识别结果:%d\r\n", num);
// 根据识别结果控制外设
switch(num)
{
case 1:
PAout(0) = 1; // 开灯
printf("指令:打开LED灯\r\n");
break;
case 2:
PAout(0) = 0; // 关灯
printf("指令:关闭LED灯\r\n");
break;
case 3:
printf("指令:打开风扇\r\n");
break;
case 4:
printf("指令:关闭风扇\r\n");
break;
default:
printf("未识别指令\r\n");
break;
}
LD3320_Clear();
LD3320_Start();
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
7. 文件6:usart.h(串口头文件)
c
#ifndef __USART_H
#define __USART_H
#include "sys.h"
void uart1_init(u32 bound);
void Usart1_SendByte(u8 data);
void Usart1_SendString(char *str);
#endif
8. 文件7:usart.c(串口驱动)
c
#include "usart.h"
#include "sys.h"
#include "delay.h"
// 串口1初始化
void uart1_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// PA9 发送
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// PA10 接收
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 串口配置
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 中断配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
// 发送字节
void Usart1_SendByte(u8 data)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, data);
}
// 发送字符串
void Usart1_SendString(char *str)
{
u8 i = 0;
while(str[i] != '\0')
{
Usart1_SendByte(str[i]);
i++;
}
}
// 重定向printf
int fputc(int ch, FILE *f)
{
while((USART1->SR & 0X40) == 0);
USART1->DR = (u8) ch;
return ch;
}
9. 文件10:main.c(主函数)
c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "spi.h"
#include "ld3320.h"
// LED初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PAout(0) = 0; // 默认关灯
}
int main(void)
{
delay_init(); // 延时初始化
uart1_init(9600); // 串口初始化 波特率9600
LED_Init(); // LED初始化
LD3320_Init(); // LD3320初始化
printf("LD3320语音识别系统初始化完成\r\n");
printf("开始添加语音指令...\r\n");
// 添加语音指令 编号1~4
LD3320_AddCommand(1, "打开灯光");
LD3320_AddCommand(2, "关闭灯光");
LD3320_AddCommand(3, "打开风扇");
LD3320_AddCommand(4, "关闭风扇");
printf("指令添加完成,启动识别\r\n");
LD3320_Start(); // 启动语音识别
while(1)
{
// 系统循环等待中断触发
}
}
六、工程配置步骤
- 打开Keil5,创建新工程,选择芯片
STM32F103C8T6 - 添加标准库文件到工程
- 将上述所有代码文件添加到工程对应分组
- 打开魔术棒→Target,设置外部晶振为
8MHz - 打开魔术棒→Output,勾选
Create HEX File - 编译工程,无报错则生成HEX文件
七、程序烧录与测试
- 使用USB转TTL连接STM32下载口(TX-RX、RX-TX、GND-GND、3.3V-3.3V)
- 打开FlyMcu,选择生成的HEX文件,点击开始编程
- 烧录完成后,打开串口调试助手,波特率9600
- 给系统上电,串口打印初始化信息
- 对着LD3320麦克风说出指令:
- 说打开灯光 → LED点亮,串口打印识别结果
- 说关闭灯光 → LED熄灭,串口打印识别结果
八、常见问题解决
-
模块无响应
- 检查3.3V供电,严禁接5V
- 检查SPI接线是否正确
- 复位引脚是否正常输出高电平
-
识别率低
- 远离噪音环境
- 说话距离模块5-20cm
- 指令使用简洁的中文词汇
-
串口乱码
- 检查波特率是否为9600
- 检查系统时钟配置是否正确
九、项目扩展
- 增加继电器模块,实现语音控制家电
- 添加蜂鸣器,识别成功后提示音
- 扩展指令数量,最多支持50条
- 结合蓝牙模块,实现远程语音控制