前不久项目上遇到了rootfs
无法挂载的问题,就着这个问题顺便了解一下rootfs
的挂载流程,总结于此分享给大家。
首先帮大家复习几个概念。
术语 | 解析 |
---|---|
rootfs | 根文件系统,对应/目录节点。分为虚拟rootfs 和真实rootfs :虚拟rootfs 由内核自己创建和加载,仅仅存在于内存之中,其文件系统是tmpfs 类型或者ramfs 类型真实rootfs 则是指根文件系统存在于存储设备上,内核在启动过程中会在虚拟rootfs上挂载这个存储设备,然后将/目录节点切换到这个存储设备上,这样存储设备上的文件系统就会被作为根文件系统使用 |
ramdisk | 将系统一部分内存区域实现为/dev/ram ,把/dev/ram 作为作为一个存储设备,最终将根目录切换到/dev/ram 的挂载,实现将/dev/ram 作为根文件系统的目的。使用 ext2 格式的文件系统 |
initramfs | kernel 2.5中引入的技术,在内核镜像中附加一个cpio 包,这个cpio 包中包含了一个小型的文件系统,当内核启动时,内核将这个 cpio 包解开,并且将其中包含的文件系统释放到rootfs 中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。 |
下面总结了一下rootfs
挂载的调用过程:
1 | start_kernel |
在挂载rootfs
前会先挂载sysfs
,确保sysfs
能够完整的记录下设备驱动模型。
sysfs_init()
完成注册和挂载sysfs
文件系统的功能;init_rootfs()
负责注册rootfs
,init_mount_tree()
负责挂载rootfs
,并将init_task
的命名空间与之联系起来。
所以在了解rootfs
的挂载之前,我们先学习一下Linux文件系统初始化以及sysfs
如何挂载。
展开vfs_caches_init
来看。
1 | void __init vfs_caches_init(void) |
vfs_caches_init()
首先建立并初始化:
dentry_hashtable
inode_hashtable
设置内核可以打开的最大文件数
通过mnt_init
完成sysfs
和rootfs
文件系统的注册和挂载
sysfs
挂载流程sysfs
负责记录Linux设备驱动模型.
https://elixir.bootlin.com/linux/v5.4.200/source/fs/namespace.c#L3766
1 | void __init mnt_init(void) |
mnt_init()
调用sysfs_init()
注册并挂载sysfs
文件系统,然后调用kobject_create_and_add()
创建fs
目录。
https://elixir.bootlin.com/linux/v5.4.200/source/fs/sysfs/mount.c#L97
1 | int __init sysfs_init(void) |
第一步:创建根节点sysfs
根节点。
第二步:building block of kernfs hierarchy,每一个节点都代表一个kernfs_node.
第三步:sysfs_init()
调用register_filesystem()
注册文件系统类型sysfs_fs_type
,并加入到全局单链表file_systems
中。
sysfs_fs_type
定义如下,.mount
成员函数负责超级块、根目录和索引节点的创建和初始化工作.
1 | static struct file_system_type sysfs_fs_type = { |
通过以上步骤,sysfs
文件系统在VFS
中的视图如下:
挂载点指向超级块和根目录;超级块处在super_blocks单链表中,并且链接起所有属于该文件系统的索引节点;根目录’/'和目录"fs"指向各自的索引节点;为了提高查找效率,索引节点保存在hash表中。
rootfs
挂载过程mnt_init()
调用init_rootfs()
注册文件系统类型rootfs_fs_type
,并加入到全局单链表file_systems
中。
rootfs_fs_typ
e定义如下,mount成员函数负责超级块、根目录和索引节点的建立和初始化工作。
1 | struct file_system_type rootfs_fs_type = { |
init_mount_tree()
调用vfs_kern_mount()
挂载rootfs
文件系统.
init_mount_tree()
创建命名空间,并设置该命名空间的挂载点为rootfs
的挂载点,同时将rootfs
的挂载点链接到该命名空间的双向链表中。
init_mount_tree()
设置init_task
的命名空间,同时调用set_fs_pwd()
和set_fs_root()
设置init_task
任务的当前目录和根目录为rootfs
的根目录’/’
1 | static void __init init_mount_tree(void) |
通过以上分析,我们发现sysfs
和rootfs
的区别在于:虽然系统同时挂载了sysfs
和rootfs
文件系统,但是只有rootfs
处于init_task
进程的命名空间内,也就是说系统启动和初始化阶段使用的是rootfs
文件系统。
sysfs
和rootfs
在VFS
中的视图如下:
sysfs
和rootfs
文件系统rootfs
处于进程的命名空间中,且进程的root目录和pwd目录都指向rootfs
的根目录VFS
已经准备好了根目录,用户可以使用系统调用对VFS
树进行扩展上一节我们介绍了vmlinux的编译过程。vmlinux是一个ELF文件,上百M,无法直接flash到板子上。不同架构最终生成的启动镜像略有区别,一般地:
vmlinux
和System.map
objcopy
移除vmlinux
中不必要段,输出binary格式ImageImage.gz
,我们重点关注arm64架构的编译情况。
根目录的Makefile include了不同架构的Makefile文件:
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L583
1 | include arch/$(SRCARCH)/Makefile |
arm64下的Makefile中:
1 | # Default target when executing plain make |
由此可以发现,arm64的启动镜像为压缩后的Image.gz
.
再进一步走到boot目录下的Makefile/arch/arm64/boot/Makefile
1 | OBJCOPYFLAGS_Image :=-O binary -R .note -R .note.gnu.build-id -R .comment -S |
install脚本如下/arch/arm64/boot/install.sh
1 | # Arguments: |
用一张图总结如下:
]]>如何编译Linux内核源码?| 量子孤岛一文中提到了,vmlinux是原始的,未经压缩的内核可执行文件,即kernel编出来的原始产物。
本文的主题只有一个:vmlinux到底是怎么生成的?
日期 | 内核版本 | 架构 |
---|---|---|
2022-9-14 | Linux5.4.200 | ARM64 |
kernel中编译的起点是根目录下的Makefile。
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1099
1 | # Final link of vmlinux with optional arch pass after final link |
由脚本可以看出,产物vmlinux的scripts/link-vmlinux.sh
有两个依赖:
autoksyms_recursive
:初步看可是ko有关的语法,暂时跳过。
1 | ifdef CONFIG_TRIM_UNUSED_KSYMS |
vmlinux-deps
重点看。
1 | vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) |
依次展开看。
KBUILD_LDS
1 | export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds |
vmlinux.lds
是非常重要的链接文件,打算后面另起一文详细讲,这里暂不展开。
KBUILD_VMLINUX_OBJS
1 | # Externally visible symbols (used by link-vmlinux.sh) |
这里岛主对比了2.6版本的Makefile写法,5.4版本删掉了原来的KBUILD_VMLINUX_INIT:
1 | export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) |
而直接把$(head-y)
$(init-y)
追加到了KBUILD_VMLINUX_OBJS
后面。
为了一探究竟,我们再依次展开KBUILD_VMLINUX_OBJS
的各个组成部分.
head-y
:与架构相关,arm64架构定义在arch/arm64/Makefile
https://elixir.bootlin.com/linux/v5.4.200/source/arch/arm64/Makefile#L107
1 | # Default value |
init-y
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L637
1 | ifeq ($(KBUILD_EXTMOD),) |
按照语句执行顺序,这里init-y
展开后为init/built-in.o
继续展开剩余的:
core-y
1 | core-y:= usr/ |
libs-y2
1 | libs-y:= lib/ |
drivers-y
1 | drivers-y:= drivers/ sound/ |
net-y
1 | net-y:= net/ |
virt-y
1 | virt-y := virt/ |
通过观察不难发现:几乎所有的依赖条件中,都会生成一个built-in.o文件.
KBUILD_VMLINUX_LIBS
1 | export KBUILD_VMLINUX_LIBS := $(libs-y1) |
依次展开后也不难发现,调用了Linux中的各个子系统的根目录,想必vmlinux也是这些根目标链接而成的。
备注:
简单回忆一下patsubst.查找<text>
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换
1 | $(patsubst <pattern>,<replacement>,<text> ) |
我们再回到根目录下的Makefile中,target:vmlinux后面还有一些重要信息需要澄清:
1 | targets := vmlinux |
我们来慢慢分析。在vmlinux-deps
中的变量,都依赖于vmlinux-dirs
变量的值。而vmlinux-dirs
展开是众多的子目录,其目标规则最终定定位到$(Q)$(MAKE) $(build)=$@
。按照之前我们的已经掌握的先验知识,在scripts/Kbuild.include
展开后即
1 | make -f scripts/Makefile.build obj=$@ |
即依次回到各个子目录执行编译。
上面详细展开了内核子系统的编译流程。当各个子模块都编译完成,这时就需要工具将它们全部粘合起来制作成vmlinux
。
开篇的第一行代码还没聊,这回该它登场了!
1 | # Final link of vmlinux with optional arch pass after final link |
$<表示依赖关系中的第一个依赖,即scripts/link-vmlinux.sh
,这个脚本完成后续的链接工作。
来吧,看看scripts/link-vmlinux.sh
!
1 | #link vmlinux.o |
vmlinux_link
是一个link函数,其中:
1 | ${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux}\ |
这里ld
的objects就是前面分析的vmlinux-deps
.
用一张图来汇总上面的分析。
]]>在嵌入式Linux的开发过程中,内核编译是一个永远也绕不开的话题。
对内核编译系统的清晰把握,至少可以:
本文从Linux kernel中.o文件的编译探索kbuild机制。
日期 | 内核版本 | 架构 |
---|---|---|
2022-9-13 | Linux5.4.200 | arm |
我们以page_alloc.o的编译为例开始本次实验。
1 | make mm/page_alloc.o |
在kernel根目录下的Makefile中有:
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1733
1 | # Single targets |
直接看不是很直观,kernel version 2.6的Makefile中语句较为清晰:
1 | %.o: %.c prepare scripts FORCE |
逻辑比较清晰,.o
的文件依赖于同名的.c
文件,然后执行一个命令来生成。
Tip:这里有个小技巧,注释掉构建语句,通过error log来逆向观察语句展开的结构
1 | $(build-dirs): prepare |
再次执行编译命令,则有:
1 | @make -f ./scripts/Makefile.build obj=mm \ |
从log可知,执行make mm/page_alloc.o
又调用了一次make,使用script/Makefile.build
这个规则文件,传入的参数是obj=mm。
由第一节的log我们知道$(build)
展开后是:
1 | -f ./scripts/Makefile.build obj |
在哪定义的呢?全局搜索后,答案是scripts/Kbuild.include
,类似头文件的东西。
https://elixir.bootlin.com/linux/v5.4.200/source/scripts/Kbuild.include#L160
1 | ### |
好,将注意力转移到scripts/Makefile.build
这个文件,寻找和编译.o
相关的语句。
1 | $(obj)/%.o: $(src)/%.c $(recordmcount_source) $$(objtool_dep) FORCE |
重点关注$(call if_changed_rule,cc_o_c)
,这里调用了if_changed_rull
变量,该变量同样定义在scripts/Kbuild.include
1 | # Usage: $(call if_changed_rule,foo) |
这是一条逻辑判断语句,当条件为真,执行逗号之前的动作。当条件为假,则执行后面的@:
。这里的@:
为的是减少一些log输出,具体可以看提交:kernel/git/torvalds/linux.git - Linux kernel source tree
而如果条件为真,rule_$(1)
展开就是rule_cc_o_c
。
好,现在回到scripts/Makefile.build
,寻找rule_cc_o_c
的定义:
1 | define rule_cc_o_c |
OK,离故事接近真相还差最后一步,$(call cmd_and_fixdep,cc_o_c)
语句是我们重点关注的。同样是call函数,所以在此回到头文件scripts/Kbuild.include
寻找定义:
1 | cmd_and_fixdep = \ |
根据经验,再次展开就是cmd_cc_o_c
了,它还是在scripts/Makefile.build文件定义。
1 | cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< |
终于,真相大白!
.o
文件的编译一共分为如下四个步骤:
1 | Makefile |
这里重点强调下前面反复提到的两个文件:
scripts/Makefile.build
:包含了几乎所有的重要规则scripts/Kbuild.include
:类似于头文件,包含很多重要函数学习Linux内核是一件振奋人心的事情,而在学习伊始对Linux内核的成功编译并运行也更会燃起更足的动力去钻研。本文从下载并编译Linux内核、编译busybox、制作一个最小的根文件系统,最后用qemu启动你编译好的内核和根文件系统 ,初步感受Linux内核的魅力所在。
首先明确两点:
再明确内核文件的产物名称:
好了,明确了上述几点,就可以开始实验了。本文的实验环境如下:
实验目标:
Index of /pub/linux/kernel/v4.x/mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
下载并解压后,进入目录。
为了演示方便,选用了x86架构,如果编译arm则需要再下载对应的toolchain。
1 | $ export ARCH=x86 |
1 | $ make x86_64_defconfig |
这一步其实是对第2步的进行微调,这里我们使用基于ncurse库编制的图形界面工具:
1 | $ make menuconfig |
如果执行该命令时出现:
原因:缺少ncurses dev工具
1 | sudo apt-get install libncurses5-dev |
如果需要内核支持ramdisk驱动,需要选中如下配置:
1 | General setup ---> |
1 | $ make -j8 |
编译成功后的内核位于:arch/x86_64/boot/bzImage
至此,内核编译完成。
什么是busybox?
busybox号称“嵌入式Linux的瑞士军刀”。BusyBox工具小巧高效,可以替代一大批常用的标准Linux命令行工具,功能有所简化,非常适合资源有限的嵌入式平台。BusyBox是模块化且高度可配置的,可以对其进行裁剪以满足特定需求。
在如下链接下载busybox:
https://busybox.net/downloads/busybox.net/downloads/
我们以busybox-1.30.0作为实验对象。
下载之后解压并进入该busybox目录开始配置并编译。这里把busybox配置为静态编译,这样不依赖其他动态库比较容易操作和演示。编译则似曾相识,与编译内核的指令是一样的!
1 | $ make menuconfig |
配置完之后进行编译和安装
1 | $ make && make install |
make是编译busybox,make install是为了在对应目录中编译安装一系列的工具。
编译完成后的busybox就安装在源码根目录下的_install目录了。
至此,我们对Linux内核和busybox进行了配置和编译。光编译肯定不过瘾,接下来演示基于busybox制作一个简单的文件系统,并通过qemu模拟器运行Linux,真正的让内核工作起来!
编译完成后的busybox就安装在源码根目录下的_install目录了,我们进入_install目录,补充一些必要的文件或目录,相关的shell命令如下:
1 | mkdir -p etc dev mnt proc sys tmp mnt |
思路:
1 | !/bin/bash |
最终生成的文件系统镜像名字为:rootfs.img.gz
准备好了内核和文件系统镜像,接下来就是见证奇迹的时刻!
1 | qemu-system-x86_64 \ |
这样一个完整的Linux系统就起来啦!
]]>本文是Linux内存管理系列文章的第一篇,先对一些常见概念有一个基本的认知。
提问环节:
去本文中找答案吧!
所谓的内存模型,是从CPU的角度来观察物理内存的分布,而CPU 通过总线去访问内存。
Linux kernel支持3种内存模型:
在经典的**平坦内存模型(Flat Memory Model)**中,物理地址是连续的,页也是连续的,每个页大小也是一样的。每个页有一个结构 struct page 表示,这个结构也是放在一个数组里面,这样根据页号,很容易通过下标找到相应的 struct page 结构。
当存在多个CPU时分布在总线的一侧,所有内存组成的整体在内存的另一侧,所有的CPU访问内存都要经过总线,而且距离都相同。这就是我们熟悉的对称多处理器SMP(Symmetric multiprocessing),如下左图所示。这种模式成为UMA(uniform memory access)。针对嵌入式系统,一般采用UMA模式。不过这种模型缺点很明显,就是所有数据都要经过同一个总线,总线会成为瓶颈。
为了提高性能和可拓展性,有了另一个模式NUMA(Non-uniform memory access),非一致内存访问。如上右图所示。仔细观察内存的划分,不再是一整块,而是每个CPU有各自的内存。CPU访问本地内存不需要经过总线,CPU+本地内存成为一个NUMA节点。如果本地不够用,就要通过总线去其他NUMA节点申请内存,时间上肯定会久一些。、
NUMA往往是非连续内存模型(Discontiguous Memory Model)。内存被分成了多个节点,每个节点再被分成一个一个的页面。由于页需要全局唯一定位,页还是需要有全局唯一的页号的。但是由于物理内存不是连起来的了,页号也就不再连续了。注意,非连续内存模型不是NUMA模式的充分条件,一整块内存中物理内存地址也可能不连续。
当memory支持hotplug,**稀疏内存模型(Sparse Memory Model)**也应运而生。sparse memory最终可以替代Discontiguous memory的,这个替代过程正在进行中。
NUMA中CPU加本地内存成为一个Node,UMA中只有一个Node。
Node在Linux中用typedef struct pglist_data pg_data_t
表示。
https://elixir.bootlin.com/linux/v5.4.200/source/include/linux/mmzone.h#L698
主要成员变量包括:
1 | typedef struct pglist_data { |
整个内存被分为多个节点,pglist_data放在一个数组中。
1 | struct pglist_data *node_data[MAX_NUMNODES] __read_mostly; |
每一个Node分成多个zone,放在数组 node_zones 中,大小为 MAX_NR_ZONES。我们来看区域的定义。
1 | enum zone_type { |
管理内存域 | 描述 |
---|---|
ZONE_DMA | 对于不能通过ZONE_NORMAL进行DMA访问的,需要预留这部分内存用于DMA操作 |
ZONE_DMA32 | 用于低于4G内存访问 |
ZONE_NORMAL | 直接映射区,从物理内存到虚拟内存的内核区域,通过加上一个常量直接映射 |
ZONE_HIGHMEM | 高端内存区,对于 32 位系统来说超过 896M 的地方,对于 64 位没必要有的一段区域 |
ZONE_MOVABLE | 可移动区域,通过将物理内存划分为可移动分配区域和不可移动分配区域来避免内存碎片 |
ZONE_DEVICE | 为支持热插拔设备而分配的Non Volatile Memory非易失性内存 |
__MAX_NR_ZONES | 充当结束标记, 在内核中想要迭代系统中所有内存域, 会用到该常量 |
Zone的结构体如下(https://elixir.bootlin.com/linux/v5.4.200/source/include/linux/mmzone.h#L417)
1 | struct zone { |
主要的变量有:
注: 如果一个页被加载到 CPU 高速缓存里面,这就是一个热页(Hot Page),CPU 读起来速度会快很多,如果没有就是冷页(Cold Page)
page是组成物理内存的基本单位.用struc_page表示.
https://elixir.bootlin.com/linux/v5.4.200/source/include/linux/mm_types.h#L68
1 | struct page { |
谦哥定眼一看,page结构真的复杂!由于一个物理页面有很多使用模式,所以这里有很多union.
模式1: 从一整页开始用
此时Union中的变量为:
模式2: 仅需分配小块内存
如果某一页是用于分割成一小块一小块的内存进行分配的使用模式,则会使用 union 中的以下变量:
1 |
|
Linux kernel支持3种内存模型:
Multiprocessors系统设计内存架构的两种模式
Linux物理内存的三大结构
在日常的工作中可能我们会经常遇到程序fork失败的问题。遇到fork失败往往有两种可能性:
cat /proc/sys/kernel/threads-max
查看进程数预设值往往很大,几乎不太能超标,所以fork失败的原因大部分都是由于内存不足造成的。
我们知道,由于MMU实现了虚拟地址到物理地址的转换,所以我们在申请虚拟地址时往往可以申请一大块内存,这实际上是对资源的有效利用,毕竟只有内存真正被投入使用时(如memset)才会实际分配物理内存,这种允许内存超额commit的机制就是overcommit_memory。
虚拟内存需要物理内存作为支撑,当分配了太多虚拟内存,导致物理内存不够时,就发生了Out Of Memory。这种允许超额commit的机制就是overcommit。
其调用链关系如下图所示。
从图中我们可以看到,fork一步一步执行到了__vm_enough_memory
,该接口是overcommit的核心。
Linux根据参数vm.overcommit_memory
设置overcommit:
overcommit
,对内存申请来者不拒。overcommit
,提交给系统的总地址空间大小不允许超过。include/uapi/linux/mman.h
中声明了该宏定义:
1 |
当sysctl_overcommit_memory
等于OVERCOMMIT_ALWAYS
时,内核的处理方式是直接返回。
1 | if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS) |
当sysctl_overcommit_memory
等于OVERCOMMIT_GUESS
时,主要代码如下:
1 | if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) { |
其中global_page_state
负责读取vm_zone_stat数组中的各项值。
1 | extern atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS]; |
NR_FREE_PAGES
:富足的未分配资源;NR_FILE_PAGES
:page cache总和;NR_SHMEM
:进程间的share memory;NR_SLAB_RECLAIMABLE
:slab可回收的页数;totalreserve_pages
:每个zone保留的不能被用户空间分配的页数;get_nr_swap_pages
:针对空闲的swap页数所以free的计算公式如下:
free = NR_FREE_PAGES + NR_FILE_PAGES - NR_SHMEM + swap_pages + NR_SLAB_RECLAIMABLE
如果此时free小于totalreserve_pages
,则判定内存不足,否则 free -= totalreserve_pages
.
如果是普通进程,则还需要保留admin_reserve_kbytes
(/proc/sys/vm/admin_reserve_kbytes)的free page.
此时free若大于申请的pages,则不会发生overcommit。
当sysctl_overcommit_memory
等于OVERCOMMIT_NEVER
时,相关代码如下:
1 | allowed = vm_commit_limit(); |
主要看如下值:
sysctl_overcommit_kbytes
:/proc/sys/vm/overcommit_kbytes的值sysctl_overcommit_ratio
:/proc/sys/vm/overcommit_ratio的值totalram_pages
:可分配的总页数total_swap_pages
:可换出的页数hugetlb_total_pages
:huge page相关sysctl_user_reserve_kbytes
:/proc/sys/vm/user_reserve_kbytes的值,用户空间保留的内存大小vm_committed_as
:保存了当前系统中已经申请的虚拟内存大小由代码可知,vm_commit_limit
包括两种情况:
overcommit_kbytes
不为0, allow值为overcommit_kbytes
的一半;overcommit_kbytes
为0,allow值为可管理的总内存与vercommit_ratio
的乘积百分比那么如何设置overcommit的值呢?有三种方法:
本文通过分析内核代码粗略得分析了overcommit_memory的原理,也为后面的OOM-killer的原理做个铺垫。
]]>铟封是一种重要的软金属封接方法,陀螺的玻璃腔体和金属电极采用铟封,实现谐振腔的真空。目前铟封多采用手
工操作,较难实现铟环、电极和腔体孔的对准,且封接一致性差,因此本项目研制自动铟封设备,实现铟环和电极与腔体孔的自动对准放置,并施加温度和封接力。利用温度控制器、加热棒、Pt 电阻设计了温度闭环控制系统,采用位移-力控制策略,通过控制热压头的位移,结合系统刚度,实现了压封力的稳定控制。
实验表明,设备的温度控制最大偏差小于±0.3 ℃ ,压封力控制偏差小于 9 N。
本设备的用途用来封接下图所示装置。
设备组成:
热压装置用于实现电极的自动封接,主要由直线推杆、伺服电机、移动横梁、导向轴、直线轴承、光栅尺和压力传感器等组成,其结构如图所示。导向轴既用于构成装置的框架结构,又起导向作用,移动部分用直线轴承与导向轴配合,保证压封过程中带动电极加热装置沿直线运动。
伺服电机为HF-KN43J-S100型伺服电机;
压力传感器为BK-4B型压力传感器,该传感器为轮辐式测力传感器
光栅尺选用KA-300型光栅尺,其量程为320mm,精度为±5μm
腔体移动平台
作业机械手主要由三自由度运动平台和作业机械臂两部分组成。其中三自由度运动平台用于实现X、Y、Z三个方向的准确定位;作业机械臂用于实现电极的自动拾取和放置。
作业机械臂用于实现电极的自动拾取与放置,其结构如图10所示,主要由机械手安装角座、侧板、对准检测镜、微动开关、弹簧片以及电极吸附装置等组成。加强筋用来提高安装角座的刚度。对准检测镜用于腔体与电极加热装置加中热体中间孔的对准,根据检测镜所采集的图像,调节腔体移动平台X、Y方向的位移使电极正对加热体的中间孔,保证压封位置的准确性并避免造成电极损坏。弹簧片作为柔顺机构,避免产生冲击,起到保护零件的作用。微动开关则用来防止弹簧片产生塑性变形,同时也防止拾取电极时发生碰撞,损坏位移台。
电极的上料由操作人员将待压封电极放置于带有圆形凹台的上料座上,上料装置结构如图(a)所示。上料装置中的上料座为三合一上料座,如图(b)所示,D27尺寸上料座用于阴极上料, D14和D16尺寸的上料座用于阳极上料。
加热头中采用的加热元件为八光电热器件公司生产的加热棒(又称筒式加热器),如图所示,型号为HLJ2042。这种加热棒具有高效率(基本能无损耗传递热量),寿命长(发热线使用高镍合金),牢固性好(能够承受使用时机械振动及冲击),电气性能良好(具有优良的绝缘性能,特别是高温时能保持稳定的绝缘性能) ,可操作性强,结构紧凑,节省空间。
加热头中采用的温度传感器为德薄膜铂电阻温度传感器,相较于绕线式陶瓷温度传感器性价比更高,其测量范围为-70~300℃,精度1/3B级。
温度控制主要由温控器完成,计算机通过RS232与温控器通讯,读取温度实测值,写入温度设定值,温控器输出4-20mA标准电流信号到触发器输入端,触发器控制可控硅的通断实现加热器的工作功率,从而实现温度自动控制。
电极自动压封设备控制软件设计有温控器通讯模块,其作用是与温控器通讯,对温控器进行简单设定,读取温度实测值,写入温度设定值。其工作流程如图所示。欧姆龙温控器可以采用多种通讯方式实现与计算机的通讯,本设备采用CompoWay/F通讯方式。
温度控制软件界面如图所示:
计算机通过软件向运动控制卡发送命令,运动控制卡输出脉冲信号到步进电机驱动器,驱动器将脉冲信号放大驱动步进电机转动,直线推杆将旋转运动转换成直线运动带动热压头上下运动,而热压头会通过腔体、底板将压力施加给压力传感器,压力传感器输出电信号,并通过信号的放大、滤波,由数据采集卡采集输入到计算机,计算机控制软件将压力实测值与压力设定值对比输出控制信号,实现对压力的准确控制。
]]>本项目面向导航关键器件的自动装配,研制陀螺组件机器视觉精密装配设备。其装配流程如下图所示。待装配的组件有两组:薄片组件和金属支架。系统装配的两个零件为层叠式,先装配薄片组件,再装配金属支架,金属支架装配到薄片组件的上方,并保证其装配精度。
通过对装配任务进行分析,将系统搭建的设备的硬件部分分为四个功能模块,分别为:
装配作业模块
精密视觉测量模块
基座安装模块
上料模块
系统需要实现以下功能:
硬件连接图为:
如图所示,为精密装配系统的装配作业模块。装配作业模块主要分为:
三轴移动滑台
机械臂结构
机械臂固定在三轴移动滑台上,通过X、Y、Z三轴移动平台的运动改变作业机械手的位置,完成零件的装配作业。
三轴移动滑台由三根同样性能的导轨X、Y与Z轴以及电机构成,如图3.3所示,从装配策略以及运动空间考虑,X轴选用了150mm的行程,Y轴与Z轴选用了75mm的行程,出于对装配在导轨A、B、C三点配备了限位开关,分别为正限位、原点与负限位,限位开关可以将导轨的移动位置转换为电信号,控制导轨的运动位置,还可以通过限位开关来限制导轨的行程,避免发生碰撞。
机械臂是实现待装配零件拾取、运输与放置功能的主要组件,也是保证装配精度的关键。如图所示,机械臂主要部件有柱塞、微动开关、吸附头、机械夹钳、力传感器、柔性结构及各部件的连接板。
薄片组件靠吸附头吸附;
金属支架靠夹指夹持
如图所示为精密装配系统的视觉检测模块,视觉检测模块主要有视觉检测装置和三轴移动滑台组成。视觉检测装置由工业相机、同轴光源、环形光源以及远心镜头组成。视觉检测模块通过相机连接板和加强筋固定在三轴移动滑台上,通过x、y、z三轴移动平台的运动改变视觉检测系统的位置,完成待装配零件的结构特征的识别。零件在装配前都需要通过视觉检测模块检测零件的位置坐标。金属支架在装配前需要进行零件的选配,视觉系统检测零件结构特征,判断零件是否合格。
本系统的图像采集系统选型为:
基座的安装模块主要功能是保证基座在装配过程中的稳定性。为了实现系统的高精度装配,在保证基座装配过程不发生移动外,还要保持基座上表面的水平。
待装配的零件在装配前需要涂胶后放在上料座中,系统设计的上料座如图3所示。薄片组件的中心和外圈需要涂胶,所以上料座该部分设置悬空,避免胶粘到上料座上。金属支架选配时是倒置在上料座上,所以在金属支架上料座的中心设计了一个圆孔,通过该圆孔与金属支架上的圆柱实现约束定位,再通过与开口槽对准,完成零件检测时位置的固定。金属支架在装配前的定位是通过金属支架上料座的凸台。
利用MFC 调试软件,集成视觉检测、图像处理、数据采集、串口通信等模块。
本软件采用模块化设计方法,单独编写、调试各个功能模块,最后再集成为一个完整的系统。用于将各个功能模块集成为一个完整系统的功能模块即为系统主控模块,其功能是人机交互,协调功能模块的运行,软件系统结构如图1所示。用户在软件界面上操作时,主控模块控制相应功能模块起停,并将执行情况在软件界面上显示,同时反馈给用户,关键装配参数和产品型号等自动记录,以备查询。
技术特点:
(1)稳定高效的智能识别算法
本软件通过对复杂的陀螺组件进行“八图拼接”并针对零件表面的各种可能出现的情况依次进行讨论,最终实现九成以上零件是否达标的筛选,效果明显,识别准确稳定,同时为同类别零件的筛选提供借鉴价值。
(2)多线程设计
本软件的装配过程单独开辟线程,与主界面操作线程分离,保证程序的快速响应,执行效率高,解决软件运算过程中由于抢占资源而卡死的情况。
(3)模块化编程思想
本软件采用的是模块化编程思想,让各个部分互不影响分别编写,方便多人合作,开发效率高,周期短。,软件中应用了STL标准数据类型、高质量的开源图像处理库OpenCV和数据驱动模式等降低软件的出错率,提高系统的鲁棒性和可复用性。
(4)界面设计可操作性强
界面设计简洁,关键键位绑定响应按钮,操作方便,生产高效。为防止用户误操作,在各种情况下,对不需要控制的按钮都设置了按钮禁用。人工只需操作控制按钮部分即可完成软件的操作,人机交互友好。
]]>一、为什么要学习数据库
二、数据库的相关概念
DBMS、DB、SQL
三、数据库存储数据的特点
四、初始MySQL
MySQL产品的介绍
MySQL产品的安装 ★
MySQL服务的启动和停止 ★
MySQL服务的登录和退出 ★
MySQL的常见命令和语法规范
五、DQL语言的学习 ★
基础查询 ★
条件查询 ★
排序查询 ★
常见函数 ★
分组函数 ★
分组查询 ★
连接查询 ★
子查询 √
分页查询 ★
union联合查询√
六、DML语言的学习 ★
插入语句
修改语句
删除语句
七、DDL语言的学习
库和表的管理 √
常见数据类型介绍 √
常见约束 √
八、TCL语言的学习
事务和事务处理
九、视图的讲解 √
十、变量
十一、存储过程和函数
十二、流程控制结构
1.持久化数据到本地
2.可以实现结构化查询,方便管理
1、DB:数据库,保存一组有组织的数据的容器
2、DBMS:数据库管理系统,又称为数据库软件(产品),用于管理DB中的数据
3、SQL: 结构化查询语言,用于和DBMS通信的语言
1、将数据放到表中,表再放到库中
2、一个数据库中可以有多个表,每个表都有一个的名字,用来标识自己。表名具有唯一性。
3、表具有一些特性,这些特性定义了数据在表中如何存储,类似java中 “类”的设计。
4、表由列组成,我们也称为字段。所有表都是由一个或多个列组成的,每一列类似java 中的”属性”
5、表中的数据是按行存储的,每一行类似于java中的“对象”。
方式一:计算机——右击管理——服务
方式二:通过管理员身份运行
net start 服务名(启动服务)
net stop 服务名(停止服务)
属于c/s架构的软件,一般来讲安装服务端
方式一:通过mysql自带的客户端
只限于root用户
1 | # 方式二:通过windows自带的客户端 |
1 | # 1.查看当前所有的数据库 |
1.不区分大小写,但建议关键字大写,表名、列名小写
2.每条命令最好用分号结尾
3.每条命令根据需要,可以进行缩进 或换行
4.注释
单行注释:#注释文字
单行注释:-- 注释文字
多行注释:/* 注释文字 */
DQL(Data Query Language)
:数据查询语言DML(Data Manipulate Language)
:数据操作语言DDL(Data Define Languge)
:数据定义语言TCL(Transaction Control Language)
:事务控制语言语法:
SELECT 要查询的东西
【FROM 表名】;
特点:
① 通过select查询完的结果 ,是一个虚拟的表格
② 要查询的东西 可以是常量值、表达式、字段、函数
常用:
distinct
——去重
as
——起别名
ifnull()
——判断某字段或表达式是否为null,如果为null 返回指定的值,否则返回原本的值
isnull()
——判断某字段或表达式是否为null,如果是,则返回1,否则返回0
加法操作
条件查询:根据条件过滤原始表的数据,查询到想要的数据
语法:
1 | select |
分类:
一、条件表达式
> < >= <= = != <>
二、逻辑表达式
逻辑运算符:
and(&&)
or(||)
not(!)
三、模糊查询
last_name like 'a%'
between
、 and
、in
、 is null
、 is not null
order by的位置一般放在查询语句的最后(除limit语句之外)
1 | 语法: |
一、单行函数
1、字符函数
2、数学函数
round ——四舍五入
rand ——随机数
floor——向下取整
ceil——向上取整
mod——取余
truncate——截断
3、日期函数
4、流程控制函数
if(条件表达式,表达式1,表达式2):如果条件表达式成立,返回表达式1,否则返回表达式2
case语句 处理多分支
1 | # 情况1 |
5、其他函数
version版本
database当前库
user当前连接用户
sum 求和max 最大值min 最小值avg 平均值count 计数特点:1、以上五个分组函数都忽略null值,除了count(*)2、sum和avg一般用于处理数值型,max、min、count可以处理任何数据类型3、都可以搭配distinct使用,用于统计去重后的结果4、count的参数可以支持:字段、*、常量值,一般放1 建议使用 count(*)
语法:
1 | select 查询的字段,分组函数 |
1 | 特点: |
笛卡尔乘积:如果连接条件省略或无效则会出现
解决办法:添加有效的连接条件
一、传统模式下的连接 :等值连接——非等值连接
二、sql99语法:通过join关键字实现连接
支持:
等值连接、非等值连接 (内连接)
外连接
交叉连接
语法:
1 | select 字段,... |
好处:连接条件和筛选条件实现了分离,简洁明了!
三、自连接
案例:查询员工名和直接上级的名称
sql99
1 | SELECT e.last_name,m.last_name |
一条查询语句中又嵌套了另一条完整的select语句,其中被嵌套的select语句,称为子查询或内查询
在外面的查询语句,称为主查询或外查询
特点:
子查询都放在小括号内
重点掌握where或having后面:标量子查询
和列子查询
。
子查询优先于主查询执行,主查询使用了子查询的执行结果
子查询根据查询结果的行数不同分为以下两类:
① 单行子查询
结果集只有一行
一般搭配单行操作符使用:> < = <> >= <=
非法使用子查询的情况:
a、子查询的结果为一组值
b、子查询的结果为空
② 多行子查询
结果集有多行
一般搭配多行操作符使用:any、all、in、not in
in: 属于子查询结果中的任意一个就行
any和all往往可以用其他查询代替
例子
1 | # 标量子查询 |
应用场景:
实际的web项目中需要根据用户的需求提交对应的分页查询的sql语句
语法:
1 | select 字段|表达式,... |
特点:
1.起始条目索引从0开始
2.limit子句放在查询语句的最后
3.重要公式:
假如:每页显示条目数size,要显示的页数 page
1 | select * |
union:合并、联合,将多次查询结果合并成一个结果
语法:
1 | select 字段|常量|表达式|函数 【from 表】 【where 条件】 |
特点:
总结各条查询语句的执行顺序
1 | select 查询列表 # 7 |
语法:
1 | insert into 表名(字段名,...) |
特点:
修改单表语法:★
1 | update 表名 set 字段=新值,字段=新值 |
修改多表语法:
1 | update 表1 别名1,表2 别名2 |
方式1:delete语句
单表删除: ★
delete from 表名 【where 筛选条件】
级联删除:
delete 别名1,别名2
from 表1 别名1,表2 别名2
where 连接条件
and 筛选条件;
方式2:truncate语句
1 | truncate table 表名 |
两种方式的区别【面试题】
1.truncate不能加where条件,而delete可以加where条件
2.truncate的效率高一丢丢
3.truncate 删除带自增长的列的表后,如果再插入数据,数据从1开始
delete 删除带自增长列的表后,如果再插入数据,数据从上一次的断点处开始
4.truncate删除不能回滚,delete删除可以回滚
库的管理:
1 | # 一、创建库 |
表的管理:
1 | # 1.创建表 |
表的复制
1 | INSERT INTO author VALUES |
对于一个关系表,除了定义每一列的名称外,还需要定义每一列的数据类型。关系数据库支持的标准数据类型包括数值、字符串、时间等:
名称 | 类型 | 说明 |
---|---|---|
INT | 整型 | 默认是有符号型,无符号添加unsigned关键字或者zerofill |
BIGINT | 长整型 | 8字节整数类型,范围约+/-922亿亿 |
REAL | 浮点型 | 4字节浮点数,范围约+/-1038 |
DOUBLE | 浮点型 | 8字节浮点数,范围约+/-10308 |
DECIMAL(M,N) | 高精度小数 | 由用户指定精度的小数,例如,DECIMAL(20,10)表示一共20位,其中小数10位,通常用于财务计算 |
CHAR(N) | 定长字符串 | 存储指定长度的字符串,例如,CHAR(100)总是存储100个字符的字符串 |
VARCHAR(N) | 变长字符串 | 存储可变长度的字符串,例如,VARCHAR(100)可以存储0~100个字符的字符串 |
BOOLEAN | 布尔类型 | 存储True或者False |
DATE | 日期类型 | 存储日期,例如,2018-06-22 |
TIME | 时间类型 | 存储时间,例如,12:20:59 |
DATETIME | 日期和时间类型 | 存储日期+时间,例如,2018-06-22 12:20:59 |
一、数值型
1、整型
tinyint(1)、smallint(2)、mediumint(3)、int/integer(4)、bigint(5)
特点:
①都可以设置无符号和有符号,默认有符号,通过unsigned设置无符号
②如果超出了范围,会报out or range异常,插入临界值
③长度可以不指定,默认会有一个长度
长度代表显示的最大宽度,如果不够则左边用0填充,但需要搭配zerofill,并且默认变为无符号整型
2、浮点型
定点数:decimal(M,D)
浮点数:
float(M,D) 4
double(M,D) 8
特点:
①M代表整数部位+小数部位的个数,D代表小数部位
②如果超出范围,则报out or range异常,并且插入临界值
③M和D都可以省略,但对于定点数,M默认为10,D默认为0
④如果精度要求较高,则优先考虑使用定点数
二、字符型
char、varchar、binary、varbinary、enum、set、text、blob
char:固定长度的字符,写法为char(M),最大长度不能超过M,其中M可以省略,默认为1
varchar:可变长度的字符,写法为varchar(M),最大长度不能超过M,其中M不可以省略
三、日期型
year年
date日期
time时间
datetime 日期+时间 8
timestamp 日期+时间 4 比较容易受时区、语法模式、版本的影响,更能反映当前时区的真实时间
通用写法:
1 | CREATE TABLE IF NOT EXISTS stuinfo( |
主键和唯一的对比:
名称 | 唯一性 | 是否能为空 | table中个数 | 是否允许组合 |
---|---|---|---|---|
primary key | √ | × | 至多1个 | √:不推荐 |
unique | √ | √ | 可以有多个 | √:不推荐 |
外键:
1、要求在从表设置外键关系
2、从表的外键列的类型和主表的关联列的类型要求一致或兼容,名称无要求
3、主表的关联列必须是一个key(一般是主键或唯一)
4、插入数据时,先插入主表,再插入从表
删除数据时,先删除从表,再删除主表
Transaction Control Language 事务控制语言
通过一组逻辑操作单元(一组DML——sql语句),将数据从一种状态切换到另外一种状态
相关步骤:
1 | # 1、开启事务 |
隐式事务,没有明显的开启和结束事务的标志
比如insert、update、delete语句本身就是一个事务
显式事务,具有明显的开启和结束事务的标志
1、开启事务——取消自动提交事务的功能
2、编写事务的——组逻辑操作单元(多条sql语句)
3、提交事务或回滚事务
事务并发问题如何发生?
当多个事务同时操作同一个数据库的相同数据时
事务的并发问题有哪些?
事务的隔离级别
级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | √ | √ | √ |
read committed | × | √ | √ |
repeatable read | × | × | √ |
serializable | × | × | × |
脏读针对更新,幻读针对插入
设置隔离级别:
1 | set session|global transaction isolation level 隔离级别名; |
查看隔离级别:
1 | select @@tx_isolation; |
设置保存点 setpoint
1 | SET autocommit=0; |
视图和表的区别:
名称 | 关键字 | 是否占用物理空间 | 使用 |
---|---|---|---|
视图 | create view | 只是保存了sql逻辑 | 一般是只查看 |
表 | create table | 保存了数据 | 增删改查 |
应用场景:
视图的好处:
语法:
CREATE VIEW 视图名
AS
查询语句;
增删改影响原始表的结构,所以下面的语句几乎不用!
1 | # 1、查看视图的数据 ★ |
1 | # 方式一: |
1 | # 方式二: |
1 | # 可以同时删除多个 |
1 | DESC test; |
1 | #1全局变量 |
1 | #2会话变量 |
1 | # 用户变量 和 局部变量 |
用户变量和局部变量的对比
作用域 | 定义位置 | 语法 | |
---|---|---|---|
用户变量 | 当前会话 | 会话的任何地方 | 加@符号,不用指定类型 |
局部变量 | 定义它的BEGIN END中 | BEGIN END的第一句话 | 一般不用加@,需要指定类型 |
一组经过预先编译的
sql
语句的集合,理解成批处理语句
好处:
分类:
参数列表包含三部分:参数模式、参数名、参数类型
,例子——in stuname varchar(20)
语法:
1 | DELIMITER $ |
注意
1 | 1、需要设置新的结束标记 |
调用存储过程
call 存储过程名(实参列表)
##函数
###创建函数
学过的函数:LENGTH、SUBSTR、CONCAT等
语法:
1 | CREATE FUNCTION 函数名(参数名 参数类型,...) RETURNS 返回类型 |
###调用函数
SELECT 函数名(实参列表)
###函数和存储过程的区别
1 | 关键字调用语法返回值应用场景 |
##流程控制结构
###分支
一、if函数
语法:if(条件,值1,值2)
特点:可以用在任何位置
二、case语句
语法:
1 | 情况一:类似于switch |
特点:
可以用在任何位置
三、if elseif语句
语法:
1 | if 情况1 then 语句1; |
特点:
只能用在begin end中!!!!!!!!!!!!!!!
三者比较:
应用场合
if函数简单双分支
case结构等值判断 的多分支
if结构区间判断 的多分支
###循环
语法:
1 | 【标签:】WHILE 循环条件 DO |
特点:
1 | 只能放在BEGIN END里面 |
1 |
|
1 |
|
1 |
|
1 |
|
关于stack和queue
标准库是以head files的形式呈现的
C++的标准模板库 >= STL
1 |
|
注:
1 | // 格式 |
1 | list<string> c; |
不定序容器是红黑树
Set、Map中的key——不可重复
Multiset、Multimap中的key——不可重复
辅助函数
1 | using std::cout; |
1 |
|
技巧:
1 |
|
说明:
就像叶子从痛苦的蜷缩中要用力舒展一样,人也要从不假思索的蒙昧里挣脱,这才是活着。
——柴静《看见》
这是我大学作为项目负责人的封山之作,完成了毕业前要做一件真正的实物产品的心愿。这件作品耗费了我巨大的心血,最终的实现效果也没有达到我理想的状态。实践本就没有完美的事情,永远都在探索完美的路上。下文是我在毕业后总结归纳的一些细枝末节,搭建博客之后开学的第二周,作为一个会议记录于此,并时刻提醒自己,接受自己的不完美,但是持续不断的努力。
本发明涉及智能寻迹爬楼载重清洁装置,属于服务类机器人技术领域,其主要功能是实现上下楼负重搬运以及楼梯清洁,可以在没有电梯的建筑楼层使用。
本发明的最初目的是解决搬运诸如水桶等重物时人工上楼搬运费时费力的问题。同时考虑到楼梯各时间段使用频繁,楼梯层数较多,人力清洁工作繁重,故在尾部加装清洁装置。两者结合通过电机带传动组成搬运清洁两用型的智能装置。国内多数爬楼载重机器人,采用升降式、腿式结构,上楼缓慢效率不高且爬楼机械装置大多不适用于平地,平地使用时所受阻力较大,行动缓慢,转弯不便。基于上述背景我们设计了独特的变形前轮,实现在平路和上下楼的智能切换,以及转向装置能自动调整前轮的角度实现快速转弯。该小车前轮采用变形轮机构,可平地导向也可上下楼梯,控制方式灵活。可适用于各种工厂、住宅楼的货物搬运。同时对车尾稍加人性化设计,便可实现清洁功能,具有较大的社会价值和经济价值。
本发明的技术方案:
一种智能寻迹爬楼载重清洁装置,包括电气部分和机械部分;
所述的电气部分4包括电源4-1和电气模块4-2,以控制超声波导距、单片机、舵机以及电机;
所述的机械部分包括变形轮机构1、转向机构2、清洁机构3和机身5;
所述的机身5包括行星轮5-1和机身车座5-2,所述的机身车座5-2为长方形的面板,所述的行星轮5-1有两个,通过轴对称固定在机身车座5-2尾部的两端;
所述的变形轮机构1有两个,包括变形轮支架1-4、变形轮分支1-1、三爪卡销1-3、变形舵机1-2、圆盘支架1-6和舵盘1-5;
所述的变形轮分支1-1共三个,依次连接组成轮子的外轮廓;所述的变形轮分支1-1是由三根带有弧度的杆组成的三杆机构,一根外侧杆、一根滑动杆和一根支撑杆;外侧杆构成轮子的外轮廓,其一端设有滑槽,变形轮变形时,相邻的变形轮分支1-1的滑动杆滑入滑槽中;滑动杆和支撑杆的一端均固定在外侧杆上,支撑杆的另一端固定在滑动杆上,滑动杆的另一端铰接于变形轮支架1-4上;所述的滑动杆的下端、内侧设有滑轨,三爪卡销1-3在滑轨中滑动;所述的圆盘支架1-6为中心开有圆孔的圆盘;所述的变形轮支架1-4为中空的圆柱体,圆盘支架1-6中心圆孔的直径与变形轮支架1-4的直径相同,变形轮支架1-4穿过外侧的圆盘支架1-6中心开口,并固定在外侧的圆盘支架1-6上;所述的三爪卡销1-3为一端均匀分布有三个爪的圆柱体,三爪卡销1-3的圆柱体嵌套在变形轮支架1-4的内部,三爪卡销1-3的爪插在三杆机构的滑轨内,三爪卡销1-3的圆柱体设有五个通孔;所述的变形轮分支1-1的滑动杆下部有一个通孔,通过销键和铰环将三个三杆机构均匀固定在变形轮支架1-4上;所述的变形轮支架1-4、三爪卡销1-3和外侧的圆盘支架1-6,共同固定在内侧的圆盘支架1-6上,内侧和外侧的圆盘支架1-6配合,以固定三爪卡销1-3并保证三杆机构在一个平面内移动;所述的变形舵机1-2一端带有五个插杆,通过内侧的圆盘支架1-6的中心圆孔插入三爪卡销1-3的通孔内,使变形舵机1-2与三爪卡销1-3连接;所述的变形舵机1-2,另一端安装有舵盘1-5,变形舵机1-2提供转矩,通过正反转实现变形轮整体和变型的切换;
所述的转向机构2包括轴承座2-1、转动连接装置2-2、转向机构电机2-3、轴2-4、转动舵机2-5、联轴器2-6、皮带2-7、球头拉杆2-8和万向节2-9;所述的轴承座2-1共两个,对称固定在机身车座5-2前部的两端;所述的轴2-4的两端固定在轴承座2-1上,轴2-4的两端安装有万向节2-9,轴2-4通过万向节2-9与舵盘1-5相连接,使变形轮机构1与转向机构2相连接;所述的转向舵机2-5固定在机身车座5-2前部,转向舵机2-5的一侧安装有转动连接装置2-2,转动连接装置2-2的两个插孔分别用于连接球头拉杆2-8,球头拉杆2-8的另一端与万向节2-9外侧连接,使得转向舵机2-5与舵盘1-5相连接;所述的联轴器2-6套装在轴2-4上,所述的转向机构电机2-3固定机身车座5-2上;所述的皮带2-7,一端套装在联轴器2-6上,另一端套装在转向机构电机2-3的输出端;所述的转向机构电机2-3通过皮带2-7和联轴器2-6带动轴2-4旋转,轴2-4来传递动力,带动变形轮机构1运转;所述的转动舵机2-5工作时会左右摆动,球头拉杆2-8将转动舵机2-5的摆动转换为角度变化传递给万向节2-9,接着将该变化传递给与万向节2-9连接的变形轮舵机1-2,从而完成转向工作;
所述的清洁机构3位于装置的尾部,包括齿条3-1、刷子3-2、清洁机构电机3-3和齿轮3-4;所述的齿条3-1固定在机身车座5-2的尾部,所述的清洁机构电机3-3固定在机身车座5-2上,所述的齿轮3-4固定在清洁机构电机3-3的输出端,齿轮3-4与齿条3-1相互咬合;所述的刷子3-2位于机身车座5-2的尾部的底表面,通过螺栓活动连接,与齿条3-1固定连接;所述的清洁机构电机3-3的转动带动齿轮3-4转动,进而使齿条3-1横向往复运动,带动刷子3-2的往复运动,实现楼梯的清洁功能;
所述的电气部分4由电源4-1和电气模块4-2组成;所述的电源4-1和电气模块4-2并排固定在机身车座5-2中间位置;所述的电气模块4-2包括单片机模块和超声波模块。
所述的机身车座5-2为亚克力板材质。
所述的清洁机构电机3-3为37型减速电机。
所述的变形舵机1-2为RDS3109双轴舵机。
所述的轴2-4的直径为6mm。
本发明的有益效果:车采用变形轮结构:平地导向时为圆形,上下楼梯时一分为三,控制方式灵活;该车克服了现有爬楼装置只用于爬楼且平地转向、前行困难的缺点,通过变形轮实现平地上楼自由转换。变形轮简化了传统的铰链机构改用销传动在导轨中移动实现变形,结构简单且不失牢固与严密性,极具创造价值;上下四个超声波模块检测距离差判断墙、楼梯从而绕行和变形;车尾安装清洁装置,可自动清洁楼梯。
]]>工作马马虎虎,只想在兴趣和游戏中寻觅快活,充其量只能获得一时的快感,绝不能尝到从心底涌出的惊喜和快乐,但来自工作的喜悦并不像糖果那样——放进嘴里就甜味十足,而是需要从苦劳与艰辛中渗出,因此当我们聚精会神,孜孜不倦,克服艰辛后的成就感,世上没有哪种喜悦可以类比.
——稻盛和夫《活法》
大二的时候,出于对工业设计和机械结构的兴趣,自学了SolidWorks,拉上几个朋友一起做了这个项目。来到威海,我们饱含着对大海的一种情怀和向往,致使我们想到了海产品的加工。鱼丸加工整个流水线很是庞大,剖割、清洗、杀菌、擂溃、调料、成型、蒸煮、冷冻到包装。我们着重从拥有较多机械结构的剖割、鱼丸成型和包装三部分建模——自动杀鱼系统、鱼丸成型系统和包装机系统。此套系统运用于鱼的前期处理包括去鳞,去内脏,去头尾,清洗等,以及鱼丸的成型和包装一体化的生产。
主要是用SolidWorks进行设计,keyshot进行渲染,Solid-Composer结合AE进行视频制作。整个项目的工程量相当大,从目标敲定到最后的完工花了将近四个月的时间。由于之间机器人比赛的经验,作为队长我很好的进行了分工和计划推进,最终的结果还是挺让人激动的。
机器主要传动方式为齿轮传动和皮带传动,将鱼平放在传送带上,前后的夹持装置把鱼尾和鱼头夹住;水平方向的刀片将鱼腹剖开,带有软毛刷的小滚筒将内脏清除,同时毛刷中的空心管冲水清洗鱼腹,内脏落至下方垃圾箱内;之后竖直方向的刀片将鱼头鱼尾切掉,鱼腹由出口处传送带运出,鱼头鱼尾回到垃圾箱上方,夹持装置上的顶杆使其落至垃圾箱中。
鱼丸的成型方面运用了行星齿轮机构并综合了能量守恒及流体力学等方面要求设计了变速管以实现鱼丸成型,防止阻塞现象的发生。
包装机则通过两侧传送装置同时送纸袋,创新地运用到三边封口装置,其中用四个锥齿轮和连杆组成传动机构同时封左右和下口,上口鱼丸掉落后,通过两个机械臂的配合使包装袋转过90度后,另一个连杆机构再封装上口,而后掉落至传送带完成包装。
这些东西相当于对之前有个交代吧,知识都是相通的,要不断得在成功的地方吸取养分,然后迁移到其他地方。正如前文的摘录,孜孜以求才能活得更大的幸福,加油!
]]>今天记录一下操作元素的思路。课题中的图像处理往往是通过各种方法提取到边缘后扫点,将点储存在容器中再拟合圆或者直线。如何快速扫描像素呢,国外的一本书中给出了14中不同的方式。
1 |
|
就像来自遥远宇宙的一束光,不知道它从哪里来,也不知道它到哪里去,从我们头上掠过,波澜不惊。
视觉测量误差主要来源有:图像采集误差,相机安装误差,运动平台误差。其中高精度的CCD传感器与图像采集卡的误差微乎其微可以忽略;相机坐标系如果与水平面不共面或共面但是存在夹角都会引入误差;三维运动平台的几何误差的垂直度问题将导致图像处理中的圆心提取和图像拼接,误差需要补偿。
各坐标系的空间位置如图所示,三维运动平台中X轴和Y轴滑台不垂直,YV0和YC0为实际的Y轴滑台运动方向。图像坐标系UOV与世界坐标系XWOYW之间的夹角为θ,以世界坐标系XW轴为基准,图像U轴向世界坐标系Y轴正方向偏转为正,反之为负。视觉测量模块三维运动平台X轴与YV0轴滑台之间的夹角为α。世界坐标系和装配作业坐标系XCOYC之间的夹角为φ,以世界坐标系X轴为基准,装配作业坐标系X轴向世界坐标系Y轴正方向偏转为正,反之为负。装配作业模块三维运动平台X轴与YC0轴滑台之间夹角为β。
根据坐标系之间的坐标转换关系建立误差补偿模型,以图像中一点为例,分别将其从图像坐标系装换到世界坐标系和装配作业坐标系中。
首先完成由图像坐标系向世界坐标系的转换,设图像中一点(u,v),其在世界坐标系中的坐标为(x,y),由于图像坐标系单位为像素,世界坐标系单位为微米,单位转换关系为:
相应的矩阵形式为:
图像坐标系和世界坐标系坐标轴之间存在夹角θ,需对图像坐标系进行旋转,旋转矩阵为:
采集过程中相机会变换采集位置,应该对图像坐标系原点和世界坐标系原点之间的距离进行补偿,需要进行平移变换,相应的平移变换矩阵为:
图像坐标系原点在世界坐标系中的坐标值(Dx,Dy)无法直接获取,从图5.4可知,相机在三维运动平台中XY向滑台位移分别为dx和dy,经计算得出此时相机在世界坐标系的坐标(Dx,Dy),计算公式为:
图像中一点(u,v)转换为世界坐标系一点(x,y)的误差补偿模型表达式为:
得到零件在世界坐标系中的坐标后,装配作业模块夹持壳体完成对正。由于世界坐标系和装配作业坐标系之间存在夹角,并且装配作业模块XY滑台不垂直,需要进行误差补偿。
装配过程仅需获取两个零件圆心的相对位置,不要求零件的绝对位置。可直接建立相对位置转换的误差补偿模型。设两零件在世界坐标系中的距离为(ΔX,ΔY),转换到装配作业坐标系后坐标为(ΔXC,ΔYC)。
首先对世界坐标系进行旋转补偿夹角φ,旋转矩阵为:
旋转变换之后得到两个圆心在装配作业坐标系中的坐标差,由于装配作业模块XY向滑台不垂直,需要计算出XY滑台应该补偿的实际距离,转换矩阵为:
零件圆心世界坐标系中的坐标差转换到装配作业坐标系中,并计算出实际的XY滑台补偿位移量(ΔXC0,ΔYC0)误差补偿模型的矩阵表达式为:
终于敲完了,latex确实很好用,矩阵书写很方便,坚持用Markdown来写博客!
]]>软件链接如下:
链接:https://www.lanzous.com/i1xnevc 密码:5vsy
注:不能更新,如果更新,可重新执2-4步
这是我的桌面,软件的主题有很多,挑自己喜欢的风格和颜色即可。
]]>1 | $ hexo new "My New Post" |
生成一篇新的博客,然后选用一款markdown
编辑器进行编辑即可
1 | $ hexo clean && hexo s |
这条语句经常用来本地测试博客的更改情况和效果。
1 | $ hexo generate |
生成文件
1 | $ hexo deploy |
部署到服务器
]]>