最近帮同学做一个项目,开发板是EFM8单片机,支持SPI和I2C协议(SMBus)。很久没搞过单片机了,而且条件限制,为了使单片机和外设成功通信,花了一个星期时间。刚开始使用SPI,发现代码逻辑都没问题,就是结果不对(后来知道是因为带中断的程序单步调试导致的,说多了都是泪),调了几天发现SPI确实调不通,就换了I2C,半天时间搞定,哈哈。本文重点解释I2C,废话少说了。
1、简介
I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。这些优点不是吹的,只需要两个IO口就行了,比起并行传输节省了不知道多少成本。
2、连接图
2条双向串行线,一条数据线SDA,一条时钟线SCL。SDA传输数据是大端传输,每次传输8bit,即一字节。支持多主控(multimastering),任何时间点只能有一个主控。总线上每个设备都有自己的一个addr,共7个bit,广播地址全0。
本文用的是ADXL345,CS引脚拉高至VDD,ADXL345处于I2C模式,需要简单2线式连接。ALT ADDRESS(SDO)引脚处于高电平,器件的7位I2C地址是0x1D,随后为R/W位。这转化为0x3A写入,0x3B读取。通过ALT ADDRESS引脚(引脚12)接地,可以选择备用I2C地址0x53(随后为R/W位)。这里特别说明,外设和MCU不需要共GND,也不需要共VDD,我刚开始纠结了好久,查了很多资料,硬是没查到。这转化为0xA6写入,0xA7读取。连线方式如下图:
3、读写流程
I2C的时序这些就不多介绍了,网上一搜一大堆,想用IO口模拟I2C可以,大多数MCU都内置I2C模块,只要连线正确,配置和操作寄存器就能正常通信了。不过,I2C读写数据的流程是必须了解的。
3.1、写流程
写寄存器的标准流程为:
1. Master发起START
2. Master发送I2C addr(7bit)和w操作0(1bit),等待ACK
3. Slave发送ACK
4. Master发送reg addr(8bit),等待ACK
5. Slave发送ACK
6. Master发送data(8bit),即要写入寄存器中的数据,等待ACK
7. Slave发送ACK
8. 第6步和第7步可以重复多次,即顺序写多个寄存器
9. Master发起STOT
3.2、读流程
读流程比写稍微麻烦一点,在读之前要先把寄存器地址写入,然后再开始读:
1. Master发起START
2. Master发送I2C addr(7bit)和w操作1(1bit),等待ACK
3. Slave发送ACK
4. Master发送reg addr(8bit),等待ACK
5. Slave发送ACK
6. Master发起START
7. Master发送I2C addr(7bit)和r操作1(1bit),等待ACK
8. Slave发送ACK
9. Slave发送data(8bit),即寄存器里的值
10. Master发送ACK
11. 第8步和第9步可以重复多次,即顺序读多个寄存器
4、程序原理
程序是根据配置和操作寄存器实现I2C通信,将I2C设为忙状态,START标志开始,后续所有收发数据在中断子程序中处理。中断子程序中,根据SMB0CN0寄存器判断是什么状态,然后做出响应的处理。
特别说明,寄存器地址和读写的数据复用放在数组SMB_DATA_OUT里。
读写函数:
void SMB_Write(uint8_t Flag) { while(SMB_BUSY); // Wait for SMBus to be free. SMB_BUSY = 1; // Claim SMBus (set to busy) SMB_RW = Flag; // Mark this transfer as a WRITE SMB0CN0_STA = 1; // Start transfer while(SMB_BUSY); } void SMB_Read(void) { while(SMB_BUSY); // Wait for bus to be free. SMB_BUSY = 1; // Claim SMBus (set to busy) SMB_RW = 1; // Mark this transfer as a READ SMB0CN0_STA = 1; // Start transfer while(SMB_BUSY); // Wait for transfer to complete }
中断处理子程序:
switch (SMB0CN0 & 0xF0) // Status vector { // Master Transmitter/Receiver: START condition transmitted. case SMB_MTSTA: SMB0DAT = TARGET; // Load address of the target slave SMB0DAT &= 0xFE; // Clear the LSB of the address for the // R/W bit SMB0DAT |= RW_FLAG; // Load R/W bit SMB0CN0_STA = 0; // Manually clear START bit sent_byte_counter = 1; // Reset the counter break; // Master Transmitter: Data byte transmitted case SMB_MTDB: if (SMB0CN0_ACK) // Slave SMB0CN0_ACK? { if (RW_FLAG == WRITE) // If this transfer is a WRITE, { if (sent_byte_counter <= NUM_BYTES_WR) { // send data byte SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1]; sent_byte_counter++; } else { SMB0CN0_STO = 1; // Set SMB0CN0_STO to terminate transfer SMB_BUSY = 0; // And free SMBus interface } } } else // If slave NACK, { SMB0CN0_STO = 1; // Send STOP condition, followed SMB0CN0_STA = 1; // By a START } break; // Master Receiver: byte received case SMB_MRDB: SMB_DATA_OUT = SMB0DAT; // Store received byte SMB_BUSY = 0; // Free SMBus interface SMB0CN0_ACK = 0; // Send NACK to indicate last byte of this transfer SMB0CN0_STO = 1; // Send STOP to terminate transfer break; default: FAIL = 1; // Indicate failed transfer // and handle at end of ISR break;
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/jeffrey0000/article/details/46848831