一.IIC总线介绍
IIC(Inter-Integrated Circuit 集成电路)总线是Philips公司在八十年代初推出的一种串行、半双工的总线, 主要用于近距离、低速的芯片之间的通信;I2C总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;I2C总线硬件结构简单,简化了PCB布线,降低了系统成本,提高了系统可靠性,因此在各个领域得到了广泛应用。
I2C总线是一种多主机总线,连接在 I2C总线上的器件分为主机和从机。主机有权发起和结束一次通信,从机只能被动呼叫;当总线上有多个主机同时启用总线时,I2C也具备冲突检测和仲裁的功能来防止错误产生;每个连接到I2C总线上的器件都有一个唯一的地址(7bit) ,传输数据的设备间是简单的主/从关系,每个器件都可以作为主机也可以作为从机(但同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;I2C总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器。I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也有利于标准化和模块化,缩短开发时间。
串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。
二.通信过程
1. 主机发送起始信号启用总线;
2. 主机发送一个字节数据指明从机地址和后续字节的传送方向;
3. 被寻址的从机发送应答信号回应主机;
4. 发送器发送一个字节数据;
5. 接收器发送应答信号回应发送器;
6. 循环步骤4、5;
7. 通信完成后主机发送停止信号释放总线;
注意:
*第4步和第5步用的是发送器和接收器,不是主机和从机,这是由第一个字节的最后一位决定主给从发,还是从给主发。也就是说, 第一个字节和最后的停止信号一定是主机发给从机,但中间就不一定了。
*发送数据过程中不允许改变发送方向。
三.IIC总线的信号类型
IIC总线在传送数据过程中共有3种类型信号: 开始信号、结束信号和响应信号。
开始信号(S)和结束信号(P)
开始信号:SCL 为高电平时,SDA由高电平向低电平跳变, 开始传送数据。
结束信号:SCL 为高电平时,SDA由低电平向高电平跳变, 结束传送数据。
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态,停止信号产生后总线被释放,处于空闲状态。空闲时,SCL与SDA都是高电平。
停止情况有两种:
1. 主机不想发了,就发送停止信号;
2. 从机不想接了,不应答,主机就发送停止信号结束此次通信。
响应信号(ACK)
接收器在接收到8位数据后,在第9个时钟周期,拉低SDA电平。
注意:SDA上传输的数据必须在SCL为高电平期间保持稳定,为低电平期间变化。
SDA和SCL发送数据时的电平转换图
四、IIC总线的数据传输格式
IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位(MSB),后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器,即一帧共有9位。
启动一个传输时,主机先发送S信号,然后发出8位数据。这8位数据中前7位为从机的地址,第8位表示传输的方向(0表示写操作,1表示读操作)。从机收到后会发出一个ACK信号。主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。
五.项目实战
接下来我以TM1637数码管为例,应用IIC协议原理显示时间。
写SRAM数据固定地址模式
#include "gpio.h"
#include "main.h"
#include "rtc.h"
#include <stdio.h>
#define TM1637_CLK_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9,GPIO_PIN_SET)
#define TM1637_CLK_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9,GPIO_PIN_RESET)
#define TM1637_DIO_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8,GPIO_PIN_SET)
#define TM1637_DIO_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8,GPIO_PIN_RESET)
#define TM1637_DELAY_TIME 2
//共阴极数码管真值表
const uint8_t segmentMap[] = {
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, // 0-7
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, // 8-9,A-F
0x00
};
//开始信号
void tm1637_start_signal(){
TM1637_CLK_H();
TM1637_DIO_H();
HAL_Delay(TM1637_DELAY_TIME);
TM1637_DIO_L();
HAL_Delay(TM1637_DELAY_TIME);
}
//停止信号
void tm1637_stop_signal(){
TM1637_CLK_L();
TM1637_DIO_L();
HAL_Delay(TM1637_DELAY_TIME);
TM1637_CLK_H();
HAL_Delay(TM1637_DELAY_TIME);
TM1637_DIO_H();
HAL_Delay(TM1637_DELAY_TIME);
}
//应答信号
void tm1637_ack(){
TM1637_CLK_L();
HAL_Delay(TM1637_DELAY_TIME);
TM1637_CLK_H();
HAL_Delay(TM1637_DELAY_TIME);
}
//发送一个字节
void tm1637_write_byte(uint8_t data){
int i;
for(i=0;i<8;i++){
TM1637_CLK_L();
HAL_Delay(TM1637_DELAY_TIME);
if((data >> i) & 0x1){
TM1637_DIO_H();
}else{
TM1637_DIO_L();
}
TM1637_CLK_H();
HAL_Delay(TM1637_DELAY_TIME);
}
}
//发送命令
void tm1637_send_cmd(uint8_t cmd){
tm1637_start_signal();
tm1637_write_byte(cmd);
tm1637_ack();
tm1637_stop_signal();
}
//给指定地址发送数据
void tm1637_send_data_to_addr(uint8_t addr,uint8_t data){
tm1637_start_signal();
tm1637_write_byte(addr);
tm1637_ack();
tm1637_write_byte(data);
tm1637_ack();
tm1637_stop_signal();
}
//测试
void tm1637_test(){
// 设置时间和日期
//HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime,uint32_tFormat)
RTC_TimeTypeDef sTime;
HAL_RTC_SetTime(&hrtc,&sTime,RTC_FORMAT_BIN);
//HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc,RTC_DateTypeDef *sDate,uint32_tFormat)
RTC_DateTypeDef sDate;
HAL_RTC_SetDate(&hrtc,&sDate,RTC_FORMAT_BIN);
// 获取时间和日期
//HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime,uint32_tFormat)
HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN);
//HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc,RTC_DateTypeDef *sDate,uint32_tFormat)
HAL_RTC_GetDate(&hrtc,&sDate,RTC_FORMAT_BIN);
//显示时间
while(1){
tm1637_send_cmd(0x44);
tm1637_send_data_to_addr(0xC0,segmentMap[sTime.Hours/10]);
tm1637_send_data_to_addr(0xC1,segmentMap[sTime.Hours%10] | 0x80);
tm1637_send_data_to_addr(0xC2,segmentMap[sTime.Minutes/10]);
tm1637_send_data_to_addr(0xC3,segmentMap[sTime.Minutes%10]);
tm1637_send_cmd(0x8F);
HAL_Delay(1000);
}
}