• 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才能和乐鑫一决高下。这整洁代码的背后,是工程师静下心来日复一日的努力的完善的结果,中间经过多少次迭代,估计只有做这件事情的工程师才清楚。唯有心平气和,不急不躁的高手才能做到。真心地认为,想学习嵌入式的同学,可以把乐鑫的代码当做模仿的对象了。绝对是一份非常好的教材。

  • 相关阅读:
    抽象工厂模式
    工厂方法模式
    单例模式
    适配器模式
    外观模式
    简单工厂模式
    设计模式开篇闲谈
    android ui更新
    android获取Context
    android 事件绑定
  • 原文地址:https://www.cnblogs.com/dylancao/p/12570742.html
Copyright © 2020-2023  润新知