一、Sensor模型介绍

Zephry针对传感器这一类设备定义了一套统一传感器驱动接口,如果你的传感器想要在Zephry上实现自己的驱动,那么需要遵守这一套接口模型标准。

因为市面上的传感器大多数所反馈的数据基本上都是差不多的,如水平X、Y轴,温度、气压等一些数据,Zephry为了统一管理实现了一套标准API与宏定义,要求开发者们根据去实现这一套标准API,在结合对应的宏定义来反馈传感器对应的值。

Zephry不关心传感器底层是如何实现的,也不关心如何与传感器进行通讯,它要求开发者们实现这套标准统一API,至于底层怎么写随开发者们,无限制,但要求传递参数与反馈值是统一的。

Zephry要求你的传感器设备需要定义在总线上,即定义在dts文件的总线上,让Zephry知道你的设备是通过什么进行通讯的,是I2C、SPI,基于这些Zephry才能解析并调用对应的API,同时这些通讯方式也要求开发者们自己实现,如果你的传感器支持I2C、SPI,那么你就需要写两套代码给Zephry。

二、统一接口

Zephry提供了五个统一开发接口:

传感器接口模型定义在"include/dirvers/sensor.h"文件中

1. sensor_sample_fetch

1.1 函数介绍

函数原型

作用

返回值

int sensor_sample_fetch(struct device *dev); 将传感器的所有类型数据放置内存 0成功,非0失败

1.2 参数介绍

参数名

类型

作用

dev struct device * 指向实例化传感器设备指针

1.3 备注

如果想要从传感器获取数据需要先执行这个函数,目的是将传感器的数据放置到内存里,我们就可以往内存里存取数据了,这里的类型是指气压,温度,湿度或者水平数据

2. sensor_sample_fetch_chan

1.1 函数介绍

函数原型

作用

返回值

int sensor_sample_fetch_chan(struct device *dev, enum sensor_channel type); 将传感器指定类型数据放置内存 0成功,非0失败

1.2 参数介绍

参数名

类型

作用

dev struct device * 指向实例化传感器设备指针

3. sensor_channel_get

1.1 函数介绍

函数原型

作用

返回值

int sensor_channel_get(struct device *dev, enum sensor_channel chan, struct sensor_value *val); 从内存获取指定类型的数据 0成功,非0失败

1.2 参数介绍

参数名

类型

作用

dev struct device * 指向实例化传感器设备指针
chan enum sensor_channel chan, 数据类型
val struct sensor_value * 存放数据的结构体指针

4. sensor_attr_set

1.1 函数介绍

函数原型

作用

参数

int sensor_attr_set(struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val); 设置传感器参数,如采样频率,触发阈值 0成功,非0失败

1.2 参数介绍

参数名

类型

作用

dev struct device * 指向实例化传感器设备指针
chan enum sensor_channel 数据类型
attr enum sensor_attribute 要设定的属性类型
val const struct sensor_value * 要设定的值

5. sensor_trigger_set

1.1 函数介绍

函数原型

作用

参数

static inline int sensor_trigger_set(const struct device *dev, struct sensor_trigger *trig, sensor_trigger_handler_t handler) 激活传感器触发器,并设置触发器的处理函数 0成功,非0失败

1.2 参数介绍

参数名

类型

作用

dev struct device * 指向实例化传感器设备指针
trig struct sensor_trigger * 触发器激活属性
handler sensor_trigger_handler_t 触发器的激活函数入口地址

三、数据类型

1.  传感器数据类型

枚举值供sensor_sample_fetch_chan、sensor_channel_get函数使用

以下枚举类型为“enum sensor_channel”

枚举

作用

表示单位

枚举

作用

表示单位

SENSOR_CHAN_ACCEL_X X轴加速度 m/s^2(每秒变化多少米)
SENSOR_CHAN_ACCEL_Y y轴加速度 m/s^2(每秒变化多少米)
SENSOR_CHAN_ACCEL_Z z轴加速度 m/s^2(每秒变化多少米)
SENSOR_CHAN_ACCEL_XYZ 任意加速度 NULL
SENSOR_CHAN_GYRO_X 绕X轴角速度 radians/s(每秒变化多少角度)
SENSOR_CHAN_GYRO_Y 绕y轴角速度 radians/s(每秒变化多少角度)
SENSOR_CHAN_GYRO_Z 绕z轴角速度 radians/s(每秒变化多少角度)
SENSOR_CHAN_GYRO_XYZ 任意角速度 NULL
SENSOR_CHAN_MAGN_X X轴地磁 Gauss(高斯)
SENSOR_CHAN_MAGN_Y y轴地磁 Gauss(高斯)
SENSOR_CHAN_MAGN_Z z轴地磁 Gauss(高斯)
SENSOR_CHAN_MAGN_XYZ 任意轴地磁 NULL
SENSOR_CHAN_TEMP 温度 ℃(摄氏度)
SENSOR_CHAN_DIE_TEMP 器件温度 ℃(摄氏度)
SENSOR_CHAN_AMBIENT_TEMP 环境温度 ℃(摄氏度)
SENSOR_CHAN_PRESS 大气压 1000Pa(千帕)
SENSOR_CHAN_PROX 距离(靠近)传感器 1表示接近
SENSOR_CHAN_HUMIDITY 湿度 %(百分比)
SENSOR_CHAN_LIGHT 可见光强 lux(勒克斯)
SENSOR_CHAN_IR 红外光强 lux(勒克斯)
SENSOR_CHAN_RED 红色光强 lux(勒克斯)
SENSOR_CHAN_GREEN 绿色光强 lux(勒克斯)
SENSOR_CHAN_BLUE 蓝色光强 lux(勒克斯)
SENSOR_CHAN_ALTITUDE 高度传感器 m(米)
SENSOR_CHAN_PM_1_0 PM1.0传感器 ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米)
SENSOR_CHAN_PM_2_5 PM2.5传感器 ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米)
SENSOR_CHAN_PM_10 PM2.5传感器 ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米)
SENSOR_CHAN_DISTANCE 距离传感器 m(米)
SENSOR_CHAN_CO2 CO2传感器, ppm(浓度)
SENSOR_CHAN_VOC VOC传感器, ppm(浓度)
SENSOR_CHAN_VOLTAGE 电压, V(电压)
SENSOR_CHAN_CURRENT 电流, A(电流)
SENSOR_CHAN_ALL 所有类型数据 NULL

1.1 备注

以上的表示单位是要求获取时的返回单位,传感器不一定要返回对应的单位,但是在调用此API时在返回时需要进行一次转换,转换为对应的单位并返回,这是Zephry定义的一个规范,你可以不遵守,没有强制条件,但如果不遵守的话会不符合规范,你的驱动无法提供给别人,只能自己用。

你的驱动要根据以上数据类型去实现对应的数据获取功能,如果里面没有你的数据类型,可以自己定义。

四、数据格式

1. sensor_value

1.1 结构体原型

struct sensor_value {s32_t val1;s32_t val2;
};

1.2 成员介绍

成员名

类型

作用

val1 s32_t 整数
val2 s32_t 小数

2. 备注

Zephry为了防止部分机器可能没有FPU或传感器与当前开发板的内存大小不一致,产生浮点数溢出或值不匹配使用一个结构体来表示整数部分与小数部分,这样有效解决了在传递浮点数时出现的精度问题,以及不支持浮点数运算的硬件条件。

现在GLIB C有一套自己的浮点数运算库,不依赖FPU,但是效率较慢,GLIB C是C语言的运行库。

五、工作参数

1. 传感器参数类型

此类型供sensor_attr_set设置时使用

以下枚举类型为“enum sensor_attribute”

一般情况下这些工作参数都是编译期间已经在DTS文件中定义好了,Zephry在编译期间就已经确定它的工作参数,在运行期间也是可以修改的

枚举

作用

SENSOR_ATTR_SAMPLING_FREQUENCY 传感器采样频率(具体含义由实际驱动实现决定,例如:一秒测量多少次)
SENSOR_ATTR_LOWER_THRESH 最低触发阈值
SENSOR_ATTR_UPPER_THRESH 最高触发阈值
SENSOR_ATTR_SLOPE_TH 斜率(任意运动)触发
SENSOR_ATTR_SLOPE_DUR 斜率维持超过一定时间触发
SENSOR_ATTR_OVERSAMPLING 过采样参数
SENSOR_ATTR_FULL_SCALE 传感器量程
SENSOR_ATTR_OFFSET 传感器值校正,sensor_channel_get返回的传感器值将被改值偏置final_value = sensor_value + offset
SENSOR_ATTR_CALIB_TARGET 传感器自身校正,用芯片内部的算法校正传感器的某个或者所有轴(传感器内部校正功能)

六、触发模式

1. 触发类型

对于一些传感器支持中断,根据传感器应用场景通常有不同的触发模式,Zephry提供了一套标准触发类型

这些类型供sensor_trigger_set函数使用

枚举类型:“enum sensor_trigger_type”

枚举

作用

SENSOR_TRIG_TIMER 定时触发
SENSOR_TRIG_DATA_READY 传感器数据准备好触发
SENSOR_TRIG_DELTA 通道量有连续变化时触发(需设置SENSOR_ATTR_SLOPE_TH和SENSOR_ATTR_SLOPE_DUR)
SENSOR_TRIG_NEAR_FAR 接近或者远离触发
SENSOR_TRIG_THRESHOLD 超过配置阈值触发(需设置SENSOR_ATTR_LOWER_THRESH和SENSOR_ATTR_UPPER_THRESH)
SENSOR_TRIG_TAP 单击触发
SENSOR_TRIG_DOUBLE_TAP 双击触发

注意sensor_attr_set仅仅是设置了工作参数,它并不会开启触发中断,开启触发中断需要sensor_trigger_set,它会根据sensor_attr_set设置的工作参数来触发对应的中断类型

七、驱动实现

开发者们需要根据sensor_driver_api成员接口进行实现,这个结构体就是最开始说的统一API的定义,开发者们的传感器驱动代码需要放到“drivers/sensor/”目录下,这是Zephry下的一个编写规范

开发者们根据传感器实现如下五个API,这些API在最开始就已经介绍过了,这里简单列出来:

  • sensor_sample_fetch
  • sensor_sample_fetch_chan
  • sensor_channel_get
  • sensor_attr_set
  • sensor_trigger_set

如我要实现sensor_channel_get

只需要将原型声明一致,名称倒无所谓,最后的时候我们会通过sensor_driver_api这个结构体里的函数指针去指向我们的函数就可以了,最后在调用DT_INST_FOREACH_STATUS_OKAY驱动注册宏将我们的驱动注册到Zephry内核中

static int mysensor_channel_get(struct device *dev,enum sensor_channel chan,struct sensor_value *val)
{//这里写你的实现
}

针对SPI和I2C通讯的传感器,也要有一套注册流程,一般情况下我们会先写好传感器数据处理的API,至于通讯的在另外实现,因为I2C和SPI就是数据交互不涉及到业务代码,所以单独实现一个I2C与SPI通讯的模块并通过特定宏注册到Sensor模型,Zephry会根据dts文件里的定义来调用对应的接口去获取数据,然后将获取到的数据在传递给业务API

1. BME280注册分析

1.1 什么是BME280

BME280是一个温度,气压、湿度三合一的传感器,可以通过它获取当前温度与气压还有湿度,支持I2C与SPI通讯接口

1.2 注册流程分析

BME280的驱动代码在“drivers/sensor/bem280”目录下

BME280的示例代码在“samples/sensor/bme280”目录下

这里为了让大家更了解Zephry如何注册我们设备,以及如何知道我们传感器使用的是什么通讯协议,从BME280驱动实现给大家分析一下

BME280实现了业务层的API与SPI、I2C通讯的三个模块。

在注册时会将I2C与SPI也注册到Zephry里

SPI的注册

#define BME280_CONFIG_SPI(inst)                     \{                               \.bus = DEVICE_DT_GET(DT_INST_BUS(inst)),        \.bus_io = &bme280_bus_io_spi,               \.bus_config.spi_cfg =                   \SPI_CONFIG_DT_INST(inst,            \BME280_SPI_OPERATION,    \0),              \}

在编译期间Zephry会解析DTS里的描述,并将实例化的结构体传递进来,这里可以看到将bus总线的指向,这里可以关注bus_io与bus_config这两个成员

它俩的作用就是告诉Zephry SPI的模块在哪,其中SPI总线是哪根线,驱动名是什么也是在DTS文件中写好的,到时候都会传递进来。

其中bme280_bus_io_spi这个结构体就是bme280下spi模块的实现

原型如下:

const struct bme280_bus_io bme280_bus_io_spi = {.check = bme280_bus_check_spi,.read = bme280_reg_read_spi,.write = bme280_reg_write_spi,
};

然后注册到Zephry里就可以了

I2C的注册

#define BME280_CONFIG_I2C(inst)                     \{                               \.bus = DEVICE_DT_GET(DT_INST_BUS(inst)),        \.bus_io = &bme280_bus_io_i2c,               \.bus_config.i2c_addr = DT_INST_REG_ADDR(inst),      \}

与SPI注册大同小异

然后在声明一个宏注册所有的模块

#define BME280_DEFINE(inst)                     \static struct bme280_data bme280_data_##inst;           \static const struct bme280_config bme280_config_##inst =    \COND_CODE_1(DT_INST_ON_BUS(inst, spi),          \(BME280_CONFIG_SPI(inst)),          \(BME280_CONFIG_I2C(inst)));         \DEVICE_DT_INST_DEFINE(inst,                 \bme280_chip_init,              \bme280_pm_ctrl,                \&bme280_data_##inst,               \&bme280_config_##inst,             \POST_KERNEL,                   \CONFIG_SENSOR_INIT_PRIORITY,           \&bme280_api_funcs);

这个宏会将bme280的data以及config、api、io通讯模块全部注册进来了,最后一步会调用DT_INST_FOREACH_STATUS_OKAY这个宏去注册,这个宏会将DTS里状态为OK的节点创建一个结构体,并传递进来

DT_INST_FOREACH_STATUS_OKAY(BME280_DEFINE)

这样BME280_DEFINE宏里的inst就变成dts文件里定义的设备详细描述结构体了,然后去注册就可以了。

这里讲一下Zephry是如何知道我们获取的是BME280这个结构体的,我们拆开DT_INST_FOREACH_STATUS_OKAY宏函数看一下

#define DT_INST_FOREACH_STATUS_OKAY(fn) \COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT),   \(UTIL_CAT(DT_FOREACH_OKAY_INST_,        \DT_DRV_COMPAT)(fn)),      \())

可以看到里面用DT_HAS_COMPAT_STATUS_OKAY这个宏去取的,其中DT_DRV_COMPAT这个宏就是要序列化DTS节点的名字

这个宏在bme240.h中有定义

#define DT_DRV_COMPAT bosch_bme280

这个名字是COMPAT的,这里可以看下bme240 dts文件里的compat的定义

 bme280@76 {compatible = "bosch,bme280";status = "okay";label = "BME280";reg = <0x76>;};

compatible表示的是厂商与设备名,用“,”分割开,但是Zephry编译器在实例化的时会将","替换为“_”,这是因为C语言里面不允许使用特殊符号,所以这也是为什么dts里是"bosch,bme280",而在引用时却是"bosch_bme280"的原因

至于Zephry是如何知道你的传感器是怎么通讯的,然后调用对应接口的,这里给大家参考一下我之前移植stm32f746g_disco的一个写法

&i2c1 {pinctrl-0 = <&i2c1_scl_pb8 &i2c1_sda_pb9>;status = "okay";clock-frequency = <I2C_BITRATE_FAST>;bme280@76 {compatible = "bosch,bme280";status = "okay";label = "BME280";reg = <0x76>;};
};

可以看到我bme280是写在i2c1下的,到时候实例化的时候这些硬件信息都会传递给我们的驱动注册宏函数里,刚刚分析的注册函数里有一条代码:

.bus = DEVICE_DT_GET(DT_INST_BUS(inst)),

这段代码就是去取当前总线类型,我们当前是i2c1,就会将这个总线取出来,类型为i2c,编号为1的这条线上,然后就是这条线的一些硬件信息,到时候这些硬件信息都会传递给驱动,让驱动知道该去哪个地址找进行控制

这里需要注意status="okay"代表这是一个驱动节点,会将这个节点传递给驱动,如果没有okay不会视为驱动节点,不会进行驱动注册时的传递

八、测试

1. 基于BME280驱动写一个Test

1.1 开发

如果不清楚如何使用Drive以及如何知道Drive注册时的名称可以参考这篇文章:Zephry I2C和SPI驱动器介绍和操作FM24V10闪存

1.1.1 包含基本头文件

#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>

注意这里不需要包含我们驱动的头文件,只需要包含sensor的头文件就可以了,因为基于它的模型,我们只需要保证我们的dev是指向我们的驱动就可以了,因为Zephry会根据okay为标志去将Drivers目录下的驱动进行注册实例化,到时候调用的时会读取dev的名称然后去调用对应的实现API,会通过Device Name来找对应的注册好的驱动结构体,然后去调用。

1.1.2 获取我们的设备

const struct device *dev = DEVICE_DT_GET_ANY("BME280");

1.1.3 获取气压,温度,湿度

while (1) {struct sensor_value temp, press, humidity;sensor_sample_fetch(dev);sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",temp.val1, temp.val2, press.val1, press.val2,humidity.val1, humidity.val2);k_sleep(K_MSEC(1000));}

1.1.4 完整代码

#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>void main(){const struct device *dev = DEVICE_DT_GET_ANY("BME280");if(dev == NULL){printk("get drive error\n");return;}while (1) {//获取设备句柄struct sensor_value temp, press, humidity;//将传感器数据读入到内存sensor_sample_fetch(dev);//读取数据sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);//打印printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",temp.val1, temp.val2, press.val1, press.val2,humidity.val1, humidity.val2);//延迟一秒k_sleep(K_MSEC(1000));}}

1.1.5 运行结果

temp: 24.080000; press: 100.887019; humidity: 55.473632
temp: 24.080000; press: 100.886230; humidity: 55.484375
temp: 24.080000; press: 100.886144; humidity: 55.484375
temp: 24.070000; press: 100.886222; humidity: 55.496093

2. 触发方式获取数据

这个需要传感器支持中断方式,触发方式也比较简单,使用的API以及结构体类型都已经介绍过了

#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>//触发函数
static void trigger_handler(struct device *dev, struct sensor_trigger *trig)
{//通过触发方式获取temp护具struct sensor_value temp;//读入内存sensor_sample_fetch(dev);//读取数据sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);//打印printf("trigger fired, temp %d.%06d\n", temp.val1, temp.val2);
}void main(void)
{//获取传感器struct device *dev = device_get_binding("Trigger_SenSor");if (dev == NULL) {printf("device not found.  aborting test.\n");return;}//触发参数struct sensor_value val;//触发类型struct sensor_trigger trig;//设置最高阈值触发为26.0度val.val1 = 26;val.val2 = 0;//设置26度为高阈值sensor_attr_set(dev, SENSOR_CHAN_AMBIENT_TEMP,SENSOR_ATTR_UPPER_THRESH, &val);//设置触发类型trig.type = SENSOR_TRIG_THRESHOLD;          //触发类型为超出这个预设阈值trig.chan = SENSOR_CHAN_AMBIENT_TEMP;       //以温度为阈值来源//设定触发状态,并设置触发处理函数if (sensor_trigger_set(dev, &trig, trigger_handler)) {printf("Could not set trigger.  aborting test.\n");return;}//进入循环等待while (1) {k_sleep(2000);}
}

当温度高出26度时会自动跳转到trigger_handler函数进行处理

Zephry传感器模型介绍和bme240测试相关推荐

  1. 乐玩自动化测试模块_自动化测试模型(一)自动化测试模型介绍

    一个自动化测试框架就是一个集成体系,在这一体系中包含测试功能的函数库,测试数据源,测试对象标准,以及各种可重用的模块.自动化测试在发展过程中经历了以下几个阶段,模块驱动测试,数据驱动测试及对象驱动测试 ...

  2. GPT模型介绍并且使用pytorch实现一个小型GPT中文闲聊系统

    文章目录 GPT模型介绍 无监督训练方式 模型结构 微调 下游任务输入形式 GPT-2 GPT-3 pytorch实现一个小型GPT中文闲聊系统 GPT模型介绍 GPT与BERT一样也是一种预训练模型 ...

  3. OSI(open system internet)七层模型介绍以及NAT(Network Address Translation)技术详解

    文章目录 1- OSI七层模型介绍 2- 物理层 (1)作用 (2)常见协议 3- 数据链路层 (1)作用 (2)常见协议 [1]ARP协议(地址解析) [2]RARP协议(逆地址解析) [3]PPP ...

  4. PanoSim仿真模型--传感器模型之单目相机

    目前,PanoSim 内置 7 类共 14 个传感器模型.传感器的一般化模型如图 5.22 所示. 图 5.22 传感器的一般化模型示意图 每一个传感器模型由三个重要部分构成:输入变量.模型的内外参数 ...

  5. 基于Python的岭回归与LASSO回归模型介绍及实践

    基于Python的岭回归与LASSO回归模型介绍及实践 这是一篇学习的总结笔记 参考自<从零开始学数据分析与挖掘> [中]刘顺祥 著 完整代码及实践所用数据集等资料放置于:Github 岭 ...

  6. 三维点云语义分割模型介绍

    三维点云语义分割模型介绍 1 三维深度学习简介 1.1 三维数据表达方式 2 PointNet 2.1 点云的属性 2.1.1 无序性 2.1.2 关联性 2.1.3 不变性 2.2 key modu ...

  7. 【传感器sensor】机器人/无人驾驶常用传感器模型、选型与安装

    系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录 前言 一.常用传感器类型及模型特性介绍[核心] 1.轮式编码器(提供里程计 ...

  8. 深度神经网络经典模型介绍

    经典模型的特点介绍 VGG,GoogleNet,ResNet,Inception-ResNet-v2 1. VGG模型 VGG又分为VGG16和VGG19, 分别在AlexNet的基础上将层数增加到1 ...

  9. 逆变器simulink模型——处理器在环测试(PIL)

    文章目录 逆变器simulink模型--处理器在环测试(PIL) 1 处理器在环测试基本思想 1.1 仿真模型基本介绍 1.2 处理器在环测试概念 2 处理器在环测试流程 2.1 测试模型介绍 2.2 ...

最新文章

  1. vue-快速原型开发
  2. Java基础结构语句和IDEA使用和数组
  3. 以相关组为基础的3D物体识别
  4. 坦克世界 与服务器连接中断,坦克世界怎么老是显示与服务器连接已中断
  5. 二叉树的创建、前序遍历、中序遍历、后序遍历
  6. mysql 数组变量_如何在MySQL中模拟数组变量?
  7. 【原译】汇编编程之:Hello World!详解- 好文!!!
  8. vue保存页面的值_vue前端页面跳转参数传递及存储
  9. 大学四年只学java_大学四年废了怎么办,不如来学java
  10. 《机器学习实战》学习总结(五)K-means算法原理
  11. 王彪20162321 2016-2017-2 《程序设计与数据结构》第6周学习总结
  12. html与js简单小游戏,使用js写了一个特别简单的小游戏
  13. wps出现安装installer_我安装WPS2002时显示,该计算机缺少installscript引擎,请运行i
  14. html中http502怎么解决,Http Error 502 解决方法
  15. linux 如何清理垃圾文件,Linux系统怎样清理垃圾文件
  16. 游戏出海Get,TikTok联手Zynga推出一款基于HTML5打造的手机游戏
  17. 两台计算机如何打印机共享打印机,多台电脑怎样共享一台打印机?方法就是这么简单!...
  18. 终端文本编辑神器--Vim命令详解。如何配置使用Vim、Vim插件?
  19. jQuery - 基于serializeArray的serializeObject
  20. 从Scratch少儿编程收获的启示

热门文章

  1. 2022年最高效,踏实的Python学习笔记以及Python学习规划
  2. 卡尔曼滤波(kalman filter)超详细推导
  3. Matlab p文件解密,pcode解密,甚至exe文件解密!
  4. 2021京东618活动脚本最新版
  5. FPGA配置启动详解系列(一)——配置文件详解
  6. pandas第四章——变形
  7. 图像视图ImageView:图像拉伸演示
  8. R入门(二)---快捷键、plot基础画图,多图拼接
  9. Separable Convolution(深度可分离卷积)
  10. java计算机毕业设计教学质量评价系统源程序+mysql+系统+lw文档+远程调试