|
在各种基于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 GND7 O9 U3 ^- h& X. z: @
- // VCC 5V/3.3v; x B' R9 W9 B2 q* G4 c
- // SCL PA5
! g& n2 \( l* [1 _" v6 F - // SDA PA7
+ E. B9 {; t2 u, ~* c! @$ u - #define OLED_MODE 0
( y# N: N" u* B$ Y. [ d8 Y - #define SIZE 8
" {. Y8 M* z$ w7 L) J! e& ` - #define XLevelL 0x008 L) X1 f" |1 m3 p0 t4 o, H
- #define XLevelH 0x103 O7 h2 C: v' i% D; s0 i2 \# }
- #define Max_Column 128) W# f: x( a7 K/ W
- #define Max_Row 64
" h; e9 q; U, O! f# d# ?# q3 Y! L - #define Brightness 0xFF , a8 D& M/ |5 I7 X
- #define X_WIDTH 128! v0 L& m3 l! A% s
- #define Y_WIDTH 64
# N0 U$ m$ c- ` A( g! t' t - #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) ! A* ~* g' u. {
- #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) / P0 Z% A/ R% e8 u0 R
- void OLED_Init(void)/ y! k+ B) |' M/ Z0 R+ u( L
- { / L. u8 {! J7 w/ d4 E( B
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
) a2 {5 Z* o1 D9 P. z0 B - GPIO_InitTypeDef GPIO_InitStruct;6 H$ P b, [2 d0 ~* u0 H. O( W
- __HAL_RCC_GPIOA_CLK_ENABLE();
- ?5 M) _' C9 N' C6 d( ~$ L - GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7; " ^' q; {" I; |6 o) M
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;/ j) i+ A7 E* a; o4 |
- GPIO_InitStruct.Pull = GPIO_PULLUP;* o0 ]& H3 r/ Z' P
- GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;1 ~ }& f$ }: U* ?
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
9 n4 E' ]: T L: g4 {6 M+ O - OLED_SCLK_Set();
6 R0 [: T' r! Z/ G - OLED_SDIN_Set();
2 e; A8 S+ s. H+ `: U - Delay_1ms(800); //delay_ms(800);! A2 R2 f2 b$ E* U9 ]) J" ]/ x
- OLED_WR_Byte(0xAE,OLED_CMD);//--display off" n8 k0 q a1 P0 X$ F7 t
- OLED_WR_Byte(0x00,OLED_CMD);//---set low column address. W N5 T* x$ b0 T' O
- OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
; a o2 y. j6 W) v' D - OLED_WR_Byte(0x40,OLED_CMD);//--set start line address
# ?2 V B: |; S3 N2 ^0 _ - OLED_WR_Byte(0xB0,OLED_CMD);//--set page address! O( P- B. a- I! b! J" _. `
- OLED_WR_Byte(0x81,OLED_CMD); // contract control
& U1 e) o' Y$ T2 C9 W - OLED_WR_Byte(0xFF,OLED_CMD);//--128
6 G$ e, L' x: H2 |5 V0 ^ - OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap : r& i9 d( s" v8 A( {
- OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
1 _. j- q6 c, C& f; T7 k# e; J - OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
0 ~7 X' g- L2 g7 W - OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
8 [( F$ m, w$ X! G9 U0 f - OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
$ `; f* R' S7 q! j* r" l1 J! y - OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset0 T1 F' [8 q4 I# q
- OLED_WR_Byte(0x00,OLED_CMD);//
+ C3 V9 N+ }" p1 ?6 P! s X - OLED_WR_Byte(0xD5,OLED_CMD);//set osc division0 _+ L% D( T2 X7 R
- OLED_WR_Byte(0x80,OLED_CMD);//* V1 X9 w; C( `
- OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off5 y# c# R ]+ o& Z! a
- OLED_WR_Byte(0x05,OLED_CMD);//( ?$ O: R% V& [* Y
- OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period" j) ^# ?. V* X* d3 @1 T
- OLED_WR_Byte(0xF1,OLED_CMD);//
& O& `3 T6 H3 s, x: ^1 j! ?3 j - OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
5 H* A" b9 g* F- C - OLED_WR_Byte(0x12,OLED_CMD);//- e' O2 ~( F; N: x5 \2 l
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
; l$ L8 L i5 ^1 ~$ { - OLED_WR_Byte(0x30,OLED_CMD);//' ~' }0 x5 Z0 R1 e' U2 P
- OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
$ K) }, a9 s9 P ?# i2 M - OLED_WR_Byte(0x14,OLED_CMD);//
( [) n' H9 _. K8 B - OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel0 R3 Z! p2 S/ z# g2 r: m! n
- }
复制代码清屏函数如下: - void OLED_Clear(void)
0 g, s6 y' a$ b, o3 b - { , `! J7 n/ B2 r, Y1 L7 F; k* u( _$ E
- unsigned char i,n; # `4 _" B! g$ t) N
- for(i=0;i<8;i++)
$ Q$ o) I" T4 F v5 p7 X - {
9 r3 s, S9 \ N5 u% y - OLED_WR_Byte (0xb0+i,OLED_CMD); + }, K) r5 e# p9 X+ b$ h J+ i( l# c8 [
- OLED_WR_Byte (0x00,OLED_CMD); + j+ a( f( a7 C+ \" g. j% I/ j
- OLED_WR_Byte (0x10,OLED_CMD);
! j2 H0 l0 p" o: O - for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
0 e" _) I B1 i; z - } 6 f; k9 Z/ h2 W ]) y% N+ {9 A. ?
- }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)
5 ~1 f/ U: _1 o' t J - {
- a: Y+ S: n2 a4 S - unsigned char j=0;( ?# g3 F" S! N& u I5 G) h
- while (chr[j]!='\0')
8 }1 `" [+ e6 |* s+ N - { OLED_ShowChar(x,y,chr[j],Char_Size);& A7 m) S- K% Z% n8 I$ Q
- x+=8;
5 t' _' f, b# F! l# _ - if(x>120){x=0;y+=2;}7 D ~$ ^' [* F$ T8 o# w5 R2 k
- j++;( m+ F- U: n; x7 `
- }/ Q4 A' I4 W6 ~- M( u+ v5 ~
- }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)+ o5 _0 w" i! @
- {
& K8 ?- i. y( z - unsigned char t,adder=0;
$ n) w9 C& N4 f2 U A - OLED_Set_Pos(x,y); & F/ @5 u1 C1 B* Y5 G7 z9 j) I9 J
- for(t=0;t<16;t++)
1 T% r5 {6 J- ?, m - {. `1 x7 t% @/ D$ u2 k! n
- OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
! ^9 l8 l7 O8 m - adder+=1;' X% g2 `6 h: }
- }
; A" k7 A* B* [4 H2 [ - OLED_Set_Pos(x,y+1); + J9 i: g7 `6 E$ k5 G2 w
- for(t=0;t<16;t++)
! ?8 ~7 v; n1 I - {
0 w; A5 ~# u e6 t# E5 u4 r8 C- N - OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);4 |6 r$ l/ U4 V) ]! S$ ^
- adder+=1;
, S% c5 a1 \; f1 [$ ?8 Q o: x - }
" Q: H. P% X) L# w4 B+ O - }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()
2 j) h, y6 I, {$ Q( X5 R - { // 莫斯科郊外的晚上, v1 k3 b3 {' V. S9 Y) X+ Y% h
- OLED_Clearp(); // 局部清除
; [ i4 q# Q$ Q/ \" u! k - OLED_ShowCHinese(18,2,3); 2 P, x( X4 ]+ J5 O$ q8 ]; f0 N
- OLED_ShowCHinese(36,2,4); 1 o, l: O- R% k4 m d* k7 t1 z
- OLED_ShowCHinese(54,2,5);
+ A4 s ]! _* w$ D8 e& [ - OLED_ShowCHinese(72,2,6);
) O6 Z7 Q- ?' o - OLED_ShowCHinese(90,2,7);
8 |# i0 Q, o5 W) h6 P - OLED_ShowCHinese(108,2,8); 2 d) a1 f$ w2 l% E- |
- OLED_ShowCHinese(18,4,9);
/ u- H# B) U$ z( z0 H) e/ z - OLED_ShowCHinese(36,4,10); ! z- z, b! Q, W$ F+ L0 l* ^2 Q
- }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择/ m l. Z9 W4 [! Y
- while(f)
, V/ Y% h3 V: { - {
2 O$ {4 J' P( M - if(i==0) cd1(); // 雪绒花7 m+ S3 F; @- B, F$ \. Z/ V9 w# h
- if(i==1) cd2(); // 莫斯科郊外的晚上
* s3 w4 k3 t9 o, o. U, L$ i: y - if(i==2) cd3(); // 等待# R$ k3 u4 n/ l
- if(i==3) cd4(); // 鸿雁
# w9 a8 W5 D3 V" L* ~9 A, S0 p - if(i==4) cd5(); // 贝加尔湖畔
7 i1 m) i6 _: M% l; b& F# z. [: x - if(i==5) cd6(); // 春暖花开
& {; [# V4 Y( n, \2 t$ A1 Y - if(i==6) cd7(); // 传奇/ ?- S4 f; B% Y5 }
- if(i==7) cd8(); // 她) u; j+ _" N3 t5 ~+ i" S% h8 y
- if(i==8) cd9(); // 味道& e! ]+ ?- {4 l# T% S
- if(i==9) cd10(); // 放心去飞
0 O. C. k; u3 j% T - cdm(); // 功能键提示栏显示
. _5 j; p* I# a$ z+ }5 S6 t - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);
$ K2 h# e2 j; c2 w' D/ I8 S: r - HAL_Delay(6000); // 长短按区分延时 5 p5 O6 U' o8 R' Q+ I1 Z2 @
- if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;
/ ]9 M- ^* V @/ T& W. p - else1 t$ q3 o* k, k; D+ q
- {# _) X5 ?3 ]* l' L6 X) x' w
- i++;
! u8 Z, x7 Y; q - if(i>9) i=0; // 轮回处理; A- A" c4 o+ v4 H# o
- }
. b9 j& [% b7 M! N% } - }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏
: r1 k. U4 w) \ - f=1;: U' D$ N9 F N
- while(f)
' Z: Y; n1 T6 N$ O$ L3 z - {
* m8 Y$ G8 p6 Y% F$ g3 o - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0)) V2 `0 A4 {+ \" |# C6 f
- {
5 a/ J: `0 S5 N* C- m! f, K; N - // 产生当前功能选择项闪动效果; s" z/ L( K) p' O% C2 o
- OLED_ShowCHinese(i*18,6,46);
; [/ B+ b: C$ O$ m n - HAL_Delay(200); 8 r0 C2 U$ f+ H. q2 ~6 {
- cdm();8 f2 N/ Q$ C8 A0 p9 P5 _* m @
- HAL_Delay(200);0 Q* t7 K- ?* O; B+ T
- }! G6 X0 e2 @+ i8 g- u
- HAL_Delay(700);
) L% n4 D# X! p, t' w' P" o6 Q - // 长短按区分处理
9 E5 M5 g3 a2 W" x' b - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;3 |% r$ y% Z' \% d- o6 D
- i++;
4 ^5 h6 S0 G) [& G- T* `8 e+ L - if(i>5) i=0; // 功能选择轮回处理 6 g, Y5 ~* m! R7 |! C( ]8 j
- HAL_Delay(500);/ T- o- _* F# 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() # s2 I6 o! W0 R1 A
- {
7 f, U/ B2 [& g' C2 P - // 定义指令内容1 q! e) `( q7 I+ J* u) y3 ]
- unsigned cha play_cmd [10] = 2 s, p3 S0 P$ {: x# d; C: n% H5 l
- { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};
! {& Q2 P. i* M% P# w$ ? - sendCmd (play_cmd);
2 T6 |/ Y* S# U7 Q, g, t - }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟! & b: d, p8 q5 m& ]- A- {" { L4 R
|
unsigned cha play_cmd [10] = 5 a- m; M/ D6 Z$ v
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};" M" g' C! P. u" c; \$ M
sendCmd (play_cmd);/ }; }. V, e( W: k" D' l: g
}
' g' y% L; p' Q" Q, }4 T
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试