历史上的今天
返回首页

历史上的今天

今天是:2025年02月10日(星期一)

正在发生

2020年02月10日 | 基于MSP430G2553官方开发板的音乐播放器

2020-02-10 来源:eefocus

实现目标

实现以蜂鸣器为播放设备,能够对简谱乐曲进行解码播放。

具有循环列表,可实时切换上下曲目,实时暂停和开始,实时通过齿轮电位器调节播放音量。


能够将歌曲列表等信息,通过串口向上位机传输并显示。

完成图展示

硬件资源

芯片资源使用情况

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


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

#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


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


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

推荐阅读

史海拾趣

Cantherm公司的发展小趣事

Cantherm公司在电子行业中以其技术创新而著称。在一次重要的研发项目中,公司团队成功开发了一种新型的散热技术,该技术能有效降低电子设备在工作时产生的热量,从而提高设备的性能和稳定性。这一突破不仅为公司带来了大量的专利和知识产权,还吸引了众多知名电子产品制造商的合作意向,使Cantherm在行业中崭露头角。

HIT(日立)公司的发展小趣事
在电路设计时充分考虑各种因素,如元件的耐压、耐流能力,以及电路的抗干扰能力等,确保电路能够稳定工作。
复旦微电子(FM)公司的发展小趣事
定期对电路进行检查和维护,及时发现并处理潜在问题,确保电路的安全性和稳定性。
Connor-Winfield公司的发展小趣事

随着全球经济的一体化,电子行业也逐渐呈现出全球化的趋势。Connor-Winfield敏锐地把握住了这一机遇,开始实施全球化战略。公司积极拓展海外市场,与多家国际知名企业建立了紧密的合作伙伴关系。这些合作不仅为公司带来了更多的商业机会,也使其在全球化竞争中保持了领先地位。

Elpac公司的发展小趣事

作为一家有社会责任感的企业,Elpac公司始终关注环境保护和可持续发展。公司积极采用环保材料和清洁能源,努力降低生产过程中的能耗和排放。同时,Elpac公司还积极参与各种公益活动和社会事务,回馈社会、关爱弱势群体。这些举措不仅提升了公司的品牌形象和社会影响力,也为公司的长远发展注入了正能量。

以上是关于电子行业里某假设性“Elpac公司”的发展故事,希望对您有所帮助。

City_Technology公司的发展小趣事

为了进一步推动公司的发展,City Technology于1993年决定售出部分股份,并在1996年成功在伦敦股票交易所上市。这一举措为公司带来了更多的资金支持,也为其后续的资本运作和战略扩张提供了可能。上市后的City Technology在资金、人才和市场资源等方面得到了极大的提升,为其日后的快速发展奠定了坚实的基础。

问答坊 | AI 解惑

秀一下论坛送的NXP的小音箱

小音箱不错!刚拿到音箱就迫不及待的给它照了几张!! 下面上图: 在用的时候还会还有彩灯闪烁!! 还有时钟,闹钟! 很不错!继续努力!!…

查看全部问答>

windows 编程 关于无线上网 端口号的 问题?

我们现在需要建立一个 100个节点的无线网络, 这 100 个节点中有97个是现场的 无线数据模块,通过手机卡实现 无线上网,3个节点 是数据中心,分别接有一台 PC机(此PC机为固定IP), 然后 这97个 无线模块 分别 向一台 PC机发送数据. ...…

查看全部问答>

求助:三星k9f2g08uom 驱动问题

谢谢关注! 我使用的板子是friendly arm 的micro2440a,因为要做nand boot启动,需要开发k9f2g08uom(每页2048byte,2048 block,每块64页)的驱动,目前碰到两个问题: 1,程序烧写不正确。使用sjf2440 软件烧写,然后读出,发现部分数据烧写部分不 ...…

查看全部问答>

keil c 编程方面的问题 跪请好心人帮忙 100分酬谢是小 大恩难忘

我要用keil c 编一个火灾报警的程序。用的探测器是感温、气体探测器,就是检测到温度或着某种气体(像CO)超标后,蜂鸣器发出报警,LED背光、休眠指示灯闪烁。硬件有MCU,串口,键盘,锁存、地址,外扩RAM,蜂鸣器,背光、休眠指示灯,滤波电路,液 ...…

查看全部问答>

wince USB摄像头驱动

我现在在写wince 下的USB摄像头驱动。开发板是S3C2410,摄像头是中星微301p.在网上看了很多资料,但是写出来的还是有问题。我已经把ZC0301P.dll,ZC0301P.reg,Platform.bib,放在了移动设备的windows下面。每次把摄像头插上去,就提示“输入此USB设 ...…

查看全部问答>

求一crc16校验源码

急啊,急啊, 帮帮我吧…

查看全部问答>

嵌入式怎么学啊?没人教我的 只能自己自学的 (在线等)

我是专科的   快大三了   熟悉C语言和汇编   对系统底层和linux下的API编程比较了解   看过一个小的linux原码(0.11)的那个 想转学嵌入式但不知道怎么入手 学嵌入式要开发板的吧   那个ARM9 ...…

查看全部问答>

这算不算STM8的BUG

我在main()函数里边定义了两个整开变量 unsigned int save1; unsigned int save2; 我仿真后发现svae1变了,save2也根着变,看一下地址才发现一样。 我又把两个定义拿到main外边。这时正常了,这是一件事,还有一件就是如下: for(;;) { ...…

查看全部问答>

STM32的低功耗唤醒问题

最近的一个STM32的应用需要进入低功耗,结果碰到一个问题很奇怪,流程如下: 1)通过WFI进入SLEEP模式,然后通过Systick来唤醒 2)通过调用库函数(如下)进入STOP模式,然后由外部电路通过一个外部中断引脚唤醒;唤醒后,重新初始化HSE和PLL ...…

查看全部问答>

【投票】说说你来【最爱TI M3/M4-Stellaris!】版块多长时间了

        今天又有几个热心的网友获得了强大的 LM3S9B96 开发板,心里为他们高兴的同时,也回想起了一年前我刚来到这个论坛的时候。         一年过去了,自从我接触 TI M3 ...…

查看全部问答>