• ESP32官方MPU6050组件介绍


    前言

    (1)因为我需要使用MPU6050的组件,但是又需要在这条I2C总线上挂载多个设备,所以我本人打算自己对官方的MPU6050的组件进行微调。建立一个I2C总线,设备依赖于这个总线挂载。
    (2)既然要做移植工作,所以就需要弄明白这个组件应当如何使用。

    MPU6050组件函数介绍

    mpu6050_create()

    使用方法

    (1)此函数用于创建一个指向MPU6050初始化信息的mpu6050_handle_t无符号类型指针。我们可以通过这个指针访问到mpu6050.c这个组件中的mpu6050_dev_t结构体。
    (2)这样做能够做到常说的高内聚,低耦合的作用。如果不明白也没事,记住他返回的这个数据是和MPU6050的信息有关,我们能用就行
    (3)如何传入数据:
    <1>当你的MPU6050是挂载在ESP32的I2C0时候,传入I2C_NUM_0。如果是I2C1,传入I2C_NUM_1。如果是挂载在低功耗I2C中,传入LP_I2C_NUM_0。需要注意的是,不是所有的ESP32都具有I2C1和低功耗I2C,这个需要自行查阅芯片手册。
    <2>关于MPU6050的地址,有两种。当MPU6050的第9号引脚AD0为低电平的时候地址为0x68,即MPU6050_I2C_ADDRESS。当这个引脚为高电平时候地址为0x69,即MPU6050_I2C_ADDRESS_1

    /**
     * @brief  初始化MPU6050相关信息
     * 
     * @param   port       要挂载在哪个I2C总线上
     *         -dev_addr   MPU6050的地址
     * 
     * @return  NULL       初始化MPU6050的信息失败
     *         -非NULL指针 初始化MPU6050的信息成功
    */
    mpu6050_handle_t mpu6050_create(i2c_port_t port, const uint16_t dev_addr)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    简单概述底层实现

    (1)mpu6050_handle_t存放在头文件中,用于暴露接口。
    (2)mpu6050_dev_t这个结构体具体作用讲实话我也没有搞太明白,只能说我知道的认为重要的两个部分。
    <1>bus,存放mpu6050是挂载在哪个I2C下。
    <2>dev_addr,MPU6050的地址。
    (3)函数实现部分介绍,注意,我也只能讲我懂的部分:
    <1>使用calloc()函数分配1个mpu6050_dev_t类型的空间,并且将这块空间全部初始化为0。
    <2>对申请到的sensor变量进行初始化,将MPU6050挂载的I2C信息,MPU6050地址信息存入这个变量。可能有些人会有疑惑,为什么地址信息dev_addr << 1需要进行一次右移操作。这个和I2C的时序逻辑有关,一般I2C设备的地址为7bit,最后1bit负责存放是对设备读还是写的信息。因此这里需要进行右移一位。
    <3>最后返回的数据进行强制类型转换为mpu6050_handle_t这样就能够实现我上述所说的高内聚,低耦合的功能。

    /*--- mpu6050.h ---*/
    typedef void *mpu6050_handle_t;
    
    /*--- mpu6050.c ---*/
    typedef struct {
        i2c_port_t bus;
        gpio_num_t int_pin;
        uint16_t dev_addr;
        uint32_t counter;
        float dt;  /*!< delay time between two measurements, dt should be small (ms level) */
        struct timeval *timer;
    } mpu6050_dev_t;
    
    mpu6050_handle_t mpu6050_create(i2c_port_t port, const uint16_t dev_addr)
    {
        mpu6050_dev_t *sensor = (mpu6050_dev_t *) calloc(1, sizeof(mpu6050_dev_t));
        sensor->bus = port;
        sensor->dev_addr = dev_addr << 1;
        sensor->counter = 0;
        sensor->dt = 0;
        sensor->timer = (struct timeval *) calloc(1, sizeof(struct timeval));
        return (mpu6050_handle_t) sensor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    mpu6050_config()

    使用介绍

    (1)用于设置MPU6050的加速度计满量程和陀螺仪满量程。
    (2)如何传入数据:
    <1>传入mpu6050_create()函数创建的mpu6050_handle_t指针。
    <2>ACCE_FS_2G,加速度计满量程为+/-2g。ACCE_FS_4G,为+/-4g。ACCE_FS_8G,为+/-8g。ACCE_FS_16G,为+/-16g。
    <3>GYRO_FS_250DPS,陀螺仪满量程是+/- 250度每秒。GYRO_FS_500DPS,为500度每秒。GYRO_FS_1000DPS,为1000度每秒。GYRO_FS_2000DPS,为2000度每秒。
    (3)关于这个设置由你自己看情况决定。如果值太高将会降低分辨率,值太低就无法测量过高的数据。

    /**
     * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
     * 
     * @param   sensor   mpu6050_create()函数创建的mpu6050_handle_t指针
     *         -acce_fs  设置加速度计满量程
     *         -gyro_fs  设置陀螺仪满量程
     * 
     * @return  ESP_OK   MPU6050配置成功
     *         -ESP_FAIL MPU6050配置失败
    */
    esp_err_t mpu6050_config(mpu6050_handle_t sensor, 
    						 const mpu6050_acce_fs_t acce_fs,
    					     const mpu6050_gyro_fs_t gyro_fs)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    简单概述底层实现

    (1)进入mpu6050_config()函数,我们会看到他先建立了一个8bit的无符号数组,将陀螺仪配置放在前面,加速度计配置放在后面。这是因为MPU6050的陀螺仪配置寄存器GYRO_CONFIG在加速度计寄存器ACCEL_CONFIG的前面,而MPU6050的每个寄存器为8bit。

    esp_err_t mpu6050_config(mpu6050_handle_t sensor, 
    						 const mpu6050_acce_fs_t acce_fs, 
    						 const mpu6050_gyro_fs_t gyro_fs)
    {
        uint8_t config_regs[2] = {gyro_fs << 3,  acce_fs << 3};
        return mpu6050_write(sensor, MPU6050_GYRO_CONFIG, config_regs, sizeof(config_regs));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    mpu6050_write()

    MPU6050写数据I2C格式

    (1)前面我们知道了mpu6050_config()配置如何实现的,但是又有一个问题mpu6050_write()里面做了什么。
    (2)我们讲解mpu6050_write()函数前,需要知道主机和MPU6050的通讯格式。
    (3)原图在MPU-6000 and MPU-6050 Product Specification Revision 3.4手册的35页,也就是9.3 I2C Communications Protocol章节。直接看图,我不想过多讲解,图依旧很清晰了。

    在这里插入图片描述
    在这里插入图片描述

    函数解析

    (1)前面了解了MPU6050的写数据的格式之后,就可以开始解析函数了。
    <1>首先对sensor这个mpu6050_handle_t类型指针强制类型转换,前面我们说了,这个mpu6050_handle_t本质就是一个无符号类型指针,利用他能够做到高内聚低耦合。这里你只需传入创建的指针数据,然后强制类型转换即可对这个指针访问。
    <2>i2c_cmd_link_create()创建一个I2C 连接的句柄。这个听起来是不是很术语,看不懂。讲实话,我看到这个我也是懵逼的。我查看了一下这个函数的源码,个人理解就是,一个双向链表,里面存储了一些I2C数据传输的信息。之后的I2C数据传输,需要利用这个链表来进行设置。至于为什么使用链表,原因很简单,你不知道I2C通讯会传输多少个数据,而且I2C数据传输肯定是从头往下走,所以采用的链表是很好的决定。
    其实和上面对sensor这个mpu6050_handle_t类型指针强制类型转换是一个道理。I2C相关的函数都在i2c.c里面,用于实现高内聚低耦合。
    <3>i2c_master_start(),由于I2C协议规定,我们需要先发送一个起始信号。所以这个函数就是将起始信号写入缓存区。
    <4>i2c_master_write_byte(),由于I2C协议规定,你发送起始信号之后,I2C上的从机都被激活。这个时候主机需要告诉从机我是在和谁通讯,因此需要传入从机地址信息,也就是MPU6050地址信息。当从机知道主机是在和谁交互时候,I2C总线上其他没有被选中的从机将会进入休眠。只有主机和从机开始通讯。
    <5>主机和从机通讯建立完成之后,主机将会告诉从机,我要对那个寄存器进行操作。
    <6>i2c_master_write(),现在主机和从机联系和对那个寄存器操作都有一个清晰的认识,于是可以开始传输数据了。
    <7>i2c_master_stop(),数据传输完成之后主机需要告诉从机,我数据写完了,你可以休息了。
    <8>i2c_master_cmd_begin(),上述操作进行完之后,起始ESP32的I2C并没有真正的工作。上述就是在缓冲区写入数据,调用这个函数,才是真正的将缓冲区的数据输出。
    <9>i2c_cmd_link_delete(),通讯结束之后,我们需要调用这个函数删除与I2C的连接。

    /**
     * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
     * 
     * @param   sensor         mpu6050_create()函数创建的mpu6050_handle_t指针
     *         -reg_start_addr 要进行写入数据的寄存器
     *         -data_buf       写入寄存器中的数据
     *         -data_len       写入寄存器中的数据长度
     * 
     * @return  ESP_OK         MPU6050寄存器数据写入成功
     *         -ESP_FAIL       MPU6050寄存器数据写入失败
    */
    static esp_err_t mpu6050_write(mpu6050_handle_t sensor, 
    						       const uint8_t reg_start_addr, 
    						       const uint8_t *const data_buf, 
    						       const uint8_t data_len)
    {
        mpu6050_dev_t *sens = (mpu6050_dev_t *) sensor;
        esp_err_t  ret;
    
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        ret = i2c_master_start(cmd);
        assert(ESP_OK == ret);
        ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_WRITE, true);
        assert(ESP_OK == ret);
        ret = i2c_master_write_byte(cmd, reg_start_addr, true);
        assert(ESP_OK == ret);
        ret = i2c_master_write(cmd, data_buf, data_len, true);
        assert(ESP_OK == ret);
        ret = i2c_master_stop(cmd);
        assert(ESP_OK == ret);
        ret = i2c_master_cmd_begin(sens->bus, cmd, 1000 / portTICK_PERIOD_MS);
        i2c_cmd_link_delete(cmd);
    
        return ret;
    }
    
    • 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

    mpu6050_wake_up()

    使用介绍

    (1)这里只需要传入MPU6050的句柄即可唤醒MPU6050。

    /**
     * @brief  唤醒MPU6050
     * 
     * @param   sensor   mpu6050_create()函数创建的mpu6050_handle_t指针
     * @return  RT_EOK   MPU6050唤醒成功
     *         -RT_ERROR MPU6050唤醒失败
    */
    esp_err_t mpu6050_wake_up(mpu6050_handle_t sensor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    简单概述底层实现

    (1)这里其实也不难,就是对MPU6050的PWR_MGMT_1寄存器bit6清零即可。因为PWR_MGMT_1寄存器的bit6如果为1,MPU6050将会进入低功耗休眠状态。
    (2)至于为什么需要先读取MPU6050的数据,很简单,如果直接写入一个数据,可能导致其他数据位数据被破坏。

    esp_err_t mpu6050_wake_up(mpu6050_handle_t sensor)
    {
        esp_err_t ret;
        uint8_t tmp;
        ret = mpu6050_read(sensor, MPU6050_PWR_MGMT_1, &tmp, 1);
        if (ESP_OK != ret) {
            return ret;
        }
        tmp &= (~BIT6);
        ret = mpu6050_write(sensor, MPU6050_PWR_MGMT_1, &tmp, 1);
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    mpu6050_read()

    MPU6050读数据I2C格式

    (1)mpu6050_wake_up()函数里面有一个mpu6050_read()没有进行介绍,这里介绍一下。
    (2)MPU6050的读数据有两次起始信号,而写数据只有一次起始信号。第二次起始信号开始之后,就可以读数据了。
    (3)原图在MPU-6000 and MPU-6050 Product Specification Revision 3.4手册的36页,也就是9.3 I2C Communications Protocol章节。

    在这里插入图片描述

    函数解析

    (1)这里要根据上图一起理解。与mpu6050_write()函数相同部分我就不再赘述了。
    <1>依旧是强制类型转换,建立I2C连接,发送起始信号。
    <2>两个i2c_master_write_byte(),这里注意,虽然我们主机ESP32是要读取数据,但是第一次还是写数据,因为从机需要知道,主机接下来是要读取那个寄存器的数据。
    <3>第二次i2c_master_start()i2c_master_write_byte(),这个是告诉从机,主机已经可以开始读取你的数据了。
    <4>i2c_master_read(),读取数据,这个函数最后一个参数为I2C_MASTER_LAST_NACK表示主机每次收到数据返回一个ACK,不过主机最后一次收到数据返回NACK,告诉从机要停止发数据了。
    <5>最后依旧是发送停止信息,使用i2c_master_cmd_begin()函数将缓冲区数据输出。删除与I2C的联系。

    /**
     * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
     * 
     * @param   sensor         mpu6050_create()函数创建的mpu6050_handle_t指针
     *         -reg_start_addr 要进行读取数据的寄存器
     *         -data_buf       读取到的数据存入空间
     *         -data_len       要读取数据的长度
     * 
     * @return  ESP_OK         MPU6050寄存器数据读取成功
     *         -ESP_FAIL       MPU6050寄存器数据读取失败
    */
    static esp_err_t mpu6050_read(mpu6050_handle_t sensor, 
                                  const uint8_t reg_start_addr, 
                                  uint8_t *const data_buf, 
                                  const uint8_t data_len)
    {
        mpu6050_dev_t *sens = (mpu6050_dev_t *) sensor;
        esp_err_t  ret;
    
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        ret = i2c_master_start(cmd);
        assert(ESP_OK == ret);
        ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_WRITE, true);
        assert(ESP_OK == ret);
        ret = i2c_master_write_byte(cmd, reg_start_addr, true);
        assert(ESP_OK == ret);
        ret = i2c_master_start(cmd);
        assert(ESP_OK == ret);
        ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_READ, true);
        assert(ESP_OK == ret);
        ret = i2c_master_read(cmd, data_buf, data_len, I2C_MASTER_LAST_NACK);
        assert(ESP_OK == ret);
        ret = i2c_master_stop(cmd);
        assert(ESP_OK == ret);
        ret = i2c_master_cmd_begin(sens->bus, cmd, 1000 / portTICK_PERIOD_MS);
        i2c_cmd_link_delete(cmd);
    
        return ret;
    }
    
    • 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

    mpu6050_get_xxx简单介绍

    (1)因为本人精力有限,不可能每个函数都完完全全介绍一边。经过上面的了解,重要的部分读者应该都了解了。其他的一些函数,对底层实现感兴趣的可以自行查看MPU-6050_Register_Map手册,对照着代码进行理解。
    (2)上面的几个函数了解了之后,就只有如下这三个函数需要了解了。
    <1>mpu6050_get_acce()函数,获取MPU6050的加速度值。
    <2>mpu6050_get_gyro()函数,获取MPU6050的陀螺仪值。
    <2>mpu6050_get_temp()函数,获取MPU6050的温度值。
    (4)这三个函数,第一个都是传入的I2C句柄。(mpu6050_create()函数创建的mpu6050_handle_t指针)第二个参数略有不同:
    <1>mpu6050_get_acce()函数,他需要传入一个mpu6050_acce_value_t类型结构体指针,最终对数据进行处理是采用acce.acce_x方法。

    typedef struct {
        float acce_x;    //x轴加速度
        float acce_y;    //y轴加速度
        float acce_z;    //z轴加速度
    } mpu6050_acce_value_t;
    
    typedef struct {
        float gyro_x;    //x轴的角速度
        float gyro_y;    //y轴的角速度
        float gyro_z;    //z轴的角速度
    } mpu6050_gyro_value_t;
    
    typedef struct {
        float temp;      //MPU6050温度
    } mpu6050_temp_value_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    MPU6050组件使用

    单元测试函数简单介绍

    (1)在官方MPU6050的组件中,你会看到很多TEST_ASSERT开头的函数,这个是Unity测试单元。作用类似于assert的断言,当测试结果失败,将会终止程序进行复位操作。
    (2)GitHub链接:https://github.com/ThrowTheSwitch/Unity
    (3)函数简单介绍:
    <1>TEST_ASSERT_NOT_NULL_MESSAGE(),用于测试传入的第一个参数是不是空指针,如果是空指针控制台将会输出第二个参数数据,并且终止程序。
    <2>TEST_ASSERT_EQUAL(),判断第二个参数是否和第一个参数相等。如果不相等将会终止程序,并且输出信息。
    <3>TEST_ASSERT_EQUAL_MESSAGE(),判断第二个参数是否和第一个参数相等。如果不相等将会输出第三个参数数据。

    官方例程

    (1)如下为官方测试例程。

    /*
     * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include 
    #include "unity.h"
    #include "driver/i2c.h"
    #include "mpu6050.h"
    #include "esp_system.h"
    #include "esp_log.h"
    
    #define I2C_MASTER_SCL_IO 26      /*!< gpio number for I2C master clock */
    #define I2C_MASTER_SDA_IO 25      /*!< gpio number for I2C master data  */
    #define I2C_MASTER_NUM I2C_NUM_0  /*!< I2C port number for master dev */
    #define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */
    
    static const char *TAG = "mpu6050 test";
    static mpu6050_handle_t mpu6050 = NULL;
    
    /**
     * @brief i2c master initialization
     */
    static void i2c_bus_init(void)
    {
        i2c_config_t conf;
        conf.mode = I2C_MODE_MASTER;
        conf.sda_io_num = (gpio_num_t)I2C_MASTER_SDA_IO;
        conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
        conf.scl_io_num = (gpio_num_t)I2C_MASTER_SCL_IO;
        conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
        conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
        conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;
    
        esp_err_t ret = i2c_param_config(I2C_MASTER_NUM, &conf);
        TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C config returned error");
    
        ret = i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
        TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C install returned error");
    }
    
    /**
     * @brief i2c master initialization
     */
    static void i2c_sensor_mpu6050_init(void)
    {
        esp_err_t ret;
    
        i2c_bus_init();
        mpu6050 = mpu6050_create(I2C_MASTER_NUM, MPU6050_I2C_ADDRESS);
        TEST_ASSERT_NOT_NULL_MESSAGE(mpu6050, "MPU6050 create returned NULL");
    
        ret = mpu6050_config(mpu6050, ACCE_FS_4G, GYRO_FS_500DPS);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
    
        ret = mpu6050_wake_up(mpu6050);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
    }
    
    TEST_CASE("Sensor mpu6050 test", "[mpu6050][iot][sensor]")
    {
        esp_err_t ret;
        uint8_t mpu6050_deviceid;
        mpu6050_acce_value_t acce;
        mpu6050_gyro_value_t gyro;
        mpu6050_temp_value_t temp;
    
        i2c_sensor_mpu6050_init();
    
        ret = mpu6050_get_deviceid(mpu6050, &mpu6050_deviceid);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
        TEST_ASSERT_EQUAL_UINT8_MESSAGE(MPU6050_WHO_AM_I_VAL, mpu6050_deviceid, "Who Am I register does not contain expected data");
    
        ret = mpu6050_get_acce(mpu6050, &acce);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
        ESP_LOGI(TAG, "acce_x:%.2f, acce_y:%.2f, acce_z:%.2f\n", acce.acce_x, acce.acce_y, acce.acce_z);
    
        ret = mpu6050_get_gyro(mpu6050, &gyro);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
        ESP_LOGI(TAG, "gyro_x:%.2f, gyro_y:%.2f, gyro_z:%.2f\n", gyro.gyro_x, gyro.gyro_y, gyro.gyro_z);
    
        ret = mpu6050_get_temp(mpu6050, &temp);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
        ESP_LOGI(TAG, "t:%.2f \n", temp.temp);
    
        mpu6050_delete(mpu6050);
        ret = i2c_driver_delete(I2C_MASTER_NUM);
        TEST_ASSERT_EQUAL(ESP_OK, ret);
    }
    
    
    • 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
  • 相关阅读:
    [附源码]JAVA毕业设计技术的游戏交易平台(系统+LW)
    JavaScript常用事件详解
    一个方法用js生成随机双色球、大乐透
    Pytorch(一)——Pytorch基础知识
    进程调度算法之时间片轮转调度(RR),优先级调度以及多级反馈队列调度
    SpringBoot下关于SpringMVC拦截器的配置
    如何在 Ubuntu 上安装 EMQX MQTT 服务器
    Android入门第36天-以一个小动画说一下Android里的Handler的使用
    华为发布“十大发明”,包含计算、智能驾驶等新领域
    Javaweb之javascript的详细解析
  • 原文地址:https://blog.csdn.net/qq_63922192/article/details/133457076