# DSP-课设
**Repository Path**: li_lue/dsp-course-design
## Basic Information
- **Project Name**: DSP-课设
- **Description**: dsp课设
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2023-10-24
- **Last Updated**: 2024-12-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# DSP课设调试记录——音频降噪设计与实现
## ----2023.12.07 扩展程序,搭建多个滤波器
扩展部分对比多个滤波器的效果,我的解决方法是在矩阵键盘上切换滤波器,1-8键对应八个不同的滤波器,9键为关闭滤波器,在数码管上显示当前选用的滤波器和滤波器的开关状态。
#### 修改滤波器的方法:
1.按照**----2023.11.23 IIR滤波器算法移植**这一章节描述的滤波器导出方法导出系数矩阵到MATLAB工作区之后,打开工程目录中 MATLAB文件夹内的**creat_filter_for_dsp.m**脚本,修改第一行的filter_index参数,这个参数代表要修改的滤波器序号,范围是 1-8,运行脚本,可以生成iir_coeffsn.h文件,例如:
```MATLAB
%将通过filterDesigner生成的SOS、G矩阵处理,保存到本地的iir_coeffs.h头文件
filter_index=4; %滤波器序号
currentDateTime = datetime('now', 'Format', 'yyyy-MM-dd HH:mm:ss');
userName = getenv('USERNAME');
······
```

2.在app/IIR/bsp_iir.c文件中Include上刚刚在matlab中生成的头文件,例如导出一个滤波8,头文件名字是iir_coeffs8.h,那就在bsp_iir.c文件中添加#include "iir_coeffs8.h",剩下的创建滤波器跟滤波器绑定按键的操作程序都已经写好,只要加入头文件就能全部自动化完成。
3.开发板上的橙色(靠外面)的插线孔为开发板的输入,接电脑的耳机孔,蓝色(中间)的插线孔为输出口,接耳机,然后下载运行。
4.我在工程目录的MATLAB文件夹里放了滤波器仿真(怎么仿真看**----2023.11.20 IIR滤波器MATLAB仿真测试**这一章)和播放指定频率噪音的程序,可以尝试用电脑放音乐或者电视剧,然后用matlab里面的脚本(Nosiy.m)播放噪音,看滤波效果。
## ----2023.12.05添加数码管显示滤波器开关状态,串口波形打印
纯娱乐向,没啥必要,觉得好玩才做的
效果:
源码:app/smg/*
```c
if (bMS1Chg)
{
// 1ms软中断,每1ms运行一次
bMS1Chg = 0; //标志位清零
if (MS1 % 2 == 0)
{
UARTa_printf("%d\r\n", outputvalue);
}
if (iir_mode == 0)
{
SMG_DisplayBuff(0xFF, 0xC1, 0xC0, 0xFF); // on
} else
{
SMG_DisplayBuff(0xB1, 0xB1, 0xC0, 0xFF); // off
}
}
```
## ----2023.11.27 IIR库完美实现实时滤波器,测试效果与MATLAB仿真效果完全一致,课设到此几乎完成
## ----2023.11.27 调用IIR滤波器库函数,实现实施滤波
初始化:
```c
void IIR_init()
{
// Configure the object
IIR_f32_setCoefficientsBPtr(hnd_iir, iir1_coeffs_B);
IIR_f32_setCoefficientsAPtr(hnd_iir, iir1_coeffs_A);
IIR_f32_setDelayLinePtr(hnd_iir, iir1_delayLine);
IIR_f32_setInputPtr(hnd_iir, (float *) &in);
IIR_f32_setOutputPtr(hnd_iir, (float *) &out);
IIR_f32_setScalePtr(hnd_iir, iir1_scaleFactors);
IIR_f32_setOrder(hnd_iir, FILTER_ORDER);
IIR_f32_setInitFunction(hnd_iir, (v_pfn_v) IIR_f32_init);
IIR_f32_setCalcFunction(hnd_iir, (v_pfn_v) IIR_f32_calc);
hnd_iir->init(hnd_iir);
}
```
滤波:
```c
float IIR_filter(float x)
{
float y;
out = FLT_MAX;
in = x;
hnd_iir->calc(hnd_iir);
y = out;
return y;
}
```
使用:
```c
interrupt void ISRMcbspRece(void)
{
int value = AIC23_INTPUT_AVG();
if (index % 2 == 0)
{
if (iir_mode == 0)
{
value = (int) IIR_filter((float) value);
}
if (index_rec < DATA_SIZE)
{
aic23_recdata[index_rec++] = value;
} else
{
index_rec = 0;
aic23_recdata[index_rec++] = value;
}
AIC23_OUTPUT(value);
}
LED9_TOGGLE;
index++;
PieCtrlRegs.PIEACK.all = 0x0020;
}
```
## ----2023.11.27 差分方程滤波测试失败
测试翻车,采用差分方程的直接计算在滤波器阶数超过4阶时莫名奇妙的出现正反馈震荡,用不了。
## ----2023.11.23 更换滤波方案,采用差分方程滤波
当使用IIR滤波器时,可以使用直接二阶级联结(Direct Form II)的结构,这是一种常见的实现方式。
主要源代码如下:
1. **滤波器结构:** 该程序采用直接二阶级联结结构,这是IIR滤波器的一种标准实现方式。每个二阶级联结(Second Order Section,SOS)由两个分母系数(a1和a2)和三个分子系数(b0、b1和b2)定义。
2. **缓冲区:** 为了保存输入和输出的历史数据,程序使用了两个缓冲区`xBuffer`和`yBuffer`。`xBuffer`保存输入信号的历史值,`yBuffer`保存输出信号的历史值。
3. **滤波器计算:** 滤波器的计算过程在`iir_filter`函数中完成。对于每个输入`x`,程序首先将其放入输入缓冲区中,并且通过滤波器的差分方程计算对应的输出`y`。然后,将新的输出值存储在输出缓冲区中。
4. **多个SOS的叠加:** 由于存在多个SOS,程序通过循环对它们进行叠加。每个SOS都有自己的系数和缩放因子。循环中的计算将多个SOS的效果叠加,产生最终的滤波效果。
```c
volatile float xBuffer[SOS1_NUM] = {0};
volatile float yBuffer[SOS1_NUM] = {0};
float IIR_filter(float x)
{
int i;
for ( i = SOS1_NUM - 1; i > 0; --i)
{
xBuffer[i] = xBuffer[i - 1];
}
xBuffer[0] = x;
float y = 0;
for ( i = 0; i < SOS1_NUM; ++i)
{
float b0 = iir1_coeffs_B[i * 3];
float b1 = iir1_coeffs_B[i * 3 + 1];
float b2 = iir1_coeffs_B[i * 3 + 2];
float a1 = iir1_coeffs_A[i * 3 + 1];
float a2 = iir1_coeffs_A[i * 3 + 2];
y += b0 * xBuffer[0] + b1 * xBuffer[1] + b2 * xBuffer[2]
- a1 * yBuffer[1] - a2 * yBuffer[2];
}
for ( i = SOS1_NUM - 1; i > 0; --i)
{
yBuffer[i] = yBuffer[i - 1];
}
yBuffer[0] = y;
return y;
}
```
理论性能比组合滤波法快百倍以上,效果未测试,今天累了,不玩了 QAQ
## ----2023.11.23 组合滤波法测试,效率太低,得换方案
本次尝试方法如下:
- 将采集数据按照一定储存深度存储
- 将新数据加入储存组并丢弃组内最早采集的数据
- 进行统一IIR滤波
- 将滤波后序列最后一项输出
测试结果:
- 32k及以上采样率,128个float缓冲,计算超时,单位采样周期内无法实现数据的完全运算
- 8k采样率,128个float缓冲,计算几乎满足要求,但轻微超时,滤波效果OK
- 8k采样率,64个数据缓冲,计算时间小于单个采样周期,滤波效果完美
综合考虑,组合滤波法效率过低,无法满足实时滤波功能要求,源代码未删除,仍放在app/IIR中,可以作为后期屏幕打印波形需求。
## ----2023.11.23 IIR滤波器算法移植
将matlab生成的滤波器成功移植到板子上,并测试滤波效果,经过调试,确定滤波器为50阶椭圆IIR滤波器
### 滤波器参数更新方法:
- 文件/导出
- 设置如下,导出数据到工作区
- 运行c语言文件生成脚本**creat_filter_for_dsp.m**
### 算法移植
源文件:APP/IIR/bsp_iir.c_h
滤波器实现主要依托于ti官方fpu库
**滤波器函数**:
```c
void IIR_filter_run(void)
{
// Locals
uint16_t i;
float in, out;
// Configure the object
IIR_f32_setCoefficientsBPtr(hnd_iir, iir1_coeffs_B);
IIR_f32_setCoefficientsAPtr(hnd_iir, iir1_coeffs_A);
IIR_f32_setDelayLinePtr(hnd_iir, iir1_delayLine);
IIR_f32_setInputPtr(hnd_iir, (float *)&in);
IIR_f32_setOutputPtr(hnd_iir, (float *)&out);
IIR_f32_setScalePtr(hnd_iir, iir1_scaleFactors);
IIR_f32_setOrder(hnd_iir, FILTER_ORDER);
IIR_f32_setInitFunction(hnd_iir, (v_pfn_v)IIR_f32_init);
IIR_f32_setCalcFunction(hnd_iir, (v_pfn_v)IIR_f32_calc);
// Run the initialization function
hnd_iir->init(hnd_iir);
for(i = 0U; i < IIR_SIZE; i++)
{
out = FLT_MAX;
in = IIR_input[i];
// Call the calculation routine
hnd_iir->calc(hnd_iir);
IIR_output[i] = out;
}
}
```
**滤波器测试**:
```c
float amp=1.0,fre=94.0,fs=2000,PI=3.1415;
for(i=0;i<512;i++)
{
IIR_input[i] = amp*sin(2.0*PI*fre/fs*i) + amp*sin(2.0*PI*3.0*fre/fs*i);
}
IIR_filter_run();
```
**测试结果**:
## ----2023.11.20 IIR滤波器MATLAB仿真测试
源文件:./Matlab/*
主要有以下几个部分:
**录音脚本**:recoding.m
- 作用:录取一个5s的wav录音文件,采样率为8000hz,输出文件名为recoding.wav
- 运行效果:
**滤波器设计文件**:iir.fda
- **设计滤波器**:用于仿真时,生成matlab函数可以用于仿真
**滤波测试**:filters.m
读取录制的wav录音文件,播放滤波前后录音以及波形和频谱,效果如下:
## ---- 2023.11.15 音频采样测试完成
SRAM共可存储262,144个16位数据,以44.1k采样率为例,共可采集262144/44100 = 5秒音频,这里采集了1秒数据进行测试
**采集程序:**
麦克风以左右双通道进行采集,实际中断触发次数为采样率的两倍,进行一次软件分频后采集左右通道的平均值作为采样值
```c
volatile unsigned int index = 0;
volatile Uint32 index_rec = 0;
interrupt void ISRMcbspRece(void)
{
int temp = AIC23_INTPUT_AVG();
if (index % 2 == 0)
{
AIC23_OUTPUT(temp);
if(index_rec
## ---- 2023.11.15 外扩SRAM测试,链接文件修改
该核心板外扩SRAM为**IS61LV25616AL**其拥有**4,194,304-bit**=512KB容量,可以存放16bit数据共262,144位。
**非常重要:这SB开发板把XINTF管脚接到LED的D10-D14上去了,所以要使用外扩RAM不能点这几个灯,不然数据会乱飞起**
**数据定义方法:**
```c
#define DATA_SIZE 44100
#pragma DATA_SECTION(<数组名称>, "ZONE7DATA");
volatile <数据类型> <数组名称>[DATA_SIZE];
```
**XINTF初始化:**
先初始化DMA,再初始化XINTF即可,调用方法:
```
DMAInitialize();
init_zone7();
```
**链接文件修改**:
修改重点:在MEMORY分页定义中已对外扩SRAM做分页处理,并通过地址映射表得知SRAM映射在ZONE7(0x200000)上,修改块起始地址为0x200000,大小为0x040000,并将ZONE7DATA指向该分块
```
MEMORY
{
···
PAGE 1:
ZONE7B : origin = 0x200000, length = 0x040000
/* TODO: XINTF zone 7 - data space 262144*16bit 512kbyte */
}
SECTIONS
{
···
ZONE7DATA : > ZONE7B, PAGE = 1
···
}
```
## ---- 2023.11.15 AIC23存在BUG,已修复,并添加采样率控制
文件:APP/AIC23.c
需要哪个采样率将define改成1即可,采样率高的优先
```c
#define SIMPLERATE_8K 0
#define SIMPLERATE_8K021 0
#define SIMPLERATE_32K 0
#define SIMPLERATE_44k1 1
#define SIMPLERATE_48K 0
```
## ----2023.11.13 AIC23编写,并通过收音/放音测试
添加源码:APP/AIC23/*
通过IICa控制AIC23芯片,并通过McBSP(多通道缓冲串口)传输音频数据
提供方法:
```c
void AIC23_OUTPUT_LEFT(int16 value); // 左声道输出
void AIC23_OUTPUT_RIGHT(int16 value); // 右声道输出
void AIC23_OUTPUT(int16 value); // 左右声道同时输出
int16 AIC23_INTPUT_LEFT(); // 左声道输入
int16 AIC23_INTPUT_RIGHT(); // 右声道输入
int16 AIC23_INTPUT_AVG(); // 左右声道输入的平均值
```
初始化过程如下:
```c
//Gpio复用设置
InitI2CGpio();
InitMcbspaGpio();
//初始化I2Ca和Mcbsp
I2CA_Init();
InitMcbspa();
//设置参数AIC23参数
AIC23Write(0x00, 0x00);
DELAY_US(100);
AIC23Write(0x02, 0x00);
DELAY_US(100);
AIC23Write(0x04, 0x7f);
DELAY_US(100);
AIC23Write(0x06, 0x7f);
DELAY_US(100);
AIC23Write(0x08, 0x14);
DELAY_US(100);
AIC23Write(0x0A, 0x00);
DELAY_US(100);
AIC23Write(0x0C, 0x00);
DELAY_US(100);
AIC23Write(0x0E, 0x43);
DELAY_US(100);
AIC23Write(0x10, 0x23);
DELAY_US(100);
AIC23Write(0x12, 0x01);
DELAY_US(100); //AIC23Init
//使能中断
EALLOW; // This is needed to write to EALLOW protected registers
PieVectTable.MRINTA = &ISRMcbspRece;
EDIS; // This is needed to disable write to EALLOW protected registers
PieCtrlRegs.PIECTRL.bit.ENPIE = 1; // Enable the PIE block
PieCtrlRegs.PIEIER6.bit.INTx5 = 1; // Enable PIE Group 6, INT 5
IER |= M_INT6; // Enable CPU INT6
```
## ----2023.11.13 按键蜂鸣器反馈(娱乐向)
## ----2023.10.25 矩阵键盘驱动 ADC采样
### 矩阵键盘:
源码路径:app/key
采用程序分离的方式,将按键的处理封装后放在主函数中,按键处理函数为
```c
/**
* TODO:按键处理程序
*/
void Key_Task()
{
uint8_t ensure = KEY_Scan(0);
switch (ensure)
{
case KEY1_PRESS:
break;
case KEY2_PRESS:
······
default:
}
if (ensure != KEY_UNPRESS)
{
UARTa_printf("KEY Press:%d\n", ensure);
}
}
```
将需要完成的操作放入对应的case中即可
函数:
```c
/**
* 矩阵键盘初始化函数
* 在main.c中PeripheralInit()被调用
*/
void KEY_Init(void);
/**
* 按键扫描
* @param mode 模式:0:点动 1:长按
* @return
*/
char KEY_Scan(char mode);
```
### ADC采样
源码路径:user/simple.c
主体由以下函数和变量构成:
```c
//采样数组,用于存放采样数据,其中adc_sample_size为采样数,在simples.h中定义
volatile uint16_t adc_buff[adc_sample_size];
//索引,在采样中起辅助作用
volatile uint32_t adc_index;
//采样完成标志,当一组数据采样完成之后adc_simple_done==1
volatile uint8_t adc_simple_done;
/**
* 初始化采样参数
* @param rate 采样率
*/
void sample_init(uint32_t rate);
/**
* 单次采样,放在TIM2中断中
*/
void sample_run();
/**
* 开始采样,采样数据将覆盖原有数据
*/
void sample_start();
```
用法:
1.调用sample_start()并等待采样完成之后,标志adc_simple_done置1,在while大循环中调用软中断回调
```c
while (1)
{
if(adc_simple_done)
{
//采样完成软中断,用于处理群采样值
adc_simple_done=0;
adc_simple_done_callback();
}
······
}
```
## ----2023.10.24 新建工程,并完成代码框架搭建
主要工作:
### 1.调通串口打印
UARTa_printf:
```c
void UARTa_printf(const char *format,...);
```
用法与传统printf无异
### 2.定时器软中断实现
通过软件中断代替Delay节省CPU资源和终端资源,注意主要有四个时间间隔的软中断,通过TIM0实现,不要改TIM0的任何配置,否则容易出错
```c
if (bMS1Chg)
{
// 1ms软中断,每1ms运行一次
bMS1Chg = 0;
}
if (bMS10Chg)
{
// 10ms软中断,每10ms运行一次
bMS10Chg = 0;
}
if (bMS100Chg)
{
// 100ms软中断,每100ms运行一次
LED14_TOGGLE;
bMS100Chg = 0;
}
if (bSecChg)
{
// 1s软中断,每1s运行一次
LED13_TOGGLE;
bSecChg = 0;
}
```
### 3.初始化过程封装
封装为两大块
```c
/**
* 系统初始化
*/
void SystemInit()
{
InitSysCtrl();
InitPieCtrl();
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
}
/**
* 外设初始化
*/
void PeripheralInit()
{
LED_Init();
//TIM0设置分频为150倍,计数器频率为1Mhz,中断每1ms触发,用于实现软中断标志位
TIM0_Init(150, 1000);
//SCIA(UARTA) 设置波特率为115200
UARTa_Init(115200);
//打印初始化完成信息
UARTa_printf("初始化完成\n");
UARTa_printf("开始运行\n");
}
```