1978|0

6828

帖子

0

TA的资源

五彩晶圆(高级)

楼主
 

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”

各部分硬件驱动

然后将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 }
}

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

 
点赞 关注

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表