imuncle.github.io icon indicating copy to clipboard operation
imuncle.github.io copied to clipboard

大叔的个人小站

Results 100 imuncle.github.io issues
Sort by recently updated
recently updated
newest added
trafficstars

随着代码结构的完善,越来越希望代码更加智能化,调参的步骤越少越好,要是啥也不用调就更好了。。。 回忆起以往看过的代码,感觉智能化第一步就是搞定flash读写。有了flash之后,一切都变得很方便了。 比如每辆车的电机安装方式不一样,那么云台中间位置就不一样,如果有了flash,直接运行校准程序,云台左右分别碰到限位开关,自动计算出中间位置,pitch轴直接用手扶正,靠陀螺仪判断是否水平,水平的时候就记下当前位置,写入flash中,不用再慢吞吞地进调试,手动记录中间位置了。 又比如现在都流行自己搞上位机,上位机校准的关键一点就是单片机需要存储上位机的信息,下次上电就可以直接使用了。 所以flash多好啊。 # flash 首先我们需要了解一个内存映射: ![image](https://user-images.githubusercontent.com/35989223/55685267-a3328e80-5986-11e9-9bd0-5988b07c72b3.png) stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。 RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。 Flash中的内容一般用来存储代码和一些定义为const的数据,断电不丢失, RAM可以理解为内存,用来存储代码运行时的数据,变量等等。掉电数据丢失。 STM32将外设等都映射为地址的形式,对地址的操作就是对外设的操作。 STM32的外设地址从0x4000 0000开始,可以看到在库文件中,是通过基于0x4000 0000地址的偏移量来操作寄存器以及外设的。 一般情况下,程序文件是从 0x0800 0000 地址写入,这个是STM32开始执行的地方,0x0800 0004是STM32的中断向量表的起始地址。 在使用keil进行编写程序时,其编程地址的设置一般是这样的: ![image](https://user-images.githubusercontent.com/35989223/55685282-c6f5d480-5986-11e9-8007-d5e3e4a2612b.png) 程序的写入地址从0x08000000(数好零的个数)开始的,其大小为0x100000也就是1024K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08100000。这与STM32的内存地址映射关系是对应的。 ##...

RM

之前写过一篇使用STM32虚拟串口功能的文章:[实现USB CDC通信](https://imuncle.github.io/content.html?id=59),但是这个有个很大的问题,它的Windows驱动的数字签名过期了,我在我的电脑里搜索了一下,发现有两个驱动: ![image](https://user-images.githubusercontent.com/35989223/69860357-033cc300-12d1-11ea-9aa0-34d392f717bb.png) 不过很可惜,这两个驱动都过期了: ![image](https://user-images.githubusercontent.com/35989223/69860420-30897100-12d1-11ea-804d-ef9f3b04f3a6.png) 这就直接导致在Windows上使用ST自己的虚拟串口需要强制跳过数字签名这一步,而每次电脑重启之后Windows就会恢复默认设置,最麻烦的是每次还必须通过重启设置,不过Linux下倒没这个问题,因为数字签名是Windows自己搞出来的东西。 但还是很烦,所以我决定抛弃ST官方的虚拟串口驱动,正好我找到了别人用STM32模拟CH341的代码:[blackmiaool/STM32_USB_CH341 ](https://github.com/blackmiaool/STM32_USB_CH341),但这已经是五六年前的代码了,当时还是标准库,所以我决定把它用HAL库实现。 踩了一些坑,这玩意儿花了我三天时间,主要还是对USB协议不太熟悉,下面就按照我踩坑的时间顺序记录。 # 第一天:让电脑识别为CH340 这一步很简单,只需要改变设备描述符就行了,具体更改如下: ## 使用STM32CubeMX生成代码 ![image](https://user-images.githubusercontent.com/35989223/69861016-5ebb8080-12d2-11ea-9c2d-53321e22cb33.png) 这里修改以下PID和VID,然后字符串名称就随便写,点击生成代码。 ## 修改设备描述符 在`usbd_desc.c`里面,修改`USBD_FS_DeviceDesc`变量: ```c __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12, /* bLength */...

RM
STM32

该算法在论文《Automatic camera and range sensor calibration using a single shot》中被提出,同时作者还给出了角点检测部分的MATLAB源代码。这篇文章的工作主要是使用OpenCV和C++复现MATLAB的代码效果,同时梳理一下角点检测的过程。 复现的代码见https://github.com/imuncle/camera_calibration 算法分为三个部分:角点提取、角点亚像素化、棋盘生长。 # 角点提取 角点提取部分采取的是类似Harris特征的检测方法。它设置的角点特征如下图所示: ![image](https://user-images.githubusercontent.com/35989223/89759558-0608dc80-db1d-11ea-9075-6221c85b39ef.png) 这里有两组特征,如果角点的旋转角度不大,则由第一组特征检测,如果角点旋转角度大,则由第二组特征检测。 使用这些特征作为卷积核对图像进行卷积。对于每一个像素i,它经过一组特征的四个卷积核的卷积后会得到四个值`f_A` `f_B` `f_C` `f_D`,其最后的卷积结果c的定义如下: ![image](https://user-images.githubusercontent.com/35989223/89760222-7a904b00-db1e-11ea-8256-8d771162ff61.png) 最后每个像素由不同大小、不同类型的卷积核卷积后取各个卷积结果的最大值,其得到角点似然域,如下图右图所示: ![image](https://user-images.githubusercontent.com/35989223/89760279-a0b5eb00-db1e-11ea-86fb-b3751ed8c266.png) 最后对角点似然域进行非极大值抑制即可得到像素精度的角点值。 # 角点亚像素化 该方法是根据角点所对应的两条线的角度来对角点坐标进行优化,同时也优化了两条线的角度(后文以角点的方向代指)。 首先使用Sobel算子对图像进行横向和纵向的滤波,滤波卷积核如下图所示: ![image](https://user-images.githubusercontent.com/35989223/89760502-1fab2380-db1f-11ea-8fdc-ac71f8ca3e4e.png)...

Project

在驱动步进电机成功之后(可看[这篇文章](https://imuncle.github.io/content.html?id=27)了解详情),剩下的就是读取编码器的值了。 编码器使用的是多摩川(Tamagawa)的39位高精度编码器(23位单圈精度+16位多圈精度),支持RS485通信,波特率为2.5Mbps,数据长度为八位。型号为TS5700N8401,属于SA48系列编码器。 使用起来很简单,只要数据手册正确以及模块能满足功能。。。这里就记录一下我这一周所踩的坑。 # RS485通信 RS485通信是从RS232中衍生出来的,解决了RS232不能联网通信的痛点。 RS485与串口通信非常相似,也是两根线,但它可以像CAN通信一样,一条总线上搭载多个设备,实现联网通信。两根线分别为A线和B线,这里就说说我踩的第一个坑: 编码器的说明书里把两个数据线写作: ![image](https://user-images.githubusercontent.com/35989223/53726832-cbbbfa00-3ea9-11e9-96c9-da17deb27afb.png) 这我怎么知道哪根是A线哪根是B线啊,百度也百度不到,拿头来接吗。。 没办法,只能试错法,结果一次“成功”,我成功收到了数据。结果后来仔细一看这数据是我自己的发出去的,当时就自闭了,`为啥我会收到我自己发的数据?`。 后来的某个晚上我突然开窍,原来是`接反了`,A、B两线反接就跟TX和RX短接一样,RX的信号立马被TX捕获,造成了接收到编码器数据的假象。 # USART低位优先 搞懂了这一点后我把线反接回来,但这下一点反馈信息都没有了,编码器就像死了一样。没办法,看通信协议哪里写错了吧。 *(这里又要吐槽一下,这编码器的数据手册是真的难找,淘宝店家给的和公司网站找到的都只有编码器的性能参数和接线方法,死活没有通信协议,后来我从CSDN上才找到一个型号很相近的TS5700N8501编码器的数据手册,这下才开工)* *下载地址:* https://download.csdn.net/download/ppl002/10787122 读了数据手册才知道编码器可以读取绝对值角度数据,也可以读写数据到内置的EEPROM中,当然我这里只需要读取绝对值角度数据就行了。 通信协议如下: ![image](https://user-images.githubusercontent.com/35989223/53727578-83054080-3eab-11e9-9061-9fd6484b7d60.png) ![image](https://user-images.githubusercontent.com/35989223/53727602-957f7a00-3eab-11e9-8d6f-5e962e96fe3e.png) ![image](https://user-images.githubusercontent.com/35989223/53727617-9fa17880-3eab-11e9-86c0-f70dcb4657d5.png) ![image](https://user-images.githubusercontent.com/35989223/53727643-ab8d3a80-3eab-11e9-888d-1f832f155d57.png) 我先读取编码器的单圈数值吧,所以我要请求`DATA_ID_0`。 ## 起始位&停止位 手册里写的是CF由10bits,但众所周知**USART**一次只能发送8bits的数据,而这里显然是不能分两次发送的。 后来我才领悟到原来这里的起始位和停止位并不需要手动发送,因为USART的通信协议中就自带了起始位和停止位。所以这里我只需要发送`0100 0000`就行了,也就是`0x40`。...

other

最近正值期末考试,今天刚考完一门,昨晚上实在是不想复习了,正想着怎么办的时候,突然想把复习的考点放在自己的博客上,正好我半年前搭建了一个博客,打开一看发现只有最开始搭建的时候的一篇博客,感觉很失败。 之前的这篇博客采用的是Hexo+NexT框架搭建的,但是我的电脑几经改装,系统也重装了几次,node.js早就没了,想要继续维护这个博客有些麻烦,索性全删了,从头手写一个博客。 首先明确目标,我对博客要求不高,能发文章就行了,所以博客的功能就两点:发文章、读文章。当然,最基本的文章分类是必须的。 然后开始找模板,因为我个人的UI设计能力实在堪忧,只有借鉴别人的设计。我仍然选择在[NexT主题](https://hexo.io/themes)中寻找,很快我找到了一个[不错的博客样式](http://lalala.lol)。然后我就使用自卑鄙的手段,按下`F12`查找元素的样式,顺手把图也扒了下来,基本上还原了博客的样式。 ## 渐变动画 博客里有**内容渐变动画**和**菜单动画**,我使用了css+Jquery实现,就拿我的博客的头像来进行说明吧。 头像的部分css样式如下: ```css .head { display: inline-block; width: 130px; height: 130px; border-radius: 50%; padding: 3px; background: #fff; box-shadow: 0 0 5px #95a5a6; position: relative;...

web

鱼眼镜头因其较大的畸变而难以标定,对鱼眼镜头的畸变建模也是多种多样,相机模型的一些重要时间节点如下: 1. 透视模型:于2000年,Zhang贡献(编码为RTCM=Radial Tangential Camera Model)。 2. 开创理论:于2000年,Geyer和Daniilidis提出UCM(Unified Camera Model)成像模型,可用于建模平面、抛物、椭圆及双曲反射相机。 3. 完善理论:于2001年,Barreto和Araujo优化UCM成像模型,从而简化了标定算法的开发。 4. 理论向工程过渡:于2006年,Scaramuzza和Martinelli及Siegwart基于泰勒展开式提出用多项式来近似所有成像模型。 5. 等距模型:于2006年,Kannala和Brandt贡献(编码为KBCM)。 6. 开创工程:于2007年,Mei和Rives融合径向切向畸变到UCM成像模型(编码为MUCM),解决UCM建模鱼眼镜头差的问题。 7. 优化工程:于2013年,Heng和Li及Pollefeys。 8. 完善工程:于2016年,Khomutenko和Garcia及Martinet提出EUCM(Extended Unified Camera Model)成像模型,解决了UCM径向畸变和建模鱼眼镜头差的问题及MUCM非闭环解问题。 9. 完善工程:于2018年,Usenko和Demmel及Cremers提出DSCM(Double Sphere Camera Model)成像模型,相对于EUCM、MUCM、KBCM等算法,在精度和速度上取得了折中。...

CV

前段时间我研究了MPU9250的数据读取和九轴姿态解析算法,效果还不错,详情点[这里](https://imuncle.github.io/content.html?id=39)。 但是今天发现九轴算法得出来的角度有两个问题,一个是波动较大,一个是会出现下图所示的斜坡。 ![image](https://user-images.githubusercontent.com/35989223/55298790-3721c900-5462-11e9-9584-15dc372053a0.png) 后来我发现,应该是陀螺仪积分出来的角度和磁力计加速度计坐标转换过来的角度不一致,导致被缓慢纠正,进一步发现pitch轴和roll轴没有这个问题,只有yaw轴有,所以是磁力计的问题。 磁力计这东西吧,问题还真挺多,到了一个新地方还要重新校准一下,而且特别容易受到干扰。我采集了一段数据后发现,加速度、角速度、磁场强度,就磁场数据波动最大,这也是角度波动大的罪魁祸首。 我尝试对磁力计进行低通滤波或者均值滤波,但效果都不是很好,最后角度数值稳定下来了,但是那个斜坡还是无法消除。 没办法,暂时抛下磁力计试试六轴算法,也就是说只使用MPU9250里面的MPU6050。 # 六轴算法 我参考了正点原子的飞控代码,在正式总结之前先说一声`正点原子牛逼!` ## 陀螺仪数据 六轴算法中最重要的就是陀螺仪数据的处理,因为陀螺仪数据是四元数更新的根本依据。加速度能纠正pitch轴和roll轴,所以yaw轴的稳定全靠陀螺仪数据的准确。 这里主要是采集一段陀螺仪静止的数据,找到三轴数据的静差。我采集了1024个数据,先计算他们的方差,如果方差大于阈值,则表明陀螺仪不是静止的,则不进行校准,当方差满足要求时,计算样本数据的平均值,当做陀螺仪的静差。 处理过后,才静止情况下三轴的陀螺仪数据小数点后两位都是0,效果还不错。 实现代码如下,函数较多: ```c #define SENSORS_NBR_OF_BIAS_SAMPLES 1024 /*计算方差的采样样本个数 */ #define GYRO_VARIANCE_BASE 4000 /* 陀螺仪零偏方差阈值 */ /*计算方差和平均值*/ static...

RM

前段时间稚晖君做出了一个[迷你机械臂](https://www.bilibili.com/video/BV12341117rG),非常强大,里面使用的示教器[Peak](https://github.com/peng-zhihui/Peak)的软件部分借鉴的是另一个开源项目[X-TRACK](https://github.com/FASTSHIFT/X-TRACK),在好奇心的驱使下我去大致阅读了两份代码,除却LVGL图形设计部分外,整个工程的框架设计对我有写额外的启发。 稚晖君自己也对X-TRACK写过一个分析文档,他绘制的代码结构如下图所示。 ![img](https://camo.githubusercontent.com/5136d7f564513f094d512211dd2128a803b4dfc91646c12457ae1da51e91a9de/68747470733a2f2f70656e677a68696875692d6d61726b646f776e2e6f73732d636e2d7368616e676861692e616c6979756e63732e636f6d2f696d672f32303231303732323136353735382e706e67) 其中的**消息框架**和**HAL层**对我有些启发。 # 消息框架 消息框架是一个消息发布订阅机制,类似于ROS里面的话题功能,这个机制可以将各个模块相互独立起来,而且对于一对多的消息传递这种情况,消息框架是非常容易管理的。 因为源代码中使用C++编写消息框架,而单片机开发用的更多的还是C语言,于是我自己简单写了一个C语言版的,其中动态数组`cvector`的实现修改自[这篇博客](https://blog.csdn.net/dengxu11/article/details/7311820)。 * cvector.h ```c #ifndef CVECTOR_H #define CVECTOR_H # include # include # include # define MIN_LEN 5 # define EXPANED_VAL 1 struct...

RM
STM32

前段时间尝试使用陀螺仪作为机器人云台的位置环反馈值,在底盘跟随的大前提下,这样做是更符合第一视角的操作的。 我去年比赛的时候买了几个维特智能的型号为WT901的九轴,当时时间紧没有用上,结果不用不知道,一用吓一跳,陀螺仪延迟高的吓人,使用J-Scope看到的图像如下: ![image](https://user-images.githubusercontent.com/35989223/54519278-1ad15700-49a1-11e9-9007-17d4b6983d16.png) 从图中可以看出延迟大约有200ms,这种延时已经不再具备方位参考值了。 我在仓库里东翻西找,找到了两个三十多块的MPU9250,相比于我买的一百来块的陀螺仪来说便宜了很多,于是我开始尝试手动解析数据。 # 读取原始数据 MPU9250内部其实是由6轴陀螺仪**MPU6500**和磁力计**AK8963**组成,可以测量三轴加速度,三轴角速度和三轴磁场强度,此外还可以测量温度,但这里我没有用到温度的数据。 数据解析的第一步就是读取原始数据。MPU6500支持IIC通信和SPI通信,AK8963只支持IIC通信,但可以通过MPU9250为中介使用SPI通信,只是比较麻烦,所以这里我选择了IIC通信。 ## IIC地址 MPU9250的IIC地址取决于AD0口的电平高低,如果AD0接GND,则地址为0x68,如果接VCC,则地址为0x69,。 这里说一下一个坑: > 在读取时,注意需要人工将地址左移1位(I2C读写为左对齐,第8位要存读写标志位) 最开始我不知道这个,一直把地址写成0x68,结果死活读不到数据,后来才发现要写成0xD0。 MPU9250的其他地址可以查看它的寄存器数据手册,我这里列出了我用到的一部分寄存器: ```c #define MPU9250_ADDRESS 0xD0 //AD0接GND时地址为0x68,接VCC时地址为0x69 #define MPU_PWR_MGMT1_REG 0X6B //电源管理寄存器1 #define MPU_GYRO_CFG_REG 0X1B //陀螺仪配置寄存器...

RM

圆筒镜艺术是一门比较冷面的艺术,百度里搜索也找不到几张图片,不过大家一看就懂,比如下面这两幅图: ![image](https://user-images.githubusercontent.com/35989223/93284444-4f77d600-f805-11ea-8a1e-6e52d6da0af4.png) ![image](https://user-images.githubusercontent.com/35989223/93284470-61597900-f805-11ea-9548-9e1f7fda867f.png) 在我看来这种艺术的精妙之处在于得到的作品看似畸变无序,看不出是个什么玩意儿,但当把圆筒镜放上去的时候,相信所有人都会惊呼【卧槽】 然而我虽然热爱绘画,但我的绘画水平不行,所以我决定用代码的方式来绘制。 # 原理分析 直观来看,圆筒镜成像的过程如下所示: ![image](https://user-images.githubusercontent.com/35989223/93284819-0f652300-f806-11ea-9b2c-14793351528e.png) 但是这个没有揭示本质,实际的成像过程应该是下图所示: ![image](https://user-images.githubusercontent.com/35989223/93285384-4ab42180-f807-11ea-8ef5-1751686967a4.png) 可以把成像过程看为两步。图中O点为相机的聚焦点,①为相机的像平面,即我们期望在相机里看到的画面,②为①的纸张平面的透射变换,③为②对于圆筒镜的反射成像,③也就是我们最后要得到的图像。 ## 透射变换 透射变换很简单,只需要求出纸张平面和相机像平面之间的单应矩阵即可,我这里直接使用相机标定中的棋盘,识别到角点后直接使用OpenCV的`findHomography`函数求得单应矩阵,这里用H表示,则两个坐标系之间的关系如下: ![image](https://user-images.githubusercontent.com/35989223/93285686-12f9a980-f808-11ea-91d2-5e352e8d4f19.png) ## 反射变换 反射变换则在纸张平面上分析,但因为圆筒镜的镜面是曲面,反射面法线方向都是改变的,所以我们首先要找到图像上每一点对应的法线方向,或者说对应的圆筒镜上的反射点。 如下图所示,我们可以根据相机像平面得到反射点,并且很容易知道,图像中同一列的像素点对应的反射点是一样的。 ![image](https://user-images.githubusercontent.com/35989223/93286057-d8444100-f808-11ea-8e89-43ec101c6594.png) 回到纸张平面,考虑一点(x, y),其反射点为(△x, -△y),反射成像点为(x', y') ![image](https://user-images.githubusercontent.com/35989223/93286382-b1d2d580-f809-11ea-8458-091a32fde352.png) 通过简单的平面几何分析可以得到反射成像点的公式。 ![image](https://user-images.githubusercontent.com/35989223/93286846-b1870a00-f80a-11ea-99ba-06b532060699.png) 写成矩阵形式即为: ![image](https://user-images.githubusercontent.com/35989223/93286919-e09d7b80-f80a-11ea-86a4-d51618a66546.png)...

CV