PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=087)
本文发布于 2019-08-24 11:48:24,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=087)
环境说明
无
起因
我们有个项目,做了一个基于TX2核心板的硬件板卡,这个板卡除了做相关算法的检测之外,还得提供一些控制LED啊、通过拨码开关这些来设置一些内容的小功能,你说气不气,这些小功能还必须要实现。如果LED和拨码开关直接挂载到tx2的gpio上的话,就没有必要写本文了,没意义,因为只要学过嵌入式的人,给他一个板子,再差劲,读取和设置一个gpio的高低电平总会吧。如果不会,建议还多学学嵌入式基础知识(从单片机玩起来,先裸奔,再上OS)。
这里我们知道,其实对于芯片来说,引脚是非常珍贵的,如果芯片所需要实现的功能复杂,那么通用的io管脚异常珍贵,这里就出现了一个种器件,叫做IO扩展器(所实话,我都不知道这样翻译对不对),从名字可知,就是较少的引脚扩展出更多的引脚,本文就是用两个引脚扩展出了16个引脚。
TX2上,由于使用了Linux,读取和设置gpio也是非常简单的,直接打开相关的gpio设备,读写即可。不要问我为啥要用linux,不用其他os,或者直接裸机控制,我只能够回答曰:我是想啊,可是我实力不允许啊,什么caffe、opencv、ncnn、cuda等等堆到其他系统或者裸机下,我着实能力不够,弄不过去,关键还麻烦。
IO扩展器
IO扩展器原理简介
其实本文的核心就是IO扩展器,这个器件由于我玩的板子少,见识少,我是第一次见到这种器件。下图就是这种器件在tx2手册里面的推荐使用方法。
这种器件就是通过某种总线,然后扩展出尽可能多的io口。这里的这个器件通过I2C总线,扩展出16个io口。
这里我们可以看到:SCL和SDA是I2C通信总线,A0和A1是可编程配置I2C从器件地址。(这里不懂也没关系,就是这个器件的地址可以编程设置,至于为啥要有这个地址,可以简单理解为一个总线挂载多个设备,某一时刻总线只能为其中一个设备提供服务,这些设备的区分就是通过地址来完成的。)
P00-P17是扩展出来的IO口。
知道以上足够了,没学过的也足够了。
这个器件的特性是:通过I2C协议操作他的寄存器,他有8个8位寄存器,0-1寄存器是INPUT用,2-3寄存器是OUTPUT用,4-5好像是优先级裁决,6-7是配置寄存器,就是配置IO口是输出还是输入,如果接触过单片机、stm32这种的GPIO程序的话,是很好理解的。(手动滑稽,我出了校门就没接触过了)
不要问下图的是什么器件(问就是不知道,手动滑稽),这只是举个例子,这个io扩展器的寄存器分配以及功能就是这样的。
IO扩展器编程操作---shell command
首先这个器件是通过I2C协议操作的,不用关心I2C是什么,他们你可以类比为HTTP。
那么Linux上怎么通过I2C操作这个器件呢?
首先,Linux上有一组工具:i2c-tool,它可以读取所有芯片的i2c bus上挂载的芯片,设置和读取寄存器等等,拿来做测试或者封装一个程序都是不错的。TX2的ubuntu16.04是自带这个工具的,他的详细用法大家去百度,我不造轮子了。
在Ubuntu里面操作I2C是非常简单的,你不需要关心I2C的具体传输规定,不用管时序这些烦人的事情。
首先我们先用工具来测试,美滋滋:
还记得上文我提了这个IO扩展器的从地址的事情吗?由于我的A0和A1都是接的低电平,在这里我的器件地址是0x74,怎么来的,看下图。
然后通过i2cdetect查看我们器件的位置(0x74)(注意,这个命令需要传入一个I2C总线序号,我这里是0,也就是说你要知道你这个IO扩展器挂载到哪个总线上的,这和SCL和SDL接线有关,有兴趣的可以去翻一翻手册就知道了,UU代表有人在占用这个设备)
shell:>i2cdetect -y -r -a 0
i2cdump可以通过标准i2c协议探测出所有的寄存器的值,下图8个寄存器的值就的出来了,分别对应上面的寄存器说明。XX代表没有这个寄存器。
shell:>i2cdump -f -y 0 0x74
然后:
- i2cset -f -y i2c_bus_num slave_addr reg_num value 设置寄存器值
- i2cget -f -y i2c_bus_num slave_addr reg_num 获取寄存器值
其实通过上面的操作就可以完成整个io扩展器的操作,我们可以通过程序执行shell命令的方式设置和操作值。
IO扩展器编程操作---syscall
实际上,linux做了很多东西,我们可以用标准的linux sys-api来完成以上内容,其实这些api就是i2ctool使用的部分。
下面不墨迹,直接给出led操作的接口,有需要的参考吧。
int open_led_device(const char * i2c_bus_num, int slave_addr){
int fd = 0;
if ( 0 > (fd = open(i2c_bus_num, O_RDWR)) ){//打开i2c总线
perror("open i2c bus error:");
return -1;
}
if(ioctl(fd, I2C_SLAVE_FORCE, DEVICE_I2C_ADDR) < 0) {//设置从器件地址,这里使用I2C_SLAVE_FORCE进行强行设置,那么这个设备忙
perror("set device slave addr error:");
return -1;
}
if ( 0 > write_led_register(fd, LED_REG1_CFG_ADDR, LED_REG1_CFG_VAL) ){//设置写寄存器值,这两个宏和你的硬件连线有关。这里不给出。
printf("init pin for output-mode failed.\n");
close(fd);
return -1;
}
return fd;
}
int read_led_register(int fd, char reg_addr, char *read_val){//读寄存器
if (write(fd, ®_addr, 1) != 1){//write reg addr ,从器件地址通过open接口设置好后,先写入要读的reg地址
perror("set reg addr error:");
return -1;
}
if ( read(fd, read_val, 1) != 1 ){//read data,等待i2c返回刚刚要查询的reg值
perror("read reg error:");
return -1;
}
return 0;
}
int write_led_register(int fd, char reg_addr, char data){//写寄存器
char tmp_buf[2];
tmp_buf[0] = reg_addr;//reg 地址
tmp_buf[1] = data;//reg 值
if (write(fd, tmp_buf, 2) != 2){//write data
perror("write data error:");
return -1;
}
return 0;
}
void close_led_device(int fd){//关闭
close(fd);
}
LED灯
此处省略XXX字。
相信每个人都知道,常规情况下,在LED灯的两边加电源正极和负极,灯就能亮。在电路设计上,一般来说,LED灯的一端都是和电源正极或者负极是连接好的,另一端和GPIO口接上。如果GPIO输出的电压和另一端电压逻辑一致(比如都是高电平、都是低电平),灯就不亮,反之就亮。
注意:这段话是有毛病的,但是一般人这样理解就行了(不了解电子电路的就看到这就行了)。对于懂的人,这里多说一句,这里还有一个三极管做开关作用,也是就说LED灯两端都接在电源正负极,中间有个三极管开关。
拨码开关
这种器件,又是另外一种新奇的东西了,感觉我这两年写的“祖传屎山“太多了,现在看到各种硬件器件都是眼前一亮的感觉。
就是类似下图这种。
其作用是:
你可以人为的按这个+-号按钮,设置数字,这个数字会反应到电路上,从而芯片可以读取你设置的数字。
说白了,你的系统中有个数字参数,你可以通过这种器件进行手动设置,通过驱动程序,就可以更改这个系统参数,是不是 so 简单。
这里简单说明一下电路是怎么反应出对应的数字的:
我就举个栗子,下图是个例子拨码开关(手动滑稽,这里多说一句,上图的拨码开关,是4个拨码开关合在一起的,下图的这个输出编码是一个拨码开关的)
这里可以简单理解为:
一个拨码开关有5个引脚,一个引脚是C,接GND或者VCC,其他4个是编码引脚,是需要接GPIO,并去取编码的。
其实很简单:
例如:C端我接VCC,1248默认值为0,那么数字1,1号引脚接通,那么8421io口输出二进制就是0001,转换为10进制,就是1.
然后写个程序读取这4个脚的值,转换一下,就OK。
后记
无
参考文献
- 无
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。