在各种基于ARM的作品中,图像显示功能的实现已经十分普遍,但实现音频播放功能的却不多。这里就以NUCLEO_L073为控制核心,介绍一种带中文菜单的MP3点播器,其组成结构如图1所示。 在整体结构中,NUCLEO_L073主要承担的任务有:按键判别处理、菜单显示、控制信号输出、串行指令发送等。 Mini MP3播放模块承担的任务有:接收控制信号、读取SDHC文件、音频变换处理及驱动处理等。该模块共有16个引脚,其外观如图3所示。 (注:在VCC和GND接L073开发板的+5V和GND引脚,ADKEY_1接L073的PC13即B1键的情况下,可简单测试歌曲的播放。在RX和TX分别与L073开发板的TX和RX 的情况下,也测试指令方式播放歌曲 。) MP3播放模块在使用过程中有2种模式,即简单的独立操控模式和基于通讯的指令操控模式。 对于独立操控模式,只需配置几个小按键就可对它进行控制,所这些的操作有播放第一首、下一首、上一首、增加音量、减小音量等。在配置相应电阻的情况下,还可通过电位的变化来实现指定顺序播放。 对于指令操控模式,其操控方式要相对灵活,但需要有串行通讯来配合工作,其通讯的波特率为9600bps。 该模块各引脚的功能如下: 菜单显示是采用0.96’双色OLED显示屏,是一种无需背光的自发光器件。该模块尽管体积小很小,但分辨率却达128*64。对于采用IIC接口方式的OLED,其引脚只有4个,所以很节省GPIO资源。对于双色的OLED显示屏,其上部的1/3为黄色,余下的2/3则为蓝色,其显示效果如图5所示。 为了实现中文菜单的显示,是采用构建小字库的方式完成的,所用的工具为PCtoLCD2002,其界面如图6所示。 在控制信号的发送过程中,即支持GPIO输出开关信号,也支持串口输出的指令信号。为了便于统一管理,在MP3播放模块上并没有直接连接按键,而是通过NUCLEO_L073按使用者的操作来发送信号。 除了硬件方面的设计,要实现相应的功能目标,软件的设计也是不可或缺的。 在显示方面,涉及的功能函数有:OLED 初始化函数、清屏函数、汉字显示函数、字符串显示函数及菜单显示函数等。 对于IIC接口的OLED其初始化函数如下: - // GND GND) A% z& y- D* a+ s& i: o
- // VCC 5V/3.3v. L* K. \& @: W/ r7 Z
- // SCL PA50 o$ S9 x9 C: w8 o7 L1 I
- // SDA PA7- ^( j* Q0 i6 a6 i5 g! j4 y
- #define OLED_MODE 0
# X8 N7 L F" t" F L0 _ - #define SIZE 8
5 Q' m4 Y( R- B/ o9 z. q3 \ - #define XLevelL 0x00
0 O9 {/ ?% ?% a" V: g& C& \! u/ p - #define XLevelH 0x10- L! i' v! u% K- e& y" p! F
- #define Max_Column 128
- ]$ s7 L2 T1 {; l - #define Max_Row 64: N* b0 {1 f5 F: d9 ^$ r+ t
- #define Brightness 0xFF ' h z( C: d* W, h3 i. f- Z
- #define X_WIDTH 128
. `# K( l" f; Q4 k- Z - #define Y_WIDTH 64
- v1 L4 I. \ Y5 v, A - #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET)
# A# Q5 I4 g7 `3 Q5 m - #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET)
! w, Q3 u# P/ _. q9 T5 g( n7 l - void OLED_Init(void)
5 G1 L+ J3 L5 j, v1 | - { , w3 x6 Q6 ~# O5 L. Q1 ^
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); " J) u6 w! W* I G) H* a Y
- GPIO_InitTypeDef GPIO_InitStruct;; x7 ]' Q n. D& F1 i7 q l7 G6 V
- __HAL_RCC_GPIOA_CLK_ENABLE();
" s; E# |: Y- t8 q/ q, [& g% x - GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
# H( @# \0 h. z6 }) m - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;% f6 Q/ S+ ` [/ `# K
- GPIO_InitStruct.Pull = GPIO_PULLUP;
( e$ X+ V- I( E/ {7 o5 u1 ` - GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;# K/ ], r' T2 `1 k( Z
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);# {- K, s( |) p
- OLED_SCLK_Set();. D L3 `% F; W6 a1 M! G
- OLED_SDIN_Set(); - T8 i' r! z$ S
- Delay_1ms(800); //delay_ms(800);
! `, x; U4 k1 V6 x, F* ^1 T( B% | - OLED_WR_Byte(0xAE,OLED_CMD);//--display off" m$ ?, j1 K. G3 n
- OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
; T5 R7 f- a% P1 Z - OLED_WR_Byte(0x10,OLED_CMD);//---set high column address& A2 E# f5 {* K( g' f* b
- OLED_WR_Byte(0x40,OLED_CMD);//--set start line address $ T0 d2 G6 Y2 t
- OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
8 \) e- u3 s5 P4 k3 | - OLED_WR_Byte(0x81,OLED_CMD); // contract control2 c1 e3 O; `5 [$ O
- OLED_WR_Byte(0xFF,OLED_CMD);//--128 6 B% o& n" d/ _) M3 c
- OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap $ a4 n D) h. M0 d) k- w
- OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse' ^9 m6 Z: b( g
- OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)) N1 Q- K: t- c- k6 g2 ]3 s1 k
- OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty# o/ ~5 A) Z" z( a% k# ^
- OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
) }- U+ s3 N9 T" X) A - OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset1 T4 U& \- _! P6 r3 W$ ^
- OLED_WR_Byte(0x00,OLED_CMD);//
5 a2 S, m5 H+ H7 S - OLED_WR_Byte(0xD5,OLED_CMD);//set osc division: \+ N! V3 f2 o
- OLED_WR_Byte(0x80,OLED_CMD);//
2 X8 f. ?2 ?( g# N3 b# w - OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
& d, K! d/ H( \7 z9 H# K" h. _ - OLED_WR_Byte(0x05,OLED_CMD);//
5 {$ Q& w' `6 n5 Y! _ - OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period+ I& @- i, s# g; `" Z, ^
- OLED_WR_Byte(0xF1,OLED_CMD);//
$ m- `3 K8 s. x4 L9 s' b+ `9 B- m - OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
* o: J8 C+ s/ K! a4 z0 c - OLED_WR_Byte(0x12,OLED_CMD);//! [' q T S# N1 W
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh9 q& x, E) X5 ]1 S$ S; r
- OLED_WR_Byte(0x30,OLED_CMD);//
# u' d. N# r/ |1 n3 n3 U2 Q - OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable; h) _) Q: c, `2 }2 E: Y
- OLED_WR_Byte(0x14,OLED_CMD);//$ v- b; t& U6 B0 ?! ~5 ?: k
- OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
, A+ y9 }/ z- [! Y9 z& y - }
复制代码清屏函数如下: - void OLED_Clear(void)
) o% b' b' L6 m' r* t2 _" M - { 3 ?, I' L! |. A9 q+ e9 |
- unsigned char i,n;
, Z: g9 L" ]- {/ o7 U2 T - for(i=0;i<8;i++)
. r! ]3 O) M5 y" ? - {
: h8 J" l0 d' z/ J8 v' n q - OLED_WR_Byte (0xb0+i,OLED_CMD); " E8 j! d7 P8 u, E8 e
- OLED_WR_Byte (0x00,OLED_CMD);
3 E3 ~- O2 g( k' e - OLED_WR_Byte (0x10,OLED_CMD); F% r( [9 ]. `, ?& E
- for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); , h6 X: R8 e( @- h# W
- }
% q2 r5 ]- G! J5 n% _ - }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)
* p8 t3 r% ^# D; {4 p - {
$ t/ a% ]" i2 O8 ~; Z8 l- u1 {2 j - unsigned char j=0;
7 i( b# h& V/ O/ U2 I# i+ Z - while (chr[j]!='\0')
7 E+ M1 J2 A u, U - { OLED_ShowChar(x,y,chr[j],Char_Size);) ^; J, q& G. j ~$ C) @
- x+=8;% \- X9 Z0 q1 n% ~
- if(x>120){x=0;y+=2;}7 q8 X. s" d7 {) o
- j++;1 n3 B ?# s, Z" O% V
- }/ `0 `( {! c" x
- }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)0 d& r0 V" X( c" V5 J- R& g0 Y8 {
- {
) r7 U, W# g2 Q4 r! U$ f8 w: W, ~ - unsigned char t,adder=0;
# G! W1 S: G' S' C j. w4 r - OLED_Set_Pos(x,y);
) S0 o& J: w/ u. K& @ b* P - for(t=0;t<16;t++)
) M. _2 _6 k6 w% q' ?) N5 \$ W _* ~* b - {/ {& f0 W; {2 P5 Z7 z- R
- OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);& p* B7 c" F, x! Z; e
- adder+=1;( w& ~' y% i" i6 g9 ~. p6 Q
- }
- V1 @5 C a7 J m - OLED_Set_Pos(x,y+1); 6 C1 e! s3 t/ J q
- for(t=0;t<16;t++)7 @' g9 O5 l5 f1 e4 T- X `8 F
- {
" G8 u6 {5 m# U - OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
. k0 H$ j9 N7 L5 H: G - adder+=1;
) v& S4 _. [5 n/ H% h+ k% D& ~: z3 F - } / z3 V* U& g# X/ `
- }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()
" T K' V' ~& d# G- e0 Q - { // 莫斯科郊外的晚上1 w4 h% K& }: l6 J
- OLED_Clearp(); // 局部清除3 V/ |' M# G0 D0 f) U
- OLED_ShowCHinese(18,2,3); , Q8 d( V3 J* Q3 j5 q# B3 Q2 [
- OLED_ShowCHinese(36,2,4);
6 z7 Y% ~$ u- h( ?/ B- ?8 K - OLED_ShowCHinese(54,2,5);
1 q4 X! J8 ?9 I - OLED_ShowCHinese(72,2,6); 1 V. R! z# T- |4 P9 x; a. o
- OLED_ShowCHinese(90,2,7);
5 W% v" R% ]9 [' Z. Q. ` - OLED_ShowCHinese(108,2,8);
) v9 K2 \$ J) r+ f0 J4 z) Q - OLED_ShowCHinese(18,4,9); , \6 ]& B: {7 @! H4 j' j O( v& a
- OLED_ShowCHinese(36,4,10); ' ]4 m) z$ p2 E) a- Z2 T( A x9 s
- }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择. C0 T* ^6 U5 F3 ^+ A# Q; {
- while(f)
3 H# w( g: S' k5 Y# h& | - {' S' r. g/ h# O5 I
- if(i==0) cd1(); // 雪绒花
$ i9 [8 Q1 o4 i0 _7 ] - if(i==1) cd2(); // 莫斯科郊外的晚上 ' o% Q+ O8 I |3 p; Q
- if(i==2) cd3(); // 等待) t U( z! K2 h5 X
- if(i==3) cd4(); // 鸿雁
9 `' r5 q$ {" h! m4 ~+ \; L8 C+ A - if(i==4) cd5(); // 贝加尔湖畔
( \; c7 p& R& m; L - if(i==5) cd6(); // 春暖花开4 F& [+ k, k( d X- P
- if(i==6) cd7(); // 传奇, [$ {/ q5 I4 `: g a
- if(i==7) cd8(); // 她6 E) e3 x5 Y C8 X p
- if(i==8) cd9(); // 味道( e9 `% `( e+ `9 I0 N
- if(i==9) cd10(); // 放心去飞# B$ Q3 G t, I) k
- cdm(); // 功能键提示栏显示
9 j/ u& ~; Y- y4 ?$ g - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);
( d0 `/ R3 g b6 g' _ - HAL_Delay(6000); // 长短按区分延时
: ~3 C+ o& x/ z5 x' x' {: _ - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;* L5 W. V9 ~( m/ K3 j0 [+ o! x+ T- C
- else. ], w& _/ t; m* e1 F. M
- {
' {9 z1 a* [ u9 z; I - i++;
2 ~# a" r9 }9 O1 h - if(i>9) i=0; // 轮回处理
) ?4 @7 [. k6 C- _, w5 O7 v% K - }
) c# T' x0 f5 r0 l - }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏
- V0 v# R) y6 F7 r - f=1;2 B4 Q. @3 y& A6 T B
- while(f)& m3 G# a5 @0 P6 p2 k% @8 {9 \
- {$ H' t- ]; H0 y s1 c
- while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0), L8 I6 j& P p4 c2 I g/ |
- { # z. ?$ T" j$ K+ z2 b; u
- // 产生当前功能选择项闪动效果! G1 H) P5 X* m$ P: Y# H: @' V
- OLED_ShowCHinese(i*18,6,46); ' a1 s' ~5 j; x, N3 ~' j
- HAL_Delay(200); 2 M% x4 y' i. U1 X4 r$ X
- cdm();5 {8 Y5 [1 {0 W9 |, H
- HAL_Delay(200);
( J; l3 ]% `9 W% M. W* r+ ^& r- y - }: S( a ^1 a) x4 y
- HAL_Delay(700);1 [7 T5 t* E3 P
- // 长短按区分处理
: R8 D' ^6 n. o0 w& N. d - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;: J1 z9 z& F) W3 E( r
- i++;# `7 M7 L9 x5 q) }- e! x9 F5 k
- if(i>5) i=0; // 功能选择轮回处理
5 I' X' p& h1 M2 Z3 ~; b9 e - HAL_Delay(500);
3 w7 k$ M5 Q' O0 k - }
复制代码在指令控制的实现上,主要涉及串行通讯的初始化、指令生成与输出等函数。测试过程中,可通过串口助手来验证相应的指令功能。 常用的指令有: 播放_7E FF 06 0D 00 00 00 FE EE EF 暂停_7E FF 06 0E 00 00 00 FE ED EF 下一首_7E FF 06 01 00 00 00 FE FA EF 上一首_7E FF 06 02 00 00 00 FE F9 EF 播放的代码如下: - void play() : e, k5 ?5 e) ~' V8 b# S
- {) F* u9 p, q8 p8 I% U+ J; |' D0 r& R
- // 定义指令内容! Z, f9 f& z4 p( h5 G3 Q8 O
- unsigned cha play_cmd [10] =
4 P9 q! Z2 ^. N1 @ - { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};9 z* j" Z1 N, ?: G: R
- sendCmd (play_cmd);7 T" ]; g% z9 B. B6 }/ [/ a1 X0 f, Y
- }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟! 7 N; k7 @/ {5 S# q0 ?6 q
|
unsigned cha play_cmd [10] = 6 _* l% m# {8 _4 o, }$ p; r
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};8 M& t# \ j/ l- I
sendCmd (play_cmd);2 ]) u l" t! n. X
}8 f) D. t/ U, K( b/ [$ _# {
$ `& z, q% G, o. u& }
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试