基于MSP430G2553官方开发板的音乐播放器

实现目标

  1. 实现以蜂鸣器为播放设备,能够对简谱乐曲进行解码播放。
  2. 具有循环列表,可实时切换上下曲目,实时暂停和开始,实时通过齿轮电位器调节播放音量。
  3. 能够将歌曲列表等信息,通过串口向上位机传输并显示。
    完成图展示

硬件资源

芯片资源使用情况

  • 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()上,实现在上位机打印信息的功能

外接硬件

外接电路板图

  1. 按键x3 (4.7k电阻x3,我的电路接的是按下为高电平,是为了失效实验板原来P1.3的按下为低电平的按键)
  2. 1k齿轮电位器x3 (510欧电阻x1,也可以选择其他的组合,原则上在降低最小电流的情况下尽量提高可测量的范围)
  3. 低电平触发的蜂鸣器模块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.4

char TA0_PWM_Init(char Clk,char Div,char Mode1,char Mode2)
{
  TA0CTL =0;																		// 清除以前设置

  switch(Clk)  																		//为定时器TA选择时钟源
  {
    case 'A': case 'a':  	TA0CTL|=TASSEL_1; break;   			//ACLK
    case 'S': case 's': 	TA0CTL|=TASSEL_2; break;  			//SMCLK
    case '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;   //1
    case 2:   TA0CTL|=ID_1; break;   //2
    case 4:   TA0CTL|=ID_2; break;   //4
    case 8:   TA0CTL|=ID_3; break;   //8
    default :  return(0);  														//设置参数有误,返回0
  }

    switch(Mode1)																	//为定时器选择计数模式
    {
    case 'F': case 'f':																//普通PWM
  		  TA0CTL |=MC_1; break;											//主定时器为增计数
    case 'B':case 'b':
  	  	  TA0CTL |=MC_1; break;											//主定时器为增计数
    case 'D': case 'd':																//死区PWM
       	  TA0CTL |=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 settle
    ADC10CTL0 |= ENC + ADC10SC;			// Sampling and conversion start
    __bis_SR_register(CPUOFF + GIE);	// Low Power Mode 0 with interrupts enabled
    
    ADC_value = ADC10MEM;
    valum =((ADC_value-333.0)*100)/688.0; // Assigns the value held in ADC10MEM to the integer called ADC_value
    volume_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 = 1MHz
    P1SEL |= BIT7;					// ADC input pin P1.7
    ADC10CTL1 = INCH_7 + ADC10DIV_3 ;         // Channel 3, ADC10CLK/3
    ADC10CTL0 = SREF_0 + ADC10SHT_3 + ADC10ON + ADC10IE;  // Vcc & Vss as reference, Sample and hold for 64 Clock cycles, ADC on, ADC interrupt enable
    ADC10AE0 |= 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 reset
    WDTCTL = 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);
  }
}

最为主要的部分函数实现如上所示
此工程为嵌入式大作业所做,可以很容易的移植为其他单片机平台使用,很值得一看。
完整工程文件点此获取

  • 11
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值