任务动机:用USB代替网络通讯,实现Cartographer上位机与下位机之间的消息传递。

任务描述:根据任务动机,研发实现用USB代替网络通讯,形成文档。

1. 在Toybrick-RK3399 ProD上编译Linux Kernel

这是个漫长且无聊的过程,因为有关这方面的教程少之又少,在本人无数次踩坑下终于搞定。感兴趣的小伙伴可以打开官方教程链接体验下想找教程却没有教程然后砸电脑未遂的心情。

1.1 编译原理

在RK3399 ProD里面,Linux的Kernel和rootfs(根文件系统)是分开地址存放的,rootfs是在Kernel之上运行的,所有的驱动和最基础的指令都存放在Kernel里面,各个厂商根据自己的特性在Kernel上开发自己的操作系统(例如Fedora和Ubuntu)。板子可以在不用重新编译内核的情况下更换操作系统,在本文档中,我们主要讲如何编译驱动到kernel里面,并支持多种操作系统。我用的rootfs链接(Ubuntu):百度云网盘提取码:q9my

1.2 编译过程

首先,RK3399 ProD是A53+A7的芯片,你需要特殊的gcc编译器,然后再编译。

git clone https://github.com/friendlyarm/prebuilts.git -b master --depth 1
cd prebuilts/gcc-x64
cat toolchain-6.4-aarch64.tar.gz* | sudo tar xz -C /

然后再编辑你的.bashrc,把下面的东西加到bashrc的最底部。

export PATH=/opt/FriendlyARM/toolchain/6.4-aarch64/bin:$PATH
export GCC_COLORS=auto

接下来,你需要下载Linux Kernel源码,由于官方给的源码太多坑,我把我修改后的直接上传到gitee供国人下载,方便快捷。

git clone -b kernel-joybrick https://gitee.com/harryzhangabc/ros_bridge-and-catographer-setup.git

这里介绍下如何给驱动添加USB RNIDS/gagnet网络功能。

cd /path/to/your/ros_bridge-and-catographer-setup/dir/
make menuconfig

然后把下面的几个选项操作成如下方式

<M>   USB Gadget Drivers
<M>     USB functions configurable through configfs
<M>     Ethernet Gadget (with CDC Ethernet support)
[*]       RNDIS support (NEW)

接着在该目录下编译内核。

./make.sh linux prod

编译完成后会生成boot_linux.img文件,把这个文件烧入到3399pro对应的区域中烧写教程,只要烧这个文件,其他的都不用管。当然你想偷懒的话可以用我编译好的:D百度网盘提取码:pt7p
        最后看下你编译好的文件,有几个驱动模块文件需要手动复制到RK3399 ProD里面手动加载

drivers/usb/gadget/function/u_ether.ko
drivers/usb/gadget/function/usb_f_ecm_subset.ko
drivers/usb/gadget/function/usb_f_ecm.ko
drivers/usb/gadget/function/usb_f_rndis.ko
drivers/usb/gadget/legacy/g_ether.ko
drivers/usb/gadget/libcomposite.ko

然后在设备上,依次加载上述模块

insmod libcomposite.ko
insmod u_ether.ko
insmod usb_f_rndis.ko
insmod usb_f_ecm.ko
insmod usb_f_ecm_subset.ko
insmod g_ether.ko

注意: 要先加载 libcomposite.ko 和 u_ether.ko,后面的模块才可以加载进去。

1.3 IP设置

  • IP 地址设置

用数据线连接 PC 机和设备的 OTG 接口,在 PC 机中执行 lsusb 命令可以查看到 USB 以太网设备,即说明连接成功。

firefly@Desktop:~$ lsusb
Bus 002 Device 003: ID 09da:5814 A4Tech Co., Ltd.
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 005: ID 04f2:b2ea Chicony Electronics Co., Ltd Integrated Camera [ThinkPad]
Bus 001 Device 004: ID 0a5c:21e6 Broadcom Corp. BCM20702 Bluetooth 4.0 [ThinkPad]
Bus 001 Device 003: ID 147e:1002 Upek Biometric Touchchip/Touchstrip Fingerprint Sensor
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 003: ID 0525:a4a2 Netchip Technology, Inc. Linux-USB Ethernet/RNDIS Gadget
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
  • 在设备中 IP 的设置

输入执行 ifconfig -a 命令,可以查看到以下信息:

root@firefly:~# ifconfig -a# eth0 是有线网卡
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500inet 168.168.100.48  netmask 255.255.0.0  broadcast 168.168.255.255inet6 fe80::1351:ae2f:442e:e436  prefixlen 64  scopeid 0x20<link>ether 8a:4f:c3:77:94:ac  txqueuelen 1000  (Ethernet)RX packets 9759  bytes 897943 (897.9 KB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 236  bytes 35172 (35.1 KB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0device interrupt 42  base 0x8000...# usb0 是虚拟的 usb 网卡
usb0: flags=4098<BROADCAST,MULTICAST>  mtu 1500ether 4a:81:b1:34:d2:ad  txqueuelen 1000  (Ethernet)RX packets 0  bytes 0 (0.0 B)RX errors 0  dropped 0  overruns 0  frame 0TX packets 0  bytes 0 (0.0 B)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

然后给 usb0 网卡自定义一个适当的 IP,注意要设置 usb0 的 IP 和有线网卡 eth0 的 IP 不在同一网段!!

sudo ifconfig usb0 192.168.2.222 up
  • 在 PC 机中 IP 的设置

首先,需要在设置里设置板子的ip,由于我设置的是192.168.2.222,那么直接在系统设置里面设置ip地址为手动,填入ip就行。(系统不同,这里就不放图了!!!)

# 设置 USB 网卡的 IP 地址和设备的 usb0 的 IP 地址在同一网段
firefly@Desktop:~$ sudo ifconfig enp0s20u2i1 192.168.2.95
#设置默认网关:要设置为设备 usb0 的 ip 地址,因为后面要通过 usb0 来进行流量的转发
firefly@Desktop:~$ sudo route add default gw 192.168.2.222

设置完设备和 PC 机的 IP 后,使用USB-C/USB-A线连接到电脑,其中,电脑对板子的ip为192.168.2.222,板子对电脑的ip地址是192.168.2.95,他们是可以互相 ping 通的,PC 机也可以用 ssh 命令登录到设备。

然后,我们使用性能测试工具ipref测试下带宽。在USB3.0接口下,带宽约为250Mbit/s左右(30MByte/s),满足日常传输使用。

配置过程中要注意以下几点:

  1. 对应好上述步骤中自己设备上的各个 IP 地址,注意设备上的 USB 虚拟网卡 IP 和有线网络 IP 不在同一网段;
  2. PC 机虚拟 USB 网卡的网关要设置为设备虚拟 USB 网卡的 IP 地址;
  3. 设备上的虚拟网卡 IP 地址、IP 转发功能、流量转发规则等设置会在设备重启后恢复,然后大家可以自行查找资料如何开机自动设置这些配置。

1.4 配置ROS

  • 在RK3399上(从机)

首先编辑/etc/hosts

sudo vi /etc/hosts

然后在中间加入以下内容

192.168.2.95 server-HB

名字自取啥都行,IP根据你自己情况来定 然后编辑.bashrc

sudo vi ~/.bashrc

加入以下内容

export ROS_HOSTNAME=server-HB
export ROS_MASTER_URI=http://192.168.2.95:11311
export ROS_IP=192.168.2.222
  • 在电脑上

编辑.bashrc

sudo gedit ~/.bashrc

加入下面内容

export ROS_MASTER_URI=http://192.168.2.95:11311

ROS连接大功告成!

1.5 测试

在电脑上输入以下命令

roscore
rosrun turtlesim turtlesim_node

然后在RK3399上打开一个终端

rosrun turtlesim turtle_teleop_key

然后你就可以控制小乌龟行走了!USBC到此配置完成!到目前为止ROS传输demo均已配置完成,Cartographer也能跑通,上位机可以直接获取到Cartographer的话题信息(这里就不放图了),剩余的硬件驱动/适配上位机程序部分需要一些时间,预计下周完成。

2. 如何在任意板子上编译RK3399/RK3399 Pro的Kernel

2.1 不同板子所对应的设备树

一般来说,不同板子都有对应的特制功能从而设置了对应的特殊硬件(比如I2C、SPI设备),这些硬件需要在内核注册相关dts节点才能使用。因此,我们在购买板子的时候商家已经注册好了这些dts节点供我们使用,我们只要直接编译内核就好,如果是我们自己的板子,就需要自己去编写相对应的节点和驱动。
Linux设备树详解

2.2 RK3399/RK3399 Pro的原始设备树

对于这款芯片而言,rockchip官方已经给出了相关的基础设备树,其芯片所有的外设的功能已经预制在/kernel/arch/arm64/boot/dts/rockchip中的rk3399.dtsi和rk3399pro.dtsi中,你只需要创建一个你自己的.dtsi,然后在里面加入你想使用的外设设备,然后再写一个驱动.c文件放到/kernel/drivers里面即可。以下是自行编写并实现PWM驱动的一个示例:

//为了省时间,我直接在原厂板子上提供的.dtsi进行修改,文件位于/kernel/arch/arm64/boot/dts/rockchip/rk3399pro-toybrick.dtsi,同时,你要自己画板子的话,可以参考这个文件的写法,写一个自己的板级dtsi文件。
// #include <dt-bindings/pwm/pwm.h>
// #include <dt-bindings/gpio/gpio.h>
// #include <dt-bindings/pinctrl/rockchip.h>
// #include <dt-bindings/input/input.h>
// #include <dt-bindings/display/drm_mipi_dsi.h>
// #include <dt-bindings/sensor-dev.h>// #include "rk3399pro.dtsi"
// #include "rk3399-opp.dtsi"
//注:如果是自己写文件的话,请手动加入以上这些库文件,然后再继续进行编写pwm1: pwm_1 {status = "okay";compatible = "pwm_1";pwm_id = <1>;};          //添加一个pwm节点backlight: backlight {        //在这个上面添加compatible = "pwm-backlight";......
编写驱动文件#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>#include <dt-bindings/pwm/pwm.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/init.h>#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>#define SYS_DEV_CONFIG 1#define PWM_1_SET_PERIOD        _IOW('A', 0x11, unsigned int)
#define PWM_1_GET_PERIOD        _IOR('A', 0x12, unsigned int)
#define PWM_1_SET_DUTY          _IOW('A', 0x13, unsigned int)
#define PWM_1_GET_DUTY          _IOR('A', 0x14, unsigned int)
#define PWM_1_ENABLE            _IO('A', 0x15)
#define PWM_1_DISABLE           _IO('A', 0x16)static int gval = 0;
struct pwm_data {int     pwm_id;struct pwm_device       *pwm;unsigned int            period;unsigned int pwm_period_ns;unsigned int max_period;unsigned int min_period;unsigned int duty_ns;bool    enabled;
};
struct pwm_data g_pdata;#ifdef SYS_DEV_CONFIG
static ssize_t pwm_store(struct device *dev, \struct device_attribute *attr, const char *buf,size_t count)
{unsigned long state;int c,ret;ret = kstrtoul(buf, 10, &state);if (state > g_pdata.max_period)state = g_pdata.max_period;else if (state < g_pdata.min_period)state = g_pdata.min_period;c = state;if (ret)return ret;g_pdata.enabled = true;pwm_config(g_pdata.pwm, c, g_pdata.pwm_period_ns);pwm_enable(g_pdata.pwm);gval = c;return count;
}
static ssize_t pwm_show(struct device *dev, \struct device_attribute *attr, char *buf)
{return sprintf(buf, "%u\n", gval);
}static struct kobject *pwm_kobj;
static DEVICE_ATTR(pwm, (S_IWUSR|S_IRUSR|S_IWGRP|S_IRGRP), pwm_show, pwm_store);
#endifstatic int pwm_status_update(struct pwm_data *pdata)  //控制占空比、周期
{if (pdata->enabled)return 0;pdata->duty_ns = pdata->duty_ns * pdata->period / 100;    pwm_enable(pdata->pwm);pwm_config(pdata->pwm, pdata->duty_ns, g_pdata.pwm_period_ns);pdata->enabled = true;gval = pdata->duty_ns;return 0;
}
ssize_t pwm_parse_dt(struct pwm_data *pdata, struct platform_device *pdev)  //寻找已经注册的参数
{struct device_node *np = pdev->dev.of_node;const __be32 *id, *min_period, *max_period, *duty_ns;int  len;id = of_get_property(np, "pwm_id", &len);if (id)pdata->pwm_id = be32_to_cpu(*id);min_period = of_get_property(np, "min_period", &len);if (min_period)pdata->min_period = be32_to_cpu(*min_period);max_period = of_get_property(np, "max_period", &len);if (max_period)pdata->max_period = be32_to_cpu(*max_period);pdata->pwm_period_ns = pdata->max_period - pdata->min_period;duty_ns = of_get_property(np, "duty_ns", &len);if (duty_ns)pdata->duty_ns = be32_to_cpu(*duty_ns);return 0;
}static void update_parameter(struct pwm_data data)
{struct pwm_data *pw_data = &data;pwm_status_update(pw_data);
}static int pwm_1_open(struct inode *inode, struct file *filp)
{g_pdata.enabled = true;update_parameter(g_pdata);return 0;
}static int pwm_1_release(struct inode *inode, struct file *filp)
{   g_pdata.enabled = false;update_parameter(g_pdata);return 0;
}static ssize_t pwm_1_read(struct file *filp, char __user *buf, size_t len, loff_t *pos)
{ return 0;
}static ssize_t pwm_1_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{return 0;
}static unsigned int pwm_1_get_period(void)
{return (g_pdata.period / 1000000);
}static long pwm_1_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)    //驱动注册成功后,会在/dev下创建一个/pwm1的接口
{unsigned int period;unsigned int duty;void __user *argp = (void __user *)arg;switch (cmd){case PWM_1_SET_PERIOD:if (argp == NULL) {printk("1: invalid argument.");return -EINVAL;}if (copy_from_user(&period, argp, sizeof(unsigned int))) {printk("copy_from_user failed.");return -EFAULT;}g_pdata.period = period;update_parameter(g_pdata);break;case PWM_1_GET_PERIOD:period = pwm_1_get_period();if (copy_to_user(argp, &period, sizeof(unsigned int))) {printk("copy_to_user failed.");return -EFAULT;}break;case PWM_1_SET_DUTY    :if (argp == NULL) {printk("1: invalid argument.");return -EINVAL;}if (copy_from_user(&duty, argp, sizeof(unsigned int))) {printk("copy_from_user failed.");return -EFAULT;}if ((duty < 0) || (duty > 100)) {printk("1: invalid argument.");return -EINVAL;}g_pdata.duty_ns = duty;update_parameter(g_pdata);break;case PWM_1_GET_DUTY    :if (copy_to_user(argp, &g_pdata.duty_ns, sizeof(unsigned int))) {printk("copy_to_user failed.");return -EFAULT;}break;case PWM_1_ENABLE:g_pdata.enabled = true;update_parameter(g_pdata);break;case PWM_1_DISABLE:g_pdata.enabled = false;update_parameter(g_pdata);break;default:printk("pwm: cmd error!\n");return -EFAULT;}return 0;
}struct file_operations pwm_1_fops = { //PWM1操作函数入口.owner = THIS_MODULE,.open = pwm_1_open,.release = pwm_1_release,.write = pwm_1_write,.read = pwm_1_read,.unlocked_ioctl = pwm_1_ioctl
};struct miscdevice pwm_1_dev =
{  .minor  =   MISC_DYNAMIC_MINOR,.fops   =   &pwm_1_fops,.name   =   "pwm",
};static int pwm_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;struct pwm_data *pdata = pdev->dev.platform_data;int ret;if (!np) {dev_err(&pdev->dev, "Device Tree node missing\n");return -EINVAL;}pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);if (!pdata)return -ENOMEM;if (np)ret = pwm_parse_dt(pdata, pdev);pdata->enabled = false;pdata->pwm = pwm_request(pdata->pwm_id, "pwm_1");if (IS_ERR(pdata->pwm)) {dev_err(&pdev->dev, "unable to request legacy PWM\n");ret = PTR_ERR(pdata->pwm);goto err;}if (pdata->pwm_period_ns > 0)pwm_set_period(pdata->pwm, pdata->pwm_period_ns);pdata->period = pwm_get_period(pdata->pwm);g_pdata = *pdata;pwm_status_update(pdata);printk("%s: Toybrick PWM by harry Demo !\n", __func__);misc_register(&pwm_1_dev);#ifdef SYS_DEV_CONFIGpwm_kobj = kobject_create_and_add("pwm", NULL);if (pwm_kobj == NULL) {printk("create kobject fail \n");ret = -ENOMEM;goto err;}ret = sysfs_create_file(pwm_kobj, &dev_attr_pwm.attr);if (ret) {printk("pwm sysfs_init: sysfs_create_group failed\n");goto err;}
#endifreturn 0;
err:
#ifdef SYS_DEV_CONFIGkobject_del(pwm_kobj);
#endifpwm_free(pdata->pwm);return ret;
}static int pwm_remove(struct platform_device *pdev)
{struct pwm_data *pdata = pdev->dev.platform_data;
#ifdef SYS_DEV_CONFIGkobject_del(pwm_kobj);
#endifpwm_free(pdata->pwm);return 0;
}static const struct of_device_id pwm_dt_ids[] = {{ .compatible = "pwm_1"},{  }
};
MODULE_DEVICE_TABLE(of, pwm_dt_ids);static struct platform_driver pwm_driver = {.driver = {.name = "pwm",.of_match_table = pwm_dt_ids,},.probe = pwm_probe,.remove = pwm_remove,
};module_platform_driver(pwm_driver);
MODULE_AUTHOR("Harry <503433013@qq.com>");
MODULE_LICENSE("GPL");

然后添加到menuconfig

config PWM_RK3399_1tristate "RK3399 PWM1 support"default yhelpGeneric PWM framework driver for RK3399.To compile this driver as a module, choose M here: the modulewill be called pwm1.

添加到makefile

obj-$(CONFIG_PWM_RK3399_1)   += pwm-rk3399-1.o

最后编译

./make.sh linux prod

注意,如果是自己制作的板子的话,可以参考make.sh里面的写法,先编译.dts/.dtsi文件,然后再编译内核

3. Cartographer STM32下位机

3.1 介绍

这是一个基于STM32的智能小车下位机(底盘控制器),兼容ROS操作系统,和cartographer项目的上位机进行适配,上位机通过STM32虚拟串口与下位机透传,波特率自适应不丢包。

3.2 软件架构

本软件基于C编写,支持一路SBUS接收机、一路GPS、一路IMU、一路编码器,支持速度闭环控制、方向控制、路径规划自动驾驶(基于GPS、测试中)、颠簸路况补偿。

支持上传GPS/IMU等信息并加入时间戳供上位机使用和参考。

3.3 任务列表

  • 完成SUBS解析。
  • 完成编码器读取和滤波。
  • 完成智能非线性PID设计与部署。
  • 完成IMU的去重力和yaw磁力计融合。
  • 完成GPS解析与自动授时。
  • 完成颠簸路段补偿设计。
  • 所有串口都走DMA以避免丢包。
  • 加入对INA219功率模块的支持。
  • 把所有接口换成紧接口,自带锁定。
  • 重新制板,所有东西包括电源全部采用模块化设计。
  • 加入对SK6812的支持。
  • 加入12VLED车灯功能。
  • 加入电压监测。
  • 完善上下位机通讯协议。
  • 加入在线调参功能。
  • 完成GPS路径规划设计(通过IMU补偿GPS精度)。

4. 参考链接

http://wiki.t-firefly.com/zh_CN/Firefly-RK3399/ubuntu_manual.html#usb-yi-tai-wang USB 以太网设置
http://t.rock-chips.com/wiki.php?mod=view&id=14 如何烧写固件
http://wiki.friendlyarm.com/wiki/index.php/NanoPC-T4/zh 12.6节 编译 FriendlyCore/FriendlyDesktop/Lubuntu/EFlasher的内核源代码

用USB代替网络通讯,实现Cartographer上位机与下位机之间的消息传递相关推荐

  1. php怎么与下位机通讯,上位机与下位机之间的连接通讯方式

    上位机与下位机之间可以用USB或者串口进行连接通讯 上位机指的是计算机(电脑),下位机指的是内部有单片机(MCU)的电子产品或者设备.计算机可以用作给下位机通讯的接口有USB和串口(比较旧的计算机还有 ...

  2. 上下位机通讯协议_上位机与下位机的区别通讯

    上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接 ...

  3. 【CNC——第6篇】PMAC上位机编程基础篇(上位机和下位机如何通信)

    拓展链接: PAMC官网:DELTA TAU. 官网手册:手册大全 PMAC官网: PCOMM32PRO用户手册 PMAC 的内部变量 内部变量分为四种,I 变量为电机等常用基本控制变量,P 变量为全 ...

  4. 上位机和下位机的概念,理解如何实现PC从PLC中读取数据?

    市面上的PLC有上百种, 西门子的, 三菱的, 欧姆龙的等等. 上位机和下位机的理解: 上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/ ...

  5. 超详细Klipper 上位机与下位机配置

    (适用多数Mega2560芯片打印机主板,本文使用香橙派ZERO2作为上位机) 上位机:ZERO2 下位机:打印机主板 下载镜像系统 首先,去Armbian官网下载Buster系统镜像:Armbian ...

  6. 计算机基础-工控机、上位机、下位机、stm32、单片机

    工控机 定义:(Industrial Personal Computer,IPC)即工业控制计算机,主要用于工业生产上. 性能:采用全钢机箱,抗震性能好,抗电磁干扰,抗冲击. 结构:包括CPU.io外 ...

  7. 什么是上位机、下位机

    上位机 上位机是指可以直接发出操控命令的计算机, 一般是PC/host computer/master computer/upper computer, 屏幕上显示各种信号变化(液压,水位,温度等). ...

  8. java实现上位机与下位机串口通信

    串口通信是在工程应用中很常见.在上位机与下位机通讯过程中常通过有线的串口进行通信,在低速传输模式下串口通信得到广泛使用.在说个之前先来简单解释一下上位机与下位机的概念. 上位机与下位机 通常上位机指的 ...

  9. 上位机、下位机的初略解释

    上位机是指:人可以直接发出操控命令的计算机,一般是PC,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接控制设备获取设备状况的的计算机,一般是PLC/单片机之类的.上位机发出的命令首先给下位 ...

最新文章

  1. PK朱广权的手语数字人,现在要到医院银行上岗了
  2. Android的debug.keystore拒绝访问导致的生成异常及解决方案
  3. java spring cloud 版 b2b2c 社交电商-服务消费者(Feign)
  4. 一文读懂最强中文NLP预训练模型ERNIE
  5. 容器编排技术 -- Kubernetes kubectl rolling-update 命令详解
  6. 【报告分享】女性自我保护手册,教你应对10种常见危险处境.pdf(附189页pdf下载链接)...
  7. 2015年自然语言处理实证方法会议(EMNLP)简介
  8. android拉勾轮播,拉勾网顶部轮播图的实现(一)以及简单闭包的应用
  9. pre和code的区别
  10. Android计算器设计实验报告
  11. ZZULIOJ 1067: 有问题的里程表
  12. 缤纷彩色文字广告代码,文字广告代码美化版,给网站添加文字广告教程
  13. python实现乘法口诀表
  14. Git的cherry-pick等一些小知识
  15. xshell中数字小键盘不能使用怎么办?
  16. 计算机共享网络热点,(传输)将win7计算机无线网络变成WiFi热点,使​​手机和笔记本电脑可以共享Internet...
  17. MYSQL将一个人的性别改为女_力扣数据库题目627变更性别
  18. GB/T 17626.2-2018下载网址
  19. ByteBuffer学习笔记
  20. Leetcode 792. 匹配子序列的单词数 C++

热门文章

  1. 2012.6.28-29一级建造师-法规精讲(陈印老师)
  2. Java多线程拾遗(五) 使用CountDownLatch同步线程
  3. 书稿《C++释难解惑》(C++130个问题)已上传到CSDN,欢迎下载
  4. 计算机专业的就业方向有哪些呢?
  5. 一周学完MyBatis源码,万字总结
  6. 关于报错Support for the experimental syntax ‘decorators-legacy‘ isn‘t currently enabled
  7. 分布式环境下的解决方案——分布式锁
  8. 薛蛮子担任顾问的新项目,去中心化数据交易平台XChain
  9. 小米wifi随身驱动macOS Mojave 10.14版
  10. Directx11进阶教程之CascadeShadowMap(层级阴影)(上)