本文介绍了如何在 RT-Thread Studio 上使用 RT-Thread Nano,并基于 BearPI-IOT STM32L431RCT6 的基础工程进行讲解如何使用 I2C 设备接口及相关软件包使用。
BearPI-IOT board
「函数」 | 「描述」 |
---|---|
rt_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 |
rt_i2c_transfer() | 传输数据 |
「函数」 | 「描述」 |
---|---|
rt_i2c_bus_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 |
rt_i2c_transfer() | 传输数据 |
rt_i2c_master_send() | 发送数据 |
rt_i2c_master_recv() | 接收数据 |
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-04-20 RiceChen first version
*/
#ifndef __I2C_H__
#define __I2C_H__
#include <rtthread.h>
#ifdef __cplusplus
extern "C" {
#endif
#define RT_I2C_WR 0x0000
#define RT_I2C_RD (1u << 0)
#define RT_I2C_ADDR_10BIT (1u << 2) /* this is a ten bit chip address */
#define RT_I2C_NO_START (1u << 4)
#define RT_I2C_IGNORE_NACK (1u << 5)
#define RT_I2C_NO_READ_ACK (1u << 6) /* when I2C reading, we do not ACK */
#define RT_I2C_NO_STOP (1u << 7)
struct rt_i2c_config
{
char *name;
rt_uint8_t scl;
rt_uint8_t sda;
};
struct rt_i2c_msg
{
rt_uint16_t addr;
rt_uint16_t flags;
rt_uint16_t len;
rt_uint8_t *buf;
};
/*for i2c bus driver*/
struct rt_i2c_bus_device
{
struct rt_i2c_config *config;
rt_uint16_t flags;
rt_uint16_t addr;
struct rt_mutex lock;
rt_uint32_t timeout;
rt_uint32_t retries;
void *priv;
};
struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name);
rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num);
rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus,
rt_uint32_t cmd,
rt_uint32_t arg);
rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
const rt_uint8_t *buf,
rt_uint32_t count);
rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
rt_uint8_t *buf,
rt_uint32_t count);
rt_inline rt_err_t rt_i2c_bus_lock(struct rt_i2c_bus_device *bus, rt_tick_t timeout)
{
return rt_mutex_take(&bus->lock, timeout);
}
rt_inline rt_err_t rt_i2c_bus_unlock(struct rt_i2c_bus_device *bus)
{
return rt_mutex_release(&bus->lock);
}
int rt_i2c_core_init(void);
#ifdef __cplusplus
}
#endif
#endif
「函数」 | 「描述」 |
---|---|
rt_i2c_core_init() | I2C 总线初始化 |
rt_i2c_bus_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 |
rt_i2c_transfer() | 数据传输 |
rt_i2c_control() | I2C 总线配置 |
rt_i2c_master_send() | 发送数据 |
rt_i2c_master_recv() | 接收数据 |
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-08-21 RiceChen the first version
*/
#include <board.h>
#include "drv_i2c.h"
#ifdef RT_USING_I2C
enum
{
#ifdef RT_USING_I2C1
I2C1_INDEX,
#endif
#ifdef RT_USING_I2C2
I2C2_INDEX,
#endif
};
static struct rt_i2c_config i2c_config[] =
{
#ifdef RT_USING_I2C1
RT_I2C1_CONFIG,
#endif
#ifdef RT_USING_I2C2
RT_I2C1_CONFIG
#endif
};
static struct rt_i2c_bus_device i2c_bus[sizeof(i2c_config) / sizeof(i2c_config[0])] = {0};
static void rt_i2c_configure(struct rt_i2c_bus_device *bus)
{
rt_uint8_t scl_pin = bus->config->scl;
rt_uint8_t sda_pin = bus->config->sda;
rt_pin_mode(scl_pin, PIN_MODE_OUTPUT_OD);
rt_pin_mode(sda_pin, PIN_MODE_OUTPUT_OD);
rt_pin_write(scl_pin, PIN_HIGH);
rt_pin_write(sda_pin, PIN_HIGH);
}
static void rt_i2c_set_sda(struct rt_i2c_bus_device *bus, rt_uint32_t state)
{
rt_uint8_t sda_pin = bus->config->sda;
if (state)
{
rt_pin_write(sda_pin, PIN_HIGH);
}
else
{
rt_pin_write(sda_pin, PIN_LOW);
}
}
static void rt_i2c_set_scl(struct rt_i2c_bus_device *bus, rt_uint32_t state)
{
rt_uint8_t scl_pin = bus->config->scl;
if (state)
{
rt_pin_write(scl_pin, PIN_HIGH);
}
else
{
rt_pin_write(scl_pin, PIN_LOW);
}
}
static rt_uint32_t rt_i2c_get_sda(struct rt_i2c_bus_device *bus)
{
rt_uint8_t sda_pin = bus->config->sda;
return rt_pin_read(sda_pin);
}
static rt_uint32_t rt_i2c_get_scl(struct rt_i2c_bus_device *bus)
{
rt_uint8_t scl_pin = bus->config->scl;
return rt_pin_read(scl_pin);
}
static void rt_i2c_udelay(rt_uint32_t us)
{
rt_hw_us_delay(us);
}
#define SET_SDA(bus, val) rt_i2c_set_sda(bus, val)
#define SET_SCL(bus, val) rt_i2c_set_scl(bus, val)
#define GET_SDA(bus) rt_i2c_get_sda(bus)
#define GET_SCL(bus) rt_i2c_get_scl(bus)
#define SDA_L(bus) SET_SDA(bus, 0)
#define SDA_H(bus) SET_SDA(bus, 1)
#define SCL_L(bus) SET_SCL(bus, 0)
static rt_err_t SCL_H(struct rt_i2c_bus_device *bus)
{
rt_tick_t start;
SET_SCL(bus, 1);
if(rt_i2c_get_scl(bus))
{
goto done;
}
start = rt_tick_get();
while (!GET_SCL(bus))
{
if ((rt_tick_get() - start) > 100)
return -RT_ETIMEOUT;
rt_thread_delay(100);
}
done:
rt_i2c_udelay(1);
return RT_EOK;
}
static void rt_i2c_start(struct rt_i2c_bus_device *bus)
{
SDA_L(bus);
rt_i2c_udelay(1);
SCL_L(bus);
}
static void rt_i2c_restart(struct rt_i2c_bus_device *bus)
{
SDA_H(bus);
SCL_H(bus);
rt_i2c_udelay(1);
SDA_L(bus);
rt_i2c_udelay(1);
SCL_L(bus);
}
static void rt_i2c_stop(struct rt_i2c_bus_device *bus)
{
SDA_L(bus);
rt_i2c_udelay(1);
SCL_H(bus);
rt_i2c_udelay(1);
SDA_H(bus);
rt_i2c_udelay(1);
}
rt_inline rt_bool_t rt_i2c_waitack(struct rt_i2c_bus_device *bus)
{
rt_bool_t ack;
SDA_H(bus);
rt_i2c_udelay(1);
if (SCL_H(bus) < 0)
{
return -RT_ETIMEOUT;
}
ack = !GET_SDA(bus);
SCL_L(bus);
return ack;
}
static rt_int32_t rt_i2c_writeb(struct rt_i2c_bus_device *bus, rt_uint8_t data)
{
rt_int32_t i;
rt_uint8_t bit;
for (i = 7; i >= 0; i--)
{
SCL_L(bus);
bit = (data >> i) & 1;
SET_SDA(bus, bit);
rt_i2c_udelay(1);
if (SCL_H(bus) < 0)
{
return -RT_ETIMEOUT;
}
}
SCL_L(bus);
rt_i2c_udelay(1);
return rt_i2c_waitack(bus);
}
static rt_int32_t rt_i2c_readb(struct rt_i2c_bus_device *bus)
{
rt_uint8_t i;
rt_uint8_t data = 0;
SDA_H(bus);
rt_i2c_udelay(1);
for (i = 0; i < 8; i++)
{
data <<= 1;
if (SCL_H(bus) < 0)
{
return -RT_ETIMEOUT;
}
if (GET_SDA(bus))
data |= 1;
SCL_L(bus);
rt_i2c_udelay(1);
}
return data;
}
static rt_size_t rt_i2c_send_bytes(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg *msg)
{
rt_int32_t ret;
rt_size_t bytes = 0;
const rt_uint8_t *ptr = msg->buf;
rt_int32_t count = msg->len;
rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
while (count > 0)
{
ret = rt_i2c_writeb(bus, *ptr);
if ((ret > 0) || (ignore_nack && (ret == 0)))
{
count --;
ptr ++;
bytes ++;
}
else if (ret == 0)
{
return 0;
}
else
{
return ret;
}
}
return bytes;
}
static rt_err_t rt_i2c_send_ack_or_nack(struct rt_i2c_bus_device *bus, int ack)
{
if (ack)
SET_SDA(bus, 0);
rt_i2c_udelay(1);
if (SCL_H(bus) < 0)
{
return -RT_ETIMEOUT;
}
SCL_L(bus);
return RT_EOK;
}
static rt_size_t rt_i2c_recv_bytes(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg *msg)
{
rt_int32_t val;
rt_int32_t bytes = 0; /* actual bytes */
rt_uint8_t *ptr = msg->buf;
rt_int32_t count = msg->len;
const rt_uint32_t flags = msg->flags;
while (count > 0)
{
val = rt_i2c_readb(bus);
if (val >= 0)
{
*ptr = val;
bytes ++;
}
else
{
break;
}
ptr ++;
count --;
if (!(flags & RT_I2C_NO_READ_ACK))
{
val = rt_i2c_send_ack_or_nack(bus, count);
if (val < 0)
return val;
}
}
return bytes;
}
static rt_int32_t rt_i2c_send_address(struct rt_i2c_bus_device *bus,
rt_uint8_t addr, rt_int32_t retries)
{
rt_int32_t i;
rt_err_t ret = 0;
for (i = 0; i <= retries; i++)
{
ret = rt_i2c_writeb(bus, addr);
if (ret == 1 || i == retries)
break;
rt_i2c_stop(bus);
rt_i2c_udelay(1);
rt_i2c_start(bus);
}
return ret;
}
static rt_err_t rt_i2c_bit_send_address(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg *msg)
{
rt_uint16_t flags = msg->flags;
rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
rt_uint8_t addr1, addr2;
rt_int32_t retries;
rt_err_t ret;
retries = ignore_nack ? 0 : bus->retries;
if (flags & RT_I2C_ADDR_10BIT)
{
addr1 = 0xf0 | ((msg->addr >> 7) & 0x06);
addr2 = msg->addr & 0xff;
ret = rt_i2c_send_address(bus, addr1, retries);
if ((ret != 1) && !ignore_nack)
{
return -RT_EIO;
}
ret = rt_i2c_writeb(bus, addr2);
if ((ret != 1) && !ignore_nack)
{
return -RT_EIO;
}
if (flags & RT_I2C_RD)
{
rt_i2c_restart(bus);
addr1 |= 0x01;
ret = rt_i2c_send_address(bus, addr1, retries);
if ((ret != 1) && !ignore_nack)
{
return -RT_EIO;
}
}
}
else
{
addr1 = msg->addr << 1;
if (flags & RT_I2C_RD)
addr1 |= 1;
ret = rt_i2c_send_address(bus, addr1, retries);
if ((ret != 1) && !ignore_nack)
return -RT_EIO;
}
return RT_EOK;
}
rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus,
rt_uint32_t cmd,
rt_uint32_t arg)
{
return RT_EOK;
}
rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num)
{
struct rt_i2c_msg *msg;
rt_int32_t i, ret;
rt_uint16_t ignore_nack;
rt_i2c_start(bus);
for (i = 0; i < num; i++)
{
msg = &msgs[i];
ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
if (!(msg->flags & RT_I2C_NO_START))
{
if (i)
{
rt_i2c_restart(bus);
}
ret = rt_i2c_bit_send_address(bus, msg);
if ((ret != RT_EOK) && !ignore_nack)
{
goto out;
}
}
if (msg->flags & RT_I2C_RD)
{
ret = rt_i2c_recv_bytes(bus, msg);
if (ret >= 1)
;
if (ret < msg->len)
{
if (ret >= 0)
ret = -RT_EIO;
goto out;
}
}
else
{
ret = rt_i2c_send_bytes(bus, msg);
if (ret >= 1)
;
if (ret < msg->len)
{
if (ret >= 0)
ret = -RT_ERROR;
goto out;
}
}
}
ret = i;
out:
rt_i2c_stop(bus);
return ret;
}
rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
const rt_uint8_t *buf,
rt_uint32_t count)
{
rt_err_t ret;
struct rt_i2c_msg msg;
msg.addr = addr;
msg.flags = flags;
msg.len = count;
msg.buf = (rt_uint8_t *)buf;
ret = rt_i2c_transfer(bus, &msg, 1);
return (ret > 0) ? count : ret;
}
rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
rt_uint8_t *buf,
rt_uint32_t count)
{
rt_err_t ret;
struct rt_i2c_msg msg;
RT_ASSERT(bus != RT_NULL);
msg.addr = addr;
msg.flags = flags | RT_I2C_RD;
msg.len = count;
msg.buf = buf;
ret = rt_i2c_transfer(bus, &msg, 1);
return (ret > 0) ? count : ret;
}
struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name)
{
rt_size_t bus_num = sizeof(i2c_bus) / sizeof(i2c_bus[0]);
for(int i = 0; i < bus_num; i++)
{
if(rt_strncmp(i2c_bus[i].config->name, bus_name, RT_NAME_MAX) == 0)
{
return &i2c_bus[i];
}
}
return RT_NULL;
}
int rt_i2c_core_init(void)
{
rt_size_t bus_num = sizeof(i2c_bus) / sizeof(i2c_bus[0]);
for(int i = 0; i < bus_num; i++)
{
i2c_bus[i].config = &i2c_config[i];
rt_i2c_configure(&i2c_bus[i]);
}
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_i2c_core_init);
#endif /* RT_USING_I2C */
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-04-20 RiceChen first version
*/
#ifndef __DRV_I2C_H__
#define __DRV_I2C_H__
#include <drv_common.h>
#include <board.h>
#include "pin.h"
#include "i2c.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef RT_USING_I2C1
#define RT_I2C1_SCL_PIN GET_PIN(B, 6)
#define RT_I2C1_SDA_PIN GET_PIN(B, 7)
#define RT_I2C1_CONFIG \
{ \
.name = "i2c1", \
.scl = RT_I2C1_SCL_PIN, \
.sda = RT_I2C1_SDA_PIN, \
}
#endif
#ifdef RT_USING_I2C2
#define RT_I2C2_SCL_PIN GET_PIN(B, 8)
#define RT_I2C2_SDA_PIN GET_PIN(B, 9)
#define RT_I2C2_CONFIG \
{ \
.name = "i2c2", \
.scl = RT_I2C2_SCL_PIN, \
.sda = RT_I2C2_SDA_PIN, \
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* __DRV_GPIO_H__ */
#include <rtthread.h>
#include <rtdevice.h>
#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称 */
#define AHT10_ADDR 0x38 /* 从机地址 */
#define AHT10_CALIBRATION_CMD 0xE1 /* 校准命令 */
#define AHT10_NORMAL_CMD 0xA8 /* 一般命令 */
#define AHT10_GET_DATA 0xAC /* 获取数据命令 */
static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C总线设备句柄 */
static rt_bool_t initialized = RT_FALSE; /* 传感器初始化状态 */
/* 写传感器寄存器 */
static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t *data)
{
rt_uint8_t buf[3];
struct rt_i2c_msg msgs;
rt_uint32_t buf_size = 1;
buf[0] = reg; //cmd
if (data != RT_NULL)
{
buf[1] = data[0];
buf[2] = data[1];
buf_size = 3;
}
msgs.addr = AHT10_ADDR;
msgs.flags = RT_I2C_WR;
msgs.buf = buf;
msgs.len = buf_size;
/* 调用I2C设备接口传输数据 */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
/* 读传感器寄存器数据 */
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
struct rt_i2c_msg msgs;
msgs.addr = AHT10_ADDR;
msgs.flags = RT_I2C_RD;
msgs.buf = buf;
msgs.len = len;
/* 调用I2C设备接口传输数据 */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
static void read_temp_humi(float *cur_temp, float *cur_humi)
{
rt_uint8_t temp[6];
write_reg(i2c_bus, AHT10_GET_DATA, RT_NULL); /* 发送命令 */
rt_thread_mdelay(400);
read_regs(i2c_bus, 6, temp); /* 获取传感器数据 */
/* 湿度数据转换 */
*cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1 << 20);
/* 温度数据转换 */
*cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20) - 50;
}
static void aht10_init(const char *name)
{
rt_uint8_t temp[2] = {0, 0};
/* 查找I2C总线设备,获取I2C总线设备句柄 */
i2c_bus = rt_i2c_bus_device_find(name);
if (i2c_bus == RT_NULL)
{
rt_kprintf("can't find %s device!\n", name);
}
else
{
write_reg(i2c_bus, AHT10_NORMAL_CMD, temp);
rt_thread_mdelay(400);
temp[0] = 0x08;
temp[1] = 0x00;
write_reg(i2c_bus, AHT10_CALIBRATION_CMD, temp);
rt_thread_mdelay(400);
initialized = RT_TRUE;
}
}
static void i2c_aht10_sample(int argc, char *argv[])
{
float humidity, temperature;
char name[RT_NAME_MAX];
humidity = 0.0;
temperature = 0.0;
if (argc == 2)
{
rt_strncpy(name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(name, AHT10_I2C_BUS_NAME, RT_NAME_MAX);
}
if (!initialized)
{
/* 传感器初始化 */
aht10_init(name);
}
if (initialized)
{
/* 读取温湿度数据 */
read_temp_humi(&temperature, &humidity);
rt_kprintf("read aht10 sensor humidity : %d.%d %%\n", (int)humidity, (int)(humidity * 10) % 10);
if( temperature >= 0 )
{
rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(temperature * 10) % 10);
}
else
{
rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(-temperature * 10) % 10);
}
}
else
{
rt_kprintf("initialize sensor failed!\n");
}
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample);
实例代码运行现象:
``` C
msh >i2c_aht10_sample i2c1
read aht10 sensor humidity : 90.0 %
read aht10 sensor temperature: 25.33°C
msh >
static void as7341(int argc, char *argv[])
{
static as7341_device_t dev = RT_NULL;
if (argc > 1)
{
if (!strcmp(argv[1], "probe"))
{
if (argc >= 3)
{
/* initialize the sensor when first probe */
if (!dev || strcmp(dev->i2c->config->name, argv[2])) // 修改点1
{
/* deinit the old device */
if(dev)
{
rt_kprintf("Deinit as7341\n");
as7341_deinit(dev);
}
dev = as7341_init(argv[2], eSpm);
if(!dev)
{
rt_kprintf("as7341 probe failed, check input args\n");
}else
{
rt_kprintf("as7341 probed, addr:0x%x\n", AS7341_ADDR) ;
}
}
}
else
{
as7341_usage();
}
}
else if (!strcmp(argv[1], "read"))
{
if (dev)
{
if(!strcmp(argv[2], "spectral"))
{
MODE_ONE_DATA_t data1;
MODE_TOW_DATA_t data2;
as7341_start_measure(dev, eF1F4ClearNIR);
data1 = as7341_read_spectral_data_one(dev);
rt_kprintf("F1(405-425nm): %d\n", data1.ADF1);
rt_kprintf("F2(435-455nm): %d\n", data1.ADF2);
rt_kprintf("F3(470-490nm): %d\n", data1.ADF3);
rt_kprintf("F4(505-525nm): %d\n", data1.ADF4);
as7341_start_measure(dev, eF5F8ClearNIR);
data2 = as7341_read_spectral_data_tow(dev);
rt_kprintf("F5(545-565nm): %d\n", data2.ADF5);
rt_kprintf("F6(580-600nm): %d\n", data2.ADF6);
rt_kprintf("F7(620-640nm): %d\n", data2.ADF7);
rt_kprintf("F8(670-690nm): %d\n", data2.ADF8);
rt_kprintf("Clear: %d\n", data2.ADCLEAR);
rt_kprintf("NIR: %d\n", data2.ADNIR);
}
else if(!strcmp(argv[2], "flicker"))
{
rt_uint8_t freq = 0;
freq = as7341_read_flicker_data(dev);
if(freq == 1)
{
rt_kprintf("Unknown frequency\n");
}
else if(freq == 0)
{
rt_kprintf("No flicker\n");
}
else
{
rt_kprintf("freq: %dHz\n", freq);
}
}
else
{
as7341_usage();
}
}
else
{
rt_kprintf("Please using 'as7341 probe <i2c dev name>' first\n");
}
}
else
{
as7341_usage();
}
}
else
{
as7341_usage();
}
}
msh >as7341 probe i2c1
as7341 id: 0x24
as7341 probed, addr:0x39
msh >
msh >as7341 read spectral
F1(405-425nm): 1
F2(435-455nm): 3
F3(470-490nm): 4
F4(505-525nm): 5
F5(545-565nm): 7
F6(580-600nm): 6
F7(620-640nm): 7
F8(670-690nm): 4
Clear: 22
NIR: 2
msh >
本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!