关于设计,生活和电子的二三事

走进MSP430(9)——MSP430G2 硬件IIC使用(完结)

本篇是MSP430系列最后一篇,到此MSP430G系列主要外设模块基本介绍完成。在更高级的系列中,会涉及到其他的外设模块,这些会在用到的时候介绍。
和上篇一样,这一片通过对芯片的实际操作讲解IIC模块,用到的代码基于MSPWare,有修改。本文及以前文章的代码和例程等都可以到QQ群:438978482下载。
IIC和SPI一样都是同步串口,和SPI不同的是,IIC只用到了两条数据线,一条是时钟,一条是双向数据线,这就是说,IIC只能进行半双工通信,一个设备在同一时刻只能接收或发送数据,不能同时收发。另外,IIC有明确的协议规范,所有设备都要根据协议规范通信,不会有SPI的时钟空闲电平或数据采样边沿的问题。在一条IIC总线上可以挂多个设备,不同设备通过设备地址进行操作。对于有些设备,设备地址可以自己设定,有些设备由于功能比较简单等原因,设备地址是固定的,不能自己设置。IIC地址有两种模式,7-bit地址模式和10-bit模式,外加1-bit读/写位。一般常用的模式是7-bit地址模式。由于IIC协议规定了一些保留地址,因此在7-bit地址模式下一条总线上理论上最多能连接112个设备,但由于实际设备地址可能有重合,实际连接设备数量会少于112个。
由于IIC速率较低,标准模式速率不超过100kbps,快速模式不超过400kbps,高速模式不超过3.4mbps,多数单片机都可以通过GPIO模拟方式达到标准或快速速率,因此硬件IIC方式最大的优势就是支持多主模式,和节约CPU资源。
IIC支持一条总线上的多个设备处于主机模式,即IIC的多主模式。IIC两条数据线都采用开漏方式,即低电平输出0V,高电平为悬空,这样在总线处于多主模式下可以通过总线仲裁确定此刻唯一有效主机,以防止冲突。仲裁失败的主机会切换到从机模式,等待此次传输结束后再尝试开始传输。对于只有一个单片机的系统,单片机为唯一主机,对于多个单片机互联的系统,可以使用多主模式简化设备间连接。但在一条总线上必须至少有一个主机。因为数据线为开漏方式,因此在总线上必须有上拉电阻,否则总线无法工作。
IIC总线传输数据流程如下:

首先由主机发送开始位,在SCL为高电平时SDA由高变低,为IIC的起始位,然后第一个字节是从机地址和读/写位,从机在收到地址位后与自己设定的地址为进行对比,如果符合地址位则在主机时钟的第9位产生一个ACK信号,即在第9个时钟位上升沿将SDA拉低,主机在收到ACK信号后可以得知要访问的从机存在,并且能够正确接收数据,然后开始后续传输过程。如果主机没有收到ACK信号,即在主机时钟第9位上升沿SDA为高(NACK信号),表明从机设备不存在或未准备好,主机需要发送停止位,即在SCL高电平时SDA由低变高,表示此次传输停止,然后再做尝试或终止传输。
ACK信号由接收数据的设备产生,即如果主机向从机中写入数据,ACK信号由从机产生,如果主机从从机读取数据,ACK信号由主机产生。在主机传输最后一个字节时,如果主机此时向从机中写入数据,在最后一个字节的ACK位从机会产生ACK信号,此时信号为ACK;如果主机此时从从机中读取数据,此时ACK位由主机产生,并且此时应产生NACK信号,然后主机发送停止位。即,主机读取从机数据的最后一个字节ACK位应为NACK信号,除此之外的其他情况ACK位都应为ACK信号。
在接收数据的设备接到从机ACK信号后,发送数据的设备会开始后续数据传输过程。每个字节数据包括8位数据位和1位ACK位,因此每个字节实际在IIC上传输的数据量是9bit。在主机传输数据完成后发送停止位,表示此次数据传输结束,IIC总线恢复空闲状态。
还有一种情况,是主机先向从机写入数据,然后紧接着读取从机数据,此时可以不发送第一个过程中的停止信号,直接发送开始信号,然后开始数据传输过程。这个过程图示如下:

图中斜线为主机发送数据,白色为从机发送数据,并且前后两次数据传输方向不同。首先主机发送开始位,然后是从机地址、读写位,然后从机产生ACK信号,然后主机向从机写入若干字节,然后主机不发送停止位,直接发送一个开始位,然后写入从机地址和读写位,然后从机产生ACK信号,然后主机从从机读取数据。此时ACK位信号由主机产生。在主机读取从机完成最后一个字节数据后,主机产生NACK信号,然后发送停止位,结束本次数据传输过程。这个过程中也可以主机先读取从机数据,然后再写入,这种情况下也可以不发送第一个停止位,不过这种情况出现较少。
需要注意的是MSP430G2553的IIC中断,和SPI、串口不同,IIC的发送和接收中断都是在USCIAB0TX_VECTOR里,而USCIAB0RX_VECTOR里是IIC的NACK、General Call、Start、Stop中断。而SPI、串口的发送中断是在USCIAB0TX_VECTOR里,接收中断是在USCIAB0RX_VECTOR里。这一点在编程时候需要特别注意,不然很有可能会进不去接收中断,在这耽误时间。附MSP430G2553中断说明。由于在MCU做主机时用不到NCAK,GC,STT,STP中断,这里就不再详述,有需要可以参考芯片编程手册。

下面是用MSP430G2553硬件IIC读取HMC5883磁强计的程序。通过读到的数据和逻辑分析仪抓到的数据可以确定程序功能正常。代码附在后面。

MSP430与HMC5883连接图如下,左侧几根线没有用到。

附程序

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

uint8_t TX_Data = 0x0a;
uint8_t TX_RX = 0;
uint8_t RX_Data[10];
uint8_t RX_CNT = 0;

int main(void)
{
    WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
    P1SEL |= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
    P1SEL2|= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0

    UCB0CTL1 |= UCSWRST;                      // Enable SW reset
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
    UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
    UCB0BR0 = 12;                             // fSCL = SMCLK/12 = ~100kHz
    UCB0BR1 = 0;
    UCB0I2CSA = 0x1E;                         // Slave Address is 048h
    UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
    IE2 |= UCB0TXIE + UCB0RXIE;                          // Enable TX interrupt
    __enable_interrupt();

    TX_RX = 1;                              //TX data
    UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
    while (UCB0CTL1 & UCTXSTT);

    while(1);
}

#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_ISR(void)
{
    static uint8_t RX_CNT = 0;

    if(IFG2 & UCB0TXIFG)
    {
        if(TX_RX)
        {
            UCB0TXBUF = TX_Data;
            TX_RX = 0;
        }
        else
        {
            UCB0CTL1 &= ~UCTR;
            IFG2 &= ~(UCB0TXIFG);
            IE2 &= ~(UCB0TXIE + UCB0RXIE);
            IE2 |= UCB0RXIE;                          // Enable RX interrupt
            UCB0CTL1 |= UCTXSTT;             // I2C TX, start condition
            while (UCB0CTL1 & UCTXSTT);
        }
    }
    if(IFG2 & UCB0RXIFG)
    {
        RX_Data[RX_CNT] = UCB0RXBUF;
        RX_CNT++;
        if(RX_CNT >= 3)
        {
            IFG2 &= ~(UCB0RXIFG + UCB0TXIFG);
            IE2 &= ~(UCB0TXIE + UCB0RXIE);
            UCB0CTL1 |= UCTXSTP;
            while (UCB0CTL1 & UCTXSTP);
            __disable_interrupt();
            while(1);
        }
    }
}

//#pragma vector = USCIAB0RX_VECTOR
//__interrupt void USCIAB0RX_ISR(void)
//{
//    static uint8_t RX_CNT = 0;
//
//    if(IFG2 & UCB0RXIFG)
//    {
//        RX_Data[RX_CNT] = UCB0RXBUF;
//        RX_CNT++;
//        if(RX_CNT > 3)
//        {
//            IFG2 &= ~(UCB0RXIFG + UCB0TXIFG);
//            IE2 &= ~(UCB0TXIE + UCB0RXIE);
//            UCB0CTL1 |= UCTXSTP;
//            while (UCB0CTL1 & UCTXSTP);
//            __disable_interrupt();
//            while(1);
//        }
//    }
//}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据