在各种基于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
8 \* A+ M( d; W7 V* C - // VCC 5V/3.3v3 g2 t7 T, x7 K
- // SCL PA59 K6 r- | t; o% \+ c! J/ A% A* O
- // SDA PA7
7 Q2 V- {/ J. a# ? - #define OLED_MODE 0
7 J3 {1 m& F& P% | H& K4 | - #define SIZE 8* Y# o4 f. S- t s' j( p7 _$ Y. `% Z
- #define XLevelL 0x00
) g0 h4 C7 {% e0 ] - #define XLevelH 0x10
' r4 a+ J$ r7 y' _4 e - #define Max_Column 1280 N3 J6 u( y% C
- #define Max_Row 64
+ X& t$ m3 g& Y: R - #define Brightness 0xFF
- q! c% q/ j+ ]$ R1 r - #define X_WIDTH 128
Z8 H6 @* T! [' X - #define Y_WIDTH 64" ~4 n0 u7 q( Z9 C# e
- #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) 8 h8 N0 d9 @ |2 n _# w$ X: m/ r
- #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) . i7 w1 y+ V+ {- I4 d
- void OLED_Init(void)5 f D" m0 Q6 f! l
- { 2 k: Y: I4 z' p" L* y- M# j
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
9 a- e, Y$ Z5 @, |1 `$ S - GPIO_InitTypeDef GPIO_InitStruct;
$ G: S! }) s6 t7 c' L: i; \ - __HAL_RCC_GPIOA_CLK_ENABLE();
7 i' v5 n0 n$ p" X; [6 Z; l - GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7; $ }7 G( r6 [3 H5 O* e% n3 b# c
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
0 t1 L; {5 D# f9 _: k o# Y1 Z3 G - GPIO_InitStruct.Pull = GPIO_PULLUP;
/ h% y* v, V2 X C; d$ | - GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;7 A: g# F% H0 P. G, {% {
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);$ b6 U9 y& I/ Q2 Q
- OLED_SCLK_Set();
- Z8 h- l7 Z2 A0 T+ q7 C - OLED_SDIN_Set();
" h, ~# j3 Q, S) R" T2 t# F - Delay_1ms(800); //delay_ms(800);! b$ L. E2 W* L% o5 N
- OLED_WR_Byte(0xAE,OLED_CMD);//--display off, D" z4 N& B6 a. D; l9 q4 q
- OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
) w7 x3 l7 ^7 I# T& S. q - OLED_WR_Byte(0x10,OLED_CMD);//---set high column address: L4 P: d& ]" o1 _) _. ~+ Q6 w+ }
- OLED_WR_Byte(0x40,OLED_CMD);//--set start line address
4 t. a6 `4 |& S9 C; u - OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
8 T" i2 f* F0 S - OLED_WR_Byte(0x81,OLED_CMD); // contract control( k/ ?9 L8 ]" y! m0 v3 k
- OLED_WR_Byte(0xFF,OLED_CMD);//--128 $ ]5 T5 k& r% N# N) `+ z6 U* \" D
- OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
$ N/ @+ x' b" t" t5 ~& C: t - OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
( n5 D7 K2 k8 a" d/ l- V$ g( z - OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)' [3 R* Z- K; B4 F
- OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty( z6 q, H# H% x
- OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
1 u! Q* J A1 r( ~7 x - OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset" r, [+ b: z( [; e/ W, ~! n
- OLED_WR_Byte(0x00,OLED_CMD);//. U9 o4 O; J( x2 _* ~# u' X3 |0 r
- OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
9 t. ~ [+ R) c* k S# m - OLED_WR_Byte(0x80,OLED_CMD);//8 T) H: V, V3 m
- OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
: S& }# m. ^$ ^! T/ D - OLED_WR_Byte(0x05,OLED_CMD);//
. T2 u+ X/ E' b. E7 B7 F; C, e, K - OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period2 y9 L# q# E& ]7 U5 S
- OLED_WR_Byte(0xF1,OLED_CMD);//
( A X- O5 b: u3 U, W - OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion1 V2 q0 Q7 Z5 P8 l
- OLED_WR_Byte(0x12,OLED_CMD);//* \- w' u. }& z
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
, f2 P% o {9 K, u6 U/ S' r - OLED_WR_Byte(0x30,OLED_CMD);//& J4 R8 b& x0 I8 |
- OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
, u# X+ T0 r# V" m - OLED_WR_Byte(0x14,OLED_CMD);//
- J8 C s) E0 i* P% ] - OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
' @2 X2 U3 r+ x0 d1 J) f- L6 [' a - }
复制代码清屏函数如下: - void OLED_Clear(void) ) S, b' X8 K1 \- q9 m+ K' u2 [
- { 7 X" j% W" z% T5 l$ M( Y* _
- unsigned char i,n;
`, ~* c( a" T; R4 v7 W - for(i=0;i<8;i++) @3 w# ^+ a& K9 ^0 t% ?# `0 [
- { " b* X2 _! v' M0 X5 ?3 p
- OLED_WR_Byte (0xb0+i,OLED_CMD); , ]5 G: `7 |* D9 ~8 ]: W
- OLED_WR_Byte (0x00,OLED_CMD);
1 o9 Q8 _, @' }9 Z" K& \ - OLED_WR_Byte (0x10,OLED_CMD);
6 z# F8 W5 p$ d4 s - for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
8 [- U6 R9 ^. p9 H! L6 v! e - }
- N4 r' ?! j1 c: O# E" B5 G* W - }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)4 C8 q: b& D U
- {1 b' D* N4 B f
- unsigned char j=0;
1 l' M( _' ]; f9 i# ?; i - while (chr[j]!='\0')
" E5 e0 n7 ^$ y, I - { OLED_ShowChar(x,y,chr[j],Char_Size);* `5 I5 s7 y2 T v$ Q
- x+=8;/ w2 T( ]: a; O+ `8 x7 K
- if(x>120){x=0;y+=2;}
* Y) a" {, s: b* a/ B - j++;1 a" b4 K; y$ S
- }- l8 W. X2 ^7 C, @
- }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)
0 H; {5 @$ ]2 l& B" ] - { ) x3 O9 G( @5 c, D+ n6 v* T
- unsigned char t,adder=0;
& J- h u) p. I5 R( b5 T - OLED_Set_Pos(x,y); ; p( P7 F1 @& _" s Q' Y& j: q
- for(t=0;t<16;t++)
9 i9 B3 t! q* E$ \ - {
9 p- s6 F2 }. ], N - OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
( ^; L7 e# e- g5 ?# J- ^ - adder+=1; q+ w' _9 {6 o; p; [
- } & X( U- a8 P& f/ U* `2 q* _; h
- OLED_Set_Pos(x,y+1);
8 x# \5 _* I- L3 m, Y1 b: @0 O - for(t=0;t<16;t++)5 v3 |8 U4 C4 U) K" v$ I2 A9 X$ U" D
- { X b# h8 `0 ?+ [
- OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);* K+ x& o& n, O! R9 A+ s* E
- adder+=1;
v" W+ N* t, q" t- Q - } 7 Y! G6 o1 E% u+ i, G: T$ s/ b
- }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()
: \# y! s- `8 ~ O! N' z3 I - { // 莫斯科郊外的晚上8 a( H; O" B, U7 R. G7 s
- OLED_Clearp(); // 局部清除9 E: E' A( Q( U5 ]+ K$ A1 w3 S" j% i: U
- OLED_ShowCHinese(18,2,3);
2 f, V) I1 I7 h$ W. R - OLED_ShowCHinese(36,2,4);
: P0 u, j2 M3 I* C/ p$ v - OLED_ShowCHinese(54,2,5); W0 R& ]* u- I" r; U
- OLED_ShowCHinese(72,2,6);
, S! _* g0 ?; @+ a, |# y" c - OLED_ShowCHinese(90,2,7);
/ c2 X# G) G1 |9 [" g - OLED_ShowCHinese(108,2,8); & T, A6 |1 H5 n. P3 O: m! Q* Z
- OLED_ShowCHinese(18,4,9);
0 Q/ v( w. j- ? - OLED_ShowCHinese(36,4,10);
/ O& P0 n) T) m: D& B - }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择9 {( C, u5 v8 J/ ~0 v6 d e6 @
- while(f)
( z6 R; q! o# b6 X7 f6 o) m, p3 ] - {' P& g& N% E& d" P, Q' H
- if(i==0) cd1(); // 雪绒花
& f, v$ O# e/ \* a/ z - if(i==1) cd2(); // 莫斯科郊外的晚上 % F' u" C3 l4 i+ f& o
- if(i==2) cd3(); // 等待8 j/ y2 z# f0 m& {8 }1 b
- if(i==3) cd4(); // 鸿雁
! b6 y. R, y4 |8 `2 s, J ~: R - if(i==4) cd5(); // 贝加尔湖畔. \: l2 Y% }- s9 s' D9 v8 _
- if(i==5) cd6(); // 春暖花开, F6 L/ Z" M0 N9 }) ~1 I, A; v
- if(i==6) cd7(); // 传奇7 V2 }; O. g) T7 B
- if(i==7) cd8(); // 她
3 ?9 t5 g. h) q* G% B - if(i==8) cd9(); // 味道
- p; {; u# _1 J4 C8 A - if(i==9) cd10(); // 放心去飞
3 ?, N8 r% X/ ~$ ^* O9 n+ G - cdm(); // 功能键提示栏显示
! j9 j/ A; e; E+ l P( P, n - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);( b N- x; ? ?* ?4 t [6 H6 i
- HAL_Delay(6000); // 长短按区分延时 9 @* W8 g0 i& z, I- x( x
- if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;! p4 u$ e9 q6 l$ [, H
- else. B0 R! m( ?! K% T
- {
- b% x6 @$ Y4 h3 M - i++;/ X+ m1 B1 n$ ]1 ^
- if(i>9) i=0; // 轮回处理5 d" ]6 j% ?5 i1 f) ~
- }: q4 y* H6 t: l' i
- }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏
4 s- e) u% Z' {' a3 k0 M - f=1;
+ Z7 e" F7 q- Y& f/ @ - while(f); ^* u' k" J8 x$ ~3 b4 ?. e' V
- {. U! n4 ~2 D* Q e! ]
- while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0)6 Y5 p! s' l2 g" u% R
- {
9 C: w9 h2 c7 w' ^& r; \8 n/ C - // 产生当前功能选择项闪动效果
9 m& |# @1 t7 d+ u0 u; v - OLED_ShowCHinese(i*18,6,46);
5 l. y. ` c: U - HAL_Delay(200);
3 O2 k- b& K7 B - cdm();& \) P: s# {) ~3 s7 h5 ^, H
- HAL_Delay(200);
, Z5 _% C- X2 Q% u - }5 p2 d" Q& Z+ t u* U
- HAL_Delay(700);
' |9 p+ |. z- X5 z2 a* Z1 w - // 长短按区分处理; W; Y% E" f' J# r. s
- if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;
4 L3 t$ F7 y% r2 X" l - i++;: |- E) Q* ], D% H* ?
- if(i>5) i=0; // 功能选择轮回处理 ( i- ]+ h/ J" D; L/ P) I
- HAL_Delay(500);
/ Y9 A4 n8 p0 V - }
复制代码在指令控制的实现上,主要涉及串行通讯的初始化、指令生成与输出等函数。测试过程中,可通过串口助手来验证相应的指令功能。 常用的指令有: 播放_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() ) Q" K8 m- Z) z) S. V
- {
* H" }" X& g& R - // 定义指令内容
7 w7 Z5 w7 I' p/ A - unsigned cha play_cmd [10] =
|; S" l9 H; d+ i2 ]! m) z" j& y - { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};
" ~" N% J) \" K4 v - sendCmd (play_cmd);
0 _1 J8 l7 I* b. a- Q - }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟! / N- C d3 _3 v. K; L" i+ x
|
unsigned cha play_cmd [10] =
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};- q+ O! P: n+ G* g; P8 \
sendCmd (play_cmd);5 e; ]( D% e( c' i
}
; T* E# C0 p5 R1 W
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试