打印

MSP430的SPI协议分析和简单实例

[复制链接]
1079|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
可可球|  楼主 | 2016-2-29 21:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
yiyigirl2014| | 2016-2-29 22:06 | 只看该作者
SPI:SPI是Motorola首先在其MC68HCXX系列处理器上定义的,它是一种同步的高速串行通信协议,有关SPI协议的详细内容,参考:SPI_互动百科。

MSP430对SPI的支持:当msp430USART模块控制器UxCTL的位SYNC置位时,USART模块工作于同步模式,对于149即工作于SPI模式,若是169,USART0可以支持I2C,可以通过另一控制位I2C控制,I2C位0则工作于SPI。在SPI模式下,允许单片机以确定的速率发送和接收7位或8位数据。

同步通信与异步通信类似;同步通信和异步通信寄存器资源一致,具体寄存器的不同位之间的功能存在差异;具体寄存器内容参见TI提供的用户指南。

USART模块的SPI操作可以是3线和4线,其信号如下:
SIMO:从进主出,主机模式下,数据输出;从机模式下,数据输入。
SOMI:从出主进,主机模式下,数据输入;从机模式下,数据输出。
UCLK:USART SPI模式时钟,信号有主机输出,从机输入。
STE:从机模式发送接收允许控制脚,用于4线模式,控制多主从系统中多个从机,避免发生冲突。

使用特权

评论回复
板凳
yiyigirl2014| | 2016-2-29 22:07 | 只看该作者

初始化函数:SpiMasterInit,实现主机模式的初始化工作,函数内容如下:
char SpiMasterInit(long baud,char dataBits,char mode,char clkMode)
{
    long int brclk;                 //波特率发生器时钟频率

    UxCTL |= SWRST;                 //初始

    //反馈选择位,为1,发送的数被自己接收,用于测试,正常使用时注释掉
    //UxCTL |= LISTEN;

    UxCTL |= SYNC + MM;             //SPI 主机模式

    //时钟源设置
    UxTCTL &=~ (SSEL0+SSEL1);       //清除之前的时钟设置
    if(baud<=16364)                 //
    {
      UxTCTL |= SSEL0;              //ACLK,降低功耗
      brclk = 32768;                //波特率发生器时钟频率=ACLK(32768)
    }
    else
    {
      UxTCTL |= SSEL1;              //SMCLK,保证速度
      brclk = 1000000;              //波特率发生器时钟频率=SMCLK(1MHz)
    }

    //------------------------设置波特率-------------------------   
    if(baud < 300||baud > 115200)   //波特率超出范围
    {
        return 0;
    }
    //设置波特率寄存器
    int fen = brclk / baud;         //分频系数
    if(fen<2)return (0);            //分频系数必须大于2
    else
    {
        UxBR0 = fen / 256;
        UxBR1 = fen % 256;
    }

    //------------------------设置数据位-------------------------   
    switch(dataBits)
    {
        case 7:case'7': UxCTL &=~ CHAR; break;      //7位数据
        case 8:case'8': UxCTL |= CHAR;  break;      //8位数据
        default :       return(0);                  //参数错误
    }
    //------------------------设置模式---------------------------   
    switch(mode)
    {
        case 3:case'3': UxTCTL |= STC;  USPI3ON;    break;  //三线模式
        case 4:case'4': UxTCTL &=~ STC; USPI4ON;    break;  //四线模式
        default :       return(0);                          //参数错误
    }

    //------------------------设置UCLK模式-----------------------  
    switch(clkMode)
    {
        case 0:case'0': UxTCTL &=~ CKPH; UxTCTL &=~ CKPL;   break;  //模式0
        case 1:case'1': UxTCTL &=~ CKPH; UxTCTL |= CKPL;    break;  //模式1
        case 2:case'2': UxTCTL |= CKPH;  UxTCTL &=~ CKPL;   break;  //模式2
        case 3:case'3': UxTCTL |= CKPH;  UxTCTL |= CKPL;    break;  //模式3
        default :       return(0);                                  //参数错误
    }

    UxME |= USPIEx;             //模块使能

    UCTL0 &= ~SWRST;            // Initialize USART state machine

    UxIE |= URXIEx + UTXIEx;    // Enable USART0 RX interrupt

    return(1);                  //设置成功
}

程序注释已经比较详细,这里不再细说;如果要改为从机模式,把时钟设置和波特率设置去掉应该就可以了。

发送函数和接收函数:
void SpiWriteDat(char c)
{
    while (TxFlag==0) SpiLpm();  // 等待上一字节发完,并休眠
    TxFlag=0;                     //
    UxTXBUF=c;
}
char SpiReadDat()
{
    while (RxFlag==0) SpiLpm(); // 收到一字节?
    RxFlag=0;
    return(UxRXBUF);
}

发送和接收函数和异步通信里面的几乎一样,如果标志位为0,则等待改变为1,然后写入或读出;标志位在中断函数里被更改;中断函数如下:
#pragma vector=USARTxRX_VECTOR
__interrupt void UartRx()
{
    RxFlag=1;
    __low_power_mode_off_on_exit();
}
#pragma vector=USARTxTX_VECTOR
__interrupt void UartTx ()
{
    TxFlag=1;
    __low_power_mode_off_on_exit();
}

中断里面仅仅置标志位后,就退出低功耗;退出后即写入或者读取数据。

读取或写入函数调用的SpiLpm函数:
void SpiLpm()
{
    if(UxTCTL&SSEL0) LPM3;  //若以ACLK 作时钟,进入LPM3休眠(仅打开ACLK)
    else             LPM0;  //若以SMCLK作时钟,进入LPM0休眠(不关闭SMCLK)
}

根据不同情况进入低功耗,如果单片机其他地方不允许进入低功耗,可以更改这个函数。

程序部分就这么多了。需要的函数在头文件里面声明,方便使用。

3.使用示例:
程序使用方式和之前的程序库相同,加入c文件,包含h文件,调用初始化函数后即可掉用程序库中的函数。
#include "msp430x16x.h"   //430寄存器头文件
#include "Spi.h"         //串口通讯程序库头文件

void main()
{
    // Stop watchdog timer to prevent time out reset
    WDTCTL = WDTPW + WDTHOLD;

    ClkInit();
    // 主机模式,波特率25000,8位数据位,三线模式,时钟模式0(具体见spi.c)
    SpiMasterInit(25000,8,3,0);
    _EINT();


    while(1)                    //串口测试
    {
        SpiWriteDat(0X20);
        char a = SpiReadDat();
    }
}

这里只是一个简单的使用示例,详细的使用,将会在下一篇给出,下一篇:MSP430程序库<六>通过SPI操作AD7708;将会使用今天的程序库,完成SPI的通信部分。


4.注意事项:

SPI是全双工通信,每次写入(发送)8位/7位数据的同时,430的SPI主模块都会在发送后半个时钟周期读取采样的0/1信号,存入接收缓冲寄存器,所以,每次的写入,均有数据读取,但不一定是从设备发送回来的,这个地方在使用430主机模式的时候必须注意,很容易出错(我也是在调试AD7708的时候才注意到这个地方的);SPI的函数已经添加SpiWriteData函数,这个函数会在发送的同时返回发送完成半个时钟周期后的接收到的数据,方便使用;不建议使用前面的发送和读取函数,很容易出错;建议使用刚添加的这个函数,程序库已经更新,可以重新下载。函数SpiWriteData:
char SpiWriteData(char c)
{
    SpiWriteDat(c);
    return SpiReadDat();
}

发送后读取即可,程序比较简单。

新的示例程序:
void main()
{
    // Stop watchdog timer to prevent time out reset
    WDTCTL = WDTPW + WDTHOLD;

    ClkInit();
    // 主机模式,波特率25000,8位数据位,三线模式,时钟模式0(具体见spi.c)
    SpiMasterInit(25000,8,3,0);
    _EINT();


    while(1)                    //串口测试
    {
        SpiWriteData(0X20);     //只写入
        char a = SpiWriteData(0xff);    //只读取
    }
}


使用特权

评论回复
地板
yiyigirl2014| | 2016-2-29 22:15 | 只看该作者
  • 硬件主要是MSP430的SPI接口和AD7708芯片的说用说明。
    msp430的SPI接口:支持主机模式和从机模式,且始终的极性和相位可调,在于AD转换芯片通信的时候,需要极性一致。
    AD7718 的外部引脚有28 个。按性质主要分为模拟、数字两个部分。模拟部分引脚有模拟输入、参考电压输入和模拟电源三类。模拟输入引脚可以配置为8通道或10通道的伪差分输入,他们共同参考AINCOM端。
    数字部分引脚有 SPI 接口、数据就绪、通用I/O 口和数字电源四类。SPI 接口的4 根标准信号线分别是片选信号CS 、串行时钟输入SCLK、串行数据输入DIN 和串行数据输出DOUT。当AD7718接在SPI 总线上时是从器件,从引脚CS 输入低电平信号使能AD7718。数据就绪RDY 是一个低电平有效的输出引脚。当所选通道数据寄存器中有有效数据时,输出低电平信号;数据被读出后,输出高电平。AD7718 的通用I/O 口是2 个一位口P1 和P2。它们既可配置成输入也可配置成输出,单片机通过SPI 口读写AD7718 片内相关寄存器实现对P1 和P2 的操作。它们扩展了单片机的I/O 接口能力。
    AD7718 的模拟电源和数字电源是分别供电的,都既可以采用+3V 供电,也可以采用+5V 供电。但必须一致,要么都用+3V,要么都用+5V。
    AD7708和AD7718是通过一组片内寄存器控制和配置的。这些寄存器的第一个是通信寄存器,它是用来控制转换器的所有操作。这些部件的所有通信必须先写通信寄存器指定要执行的下一个操作。上电或复位后,设备默认等待写通信寄存器。 STATUS寄存器包含转换器的操作条件的有关信息。 STATUS寄存器是只读寄存器。模式寄存器用于配置转换模式,校准,斩波(chop)启用/禁用,参考电压选择,通道配置和伪差分AINCOM模拟输入操作时的缓冲或无缓冲。模式寄存器是一个读/写寄存器。 ADC控制寄存器是一个读/写寄存器,用来选择活动的通道和编码输入范围和双极性/单极性操作。I/O控制寄存器是一个读/写寄存器,用于配置了2个I/O端口的操作。滤波寄存器是一个读/写寄存器,用于编码转换器的数据更新率。 ADC数据寄存器是一个只读寄存器,它包含在所选通道上的一个数据转换的结果。 ADC的失调寄存器读/写寄存器包含偏移校准数据。有五个偏移寄存器,每个全差分输入通道之一。当配置为伪差分输入模式下的通道共用偏移寄存器。 ADC增益寄存器是读/写寄存器,包含增益校准数据。有5个ADC增益寄存器,每个全差分输入通道之一。当配置为伪差分输入模式通道共享增益寄存器。该ADC包含工厂使用的测试寄存器,用户应不改变这些寄存器的操作条件。 ID寄存器是一个只读寄存器,用于硅识别目的。
    我用的硬件连线方式:430的P3.0接AD7708的CS端,P3.1-P3.2接对应的AD芯片的SPI口;RDY信号没有接;所以,程序使用的是查询方式,等待STATUS寄存器的RDY位指示转换完成。
    有关AD7708的详细信息可以参考它的datasheet;另外我对数据手册的寄存器部分和程序流程的部分进行了翻译,如果需要,可以在本博客底部的附件中下载。

使用特权

评论回复
5
yiyigirl2014| | 2016-2-29 22:16 | 只看该作者
程序实现:
首先是对AD7708的读写寄存器函数,AD7708的每次操作都以写通信寄存器开始,通过这一步,指示下一步将进行什么操作;有关寄存器每一位的意义,参考附件(博客结尾)中的AD7708-寄存器

写入寄存器:
void AD7708WriteRegister(char addr,long dat)
{
    SpiWriteData(addr);     //写通信寄存器,通知下个操作:写addr寄存器
    if(IsLong[addr])        //如果是16位寄存器, 7718则24位若移植要改if内语句
    {
        SpiWriteData(dat>>8);
    }
    SpiWriteData(0xFF&dat);       //写入低位数据
}

寄存器地址,可以查阅datasheet或我翻译的那部分;IsLong字符数组指示对应的寄存器是8位还是16位的:
char IsLong[16] = {0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0};

读取寄存器:
long AD7708ReadRegister(char addr)
{
    char h = 0,l = 0;           //高低字节数据
    SpiWriteData(0x40|addr);    //写通信寄存器,通知下个操作:读addr寄存器
    if(IsLong[addr])
    {
        h = SpiWriteData(0xFF);
    }
    l = SpiWriteData(0xFF);
    return ((unsigned int)h<<8)|l;
}

SPI解释:430是SPI主机模块,当发送的时候,同时,另外一个时钟沿采样接收,所以,每次发送完成后的半个周期,均可得到读出的数据;所以SpiWriteData函数写入的同时返回同时收到的字符。发送0xFF即是为提供读取即将到来的数据提供时钟,详细可以参考上一篇的注意事项部分(刚更新的)。

读取结果数据:
long AD7708ReadResultData()
{
    while((AD7708ReadRegister(0x00)&0x80)==0); //等待转换完成
    return AD7708ReadRegister(0x04);
}

等待STATUS的RDY位变高(AD数据转换更新完成),读取data寄存器的内容。

校准:校准的过程在datasheet中有详细的流程图;可以参考datasheet或者附件中的AD7708-寄存器,这个子函数只完成一个通道的校准,通道地址有参数输入,方便调用:
void AD7708Cal(char channel)
{
    adccon = (adccon&0x0f)|(channel<<4);
    mode = (mode&0xf8)|0x04;                //内部0校准
    AD7708WriteRegister(0x02,adccon);       //ADC控制寄存器,channel通道
    AD7708WriteRegister(0x01,mode);         //模式寄存器
    while((AD7708ReadRegister(0x01)&0x07)!=0x01);   //等待校准完成
   
    mode = (mode&0xf8)|0x05;                //内部 满标度校准
    AD7708WriteRegister(0x01,mode);         //模式寄存器
    while((AD7708ReadRegister(0x01)&0x07)!=0x01);   //等待校准完成
}

adccon是程序记录的前一次输入的ADCCON寄存器的内容,mode是程序记录的上一次输入的MODE寄存器的内容,因为串口读取需要时间,为了获取更快的速度,程序记录了这两个变量,以供使用。通道地址参考datasheet,或附件中的文档。

初始化:
void AD7708Init(char chop)
{

P3DIR|=BIT0;
P3OUT&=~BIT0;                            //CS选中
    //主机模式,115200,8位数据位,三线模式,时钟模式1(具体见spi.c)
    SpiMasterInit(115200,8,3,1);        //时钟不是准确的115200(具体见spi.c)
    _EINT();                            //开中断,spi读写程序要需要中断
   
    char filter;
    adccon = 0x0f;
    if(chop == 0)
    {
        filter = 0x03;                  //滤波寄存器设为最小值,可以更改
        mode = 0x91;                    //斩波禁止,10通道,无缓冲,空闲模式
    }
    else
    {
        filter = 0x0D;                  //滤波寄存器设为最小值,可以更改
        mode = 0x11;                    //斩波启用,10通道,无缓冲,空闲模式
    }
   
    AD7708WriteRegister(0x07,0x00);     //IO寄存器,不用==
    AD7708WriteRegister(0x03,filter);   //滤波寄存器
    AD7708WriteRegister(0x02,0x0F);     //ADC控制寄存器,0通道,单极性
    AD7708WriteRegister(0x01,mode);     //模式寄存器
    if(chop == 0)
        for(int i = 0; i<5;i++)
        {
            //校准,因只有5个失调寄存器,多的就会覆盖之前的,只校准5个即可
            AD7708Cal(5);
        }
   
    _DINT();
}

初始化制引入了斩波这一个参数,其他的均使用固定的参数:10通道伪差分、单极性、无缓冲、滤波寄存器设为斩波或禁止斩波时候的最快速度,需要的话可以自行修改。SPI初始化之后开中断,目的是向AD写内容以初始化AD。初始化完成后关中断,为了让程序库的初始化后一致,但调用这个函数后,需要开中断,才能正常使用AD采样的其它函数。

采样启动:本程序只支持了单词采样的开始,若需要连续模式的,可以自行实现(比较容易实现:只需更改寄存器的值即可):
void AD7708Start(char channel)
{
    adccon = (adccon&0x0f)|(channel<<4);
    mode = (mode&0xf8)|0x02;
    AD7708WriteRegister(0x02,adccon);
    AD7708WriteRegister(0x01,mode);
}

根据之前一次的控制寄存器和模式寄存器的设置,更改现在需要的值,写入相应寄存器即可。

到此,程序部分完成,需要扩展,可以自行添加。

3.使用说明:
使用时,只需加入AD7708.c,文件包含AD7708.h,然后就可以正常使用本程序提供的函数;具体可以参考示例工程和其中的main.c文件。
long a;
void main()
{
    // Stop watchdog timer to prevent time out reset
    WDTCTL = WDTPW + WDTHOLD;
    ClkInit();
    AD7708Init(0);  //禁止斩波 1时启用斩波
   
    _EINT();        //开中断,程序需要用到SPI的中断;冲突,可以更改SPI函数
   
    while(1)                    //串口测试
    {
        AD7708Start(0);
        a = AD7708ReadResultData();         //读取AD采样后的结果
        //电压计算方法:a*1.024*2.5(参考电压)/65535
        a = AD7708ReadRegister(0);          //去状态值,此处函数不需要用
        
    }
}

AD7708的程序库(简化,其他需求可以自行添加:有读写寄存器的函数之后,添加其他功能比较简单)已经完成,有什么不足之处欢迎大家讨论;谢啦。

使用特权

评论回复
6
yiyigirl2014| | 2016-2-29 23:04 | 只看该作者
mode是程序记录的上一次输入的MODE寄存器的内容,因为串口读取需要时间,为了获取更快的速度,程序记录了这两个变量,以供使用

使用特权

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

本版积分规则

97

主题

675

帖子

1

粉丝