IIC协议协议: 协议简单来说就是主机与从机双方约定一组动作,只要一方做了特定的动作,另一方就可以知道你要干什么,然后就可以给出特定的回复动作,多次重复就可以实现通信。
以下是我总结的IIC读写的步骤,可供参考: IIC 读数据:
发送起始信号 发送从机地址 - 发送(写入) 发送寄存器地址 起始信号 发送从机地址 - 接收(读取) 接收数据 发送停止信号 IIC 写数据:
发送起始信号 发送从机地址 - 发送(写入) 发送寄存器地址 发送数据 发送停止信号 软件模拟IIC实现与硬件IIC 由于在IIC总线的通信过程中,时钟信号由主设备产生,从设备根据时钟信号进行数据传输(即同步通信)。故可通过代码方便地模拟该协议,也即所谓的模拟IIC协议。
在软件模拟 IIC 时 GPIO 一般为开漏模式 ,支持线与功能;不过由于开漏模式无法输出真正的高电平,所以需要外部上拉(IIC的电平只是通信使用,所以负载强度不大;一般总线上认为,低于0.3Vdd为低电平,高于0.7Vdd为高电平) 推挽输出不能实现线与功能,因为如果两个输出引脚,一个输出高电平P-MOS管导通,一个输出低电平N-MOS管导通,则P-MOS管上方的高电平会经过P-MOS -> N-MOS -> GND,整个通路上没有外接电阻,因此电阻很小相当于高电平直接接到低电平造成了短路。
模拟 I2C是用两条 GPIO管脚的软件模拟的,将一个 GPIO设置为数据线 SDA,另外一个设置为时钟线 SCL。 硬件 I2C则是通过一个 I2C控制器实现的,该控制器被建立在微控制器芯片或单独的 I2C芯片中,通过集成的硬件内部逻辑和电路来控制时序和数据格式,实现 I2C总线通信。 软件与硬件的IIC各有优缺点,可根据实际情况选用。
注: 在使用软件模拟IIC时,其实IIC的IO口既可以配置为推挽输出也可以配置为开漏输出,不同之处在于:
当IO口配置为推挽输出时,发送和接收数据时需要切换IO口的输入输出模式,发送数据时需要将IO口切换为输出模式,接收数据时需要将IO口切换为输入模式。 如果配置开漏输出则不需要切换IO口的输入输出模式。我们知道推挽输出不具有线与的功能,但是由于我们使用软件IIC时通常不会有多个设备连接到一个总线上的情况,所以只有一个从设备的话,也就不会有线与的情况发生了,可以使用推挽输出。但是我们需要根据发送数据和接收数据来切换IO口的工作模式。 模拟IIC代码实现 注:有时候器件规格书上标明的设备地址不一定非得按照该地址进行寻址!
具体来说就是,假如:现有一传感器,使用IIC通信;其规格书标明设备地址可选(0x18、0x19),假如根据硬件连接,我们选用的地址为0x18;这并不意味着我们寻址是直接使用#define SLAVE_ADDR 0x18就行,很可能需要进行一番运算,即:#define SLAVE_ADDR (0x18 << 1)。如果IIC使用7位地址,那么<< 1的作用就很明了了:将最低位让出,用做读写位。
所以如果使用IIC时,根据规格书上的地址寻不到从设备且检查并无其他问题,那么就可以考虑一下该情况!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 void drv_iic_init (void ) { rcu_periph_clock_enable(RCU_GPIOF); gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6 | GPIO_PIN_7); gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_MAX, GPIO_PIN_6 | GPIO_PIN_7); gpio_bit_set(GPIOF, GPIO_PIN_6 | GPIO_PIN_7); } void iic_delay (void ) { unsigned short i = 20 ; while (i--) ; } void iic_write_scl (bit_status bit_value) { gpio_bit_write(GPIOF, GPIO_PIN_6, (bit_status)bit_value); iic_delay(); } void iic_write_sda (bit_status bit_value) { gpio_bit_write(GPIOF, GPIO_PIN_7, (bit_status)bit_value); iic_delay(); } unsigned char iic_read_sda (void ) { unsigned char value = 0 ; value = gpio_input_bit_get(GPIOF, GPIO_PIN_7); iic_delay(); return value; } void iic_start (void ) { iic_write_sda(SET); iic_write_scl(SET); iic_write_sda(RESET); iic_write_scl(RESET); } void iic_stop (void ) { iic_write_sda(RESET); iic_write_scl(SET); iic_write_sda(SET); } void iic_send_byte (unsigned char data) { unsigned char i = 0 ; for (i = 0 ; i < 8 ; i++) { iic_write_sda((bit_status)(data & (0x80 >> i))); iic_write_scl(SET); iic_write_scl(RESET); } } unsigned char iic_read_byte (void ) { unsigned char i = 0 ; unsigned char data = 0 ; iic_write_sda(SET); for (i = 0 ; i < 8 ; i++) { iic_write_scl(SET); if (iic_read_sda() == 1 ) { data |= (0x80 >> i); } iic_write_scl(RESET); } return data; } void iic_send_ack_or_not (bit_status bit_value) { iic_write_sda(bit_value); iic_write_scl(SET); iic_write_scl(RESET); } unsigned char iic_read_ack_value (void ) { unsigned char ack = 0 ; iic_write_sda(SET); iic_write_scl(SET); ack = iic_read_sda(); iic_write_scl(RESET); return ack; } iic_hall_status_enum write_data_to_slave (const unsigned char DeviceAddr, const unsigned char ResAddr, const unsigned char data) { iic_start(); iic_send_byte(DeviceAddr | 0x00 ); if (iic_read_ack_value()) { iic_stop(); return ERROR; } iic_send_byte(ResAddr); if (iic_read_ack_value()) { iic_stop(); return ERROR; } iic_send_byte(data); if (iic_read_ack_value()) { iic_stop(); return ERROR; } iic_stop(); return DONE; } iic_hall_status_enum read_data_from_slave (const unsigned char DeviceAddr, const unsigned char ResAddr, unsigned char * buf, unsigned char len) { iic_start(); iic_send_byte(DeviceAddr | 0x00 ); if (iic_read_ack_value()) { iic_stop(); return ERROR; } iic_send_byte(ResAddr); if (iic_read_ack_value()) { iic_stop(); return ERROR; } iic_start(); iic_send_byte(DeviceAddr | 0x01 ); if (iic_read_ack_value()) { iic_stop(); return ERROR; } while (1 ) { *buf++ = iic_read_byte(); if (--len == 0 ) { iic_send_ack_or_not((bit_status)SET); break ; } iic_send_ack_or_not((bit_status)RESET); } iic_stop(); return DONE; }
硬件IIC通信示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 void My_IIC_Init (void ) { rcu_periph_clock_enable(RCU_I2C1); rcu_periph_clock_enable(RCU_GPIOB); gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_10); gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_11); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10); gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_11); gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_11); i2c_clock_config(I2C1, 100000 , I2C_DTCY_2); i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, OWN_ADDRESS7); i2c_enable(I2C1); i2c_ack_config(I2C1, I2C_ACK_ENABLE); } void I2C_Read_Data (void ) { uint16_t timeout = 500 ; while (i2c_flag_get(I2C1, I2C_FLAG_I2CBSY)); i2c_start_on_bus(I2C1); while (!i2c_flag_get(I2C1, I2C_FLAG_SBSEND)); i2c_master_addressing(I2C1, SLA_ADDRESS7, I2C_TRANSMITTER); while (!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND)) { timeout--; if (timeout == 0 ) { i2c_stop_on_bus(I2C1); printf ("No I2C Device\r\n" ); return ; } } i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND); i2c_data_transmit(I2C1, REG_ADDRESS7); while (!i2c_flag_get(I2C1, I2C_FLAG_BTC)); i2c_start_on_bus(I2C1); while (!i2c_flag_get(I2C1, I2C_FLAG_SBSEND)); i2c_master_addressing(I2C1, SLA_ADDRESS7, I2C_RECEIVER); while (!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND)); i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND); for (int index = 0 ; index < 9 ; index++) { if (index == 8 ) { while (!i2c_flag_get(I2C1, I2C_FLAG_BTC)); i2c_ack_config(I2C1, I2C_ACK_DISABLE); } while (!i2c_flag_get(I2C1, I2C_FLAG_RBNE)); i2c_recv_buf[index] = i2c_data_receive(I2C1); } i2c_stop_on_bus(I2C1); while (I2C_CTL0(I2C1) & I2C_CTL0_STOP); i2c_ack_config(I2C1, I2C_ACK_ENABLE); }
注意 SCL与SDA需默认拉高 ,以释放总线状态 ,此时为准备好数据传输状态IIC起始信号:SCL高电平,SDA下降沿(注:起始后需把SCL也拉低,使主机占用总线准备发送数据) ;IIC结束信号:SCL高电平,SDA上升沿;主机发送:每次发送1位,循环8次即可发送一个字节;在发送数据时应注意:SCL高电平时,此时SDA上数据有效,不可被更改;SCL低电平时,SDA上数据可被更改,也只有这时数据可被更改 从机接收:在接收前,应先拉高SDA,避免主机抢占SDA,导致数据出错;为存储接收到的数据,需拉高SCL,使主机在SCL高电平期间读取SDA,每读取一位就存入提前申请好的变量中;读完后即可得到一个字节的数据。然后拉低SCL,使从机可写入SDA,这样即可实现从机发送。 应答信号:主机把应答位数据放到SDA线(即:如需应答则拉低SDA,如无需应答则拉高SDA);然后先拉高SCL,以读取应答位;再拉低SCL,开始下一个时序模块;