您现在的位置是:网站首页> 硬件

单片机上层开发技术及项目收集

  • 硬件
  • 2024-12-18
  • 1013人已阅读
摘要

单片机上层开发技术及项目收集


物联网智能快递柜解决方案.pdf

linkboy 天问block国产图形化硬件编程

图形化简单智能硬件和手机app制作

Proteus软件初学入门篇

OPC技术

Linux驱动基础篇:hello驱动

CAN总线

c#与VisionPro联合编程使用相机获取图像

Opencv+Zbar二维码识别(二维码校正)

winavr  和avr studio 的关系

监控视频编码器4路模拟转网络监控视频转换器视频服务器兼容海康

AGV导航

设备驱动




OPC技术

OPC(OLE for Process Control)技术是指为了给工业控制系统应用程序之间的通信建立一个接口标准,在工业控制设备与控制软件之间建立统一的数据存取规范。它给工业控制领域提供了一种标准数据访问机制,将硬件与应用软件有效地分离开来,是一套与厂商无关的软件数据交换标准接口和规程,主要解决过程控制系统与其数据源的数据交换问题,可以在各个应用之间提供透明的数据访问。

近段时间,遇到不少人都被OPCClient与OPCServer之间的通讯搞得头大,通过几次远程协助后,总结了OPCClient和OPCServer在Windows上运行方式的恩怨,希望对各位有用。 
目前市场上的OPCClient和OPCServer软件在Windows上的运行方式有Windows 桌面程序和Windows NT服务。本来也没啥。但由于OPCCLient是一个厂家的软件,而OPCServer是另外一个厂家的软件,由于软件的多样性,也就导致了如下一些现象:
1. OPCCLient连接目标OPCServer,发现无法连接,但在OPCServer计算机上明明看见OPCServer进程已经启动。
2. OPCCLient连接目标OPCServer,能连接,也能看见测点,但无法获取到数据。
经过多次现场的积累后,发现此类问题多出现在OPCClient和OPCServer软件在Windows上的运行方式不同导致的。也就是说,OPCClient和OPCServer软件的运行方式不一样。譬如,OPCCLient是Windows NT服务方式,而OPCServer是桌面程序方式(多是组态软件的OPCServer都是桌面程序方式吧!!)。而当OPCCLient是Windows 桌面程序方式,OPCServer时Windows NT服务时,发现上面的现象基本不出现。这是为什么呢?
原因如下:
OPCClient和OPCServer都是基于DCOM的应用,DCOM的特点是OPCServer无需先运行或启动,等待OPCCLient请求时,由操作系统在将OPCServer拽起来。这种机制的好处就是随用随启。但这种机制如果处理不好吧,就会导致一些问题。当OPCCLient是Windows NT服务时,OPCServer被拽起来后,是运行在System这个系统账户下面的。相对于Windows的桌面用户来说,是另外一个隔离开的空间。因此当桌面运行类型的OPCServer被Windows NT服务方式的OPCCLient拽起来后,被运行在System这个系统账户的空间。而如果这个OPCServer程序又做了全局唯一进程运行的限制或与数据库只允许一个TCP连接时,上述的两种现象基本就会出现。这就是这段时间好几个朋友遇到的OPC通讯故障现象。
如果让自己开发的OPC程序兼容性更好呢?
1. 当开发OPCCLient程序时,最好使用Windows桌面程序方式,这种方式可兼容OPCServer程序运行在Windows桌面程序方式和Windows NT服务方式。
2. 当开发OPCServer程序时,最好使用Windows NT服务方式,这种方式可兼容OPCClient程序运行在Windows桌面程序方式和Windows NT服务方式。
如果很不幸遇到了Windows NT服务的OPCClient去采集Windows 桌面程序的OPCServer(加上OPCServer本身的全局唯一限制),那么你可以去Windows NT服务的管理器中将Windows NT服务的OPCClient更改为指定的系统用户运行,大多数情况下可以解决问题



Linux驱动基础篇:hello驱动

1.jpg


我们学习编程的时候都会从hello程序开始。同样的,学习Linux驱动我们也从最简单的hello驱动学起。

驱动层和应用层

还记得实习那会儿我第一次接触嵌入式Linux项目的时候,我的导师让我去学习项目的其它模块,然后尝试着写一个串口相关的应用。那时候知道可以把设备当做文件来操作,但是不知道为什么是这样,就去网上搜了一些代码(驱动代码),然后和我的应用代码放在同一个文件里。给导师看了之后,导师说那些驱动程序不需要我写,那些驱动已经写好被编译到内核里了,可以直接用了,我只需关注应用层就好了。我当时脑子里就在打转。。what?STM32用一个串口不就是串口初始化,然后想怎么用就怎么用吗?后来经过学习才知道原来是那么一回事呀。这就是单片机转转嵌入式Linux的思维误区之一。学嵌入式Linux之前我们有必要暂时忘了我们单片机的开发方式,重新梳理嵌入式Linux的开发流程。下面看一下STM32裸机开发与嵌入式Linux开发的一些区别:

2.png


3.png


4.png


嵌入式Linux的开发方式与STM32裸机开发的方式有点不一样。在STM32的裸机开发中,驱动层与应用层的区分可能没有那么明显,常常都杂揉在一起。当然,有些很有水平的裸机程序分层分得还是很明显的。但是,在嵌入式Linux中,驱动和应用的分层是特别明显的,最直观的感受就是驱动程序是一个.c文件里,应用程序是另一个.c文件。比如我们这个hello驱动实验中,我们的驱动程序为hello_drv.c、应用程序为hello_app.c。驱动模块的加载有两种方式:第一种方式是动态加载的方式,即驱动程序与内核分开编译,在内核运行的过程中加载;第二种方式是静态加载的方式,即驱动程序与内核一同编译,在内核启动过程中加载驱动。在调试驱动阶段常常选用第一种方式,因为较为方便;在调试完成之后才采用第二种方式与内核一同编译。

STM32裸机开发与嵌入式Linux开发还有一点不同的就是:STM32裸机开发最终要烧到板子的常常只有一个文件(除开含有IAP程序的情况或者其它情况),嵌入式Linux就需要分开编译、烧写。

Linux字符设备驱动框架

我们先看一个图:

5.png


当我们的应用在调用open、close、write、read等函数时,为什么就能操控硬件设备。那是因为有驱动层在支撑着与硬件相关的操作,应用程序在调用打开、关闭、读、写等操作会触发相应的驱动层函数。

本篇笔记我们以hello驱动做分享,hello驱动属于字符设备。实现的驱动函数大概是怎么样的是有套路可寻的,这个套路在内核文件include/linux/fs.h中,这个文件中有如下结构体:

6.png


这个结构体里的成员都是些函数指针变量,我们需要根据实际的设备确定我们需要创建哪些驱动函数实体。比如我们的hello驱动的几个基本的函数(打开/关闭/读/写)可创建为(以下代码来自:百问网):

(1)打开操作

static int hello_drv_open (struct inode *node, struct file *file){
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);    return 0;
}

打开函数的两个形参的类型要与struct file_operations结构体里open成员的形参类型一致,里面有一句打印语句,方便直观地看到驱动的运行过程。

(2)关闭操作

static int hello_drv_close (struct inode *node, struct file *file){
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);    return 0;
}

(3)读操作

static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_to_user(buf, kernel_buf, MIN(1024, size));    return MIN(1024, size);
}

copy_to_user函数的原型为:

static inline int copy_to_user(void __user *to, const void *from, unsigned long n);

用该函数来读取内核空间(kernel_buf)的数据给到用户空间(buf)。 另外,kernel_buf的定义如下:

static char kernel_buf[1024];

MIN为宏:

#define MIN(a, b) (a < b ? a : b)

MIN(1024, size)作为copy_to_user的实参意在对拷贝的数据长度做限制(不能超出kernel_buf的大小)。

(4)写操作

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){    int err;
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(kernel_buf, buf, MIN(1024, size));    return MIN(1024, size);
}

copy_from_user函数的原型为:

static inline int copy_from_user(void *to,const void __user volatile *from,unsigned long n)

用该函数来将用户空间(buf)的数据传送到内核空间(kernel_buf)。

有了这些驱动函数,就可以给到一个struct file_operations类型的结构体变量hello_drv,如:

static struct file_operations hello_drv = 
{
    .owner   = THIS_MODULE,
    .open    = hello_drv_open,
    .read    = hello_drv_read,
    .write   = hello_drv_write,
    .release = hello_drv_close,
};

有些朋友可能没见过这种结构体初始化的形式(结构体成员前面加个.号),可以去看往期笔记:指定初始化器进行了解。

上面这个结构体变量hello_drv容纳了我们hello设备的驱动接口,最终我们要把这个hello_drv注册给Linux内核,套路就是这样的:把驱动程序注册给内核,之后我们的应用程序就可以使用open/close/write/read等函数来操控我们的设备,Linux内核在这里起到一个中间人的作用,把两头的驱动与应用协调得很好。

我们前面说了驱动的装载方式之一的动态装载:把驱动程序编译成模块,再动态装载。动态装载的体现就是开发板已经启动运行了Linux内核,我们通过开发板串口终端使用命令来装载驱动。装载驱动有两个命令,比如装载我们的hello驱动:

方法一:insmod hello_drv.ko方法二:modprobe hello_drv.ko

其中modprobe命令不仅能装载当前驱动,而且还会同时装载与当前驱动相关的依赖驱动。有了转载就有卸载,也有两种方式:

方法一:rmmod hello_drv.ko方法二:modprobe -r hello_drv.ko

其中modprobe命令不仅卸载当前驱动,也会同时卸载依赖驱动。

我们在串口终端调用装载与卸载驱动的命令,怎么就会执行装载与卸载操作。对应到驱动程序里我们有如下两个函数:

module_init(hello_init); //注册模块加载函数module_exit(hello_exit); //注册模块卸载函数

这里加载与注册有用到hello_inithello_exit函数,我们前面说的把hello_drv驱动注册到内核就是在hello_init函数里做,如:

static int __init hello_init(void)
{
	int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);    /* 注册hello驱动 */
	major = register_chrdev(0, 			  /* 主设备号,为0则系统自动分配 */
                            "hello", 	  /* 设备名称 */
                            &hello_drv);  /* 驱动程序 */
    
	/* 下面操作是为了在/dev目录中生成一个hello设备节点 */
    /* 创建一个类 */
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "hello");		return -1;
	}	
    /* 创建设备,该设备创建在hello_class类下面 */
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	
	return 0;
}

这里这个驱动程序入口函数hello_init中注册完驱动程序之后,同时通过下面连个创建操作来创建设备节点,即在/dev目录下生成设备文件。据我了解,在之前版本的Linux内核中,设备节点需要手动创建,即通过创建节点命令mknod 在/dev目录下自己手动创建设备文件。既然已经有新的方式创建节点了,这里就不抠之前的内容了。

以上就是分享关于驱动一些内容,通过以上分析,我们知道,其是有套路(就是常说的驱动框架)可寻的,比如:

#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>/* 其她头文件...... *//* 一些驱动函数 */static ssize_t xxx_read (struct file *file, char __user *buf, size_t size, loff_t *offset){

}static ssize_t xxx_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){

}static int xxx_open (struct inode *node, struct file *file){

}static int xxx_close (struct inode *node, struct file *file){

}/* 其它驱动函数...... *//* 定义自己的驱动结构体 */static struct file_operations xxx_drv = {
	.owner	 = THIS_MODULE,
	.open    = xxx_open,
	.read    = xxx_read,
	.write   = xxx_write,
	.release = xxx_close,	/* 其它程序......... */};/* 驱动入口函数 */static int __init xxx_init(void){

}/* 驱动出口函数 */static void __exit hello_exit(void){

}/* 模块注册与卸载函数 */module_init(xxx_init);
module_exit(xxx_exit);/* 模块许可证(必选项) */MODULE_LICENSE("GPL");

按照这样的套路来开发驱动程序的,有套路可寻那就比较好学习了,至少不会想着怎么起函数名而烦恼,按套路来就好,哈哈

关于驱动的知识,这篇笔记中还可以展开很多内容,限于篇幅就不展开了。我们之后再进行学习、分享。下面看一下测试程序/应用程序(hello_drv_test.c中的内容,以下代码来自:百问网):

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */int main(int argc, char **argv){	int fd;	char buf[1024];	int len;	
	/* 1. 判断参数 */
	if (argc < 2) 
	{		printf("Usage: %s -w <string>\n", argv[0]);		printf("       %s -r\n", argv[0]);		return -1;
	}	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);	if (fd == -1)
	{		printf("can not open file /dev/hello\n");		return -1;
	}	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';		printf("APP read : %s\n", buf);
	}
	
	close(fd);	
	return 0;
}

就是一些读写操作,跟我们学习文件操作是一样的。学单片机的有些朋友可能不太熟悉main函数的这种写法:

int main(int argc, char **argv)

main函数在C中有好几种写法,在Linux中常用这种写法。argc与argv这两个值可以从终端(命令行)输入,因此这两个参数也被称为命令行参数。argc为命令行参数的个数,argv为字符串命令行参数的首地址。

最后,我们把编译生成的驱动模块hello_drv.ko与应用程序hello_drv_test放到共享目录录nfs_share中,同时在开发板终端挂载共享目录:

mount -t nfs -o nolock,vers=4 192.168.1.104:/home/book/nfs_share /mnt

关于nfs网络文件系统的使用可查看往期笔记:如何挂载网络文件系统? 。

然后我们通过insmod 命令装载驱动,但是出现了如下错误:

7.png


这是因为我们的驱动的编译依赖与内核版本,编译用的内核版本与当前开发板运行的内核的版本不一致所以会产生该错误,重新编译内核,并把编译生成的Linux内核zImage映像文件与设备树文件*.dts文件拷贝到开发板根文件系统的/boot目录下,然后进行同步操作:

#mount -t nfs -o nolock,vers=4 192.168.1.114:/home/book/nfs_share /mnt#cp /mnt/zImage /boot#cp /mnt/.dtb /boot#sync

最后,重启开发板。最后,成功运行程序:

1.png


下面是完整的hello驱动程序(来源:百问网):

// 公众号:嵌入式大杂烩#include <linux/module.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/miscdevice.h>#include <linux/kernel.h>#include <linux/major.h>#include <linux/mutex.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/stat.h>#include <linux/init.h>#include <linux/device.h>#include <linux/tty.h>#include <linux/kmod.h>#include <linux/gfp.h>/* 1. 确定主设备号                                                                 */static int major = 0;static char kernel_buf[1024];static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));	return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));	return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file){
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);	return 0;
}static int hello_drv_close (struct inode *node, struct file *file){
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);	return 0;
}/* 2. 定义自己的file_operations结构体                                              */static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
	.release = hello_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序                                *//* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */static int __init hello_init(void){	int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */


	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "hello");		return -1;
	}
	
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	
	return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */static void __exit hello_exit(void){
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major, "hello");
}/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

嵌入式Linux的学习内容是很多的、坑也是很多的,死磕到底即可。


CAN总线

CAN是控制器局域网络(ControllerAreaNetwork,CAN)的简称,是由以研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO11898),是国际上应用最广泛的现场总线之一。在北美和西欧,CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。

CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一对差分信号线,使用比较特别的差分信号

CAN总线的特点

  (1)多主机方式工作:网络上任意节点可在任意时刻其他节点发送数据,通信方式灵活;

  (2)网络上每个节点都有不同的优先级,可以满足实时性的要求;

  (3)采用非破坏性仲裁总线结构,当两个节点同时向网络上传送信息时,优先级高的优先传送;

  (4)传送方式有点对点、点对多点、点对全局广播三种;

  (5)通信距离可达6km;通信速率可达1MB/s;节点数可达110个;

  (6)采用的是短帧结构,每帧有8个有效字节;

  (7)具有可靠的检错机制,使得数据的出错率极低;

  (8)当发送的信息遭到破坏后,可自动重发;

  (9)节点在严重错误时,会自动切断与总线联系,以免影响总线上其他操作。

1.jpg

  CAN总线原理

  CAN总线以广播的方式从一个节点向另一个节点发送数据,当一个节点发送数据时,该节点的CPU把将要发送的数据和标识符发送给本节点的CAN芯片,并使其进入准备状态;一旦该CAN芯片收到总线分配,就变为发送报文状态,该CAN芯片将要发送的数据组成规定的报文格式发出。此时,网络中其他的节点都处于接收状态,所有节点都要先对其进行接收,通过检测来判断该报文是否是发给自己的。

2.jpg

  由于CAN总线是面向内容的编址方案,因此容易构建控制系统对其灵活地进行配置,使其可以在不修改软硬件的情况下向CAN总线中加入新节点。

  CAN总线的应用

  CAN总线在组网和通信功能上的优点以及其高性价比据定了它在许多领域有广阔的应用前景和发展潜力。这些应用有些共同之处:CAN实际就是在现场起一个总线拓扑的计算机局域网的作用。不管在什么场合,它负担的是任一节点之间的实时通信,但是它具备结构简单、高速、抗干扰、可靠、价位低等优势。CAN总线最初是为汽车的电子控制系统而设计的,目前在欧洲生产的汽车中CAN的应用已非常普遍,不仅如此,这项技术已推广到火车、轮船等交通工具中。

  (1)CAN总线技术的应用:国外知名汽车基本都已经采用了CAN总线技术,例如沃尔沃、林肯、奥迪、宝马等,而国内汽车品牌,例如奇瑞等公司也已经有几款车型应用了总线技术。CAN总线技术就是通过遍布车身的传感器,将汽车的各种行驶数据发送到“总线”上,在这个信息共享平台上,凡是需要这些数据的接收端都可以从“总线”上读取需要的信息,从而使汽车的各个系统协调运作、信息共享、保证车辆安全行驶、舒适和可靠。一般来说,越高档的车配备的CAN_BUS数量越多,价格也越高,如途安、帕萨特等车型当中都配备了多个CAN总线。

  (2)汽车CAN总线节点ECU的硬件设计:汽车CAN总线研发的核心技术就是对带有CAN接口的ECU进行设计,其中ECU的CAN总线模块由CAN控制器和CAN收发器构成。CAN控制器执行完整的CAN协议,完成通讯功能,包括信息缓冲和接收滤波。CAN控制器与物理总线之间需CAN收发器作为接口,它实现CAN控制器与总线之间逻辑电平信号的转换。

  (3)CAN总线在国内自主品牌汽车中的应用:由于受成本控制、技术实力等因素的限制,CAN_BUS总线技术一般都出现在国外高端汽车,在A级及以下级别车型当中,该项技术大多出现在合资品牌当中,如POLO、新宝来等。在自主品牌中,采用CAN总线技术的车型中很少,风云2则是其中的代表车型。风云2CAN总线技术,可以实现发动机、变速箱、ABS、车身、仪表及其他控制器的通讯,做到全车信息及时共享。在风云2的组合仪表盘当中,阶段里程、未关车门精确显示、安全带未系提醒等20多项信息全部可以显示,比同级产品增加一倍,这样增加了驾驶过程中的安全度。

3.jpg

  can总线是数字信号还是模拟信号

  can总线是数字信号,与一般的通信总线相比,CAN总线的数据通信具有突出的可靠性、实时性和灵活性。由于其良好的性能及独特的设计,CAN总线越来越受到人们的重视。


c#与VisionPro联合编程使用相机获取图像

c#和Vpro,极简实现,5步完成一个可以运行的视觉程序

机器人无损收发小数的原理

TCP通信 | 三行代码制作自己的专属工具

vs+c#做的HMI程序模板,可以拿来直接用

源程序 | c#做的HMI程序模板

WinForm | 一种程序配置文件的读写方法

观察者模式 | .net中的事件

如何调试TCP通信

用队列消息状态机实现TCP通信

VisionPro | 使用卡尺工具测量宽度

今天要说的内容是:1)c#下使用VisionPro的工具驱动相机获取图像;2)制作显示界面,显示相机获取的图像;3)相机响应用户指令。

用到的工具:1)gige相机;2)VisionPro9.0;3)VisualStudio2019 。


1 一个难懂的地方

在VisionPro的帮助文档中有个词“帧获取器”,大家默默的把它翻译成相机就行了。使用GigE相机的时候就是指相机,其他的也许有不同,不知道。

VisionPro中,相机的初始化设置界面包含了相机的所有属性。这些在c#环境中都可以通过编码的方式实现。

1.png

2 取像工具和取像队列

上图对应的工具是CogAcqFifoTool,这个类包含上图中的所有内容。只有一个相机的时候,可以直接使用完成与相机的连接。

CogAcqFifoTool acqTool = new CogAcqFifoTool();

拍照获取图像使用的是取像工具的属性Operator,它是ICogAcqFifo类型。

ICogAcqFifo AcqFifo = AcqTool.Operator;

使用取像队列的Acquire方法即可获得一张图像,取像队列还提供了异步方法,通常的应用中这个同步方法就足够了。

ICogImage image= AcqFifo.Acquire(out numAcqs);

3 构建项目

工具都准备好了,但是代码不能慌着写。使用相机获取一张图像这个任务可以分成两个子任务:1)获取图像;2)展示图像。

2.png

展示图像使用CogRecordDispaly控件,找个喜欢的位置放好就可以了。

获取图像怎么做?可以暂停,思考一下。

VisionPro提供了获取图像的例子,可以用来学习(2 取像工具和取像队列)中工具的使用技巧。在项目中不可以完全使用。

从“单一职责”的角度考虑,界面类中不应该存在连接相机内容。界面的职责是显示,所需要的只是一张图像。就像讨厌的老板:“我只要结果,过程我不管”。

利用面向对象分析这个项目,相机是一个独立存在的个体,有自己的名字,划分为一个单独的类,很正常。这个类就命名为 Camra 。

硬件设备的使用过程通常分三步:连接→使用功能→断开。所以,Camra类的方法按照这三部分添加:GetImageClose。连接放在构造方法中。

3.png

设计好类才,编码工作才可以开始。

 public class Camra 

  {    

    private CogAcqFifoTool acqTool = null;   

      private int numAcqs = 0;      

  public ICogAcqFifo AcqFifo { get; set; } = null;     

   public Camra()        {       

     acqTool = new CogAcqFifoTool();       

     AcqFifo = acqTool.Operator;           

 if (AcqFifo is null)          

  {               

 throw new Exception("No camra found.");     

       }     

   }       

 public ICogImage GetImage()  

      {           

 ICogImage image= AcqFifo.Acquire(out int triggerNumber);      

      numAcqs++;        

    if (numAcqs > 4)      

     {              

  numAcqs = 0;         

       GC.Collect();       

     }          

  return image;      

  }     

   public void Close()        

{          

  CogFrameGrabbers frameGrabbers = new CogFrameGrabbers();       

     foreach (ICogFrameGrabber fg in frameGrabbers)    

        {             

   fg.Disconnect(false);       

     }      

  }  

  }



Opencv+Zbar二维码识别(二维码校正)

二维码和车牌识别基本都会涉及到图像的校正,主要是形变和倾斜角度的校正,一种二维码的畸变如下图:


1.jpg

这个码用微信扫了一下,识别不出来,但是用Zbar还是可以准确识别的~~。


这里介绍一种二维码校正方法,通过定位二维码的4个顶点,利用仿射变换校正。基本思路:滤波->二值化->膨胀(腐蚀)操作->形态学边界->寻找直线->定位交点->仿射变换校正->Zbar识别。




滤波、二值化:

2.jpg


腐蚀操作:

3.jpg



形态学边界:


4.jpg


寻找直线:


5.jpg


角点定位:


6.jpg


仿射变换校正:

7.jpg



Zbar识别:


8.jpg


Code实现:


#include "zbar.h"      

#include "cv.h"      

#include "highgui.h"      

#include <iostream>      

 

using namespace std;      

using namespace zbar;  //添加zbar名称空间    

using namespace cv;      

 

int main(int argc,char*argv[])    

{  

Mat imageSource=imread(argv[1],0);

Mat image;

imageSource.copyTo(image);

GaussianBlur(image,image,Size(3,3),0);  //滤波

threshold(image,image,100,255,CV_THRESH_BINARY);  //二值化

imshow("二值化",image);

Mat element=getStructuringElement(2,Size(7,7));  //膨胀腐蚀核

//morphologyEx(image,image,MORPH_OPEN,element);

for(int i=0;i<10;i++)

{

erode(image,image,element);

i++;

}

imshow("腐蚀s",image);

Mat image1;

erode(image,image1,element);

image1=image-image1;

imshow("边界",image1);

//寻找直线 边界定位也可以用findContours实现

vector<Vec2f>lines;

HoughLines(image1,lines,1,CV_PI/150,250,0,0);

Mat DrawLine=Mat::zeros(image1.size(),CV_8UC1);

for(int i=0;i<lines.size();i++)

{

float rho=lines[i][0];

float theta=lines[i][1];

Point pt1,pt2;

double a=cos(theta),b=sin(theta);

double x0=a*rho,y0=b*rho;

pt1.x=cvRound(x0+1000*(-b));

pt1.y=cvRound(y0+1000*a);

pt2.x=cvRound(x0-1000*(-b));

pt2.y=cvRound(y0-1000*a);

line(DrawLine,pt1,pt2,Scalar(255),1,CV_AA);

}

imshow("直线",DrawLine);

Point2f P1[4];

Point2f P2[4];

vector<Point2f>corners;

goodFeaturesToTrack(DrawLine,corners,4,0.1,10,Mat()); //角点检测

for(int i=0;i<corners.size();i++)

{

circle(DrawLine,corners[i],3,Scalar(255),3);

P1[i]=corners[i];

}

imshow("交点",DrawLine);

int width=P1[1].x-P1[0].x;

int hight=P1[2].y-P1[0].y;

P2[0]=P1[0];

P2[1]=Point2f(P2[0].x+width,P2[0].y);

P2[2]=Point2f(P2[0].x,P2[1].y+hight);

P2[3]=Point2f(P2[1].x,P2[2].y);

Mat elementTransf;

elementTransf= getAffineTransform(P1,P2);

warpAffine(imageSource,imageSource,elementTransf,imageSource.size(),1,0,Scalar(255));

imshow("校正",imageSource);

//Zbar二维码识别

ImageScanner scanner;      

scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1); 

int width1 = imageSource.cols;      

int height1 = imageSource.rows;      

uchar *raw = (uchar *)imageSource.data;         

Image imageZbar(width1, height1, "Y800", raw, width * height1);        

scanner.scan(imageZbar); //扫描条码    

Image::SymbolIterator symbol = imageZbar.symbol_begin();  

if(imageZbar.symbol_begin()==imageZbar.symbol_end())  

{  

cout<<"查询条码失败,请检查图片!"<<endl;  

}  

for(;symbol != imageZbar.symbol_end();++symbol)    

{      

cout<<"类型:"<<endl<<symbol->get_type_name()<<endl<<endl;    

cout<<"条码:"<<endl<<symbol->get_data()<<endl<<endl;       

}      

namedWindow("Source Window",0);

imshow("Source Window",imageSource);        

waitKey();    

imageZbar.set_data(NULL,0);  

return 0;  

}



winavr  和avr studio 的关系

简单地说,程序的编写时在WINAVR上进行的,然后用它编译成Hex文件。 然后利用AVR Studio把该Hex文件下载到单片机(该步骤需要用到编程器),或者仿真。



监控视频编码器4路模拟转网络监控视频转换器视频服务器兼容海康

4路网络视频服务器强大功能

1、1-4路模拟信号转网络信号,接入海康,大华,雄迈,宇视,天地伟业NVR,显示4个独立通道画面。

2、支持同轴高清编码,兼容雄迈AHD,海康TVI,大华CVI同轴摄像机

3、编码分辨率可高达1080P,无损画质,实时流畅

4、H.265X编码,兼容H264,比H264传输更稳定,更流畅!

5、简便手机监控,只需输入设备序列号就可以实现手机监控

6、支持音视频对讲,实现视频会议功能

7、支持RTSP协议,提供SDK开发包

 

 

夜视全黑效果

户外实拍效果

 

 

 

 


 


AGV导航

AGV导航

1.png

设备驱动

驱动程序通过硬件连接到的计算机总线或通信子系统与设备进行通信。当调用程序调用驱动程序中的例程时,驱动程序向设备发出命令。设备将数据发送回驱动程序后,驱动程序可以调用原始调用程序中的例程

程序目的 

设备驱动程序的主要目的是通过充当硬件设备与使用该设备的应用程序或操作系统之间的转换器来提供抽象。程序员可以独立于最终用户使用的任何特定硬件来编写更高级别的应用程序代码。例如,用于与串行端口交互的高级应用程序可能仅具有“发送数据”和“接收数据”两个功能。在较低级别上,实现这些功能的设备驱动程序将与安装在用户计算机上的特定串行端口控制器进行通信。控制16550 UART所需的命令与控制FTDI所需的命令有很大不同串行端口转换器,但是每个特定于硬件的设备驱动程序都将这些详细信息抽象到相同(或相似)的软件接口中。


设备驱动的发展 

编写设备驱动程序需要深入了解给定平台功能的硬件和软件工作方式。因为驱动程序需要对硬件功能的低级别访问才能运行,所以驱动程序通常在特权较高的环境中运行,并且如果出现问题,可能导致系统运行问题。相反,可以停止现代操作系统上的大多数用户级软件,而不会严重影响系统的其余部分。如果对设备进行了错误的编程,即使在用户模式下执行的驱动程序也可能使系统崩溃。这些因素使诊断问题更加困难和危险。


因此,编写驱动程序的任务通常落在为硬件开发公司工作的软件工程师或计算机工程师身上。这是因为与大多数外部人士相比,他们在硬件设计方面拥有更好的信息。此外,从传统意义上讲,为了保证其客户可以最佳方式使用其硬件,符合硬件制造商的利益。通常,逻辑设备驱动程序(LDD)由操作系统供应商编写,而物理设备驱动程序(PDD)由设备供应商实现。但是,近年来,非供应商为专有设备编写了许多设备驱动程序,主要用于免费和开源 操作系统。在这种情况下,硬件制造商提供有关设备通信方式的信息非常重要。尽管可以通过逆向工程来学习此信息,但是使用硬件要比使用软件困难得多。


Microsoft尝试通过创建用于驱动程序开发的新框架称为Windows Driver Foundation(WDF)来减少由于设备驱动程序编写不当而引起的系统不稳定。这包括鼓励用户开发某些类型的驱动程序(主要是那些实现基于消息的协议以与其设备通信的驱动程序)的用户模式驱动程序框架(UMDF),作为用户模式驱动程序。如果此类驱动程序发生故障,则不会引起系统不稳定。在内核模式驱动程序框架 (KMDF)模型继续允许开发内核模式设备驱动程序,但是尝试提供已知会引起问题的功能的标准实现,包括取消I / O操作,电源管理以及即插即用设备支持。



 

苹果有一个用于在macOS上开发驱动程序的开源框架,称为I / O Kit。


在Linux环境中,程序员可以将设备驱动程序作为内核的一部分,分别作为可加载模块或作为用户模式驱动程序来构建(对于存在内核接口的某些类型的设备,例如USB设备)。Makedev包含Linux中设备的列表,包括ttyS(终端)、lp(并行端口)、hd(磁盘)、循环和声音(其中包括Mixer、sequencer、dsp和音频)。


Microsoft Windows .sys文件和Linux .ko文件可以包含可加载的设备驱动程序。可加载设备驱动程序的优点在于,仅在必要时才可以加载它们,然后再将其卸载,从而节省了内核内存。


内核模式与用户模式 

设备驱动程序,特别是在现代Microsoft Windows平台上,可以在内核模式(x86 CPU上为Ring 0)或在用户模式下(x86 CPU上为Ring 3)运行。在用户模式下运行驱动程序的主要好处是提高了稳定性,因为编写不良的用户模式设备驱动程序无法通过覆盖内核内存来使系统崩溃。另一方面,用户/内核模式转换通常会带来相当大的性能开销,因此使内核模式驱动程序成为低延迟网络的首选。


用户模块只能通过使用系统调用来访问内核空间。最终用户程序(例如UNIX Shell或其他基于GUI的应用程序)是用户空间的一部分。这些应用程序通过内核支持的功能与硬件交互。


虚拟设备驱动程序 

虚拟设备驱动程序代表设备驱动程序的特定变体。它们用于仿真硬件设备,特别是在虚拟化环境中,例如在Microsoft Windows计算机上运行DOS程序时或在Xen主机上运行客户机操作系统时,例如。虚拟设备驱动程序没有使来宾操作系统与硬件对话,而是扮演相反的角色并模拟一块硬件,从而使来宾操作系统及其驱动程序在虚拟机中运行可能会有访问真实硬件的幻想。来宾操作系统尝试访问硬件的尝试被路由到主机操作系统中的虚拟设备驱动程序,例如 函数调用。虚拟设备驱动程序还可以将模拟的处理器级事件(例如中断)发送到虚拟机中。


虚拟设备也可以在非虚拟环境中运行。例如,虚拟网络适配器与虚拟专用网络一起使用,而虚拟磁盘设备与iSCSI一起使用。虚拟设备驱动程序的一个很好的例子是Daemon Tools。


虚拟设备驱动程序有多种变体,例如VxD、VLM和VDD。


图形化简单智能硬件和手机app制作

画面包板软件https://fritzing.org/download/ 

做app软件https://blynk.io/





Top