前段时间,和小伙伴们一起基于STMF407的核心板,做了一个录音装置。便来回顾一下这个过程,针对过程中的问题,做点笔记。
以下是录音装置基本要求:
(1)语音信号滤波器:通带为300Hz~3.4kHz;
(2)ADC:采样频率fs ≥16kHz,字长12位;
(3)语音存储时间≥20秒;
(4)DAC:变换频率fc ≥16kHz,字长12位;
(5)回放语音质量良好。
(6)增加音量和播放控制功能;
(7)二级菜单界面,并显示播放和录音时间。
我们遇到的第一个问题是关于F407片内Flash的写入速度。写入太慢,采样率低且可能出现断续录音。
起初呢,我们一直用的正点原子探索者F407的库,一直也用的挺好。但由于库对于Flash的读写,正点原子为了保证每次都可以写到任意一个块(4字节的空间),所以在每次写入之前都会计算并擦除一次块所在的扇区,浪费了很多时间。(NandFlash 数据写入时只能写0,不能写1 所以写入前须擦除,保证写入前的位都为1。而且NandFlash擦除一次要擦一个扇区,读取一次读取一个块字节(此处是4个字节))。
- 库函数里的写入前擦除操作
//从指定地址开始写入指定长度的数据
//特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
// 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
// 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
// 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.
//该函数对OTP区域也有效!可以用来写OTP区!
//OTP区域地址范围:0X1FFF7800~0X1FFF7A0F
//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.)
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
{
.......
addrx=WriteAddr; //写入的起始地址
endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!
{
while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
{
if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
{
status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!!
if(status!=FLASH_COMPLETE)break; //发生错误了
}else addrx+=4;
}
}
.......
}
//获取某个地址所在的flash扇区
//addr:flash地址
//返回值:0~11,即addr所在的扇区
uint16_t STMFLASH_GetFlashSector(u32 addr)
{
if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
return FLASH_Sector_11;
}
为了解决这种写入慢的问题,我们采用录音前初始化Flash的方式,将擦除时间集中在数据采集前,保证写入过程时无需擦除,提高写入速度。
Flash部分代码过程如下:
//Flash 扇区擦除,屏蔽部分是避免擦除前3个扇区,导致用户代码也被擦除。可以根据生成的Hex文件大小而定
u8 STMFLASH_erase(u8 lock_flag)//lock_flag 上锁标志,为真上锁
{
FLASH_Status status = FLASH_COMPLETE;
FLASH_Unlock(); //解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
// status = FLASH_EraseSector(FLASH_Sector_0, VoltageRange_3); //VCC=2.7~3.6V之间!!
// if(status != FLASH_COMPLETE) return 1; //发生错误了
// status = FLASH_EraseSector(FLASH_Sector_1, VoltageRange_3); //VCC=2.7~3.6V之间!!
// if(status != FLASH_COMPLETE) return 1; //发生错误了
// status = FLASH_EraseSector(FLASH_Sector_2, VoltageRange_3); //VCC=2.7~3.6V之间!!
// if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_3, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_6, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_7, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_8, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_9, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_10, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
status = FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3); //VCC=2.7~3.6V之间!!
if(status != FLASH_COMPLETE) return 1; //发生错误了
FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
if(lock_flag) FLASH_Lock(); //上锁
return 0;
}
//数据写入函数
int STMFLASH_Write(u32 WriteAddr, u32 *pBuffer, u32 NumToWrite)
{
u8 Flag = 0;
u32 ppBuffer = 0;
FLASH_Status status = FLASH_COMPLETE;
u32 startaddr = Romaddr + WriteAddr;
endaddr = startaddr + NumToWrite * 4; //写入的结束地址
if(startaddr < STM32_FLASH_BASE || startaddr % 4) return 1; //非法地址
if(endaddr >= 0x080fffff) {WADDR_staus = full ; return 2;}
FLASH_Unlock(); //解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
if(status == FLASH_COMPLETE)
{
while(WriteAddr+Romaddr < endaddr) //写数据
{
if(FLASH_ProgramWord(WriteAddr+Romaddr, *(pBuffer+ppBuffer)) != FLASH_COMPLETE) //写入数据
{
Flag = 1;
break; //写入异常
}
Romaddr += 4;
ppBuffer++;
ppBuffer = ppBuffer%NumToWrite;
}
}
FLASH_Lock();//上锁
if(Flag) return 3;
else return 0;
}
效果对比:
修改前
写入100个32bit数据,耗时:218ms.
修改后
Flash 擦除扇区 耗时约10s
写入100个32bit数据,耗时:1.48ms 14.8us/32bits
- 第二个问题是尖端脉冲
如图,每隔10ms会间隔出现一个尖端脉冲现象。
问题分析:可能是由于程序中开了两个定时器,定时器的中断级别造成的。
定时器4 用于控制录音和播放的采样率,为达到16kHz的采样率,定时间隔60us
定时器5 用于控制播放和录音时间,并刷新界面。
TIM4_Init(60 - 1, 84 - 1);//84M 60us
TIM5_Init(1000-1, 840 - 1);//84M 10ms
调整了定时器级别如下:
void TIM3_Init(u16 arr, u16 psc)
{
.......
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
.......
}
void TIM5_Init(u16 arr, u16 psc)
{
.......
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
.......
}
问题依然存在;
猜想可能是因为定时器冲突。
解决:采用屏蔽定时器5的方式,只采用定时器3. 控制时间对60us进行计数得到。但问题依然未解决。
于是直接屏蔽了时间显示模块,问题解决,
猜想时间模块出现问题,逐渐屏蔽发现
void time_show(void)
{
Data_ShowNum(90,200,time_s,Red,White,4);
LCD_ShowString(110,200,":",Black,White);
Data_ShowNum(120,200,time_ms,Red,White,4);
LCD_ShowString(150,200," s",Black,White);
}
此函数中的Data_ShowNum();屏蔽后问题消失,得出Data_ShowNum()函数有问题;函数内部如下
void Data_ShowNum(u8 x, u8 y, u32 Num, u16 PenColor, u16 BackColor, u8 P_Index)
{
u8 Num_Temp[15], i = 1, j = 0;
u8 count, x_state;
u8 text[9];
text[0] = 0X2D;
count = Data_Len(Num);
while(i <= count)
{
Num_Temp[i] = Num % 10 + 48;
Num /= 10;
i++;
}
i--;
x_state = x + 10 * P_Index;
while(i)
{
if(PenColor == Black | P_Index != j)
LCD_ShowChar(x, y, Num_Temp[i], 16, PenColor, BackColor);
if(PenColor == Blue)
LCD_ShowCharString(x_state, y, &text[0], White, PenColor);
x += 10;
j++;
i--;
}
}
由于未发现Data_ShowNum函数与10ms的关系,无法定位问题。
所以我们换了显示函数,采取如下显示的方式
void time_show(void)
{
u8 display_s[10];
u8 display_ms[10];
sprintf(display_s,"%d",time_s);
LCD_ShowString(90,200,display_s,Black,White);
LCD_ShowString(110,200,":",Black,White);
sprintf(display_ms,"%d",time_ms);
LCD_ShowString(120,200,display_ms,Black,White);
LCD_ShowString(150,200," s",Black,White);
// Data_ShowNum(90,200,time_min,Red,White,4);
// LCD_ShowString(110,200,":",Black,White);
// Data_ShowNum(90,200,time_s,Red,White,4);
// Data_ShowNum(120,200,time_ms,Red,White,4);
}
问题得到解决;波形如下
对于Data_ShowNum()函数的原因,还在查找中,找到后会继续补充。
完!