|
在各种基于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
! o: [; }+ p( @( M; A$ \ - // VCC 5V/3.3v: T1 I3 u, m5 z" `$ m
- // SCL PA5' B! i1 U2 t, q
- // SDA PA7, @& K& S8 J: m1 c0 R9 h6 R
- #define OLED_MODE 0- Y) _4 p. m$ u; \( t
- #define SIZE 8
& _" N4 R8 p+ k# z* V - #define XLevelL 0x00
: ]1 U7 a' B& h( N! d; W - #define XLevelH 0x10
6 Z7 _8 K7 g! ^ - #define Max_Column 128$ e8 T" K" z+ ?% _1 D) k' `
- #define Max_Row 64! G0 b2 N* l7 }, W- L- ~
- #define Brightness 0xFF
7 N# a1 g: h9 D' P - #define X_WIDTH 128( x8 w/ r4 K8 w+ X
- #define Y_WIDTH 64
2 P g0 R! U# g$ V: B - #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) ! e) o. O0 O; q @! C7 W7 ?
- #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) & T6 J. l& r8 d: r7 B3 ]% l
- void OLED_Init(void)
& |2 R1 n9 ^6 h: s- T+ T1 p2 z - { 8 s* {) |+ e7 m3 p1 X0 \
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); , d% T* {" \1 ^/ P' Z* V
- GPIO_InitTypeDef GPIO_InitStruct;
3 h- D6 X+ n O+ e ^% K" i7 y - __HAL_RCC_GPIOA_CLK_ENABLE();
1 i# c3 b) G- U/ d$ B - GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
& ^8 m& d$ `3 j* O; N - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;1 [" W$ b; Q6 ~0 U# m6 f
- GPIO_InitStruct.Pull = GPIO_PULLUP;
: y! @& }( |6 M7 `' W% F - GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;! k9 m/ c# t0 A: d, ]3 E+ p
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);$ w/ n& D: N6 l5 A( c
- OLED_SCLK_Set();4 G+ m; }1 X1 X
- OLED_SDIN_Set(); 2 g; i X7 t- w! F
- Delay_1ms(800); //delay_ms(800);
G9 X" _! _5 G) B5 h+ d4 o# a* w, p j) T - OLED_WR_Byte(0xAE,OLED_CMD);//--display off
3 i9 Y. r' K4 x2 E) ^) _ - OLED_WR_Byte(0x00,OLED_CMD);//---set low column address4 Y% p, s c( K7 [
- OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
( b9 a; v: e6 n - OLED_WR_Byte(0x40,OLED_CMD);//--set start line address 0 M8 r3 f/ i& W7 a7 m# Q5 G/ e+ G
- OLED_WR_Byte(0xB0,OLED_CMD);//--set page address L2 ]/ p. g: ]+ R( ]+ G5 u
- OLED_WR_Byte(0x81,OLED_CMD); // contract control
# T( ~# b1 C( m7 h& V - OLED_WR_Byte(0xFF,OLED_CMD);//--128
) `1 W1 a, w. M! w, r - OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
y* S& t' Y% g& S% f# Q - OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
$ L+ L6 z: \+ c2 }6 C& ?, M) { - OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)7 L1 d2 ?: v, S5 J: V
- OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty1 |3 p0 U& k* a8 H
- OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
/ U F* |. c6 S K. L9 y# ~* q - OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
' D1 G$ f: r# a0 t5 `- {! k - OLED_WR_Byte(0x00,OLED_CMD);//
( B! ]/ P/ j! S( `: ? - OLED_WR_Byte(0xD5,OLED_CMD);//set osc division5 j) u9 G( L% d' c5 r9 F' k
- OLED_WR_Byte(0x80,OLED_CMD);//
0 J, |7 w% o& s+ _) l - OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off _" Y6 I" J# V, I' f& m: }. S" I
- OLED_WR_Byte(0x05,OLED_CMD);//6 V! I' z$ _6 ^& u& \, b% |4 @
- OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
; d: s$ d- h4 R$ [4 u a( l& ` - OLED_WR_Byte(0xF1,OLED_CMD);//
) ^0 k$ Y/ o+ e( v- u/ a0 B$ I. L - OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
) t f& B" Q# \$ _* ]2 r o/ s - OLED_WR_Byte(0x12,OLED_CMD);//; V5 n' f3 T1 {* `
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
. F2 x9 [" C0 x9 t1 @ - OLED_WR_Byte(0x30,OLED_CMD);//
( H* W7 _. r8 _1 U$ h; z9 H9 o - OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable& S9 j( L$ \6 t# ~$ j
- OLED_WR_Byte(0x14,OLED_CMD);//
5 G% F7 J0 d: Q, A - OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel8 `% v" H8 A+ r% d; e
- }
复制代码清屏函数如下: - " ]9 O* R/ G: v1 B! |" W, E p! ^5 h
复制代码- void OLED_Clear(void) ' r; G9 J a* w, y
- { . h& P6 E. o& S- [
- unsigned char i,n; 4 j" I: @* u0 e: D! D
- for(i=0;i<8;i++) 2 s& Q( F7 ~& Z8 u
- {
; Y) c7 b. _9 J4 G9 A8 Z) E - OLED_WR_Byte (0xb0+i,OLED_CMD);
4 W; }: \6 K7 h- y6 `7 f a - OLED_WR_Byte (0x00,OLED_CMD);
0 f+ W+ F K& q - OLED_WR_Byte (0x10,OLED_CMD); . b8 H: z0 k( C0 n* S ^2 k! ^
- for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 1 A! Y$ d* L3 e- Q, o9 ]( z6 A) o
- }
; y |- `( S) G - }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)
. j4 J; x" g* @7 H; q6 z. m - {: K8 w# ]" W9 t, B/ F
- unsigned char j=0;" h/ ~' {; D s' B) E! K2 h4 f7 s
- while (chr[j]!='\0')
0 J$ @* I7 C K* n" L - { OLED_ShowChar(x,y,chr[j],Char_Size);
& k# ~2 H0 o( h1 p6 k0 H. V! | - x+=8;1 A0 Q9 K/ a( g7 ?) o+ K4 h3 B) B: }
- if(x>120){x=0;y+=2;}
! n( E5 X5 P1 R6 A; k - j++;
" P2 R. Q' z4 |1 o, N0 c - }3 W( N8 C# ^) \/ q2 ?$ a7 }' A
- }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)
6 n. a2 `' r/ D1 ~) j. f; d- g - { 4 S; v& [/ \1 t/ T# P6 [6 Q
- unsigned char t,adder=0;
( q# D! j# G( }5 h - OLED_Set_Pos(x,y);
# P! Y3 B! R5 f& j6 Z6 | - for(t=0;t<16;t++)
0 k# L: q% C$ Y( j a+ e E% {( c - {- L( k8 K- u8 u) Y) c" Y" h" I. Q
- OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
0 b/ ^0 c9 @6 g$ g4 K9 ~$ F - adder+=1;0 Z# }4 W. h8 D5 t, G
- }
+ B9 v) R& O: \+ N0 b - OLED_Set_Pos(x,y+1);
, N& P' B* b" K: N% p - for(t=0;t<16;t++)- Y) b4 z! N: f4 p
- { ! a, w* d+ H! g
- OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
% y( y3 P9 b- z: L - adder+=1;6 x( _ P+ g- `+ N0 w$ V
- } # P; _7 G1 S( E0 A( K# k. ^
- }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()( D7 \( h+ B4 k- i: R& b
- { // 莫斯科郊外的晚上
3 q% o: {' N( d) t/ V( { - OLED_Clearp(); // 局部清除# b V5 f, w8 x( U
- OLED_ShowCHinese(18,2,3); # H' Y) q2 }% j1 w9 ?- ^) F
- OLED_ShowCHinese(36,2,4); 1 P' H( }# @0 U4 P/ z
- OLED_ShowCHinese(54,2,5);
. t$ \5 k/ R" ~! A - OLED_ShowCHinese(72,2,6); & j+ h2 ]! Y) G) V' ?2 E k$ J6 r
- OLED_ShowCHinese(90,2,7);
; y1 D1 j% N- `6 f0 w" c- u% O - OLED_ShowCHinese(108,2,8);
9 I0 R& m) g" m- @# }9 l - OLED_ShowCHinese(18,4,9); / Z8 B1 a6 v. p2 k7 [
- OLED_ShowCHinese(36,4,10);
7 B+ k( j9 z+ s) C/ k, G0 n e3 o - }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择$ f7 A5 K/ R9 Y" ]' y
- while(f)
) d) H( `6 @1 z - {
6 [; c) N$ ^% S6 j% c. o$ {! m - if(i==0) cd1(); // 雪绒花* F& ^$ D( {5 v$ v0 ~
- if(i==1) cd2(); // 莫斯科郊外的晚上
. O( z* }3 v6 j h$ u' v - if(i==2) cd3(); // 等待
2 N) ^% c8 ?0 D - if(i==3) cd4(); // 鸿雁; B7 R5 |5 t3 T, F! o: P
- if(i==4) cd5(); // 贝加尔湖畔
$ z! [0 A/ D! ^% `% |. D - if(i==5) cd6(); // 春暖花开2 D p5 d& X2 N8 \% |3 G
- if(i==6) cd7(); // 传奇
; E O7 H1 I: c/ w - if(i==7) cd8(); // 她
' W2 j M* A. K3 d- z n - if(i==8) cd9(); // 味道
3 r0 w* g* X$ {9 M) u - if(i==9) cd10(); // 放心去飞
! A( n" {2 C: k& a2 d! q - cdm(); // 功能键提示栏显示
3 i: W, ~; F* ~6 Y8 I0 ~ - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);
" Z' D$ _1 Y" h# g" Y }) E8 _ - HAL_Delay(6000); // 长短按区分延时
( J: S6 x# k% f4 D! L - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;/ N# ?0 P2 R$ @% T$ G
- else) y0 O# Z- K4 \8 k8 l9 ~6 ^: S3 ~
- {( v" t& o/ j; e4 {% \# @
- i++;6 q( Z! L! b5 S
- if(i>9) i=0; // 轮回处理
4 R$ y. t5 Q# b5 ?' S* n: R - }
! v. f/ ^ \. s - }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏 o, O9 s! e- q) r5 [
- f=1;
! `7 T( [! C d) T4 D9 ~6 V! r9 j - while(f)) s! e1 f& b; B1 ~( b/ R8 v
- {- N- ^% d& R9 e" H" A7 G
- while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0)9 y+ E. F0 A) {1 ]
- { ! T; A: z3 d. V0 n+ T' v6 d g
- // 产生当前功能选择项闪动效果- n' R" v0 I. r" u% h, C( J2 w( g
- OLED_ShowCHinese(i*18,6,46);
- D* B7 Z/ k/ t# u9 R - HAL_Delay(200); 1 \$ v3 `3 H, t
- cdm();
. p% p1 k! l$ g - HAL_Delay(200);
8 X0 Y( \. d( Y c- F$ V - }7 u" m# K! r+ e
- HAL_Delay(700);2 {9 w, U! p9 V! y/ }- l
- // 长短按区分处理& Y7 [* G7 n8 w
- if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;6 U3 m6 J7 x- Q# X) I% L
- i++;
* T9 s2 O+ ~4 q' U - if(i>5) i=0; // 功能选择轮回处理
f! W+ @/ F3 B - HAL_Delay(500);' |9 @: d1 v8 f9 @, 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() " h& ] V* f1 k# Q0 _
- {# ?& V5 c6 Y8 B( }. R8 ~: h. W& B
- // 定义指令内容
/ ?9 H5 `- k4 @- F4 b6 ^) E" b. p - unsigned cha play_cmd [10] = + P v# E2 [( t- `' w
- { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};
( Y t& T" \' l; i9 Q& w4 W) U5 T - sendCmd (play_cmd);
. F" C' e3 T0 ?8 S - }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟!
; {" {& k" V) T0 t& u3 ?7 V: u |
unsigned cha play_cmd [10] =
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};, k! s6 k" K. h# j
sendCmd (play_cmd);4 U: r9 ~! l3 [0 _2 w2 q
}
+ l0 S0 S, X& l# l6 e9 j% [. ?
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试