本帖最后由 jinglixixi 于 2023-5-17 18:48 编辑
#申请原创# @21小跑堂 正点原子开发板以外设丰富而著称,基本可以全部支撑起多媒体方面的应用,如数码相框功能(已在前面介绍过)、视频播放功能、文本阅读器功能及音频播放功能等。 这次,就利用这些外设来实现一个具有中文菜单的音乐播放器,所涉及的主要外设为:I2S语音播放电路及字库存储电路,参见图1和图2所示。 图1 I2S语音播放电路 图2 字库存储电路
这里之所以使用闪存W25Q128来构建字库是因为,它的通用性更强,可以直接在编程时就以掌握来设计菜单。而在以往,要设计中文菜单,均是采用构建专用小字库的方式来实现。这就导致在输出汉字时,多是以汉字在小子库中的次序编号来调用字模,故通用性极差。
在W25Q128中,其字模是按列行式来取模的,见图3所示。此外,它共含有3种字库,即GBK12、GBK16及GBK24。 图3 取模方式
此外,为了便于中西为的混排,其文字显示函数为: void text_show_font(uint16_t x, uint16_t y, uint8_t *font, uint8_t size, uint8_t mode, uint16_t color)
{
uint8_t temp, t, t1;
uint16_t y0 = y;
uint8_t *dzk;
uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size);
/* 得到状态一个字符对应点阵集所占的字节数 */
if (size != 12 && size != 16 && size != 24 && size != 32)
{
return; /*不支持的size */
}
dzk = mymalloc(SRAMIN, size); /* 申请内存 */
if (dzk == 0) return; /* 内存不足 */
text_get_hz_mat(font, dzk, size); /* 得到相应读写的点阵数据 */
for (t = 0; t < csize; t++) // 一个字符
{
temp = dzk[t]; /* 得到点阵数据 */
for (t1 = 0; t1 < 8; t1++) // 一个字节
{
if (temp & 0x80) // 1000 0000 从左到右
{
//lcd_draw_point(x, y, color); /* 画需要显示的点 */
ili9341_draw_pixel(color, x, y);
}
else if (mode == 0) /* 如非叠加模式,则不需要显示的点,用背景色填充 */
{
//lcd_draw_point(x, y, g_back_color); /* 填充背景色 */
ili9341_draw_pixel(g_back_color, x, y);
}
temp <<= 1;
y++; //由上至下
if ((y - y0) == size)
{
y = y0;
x++; //换列 --->存放形式:列行式
break;
}
}
}
myfree(SRAMIN, dzk); /* 释放内存 */
}
由于本人所所用的显示屏为MDM-2802显示模块,故对原显示函数中的画点函数进行了相应地替换,并注释掉了原来的函数。 图4 中文播放界面
要实现图4所示的中文播放界面,其主程序如下: int main(void)
{
uint8_t key;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口1初始化为115200 */
usmart_dev.init(84); /* 初始化USMART */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
sram_init(); /* SRAM初始化 */
norflash_init(); /* 初始化NORFLASH */
LCD_Init();
delay_ms(10);
ili9341_init();
ili9341_clear(RED);
BACK_COLOR=RED;
POINT_COLOR=YELLOW;
LCD_DrawLine(WHITE ,0, 35, 239, 35);
LCD_DrawLine(WHITE ,0, 275, 239, 275);
my_mem_init(SRAMIN); /* 初始化内部SRAM内存池 */
my_mem_init(SRAMEX); /* 初始化外部SRAM内存池 */
while (sd_init()) /* 检测SD卡 */
{
LCD_ShowString(30,50,"SD Card Failed!");
delay_ms(200);
delay_ms(200);
}
exfuns_init(); /* 为fatfs相关变量申请内存 */
f_mount(fs[0], "0:", 1); /* 挂载SD卡 */
f_mount(fs[1], "1:", 1); /* 挂载FLASH */
while (fonts_init()) /* 检查字库 */
{
LCD_ShowString(30,50,"Font Error!");
delay_ms(200);
}
es8388_init(); /* ES8388初始化 */
es8388_adda_cfg(1, 0); /* 开启DAC关闭ADC */
es8388_output_cfg(1, 1); /* DAC选择通道输出 */
es8388_hpvol_set(25); /* 设置耳机音量 */
es8388_spkvol_set(26); /* 设置喇叭音量 */
g_back_color=RED;
text_show_string(30, 13, 200, 16, "中文菜单音乐播放器", 16, 0, YELLOW);
POINT_COLOR=WHITE;
text_show_string(30, 50, 200, 16, "1 - 最美的期待", 16, 0, WHITE);
text_show_string(30, 70, 200, 16, "2 - 野百合也有春天", 16, 0, WHITE);
text_show_string(30, 90, 200, 16, "3 - 光阴的故事", 16, 0, WHITE);
text_show_string(30, 110, 200, 16, "4 - 莫斯科郊外的晚上", 16, 0, WHITE);
text_show_string(30, 130, 200, 16, "5 - 东方之珠", 16, 0, WHITE);
text_show_string(30, 150, 200, 16, "6 - 光辉岁月", 16, 0, WHITE);
text_show_string(30, 170, 200, 16, "7 - 同桌的你", 16, 0, WHITE);
text_show_string(30, 190, 200, 16, "8 - 亚洲雄风", 16, 0, WHITE);
text_show_string(30, 210, 200, 16, "9 - 咖啡屋", 16, 0, WHITE);
text_show_string(30, 230, 200, 16, "10- 军港之夜", 16, 0, WHITE);
POINT_COLOR=YELLOW;
text_show_string(30, 280, 200, 16, "KEY0:下一首 KEY2:上一首", 16, 0, YELLOW);
text_show_string(30, 300, 200, 16, "KEY_UP:暂停/播放", 16, 0, YELLOW);
POINT_COLOR=WHITE;
key = key_scan(0);
while(key != KEY1_PRES)
{
key = key_scan(0);
}
while (1)
{
audio_play(); /* 播放音乐 */
}
}
有了可以进行播放功能的设计了,其中标识当前播放曲目的函数为: uint8_t audio_play_song(char* fname)
{
uint8_t res;
res = exfuns_file_type(fname);
switch (res)
{
case T_WAV:
POINT_COLOR=0xF800;
text_show_string(12, 50+(np*20), 200, 16, "*", 16, 0, 0xF800);
POINT_COLOR=0xFFE0;
text_show_string(12, 50+(n*20), 200, 16, "*", 16, 0, 0xFFE0);
np=n;
res = wav_play_song(fname);
break;
default:
res = KEY0_PRES;
break;
}
return res;
}
而获取相应音频播放文件的函数为: uint16_t audio_get_tnum(char *path)
{
uint8_t res;
uint16_t rval = 0;
DIR tdir; /* 临时目录 */
FILINFO* tfileinfo; /* 临时文件信息 */
tfileinfo = (FILINFO*)mymalloc(SRAMIN, sizeof(FILINFO)); /* 申请内存 */
res = f_opendir(&tdir, (const TCHAR*)path); /* 打开目录 */
if ((res == FR_OK) && tfileinfo)
{
while (1) /* 查询总的有效文件数 */
{
res = f_readdir(&tdir, tfileinfo); /* 读取目录下的一个文件 */
if ((res != FR_OK) || (tfileinfo->fname[0] == 0))
{
break; /* 错误了/到末尾了,退出 */
}
res = exfuns_file_type(tfileinfo->fname);
if ((res & 0xF0) == 0x40) /* 取高四位,看看是不是音乐文件 */
{
rval++; /* 有效文件数增加1 */
}
}
}
myfree(SRAMIN, tfileinfo); /* 释放内存 */
return rval;
}
实现音频播放与乐曲选择的功能函数为: void audio_play(void)
{
uint8_t res;
DIR wavdir; /* 目录 */
FILINFO *wavfileinfo; /* 文件信息 */
char *pname; /* 带路径的文件名 */
uint16_t totwavnum; /* 音乐文件总数 */
uint16_t curindex; /*当前索引 */
uint8_t key; /* 键值 */
uint32_t temp;
uint32_t *wavoffsettbl; /* 音乐offset索引表 */
es8388_adda_cfg(1, 0); /* 开启DAC关闭ADC */
es8388_output_cfg(1, 1); /* DAC选择通道1输出 */
while (f_opendir(&wavdir, "0:/MUSIC")) /* 打开音乐文件夹 */
{
LCD_ShowString(30,190,"MUSIC ERROR");
delay_ms(200);
delay_ms(200);
}
totwavnum = audio_get_tnum("0:/MUSIC"); /* 得到总有效文件数 */
while (totwavnum == NULL) /* 音乐文件总数为0 */
{
LCD_ShowString(30,190,"NO MUSIC !");
delay_ms(200);
delay_ms(200);
}
wavfileinfo = (FILINFO*)mymalloc(SRAMIN, sizeof(FILINFO)); /* 申请内存 */
pname = mymalloc(SRAMIN, FF_MAX_LFN * 2 + 1); /* 为带路径的文件名分配内存 */
wavoffsettbl = mymalloc(SRAMIN, 4 * totwavnum); /* 申请4*totwavnum个字节的内存,用于存放音乐文件off block索引 */
while (!wavfileinfo || !pname || !wavoffsettbl) /* 内存分配出错 */
{
LCD_ShowString(30,190,"memory Failed");
delay_ms(200);
delay_ms(200);
}
/* 记录索引 */
res = f_opendir(&wavdir, "0:/MUSIC"); /* 打开目录 */
if (res == FR_OK)
{
curindex = 0; /* 当前索引为0 */
while (1) /* 全部查询一遍 */
{
temp = wavdir.dptr; /* 记录当前index */
res = f_readdir(&wavdir, wavfileinfo); /* 读取目录下的一个文件 */
if ((res != FR_OK) || (wavfileinfo->fname[0] == 0))
{
break; /* 错误了/到末尾了,退出 */
}
res = exfuns_file_type(wavfileinfo->fname);
if ((res & 0xF0) == 0x40) /* 取高四位,看看是不是音乐文件 */
{
wavoffsettbl[curindex] = temp; /* 记录索引 */
curindex++;
}
}
}
curindex = 0; /* 从0开始显示 */
res = f_opendir(&wavdir, (const TCHAR*)"0:/MUSIC"); /* 打开目录 */
while (res == FR_OK) /* 打开成功 */
{
dir_sdi(&wavdir, wavoffsettbl[curindex]); /* 改变当前目录索引 */
res = f_readdir(&wavdir, wavfileinfo); /* 读取目录下的一个文件 */
if ((res != FR_OK) || (wavfileinfo->fname[0] == 0))
{
break; /* 错误了/到末尾了,退出 */
}
strcpy((char*)pname, "0:/MUSIC/"); /* 复制路径(目录) */
strcat((char*)pname, (const char*)wavfileinfo->fname); /* 将文件名接在后面 */
audio_index_show(curindex + 1, totwavnum);
key = audio_play_song(pname); /* 播放这个音频文件 */
if (key == KEY2_PRES) /* 上一曲 */
{
if (curindex)
{
curindex--;
}
else
{
curindex = totwavnum - 1;
}
}
else if (key == KEY0_PRES) /* 下一曲 */
{
curindex++;
if (curindex >= totwavnum)
{
curindex = 0; /* 到末尾的时候,自动从头开始 */
}
}
else
{
break; /* 产生了错误 */
}
n=curindex;
}
myfree(SRAMIN, wavfileinfo); /* 释放内存 */
myfree(SRAMIN, pname); /* 释放内存 */
myfree(SRAMIN, wavoffsettbl); /* 释放内存 */
}
其中:为板载4个按键所赋予的功能为: KEY1:控制进入播放功能 KEY0:播放下一首 KEY2:播放前一首 KEY_UP:暂停与恢复播放
经程序的编译与下载,其播放界面如图5所示。 图5 播放界面
视频演示:
|
旧板不吃灰,稍加改造实现中文菜单音乐播放器,代码设计合理,界面简洁,附带演示视频,完成度较好,继续加油!