esp8266 I2C 实例解析及源码分析

一  前言

   作为一个方案商兼芯片开发者,研究芯片和功能实现除了基本的工作需要,还有一层就是也变成了一种职业习惯。从芯片到方案,发现很多方案公司的人水平都比较堪忧,只会调用api,根本不会看底层的代码实现逻辑。这次调试I2C挂载传感器之后。

作为一个课题,笔者就好好地研究了一下ESP8266的I2C的源码,没想到的是,还收获挺大的,具体什么收获,请看完代码分析再说吧。

二 实例分析

  1 和很多主控芯片一样,esp8266的I2C接口也只是开放了master的底层,slave的底层没有代码实现部分。

     这个也许是需求考量,因为这种芯片一般的都是挂载传感器,传感器都是I2C slave的,也为了方便挂载多个传感器。

  2 主函数:

   初始化: i2c_example_master_init() 这里主要是clk和sda的初始化和选择。

   写函数: i2c_example_master_mpu6050_write 该函数主要是负责往特定寄存器中写入数据。

   读函数: i2c_example_master_mpu6050_read 该函数主要负责从slave中读取数据。

   特定传感器初始化函数:i2c_example_master_mpu6050_init 该函数主要负责传感器寄存器的初始化。

   简简单单的四个函数,就把I2C的所有功能囊括了,真是惊叹乐鑫的代码整洁啊。

  这个只要按照例子操作,硬件ok的情况下,一般都能读到数据了。挂载多个传感器的也只需要启动多个线程即可。

三 底层源码分析

   其实,假如要想深刻理解I2C的协议的话,最好看一下底层代码,幸运的是,esp8266 的I2C的底层代码是提供了的。我简单的阅读之后,发现了所有的核心就在一个函数中:

该函数如下所示:

static void i2c_master_cmd_begin_static(i2c_port_t i2c_num)
{
    i2c_obj_t *p_i2c = p_i2c_obj[i2c_num];
    i2c_cmd_t *cmd;
    uint8_t dat;
    uint8_t len;
    int8_t i, k;
    uint8_t retVal;

    // This should never happen
    if (p_i2c->mode != I2C_MODE_MASTER) {
        return;
    }

    while (p_i2c->cmd_link.head) {
        cmd = &p_i2c->cmd_link.head->cmd;

        switch (cmd->op_code) {
            case (I2C_CMD_RESTART): {
                i2c_master_set_dc(i2c_num, 1, i2c_last_state[i2c_num]->scl);
                i2c_master_set_dc(i2c_num, 1, 1);
                i2c_master_wait(1);     // sda 1, scl 1
                i2c_master_set_dc(i2c_num, 0, 1);
                i2c_master_wait(1);     // sda 0, scl 1
            }
            break;

            case (I2C_CMD_WRITE): {
                p_i2c->status = I2C_STATUS_WRITE;

                for (len = 0; len < cmd->byte_num; len++) {
                    dat = 0;
                    retVal = 0;
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);

                    for (i = 7; i >= 0; i--) {
                        if (cmd->byte_num == 1 && cmd->data == NULL) {
                            dat = (cmd->byte_cmd) >> i;
                        } else {
                            dat = ((uint8_t) * (cmd->data + len)) >> i;
                        }

                        i2c_master_set_dc(i2c_num, dat, 0);
                        i2c_master_wait(1);
                        i2c_master_set_dc(i2c_num, dat, 1);
                        i2c_master_wait(2);

                        if (i == 0) {
                            i2c_master_wait(1);   // wait slaver ack
                        }

                        i2c_master_set_dc(i2c_num, dat, 0);
                    }

                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);
                    i2c_master_set_dc(i2c_num, 1, 0);
                    i2c_master_set_dc(i2c_num, 1, 1);
                    i2c_master_wait(1);
                    retVal = i2c_master_get_dc(i2c_num);
                    i2c_master_wait(1);
                    i2c_master_set_dc(i2c_num, 1, 0);

                    if (cmd->ack.en == 1) {
                        if ((retVal & 0x01) != cmd->ack.exp) {
                            p_i2c->status = I2C_STATUS_ACK_ERROR;
                            return ;
                        }
                    }
                }
            }
            break;

            case (I2C_CMD_READ): {
                p_i2c->status = I2C_STATUS_READ;

                for (len = 0; len < cmd->byte_num; len++) {
                    retVal = 0;
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);

                    for (i = 0; i < 8; i++) {
                        i2c_master_set_dc(i2c_num, 1, 0);
                        i2c_master_wait(2);
                        i2c_master_set_dc(i2c_num, 1, 1);
                        i2c_master_wait(1);     // sda 1, scl 1
                        k = i2c_master_get_dc(i2c_num);
                        i2c_master_wait(1);

                        if (i == 7) {
                            i2c_master_wait(1);
                        }

                        k <<= (7 - i);
                        retVal |= k;
                    }

                    i2c_master_set_dc(i2c_num, 1, 0);
                    memcpy((uint8_t *)(cmd->data + len), (uint8_t *)&retVal, 1);
                    i2c_master_set_dc(i2c_num, i2c_last_state[i2c_num]->sda, 0);
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 0);
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 1);
                    i2c_master_wait(4);     // sda level, scl 1
                    i2c_master_set_dc(i2c_num, cmd->ack.val, 0);
                    i2c_master_set_dc(i2c_num, 1, 0);
                    i2c_master_wait(1);
                }
            }
            break;

            case (I2C_CMD_STOP): {
                i2c_master_wait(1);
                i2c_master_set_dc(i2c_num, 0, i2c_last_state[i2c_num]->scl);
                i2c_master_set_dc(i2c_num, 0, 1);
                i2c_master_wait(2);     // sda 0, scl 1
                i2c_master_set_dc(i2c_num, 1, 1);
                i2c_master_wait(2);     // sda 1, scl 1
            }
            break;
        }

        p_i2c->cmd_link.head = p_i2c->cmd_link.head->next;
    }

    p_i2c->status = I2C_STATUS_DONE;
    return;
}

  仔细阅读这段代码你就会发现,这个函数是esp8266的I2C的全部精华部分。 通过四个命令:restart,write read  stop 很清楚的列出了I2C的时序。假如这个时候,你对着示波器查看这些指令,再修改一下延时值,估计很快你就明白了I2C是怎么的工作模式。

从这段代码来看,esp8266的I2C是使用软件模拟的。

四 总结

   通过分析I2C的代码,很惊叹乐鑫的工程师的代码水平,笔者也在几个芯片公司待过,说实在的,感觉代码规范程度,只有st才能和乐鑫一决高下。这整洁代码的背后,是工程师静下心来日复一日的努力的完善的结果,中间经过多少次迭代,估计只有做这件事情的工程师才清楚。唯有心平气和,不急不躁的高手才能做到。真心地认为,想学习嵌入式的同学,可以把乐鑫的代码当做模仿的对象了。绝对是一份非常好的教材。

猜你喜欢

转载自www.cnblogs.com/dylancao/p/12570742.html