admin管理员组

文章数量:1648662

写在前面:

        写下这篇东西的目的首先是希望自己以后忘记MSP430如何使用的时候,能够在看到这篇记录之后能够迅速想起。其次,也希望能够为其他同学或者有需要的人提供一点帮助,话不多说,下面开始正文。

目录

一、开发环境

二、学习方式

三、程序学习

1、点灯

2、LED闪烁

呼吸灯

3、时钟配置

4、定时器

5、外部中断

6、Uart

7、OLED显示

8、超声波

最后

附录


一、开发环境

        现在网上提到的一般有CCSIARGCC,KEIL,每种开发环境在CSDN上面应该都能找到相应的搭建方法,我学习MSP430也只是为了应付电赛,相应的也就能倾向于使用TI自己出的开发平台CCS 比较方便 (这里附上链接:CCSTUDIO IDE、配置、编译器或调试器 | TI)。下载安装完成之后,无需再配置什么东西,即可开始对于MSP430的开发了。

二、学习方式

        临近电赛,本来是想要在B站或者其他学习网站上面找一些相关视频来快速入门,但是后面觉得网上的教学视频质量参差不齐,而且专门针对F5529的视频比较少。所以在这里以文章的形式,让大家可以快速的上手MSP430F5529。

        网上的视频中有很大一部分都是采用的driverlib库函数来写的,我当时因为各种奇奇怪怪的原因,始终用不了driverlib同时也发现430的寄存器配置好像也不是很难,就决定使用寄存器配置MSP430了。

        做出选择很简单,只是一两秒的事情。但真的想要上手寄存器配置,还是需要花费大量的时间的。这里推荐硬禾学堂上面的中央民族大学信息工程学院教授王继业老师讲的入门MSP430的视频(附上链接:MSP430单片机原理(一个简易版的微机原理)),当时看了真的是受益良多。B站上面也有很多同学讲解了寄存器配置的视频,这里就不举例了,有想法的同学自己去找一找。

        使用寄存器配置MSP430,除了根据网上的资料来了解一些常用的寄存器是什么东西,怎么配置之外,我们还需要另一种东西:MSP430的 user’s guid 和 datasheet (在官网上面搜索你使用的430型号,就可以找到相应的用户手册啦~)。这两个分别可以让我们了解我们手上的芯片里面有哪些寄存器寄存器的使用方法;芯片的一些管脚功能重要参数等等。在比赛途中,如果网上找不到自己对应芯片已经写好的例程,那么会用用户手册是十分必要的。

        东西准备齐全之后,就让我们一步步来写关于MSP430的代码吧!

三、程序学习

1、点灯

        相信我们学习任何一门语言,都是先来一遍Hello World! ,学习一款新的单片机,往往第一步就是点灯。下面来看程序:

void main()
{
    /*stop watch dog timer*/
	WDTCTL = WDTPW | WDTHOLD;

    /*led1,p1.0*/
    P1DIR |= BIT0;
    /*熄灭状态*/
//    P1OUT &= ~BIT0; 
    /*点亮状态*/
    P1OUT |= BIT0;  
    
    while(1)
    {
        ;
    }
}

        MSP430配置IO ,PxDIR寄存器来设置是输入还是输出,为了避免影响其他的IO口,我们一般采用的是 |=  。BIT0 代表的就是我们使用的是第0脚,具体含义可以自己查看定义。意思就是根据相关寄存器的定义,将想要设置为输出的IO口的位置置1,就可以了。

2、LED闪烁

        想要LED闪烁的方法有很多,这里直接在while循环里面改变LED输出状态就可以实现反转。

代码如下:

void main(void)
{
	WDTCTL = WDTPW | WDTHOLD;	// stop watchdog timer
	P1DIR |= BIT0;
	P4DIR |= BIT7;
	P1OUT |= BIT1;
	P4OUT |= BIT7;
	while(1)
	{
	    P1OUT ^= BIT0;
	    delay_ms(60);
	    P4OUT ^= BIT7;
	    delay_ms(60);
	}
}

        关于延时函数,我看网上大多是采用的__delay_cycles();来完成延时的,这里用的是宏定义,代码如下:

#define CPU_F ((double)1000000)
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))//重新定义延时函数
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))

        需要注意的是,如果我们没有主动配置时钟源,那么默认是1Mhz,如果修改了时钟频率,那么上面的代码应该也需要对应修改的。

呼吸灯

        我们在学习PWM过程中,呼吸灯也是我们比较基础的东西,那怎么才能实现LED呼吸灯的效果呢?

        一般为了准确的控制占空比,我们还是需要用到定时器的。

void TimeA0__PWM_Init(void)
{
    P1SEL |= BIT3; //IO口复用
    P1DIR |= BIT3;

    TA0CTL = TASSEL__SMCLK  + MC_3; //SMCLK,增减模式,计数到CCR0处

    TA0CCR0 = 0x00ff;   //都设置为00ffH,则初始占空比为0
    TA0CCR2 = 0x00ff; //占空比(TACCR0 - TACCR2) / TACCR0,频率 = SMCLK / (TACCR0 + 1) / 2

    TA0CCTL2 = OUTMOD_6; //选择比较模式,模式6:Toggle/set

}


int cnt;
int main(void)
{
	WDTCTL = WDTPW | WDTHOLD;	// stop watchdog timer
	TimeA0__PWM_Init();
	while(1)
	{

	    for(cnt=0;cnt < 0x00ff;cnt++)
	    {
	        TA0CCR2 = cnt;
	        __delay_cycles(5000);

	    }
	    for(cnt=0x00ff;cnt > 0;cnt--)
	    {
	        TA0CCR2 = cnt;
	        __delay_cycles(5000);

	    }
	}

}

        通过在while循环里面不断改变CNT的值,从而实现占空比的不断改变,大家对这个方法应该都是比较熟悉的。 

3、时钟配置

        对于MSP430来说,时钟的配置是十分的复杂的,相信大部分童鞋希望的是直接配置成想要的时钟频率对吧?这里为了节省大家时间,直接附上一份CSDN上大佬的配置时钟文章链接:(MSP430F5529系统时钟频率设置---超频40MHz_Coder_BCM的博客-CSDN博客)

这是大佬的超级代码:

void Clock_Init(unsigned char Fre)//修改主频程序
{
    /*开启外部两个时钟*/
    P5SEL |= BIT2|BIT3|BIT4|BIT5;

    UCSCTL6 |= XCAP_3|XT1OFF;          // XT1 相关 配置
    UCSCTL6 |= XT2DRIVE_0 |XT2OFF;     // XT2 相关 配置

    /*以下是提升核心电压部分的代码*/
    PMMCTL0_H = 0xA5;                                          //开PMM电源管理
    SVSMLCTL |= SVSMLRRL_1 + SVMLE;                            //配置SVML电压
    PMMCTL0 =  PMMPW + PMMCOREV_3;                             //配置内核电压
    while((PMMIFG & SVSMLDLYIFG ) == 0);                       //等待设置完成
    PMMIFG &= ~(SVMLVLRIFG + SVMLIFG + SVSMLDLYIFG);
    if((PMMIFG & SVMLIFG) == 1)                                //判断内核电压是否上升到
    while((PMMIFG & SVMLVLRIFG) == 0);                         //如果没有等待
    SVSMLCTL &= ~SVMLE;                                        //关掉SVML模块
    PMMCTL0_H = 0X00;

    __bis_SR_register(SCG0);                 //该语法为固定格式,意为将括号内的变量置位,

    UCSCTL0 = 0;                             //先清零,FLL 运行时,该寄存器系统会自动配置,
    UCSCTL6 = (UCSCTL6&(~(XT2OFF|XT1OFF))|XCAP_3|XT2DRIVE_0);
    UCSCTL3 = (5<<4)|(2<<0);                 //选择 XTAL2 的时钟信号作为参考信号 并且分频到
    UCSCTL4|= SELA_5;                        //选择 XTAL2 的时钟信号作为参考信号
    if(Fre < 5)
        UCSCTL1 = DCORSEL_2;
    else if(Fre<15)
        UCSCTL1 = DCORSEL_4;
    else
        UCSCTL1 = DCORSEL_7;
   UCSCTL2 = (Fre-1);
    __bic_SR_register(SCG0);
    __delay_cycles(782000);
    while (SFRIFG1 & OFIFG) {                               // Check OFIFG fault flag
      UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);           // Clear OSC flaut Flags
      SFRIFG1 &= ~OFIFG;                                    // Clear OFIFG fault flag
    }
    UCSCTL4 = UCSCTL4&(~(SELS_7|SELM_7))|SELS_3|SELM_3;
}

        体验是非常良好的,建议大家都去看看大佬文章里具体是怎么实现的。但我在使用途中出现了一个问题,我将主频设置到了40Mhz之后,Uart最高的频率只能20Mhz,这导致我无法再使用Uart,我尝试过使用其他的时钟源,但是效果都不理想,后面因为时间问题,没有时间仔细琢磨了,就直接将主频设置为20Mhz使用了。如果有同学知道这个问题怎么解决,可以在评论区说明一下~~。

4、定时器

        看到这里,相信大家对于MSP430都有一个简单的理解了,其实和其他的单片机差不多,都含有各种外设,只是配置的方式有些许变化而已,一通万通!

        F5529的定时器有TimerA 和 TimerB ,两个定时器下面还能细分,这里就不再过多介绍,大家不清楚的自行CSDN。。。 ,我们一般使用的是基本定时功能,PWM输出用来驱动电机。代码如下:

//基本定时器配置
void TimerA1_Init(void)
{
    TA1CTL = TASSEL_2 + TACLR + MC_1;//SMCLK,增计数模式,计数到CCR0处
    TA1CCTL0 = CCIE;         //捕获/比较中断使能
    TA1CCR0  = 19999;        //设定计数值20000, 20Mhz耗时1ms
    TA1CTL  &= ~TAIFG;       //清除标志位
}
/*
 * 功能:初始化PWM
 */

void PWM_Init(void)
{
    /*设置P1.2/3/4/5为输出*/
    P1DIR |= BIT2 + BIT3 + BIT4 + BIT5;
    /*复用IO为PWM输出*/
    P1SEL |= BIT2 + BIT3 + BIT4 + BIT5;
    /*设置自动重装载值Period*/
    TA0CCR0 |= PWM_Period ; //设置为800,为10KHZ的PWM
    /*选择时钟为SMCLK,计数模式为增计数模式*/
    TA0CTL |= TASSEL_2 + MC_1 + TACLR;
    /*设置PWM的输出模式为模式7*/
    TA0CCTL1 |= OUTMOD_7;
    TA0CCTL2 |= OUTMOD_7;
    TA0CCTL3 |= OUTMOD_7;
    TA0CCTL4 |= OUTMOD_7;
}

        这里需要说明一下的是,我是使用PWM来驱动电机的,两路PWM可以驱动一个减速电机,这里配置了四路,驱动两个电机。

/*
 * @brief:设置PWM占空比
 */
void set_pwm(int Motor ,int PWM)
{
    switch (Motor)
    {
        case 1: if(PWM > 0) {
            TA0CCR1 = PWM; TA0CCR2 = 0;
        } else
        {
            TA0CCR1 = 0; TA0CCR2 = -PWM;
        } break;
        case 2: if(PWM > 0) {TA0CCR3 = 0; TA0CCR4 = PWM;} else {TA0CCR3 = -PWM; TA0CCR4 = 0;} break;
        default: TA0CCR1 = 0; TA0CCR2 = 0;TA0CCR1 = 0; TA0CCR2 = 0; break;
    }

}

        通过设置占空比的值,来达到控制电机速度以及正反转,说到这里不得不吐槽一下某宝上某趣买的电机真的不好用,设计的跟个鬼一样。。。 ,当然,如果你觉得这里设置占空比直接填数字不方便直观,还可以简单的映射一下。

        关于这个编码器测速,我尝试了一下之后,但是感觉效果不是很理想,最终没有使用编码器,这里就不多说了,有兴趣的同学可以自己尝试一下。

        差点忘记了,我们配置完定时器,肯定是要写相应的中断函数的,代码如下:

#pragma vector = TIMER1_A0_VECTOR
__interrupt void TIMER1_TA0_ISR(void)
{
    static uint16_t cnt = 0;
    if(TAIFG)
    {
        cnt++;

        if(cnt == 1000) 
        {P1OUT ^= BIT0 ; cnt = 0;}

    }
    TA0CTL ^= TAIFG;
}

        不同的中断,中断向量是不一样的,对于不同的中断函数,要填写对应的中断向量。记得一定要清除标志位,不然会出现程序一直在中断的情况!!!

5、外部中断

        在LP板上,我们一般需要对两个按键写对应的外部中断,来对应表示我们按键按下做一些事情。代码如下:

void Key_Init(void)
{
    /*配置P2.1*/
    P2DIR &= ~BIT1;          //设置P2.1口为输入
    P2IN |= BIT1;
    P2REN |= BIT1;
    /*配置P2.1,开启中断*/
    P2OUT |= BIT1;          //配置P2.1上拉电阻
    P2IE |= BIT1;           //P2.1中断使能
    P2IES |= BIT1;          //P2.1下降沿触发中断
    P2IFG &= ~BIT1;        //清除中断标志位

    /*配置P1.1*/
    P1DIR &= ~BIT1;          //设置P1.1口为输入
    P1IN |= BIT1;
    P1REN |= BIT1;
    /*配置P1.1,开启中断*/
    P1OUT |= BIT1;          //配置P1.1上拉电阻
    P1IE |= BIT1;           //P1.1中断使能
    P1IES |= BIT1;          //P1.1下降沿触发中断
    P1IFG &= ~BIT1;        //清除中断标志位

    _EINT();

}

#pragma vector = PORT2_VECTOR
__interrupt void P2_ISR(void)
{
    uint16_t a;
    if(P2IFG & BIT1)  //判断是否是P2.1产生的中断
        {
            P2IFG &= ~BIT1;
            if((P2IN&BIT1)==0)
            {
                for(a=0;a<=1000;a++);  //按键的消抖功能
                if((P2IN&BIT1) == 0)
                {               
                    led3_start();
                }
            }
        }
    P2IFG &= ~BIT1;
}


#pragma vector = PORT1_VECTOR
__interrupt void P1_ISR(void)
{
    uint16_t a;
    if(P1IFG & BIT1)  //判断是否是P1.1产生的中断
    {
       P1IFG &= ~BIT1;
       if((P1IN&BIT1) == 0)
       {
           for(a=0;a<=1000;a++);  //按键的消抖功能
           if((P1IN&BIT1) == 0)
           {
               P4OUT ^= BIT7;
           }
       }
    }
    P1IFG &= ~BIT1;
}

           一般情况下,我们都是需要在主函数里面开启总中断的,开启总中断的表达方式有很多,我一般是用_EINT(); 。

          如果,我们需要一个按键来让小车开始跑起来,那么就需要在按键的中断里写上相应的代码。

6、Uart

        串口往往是必不可少的,本次电赛小车题,我们就采用了蓝牙进行通信,效果还算不错,代码如下:

uint16_t ReBuf[20] = {0};
uint8_t rec_yes = 0;

/*
 * @Brief: Initialize the Uart and set the baud rate
 */
void Uart_Init(void)
{
    /*使能P4.4/P4.5端口复用*/
    P4SEL    |= BIT4+BIT5 ;

    /*Uart Init*/
    UCA1CTL1 |= UCSWRST; //复位USCI_A1
    UCA1CTL1 |= UCSSEL__SMCLK; //选择SMCLK为Uart时钟源@1Mhz
    UCA1BR0  |= 0x23;
    UCA1BR1  |= 0x08; //see user's guid. bps set as 9600@1Mhz

    UCA1MCTL |= UCBRS2 + UCBRF_0; //UCBRS = 1,UCBRF = 0

    /*清除复位,使能Uart*/
    UCA1CTL1 &= ~UCSWRST;

    /*使能中断*/
    UCA1IE |= UCRXIE; //开启接收中断
    UCA1IFG &= ~UCRXIFG; //清除中断标志位
    _EINT();   //使能总中断

}


void Send_String(uint8_t *Ptr)
{
    while(*Ptr != '\0')
    {
        while((UCA1STAT & UCBUSY)); //当上一个还没发送完成后,先卡在这里
        UCA1TXBUF = *Ptr;
        Ptr++;
    }
}


void Send_float(double num)
{
    uint8_t charbuff[] = {0,'.',0,0,0};
    uint16_t temp = (uint16_t)(num * 1000);
    charbuff[0] = (uint8_t)(temp / 1000) + '0';
    charbuff[2] = (uint8_t)((temp % 1000) / 100) + '0';
    charbuff[3] = (uint8_t)((temp % 100) / 10) + '0';
    charbuff[4] = (uint8_t)(temp % 10) + '0';
    Send_String(charbuff);

}


void Print_Num(uint16_t Num)
{
    uint8_t Buf[7] = {0,0,0,0,0,'\r','\n'},cnt;
    for(cnt = 0;cnt < 5 ; cnt++)
    {
        Buf[4-cnt] = (uint8_t)(Num%10 + '0');
        Num /= 10;
    }
    Send_String(Buf);
}


#pragma vector = USCI_A1_VECTOR
__interrupt void USCI_A1(void)
{
    static uint8_t cnt = 0;
    switch(_even_in_range(UCA1IV,4))
    {
    case 0:
        break; // No interrupt pending
    case 2:
        UCA1TXBUF = UCA1RXBUF;    //将接收到的数据发送出去

        /*将接收到的数据存储起来,可以通过处理来对外设进行控制*/
        UCA1IFG &= ~UCRXIFG; //Reset UCRXIFG
        ReBuf[cnt++] = UCA1RXBUF;
        cnt %= 20;
        if(ReBuf[cnt - 1] == '\0') //如果数据接收完成
        {
            cnt = 0;
            rec_yes = 1; //接收完成标志
        }
        rec_yes = 0;
        break;
    case 4:
        break; //TXIFG
    default:
        break;
    }
}

        F5529的串口有两个,现在配置的这一个串口可以直接用USB连接电脑进行数据收发,如果不想使用USB,就需要拔掉板子上面的跳线帽了,另一个串口的配置大致相同,只是需要更改IO引脚与之对应。        

在中断函数里面,我们对于多个中断源共同使用一个中断向量的情况,常常使用switch来判断产生中断的是哪一个。

        如果想要更改波特率和时钟源,更改这三个就好了,具体更改细节请查看用户手册或者CSDN。

UCA1CTL1 |= UCSSEL__SMCLK;
UCA1BR0  |= 0x23;
UCA1BR1  |= 0x08;

 

7、OLED显示

        OLED的显示其实都是在网上找的代码直接CV的,代码长,比较占空间,这里就不过多讲解,我会在文末放上工程链接,以供参考,有需要的童鞋可以自取。

8、超声波

        超声波同样是在网上找的代码,在比赛过程中,引脚冲突了,所以更换了引脚。为了显示方便,将测量精度保持在CM,整体来说效果不错,需要注意的是,我采用的主频是20Mhz,所有代码都是再次基础之上完成的,如果你不是采用的20M,那么算距离的时候也应该进行修改,具体的方法可以CSDN,这里也不过多提及。

#define Trig1(a)  if(a==1) P2OUT |= BIT2; else P2OUT &= ~BIT2

unsigned int cap_new = 0;           // 首次捕捉的ta0r值
unsigned int cap_old = 0;           // 二次捕捉的ta0r值

char cap_N = 0;                     // 溢出次数
char state = 0x00;                  // zhuangtai
uint16_t cap_data = 0;                  // 距离值


/*
 *  P7.4为echo引脚             捕获模式
 *  P2.2 为Trig          数字i/o模式
 */
void Hc_sr_Init(void)           // 超声波模块初始化
{

    P7OUT &= ~( BIT2 + BIT4 );
    P2DIR |=  BIT2;
    P7SEL |=  BIT4;
    TB0CTL   = TBSSEL__SMCLK + ID__8 + MC_2 + TBCLR + TBIE;
    TB0CCTL2 = CM_1 + SCS +CAP + CCIE + CCIS_0;
}

void Hc_sr_Open(void)           //生成一个持续10us的高电平
{
    Trig1(1);
    delay_us(10);
    Trig1(0);
    delay_us(10);
}

#pragma vector=TIMER0_B1_VECTOR
__interrupt void TIMER0_B1_ISR(void)
{
    switch(__even_in_range(TB0IV,14))
    {
      case  0:break;                                 // No interrupt
      case  2:break;                          // CCR1 not used
      case  4:
          state =  TB0CCTL2 >> 14;
          TB0CCTL2 &= ~CCIFG;
          if( TB0CCTL2 & CM_1)
          {
          cap_new = TB0CCR2;
          TB0CCTL2 &= ~CM_1;
          TB0CCTL2 |=  CM_2;
          }
          else if ( TB0CCTL2 & CM_2)
          {
          cap_old = TB0CCR2;
          cap_data = ( cap_old - cap_new ) * 0.34 /50;

          TB0CCTL2 &= ~CM_2;
          TB0CCTL2 |=  CM_1;
          }
          else ;   break;             // 判断CM位捕捉模式 break;                         
      case  6: break;                 // reserved
      case  8: break;                 // reserved
      case 10: break;                 // reserved
      case 12: break;                 // reserved
      case 14:
          TB0CTL &= ~TBIFG;
          if(cap_old   < cap_new ){
              cap_N += 1;
          }
          break;                         
      default: break;
    }
}

        更改主频后,需要对此进行修改cap_data = ( cap_old - cap_new ) * 0.34 /50;

最后

        以上大概就是本次电赛我所使用到的一些东西,虽然最后成绩比价拉跨,但还是收获了很多,希望在将来还可以继续进步,不断学习,加油!

附录

        整个工程文件,需要自取。

https://download.csdn/download/qq_53870874/86341614

 

本文标签: 例程MSP430F5529LP