骑IC看MCU

  • 2020-01-17
  • 发表了日志: TI - MCU - MSP430使用指南3 -> SYS系统控制

  • 发表了主题帖: TI - MCU - MSP430使用指南3 -> SYS系统控制

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   MSP430 MCU的内部系统控制主要包括以下几块: 上电及复位控制 中断管理 MCU运行模式 TLV数据 对于MSP430的系统控制吧,如果仅仅是站在应用层的角度来说,可以不用太怎么了解,毕竟使用起来的话,SYS模块的的寄存器直接默认就好啦,不会有什么问题,基本都是存在比较深的解决不了的问题时才会牵扯到这一块的内容,因此我的讲解也比较有限,主要说一些关键的内容吧(主要是 我了解的也不是特别深。。。。)。下面开始进入我们今天的分享: 上电及复位管理 BOR:Brownout Reset,也就是掉电复位。 POR:Power on Reset,上电复位。 PUC:Power up Clear,通电清除 这三个有什么区别呢?为什么TI要设计三个复位模式呢?下面首先来看一下内部的复位结构图。 可以看到,BOR的影响最大,其次是POR,再其次是PUC,也就是说只要产生了BOR复位,那么肯定会生成POR复位和PUC复位。如果产生了POR复位,那么肯定会生成PUC复位但不一定会生成BOR复位。那么哪些条件下会产生响应的复位呢? 对于BOR: 设备上电 RST/NMI引脚出现低电平,进入复位模式 从LPM3.5或LPM4.5模式下唤醒 SVS电压监控模块出现了低,也就是内核电压再规定范围外。(SVS内容请参见指南4) 软件BOR事件 对于POR: BOR产生 软件POR事件 对于PUC: BOR或POR产生 看门狗定时时间到 看门狗定时器密码写入错误 FRAM写密码错误(FRAM内容请参见指南9) PMM写密码错误(PMM内容请参见指南4) 其他外围条件 PS:有些不同的芯片 这个条件不一样的,具体哪些条件需要看芯片的datasheet和user guide。 那么这三个不同的复位由不同的条件产生,那么产生后的结果是什么呢: RST/NMI引脚设置成RST模式 所有I/O设置成通用I/O中的输入引脚模式 其他外设初始化,这个初始化成什么模式需要看外设寄存器中复位后的状态 状态寄存器SR复位 看门狗开启并初始化成Active模式 PC指针指向复位中断向量0x0FFFE处,开始执行boot code (PS:多说一点,在MCU上电开始正常执行程序之前,最初是在复位中断向量处的,执行boot code程序,这一块程序是MCU在生产时就固化在MCU内部的程序,这一块程序主要负责搬运程序到RAM中,初始化一些参数等) 那么看起来,这三种复位模式产生后对MCU动作都是一样的,那么为什么要这样设置呢? 放心TI工程师肯定不会闲着无聊做没有意义事情,那么原因就在PMM模块上,PMM模块四MSP430可以做到30nA功耗的核心了,就是这个电源管理(请参见指南4),其实在这里BOR,POR和PUC的区别就是会有不同的标志位,BOR和POR标志位时PMMBORIFG和PMMPORIFG,PUC的产生条件很多,因此要根据每个模块去看,基本在每个模块中都有PUC产生的标志。 那么,就知道啦,当复位后,我们就可以通过检查寄存器的值来确定复位源,从而执行不同的操作,比如保存数据呀,记录复位原因呀等等。 中断管理 学习过微型计算机基础的可能都很熟悉这一块,中断管理嘛,本质上就是一个优先级问题。首先分为可屏蔽中断和不可屏蔽中断,不可屏蔽中断NMI肯定是优先级最高的,只要产生这个中断,那么不论MCU在做什么,立马停下来响应这个中断。其他的则可以使能响应中断,也可以失能。当遇到两个中断情况,则要看这两个中断的优先级来决定了,中断服务程序的响应过程也是比较简单的,入栈PUSH和出栈POP嘛,所以MCU会有一个中断向量表,对应着每个中断的入口地址。 MSP430 MCU不支持硬件上设置中断优先级的,有ICC模块的除外,有ICC模块的时可以设置优先级的。 比如下图时MSPFR2355的中断优先级:(PS:FR2355是有ICC模块的,可以设定比较优先级)   MCU运行模式 先上一个比较乱的运行模式切换图,如上图所示,什么复位呀,LPM模式呀,Active模式呀等等,如何切换到呀,哪些模块运行呀,比较乱,下面稍微讲解一下。 MSP430 MCU之所以能做到超低功耗,主要就是有多种低功耗模式LPM,每种模式下关闭不同的时钟或者资源,甚至时CPU,以实现降低功耗的效果,但是关闭这些资源的时候,肯定会留响应的唤醒机制,如下表所示: SCG0和SCG1时在SR状态寄存器里的时钟生成标志位(参考指南2)。还有OSC晶振,CPU等,主要就是通过不同的模式下关闭不同的资源,其中LPM3.5/LPM4.5模式是在LPM3/LPM4模式的基础上关闭特定的点实现的,功耗最低,当然在使用过程中也要时刻注意着唤醒的方法和唤醒时间。 如何退出,或者进入指定的LPM模式可以参见user guide,不过使用起来其实就是两条语句: __bis_SR_register(LPM0_bits | GIE);                   // Enter LPM0 w/ interrupts __bic_SR_register_on_exit(LPM0_bits); // Exit LPM0 但是使用起来你会发现只有LPM0_bits, LPM1_bits , LPM2_bits , LPM3_bits, LPM4_bits,那么如何进入LPM3.5和LPM4.5模式呢? 如下是进入LPM3.5模式下的程序:(其实就是进入LPM3的基础上关闭PMM) PMMCTL0_H = PMMPW_H;                    // Open PMM Registers for write PMMCTL0_L |= PMMREGOFF;                 // and set PMMREGOFF __bis_SR_register(LPM3_bits | GIE); 注意,从LPM3.5模式下唤醒,肯定是会从复位模式开始的,不过有响应的标志位给用户用来确定是LPM3.5唤醒导致的复位。 LPM4.5模式类似: PMMCTL0_H = PMMPW_H;                // Open PMM Registers for write PMMCTL0_L |= PMMREGOFF;             // and set PMMREGOFF PMMCTL0_H = 0;                      // Lock PMM Registers __bis_SR_register(LPM4_bits | GIE); 唤醒后也是从Reset中断向量开始执行。 TLV数据 TLV是个什么东西呢? 其实就是一个保存MCU内部一些信息的数据,包括MCU的代号,Die的位置等,当然还有一些校准的数据供用户使用,比如ADC的校准数据,CS的校准数据等。如下图所示: 如上图所示,是MSP430FR2355的TLV数据,可以看到里面包含的基本信息和ADC,DCO的校准信息。

  • 发表了主题帖: TI - MCU - MSP430使用指南14 -> I2C通信(eUSCI)

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   说到MSP430 MCU的I2C资源,那么首先就得先看一下USCI。 如果你了解过多款MSP430 MCU你会发现,内部资源里有USCI和eUSCI,那么他们什么意思呢? USCI (Universal Serial Communication Interface),即通用串行通信接口,eUSCI中的e则是enhanced的意思,即增强型,那么有什么区别呢? 一些主要的区别如下表所示,F2xx这一列代表USCI,FR57xx代表eUSCI 更多详细区别,请查看TI官方文档: http://www.ib939.com/368/lit/an/slaa522a/slaa522a.pdf 其实你会发现 区别不是很大,仅仅是寄存器的名字变了一点,在使用起来很是相似,因此本次使用eUSCI进行讲解。 eUSCI又分两种:A和B,有什么区别呢: A:支持UART和SPI B:支持I2C和SPI 也就是说两种都支持SPI,但A仅支持UART和SPI,B仅支持I2C和SPI。想过为什么吗? (这段内容比较深,也不一定是全部都是正确的:我认为毕竟eUSCI就是数字逻辑电路嘛,TI设计成这种格式去给客户机会去选用SPI或I2C/UART,相对于单独设计I2C,SPI,UART内核来说,第一节省电路,可以降低功耗和成本,第二减少引脚数量。那为什么都支持SPI嘛,因为SPI协议简单嘛,没有I2C那种起始位,停止位,也没有UART那种固定的波特率,SPI自己有自己的时钟,所以内部电路集成方面与UART或I2C有一定的公共成分,而UART和I2C差别较大,结合起来没什么性价比吧,所以出现了A版本和B版本。这些仅是我的猜测。。。em……) 好啦,那么正式进入我们今天的主题  à I2C协议。 首先,第一部分肯定要讲清楚什么是I2C协议,这类资料网上一大堆一大堆,也可以很容易的搜索到资料来参考,我就简单的过一下吧。 I2C(Inter-Integrated Circuit) 是Philips公司发明的一种用于片级连接的总线方式,有什么好处呢: 简单,就两根线SDA和SCL。 总线方式,可以挂接多个设备。 通信速度有低速,普通,高速等模式,目前最快好像可以实现3.4Mbps速度。 其实还有挺多优点的,不然也不可能经历那么多年I2C依旧很主流。首先我想稍微解释一下挂接设备数量问题: 这个可以挂接多个设备主要是由于I2C的开漏特性决定的,即0有效,外部需要上拉电阻,开漏和推挽的资料网上也很多,可以自己查来看看。那么能挂接多少设备呢?这个本质上是由总线上的容性决定的,我们都知道,电容越大,充电时间越长,那么信号上升速度越满,当慢到不能满足I2C要求是,那么则不能挂接了,挂接设备相当于并联电容嘛,电容就变大了,然后就。。。,就。。。。 I2C协议如下: 简单吧,就两根线嘛,首先呢,起始位。如果你是个Master,需要发信号给Slave,那么肯定要给个起始位嘛,告诉Slave你开始发数据了,如上图:SCL=1,SDA由0变1标志着起始位,然后呢,肯定是要写地址了,从设备地址,总线上挂接了很多Slave,你要发送给谁呢? 那么就写谁的地址,I2C地址是7位的(别抬杠,知道有10位地址模式,不过原理相同的),然后呢,下一位是R/W,你是读Slave里的数据呢,还是写数据到Slave呢?需要吧? 后面就是数据了,当然,当你写完一个数据后,Slave会给你个回应那个,then你接着写,Slave再回应,就这样最后Master发送一个STOP位(SCL=1,SDA由低变高),好啦,那么一包数据通信即结束了。 PS:讲解一个小知识:你细看I2C协议你会发现:SDA数据线上只能再SCL=0的时候改变,SCL=1时需要保持状态,为什么呢? 简单嘛,请看START和STOP信号,你就知道了。 好啦,I2C协议讲解完了,那么正式进入正题:MSP430 I2C如何使用: 先上一张I2C模块的内部结构图: 一堆一堆的寄存器,标志位对吧,最终出来两根线SDA和SCL是吧。就是这样的嘛,内部的模块不就是一些逻辑电路加一些Memory嘛,迷糊没事的,后面看看寄存器就知道了: 那么就开始讲解如何使用吧,也就是寄存器内容啦: 看一下有哪些寄存器: UCBxCTLW0   UCA10:10位地址选择,1时为10位地址模式,0时为7位地址模式。 UCLSA10:Slave10位地址选择,也就是说你作为Master时需要通信的Slave是否是10位地址的模式。 UCMM:多Master选择,因为正常I2C时钟是由Master控制的,因此如果存在多Master时,需要释放SCL的,故设计了这一位用来应对多Master模式。 UCMST:Master或Slave选择,决定MSP430用作I2C中的Master还是Slave。 UCMODEx:模式选择:SPI或者I2C,同时SPI还分为三线和四线模式。 UCSSELx:时钟源选择,建议选SMCLK吧,频率高一点,精度也就高一点。 UCTXACK:回应地址正确,这个是再Slave或者多Master模式下,开启地址掩码功能后,只对比除去掩码的剩下地址,如果一致的话,需要发送个地址确认位(UCSWACK=1时需要手动设置发送,UCSWACK=0时会自动发送),这个就是那个地址确认位。 UCTR:发送还是接受,数据给Slave时是发送,读的时候则是接受或者作为Slave也是接受。 UCTXNACK:发送ACK信号,在Slave模式下使用。 UCTXSTP:生成STOP信号,在Master模式下使用。 UCTXSTT:生成起始信号,在Master模式下使用。 UCSWRST:软件复位使能,默认是1,即不能写一些寄存器设置I2C参数,有些寄存器想进行操作时需要复位这一位。 UCBxCTLW1               UCETXINT:这一位仅在Slave模式下有用,决定着什么时候会置位UCTXIFG0(这一位置位标志着有能力去发送数据了),当这一位是1时,收到Master发送过来的START信号就会置位,当这一位是0时,则等到接收到Master发送的地址与自己的地址匹配时,才会置位UCTXIFG0(注意这种情况下地址1-3都要disable)。               UCCLTO:I2C总线时钟超时,也就是可能总线上卡死了,这一位用来设置这个时间,也可以关闭此项功能。如果设置了时间,在超时之后会触发UCCLTOIFG中断,用户可以在中断里重新初始化I2C。               UCSTPNACK:这一位是指在MCU作为Master接受数据时,在收到Slave的最后一个字节数据后可以发送一个响应给Slave,不过这并不符合I2C协议,因此只用在一些特定场合,正常的I2C协议,请忽略。 UCSWACK:看UCBxCTLW0中的UCTXACK描述,这一位和地址掩码有关系。               UCASTPx:自动生成STOP设置,这一位只用在Master模式下,在设置自动生成STOP信号后,发送完设置长度的数据后,I2C内核直接自动发送停止位,这样会简化用户操作。注意:你可能会遇到一个情况:设置自动STOP后,会设置一个发送数据的长度,而这个数据长度寄存器,也就是UCTBCNTx,这个是8位的,也就是最大值就是255了,因此,如果I2C一次性需要传递的数据包超过了255字节,那么有两种方案解决:不要开启STOP,选择手动设置STOP信号,或者启动DMA模块,这两张方式都可以。手动STOP简单一点,DMA速度快一点。               UCGLITx:错误检测位,这一位可以检测信号线上的毛刺噪声,建议直接默认就好啦。 UCBxBRW                这个寄存器是控制I2C波特率的,目前MSP430的I2C模块支持快速的400Kbps(资料中说的严谨,起始可以更快的)。那么如何计算波特率的呢?看下面框图: 就是对时钟进行分频后,就直接传输给SCL线上了,因此很容易计算,在选择时钟源之后,时钟源的频率除以这一位,就是SCL的时钟频率。 UCBxSTATW UCBCNTx:只读寄存器,用来获取I2C总线上接受或者发送的数据量。 UCSCLLOW:用来查看SCL状态,可以确定是否有其他设备将此总线时钟拉低了。 UCGC:只读寄存器,标志着是否接收到了通用的Call 地址。 UCBBUSY:只读寄存器,用来查看总线上是否繁忙,一般情况下,为了保证数据发送的严谨性,在数据发送前都要查询这一位来决定是否可以发送数据的。   UCBxTBCNT UCTBCNTx:在上面自动生成STOP信号时提过,这一位用来设置I2C一包数据发送的数量。 UCBxRXBUF I2C数据接受缓冲区,在收到I2C接受中断后(当然是在使能中断后啦),接收到的数据会存放在这个缓冲区里,需要读取出来,读取后会自动清除接收中断。   UCBxTXBUF               I2C数据发送缓冲区,也就是如果你想通过I2C协议发送一个数据,那么你只需要把这个数据写入这个寄存器中即可,当数据移入移位寄存器后,开始发送,发送完成后则会触发发中断,代表着当前寄存器的值已经空了,数据已经发送完成,可以写入下一个数据进行发送了。 UCBxI2COA0               I2C的general call是一个什么呢? 就类似于广播模式嘛,地址就是0,更多详细资料,请查看I2C官网:http://www.ib939.com/addressing/(PS:推荐大家有问题多去官网查看,官方发布的肯定没什么问题,理解起来不会有误区)                UCOAEN:使能自己的I2C地址。                I2COAx:自己的I2C地址0。 UCBxI2COA1 参考UCBxI2COA0                UCOAEN:使能自己的I2C地址。                I2COAx:自己的I2C地址1。   UCBxI2COA2 参考UCBxI2COA0                UCOAEN:使能自己的I2C地址。                I2COAx:自己的I2C地址2。   UCBxI2COA3 参考UCBxI2COA0                UCOAEN:使能自己的I2C地址。                I2COAx:自己的I2C地址3。 总的来说,就是MSP430的I2C模块可以设置4个Slave地址,可以产生不同的I2C中断。 UCBxADDRX               ADDRXx:只读寄存器,接收到数据对方的地址。 UCBxADDMASK                ADDMASKx:地址掩码,可以选择性的接受对象固定地址的设备发送来的数据。 UCBxI2CSA               I2CSAx:Slave设备的地址,注意,这个只用在MCU作为Master模式下,这一位用来设置你要发送的Slave设备他的地址。 UCBxIE                各种各样的中断使能位,这个和IFG中断标志位是匹配的,也就是使能相应的中断后,产生中断的情况下后,会置位相应的中断标志位,然后产生中断。具体每个中断时什么用,请看IFG寄存器的描述。 UCBxIFG               中断标志位寄存器,看起来好多吧,最常用的就是UCTXIFG0和UCRXIF0了,当然一些NACK,STT,STP也会用到,具体内容看下面这四张图:分别表示在Slave模式下T/R和Master下T/R,什么时候会将这些标志位置位(7bit地址模式,10bit模式的类似,请直接擦看官方的user guide)   这四张图解释的很清楚,在I2C数据包那一块会触发什么中断,这样用户可以很灵活的使用这些中断标志位和中断入口。 UCBxIV 中断向量表,可以和中断标志位匹配。 好啦,寄存器内容讲解完成后,接下来就是如何使用的环节了,直接上程序吧(MS430FR2355): 其实寄存器很多,但是真正常用的寄存器很少,其他的直接默认就行了,因此不用担心看完了记不住,不重要的,看下下面程序,哪些不懂得地方直接回去找就好啦,User guide就像一个字典,你不需要去背,需要的是在遇到问题是,如何去查找就行。 Master模式+Slave模式:(单个地址,Slave在接收到Master数据后,发送从0开始的递增数据)两个程序可以下载到两个MCU中,实现两个MCU的I2C通信验证。 Maser 程序: #include <msp430.h> volatile unsigned char RXData; int main(void) {     WDTCTL = WDTPW | WDTHOLD;     // Configure GPIO     P1OUT &= ~BIT0;                         // Clear P1.0 output latch     P1DIR |= BIT0;                          // For LED     P1SEL0 |= BIT2 | BIT3;                  // I2C pins     // Disable the GPIO power-on default high-impedance mode to activate     // previously configured port settings     PM5CTL0 &= ~LOCKLPM5;     // Configure USCI_B0 for I2C mode     UCB0CTLW0 |= UCSWRST;                   // Software reset enabled     UCB0CTLW0 |= UCMODE_3 | UCMST | UCSYNC; // I2C mode, Master mode, sync     UCB0CTLW1 |= UCASTP_2;                  // Automatic stop generated                                             // after UCB0TBCNT is reached     UCB0BRW = 0x0008;                       // baudrate = SMCLK / 8     UCB0TBCNT = 0x0005;                     // number of bytes to be received     UCB0I2CSA = 0x0048;                     // Slave address     UCB0CTL1 &= ~UCSWRST;     UCB0IE |= UCRXIE | UCNACKIE | UCBCNTIE;     while (1)     {         __delay_cycles(2000);         while (UCB0CTL1 & UCTXSTP);         // Ensure stop condition got sent         UCB0CTL1 |= UCTXSTT;                // I2C start condition         __bis_SR_register(LPM0_bits|GIE);   // Enter LPM0 w/ interrupt     } } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = USCI_B0_VECTOR __interrupt void USCIB0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void) #else #error Compiler not supported! #endif {   switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))   {     case USCI_NONE: break;                  // Vector 0: No interrupts     case USCI_I2C_UCALIFG: break;           // Vector 2: ALIFG     case USCI_I2C_UCNACKIFG:                // Vector 4: NACKIFG       UCB0CTL1 |= UCTXSTT;                  // I2C start condition       break;     case USCI_I2C_UCSTTIFG: break;          // Vector 6: STTIFG     case USCI_I2C_UCSTPIFG: break;          // Vector 8: STPIFG     case USCI_I2C_UCRXIFG3: break;          // Vector 10: RXIFG3     case USCI_I2C_UCTXIFG3: break;          // Vector 14: TXIFG3     case USCI_I2C_UCRXIFG2: break;          // Vector 16: RXIFG2     case USCI_I2C_UCTXIFG2: break;          // Vector 18: TXIFG2     case USCI_I2C_UCRXIFG1: break;          // Vector 20: RXIFG1     case USCI_I2C_UCTXIFG1: break;          // Vector 22: TXIFG1     case USCI_I2C_UCRXIFG0:                 // Vector 24: RXIFG0       RXData = UCB0RXBUF;                   // Get RX data       __bic_SR_register_on_exit(LPM0_bits); // Exit LPM0       break;     case USCI_I2C_UCTXIFG0: break;          // Vector 26: TXIFG0     case USCI_I2C_UCBCNTIFG:                // Vector 28: BCNTIFG       P1OUT ^= BIT0;                        // Toggle LED on P1.0       break;     case USCI_I2C_UCCLTOIFG: break;         // Vector 30: clock low timeout     case USCI_I2C_UCBIT9IFG: break;         // Vector 32: 9th bit     default: break;   } } Slave程序: #include <msp430.h> volatile unsigned char TXData; int main(void) {   WDTCTL = WDTPW | WDTHOLD;   // Configure GPIO   P1SEL0 |= BIT2 | BIT3;                    // I2C pins   // Disable the GPIO power-on default high-impedance mode to activate   // previously configured port settings   PM5CTL0 &= ~LOCKLPM5;   // Configure USCI_B0 for I2C mode   UCB0CTLW0 = UCSWRST;                      // Software reset enabled   UCB0CTLW0 |= UCMODE_3 | UCSYNC;           // I2C mode, sync mode   UCB0I2COA0 = 0x48 | UCOAEN;               // own address is 0x48 + enable   UCB0CTLW0 &= ~UCSWRST;                    // clear reset register   UCB0IE |= UCTXIE0 | UCSTPIE;              // transmit,stop interrupt enable     __bis_SR_register(LPM0_bits | GIE);     // Enter LPM0 w/ interrupts     __no_operation(); } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = USCI_B0_VECTOR __interrupt void USCIB0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void) #else #error Compiler not supported! #endif {   switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))   {     case USCI_NONE: break;                  // Vector 0: No interrupts     case USCI_I2C_UCALIFG: break;           // Vector 2: ALIFG     case USCI_I2C_UCNACKIFG: break;         // Vector 4: NACKIFG     case USCI_I2C_UCSTTIFG: break;          // Vector 6: STTIFG     case USCI_I2C_UCSTPIFG:                 // Vector 8: STPIFG       TXData = 0;       UCB0IFG &= ~UCSTPIFG;                 // Clear stop condition int flag       break;     case USCI_I2C_UCRXIFG3: break;          // Vector 10: RXIFG3     case USCI_I2C_UCTXIFG3: break;          // Vector 14: TXIFG3     case USCI_I2C_UCRXIFG2: break;          // Vector 16: RXIFG2     case USCI_I2C_UCTXIFG2: break;          // Vector 18: TXIFG2     case USCI_I2C_UCRXIFG1: break;          // Vector 20: RXIFG1     case USCI_I2C_UCTXIFG1: break;          // Vector 22: TXIFG1     case USCI_I2C_UCRXIFG0: break;          // Vector 24: RXIFG0     case USCI_I2C_UCTXIFG0:        UCB0TXBUF = TXData++;        break;                               // Vector 26: TXIFG0     case USCI_I2C_UCBCNTIFG: break;         // Vector 28: BCNTIFG     case USCI_I2C_UCCLTOIFG: break;         // Vector 30: clock low timeout     case USCI_I2C_UCBIT9IFG: break;         // Vector 32: 9th bit     default: break;   } } Master模式+Slave模式:(单个地址,Slave在接收到Master数据后,接收到的数据)两个程序可以下载到两个MCU中,实现两个MCU的I2C通信验证。这个程序里一个Slave可以设置四个地址,产生四个中断,这个也是MSP430 I2C模块的特点。 Maser 程序: #include <msp430.h> unsigned char TXData[]= {0xA1,0xB1,0xC1,0xD1};        // Pointer to TX data unsigned char SlaveAddress[]= {0x0A,0x0B,0x0C,0x0D}; unsigned char TXByteCtr; unsigned char SlaveFlag = 0; int main(void) {     WDTCTL = WDTPW | WDTHOLD;                         // Stop watchdog timer     // Configure Pins for I2C     P1SEL0 |= BIT2 | BIT3;                            // I2C pins     // Disable the GPIO power-on default high-impedance mode     // to activate previously configured port settings     PM5CTL0 &= ~LOCKLPM5;     // Configure USCI_B0 for I2C mode     UCB0CTLW0 |= UCSWRST;                             // put eUSCI_B in reset state     UCB0CTLW0 |= UCMODE_3 | UCMST;                    // I2C master mode, SMCLK     UCB0BRW = 0x8;                                    // baudrate = SMCLK / 8     UCB0CTLW0 &=~ UCSWRST;                            // clear reset register     UCB0IE |= UCTXIE0 | UCNACKIE;                     // transmit and NACK interrupt enable     SlaveFlag =0;     while(1)     {     __delay_cycles(1000);                             // Delay between transmissions     UCB0I2CSA = SlaveAddress[SlaveFlag];              // configure slave address     TXByteCtr = 1;                                    // Load TX byte counter     while (UCB0CTLW0 & UCTXSTP);                      // Ensure stop condition got sent     UCB0CTLW0 |= UCTR | UCTXSTT;                      // I2C TX, start condition     __bis_SR_register(LPM0_bits | GIE);               // Enter LPM0 w/ interrupts                                                       // Remain in LPM0 until all data                                                       // is TX'd     // Change Slave address     SlaveFlag++;     if (SlaveFlag>3)                                  // Roll over slave address       {         SlaveFlag =0;       }     } } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = USCI_B0_VECTOR __interrupt void USCIB0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void) #else #error Compiler not supported! #endif {   switch(__even_in_range(UCB0IV,USCI_I2C_UCBIT9IFG))   {         case USCI_NONE: break;                        // Vector 0: No interrupts break;         case USCI_I2C_UCALIFG: break;         case USCI_I2C_UCNACKIFG:             UCB0CTL1 |= UCTXSTT;                      //resend start if NACK           break;                                      // Vector 4: NACKIFG break;         case USCI_I2C_UCSTTIFG: break;                // Vector 6: STTIFG break;         case USCI_I2C_UCSTPIFG: break;                // Vector 8: STPIFG break;         case USCI_I2C_UCRXIFG3: break;                // Vector 10: RXIFG3 break;         case USCI_I2C_UCTXIFG3: break;                // Vector 14: TXIFG3 break;         case USCI_I2C_UCRXIFG2: break;                // Vector 16: RXIFG2 break;         case USCI_I2C_UCTXIFG2: break;                // Vector 18: TXIFG2 break;         case USCI_I2C_UCRXIFG1: break;                // Vector 20: RXIFG1 break;         case USCI_I2C_UCTXIFG1: break;                // Vector 22: TXIFG1 break;         case USCI_I2C_UCRXIFG0: break;                // Vector 24: RXIFG0 break;         case USCI_I2C_UCTXIFG0:         if (TXByteCtr)                                // Check TX byte counter            {             UCB0TXBUF = TXData[SlaveFlag];            // Load TX buffer             TXByteCtr--;                              // Decrement TX byte counter            }         else            {             UCB0CTLW0 |= UCTXSTP;                     // I2C stop condition             UCB0IFG &= ~UCTXIFG;                      // Clear USCI_B0 TX int flag             __bic_SR_register_on_exit(LPM0_bits);     // Exit LPM0            }           break;                                      // Vector 26: TXIFG0 break;         case USCI_I2C_UCBCNTIFG: break;               // Vector 28: BCNTIFG         case USCI_I2C_UCCLTOIFG: break;               // Vector 30: clock low timeout         case USCI_I2C_UCBIT9IFG: break;               // Vector 32: 9th bit         default: break;   } } Slave程序: #include <msp430.h> unsigned char RXData0=0; unsigned char RXData1=0; unsigned char RXData2=0; unsigned char RXData3=0; int main(void) {     WDTCTL = WDTPW | WDTHOLD;                             // Stop watchdog timer     // Configure Pins for I2C     P1SEL0 |= BIT2 | BIT3;                                // I2C pins     // Disable the GPIO power-on default high-impedance mode     // to activate previously configured port settings     PM5CTL0 &= ~LOCKLPM5;     // Configure USCI_B0 for I2C mode     UCB0CTLW0 |= UCSWRST;                                 //Software reset enabled     UCB0CTLW0 |= UCMODE_3;                                //I2C slave mode, SMCLK     UCB0I2COA0 = 0x0A | UCOAEN;                           //SLAVE0 own address is 0x0A| enable     UCB0I2COA1 = 0x0B | UCOAEN;                           //SLAVE1 own address is 0x0B| enable     UCB0I2COA2 = 0x0C | UCOAEN;                           //SLAVE2 own address is 0x0C| enable     UCB0I2COA3 = 0x0D | UCOAEN;                           //SLAVE3 own address is 0x0D| enable     UCB0CTLW0 &=~UCSWRST;                                 //clear reset register     UCB0IE |=  UCRXIE0 | UCRXIE1| UCRXIE2 | UCRXIE3;      //receive interrupt enable     __bis_SR_register(LPM0_bits | GIE);                   // Enter LPM0 w/ interrupts     __no_operation(); } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = USCI_B0_VECTOR __interrupt void USCIB0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void) #else #error Compiler not supported! #endif {    switch(__even_in_range(UCB0IV,USCI_I2C_UCBIT9IFG))     {       case USCI_NONE: break;                            // Vector 0: No interrupts break;       case USCI_I2C_UCALIFG: break;                     // Vector 2: ALIFG break;       case USCI_I2C_UCNACKIFG: break;                   // Vector 4: NACKIFG break;       case USCI_I2C_UCSTTIFG: break;                    // Vector 6: STTIFG break;       case USCI_I2C_UCSTPIFG: break;                    // Vector 8: STPIFG break;       case USCI_I2C_UCRXIFG3:                           // SLAVE3         RXData3 = UCB0RXBUF;         break;                                          // Vector 10: RXIFG3 break;       case USCI_I2C_UCTXIFG3: break;                    // Vector 14: TXIFG3 break;       case USCI_I2C_UCRXIFG2:                           // SLAVE2         RXData2 = UCB0RXBUF;         break;                                          // Vector 16: RXIFG2 break;       case USCI_I2C_UCTXIFG2: break;                    // Vector 18: TXIFG2 break;       case USCI_I2C_UCRXIFG1:                           // SLAVE1         RXData1 = UCB0RXBUF;         break;                                          // Vector 20: RXIFG1 break;       case USCI_I2C_UCTXIFG1: break;                    // Vector 22: TXIFG1 break;       case USCI_I2C_UCRXIFG0:                           // SLAVE0         RXData0 = UCB0RXBUF;                            // Get RX data         break;                                          // Vector 24: RXIFG0 break;       case USCI_I2C_UCTXIFG0: break;                    // Vector 26: TXIFG0 break;       case USCI_I2C_UCBCNTIFG: break;                   // Vector 28: BCNTIFG break;       case USCI_I2C_UCCLTOIFG: break;                   // Vector 30: clock low timeout break;       case USCI_I2C_UCBIT9IFG: break;                   // Vector 32: 9th bit break;       default: break;     } } 最后,:I2C通信仅仅是规定了协议格式 START STOP等,其实在和外设通信过程中,会在I2C基础上叠加一个特殊的东西,比如你去读EEPROM,那么I2C的数据段需要包括你想读的EEPROM地址,等数据,这个要看每个使用I2C协议的芯片的规定。 最后最后,如果你不想捣鼓这些I2C的东西,你就喜欢UART,那么看下面这个Application Note: Uart to I2c:   http://www.ib939.com/lit/an/slaa908/slaa908.pdf  

  • 发表了日志: TI - MCU - MSP430使用指南14 -&gt; I2C通信(eUSCI)

  • 回复了主题帖: TI - MCU - MSP430使用指南32 -> SAC智能模拟组合

    本帖最后由 骑IC看MCU 于 2020-1-17 10:18 编辑
    freebsder 发表于 2020-1-17 09:25 骑IC看MCU 发表于 2020-1-16 18:59 比较新地外设吧,主要面向的市场应该是烟雾检测,生物医学方向了。在 ...
    比较有代表性的器件: SAC(智能模拟组合):MSP430FR2355 / MSP-EXP430FR2355 http://www.3447733.com/359/tool/MSP-EXP430FR2355?keyMatch=MSP-EXP430FR2355&tisearch=Search-EN-everything&usecase=part-number USS(超声波):MSP430FR6043 / EVM430-FR6043 http://www.sg378.com/263/tool/EVM430-FR6043 CapTIvate(电容触摸):MSP430FR2633 / MSP-CAPT-FR2633 http://www.ib939.com/56/tool/MSP-CAPT-FR2633 SD24(24 bit ADC):MSP430AFE253 / MSP430I2040 LCD:MSP430FR4133 / MSP-EXP430FR4133 http://www.bnt.cao802.com/tool/MSP-EXP430FR4133?keyMatch=MSP-EXP430FR4133&tisearch=Search-EN-everything&usecase=part-number 这几个都比较有特点,当然还有其他的,等我写完这一系类后,总结一下

  • 2020-01-16
  • 回复了主题帖: TI - MCU - MSP430使用指南32 -> SAC智能模拟组合

    freebsder 发表于 2020-1-16 17:24 这个外设有点新哦,楼主研究很透彻的样子,仔细的学习了,谢谢分享!
    比较新地外设吧,主要面向的市场应该是烟雾检测,生物医学方向了。在检测微弱电流,光感这一块用起来还是挺方便的, 内部OPA地性能也差不多和LM358相当。 有问题一起来讨论

  • 发表了主题帖: TI - MCU - MSP430使用指南32 -> SAC智能模拟组合

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   SAC(Smart Analog Combo),即智能模拟组合,这个IP核出来的时间并不长,目前只用在了两个片子上:MSP430FR2353和MSP430FR2355。首先看一下SAC内部是个什么结构呢: 从上图可以看到,内部本质上就是一个OPA放大器,同时在运放的+和-的输入端有个选择开关,可以根据客户的需求搭建不同的电路,同时内部还集成一个12 bit的DAC模块,用于在一些使用环境下提供固定的电压,可以实现电压偏移的目的。 针对上述这个内部结构,我们进行细致的分析: 对于+输入端,有三个可以选择的通道: 00:引脚接入   01:12 bit DAC信号   10:内部Amp的输出,也就是其他SAC模块的输出,用于多级Amp的组合。 对于-输入端,同样也有三个可以选择的通道: 00:引脚接入(也可以选择本Amp的输出构成反馈电路,也可以直接接地) 01:可编程的增益 10:内部Amp的输出,也就是其他SAC模块的输出,用于多级Amp的组合。 那么如何使用呢,主要用在哪些方面呢: 总的一句话来说,只要是使用Amp的电路设计上,都能用到这个模块的资源,在聚焦一点的话,就是微小电流的检测,比如生物科技中的血糖仪,血氧仪,脉搏检测器等。同时也可以用于驱动一定功率的红外或者led。 下面讲述有哪几种常用的方法即连接方式: General-Purpose Mode (通用模式) 如上图所示的通用模式,OA的正端和负端均选中位外部引脚作为输入,同时输出端不反馈,因此可直接作为一个OA使用,用户可以自己搭建外围电路。 Buffer Mode(缓冲器模式) 如上这种电路拓扑结构,是电压跟随器,电压缓冲器的连接方式(跟随器和缓冲器有一定区别,不细说),在用户要求一些输入阻抗高,输出阻抗低的情况下可以使用。   Inverting  Mode(反转模式)   反转模式,如上所示,这种电路结构应该是用户使用最多的电路了,负端作为输入,正端可以提供一个需要的偏执电压,具体公式不细说,比较简单了资料也很多。 对于正端提供的偏执电压呢,有两种方式:内部12 bitDAC和直接引入外部的电压。即位如上所示的两种结构。   Noninverting  Mode(同相模式) 这个电路拓扑是同相模式,输入端从正端输入,负端和输出通过可调节的增益相连接构成一个反馈电路。   SAC内部本质上就是运放结构,同时在输入及输出端搭配了多个选择开关,从而用户在使用过程中可以免去外部的电阻,电容等电路,节省板载空间,也可以加速用户设计。同时内部的SAC模块可以级联,进行多级放大,更便于使用。那么如何设置内部的电路结构呢?当然是操作寄存器! 下面详细讲解一下相关的寄存器设置并给出参考程序。   先整体看一下寄存器的内容,SAC模块包括6个寄存器,如下:   SACxOA   SACEN : SAC模块的使能位,1时使能所有的SAC模块,失能情况下输出位高阻状态。 OAPM : OA的模式选择,也就是可以是高速或低速的,当然高速或者低速会影响功耗,带宽,速度等,具体参数可以参考芯片的datasheet,如下图所示位FR2355的数据。 OAEN :内部OA使能,这一位用于控制SAC内部的OA模块的使能。 NMUXEN : 这一位控制着OA负端输入的使能,看内部结构框图可以看出,在OA负端输入前有两级:第一级时MUX,也就是选择输入源,第二季是使能MUX,也就是说即使选择了MUX的输入源,这个也必须使能,否则MUX与OA的负输入端会断开。 NSEL : 负端输入选择位。 PMUXEN : 这一位和负端的NMUXEN类似,控制着正端的MUX与OA的连接或断开。 PSEL : 正端输入选择位。   SACxPGA GAIN : OA增益选择,也就是在上面讲述的图中的可调增益的设置位,有三位,配合NSEL PSEL MSEL可以实现多种模式下的增益选择。 MSEL : PGA模式的选择,控制的是如下图中的内部电路结构: 综上可以发现,电路拓扑即增益等参数与MSEL PSEL NSEL GAIN有关系,具体这些参数怎么设置呢? 下图给了一些参数的示例,在使用过程中可以根据下述资料进行寄存器的设置。   SACxDAC   SACxDAC寄存器显而易见是用来控制SAC内部那个12bit DAC模块的,那么内部这个DAC模块的电路结构是什么样子的呢? 那么我们对着这张DAC内部的结构图就可以很容易的看懂这个寄存器里每一位的含义了。 DACSREF : 这一位决定着DAC的参考电压,有两个选项:primary和secondary,每一项的参考源在芯片的datasheet中可以查看到,如下图是FR2355的信息。   DACLSEL : 从上面结构上可以看到,这一位决定着设置的DAC值是否进入DAC CORE,当设置的数据进入DAC CORE后,则会驱动相应电压输出,也就是说这一位决定着DAC内核电压改变的频率,可以是实时的,也可以是脉冲控制的。 DACDMAE : DAC内部DMA请求使能,当DAC数据更新时,如果此位使能的情况下,则会有一个DMA的请求。 DACIE : DAC中断的使能信号,在DAC数据更新时可以产生一个中断信号给CPU。 DACEN : DAC使能位。   SACxDAT 这个寄存器是用于设置DAC电压值的,DAC的输出电压值是多少呢? 根据下面这个公式进行计算:   SACxDACSTS   这个寄存器是DAC的状态标志寄存器,目前只有0位用于DACIFG,其他均是预留的。 DACIFG是DAC数据更新的标志位。   SACxIV DAC中断向量寄存器,只读寄存器,和DAC内部数据更新产生的中断有关系。当产生中断时,会触发中断向量处的程序,进而可以执行用户的中断服务程序。   好啦,在描述完SAC的所有寄存器后,提供几个例程以供大家参考使用(MSP430FR2355): General Purpose Mode: #include <msp430.h> int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watch dog timer   P1SEL0 |= BIT1 + BIT2 + BIT3;             // Select P1.1 P1.2 P1.3 OA function   P1SEL1 |= BIT1 + BIT2 + BIT3;             // Select P1.1 P1.2 P1.3 OA function   SAC0OA |= NMUXEN + PMUXEN;                // Enable negative and positive input   SAC0OA |= OAPM;                           // Select low speed and low power mode   SAC0OA |= SACEN + OAEN;                   // Enable SAC and OA   __bis_SR_register(LPM3_bits);             // Enter LPM3 } Buffer Mode: #include <msp430.h> int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watch dog timer   P1SEL0 |= BIT1 + BIT3;                    // Select P1.1 P1.3 as external OA function   P1SEL1 |= BIT1 + BIT3;                    // Select P1.1 P1.3 as external OA function   SAC0OA |= NMUXEN + PMUXEN + NSEL_1 + OAPM;// Enable negative and positive input                                             // Select PGA source as OA negative input                                             // Select low speed and low power mode   SAC0PGA |= MSEL_1;                        // Set as Unity-Gain Buffer Mode   SAC0OA |= SACEN + OAEN;                   // Enable SAC and OA   __bis_SR_register(LPM3_bits);             // Enter LPM3 } Inverting  Mode: #include <msp430.h> int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watch dog timer   // Disable the GPIO power-on default high-impedance mode   // to activate previously configured port settings   PM5CTL0 &= ~LOCKLPM5;   // Configure reference module   PMMCTL0_H = PMMPW_H;                    // Unlock the PMM registers   PMMCTL2 = INTREFEN | REFVSEL_0;         // Enable internal 1.5V reference   while(!(PMMCTL2 & REFGENRDY));          // Poll till internal reference settles   P1SEL0 |= BIT1 | BIT2 | BIT3;             // Select P1.1 P1.2 P1.3 OA function   P1SEL1 |= BIT1 | BIT2 | BIT3;             // Select P1.1 P1.2 P1.3 OA function   SAC0DAC = DACSREF_1;                       // Select 1.5V int Vref as DAC reference   SAC0DAT = 0x0FFF;                         // Set SAC DAC data to 1.5V   SAC0DAC |= DACEN;                         // Enable DAC   SAC0OA = NMUXEN + PMUXEN + PSEL_1 + NSEL_1;//Select positive and negative pin input   SAC0OA |= OAPM;                           // Select low speed and low power mode   SAC0PGA = GAIN2 + MSEL_0;                 // Set inverting PGA mode with Gain=8   SAC0OA |= SACEN + OAEN;                   // Enable SAC and OA   __bis_SR_register(LPM3_bits);             // Enter LPM3 } Non-inverting  Mode: #include <msp430.h> int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watch dog timer   P1SEL0 |= BIT1 + BIT2 + BIT3;             // Select P1.1 P1.2 P1.3 OA function   P1SEL1 |= BIT1 + BIT2 + BIT3;             // Select P1.1 P1.2 P1.3 OA function   SAC0OA = NMUXEN + PMUXEN + PSEL_0 + NSEL_1;//Select positive and negative pin input   SAC0OA |= OAPM;                           // Select low speed and low power mode   SAC0PGA = GAIN0 + GAIN2 + MSEL_2;         // Set Non-inverting PGA mode with Gain=17   SAC0OA |= SACEN + OAEN;                   // Enable SAC and OA   __bis_SR_register(LPM3_bits);             // Enter LPM3 } DAC Buffer Mode : 就是把DAC数据输出,将SAC当作DAC使用 #include <msp430.h> unsigned int DAC_data=0; int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watch dog timer   P1SEL0 |= BIT1;                           // Select P1.1 as OA0O function   P1SEL1 |= BIT1;                           // OA is used as buffer for DAC   PM5CTL0 &= ~LOCKLPM5;                // Disable the GPIO power-on default high-impedance mode                                        // to activate previously configured port settings   // Configure reference module   PMMCTL0_H = PMMPW_H;                      // Unlock the PMM registers   PMMCTL2 = INTREFEN | REFVSEL_2;           // Enable internal 2.5V reference   while(!(PMMCTL2 & REFGENRDY));            // Poll till internal reference settles   SAC0DAC = DACSREF_1 + DACLSEL_2 + DACIE;  // Select int Vref as DAC reference   SAC0DAT = DAC_data;                       // Initial DAC data   SAC0DAC |= DACEN;                         // Enable DAC   SAC0OA = NMUXEN + PMUXEN + PSEL_1 + NSEL_1;//Select positive and negative pin input   SAC0OA |= OAPM;                            // Select low speed and low power mode   SAC0PGA = MSEL_1;                          // Set OA as buffer mode   SAC0OA |= SACEN + OAEN;                    // Enable SAC and OA   // Use TB2.1 as DAC hardware trigger   TB2CCR0 = 100-1;                           // PWM Period/2   TB2CCTL1 = OUTMOD_6;                       // TBCCR1 toggle/set   TB2CCR1 = 50;                              // TBCCR1 PWM duty cycle   TB2CTL = TBSSEL__SMCLK | MC_1 | TBCLR;     // SMCLK, up mode, clear TBR   __bis_SR_register(LPM3_bits + GIE);        // Enter LPM3, Enable Interrupt } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = SAC0_SAC2_VECTOR __interrupt void SAC0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(SAC0_SAC2_VECTOR))) SAC0_ISR (void) #else #error Compiler not supported! #endif {   switch(__even_in_range(SAC0IV,SACIV_4))   {     case SACIV_0: break;     case SACIV_2: break;     case SACIV_4:         DAC_data++;         DAC_data &= 0xFFF;         SAC0DAT = DAC_data;                 // DAC12 output positive ramp         break;     default: break;   } } SAC0 + SAC2 级联模式: #include <msp430.h> unsigned int adcResult; int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watchdog timer   P1DIR |= BIT0;                            // Select P1.0 as output   P1OUT &= ~BIT0;                           // Set P1.0 output low   PM5CTL0 &= ~LOCKLPM5;                  // Disable the GPIO power-on default high-impedance mode                                          // to activate previously configured port settings   //Select P1.1 P1.2 P1.3 as SAC0 function   //Select P3.1 P3.2 P3.3 as SAC2 function   P1SEL0 |= BIT1 + BIT2 + BIT3;   P1SEL1 |= BIT1 + BIT2 + BIT3;   P3SEL0 |= BIT1 + BIT2 + BIT3;   P3SEL1 |= BIT1 + BIT2 + BIT3;   SAC2OA = NMUXEN + PMUXEN + PSEL_0 + NSEL_1;//Select positive and negative pin input   SAC2OA |= OAPM;                           // Select low speed and low power mode   SAC2PGA = GAIN1 + MSEL_2;                 // Set Non-inverting PGA mode with Gain=3   SAC2OA |= SACEN + OAEN;                   // Enable SAC2 and OA   SAC0OA = NMUXEN + PMUXEN + PSEL_2 + NSEL_1;//Select Pair OA source   SAC0OA |= OAPM;                           // Select low speed and low power mode   SAC0PGA = GAIN0 + GAIN1 + MSEL_2;         // Set Non-inverting PGA mode with Gain=5   SAC0OA |= SACEN + OAEN;                   // Enable SAC0 and OA   // Configure ADC12   ADCCTL0 &= ~ADCENC;                       // Disable ADC   ADCCTL0 = ADCSHT_2 | ADCON;               // ADCON, S&H=16 ADC clks   ADCCTL1 = ADCSHP;                         // ADCCLK = MODOSC; sampling timer   ADCCTL2 = ADCRES_2;                       // 12-bit conversion results   ADCIE = ADCIE0;                           // Enable ADC conv complete interrupt   ADCMCTL0 = ADCINCH_1 | ADCSREF_0;         // A1 ADC input select = OA0 output                                             // Vref = DVCC   while(1)   {       ADCCTL0 |= ADCENC | ADCSC;          // Sampling and conversion start       __bis_SR_register(LPM0_bits | GIE); // Enter LPM0, ADC_ISR will force exit       __no_operation();                   // For debug only       if (adcResult > 2047)                // OA output > 1/2 VCC           P1OUT |= BIT0;                  // Set P1.0 LED on       else           P1OUT &= ~BIT0;                 // Clear P1.0 LED off   } } // ADC interrupt service routine #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector=ADC_VECTOR __interrupt void ADC_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(ADC_VECTOR))) ADC_ISR (void) #else #error Compiler not supported! #endif {   switch(__even_in_range(ADCIV, ADCIV_ADCIFG))   {       case ADCIV_NONE:           break;       case ADCIV_ADCOVIFG:           break;       case ADCIV_ADCTOVIFG:           break;       case ADCIV_ADCHIIFG:           break;       case ADCIV_ADCLOIFG:           break;       case ADCIV_ADCINIFG:           break;       case ADCIV_ADCIFG:           adcResult = ADCMEM0;            // Read ADC memory           __bic_SR_register_on_exit(LPM0_bits);// Exit from LPM           break;       default:           break;   } } SAC0 + SAC2 + eCOMP0 级联+比较器模式(SAC可以直接连接到内部比较器上) 比较器模块的使用教程,请参考我所写的其他帖子。 #include <msp430.h> unsigned int adcResult; int main(void) {   WDTCTL = WDTPW + WDTHOLD;                 // Stop watchdog timer   P1DIR |= BIT5;                            // Select P1.5 as output   P1OUT &= ~BIT5;                           // Set P1.5 output low   PM5CTL0 &= ~LOCKLPM5;                  // Disable the GPIO power-on default high-impedance mode                                          // to activate previously configured port settings   // Configure reference module   PMMCTL0_H = PMMPW_H;                    // Unlock the PMM registers   PMMCTL2 = INTREFEN | REFVSEL_0;         // Enable internal 1.5V reference   while(!(PMMCTL2 & REFGENRDY));          // Poll till internal reference settles   //Select P1.1 P1.2 P1.3 as SAC0 function   //Select P3.1 P3.2 P3.3 as SAC2 function   P1SEL0 |= BIT1 + BIT2 + BIT3;   P1SEL1 |= BIT1 + BIT2 + BIT3;   P3SEL0 |= BIT1 + BIT2 + BIT3;   P3SEL1 |= BIT1 + BIT2 + BIT3;   SAC0OA = NMUXEN + PMUXEN + PSEL_0 + NSEL_1;//Select positive and negative pin input   SAC0OA |= OAPM;                           // Select low speed and low power mode   SAC0PGA = GAIN0 + GAIN1 + MSEL_2;         // Set Non-inverting PGA mode with Gain=5   SAC0OA |= SACEN + OAEN;                   // Enable SAC0 and OA0   SAC2DAC = DACSREF_1;                      // Select 1.5V internal Vref as SAC2 DAC reference   SAC2DAT = 0xAAA;                          // Set SAC2 DAC output = 1V   SAC2DAC |= DACEN;                         // Enable SAC2 DAC   SAC2OA = NMUXEN + PMUXEN + PSEL_1 + NSEL_1;//Select positive and negative pin input   SAC2OA |= OAPM;                           // Select low speed and low power mode   SAC2PGA = MSEL_1;                         // Set OA as buffer mode   SAC2OA |= SACEN + OAEN;                   // Enable SAC2 and OA2   // Setup eCOMP   CP0CTL0 = CPPSEL_5 | CPNSEL_5;             // Select SAC0 and SAC2 output as eCOMP0 input   CP0CTL0 |= CPPEN | CPNEN;                  // Enable eCOMP input   CP0CTL1 |= CPIIE | CPIE;                   // Enable eCOMP dual edge interrupt   CP0CTL1 |= CPEN | CPMSEL;                  // Turn on eCOMP, in low power mode   __bis_SR_register(LPM3_bits | GIE);       // Enter LPM3   __no_operation();                         // For debug } // eCOMP interrupt service routine #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector=ECOMP0_ECOMP1_VECTOR __interrupt void ECOMP0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(ECOMP0_ECOMP1_VECTOR))) ECOMP0_ISR (void) #else #error Compiler not supported! #endif {     switch(__even_in_range(CP0IV, CPIV__CPIIFG))     {         case CPIV__NONE:             break;         case CPIV__CPIFG:             P1OUT |= BIT5;                  // OA0+ input > 200mV             break;         case CPIV__CPIIFG:             P1OUT &= ~BIT5;                 // OA0+ input < 200mV             break;         default:             break;     } }   OK,到这里SAC的所有内容就讲解完了,基本常用的程序例程也已经提供,我就不进行细致的程序讲解了,使用程序过程中对应着上述的寄存器描述就能比较容易地看懂程序地配置。

  • 发表了日志: TI - MCU - MSP430使用指南32 -&gt; SAC智能模拟组合

  • 2020-01-02
  • 回复了主题帖: TI - MCU - MSP430使用指南5 -> ICC中断控制器

    middfat 发表于 2020-1-2 06:34 为什么没有第3和第四呢?
    正在写 序号我已经编好了 我在实际写的过程中没有按照1 2 3 4 来写 总共有34篇左右 后面我会尽快更新 欢迎一起来讨论 提提建议

  • 2019-12-30
  • 发表了日志: TI - MCU - MSP430使用指南13 -&gt; Timer定时器模块

  • 发表了主题帖: TI - MCU - MSP430使用指南13 -> Timer定时器模块

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   定时器,作为MCU最重要也是最基本的功能集成在每一个MCU中。随着MCU功能的日渐强大,定时器的功能也越来越强大,因此配置和使用起来也就比较麻烦,下面我们针对MSP430的Timer模块进行详细讲解,配合多种可以直接使用的例程,方便用户直接移植和深入理解。 首先,普及一下定时器知识:本质上就是一个计数器,可以由用户自定义计数的值,同时到达计数值后可以执行相应的动作,因此可以时间周期性动作,采集捕捉动作等,用于实时控制及多种功能实现。 MSP430 MCU中有三种定时器:Timer_A   Timer_B   Timer_D. 首先 Timer_D定时器基本没有集成在MCU中,很少很少使用,是一个高分辨率的定时器,因此我们不做讲解(内部寄存器和使用与Timer_A/B很是相似,如果有用户用到,可以直接参考,如果有人需要可以留言,我会开贴再次单独讲解Timer_D)。 因此本次内容主要讲解一下Timer_A和Timer_B定时/计数器。首先先说一下MSP430 MCU内部Timer的主要功能: 16位定时/计数器,支持四种工作模式/计数模式:STOP  UP Continuous UP/DOWN 输入捕捉功能 输出比较功能,即PWM波功能 主要就这三种功能,用户在使用过程中也就是这三种功能,然后Timer有一些自己的特点: 时钟源可以选择 异步输入,输出所存 中断向量寄存器等 那么为什么MSP430会有两种定时器呢:A和B,有什么区别呢: B可以配置成8,10,12,16位模式,A只能用16位模式 B的CCRn寄存器是双缓冲,即Double-buffered,可以形成组 B的每一个输出口都可配置成高阻态 B中没有SCCI位 什么?不懂?没事,等我讲解完Timer所有功能后你就了解了,如果有疑问请接着往后看,最后在讲解完之后,我会重新详细分析一下Timer_A和Timer_B的区别。 首先先了解一下Timer_A的内部结构图,从下图可以看到,内部的定时器位数是16位,同时支持TAxCLK,ACLK,SMCLK,INCLK作为时钟源,后可以进行进一步的分频使用,同时拥有7个比较输出模块,可用于输出PWM波(注意 一个Timer只能输出6个PWM波,因为有一路计数器需要作为base使用:在可以调节PWM频率和占空比的情况下) 内部结构图决定的是模块的实现方式,因此可以清晰地看出实现的原理和寄存器中每个位的作用,因此最好养成观看内部结构图的习惯,这样有利于高效地调试自己的程序,当然现在刚开始看起来有点懵,不太理解也比较正常,但当学习完整个Timer后,对比着程序来看就会恍然大悟。 好啦,下面开始进入Timer地核心内容区域:使用模式。 简单来说就是如何使用它,怎么使用,有几种使用方法。对于MSP430 Timer_A定时器来说,总共有四种工作模式,如下图所示,由寄存器TAxCLT中MC位决定,如下图所示: 模式0:默认模式,即Stop,此模式下定时器内部不会开始计数,处于停止状态。 模式1:Up模式,此模式下定时器从0开始计数,一直计数到TAxCCR0寄存器中地值为止产生中断(如果使能了中断)并重新回到0,再次开始计数,如下图所示:               在使用这种模式下,中断在什么时候产生呢?这时候就要弄清楚一个问题:Timer_A模块  有两种中断:TAxIFG(定时器中断)和TAxCCRx(捕捉/比较中断),TAxIFG时定时器的基本中断,一个定时器模块只有一个,就是当内部计数到设定值后就会产生,TAxCCRx时输入/比较中断,其实内部就一个定时器在计数,但是有不同通道的几个值可以设定,也就是TAxCCRx寄存器,因此当相对应的通道设定了值之后,则会触发相应的中断。 触发中断的时间如下图所示: TAxIFG是在计数完CCR0后会产生的,TAxCCR0中断时在计数完CCR0-1后产生的,两者相差一个时钟位。不过在使用过程中,基本可以忽略了,只是在理论上了解一下更好。 那么还有一个问题:当前的计数值是在寄存器TAxR(看内部结构框图可以发现)中的,TAxCCR0只是设定一个值,那么当模式从Stop开始到Up模式时,TAxR的值不一定为0,那么设定就会出现两个情况: 当设定CCR0值时,TAxR值小于CCR0的值,那么定时器从当前值(TAxR)开始计数,计数到CCR0值后产生中断,回到0,从新开始计数。。。。。 当设定CCR0值时,TAxR值大于CCR0的值,那么计数器会直接回到0,从0开始计数。 因此这个时候就会出现两种情况,然而你在设定时如果不去检查TAxR的值,那么可能对于第一个周期,就会出现不同的结果。为了避免这个情况,建议在设定CCR0值的时候,将MC设置成Stop模式(TI建议),其实这时候不仅仅应该设置成Stop模式,最重要的是要将TACLR设置成1(注意这个位是TAxR清除位,直接设置成1后会自动清0,去读的话 也都是0)。 模式2:Continuous模式,此模式下定时器从0开始计数,一直计数到0xFFFF为止产生中断(如果使能了中断)并重新回到0,和Up模式地区别就是Up是计数到TAxCCR0(用户自己设定)值,而Continuous模式是直接计数到0xFFFF,这个值是固定的,不能设置,这样的话,定时器溢出的之间只能由计数频率决定了。如下图所示: 这种模式下,仅有TAxIFG中断会用到,因为没有设定CCR寄存器,也不需要这个寄存器,产生中断的时刻如下图所示: 在完成0xFFFF计数后才会产生中断。那么当工作在Continuous模式下 CCRx就不能使用了吗? NO,也可以使用,而且可以根据客户的需求来定义不同占空比 不同形状的波形,如下图所示,使用两个CCRx时可以产生的中断(加上TAxIFG中断会更多,灵活使用): 上图可以看到,在CCR0和CCR1处都会产生中断,同时TAxIFG也是存在的。这种模式下在设定CCRx值时就仅有一种情况了,因为TAxR一直在计数,因此设定CCRx值时并不影响计数,只是看什么时候会产证中断而已,如果当前计数到10000,你设定10001,那么下一个时钟就会中断,如果当前10000,你设定9999,那么需要等下一次TAxR值等于9999时才能产生中断了,与Up模式不同的时,设定CCRx的值,不会改变或影响TAxR的值。 模式3:Up/down模式,这个模式下和Up比较类似,唯一的区别是Up模式下计数到TAxCCR0时会产生中断并再次从0开始,但是Up/down模式下则不会从0开始,而是从现在值开始往下计数(减计数),如下图所示: 触发中断的时间如下图所示: TAxIFG会在计数到0(计完1)后触发,CCIFG会在计数完CCR0-1后触发。 那么改变CCRx值时是什么状况呢? 当设定CCR0值时,TAxR值小于CCR0的值,那么定时器从当前值(TAxR)开始计数,不影响TAxR的值。 当设定CCR0值时,TAxR值大于CCR0的值,那么计数器会立刻开始递减,进行减计数,减到0后再开始递增计数,最后正常工作。 因此此时设定CCRx值时,推荐和Up模式下保持一致。 上图显示的是在Up/down模式下CCRx使用方式,当然 这个也是PWM的输出方法,由CCR0通道作为base计数,用于调节PWM的频率,CCRx则用来调节占空比,而且还支持多种输出模式。这个模式的区别,后面会详细讲解。 到此处位置,Timer_A的四种工作模式,其实就三种(除去Stop)已经全部讲解完成,本质上也就是每个定时器模块内有一个计数器,一直在计数,然后有一些CCRx寄存器,可用于用户设定相应的值,当计数到这个值时产生相应的中断,去执行相应的操作。那么如何使用描述的这些功能呢? 我认为需要了解五块内容,会和你的使用息息相关: 中断方式,寄存器,输入捕捉,输出比较(PWM),例程。 下面按照这种方法来进行讲解(比较乱,但是我认为这种方式比较合理,先搞清楚定时器原理及模式,在进一步细看寄存器,中断实现方式,在面向使用去了解输入捕捉和输出比较,最后进行例程的学习)。 Timer_A定时器的中断 在细看完上面的讲解后,你可能会有个印象,Timer_A模块有两种中断:TAxIFG和TAxCCRx中断,那么中断向量如何呢?(中断向量是中断的入口) 如果拆开看有8个中断:TAxIFG,CCR0IFG-CCR6IFG,每个都分配一个中断向量? 太浪费了吧! 因此MSP430只分配了两个中断向量: TAxCCR0自己单独一个中断向量,名字叫做:TIMER0_A0 (TimerA0),这个不需要记,去看工程下的cmd文件即可。 剩下的TAxIFG和TAxCCRx共同享用一个中断向量:TIMER0_A1 (TimerA0),因此你会发现每个定时器仅有两个中断向量,也就够使用了,那么如何用呢? 对于CCR0中断: // Timer0_A0 interrupt service routine #pragma vector = TIMER0_A0_VECTOR __interrupt void Timer0_A0_ISR (void) {   P1OUT ^= BIT0; }   对于其他中断,进入中断后需要进行中断类型判断: // Timer0_A1 Interrupt Vector (TAIV) handler #pragma vector=TIMER0_A1_VECTOR __interrupt void TIMER0_A1_ISR(void) {   switch(__even_in_range(TA0IV, TA0IV_TAIFG))   {     case TA0IV_NONE:   break;               // No interrupt     case TA0IV_TACCR1: break;               // CCR1 not used     case TA0IV_TACCR2: break;               // CCR2 not used     case TA0IV_TACCR3: break;               // reserved     case TA0IV_TACCR4: break;               // reserved     case TA0IV_TACCR5: break;               // reserved     case TA0IV_TACCR6: break;               // reserved     case TA0IV_TAIFG:                       // overflow       P1OUT ^= BIT0;       break;     default: break;   } } 注意:并不是所有的Timer_A都有6个CCR寄存器,具体有几个看每个芯片的datasheet。 Timer_A寄存器 先看一下总共有多少寄存器:18个寄存器,但是只有六类寄存器,其他都是相同的。 TAxCTL寄存器 TASSEL :  时钟源选择,如上图所示,有四种时钟源。 ID : 时钟源分频,对选中的时钟源进行分频后再输入Timer用于计数。 MC : 定时器模式选择,设定上述所讲的四种模式。 TACLR : 计数器清零位,写1会清除当前计数值,会自动复位。 TAIE : Timer_A中断使能位。 TAIFG : Timer_A中断标志位,即使中断不使能,当产生中断信号时,标志位也会置位。   TAxR寄存器 TAxR : 计数器当前计数的值,可以进行读写操作。   TAxCCTLn寄存器 CM : 捕捉边沿类型,这个用于捕捉外部的信号,可以选择上升沿,下降沿,上升/下降沿。 CCIS :  捕捉输入通道选择,可以选择成A, B, GND, VCC,每个通道对应的GPIO口不同,具体看datasheet。 SCS : 同步捕捉还是异步捕捉,是否将输入信号与时钟信号进行同步,看结构图可以看到内部就是使用了一个与门,可以实现时钟信号与输入信号的同步,这个根据客户需要进行选择,一般来说两种模式都可以,具体看客户使用场景,在学习是注意一下有这个点即可,当遇到问题时,如果可能是时序问题的话,查看一下这个。 SCCI : 同步输入信号加载紧EQU失能位选择,就是给EQU多一个输入源CCI。 CAP : 输入捕捉模式还是输出比较模式选择。 OUTMOD : 输出模式类型,总共有7中模式,也就是再不同中断情况下进行不同的操作,可能是置位,也可能是复位,或者反转等,很多模式都可以输出PWM波,用户自己看就可以。 给出一个再Up模式下不同模式的波形图: 模式1(001) :   CCR1中断时直接置位。 模式2(010)  :     CCR1中断时反转,CCR0中断时复位。 模式3(011)  :     CCR1中断时置位,CCR0中断时复位。 模式4(100)  :     CCR1中断时反转。 模式5(101)  :     CCR1中断时复位。 模式6 (110):      CCR1中断时反转,CCR0中断时置位。 模式7 (111):      CCR1中断时复位,CCR0中断时置位。 CCIE : 输入捕捉或输出比较使能。 CCI : 只读寄存器,可以读取被选中通道的点评状态。 OUT : 输出比较模式时初始输出状态。当OUTMOD=000时,就直接一致输出这个状态。 COV : 输入捕捉overflow产生,即溢出了,当输入的数大于0xFFFF时会溢出,这一位必须软件复位。注意,在计算输入捕捉到的脉冲数量时,需要检测这一位,否则会出现溢出情况导致捕捉到的脉冲是负数或者差一个0xFFFF。 CCIFG : 输入捕捉/输出比较中断标志位。   TAxCCRn寄存器 TAxCCRn : 输出比较时使设定值,输入捕捉时是当收到捕捉信号时定时器的值。   TAxIV寄存器 TAIV : 只读寄存器,中断类型标志位,用来识别是哪个中断源引起的中断(因为除了TAxCCR0,其他中断源共享一个中断向量)   TAxEX0寄存器 TAxIDEX : 输入时钟分频寄存器,和TAxCTL寄存器中ID位效果一致,用来对所选的时钟进行分频后再输入给Timer_A。 Timer_A输入捕捉 首先看,什么叫输入捕捉,用在什么地方:主要用于测量脉冲数量,监控外部脉冲等功能,其实最主要的就是连接增量式编码器来获取固定时间内的脉冲数量从而计算电机的转速,这个是最最常用的功能。 如下是MSP430 输入捕捉功能的工作流程图: 那么如何设定输入捕捉呢? 这个主要再TAxCCTLn寄存器,首先你需要将CAP设置成1,代表使用输入捕捉模式,其次根据你的硬件连接选择A或者B通道,然后设定同步位,捕捉类型等,最后依旧是要设定最基本的定时器功能,因为定时器要工作,开始计数嘛! 当然设置完这些后输入捕捉就可以工作了,但是捕捉到信号之后怎么办呢? 需要开中断嘛,这时进中断,将这个时刻计数器的值给记下来,用于脉冲频率等的测量。 后续如何使用此项功能,在后面会给出详细的Example Code. Timer_A输出比较 输出比较最常用的就是输出PWM波了,这个电机控制,LED控制等中非常常用,原理上前面已经描述清除,就是靠Base计数器和CCRx计数器,当计数到CCRx寄存器时会进行相应的动作,从而实现PWM功能的输出,通过CCR0调节PWM频率,CCRx调节占空比。 Timer_A例程 好啦,上面讲了一大堆最最主要的还是程序应该怎么写,怎么使用每个功能,下面给出几种常用的程序。(例程使用MSP430FR5969,别问为什么 主要因为刚好有这个板子。。。其实MSP430 内部Timer_A模块都是一样的,因此程序基本通用,可能仅仅有一些寄存器不一样或者功能有点区别而已,总的来说大同小异。) 基本定时功能(20ms中断) #include <msp430.h> int main(void) {   WDTCTL = WDTPW | WDTHOLD;                 // Stop WDT   // Configure GPIO   P1DIR |= BIT0;   P1OUT |= BIT0;   // Disable the GPIO power-on default high-impedance mode to activate   // previously configured port settings   PM5CTL0 &= ~LOCKLPM5;   TA0CCTL0 = CCIE;                          // TACCR0 interrupt enabled   TA0CCR0 = 20000;                          // SMCLK default 1MHz,                                      // Set 20000 : 20000/1000000 = 20ms   TA0CTL = TASSEL__SMCLK | MC__UP;          // SMCLK, UP mode   __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupt   __no_operation();                         // For debugger } // Timer0_A0 interrupt service routine #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = TIMER0_A0_VECTOR __interrupt void Timer0_A0_ISR (void) #elif defined(__GNUC__) void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) Timer0_A0_ISR (void) #else #error Compiler not supported! #endif {   P1OUT ^= BIT0; }   输入捕捉功能 (开始捕捉中断,捕捉到20个脉冲后反转P1.1口,同时记录下每个脉冲时刻的计数器的值) #include "msp430.h" #define NUMBER_TIMER_CAPTURES       20 volatile unsigned int timerAcaptureValues[NUMBER_TIMER_CAPTURES]; unsigned int timerAcapturePointer = 0; int main(void) {   WDTCTL = WDTPW | WDTHOLD;                 // Stop watchdog timer   // Configure GPIO   P1OUT &= ~0x01;                           // Clear P1.0 output   P1DIR |= 0x01;                            // Set P1.0 to output direction   // Disable the GPIO power-on default high-impedance mode to activate   // previously configured port settings   PM5CTL0 &= ~LOCKLPM5;   // Clock System Setup   CSCTL0_H = CSKEY >> 8;                    // Unlock CS registers   CSCTL2 &= ~SELA_7;   CSCTL2 |= SELA__VLOCLK;                   // Select ACLK=VLOCLK   CSCTL0_H = 0x00;                          // Lock CS module (use byte mode to upper byte)   __delay_cycles(1000);                     // Allow clock system to settle   // Timer0_A3 Setup   TA0CCTL2 = CM_1 | CCIS_1 | SCS | CAP | CCIE;                                             // Capture rising edge,                                             // Use CCI2B=ACLK,                                             // Synchronous capture,                                             // Enable capture mode,                                             // Enable capture interrupt   TA0CTL = TASSEL__SMCLK | MC__CONTINUOUS;  // Use SMCLK as clock source,                                             // Start timer in continuous mode   __bis_SR_register(LPM0_bits | GIE);   __no_operation(); } // Timer0_A3 CC1-4, TA Interrupt Handler #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector = TIMER0_A1_VECTOR __interrupt void Timer0_A1_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(TIMER0_A1_VECTOR))) Timer0_A1_ISR (void) #else #error Compiler not supported! #endif {   switch (__even_in_range(TA0IV, TA0IV_TAIFG)) {     case TA0IV_TA0CCR1:       break;     case TA0IV_TA0CCR2:       timerAcaptureValues[timerAcapturePointer++] = TA0CCR2;       if (timerAcapturePointer >= 20) {         while (1) {           P1OUT ^= 0x01;                    // Toggle P1.0 (LED)           __delay_cycles(100000);         }       }       break;     case TA0IV_TA0IFG:       break;     default:       break;   } }   输出比较功能(输出两路PWM波,频率1KHz,占空比分别为25%和75%) #include <msp430.h> int main(void) {   WDTCTL = WDTPW | WDTHOLD;                 // Stop WDT   // Configure GPIO   P1DIR |= BIT0 | BIT1;                     // P1.0 and P1.1 output   P1SEL0 |= BIT0 | BIT1;                    // P1.0 and P1.1 options select   // Disable the GPIO power-on default high-impedance mode to activate   // previously configured port settings   PM5CTL0 &= ~LOCKLPM5;   CSCTL0_H = CSKEY >> 8;                    // Unlock CS registers   CSCTL1 = DCOFSEL_6;                       // Set DCO = 8MHz   CSCTL2 = SELA__VLOCLK | SELS__DCOCLK | SELM__DCOCLK;// Set ACLK=VLO SMCLK=DCO   CSCTL3 = DIVA__8 | DIVS__8 | DIVM__8;     // Set all dividers   CSCTL0_H = 0;                             // Lock CS registers   // Configure Timer0_A   TA0CCR0 = 1000-1;                         // PWM Period 1KHz   TA0CCTL1 = OUTMOD_7;                      // CCR1 reset/set   TA0CCR1 = 750;                            // CCR1 PWM duty cycle   TA0CCTL2 = OUTMOD_7;                      // CCR2 reset/set   TA0CCR2 = 250;                            // CCR2 PWM duty cycle   TA0CTL = TASSEL__SMCLK | MC__UP | TACLR;  // SMCLK, up mode, clear TAR   __bis_SR_register(LPM0_bits);             // Enter LPM0   __no_operation();                         // For debugger }   下面看一下Timer_B,和Timer_A基本都没区别,功能,使用方法等,那么回归最初的问题,区别在那些地方呢: B可以配置成8,10,12,16位模式,A只能用16位模式 B的CCRn寄存器是双缓冲,即Double-buffered,可以形成组 B的每一个输出口都可配置成高阻态 B中没有SCCI位   先看一下结构图的区别:B可以成组,因此多了TBCLGRP,其次可以设置成8,10,12,16位模式,因此多了CNTL位,没有SCCI位,故CCI不能到EQU模块。 先说第一条:B可以设定成8,10,12,16位模式,也就是说计数的最大值不同,这个在TBxCTL中你会发现多了CNT位(12-11),用于选择定时器位数:   其次没有SCCI,因此也就没有这个设定位了。 最后就是Group,看下面寄存器截图: 这个功能就是在输出比较模式下,这几个通道可以同时成组的去更新数据,用于多个输出比较形成同步的功能。  

  • 2019-12-25
  • 回复了主题帖: TI - MCU - MSP430使用指南2 -> CPU/CPUX

    freebsder 发表于 2019-12-24 10:04 430架构也有些老了,得跟上时代,要不cortex太强势。
    确实内核比较老了 不过也比cortex-m系列简单点,上手也比较快。而且工业场合,在能满足要求的基础上还是有一定的市场份额的。

  • 回复了主题帖: TI - MCU - MSP430使用指南2 -> CPU/CPUX

    lcofjp 发表于 2019-12-23 18:02 期待全系列的,哈哈!
    正在慢慢写,会介绍整个MSP430系列的资源 下一次分享Timer 欢迎一起讨论

  • 回复了主题帖: TI - MCU - MSP430使用指南12 -> GPIO

    freebsder 发表于 2019-12-24 10:05 快速入门不错,希望楼主坚持写完了做一个pdf
    好嘞 谢谢建议,后续会把一系列pdf 附上

  • 2019-12-23
  • 发表了主题帖: TI - MCU - MSP430使用指南5 -> ICC中断控制器

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   ICC,即Interrupt Compare Controller,中断比较控制器,作用便是设定中断优先级,同时通过比较中断优先级等实现中断的硬件嵌套。 首先普及一下MSP430的中断系统,大部分的MSP430  MCU中断优先级是不能设置的,也就是说对于每一个中断源的优先级是体现设定好的,不支持后期用户自动设置,因此可以说如果正在执行低优先级的程序,突然来了一个高的优先级中断,是可以打断低优先级的服务程序的,但是前提是进入低优先级中断服务程序时开启了全局中断即GIE。但是如果高优先级中断正在执行服务程序,来了一个低优先级的中断,那么只能等待高优先级中断服务程序执行完之后,才可以去执行低优先级的中断服务程序,这样也就是说不能灵活实现中断的嵌套。 ICC模块就是为了解决这个问题而出现的,即可以通过寄存器配置,来实现低优先级的中断打断高优先级的中断,这就需要比较电路了,ICC内部就是这个原理,内部结构如下图所示: 目前到2019年低为止,MSP430 MCU(16bit)只有四款含有ICC模块:MSP430FR3153,MSP430FR2155, MSP430FR2353, MSP430FR2355。 下面再简单的描述一下中断嵌套的含义,如下图所示,ISR2优先级高于ISR1,那么在正在只从ISR1程序时,遇到ISR2时,则会先把ISR1暂停一下,去执行ISR2,当ISR2执行完之后,返回再接着执行ISR1。 而MCU时如何实现去执行另一个程序,执行完之后再返回原本的点执行程序的呢?如下图所示,这个就类似与中断向量即堆栈,分为Push和Pop,首先当正在执行ISR1时,遇到了ISR2中断,那么MCU会把ISR1当前执行的程序地址推入堆栈保存起来,然后把ISR2的中断向量填入PC指针,去执行ISR2的程序,当ISR2程序执行完后,再从堆栈中读出当时保存的ISR1执行的程序地址,然后接着执行。 入栈如下图所示:   出栈如下图所示: 下面介绍一下ICC相关的寄存器,比较简单,因为你会发现,核心的两个寄存器,只有一位是读写的,其他都是只读寄存器,先附上所有寄存器的信息: 从上图可以看到,寄存器可以归为三个:ICCSC,ICCMVS,ICCILSRx。 ICCSC,ICC控制寄存器 你会发现,这个寄存器里,只有一位ICCEN(使能ICC功能寄存器)是支持读写操作的,其他的均为只读位,VSEFLG是堆栈标志位,可以通过观察此位的值来查看是否产生了ICC中断嵌套,因为出现嵌套的话,肯定会存在Push和Pop操作。 VSFLG位(这个User guide中标错了,应该是VSFLG而不是VSEFLG),这个是标注着ICCMVS寄存器中的MVSSP是否满了,也就是堆栈是否存满了。 ICMC则是当前比较的中断的设置优先级。(注意:这里的设置优先级不是默认的那个什么59 60之类的数,是通过ICCILSRx寄存器设定的,可以设定四个优先级,因为只有两位,具体见ICCILSRx寄存器配置)。 ICCMVS  可屏蔽中断堆栈寄存器 这个寄存器里面所有的位均为制度寄存器,其实就是存储着:当前堆栈了几个中断,然后ICM0-ICM3是比较位,也就是当前参与中断比较的这四位分别是谁而已。不需要用户设置,在调试中可能会使用。 ICCILSRx  中断优先级寄存器 ICCILSRx中断优先级寄存器有8个,每个寄存器里可以配置8个中断源,因此总共可以配置64个中断源,每个中断源对应那个资源的中断请查看每个芯片的datasheet,如下图所示是MSP430FR2355的中断源对应表: OK,在功能和寄存器都描述完之后,我们来看一下使用过程中需要注意哪些点,或者说我们应该怎么正确的使用ICC资源。 下图是在Main函数中使用ICC的流程:   需要注意的是ICC使能和GIE使能的顺序即失能的顺序,即可以理解为,在使能或者失能ICC中断时,均要处于全局中断失能的状态下(即GIE=0),如下图所示,图中Disable  ICC处有点错误,我已经标明:   另外为了保证ICC功能的稳定正常运行,强烈建议用户在进入中断服务程序后使能GIE中断,因为在执行一个中断服务程序时,需要打开整体的中断使能,这样才能允许其他中断产生,从而才不会阻挡中断嵌套的功能,流程如下图所示:   最后附上使能ICC和失能ICC的程序: ICC使能 Enable ICC Code Example: //---------------------------------------------------------------------------------------- // Initializing and Enabling ICC //---------------------------------------------------------------------------------------- void ICC_init (void) { // disable global interrupt __bic_SR_register(GIE); // setting customized interrupt priorities. ICCILSR0 = 0xFFFF; ICCILSR1 = 0xFFFF; ICCILSR2 = 0xFFFF; ICCILSR3 = 0xFFFC; // enable ICC module ICCSC |= ICCEN; // enable global interrupt __bis_SR_register(GIE); }   ICC失能 //---------------------------------------------------------------------------------------- // Disabling ICC //---------------------------------------------------------------------------------------- void ICC_disable (void) { // disable global interrupt __bic_SR_register(GIE); // enable ICC module ICCSC &= ~ICCEN; // enable global interrupt __bis_SR_register(GIE); }  

  • 发表了日志: TI - MCU - MSP430使用指南5 -&gt; ICC中断控制器

  • 发表了日志: TI - MCU - MSP430使用指南2 -&gt; CPU/CPUX

  • 发表了主题帖: TI - MCU - MSP430使用指南2 -> CPU/CPUX

    本帖最后由 骑IC看MCU 于 2019-12-23 13:49 编辑 此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   首先,先说明一下CPU位数代表的是什么意思,从市面上看8位,16位,32位甚至64位的MCU内核都已出现,位数指的是什么呢: 严谨说法:指CPU一次能处理的数据宽度,也就是参与运算的寄存器的数据长度。 MSP430 是16位的MCU 我们平时对MCU的C语言编程,都会经过编译器编译成机器码,也就是二进制,最后通过CPU内部寄存器(一般有20-100个)来实现运算数据,因此我们写的所有程序,实现的所有功能,最终都要由CPU内部寄存器的读写来完成。 CPU寄存器一般分为专用寄存器和通用寄存器,对于MSP430来说,总共有16个寄存器:R0 - R15, 其中四个(R0 – R3)为专用寄存器,其余的为通用寄存器,下面详细讲解一下CPU的内部结构和寄存器的功能。 1. CPU和CPUX 多看MSP430的User Guide你会发现,对于CPU介绍的章节,有些是CPU,有些则是CPUX,那么这两个有什么区别呢? 为什么要出现CPUX呢: 我们来计算一下: 首先,16位的寄存器,也就是说我们由于需要寻址去执行相应的程序,即我们最多只有16跟地址总线,那么寻址范围呢:216=64K,每个地址可以存储1B的数据,因此CPU的最大寻址空间为64KB,这也就是CPU内核的MCU内部最大的Flash或FRAM空间了,但是如果用户程序很复杂呢,算法量很多呢,或者需要在内部Flash或FRAM中存储大量数据呢,必定会造成一个问题,内存不够,这就很尴尬,基于此,TI才开发了CPU的升级版CPUX内核,地址总线为20为,那么最大可以支持1MB的存储,这就可以解决这个问题了。 什么?1MB还不够?程序占有量没那么大的,1MB的内存基本满足90%以上的要求了,这是MCU,MCU,不是高端CPU! 2.CPU/CPUX的结构图:   上侧是CPU的内部结构,下侧为CPUX的结构图,从图上看,唯一的区别就是寄存器位数和地址总线了,因此,这两个CPU内核是一致的,仅仅区别在地址总线上而已。 PS: 不论是ARM内核,还是AVR内核,MSP430内核等,基本原理都大同小异,包含的CPU寄存器也很相似,不需要一致迷恋ARM内核好等,理性来说,适合自己项目的最好,ARM内核性能高,但内部也比较复杂,MSP430内核则比较简单,操作起来更便利,因此在很多场合下,MSP430内核更好理解和上手。 好啦,内核结构基本就这样,什么?What’s the fuck? 看不懂? 没事呀,作为硬件工程师甚至是嵌入式软件工程师,不需要理解,就知道它就这样子,内部就是一堆寄存器和运算器就OK了,我们也很少会接触到它。   3.CPU/CPUX内部寄存器: 首先介绍一下MSP430 CPU/CPUX的主要特征: 丰富的寄存器资源 单周期操作寄存器 16位数据总线 16位/20位地址总线 常数发生器 直接存储器到存储器的访问 位,字节和字的操作方式。 寄存器列表:   寄存器 描述 R0 程序计数器PC,指示下一条程序将要执行的指令地址。 R1 堆栈指针SP,指向堆栈栈顶 R2 状态寄存器SR/常数发生器CG1 R3 常数发生器CG2 R4 通用寄存器 … ………………… R15 通用寄存器   下面以CPU(16位地址线)为例讲解一下每个寄存器的作用: R0寄存器:PC 程序计数器 CPU中最核心的寄存器,指示下一条即将执行指令的地址。 PC寄存器的内容总是偶数,指向偶字节地址。程序计数器PC可以像其他寄存器一样用所有指令和所有寻址方式访问,但对程序存储器的访问必须以字为单位,否则会清除高位字节。PC的宽度决定了可以直接寻址的范围。16bits -> 64KB, 20bits -> 1MB。 汇编Code示例: MOV #LABLE, PC              ;跳转到地址LABLE开始执行 MOV LABLE, PC                ;程序开始执行的地址为LABLE所在内存中的数 MOV @R14, PC                ; 程序开始执行的地址为寄存器R14中的数   R1寄存器:SP 堆栈指针 堆栈指针的所用主要是保护当前程序执行的地址,当正在执行一段程序,突然进来了一个中断,因此需要把当前执行程序的地址保护起来,将中断服务程序的矢量地址写入PC寄存器,开始执行中断服务程序,当中断服务程序执行完后,再将堆栈中的数据赋给PC寄存器,返回到原本程序位置接着执行。同时堆栈再函数调用期间也可以保存寄存器变量,局部变量参数等。 堆栈指针SP总是指向堆栈的顶部,系统在将数据压入堆栈时,总是先将堆栈指针SP的值减2,然后再将数据送到SP所指的RAM单元中。将数据从堆栈中弹出正好与压入过程相反;先将数据从SP所指示的内存单元取出,再将SP值加2。 汇编Code示例: MOV 2(SP), R6                ;将内存单元I2中的数据放到R6中。 MOV R7, 0(SP)                ;将R7中的数据放到堆栈顶所在的单元(I3)中。 PUSH #0123h                  ;将SP的值减2,再将#0123h放到SP所指向的单元中。 POP R8                             ;将SP所指单元中的数(#0123h)放到R8中,再将SP的值加2。 实现功能的图如下: R2寄存器:SR 状态寄存器 状态寄存器指示CPU目前工作遇到的一些零标志,负标志等,同时可以控制CPU的中断功能,具体功能见下表: 0 C 进位标志。当运算结果产生进位时C置位,否则C复位。 1 Z 零标志。当运算结果为零时Z置位,否则Z复位。 2 N 负标志。当运算结果为负时N置位,否则N复位。 3 GIE 中断控制位。控制可屏蔽中断,当GIE置位CPU可响应可屏蔽中断,否则不可响应可屏蔽中断。 4 CPUOFF 置位CPUOFF位可使CPU进入关闭模式,可用所有允许的中断将CPU唤醒。 5 OSCOFF 置位OSCOFF位可使晶体振荡器处于停滞状态,同时CPUOFF也需要置位。可用外部中断或者NMI唤醒。 6 SCG0 SCG0置位关闭SMCLK,与SCG1一起控制系统时钟发生器的四种状态。 7 SCG1 SCG1置位关闭DCO发生器,与SCG0一起控制系统时钟发生器的4中状态。 8 V 溢出标志。当运算结果超出有符号数范围时置位。溢出情况如下: 正数 + 正数 = 负数 负数 + 负数 = 正数 正数 - 负数 = 负数 负数 - 正数 = 正数 9~15   保留未用   R3寄存器:常数发生器 常熟发生器用于产生一些常用的常数,而不必占用一个16 bits的字。所用常数的数值由寻址位As来定义,硬件完全自动地产生数字:-1,0,1,2,4,8   具体如下: 寄存器 As 常数 说明 R2 00 - 寄存器模式 R2 01 (0) 绝对寻址模式 R2 10 00004H +4位处理 R2 11 00008H +8位处理 R3 00 00000H 0字处理 R3 01 00001H +1 R3 10 00002H +2位处理 R3 11 0FFFFH -1字处理   R4~R15寄存器:通用寄存器,可用于自由的读写操作。 汇编Code示例: MOV #1234H, R15         ;执行后R15内容为1234H MOV.B #23H, R15          ;执行后R15内容为0023H ADD.B #34H, R15           ;执行后R15内容为0057H   4.CPU/CPUX存储结构: 特点:MSP430 MCU采用“冯-诺依曼”结构,物理上完全分离的存储区域,如ROM,FLASH,FRAM,RAM外围模块,特殊功能寄存器SFR等,被安排在同一地址空间,这样就可以使用一组地址、数据总线、相同的指令访问。   中断向量被安排再相同的空间:0FFE0~0FFFFH; 8位,16位外围模块占用相同范围的存储器地址; 所有器件的特殊功能寄存器占用相同范围的存储器地址:00H~0FH; 数据存储器开始于相同的地址,即从0200H处开始; 代码存储器的最高地址都是0FFFFH.   5.编程时内联函数: 再使用CCS或IAR编程过程中,你可能会发现,有一些函数类似于库里面的,找不到定义,这些函数是内联函数,具有一些特殊功能,在使用过程中可以减少自己的开发时间。 MSP430总共有42个内联函数,这里列出几种常用的,并描述其功能: __no_operation(); 仅仅是空操作而已,类似于NOP(); 调试使用,可以定位到程序运行的位置。 __delay_cycles(10); 延时函数,参数指的是MCLK时钟周期,比如:MCLK  1MHz,那么参数写1000,则大约延时1ms。 __bis_SR_register(); 针对寄存器中单独的某一位进行置位,常用的如: __bis_SR_register(SCG0);                 // disable FLL __bis_SR_register(LPM3_bits|GIE);         // Enter LPM3, interrupts enabled   __bic_SR_register(); 针对寄存器中单独的某一位进行复位,常用的如: __bic_SR_register(SCG0);                 // enable FLL __bic_SR_register_on_exit(); 针对寄存器中单独的某一位进行复位同时退出到main中,常用的如: __bic_SR_register_on_exit(LPM0_bits + GIE);  // Exit LPM0 on return to main 针其他内联函数平时很少使用,有些也不太需要很深入的了解函数的含义,具体内容,可以参见MSP430 Optimizing C C++ Compiler.pdf文件。 连接如下:http://www.ib939.com/lit/ug/slau132u/slau132u.pdf?keyMatch=MSP430%20OPTIMIZING%20C%20C%20COMPILER&tisearch=Search-EN-everything  

  • 发表了日志: TI - MCU - MSP430使用指南8 -&gt; WDT看门狗模块

  • 发表了主题帖: TI - MCU - MSP430使用指南8 -> WDT看门狗模块

    此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处   看门狗WDT电路在平时调试过程中使用并不多,但在真正产品应用上,可以说每一个系统都会使用看门狗,其主要功能和作用如下: 看门狗电路基本功能是在发生软件问题和程序跑飞后使系统重新启动。看门狗计数器正常工作时自动计数,程序流程定期将其复位清零,如果系统在某处卡死或跑飞,该定时器将溢出,并将进入中断。在定时器中断中执行一些复位操作。 使系统恢复正常的工作状态,即在程序没有正常运行期间,如期复位看门狗以保证所选择的定时溢出归零,使处理器重新启动。看门狗电路的定时时间长短可由具体应用程序的循环周期决定,通常比系统正常工作时最大循环周期的时间略长即可。 MSP430  MCU内部直接集成了看门狗模块,可以通过用户的设置进行对程序是否跑死进行监控,同时可以通过寄存器配置成两种模式,使得客户使用起来更加方便,看门狗电路内部结构图如下图所示: 由上图结构图中可以看出,MSP430   MCU内部会提供一个PUC信号,用于清除看门狗计数器,这也就是外部看门狗所描述的喂狗操作,当程序跑死后,这个信号没有产生,因此当看门狗定时器定时时间到时,则会产生一个复位信号,使得系统重新启动。同时看门狗计数器的时钟源可以由用户进行选择。 MSP430看门狗比较简单,可以配置成两种模式:看门狗模式和间隔时间模式。这两种模式的描述及区别如下: 看门狗模式:和硬件看门狗功能一致,在设置好看门狗计数时间后,如果程序跑死情况出现,在设定的时间过后就会触发看门狗中断,同时软件复位,程序指针PC会指向复位向量,程序实现软件复位。注意:看门狗复位顺序是:程序跑死 -> 触发看门狗中断 -> 执行中断服务程序(保存数据等操作,这个由用户决定) -> 复位程序。 间隔时间模式:这个和定时器类似,不过时间不是很精确,在看门狗计数器达到设定时间后,会触发看门狗中断,但不会让软件重启,同时间隔时间模式是不存在喂狗操作的,类似于定时器,每隔设定的时间周期,都会触发看门狗中断,因此可以当作定时器使用。 下面讲解一下看门狗的寄存器,这个比较简单,看门狗仅有一个寄存器,如下图所示: WDTCLY寄存器: WDTPW:首先看这个寄存器的高八位,是WDT的密码,在操作这个寄存器时,必须同时输入密码(5Ah),否则写任何数字都会导致看门狗复位程序。(注意,是同事写入不是先写密码,再操作寄存器) 同时这个高八位密码寄存器的读取结果一致都是69h。 WDTHOLD:看门狗定时器的使能,0停止此定时器,1使能此定时器。 WDTSSEL:看门狗定时器时钟源选择,默认是SMCLK,X_CLK时钟尽在部分MSP430 MCU中存在。 WDTTMSEL:模式选择,0选择看门狗模式,1选择间隔时间模式。 WDTCNTCL:看门狗定时器清除位,写1会清除看门狗定时器当前的值,在此定时器达到定时时间后会自动清除。 WDTIS:定时时间设置,在这里有八个定时时间可选,起始这个就是对定时器的设定值进行设定,看门狗计数器是个32位寄存器,最大数为232,但由于看门狗并不要求精确时间,因此设置成8段,提供八个时间足够了,如上图所示计算时间,这个跟时钟源有关系,如图如果时钟源频率是32768Hz,WDTIS = 100b,那么也就是设置的计数值是215=32768,因此定时时间为1秒。 同时,细心的话你会发现,如果正常使用看门狗功能的话,还缺少两个寄存器:中断使能寄存器和中断标志位寄存器。这两个参数WDTIE和WDEIFG分别在SFRIE1.0和SFRIFG1.0寄存器中,如下图所示: WDTIE(仅有1bit)   WDTIE在SFRIE1寄存器的0bit上,默认是0,即失能看门狗中断,注意:就算失能看门狗终端,如果看门狗定时器设置在看门狗模式下,在程序跑飞后,依旧会复位程序,知识不产生中断了而已,因此如果不适用看门狗定时器,请失能定时器,用下面程序:   WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT   WDTIFG(仅有1bit)   WDTIFG在SFRIFG1寄存器的0bit上,默认是0,即没有中断产证,可以手动清除,当然,看门狗定时器在进入中断后会自动清除此标志位。   好啦,讲解完原理和寄存器之后,依旧给出看门狗的程序: Code 1: 间隔时间模式,toggle LED: //****************************************************************************** #include <msp430.h> int main(void) {     WDTCTL = WDT_MDLY_32;                   // WDT 32ms, SMCLK, interval timer     P1OUT &= ~BIT0;                       // Clear P1.0 output latch for a defined power-on state     P1DIR |= BIT0;                          // Set P1.0 to output direction     PM5CTL0 &= ~LOCKLPM5;                // Disable the GPIO power-on default high-impedance mode                                             // to activate previously configured port settings     SFRIE1 |= WDTIE;                        // Enable WDT interrupt     __bis_SR_register(LPM0_bits | GIE);     // Enter LPM0, enable interrupts     __no_operation();                       // For debug } // Watchdog Timer interrupt service routine #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector=WDT_VECTOR __interrupt void WDT_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(WDT_VECTOR))) WDT_ISR (void) #else #error Compiler not supported! #endif {     P1OUT ^= BIT0;                          // Toggle P1.0 (LED) } Code 2: 看门狗模式,1秒(使用过程中,看门狗时间需要用户根据自身程序去设定): //****************************************************************************** #include <msp430.h> int main(void) {     // Selects the ACLK source to REFO (internal 32kHz clock source) as default     WDTCTL = WDT_ARST_1000;                 // Set Watchdog Timer timeout 1s     P1DIR |= BIT0;                          // Set P1.0 to output direction     // Disable the GPIO power-on default high-impedance mode     // to activate previously configured port settings     PM5CTL0 &= ~LOCKLPM5;     P1OUT ^= BIT0;                          // Toggle P1.0     __bis_SR_register(LPM3_bits | GIE);     // Enter LPM3     return 0; }  

最近访客

< 1/1 >

统计信息

已有11人来访过

  • 芯币:73
  • 好友:2
  • 主题:11
  • 回复:6
  • 课时:--
  • 资源:--

留言

你需要登录后才可以留言 登录 | 永利游戏现金直营


现在还没有留言

申博太阳城戏码0.8%登入2007 优发娱乐网站 迅盈比分 澳门偷筹码案件网上娱乐场 24小时在线娱乐注册
mg地球生物 银河百家乐游戏中心