msp430简介

MSP430

该MCU是由德州仪器TI生产的16位低功耗单片机

主要分以下型号:

  • 专注低功耗的1xx通用型,配备1KB-60KB FLASH、512B-10KB RAM,工作时耗电仅达200uA/MIPS,RAM保持模式耗电0.1uA,RTC模式耗电0.7uA;可在6us之内快速唤醒。搭载10/12位斜率SAR ADC,集成模拟比较器、DMA、硬件乘法器、BOR、SVS、12位DAC
  • 能耗比高的F2xx通用型,性能16MIPS@3.3V,配备1-120KB FLASH,8-128KB RAM,工作耗电220uA,配备10/12位斜率SAR ADC,集成16位Σ-ΔADC,基本上等于1xx的升级版
  • 性价比高的G2xx经济型,性能16MIPS@3.3V,对标友商stm32l,主打模拟外设和低功耗
  • 面向计量和智能电网的AFE2xx专用型:性能略低于以上两个2xx系列,但是集成了1-3个独立的24位Σ-ΔADC,一个16位定时器、一个16位硬件乘法器、USART控制器、看门狗和GPIO
  • 停产的老型号3xx
  • 面向低功耗多媒体4xx控制型,8-16MIPS处理性能,配备LCD控制器FLL、SVS,针对低功耗测量和医疗应用,功耗和1xx相近,4-120KB FLASH、8-256KB RAM,引脚丰富最多可达80Pin,配备10/12位斜率SAR、16位Σ-ΔADC,同样集成了12位ADC、DMA、硬件乘法器、运放、USCI模块等
  • 超高能耗比的5xx超低功耗型,能达到25MIPS@3.3V,工作模式功耗165uA/MIPS,RTC模式2.5uA,RAM保持模式可达1uA,待机唤醒时间极短,小于5ms,配备256KB FLASH、18KB RAM,额外集成了USB、模拟比较器
  • 高性能、低功耗的6xx系列旗舰型,达到25MIPS@3.3V,配备功耗优化的创新电源管理模块和USB控制器,配备LCD控制器,有256KB FLASH、18KB RAM,74Pin引脚,功耗与5xx系列相同,还额外集成了电压管理模块
  • 基于FRAM技术的FRxx系列,和主要的F系列差别在于使用了FRAM存储技术,能够达到更快的FLASH访问速度并在所有功率模式下实现零功率状态保持,即使发生功率损耗的情况也可以保证写入操作,写入寿命能达到100M个周期,不再需要EEPROM
  • 低电压C、L系列,两个谢列都可以在0.9-1.65V电压范围内工作并提供4MIPS的性能
  • 集成射频基带的CC无线系列,具有低于1GHz的片上射频收发器,工作电压为1.8-3.3V,处理性能20MIPS
  • 特殊系列:面对车规应用、电容触摸、超声波测量、DSP等等特殊用途的系列设备

综合来看MSP430具有以下特点:

  1. 超低功耗

    使用1.8-3.6V低电压供电,RAM数据保持方式下耗电仅为0.1μA/MIPS,活动模式下耗电也仅仅为250μA/MIPS,IO输入端口漏电流仅为50mA,相比之下只有stm8和stm32l0系列能够达到同等级的低功耗水准。普通的8051则远远落后

  2. 能效比高,针对算法加速

    MSP430基于RISC架构,采用了一般DSP才有的16位多功能硬件乘法器、硬件乘-加功能、DMA等架构,可以高效实现如FFT、DFT、FIR滤波等数字信号处理算法

  3. 模拟外设技术高

    MSP430片内集成多种模拟外设,包括液晶驱动器和ADC、DAC等,具体外设由型号决定

  4. 外设寄存器直接按位寻址

    外设寄存器可以直接进行赋值、按位操作

msp430系列使用了冯诺依曼架构,并构建了MAB(存储器地址总线)、MDB(存储器数据总线)两个总线协议,其中RAM、FLASH共用同一个地址空间,程序被下载到FLASH,设备复位后自动读取并执行程序指令,局部变量存储在RAM,BSS段变量存储在FLASH,FLASH掉电不丢失

设备内部时钟至少具有3套时钟源:

  1. LFXT1CLK:低频时钟,32.768kHz
  2. XT2CLK:高频时钟,8MHz
  3. DCOCLK:片内数字控制RC振荡器,经常用作系统和外设时钟信号,其稳定性由FLL与硬件控制

三套时钟源可以被设备单独选用,时钟通过片内总线提供给设备;有些型号还具有更多类型的时钟源

DMA可以直接接管总线以提高传输效率(不同于AMBA总线的仲裁,MAB、MDB总线只采用主控设备-从设备的方式)

CPU则采用了双总线位宽的灵活处理方式,分为16位寻址的CPU20位寻址的CPUX。CPU采用RISC架构,配备27条指令和7种统一的寻址模式,寻址空间64KB;CPUX寻址空间为1MB,采用面向控制的结构和指令系统,集成了计算分支、表处理等特性,可以在不分页的情况下处理1MB的地址范围,属于RISC正交指令集(正交:指令集的绝大多数指令格式相同、长度相同,所有寄存器的寻址可以替换;而指令的操作码、寻址方式、操作数寄存器字段的取值相互独立),可以实现MTM(内存到内存)传输,不需要经过中间寄存器,一并对16位CPU实现了兼容

在电赛中最常用的就是MSP430F5529,下面均以F5529为例说明

外围电路设计

供电

MCU部分模拟外设和FLASH、RAM对于电源要求较高,但是MCU数字部分对于电源要求较低,因此采用双电源——模拟/数字的方式为MCU供电。模拟-数字电源之间采用磁珠跨接3.3V和地除杂波,同时需要使用10uF、100nF电容并联进行滤波,10uF用于滤除低频杂波,100nF则用于旁路

复位

RST引脚低电平有效,因此和一般单片机的复位电路一样即可

晶振

需要使用两个晶振接入来保证电源稳定,XT1接低频32.768kHzXT2接高频晶振,一般为4MHz,因为内部电容不足以起振,所以同时需要单独配备20-30pF的匹配电容,一般使用22pF电容

USB

f5529具有USB控制器,能够使用4芯电缆:5V、D+、D-、GND,并可以兼容USB OTG的ID线。

编译烧录

MSP430支持JTAG和SBW(Spy-Bi-Wire,TI指定的两线调试接口,信号叫为SBWTCK和SBWTDIO)

同时也支持BSL(BootStrap Loader),或者说BootLoader加载程序烧录可以通过USB、UART等对单片机进行ISP烧录,在PUR引脚和USB D+之间跨接1.4k电阻,下连1M电阻到地,并通过一个加了限流电阻(一般为100Ω)的微动开关连接到VCC即可实现USB的BSL烧录

MSP430的开发环境是TI基于eclipse开发的Code Composer Studio,简称CCS,在其中使用专用的MSP430 Compile与Linker即可实现C程序编译链接

关键字和内联函数

同时CCS支持了一些扩展关键字,列举如下:

  • __asm用于C语言内嵌汇编,这个和keil一样

  • __interrupt放在函数前指示中断函数,一般和#pragma指令共用

    #pragma vector=UART0RX_VECTOR
    __interrupt void UART_ISR(void)
    {}
    

    上例指示了一个串口0接收中断,#pragma指令让中断向量表中的地址位重定向了

  • __monitor放在函数前,在执行到函数时自动关闭全局中断,类似__atom指令

  • __no_init放在全局变量钱让程序启动时不被变量赋初值

  • __raw关闭中断服务函数的恢复现场能力,这会导致中断服务函数无法返回

  • __regvar声明变量为寄存器变量,注意不能使用指针指向寄存器变量,并且必须搭配使用__no_init禁止初始化

  • sfrb用于声明单字节IO数据类型对象,和51一样用于定义寄存器地址

除了关键字外,CCS还包含了许多内联函数,常见的几个摘录如下

__no_operation();//空指令,相当于NOP
__enable_interrupt();//打开全局中断
__disable_interrupt();//关闭全局中断
__delay_cycles(unsigned long __cycles);//延时__cycles个主时钟(MCLK)周期
__set_SP_register(unsigned short);//为堆栈指针寄存器SP赋值

预定义寄存器

CCS中还预置了一些单片机常用的寄存器和配置,如下所示

  1. 端口定义,其中x表示端口号

    PxIN:端口输入寄存器

    PxOUT:端口输出寄存器

    PxDIR:端口方向控制寄存器

    PxSEL:端口复用寄存器

    注意:MSP430不支持位操作,一般通过屏蔽位的方法实现位操作,这是它和51开发方面最大的不同,如下所示

    #define BIT0 00000001b
    #define BIT1 00000010b
    #define BIT2 00000100b
    #define BIT3 00001000b
    #define BIT4 00010000b
    #define BIT5 00100000b
    #define BIT6 01000000b
    #define BIT7 10000000b
    
    P1OUT |= BIT0;
    //这样可以实现P1.0输出
    
    P1OUT |= BIT4;
    //这样可以实现P1.0和P1.3同时输出
    
    P1OUT &= ~BIT1;
    //这样可以取消P1.0的输出
    
  2. 低功耗模式的进入和退出

    CCS预定义了一些宏指令用来实现低功耗模式

    LPM3;//进入低功耗模式
    LPM3_EXIT;//退出低功耗模式
    

    其中数字可以写0-4,分别对应四种低功耗模式

  3. 外设寄存器

    各种片上外设的寄存器都被定义为了宏,可以通过|=&=~的方式进行按位控制

  4. 部分常用代码

    _dint(); //等效于__disable_interrupt
    _EINT(); //等效于__enable_interrupt
    _NOP(); //空指令
    _OPC(x); //在指令流中插入一个常熟,对与参数对应的任何指令进行编码
    _SWAP_BYTES(x); //将无符号16位整数的高8位和低8位交换
    monitor //关键字__monitor的宏定义
    no_init //关键字__no_init的宏定义
    

综上所述,MSP430的开发和8051的开发非常类似,并没有stm32的库封装,而是直接操作寄存器。这不仅仅是由于MSP430的性能较低,也是由于这样的编程方式写出的代码更加简洁、指令量更少,能突出体现MSP430低功耗的优势

片上外设开发

MSP430的片上外设寄存器具体配置和51单片机的很像,但是有一些功能更加复杂,和stm32的寄存器接近,同时也提供了简化操作的库函数

GPIO

f5529一共有80个引脚,和stm32的gpio结构类似,并且也具有复用功能。除此之外某些引脚具备基本的电源功能,分别能够为MCU的片上数字电路和模拟电路供电,一般情况下可以共用一个电源,但在某些高精度测量场合需要双电源隔离供电。msp430还具有一个USB电源,可以直接输出5V供电,经过片上LDO后能在端口VBUS处输出稳定的3.3V电压供单片机和外设使用,最大驱动电流60mA

其中P1、P2端口IO都具有外部中断能力,分别对应P1IV中断向量和P2IV中断向量。端口可单独配置强驱动和弱驱动模式,强驱动模式下全片最大输出电流100mA,单端口最大电流15mA;弱驱动模式下全片最大输出电流48mA,单端口最大电流6mA

寄存器操作可以通过上面介绍过的屏蔽位法,也可以通过分别操作寄存器高8位和低8位的方法实现

//使用三种方法将P1.1和P2.2配置为输出功能
P1DIR |= 0x02; //0x02 == 00000010b
P2DIR |= 0x04; //0x04 == 00000100b

PADIR_L |= 0x02; //DIR寄存器低8位,代表P1
PADIR_H |= 0x04; //DIR寄存器高8位,代表P2

PADIR |= 0x0402; //直接操作DIR寄存器,将其视作uint16_t
  1. 端口配置

    如下配置端口为输入状态并配置内部上拉

    P1DIR &= ~BIT1; //BIT1 == 0x00000010b,设置P1.1为输入模式
    P1REN |= BIT1; //使能上下拉电阻
    P1OUT |= BIT1; //P1.1配置上拉电阻
    

    通过配置PxDIR.n |= 1可以将相应的IO口配置为输出状态,在输出状态下,PxREN、PxIN无效。

    P1OUT |= BIT1; //P1.1输出高电平
    P1OUT &= ~BIT1; //P1.1输出低电平
    
    P1DS.1 &= ~BIT1; //配置P1.1为弱驱动输出
    P1DS.1 |= BIT1; //配置P1.1为强驱动输出
    
  2. 端口复用配置

    基本每个IO都有端口复用功能,通过配置PxSEL.n把对应的IO口配置为复用功能

    使用以下代码配置P1.0为定时器A0时钟输入

    P1DIR &= ~BIT0; //设置P1.0为输入状态
    P1SEL |= BIT0; //将P1.0复用为定时器A0时钟输入
    

    相关配置需要按照datasheet中的端口复用表选择

时钟系统与低功耗

msp430f5529具有5种时钟源(XT1CLK、XT2CLK、VLOCLK、REFOCLK、DCOCLK)和3种时钟信号(MCLK、SMCLK、ACLK)

时钟系统可以软件配置成不需要外部晶振、需要一个外部晶振、需要两个外部晶振、外部时钟输入等方式,最极端的情况下单片机内部具有自身振荡器可以为CPU及片上外设提供系统时钟

时钟系统的安全性比较重要,msp430配备了紧急保护系统,在外部时钟故障时会自动选择内部时钟源REFOCLK或VLOCLK作为时钟信号,并产生响应故障信号(可选中断)

系统时钟大致分为两级,信号生成级信号分配级,中间通过MUX连接。信号生成级别分为三个模块基本的OSC模块可以通过晶振旁路、内部REFO或VLO直接输出XT1CLK、VLOCLK、REFOCLK三种信号;可选的XT2模块直接输出XT2晶振的4MHz时钟作为XT2CLK;可以通过晶振旁路和FLL(Frequency Locked Loop锁频环)进行晶振时钟倍频和分频,信号源(即FLLREFCLK反馈时钟)通过MUX直接引用XT1CLK、REFOCLK、XT2CLK之一,经过多个倍频分频器后输出为DCOCLK和DCOCLKDIV。所有信号分别输出到信号分配级,通过MUX分配给ACLK、MCLK、SMCLK

XT1CLK外部低频或高频时钟源,默认关闭,需要接入外部晶振并通过软件使晶振起振后再使用,一般使用32.768kHz的低频晶振,但是也可以使用4-32MHz的外部高频时钟源,端口P5.4、P5.5

使用下面的代码对时钟源进行配置

P5SEL |= BIT4 | BIT5; //配置P5.4、P5.5为XT1复用功能
UCSCTL6 |= XCAP_3; //配置匹配电容为12pF
UCSCTL6 &= ~XT1OFF; //使能XT1,使外部晶振起振
while(SFRIFG1 & OFIFG)
{
    UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG); //清除三类时钟故障标志位,等待起振
    SFRIFG1 &= ~OFIFG; //清除震荡器故障标志位
}

XT2CLK:和XT1CLK类似,但只能接4-32MHz的高频晶振,一般接入4MHz晶振,需要额外加匹配电容方便起振。端口P5.2、P5.3

需要注意的是在配置SMCLK和MCLK为XT2CLK时钟源之前需要先修改ACLK和REFCLK的时钟源,因为它们的时钟源默认为XT1CLK,但这里并没有启动,所以会导致没有必要的XT1CLK始终故障,会影响判断XT2是否起振,实现代码如下

P5SEL |= BIT2 | BIT3; //配置P5.2、P5.3为XT2复用功能
UCSCTL6 &= ~XT2OFF; //使能XT2
UCSCTL4 = UCSCTL4 & (~(SELA_7)) | SELA_1; //将ACLK配置为VLOCLK
UCSCTL3 |= SELREF_2; //将REFCLK配置为REFOCLK

while(SFRIFG1 & OFIFG)
{
    UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG); //清除三类时钟故障标志位,等待起振
    SFRIFG1 &= ~OFIFG; //清除震荡器故障标志位
}

VLOCLK内部低功耗、低频时钟源频率10kHz,精度较低,会随电源电压和温度产生较大漂移,用于不需要精准时钟基准的系统控制,被使用时自动开启、不使用时自动关闭,低功耗唤醒模式下回优先使用该时钟源作为系统和看门狗时钟

配置UCSCTL4选择

REFOCLK内部修整低频参考时钟源,精度较高,32.768kHz,和VLOCLK一样不需要配置寄存器进行起振,若未使用外部晶振,系统会自动选择该时钟源作为ACLK和DCOCLK锁频环参考时钟源

通过UCSCTL4选择

DCOCLK:内部数字控制时钟源,具有宽工作频率,最高可产生25MHz时钟频率,可以和FLL配合控制参考时钟,也可以引入其他时钟源反馈进行时钟分频/倍频,但是需要额外配置

这是f5xx中最常用的时钟源,类似于stm32的PLL时钟(它的内部也是类似的PLL)

其频率计算公式如下

\[DCOCLK = D \times (N+1) \times \frac{REFCLK}{n} \\ DCOCLKDIV = (N+1) \times \frac{REFCLK}{n} \]

REFCLK来源见上文

n为输入时钟分频,通过UCSCTL3中的FLLCLKDIV设定,查找该寄存器介绍可知其取值0-7,对应n取值2p,默认为0,不分频

D通过UCSCTL2中的FLLD设对,可取值0-7,对应D取值2p,默认为1,即D=2,二分频

N可以通过UCSCTL2中的FLLN设定,取值0-1023,当FLLN=0时,N=1,除此之外N=FLLN,默认为31,即N=31

如果系统复位后不进行任何设置,DCOCLK=2097152Hz,DCOCLKDIV=1048576Hz

MCLK和SMCLK都默认选择DCOCLKDIV作为时钟源

通过配置DCORSEL、DCOx、MOD来选择DCO的频率设置范围(最小值和最大值)

详细内容参考datasheet

MODOSC:内部模块振荡器,是UCS时钟模块下属的振荡器,能产生4.8MHz的MODCLK时钟,用于FLASH、ADC等片上外设

MCLK:为CPU和片上外设提供主时钟,默认使用DCOCLKDIV

通过配置DIVM选择MCLK分频系数为1、2、4、8、16、32

UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_0; //XT1CLK时钟源
UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_1; //VLOCLK时钟源
UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_2; //REFOCLK时钟源
UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_3; //DCOCLK时钟源
UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_4; //DCOCLKDIV时钟源
UCSCTL4 = UCSCTL4 & (~SELM_7) | SELM_5; //XT2CLK时钟源
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_0; //MCLK不分频
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_1; //MCLK 2分频
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_2; //MCLK 4分频
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_3; //MCLK 8分频
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_4; //MCLK 16分频
UCSCTL5 = UCSCTL5 & (~DIVM_7) | DIVM_5; //MCLK 32分频

ACLK辅助时钟,专用来为外围模块提供信号。默认使用XT1CLK时钟源,如果未起振,则使用REFOCLK。配置方法和MCLK完全一致

SMCLK子系统主时钟,和MCLK基本一致,只是不为CPU提供时钟

上电复位后,UCS默认配置如下:

  • ACLK选择XT1为时钟源,如果未起振则使用REFOCLK并生成时钟故障标志
  • MCLK选择DCOCLKDIV
  • SMCLK选择DCOCLKDIV

需要注意:msp430f5529的XTIN和XTOUT引脚默认为GPIO功能,并在上电情况下不会启动,需要额外进行软件设置

同时P7.7、P2.2、P1.0分别能够对外输出MCLK、SMCLK、ACLK时钟

这里用最为复杂的DCO配置说明整个时钟系统的配置流程

#include <msp430f5529.h>
#include <stdint.h>

void SetVcoreUp(uint_32_t level); //提升核心电压以提升工作频率
void XT1_ON(void); //启动XT1
void DCO__16MHz(void); //使用DCO将XT1倍频到16MHz

void main(void)
{
    WDTCTL = WDTPW + WDTHOLD;
    P1SEL |= BIT0; //P1.0 ACLK输出
    P1DIR |= BIT0;
    P2SEL |= BIT2; //SMCLK输出
    P2DIR |= BIT2;
    P7SEL |= BIT7; //MCLK输出
    P7DIR |= BIT7;
    P7DIR |= BIT0; //P7.0 LED驱动输出
    P1OUT |= BIT0;
    
    XT1_ON();
    DCO__16MHz();
    
    while(1)
    {
        __delay_cycles(8000000); //每0.5s
        P7OUT ^= BIT0 ;//LED状态翻转一次
    }
}

void SetVcoreUp(uint_32_t level)
{
    PMMCTL0_H = PMMPW_H; //解锁PMM寄存器,允许写入
    SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level; //设置SVS/SVM高侧到新的等级
    SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level; //设置SVS低侧到新的等级
    while((PMMIFG & SVSMLDLYIFG) == 0); //等待SVM稳定
    PMMIFG &= ~(SVMLVLRIFG + SVMLIFG); //清除已经置位的标志
    PMMCTL0_L = PMMCOREV0 * level; //设置VCORE到新的等级
	
    if((PMMIFG & SVMLIFG)) //等待达到新的电压等级
    {
        while((PMMIFG & SVMLVLRIFG) == 0);
    }
    
    //设置SVS/SVM低侧到新的水平
    SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
    PMMCTL0_H = 0x00; //锁住PMM的写入路径
}

void XT1_ON(void)
{
    P5SEL |= BIT4 |BIT5; //配置XT1引脚
    UCSCTL6 |= XCAP_3; //配置电容为12pF
    UCSCTL6 &= ~XT1OFF; //使能XT1
    
    while(SFRIFG1 & OFIFG)
    {
        UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG); //清除三类时钟错误标志位
        SFRIFG1 &= ~OFIFG; //清除时钟错误标志位
    }
}

void DCO__16MHz(void)
{
    SetVcoreUp(1); //一级一级提升核心电压,不能跨级
    SetVcoreUp(2);
    
    /* 配置寄存器使DCOCLK=4.9MHz,DCOCLKDIV=2.45MHz */
    __bis_SR_register(SCG0); //关闭FLL库函数
    UCSCTL0 = 0x0000; //清零寄存器值,FLL运行时系统会自动配置该寄存器
    UCSCTL1 = DCORSEL_5; //选择DCOCLK频率范围 6-23.7MHz
    
    //FLLD=0,则D=1;FLLN=487,则N=487;N在UCSCTL3寄存器,默认值为1,则DCOCLK=1*(487+1)*32768=15.990784MHz
    //DCODIVCLK=(487+1)*32768=15.990784MHz
    UCSCTL2 = FLLD_0 + 487; 
    
    __bic_SR_register(SCG0); //开启FLL控制回路
    __delay_cycles(76563); //延时等待时钟稳定
    
    while(SFRIFG1 & OFIFG) //检测时钟错误并等待时钟稳定
    {
        UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
        SFRIFG1 &= ~OFIFG;
    }
}

低功耗配置

msp430一共有8种工作模式

  • 活跃模式(AM)
  • 低功耗模式(LPM)0
  • 低功耗模式1
  • 低功耗模式2
  • 低功耗模式3
  • 低功耗模式3.5
  • 低功耗模式4
  • 低功耗模式4.5

但是并不是所有系列都支持这些工作模式,对于f5529来说,不支持LPM3.5

使用以下指令来进入和退出低功耗模式0-4

/* 开总中断并进入低功耗模式 */
__bis_SR_register(LPMn_bits + GIE);

/* 退出低功耗模式 */
LPMn_EXIT; //其中n可以换成数字0-4

需要注意:低功耗模式唤醒都需要使用外部中断,所以需要在进入低功耗模式同时开启总中断

在最高级别LPM4.5低功耗模式下,RAM中内容会直接丢失,所以在从LPM4.5唤醒后需要重新配置寄存器和相关设置

中断

低功耗模式唤醒使用的指令实际上是通过直接修改SR寄存器的值,清除休眠标志,它内联到以下函数

_bic_SR_register_on_exit(LPM3_bits);//退出LPM3

MSP430和arm一样都具有系统中断不可屏蔽中断可屏蔽中断三种,其中系统中断和不可屏蔽中断优先级最高;可屏蔽中断可以通过状态寄存器SR中的GIE位来屏蔽和开启

大致的中断作用与arm类似,而使用方式与51类似

下面主要介绍外部中断

  1. 初始化端口时要清空中断标志位

    P1IFG &= ~(BIT0); //清空中断标志位
    
  2. 使用PIES寄存器选择触发边沿(0为上升沿,1为下降沿)并使用PxIE寄存器使能中断

    P1IES &= ~BIT0; //P1.0上升沿触发
    P1IES |= BIT1; //P1.1下降沿触发
    P1IES |= BIT2; //P1.2下降沿触发
        
    P1IE |= BIT0; //使能P1.0中断
    P1IE |= BIT1; //使能P1.1中断
    P1IE |= BIT2; //使能P1.2中断
    
  3. 开总中断

    __bis_SR_register(GIE);
    
  4. 编写中断服务函数

    #pragma vector = 中断向量地址
    __interrupt void ISR_function(void)
    {
        //中断服务函数
    }
    
  5. 在中断函数内部清零中断标志位

    __even_in_range(P1IV, 16); //用于查询P1的所有中断标志位并自动清零,使用该函数可以实现将所有P1的外部中断放在同一个函数内解决的功能,如下所示
    
    switch(__even_in_range(P1IV, 16))
    {
        case 0: //无中断
            break;
        case 2: //P1IFG.0
            /* 中断处理函数 */
            break;
        case 4: //P1IFG.1
            /* 中断处理函数 */
            break;
        case 6: //P1IFG.2
            /* 中断处理函数 */
            break;
        case 8: //P1IFG.3
            break;
        case 10: //P1IFG.4
            break;
        case 12: //P1IFG.5
            break;
        case 14: //P1IFG.6
            break;
        case 16: //P1IFG.7
            break;
        default: //出错情况
            break;
    }
    

此外,msp430还支持手动的嵌套中断

示例程序如下所示(TI的七段数码管驱动示例程序)

#include <msp430.h> 

const unsigned char SEVENSEG_OUTPUT[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f}; 

unsigned char loopCounter;

unsigned char timeCounter1; 
unsigned char timeCounter2;
unsigned char timeCounter3;

int main(void)
{
    WDTCTL = WDTPW | WDTHOLD; //关闭看门狗

    PADIR = 0x03ff; // P1, P2.0 and P2.1 output, P2.6 and P2.7 input
    PAOUT = 0xc03f;

    P2REN = 0xc0; // P2.6 P2.7 上拉电阻使能
    P2IES = 0x3f; // P2.6 P2.7 配置中断为上升沿
    P2IE = 0xc0; // P2.6 P2.7 interrupt enabled

    PM5CTL0 &= ~LOCKLPM5; // 关闭GPIO高阻抗模式

    RTCMOD = 50; // 设置RTC重装计数值为50
                 // 64/32768 * 51 = ~0.1 sec.
    SYSCFG2 |= RTCCKSEL; // Source = ACLK = REFO,64分频,选择ACLK作为RTC时钟
    RTCCTL = RTCSS_1 | RTCSR | RTCPS__64;

    P2IFG = 0; // 清除P1.3中断标志位
    __bis_SR_register(GIE); //开全局中断

    while(1)
    {
        PAOUT |= (BIT9 | SEVENSEG_OUTPUT[timeCounter3]);  // 按顺序显示七段数码管数字
        __delay_cycles(100);
        PAOUT = 0xc000; // 清除引脚

        PAOUT |= (BIT8 | SEVENSEG_OUTPUT[timeCounter2]);
        __delay_cycles(100);
        PAOUT = 0xc000;

        P1OUT |= (BIT7 | SEVENSEG_OUTPUT[timeCounter1]);
        __delay_cycles(100);
        P1OUT = 0;
    }
}

//RTC中断服务函数
#pragma vector=RTC_VECTOR
__interrupt void RTC_ISR(void)
{
    __bis_SR_register(GIE); // 允许嵌套中断
    RTCIV = 0;
    timeCounter1++; // timeCounter1代表0.1s, timeCounter2代表1s,timeCounter3代表10s,经典延时操作
    
    if(timeCounter1 > 9)
    {
        timeCounter1 = 0;
        timeCounter2++;
        if(timeCounter2 > 9)
        {
            timeCounter3++;
            timeCounter2 = 0;
        }
        if(timeCounter3 > 9)
        {
            timeCounter3 = 0;
        }
    }
}

//P2中断服务函数
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
    __bis_SR_register(GIE); // 允许嵌套中断
    if(P2IFG & BIT6)
    {
        P2IFG &= ~BIT6; // 清除P2.6中断标志位
        // 第一次按下按钮时开启定时器;第二次按下时停止定时器
        if(loopCounter == 0)
        {
          	loopCounter++; //开启一轮循环
            RTCCTL |= RTCIE; //开启定时器
        }
        else
        {
            RTCCTL &= ~RTCIE; //关闭定时器
            loopCounter = 0; //循环清零
        }
    }
    if(P2IFG & BIT7)
    {
        P2IFG &= ~BIT7; // 清除P2.7中断标志位并复位秒表
        RTCCTL &= ~RTCIE; //关闭定时器
        
        loopCounter = 0; //复位所有变量
        timeCounter3 = 0;
        timeCounter2 = 0;
        timeCounter1 = 0;
    }
}

实现中断嵌套需要注意以下几点:

msp430默认关闭中断嵌套,一定要中断嵌套的话,就必须在中断服务程序中打开总中断

msp430的指令中,_DINT()和_EINT()分别指关和开总中断

当进入中断服务程序时,只要不在中断服务程序中再次开中断,则总中断是关闭的,此时来中断不管是比当前中断的优先级高还是低都不执行

若在中断服务程序A中开了总中断,则可以响应后来的中断B(不管B的优先级比A高还是低),B执行完再继续执行A

注意:进入中断服务程序B后总中断同样也会关闭,如果B中断程序执行时需响应中断C,则此时也要开总中断,若不需响应中断,则不用开中断,B执行完后跳出中断程序进入A程序时,总中断会自动打开

若在中断服务程序中开了总中断,后来的中断同时有多个,则会按优先级来执行,即中断优先级只有在多个中断同时到来时才起作用,中断服务不执行抢先原则

对于单源中断,只要响应中断,系统硬件会自动清除中断标志位。对于TA/TB定时器的比较/捕获中断,只要访问TAIV/TBIV,标志位就会被自动清除;对于多源中断要手动清标志位,比如P1/P2口中断,要手工清除相应的标志。如果在这种中断里用_EINT();开中断,而在打开中断前没有清标志,就会有相同的中断不断嵌入,导致堆栈溢出引起复位,所以在这类中断中必须先清标志再打开中断开关

常用中断向量表

#define BASICTIMER_VECTOR   (0 * 2u) /* 0xFFE0 Basic Timer */

#define PORT2_VECTOR        (1 * 2u)  /* 0xFFE2 Port 2 */

#define USART1TX_VECTOR     (2 * 2u) /* 0xFFE4 USART 1 Transmit */

#define USART1RX_VECTOR     (3 * 2u) /* 0xFFE6 USART 1 Receive */

#define PORT1_VECTOR        (4 * 2u)  /* 0xFFE8 Port 1 */

#define TIMERA1_VECTOR      (5 * 2u) /* 0xFFEA Timer A CC1-2, TA */

#define TIMERA0_VECTOR      (6 * 2u) /* 0xFFEC Timer A CC0 */

#define ADC12_VECTOR          (7 * 2u)  /* 0xFFEE ADC */

#define USART0TX_VECTOR     (8 * 2u) /* 0xFFF0 USART 0 Transmit */

#define USART0RX_VECTOR     (9 * 2u) /* 0xFFF2 USART 0 Receive */

#define WDT_VECTOR          (10 * 2u) /* 0xFFF4 Watchdog Timer */

#defineCOMPARATORA_VECTOR  (11 * 2u) /* 0xFFF6Comparator A */

#define TIMERB1_VECTOR      (12 * 2u) /* 0xFFF8 Timer B CC1-6, TB */

#define TIMERB0_VECTOR      (13 * 2u) /* 0xFFFA Timer B CC0 */

#define NMI_VECTOR          (14 * 2u) /* 0xFFFC Non-maskable */

#define RESET_VECTOR        (15 * 2u) /* 0xFFFE Reset [HighestPriority] */

定时器

msp430有四个基本定时器,外加一个RTC定时器,部分定时器具有PWM输出功能

计数器核心是一个计数寄存器,对输入的时钟信号进行计数,可以配置其捕获跳变沿种类和分频系数,使用寄存器TAxR获取当前计数值。定时器主要分成两部分:主计数器和捕获比较器模块。捕获比较器模块与主计数器模块通过TAxR连通,主计数器会根据输入的信号跳变沿递增/递减寄存器TAxR/TBxR的值,捕获比较器会根据收到的比较值对寄存器的值进行检查,根据当前模式不同做出不同反应,因为计数寄存器被共用,所以可以将其分成多个通道。一般当捕获比较器满足设置的条件时就会产生中断,存储计数值或输出相应的信号。只需要主计数器即可完成定时工作,捕获比较器的作用则在于配合主计数器完成更多扩展功能

  • Timer_A16位定时器(最大值65535),具有7个捕获比较器,支持多路捕获比较PWM输出间隔定时功能

    F5529中包含两个Timer_A模块,记作Timer_A0、Timer_A1、Timer_A2,三个模块的主计数器在结构上完全相同,单捕获比较器的数量不相同:Timer_A0有7个,Timer_A1和Timer_A2各有3个。

  • Timer_B16位定时器(最大值65535),具备Timer_A的所有功能,但它还具备双缓冲比较锁存同步加载功能

定时器的基本模式如下:

  • 捕获器模式

    触发信号到来时捕获器将计数寄存器的值复制到捕获比较器的计数值寄存器TAxCCRn/TBxCCRn,并产生中断请求

  • 比较器模式

    需要程序向计数值寄存器TAxCCRn/TBxCCRn中写入初值,当主计数器的计数寄存器TAxR/TBxR计数值达到寄存器中存储的初值后定时器模块就会向CPU请求中断

通过配置TASSEL可以选择时钟来自ACLK、SMCLK、TAxCLK(外部输入)或INCLK(定时器级联)

定时器A

msp430的定时器A的主计数器具有以下几个工作模式

  • 增模式

    设备会重复从0自增到TAxCCRn的值,溢出时触发中断

    最基础的功能

  • 连续模式

    设备会重复从0自增到0FFFFh,然后从0重新开始计数

    一般用于生成独立的时间间隔和输出频率,时间间隔完成时会生成中断

    起始设置TAxCCRn的初值,并在中断服务函数中重新设置TAxCCRn的值,使其与初值的计数个数相同即可产生固定的时间间隔,这种操作还可以应用多个通道,因为多通道之间相互独立

    最基础的功能

  • 增减模式

    定时器从0自增到TAxCCR0,再自减到0,也就是说其定时周期为两倍的TAxCCR0

捕获比较器工作模式如下:

通过设置捕获比较器中的CAP位可以选择捕获比较器的工作模式为比较模式(0)或捕获模式(1),

  • 捕获模式

    当CAP=1时选择捕获模式,用于记录时间时间。

    触发信号输入CCIxA/CCIxB连接外部的引脚或内部的信号,通过CCIS位来选择;通过CM位选择触发捕获事件的输入信号触发沿

    每当触发信号到来时,捕获比较器会1. 将TAxR的值复制到TAxCCRn寄存器中;2. 将捕获器中断标志CCIFG置位触发中断

  • 比较模式

    如果计数器TAxR的值和某个TAxCCRn的值相等时,相应的中断标志位会被置位,产生一个比较中断。一般该模式用于产生PWM信号

  • 输出模式

    传统的定时器通过标志位判断来触发事件,但msp430配备了专用的输出模块,使用输出模块寄存器OUTMODEx可以快速输出PWM信号或其他控制信号

定时器中断

定时器A具有两个中断源,捕获比较器0中断独立,其他所有中断(定时器溢出中断、捕获比较器1中断、捕获比较器2中断等等)共用中断源,通过TAxIV来确定具体触发中断的中断源

使用步骤如下:

  1. 设置主计数器时钟

    TA0CTL = TASSEL_1;
    
  2. 设置分频

    TA0EX0 = TAIDEX_7 //8分频
    
  3. 初始化CCR寄存器(设置初值)并使能中断

    TA0CCR0 = 9000; //设置初值
    TA0CCTL0 = CCIE; //使能TA0CCR0中断
    
  4. 设置捕获比较器模式

    TA0CTL |= MC_2 + TACLR; //清除TA0R,启动定时器,选择连续计数模式
    

    也可以使用下面的代码启用其他模式

    MC_1 增模式
    MC_3 减增模式
    
  5. 开总中断

    __bis_SR_register(GIE);
    
  6. 配置中断服务函数

    #pragma vector = TIMER0_A0_VECTOR //TA0CCR0中断
    __interrupt void TA0CCR0_ISR(void)
    {
        TA0CCR0 += 16384; //添加偏置
        /* 中断处理函数 */
    }
    

定时器B

在捕获比较器和比较器之间加入的比较锁存器可以分组控制比较值载入的时刻,实现同步更新数据

在Timer_B中可以通过配置寄存器TBxCCTLn来选择TBxCCRn载入TBxCLn的时刻,在Timer_B中起到比较作用的是比较锁存器TBxCLn而不是CCR寄存器,当TBxR的值达到TBxCLn时,相应的中断标志位置位,产生比较器中断请求,TBxCCRn的值会在寄存器设置的时间点载入TBxCLn,从而实现比较延时更新

输入捕获

使用定时器的外部计数功能来测量脉冲个数,可用于测速、编码器驱动等场合

基本使用方式和上面的定时器中断类似,但是需要以下额外语句

  1. 配置外部时钟源,使能溢出中断

    TA0CTL = TASSEL_0 + TAIE;
    
  2. 清除TAxR、启动定时器并工作于连续模式

    TA0CTL |= TACLR + MC_2;
    
  3. 在定时器中断服务函数内递增全局计数变量用于获取输入捕获的脉冲数

    uint8_t counter;
    uint8_t loop;
    
    #pragma vector = TIMER0_A1_VECTOR
    __interrupt void TA0_ISR(void)
    {
        counter++;
        if(counter>10)
        {
            loop++;
            counter=0;
        }
    }
    

PWM输出

msp430可以实现在不占用CPU资源的情况下输出PWM信号,程序如下所示

#include <msp430f5529.h>

void main(void)
{
    WDTCTL = WDTPW | WDTHOLD; //关闭看门狗
    P1DIR |= BIT2 | BIT3;
    P1SEL |= BIT2 | BIT3; //设置为定时器复用
    TA0CCR0 = 512-1; //PWM周期,频率=32768/512=64
    TA0CCTL1 = OUTMODE_7; //CCR1输出模式7
    TA0CCR1 = 384; //CCR1 PWM占空比设置为384/512=75%
    TA0CCTL2 = OUTMODE_7; //CCR2输出模式7
    TA0CCR2 = 128; //CCR2 PWM占空比设置为128/512=25%
    //开启定时器
    TA0CTL = TASSEL_1 + MC_1 + TACLR; //定时器时钟设置为32768Hz的ACLK,配置为增模式,清空TA0R
    
    while(1)
    {
        /* 可实现在P1.2上输出75%占空比,在P1.3上输出25%占空比,频率都是64Hz的PWM信号 */
    }
}

总线协议

msp430f5529中配备了通用串行通信接口模块USCI,它支持了多种通信模式,UART、SPI、IIC都可以使用该外设进行处理

使用方法很类似,仅给出示例代码

UART

串口接收并复读数据

#include <msp430f5529.h>

void main(void)
{
    WDTCTL = WDTPW + WDTHOLD;
    
    /* 初始化UART */
    P4SEL |= BIT4 + BIT5; //配置P4.4、P4.5为USCI_A1 Tx、Rx
    UCA1CTL1 |= UCSWRST; //复位USCI_A1
    UCA1CTL1 |= UCSSEL_2; //SMCLK 无校验位 8字符长度 1个停止位
    UCA1BR0 = 9; //低8位=9
    UCA1BR1 = 0; //高8位=0,调制后波特率约为115200bps
    UCA1MCTL |= UCBRS_1 + UCBRF_0; //调制器UCBRSx=1,UCBRFx=0
    UCA1CTL1 &= ~UCSWRST; //启动USCI_A1
    UCA1IE |= UCRXIE; //使能USCI_A1接收中断
    __bis_SR_register(LPM0_bits + GIE); //使能全局中断
    
    while(1);
}

#pragma vector=USCI_A1_VECTOR
__interrupt void USCI_A1_ISR(void)
{
    switch(__even_in_range(UCA1IV, 4))
    {
        case 0: //无中断
            break;
        case 2: //接收中断RXIFG
            UCA1TXBUF = UCA1RXBUF; //复读
            break;
        case 4: //发送中断TXIFG
            break;
        default:
            break;
    }
}

UCA1TXBUF:串口发送寄存器

UCA1RXBUF:串口接收寄存器

UCA1IE:串口中断控制寄存器,可以选择接收中断、发送中断

IIC

需要注意,msp430的IIC无法内部上拉,所以必须在外部接入4.7k的上拉电阻到VCC(3.3V)

下面的代码仅说明如何将USCI配置成IIC驱动模式

  1. 包含头文件

    #include <msp430.h>
    
  2. 初始化引脚复用功能

    P4SEL |= BIT1 + BIT2;
    
  3. 初始化USCI,并配置传输速率

    UCB1CTL1 |= UCSWRST; //复位USCI_B1
    UCB1CTL0 |= UCMST + UCMODE_3 + UCSYNC; //配置为IIC主机,同步模式
    UCB1CTL1 = UCSSEL_2 + UCSWRST; //SMCLK,保持UCSWRST置位
    
    UCB1BR0 = 12; //fscl=SMCLK/12=100kHz
    UCB1BR1 = 0;
    
  4. 设置从机地址

    这里假设从机地址是0x48

    UCB1I2CSA = 0x48; //从机地址0x48
    
  5. 启动外设

    UCB1CTL1 &= ~UCSWRST; //清除复位标志,外设开始运行
    
  6. 编写中断控制函数

    uint8_t IIC_RXByte;
    uint8_t IIC_TXByte;
    uint8_t *PRxData; //接收缓存
    uint8_t *PTxData; //发送缓存
    
    #pragma vector=USCI_B1_VECTOR
    __interrupt void USCI_B1_ISR(void)
    {
        switch(__even_in_range(UCB1IV, 12))
        {
            case 0: //无中断
                break;
            case 2: //ALIFG
                break;
            case 4: //无响应中断NACKIFG
                break;
            case 6: //STTIFG
                break;
            case 8: //停止位中断STPIFG
                break;
            case 10: //接收中断RXIFG
                IIC_RXByte--; //递减字节计数变量
                if(IIC_RXByte) //如果没有接收完毕
                {
                    *PRxData++ = UCB1RXBUF; //接收剩下的数据到缓存区
                    if(IIC_RXByte == 1) //检查是否只剩一个字节未接收
                    {
                        UCB1CTL1 |= UCTXSTP; //发送停止条件
    				}
                }
                else //已经接收完毕
                {
                    *PRxData = UCB1RXBUF; //将最后一字节数据存储到缓存区
                    UCB1IE &= ~UCRXIE; //禁用接收中断
                    __bic_SR_registe_on_exit(LPM0_bits); //退出LPM0,进入活跃模式
    			}
                break;
            case 12: //发送中断TXIFG
                if(IIC_TXByte) //如果没有发送完毕
                {
                    UCB1TXBUF = *PTxData; //IIC发送数据
                    IIC_TXByte--; //递减字节计数变量
                }
                else //已经接收完毕
                {
                    UCB1CTL1 |= UCTXSTP; //置位发送停止条件位
                    UCB1IFG &= ~UCTTXIFG; //清除发送中断标志位TXIFG
                    UCB1IE &= ~UCTXIE; //禁用发送中断
                    __bic_SR_registe_on_exit(LPM0_bits); //退出LPM0,进入活跃模式
    			}
                break;
            default:
                break;
        }
    }
    
  7. 编写相关驱动

    TxData[8] = {0}; //发送缓存区
    RxData[8] = {0}; //接收缓存区
    uint8_t IIC_RXByte;
    uint8_t IIC_TXByte;
    uint8_t *PRxData; //接收缓存
    uint8_t *PTxData; //发送缓存
    
    void main(void)
    {
        /* 这里省略初始化部分 */
        disable_WDG();
        init_iic();
        
        while(1)
        {
            /* 发送设置 */
            PTxData = TxData;
            IIC_TXByte = 8;
            while(UCB1CTL1 & UCTXSTP); //确保停止条件已发送,总线空闲
            
            UCBCTL1 |= UCTR; //设置主机工作在发送机模式
            UCBCTL1 |= UCTXSTT; //发送开始条件并发送有“写标志位”的地址
            UCB1IE |= UCTXIE; //使能发送中断
            __bis_SR_register(LPM0_bits + GIE); //进入LPM0,使能全局中断
            
            /* 接收设置 */
            PRxData = RxData;
            IIC_RXByte = 8;
            while(UCB1CTL1 & UCTXSTP); //确保停止条件已发送,总线空闲
            UCBCTL1 &= ~UCTR;//设置主机工作在接收机模式
            UCBCTL1 |= UCTXSTT; //发送开始条件并发送有“读标志位”的地址
            UCB1IE |= UCRXIE; //使能接收中断
            __bis_SR_register(LPM0_bits + GIE); //进入LPM0,使能全局中断
        }
    }
    

SPI

使用以下代码设置SPI

/* 这里省略端口复用设置 */
UCB0CTL1 |= UCSWrST;
UCB0CTL0 |= UCMST + UCSYNC; //设置为三线SPI主机模式,8位数据位
UCB1CTL1 = UCSSEL_2; //时钟设置为SMCLK
UCB1BR0 = 0xFF; //UCB0CLK = SMCLK / 0xFFF
UCB1BR1 = 0x0F; //一般来说可以选择100kHz以上的频率,通常使用4MHz频率

UCB1CTL1 &= ~UCSWRST; //清除复位标志,外设开始运行

中断和驱动编写部分和IIC基本一致,不再赘述

片上模拟外设

msp430集成了12位ADC/DAC和模拟比较器外设

ADC

f5529配备了SAR架构的ADC12_A模块,支持12位ADC,具有16个模拟输入通道、16个独立的转换和存储单元,可在脱离CPU情况下完成ADC转换,最高200ksp(千次采样/每秒)

基本配置流程如下:

  1. 配置核心控制寄存器,选择时钟、转换模式、启动参考电压生成器

    ADC12_A内部具有独立的REF模块,可以提供1.5V、2V、2.5V参考电压。通过REF模块的REFMSTR位选择参考电压:置1时(默认状态)使用REF模块控制参考电压;置0时使用ADC12_A的参考电压模块控制参考电压

    使用ADC12REF2_5V控制参考电压大小,ADC12REFON控制是否开启电压生成器,ADC12REFOUT控制是否输出参考电压

    ADC使用ADC12CLK时钟用来控制采样和转换的时间和周期,时钟源可选择SMCLK、MCLK、ACLK和ADC12OSC(UCS模块的MODCLK的5MHz内部振荡器),时钟源使用ADC12DIV控制的预分频器和ADC12SSELx控制的分频器进行分频,可选择1-32分频

    基本配置程序如下

    ADC12CTL0 = ADC12ON+ADC12SHT0_8+ADC12MSC; //开启ADC12,设置采样时间,设置采样模式(这里使用了多采样转换)
    

    ADC具有4种模式,通过CONSEQx位选择

    • 单通道模式:单通道只采样和转换一次,当ADC12SC置位时触发一次采样转换操作,持续一段时间后自动复位
    • 序列通道(自动扫描模式:使用CSTARTADDx位选择开始转换的第一个ADC1MCTLx,指定后序列启动指针会自动递增,被它指向的通道会自动开始转换,转换完成后自动复位,操作一直继续直到处理到ADC12EOS=1的ADC12MCTLx才会停止,ADC12EOS作为序列结束的标志,只在序列转换模式下使用
    • 重复单通道模式:一个单独的通道会被不断采样转换,可以设置完成中断来读取转换结果
    • 重复序列通道(自动重复)模式:一序列通道会被重复采样和转换,使用CSTARTADDx定义第一个ADC12McTLx,序列再检测到ADC12EOS(序列结束标志)置位后会自动结束,下一个触发信号将重新开始序列
  2. 保持其处于禁用状态,ADC12ENC=0

  3. 配置引脚复用

    将GPIO复用为ADC输入引脚

    PxSEL |= 0x0n; //选择Px.n引脚复用为ADC输入
    

    注意:只有能接入ADC的对应IO口才能复用

  4. 配置采样定时器

    一次转换由一个采样信号SHI的上升沿引起,可通过SHSx位来选择,可以选为直接由ADC12SC位控制或使用定时器来控制

    • ADC12SHP=0,使用扩展采样模式,SHI信号直接控制SAMPCON并定义采样周期长度;SAMPCON=1时采样活跃,SAMPCON的下降沿会在同步ADC12CLK信号后启动转换
    • ADC12SHP=1,使用脉冲采样模式,SHI信号用于触发采样定时器,采样定时器在同步AD12CLK后将SAMPCON保持在高电平并持续一个可编程的间隔\(t_{sample}\),整个采样时间就是\(t_{sample}+t_{sync}\)
  5. 可单独配置每个通道的参考电压和输入源

    使用ADC12MCTLx(x为0-15)控制转换存储单元,通过ADC12SREF和ADC12INCH分别选择参考电压和模拟信号的输入通道

    在单通道单转换模式中,复位ADC12ENC立刻停止一个转换且会导致转换结果不可预知,一般来说需要使用以下语句停止单通道转换

    while(ADC12BUSY != 0)
    {
        delay();
    }
    ADC12ENC = 0; //等待ADC12BUSY = 0后才能停止单通道转换
    

    重复单通道模式下,复位ADC12ENC会在当前转换结束时停止转换器

    序列通道或重复序列通道中,复位ADC12ENC会在序列结束时停止转换器

    任何模式中都可以通过清零ADC12CONSEQ并复位ADC12ENC位来立刻停止,但这样会导致转换结果不可预知

  6. 可以使能集成温度传感器或配置转换完成中断

    ADC可以直接连接内部的温度传感器(这是参考电压生成器的一部分)来获取内部温度,计算公式如下

    \[T=(ADC_{raw} - CAL\_ADC\_T30) \times (\frac{85-30}{CAL\_ADC\_T85 - CAL\_ADC\_T30}) + 30 \]

    其中T是精确温度值,\(ADC_{raw}\)是数模转换结果,两个\(CAL\_ADC\_T\)为温度矫正参数,需要通过地址进行访问调用,每个设备的矫正参数都不同,该参数会被使用TLV(Tag-Length-Value)的方式写入单片机,地址调用形式如下

    *((unsigned int*)0x1A1A) //具体地址需要根据电压和设备的不同来确定,详细内容参考datasheet即可
    

    ADC12_A具有18个中断,共用一个中断源

    可单独配置某通道完成中断、ADC12MEMx溢出中断、ADC12_A计时溢出中断,所有中断通过唯一的中断向量寄存器来配置,也就是说只使用一个中断服务函数

    注意:任何对于ADC12IV的读写操作都会自动复位ADC12OV或ADC12TOV,如果中断服务函数在访问ADC12IV寄存器时有ADC12OV和ADC12IFGx中断生成,那么ADC12OV中断条件会自动复位,在中断服务函数返回后紧接着处理其他中断,因此应避免中断执行时间过长导致ADC中断占用前台应用

    使用下面的程序来开启ADC中断

    ADC12IE = 0x01;
    
  7. 将ADC12ENC=1来使能设备

    ADC12CTL0 |= ADC12ENC;
    ADC12CTL0 |= ADC12SC;
    
  8. 通过读取ADC12MCTLx对应的ADC12MEMx来获取转换结果

单通道单次转换示例如下(测量引脚电压高于参考电压则指示灯亮)

#include <msp430.h>

int main(void)
{
  	WDTCTL = WDTPW + WDTHOLD;  //停止WDG
  	ADC12CTL0 = ADC12SHT02 + ADC12ON; //设置采样时间并开启ADC12_A
 	ADC12CTL1 = ADC12SHP; //使用采样定时器
  	ADC12IE = 0x01; //使能中断
  	ADC12CTL0 |= ADC12ENC; //使能转换通道
  	P6SEL |= 0x01; //配置P6.1复用为ADC输入
  	P1DIR |= 0x01; //配置P1.0信号指示输出

  	while (1)
  	{
    	ADC12CTL0 |= ADC12SC; //开始依次采样

    	__bis_SR_register(LPM0_bits + GIE); //进入LPM0,开启全局中断
    	__no_operation(); //用于调试器打断点的空指令
  	}
}

/* ADC中断服务函数 */
#pragma vector = ADC12_VECTOR
__interrupt void ADC12_ISR(void)
{
  	switch(__even_in_range(ADC12IV,34))
  	{
  	case  0: break;                           // Vector  0:  No interrupt
  	case  2: break;                           // Vector  2:  ADC溢出中断
  	case  4: break;                           // Vector  4:  ADC超时中断
  	case  6:                                  // Vector  6:  ADC12IFG0
		/* 测量P1引脚 */
    	if (ADC12MEM0 >= 0x7ff)               // ADC12MEM = A0 > 0.5AVcc?
      		P1OUT |= BIT0;                    // P1.0 = 1
    	else
      		P1OUT &= ~BIT0;                   // P1.0 = 0

    	__bic_SR_register_on_exit(LPM0_bits); //退出LPM0模式
  	case  8: break;                           // Vector  8:  ADC12IFG1
  	case 10: break;                           // Vector 10:  ADC12IFG2
  	case 12: break;                           // Vector 12:  ADC12IFG3
  	case 14: break;                           // Vector 14:  ADC12IFG4
  	case 16: break;                           // Vector 16:  ADC12IFG5
  	case 18: break;                           // Vector 18:  ADC12IFG6
  	case 20: break;                           // Vector 20:  ADC12IFG7
 	case 22: break;                           // Vector 22:  ADC12IFG8
  	case 24: break;                           // Vector 24:  ADC12IFG9
  	case 26: break;                           // Vector 26:  ADC12IFG10
  	case 28: break;                           // Vector 28:  ADC12IFG11
  	case 30: break;                           // Vector 30:  ADC12IFG12
  	case 32: break;                           // Vector 32:  ADC12IFG13
  	case 34: break;                           // Vector 34:  ADC12IFG14
  	default: break; 
  }
}

多通道重复转换示例如下(连续读取P6.0、P6.1、P6.2、P6.3的ADC值)

#include <msp430.h>

#define Num_of_Results 8

volatile unsigned int A0results[Num_of_Results];
volatile unsigned int A1results[Num_of_Results];
volatile unsigned int A2results[Num_of_Results];
volatile unsigned int A3results[Num_of_Results];

int main(void)
{
  	WDTCTL = WDTPW+WDTHOLD;                   //关闭WDG
    
  	P6SEL = 0x0F;                             //使能ADC复用引脚(P6的低四位0、1、2、3进行复用)
  	ADC12CTL0 = ADC12ON+ADC12MSC+ADC12SHT0_8; //开启ADC、扩展采样时间避免结果溢出
  	ADC12CTL1 = ADC12SHP+ADC12CONSEQ_3;       //使用采样定时器,设置为多通道重复采样模式
    
    /* 设置0、1、2、3通道的参数 */
  	ADC12MCTL0 = ADC12INCH_0;                 // ref+=AVcc, channel = A0
  	ADC12MCTL1 = ADC12INCH_1;                 // ref+=AVcc, channel = A1
  	ADC12MCTL2 = ADC12INCH_2;                 // ref+=AVcc, channel = A2
  	ADC12MCTL3 = ADC12INCH_3+ADC12EOS;        // ref+=AVcc, channel = A3 ,设置ADC结束标志
    
  	ADC12IE = 0x08;                           //使能ADC12IFG.3中断
  	ADC12CTL0 |= ADC12ENC;                    //使能转换通道
  	ADC12CTL0 |= ADC12SC;                     //开始转换-软件触发
  
  	__bis_SR_register(LPM0_bits + GIE);       //进入LPM0并开启全局中断
  	__no_operation();                         //用于调试器打断点
}

#pragma vector=ADC12_VECTOR
__interrupt void ADC12ISR (void)
{
 	static unsigned int index = 0;

  	switch(__even_in_range(ADC12IV,34))
  	{
  	case  0: break;                           // Vector  0:  No interrupt
  	case  2: break;                           // Vector  2:  ADC overflow
  	case  4: break;                           // Vector  4:  ADC timing overflow
  	case  6: break;                           // Vector  6:  ADC12IFG0
  	case  8: break;                           // Vector  8:  ADC12IFG1
 	case 10: break;                           // Vector 10:  ADC12IFG2
  	case 12:                                  // Vector 12:  ADC12IFG3
    	A0results[index] = ADC12MEM0;         // Move A0 results, IFG is cleared
    	A1results[index] = ADC12MEM1;         // Move A1 results, IFG is cleared
    	A2results[index] = ADC12MEM2;         // Move A2 results, IFG is cleared
    	A3results[index] = ADC12MEM3;         // Move A3 results, IFG is cleared
    	index++;                              // 保存到结果缓存区
    
    	if (index == 8)
    	{
      		(index = 0);
    	}
  	case 14: break;                           // Vector 14:  ADC12IFG4
  	case 16: break;                           // Vector 16:  ADC12IFG5
  	case 18: break;                           // Vector 18:  ADC12IFG6
  	case 20: break;                           // Vector 20:  ADC12IFG7
  	case 22: break;                           // Vector 22:  ADC12IFG8
  	case 24: break;                           // Vector 24:  ADC12IFG9
  	case 26: break;                           // Vector 26:  ADC12IFG10
  	case 28: break;                           // Vector 28:  ADC12IFG11
  	case 30: break;                           // Vector 30:  ADC12IFG12
  	case 32: break;                           // Vector 32:  ADC12IFG13
  	case 34: break;                           // Vector 34:  ADC12IFG14
  	default: break; 
  }  
}

DAC

msp430f5529配备了DAC12_A模块,模块组成结构如下:

  • 核心

    可以通过修改DAC12RES的值将DAC12_A配置为8位或12位模式;配置DAC12IR和DAC12OG位可以将满标度输出配置为所选参考电压的1倍、2倍或3倍;配置DAC12DF位选择输入的数据格式是原码还是补码。

  • 端口

    大多数DAC复用的端口都有其他复用功能,但是当DAC12AMPx>0时,DAC12_A会忽略PxSEL.y和PxSEL.x的值,自动配置端口为DAC12_A输出复用功能

    每个DAC通道都能输出到两个不同的端口,通过DAC122OPS选择,详细参数需要查阅datasheet

  • 参考电压

    使用DAC12SREFx选择DAC12_A参考电压,该值从AVCC、外部电压输入、内部1.16V参考电压、内部REF模块提供1.5V、2V、2.5V参考电压之一选择

  • 参考输入和电压输出缓冲区

    参考输入和电压输出缓冲区通过寄存器配置来平衡建立时间和功耗,通过配置DAC12AMPx来选择组合,其值越小、建立时间越长、缓冲区上的电流消耗越小

  • 数据格式

    使用原码或补码形式都可以设置DAC,使用DAC12_xDAT(取值范围0800h(输出0V)-07ffh(输出标度),另外取值0000h时输出标度的一半)控制

相关示例程序如下

/* 输出固定电压 */
void main(void)
{
    WDTCTL = WDTPW+WDTHOLD; //关闭WDG
    
    //设置无增益,参考电压AVCC,启动DAC校准
    DAC12_0CTL0 = DAC12IR + DAC12SREF_1 + DAC12AMP_5 + DAC12CALON;
	DAC12_0CTL0 |= DAC12ENC; //使能DAC12_A
    //需要注意这里的输出值是12位
    DAC12_0DAT = 0x000; //DAC输出0V
    //和上面一样的流程
    DAC12_1CTL0 = DAC12IR + DAC12SREF_1 + DAC12AMP_5 + DAC12CALON;
    DAC12_0CTL0 |= DAC12ENC; //使能DAC12_A
    DAC12_0DAT = 0x700; //输出1.4V
    __bis_SR_register(LPM4_bits); //进入LPM4,DAC会自动输出
}

/* 输出波形 */
//以FLASH换内存
static int Sin_tab[100] = { 
1638,1740,1843,1944,2045,2143,2240,2335,2426,2515,
2600,2681,2758,2831, 2899,2962,3020,3072,3119,3160,
3195,3224,3246,3262,3272,3272,3263,3247,3224,3196,
3161,3120,3074,3021,2964,2901,2833,2760,2683,2602,
2517,2429,2337,2243,2146,2047,1947,1845,1743,1640,
1537,1435,1333,1233,1134,1037,943,851,762,677,596,
519,446,378,314,256,204,157,116,81,52,29,13,3,0,2,
12,28,50,78,113,154,200,252,310,373,440,513,590,
671,756,756,844,936,1030,1127,1225,1326,1427,1529};

void main(void)
{
    WDTCTL = WDTPW + WDTHOLD; //停看门狗
 	INIT_XT2(); //开启时钟   

 	P5SEL = 0XFF;
 	ADC12CTL0 = REFON; //参考电压为内部2.5v  
    
    /* 配置DMA,直接将Sin_tab数据传输到DAC */
 	DMA0SA = (int) Sin_tab; //源地址寄存器
 	DMA0DA = DAC12_0DAT_; //目的地址寄存器
	DMA0SZ = 100; //传输基本单元的个数
 	DMACTL0 = DMA0TSEL_5; // DAC12_0CTL的DAC12IFG标志
 	DMA0CTL = DMADT_4 + DMASRCINCR_3 + DMAEN; //DMADT_4:重复的块传输方式
    
    /* 使用内部1.5V参考电压,无增益,使能DAC12_A校准并使能DAC12_A */
	DAC12_0CTL = DAC12LSEL_2 + DAC12IR + DAC12AMP_5 + DAC12IFG + DAC12ENC; //配置DAC
    
    /* 强制输出第一个中断 */
 	CCTL1 = OUTMOD_3; //设置并复位
	CCR1 = 1; // PWM Duty Cycle   
	CCR0 = 8-1; //1kHz频率
	TACTL = TASSEL_2 + MC_1; //使用SMCLK时钟源,向上计数模式  
	__bis_SR_register(LPM0_bits); //进入LPM0,DMA和DAC都在工作,会自动输出
}

可编程比较器

f5529配备了Comp_B模块,支持精密线性数模转换、电源电压监控、外部模拟信号电压监测功能

其中核心是一个精密电压比较器,同相端比反相端电压高,则输出高电平,否则输出低电平,使用CBON位打开/关闭比较器

使用CBCTL0寄存器控制外部输入端口,CBIPEN和CBIMEN分别控制同相端和反相端;使用CBIMSEL和CBIPSEL控制端子连接的GPIO,应选择P6的端口,以这两个寄存器控制端口号

可以使用CBSHORT短路正反相输入,可用来建立简单的采样-保持机制,一般来说设置采样时间为3-10τ,3τ可以将采样电容充电到95%的输入信号电压值,5τ可以将采样电容充电到99%,10τ可以满足12位的精度

使用CBF控制位控制输出信号的输出滤波器

使用参考电压生成器来生成VREF,可以应用于比较器输入端,使用CBREF0x和CBREF1xl来控制

使用CBPWRMD来选择比较器功耗模式,默认为00——最大功耗、最快速度;可以调节到11来使用最低功耗、最低速度

使用CBCTL3来控制比较器的端口是否禁用;使用CBIPSEL或CBIMSEL来控制对应端口的输入缓冲区

比较器也可以开启中断

对于可编程电压比较器而言,一般使用滞后比较来让参考电压根据输出值变化,可以让比较器输出更加稳定,降低噪声

代码如下

void main(void)
{
    WDTCTL = WDTPW + WDTHOLD; //停看门狗
    P1DIR |= BIT6; //P1.6作输出
    P1SEL |= BIT6; //复用为比较器输出CBOUT
    
    P7DIR |= 0xFF; //P7设置为输出,用来驱动LED显示当前比较器结果
    
    CBCTL0 |= CBIPEN + CBIPSEL_0; //比较器施恩那个,设置输入通道CB0为P6.0
    CBCTL1 |= CBPWRMD_0; //设置为高速模数
    CBCTL2 |= CBRSEL; //使用VREF作为反相端的参考电压
    
    /* 设置高低两个阈值电压 */
    CBCTL2 |= CBRS_1 + CBREF13; //以VCC为参考电压,CBREF1=8,VREF=VCC/4
    CBCTL2 |= CBREF04 + CBREF03; //VREF0=VCC * 3/4
    
    CBCTL3 |= BIT0; //打开Comp_B
    CBCTL1 |= CBON; //使能比较器
    __delay_cycles(75); //等待比较器内部参考电压达到稳定
    
    while(1)
    {
        if(CBCTL1 & CBOUT) //如果CBOUT为高电平
        {
            P7OUT = 0x00; //LED点亮
        }
        else
        {
            P7OUT = 0xFF; //LED熄灭
		}
    }
}

以上代码在输入电压大于VREF0=3/4 VCC时CBOUT输出高电平,LED点亮;输入电压小于VREF0=1/4 VCC时CBOUT输出低电平,LED熄灭;当电压在二者之间时,CBOUT状态不变,起到了稳定输出的作用

同样的比较器也可以产生中断信号,在检测到设定的跳变沿(上升沿或下降沿)后触发中断,进入对应的中断服务函数,可以在其中判断比较器输出的电平,避免了轮询

CBCTL1 |= CBPWRMD_1 + CBF + CBFDLY_3; //普通模式,选择中断边压,使用输出滤波
/* 在中间设置其他比较器参数 */
/* 等待一定时间来让参考电压稳定 */
CBINT &= ~(CBIFG + CCBIIFG); //清除错误中断标志
CBINT |= CBIE + CBIIE; //使能比较器输出中断和输出反相中断
__bis_SR_register(LPM0_bits + GIE); //开启全局中断

#pragma vector=COMP_B_VECTOR
__interrupt void Comp_B_ISR(void)
{
    switch(__even_in_range(CBIV, 4))
    {
        case 0: //无中断
            break;
		case 2: //中断CBIFG
            if(CBCTL1 & CBOUT) //判断是否输出高电平
            {
                
			}
            break;
        case 4: //反相中断CBIIFG
            if(!(CBCTL1 & CBOUT)) //反相端需要相反的判断
            {
                
            }
            break;
        default:
            break;
	}
}

这里仅列出中断服务函数和其开启方式

DMA

msp430的DMA最多有8个通道,但是msp430f5529只有3个通道

基本使用方法和stm32的DMA完全一样,四种模式、单传输、块传输、连续传输都和stm32的传输方法类似,但设置更为简单

一个使用DMA进行UART传输的例子如下所示:

#include <msp430.h>
#include <stdint.h>

static uint8_t String1[] = {"Hello World\r\n"};

void main(void)
{
    WDTCTL = WDTPW + WDTHOLD; //停看门狗
    
    /* 端口配置 */
    P4SEL = BIT4 + BIT5; //配置P4.4、P4.5为Tx、Rx端口
        
    /* 配置USCI_A1为UART模式 */
    UCA1CTL1 = UCSSEL_1; //使用ACLK为时钟源
    //控制分频器设置波特率为9600
	UCA1BR0 = 0x03; //分频器高八位
    UCA1BR1 = 0x0; //分频器低八位
    UCA1MCTL = UCBRS_3 + UCBRF_0; //调制器UCBRSx = 3
    UCA1CTL1 &= ~UCSWRST; //启动设备
    /* 配置DMA */
    DMACTL0 = DMA0TSEL_1; //以定时器TA0CCR0 CCIFG为触发源
    
    __data16_write_addr((uint16_t)&DMA0SA, (uint32_t)String1); //源地址:String1字符串
    __data16_write_addr((uint16_t)&DMA0DA), (uint32_t)&UCA1TXBUF); //目标地址:UART发送缓存区域
	
	DMA0CTL = DMADT_4 + DMASRCINCR_3 + DMASBDB +DMAEN; //重复单传输,递增模式,字节到字节,并使能DMA
    /* 配置定时器 */
    TA0CCR0 = 8192; //字符传输频率=32768/8192=4 字节/s
	TA0CTL = TASSEL_1 + MC_1; //使用ACLK作为时钟源,采用增模式
    __bis_SR_register(LPM0_bits); //进入LPM3
}

这个程序会连续向外以9600波特率发送串口数据

电源管理

msp430f5529的电源管理模块PMM由监督器SVS和监视器SVM组成

SVS是强制要求的,用于保障设备稳定运行

SVM是宽松可编程的,用于进行一些低功耗控制和安全保障

可以通过对SVM编程提高VCORE来支持更高的MCLK,也就是所谓的超频

posted on 2021-11-12 00:09  redlightASl  阅读(1160)  评论(0编辑  收藏  举报