MSP430F5529小记(01): MSP430Ware使用及时钟配置

MSP430F5529小记(01): MSP430Ware使用及时钟配置

作者 日期 硬件工具 软件工具
CNPP 2019-08-14 MSP-EXP430F5529LP CCS V9, VS Code

前言

相比于意法半导体公司(ST)的STM32系列单片机,德州仪器公司(TI)的MSP430系列同样在单片机领域占有重要的地位,再加上笔者手头有挺多TI家的LanunchPad,感觉还是有必要适应一下TI的MCU产品生态链及开发方式的,那么,就从这块性能适中且比较常见的MSP-EXP430F5529LP LaunchPad 开始吧。

mFIgtH.png

MSP430Ware 安装及CCS配置

对于习惯了STM32库函数开发的笔者来说,硬着头皮一个个配MSP430寄存器实在是难以接受,于是,我还是决定使用TI为MSP430系列准备的MSP430Ware库函数开发包,其中包含了我们接下来要用到的外设驱动库DriverLib,以及USB、DSP、GUI等丰富的中间层库。当然,寄存器开发也是一项值得掌握的重要的技能。

MSP430Ware可以直接在CCS的Resource Explorer中下载并安装,也可以在TI官网下载。由于Resource Explorer时常打不开,还是推荐后者。笔者建议将Ware等相关Packages都安装在CCS的同一目录下以便管理,另外,Resource Explorer中改变安装路径的选项位于页面右上角的菜单图标中。

mFoOaD.png
mFIrnK.png

安装完成后,CCS在启动时理论上能自动检测到安装包,不过笔者并没有遇到过,所以还是需要我们手动安装。此前笔者在如何安装MSP430Ware并与CCS联动上花费了大量的时间,由于网上的资料大多针对的是较老版本的CCS,在笔者使用的CCS V9.0.1上根本行不通,不过好在总算摸索出了方法。浏览MSP430Ware的安装目录,可以在msp430ware_3_80_07_00\driverlib下找到说明文件release_notes,其中详细描述了手动安装的方法:

mFIs0O.png
mFHi4g.png

完成后,重启CCS,就能快乐地建立MSP430 DriverLib 工程了:

mFIBX6.png

参考例程及库函数讲解

对于库函数开发笔者参考的资料主要是以下四个手册:

mFovPH.png

分别为库函数用户手册、单片机数据手册、LaunchPad参考手册以及单片机用户手册。另外,位于msp430ware_3_80_07_00\driverlib\examples\MSP430F5xx_6xx下的参考例程是官方库函数的最好教材。

MSP430的时钟系统被称为“统一时钟系统”,即UCS,手册中的相关介绍及库函数均以其打头(DriverLib User’s Guide中的函数介绍是以首字母顺序排列的,时钟配置反而在后面)。关于UCS的具体介绍,笔者才学疏浅,就不多说了,我们就直奔主题,想办法将LaunchPad板上连接在XT1的32.768KHz时钟晶振和连接在XT2上的4MHz高速晶振利用上,并将辅助时钟ACLK设置为32.768KHz,将主时钟MCLK及子系统主时钟SMCLK设置为XT2的六倍频,即24MHz。查阅MSP430x5xx and MSP430x6xx Family User’s Guide中UCS部分的时钟树,我们即可确定编程思路:

mFIW9A.png

查阅参考例程,其中ucs_ex1_DCO12MHz.c和ucs_ex4_XTSourcesDCOInternal.c分别为使用内部基准振荡器REFO通过FLL倍频至12MHz提供给MCLK和使用外部晶振提供给ACLK与MCLK,结合这两个例程,我们就能实现我们需要的功能。

首先,MSP430F5529额定最高主频25MHz,我们选定为24MHz,根据数据手册,需要提升核心供电电压,于是,查询参考手册和库函数手册电源管理模块PMM部分的内容:

mFIhct.png
mFIf1I.png

MSP430F5529片上有三个引脚可分别复用为ACLK、MCLK、SMCLK输出,另外需要将连接两个晶振的四个引脚设置为复用输入模式,分别调用GPIO_setAsPeripheralModuleFunctionOutputPin、GPIO_setAsPeripheralModuleFunctionInputPin函数。

接着,开启外部晶振,参数为两晶振的频率,单位为Hz:

mFI4jP.png

开启XT1,由于使用的是32.768KHz晶振,使用低速模式,在此处可选择驱动强度和内置辅助电容容量,不同驱动强调下的功耗及辅助电容容量可在数据手册中查询:

mFIInf.png
mFIHAg.png
mFITHS.png

开启XT2,根据外部晶振的频率选择参数:

mFIoB8.png

接下来,我们需要进行时钟分配,使用的函数为UCS_initClockSignal,其作用即为控制时钟树中的复用器及分频器,例如,我们想让XT1作为ACLK的时钟源,不分频,则三个参数分别为UCS_ACLK,UCS_XT1CLK_SELECT,UCS_CLOCK_DIVIDER_1;同理,再次使用此函数将XT2分配给FLL的REFCLK。需要注意的是启用倍频器FLL后,其输出将自动分配给MCKLK和SMCLK,不需要手动分配。

mFIO9s.png
mFIbNQ.png

我们希望将XT2的4MHz 6倍频为24MHz作为主时钟,调用函数UCS_initFLLSettle,第一个参数为目标频率,此处为24000,单位KHZ,第二个参数为倍频系数,此处为6:

mFIqhj.png

最后,仿照例程,开启振荡器错误中断,编写中断服务函数即可。

函数编写

msp430f5529_clock.h

/*
 * msp430f5529_clock.h
 *
 *  Created on: 2019年8月13日
 *      Author: callo
 */
//---------------------------------------------------------------------------
//!                MSP430F5529
//!             -----------------
//!        /|\ |         P5.4/XIN|-
//!         |  |                 | 32kHz
//!         ---|RST     P5.5/XOUT|-
//!            |                 |
//!            |                 |
//!            |       P5.2/XT2IN|-
//!            |                 | 4MHz
//!            |      P5.3/XT2OUT|-
//!            |                 |
//!            |             P1.0|-->ACLK = 32kHz Crystal Out
//!            |                 |
//!            |             P7.7|-->MCLK
//!            |                 |
//!            |             P2.2|-->SMCLK = High Freq Xtal or Resonator Out
//---------------------------------------------------------------------------

#ifndef CUSTOM_INC_MSP430F5529_CLOCK_H_
#define CUSTOM_INC_MSP430F5529_CLOCK_H_

#include "driverlib.h"

#define UCS_XT1_TIMEOUT 50000
#define UCS_XT2_TIMEOUT 50000

#define UCS_XT1_CRYSTAL_FREQUENCY 32768
#define UCS_XT2_CRYSTAL_FREQUENCY 4000000

#define UCS_MCLK_DESIRED_FREQUENCY_IN_KHZ 24000
#define UCS_MCLK_FLLREF_RATIO 6

void MSP430F5529_Clock_Init(void);

#endif /* CUSTOM_INC_MSP430F5529_CLOCK_H_ */

msp430f5529_clock.c

/*
 * msp430f5529_clock.c
 *
 *  Created on: 2019年8月13日
 *      Author: callo
 */
#include "msp430f5529_clock.h"

//Variable to store status of Oscillator fault flags
uint16_t msp430f5529_clock_status;

void MSP430F5529_Clock_Init(void)
{
    //Set VCore = 3 for 24MHz clock
    PMM_setVCore(PMM_CORE_LEVEL_3);

    //ACLK,MCLK,SMCLK set out to pins
    //Optional
    GPIO_setAsPeripheralModuleFunctionOutputPin(
            GPIO_PORT_P1,
            GPIO_PIN0);
    GPIO_setAsPeripheralModuleFunctionOutputPin(
            GPIO_PORT_P7,
            GPIO_PIN7);
    GPIO_setAsPeripheralModuleFunctionOutputPin(
            GPIO_PORT_P2,
            GPIO_PIN2);

    //Port select XT1
    GPIO_setAsPeripheralModuleFunctionInputPin(
            GPIO_PORT_P5,
            GPIO_PIN4 + GPIO_PIN5);

    //Initializes the XT1 and XT2 crystal frequencies being used
    UCS_setExternalClockSource(
        UCS_XT1_CRYSTAL_FREQUENCY,
        UCS_XT2_CRYSTAL_FREQUENCY
        );

    //Initialize XT1.
    UCS_turnOnLFXT1WithTimeout(
        UCS_XT1_DRIVE_0,
        UCS_XCAP_3,
        UCS_XT1_TIMEOUT
        );

    //Startup HF XT2 crystal Port select XT2
    GPIO_setAsPeripheralModuleFunctionInputPin(
        GPIO_PORT_P5,
        GPIO_PIN2 + GPIO_PIN3
        );

    //Initialize XT2.
    UCS_turnOnXT2WithTimeout(
        UCS_XT2_DRIVE_4MHZ_8MHZ,
        UCS_XT2_TIMEOUT
        );

    //Select XT1 as ACLK source
    UCS_initClockSignal(
        UCS_ACLK,
        UCS_XT1CLK_SELECT,
        UCS_CLOCK_DIVIDER_1
        );

    //Set DCO FLL reference = XT2
    UCS_initClockSignal(
        UCS_FLLREF,
        UCS_XT2CLK_SELECT,
        UCS_CLOCK_DIVIDER_1
        );

    //Set Ratio and Desired MCLK Frequency  and initialize DCO
    UCS_initFLLSettle(
            UCS_MCLK_DESIRED_FREQUENCY_IN_KHZ,
            UCS_MCLK_FLLREF_RATIO
            );

    // Enable global oscillator fault flag
    SFR_clearInterrupt(SFR_OSCILLATOR_FAULT_INTERRUPT);
    SFR_enableInterrupt(SFR_OSCILLATOR_FAULT_INTERRUPT);
}

void NMI_ISR(void)
{
  do {
    // If it still can't clear the oscillator fault flags after the timeout,
    // trap and wait here.
      msp430f5529_clock_status = UCS_clearAllOscFlagsWithTimeout(1000);
  } while(msp430f5529_clock_status != 0);
}

在主函数中编写以下代码,使P4.7的LED闪烁(P1.0已作为ACLK输出):

main.c

#include "driverlib.h"
#include "msp430f5529_clock.h"
#include "msp430f5529_delay.h"

void main(void)
{
    WDT_A_hold(WDT_A_BASE);

    //Set P1.0 to output direction
    GPIO_setAsOutputPin(
        GPIO_PORT_P4,
        GPIO_PIN7
        );

    //Initialize MCLK to 24MHz
    MSP430F5529_Clock_Init();

    //Enable global interrupt
    __bis_SR_register(GIE);

    while(1)
    {
        GPIO_toggleOutputOnPin(GPIO_PORT_P4, GPIO_PIN7);
//        __delay_cycles(24000000);
        SYS_Delay_500ms();
    }

}

其中,__delay_cycles(n)是编译器自带的内联函数,作用是延时n个CPU周期,例如此时CPU周期为24MHz,那么__delay_cycles(24000000)即为延时1s,为了方便使用,我进行了一 些宏定义,大家可以自行发挥:

/*
 * msp430f5529_delay.h
 *
 *  Created on: 2019年8月13日
 *      Author: callo
 */

#ifndef CUSTOM_INC_MSP430F5529_DELAY_H_
#define CUSTOM_INC_MSP430F5529_DELAY_H_

#include 

#define SYS_MCLK_IN_HZ 24000000
#define SYS_MCLK_IN_KHZ 24000
#define SYS_MCLK_IN_MHZ 24

#if SYS_MCLK_IN_HZ >= 1000000
#define SYS_Delay_1us() __delay_cycles(SYS_MCLK_IN_MHZ)
#define SYS_Delay_1ms() __delay_cycles(SYS_MCLK_IN_KHZ )
#define SYS_Delay_500ms() __delay_cycles(SYS_MCLK_IN_HZ / 2)
#define SYS_Delay_1s() __delay_cycles(SYS_MCLK_IN_HZ)
#else
#error SYS_MCLK_IN_KHZ is defined impoliticly!
#endif

#endif /* CUSTOM_INC_MSP430F5529_DELAY_H_ */

需要注意的是,若CCS对自行定义的delay函数给出了以下警告,则表明有数据溢出,该延时是不能正常使用的。

mFIy7D.png

运行效果

将示波器探头调节至x10衰减/补偿,连接到LaunchPad的P2.2,可观察到24MHz的SMCLK输出(P7.7/MCLK在LaunchPad上并没有引出):

mFI2hd.png

后记 + 吐槽

我自认为,学习单片机,入门是最难受的一步,因为有太多劝退的理由,在这些理由之中,最为劝退的又是开发环境的配置和时钟树(这就从一方面解释了Arduino成功的原因)。记得刚开始学习STM32时就因为建立标准库工程太复杂(相比于C51)而放弃挣扎了好几个礼拜(挺虚度光阴的2333)。后来,跟着教程走到时钟树,乖乖,看不下去了,又放弃挣扎了相当一段时间。后来,想要学习MSP430,离开MDK舒适的开发环境,上手基于Eclipse的CCS,仅是因为代码补全麻烦,我又放弃了(现在更加偷懒,VS Code搞定一切,真香)。同样,因为过惯了安逸的生活,拒绝寄存器开发(逃),前两天我一直在想办法将MSP430Ware装进CCS里,四处查资料无果,自己在CCS上一顿乱捣也不行,还得一遍遍忍受巨慢无比的Resource Explorer,真的是又要劝退了,好在最后发现,人家官方的解决方法就乖乖躺在DriverLib文件夹里呢。至此,MSP430我算是入门了。我是个菜鸟,这才是我第一次在完全不使用第三方教程的情况下,根据官方手册和例程来学习一款单片机,感觉,收获还是巨大的。玩单片机总归应该是一个快乐的过程,在这里吐槽完我的痛苦(算不了什么)经历,希望接下来能痛快开玩。

发表评论