jinglixixi 发表于 2023-5-17 17:34

【技术分享】基于正点原子开发板的中文菜单音乐播放器实现

本帖最后由 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;                  /* 得到点阵数据 */
      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:", 1);             /* 挂载SD卡 */
    f_mount(fs, "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))
            {
                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))
            {
                break;/* 错误了/到末尾了,退出 */
            }
            res = exfuns_file_type(wavfileinfo->fname);
            if ((res & 0xF0) == 0x40)         /* 取高四位,看看是不是音乐文件 */
            {
                wavoffsettbl = temp;/* 记录索引 */
                curindex++;
            }
      }
    }
    curindex = 0;         /* 从0开始显示 */
    res = f_opendir(&wavdir, (const TCHAR*)"0:/MUSIC"); /* 打开目录 */
    while (res == FR_OK)    /* 打开成功 */
    {
      dir_sdi(&wavdir, wavoffsettbl);   /* 改变当前目录索引 */
      res = f_readdir(&wavdir, wavfileinfo);         /* 读取目录下的一个文件 */
      if ((res != FR_OK) || (wavfileinfo->fname == 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 播放界面
视频演示:https://www.bilibili.com/video/BV1rP411R7TH/?vd_source=f302fc0cc3a0425328db53a3b92082ca

Pretext 发表于 2023-5-18 14:28

视频有点抖动,是直接用手拿着拍摄的?

天天向善 发表于 2023-5-18 14:29

音乐播放,这个功能做的不错!

jinglixixi 发表于 2023-5-18 14:47

Pretext 发表于 2023-5-18 14:28
视频有点抖动,是直接用手拿着拍摄的?

的确是

Pretext 发表于 2023-6-8 15:06

jinglixixi 发表于 2023-5-18 14:47
的确是

弄个支架呀,直接拿手拍多累呀!

jinglixixi 发表于 2023-6-8 16:27

Pretext 发表于 2023-6-8 15:06
弄个支架呀,直接拿手拍多累呀!

有道理,只是有时还要中间调整比较麻烦。

hilahope 发表于 2023-6-10 16:41

如何用stm32实现图片显示和音乐播放同时进行

lihuami 发表于 2023-6-10 17:59

如何用STM32内置ADC采集音频信号

jinglixixi 发表于 2023-6-11 08:55

lihuami 发表于 2023-6-10 17:59
如何用STM32内置ADC采集音频信号

原理上是将麦克采集的模拟信号接到指定的ADC采集通道进行离散化数字处理,然后再进行后续处理(或是存储或是进行DAC转换来播放)

sheflynn 发表于 2023-6-13 21:36

需要进行软件设计,包括界面设计、音乐列表管理、音乐播放控制等方面。由于本例中使用中文菜单,因此需要使用中文字符集来进行界面显示和用户交互。可以采用图形库(如STemWin)来实现GUI界面,也可以使用自行编写的代码来实现。

mnynt121 发表于 2023-6-13 22:16

STM32系列的微控制器,具有强大的处理能力和丰富的外设资源。

vivilyly 发表于 2023-6-13 22:54

方便用户交互,可以设计一个中文菜单,显示当前音乐列表和各种操作选项,并根据用户选择来进行相应的操作。在程序中可以采用图形库来绘制菜单界面,并根据用户输入来更新菜单内容和显示效果。

cemaj 发表于 2023-6-14 17:53

用正点原子的F7核心板或者H7核心板,并配套使用VS1053B音频解码芯片,来实现音乐文件的播放和解码。

mollylawrence 发表于 2023-6-19 08:32

实现过程可能会涉及到很多技术细节和实现难点,如音频解码算法、GUI界面设计、中文字符集处理等方面。

vivilyly 发表于 2023-6-19 14:03

使用Keil或者其他工具编写代码实现中文菜单和音乐播放功能。可以通过TFT LCD屏幕显示中文菜单,通过按键控制菜单选择和音乐播放。

sdlls 发表于 2023-6-19 14:19

怎么用无源蜂鸣器播放音乐            

jinglixixi 发表于 2023-6-20 00:04

sdlls 发表于 2023-6-19 14:19
怎么用无源蜂鸣器播放音乐

无源蜂鸣器只能播放类似于MID类的音乐,是通过改变频率来发出不同的音调。

LOVEEVER 发表于 2023-6-21 10:39

STM32依然很强大,音乐播放调理很清晰
页: [1]
查看完整版本: 【技术分享】基于正点原子开发板的中文菜单音乐播放器实现