打印

MSP430F249编程教程分享--连载

[复制链接]
6695|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 电子缘科技 于 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. MCLKLFXT1CLKXT2CLKDCOCLK的三者之一决定,由软件选择,然后经1/2/4/8分频后得到,主要用于CPU和系统。
3. SMCLKLFXT1CLKDCOCLK,或者XT2CLKDCOCLK决定,然后经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日

相关帖子

沙发
电子缘科技|  楼主 | 2014-7-2 11:04 | 只看该作者

IO控制LED

本帖最后由 电子缘科技 于 2014-7-2 20:16 编辑

实例1---IO控制LED-----程序目的:学会如何配置IO口
硬件:MSP430F249+LED


LED灯的负极接至单片机P5.7口,正极接至3.3v电源,还串入270R电阻,在点亮LED时,起到分压分流作用,串入270R电阻,LED分到的电压大概2.7V,从而保护了LED灯。从原理图得出,只要我们P5.7输出低电平LED灯就可以点亮,输出高电平就熄灭。熄灭我们就看如何编程:
先建立个工程,对于IAR还不会使用的同学,请自己去查找些资料看看,在这里我就不赘述了。我现在所使用的是IAR 5.5版本,建立工程并且配置好选项后我们再单独建立led.c与led.h文件,注意学会模块化编程。先来看led.c文件,在led.c文件中我们写入一个led初始化:
//led底层驱动文件
#include <msp430f249.h>
#include "led.h"

//led灯控制IO初始化
void Led_Init(void)
{
        P5SEL &= ~BIT7;//设置P5.7IO口为普通I/O模式
        P5DIR |= BIT7;//设置P5.7IO口方向为输出
        P5OUT |= BIT7;//初始设置P5.7IO为高电平
}

//led底层驱动头文件

#ifndef __LED_H
#define __LED_H

#include "msp430type.h"

//IO口定义
#define LED_H  P5OUT |= BIT7//熄灭led灯
#define LED_L  P5OUT &= ~BIT7//点亮led灯
#define LED_HL  P5OUT ^= BIT7//翻转led灯状态

//函数声明
//led灯控制IO初始化
void Led_Init(void);

#endif

还有一个专门针对系统设置的点C文件,在这个点C文件里含有了时钟的初始化及看门狗的设置
//系统配置底层驱动文件
#include <msp430f249.h>
#include "system.h"
#include <intrinsics.h>

//系统时钟初始化
//MCLK=16MHz
//SMCLK=8MHz
//ACLK=32.678KHz
void Clock_Init(void)
{
        u16 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
        }               
}

//关闭狗初始化
void Wdt_Off(void)
{
   WDTCTL = WDTPW + WDTHOLD;//关闭看门狗
}

//打开看门狗
void Wdt_On(void)
{
        WDTCTL = WDT_ARST_1000;//看门狗模式,定时1000ms
}

再来我们就看主函数
#include <msp430f249.h>
#include "msp430type.h"
#include "system.h"
#include "delay.h"
#include "led.h"

//主函数
void main(void)
{
        Wdt_Off();//关闭看门狗
        Clock_Init();//系统时钟初始化
        Led_Init();//led灯控制IO初始化
        
        LED_L;//点亮led灯
        _BIS_SR(LPM4_bits);//SCG1+SCG0+OSCOFF+CPUOFF
}

控制上直接调用下面的底层函数,不推荐大家直接在main函数里些底层,主函数越简单明了越好,虽然就一个简单的点亮LED程序,但程序层次显得分明。
点亮LED课程完结。源程序: 001 led.rar (183.1 KB)     学习讨论Q群:167390222     2014年7月2日

实例2---IO口控制LED灯闪烁-----程序目的:学会如何实现软件延时与应用IAR软件自带延时
在上程序前我们先来了解下MSP430的时钟周期及机器周期,在MSP430单片机中,一个时钟周期 = MCLK晶振的倒数。如果MCLK是8MHz,则一个时钟周期为1/8us。一个机器周期 = 一个时钟周期,即MSP430每个动作都能完成一个基本操作。一个指令周期 = 1~6个机器周期,具体根据具体指令而定。就因为一个机器周期等于一个时钟周期,所以软件延时还是比较准确。举个例子,写个1ms的延时函数,我们调用个500次,延时基本上就是500ms。但是如果是51单片机,那就未必,特别是调用的函数延时越小,所产生的误差就越大,原因很简单,因为51的指令周期需要的时钟周期比较多,在调用函数时的过程也产生延时。下面我们来做些测试:
首先写个1ms的延时函数
时钟晶振为16MHz
void delay_ms(u16 ms)
{
        u16 i,j;
        for(i=ms; i>0; i--)
                for(j=4000; j>0; j--);
}
用逻辑分析捕抓了1ms函数,看图,有图有真相

基本上就是1ms。
下面我们调用500次,看是不是有500ms,还是比500ms多
while(1)
{
        LED_L;//点亮led灯
        delay_ms(500);
        LED_H;//熄灭led灯
        delay_ms(500);
}
看图

基本上就是500ms,是吧,还是很不错的。大伙用手上的51板子测试下,看有没有这个效果。

下面咱们就来看看利用IAR的延时
/* Insert a delay with a specific number of cycles. */
__intrinsic void __delay_cycles(unsigned long __cycles);
在使用这个延时时就要加入intrinsics.h这个头文件,别问我为什么,因为这延时就在这个头文件里。现在我看看如何使用这个延时,新建个点H文件,比如叫delay.h,我们就在这个文件里写如下代码:

//延时函数代码

#ifndef __DELAY_H
#define __DELAY_H
#include <intrinsics.h>

//IAR自带延时函数
#define CPU_F ((double)16000000)//晶振频率,如果是8MHz就写8000000
#define Delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define Delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))

#endif

就这么简单,然后你在你的其他代码里要用到延时,包含此头文件即可,然后就可以调用Delay_us(x)或者Delay_ms(x),注意延时超过1ms就不要调用Delay_us(x)这个函数。下面我们也来测试这个准不准,我们就调用200次这样:
while(1)
{
        LED_L;//点亮led灯
        Delay_ms(200);
        LED_H;//熄灭led灯
        Delay_ms(200);
}
看图

怎么样,这个精度惊人吧,199.9999ms。TI的品质再加IAR的牛X,有时候牛X不是用来吹的。
IO口控制LED灯闪烁课程完结。源程序: 002 led-flash.rar (184.02 KB)    学习讨论Q群:167390222     2014年7月2日

实例3---按键控制LED灯-----程序目的:学会如何配置IO为输入与打开上拉电阻
硬件:MSP430F249+轻触按键+LED

由原理图而知,KEY1接在P1.0上可设置为外部中断按键,KEY2接在P6.3为普通按键,另一端则接在地,也就是说按键为低电平触发,并且没有外加上拉电阻。这就意味我们要使用内部上拉电阻提供在按键释放时的高电平状态,下面我们具体来看看如何配置按键的IO。
我们单独编写个key.c与key.h文件,下面直接贴出代码:
//独立按键底层驱动文件

#include <msp430f249.h>
#include "delay.h"
#include "key.h"

//按键初始化
void Key_Init(void)
{
        P1SEL &= ~BIT0;//设置P1.0IO口为普通I/O模式
        P1DIR &= ~BIT0;//设置P1.0IO口方向为输入
        P1REN |= BIT0;//使能P1.0的内部电阻
        P1OUT |= BIT0;//设置P1.0内部上拉电阻为上拉方式
        P6SEL &= ~BIT3;//设置P6.3IO口为普通I/O模式
        P6DIR &= ~BIT3;//设置P6.3IO口方向为输入
        P6REN |= BIT3;//使能P6.3的内部电阻
        P6OUT |= BIT3;//设置P6.3内部上拉电阻为上拉方式
}

//按键扫描
u8 Kek_Scan(void)
{
        static u8 KeyUpFlag = 1;//按键按松开标志        
        if(KeyUpFlag && (!KEY1_I || !KEY2_I))
        {
                Delay_ms(10);//软件去抖
                KeyUpFlag = 0;
                if(!KEY1_I) return 1;
                if(!KEY2_I) return 2;
        }
        else if(KEY1_I && KEY2_I) KeyUpFlag = 1;
        return 0;
}
此按键扫描里有一个KeyUpFlag按键松开标记,主要是为了防止按键一直按住不放而影响外设运行。不推荐大家使用用while(KEY1_I ==0)这种语句判断按键释放,因为这样会死在while语句里,如果外设带有需要快速扫描设备时就被影响,比如是数码管,相信大伙都遇过这种现象,特别是初学者,很多时候都不会去释放CPU,对于一些延时建议多用定时器来做。

//独立按键底层驱动头文件

#ifndef __KEY_H
#define __KEY_H

#include "msp430type.h"

//IO口定义
#define KEY1_I  (P1IN & BIT0)//按键1输入
#define KEY2_I  (P6IN & BIT3)//按键2输入

//按键初始化
void Key_Init(void);
//按键扫描
u8 Kek_Scan(void);

#endif

再看就是主函数:
#include <msp430f249.h>
#include "msp430type.h"
#include "system.h"
#include "delay.h"
#include "led.h"
#include "key.h"

//主函数
void main(void)
{
        u8 Key = 0;
        
        Wdt_Off();//关闭看门狗
        Clock_Init();//系统时钟初始化
        Led_Init();//led灯初始化
        Key_Init();//按键初始化      
        while(1)
        {
                Key = Kek_Scan();//按键扫描
                switch(Key)
                {
                case 1:
                        LED_L;//点亮led灯
                        break;
                case 2:
                        LED_H;//熄灭led灯
                        break;
                default:
                        break;                        
                }
        }
}
主函数用到了switch开关语句,只要符合条件就进入,否则我连门都不让你进。实现功能很简单,按下按键1则点亮led灯,按键2就熄灭led灯。

按键输入控制LED灯课程完结。源程序:
003 key.rar (222.3 KB)    学习讨论Q群:167390222     2014年7月2日




使用特权

评论回复
板凳
电子缘科技|  楼主 | 2014-7-2 11:05 | 只看该作者

实例4

本帖最后由 电子缘科技 于 2014-7-5 11:31 编辑

实例4---外部中断控制LED灯-----程序目的:学会如何配置外部中断
硬件与实例3一致,仅KEY1有外部中断能力,单片机的P1与P2口具有第二功能就是外部中断,并且一个端口的所有IO共用一个中断向量,说白了就是一个中断入口,所以我们就需要判断中断源,到底来自于哪个IO口,我们只要判断中断标志位就可以了。需要注意就是在进入中断后应首先判断中断源,退出中断前应清除中断标志,否则将再次引发中断。下面我们直接看程序,程序有计较详细的注释,在这里就不再赘述。同样我们也新建exti.c与exti.h文件:
exti.c:
//外部中断P1口底层驱动文件

#include <msp430f249.h>
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"

//初始化外部中断P1.0
void ExtiP10_Init(void)
{
        P1SEL &= ~BIT0;//设置P1.0IO口为普通I/O模式
        P1DIR &= ~BIT0;//设置P1.0IO口方向为输入
        P1REN |= BIT0;//使能P1.0的内部电阻
        P1OUT |= BIT0;//设置P1.0内部上拉电阻为上拉方式
        P1IES |= BIT0;//触发方式为下降沿
        P1IE |= BIT0;//使能P1.0中断
        P1IFG &= ~BIT0;//清零P1.0中断标记
}

//P1口中断执行
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
        if((P1IFG & BIT0) == BIT0)//P1.0中断源-按键KEY1
        {
                P1IFG &= ~BIT0;//中断标志位清零
                Delay_ms(10);//软件消抖
                if(!KEY1_I) LED_HL;//翻转led灯状态
        }               
}

exti.h:
//外部中断P1口底层驱动头文件

#ifndef __EXTI_H
#define __EXTI_H

#include "msp430type.h"

//初始化外部中断P1.0
void ExtiP10_Init(void);

#endif

主函数:
#include <msp430f249.h>
#include "msp430type.h"
#include "system.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"

//主函数
void main(void)
{
        Wdt_Off();//关闭看门狗
        Clock_Init();//系统时钟初始化
        Led_Init();//led灯初始化
        Key_Init();//按键初始化
        ExtiP10_Init();//初始化外部中断P1.0
        
        _BIS_SR(GIE);//使能总中断
        while(1);
}
整个程序就是这么简单,需要注意的就是要打开总中断。

外部中断控制LED灯课程完结。源程序: 004 exti.rar (257.95 KB)      学习讨论Q群:167390222     2014年7月2日


实例5---UART串行通信-----程序目的:学会如何应用UART通信

MSP430的通用串行结构USCI支撑多种串行通信模式。在MSP430F249这个型号中含有4个USCI模块,下面我们就看支持UART模式的USCI_Ax的相应寄存器:

1.0 UCAxCTL0:控制寄存器0


2.0 UCAxCTL1:控制寄存器1


3.0 UCAxBR0与UCAxBR1:波特率控制寄存器
4.0 UCAxMCTL:调制控制寄存器


低频模式下的波特率调制器调整模式


5.0 UCAxSTAT:状态寄存器


6.0 UCAxRXBUF与UCAxTXBUF:接收数据寄存器与发送数据寄存器
7.0 UCAxABCTL:自动波特率寄存器


8.0 IE2:中断使能寄存器2


9.0 IFG2:中断标记寄存器2


这些寄存器的内容在截图当中已经说了很明确,从这节开始不再去翻译内容了,直接就讲配置及计算方法。下面我们就讲解UART的初始化与复位流程:
  • 配置端口,在MSP430F249的P3.4~3.7具有UART通信的第二功能,所以要配置位UART模式。
  • 置位UCSWRST,UCSWRST等于1时,将初始化所有USCI寄存器。
  • 设置UART时钟源及波特率。
  • 清除UCSWRST位,禁止复位,正常操作。
  • 根据需求是否开启中断。




现在我们来看如何设置波特率:
  • 先确定时钟源,UART模块可以用三种时钟源其中一种,这三种分别是外部时钟(UCAxCLK),ACLK与SMCLK;其波特率的产生有两种模式,一种是低频模式,一种是过采样模式。我们就来看低频模式,当UCAxMCTL调制控制寄存器中的UCOS16为0时,选择低频模式,低频模式可以使用较低的频率的时钟产生波特率,比如32768Hz就可以。低频主要是可以降低功耗。在此模式下,波特率发生器使用预分频器与调制器产生时钟时序。预分频器是由USCI_Ax波特率寄存器UCAxBR0与UCAxBR1实现,调制器由USCI_Ax波特率调制寄存器UCAxMCTL实现。
  • 计算低频模式下波特率,这里所谓的低频就是时钟源选择32768Hz。分频器分频系数=32768/9600=3.413取整=3。剩下的0.413用来设置调制寄存器,由于在低频模式下,所以就有8种波特率调整模式(请看波特率调制器调整模式表--上面),从而可以得出调制器的值为:UCBRSx=0.413*8=3.304取整=3。下面直接上程序,通过程序进一步的了解:




//UART0底层驱动文件

#include <msp430f249.h>
#include "uart.h"

//串口0初始化
void Uart0_Init(void)
{
        P3SEL |= 0x30;//设置P3.4与P3.5为串口TXD/RXD
        UCA0CTL1 |= UCSSEL0 + UCSWRST;//设置串口0时钟为ACLK,置位UCSWRST,初始化所有的USCI寄存器
        UCA0BR0 = 0x03;//32768Hz/9600=3.41
        UCA0BR1 = 0x00;
        UCA0MCTL = UCBRS1 + UCBRS0;//UCBRSx=0.413*8=3.304取整=3
        UCA0CTL1 &= ~UCSWRST;//清除UCSWRST位
        IE2 |= UCA0RXIE;//接收中断使能位
}

//串口0发送数据
void Uart0_Send_Byte(u8 data)
{
        while(!(IFG2&UCA0TXIFG));//发送寄存器空的时候发送数据
        UCA0TXBUF = data;
}

//串口0接收中断
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void)
{
        u8 data=0;
        
        data = UCA0RXBUF;//接收到的数据存起来
        Uart0_Send_Byte(data);//将接收到的数据再发送出去
}


//UART0底层驱动头文件

#ifndef __UART_H
#define __UART_H

#include "msp430type.h"

//串口0初始化
void Uart0_Init(void);
//串口0发送数据
void Uart0_Send_Byte(u8 data);

#endif


主函数
#include <msp430f249.h>
#include "msp430type.h"
#include "system.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "uart.h"

//主函数
void main(void)
{        
        Wdt_Off();//关闭看门狗
        Clock_Init();//系统时钟初始化
        Led_Init();//led灯初始化
        Key_Init();//按键初始化
        Uart0_Init();//串口0初始化
        
        _BIS_SR(GIE);//使能总中断
        while(1)
        {
                LED_L;//点亮led灯
                Delay_ms(200);
                LED_H;//熄灭led灯
                Delay_ms(200);
        }
}

程序使用中断方式,由串口中断函数得知,电脑发送一个数据到单片机,单片机则按原数据返回。
UART串行通信课程完结。源程序:
005 uart.rar (296.58 KB)     学习讨论Q群:167390222     2014年7月3日

使用特权

评论回复
地板
电子缘科技|  楼主 | 2014-7-2 11:05 | 只看该作者

实例

本帖最后由 电子缘科技 于 2014-7-5 11:29 编辑

实例6---程序调试助手printf-----程序目的:如何利用标准库实现printf函数
printf函数称为格式输出函数,其关键字最末一个字母f即为“格式”(format)之意。其功能是按用户指定的格式,把指定的数据显示到显示器屏幕上。
printf函数是一个标准库函数,它的函数原型在头文件“stdio.h”中,printf函数调用的一般形式为:
printf(“格式控制字符串”, 输出表列)
其中格式控制字符串用于指定输出格式。格式控制串可由格式字符串和非格式字符串两种组成。格式字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如:
%d 十进制有符号整数
%u 十进制无符号整数
%f 浮点数
%s 字符串
%c 单个字符
%p 指针的值
%e 指数形式的浮点数
%x或者%X 无符号以十六进制表示的整数,小写则输出小写,大写则输出大写
%o 无符号以八进制表示的整数
%g 自动选择合适的表示法
%p 输出地址符
如果这个是在VC环境下,printf就可以直接使用。那在MSP430的平台下如何实现,下面我们就来编写程序,首先确定的就是printf我们调用的是IAR环境下的标准库函数,那么我们只要写个底层就可以了,也是就我们写个putchar函数即可,不了解putchar函数的同学自己问下度娘,putchar函数如下:

//向终端输出一个字符
int putchar(int c)
{
        if(c == '\n')
        {
                  UCA0TXBUF = '\r';
                  while(!(IFG2 & UCA0TXIFG));
        }
        UCA0TXBUF = c;
        while(!(IFG2 & UCA0TXIFG));
        
        return c;
}
注意形式参数与函数类型都是int型,不要更改这个数据类型,否则不兼容。这个我们可以看下printf的声明
__EFF_NW1    __PRINTFPR __ATTRIBUTES  int  printf(const char *_Restrict, ...);

下面我们看演示程序:
在我们上节课的uart.c的文件里我们添加putchar函数
//UART0底层驱动文件

#include <msp430f249.h>
#include "uart.h"

//串口0初始化 波特率9600
void Uart0_Init(void)
{
        P3SEL |= 0x30;//设置P3.4与P3.5为串口TXD/RXD
        UCA0CTL1 |= UCSSEL0 + UCSWRST;//设置串口0时钟为ACLK,置位UCSWRST,初始化所有的USCI寄存器
        UCA0BR0 = 0x03;//32768Hz/9600=3.41
        UCA0BR1 = 0x00;
        UCA0MCTL = UCBRS1 + UCBRS0;//UCBRSx=0.41*8=3.28
        UCA0CTL1 &= ~UCSWRST;//清除UCSWRST位
        IE2 |= UCA0RXIE;//接收中断使能位
}

//串口0发送数据
void Uart0_Send_Byte(u8 data)
{
        while(!(IFG2&UCA0TXIFG));//发送寄存器空的时候发送数据
        UCA0TXBUF = data;
}

//向终端输出一个字符
int putchar(int c)
{
        if(c == '\n')
        {
                  UCA0TXBUF = '\r';
                  while(!(IFG2 & UCA0TXIFG));
        }
        UCA0TXBUF = c;
        while(!(IFG2 & UCA0TXIFG));
        
        return c;
}

//串口0接收中断
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void)
{
        u8 data=0;
        
        data = UCA0RXBUF;//接收到的数据存起来
        Uart0_Send_Byte(data);//将接收到的数据再发送出去
}

//主函数
#include <msp430f249.h>
#include "msp430type.h"
#include "system.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "uart.h"

//主函数
void main(void)
{        
        Wdt_Off();//关闭看门狗
        Clock_Init();//系统时钟初始化
        Led_Init();//led灯初始化
        Key_Init();//按键初始化
        Uart0_Init();//串口0初始化 波特率9600
        
        _BIS_SR(GIE);//使能总中断
        
        while(1)
        {
                printf("标准库printf函数发送数据到终端测试\r\n");
                LED_L;//点亮led灯
                Delay_ms(200);
                LED_H;//熄灭led灯
                Delay_ms(200);               
        }
}

主函数很简单,就是不断发送字符串,led灯不断闪烁。上图看下串口调试助手有没有收到数据。


其他的格式输出大家自己去验证,这里就不一一演示了。

printf函数实现课程完结。源程序: 006 uart-printf.rar (347.8 KB)     学习讨论Q群:167390222     2014年7月5日

使用特权

评论回复
5
电子缘科技|  楼主 | 2014-7-2 11:06 | 只看该作者
为**预留位置4

使用特权

评论回复
6
电子缘科技|  楼主 | 2014-7-2 11:06 | 只看该作者
为**预留位置5

使用特权

评论回复
7
lijunlinv| | 2014-7-2 11:21 | 只看该作者
后面的看不见啊??

使用特权

评论回复
8
电子缘科技|  楼主 | 2014-7-2 13:38 | 只看该作者
lijunlinv 发表于 2014-7-2 11:21
后面的看不见啊??

程序已添加

使用特权

评论回复
9
lijunlinv| | 2014-7-2 14:31 | 只看该作者
“此帖仅作者可见”,我们都看不见啊

使用特权

评论回复
10
电子缘科技|  楼主 | 2014-7-2 14:48 | 只看该作者
lijunlinv 发表于 2014-7-2 14:31
“此帖仅作者可见”,我们都看不见啊

嗯,现在看下,修改了权限

使用特权

评论回复
11
lijunlinv| | 2014-7-2 16:09 | 只看该作者
好,的很好,非常感谢,持续关注...

使用特权

评论回复
12
bhuner| | 2015-5-15 14:14 | 只看该作者
“time--“
我觉这个地方是有问题的,老师。
   应该换成     --time
  否则,即便是晶振失效也不会关闭XT2吧。
  因为当time==0时  还要进行一次循环,  当再一次判断time==0?
    time已经不等于0了

QQ截图20150515141236.png (17.31 KB )

QQ截图20150515141236.png

使用特权

评论回复
13
shenmu2012| | 2015-5-17 11:47 | 只看该作者
3种时钟振荡器分别为:低频时钟源LFXT1CLK、高频时钟源XT2CLK、数字控制RC振荡器DCOCLK。

使用特权

评论回复
14
shenmu2012| | 2015-5-17 11:49 | 只看该作者
电子缘科技 发表于 2014-7-2 11:04
实例1---IO控制LED-----程序目的:学会如何配置IO口
硬件:MSP430F249+LED

最基本的LED灯的控制是最基础的。

使用特权

评论回复
15
JorryZhao| | 2015-7-5 15:29 | 只看该作者

使用特权

评论回复
16
bobde163| | 2015-10-7 10:45 | 只看该作者
电子缘科技 发表于 2014-7-2 11:05
实例6---程序调试助手printf-----程序目的:如何利用标准库实现printf函数
printf函数称为格式输出函数,其 ...

请问您有关于在CCS中实现printf功能的方法吗?

使用特权

评论回复
17
sgq151679| | 2016-4-27 12:01 | 只看该作者
怎么不更新了?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

13

帖子

0

粉丝