本帖最后由 电子缘科技 于 2014-7-2 15:22 编辑
MSP430F249编程教程分享--连载
针对于MSP430F249单片机,设计了一款Tiny型的学习板,在这里主要是与大家一起学习MSP430单片机,例程写了30多个这样。从最简单的IO操作到整个系统的综合,并且在SD卡的文件管理上移植了著名的Fatfs微型文件系统,这些可以互相学习,有什么问题都可以留言讨论。
在上例程之前先讲解下MSP430单片机的时钟配置及IO配置
一:时钟配置
大家都知道MSP430是一款低功耗的单片机,超低功耗一直都是MSP430系列单片机的口号,为了适应各种功耗要求,比如在用电池供电的场合下,对于功耗是个严峻的挑战,所以MSP430单片机可以配置3种时钟振荡器,这3种时钟振荡器分别为:低频时钟源LFXT1CLK、高频时钟源XT2CLK、数字控制RC振荡器DCOCLK。其中DCOCLK是在单片机内部,实际上就是RC振荡器,并且可编程。在保持默认时,时钟频率大概在1MHz左右(这个是实际测试出来的),规格书有的讲在800KHz左右。在MSP430的时钟模块就有3个,分别为:辅助时钟ACLK 、主时钟MCLK 、子系统时钟SMCLK。
下面我来看这3个时钟模块的时钟源可以来自于哪些:
1. ACLK:LFXT1CLK信号经1/2/4/8分频后得到的,主要用作低速外围的时钟。
2. MCLK:LFXT1CLK,XT2CLK,DCOCLK的三者之一决定,由软件选择,然后经1/2/4/8分频后得到,主要用于CPU和系统。
3. SMCLK:LFXT1CLK和DCOCLK,或者XT2CLK与DCOCLK决定,然后经1/2/4/8分频后得到,主要用于高速外围模块,比如SPI模块等。
下面我们就看如何配置时钟:
MSP430的时钟模块由DCOCTL,BCSCTL1,BCSCTL2,IFG1这几个寄存器来确定,具体的功能如下所示:
1. DCOCTL:DCO Control Register,地址为56H,初始值为60H
BIT:DCO0~DCO2:定义了8种频率之一,而频率由注入直流发生器的电流定义
BIT:MOD0~MOD4:频率的微调
2. BCSCTL1:Basic Clock System Control Register 1,地址为58H,初始值为84H
BIT:XT2OFF:控制XT2振荡器的开启(XT2OFF=0)与关闭(XT2OFF=1)
BIT:XTS:选择LFXT1工作在低频晶体模式(XTS=0)还是高频晶体模式(XTS=1)
BIT:DIVA0~DIVA1:选择ACLK的分频系数。DIVA=0,1,2,3(DIVA_0,DIVA_1...),ACLK的分频系数分别为:1,2,4,8
BIT:RSEL2~RSEL0:选择某个内部电阻以决定标称频率(0最低,7最高)
3. BCSCTL2:Basic Clock System Control Register 2,地址为58H,初始值为00H
BIT:SELM0~SELM1:选择MCLK的时钟源,00或者01:DCOCLK;10:XT2CLK;11:LFXT1CLK
BIT:DIVM0~DIVM1:选择MCLK的分频因子,DIVM=0,1,2,3,对应MCLK的分频因子为1,2,4,8
BIT:SELS:选择SMCLK的时钟源,0:DCOCLK;1:XT2CLK/LFXTCLK
BIT:DIVS0~DIVS1:DIVS=0,1,2,3,对应SMCLK的分频因子为1,2,4,8
BIT:DCOR:0—选择内部电阻,1—选择外部电阻
4. IFG1:Interrupt Flag Register 1
BIT:OFIE:振荡器故障中断标志位 0:没有故障;1:有故障
详细的看了这几个寄存器后,感觉是不是还是搞不懂是吧。没关系很正常,下面我们就来看DCO是如何配置:
1.0 设置BCSCTL2寄存器中的DCOR来选择是外部电阻还是内部电阻,以确定一个基准频率。
2.0 通过BCSCTL1寄存器的RSELx来进行分频,确定时钟频率。
3.0 通过DCOCTL寄存器中DCOx在标称频率基础上分段粗调,选择频率。
4.0 通过DCOCTL寄存器中MODx的值对频率进行细调,选择DCOx与DCOx+1之间的频率。
例子:
DCOCTL初始值为60H,即DCOCTL |= DCO1 + DCO2;
DCOCTL |= DCO0 + DCO1 + DCO2;// Max DCO
//MOD0~MOD4:Modulation Bit,频率的微调一般保持默认即可
//系统默认情况下,RSELx=4
下面我们再来看如何配置外面高速时钟:
由于在PUC信号后,默认情况下由DCOCLK作MCLK与SMCLK的时钟信号,DCOCTL初始值为60H,频率比较低,所有需要高速运行的则要将MCLK的时钟源另外设置为LFXT1或者XT2,设置的一般顺序如下:
1.0 清OSCOFF/XT2
2.0 清OFIFG
3.0 延时等待至少50us
4.0 再次检查OFIFG,如果仍置位,则重复(1)~(4)步,直到OFIFG=0为止
5.0 设置BCSCTL2的相应SELM
例子:一般的初始化程序
void Clock_Init(void)
{
unsigned int i;
BCSCTL1=0x00;//XT2开启,LFXTCLK为低频模式,ACLK分频为0
do
{
IFG1&=~OFIFG;
for(i=0x20;i>0;i--);
}
while((IFG1&OFIFG)==OFIFG);//当OSCFault=1 即晶振不起振则等待
BCSCTL2=0X00;
BCSCTL2|=SELM1;//MCLK 时钟为XT2,
BCSCTL2|=SELS;//SMCLK时钟为XT2
}
这个例子我为什么说一般的初始化咧,有个位置就是在判断晶振状态的while语句,如果外部晶振的真的有问题那程序是不是就一直死在此地方?我就想问下,内部没有问题为何不切换到内部?虽然内部时钟比较慢,但是我系统好歹可以运行,所以在我写的程序里都加入了时钟状态判断时间,下面就是我用的初始化,当然这只是个用法,大家如果还有什么更好的方法可以贴出来,分享分享
//系统时钟初始化
//MCLK=16MHz
//SMCLK=8MHz
//ACLK=32.678KHz
void Clock_Init(void)
{
unsigned int time = 10000;//外部时钟在这个时间内如果还是不能起振则选择内部时钟
BCSCTL1 &= 0x00;//开启XT2振荡器,LFXT1工作在低频晶体模式,ACLK的分频系数0
do
{
IFG1 &= ~OFIFG;//清OFIFG标记
__delay_cycles(100);//在外部晶振还没有起振时,时钟来源于内部大概1MHz的DCO
}
while((IFG1 & OFIFG) && time--);//查询时钟切换成功
if(time == 0)//外部时钟有问题
{
BCSCTL1 |= XT2OFF;//关闭XT2振荡器
}
else//切换成功
{
BCSCTL2 &= 0x00;
BCSCTL2 |= SELM1+SELS+DIVS0;//MCLK与SMCLK的时钟源为XT2,SMCLK的分频系数2
}
}
时钟的配置完结。学习讨论Q群:167390222 2014年7月2日
二:IO口配置
MSP430F249有6组IO端口,即P1~P6,每一组IO端口都有8个可以独立编程的引脚。比如P1,有P1.0~P1.7。在MSP430所有的端口都有控制输入输出方向和进行输入、输出的能力。其中P1、P2 端口能够响应外部中断,大部分端口拥有第二功能。每一个端口都有PxDIR(信号方向)、PxIN(输入)、PxOUT(输出)3 个寄存器。拥有第二功能的端口会有PxSEL 寄存器,用来选择端口的第二功能。P1、P2 可以配置为输入信号上升沿或者下降沿触发中断,但固定的电平不会引起中断,中断所使用的寄存器为:PxIE(中断使能)、PxIES(中断触发沿)、PxIFG(中断标志)。还有一个寄存器PxREN,这个寄存器主要是配置单片机内部的上下拉电阻,注意,早期的单片机并没有内部上下拉电阻。下面就贴出P1口的寄存器:
1.0 PxDIR:信号方向控制寄存器,设置为1时,则该引脚的信号方向为输出;设置为0则为输入。在上电复位时初始值全部默认为输入方向。
2.0 PxIN:输入寄存器,在输入模式下,读取该寄存器的相应比特位来获取相应引脚上的数据。如果相应引脚输入的是高电平则读取到1,如果是低电平则为0。
3.0 PxOUT:输出寄存器,在输出模式下,如果该寄存器的相应比特位设置为1,则相应比特位的引脚输出高电平,如果设置为0则输出低电平。
4.0 PxSEL:功能选择寄存器,用于选择是普通数字IO还是作为外围模块的功能。在含有第二功能引脚上的相应比特位设置为1时则设置为外围模块功能,设置为0则为普通IO。
5.0 PxIE:中断使能寄存器,该寄存器控制P1,P2端口的中断使能。也就是说MSP430F249的外部中断输入引脚就有16个。在相应引脚的比特位设置为1则使能中断,设置为0则屏蔽中断。在系统复位时,其初始值全部为0,默认不允许中断。
6.0 PxIES:中断触发沿选择寄存器,此寄存器主要针对于P1,P2端口中断的触发方式选择。设置相应引脚的相应比特位为1时,则相应引脚的触发方式为下降沿,设置为0是则为上升沿。在系统复位时,其初始值全部为0,默认上升沿触发中断。
7.0 PxIFG:中断标志寄存器,读取该寄存器相应P1,P2端口的比特位,如果为1则说明在相应的引脚上有外部中断发生;如果为0则没有外部中断发生。
8.0 PxREN:内部上下拉电阻选择寄存器,设置相应引脚的比特位为1则开启上下拉电阻,为0则关闭内部上下拉电阻。针对于这个寄存器我们详情讲下:
上下拉是否开启由PxREN寄存器决定,而上拉还是下拉是由PxOUT寄存器决定。在作为输出时,随着PxOUT的高低,自动选择上下拉,这就是平时我们没有特别的去设置内部上下拉也可以输出高电平的原因。但是作为输入的时候,这个就要配置了,我们可以人为的赋PxOUT寄存器来得到上下拉,具体的工作原理看图:
、
对于寄存器的介绍就这么多,下面我们先看下如何进行位操作:
位操作指令主要是存在于早期速度并不高的CISC处理器上,比如8051;这个位操作主要是为了弥补CPU运算速度慢的不足,但目前几乎所有的RISC型处理器都取消了此指令,比如AVR,当然MSP430也不例外的取消了。那如何进行操作,请看下面程序例子:
比如要设置P1.0为高电平输出,P1.1为低电平输出,P1.2取反,读取P1.3的数据
P1OUT |= 0x01;//P.0输出高电平
P1OUT &= ~0x02;//P.1输出低电平
P1OUT ^= 0x04;//P.2取反
if((P1OUT & 0x08) == 0)//判断P1.3的是否为低电平
{
//执行代码
}
这个是不依赖头文件的编写,如果是在IAR的环境下编写,上面的例子可以这样写
P1OUT |= BIT0;//P.0输出高电平
P1OUT &= ~BIT1;//P.1输出低电平
P1OUT ^= BIT2;//P.2取反
if((P1OUT & BIT3) == 0)//判断P1.3的是否为低电平
{
//执行代码
}
这样是不是很简单,大家去看下头文件里的BIT0实际上就是0x01的一个宏定义,也就是BIT0~BIT7对应0x01~0x80。
还有一种比较流行的位操作写法:
P1OUT |= (1<<0);//P.0输出高电平
P1OUT &= ~(1<<1);//P.1输出低电平
P1OUT ^= (1<<2);//P.2取反
if((P1OUT & (1<<3)) == 0)//判断P1.3的是否为低电平
{
//执行代码
}
这种写法是不是比较熟悉,这种和第一种都是不依赖头文件,纯粹是C语言表达式。但本人还是比较喜欢第二种写法,比较直观。
IO口的具体配置请看后面的实例。
IO配置完结。学习讨论Q群:167390222 2014年7月2日
|