# project_summary **Repository Path**: harryzhangabc/project_summary ## Basic Information - **Project Name**: project_summary - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-10-11 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[TOC](catographer编译内核及下位机功能) # 项目介绍 项目旨在用catographer实现上位机与下位机之间的消息传递。 # 如何在toybrick-rk3399prod上编译linux-kernel 这是个漫长且无聊的过程,因为有关这方面的教程少之又少,在本人无数次踩坑下终于搞定。 感兴趣的小伙伴可以打开[官方教程链接](http://t.rock-chips.com/wiki.php?mod=view&id=26)体验下想找教程却没有教程然后砸电脑未遂的心情。 ## 1.编译原理 在RK3399prod里面,Linux的kernel和rootfs(根文件系统)是分开地址存放的,rootfs是在kernel之上运行的。意思是一台电脑所有的驱动和最基础的指令都存放在kernel里面,各个厂商根据自己的特性在kernel上开发自己的操作系统(例如fedora和ubuntu)。于是这款板子可以在不用重新编译内核的情况下更换操作系统,在这次教程中,我们主要讲如何编译驱动到kernel里面,并支持多种操作系统。 我用的rootfs链接(ubuntu):[百度云网盘提取码:q9my](https://pan.baidu.com/s/1qU5noFQWEZ_M3hlIlAlcAA) ## 2.编译过程 首先,rk3399pro是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 ``` 然后把下面的几个选项操作成如下方式 ``` USB Gadget Drivers USB functions configurable through configfs Ethernet Gadget (with CDC Ethernet support) [*] RNDIS support (NEW) ``` 接着在该目录下编译内核。 ``` ./make.sh linux prod ``` 编译完成后会生成boot_linux.img文件,把这个文件烧入到3399pro对应的区域中[烧写教程](http://t.rock-chips.com/wiki.php?mod=view&id=14),只要烧这个文件,其他的都不用管。 当然你想偷懒的话可以用我编译好的:D[百度网盘提取码:pt7p](https://pan.baidu.com/s/1xrQr29kcJ-dr1g5A8dfVOg) 最后看下你编译好的文件,有几个驱动模块文件需要手动复制到3399pro里面手动加载 ``` 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,后面的模块才可以加载进去。 ## 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 mtu 1500 inet 168.168.100.48 netmask 255.255.0.0 broadcast 168.168.255.255 inet6 fe80::1351:ae2f:442e:e436 prefixlen 64 scopeid 0x20 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 0 TX packets 236 bytes 35172 (35.1 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 device interrupt 42 base 0x8000 ... # usb0 是虚拟的 usb 网卡 usb0: flags=4098 mtu 1500 ether 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 0 TX 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 虚拟网卡 firefly@Desktop:~$ ifconfig enp0s20u2i1: flags=4163 mtu 1500 inet 192.168.2.90 netmask 255.255.255.0 broadcast 192.168.2.255 inet6 fe80::871c:b87e:1327:7fd4 prefixlen 64 scopeid 0x20 ether 46:fe:6e:97:ee:a6 txqueuelen 1000 (以太网) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1 bytes 54 (54.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 ... # 设置 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测试下带宽。 ![1.png](https://img-blog.csdnimg.cn/img_convert/dcf53af4b21f1cda4a6cbe3cae57f441.png) 如图所示,在USB3.0接口下,带宽约为250Mbit/s左右(30MByte/s),满足日常传输使用。 配置过程中要注意以下几点: 1.对应好上述步骤中自己设备上的各个 IP 地址,注意设备上的 USB 虚拟网卡 IP 和有线网络 IP 不在同一网段; 2.PC 机虚拟 USB 网卡的网关要设置为设备虚拟 USB 网卡的 IP 地址; 3.设备上的虚拟网卡 IP 地址、IP 转发功能、流量转发规则等设置会在设备重启后恢复,然后大家可以自行查找资料如何开机自动设置这些配置。 ## 配置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连接大功告成! ## 测试 在你自己电脑上输入以下命令 ``` roscore rosrun turtlesim turtlesim_node ``` 然后在RK3399上打开一个终端 ``` rosrun turtlesim turtle_teleop_key ``` 然后你就可以控制小乌龟行走了!USBC到此配置完成! ![2.png](https://img-blog.csdnimg.cn/img_convert/55e1d59002c4982a664c9ff3df030d85.png) # catographer 到目前为止ROS传输demo均已配置完成,catographer也能跑通,上位机可以直接获取到catographer的话题信息(这里就不放图了),剩余的硬件驱动/适配上位机程序部分需要一些时间,预计下周完成。 # 如何在任意板子上编译RK3399/RK3399pro的kernel(一) ## 1.不同板子所对应的设备树 一般来说,不同板子都有对应的特制功能从而设置了对应的特殊硬件(比如I2C、SPI设备),这些硬件需要在内核注册相关dts节点才能使用。因此,我们在购买板子的时候商家已经注册好了这些dts节点供我们使用,我们只要直接编译内核就好,如果是我们自己的板子,就需要自己去编写相对应的节点和驱动。 [Linux设备树详解](https://blog.csdn.net/u012489236/article/details/97137007) ## 2.RK3399/RK3399pro的原始设备树 对于这款芯片而言,rockchip官方已经给出了相关的基础设备树,其芯片所有的外设的功能已经预制在/kernel/arch/arm64/boot/dts/rockchip中的rk3399.dtsi和rk3399pro.dtsi中,你只需要创建一个你自己的.dtsi,然后在里面加入你想使用的外设设备,然后再写一个驱动.c文件放到/kernel/drivers里面即可。以下是自行编写并实现PWM驱动的一个示例: ```c //为了省时间,我直接在原厂板子上提供的.dtsi进行修改,文件位于/kernel/arch/arm64/boot/dts/rockchip/rk3399pro-toybrick.dtsi,同时,你要自己画板子的话,可以参考这个文件的写法,写一个自己的板级dtsi文件。 // #include // #include // #include // #include // #include // #include // #include "rk3399pro.dtsi" // #include "rk3399-opp.dtsi" //注:如果是自己写文件的话,请手动加入以上这些库文件,然后再继续进行编写 pwm1: pwm_1 { status = "okay"; compatible = "pwm_1"; pwm_id = <1>; }; //添加一个pwm节点 backlight: backlight { //在这个上面添加 compatible = "pwm-backlight"; ...... ``` 编写驱动文件 ```c #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); #endif static 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_CONFIG pwm_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; } #endif return 0; err: #ifdef SYS_DEV_CONFIG kobject_del(pwm_kobj); #endif pwm_free(pdata->pwm); return ret; } static int pwm_remove(struct platform_device *pdev) { struct pwm_data *pdata = pdev->dev.platform_data; #ifdef SYS_DEV_CONFIG kobject_del(pwm_kobj); #endif pwm_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_1 tristate "RK3399 PWM1 support" default y help Generic PWM framework driver for RK3399. To compile this driver as a module, choose M here: the module will be called pwm1. ``` 添加到makefile ``` obj-$(CONFIG_PWM_RK3399_1) += pwm-rk3399-1.o ``` 最后编译 ``` ./make.sh linux prod ``` **注意,如果是自己制作的板子的话,可以参考make.sh里面的写法,先编译.dts/.dtsi文件,然后再编译内核** # cartographer STM32下位机 #### 介绍 这是一个基于STM32的智能小车下位机(底盘控制器),兼容ROS操作系统,和cartographer项目的上位机进行适配,上位机通过STM32虚拟串口与下位机透传,波特率自适应不丢包。 #### 软件架构 本软件基于C编写,支持一路SBUS接收机、一路GPS、一路IMU、一路编码器,支持速度闭环控制、方向控制、路径规划自动驾驶(基于GPS、测试中)、颠簸路况补偿。 支持上传GPS/IMU等信息并加入时间戳供上位机使用和参考。 #### 任务列表 - [x] 完成SUBS解析。 - [x] 完成编码器读取和滤波。 - [x] 完成智能非线性PID设计与部署。 - [x] 完成IMU的去重力和yaw磁力计融合。 - [x] 完成GPS解析与自动授时。 - [x] 完成颠簸路段补偿设计。 - [x] 所有串口都走DMA以避免丢包。 - [ ] 加入对INA219功率模块的支持。 - [ ] 把所有接口换成紧接口,自带锁定。 - [ ] 重新制板,所有东西包括电源全部采用模块化设计。 - [ ] 加入对SK6812的支持。 - [ ] 加入12VLED车灯功能。 - [ ] 加入电压监测。 - [ ] 完善上下位机通讯协议。 - [ ] 加入在线调参功能。 - [ ] 完成GPS路径规划设计(通过IMU补偿GPS精度)。 ### 参考链接 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的内核源代码