由文章,linux设备树笔记__dts基本概念及语法,我们知道了基本概念,知道了大概的设备树节点及其属性,而节点下的属性大多是自定义,除了保留的几个属性,大多从.dts是无法知道其用途的,这个就需要看驱动是如何解析属性值的了,这点也可作技术细节的部分隐藏。

在源码的msm8x12\kernel\arch\arm\boot\dts下有很多xxx.dts,xxx.dtsi,一般一个board就有一个.dts,所以你能看见很多dts文件,即使一个SOC也可能有不同的dts,这样不同的dts会共用很多的设备,那么.dtsi就是起共用设备树的作用,一个板子dts可以像C语言一样来包含dtsi,如/include/ "msm8612-qrd-camera-sensor.dtsi"。

msm8x12\kernel\arch\arm\mach-msm\Makefile.boot关于编译msm8x12的dts部分。

# MSM8610zreladdr-$(CONFIG_ARCH_MSM8610) := 0x00008000dtb-$(CONFIG_ARCH_MSM8610)    += msm8610-v1-cdp.dtbdtb-$(CONFIG_ARCH_MSM8610)   += msm8610-v2-cdp.dtbdtb-$(CONFIG_ARCH_MSM8610)   += msm8610-v1-mtp.dtbdtb-$(CONFIG_ARCH_MSM8610)   += msm8610-v2-mtp.dtbdtb-$(CONFIG_ARCH_MSM8610)   += msm8610-rumi.dtbdtb-$(CONFIG_ARCH_MSM8610) += msm8610-sim.dtbdtb-$(CONFIG_ARCH_MSM8610)  += msm8610-v1-qrd-skuaa.dtbdtb-$(CONFIG_ARCH_MSM8610) += msm8610-v1-qrd-skuab.dtbdtb-$(CONFIG_ARCH_MSM8610) += msm8610-v2-qrd-skuaa.dtbdtb-$(CONFIG_ARCH_MSM8610) += msm8610-v2-qrd-skuab.dtb
ifeq ($(CONFIG_ARCH_MSM8610_CB01),y)dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-cb01.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D330S),y)dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d330s.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D500),y)dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d500.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D510),y)dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d510.dtb
endif

在msm8x12的SOC我们有3个板子型号,假设我们选择的是d500的型号,那么我们一般的不同硬件配置相关都是在board-seuci-d500.dtb中修改,其他的dtb目标文件都是共有的硬件配置,因为我们都是一样的SOC嘛。

/ {model = "Qualcomm MSM 8610v2 QRD SKUAB D510";compatible = "qcom,msm8610-qrd", "qcom,msm8610", "qcom,qrd";qcom,board-id = <0x6000b 3>;
};

其中model和compatible每个板子都有,前者是SOC描述,后者是board型号,后者一般用公司名加上soc型号组合,多个值表示兼容board,并且不要和其他board重名,qcom,board-id是自定义的,有的板子还有qcom,msm-id。

板子中的设备节点一般通过compatible来标示查找的,下面提取board-seuic-d500.dtb的部分设备进行解析并查看驱动是如何利用的:

LED设备:

&soc {
...gpio-leds {compatible = "gpio-leds";//驱动要匹配的关键字status = "ok";/*  scan_led  */red1_led {gpios = <&pm8110_gpios 1 0x0>;label = "scan";linux,default-trigger = "heartbeat";default-state = "off";};/*  charge_ing  */red2_led {gpios = <&pm8110_gpios 3 0x0>;label = "red1";linux,default-trigger = "battery-charging";default-state = "off";retain-state-suspended = "yes";};/*  charge_complete  */green_led {gpios = <&pm8110_gpios 2 0x0>;label = "green1";linux,default-trigger = "battery-full";default-state = "off";retain-state-suspended = "yes";};};
...
};

设备树中的SOC下一级的设备gpio-leds,有三个子节点,分别是扫描灯,充电中指示灯,充电完成指示灯,下面以充电完成指示灯来说明。

对应的驱动在msm8x12\kernel\drivers\leds\leds-gpio.c,驱动中使用of相关驱动来解析dts,且全局宏为CONFIG_OF_GPIO。

驱动中在probe前要匹配的关键字是compatible的值,如下,并且匹配优先级of_match_table > id_table > driver.name,且不管是id_table还是of_match_table都要以一个空元素结尾。

static const struct of_device_id of_gpio_leds_match[] = {{ .compatible = "gpio-leds", },{},
};
static struct platform_driver gpio_led_driver = {.probe        = gpio_led_probe,.remove       = __devexit_p(gpio_led_remove),.driver     = {.name   = "leds-gpio",.owner = THIS_MODULE,.of_match_table = of_gpio_leds_match,},
};module_platform_driver(gpio_led_driver);

匹配成功后才会调用probe,其中module_platform_driver(gpio_led_driver)是一个模块初始化和退出的宏:

#define module_platform_driver(__platform_driver) \
static int __init __platform_driver##_init(void) \
{ \return platform_driver_register(&(__platform_driver)); \
} \
module_init(__platform_driver##_init); \
static void __exit __platform_driver##_exit(void) \
{ \platform_driver_unregister(&(__platform_driver)); \
} \
module_exit(__platform_driver##_exit);

gpio_led_probe->gpio_leds_create_of()函数就是解析上述LED设备节点的过程:

static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node, *child;struct gpio_leds_priv *priv;int count = 0, ret;/* count LEDs in this device, so we know how much to allocate */for_each_child_of_node(np, child)count++;if (!count)return NULL;priv = kzalloc(sizeof_gpio_leds_priv(count), GFP_KERNEL);if (!priv)return NULL;for_each_child_of_node(np, child) {struct gpio_led led = {};enum of_gpio_flags flags;const char *state;const char *retain_state;led.gpio = of_get_gpio_flags(child, 0, &flags);//获取gpio序号及有效标志,实际上是通过gpios属性获取led.active_low = flags & OF_GPIO_ACTIVE_LOW;//如为OF_GPIO_ACTIVE_LOW即值为1,那么点亮set 0就是set 1,要进行逻辑取反led.name = of_get_property(child, "label", NULL) ? : child->name;//label属性为子节点设备名led.default_trigger =//默认的LED triggerof_get_property(child, "linux,default-trigger", NULL);state = of_get_property(child, "default-state", NULL);//开机默认的LED状态if (state) {if (!strcmp(state, "keep"))led.default_state = LEDS_GPIO_DEFSTATE_KEEP;else if (!strcmp(state, "on"))led.default_state = LEDS_GPIO_DEFSTATE_ON;elseled.default_state = LEDS_GPIO_DEFSTATE_OFF;}retain_state = of_get_property(child, "retain-state-suspended", NULL);//休眠时LED的状态if (retain_state) {if (!strcmp(retain_state, "yes"))led.retain_state_suspended = 1;elseled.retain_state_suspended = 0;}ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],//创建LED设备&pdev->dev, NULL);if (ret < 0) {of_node_put(child);goto err;}}return priv;err:for (count = priv->num_leds - 2; count >= 0; count--)delete_gpio_led(&priv->leds[count]);kfree(priv);return NULL;
}

如驱动中所示,大多通过of_xxx的API函数来解析dts设备节点属性,属性就是KEY-VALUE结构,至于值的意义完全由驱动来控制。比如of_get_property(child, "linux,default-trigger", NULL),其中child是当前设备节点,参数二就是待查找的属性,返回值是对应属性的值,此处就是对应于DTS的linux,default-trigger = "battery-full",进行解析,返回值应该为“battery-full”,至于这个LED trigger的意义及用法见我的另一篇文章《linux驱动____LED子系统笔记》。

NOTE:另外需要注意的是,对于属性名,是否可以随意定义,要看of的API代码,比如gpios属性名是API里使用的,用来指示使用的gpio号及有效标志位,就必须这么用不能改名;

/*** of_get_gpio_flags() - Get a GPIO number and flags to use with GPIO API* @np:       device node to get GPIO from* @index:  index of the GPIO* @flags: a flags pointer to fill in** Returns GPIO number to use with Linux generic GPIO API, or one of the errno* value on the error condition. If @flags is not NULL the function also fills* in flags for the GPIO.*/
static inline int of_get_gpio_flags(struct device_node *np, int index,enum of_gpio_flags *flags)
{return of_get_named_gpio_flags(np, "gpios", index, flags);
}

而对于像"linux,default-trigger"是可以随便定义的,因为它唯一使用的地方就是使用of相关API在驱动中获取属性值,完全由驱动开发者的思维逻辑控制的。

触屏

下面再举1个触屏的例子,同一个I2C总线挂了多个设备,DTS代码只贴出2个:

&soc {
...i2c@f9923000{/*synaptics v2.3 DS4/DS5*/synaptics_dsx@20 {//节点一般以设备名@addr组合compatible = "synaptics,dsx";//触屏设备名reg = <0x20>;//从设备I2C地址interrupt-parent = <&msmgpio>;//I2C的中断给谁处理interrupts = <1 0x2002>;//I2C中断给msmgpio处理,后面详细解释vdd-supply = <&pm8110_l19>;//I2C设备的VDD由电源管理芯片pm8110的pin_114供电vcc_i2c-supply = <&pm8110_l14>;//与上类似synaptics,pwr-reg-name = "vdd";//供电名synaptics,bus-reg-name = "vcc_i2c";//与上类似synaptics,irq-gpio = <&msmgpio 1 0x2008>;//具体中断资源连那个gpiosynaptics,irq-flags = <0x2002>;/* IRQF_ONESHOT | IRQF_TRIGGER_FALLING */synaptics,power-delay-ms = <160>;synaptics,reset-delay-ms = <100>;synaptics,swap-axes;//synaptics,x-flip;synaptics,y-flip;synaptics,irq-on-state = <0>;};akm09911@d {compatible = "qcom,nfc-akm09911";//电子罗盘设备名reg = <0x0d>;//I2C从地址vdd-supply = <&pm8110_l19>;vio-supply = <&pm8110_l14>;akm09911,layout = <3>; //厂商定制的芯片贴片时的布局标志akm09911,reset-pin = <35>;};}
...
}

驱动解析在msm8x12\kernel\drivers\input\touchscreen\synaptics_dsx\synaptics_dsx_i2c.c

dts设备匹配驱动,我们的DTS中compatible = "synaptics,dsx"与of_match_table是中的值是一致的,匹配成功!

#ifdef CONFIG_OF
static struct of_device_id synaptics_rmi4_of_match_table[] = {{.compatible = "synaptics,dsx",},{},
};
MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table);
#else
#define synaptics_rmi4_of_match_table NULL
#endif
static const struct i2c_device_id synaptics_rmi4_id_table[] = {{"synaptics_dsx_i2c", 0},{},
};
MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table);
static struct i2c_driver synaptics_rmi4_i2c_driver = {.driver = {.name = I2C_DRIVER_NAME,.owner = THIS_MODULE,.of_match_table = synaptics_rmi4_of_match_table,},.probe = synaptics_rmi4_i2c_probe,.remove = __devexit_p(synaptics_rmi4_i2c_remove),.id_table = synaptics_rmi4_id_table,
};

在synaptics_rmi4_i2c_probe()->parse_dt()中,

static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata)
{int retval;u32 value;const char *name;struct property *prop;struct device_node *np = dev->of_node;bdata->irq_gpio = of_get_named_gpio_flags(np,//中断gpio号,忽略flag"synaptics,irq-gpio", 0, NULL);retval = of_property_read_u32(np, "synaptics,irq-on-state",//中断处理时gpio电平是H还是L&value);if (retval < 0)bdata->irq_on_state = 0;elsebdata->irq_on_state = value;retval = of_property_read_u32(np, "synaptics,irq-flags", &value);//中断触发处理标志if (retval < 0)return retval;elsebdata->irq_flags = value;retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);//供电名,作为后面regulator_get的参数idif (retval == -EINVAL)bdata->pwr_reg_name = NULL;else if (retval < 0)return retval;elsebdata->pwr_reg_name = name;retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);//同上if (retval == -EINVAL)bdata->bus_reg_name = NULL;else if (retval < 0)return retval;elsebdata->bus_reg_name = name;if (of_property_read_bool(np, "synaptics,power-gpio")) {//DTS未使用到。上电时,power_gpio初始状态bdata->power_gpio = of_get_named_gpio_flags(np,"synaptics,power-gpio", 0, NULL);retval = of_property_read_u32(np, "synaptics,power-on-state",&value);if (retval < 0)return retval;elsebdata->power_on_state = value;} else {bdata->power_gpio = -1;}if (of_property_read_bool(np, "synaptics,power-delay-ms")) {//电源上电或resume时要等待的时间retval = of_property_read_u32(np, "synaptics,power-delay-ms",&value);if (retval < 0)return retval;elsebdata->power_delay_ms = value;} else {bdata->power_delay_ms = 0;}if (of_property_read_bool(np, "synaptics,reset-gpio")) {//DTS未使用bdata->reset_gpio = of_get_named_gpio_flags(np,"synaptics,reset-gpio", 0, NULL);retval = of_property_read_u32(np, "synaptics,reset-on-state",&value);if (retval < 0)return retval;elsebdata->reset_on_state = value;retval = of_property_read_u32(np, "synaptics,reset-active-ms",&value);if (retval < 0)return retval;elsebdata->reset_active_ms = value;} else {bdata->reset_gpio = -1;}if (of_property_read_bool(np, "synaptics,reset-delay-ms")) {//执行soft-reset时的等待时间retval = of_property_read_u32(np, "synaptics,reset-delay-ms",&value);if (retval < 0)return retval;elsebdata->reset_delay_ms = value;} else {bdata->reset_delay_ms = 0;}if (of_property_read_bool(np, "synaptics,max-y-for-2d")) {//DTS未使用,触屏报点的Y方向最大值retval = of_property_read_u32(np, "synaptics,max-y-for-2d",&value);if (retval < 0)return retval;elsebdata->max_y_for_2d = value;} else {bdata->max_y_for_2d = -1;}bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes");//触屏报点X,Y交换bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip");//X值变为关于X中轴对称bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip");//Y值变为关于Y中轴对称prop = of_find_property(np, "synaptics,cap-button-codes", NULL);//DTS未使用if (prop && prop->length) {bdata->cap_button_map->map = devm_kzalloc(dev,prop->length,GFP_KERNEL);if (!bdata->cap_button_map->map)return -ENOMEM;bdata->cap_button_map->nbuttons = prop->length / sizeof(u32);retval = of_property_read_u32_array(np,"synaptics,cap-button-codes",bdata->cap_button_map->map,bdata->cap_button_map->nbuttons);if (retval < 0) {bdata->cap_button_map->nbuttons = 0;bdata->cap_button_map->map = NULL;}} else {bdata->cap_button_map->nbuttons = 0;bdata->cap_button_map->map = NULL;}prop = of_find_property(np, "synaptics,vir-button-codes", NULL);//DTS未使用if (prop && prop->length) {bdata->vir_button_map->map = devm_kzalloc(dev,prop->length,GFP_KERNEL);if (!bdata->vir_button_map->map)return -ENOMEM;bdata->vir_button_map->nbuttons = prop->length / sizeof(u32);bdata->vir_button_map->nbuttons /= 5;retval = of_property_read_u32_array(np,"synaptics,vir-button-codes",bdata->vir_button_map->map,bdata->vir_button_map->nbuttons * 5);if (retval < 0) {bdata->vir_button_map->nbuttons = 0;bdata->vir_button_map->map = NULL;         }} else {bdata->vir_button_map->nbuttons = 0;bdata->vir_button_map->map = NULL;}return 0;
}

of_get_named_gpio_flags(np, "synaptics,irq-gpio", 0, NULL)从DTS中获取中断gpio即触屏的中断连接到soc哪个GPIO上,然后调用request_threaded_irq申请中断及处理函数。

of_property_read_u32(np, "synaptics,irq-on-state", &value),中断中使用:

static irqreturn_t synaptics_rmi4_irq(int irq, void *data)
{struct synaptics_rmi4_data *rmi4_data = data;const struct synaptics_dsx_board_data *bdata =rmi4_data->hw_if->board_data;if (gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state)//当前中断线gpio值与irq_on_state值一致才会报点goto exit;synaptics_rmi4_sensor_report(rmi4_data);exit:return IRQ_HANDLED;
}

of_property_read_u32(np, "synaptics,irq-flags", &value)是request_threaded_irq()申请中断处理资源的中断方式标志,对应于DTS中的synaptics,irq-flags = <0x2002>,那么这个值是什么意思,就是使用标准的中断方式定义,在msm8x12\kernel\include\linux\interrupt.h中有定义:

//hardware interrupt line behaviour flags
#define IRQF_TRIGGER_NONE   0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING    0x00000002
#define IRQF_TRIGGER_HIGH   0x00000004
#define IRQF_TRIGGER_LOW    0x00000008
#define IRQF_TRIGGER_MASK   (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE  0x00000010
//used only by the kernel as part of the irq handling routines.
#define IRQF_DISABLED       0x00000020
#define IRQF_SAMPLE_RANDOM  0x00000040
#define IRQF_SHARED     0x00000080
#define IRQF_PROBE_SHARED   0x00000100
#define __IRQF_TIMER        0x00000200
#define IRQF_PERCPU     0x00000400
#define IRQF_NOBALANCING    0x00000800
#define IRQF_IRQPOLL        0x00001000
#define IRQF_ONESHOT        0x00002000
#define IRQF_NO_SUSPEND     0x00004000
#define IRQF_FORCE_RESUME   0x00008000
#define IRQF_NO_THREAD      0x00010000
#define IRQF_EARLY_RESUME   0x00020000#define IRQF_TIMER        (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

中断标志定义都是掩码,基本常用的就是上下降沿、高低电平,还有常用的IRQF_DISABLED进行或组合。上述DTS的中断标志0x2002就是IRQF_ONESHOT | IRQF_TRIGGER_FALLING,即下降沿触发中断,且在中断thread未完成前不开放中断。

of_property_read_string(np, "synaptics,pwr-reg-name", &name)对应于DTS的synaptics,pwr-reg-name = "vdd",实际上是regulator的id,根据这个"vdd"的id,可查找到系统中的vdd-supply,然后控制供电行为的enable和disable。那么此处是如何做的呢?!

synaptics_rmi4_probe()->

synaptics_rmi4_get_reg()->

regulator_get(rmi4_data->pdev->dev.parent,bdata->pwr_reg_name)->

_regulator_get->

regulator_dev_lookup->

of_get_regulator->

regnode = of_parse_phandle(dev->of_node, prop_name, 0)

最后一个函数使用snprintf(prop_name, 32, "%s-supply", supply),也就是说返回的regnode是对应于id为“vdd-supply”的设备节点;说到底对于struct regulator *regulator_get(struct device *dev, const char *id),他对于dts的处理就是查找id为"$(id)-supply"对应的dts属性值指向的regulator并返回(此处对应于DTS的vdd-supply = <&pm8110_l19>,就是查找pm8110_119所引用的regulator并返回),这样驱动就能去控制供电行为了。这样在触屏suspend()和resume()是去对应调用regulator_disable()和regulator_enable()进行供电的关和开。

其他DTS属性大多是这样用的。

linux设备树笔记__基于msm8x10的基本分析相关推荐

  1. linux设备树笔记__dts基本概念及语法

    设备树手册(Device Tree Usage)原文地址:http://www.devicetree.org/Device_Tree_Usage 有关device tree数据格式的更完整技术说明,读 ...

  2. 【驱动】linux设备树笔记

    1.设备树相关工具dtc 1.1 简介 dtc(Device Tree Compiler):设备树编译器 设备树编译器(dtc)以给定格式的设备树作为输入,并输出另一种格式的设备树,用于引导嵌入式系统 ...

  3. Linux设备树语法详解【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备 ...

  4. linux spidev 应用_嵌入式Linux设备树语法总结

    1 设备树的说明 在写完嵌入式驱动总结后,对于设备树相关的语法和使用一直都想进行系统的描述,但是因为最近比较忙碌,所以一直拖到现在才完成初版,对于整个嵌入式Linux驱动开发中,设备树语法和构建是其中 ...

  5. Linux 设备树的使用技巧

    Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写.引入了设备树之后,驱动代码只负 ...

  6. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  7. Linux设备树led,linux设备树下LED灯控制

    linux设备树下LED灯控制 linux设备树下LED灯控制 原理图: 所以在设备树下子节点下插入gpioled节点: gpioled { #address-cells = <1>; # ...

  8. 【正点原子Linux连载】第四十三章 Linux设备树 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 探索Linux设备树:硬件描述与驱动程序的桥梁

    目录标题 引言:Linux设备树简介 | Introduction: Linux Device Tree Overview a. 设备树的背景与发展 | Background and Developm ...

最新文章

  1. Object-C---gt;Swift之(八)类和结构体
  2. 使用foreach循环遍历Collection集合
  3. 蚂蚁金服回应海外和科创板上市传言:暂无上市时间表
  4. 1.7 什么时候该改变开发_测试集和指标
  5. Toolbar的困惑
  6. c语言加密字母向右移两位,C语言二进制除法用左右移位来表示
  7. Unity清除一个物体所有的子物体
  8. python监控桌面捕捉,用Python从屏幕上捕获视频数据
  9. PDF文件点击打印按钮没反应
  10. linux关闭防火墙时出现问号乱码,linux文件名乱码问题的解决方...-tcp_wrappers防火墙配置方法-su 与 su - 的比较_169IT.COM...
  11. 基于CIM的新型智慧城市发展政策解析
  12. 新闻稿标题写作中的五大技巧
  13. vue 在线预览 word ,Excel,pdf,图片 数据流 内网文件流 亲测有效(word 目前支持docx文件以及doc文件(doc需要后端处理))
  14. 发现ULC(UltraLightClient)
  15. 操作的基本原则,每日必读【不断更新中】
  16. BLE MESH----Mesh beacons
  17. 程序设计学习(c++)(课堂学习2)
  18. 小狐狸VF的一些命令及技巧
  19. laravel 教程链接
  20. 使用nslookup命令检查DNS服务

热门文章

  1. python模拟浏览器上传文件_Python模拟浏览器上传文件脚本的方法(Multipart/form-data格式)...
  2. DTLS 技术要点解析
  3. Android中动画详细讲解
  4. 【Head First Servlets and JSP】笔记24:include指令与include动作 param动作 foward动作...
  5. 带你开发一个远程控制项目---->STM32+标准库+阿里云平台+传感器模块+远程显示。
  6. weblogic之T3反序列化
  7. 三千左右高性价比全单吉他推荐之VEAZEN费森S88系列,适合初学者和进阶购琴一步到位
  8. 小白都可以操作2021版(Github的注册与使用,超详细)
  9. 关于 django.db.migrations.exceptions.InconsistentMigrationHistory
  10. ES官网reference翻译文章(19)—Scripted Metric Aggregation