基于MSP430G2553官方开发板的音乐播放器
基于MSP430G2553官方开发板的音乐播放器
- 实现目标
- 硬件资源
- 芯片资源使用情况
- 外接硬件
- 程序实现
- 开发环境配置
- 各部分硬件驱动
- 主循环功能实现
实现目标
- 实现以蜂鸣器为播放设备,能够对简谱乐曲进行解码播放。
- 具有循环列表,可实时切换上下曲目,实时暂停和开始,实时通过齿轮电位器调节播放音量。
- 能够将歌曲列表等信息,通过串口向上位机传输并显示。
硬件资源
芯片资源使用情况
- P1.3 P1.4 P1.5:使用了3个io作为按键输入
- P1.7:一个ADC通道采集电位器的变化情况
- P1.6:一个定时器a的PWM输出通道
- P1.0:一个io输出接到led作为运行状态显示
- P1.1 P1.2:串口1映射到printf()上,实现在上位机打印信息的功能
外接硬件
- 按键x3 (4.7k电阻x3,我的电路接的是按下为高电平,是为了失效实验板原来P1.3的按下为低电平的按键)
- 1k齿轮电位器x3 (510欧电阻x1,也可以选择其他的组合,原则上在降低最小电流的情况下尽量提高可测量的范围)
- 低电平触发的蜂鸣器模块x1 (无源蜂鸣器,淘宝两三块一个,不需要加放大电路直接可以用)
程序实现
本播放器的主体功能代码来自于RT-Thread的播放器教程,本身用于Kiel下的STM32单片机。由于原始程序需要OS提供的时间片轮转支持,对于移植时候的逻辑构建造成了很大障碍,所以在本工程之前没有将其移植到MSP上的类似案例。
RT-Thread教程请点这里
开发环境配置
一开始想使用CCS进行工程开发,可以很轻松的利用官方硬件驱动。但是由于未知的原因,CCS对存储简谱的数组疯狂报错,导致最终选择转移到IAR下完成了工程。
相较于CCS,IAR下新工程需要配置的内容更为简洁
首先在工程设置中将Device选成当前使用的芯片型号
然后将Debugger中的Driver选项从模拟改成硬件
此两步之后就完成了对于新建工程的配置,至于添加PATH的操作和其他开发工具基本一致。
注意:CCS下使用的头文件在IAR下容易报错,需要改换成"io430g2553.h"
如果需要使用中断,则还需#include “in430.h”
各部分硬件驱动
- LED
#include "led.h"
#include "io430g2553.h"
#include <stdint.h>int led_init(void)
{/* 设定 LED 引脚为输出模式 */P1DIR = LED_PIN_R;P1OUT &= ~LED_PIN_R;return 0;
}int led_on(void)
{/* 调用 API 输出低电平 */P1OUT |= LED_PIN_R;return 0;
}int led_off(void)
{/* 调用 API 输出高电平 */P1OUT &= ~LED_PIN_R;return 0;
}int led_toggle(void)
{/* 调用 API 读出当前电平 然后输出相反电平 */P1OUT ^= LED_PIN_R;return 0;
}
- PWM
#include "io430g2553.h"#define DEADTIME 20 //预设死区时间,以TA的clk为单位
/*******设定TA输出IO口,目前设定为MSP430G2553,20Pin封装无TA0.2********/
#define TA01_SET P1SEL |= BIT6; P1DIR |= BIT6 //P1.6
#define TA02_SET P3SEL |= BIT0; P3DIR |= BIT0 //P3.0
#define TA11_SET P2SEL |= BIT2; P2DIR |= BIT2 //P2.2
#define TA12_SET P2SEL |= BIT4; P2DIR |= BIT4 //P2.4
#define TA01_OFF P1SEL&= ~BIT6 //P1.6
#define TA02_OFF P3SEL &= ~BIT0 //P3.0
#define TA11_OFF P2SEL &= ~BIT2 //P2.2
#define TA12_OFF P2SEL &= ~BIT4 //P2.4char TA0_PWM_Init(char Clk,char Div,char Mode1,char Mode2)
{TA0CTL =0; // 清除以前设置switch(Clk) //为定时器TA选择时钟源{case 'A': case 'a': TA0CTL|=TASSEL_1; break; //ACLKcase 'S': case 's': TA0CTL|=TASSEL_2; break; //SMCLKcase 'E': TA0CTL|=TASSEL_0; break; //外部输入(TACLK)case 'e': TA0CTL|=TASSEL_3; break; //外部输入(TACLK取反)default : return(0); //设置参数有误,返回0}switch(Div) //为定时器TA选择分频系数{case 1: TA0CTL|=ID_0; break; //1case 2: TA0CTL|=ID_1; break; //2case 4: TA0CTL|=ID_2; break; //4case 8: TA0CTL|=ID_3; break; //8default : return(0); //设置参数有误,返回0}switch(Mode1) //为定时器选择计数模式{case 'F': case 'f': //普通PWMTA0CTL |=MC_1; break; //主定时器为增计数case 'B':case 'b':TA0CTL |=MC_1; break; //主定时器为增计数case 'D': case 'd': //死区PWMTA0CTL |=MC_3; break; //主定时器为增减计数default : return(0); //其他情况都是设置参数有误,返回0}switch(Mode1) //设置PWM通道1的输出模式。{case 'F': case 'f':TA0CCTL1 = OUTMOD_7;TA01_SET;break;case 'B': case 'b':TA0CCTL1 = OUTMOD_3;TA01_SET;break;case 'D': case'd':TA0CCTL1 = OUTMOD_6;TA01_SET;break;case '0':case 0: //如果设置为禁用TA01_OFF; //TA0.1恢复为普通IO口break;default : return(0); //设置参数有误,返回0}switch(Mode2) //设置PWM通道2的输出模式。{case 'F': case 'f':TA0CCTL2 = OUTMOD_7;TA02_SET; break;case 'B': case 'b':TA0CCTL2 = OUTMOD_3;TA02_SET;break;case 'D': case 'd':TA0CCTL2 = OUTMOD_2;TA02_SET;break;case '0':case 0: //如果设置为禁用TA02_OFF; //TA0.1恢复为普通IO口break;default : return(0); //设置参数有误,返回0}return(1);
}char TA0_PWM_SetPeriod(unsigned int Period)
{if (Period>65535) return(0);TA0CCR0 = 12000/Period;return(1);
}char TA0_PWM_SetPermill(char Channel,unsigned int Duty)
{unsigned char Mod = 0;unsigned int DeadPermill=0;unsigned long int Percent=0; //防止乘法运算时溢出Percent=Duty;DeadPermill=((DEADTIME*1000)/TACCR0); //将绝对死区时间换算成千分比死区时间switch (Channel) //先判断出通道的工作模式{case 1:Mod = (TA0CCTL1& 0x00e0)>>5; break; //读取输出模式,OUTMOD0位于5-7位case 2:Mod = (TA0CCTL2 & 0x00e0)>>5; break; //读取输出模式,OUTMOD1位于5-7位default: return(0);}switch(Mod) //根据模式设定TACCRx{case 2: case 6: /**死区模式2,6时,需要判断修正死区时间,且同时设定TA0CCR1/2 的值*/{if((1000-2*Percent)<=DeadPermill) //预留死区时间Percent=(1000-DeadPermill)/2;TA0CCR1=Percent*TA0CCR0/1000;TA0CCR2= TA0CCR0-TA0CCR1;break;}case 7:{if(Percent>1000) Percent=1000;if(Channel==1) TA0CCR1=Percent* TA0CCR0/1000;if(Channel==2) TA0CCR2=Percent* TA0CCR0/1000;break;}case 3: //占空比一律为正脉宽,所以需要 TA0CCR0减去占空比{if(Percent>1000) Percent=1000;if(Channel==1) TA0CCR1= TA0CCR0-Percent*TA0CCR0/1000;if(Channel==2) TA0CCR2= TA0CCR0-Percent*TA0CCR0/1000;break;}default: return(0);}return (1);
}
TA1的驱动函数与TA0相同
TA0_PWM_SetPeriod()此函数中,TA0CCR0 = 12000/Period 的12k应该改为你所配置的低速外设时钟速度,才能获得正确的声音频率
- BEEP
#include "beep.h"
#include <stdint.h>
#include "io430g2553.h"
#include "TA_PWM.h"int beep_init(void)
{ /* 初始化BEEP设备 */
// BCSCTL1 = CALBC1_8MHZ;
// DCOCTL = CALDCO_8MHZ;/* TA0CTL = TASSEL_1 + MC_1 + ID_0; // //TA0设为增计数模式,时钟=ACLK */return 0;
}int beep_on(void)
{ //使能蜂鸣器对应的 PWM 通道TA0_PWM_Init('A',1,'F',0); return 0;
}int beep_off(void)
{//失能蜂鸣器对应的 PWM 通道TA0_PWM_Init('A',1,0,0); //A 12kHz return 0;
}int beep_set(uint16_t freq, uint8_t volume)
{// uint32_t period, pulse;TA0_PWM_SetPeriod(freq); /* 根据声音大小计算占空比 蜂鸣器低电平触发 *//*pulse = period - period / 100 * volume;*/TA0_PWM_SetPermill(7,1000-10*volume); return 0;
}
实现了pwm驱动之后蜂鸣器只需要这几个接口就能正常使用
- KEY
#include "io430g2553.h"
#include "in430.h"
#include <stdint.h>void key_init(void)
{P1REN |=BIT3;P1OUT &= ~BIT3;P1DIR &= ~BIT3;P1REN |=BIT4;P1OUT &= ~BIT4;P1DIR &= ~BIT4;P1REN |=BIT5;P1OUT &= ~BIT5;P1DIR &= ~BIT5;
}void scan_key(void)
{if(P1IN&BIT3){__delay_cycles(10000);NEXT_FLAG = 1;while(P1IN&BIT3);}if(P1IN&BIT4){__delay_cycles(10000);STOP_FLAG = 1;while(P1IN&BIT4);}if(P1IN&BIT5){__delay_cycles(10000);LAST_FLAG = 1;while(P1IN&BIT5);}
}
使用中断模式容易打断ADC模块的转换,所以采用了扫描模式来读取按键状态
- ADC
#include "io430g2553.h"
#include "in430.h"
#include <stdint.h>float ADC_value=0;
float valum;
int volume_a;void change_volume(void)
{__delay_cycles(1000); // Wait for ADC Ref to settleADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start__bis_SR_register(CPUOFF + GIE); // Low Power Mode 0 with interrupts enabledADC_value = ADC10MEM;valum =((ADC_value-333.0)*100)/688.0; // Assigns the value held in ADC10MEM to the integer called ADC_valuevolume_a=100-valum;if(volume_a >= 90)volume_a = 90;else if(volume_a <= 1)volume_a = 1;}void adc_init(void)
{ BCSCTL2 &= ~(DIVS_3); // SMCLK = DCO = 1MHzP1SEL |= BIT7; // ADC input pin P1.7ADC10CTL1 = INCH_7 + ADC10DIV_3 ; // Channel 3, ADC10CLK/3ADC10CTL0 = SREF_0 + ADC10SHT_3 + ADC10ON + ADC10IE; // Vcc & Vss as reference, Sample and hold for 64 Clock cycles, ADC on, ADC interrupt enableADC10AE0 |= BIT7; // ADC input enable P1.3__enable_interrupt(); // Enable interrupts.
}// ADC10 interrupt service routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR (void)
{__bic_SR_register_on_exit(CPUOFF); // Return to active mode }
}
建议直接放在main.c之中减少出错的概率
UART由于映射之后出现了printf()不能每次都成功输出正确信息的情况,最后就移除了这个功能。
主循环功能实现
int main( void )
{// Stop watchdog timer to prevent time out resetWDTCTL = WDTPW + WDTHOLD;clock_init();key_init();player_init();adc_init();while(1){//更改play对象状态change_volume();scan_key();change_player();play_loop(&player);}return 0;
}void play_loop(void *parameter)
{player_t player = (player_t)parameter;uint8_t buffer[PLAYER_BUFFER_SIZE], size;if (player->status == PLAYER_RUNNING){size = player->song_time_all - player->song_time_pass;if (size > PLAYER_BUFFER_SIZE) size = PLAYER_BUFFER_SIZE;size = player->decode->read(player->song_sheet[player->song_current - 1], player->song_time_pass, buffer, size);if (size > 0){player->audio->write(buffer, size);player->song_time_pass += size;}/* 如果播放时间到了,切换到下一首 */if (player->song_time_pass >= player->song_time_all){player_next(player);player_show(player);}}else{/* 暂停播放时关闭音频设备*/player->audio->close();/* 等待播放的信号量 */while(player->status != PLAYER_RUNNING){scan_key();change_player();}/* 开始播放时打开音频设备*/player->audio->open();}
}void change_player(void)
{uint8_t volume;if(LAST_FLAG){LAST_FLAG = 0;player_control(&player, PLAYER_CMD_LAST, 0);}else if(NEXT_FLAG){NEXT_FLAG = 0;player_control(&player, PLAYER_CMD_NEXT, 0);}else if(STOP_FLAG){STOP_FLAG = 0;if (player.status == PLAYER_RUNNING){player_control(&player, PLAYER_CMD_STOP, 0);}else{player_control(&player, PLAYER_CMD_PLAY, 0);}}player_control(&player, PLAYER_CMD_GET_VOL, &volume);if (volume_a != volume){volume = volume_a;player_control(&player, PLAYER_CMD_SET_VOL, &volume);}
}
最为主要的部分函数实现如上所示
此工程为嵌入式大作业所做,可以很容易的移植为其他单片机平台使用,很值得一看。
完整工程文件点此获取
基于MSP430G2553官方开发板的音乐播放器相关推荐
- 基于Arduino Uno开发板制作音乐播放器
基于Arduino Uno开发板制作音乐播放器 本文将基于Arduino开发板实现一个音乐播放器. 利用Arduino Uno读取sd卡模块中内存卡的音乐,传输信号到扬声器进行播放. 一.项目软硬件简 ...
- 基于Android系统开发的简易音乐播放器
大概做了一周左右,一个简易版本的音乐播放器.主要有三个界面,先上图: 一个主界面:主要负责1加载外部存储的音乐文件信息到应用内置数据库中2转到音乐文件列表界面 一个音乐文件列表界面:主要1负责展示音乐 ...
- 基于H5+js开发一款音乐播放器
前言:当下音乐播放器不胜其数,为了更好的掌握一些东西,我们来自己制作一个音乐播放器. 文章目录: 一.开发环境: 二.页面视图: 1.主文件入口(首页): 2.音乐播放界面: 三.功能实现 (1).i ...
- 搬砖之路----MusicPlayer 一个基于Vlc(2.0+)开发的android音乐播放器--浅析在android开发过程中播放器选择之路!
前言 MusicPlayer 是一款基于vlc播放器开发的一个音乐播放器,你也可以理解为在此核心上的搬砖之路,核心的内容并不是我写的,因此在正式写blog之前,感谢那些vlc核心的开发人员让我用到这么 ...
- 基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署
基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署 基于JAVAvue开发一个简单音乐播放器计算机毕业设计源码+数据库+lw文档+系统+部署 本源码技术栈: 项目 ...
- java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署
java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署 本源码技 ...
- java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署
java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 java计算机毕业设计vue开发一个简单音乐播放器MyBatis+系统+LW文档+源码+调试部署 本源 ...
- 计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档)
计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档) 计算机毕业设计Javavue开发一个简单音乐播放器(源码+系统+mysql数据库+lw文档) 本源码技术栈 ...
- JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库
JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈: 项目 ...
最新文章
- C#.NET 大型通用信息化系统集成快速开发平台 4.1 版本 - 角色成员功能的改进支持公司加入到角色...
- 2021技术人新展望
- 超震撼!你没见过的24张震撼照片
- 7-1 作业调度算法--先来先服务 (30 分)(思路+详解+vector+map+map做法)Come Baby!!!!!!!!!!!
- turtle fillcolor_使Python中的turtle模块画图两只小羊
- js json过滤_如何在浏览器不崩溃的情况下过滤 200 万行数据?
- 安阳师范学院计算机与信息工程学院吴琴霞,基于甲骨文字形动态描述库的甲骨文输入方法...
- 特斯拉地图数据服务以后由百度地图提供
- c语言用栈输出迷宫所有路径,如何在迷宫中使用到栈
- DataGridView的DataGridViewComboBoxColumn列点击后触发其他方法
- 0514JS函数练习
- 【产品必备软件合集】
- HeadFirstJava 11异常处理
- html微博分享功能,js页面文字选中后分享到新浪微博实现
- linux操作系统启动盘,轻松制作Linux操作系统启动盘的四种通行方法
- wps如何对比两列数据找出不同
- 苹果手机html吊起拍照,一张好照片不是只按快门 iPhone手机拍照指南
- 细说 AppbarLayout,如何理解可折叠 Toolbar 的定制
- 毕业设计:新闻大数据搜索系统 (完整代码运行)
- Visual Studio/AnkhSVN在VS中出现SVN代码冲突的解决方法