基于STM32的录音与播音

2023-11-03

基于STM32的录音与播音

设计方案

本设计通过STM32的内置ADC加一个麦克风和放大电路(可以在网上买模块)实现音频的采集,然后存放在SD卡中(这里可以参考我之前的博客FATFS文件系统),然后再读取SD卡里存放的数据通过单片机的内置DAC接一个放大电路和一个喇叭(可以在网上买模块)进行音频播放(参考我前面的博客)。再进行ADC采集和DAC输出时是借助定时器中断,设置定时器的频率就可以设置ADC和DAC的频率了。

通过STM32CUBEMX配置工程

1、配置SDIO和FATFS

参考我前面的博客。https://blog.csdn.net/qq_53000374/article/details/126390968

2、配置DAC和定时器

参考我前面的博客。https://blog.csdn.net/qq_53000374/article/details/126272701

3、配置ADC和定时器

这里只需要配置ADC,定时器和DAC共用一个。
在这里插入图片描述
其他的不用管。

添加代码


//main前面
#include "string.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define KEY0	HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)
#define KEY1	HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)

#define	val_count_max		5000  //最大采样个数
uint16_t val_count=0;    //实际采样个数

uint16_t val_data[val_count_max]={0};   
uint16_t val_data_buff[val_count_max]={0};

#define DAC_Count 4096
uint8_t DAC_Data[5000]={0};

uint16_t DAC_F=2;  
volatile  uint16_t Time_Count=0;


char* ch_tab="12345";

char read_buff[100]={0};

FATFS fs;  //文件系统工作区指针
FIL fp;   //文件对象结构指针
FRESULT f_res;  //函数放回结果
uint16_t fnum;               /* 文件成功读写数量 */

uint16_t fp_num;   //文件大小
UINT fp_rw_num;  //实际文件读写字节数

uint8_t fp_wriet_flag=0;  //SD卡写标志
uint8_t sound_record_flag=0;   //录音标志

uint8_t fp_read_flag=0;  //SD卡读标志
uint8_t play_record_flag=0;   //播音标志

uint8_t key0_down_flag=0;  //按键按下一次标志,
uint8_t key1_down_flag=0;  //按键按下一次标志,

uint16_t fp_memory=0;

char Read_Data[DAC_Count]={0};
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */


void KEY_Scanf()
{
	if(KEY0 == 0)  //录音开始和停止
	{
		HAL_Delay(10);
		if(KEY0==0)
		{
			while(!KEY0);
			key0_down_flag=0;
			sound_record_flag = !sound_record_flag;  //0:停止  1:开始
		}
	}
	
	if(KEY1 == 0)
	{
		HAL_Delay(10);
		if(KEY1==0)
		{
			while(!KEY1);
			key1_down_flag=0;
			play_record_flag = !play_record_flag;
		}
	}
}

void KEY_Handle(void)  
{
	if((sound_record_flag != 0) && (key0_down_flag==0))  //开始录音
	{
		key0_down_flag=1;
		val_count=0;
		HAL_TIM_Base_Start_IT(&htim3);
		HAL_ADC_Start(&hadc1);
	}
	else if((sound_record_flag == 0) && (key0_down_flag==0))  //停止录音
	{
		key0_down_flag=1;
		HAL_TIM_Base_Stop_IT(&htim3);
		HAL_ADC_Stop(&hadc1);
		f_close(&fp);
		f_mount(NULL,"0:",1);		 //取消挂载
	}
	
	if((play_record_flag !=0) && (key1_down_flag==0))  //开始播音
	{
		key1_down_flag=1;
		HAL_TIM_Base_Start_IT(&htim3);
		HAL_ADC_Start(&hadc1);
		//在外部 SD 卡挂载文件系统,文件系统挂载时会对 SD 卡初始化
    f_res = f_mount(&fs, "0:", 1);	
		f_res = f_open(&fp, "0:ADC_DATA.txt", FA_OPEN_EXISTING | FA_READ);
		
		if(f_res==FR_OK)
    {
			f_lseek(&fp, 44);  //偏移文件起始位置44个字节,后面的就是所需要的数据
			fp_memory=f_size(&fp);
			printf("文件大小:%d\r\n",fp_memory);
		}
	}
	else if((play_record_flag ==0) && (key1_down_flag==0))
	{
		key1_down_flag=1;
		HAL_TIM_Base_Stop_IT(&htim3);
		HAL_ADC_Stop(&hadc1);
		f_close(&fp);
		f_mount(NULL,"0:",1);		 //取消挂载	
	}
}

void FILE_RW_Test(void)   //文件系统读写测试
{
	printf("挂载SD卡驱动\r\n");
	f_res=f_mount(&fs,"0:",1);
	 f_unlink("0:ADC_DATA.txt");
	if(f_res == FR_OK)
	{
		printf("挂载成功\r\n");
		f_res=f_open(&fp,"0:ADC_DATA.txt",FA_WRITE | FA_OPEN_ALWAYS);
		if(f_res == FR_OK)
		{
			printf("文件打开成功\r\n");
			f_res=f_write(&fp,tab,10,&fp_rw_num);
			if(f_res == FR_OK)
			{
				printf("写入成功\r\n实际写入字节数:%d\r\n",fp_rw_num);
			}
		}
	}
	f_close(&fp);
	f_res=f_open(&fp,"0:ADC_DATA.txt",FA_READ | FA_OPEN_ALWAYS);
	if(f_res == FR_OK)
	{
		printf("文件打开成功\r\n");
		f_res=f_read(&fp,read_buff,5,&fp_rw_num);
		if(f_res == FR_OK)
		{
			printf("读取成功\r\n");
			printf("%s\r\n",read_buff);
		}
	}
}

void Audio_SD_Write(void)
{
	if(fp_wriet_flag == 1)
	{
		fp_wriet_flag = 0;
			
		f_res=f_mount(&fs,"0:",1);
		if(f_res == FR_OK)
		{
			printf("挂载成功\r\n");
		}
		else
			printf("挂载失败\r\n");
			
		f_res=f_open(&fp,"0:ADC_DATA.txt",FA_OPEN_ALWAYS | FA_WRITE);
		if(f_res == FR_OK)
			printf("文件打开成功\r\n");
		else
			printf("打开失败\r\n");
		f_lseek(&fp, f_size(&fp));//确保写词写入不会覆盖之前的数据
			
		f_res=f_write(&fp,val_data_buff,4096,&fp_rw_num);
		if(f_res == FR_OK)
		{
			memset(val_data_buff,0,5000);
			printf("写入成功\r\n实际写入字节数:%d\r\n",fp_rw_num);
		}
		else
			printf("写入失败\r\n");
	}
	f_close(&fp);
	f_mount(NULL,"0:",1);		 //取消挂载	
}

void Audio_SD_Read(void)
{
	if(Time_Count>=fnum){  //当DAC输出的字节大于等于读取到的字节数
		Time_Count = 0;
		memset(DAC_Data,0,sizeof(DAC_Data));  //清空数组
		f_res=f_read(&fp,Read_Data,4096,(UINT*)&fnum);//读出4096个字节  
					
		memcpy(DAC_Data,Read_Data,fnum);  //数组复制
	}

	if(f_eof(&fp)) //已经读取完了
	{
		f_lseek(&fp, 44);  //跳过前面44个字节
	}
}
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
int wd4;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM3)
	{
		if((sound_record_flag !=0) && (play_record_flag ==0))
		{
			val_data[val_count]=HAL_ADC_GetValue(&hadc1);
			val_count++;
			if(val_count == 2048)
			{
				memcpy(val_data_buff,val_data,sizeof(val_data));
				val_count=0;
				fp_wriet_flag=1;
			}
		}
		
		if((sound_record_flag ==0) && (play_record_flag !=0))
		{
			Time_Count += DAC_F;
			
			wd4 = ((DAC_Data[Time_Count-1]-0x80)<<8)|(DAC_Data[Time_Count-2]);   //将16位的音频数据转为12位。
		
			wd4 = 0xfff & (wd4>>4);
			HAL_DAC_SetValue(&hdac,DAC_CHANNEL_2,DAC_ALIGN_12B_R,wd4);//12位右对齐数据格式设置DAC值
		}
	}
}

//main里面
HAL_DAC_Start(&hdac,DAC_CHANNEL_2);
	
	HAL_TIM_Base_Stop_IT(&htim3);
	HAL_ADC_Stop(&hadc1);
	
	HAL_Delay(1);
	
//	FILE_RW_Test();  //测试函数
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		KEY_Scanf();
		KEY_Handle();
		
		if((sound_record_flag !=0) && (play_record_flag ==0))
			Audio_SD_Write();  //录音
		else if((sound_record_flag ==0) && (play_record_flag !=0))
			Audio_SD_Read();  //播音
	}
    /* USER CODE END WHILE */


过程中遇到的问题

我在过程中遇到一个问题,就是在进行SD卡写数据时,一直循环写数据会出现写入失败的情况,我在网上找到几个可能的原因:
STM32读写SD卡

问题1:在循环进行写SD卡时,会出来写多次后出现写错误的情况。

问题原因1:

​ 可能是通过APB2总线往FIFO装填数时,发生了中断,导致总线被占用或其他资源被占用,从而无法及时完成FIFO的装填,而后面从FIFO取数的频率又非常快,导致无法取到数。

解决方案1:

​ 在调用f_write之前调用 __disable_irq() 接口关闭中断,写操作完成后调用 __enable_irq()接口启用中断:

__disable_irq();

if(FR_OK ==f_write())

{ }

else

{}

__disable_irq() ;

优点:简单

缺点:在写操作期间CPU无法响应中断

问题原因2:

​ SD卡写的时候出现了坏块,导致写不成功,在写失败时需要重试一下。

解决方案2:

​ 重写bsp_driver_sd.c文件中的BSP_SD_WriteBlocks函数是_weak 函数,可以在自己的源文件myfile.c中重写该函数,当写发生错误后,清除错误标记,再重试几次即可,在我的单片机上,一般第一次重试即可成功

uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout)

{

​ int i;

​ for (i = 1; i <= TY_SDIO_RETRY_MAX; i ++)

​ {

​ if (HAL_SD_WriteBlocks(&hsd, (uint8_t *)pData, WriteAddr, NumOfBlocks, Timeout) == HAL_OK)

​ {

​ if (i > 1)

​ printf(“[INFO] SDIO writing succeeded: retry %d.\n”, i);

​ return (MSD_OK); // Succeeded

​ }

​ else

​ {

​ printf(“[ERROR] SDIO writing failure: retry %d, error code %#x, addr %#x, %u blocks.\n”,

​ i, hsd.ErrorCode, WriteAddr, NumOfBlocks);

​ HAL_SD_Abort(&hsd); //clear error flag

​ }

​ }

​ return (MSD_ERROR);

}

优点:上层应用软件不需要更改,也不需要禁用中断

缺点:频率较高时(我的是30MHZ)时,经常会发生写重试

问题原因3:

​ 写速度太快,导致FIFO来不及装填,产生错误,需要降速。

解决方案3:

​ 更改sdio.c中的MX_SDIO_SD_Init函数,将hsd.Init.ClockDiv设置为48分频,即SDIO的频率为主频除以48,我这里为72/48=1.5MHz。

优点:非常简单;无需禁用中断;无需对上层软件进行修改。

缺点:降低了写速度

我这里选择的时第3种,降低SD卡的读写速度
在这里插入图片描述
将这里的分频系数修改为48,速度就为72MHz/48.

注意事项:

​ 在挂载SD卡,文件打开,完成一次读或写操作之后,要尽量关闭文件,取消SD卡挂载,下一次操作重新挂载和打开。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于STM32的录音与播音 的相关文章

  • 【超快捷】Windows系统自带的快捷键合集

    总览 一 常用 Win E 打开我的电脑 Win R 打开 运行 对话框 Win L 锁定计算机 Ctrl Shift Esc 打开任务管理器 Ctrl Alt Delete 打开安全窗口 F2 重命名文件 F5 刷新当前页面或者桌面 Al
  • ERP让员工和企业双赢

    企业的科学管理 离不开数字化 而数字化 就需要企业积累数据 这就离不开ERP ERP实施 如果只是员工增加了数据录入量 而没有获得实际的益处 比如 效率提高 难度降低等 则员工内心会抵制ERP 员工要的是方便 企业要的是数据 必须兼顾二者

随机推荐

  • 快速理解聚集索引和非聚集索引

    数据库的索引 听起来挺神秘的 仔细想想 这些索引 其实就是平时咱们查东西时候常用的两种手段 无非就是为了提高我们找东西的效率而已 那么我们平时又是怎么查东西呢 聚集索引 聚集索引 来源于生活尝试 这中索引可以说是按照数据的物理存储进行划分的
  • 工作中遇到的数据库死锁问题 - 排查方案 CannotAcquireLockException

    问题 重现 正常情况下 销售人员在使用我们的销售系统为客户创建订单时候 后台java代码就会开启事务 然后往数据库中添加订单信息和订单详情信息 以及一些其他业务操作 但由于某次异常操作 可能是网络或者其他的原因 导出系统出现问题无法工作 于
  • Font Awesome矢量版,十六进制版,WPF字体使用

    Font Awesome为您提供可缩放的矢量图标 您可以使用CSS所提供的所有特性对它们进行更改 包括 大小 颜色 阴影或者其它任何支持的效果 WPF可以使用FontAwesome字体 Android可以使用FontAwesome字体 需要
  • python小游戏毕设 接金币小游戏设计与实现 (源码)

    文章目录 1 项目简介 1 游戏介绍 2 实现效果 3 开发工具 3 1 环境配置 3 2 Pygame介绍 4 具体实现 5 最后 1 项目简介 Hi 各位同学好呀 这里是L学长 今天向大家分享一个今年 2022 最新完成的毕业设计项目作
  • 【华为OD机试真题】日志采集系统(python)100%通过率 超详细代码注释 代码优化

    华为OD机试真题 2022 2023 真题目录 点这里 华为OD机试真题 信号发射和接收 试读 点这里 华为OD机试真题 租车骑绿道 试读 点这里 日志采集系统 时间限制 1s空间限制 256MB限定语言 不限 题目描述 日志采集是运维系统
  • 【Android】BottomNavigationView+ViewPager实现底部导航栏(步骤+源码)

    吐糟大会 时隔好久再一次在博客上与大家见面 上一年九月份怀疑自己不适合这行毅然辞职 我没结婚没车贷没房贷本以为能很潇洒 但现实给我开了个玩笑 我发现除了这一行其他什么都不会 也许是我很懒吧 为了能吃饱饭我再次踏入这一行 回归初衷为了让更多刚
  • RDF与可视化

    Rudolf RDFViz Exploring tools for RDF Graph Visualisation http www ilrt bris ac uk discovery rdf dev rudolf rdfviz To vi
  • Win10系统无法打开chm文档提示无法显示该页面怎么办?

    网上下载了一个chm文档 但打开后发现无法显示该页 提示 确保 Web 地址 ieframe dll dnserrordiagoff htm 正确 通过查找相关资料 迅速了解决该问题 特此分享 一 CHM文档定义 CHM Compiled
  • 查看电脑CPU是否支持虚拟化

    有两次方法 使用securable 可以在一下地址下载https www grc com files securable exe 安装双击就会出现如图 32 64 就是表示电脑的位数 D E P 表示的是硬件 Virtualization表
  • VSCode 下python输出中文乱码问题

    在处理这个问题之前 你首先得知道为什么会出现这个这个问题 你在使用VScode编辑代码时 代码页面中文正常 而终端输出那里中文却为乱码 出现这个现象的原因是因为编码方式的不同 VScode的默认编码方式为UTF 8 中国地区下cmd的编码方
  • github下载的项目如何build--如何利用configure.ac和Makefile.am,生成Makefile。

    环境是Ubuntu18 04 1 aclocal命令根据configure ac文件的内容 自动生成aclocal m4文件 2 autoconf命令会根据configure ac和aclocal m4文件 生成configure文件 3
  • 基于 Flutter+Dart 聊天实例

    1 项目介绍 Flutter是目前比较流行的跨平台开发技术 凭借其出色的性能获得很多前端技术爱好者的关注 比如阿里闲鱼 美团 腾讯等大公司都有投入相关案例生产使用 flutter chatroom项目是基于Flutter Dart chew
  • TortoiseGit 入门指南11:还原与重置

    Git 就像个时光机器 能让我们还原到任何提交 还原未提交的更改 假如我们在查看一个干净的代码仓库 干净意味着工作区中的文件保持着最后一次提交的状态 没有修改 在查看的过程中 我们有意或无意的修改了工作区中的文件 之后我们想把这些文件恢复成
  • STLINK-V3 STDC14座转2.54mm排针转接板Kicad工程

    简介 这是一个 STLINK V3 STDC14座转2 54mm排针转接板Kicad工程 STDC14座实际工作中不太方便 所以搞了这个转接板 另外转接版上提供了可选的电源输出功能 An adapter board for STLINK V
  • EMC测试仪器_EMC测试整改流程及常见问题

    EMC主要是通过测试产品在电磁方面的干扰大小和抗干扰能力的综合评定 是产品在质量安全认证重要的指标之一 很多产品在做产品安全认证时都会遇到产品测试不合格的情况 尤其是在电磁兼容测试 即EMC测试 出错频率更是普遍 当产品一旦测试不合格 那么
  • J-002 Jetson电路设计之电源设计--NANO && XAVIER NX

    Jetson电源设计 1 电源说明 1 1 电源和系统引脚描述 1 2 电源控制框图详情 2 上电的时许 2 1 框图分析 2 2 上电时序 3 GND引脚 1 电源说明 Jetson NANO和XAVIER NX核心板的电源为DC 5V
  • 白盒测试之静态检查

    态检查一般是检查编码标准规范 错误列表 编码规范往往团队 会根据自己的经验和风格进行设置一些规范 现在很多IDE工具都会 在编辑代码的时候实时的提醒是否符合代码风格 错误列表 一般 是代码潜在的bug 由于某种代码写法虽然没有语法错误 但是
  • 现在有多个异步操作ajax请求,我们需要当所有异步请求都成功的时候,执行后续操作

    1 场景 现在有多个异步操作ajax请求 我们需要当所有异步请求都成功的时候 执行后续操作 2 方法 方法一 通常的讲 我们可以设置一个flag变量 然后在各自的ajax的成功回调内去维护这个变量数量 当满足条件时 我们来触发后续函数 方法
  • stat函数(stat、fstat、lstat)

    include
  • 基于STM32的录音与播音

    基于STM32的录音与播音 设计方案 本设计通过STM32的内置ADC加一个麦克风和放大电路 可以在网上买模块 实现音频的采集 然后存放在SD卡中 这里可以参考我之前的博客FATFS文件系统 然后再读取SD卡里存放的数据通过单片机的内置DA