在各种基于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& ]" y4 P; n+ \* r6 j
- // VCC 5V/3.3v( v& ~/ O- y A" [
- // SCL PA5
( I+ H( ^2 D) d - // SDA PA7, H2 R# v; F4 }4 H5 A3 s3 [
- #define OLED_MODE 0
7 I7 w! k7 D _ H* K5 c, d - #define SIZE 8
a( O1 b( \: t0 `! u( X - #define XLevelL 0x00$ q" l' S5 y; \$ h6 i8 @
- #define XLevelH 0x10! ?! P9 n4 r' b8 G* |2 q4 N
- #define Max_Column 128! k+ v2 s( o0 p1 V5 a
- #define Max_Row 645 M0 j4 ?2 ~& O' W
- #define Brightness 0xFF $ o4 _8 f7 K4 l1 a- Y# A
- #define X_WIDTH 1289 t' c6 B) b- i4 G% D! i
- #define Y_WIDTH 64
! e( D8 `, \3 l" P( P% p - #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) & Y7 c) ?, Z& M) S" `) a
- #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET)
) S1 b( `$ u6 I7 M! e% |8 s( w. B, n - void OLED_Init(void)# m( H2 Y3 i* V
- { 5 J, S6 X y; i; P& q$ n
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 1 h& z0 o! Q/ w1 o- G1 D, w
- GPIO_InitTypeDef GPIO_InitStruct;1 F# |: K( ]# h
- __HAL_RCC_GPIOA_CLK_ENABLE();3 h# ?6 {0 k1 X8 @! N
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
' X- Z0 e( h3 j' U' Q- | - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;0 A$ P. N: }' a4 ?
- GPIO_InitStruct.Pull = GPIO_PULLUP;
/ R. J- o0 g7 ` - GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
# U2 p( n# i' r1 r" J& j: W( q - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);* D. o$ ]$ H% I! V2 x/ a
- OLED_SCLK_Set();- C% C( |5 s, ^8 h1 T& Q7 S
- OLED_SDIN_Set();
; ^% f- C6 d* l. `/ D - Delay_1ms(800); //delay_ms(800);
5 @# q$ ~ t. [/ ?/ ]& x - OLED_WR_Byte(0xAE,OLED_CMD);//--display off, s5 M4 P! }7 {$ o$ B
- OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
4 G8 q8 ?8 ~8 @! R/ ^ - OLED_WR_Byte(0x10,OLED_CMD);//---set high column address1 M2 @$ m) K: \2 [/ o# i
- OLED_WR_Byte(0x40,OLED_CMD);//--set start line address ( |$ F! U9 P. b+ Z* `$ e
- OLED_WR_Byte(0xB0,OLED_CMD);//--set page address [" k+ ~. @4 l8 D: }1 j1 t
- OLED_WR_Byte(0x81,OLED_CMD); // contract control5 T0 H, b( ~1 |. Q, h3 ]+ [. z
- OLED_WR_Byte(0xFF,OLED_CMD);//--128 8 v& J7 A& d' |
- OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap % E! `% M8 t K4 T& d& G
- OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse0 |" v2 d9 M& z
- OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
& z* D; s3 x+ N3 U- C0 a$ M - OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
4 h& V+ [5 j. B- u - OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction& T) H: E3 M- w0 b' k
- OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
% N6 C2 u, V* O, g8 a - OLED_WR_Byte(0x00,OLED_CMD);//4 E; g+ w5 P& u/ f! f# O$ [
- OLED_WR_Byte(0xD5,OLED_CMD);//set osc division# X8 ^1 K p' J; d4 H8 \
- OLED_WR_Byte(0x80,OLED_CMD);//( C+ D. }# Z* @" Q/ J, ~
- OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
% } P. m. @- {1 X: {8 @4 ?& E - OLED_WR_Byte(0x05,OLED_CMD);/// s- b7 Z' S' X _* y. K3 V
- OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
, [: T1 N3 O( K+ F* H& X$ n - OLED_WR_Byte(0xF1,OLED_CMD);//8 |2 ?; G, J6 d( t& a+ Y
- OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion/ O$ s* r3 I7 j3 ~4 H
- OLED_WR_Byte(0x12,OLED_CMD);//9 b' G k( L3 x! V
- OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
3 N5 @0 {8 v+ e" H& o6 N; A - OLED_WR_Byte(0x30,OLED_CMD);//; k0 z+ l) u7 O+ _
- OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable: ?6 r C9 B9 X' A) W
- OLED_WR_Byte(0x14,OLED_CMD);//
' ^5 G( ?' ~: ` - OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel+ T, K, D+ \+ ]% V
- }
复制代码清屏函数如下: - void OLED_Clear(void)
c p/ `( Y+ q1 G - {
, M& M7 m4 b s& G5 h! v - unsigned char i,n;
8 J$ ~# p0 \9 \: { P' w4 o4 K( l - for(i=0;i<8;i++)
5 N4 L0 {% m3 B! J - { # s( W+ l+ w; o5 U; I: T
- OLED_WR_Byte (0xb0+i,OLED_CMD); % o- G, d) `" a; D8 z1 r
- OLED_WR_Byte (0x00,OLED_CMD);
Y0 P% o+ p! T* `8 u: p' x - OLED_WR_Byte (0x10,OLED_CMD);
. B0 v7 ^% k/ m' K* ` f- N* B - for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
% z. e0 n5 H0 O2 A( q1 T - }
8 @* \/ h4 ]8 {' e+ P: c3 ` - }
复制代码在字符显示方面,是使用程序数组中所存储的字模,并分为不同显示大小的规格,字符串显示函数如下: - void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr,unsigned char Char_Size)
. }1 T: p$ D$ D - {3 D0 p/ l; P/ B! F2 S& p
- unsigned char j=0;' t$ b! h. \- Z7 h
- while (chr[j]!='\0')
# J7 t8 a% S: F - { OLED_ShowChar(x,y,chr[j],Char_Size);
# q3 n( V: z7 j - x+=8;
% g2 T" g0 u/ B, Q - if(x>120){x=0;y+=2;}; l6 u; l% a: i' `' k+ d) c. k0 ]
- j++;9 Y% d, c% U1 U4 x& o e
- }
* L% y% J- h, [$ D+ K" p* ~ - }
复制代码在汉字显示方面,是采用自行构建的小字库,因此在使用是按排列顺序来建立映射关系,具体的汉字显示函数如下: - void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)/ b4 e" Z0 y$ u8 ~
- {
) L4 Q8 k `( }' s; i - unsigned char t,adder=0;
) b0 t; [8 n" [# u. a/ p- h- z* Y - OLED_Set_Pos(x,y); + ^0 I% `2 w: r# }( i( S/ N
- for(t=0;t<16;t++)
. U. s/ m. H6 ~ - {6 ?& O Q3 J, m3 K, i
- OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
. y! m( G* u$ @9 V) a( U - adder+=1;
. ?$ d% |+ ~, ^/ \# o L6 m# \ - } + K# h5 I7 E- ^1 g+ a. k) x/ }1 m
- OLED_Set_Pos(x,y+1); + `' c$ Q( v0 a+ S) `' L- x' t
- for(t=0;t<16;t++)
; [& G( j! E1 M" e2 M% G7 e, O - {
" I0 {- D1 Z0 j* e) F/ u - OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
" {, _) P$ [, l; |( `/ n5 v* ? - adder+=1;
" u- e: G/ R, v% D. w3 b/ A8 ?& Z - }
2 v9 d' e7 A+ b8 s6 T2 r6 F6 \ - }
复制代码为了便于快捷地构建乐曲菜单,是将各首歌名分别建立一个函数来实现,以“莫斯科郊外的晚上”为例,其显示函数为: - void cd2()' @5 ?# N" X- g- Z1 Z( s) v
- { // 莫斯科郊外的晚上
- ~' k: k2 A8 {8 L! W( j - OLED_Clearp(); // 局部清除1 I) z7 v+ V# [) X, \% m
- OLED_ShowCHinese(18,2,3);
7 V Z2 e9 |3 a4 { - OLED_ShowCHinese(36,2,4); ' o, G! A. e# F7 A* r& |7 |: @
- OLED_ShowCHinese(54,2,5); & o8 S% T2 F( F- p6 M" Q
- OLED_ShowCHinese(72,2,6); / j3 ]2 q# c; m0 I* L
- OLED_ShowCHinese(90,2,7); / |3 g) u: W' ]/ X. |
- OLED_ShowCHinese(108,2,8);
2 C+ n5 s: E. V* O: R c2 K0 B8 d$ { - OLED_ShowCHinese(18,4,9); $ E8 m$ y* F" o5 t1 d/ d! `, G
- OLED_ShowCHinese(36,4,10);
" R3 f# M- D4 u( o+ T8 O' R3 w& k: ? - }
复制代码MP3点播器的处理流程为: 选择歌曲播放的方向(前2项)->选择歌曲->选择播放中的控制功能(后4项)->轮回处理 由于在NUCLEO_L073开发板上只配置了一个供控制所用的按键,为了省去另加按键的麻烦,这里是以单按键的方式来执行选择处理,即短按为选择,长按为确认。 在点播过程中,主要分为两类操作,即歌曲的选择(可选上一首或下一首)和播放控制(暂停、退出、加大音量、减小音量)。 受OLED屏显示信息量的限制,在歌曲选择时,是随着选择来更换歌曲名称;而在播放控制时,是通过控制工具栏的符号闪动来提示当前供选择的功能项,其处理效果如图7所示。 菜单选择的程序代码如下: - f=1; // 进入菜单选择" q* K; h+ ]1 N+ _ \7 x
- while(f)5 N1 H, h. ~4 {5 q8 [
- {
5 ?+ \! V: P8 `! i) V - if(i==0) cd1(); // 雪绒花
- h$ u; R& w( U1 f- ^% p - if(i==1) cd2(); // 莫斯科郊外的晚上 : W$ Q( A( u8 L4 D- D' h
- if(i==2) cd3(); // 等待
) ?& t! Q7 d7 \8 E$ d, H+ v - if(i==3) cd4(); // 鸿雁
+ m7 t4 N5 t% j s) s! E6 ~, @& g% g4 m - if(i==4) cd5(); // 贝加尔湖畔2 p; l! u2 Q5 J# C
- if(i==5) cd6(); // 春暖花开4 s( L, b$ @* @$ |9 X# \" v5 V
- if(i==6) cd7(); // 传奇- q8 `* c# D' z
- if(i==7) cd8(); // 她+ ?+ G' s5 g+ d- S. i1 T" r {
- if(i==8) cd9(); // 味道7 ?0 n4 i* {* G3 s- U, y* m
- if(i==9) cd10(); // 放心去飞
& `2 d8 K6 u' a) m - cdm(); // 功能键提示栏显示
: T, F9 G6 y6 s; c' Y. z - while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0);4 M5 z9 I! B. K% d8 e$ G( I7 W6 R
- HAL_Delay(6000); // 长短按区分延时
l' B+ g/ N& S5 y. @" H9 o* D' u - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;% ~) ~# b( t5 m6 N, `! G
- else& V- p$ W8 Q! h$ ^' V
- {
) F( f* Z" `7 Y" B& a; { - i++;) z4 Y7 t" u- h0 u0 M$ `
- if(i>9) i=0; // 轮回处理
& s0 i! c8 r- Z6 H - }( [& o e" E( d
- }
复制代码播放控制的处理代码如下: - cdm(); // 显示功能选择栏
6 L) p% ^6 A, `& Q) ~ - f=1;
( z8 I6 B. d: t. z8 Z - while(f)$ s% R2 j7 _% N3 I! P; k# Z/ A/ p
- {/ @( ^. }, m- k) ]
- while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)!=0)
# H5 n" V+ R# `: X" H; \ - {
" J0 m, h! V9 W7 Q0 G6 m9 z& z0 Y - // 产生当前功能选择项闪动效果8 C, X @6 G6 T( y, d& u2 h# c9 H
- OLED_ShowCHinese(i*18,6,46); ; h2 \$ s$ e+ S$ A
- HAL_Delay(200);
! ^+ z* {9 b- f2 G- T9 X5 V0 X - cdm();
; q7 g. y( E8 w - HAL_Delay(200);
9 s. z+ b2 u0 z - }$ Z. D9 X2 S2 H3 }. D# T7 e: D
- HAL_Delay(700);* X5 t- \5 ]# J2 g2 s7 k
- // 长短按区分处理
& E( x2 M8 N6 p- A4 w$ X4 q" m - if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==0) f=0;
" q4 K& d9 Z9 @! X+ t - i++;
6 \ V. y& o& Y3 P6 C4 `3 b - if(i>5) i=0; // 功能选择轮回处理 2 b$ }1 s6 H3 D5 {5 U/ b# r0 P
- HAL_Delay(500);
# c" B. n" t3 Y3 d' V7 M# D1 L: b) u - }
复制代码在指令控制的实现上,主要涉及串行通讯的初始化、指令生成与输出等函数。测试过程中,可通过串口助手来验证相应的指令功能。 常用的指令有: 播放_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()
$ m o. h8 H* v9 c - {
. W& n4 e! {6 ^+ A5 ?2 l - // 定义指令内容- i. T5 O; o3 Q4 C5 u% n
- unsigned cha play_cmd [10] = ) t- b5 H. a4 O5 Q8 A
- { 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};/ H& O0 u) q/ o8 Q- z
- sendCmd (play_cmd);2 P4 O: G: h# b# g8 g3 y
- }
复制代码未来的改进方向,是采用TFT触摸屏来取代OLED屏显示,从而使菜单所列出的选项更丰富,操作也更便捷。此外,采用硬字库替代小字库也使菜单的更新更灵活。当然你要有承担造价会较大提升的心理准备哟!
8 H5 O5 a$ J& e0 F |
unsigned cha play_cmd [10] =
{ 0X7E, 0xFF, 0x06, 0X0D, 00, 00, 00, 0xFE, 0xee, 0XEF};( q3 H$ l! `3 c8 s4 i( M
sendCmd (play_cmd);; X# c- \5 r- u' ?4 O6 o- U/ g
}& r" u3 e8 W& a/ l" ]$ e( i* {
以数组的方式按字节发送指令内容,至于指令是按参考资料分析出来的。
楼主能不能加个qq啊 我想详细咨询一下这个到底该怎么去做1658348073
太具体的也难帮上你了,前一段电脑完蛋了,多年的积累毁于一旦。此外,本来的双核大赛前三,也因工程文件的丢失而名落孙山,唉参透了!
多谢了!
谢了!
板子上少了flash作为字库,否则用串口传一下就可以。
谢了!
不妨一试