Quantcast
Channel: Sam的技术Blog
Viewing all 158 articles
Browse latest View live

Ubuntu64bit-15.04 开发环境安装配置

$
0
0

作者: Sam (甄峰)  sam_code@hotmail.com

 

最近要重新安装ROS, 看ROS支持Ubuntu 15.04. 所以安装之。

 

0. 修改软件仓库:

在咱们伟大的天朝,Linux,Android开发者都明白的。 同时也要感谢那些把Linux软件仓库,Android开发者仓库搬运到国内的人和组织。

0.1:修改仓库列表权限:

chmod 777 /etc/apt/sources.list

0.2: 修改内容:

deb http://ftp.sjtu.edu.cn/ubuntu/ dapper main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ dapper-backports main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ dapper-proposed main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ dapper-security main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ dapper-updates main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu-cn/ dapper main multiverse restricted universe 

0.3:

sudo apt-get update  //更新源

sudo apt-get upgrade //更新系统

 

 

 

1. 安装必要的软件包:

1.1:安装NFS Server:

$sudo apt-get install nfs-kernel-server

 

 

1.2:安装samba Server:

$sudo apt-get install samba

 

1.3: 安装开机管理工具:

sudo apt-get install sysv-rc-conf

 

1.4:安装Samba配置软件:

sudo apt-get install system-config-samba

 

2. 设置Samba:

添加用户

$sudo smbpasswd -a sam

$sudo system-config-samba

也许会出如下错误:

SystemError: could not open configuration file /etc/libuser.conf.

可以 $sudo touch /etc/libuser.conf

即可解决。

$system-config-samba

使用它设置samba.

 

3.关闭放火墙:

$sudo ufw disable


 

I2C基础学习

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

最近想在Android-ARM上添加一个9轴Sensor,倾向使用I2C串行总线。这就涉及到以下几个方面:
A: 通过I2C,将Sensor与开发板连接起来。
B: 在Linux层写Driver。使上层程序可以拿到Sensor数据。(此处指NativeC程序可以得到数据)
C: 使Android Framework得到Sensor数据,并通过SensorManager使Java程序可以得到Sensor数据。

那所有的基础,就是I2C。在这里学习之。

0. I2C串行总线简介:
采用串行总线技术,可以使系统的硬件设计大为简化,系统的体积减小,可靠性提高。并使更改和扩展变得更容易。

0.1: 常见的串行扩展总线包括:
I2C(Inter  IC BUS)
单总线(1-WIRE BUF)
SPI(Serial Peripheral Interface)总线
Microwire/PLUE

0.2: I2C串行总线概述:
I2C总线是PHLIPS推出的一种串行总线,具备多主机系统所需的总线裁决和高低速器件同步功能的高性能串行总线。
I2C总线只有两根双向信号线,一根是数据线SDA, 另一根是时钟线SCL.

多个I2C器件可以连接在同一I2C总线上。如何区分数据是发送给谁的呢?
每个连接到I2C总线上的器件都有一个唯一的地址。


0.3:I2C 数据位的有效性标准:
I2C总线数据位的有效性规定如下:时钟信号(SCL)为高电平时,数据线(SDA)上的数据必须保持稳定. 只有时钟信号为低电平期间,数据线上的电平财允许变化。



0.4: I2C起始信号和终止信号:
主机(只有主机)会发送起始和终止信号,起始和终止信号之间,总线处于被占用状态。
起始信号,终止信号如下:
SCL为高电平时,SDA由高向低的变化,即为起始信号。
SCL为高电平时,SDA由低向高的变化,即为终止信号。


很显然,因为数据位在SCL为高电平时是不变的,所以SDA在SCL为高时的两种变化,很容易被区分出来。他们就是起始和终止信号
总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。

 
0.5: I2C总线寻址:
I2C总线协议中规定:采用7Bit寻址,1-7bit表示从机地址,0bit表示数据方向(即:读/写,0: 表示从主机向从机写,1:表示由从机往主机读)
从机地址还包括:固定地址部分,和可编程地址部分。
器件类型由:D7-D4 4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的
用户自定义地址码:D3-D13位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。
所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了


0.6: I2C数据传送格式
数据传送时,1个字节8位数据,后面跟一个应答位。所以,一桢有9位。



主机在发送起始信号后,后面必须立刻发送一个从机地址,以表明这个数据是发送给哪个从机的。从机发现这个地址和自己的地址匹配,则必须发送应答信号,表示自己在线。
主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位,此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。


0.7: I2C总线操作(数据的读写)
操作I2C总线,无非就是主机和丛机之间数据交换,大致分以下三种情况:
A:主机向从机写数据:


主机发送起始信号,后面紧跟7bit的从机地址,1bit的写标记。表明数据是从主机到从机的。
8bit后,从机发送ACK,表明自己存在。
之后主机不断送数据,从机相应发送ACK。
直到最终主机发送停止信号
 
 
B:主机由从机读数据:

主机发送起始信号,后面紧跟7bit的从机地址,1bit的读标记。表明数据是由从机到主机的。
8bit后,从机发送ACK,表明自己存在。
之后主机不断由从机读取数据,从机相应发送ACK。
直到没有数据,则回复NAK。
主机则发送停止信号。停止这次通讯
 
C:


利用RS,重复的起始条件,可以更快的开始下一次读写。要比停止信号后再次开始更有效率。
 
 
 
 
1. I2C硬件接口电路:
这部分电路全部来自网络。
 

这个电路是基于LPC2368 ARM7芯片进行设计的,使用其内部的I2C接口作为主设备,使用ADT75和SC16IS740作为两个从设备的I2C总线应用。

ADT75是一个带I2C接口的温度传感器器件。 它的数据手册上说明:地址跟A0、A1、A2引脚的接法有关。



我们这里的实例是将A0、A1、A2全部接到高电平上,因此其地址是:1001111(即0x4F),又因根据协议再给地址添加一个最低位(方向位,默认给写方向),因此最后这个温度传感器作为从设备的地址是:10011110(即0x9E)。

 
 
Sam在处理MPU9250时,也看到:AD0 = 0时,地址1101000, AO0 =1时地址是1101001. 当然还要添加读写位。这里感谢Jeamson指出,以前一直以为这个设置方法是使用程序实现的呢。
所以:IIC地址,固定部分由芯片出厂时指定,可编程部分可由芯片Pin脚接线调整。
 
 
 
2. 无操作系统下IIC的使用简介
2.1: IIC主机寄存器介绍:
以LPC2368为例:
 LPC2368中I2C接口寄存器描述
    LPC2368中有三个I2C总线接口,分别表示为I2C0、I2C1和I2C2,每个I2C接口都包含7个寄存器。它们分别是:
I2C控制置位寄存器(I2CONSET): 8位寄存器,各位不同的设置是对I2C总线不同的控制。






符号



描述



复位值



1:0



-



保留,用户软件不要向其写入1。从保留位读出的值未被定义



NA



2



AA



声明应答标志。为1时将为需要应答的情况产生一个应答



0



3



SI



I2C中断标志。当I2C状态改变时该位置位



0



4



STO



总线停止条件控制。1发出一个停止条件,当总线检测到停止条件时,STO自动清零



0



5



STA



总线起始条件控制。1进入主模式并发出一个起始条件



0



6



I2EN



总线使能控制。1为使能



0



7



-



保留,用户软件不要向其写入1。从保留位读出的值未被定义



NA



I2C控制清零寄存器(I2CONCLR): 8位寄存器,对I2CONSET寄存器中的相应为清零。








符号



描述



复位值



1:0



-



保留,用户软件不要向其写入1。从保留位读出的值未被定义



NA



2



AAC



声明应答标志清零位。向该位写入1清零I2CONSET寄存器中的AA



0



3



SIC



中断标志清零位。向该位写入1清零I2CONSET寄存器中的SI



0



4



-



保留,用户软件不要向其写入1。从保留位读出的值未被定义



NA



5



STAC



起始条件清零位。向该位写入1清零I2CONSET寄存器中的STA



0



6



I2ENC



总线禁能控制。写入1清零I2CONSET寄存器中的I2EN



0



7



-



保留,用户软件不要向其写入1。从保留位读出的值未被定义



NA




I2C状态寄存器(I2STAT): 8位只读寄存器,用于监控总线的实时状态(可能存在26种状态)。





符号

描述

复位值

2:0

-

3个位不使用且总是为0 

0

7:3

Status

这些位给出I2C接口的实时状态,不同的值代表不同的状态,状态码请参考数据手册

0x1F


I2C数据寄存器(I2DAT): 8位寄存器,在SI置位期间,I2DAT中的数据保持稳定。





符号

描述

复位值

7:0

Data

该寄存器保留已经接收到或者准备要发送的数据值 

0


I2C从地址寄存器(I2ADR): 8位寄存器,I2C总线为从模式时才使用。主模式中该寄存器无效。





符号

描述

复位值

0

GC

通用调用使能位 

0

7:1

Address

从模式的I2C器件地址

0x00


SCH占空比寄存器(I2SCLH): 16位寄存器,用于定义SCL高电平所保持的PCLK周期数。





符号

描述

复位值

15:0

SCLH

SCL高电平周期选择计数

0x0004


SCL占空比寄存器(I2SCLL): 16位寄存器,用于定义SCL低电平所保持的PCLK周期数。





符号

描述

复位值

15:0

SCLL

SCL低电平周期选择计数

0x0004

 
 2.2:无操作系统下,主机控制IIC数据传输方法
对I2C总线上主从设备的读写可使用两种方法,一是使用轮询的方式,二是使用中断的方式。轮询方式即是在一个循环中判断I2C状态寄存器当前的状态值来确 定总线当前所处的状态,然后根据这个状态来进行下一步的操作。中断方式即是使能I2C中断,注册I2C中断服务程序,在服务程序中读取I2C状态寄存器的 当前状态值,再根据状态值来确定下一步的操作。
 
IIC状态寄存器一些关键值如下:

0x08: 表明主设备向总线已发出了一个起始条件;
0x10: 表明主设备向总线已发出了一个重复的起始条件;
0x18: 表明主设备向总线已发送了一个从设备地址(写方向)并且接收到从设备的应答;
0x20: 表明主设备向总线已发送了一个从设备地址(写方向)并且接收到从设备的非应答;
0x28: 表明主设备向总线已发送了一个数据字节并且接收到从设备的应答;
0x30: 表明主设备向总线已发送了一个数据字节并且接收到从设备的非应答;
0x40: 表明主设备向总线已发送了一个从设备地址(读方向)并且接收到从设备的应答;
0x48: 表明主设备向总线已发送了一个从设备地址(读方向)并且接收到从设备的非应答;
0x50: 表明主设备从总线上已接收一个数据字节并且返回了应答;
0x58: 表明主设备从总线上已接收一个数据字节并且返回了非应答;

 

例1:轮询方式:


#define 
SC16IS740_ADDR 0x92 
#define 
ADT75A_ADDR 0x9E 
#define 
ADT75A_TEMP 0x00 


#define 
CHANNEL_GPRS 0
#define CHANNEL_TEMPERATURE 1


#define 
BIT(x) (1 <</SPAN><</SPAN> x)
#define 
I2C_EN BIT(6)
#define 
I2C_STA BIT(5)
#define 
I2C_STO BIT(4)
#define 
I2C_SI BIT(3)
#define 
I2C_AA BIT(2)


#define 
SAFETY_COUNTER_LIMIT 3000


void 
I2C0_Init(void)
{
    
    PINSEL0 |= (0x03 <</SPAN><</SPAN> 0) | (0x03 <</SPAN><</SPAN> 2);

    
    PCONP |= (0x01 <</SPAN><</SPAN> 7 );

    
    I20CONCLR = (0x01 <</SPAN><</SPAN> 2) | (0x01 <</SPAN><</SPAN> 3) | (0x01 <</SPAN><</SPAN> 5) | (0x01 <</SPAN><</SPAN> 6);

    
    I20CONSET = (0x01 <</SPAN><</SPAN> 6);

    
    I20SCLH = 0x5A;
    I20SCLL = 0x5A;
}


BOOL 
I2C0_ReadRegister(uint32 channel, uint8 registerAddress, uint8 *pData)
{
    
    uint32 loopSafetyCounter = 0;
    uint32 addressSendSafetyCounter = 0; 

    
    do
    {
        
        I20CONSET = I2C_STA | I2C_SI;
        I20CONCLR = I2C_SI;

        
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; 
            }
        }

        
        if(channel == CHANNEL_GPRS)
            I20DAT = SC16IS740_ADDR;
        else if(channel == CHANNEL_TEMPERATURE)
            I20DAT = ADT75A_ADDR;

        I20CONCLR = I2C_STA | I2C_SI;

        
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; 
            }
        }

        addressSendSafetyCounter ++;
        if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }

    } while (I20STAT != 0x18); 

    
    I20DAT = registerAddress <</SPAN><</SPAN> 3; 
    I20CONCLR = I2C_SI; 

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    I20CONSET = I2C_STA | I2C_SI;
    I20CONCLR = I2C_SI;

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    if(channel == CHANNEL_GPRS)
        I20DAT = SC16IS740_ADDR | 0x01;
    else if(channel == CHANNEL_TEMPERATURE)
        I20DAT = ADT75A_ADDR | 0x01;

    I20CONCLR = I2C_STA | I2C_SI;

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    I20CONCLR = I2C_SI | I2C_AA; 

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    *pData = I20DAT; 

    
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    return TRUE;
}


BOOL 
I2C0_WriteRegister(uint32 channel, uint8 registerAddress, uint8 data)
{
    uint32 loopSafetyCounter = 0;
    uint32 addressSendSafetyCounter = 0;

    
    do
    {
        
        I20CONSET = I2C_STA | I2C_SI;
        I20CONCLR = I2C_SI;

        
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; 
            }
        }

        
        if(channel == CHANNEL_GPRS)
            I20DAT = SC16IS740_ADDR;
        else if(channel == CHANNEL_TEMPERATURE)
            I20DAT = ADT75A_ADDR;

        I20CONCLR = I2C_STA | I2C_SI;

        
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; 
            }
        }

        addressSendSafetyCounter ++;
        if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }

    } while (I20STAT != 0x18); 

    
    I20DAT = registerAddress <</SPAN><</SPAN> 3; 
    I20CONCLR = I2C_SI; 

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    I20DAT = data; 
    I20CONCLR = I2C_SI; 

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    return TRUE;
}

 

 

 


 3. Linux系统IIC子系统简介:
在Linux下要使用I2C总线并没有像无系统中的那样简单,为了体现Linux中的模块架构,Linux把I2C总线的使用进行了结构化。这种结构分三部分组成,他们分别是:I2C核心部分、I2C总线驱动部分和I2C设备驱动。


在Linux下驱动I2C总线不像单片机中那样简单的操作几个寄存器了,而是把I2C总线结构化、抽象化了,符合通用性和Linux设备模型
这就是后面要学习的内容了。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 再次感谢:

 

Linux系统IIC驱动程序  基础知识

$
0
0

作者:Sam (甄峰)  sam_code@hotmail.com

 

0. Linux I2C驱动体系概述:

Linux I2C驱动体系结构由以下三部分组成:

A:  I2C Core。  B:I2C总线驱动。  C:I2C设备驱动。



I2C核心是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通

I2C总线驱动填充i2c_adapteri2c_algorithm结构体。

I2C设备驱动填充i2c_driveri2c_client结构体。

 

很显然,I2C Core是Linux Kernel提供好的用来支持的模块。而根据I2C总线和I2C设备的区别,I2C Driver也分两个层次。

 

I2C 总线层驱动: 根据核心板的芯片手册,编写总线层驱动。例如 使用Nvidia K1开发板手册中的I2C总线文档,实现总线层驱动。总线层主要向内核注册一个Adapter,并填充Adapter支持的类型和方法。

I2C 设备层驱动:  I2C Driver设备层,主要针对不同的I2C硬件设备编写驱动,并为用户提供接口。

I2C子系统下设备驱动有两种模式:

A: 用户模式驱动,依赖于I2C子系统中的i2c-dev.

B: 普通Device Driver。

 

 

1. Linux I2C驱动程序目录结构

1.1:I2C Core:

kernel/drivers/i2c

i2c-boardinfo.c  i2c-core.c  i2c-dev.c  i2c-mux.c  i2c-slave.c  i2c-smbus.c  i2c-stub.c

 

i2c-core.c

可以看到,模块自我介绍为:I2C-Bus main module

提供(EXPORT_SYMBOL)了很多I2C核心功能。

如:

i2c_new_device() ,i2c_unregister_device(): 增加,删除i2c device---struct i2c_client  

i2c_add_adapter(). i2c_del_adapter(): 增加,删除 i2c adapter--struct i2c_adapter

i2c_register_driver(), i2c_del_driver(): 增加,删除i2c driver.

i2c_master_send()

同时,它注册了一个 bus_register(&i2c_bus_type);

这里有有一个函数:i2c_device_match(). 它来判断哪个设备驱动和I2C设备是匹配的。

它调用: of_driver_match_device(),和i2c_match_id().

 

 

i2c-dev.c:

I2C /dev entries driver

实现了 I2C适配器设备文件功能。

/dev/i2c-0,  /dev/i2c-1, /dev/i2c-n

创建了字符设备驱动--register_chrdev()

创建了/dev/i2c-n  node,device_create()

提供了read, write...等功能。

这样,用户就可以在应用程序层通过读写/dev/i2c-n 来操作I2C设备。

按照Sam当前理解:每个node(/dev/i2c-0..... /dev/i2c-n)对应一个I2C总线。每个总线可以挂载多个I2C设备。那么打开node,就应该可以和挂载在总线上的设备通讯了。

 

 

 1.2:I2C总线驱动:

I2C总线驱动和当前目标开发板有关,例如:Sam当前拿的是Nvidia K1开发板。则开发I2C总线驱动,需要详细读取K1 IIC部分文档。 幸好这部分在出厂前会由厂商搞定,(BSP部分)。我们就姑且看之吧。

总线驱动位于kernel/drivers/i2c/busses/目录。这里包含很多常用的I2C控制器驱动

 

kernel/drivers/i2c/busses/i2c-tegra.c -----nVidia Tegra2 I2C Bus Controller driver

它首先注册了一个platform drivert : platform_driver_register()

 

当系统发现匹配的I2C设备时,会调用:tegra_i2c_probe(), 这里初始化Tegra I2C总线,设置了clk的频率, 配置了中断处理程序,并调用i2c_add_numbered_adapter() 增加一个adpter。

 

Adapter是I2C总线驱动的关键,那Adapter到底是什么语义的?

Sam当前理解是:Adapter对应物理上存在的一个I2C总线。一个总线上能够挂载多个I2C设备。总线控制器控制数据传输。

 

struct i2c_adapter {
 struct module *owner;
 unsigned int class;   
 const struct i2c_algorithm *algo;
 void *algo_data;

 
 struct rt_mutex bus_lock;

 int timeout;   
 int retries;
 struct device dev;  

 bool cancel_xfer_on_shutdown;
 bool atomic_xfer_only;

 int nr;
 char name[48];
 struct completion dev_released;

 struct mutex userspace_clients_lock;
 struct list_head userspace_clients;

 struct i2c_bus_recovery_info *bus_recovery_info;
};

 

这里给I2C总线起了名字:

strlcpy(i2c_dev->adapter.name, "Tegra I2C adapter",sizeof(i2c_dev->adapter.name));

叫:Tegra I2C adapter

cat /sys/class/i2c-dev/i2c-0/name

Tegra I2C adapter

 

 

这里还有一个概念需要注意:

struct i2c_algorithm {

 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
      int num);
 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
      unsigned short flags, char read_write,
      u8 command, int size, union i2c_smbus_data *data);


 u32 (*functionality) (struct i2c_adapter *);
};

master_xfer:I2C总线的通讯协议函数。

smbus_xfer: SMBus总线的通讯协议函数。 

 

i2c_algorithm:作用就是这个I2C Bus的数据传输。如果没有它,这个I2C Bus不能传送数据。

数据格式为: struct i2c_msg。

这部分实现和I2C总线控制器设计有关,它按照芯片手册中的说明,操作寄存器控制数据发送和接收。方便I2C设备驱动使用。

 

这里还有个知识点:

Linux I2C GPIO Driver. 当没有I2C总线控制器时,可以使用GPIO来模拟I2C总线,使用2个GPIO分别模拟SDA, SCL, 并使用驱动,让GPIO模拟I2C总线时序,完成Linux 与I2C设备的通讯过程。

与有I2C控制芯片的驱动不同的是传输算法的实现,GPIO模拟I2C驱动,不再是设置寄存器控制数据发送和接收。而是用程序来控制。代码在drivers/i2c/busses/i2c-gpio.c,时序控制:drivers/i2c/algos/i2c-algo-bit.c

理论上,如果没有I2C控制器,只需要修改总线驱动中master_xfer为GPIO模式即可。

至于一些具体设置,如哪个GPIO对应SDA, 哪个GPIO对应SCL等信息,则在板级代码中写入:

static struct i2c_gpio_platform_data pdata = {
 .sda_pin  = AT91_PIN_PA25,
 .sda_is_open_drain = 1,
 .scl_pin  = AT91_PIN_PA26,
 .scl_is_open_drain = 1,
 .udelay   = 2,  
};

(请注意:Linux Device Tree使用后,这些板级代码可能被合并到DTS文件内了)

 

 

 

如需要使用Linux I2C GPIO Driver, 则需要make menuconfig时加入此modules.

 

 

 

 

 

 

1.3:I2C设备层驱动:

1.3.1: I2C接口硬件设备驱动程序不同的实现方式:

从广义上说,I2C设备层驱动是指为各种I2C接口的硬件提供驱动。在不同纬度上,有不同的实现方式:

A: 用户模式驱动程序。

B: 普通驱动程序。

 

1.3.2:用户模式驱动程序:

依赖于i2c子系统中的/dev/i2c-0,  /dev/i2c-1, /dev/i2c-n

打开/dev/i2c-n,这个node, 相当于打开了一个物理存在的I2C适配器。按Sam理解,也就是得到了一个I2C Bus。 后面向不同I2C数据写入读出数据,就是向某个挂载在这个I2C总线上的设备写入和读出。

很难说这就是Driver。但却是实现了向I2C设备发送和接收数据。  

 

以后再填充内容。

 

1.3.3:普通驱动程序:

不利用i2c-dev.c, 直接写I2C设备驱动

 

在比较老的Linux Kernel中,I2C设备Driver也可以使用两种方式。Adapter方式和Probe方式。Kernel推荐使用Probe方式。且在某个Kernel版本后,Adapter方式不再支持了(存疑)

 

1.3.3.1: Adapter方式:

这个方式的思路是,创建一个i2c_drveir结构体并注册给kernel:

A: I2C Device Driver开发者填充i2c_driver结构体。

最重要的两个成员是函数指针:attach_adapter()--调用时机较复杂,后面再说, detach_client()--脱离适配器时被调用。

B: 注册这个 i2c_driver:

i2c_add_driver(&inv_mod_driver);

如此,整个驱动就完整了。但如何运作的呢?

前面谈到过,I2C 总线驱动会创建几个I2C总线Adapter. 每个Adapter代表一个实际存在的I2C Bus。

当I2C 设备驱动使用i2c_add_drvier()注册了一个设备驱动时,则会依次使用每个Adapter来尝试连接我们的I2C Device。 这个过程,就是调用attach_adapter(struct i2c_adapter* adapter).

 

attach_adapter中调用:

i2c_probe(adapter, &addr_data, xxxx_detect);
               

 其中:addr_data很关键:

static unsigned short normal_i2c[] = { xx,xx,xx,xx,xx,xx,xx,xx

I2C_CLIENT_END };

地址必须是I2C芯片的地址。否则无法探测到。

 

xxxxx_detect(). 它是在探测到设备后,会被调用的函数。

这个过程很关键,填充i2c_client,创建node均在此处实现。

xxxxx_detect(struct i2c_adapter *adapter, int address, int kind)

可以

A:创建i2c_client, 填充其内容。利用i2c_attach_client()注册i2c_client。

i2c_client--代表的就是一个I2C Device。

B:创建字符设备node.

C:  实现字符设备的read,write,ioctl等。

 

其它部分如detach()等,就是反向操作了。

 

 

1.3.3.2:Probe方式I2C Device Driver

这个方式也正是被推荐的方式。思路如下:

A:I2C Device Driver开发者填充i2c_driver结构体。与Adapter方式不同的主要是i2c_driver中需要被填充的内容。

比较重要的是probe(), remove(), id_table.

 

 

 static struct i2c_driver inv_mod_driver = {
 .class = I2C_CLASS_HWMON,
 .probe  = nvi_probe,
 .remove  = nvi_remove,
 .id_table = nvi_mpu_id,

 .driver = {
  .owner = THIS_MODULE,
  .name = "inv_dev",
  .of_match_table = of_match_ptr(nvi_mpu_of_match),
#ifdef CONFIG_PM
  .pm = &nvi_pm_ops,
#endif
 },
 .address_list = normal_i2c,
 .shutdown = nvi_shutdown,
};

 

 B:注册Driver:

i2c_add_driver(&inv_mod_driver);

具体创建字符设备Device Node等工作。都在probe中做了。

 

现在具体分析:

我们知道,i2c_driver对应的是一个i2C driver. 它的probe()函数,就是当有符合条件的I2C Device出现在I2C Bus上时被Kernel调用的。但Kernel如何知道哪个I2C Device复合Driver条件呢?和USB设备一样,使用id_table.

 

static struct i2c_device_id nvi_mpu_id[] = {
 {"itg3500", INV_ITG3500},
 {"mpu3050", INV_MPU3050},
 {"mpu6050", INV_MPU6050},
 {"mpu9150", INV_MPU9150},
 {"mpu6500", INV_MPU6500},
 {"mpu9250", INV_MPU9250},
 {"mpu6xxx", INV_MPU6XXX},
 {"mpu9350", INV_MPU9350},
 {"mpu6515", INV_MPU6515},
 {}
};

系统会调用:i2c_device_match()----i2c_match_id()来判断咱们注册的I2C Driver和Bus 上链接的Device是否匹配。如果匹配,则调用probe.

 

但Sam在想,USB 设备是因为其可以存储一小块数据,这块数据和 .id_table的内容比对,看是否匹配。但I2C设备理应没有这块区域啊。那i2c_driver中存储的.id_table的数据,和谁比对呢?

Sam认为, kernel的平台代码(arch/arm/mach-xxxx.   在NV-Tegra平台是:arch/arm/mach-tegra/board-ardbeg-sensors.c)中,会针对将要支持的Device,构建各自的i2c_board_info。

 

类似:

static struct i2c_board_info __initdata inv_mpu9250_i2c0_board_info[] = {
 {
  I2C_BOARD_INFO(MPU_GYRO_NAME, MPU_GYRO_ADDR),
  .platform_data = &mpu9250_gyro_data,
 },


 {
  I2C_BOARD_INFO(MPU_BMP_NAME, MPU_BMP_ADDR),
  .platform_data = &mpu_bmp_pdata,
 },


 {
  I2C_BOARD_INFO(MPU_COMPASS_NAME, MPU_COMPASS_ADDR),
  .platform_data = &mpu_compass_data,
 },
};

 

然后调用 i2c_register_board_info()。

 

Probe分析:

维护字符设备驱动程序。使用户层程序可以访问Device。

 

 

 

 附录1:



这个图标上,可以很清晰的看到,I2C总线驱动部分,I2C设备驱动部分,各自有两个概念:

i2c_adapter, i2c_algorithm. i2c_client, i2c_driver. 咱们总结一下他们各自的用途和语义。

 

Sam认为;

i2c_adapter: 对应的就是一个I2C总线,物理上存在的I2C Bus。每个I2C总线上,可以挂载多个I2C设备。

i2c_algorithm:每个I2C总线,都要包含一个i2c_algorithm,它相当于传输协议。没有它,I2C总线无法传输数据。

i2c_driver: 对应一个I2C驱动。驱动程序可以与I2C设备匹配,帮助用户访问I2C设备。

i2c_client: 对应一个物理存在的I2C 设备。

 

 

 

 

 

 


 

OpenMP在ARM-Linux以及NDK中的编译和使用

$
0
0

作者:Sam (甄峰) sam_code@hotmail.com

 

以前对OpenCV在ARM-Linux, ARM-Android上的优化做了很多编译方面的努力,例如添加TBB支持,添加CUDA支持(Nvidia K1平台上)。这次突然听同事说增加了OpenMP选项后,在Windows+X86上有极大的优势,adaboost速度提高3倍。所以赶快在ARM-Android-NDK上测试一下。

 

 

0. OpenMP基础:

OpenMP(Open Multi-Processing)是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性注释(Compiler Directive)。OpenMP支持的编程语言包括C语言C++Fortran;而支持OpenMP的编译器包括Sun StudioIntel Compiler,以及开放源码GCCOpen64编译器。OpenMP提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。

 

1. OpenMP在X86 Linux上的展现:

例子代码:

#include
int main(int argc, char* argv[])
{

#pragma omp parallel

printf("Hello, world.\n");
return 0;
}

普通编译:

g++ OpenMP_Test.cpp -o test

运行:

# ./test
Hello, world.

 

增加OpenMP 编译选项的编译:

 g++ -fopenmp OpenMP_Test.cpp -o test

运行:

#./test
Hello, world.
Hello, world.
Hello, world.
Hello, world.

证明-fopenmp 在GCC下有效。代码的OpenMP能力得到支持。

#pragma omp parallel 仅在您指定了 -fopenmp 编译器选项后才会发挥作用。在编译期间,GCC 会根据硬件和操作系统配置在运行时生成代码,创建尽可能多的线程。每个线程的起始例程为代码块中位于指令之后的代码。这种行为是 隐式的并行化,而 OpenMP 本质上由一组功能强大的编译指示组成,帮您省去了编写大量样本文件的工作。我的Linux机器为4核CPU。所有有4个thread. 

 

 

2. OpenMP在ARM-Anrdoid-NDK上的展现:

代码不变。

Android.mk内容如下:

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := test
LOCAL_SRC_FILES := OpenMP_Test.cpp

 

 

LOCAL_CXXFLAGS := -fopenmp
LOCAL_CFLAGS +=  -fopenmp
LOCAL_LDLIBS := -llog -fopenmp
include $(BUILD_EXECUTABLE)

 

Application.mk内容如下:

# Build both ARMv5TE and ARMv7-A machine code.
APP_PLATFORM = android-8

APP_ABI := armeabi-v7a
#APP_ABI := $(ARM_ARCH)

#Sam modify it to release
#APP_OPTIM := release
APP_OPTIM := debug
#APP_OPTIM = $(MY_OPTIM)

APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -frtti

#sam modify it from gnustl_static to gnustl_shared
#APP_STL := gnustl_static
#APP_STL        := gnustl_shared
APP_STL := gnustl_shared

#APP_CPPFLAGS += -fno-rtti


#
APP_CPPFLAGS += -Dlinux -fsigned-char
APP_CFLAGS += -fsigned-char
#APP_CPPFLAGS += $(MY_CPPFLAGS) -Dlinux
#STLPORT_FORCE_REBUILD := true

 

编译后运行:

$ ./test                                      
Hello, world.
Hello, world.
Hello, world.
Hello, world.

证明-fopenmp 在NDK下有效。代码的OpenMP能力得到支持。

K1平台是4 Core的。所以有4个thread. 

 

3. OpenCV4Android OpenMP支持:

#!/bin/sh
cd `dirname $0`/..

mkdir -p build_android_arm
cd build_android_arm

cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DHAVE_EIGEN=1  -DHAVE_CAMV4L2=ON -DBUILD_TBB=ON -DWITH_TBB=ON -DHAVE_OPENMP=1 -DBUILD_EXA
MPLES=1 -DANDROID_ABI="armeabi-v7a"  -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../..

只要如此编译,则OpenCV 支持OpenMP.

 

4. OpenMP 指令和库函数:

 C/C++中,OpenMP指令的使用格式为:

pragma omp 指令 [子句[子句]…]

#pragma omp parallel for
     for (int j = 0; j < 4; j++)
  {
         printf("j=[%d], ThreadId =[%d]\n", j, omp_get_thread_num());
     }
#endif

 

如果报找不到符号,可以: -lgomp

 

 

5. OpenCV(support OpenMP)效果测试:

 

 


 

Linux系统IIC驱动程序  I2C设备驱动

$
0
0

作者: Sam (甄峰)  sam_code@hotmail.com

 

上次谈了Linux 下I2C的框架,这次专门以MPU9250驱动为例,学习I2C设备驱动。

 

之前谈过, I2C设备驱动有两种模式:Adapter模式和Probe模式。前者在新kernel版本中已经不支持了,后者则是推荐方式。这次我们就以MPU9250 IIC Driver 为例,分析Probe模式的I2C driver.

 

因为具体分析的是Nvidia Tegra平台,所以板目录在kernel/arch/arm/mach-tegra/ 

具体文件为:board-ardbeg-sensors.c

 

1. 从头开始:

ardbeg_sensors_init()中,调用了mpuirq_init(). 这里面做了一个准备工作A。之后调用了:i2c_register_board_info()

 

1.0: i2c_register_board_info()分析

int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)

静态的声明I2C Device。

busnum: Bus标识符,device将要挂载的Bus标识符。

info: i2c device 描述符vector。

len: vector中描述符的数量。

 

1.0.1: 内容填充:

对于vector中的每个描述符,创建一个i2c_devinfo. 并将其加入链表:__i2c_board_list

 

1.0.2:链表中内容分析:

 

1.0.3:  什么时候用到链表:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)中,

如果__i2c_board_list 中的某一项所标识的BUS id 与参数给定的adapter所代表的bus是同一个,则调用:i2c_new_device(adapter, &devinfo->board_info)来真正的创建Device--(struct i2c_client, 正如我们所说,I2C设备对应的概念是:i2c_client)

 

而i2c_scan_static_board_info()被谁调用呢?正是:i2c_register_adapter(struct i2c_adapter *adap),其实最终由:i2c_add_adapter()调用。

也就是说,当增加一个新的I2C BUS 时,它会从__i2c_board_list链表中把所有内容查看一遍,看哪些是挂载在自己身上的,如果有,就给它创建Device (i2c_client). 这个i2c_client,再通过id_table. 与i2c_driver相连接。

 

 


 

diff和patch的使用

$
0
0
作者:Sam (甄峰)  sam_code@hotmail.com

0. 基础知识
diff和patch是一对对应工具。从某种意义来说,diff就像是差值运算,patch就像是和运算。
diff命令比较两个文件或者连个文件夹的差异,并记录到到一个文件中,它就是patch文件,即补丁文件。
patch命令可以将补丁文件和之前的两个元素之一组合,形成另一个集合。

1. diff使用实例
1.1:diff 两个文件
diff a_file b_file > patch_file
这里把a_file叫做原始文件,b_file当作修改后的文件。patch_file则是A的补丁文件。
有两个文件,a.cpp, b.cpp
他们内容相近,只是a.cpp的21,22行, b.cpp的21-24行内容不同。

a.cpp:
#include

#include
#include

#include
#include
#include
#include
#include
#include

bool disable_ertm;

static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN;
static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP, };

static LIST_HEAD(chan_list);
static DEFINE_RWLOCK(chan_list_lock);

static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
    struct sk_buff_head *skbs, u8 event);

b.cpp:
#include

#include
#include

#include
#include
#include
#include
#include
#include
#include

bool disable_ertm;

static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN;
static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP, };

static LIST_HEAD(chan_list);
static DEFINE_RWLOCK(chan_list_lock);

static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
      u8 code, u8 ident, u16 dlen, void *data);
static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
  void *data);


$diff a.cpp b.cpp > test.patch
test.patch内容为:
11a12
> #include
21,22c22,25
< static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
<                    struct sk_buff_head *skbs, u8 event);
---
> static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
>                                      u8 code, u8 ident, u16 dlen, void *data);
> static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
>                          void *data);
解读:
11a12
> #include
原始文件a.cpp11行后添加一行。内容如上。

21,22c22,25
< static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
<                    struct sk_buff_head *skbs, u8 event);
---
> static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
>                                      u8 code, u8 ident, u16 dlen, void *data);
> static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
>                          void *data);
原始文件a.cpp21-22行修改为后面的22-25行内容。

这个格式虽然简单,但看起来有乱,有时候想加入上下文,这时,可以加入-c . 则会加入上下文。缺省是加入三行。也可以自己指定,如-c5. 表示加入5行上下文。


$diff -c2 a.cpp b.cpp > test.patch
test.patch内容为:

*** a.cpp       2016-01-19 17:36:58.371134156 +0800
--- b.cpp       2016-01-19 17:37:01.516312351 +0800
***************
*** 10,13 ****
--- 10,14 ----
  #include
  #include
+ #include

  bool disable_ertm;
***************
*** 19,22 ****
  static DEFINE_RWLOCK(chan_list_lock);

! static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
!                    struct sk_buff_head *skbs, u8 event);
--- 20,25 ----
  static DEFINE_RWLOCK(chan_list_lock);

! static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
!                                      u8 code, u8 ident, u16 dlen, void *data);
! static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
!                          void *data);

和之前类似,不需要讲解。
+表示增加line.
- 表示删除line.
! 表示要交换的某line.

思考:
diff a b >c 与 diff b a > c。 那两个c会相同的么?
当然不相同。他们更应该是一个相反的数据。
例如:
diff b.cpp a.cpp > test1.patch -c2
那test1.patch内容如下:
*** b.cpp       2016-01-19 17:37:01.516312351 +0800
--- a.cpp       2016-01-19 17:36:58.371134156 +0800
***************
*** 10,14 ****
  #include
  #include
- #include

  bool disable_ertm;
--- 10,13 ----
***************
*** 20,25 ****
  static DEFINE_RWLOCK(chan_list_lock);

! static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
!                                      u8 code, u8 ident, u16 dlen, void *data);
! static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
!                          void *data);
--- 19,22 ----
  static DEFINE_RWLOCK(chan_list_lock);

! static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
!                    struct sk_buff_head *skbs, u8 event);
与test.patch刚好是相对的。


1.2: diff 两个目录
比较两个目录时,要注意一个参数 
-r: 
recursively compare any subdirectories found. 递归的比较子目录。

有一个目录Dir_top, 其中包含两个自目录:Dir_A, Dir_B.
#diff -rc Dir_A Dir_B > dir.patch

dir.patch文件内纪录了是哪些目录,那些文件比较:
diff -rc Dir_A/SerialPort_Utils.h Dir_B/SerialPort_Utils.h
*** Dir_A/SerialPort_Utils.h    2016-01-20 12:23:57.634223761 +0800
--- Dir_B/SerialPort_Utils.h    2016-01-20 12:32:48.352736912 +0800





2. patch使用实例:
2.1:patch 文件和补丁:
patch命令用于根据原文件和补丁文件生成新的目标文件。
所以,如果diff a b > c
那么patch a c 则得到b.

以上面a.cpp, b.cpp, test.patch为例。
$patch < test.patch
patching file a.cpp
此时,a.cpp的内容根据test.patch修改为b.cpp了。
patch是如何知道原始文件是谁呢?因为在patch文件中已记录了。

此时,a.cpp的内容被修改为目标文件b.cpp  , 但如果我们想找回原始a.cpp内容呢?
patch a.cpp < test.patch -R
此时a.cpp内容又变回原始内容了。

还是用a.cpp, b.cpp, test.patch为例。
patch a.cpp < test.patch   //把a.cpp内容根据补丁,变化到b.cpp去。(原始变目标
patch -R b.cpp < test.patch //把b.cpp的内容根据补丁,变化到a.cpp去。(目标变原始)


2.2:两个目录补丁操作
与1.2的目录结构类似:
有一个目录Dir_top, 其中包含两个自目录:Dir_A, Dir_B.
Dir_top下还有Dir_A与Dir_B diff出来的补丁。dir.patch.
在Dir_top目录下:
patch  -p0 <  dir.patch 
则给Dir_A打上补丁。

如果打上补丁又后悔了,想恢复原状。同样用 -R
patch -p0 < dir.patch -R


2.3: 关于patch时的目录层级和-pNum 的关系。
如上面1.2, 2.2的情况:
有一个目录Dir_top, 其中包含两个自目录:Dir_A, Dir_B.
Dir_top下还有Dir_A与Dir_B diff出来的补丁。dir.patch.

这个dir.patch是在Dir_top目录下打得。所以patch文件中包含了文件的目录信息,Dir_A, Dir_B
*** Dir_A/hi_audio.cpp  2016-01-20 12:23:57.617222781 +0800
--- Dir_B/hi_audio.cpp  2016-01-20 12:28:32.630397684 +0800

那么此时,如果我们把这个dir.patch文件放在Dir_top根目录下。则文件内的目录结构和patch文件所在位置是符合的。patch  -p0 <  dir.patch 

但很多情况下,我们会把patch文件copy 到原文件目录内,Dir_A中去。此时,文件内的目录结构就和patch文件所在位置无法对应了。因为它找不到Dir_A/hi_audio.cpp 文件。
所以,可以采用:patch  -p1 <  dir.patch 
-p1: patch会忽略掉第1个”/”之前的内容,认为原始文件是hi_audio.cpp。 这样就可以找到原始文件了。



3. patch文件格式:
补丁头:
成对出现的文件,上面的是原始文件,后面的是目标文件。
例:
---- a.cpp       2016-01-20 00:00:02.433388026 +0800
+++ b.cpp       2016-01-20 00:02:01.253181756 +0800

或者:
*** a.cpp       2016-01-20 00:00:02.433388026 +0800
--- b.cpp       2016-01-20 00:02:01.253181756 +0800
又或者:
diff -rc Dir_A/hi_audio.cpp Dir_B/hi_audio.cpp
*** Dir_A/hi_audio.cpp  2016-01-20 12:23:57.617222781 +0800
--- Dir_B/hi_audio.cpp  2016-01-20 12:28:32.630397684 +0800
每个补丁文件中,可以包含多个补丁头。

补丁块
块是补丁中要修改的地方。它通常由一部分不用修改的东西开始和结束(上下文部分,由diff参数 -c,-u指定)。

块会缩进一列,而这一列是用来表示这一行是要增加还是要删除的。

块的第一列

+号表示这一行是要加上的。

-号表示这一行是要删除的。

没有加号也没有减号表示这里只是引用的而不需要修改。



 

Linux设备树(Linux Device Tree)学习

$
0
0

作者: Sam (甄峰)  sam_code@hotmail.com

 

0. Linux Device Tree基础:

在早期的ARM Linux Kernel中,板级细节使用代码形式,存放在arch/arm/目录。名字通常为:plat-xxxx或者mach-xxxx,内容多为特定板上细节信息,如platform, i2c_board_info, spi_board_info和各类资源数据。(Sam在学习I2C时,Borad就在类似目录mach-tegra)

但这类目录对Linux Kernel来说,意义非常有限,因为板级代码只对相应开发板有用。使用这种方法显然不是一个好办法。与此同时PowerPC等其它架构已经在使用一种新的方法:Flattened Device Tree(FDT)。Device Tree是一种描述硬件的数据结构。它包含板级硬件细节信息,这样,通过Device Tree,就可以把硬件信息传递给Kernel,而不需要再硬编码了。

 

1. 无Linux Device Tree之前的状况:

在没有使用Linux Device Tree之前,要porting Linux 到一个新的ARM开发板上时,工作流程如下:

A:构建Bootloader,copy kernel到内存,并传递参数。并统一Bootloader和Kernel中的machine type ID。这个Machine Type ID与此开发板一一对应。

 

DT_MACHINE_START(ARDBEG, "ardbeg")

ARDBEG:就是Machine ID。

定义在arch/arm/tools/mach-types 文件中。

 

B:在kernel/arch/arm目录下创建mach-xxxx目录。这个目录下的文件就是板级代码,只与开发板硬件相关。

DT_MACHINE_START(ARDBEG, "ardbeg")
 .atag_offset = 0x100,
 .smp  = smp_ops(tegra_smp_ops),
 .map_io  = tegra_map_common_io,
 .reserve = tegra_ardbeg_reserve,
 .init_early = tegra_ardbeg_init_early,
 .init_irq = irqchip_init,
 .init_time = clocksource_of_init,
 .init_machine = tegra_ardbeg_dt_init,
 .restart = tegra_assert_system_reset,
 .dt_compat = ardbeg_dt_board_compat,
 .init_late      = tegra_init_late
MACHINE_END

 

在tegra_ardbeg_dt_init()中,初始化和定义了板级设备。如memory, display, uart, usb, i2c, audio等。

可以看到,每个ARM开发板,均需要对应的Machine ID对应,并用代码实现对应的初始化和功能定义。

 

2.无Linux Device Tree之前ARM Board 存在的问题:

很明显,其实Board Info独立于Kernel之外。所以每次Kernel升级,都需要Board Info的实现者对应做升级和测试工作。又因为ARM 对应开发板极其众多,所以这堆代码(arch/arm/)越来越庞大。各个Table之间可能会有冲突。且很多代码有重合和冲突的可能。

在一次Kernel升级后,OMAP平台的维护者Tony Lindgren提交了OMAP的升级。但长期被ARM板级代码困扰的Linus终于爆发了:Gaah. Guys, this whole ARM thing is a f*cking pain in the ass.

ARM Linux维护者也提出了各种不同意见和改进建议。

 

分析下来,同一件事,在不同的人看来,有不同的层次和意义(屁股决定大脑):

A. 内核维护者(与CPU体系结构无关的代码)

B. 维护ARM系统结构者。

C. 维护ARM sub architecture者(来自ARM SOC vendor)

ARM Sub Architecture维护者来自 ARM芯片提供厂商,他们的第一要素是尽快把SOC推出市场,而不是代码多么合理和美观。

维护ARM体系结构的人能力不容置疑,但他们面对的是不断有新SOC加入的局面。且这也是ARM能够与Intel抗衡的根本。

内核维护者对整个系统更加高屋建瓴。他们注意到每次kernel升级,ARM代码变化量占整个ARCH目录的60%,他们认为这明显意味着ARM Linux 代码存在问题。

 

如此人员和架构设计,会导致ARM sub architecture缺乏沟通,代码有重复。且质量不高。

 

 

3. 解决方案:

针对重复的代码问题,如果不同的SOC使用了相同的IP block(例如I2C controller),那么这个driver的code要从各个arch/arm/mach-xxx中独立出来,变成一个通用的模块供各个SOC specific的模块使用。移动到哪个目录呢?对于I2C或者USB OTG而言,这些HW block的驱动当然应该移动到kernel/drivers目录。因为,对于这些外设,可能是in-chip,也可能是off-chip的,但是对于软件而言,它们是没有差别的(或者说好的软件抽象应该掩盖底层硬件的不同)。对于那些system level的code呢?例如clock control、interrupt control。其实这些也不是ARM-specific,应该属于linux kernel的核心代码,应该放到linux/kernel目录下,属于core-Linux-kernel frameworks。当然对于ARM平台,也需要保存一些和framework交互的code,这些code叫做ARM SoC core architecture code。OK,总结一下:

1、ARM的核心代码仍然保存在arch/arm目录下

2、ARM SoC core architecture code保存在arch/arm目录下

3、ARM SOC的周边外设模块的驱动保存在drivers目录下

4、ARM SOC的特定代码在arch/arm/mach-xxx目录下

5、ARM SOC board specific的代码被移除,由Device Tree机制来负责传递硬件拓扑和硬件资源信息。

 

4. Device Tree使用概述:

从前面的文字看来,Device Tree是用来替代之前将Hardware配置信息直接写入Code的办法,改用写入一个DB的方法。

之前习惯于每个ARM platform编译一个Kernel Image。但从长远来看,未来将会希望所有Platform对应一个Image。那么Bootloader在启动kernel,需要指出识别出的Platform信息。

 

那Device Tree从生成到使用流程如何呢?

A: 充分了解硬件配置和系统运行参数,并将信息写入Device Tree source file.

B: 使用Device Tree Compiler(DTC)将Device Tree Source File编译成适合机器处理的Device Tree binary file(又叫:Device Tree Blob----DTB).

C: 在系统启动时,Boot Program(BIOS,Bootloader)可以将Flash中的DTB copy到内存。

D: 把DTB内存地址传递给后续程序(bootloader或Kernel)。

 

 

5. Device Tree Source file

Device Tree Source file中并不需要描述系统中所有硬件,一些可以动态探测到的设备不需要被描述,如USB Device。但对于SOC上的USB Host Controller,因为无法自动识别,需要在Device Tree Source file中描述。

其实这和Device Tree 出现前的Board file时的准则是一致的。

 

 Device Tree Source file是由一个或多个node组成的,node格式是:

[label:] node-name[@unit-address] { 
   [properties definitions] 
   [child nodes] 
}

以kernel/arch/arm/boot/dts/skeleton.dtsi为例(dtsi: device tree source include):

/ {
 #address-cells = <1>;
 #size-cells = <1>;
 chosen { };
 aliases { };
 memory { device_type = "memory"; reg = <0 0>; };
};

可以看到,"/"是跟节点的node name, {}之间是该node的具体定义,其内容包括各种属性和child node.

属性包括:

 #address-cells = <1>;
 #size-cells = <1>;

 

子node包括:

 chosen { };
 aliases { };
 memory { device_type = "memory"; reg = <0 0>; };

 

 

6. Device Tree Compiler:

DTC 源码在scripts/dtc目录下。而某个Platform编译时,哪些dtb会被编译出来,则可以在arch/arm/boot/dts/Makefile中看到。

这里可以看出,dtb其实是和Linux Kernel分离的。所以,它可以单独被编译出来:

make dtbs

 

 

7. Device Tree对BSP和Device Driver造成的影响和变更

如上所述,Device Tree就是为了取代板级信息代码而推出的。所以,大量在arch/arm/plat-xxxx,  arch/arm/mach-xxxx中做的工作不再是必要的了。

A: 注册platform_device,绑定resource,即内存、IRQ等板级信息

形如

 

static struct resource xxx_resources[] = {
         [0] = {
                 .start  = …,
                 .end    = …,
                 .flags  = IORESOURCE_MEM,
         },
         [1] = {
                 .start  = …,
                 .end    = …,
                 .flags  = IORESOURCE_IRQ,
        },
 };

 static struct platform_device xxx_device = {
         .name           = "xxx",
         .id             = -1,
         .dev            = {
                                 .platform_data          = &xxx_data,
         },
         .resource       = xxx_resources,
         .num_resources  = ARRAY_SIZE(xxx_resources),
 };

 

 

 

之类的platform_device代码都不再需要,其中platform_device会由kernel自动展开。而这些resource实际来源于.dts中设备结点的reg、interrupts属性。典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device。譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:

 static struct of_device_id xxx_of_bus_ids[] __initdata = {
         { .compatible = "simple-bus", },
         {},
 };

 void __init xxx_mach_init(void)
 {
         of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
 }

 #ifdef CONFIG_ARCH_XXX

 DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
         …
         .init_machine   = xxx_mach_init,
         …
 MACHINE_END
 #endif

 

B. 注册i2c_board_info,指定IRQ等board级别信息:

 

 

static struct i2c_board_info __initdata inv_mpu9250_i2c0_board_info[] = {
 {
  I2C_BOARD_INFO(MPU_GYRO_NAME, MPU_GYRO_ADDR),// MPU_GYRO_ADDR=0x68
  .platform_data = &mpu9250_gyro_data,
 },
 {
  I2C_BOARD_INFO(MPU_BMP_NAME, MPU_BMP_ADDR),
  .platform_data = &mpu_bmp_pdata,
 },
 {
  I2C_BOARD_INFO(MPU_COMPASS_NAME, MPU_COMPASS_ADDR),
  .platform_data = &mpu_compass_data,
 },
};

 

 

 

 

 

inv_mpu9250_i2c0_board_info[0].irq = gpio_to_irq(MPU_GYRO_IRQ_GPIO);

i2c_register_board_info(gyro_bus_num, inv_mpu9250_i2c0_board_info, ARRAY_SIZE(inv_mpu9250_i2c0_board_info));

 

 

 

 

可以在dtsi中修改为:

/ {
 i2c@7000c400 {
  mpu6515@69 {
   compatible = "invensense,mpu6515";
   reg = <<STRONG>0x68>;
   interrupt-parent = <&gpio>;
   interrupts = <169 0x01>;
   invensense,int_config = <0x10>;
   invensense,level_shifter = <0>;
   invensense,orientation = [ff 00 00 00 01 00 00 00 ff];
   invensense,sec_slave_type = <0>;
   invensense,key = [4e cc 7e eb f6 1e 35 22
       00 34 0d 65 32 e9 94 89];
   vlogic-supply = <&palmas_smps8>;
   vdd-supply = <&palmas_smps9>;
  };

  ak8963c@0d {
   compatible = "ak,ak8963";
   reg = <0x0c>;
   orientation = [ff 00 00 00 01 00 00 00 ff];
   config = "mpu";
   vid-supply = <&palmas_smps8>;
   vdd-supply = <&palmas_smps9>;
  };
 };
};

 

 

 

 

 

 

 

 

内容参考于:

http://blog.csdn.net/21cnbao/article/details/8457546


 

Invensense mlSDK分析

$
0
0

作者:Sam(甄峰)  sam_code@hotmail.com

 

0. 背景 

最近在Nvdia Tegra K1平台上使用Invensense MPU9250 Sensor。MPU9250使用I2C连接到K1平台。在Driver支持后,/dev/input/中新建3个node,  /dev/input/eventX. 通过ioctl(EVIOCGNAME)得到其名,分别为:MPU6500, INV_DMP, akm89xx. 这与I2C设备驱动中创建的device对应起来。且其中有数据。

这说明I2C设备驱动这块任务完成,已经把Raw Data传送到/dev/input/eventX中去了。

 

然后在Android APP层通过SensorManager获取Sensor实体,并尝试得到数据:

可以发现,它拥有以下Sensor:

1. MPL Rotation Vector(旋转矢量传感器)

2. MPL Linear Accel. (加速度,不包括重力)

3. MPL Gravity. (仅包括重力)

4. MPL Gyro. (角速度)

5. MPL Accel. (加速度)

6. MPL magnetic field

7. MPL Orientation.(方向)

 

MPU9250=MPU6500+AK8963,所以加速度,角速度信息是直接从MPU6500得到的,方向信息则从AK8963得到,其它几个SensorManager层面的Sensor,则都是通过软件计算模拟出来的。具体实现则在sensors.xxxx.so部分实现. 通常是sensors.default.so, 在一些平台,则由自己定制的库实现,如K1平台使用sensors.tegra.so.

 

 

1. mlSDK的发现(抽丝拨茧):

A:在MPU9250通过I2C连接K1,Driver insmod成功后,尝试使用Android SensorManager得到Sensor,获取Sensor数据,Gyro,Acc , Magnetic, Linear_Acc, Orientation, Gravity均一切正常,说明Framework中已经针对MPU9250数据做过处理。

 

B:之后想使用NativeC程序直接从/dev/input/eventX中读取数据(input_event),于是打开设备名为MPU9250的node(/dev/input/event4). 读取input event. 问题立刻来了。

 

首先看数据格式:

 

#define EV_SYN 0x00

#define EV_REL 0x02

 

struct input_event {
 struct timeval time;
 __u16 type;
 __u16 code;
 __s32 value;
};

 

获取的数据很有规律(driver中填充的):

Data[0]: type=2,code=3

Data[1]: type=2,code=4

Data[2]: type=2,code=5

 

Data[3]: type=2,code=0

Data[4]: type=2,code=1

Data[5]: type=2,code=2

 

Data[6]: type=2,code=9

Data[7]: type=2,code=8

 

Data[0]: type=0,code=0

 

这就是一组数据。从Driver中可以看到:

Data[0]: type=2,code=3

Data[1]: type=2,code=4

Data[2]: type=2,code=5

他们分别是ACC的X,Y,Z的Raw Data。

 

 

Data[3]: type=2,code=0

Data[4]: type=2,code=1

Data[5]: type=2,code=2

 他们分别是GYRO的X,Y,Z的Raw Data。

 

Data[6]: type=2,code=9

Data[7]: type=2,code=8

timestamp的高32和低32bit.

 

 Data[0]: type=0,code=0

SYNC,作为一组数据的结束标记。

 

直到这里,还一切正常。但ACC, GYRO的值,却异常奇怪,很难想到公式把他们转换的正常的加速度和角速度单位。Sam自然想到,sensors.tegra.so也是读取以上node数据,但SensorManager却拿到正确数据,那肯定在这一层做了转换。

 

C:查数据转换踪迹:

在device/nvidia/ardbeg/sensors/目录下,可以看到sensors.cpp 和Android.mk

从Android.mk内容判断,

LOCAL_SRC_FILES := sensors.cpp

LOCAL_MODULE := sensors.tegra

sensors.cpp最终将生成sensors.tegra.so.

但请注意,其实它还包含了一系列库。

libsensors.base

libinvensense_hal

libsensor.mpl

libsensors.nvs_input

libsensors.iio.lights

libsensors.max44005

libsensors.bmpx80

那这些库的源码在哪呢?

大都在device/nvidia/drivers/sensors目录下。

这几个库中,最关键的就是libsensors.mpl,它由MPLSensor.cpp, MPLSupport.cpp CompassSensor.cpp生成,但同时,它又依赖于两个库:

libmllite.so,   libmplmpu.so

这两个库,就是数据转换的关键。

 

D: mlsdk踪迹:

先分析libmllite.so, 它的源码在device/nvidia/drivers/sensors/mlsdk/目录下,相信此目录下源码和头文件均为Invesense提供。主要源码在device/nvidia/drivers/sensors/mlsdk/mllite目录。

将由它提供接口,把Raw Data转换为标准单位的数据。

 

其次是libmplmpus.so,

这个库并没有提供源码,只提供了库文件。

vendor/nvidia/tegra/prebuilt/ardbeg/target/product/ardbeg/lib/

(这意味着移植是个大问题)

 

2. NativeC 程序使用mlSDK:

 既然清楚了sensors.tegra.so如何使用mlsdk把Raw data转化成标准单位数据。那理论上我们NativeC程序也可以做到这一点。

2.1: 生成自己的libmllite.so库:

想直接链接libmllite.so时,总会出问题,怀疑是编译选项的差异造成的,干脆自己编译一份库好了。

于是copy mlsdk整个目录,并按自己的习惯写了Android.mk+Application.mk.  编译,正常通过。

2.2: 修改代码,使用mlsdk,

于是可以得到正常单位数据了。(只尝试了ACC, GYRO)

 

 

3.  sensors.xxxx.so的功能和结构

在查看mlSDK使用情况时,也也通过sensors.tegra.so复习了一下sensors.xxxx.so的作用和写法。(之前在模拟ACC设备时,TT曾告诉我这个模板和方法)

总的来说:这个模快提供一个方法,让Framework能够得到底层Sensor(包括硬件Sensor,软件Sensor)的信息,并得到Sensor数据。

 

所以它需要做三件事:

A. 维护一个Sensor list. 把所有Sensor信息列入其中

 

B. 提供一个接口,供上层能够拿到Sensor信息,并控制Sensor(打开,关闭,得到数据)

 

C. 提供Sensor数据。

 

Sensor List如下:

static struct sensor_t sSensorList[10] = {
      MPLROTATIONVECTOR_DEF,
      MPLLINEARACCEL_DEF,
      MPLGRAVITY_DEF,
      MPLGYRO_DEF,
      MPLACCEL_DEF,
      MPLMAGNETICFIELD_DEF,
      MPLORIENTATION_DEF,
};

具体定义如下:

#define MPLROTATIONVECTOR_DEF {                         \
    "MPL rotation vector",                              \
    "Invensense",                                       \
    1, ID_RV,                                           \
    SENSOR_TYPE_ROTATION_VECTOR, 10240.0f, 1.0f,        \
    0.5f, 20000, 0, 0, { } }

 

含义如下:

struct sensor_t {

    // Name of this sensor.
    // All sensors of the same "type" must have a different "name".

    const char*     name;

    //vendor of the hardware part //
    const char*     vendor;

    // version of the hardware part + driver. The value of this field
    // must increase when the driver is updated in a way that changes the
    // output of this sensor. This is important for fused sensors when the
    // fusion algorithm is updated.

    int             version;

    // handle that identifies this sensors. This handle is used to reference
    //this sensor throughout the HAL API.
/
    int             handle;

    //this sensor's type. //
    int             type;

    // maximum range of this sensor's value in SI units /
    float           maxRange;

    / smallest difference between two values reported by this sensor /
    float           resolution;

    // rough estimate of this sensor's power consumption in mA /
    float           power;

    // this value depends on the trigger mode:
    
    //   continuous: minimum sample period allowed in microseconds
    //   on-change : 0
    //   one-shot  :-1
     //   special   : 0, unless otherwise noted
    
    int32_t         minDelay;

    // number of events reserved for this sensor in the batch mode FIFO.
     //If there is a dedicated FIFO for this sensor, then this is the
    // size of this FIFO. If the FIFO is shared with other sensors,
    // this is the size reserved for that sensor and it can be zero.
    
    uint32_t        fifoReservedEventCount;

    // maximum number of events of this sensor that could be batched.
     // This is especially relevant when the FIFO is shared between
     //several sensors; this value is then set to the size of that FIFO.
    
    uint32_t        fifoMaxEventCount;

    // reserved fields, must be zero
    void*           reserved[6];
};

 

接口如下:

struct sensors_module_t HAL_MODULE_INFO_SYM = {
        common: {
                tag: HARDWARE_MODULE_TAG,
                version_major: 1,
                version_minor: 0,
                id: SENSORS_HARDWARE_MODULE_ID,
                name: "Ardbeg sensors module",
                author: "nvidia",
                methods: &sensors_module_methods,
                dso: NULL,
                reserved: {0}
        },
        get_sensors_list: sensors__get_sensors_list,
};

 

static int sensors__get_sensors_list(struct sensors_module_t* module, struct sensor_t const** list)

把维护的Sensor通过参数二提供给调用方。

 

 

提供操作Sensor的接口;

 static struct hw_module_methods_t sensors_module_methods = {
        open: open_sensors
};

 

 

 

最重要的当然是如何提供数据:

dev->device.poll            = poll__poll;

 

pollEvents(sensors_event_t* data, int count)

在这个函数内提供数据:

其中参数1,即为存放数据的结构体。(sensors_event_t结构,在附2)

参数2应该是Sensor个数。

 

sensors.xxxx.so的开发者应该依次填充sensors_event_t结构体数组中的内容。以供上层使用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注1:MPU9150,MPU9250介绍:

MPU9150和MPU9250均为9轴芯片。

MPU9150着眼于精度和性能,内部采用MPU6050(3轴加速度,3轴陀螺仪)+AK8975(磁)。 只支持I2C

MPU9250着眼于低功耗,内部采用MPU6500(3轴加速度,3轴陀螺仪)+AK8963。支持I2C和SPI。

 

 

注2:sensors_event_t

typedef struct sensors_event_t {
   
    int32_t version;

   
    int32_t sensor;

   
    int32_t type;

   
    int32_t reserved0;

   
    int64_t timestamp;

    union {
        union {
            float           data[16];

           
            sensors_vec_t   acceleration;

           
            sensors_vec_t   magnetic;

           
            sensors_vec_t   orientation;

           
            sensors_vec_t   gyro;

           
            float           temperature;

           
            float           distance;

           
            float           light;

           
            float           pressure;

           
            float           relative_humidity;

           
            uncalibrated_event_t uncalibrated_gyro;

           
            uncalibrated_event_t uncalibrated_magnetic;

           
            meta_data_event_t meta_data;
        };

        union {
            uint64_t        data[8];

           
            uint64_t        step_counter;
        } u64;
    };
    uint32_t reserved1[4];
} sensors_event_t;


 

ffmpeg NDK再编译

$
0
0

作者:Sam (甄峰)  sam_code@hotmail.com

 

之前使用过NDK编译过ffmpeg(http://blog.sina.com.cn/s/blog_602f87700102ux7o.html)。这次又要编译新版本ffmpeg.  那次是直接在各模块下添加Android.mk,  Application.mk的方式,分别编译各模块。并最终编译出整个版本。

 

早在ARM-Linux, MIPS-Linux时代,就常用configure 交叉编译ARM或MIPS版本。通常的做法是:

CC=XXX-Linux-gcc  LD=XXX-linux-gcc ./configure --host=arm-linux  --tegra=.....

接触到Android NDK后,也总觉得可以使用这种方法,而非一定要使用ndk-build 来编译NDK版本的第三方库。但一直没有尝试过。这次在网络上看到RockPlayer编译FFMPEG的方式正是如此。所以干好学习体验之。

在此感谢RockPlayer和相关网友的工作和分享。

 

思路完全延续RockPlayer, 只针对新版本NDK R9 做了一点点修改:

 

#!/bin/bash

 

######################################################
# FFmpeg builds script for Android+ARM platform
#
# This script is released under term of
#   CDDL (http://www.opensource.org/licenses/cddl1)
# Wrote by pinxue (~@gmail.com) from RockPlayer.com
#                                   2010-8 ~ 2011-4
######################################################

######################################################
# Modify it by sam.zhen (sam_code@hotmail.com)
#                                   2016-3
######################################################

 

######################################################
# Usage:
#   put this script in top of FFmpeg source tree
#   ./build_android_ffmpeg
#
# It generates binary for following architectures:
#     ARMv6
#     ARMv6+VFP
#     ARMv7+VFPv3-d16 (Tegra2)
#     ARMv7+Neon (Cortex-A8)
#
# Customizing:
# 1. Feel free to change ./configure parameters for more features
# 2. To adapt other ARM variants
#       set $CPU and $OPTIMIZE_CFLAGS
#       call build_one
######################################################

 

#SamInfo: MUST modify it for YOUR toolchain path
NDK=/opt/android-ndk-r9
PLATFORM=$NDK/platforms/android-8/arch-arm/
PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64

 

function build_one
{
# -fasm : required. Android header file uses asm keyword instead of __asm__ , but most of c dialect (like ansi,c99,gnu99) implies -fno-asm.
#   ~/android/android-ndk-r4/build/platforms/android-5/arch-arm//usr/include/asm/byteorder.h: In function '___arch__swab32':
#   ~/android/android-ndk-r4/build/platforms/android-5/arch-arm//usr/include/asm/byteorder.h:25: error: expected ')' before ':' token

 

# -fno-short-enums : optimized.  Else FFmpeg obj will generate a huge number of warning for variable-size enums,
#   though we may suppress them by --no-enum-size-warning, it would be better to avoid it.
#   .../ld: warning: cmdutils.o uses variable-size enums yet the output is to use 32-bit enums; use of enum values across objects may fail
 
# --extra-libs="-lgcc" : required. Else cannot solve some runtime function symbols
#   ... undefined reference to `__aeabi_f2uiz'

# --enable-protocols : required. Without this option, the file open always fails mysteriously.
#   FFmpeg's av_open_input_file will invoke file format probing functions, but because most of useful demuxers has flag of zero
#   which cause them are ignored during file format probling and fall to url stream parsing,
#   if protocols are disabled, the file:// url cannot be opened as well.

 

# $PREBUILT/bin/arm-eabi-ar d libavcodec/libavcodec.a inverse.o : required.
#   FFmpeg includes two copies of inverse.c both in libavutil and libavcodec for performance consideration (not sure the benifit yet)
#   Without this step, final ld of generating libffmpeg.so will fail silently, if invoke ld through gcc, gcc will collect more reasonable error message.
 

# -llog: debug only, FFmpeg itself doesn't require it at all.
#   With this option, we may simply includes "utils/Log.h" and use LOGx() to observe FFmpeg's behavior
#   PS, it seems the toolchain implies -DNDEBUG somewhere, it would be safer to use following syntax
#    #ifdef NDEBUG
#        #undef NDEBUG
#        #define HAVE_NDEBUG
#    #endif
#    #include "utils/Log.h"
#    #ifdef HAVE_NDEBUG
#        #define NDEBUG
#        #undef HAVE_NDEBUG
#    #endif

 

# --whole-archive : required. Else ld generate a small .so file (about 15k)

 

# --no-stdlib : required. Android doesn't use standard c runtime but invited its own wheal (bionic libc) because of license consideration.

 

# space before \ of configure lines: required for some options. Else next line will be merged into previous lines's content and cause problem.

#   Especially the --extra-cflags, the next line will pass to gcc in this case and configure will say gcc cannot create executable.

 

# many options mentioned by articles over internet are implied by -O2 or -O3 already, need not repeat at all.

 

# two or three common optimization cflags are omitted because not sure about the trade off yet. invoke NDK build system with V=1 to find them.

 

# -Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x mentioned by almost every articles over internet, but it is not required to specify at all.

 

# -Dipv6mr_interface=ipv6mr_ifindex : required. Android inet header doesn't use ipv6mr_interface which is required by rfc, seems it generate this user space header file directly from kernel header file, but Linux kernel has decided to keep its own name for ever and ask user space header to use rfc name.

 

# HAVE_SYS_UIO_H : required. Else:

# In file included from ~/android/android-ndk-r4/build/platforms/android-5/arch-arm//usr/include/linux/socket.h:29,

#                 from ~/android/android-ndk-r4/build/platforms/android-5/arch-arm//usr/include/sys/socket.h:33,

#                 from libavformat/network.h:35,

#                 from libavformat/utils.c:46:

#~/android/android-ndk-r4/build/platforms/android-5/arch-arm//usr/include/linux/uio.h:19: error: redefinition of 'struct iovec'

#

 

# --disable-doc : required because of strange bug of toolchain.

 

./configure --target-os=linux \
    --prefix=$PREFIX \
    --enable-cross-compile \
    --extra-libs="-lgcc" \
    --arch=arm \
    --enable-gpl \
    --cc=$PREBUILT/bin/arm-linux-androideabi-gcc \
    --cross-prefix=$PREBUILT/bin/arm-linux-androideabi- \
    --nm=$PREBUILT/bin/arm-linux-androideabi-nm \
    --sysroot=$PLATFORM \
    --extra-cflags=" -O3 -fpic -DANDROID -DHAVE_SYS_UIO_H=1 -Dipv6mr_interface=ipv6mr_ifindex -fasm -Wno-psabi -fno-short-enums  -fno-strict-aliasing -finline-limit=300 $OPTIMIZE_CFLAGS " \
    --disable-shared \
    --enable-static \
    --extra-ldflags="-Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib  -nostdlib -lc -lm -ldl -llog" \
    --enable-parsers \
    --enable-encoders  \
    --enable-decoders \
    --enable-muxers \
    --enable-demuxers \
    --enable-swscale  \
    --enable-swscale-alpha \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --enable-network \
    --enable-indevs \
    --disable-bsfs \
    --enable-filters \
    --enable-avfilter \
    --enable-protocols  \
    --enable-asm \
    $ADDITIONAL_CONFIGURE_FLAG

 

 

#make clean
make
make  -j4 install
 

$PREBUILT/bin/arm-linux-androideabi-ar d libavcodec/libavcodec.a inverse.o
$PREBUILT/bin/arm-linux-androideabi-ld -rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib  -soname libffmpeg.so -shared -nostdlib  -Bsymbolic --whole-archive --no-undefined -o $PREFIX/libffmpeg.so libavcodec/libavcodec.a libavformat/libavformat.a libavutil/libavutil.a libavfilter/libavfilter.a libswscale/libswscale.a libavdevice/libavdevice.a libswresample/libswresample.a libpostproc/libpostproc.a  -lc -lm -lz -ldl -llog  --dynamic-linker=/system/bin/linker $PREBUILT/lib/gcc/arm-linux-androideabi/4.8/libgcc.a

 

}

 

#arm v6

#CPU=armv6
#OPTIMIZE_CFLAGS="-marm -march=$CPU"
#PREFIX=./android/$CPU
#ADDITIONAL_CONFIGURE_FLAG=
#build_one

 

#arm v7vfpv3

CPU=armv7-a
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU "
PREFIX=./android/$CPU
ADDITIONAL_CONFIGURE_FLAG=
build_one

 

#arm v7vfp

#CPU=armv7-a
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
#PREFIX=./android/$CPU-vfp
#ADDITIONAL_CONFIGURE_FLAG=
#build_one

 

#arm v7n

#CPU=armv7-a
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
#PREFIX=./android/$CPU
#ADDITIONAL_CONFIGURE_FLAG=--enable-neon
#build_one

 

#arm v6+vfp

#CPU=armv6
#OPTIMIZE_CFLAGS="-DCMP_HAVE_VFP -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
#PREFIX=./android/${CPU}_vfp
#ADDITIONAL_CONFIGURE_FLAG=
#build_one

 

 

此文件命名为:

build_android_ffmpeg

 

./build_android_ffmpeg

则最终生成 libffmpeg.so

 

 


 

Android Developent with OpenCV3.1

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

本文参考:
http://docs.opencv.org/2.4/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.html#dev-with-ocv-on-android

http://docs.opencv.org/2.4/doc/tutorials/introduction/android_binary_package/O4A_SDK.html#o4a-sdk

0. 准备工作:
在开发机上:JDK, Android SDK, NDK, Eclipse,  ADT和CDT Eclipse插件等。并且安装了OpenCV4Android.
 
在运行环境中,OpenCV_3.1.0_Manager_3.10_armeabi-v7a.apk需要安装(SDK中提供不同Core和指令集的APK,根据运行环境不同而安装不同APK)


1.  Android Project使用OpenCV Library:
1.1:OpenCV Manager介绍
从OpenCV-2.4.2 For Android 版本开始, OpenCV Manager就用作为Android Project提供支持,帮助它使用OpenCV library.
 
OpenCV Manager是一个Android Service, 用来在用户终端上管理OpenCV Library. 它允许在同一设备上,多个应用程序访问同一套共享库,并提供以下好处:
A:节约内存空间,所有APP均使用Service中同一份二进制代码,并且不需要把native library编译进每一个APP中。
B:  对所有支持平台均提供硬件优化。
C:  支持update和bug fix.
 
 
1.2:Java代码使用OpenCV:
1.2.1: 使用程序异步初始化方式使用OpenCV
官方推荐使用异步初始化方式, 它通过之前安装好的OpenCV Manager访问OpenCV Library。
具体路径如下:
A:增加OpenCV Library Project到workspace中
File -> Import -> Existing project in your workspace.

编译之。则会生成opencv_library-3.1.0.jar
 
B: 导入异步初始化的例子程序--15 puzzle


C: 把OpenCV Library 作为Jar库加入Application


注意:Application的Project Build target与OpenCV Library Project的Build Target相同。
 

D:  在Activity中添加
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");
                   
                    mOpenCvCameraView.setOnTouchListener(Puzzle15Activity.this);
                    mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
这里要注意这个抽象类:
abstract class BaseLoaderCallback
抽象类不是无法实例化么? 但其实可以这样理解,在这里创建了一个匿名的类为BaseLoaderCallback的子类。
作为Activity的内部类。
 
这个抽象类有两个方法:
Method Summary
 void onManagerConnected(int status)
          Callback method, called after OpenCV library initialization.
 void onPackageInstall(int operation, InstallCallbackInterface callback)
          Callback method, called in case the package installation is needed.
注意:OpenCV文档中明确说: OnManagerConnected callback will be called in UI thread
由于不允许在Initialization Finish完成前调用OpenCV API或Native API。所以任何OpenCV相关调用都应该在此Callback之后。
 
E:OpenCVLoader:
Helper class provides common initialization methods for OpenCV library.
Method Summary
static boolean initAsync(java.lang.String Version, android.content.Context AppContext, LoaderCallbackInterface Callback)
          Loads and initializes OpenCV library using OpenCV Engine service.
static boolean initDebug()
          Loads and initializes OpenCV library from current application package.
 
 @Override
    public void onResume()
    {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
 
initDebug():
Loads and initializes OpenCV library from current application package. Roughly, it's an analog of system.loadLibrary("opencv_java").
从本应用程序包内载入和初始化OpenCV Library,相当于:system.loadLibrary("opencv_java3");
 
如果本地APP包含了OpenCV Library, 则返回True。 否则,返回false.
这里,我们使用OpenCV Manager, 并没有加入OpenCV Native Library。所以返回false.
 
 
initAsync():
Loads and initializes OpenCV library using OpenCV Engine service.
使用OpenCV Manager 初始化。
 
整个逻辑关系就是:
在onResume()时,使用initDebug()判断是否为本App Package OpenCV  Library. 如果是,则手动调用:
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
如果不是,则说明应该使用initAsync()调用OpenCV Manager来初始化。 OpenCV Manager 初始化完成后,则会调用onManagerConnected
 
 
F:如果要使用Camera,则还需要添加Camera权限。
 
完整例子,从15-puzzle为基础做的例子,它使用Async Init模式,所以完全不需要把Native库加进来。
 
package com.android.example.spinner;
 

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import org.opencv.android.JavaCameraView;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class SpinnerActivity extends Activity implements CvCameraViewListener {
 private static final String  TAG = "SamInfo";
 private CameraBridgeViewBase mOpenCvCameraView;
 
 
 private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");
                   
                   
                    mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
 
 
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
       
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        Log.d(TAG, "Creating and setting view");
        mOpenCvCameraView = (CameraBridgeViewBase) new JavaCameraView(this, -1);
        setContentView(mOpenCvCameraView);
        mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE);
// 这里创建一个JavaCameraView,它是CameraBridgeViewBase的子Class. 而CameraBridgeViewBase又是SurfaceView的子Class。
// 同时,直接把这个CameraBridegViewBase设置为Layout。且可见,而且屏幕又不会灭

        mOpenCvCameraView.setCvCameraViewListener(this);
//这句话很关键,指出当前CameraBridgeViewBase被谁所监听。监听者会在开始,结束,有数据时被调用。

       
    }
 

   
    @Override
    public void onResume() {
       
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
// 此前解释过
       
    }
   
    @Override
    public void onPause() {
       
        super.onPause();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }
 

 @Override
 public void onCameraViewStarted(int width, int height) {
  // TODO Auto-generated method stub
  Log.e(TAG, String.format("onCameraViewStarted: w:%d. H:%d", width, height));
  
 }
 

 @Override
 public void onCameraViewStopped() {
  // TODO Auto-generated method stub
  Log.e(TAG, "onCameraViewStopped()");
 }
 

 @Override
 public Mat onCameraFrame(Mat inputFrame) {
  // TODO Auto-generated method stub
  Log.e(TAG, "onCameraFrame()");
  return inputFrame;
 }
 //这里每帧有数据时被调用,可以处理这个数据,处理后的数据返回,返回值则被显示在SurfaceView。
}
 

1.2.2:应用程序使用静态初始化方式使用OpenCV  Library:
除了使用Async init 的方式使用OpenCV Library外,还有个Static Init方式使用。但如果使用这个方式,则意味着所有OpenCV 二进制要包含进本App Package。这个方法大都使用在开发阶段,产品release阶段,还是以Async init 方式为宜。
 
A:增加OpenCV Library Project到workspace中
File -> Import -> Existing project in your workspace.

 
B:把OpenCV Library 作为Jar库加入Application


C:如果App没有JNI部分,则直接copy OpenCV-android-sdk/sdk/native/libs//libopencv_java3.so 到工程libs/
例如:
# cp OpenCV-android-sdk/sdk/native/libs/armeabi-v7a/libopencv_java3.so OpenCV-android-sdk/samples/Spinner/libs/armeabi-v7a/
 
此时,因为本地Package内包含 libopencv_java3.so .  所以initDebug()返回True。
 

 

 

D:如果APP有JNI部分,则不用再去手动copy动态库,而是在Android.mk中添加:


OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on

此时,编译中,会自动把libopencv_java3.so copy过来,和jni模块编译出的库放在一起。

 

这是,在Linux版本中,常会出现类似:ndk-build.cmd无法使用等。

需要设置两点:

1. NDK目录。

2. ndk-build 指令。

 

分别设置如下:

1. NDK 目录

方法一: Window -->Preferences-->Android-->NDK



方法二:

window-->Preferences-->C/C++-->build variables



2. 修改ndk-build.cmd问题:

这个在Eclipse下比较难找;

右击项目, Build Path-->Configure Build Path -->C++ build



把ndk-build.cmd 修改为ndk-build即可

 

 

附录:OpenCV API:

http://dalab.se.sjtu.edu.cn/docs/opencv/docs.opencv.org/java/index-all.html


 

常见移动机器人底盘介绍

$
0
0

作者:Sam (甄峰)   sam_code@hotmail.com

 

在移动机器人开发中,首先要选择的,就是移动机器人底盘。除了能够自己设计制造外,也可以选择成熟产品,或者使用成熟产品做算法,后期再使用自己的地盘做替代。现在就介绍几种非常常见的底盘。

 

1. 大名鼎鼎的先锋--Pioneer:

Mobilerobots研发的大名鼎鼎的Poineer平台。都快卖了20年了,它的Pioneer3-DX(两轮差分),AT(4轮驱动)遍布世界各地的科研机构。它有一系列产品(http://www.leemanrobot.com.cn/produ_linei.aspx?cid=4&fid=8&id=3)

后公司被Adept收购。并推出工业级别的Lynx平台。

Adept后被欧姆龙收购。

 

Pioneer-3DX是一款差分驱动的机器人底盘,主要用于教学和科研。它装配有500线编码器电机,直径19cm轮胎。前置8个防碰撞声纳。大直径充气轮胎。通过性较好。可外接3块电池。

可负载30KG。 (有些型号负载100KG)

价格较贵。

 

2. Turtlebot平台:

TurtleBot 2是著名的柳叶车库(Willow Garage)设计的一款性能优良,价格低廉的机器人开发平台。

移动底座使用韩国Yujin Kobuki移动平台。Kinect视觉传感器、双核笔记本、2200mAh电池和可装卸的结构模块。TurtleBot 2使用目前最流行的ROS(Robot Operating System )作为操作系统,能实现3D地图导航、跟随等功能

电池采用2200mAH

底盘价格:4500。

底盘+配件:6500-10000不等。

缺点:电池容量不足。轮子通过性不强。

 

3.  EAI 机器人底盘---Dashgo D1

玩智商Enjoy AI

http://www.hahabot.com/

这款底盘负载可达50KG.

12V  7AH电池。

 

 

4. Hands Free:

2WD, 3WD

 

5. ExBot据说要做一个大电池,高负载底盘。

 

 

6. Drrobot:  

http://www.drrobot.com/products_item.asp?itemNumber=X80Pro

 

 


 

ROS学习  ROS简介和简易安装

$
0
0

作者: Sam (甄峰)    sam_code@hotmail.com

 

Sam搞机器人相关也很久了,但一直徘徊在ROS门外。外来会需要仿照ROS内一些组件来作为机器人模块,所以从头开始学习ROS,作为记录存放在这。

 

0. ROS介绍

0.1:What is ROS?

分布式的进程框架,属于次级操作系统。

底层:硬件抽象描述,底层驱动程序管理,进程间消息传递和程序包管理。

顶层:开发者提供共享的各种软件功能包

 

ROS=通信管道+工具+功能库+生态系统

 

 

0.2:ROS特点

A:不依赖变成语言,或者说不指定编程语言,多种语言可以共存。

C++, Python, Lisp, Java

 

B:精简和集成

封装:复杂的和重复使用的驱动和算法。

模块化:单独编译。

 

C:测试和调试方便

1. 利用模拟器替代底层硬件,独立测试顶层部分。提高测试开发效率。

2. 按时间戳回放记录的传感器数据和消息数据。(调试方便)

3. 可以用一组记录的Sensor数据调试算法。也就是说,可以记录传感器的数据以及其他类型的消息数据,并在之后的开发中回放。不断调试算法。ROS中,这种数据叫做Bag,  有个rosbag的工具可以记录和回放数据。

 

D:丰富的功能包

Rviz

rqt

Gazebo

rxplot

rxgraph

 

 

 

 

1. ROS 安装

1.1: 平台和版本选择:

ROS可以安装在Linux(Ubuntu, Fedora, OpenSuse)系统,ARM-Linux,Windows, Mac OS X。 但最通用的做法还是安装在Ubuntu14.04 LTS上。

ROS新版本通常不够稳定,所以网上简易的版本还是indigo.

 

1.2:简易安装方法

Ubuntu for ROS.

从一台裸机开始安装配置ROS,过程比较复杂,且天朝的国情决定了一些网络连接状况频出。所以很多开发者提供了一套非常简单的解决方案,Ubuntu for ROS.

下载这份ISO,安装后,ROS和相关软件和开发工具就已经安装配置好了。

http://blog.exbot.net/archives/1206

http://www.aicrobo.com/ubuntu_for_ros.html

均可以下载安装。

 

 

 

 


 

ROS学习  ROS基本概念

$
0
0

作者:Sam (甄峰)  sam_code@hotmail.com

 

这里介绍几个ROS基本概念。

 

1. 节点(Node)

节点是ROS中最重要的概念之一。每个ROS运行实例被称为节点。

换句话说,每个ROS程序运行的进程,就是一个节点。每个机器人程序,可以有非常多个节点。

我们也可以运行同一个程序的多个副本(但要确保每个副本使用不同节点名),则每个副本都被当作一个独立节点。

节点(Node)可以用来提供某种数据或能力,比如读取某Sensor数据。也可以是一个获取这些数据并处理的节点(Node), 例如从别的节点那拿到Sensor并处理。但他们之间如何知道对方存在与否呢?哪些node需要哪种数据呢?这就需要有个管理者,它就是Node Master/ Node Core。

每个Node启动时,需要向它注册,申明自己提供什么服务或需要什么数据。

 

为何说Node是ROS核心,看看它通常被用来做什么:

控制电机速度,获取激光雷达数据,获取Camera数据,利用以上数据定位,路径规划。

 

启动一个Node

#rosrun package-name executable-name

两个参数,参数1:功能包名。 参数2:可执行文件名。(后面会谈到功能包)

 

查看当前有哪些Node

#rosnode list

 

停止一个Node:

#rosnode kill node-name

虽然也可以ctrl-c停止,但此时Node master 可能会没有及时注销此node. 造成误会。(当然这种情况可以:rosnode cleanup)

 

 

 

 

2. 节点管理器(Node Master/ Node Core)

为了每个独立的节点(Node)能够彼此通讯,ROS中有ROS节点管理器存在。

它用来保存节点的话题与服务的注册信息和查找表。

ROS Node在启动时连接到ROSCore上,如果ROSCore被终止,哪怕再次启动。ROS Node也不会自动重新连接。

启动roscore:

#roscore.

 

 

举例说明:

开三个终端,依次启动 roscore, turtle_teleop_key, turtlesim_node.

#roscore

#rosrun turtlesim turtlesim_node

#rosrun turtlesim turtle_teleop_key

首先启动roscore.

接着启动turtlesim功能包内的两个程序。

功能包turtlesim中的turtle_teleop_key程序接收Keyboard信号,发送消息给turtlesim_node,以控制小乌龟移动。 

 

#rosnode list

则可以看到三个node,他们名字分别为:

/rosout

/teleop_turtle

/turtlesim

可以看到,节点名与可执行程序名并不一定相同

 

 

 

3. 关于Node名,应用程序名的思考:

前面提到,Node就是一个进程,是一个应用程序的执行实例。

应用程序名,则是编译时指出编译结果是何种文件名。

Node名,可以理解为进程名。而同一时刻,正在运行的Node,名字不能重复。(应该类似PID不能重复)

但我们可能需要同一个应用程序运行多次,形成多个Node。该如何处理呢? 只需要在启动Node时,指定不同应用程序名即可。

 

#rosrun package-name executable-name __name:=Node-name

所以上面启动小乌龟的程序,就可以启动两次小乌龟如下:

#roscore

#rosrun turtlesim turtlesim_node __name:=tur1

#rosrun turtlesim turtlesim_node __name:=tur2

#rosrun turtlesim turtle_teleop_key

 

用键盘可以控制两个小乌龟运动。

 

#rosnode list

可以看到,两个乌龟程序node名分别为:tur1, tur2.

 

 

 

 

4. ROS Message:

Node之间,是通过Message来传递消息的。

 

Node之间通过Message通讯。每个Message就是一个数据结构。geonetry_msgs/Twist.msg.

 

5. ROS Topic(主题):

ROS Node之间通讯方式主要依靠的就是主题(Topic)和服务两种(服务后面再讲)。Message就是放在Topic中

 

Message传递的的理念

当一个节点想要分享信息时,它就会发布(publish)消息到对应的一个或多个Topic(主题);当一个节点想要接收消息时,它就会订阅(Subscribe)它所需要的一个或多个Topic。

ROS Master负责确保发布Node和订阅Node能找到对方。而Message是直接从发布Node传递到订阅Node. 并不需要经过Node Master。

A:消息以一种publish/subscribe的方式传递。

B:节点可以在给定的主题中发布/订阅消息。

C:一个节点可以订阅/发布多个不同主题。

D:允许多个Node订阅/发布同一个主题。

E:订阅节点和发布节点并不知道相互之间的存在。

 

Message注册和订阅过程:



Talker这个Node,启动后注册了一个Topic,名为bar, 并告知Node Master,接收端口为:1234。

Listener这个Node启动后订阅了一个Topic,名为bar.

Node Master告知Listener,主题的端口为1234。

Listener去连接1234端口,Talker监听到有人连接。就告知对方,数据接口是2345。

于是两个Node就连通起来了。

 

 

同一个时刻,只有一个Node Master。所以可以共享数据。



6.  ROS Service (ROS服务)

ROS服务与Topic不同。

A:它是一种Request/reply的方式传递。

B:节点之间发送请iu和接受应答。

C:一对一模式,一个请求,一个响应。



 

 

 

7. 功能包/软件包  (Packages)

功能包适ROS中组织软件的主要形式,可以编写代码并编译,执行。一个功能包一般包含程序文件,库文件,编译描述文件,配置文件,脚本等。

它是一组用于实现特定功能的相关文件集合。每个功能包由一个清单文件(package.xml)定义。

功能包相关命令:

#rospack list     :列出所有功能包。

#rospack find package-name   : 寻找功能包

 

 

8. 功能包集(stack)

 

从Groovy版本开始,功能包集的概念被逐步淘汰,取而代之的是元功能包(metapackages)

 

 

9. 功能包的安装

一些功能包已经被加入仓库中了。所以可以使用apt-get install来安装之。

例如:


sudo apt-get install ros-hydro-ros-tutorials

 

Android系统移植和设置udev

$
0
0

作者: Sam (甄峰)   sam_code@hotmail.com

 

0. 需求:

在当前项目中,Android设备会插入多个USB-Serial接口,用来传输不同的外部数据。可/dev/ttyUSB0, /dev/ttyUSB1, /dev/ttyUSB2等Node与外部设备并不是直接一一对应,而是哪个设备先boot或插入,那个设备就占据前面的node.   这对编程来说是个障碍。所以想把设备和node有个明确的对应,例如:STM32 A 与Android通过USB-Serial连接,对应node为/dev/ttyUSB0,  STM32 B 与Android通过USB-Serial连接,对应node 为/dev/ttyUSB1.......

如此则想到了udev。

 

1. udev简介

 

 

2. udev向Android系统移植

 

3. udev设置

 


 

ROS学习 准备工作

$
0
0
作者: Sam(甄峰)  sam_code@hotmail.com


在使用简易方法安装ROS开发包后, 其实Exbot已经把环境和ROS开发环境已经设置成功了。但如果从裸Ubutu安装ROS开发环境, 则需要设置ROS开发环境和工作空间。现记录如下:


0.创建ROS工作空间:

对于ROS Groovy和之后的版本可以参考以下方式建立catkin工作环境:

运行:

1 mkdir -p ~/catkin_ws/src
2 cd ~/catkin_ws/src
3 catkin_init_workspace

可以看到在src文件夹中可以看到一个CMakeLists.txt的链接文件,即使这个工作空间是空的(在src中没有package),任然可以建立一个工作空间。

1 cd ~/catkin_ws/
2 catkin_make

catkin_make 命令可以非常方便的建立一个catkin工作空间,在你的当前目录中可以看到有build和devel两个文件夹,在devel文件夹中可以看到许多个 setup.*sh 文件。启用这些文件都会覆盖你现在的环境变量,想了解更多可以查看文档catkin。在继续下一步之前先启动你的新的setup.*sh 文件。

1 source devel/setup.bash

为了确认你的环境变量是否被setup脚本覆盖了,可以运行一下命令确认你的当前目录是否在环境变量中:

1 echo $ROS_PACKAGE_PATH

输出:

/home/exbot/rosbuild_ws/sandbox:/home/exbot/catkin_ws/src:/opt/ros/hydro/share:/opt/ros/hydro/stacks
说明已经加入成功了。





1. 文件组织
Packages是ROS代码的软件组织单元。每个Packages都包含函数库,可执行文件,脚本或者其他文件.
Packages放置在不同位置,各Package中包含各自的内容。
该如何管理和查找,则成了一个问题。所以需要增加一个设置:ROS_PACKAGE_PATH

echo $ROS_PACKAGE_PATH
/home/exbot/rosbuild_ws/sandbox:/home/exbot/catkin_ws/src:/opt/ros/hydro/share:/opt/ros/hydro/stacks
所有在这个环境变量中包含的目录,才能被ros各命令查找和进入。

1.1:ROS Package命令:
rospack: ros Package相关命令:
rospack list   //列出所有Package名和相关目录
rospack find [Package Name]
例:
rospack find turtlesim
/opt/ros/hydro/share/turtlesim

1.2: ROS目录命令---roscd
roscd Package_Name
roscd turtlesim/
exbot@ubuntu:/opt/ros/hydro/share/turtlesim$ pwd
/opt/ros/hydro/share/turtlesim


roscd和其它ros工具只会在ROS_PACKAGE_PATH中指定了的目录中才能找到ROS packages.








ROS中文资料:
ROS基础教程 :
http://www.rosclub.cn/post-69.html









 

 

ROS学习  Package创建和编译

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

0. Package(功能包)简介:
功能包(Package)是ROS中组织软件的主要形式,可以编写代码并编译,执行。一个功能包一般包含程序文件,库文件,编译描述文件,配置文件,脚本等。 它是一组用于实现特定功能的相关文件集合。每个功能包由一个清单文件(package.xml)定义


1.Package的建立
可以使用roscreate-pkg或者catkin创建Package. 我们先看如何使用catkin建立Package。

1.1: catkin Package特征:
一个Package被认为是catkin Package,必须满足以下条件:
A:必须包含一个package.xml文件(提供关于package的元信息)
B:必须包含一个使用catkin的CMakeLists.txt文件。
C:同一个文件夹内只能有一个Package。

所以,catkin内,通常包含: CMakeLists.txt  config  launch  package.xml


1.2:catkin工作空间中的Package和目录管理:
Package建议放在ROS工作空间内.  目录结果通常如此:
catkin_ws,
catkin_ws/build,   catkin_ws/devel,   catkin_ws/src
catkin_ws/src/CMakeLists.txt  //顶级CMake文件
catkin_ws/src/xxx/Package1 ,  catkin_ws/src/xxx/Package2   catkin_ws/src/xxx/Package3  //包都放在这里

1.3:catkin package创建:
使用catkin_create_pkg脚本创建一个新的package,  要指出包名,以及它依赖的包。
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
catkin_create_pkg  Pakage_Name  新包依赖的Package1  新包依赖的Package2  新包依赖的Package3 



2. 编译Package:
在catkin_ws 目录内,运行catkin_make

catkin_make






 

ROS学习  ROS第一个简单例子

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

ROS学习过程中,Sam准备学习的第一个例子是:从头建立一个catkin package, 并创建一个发布者(Publisher)和订阅者(Subscriber)。

1. catkin package的创建:
Sam在 catkin_ws/src/Test_Source目录下,运行:
$catkin_create_pkg Message_Source std_msgs roscpp
创建了Message_Source这个catkin Package. 它依赖于std_msgs, roscpp等Package.
(这里是有问题的,Package Name应该以小写字母为开头)
所以修改为:
$catkin_create_pkg messager std_msgs roscpp


可以看到如下结果:
在catkin_ws/src/Test_Source目录下,可以看到messager目录。
而messager目录内又包含:
CMakeLists.txt  include  package.xml  src
可以看到,它既包含CMakeLists.txt,  又包含package.xml.  所以,它正是一个catkin Package.

修改package.xml:
  exbot
 BSD


2. 创建一个Publisher Node.
在catkin_ws/src/Test_Source/Message_Source/src目录内,创建一个文件,talker_sam.cpp.

// %Tag(FULLTEXT)%
// %Tag(ROS_HEADER)%
#include "ros/ros.h"
// %EndTag(ROS_HEADER)%
// %Tag(MSG_HEADER)%
#include "std_msgs/String.h"
// %EndTag(MSG_HEADER)%

#include


int main(int argc, char **argv)
{
 
// %Tag(INIT)%
  ros::init(argc, argv, "talker");
// %EndTag(INIT)%

 
// %Tag(NODEHANDLE)%
  ros::NodeHandle n;
// %EndTag(NODEHANDLE)%

 
// %Tag(PUBLISHER)%
  ros::Publisher chatter_pub = n.advertise("chatter", 1000);
// %EndTag(PUBLISHER)%

// %Tag(LOOP_RATE)%
  ros::Rate loop_rate(10);
// %EndTag(LOOP_RATE)%

 
// %Tag(ROS_OK)%
  int count = 0;
  while (ros::ok())
  {
// %EndTag(ROS_OK)%
   
// %Tag(FILL_MESSAGE)%
    std_msgs::String msg;

    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();
// %EndTag(FILL_MESSAGE)%

// %Tag(ROSCONSOLE)%
    ROS_INFO("%s", msg.data.c_str());
// %EndTag(ROSCONSOLE)%

   
// %Tag(PUBLISH)%
    chatter_pub.publish(msg);
// %EndTag(PUBLISH)%

// %Tag(SPINONCE)%
    ros::spinOnce();
// %EndTag(SPINONCE)%

// %Tag(RATE_SLEEP)%
    loop_rate.sleep();
// %EndTag(RATE_SLEEP)%
    ++count;
  }


  return 0;
}
// %EndTag(FULLTEXT)%




3. 创建一个Subscriber:
在catkin_ws/src/Test_Source/Message_Source/src目录内,创建一个文件,listener_sam.cpp

// %Tag(CALLBACK)%
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}
// %EndTag(CALLBACK)%

int main(int argc, char **argv)
{
 
  ros::init(argc, argv, "listener");

 
  ros::NodeHandle n;

 
// %Tag(SUBSCRIBER)%
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// %EndTag(SUBSCRIBER)%

 
// %Tag(SPIN)%
  ros::spin();
// %EndTag(SPIN)%

  return 0;
}
// %EndTag(FULLTEXT)%

owing disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

// %Tag(FULLTEXT)%
#include "ros/ros.h"
#include "std_msgs/String.h"


// %Tag(CALLBACK)%
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}
// %EndTag(CALLBACK)%

int main(int argc, char **argv)
{
 
  ros::init(argc, argv, "listener");

 
  ros::NodeHandle n;

 
// %Tag(SUBSCRIBER)%
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// %EndTag(SUBSCRIBER)%

 
// %Tag(SPIN)%
  ros::spin();
// %EndTag(SPIN)%

  return 0;
}
// %EndTag(FULLTEXT)%


4. 修改CMakeLists.txt
把talker_sam.cpp, listener.cpp添加入Makefile.
add_executable(talker_sam src/talker_sam.cpp)
target_link_libraries(talker_sam ${catkin_LIBRARIES})

add_executable(listener_sam src/listener_sam.cpp)
target_link_libraries(listener_sam  ${catkin_LIBRARIES})


5. 运行与验证
5.1:首先运行所有程序:
$roscore
$rosrun messager takler_sam
$rosrun messager listener_sam

可以看到,终端上各自打印出各类信息。

5.2:验证:
$rosrun rqt_graph rqt_graph
/talker---->/listener
Topic名为:/chatter

5.3:查看Topic内容:
前面可以看到Topic的名子为:/chatter
$rostopic echo /chatter
可以看到Topic内容。

$rostopic type /chatter
可以看到Topic Message内容。



 

ubuntu 12.04 64bit 使用

$
0
0
 作者: Sam  (甄峰)  sam_code@hotmail.com

Sam一直习惯与在Fedora下做开发工作,所以服务器设置,工作环境设置等都在Fedora下比较熟悉。对Ubuntu下的设置一直一知半解。 但在做ROS后,推荐的工作环境是Ubuntu 12.04. 所以必须在Ubuntu下建立开发环境,设置各类服务。现记录如下。

1. 查看Ubuntu各类版本信息:
1.1: 查看Kernel版本:
与Fedora类似,可以使用:
$uname -r
3.11.0-20-generic
如果想看到更多信息,可以使用:
$uname -a
或者:
$cat /proc/version

1.2: 查看Ubuntu发布版本:
$lsb_release -a

Distributor ID:    Ubuntu
Description:    Ubuntu 12.04.4 LTS
Release:    12.04
Codename:    precise
即可看到Ubuntu版本信息。




 

Android NDK下 Neon探索

$
0
0
作者:Sam (甄峰)   sam_code@hotmail.com

之前在一些ARM CPU下,曾在编译时指定过Neon。

0. Neon简介:
0.1: 简介:
ARM Advanced SIMD延伸集,(ARM Cortex-A系列处理器的128位SIMD架构扩展)称为NEON技术,它是一个结合64bit和128bit的SIMD(Single Instruction Multiple Data单指令多数据)指令集。其针对多媒体和讯号处理程式具备标准化加速的能力,NEON具有一组广泛的指令集、各自的寄存器阵列,以及独立执行的硬件。ARM ENON技术可加速多媒体和信号处理算法(如视频编码/解码、2D/3D图形、游戏、音频和语音处理、图像处理技术、电话和声音合成)。

NEON的寄存器:有16个128位四字寄存器Q0-Q15,32个64位双字寄存器D0-D31,两个寄存器是重叠的,在使用的时候需要特别注意,不小心就会被覆盖掉。

0.2: SIMD:
通常我们进行多媒体处理的时候,很多的数据都是16位或者8位的,如果这些程序运行在32位的机器上,那么计算机有一部分的计算单元是没有工作的.所以这是一种浪费.为了更好的使用那些被浪费的资源.SIMD就应运而生了.SIMD这种技术就是使用一条指令,但对多个相同类型和尺寸的数据进行并行处理.就像我们现实生活中的好几个人都在做同一件事情那样,这样就可以将速度提升很多倍

0.3:使用Neon的方式
按Sam的理解,使用Neon的方式有以下几种:
A:使用C的neon内联函数。
B:直接使用Neon汇编指令。
C:使用某些第三方库如OpenMAX.

各自的优缺点:
A:使用Neon intrinsics函数,可以在直接接触ASM的情况下,使用Neon。这些函数被定义在:
arm_neon.h 中。
类似于:
vadd_s8 (int8x8_t __a, int8x8_t __b)
此方法需要注意2点:
1.必须: #include
2.编译时必须加入; -mfloat-abi=softfp -mfpu=neon

B:使用汇编指令
效率最高. 使用intrinsics没法控制寄存器分配和内存对齐等。

1. Android NDK下 Neon的探索
Sam着重研究的是使用intrinsics函数。研究标本为:/opt/android-ndk-r10b/samples/hello-neon/jni
为了不遗漏任何关键点,Sam从空文件main.cpp中开始写代码。
可以看到,代码基本全是从例子copy过来的。#include 必须要有。


main.cpp: 

#include
#include

#include          
#include
#include
#include
#include

#include

static void fir_filter_c(short *output, const short* input, const short* kernel, int width, int kernelSize);
static double now_ms(void);
void fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize);

//#include
#include "cpu-features.h"


#define  FIR_KERNEL_SIZE   32
#define  FIR_OUTPUT_SIZE   2560
#define  FIR_INPUT_SIZE    (FIR_OUTPUT_SIZE + FIR_KERNEL_SIZE)
#define  FIR_ITERATIONS    600

static const short  fir_kernel[FIR_KERNEL_SIZE] = { 
    0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10,
    0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10 };

static short        fir_output[FIR_OUTPUT_SIZE];
static short        fir_input_0[FIR_INPUT_SIZE];
static const short* fir_input = fir_input_0 + (FIR_KERNEL_SIZE/2);
static short        fir_output_expected[FIR_OUTPUT_SIZE];



static double now_ms(void)
{
    struct timespec res;
    clock_gettime(CLOCK_REALTIME, &res);
    return 1000.0*res.tv_sec + (double)res.tv_nsec/1e6;
}


static void fir_filter_c(short *output, const short* input, const short* kernel, int width, int kernelSize)
{
    int  offset = -kernelSize/2;
    int  nn;
    for (nn = 0; nn < width; nn++) {
        int sum = 0;
        int mm;
        for (mm = 0; mm < kernelSize; mm++) {
            sum += kernel[mm]*input[nn+offset+mm];
        }
        output[nn] = (short)((sum + 0x8000) >> 16);
    }
}


void fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize)
{
#if 1
   int nn, offset = -kernelSize/2;

   for (nn = 0; nn < width; nn++)
   {
        int mm, sum = 0;
        int32x4_t sum_vec = vdupq_n_s32(0);
        for(mm = 0; mm < kernelSize/4; mm++)
        {
            int16x4_t  kernel_vec = vld1_s16(kernel + mm*4);
            int16x4_t  input_vec = vld1_s16(input + (nn+offset+mm*4));
            sum_vec = vmlal_s16(sum_vec, kernel_vec, input_vec);
        }

        sum += vgetq_lane_s32(sum_vec, 0);
        sum += vgetq_lane_s32(sum_vec, 1);
        sum += vgetq_lane_s32(sum_vec, 2);
        sum += vgetq_lane_s32(sum_vec, 3);

        if(kernelSize & 3)
        {
            for(mm = kernelSize - (kernelSize & 3); mm < kernelSize; mm++)
                sum += kernel[mm] * input[nn+offset+mm];
        }

        output[nn] = (short)((sum + 0x8000) >> 16);
    }
#else
    int nn, offset = -kernelSize/2;
    for (nn = 0; nn < width; nn++) {
        int sum = 0;
        int mm;
        for (mm = 0; mm < kernelSize; mm++) {
            sum += kernel[mm]*input[nn+offset+mm];
        }
        output[n] = (short)((sum + 0x8000) >> 16);
    }
#endif
}


int main(int argc, char** argv)
{
uint64_t features;
char buffer[512];
    char tryNeon = 0;
    double  t0, t1, time_c, time_neon;


if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {
        printf("\nCPU Not ARM\n");
return -1;
    }


features = android_getCpuFeatures();
if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {
        printf("\nNot an ARMv7 CPU !\n");
        return -1;
    }

    if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {
        printf("\nCPU doesn't support NEON !\n");
        return -1;
    }

printf("\nFeature: 0x%llx\n", features);









    {
        int  nn;
        for (nn = 0; nn < FIR_INPUT_SIZE; nn++) {
            fir_input_0[nn] = (5*nn) & 255;
        }
        fir_filter_c(fir_output_expected, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);
    }

   
    t0 = now_ms();
    {
        int  count = FIR_ITERATIONS;
        for (; count > 0; count--) {
            fir_filter_c(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);
        }
    }
    t1 = now_ms();
    time_c = t1 - t0;

printf("\nFIR Filter benchmark:\nC version          : %g ms\n", time_c);





printf("\n\nNeon version   : ", sizeof buffer);

t0 = now_ms();
    {
        int  count = FIR_ITERATIONS;
        for (; count > 0; count--) {
            fir_filter_neon_intrinsics(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);
        }
    }
    t1 = now_ms();
    time_neon = t1 - t0;
    printf(" %g ms (x%g faster)\n", time_neon, time_c / (time_neon < 1e-6 ? 1. : time_neon));




return 0;
}

Android.mk:
LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := Test_Neon
LOCAL_STATIC_LIBRARIES := cpufeatures
LOCAL_SRC_FILES := main.cpp



LOCAL_CFLAGS := -DHAVE_NEON=1  -I/opt/android-ndk-r10b/sources/android/cpufeatures -mfloat-abi=softfp -mfpu=neon
LOCAL_CXXFLAGS := -DHAVE_NEON=1 -I/opt/android-ndk-r10b/sources/android/cpufeatures -mfloat-abi=softfp -mfpu=neon

LOCAL_LDLIBS := -L/opt/android-ndk-r10b/sources/android/libs/armeabi-v7a -lcpufeatures
include $(BUILD_EXECUTABLE)


Application.mk:
# Build both ARMv5TE and ARMv7-A machine code.
APP_PLATFORM = android-8

APP_ABI := armeabi-v7a
#APP_ABI := $(ARM_ARCH)

#Sam modify it to release
APP_OPTIM := release
#APP_OPTIM := debug
#APP_OPTIM = $(MY_OPTIM)

APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -frtti

#sam modify it from gnustl_static to gnustl_shared
#APP_STL := gnustl_static
#APP_STL := gnustl_shared
APP_STL := gnustl_shared

#APP_CPPFLAGS += -fno-rtti


#
APP_CPPFLAGS += -Dlinux -fsigned-char
APP_CFLAGS += -fsigned-char
#APP_CPPFLAGS += $(MY_CPPFLAGS) -Dlinux
#STLPORT_FORCE_REBUILD := true



编译过程会遇到以下几个问题:
1. android_getCpuFeatures系列函数未声明:
则加入#include "cpu-features.h"
并在Android.mk中加入其路径:-I/opt/android-ndk-r10b/sources/android/cpufeatures

2. android_getCpuFeatures系列函数未定义:
进入:/opt/android-ndk-r10b/sources/android/cpufeatures
并编译之,Sam修改Android.mk, 把它编译成动态库。并保持Application.mk统一配置。



在K1平台上,运行结果是:
./Test_Neon                                   

Feature: 0x7ff

FIR Filter benchmark:
C version          : 117.865 ms


Neon version   :  26.5964 ms (x4.43163 faster)

显示对FIR Filter,使用Neon有4倍速度。






2. 如何判断当前平台是否支持Neon指令集
2.1: 字符界面查看:
cat /proc/cpuinfo
在Features 项目中,看是否包含neon.

2.2:编程查看:
利用上一节中编译出的libcpufeatures.so 库得到CPU Features信息:
if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {
        printf("\nCPU Not ARM\n");
return -1;
    }


features = android_getCpuFeatures();
if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {
        printf("\nNot an ARMv7 CPU !\n");
        return -1;
    }

    if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {
        printf("\nCPU doesn't support NEON !\n");
        return -1;
    }




 

ROS学习  ROS launch

$
0
0
 作者: Sam (甄峰)  sam_code@hotmail.com

0. 背景:
ROS提供一个同时启动节点管理器(Node Master, Node Core)和多个节点的方法------Launch file.

要实现一个较为复杂的功能,通常需要多个Node启动,有时还有一些配置和参数。如果一一手动启动, 不光麻烦重复,而且容易出错。所以就有了Launch File. 在文件中把需要启动的Node标记好,一次性全部启动。

1. Launch 文件格式
Launch文件通常以.launch为后缀名。 并统一存放在Package的launch目录内。

Launch是一个XML文件格式。每个Launch文件都必须包含一个根元素,根元素由一对launch定义:


 
Viewing all 158 articles
Browse latest View live