|
在各种基于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
; Z8 t f: k7 T. j& H7 f - // VCC 5V/3.3v
7 X3 L" ?/ u- p - // SCL PA5: j8 ^; I \6 s) C+ k l" J
- // SDA PA78 r. v" S& X f6 F5 o' K& S
- #define OLED_MODE 0
: |. i) W6 Y8 O: | - #define SIZE 8
/ G1 {$ |/ O5 _" i - #define XLevelL 0x00
. E `/ [+ B: Z$ Q - #define XLevelH 0x10' e4 {& N1 O+ K; e
- #define Max_Column 128( l6 c G$ g4 ]7 T# {9 q
- #define Max_Row 64; {$ ]/ l' p- @
- #define Brightness 0xFF ( C# e/ G4 C/ e. R* i- b! z
- #define X_WIDTH 128
! Y' w+ D# H2 z& K, d - #define Y_WIDTH 64) p' m6 s. E) q9 l2 D7 I" K; c
- #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET)
0 K5 M( X/ E& E3 A. Q" I - #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET)
# R2 ^3 a' [) H - void OLED_Init(void)! V( H5 X4 g2 s4 I2 m
- {
* }5 Q0 G1 a1 ]$ D - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
& a7 s5 J0 r9 J - GPIO_InitTypeDef GPIO_InitStruct;4 T" L- i, [0 F8 M: T* \, B3 ~
- __HAL_RCC_GPIOA_CLK_ENABLE();
* G0 H& E7 X$ z; Z6 K& Y; M - GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7; ' V5 [* z2 p4 Q, c
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
) Z" O6 }" t; Z6 l/ F - GPIO_InitStruct.Pull = GPIO_PULLUP;
6 E* P+ b% c9 O* } - GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
& S( |: B4 ]' K" Q2 m7 Z4 e - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);9 Q0 L" T' u5 J7 Q( O% H
- OLED_SCLK_Set();
0 J# b3 f' y- w& K - OLED_SDIN_Set(); : B$ C! }; q" f6 h! f
- Delay_1ms(800); //delay_ms(800);& n5 l) l7 ]9 b3 x
- OLED_WR_Byte(0xAE,OLED_CMD);//--display off/ \: F5 L; e ~1 ` u8 d) x& A) X
- OLED_WR_Byte(0x00,OLED_CMD);//---set low column address: ~' ?) P$ Z% b( J4 f: {* m* |
- OLED_WR_Byte(0x10,OLED_CMD);//---set high column address: \* O; @, W* g$ }8 J, {
- OLED_WR_Byte(0x40,OLED_CMD);//--set start line address 9 F( x+ z! W. N% I
- OLED_WR_Byte(0xB0,OLED_CMD);//--set page address. Z4 E. R. A+ b8 Y9 q
- OLED_WR_Byte(0x81,OLED_CMD); // contract control: V Q# o4 G4 x+ e
- OLED_WR_Byte(0xFF,OLED_CMD);//--128 0 x% g3 A8 ~: {
- OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
X) q9 r9 n" s- M6 N5 k4 U/ r( S - OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse0 c( W# y+ t3 W' D' N/ J, M4 ]6 D- G
- OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
/ m7 q) |6 A9 J; l- W) Q l - OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
$ ^& \3 `3 |. g3 g% m. ]; ^ - OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction3 ?+ h# `- |$ S# L: m }
- OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
; ?. h: D3 f) h - OLED_WR_Byte(0x00,OLED_CMD);//
+ o+ l0 M l# L9 O- b( K6 r7 q0 r - OLED_WR_Byte(0xD5,OLED_CMD);//set osc division2 p$ I6 ]+ p9 X
- OLED_WR_Byte(0x80,OLED_CMD);//- {" e7 `0 k( g, {5 Q& U" x% _
- OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
2 {! k% S& y5 n+ y& i- V7 e - OLED_WR_Byte(0x05,OLED_CMD);//
P* i4 V1 |- o. Z8 D; f: j - OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period. [; ^+ g( V& k: p) L$ _( x
- OLED_WR_Byte(0xF1,OLED_CMD);//
& [* c7 t0 q0 m - OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
, d9 `/ t* l! T8 F0 R4 d - OLED_WR_Byte(0x12,OLED_CMD);//* L, a* F+ v' Q7 W9 X8 ]
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh N4 I6 g" @$ w! e
- OLED_WR_Byte(0x30,OLED_CMD);//
, Y% {7 q" V/ t1 I3 { - OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable' w3 S2 \* A; P1 r. c d1 w
- OLED_WR_Byte(0x14,OLED_CMD);//0 K' a2 W6 K" t" B/ V& ?( r0 q
- OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
6 m# |; n+ @; I2 m$ k$ B - }
复制代码清屏函数如下:
: r! y; \& t1 e' k; `4 [2 T; R
复制代码- void OLED_Clear(void) 2 l9 g- q: x( c# |% w7 p
- {
( _' \" W& ], T4 S7 y" Y - unsigned char i,n; & f3 A- V) F: Q) T H* L n
- for(i=0;i<8;i++) 6 Q/ L9 d" r3 i N
- { , `/ D0 S. i j2 D" ], f8 n; p
- OLED_WR_Byte (0xb0+i,OLED_CMD);
% l4 u" S) X! D$ n7 r' |6 s - OLED_WR_Byte (0x00,OLED_CMD);
% O* z' c z, A L0 \) h9 ^ - OLED_WR_Byte (0x10,OLED_CMD); $ {# A. A7 V' i, O0 d
- for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 1 k0 G9 s7 |. B; ^4 Q [; J
- } # ~, b) E' q$ N$ X+ ]7 z( F
- }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)
$ `+ u" m1 |7 E G7 t% i& J; i) Q6 v. m, r - {
9 P7 i4 p% W( g7 ?; p - unsigned char j=0;* t7 f* [. y1 v( C1 F
- while (chr[j]!='\0')
6 G- H3 ^( R, y' N* ^% @ - { OLED_ShowChar(x,y,chr[j],Char_Size);
! ~3 x, A9 X4 o" q, s" a+ G - x+=8;
5 u" i1 x0 }" T2 B - if(x>120){x=0;y+=2;}
+ H9 u7 m1 m, e - j++;: c/ F- c. _. F2 ~* ^' ^/ K
- }, ~+ {0 w5 x% b! [: |/ K
- }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)
6 Y# [6 C8 {% r/ z1 a - {
6 c* ` R8 K+ D: P# Z; m - unsigned char t,adder=0;
* K2 V' M5 s) B1 o8 a! }0 s - OLED_Set_Pos(x,y); " P3 H" u/ {6 x1 [
- for(t=0;t<16;t++)* ]. A4 @3 _6 h+ Q4 _( q2 m
- {
5 @0 {/ j" I" J* ]* q- y$ V- u6 Y: @ - OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
& Y7 y* ~3 }. p1 @ u0 X( V q/ E3 r - adder+=1;
$ N2 I! I0 p7 L* ~! V5 V - }
& E' w0 G$ }: ~% ^$ O/ p - OLED_Set_Pos(x,y+1); B. s" V6 `* v" e! C, H
- for(t=0;t<16;t++)
. E; p8 p0 U$ r. }) \0 y ^ - {
8 o* O _0 a# Y( B' P - OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
) z9 O. O6 o4 X0 Q! D9 j5 x$ {& [; ^ - adder+=1;/ \6 P A$ I% [7 e
- } 7 W3 z8 A3 b% q
- }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()
9 r1 R- E7 E+ i( v/ B9 Z - { // 莫斯科郊外的晚上
* |/ y$ i6 [* g$ k/ O - OLED_Clearp(); // 局部清除
9 @4 T- ^- v0 g$ K8 W* W - OLED_ShowCHinese(18,2,3);
& ^4 ]& h) o- m1 d5 F - OLED_ShowCHinese(36,2,4);
* B p3 y2 T) \9 H - OLED_ShowCHinese(54,2,5);
! g7 q- M% g5 D$ a - OLED_ShowCHinese(72,2,6); 5 H8 ^; }, Y7 Q' {
- OLED_ShowCHinese(90,2,7);
1 R( M4 V& V) z# R! r - OLED_ShowCHinese(108,2,8);
8 ~+ L% B2 z" R Y& x5 M - OLED_ShowCHinese(18,4,9); 0 A* ~& h/ I3 l! `6 h5 \1 z8 O
- OLED_ShowCHinese(36,4,10); # ]! C, u6 a# Q. s6 `$ H
- }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择 i! I9 E$ \, S$ J2 [: V- I
- while(f)
2 Y+ P1 ]" }- P - {
% s* g" O9 e4 M# E( y - if(i==0) cd1(); // 雪绒花
( \0 v3 u# W2 a% m - if(i==1) cd2(); // 莫斯科郊外的晚上
( ?4 p" B; Y- N - if(i==2) cd3(); // 等待% Y6 V' b* {- m1 p7 N, v( q1 u' H
- if(i==3) cd4(); // 鸿雁. h x. T' h8 L1 i% C# D
- if(i==4) cd5(); // 贝加尔湖畔" T& I1 [$ z+ ^6 U7 V# x
- if(i==5) cd6(); // 春暖花开
- `. I! b& b# P' p G; E - if(i==6) cd7(); // 传奇
5 W5 v. A" ?5 j W8 L1 W" r - if(i==7) cd8(); // 她 n' \9 z6 M' I1 D* W) Q N
- if(i==8) cd9(); // 味道2 s: F' O9 m! R. k6 }# a
- if(i==9) cd10(); // 放心去飞
7 B: K) m2 q- Q2 w - cdm(); // 功能键提示栏显示' `) y4 l: p- |4 r, [! _$ N+ O' E
- while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);
% L- p7 T" G+ s. b - HAL_Delay(6000); // 长短按区分延时
4 v) i0 @; i! F6 I+ j - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;! V4 F S; H) i1 S4 ^1 M3 h6 F+ |
- else
9 C0 y2 B+ o$ I1 a - {
7 w$ m% a. m9 Z- o- Y8 Y/ d' { - i++;- q) f$ i' x; Q$ ~ @& Q3 w; c
- if(i>9) i=0; // 轮回处理
8 Y; O+ \0 N5 R5 W* W2 P - }
8 r& }, |1 j1 K: I* o - }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏
& @2 w: ?& s) ~2 d3 p% C7 m& d - f=1;
: k* S) S% k; r; L0 e( A - while(f) _$ ~8 q0 c9 ^# m
- {
: x) P7 H# j. o1 b, ] - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0)% w2 a- \" I& r9 p
- { 3 a; f) h8 {1 Y8 ^; F4 j
- // 产生当前功能选择项闪动效果( S% L9 G8 V: f4 Q, q \
- OLED_ShowCHinese(i*18,6,46);
5 l d4 n2 t- }# K& F - HAL_Delay(200); ; J% f* J0 G2 y& E7 x Y1 ?! |
- cdm();: j7 b) k) s7 v/ l: A
- HAL_Delay(200);0 @! `) `! _( O1 G$ Z/ [- N
- }. G$ ^! g1 g, g1 B! V
- HAL_Delay(700);6 K: T5 Y9 j8 K" n/ x
- // 长短按区分处理" v) C/ Z( s% F+ o1 O, n3 e
- if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;
5 e- O3 T( ~8 L0 A* D- ` - i++;+ U9 q# Z+ y. P, y. `
- if(i>5) i=0; // 功能选择轮回处理 4 z6 y' m9 t& {: V, Q! n6 ]' ~% G
- HAL_Delay(500);2 E: A v3 I' \, t- J! ~
- }
复制代码在指令控制的实现上,主要涉及串行通讯的初始化、指令生成与输出等函数。测试过程中,可通过串口助手来验证相应的指令功能。 常用的指令有: 播放_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() 9 H5 }/ a( M4 |' A
- {
?7 b( E, m1 D# |+ V9 f - // 定义指令内容
8 w- N2 p7 p/ y: F - unsigned cha play_cmd [10] = 9 ]! K1 H, Q3 }+ U7 L& Z
- { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};6 v3 m2 S* I+ k( o: t" D
- sendCmd (play_cmd);
, b1 z3 K$ E5 H4 \2 {5 w - }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟!
3 X! Y' ~- B) c; D6 v6 [0 [( V3 d9 _ |
unsigned cha play_cmd [10] = 3 P$ ]& {' a0 ~
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};8 n- u/ f c9 ?! g
sendCmd (play_cmd);3 R* F5 K& a' b# ?; D" |+ Y
}
k4 A) Z/ @! u1 j6 S
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试