精华内容
下载资源
问答
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第六十章Linux RTC驱动实验RTC也就是实时时钟,用于记录当前系统时间,对于Linux...

    1)实验平台:正点原子Linux开发板
    2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南
    关注官方微信号公众号,获取更多资料:正点原子

    1b3c573a539d01aa2d1451c2465a6928.png

    第六十章Linux RTC驱动实验

    RTC也就是实时时钟,用于记录当前系统时间,对于Linux系统而言时间是非常重要的,就和我们使用Windows电脑或手机查看时间一样,我们在使用Linux设备的时候也需要查看时间。本章我们就来学习一下如何编写Linux下的RTC驱动程序。

    60.1 Linux内核RTC驱动简介

    RTC设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作,关于RTC硬件原理部分我们已经在裸机篇中的第二十五章进行了详细的讲解,这里就不再废话了。

    Linux内核将RTC设备抽象为rtc_device结构体,因此RTC设备驱动就是申请并初始化rtc_device,最后将rtc_device注册到Linux内核里面,这样Linux内核就有一个RTC设备的。至于RTC设备的操作肯定是用一个操作集合(结构体)来表示的,我们先来看一下rtc_device结构体,此结构体定义在include/linux/rtc.h文件中,结构体内容如下(删除条件编译):

    示例代码60.1.1 rtc_device结构体

    104struct rtc_device

    105{

    106struct device dev; /* 设备 */

    107struct module *owner;

    108

    109int id; /* ID */

    110char name[RTC_DEVICE_NAME_SIZE];/* 名字 */

    111

    112conststruct rtc_class_ops *ops; /* RTC设备底层操作函数 */

    113struct mutex ops_lock;

    114

    115struct cdev char_dev; /* 字符设备 */

    116unsignedlong flags;

    117

    118unsignedlong irq_data;

    119 spinlock_t irq_lock;

    120 wait_queue_head_t irq_queue;

    121struct fasync_struct *async_queue;

    122

    123struct rtc_task *irq_task;

    124 spinlock_t irq_task_lock;

    125int irq_freq;

    126int max_user_freq;

    127

    128struct timerqueue_head timerqueue;

    129struct rtc_timer aie_timer;

    130struct rtc_timer uie_rtctimer;

    131struct hrtimer pie_timer;/* sub second exp, so needs hrtimer */

    132int pie_enabled;

    133struct work_struct irqwork;

    134/* Some hardware can't support UIE mode */

    135int uie_unsupported;

    ......

    147};

    我们需要重点关注的是ops成员变量,这是一个rtc_class_ops类型的指针变量,rtc_class_ops为RTC设备的最底层操作函数集合,包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此,rtc_class_ops是需要用户根据所使用的RTC设备编写的,此结构体定义在include/linux/rtc.h文件中,内容如下:

    示例代码60.1.2 rtc_class_ops结构体

    71struct rtc_class_ops {

    72int(*open)(struct device *);

    73void(*release)(struct device *);

    74int(*ioctl)(struct device *,unsignedint,unsignedlong);

    75int(*read_time)(struct device *,struct rtc_time *);

    76int(*set_time)(struct device *,struct rtc_time *);

    77int(*read_alarm)(struct device *,struct rtc_wkalrm *);

    78int(*set_alarm)(struct device *,struct rtc_wkalrm *);

    79int(*proc)(struct device *,struct seq_file *);

    80int(*set_mmss64)(struct device *, time64_t secs);

    81int(*set_mmss)(struct device *,unsignedlong secs);

    82int(*read_callback)(struct device *,int data);

    83int(*alarm_irq_enable)(struct device *,unsignedint enabled);

    84};

    看名字就知道rtc_class_ops操作集合中的这些函数是做什么的了,但是我们要注意,rtc_class_ops中的这些函数只是最底层的RTC设备操作函数,并不是提供给应用层的file_operations函数操作集。RTC是个字符设备,那么肯定有字符设备的file_operations函数操作集,Linux内核提供了一个RTC通用字符设备驱动文件,文件名为drivers/rtc/rtc-dev.c,rtc-dev.c文件提供了所有RTC设备共用的file_operations函数操作集,如下所示:

    示例代码60.1.3 RTC通用file_operations操作集

    448staticconststruct file_operations rtc_dev_fops ={

    449.owner = THIS_MODULE,

    450.llseek = no_llseek,

    451.read = rtc_dev_read,

    452.poll = rtc_dev_poll,

    453.unlocked_ioctl = rtc_dev_ioctl,

    454.open = rtc_dev_open,

    455.release = rtc_dev_release,

    456.fasync = rtc_dev_fasync,

    457};

    看到示例代码60.1.3是不是很熟悉了,标准的字符设备操作集。应用程序可以通过ioctl函数来设置/读取时间、设置/读取闹钟的操作,那么对应的rtc_dev_ioctl函数就会执行, rtc_dev_ioctl最终会通过操作rtc_class_ops中的read_time、set_time等函数来对具体RTC设备的读写操作。我们简单来看一下rtc_dev_ioctl函数,函数内容如下(有省略):

    示例代码60.1.4 rtc_dev_ioctl函数代码段

    218staticlong rtc_dev_ioctl(struct file *file,

    219unsignedint cmd,unsignedlong arg)

    220{

    221int err =0;

    222struct rtc_device *rtc = file->private_data;

    223conststruct rtc_class_ops *ops = rtc->ops;

    224struct rtc_time tm;

    225struct rtc_wkalrm alarm;

    226void __user *uarg =(void __user *) arg;

    227

    228 err = mutex_lock_interruptible(&rtc->ops_lock);

    229if(err)

    230return err;

    ......

    269switch(cmd){

    ......

    333case RTC_RD_TIME:/* 读取时间 */

    334 mutex_unlock(&rtc->ops_lock);

    335

    336 err = rtc_read_time(rtc,&tm);

    337if(err <0)

    338return err;

    339

    340if(copy_to_user(uarg,&tm,sizeof(tm)))

    341 err =-EFAULT;

    342return err;

    343

    344case RTC_SET_TIME:/* 设置时间 */

    345 mutex_unlock(&rtc->ops_lock);

    346

    347if(copy_from_user(&tm, uarg,sizeof(tm)))

    348return-EFAULT;

    349

    350return rtc_set_time(rtc,&tm);

    ......

    401default:

    402/* Finally try the driver's ioctl interface */

    403if(ops->ioctl){

    404 err = ops->ioctl(rtc->dev.parent, cmd, arg);

    405if(err ==-ENOIOCTLCMD)

    406 err =-ENOTTY;

    407}else

    408 err =-ENOTTY;

    409break;

    410}

    411

    412 done:

    413 mutex_unlock(&rtc->ops_lock);

    414return err;

    415}

    第333行,RTC_RD_TIME为时间读取命令。

    第336行,如果是读取时间命令的话就调用rtc_read_time函数获取当前RTC时钟,rtc_read_time函数,rtc_read_time会调用__rtc_read_time函数,__rtc_read_time函数内容如下:

    示例代码60.1.5 __rtc_read_time函数代码段

    23staticint __rtc_read_time(struct rtc_device *rtc,

    struct rtc_time *tm)

    24{

    25 int err;

    26 if(!rtc->ops)

    27 err =-ENODEV;

    28 elseif(!rtc->ops->read_time)

    29 err =-EINVAL;

    30 else{

    31 memset(tm,0,sizeof(struct rtc_time));

    32 err = rtc->ops->read_time(rtc->dev.parent, tm);

    33 if(err <0){

    34 dev_dbg(&rtc->dev,"read_time: fail to read: %d",

    35 err);

    36 return err;

    37 }

    38

    39 err = rtc_valid_tm(tm);

    40 if(err <0)

    41 dev_dbg(&rtc->dev,"read_time: rtc_time isn't valid");

    42 }

    43 return err;

    44}

    从示例代码60.1.5中的32行可以看出,__rtc_read_time函数会通过调用rtc_class_ops中的read_time来从RTC设备中获取当前时间。rtc_dev_ioctl函数对其他的命令处理都是类似的,比如RTC_ALM_READ命令会通过rtc_read_alarm函数获取到闹钟值,而rtc_read_alarm函数经过层层调用,最终会调用rtc_class_ops中的read_alarm函数来获取闹钟值。

    至此,Linux内核中RTC驱动调用流程就很清晰了,如图60.1.1所示:

    db610e93d2981a7abb1bb937d55b2c9e.png

    图60.1.1 Linux RTC驱动调用流程

    当rtc_class_ops准备好以后需要将其注册到Linux内核中,这里我们可以使用rtc_device_register函数完成注册工作。此函数会申请一个rtc_device并且初始化这个rtc_device,最后向调用者返回这个rtc_device,此函数原型如下:

    struct rtc_device *rtc_device_register(const char *name,

    struct device *dev,

    const struct rtc_class_ops *ops,

    struct module *owner)

    函数参数和返回值含义如下:

    name:设备名字。

    dev:设备。

    ops:RTC底层驱动函数集。

    owner:驱动模块拥有者。

    返回值:注册成功的话就返回rtc_device,错误的话会返回一个负值。

    当卸载RTC驱动的时候需要调用rtc_device_unregister函数来注销注册的rtc_device,函数原型如下:

    void rtc_device_unregister(struct rtc_device *rtc)

    函数参数和返回值含义如下:

    rtc:要删除的rtc_device。

    返回值:无。

    还有另外一对rtc_device注册函数devm_rtc_device_register和devm_rtc_device_unregister,分别为注册和注销rtc_device。

    60.2 I.MX6U内部RTC驱动分析

    先直接告诉大家,I.MX6U的RTC驱动我们不用自己编写,因为NXP已经写好了。其实对于大多数的SOC来讲,内部RTC驱动都不需要我们去编写,半导体厂商会编写好。但是这不代表我们就偷懒了,虽然不用编写RTC驱动,但是我们得看一下这些原厂是怎么编写RTC驱动的。

    分析驱动,先从设备树入手,打开imx6ull.dtsi,在里面找到如下snvs_rtc设备节点,节点内容如下所示:

    示例代码60.2.1 imx6ull.dtsi文件rtc设备节点

    1 snvs_rtc: snvs-rtc-lp {

    2 compatible ="fsl,sec-v4.0-mon-rtc-lp";

    3 regmap =snvs>;

    4 offset =<0x34>;

    5 interrupts =<GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 20

    IRQ_TYPE_LEVEL_HIGH>;

    6};

    第2行设置兼容属性compatible的值为“fsl,sec-v4.0-mon-rtc-lp”,因此在Linux内核源码中搜索此字符串即可找到对应的驱动文件,此文件为drivers/rtc/rtc-snvs.c,在rtc-snvs.c文件中找到如下所示内容:

    示例代码60.2.2 rtc设备platform驱动框架

    380staticconststruct of_device_id snvs_dt_ids[]={

    381{.compatible ="fsl,sec-v4.0-mon-rtc-lp",},

    382{/* sentinel */}

    383};

    384 MODULE_DEVICE_TABLE(of, snvs_dt_ids);

    385

    386staticstruct platform_driver snvs_rtc_driver ={

    387.driver ={

    388.name ="snvs_rtc",

    389.pm = SNVS_RTC_PM_OPS,

    390.of_match_table = snvs_dt_ids,

    391},

    392.probe = snvs_rtc_probe,

    393};

    394 module_platform_driver(snvs_rtc_driver);

    第380~383行,设备树ID表,有一条compatible属性,值为“fsl,sec-v4.0-mon-rtc-lp”,因此imx6ull.dtsi中的snvs_rtc设备节点会和此驱动匹配。

    第386~393行,标准的platform驱动框架,当设备和驱动匹配成功以后snvs_rtc_probe函数就会执行。我们来看一下snvs_rtc_probe函数,函数内容如下(有省略):

    示例代码60.2.3 snvs_rtc_probe函数代码段

    238staticint snvs_rtc_probe(struct platform_device *pdev)

    239{

    240struct snvs_rtc_data *data;

    241struct resource *res;

    242int ret;

    243void __iomem *mmio;

    244

    245 data = devm_kzalloc(&pdev->dev,sizeof(*data), GFP_KERNEL);

    246if(!data)

    247return-ENOMEM;

    248

    249 data->regmap =

    syscon_regmap_lookup_by_phandle(pdev->dev.of_node,"regmap");

    250

    251if(IS_ERR(data->regmap)){

    252 dev_warn(&pdev->dev,"snvs rtc: you use old dts file,

    please update it");

    253 res = platform_get_resource(pdev, IORESOURCE_MEM,0);

    254

    255 mmio = devm_ioremap_resource(&pdev->dev, res);

    256if(IS_ERR(mmio))

    257return PTR_ERR(mmio);

    258

    259 data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio,

    &snvs_rtc_config);

    260}else{

    261 data->offset = SNVS_LPREGISTER_OFFSET;

    262 of_property_read_u32(pdev->dev.of_node,"offset",

    &data->offset);

    263}

    264

    265if(!data->regmap){

    266 dev_err(&pdev->dev,"Can't find snvs syscon");

    267return-ENODEV;

    268}

    269

    270 data->irq = platform_get_irq(pdev,0);

    271if(data->irq <0)

    272return data->irq;

    ......

    285

    286 platform_set_drvdata(pdev, data);

    287

    288/* Initialize glitch detect */

    289 regmap_write(data->regmap, data->offset + SNVS_LPPGDR,

    SNVS_LPPGDR_INIT);

    290

    291/* Clear interrupt status */

    292 regmap_write(data->regmap, data->offset + SNVS_LPSR,

    0xffffffff);

    293

    294/* Enable RTC */

    295 snvs_rtc_enable(data, true);

    296

    297 device_init_wakeup(&pdev->dev, true);

    298

    299 ret = devm_request_irq(&pdev->dev, data->irq,

    snvs_rtc_irq_handler,

    300 IRQF_SHARED,"rtc alarm",&pdev->dev);

    301if(ret){

    302 dev_err(&pdev->dev,"failed to request irq %d: %d",

    303 data->irq, ret);

    304goto error_rtc_device_register;

    305}

    306

    307 data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,

    308&snvs_rtc_ops, THIS_MODULE);

    309if(IS_ERR(data->rtc)){

    310 ret = PTR_ERR(data->rtc);

    311 dev_err(&pdev->dev,"failed to register rtc: %d", ret);

    312goto error_rtc_device_register;

    313}

    314

    315return0;

    316

    317 error_rtc_device_register:

    318if(data->clk)

    319 clk_disable_unprepare(data->clk);

    320

    321return ret;

    322}

    第253行,调用platform_get_resource函数从设备树中获取到RTC外设寄存器基地址。

    第255行,调用函数devm_ioremap_resource完成内存映射,得到RTC外设寄存器物理基地址对应的虚拟地址。

    第259行,Linux3.1引入了一个全新的regmap机制,regmap用于提供一套方便的API函数去操作底层硬件寄存器,以提高代码的可重用性。snvs-rtc.c文件会采用regmap机制来读写RTC底层硬件寄存器。这里使用devm_regmap_init_mmio函数将RTC的硬件寄存器转化为regmap形式,这样regmap机制的regmap_write、regmap_read等API函数才能操作寄存器。

    第270行,从设备树中获取RTC的中断号。

    第289行,设置RTC_ LPPGDR寄存器值为SNVS_LPPGDR_INIT= 0x41736166,这里就是用的regmap机制的regmap_write函数完成对寄存器进行写操作。

    第292行,设置RTC_LPSR寄存器,写入0xffffffff,LPSR是RTC状态寄存器,写1清零,因此这一步就是清除LPSR寄存器。

    第295行,调用snvs_rtc_enable函数使能RTC,此函数会设置RTC_LPCR寄存器。

    第299行,调用devm_request_irq函数请求RTC中断,中断服务函数为snvs_rtc_irq_handler,用于RTC闹钟中断。

    第307行,调用devm_rtc_device_register函数向系统注册rtc_devcie,RTC底层驱动集为snvs_rtc_ops。snvs_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。snvs_rtc_ops内容如下:

    示例代码60.2.4 snvs_rtc_ops操作集

    200staticconststruct rtc_class_ops snvs_rtc_ops ={

    201.read_time = snvs_rtc_read_time,

    202.set_time = snvs_rtc_set_time,

    203.read_alarm = snvs_rtc_read_alarm,

    204.set_alarm = snvs_rtc_set_alarm,

    205.alarm_irq_enable = snvs_rtc_alarm_irq_enable,

    206};

    我们就以第201行的snvs_rtc_read_time函数为例讲解一下rtc_class_ops的各个RTC底层操作函数该如何去编写。snvs_rtc_read_time函数用于读取RTC时间值,此函数内容如下所示:

    示例代码60.2.5 snvs_rtc_read_time函数代码段

    126staticint snvs_rtc_read_time(struct device *dev,

    struct rtc_time *tm)

    127{

    128struct snvs_rtc_data *data = dev_get_drvdata(dev);

    129unsignedlong time = rtc_read_lp_counter(data);

    130

    131 rtc_time_to_tm(time, tm);

    132

    133return0;

    134}

    第129行,调用rtc_read_lp_counter获取RTC计数值,这个时间值是秒数。

    第131行,调用rtc_time_to_tm函数将获取到的秒数转换为时间值,也就是rtc_time结构体类型,rtc_time结构体定义如下:

    示例代码60.2.6 rtc_time结构体类型

    20struct rtc_time {

    21 int tm_sec;

    22 int tm_min;

    23 int tm_hour;

    24 int tm_mday;

    25 int tm_mon;

    26 int tm_year;

    27 int tm_wday;

    28 int tm_yday;

    29 int tm_isdst;

    30};

    最后我们来看一下rtc_read_lp_counter函数,此函数用于读取RTC计数值,函数内容如下(有省略):

    示例代码60.2.7 rtc_read_lp_counter函数代码段

    50static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)

    51{

    52 u64 read1, read2;

    53 u32 val;

    54

    55 do{

    56 regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR,

    &val);

    57 read1 = val;

    58 read1 <<=32;

    59 regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR,

    &val);

    60 read1 |= val;

    61

    62 regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR,

    &val);

    63 read2 = val;

    64 read2 <<=32;

    65 regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR,

    &val);

    66 read2 |= val;

    67 /*

    68 * when CPU/BUS are running at low speed, there is chance that

    69 * we never get same value during two consecutive read, so here

    70 * we only compare the second value.

    71 */

    72 }while((read1 >> CNTR_TO_SECS_SH)!=(read2 >>

    CNTR_TO_SECS_SH));

    73

    74 /* Convert 47-bit counter to 32-bit raw second count */

    75 return(u32)(read1 >> CNTR_TO_SECS_SH);

    76}

    第56~72行,读取RTC_LPSRTCMR和RTC_LPSRTCLR这两个寄存器,得到RTC的计数值,单位为秒,这个秒数就是当前时间。这里读取了两次RTC计数值,因为要读取两个寄存器,因此可能存在读取第二个寄存器的时候时间数据更新了,导致时间不匹配,因此这里连续读两次,如果两次的时间值相等那么就表示时间数据有效。

    第75行,返回时间值,注意这里将前面读取到的RTC计数值右移了15位。

    这个就是snvs_rtc_read_time函数读取RTC时间值的过程,至于其他的底层操作函数大家自行分析即可,都是大同小异的,这里就不再分析了。关于I.MX6U内部RTC驱动源码就讲解到这里。

    60.3 RTC时间查看与设置

    1、时间RTC查看

    RTC是用来计时的,因此最基本的就是查看时间,Linux内核启动的时候可以看到系统时钟设置信息,如图60.3.1所示:

    9ce8891c79876b9667163e23714e82c3.png

    图60.3.1 Linux启动log信息

    从图60.3.1中可以看出,Linux内核在启动的时候将snvs_rtc设置为rtc0,大家的启动信息可能会和图60.3.1中的不同,但是内容基本上都是一样的。

    如果要查看时间的话输入“date”命令即可,结果如图60.3.2所示:

    b2aa762f2dd3d4659775a26234cb1e95.png

    图60.3.2 当前时间值

    从图60.3.2可以看出,当前时间为1970年1月1日00:06:11,很明显是时间不对,我们需要重新设置RTC时间。

    2、设置RTC时间

    RTC时间设置也是使用的date命令,输入“date--help”命令即可查看date命令如何设置系统时间,结果如图60.3.3所示:

    9cf058af2d635bb5b0550fc673854a8c.png

    图60.3.3 date命令帮助信息

    现在我要设置当前时间为2019年8月31日18:13:00,因此输入如下命令:

    date -s "2019-08-31 18:13:00"

    设置完成以后再次使用date命令查看一下当前时间就会发现时间改过来了,如图60.3.4所示:

    3b9ddfe178b7ab4abeb285ed2c2042f7.png

    图60.3.4 当前时间

    大家注意我们使用“date -s”命令仅仅是将当前系统时间设置了,此时间还没有写入到I.MX6U内部RTC里面或其他的RTC芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到RTC里面,这里要用到hwclock命令,输入如下命令将系统时间写入到RTC里面:

    hwclock-w //将当前系统时间写入到RTC里面

    时间写入到RTC里面以后就不怕系统重启以后时间丢失了,如果I.MX6U-ALPHA开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。大家可以尝试一下不断电重启和断电重启这两种情况下开发板时间会不会丢失

    展开全文
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第七十章Linux WIFI驱动实验 WIFI的使用已经很常见了,手机、平板、汽车等等,虽然可以...

    1)资料下载:点击资料即可下载

    2)对正点原子Linux感兴趣的同学可以加群讨论:935446741

    3)关注正点原子公众号,获取最新资料更新

    95b1134667ddd4e7dee6f7141fd321c7.png

    第七十章Linux WIFI驱动实验

    WIFI的使用已经很常见了,手机、平板、汽车等等,虽然可以使用有线网络,但是有时候很多设备存在布线困难的情况,此时WIFI就是一个不错的选择。正点原子I.MX6U-ALPHA开发板支持USB和SDIO这两种接口的WIFI,本章我们就来学习一下如何在I.MX6U-ALPHA开发板上使用USB和SDIO这两种WIFI。

    70.1 WIFI驱动添加与编译

    正点原子的I.MX6U-ALPHA开发板目前支持两种接口的WIFI:USB和SDIO,其中USB WIFI使用使用的芯片为RTL8188EUS,SDIO接口的WIFI使用芯片为RTL8189FS,也叫做RTL8189FTV。这两个都是realtek公司出品的WIFI芯片。WIFI驱动不需要我们编写,因为realtek公司提供了WIFI驱动源码,因此我们只需要将WIFI驱动源码添加到Linux内核中,然后通过图形化界面配置,选择将其编译成模块即可。

    正点原子I.MX6U-ALPHA开发板默认会赠送一个RTL8188 USB WIFI,RTL8188 USB WIFI如图70.1.1所示:

    fde97e2ad894a6a22ab819fa1e51cfee.png

    图70.1.1 RTL8188 USB WIFI

    另外,正点原子还有一款采用RTL8189FTV芯片的SDIO WIFI,如图70.1.2所示:

    7eb95021be51387281d596d4fd61d77a.png

    图70.1.2 RTL8188 SDIO WIFI

    70.1.1 向Linux内核添加WIFI驱动

    1、rtl81xx驱动文件浏览

    WIFI驱动源码已经放到了开发板光盘中,路径为:1、例程源码->5、模块驱动源码->1、RTL8XXX WIFI驱动源码-> realtek。realtek目录下就存放着RTL8188EUS和RTL8189FS这两个芯片的驱动源码,如图70.1.1.1所示:

    ff0ca14459ca351445634a4b818b191f.png

    图70.1.1.1 rtl8xxx WIFI驱动

    其中rtl8188EUS下存放着RTL8188EUS驱动,RTL8189FS存放着RTL8189FS/FTV的驱动文件。Kconfig文件是WIFI驱动的配置界面文档,这样可以通过Linux内核图形化配置界面来选择是否编译WIFI驱动,Kconfig文件内容如下所示:

    示例代码70.1.1.1 Kconfig文件内容

    1 menuconfig REALTEK_WIFI

    2 tristate "Realtek wifi"

    3

    4if REALTEK_WIFI

    5

    6 choice

    7 prompt "select wifi type"

    8 default RTL9189FS

    9

    10 config RTL9189FS

    11 depends on REALTEK_WIFI

    12 tristate "rtl8189fs/ftv sdio wifi"

    13

    14 config RTL8188EUS

    15 depends on REALTEK_WIFI

    16 tristate "rtl8188eus usb wifi"

    17

    18 endchoice

    19 endif

    Makefile文件内容如下所示

    示例代码70.1.1.2 Makefile文件内容

    1 obj-$(CONFIG_RTL8188EUS)+= rtl8188EUS/

    2 obj-$(CONFIG_RTL8189FS)+= rtl8189FS/

    2、将rtl81xx驱动添加到Linux内核中

    将realtek整个目录拷贝到ubuntu下Linux内核源码中的drivers/net/wireless目录下,此目录下存放着所有WIFI驱动文件。拷贝完成以后此目录如图70.1.1.1所示:

    0ace52a954c1c5477b68a4c9640743ad.png

    图70.1.1.1 拷贝完成的wireless目录

    图70.1.1.1中框选出来的就是我们刚刚拷贝进来的realtek目录。

    3、修改drivers/net/wireless/Kconfig

    打开drivers/net/wireless/Kconfig,在里面加入下面这一行内容:

    source "drivers/net/wireless/realtek/Kconfig"

    添加完以后的Kconfig文件内容如下所示:

    示例代码70.1.1.3 drivers/net/wireless/Kconfig文件内容

    1 #

    2 # Wireless LAN device configuration

    3 #

    4

    5 menuconfig WLAN

    ......

    286 source "drivers/net/wireless/rsi/Kconfig"

    287 source "drivers/net/wireless/realtek/Kconfig"

    286

    289 endif # WLAN

    第287行就是添加到drivers/net/wireless/Kconfig中的内容,这样WIFI驱动的配置界面才会出现在Linux内核配置界面上。

    3、修改drivers/net/wireless/Makefile

    打开drivers/net/wireless/Makefile,在里面加入下面一行内容:

    obj-y += realtek/

    修改完以后的Makefile文件内容如下所示:

    示例代码70.1.1.4 drivers/net/wireless/Makefile文件内容

    1 #

    2 # Makefile for the Linux Wireless network device drivers.

    3 #

    4

    5 obj-$(CONFIG_IPW2100)+= ipw2x00/

    ......

    62 obj-$(CONFIG_CW1200)+= cw1200/

    63 obj-$(CONFIG_RSI_91X)+= rsi/

    64

    65 obj-y += realtek/

    第65行,编译realtek中的内容,至此,Linux内核要修改的内容就全部完成了。

    70.1.2 配置Linux内核

    在编译RTL8188和RTL8189驱动之前需要先配置Linux内核。

    1、配置USB支持设备

    配置路径如下:

    -> Device Drivers

    -><*>USB support

    -><*>Support for Host-side USB

    -><*> EHCI HCD (USB 2.0) support

    -><*> OHCI HCD (USB 1.1) support

    -><*> ChipIdea Highspeed Dual Role Controller

    -> [*] ChipIdea device controller

    -> [*] ChipIdea host controller

    2、配置支持WIFI设备

    配置路径如下:

    -> Device Drivers

    ->[*] Network device support

    ->[*] Wireless LAN

    -><*> IEEE 802.11 for Host AP (Prism2/2.5/3 and WEP/TKIP/CCMP)

    -> [*] Support downloading firmware images with Host AP driver

    -> [*] Support for non-volatile firmware download

    配置完如图70.1.2.1所示:

    15f13027ac9d41dec0295ededb3b4ae4.png

    图70.1.2.1 配置支持WIFI设备

    3、配置支持IEEE 802.11

    配置路径如下:

    -> Networking support

    -> -*- Wireless

    ->[*] cfg80211 wireless extensions compatibility

    -><*> Generic IEEE 802.11 Networking Stack (mac80211)

    配置完如图70.1.2.2所示:

    4253b6c0165fdf009d79eab55cb89c61.png

    图70.1.2.2 IEE 802.11配置项

    配置好以后重新编译一下Linux内核,得到新的zImage,后面使用新编译出来的zImage启动系统。

    70.1.3 编译WIFI驱动

    执行“makemenuconfig”命令,打开Linux内核配置界面,然后按照如下路径选择将rtl81xx驱动编译为模块:

    -> Device Drivers

    -> Network device support (NETDEVICES [=y])

    -> Wireless LAN (WLAN [=y])

    -> Realtek wifi (REALTEK_WIFI [=m])

    -> rtl8189ftv sdio wifi

    -> rtl8188eus usb wifi

    配置结果如图70.1.3.1所示:

    b9dda3453a573fe9994b4845f1586cde.png

    图70.1.3.1 WIFI配置界面

    图70.1.3.1中的配置界面就是我们添加进去的WIFI配置界面,选中“rtl8189fs/ftv sdio wifi”和“rtl8188eus usb wifi”,将其编译为模块。执行如下命令编译模块:

    makemodules -j12 //编译驱动模块

    编译完成以后就会在rtl8188EUS和rtl8189FS文件夹下分别生成8188eu.ko和8189fs.ko这两个.ko文件,结果如图70.1.3.2所示:

    d180e9db992095f48c36007c1472bb41.png

    图70.1.3.2 编译结果

    图70.1.3.2中的8188eu.ko和8189fs.ko就是我们需要的RTL8188EUS和RTL8189FS的驱动模块文件,将这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,命令如下:

    sudo cp 8189fs.ko /home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -rf

    sudo cp 8188eu.ko /home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -rf

    因为我们重新配置过Linux内核,因此也需要使用新的zImage启动,将新编译出来的zImage镜像文件拷贝到Ubuntu中的tftpboot目录下,命令如下:

    cp arch/arm/boot/zImage /home/zuozhongkai/linux/tftpboot/ -f

    然后重启开发板!!!

    70.1.4 驱动加载测试

    1、RTL8188 USB WIFI驱动测试

    重启以后我们试着加载一下8188eu.ko和8189fs.ko这两个驱动文件,首先测试一下RTL8188的驱动文件,将RTL8188 WIFI模块插到开发板的USB HOST接口上。进入到目录lib/modules/4.1.15中,输入如下命令加载8188eu.ko这个驱动模块:

    depmod //第一次加载驱动的时候需要运行此命令

    modprobe 8188eu.ko //加载驱动模块

    如果驱动加载成功的话如图70.1.4.1所示:

    87b356356be6a02b3f4d3725f8f1c7cf.png

    图70.1.4.1 RTL8188驱动加载成功

    输入“ifconfig-a”命令,查看wlanX(X=0….n)网卡是否存在,一般都是wlan0,除非板子上有多个WIFI模块在工作,结果如图70.1.4.2所示:

    6f4ab5326d1fafd04ea3e0bcf86931a0.png

    图70.1.4.2 当前开发板所有网卡

    从图70.1.4.2中可以看出,当前开发板有一个叫做“wlan0”的网卡,这个就是RTL8188对应的网卡。

    2、RTL8189 SDIO WIFI驱动测试

    测试完RTL8188以后,再来测试一下RTL8189这个SDIO WIFI,因为I.MX6U-ALPHA开发板的SDIO WIFI接口与SD卡公用一个SDIO接口。因此SD卡和SDIO WIFI只能二选其一,一次只能一个工作,所以测试RTL8189 SDIO WIFI的时候需要拔插SD卡。SDIO WIFI接口原理图如图70.1.4.3所示:

    ac18c74b9817582f01b77fa8da2f3131.png

    图70.1.4.3 SDIO WIFI接口

    测试开始之前要先将SD卡拔出,然后将RTL8189 SDIO WIFI模块插入到SDIO WIFI座子上,如图70.1.4.4所示:

    dd2800d5ad6208e2410182bafe59fb0d.png

    图70.1.4.4 SDIO WIFI连接图

    SDIO WIFI与开发板连接好以后就可以测试了,输入如下命令加载8189fs.ko这个驱动模块:

    depmod //第一次加载驱动的时候需要运行此命令

    modprobe 8189eu.ko //加载驱动模块

    如果驱动加载成功的话如图70.1.4.5所示:

    7bbb240db861d90e9226e9d3678e54d1.png

    图70.1.4.5 RTL8189驱动加载成功

    从70.1.4.5可以看出,RTL8189 SDIO WIFI驱动加载成功,同样使用“ifconfig -a”命令查看一下是否有wlanX(X=0…n)网卡存在,如果有的话就说明RTL8189 SDIO WIFI驱动工作正常。

    不管是RTL8188 USB WIFI还是RTL8189 SDIO WIFI,驱动测试都工作正常,但是我们得能联网啊,不能联网的话要他有什么用呢?WIFI要想联网,需要移植一些其他第三方组件,否则无法连接路由器,接下来我们就移植这些第三方组件。

    70.2 wireless tools工具移植与测试

    70.2.1 wireless tools移植

    wirelesstools是操作WIFI的工具集合,包括一下工具:

    ①、iwconfig:设置无线网络相关参数。

    ②、iwlist:扫描当前无线网络信息,获取WIFI热点。

    ③、iwspy:获取每个节点链接的质量。

    ④、iwpriv:操作WirelessExtensions 特定驱动。

    ⑤、ifrename:基于各种静态标准命名接口。

    我们最常用的就是iwlist和iwconfig这两个工具,首先获取到相应的源码包,这里我们已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》iwlist_for_visteon-master.tar.bz2。将iwlist_for_visteon-master.tar.bz2拷贝到Ubuntu中前面创建的tool目录下,拷贝完成以后将其解压,生成iwlist_for_visteon-master文件夹。进入到iwlist_for_visteon-master文件夹里面,打开Makefile文件,修改Makefile中的CC、AR和RANLIB这三个变量,修改后的值如图70.2.1.1所示:

    49582800ecab6172691a069e968682f5.png

    图70.2.1.1修改后的CC、AR和RANLIB值

    图70.2.1.1中CC、AR和RANLIB这三个变量为所使用的编译器工具,将其改为我们所使用的arm-linux-gnueabihf-xxx工具即可。修改完成以后就可以使用如下命令编译:

    makeclean //先清理一下工程

    make //编译

    编译完成以后就会在当前目录下生成iwlist、iwconfig、iwspy、iwpriv、ifrename这5个工具,另外还有很重要的libiw.so.29这个库文件。将这5个工具拷贝到开发板根文件系统下的/usr/bin目录中,将libiw.so.29这个库文件拷贝到开发板根文件系统下的/usr/lib目录中,命令如下:

    sudo cp iwlist iwconfig iwspy iwpriv ifrename /home/zuozhongkai/linux/nfs/rootfs/usr/bin/ -f

    sudo cp libiw.so.29 /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -f

    拷贝完成以后可以测试iwlist是否工作正常。

    70.2.2 wirelesstools工具测试

    这里我们主要测试一下iwlist工具,要测试iwlist工具,先测试一下iwlist工具能不能工作,输入iwlist命令,如果输出图70.2.2.1所示信息就表明iwlist工具工作正常。

    7979d5f4776c795ce886db28276b4a18.png

    图70.2.2.1 iwlist工具

    正式测试iwlist之前得先让WIFI模块工作起来。RTL8188或RTL8189都可以,以RTL8188 USB WIFI为例,先将RTL8188 WIFI模块插到开发板的USB HOST接口上,然后加载RTL8188驱动模块8188eu.ko,驱动加载成功以后在打开wlan0网卡,命令如下:

    modprobe 8188eu.ko //加载RTL8188驱动模块

    ifconfig wlan0 up //打开wlan0网卡

    wlan0网卡打开以后就可以使用iwlist命令查找当前环境下的WIFI热点信息,也就是无线路由器,输入如下命令:

    iwlist wlan0 scan

    上述命令就会搜索当前环境下的所有WIFI热点,然后将这些热点的信息信息答应出来,包括MAC地址、ESSID(WIFI名字)、频率、速率,信号质量等等,如图70.2.2.2所示:

    d657b4302848afce827fca1d891c0379.png

    图70.2.2.2 扫描到的WIFI热点信息

    在扫描到的所有热点信息中找到自己要连接的WIFI热点,比如我要连接到“ZZK”这个热点上,这个WIFI热点信息如图70.2.2.3所示:

    d269410bf568d498594e0a3138995404.png

    图70.2.2.3 ZZK热点信息

    可以看出,“ZZK”这个热点信息已经被扫描到了,因此可以连接。要想连接到指定的WIFI热点上就需要用到wpa_supplicant工具,所以接下来就是移植此工具。

    70.3 wpa_supplicant移植

    70.3.1 libopenssl移植

    wpa_supplicant依赖于libopenssl,因此需要先移植libopenssl,libopenssl源码已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》openssl-1.1.1-stable-SNAP-20190915.tar.gz。将openssl源码压缩包拷贝到Ubuntu中前面创建的tool目录下,然后使用如下命令将其解压:

    tar -vxzf openssl-1.1.1-stable-SNAP-20190915.tar.gz

    解压完成以后就会生成一个名为openssl-1.1.1-stable-SNAP-20190915的目录,然后在新建一个名为“libopenssl”的文件夹,用于存放libopenssl的编译结果。进入到解压出来的openssl-1.1.1-stable-SNAP-20190915目录中,然后执行如下命令进行配置:

    ./config shared no-asm --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl

    配置成功以后会生成Makefile,打开Makefile,找到所有包含“-m64”的内容,一共两处分别为变量CNF_CFLAGS和CNF_CXXFLAGS,将这两个变量中的“-m64”删除掉,删除以后如图70.3.1.1所示:

    2f6601373cb7c9591a9d28a2ea103a30.png

    图70.3.1.1 删除掉“-m64”

    Makefile修改好以后使用如下命令编译并安装libopenssl:

    make CROSS_COMPILE=arm-linux-gnueabihf- -j12

    makeinstall

    编译安装完成以后的libopenssl目录内容如图70.3.1.2所示:

    cfba0ae4e170cc7993fcc4750caae34e.png

    图70.3.1.2 编译并安装成功的libopenssl目录

    将图70.3.1.2中的lib目录是我们需要的,将lib目录下的所有文件拷贝到开发板根文件系统中的/usr/lib目录下,命令如下:

    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rf

    70.3.2 libnl库移植

    wpa_supplicant也依赖于libnl,因此还需要移植一下libnl库,libnl源码已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》libnl-3.2.23.tar.gz。将libnl源码压缩包拷贝到Ubuntu中前面创建的tool目录下,然后使用如下命令将其解压:

    tar -vxzf libnl-3.2.23.tar.gz

    得到解压完成以后会得到libnl-3.2.23文件夹,然后在新建一个名为“libnl”的文件夹,用于存放libnl的编译结果。进入到libnl-3.2.23文件夹中,然后执行如下命令进行配置:

    ./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/libnl/

    --host用于指定交叉编译器的前缀,这里设置为“arm-linux-gnueabihf”,--prefix用于指定编译结果存放目录,这里肯定要设置为我们刚刚创建的libnl文件夹。配置完成以后就可以执行如下命令对libnl库进行编译、安装:

    make-j12 //编译

    makeinstall //安装

    编译安装完成以后的libnl目录如图70.3.2.1所示:

    f57bc773f90a244d18b4d532a40086d1.png

    图70.3.2.1 编译安装完成后的libnl目录

    我们需要图70.3.2.1中lib目录下的libnl库文件,将lib目录下的所有文件拷贝到开发板根文件系统的/usr/lib目录下,命令如下所示:

    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rf

    70.3.3 wpa_supplicant移植

    接下来移植wpa_supplicant,wpa_supplicant源码我们已经放到了开发板光盘中,路径为:1、例程源码->7、第三方库源码->wpa_supplicant-2.7.tar.gz,将wpa_supplicant-2.7.tar.gz拷贝到Ubuntu中,输入如下命令进行解压:

    tar -vxzf wpa_supplicant-2.7.tar.gz

    解压完成以后会得到wpa_supplicant-2.7文件夹,进入到此文件夹中,wpa_supplicant-2.7目录内容如图70.3.3.1所示:

    2e94dd8c5b1637f25358accd85f38912.png

    图70.3.3.1 wpa_supplicant-2.7目录

    进入到图70.3.3.1中的wpa_supplicant目录下,然后进行配置,wpa_supplicant的配置比较特殊,需要将wpa_supplicant下的defconfig文件拷贝一份并重命名为.config,命令如下:

    cd wpa_supplicant/

    cp defconfig .config

    完成以后打开.config文件,在里面指定交叉编译器、openssl、libnl库和头文件路径,设置如下:

    示例代码70.3.3.1 .config文件需要添加的内容

    1 CC = arm-linux-gnueabihf-gcc

    2

    3 #openssl库和头文件路径

    4 CFLAGS +=-I/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl/include

    5LIBS+=-L/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl/lib-lssl

    -lcrypto

    6

    7#libnl库和头文件路径

    8 CFLAGS +=-I/home/zuozhongkai/linux/IMX6ULL/tool/libnl/include/libnl3

    9 LIBS +=-L/home/zuozhongkai/linux/IMX6ULL/tool/libnl/lib

    CC变量用于指定交叉编译器,这里就是arm-linux-gnueabihf-gcc,CFLAGS指定需要使用的库头文件路径,LIBS指定需要用到的库路径。编译wap_supplicant的时候需要用到openssl和libnl库,所以示例代码70.3.3.1中指定了这两个的库路径和头文件路径。上述内容在.config中的位置见图70.3.3.2:

    9358a5479ac65da6f58c23fd606868ff.png

    图70.3.3.2 添加到.config中的内容

    .config文件配置好以后就可以编译wpa_supplicant了,使用如下命令编译:

    export PKG_CONFIG_PATH=/home/zuozhongkai/linux/IMX6ULL/tool/libnl/lib/pkgconfig:

    $PKG_CONFIG_PATH //指定libnl库pkgconfig包位置

    make -j12 //编译

    首先我们使用export指定了libnl库的pkgconfig路径,环境变量PKG_CONFIG_PATH保存着pkgconfig包路径。在tool/libnl/lib/下有个名为“pkgconfig”的目录,如图70.3.3.3所示:

    c94e1300c35af7360cbbe3e738861313.png

    图70.3.3.3 libnl的pkgconfig目录

    编译wpa_supplicant的时候是需要指定libnl的pkgconfig路径,否则会提示“libnl-3.0”或者“libnl-3.0.pc”找不到等错误。编译完成以后就会在本目录下生成wpa_supplicant和wpa_cli这两个软件,如图70.3.3.3所示:

    4086cf56af087feca48ed654d77dac46.png

    图70.3.3.3 编译出来的wpa_cli和wpa_supplicant文件

    将图70.3.3.3中的wpa_cli和wpa_supplicant这两个文件拷贝到开发板根文件系统的/usr/bin目录中,命令如下:

    sudo cp wpa_cli wpa_supplicant /home/zuozhongkai/linux/nfs/rootfs/usr/bin/ -f

    拷贝完成以后重启开发板!输入“wpa_supplicant -v”命令查看一下wpa_supplicant版本号,如果wpa_supplicant工作正常的话就会打印出版本号,如图70.3.3.4所示:

    b5c67052be1299c0d1710906e90e69ef.png

    图70.3.3.4 wpa_supplicant版本号

    从图70.3.3.4可以看出, wpa_supplicant的版本号输出正常,说明wpa_supplicant移植成功,接下来就是使用wpa_supplicant将开发板的WIFI链接到路由器上,实现WIFI上网功能。

    70.4 WIFI联网测试

    不管是USB WIFI还是SDIO WIFI,联网的操作步骤如下所示:

    ①、插上WIFI模块,如果是板子集成的就不需要这一步。如果是SDIO WIFI的话确保WIFI所使用的SDIO接口没有插其他的模块,比如SD卡,防止其他模块对SDIO WIFI造成影响。

    ②、加载RTL8188或者RTL8189驱动模块。

    ③、使用ifconfig命令打开对应的无线网卡,比如wlan0或wlan1……

    ④、无线网卡打开以后使用iwlist命令扫描一下当前环境下的WIFI热点,一来测试一下WIFI工作是否正常。二来检查一下自己要连接的WIFI热点能不能扫描到,扫描不到的话肯定就没法连接了。

    当上述步骤确认无误以后就可以使用wpa_supplicant来将WIFI连接到指定的热点上,实现联网功能。

    70.4.1 RTL8188 USB WIFI联网测试

    首先测试一下RTL8188 USB WIFI联网测试,确保RTL8188能扫描出要连接的WIFI热点,比如我要连接“ZZK”这个WIFI,iwlist扫描到的此WIFI热点信息如图70.4.1.1所示:

    163b78e5f219566843fb4fbedc3cd42e.png

    图70.4.1.1 ZZK WIFI热点

    要连接的WIFI热点扫描到以后就可以连接了,先在开发板根文件系统的/etc目录下创建一个名为“wpa_supplicant.conf”的配置文件,此文件用于配置要连接的WIFI热点以及WIFI秘密,比如我要连接到“ZZK”这个热点上,因此wpa_supplicant.conf文件内容如下所示:

    示例代码70.4.1.1 wpa_supplicant.conf文件内容

    1 ctrl_interface=/var/run/wpa_supplicant

    2 ap_scan=1

    3 network={

    4 ssid="ZZK"

    5 psk="xxxxxxxx"

    6}

    第4行,ssid是要连接的WIFI热点名字,这里我要连接的是“ZZK”这个WIFI热点。

    第5行,psk就是要连接的WIFI热点密码,根据自己的实际情况填写即可。

    注意,wpa_supplicant.conf文件对于格式要求比较严格,“=”前后一定不能有空格,也不要用TAB键来缩进,比如第4行和5行的缩进应该采用空格,否则的话会出现wpa_supplicant.conf文件解析错误!最重要的一点!wpa_supplicant.conf文件内容要自己手动输入,不要偷懒复制粘贴!!!

    wpa_supplicant.conf文件编写好以后再在开发板根文件系统下创建一个“/var/run/wpa_supplicant”目录,wpa_supplicant工具要用到此目录!命令如下:

    mkdir /var/run/wpa_supplicant -p

    一切准备好以后就可以使用wpa_supplicant工具让RTL8188 USB WIFI连接到热点上,输入如下命令:

    wpa_supplicant -D wext -c /etc/wpa_supplicant.conf -i wlan0 &

    当RTL8188连接上WIFI热点以后会输出如图70.4.1.2所示的信息:

    5b9257152ef2abe28f623b730e058d85.png

    图70.4.1.2 连接成功

    从图70.4.1.2可以看出,当RTL8188连接到WIFI热点上以后会输出“wlan0: CTRL-EVENT-CONNECTED”字样。接下来就是最后一步了,设置wlan0的IP地址,这里使用udhcpc命令从路由器申请IP地址,输入如下命令:

    udhcpc -i wlan0 //从路由器获取IP地址

    IP地址获取成功以后会输出如图70.4.2.2所示信息:

    22d358fb649daafbea4a0ed614a4e02f.png

    图70.4.2.2 wlan0网卡WIFI地址获取成功

    从图70.4.2.2可以看出,wlan0的IP地址获取成功,IP地址为192.168.1.126。可以输入如下命令查看一下wlan0网卡的详细信息:

    ifconfigwlan0

    结果如图70.4.2.3所示:

    ee7d48f7d20336cd90af00c85c5a8ee4.png

    图70.4.2.3 wlan0网卡详细信息

    可以通过电脑ping一下wlan0的192.168.1.126这个IP地址,如果能ping通就说明RTL8188 USB WIFI工作正常。也可以直接在开发板上使用wlan0来ping一下百度网站,输入如下命令:

    ping -I 192.168.1.126 www.baidu.com

    -I是指定执行ping操作的网卡IP地址,我们要使用wlan0去ping百度网站,因此要通过“-I”指定wlan0的IP地址。如果WIFI工作正常的话就可以ping通百度网站,如图70.4.2.4所示:

    f2eb2f134e1532531f0d07e5bc820b13.png

    图70.4.2.4 百度网站ping成功

    至此RTL8188 USB WIFI我们就完全驱动起来了,大家就可以使用WIFI来进行网络通信了。

    70.4.2 RTL8189 SDIO WIFI联网测试

    RLT8189 SDIO WIFI的测试和RTL8188 USB WIFI的测试方法基本一致,如果插了SD卡的话先将SD卡从I.MX6U-ALPHA开发板上拔出,因为I.MX6U-ALPHA开发板的SD卡和SDIO WIFI公用一个SDIO接口。插入RTL8189 SDIO WIFI模块,然后加载RTL8189驱动,并且打开对应的wlan0(如果只有RTL8189一个WIFI的话)网卡,使用iwlist命令搜索要连接的WIFI热点是否存在,如果存在的话就可以连接了。

    RTL8189 SDIO WIFI同样使用wpa_supplicant来完成热点连接工作,因此同样需要创建/etc/wpa_supplicant.conf文件,具体过程参考70.4.1小节。一切准备就绪以后输入如下命令来完成WIFI热点连接:

    wpa_supplicant -Dnl80211 -c /etc/wpa_supplicant.conf -i wlan0 &

    注意红色字体,使用RTL8189的话应该使用“-Dnl80211”,这里不要填错了!WIFI热点连接成功以后会输出如图70.4.2.1所示信息:

    495115741600871dca49cb3cc69ff54d.png

    图70.4.2.1 RTL8189 SDIO WIFI连接成功

    使用udhcpc命令获取IP地址,命令如下:

    udhcpc -i wlan0

    IP地址获取过程如图70.4.2.2所示:

    4fe57054d0f11168ddef96f7fc56924d.png

    图70.4.2.2 udhcpc获取IP地址过程

    从图70.4.2.2可以看出,wlan0的IP地址为192.168.1.118,大家可以使用“ifconfigwlan0”查看一下wlan0网卡的详细信息。可以通过电脑ping一下192.168.1.118测试WIFI是否工作正常,或者在开发板上使用wlan0网卡ping一下百度网址来测试一下WIFI工作是否正常,输入如下命令:

    ping -I 192.168.1.118 www.baidu.com

    如果ping成功的话结果如图70.4.2.3所示:

    2491e522cc52c50415b9bd67266895b4.png

    图70.4.2.3 ping百度网站测试成功

    至此,如何在I.MX6U-ALPHA开发板上使用WIFI就全部讲解完了,包括USB WIFI和SDIO WIFI。其实不管是在I.MX6U上,还是在其他的SOC上,USB WIFI和SDIO WIFI的驱动都是类似的,大家可以参考本章教程讲RTL8188、RTL8189这两款WIFI的驱动移植到芯片或者开发板上。

    展开全文
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子前面章节中我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的...

    1)实验平台:正点原子Linux开发板

    2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南

    关注官方微信号公众号,获取更多资料:正点原子

    2e9d081afeb973b70757ab062b25d913.png

    前面章节中我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的讲解什么是“设备树”,本章我们就来详细的谈一谈设备树。掌握设备树是Linux驱动开发人员必备的技能!因为在新版本的Linux中,ARM相关的驱动全部采用了设备树(也有支持老式驱动的,比较少),最新出的CPU其驱动开发也基本都是基于设备树的,比如ST新出的STM32MP157、NXP的I.MX8系列等。我们所使用的Linux版本为4.1.15,其支持设备树,所以正点原子I.MX6U-ALPHA开发板的所有Linux驱动都是基于设备树的。本章我们就来了解一下设备树的起源、重点学习一下设备树语法。

    43.1 什么是设备树?

    设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU数量、内存基地址、IIC接口上接了哪些设备、SPI接口上接了哪些设备等等,如图43.1.1所示:

    7cb6b1bea33006aeee12e2572f0bece2.png

    图43.1.1 设备树结构示意图

    在图43.1.1中,树的主干就是系统总线,IIC控制器、GPIO控制器、SPI控制器等都是接到系统主线上的分支。IIC控制器有分为IIC1和IIC2两种,其中IIC1上接了FT5206和AT24C02这两个IIC设备,IIC2上只接了MPU6050这个设备。DTS文件的主要功能就是按照图43.1.1所示的结构来描述板子上的设备信息,DTS文件描述设备信息是有相应的语法规则要求的,稍后我们会详细的讲解DTS语法规则。

    在3.x版本(具体哪个版本笔者也无从考证)以前的Linux内核中ARM架构并没有采用设备树。在没有设备树的时候Linux是如何描述ARM架构中的板级信息呢?在Linux内核源码中大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,这些文件夹里面的文件就是对应平台下的板级信息。比如在arch/arm/mach-smdk2440.c中有如下内容(有缩减):

    示例代码43.1.1 mach-smdk2440.c文件代码段

    90staticstruct s3c2410fb_display smdk2440_lcd_cfg __initdata ={

    91

    92.lcdcon5 = S3C2410_LCDCON5_FRM565 |

    93 S3C2410_LCDCON5_INVVLINE |

    94 S3C2410_LCDCON5_INVVFRAME |

    95 S3C2410_LCDCON5_PWREN |

    96 S3C2410_LCDCON5_HWSWP,

    ......

    113};

    114

    115staticstruct s3c2410fb_mach_info smdk2440_fb_info __initdata ={

    116.displays =&smdk2440_lcd_cfg,

    117.num_displays =1,

    118.default_display =0,

    ......

    133};

    134

    135staticstruct platform_device *smdk2440_devices[] __initdata ={

    136 &s3c_device_ohci,

    137&s3c_device_lcd,

    138&s3c_device_wdt,

    139&s3c_device_i2c0,

    140&s3c_device_iis,

    141};

    上述代码中的结构体变量smdk2440_fb_info就是描述SMDK2440这个开发板上的LCD信息的,结构体指针数组smdk2440_devices描述的SMDK2440这个开发板上的所有平台相关信息。这个仅仅是使用2440这个芯片的SMDK2440开发板下的LCD信息,SMDK2440开发板还有很多的其他外设硬件和平台硬件信息。使用2440这个芯片的板子有很多,每个板子都有描述相应板级信息的文件,这仅仅只是一个2440。随着智能手机的发展,每年新出的ARM架构芯片少说都在数十、数百款,Linux内核下板级信息文件将会成指数级增长!这些板级信息文件都是.c或.h文件,都会被硬编码进Linux内核中,导致Linux内核“虚胖”。就好比你喜欢吃自助餐,然后花了100多到一家宣传看着很不错的自助餐厅,结果你想吃的牛排、海鲜、烤肉基本没多少,全都是一些凉菜、炒面、西瓜、饮料等小吃,相信你此时肯定会脱口而出一句“F*k!”、“骗子!”。同样的,当Linux之父linus看到ARM社区向Linux内核添加了大量“无用”、冗余的板级信息文件,不禁的发出了一句“This whole ARM thing is a f*cking pain in the ass”。从此以后ARM社区就引入了PowerPC等架构已经采用的设备树(Flattened Device Tree),将这些描述板级硬件信息的内容都从Linux内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。一个SOC可以作出很多不同的板子,这些不同的板子肯定是有共同的信息,将这些共同的信息提取出来作为一个通用的文件,其他的.dts文件直接引用这个通用文件即可,这个通用文件就是.dtsi文件,类似于C语言中的头文件。一般.dts描述板级信息(也就是开发板上有哪些IIC设备、SPI设备等),.dtsi描述SOC级信息(也就是SOC有几个CPU、主频是多少、各个外设控制器信息等)。

    这个就是设备树的由来,简而言之就是,Linux内核中ARM架构下有太多的冗余的垃圾板级信息文件,导致linus震怒,然后ARM社区引入了设备树。

    43.2 DTS、DTB和DTC

    上一小节说了,设备树源文件扩展名为.dts,但是我们在前面移植Linux的时候却一直在使用.dtb文件,那么DTS和DTB这两个文件是什么关系呢?DTS是设备树源码文件,DTB是将DTS编译以后得到的二进制文件。将.c文件编译为.o需要用到gcc编译器,那么将.dts编译为.dtb需要什么工具呢?需要用到DTC工具!DTC工具源码在Linux内核的scripts/dtc目录下,scripts/dtc/Makefile文件内容如下:

    示例代码43.2.1 scripts/dtc/Makefile文件代码段

    1 hostprogs-y := dtc

    2 always :=$(hostprogs-y)

    3

    4 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o

    5 srcpos.o checks.o util.o

    6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o

    ......

    可以看出,DTC工具依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出DTC这个主机文件。如果要编译DTS文件的话只需要进入到Linux源码根目录下,然后执行如下命令:

    makeall

    或者:

    makedtbs

    “makeall”命令是编译Linux源码中的所有东西,包括zImage,.ko驱动模块以及设备树,如果只是编译设备树的话建议使用“makedtbs”命令。

    基于ARM架构的SOC有很多种,一种SOC又可以制作出很多款板子,每个板子都有一个对应的DTS文件,那么如何确定编译哪一个DTS文件呢?我们就以I.MX6ULL这款芯片对应的板子为例来看一下,打开arch/arm/boot/dts/Makefile,有如下内容:

    示例代码43.2.2 arch/arm/boot/dts/Makefile文件代码段

    381 dtb-$(CONFIG_SOC_IMX6UL) +=

    382 imx6ul-14x14-ddr3-arm2.dtb

    383 imx6ul-14x14-ddr3-arm2-emmc.dtb

    ......

    400 dtb-$(CONFIG_SOC_IMX6ULL) +=

    401 imx6ull-14x14-ddr3-arm2.dtb

    402 imx6ull-14x14-ddr3-arm2-adc.dtb

    403 imx6ull-14x14-ddr3-arm2-cs42888.dtb

    404 imx6ull-14x14-ddr3-arm2-ecspi.dtb

    405 imx6ull-14x14-ddr3-arm2-emmc.dtb

    406 imx6ull-14x14-ddr3-arm2-epdc.dtb

    407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb

    408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb

    409 imx6ull-14x14-ddr3-arm2-lcdif.dtb

    410 imx6ull-14x14-ddr3-arm2-ldo.dtb

    411 imx6ull-14x14-ddr3-arm2-qspi.dtb

    412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb

    413 imx6ull-14x14-ddr3-arm2-tsc.dtb

    414 imx6ull-14x14-ddr3-arm2-uart2.dtb

    415 imx6ull-14x14-ddr3-arm2-usb.dtb

    416 imx6ull-14x14-ddr3-arm2-wm8958.dtb

    417 imx6ull-14x14-evk.dtb

    418 imx6ull-14x14-evk-btwifi.dtb

    419 imx6ull-14x14-evk-emmc.dtb

    420 imx6ull-14x14-evk-gpmi-weim.dtb

    421 imx6ull-14x14-evk-usb-certi.dtb

    422 imx6ull-alientek-emmc.dtb

    423 imx6ull-alientek-nand.dtb

    424 imx6ull-9x9-evk.dtb

    425 imx6ull-9x9-evk-btwifi.dtb

    426 imx6ull-9x9-evk-ldo.dtb

    427 dtb-$(CONFIG_SOC_IMX6SLL) +=

    428 imx6sll-lpddr2-arm2.dtb

    429 imx6sll-lpddr3-arm2.dtb

    ......

    可以看出,当选中I.MX6ULL这个SOC以后(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL这个SOC的板子对应的.dts文件都会被编译为.dtb。如果我们使用I.MX6ULL新做了一个板子,只需要新建一个此板子对应的.dts文件,然后将对应的.dtb文件名添加到dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts编译为二进制的.dtb文件。

    示例代码43.2.2中第422和423行就是我们在给正点原子的I.MX6U-ALPHA开发板移植Linux系统的时候添加的设备树。关于.dtb文件怎么使用这里就不多说了,前面讲解Uboot移植、Linux内核移植的时候已经无数次的提到如何使用.dtb文件了(uboot中使用bootz或bootm命令向Linux内核传递二进制设备树文件(.dtb))。

    43.3 DTS语法

    虽然我们基本上不会从头到尾重写一个.dts文件,大多时候是直接在SOC厂商提供的.dts文件上进行修改。但是DTS文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts文件。大家不要看到要学习新的语法就觉得会很复杂,DTS语法非常的人性化,是一种ASCII文本文件,不管是阅读还是修改都很方便。

    本节我们就以imx6ull-alientek-emmc.dts这个文件为例来讲解一下DTS语法。关于设备树详细的语法规则请参考《Devicetree SpecificationV0.2.pdf》和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档,此两份文档已经放到了开发板光盘中,路径为:4、参考资料->Devicetree SpecificationV0.2.pdf、4、参考资料->Power_ePAPR_APPROVED_v1.12.pdf

    43.3.1 .dtsi头文件

    和C语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在imx6ull-alientek-emmc.dts中有如下所示内容:

    示例代码43.3.1.1 imx6ull-alientek-emmc.dts文件代码段

    12 #include <dt-bindings/input/input.h>

    13 #include "imx6ull.dtsi"

    第12行,使用“#include”来引用“input.h”这个.h头文件。

    第13行,使用“#include”来引用“imx6ull.dtsi”这个.dtsi头文件。

    看到这里,大家可能会疑惑,不是说设备树的扩展名是.dtsi吗?为什么也可以直接引用C语言中的.h头文件呢?这里并没有错,.dts文件引用C语言中的.h文件,甚至也可以引用.dts文件,打开imx6ull-14x14-evk-gpmi-weim.dts这个文件,此文件中有如下内容:

    示例代码43.3.1.2 imx6ull-14x14-evk-gpmi-weim.dts文件代码段

    9 #include "imx6ull-14x14-evk.dts"

    可以看出,示例代码43.3.1.2中直接引用了.dts文件,因此在.dts设备树文件中,可以通过“#include”来引用.h、.dtsi和.dts文件。只是,我们在编写设备树头文件的时候最好选择.dtsi后缀。

    一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,比如UART、IIC等等。比如imx6ull.dtsi就是描述I.MX6ULL这颗SOC内部外设情况信息的,内容如下:

    示例代码43.3.1.3 imx6ull.dtsi文件代码段

    10 #include <dt-bindings/clock/imx6ul-clock.h>

    11 #include <dt-bindings/gpio/gpio.h>

    12 #include <dt-bindings/interrupt-controller/arm-gic.h>

    13 #include "imx6ull-pinfunc.h"

    14 #include "imx6ull-pinfunc-snvs.h"

    15 #include "skeleton.dtsi"

    16

    17/{

    18 aliases {

    19 can0 =&flexcan1;

    ......

    48};

    49

    50 cpus {

    51 #address-cells =<1>;

    52 #size-cells =<0>;

    53

    54 cpu0: cpu@0 {

    55 compatible ="arm,cortex-a7";

    56 device_type ="cpu";

    ......

    89};

    90};

    91

    92 intc: interrupt-controller@00a01000 {

    93 compatible ="arm,cortex-a7-gic";

    94 #interrupt-cells =<3>;

    95 interrupt-controller;

    96 reg =<0x00a010000x1000>,

    97<0x00a020000x100>;

    98};

    99

    100 clocks {

    101 #address-cells =<1>;

    102 #size-cells =<0>;

    103

    104 ckil: clock@0 {

    105 compatible ="fixed-clock";

    106 reg =<0>;

    107 #clock-cells =<0>;

    108 clock-frequency =<32768>;

    109 clock-output-names ="ckil";

    110};

    ......

    135};

    136

    137 soc {

    138 #address-cells =<1>;

    139 #size-cells =<1>;

    140 compatible ="simple-bus";

    141 interrupt-parent =gpc>;

    142 ranges;

    143

    144 busfreq {

    145 compatible ="fsl,imx_busfreq";

    ......

    162};

    197

    198 gpmi: gpmi-nand@01806000{

    199 compatible ="fsl,imx6ull-gpmi-nand","fsl, imx6ul-gpmi-nand";

    200 #address-cells =<1>;

    201 #size-cells =<1>;

    202 reg =<0x018060000x2000>,<0x018080000x4000>;

    ......

    216};

    ......

    1177};

    1178};

    示例代码43.3.1.3中第54~89行就是cpu0这个设备节点信息,这个节点信息描述了I.MX6ULL这颗SOC所使用的CPU信息,比如架构是cortex-A7,频率支持996MHz、792MHz、528MHz、396MHz和198MHz等等。在imx6ull.dtsi文件中不仅仅描述了cpu0这一个节点信息,I.MX6ULL这颗SOC所有的外设都描述的清清楚楚,比如ecspi1~4、uart1~8、usbphy1~2、i2c1~4等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

    43.3.2设备节点

    设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从imx6ull.dtsi文件中缩减出来的设备树文件内容:

    示例代码43.3.2.1 设备树模板

    1/{

    2 aliases {

    3 can0 =&flexcan1;

    4 };

    5

    6 cpus {

    7 #address-cells =<1>;

    8 #size-cells =<0>;

    9

    10 cpu0: cpu@0 {

    11 compatible ="arm,cortex-a7";

    12 device_type ="cpu";

    13 reg =<0>;

    14 };

    15 };

    16

    17 intc: interrupt-controller@00a01000 {

    18 compatible ="arm,cortex-a7-gic";

    19 #interrupt-cells =<3>;

    20 interrupt-controller;

    21 reg =<0x00a010000x1000>,

    22 <0x00a020000x100>;

    23 };

    24}

    第1行,“/”是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现,imx6ull.dtsi和imx6ull-alientek-emmc.dts这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为这两个“/”根节点的内容会合并成一个根节点。

    第2、6和17行,aliases、cpus和intc是三个子节点,在设备树中节点命名格式如下:

    node-name@unit-address

    其中“node-name”是节点名字,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。

    但是我们在示例代码43.3.2.1中我们看到的节点命名却如下所示:

    cpu0:cpu@0

    上述命令并不是“node-name@unit-address”这样的格式,而是用“:”隔开成了两部分,“:”前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示:

    label: node-name@unit-address

    引入label的目的就是为了方便访问节点,可以直接通过&label来访问这个节点,比如通过&cpu0就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点“intc: interrupt-controller@00a01000”,节点label是intc,而节点名字就很长了,为“interrupt-controller@00a01000”。很明显通过&intc来访问“interrupt-controller@00a01000”这个节点要方便很多!

    第10行,cpu0也是一个节点,只是cpu0是cpus的子节点。

    每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:

    ①、字符串

    compatible = "arm,cortex-a7";

    上述代码设置compatible属性的值为字符串“arm,cortex-a7”。

    ②、32位无符号整数

    reg = <0>;

    上述代码设置reg属性的值为0,reg的值也可以设置为一组值,比如:

    reg = <0 0x123456 100>;

    ③、字符串列表

    属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:

    compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

    上述代码设置属性compatible的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

    43.3.3 标准属性

    节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。

    1、compatible属性

    compatible属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible属性的值是一个字符串列表,compatible属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible属性的值格式如下所示:

    "manufacturer,model"

    其中manufacturer表示厂商,model一般是模块对应的驱动名字。比如imx6ull-alientek-emmc.dts中sound节点是I.MX6U-ALPHA开发板的音频设备节点,I.MX6U-ALPHA开发板上的音频芯片采用的欧胜(WOLFSON)出品的WM8960,sound节点的compatible属性值如下:

    compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

    属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。sound这个设备首先使用第一个兼容值在Linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个Linux内核也没有找到对应的驱动。

    一般驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在文件imx-wm8960.c中有如下内容:

    示例代码43.3.3.1 imx-wm8960.c文件代码段

    632staticconststruct of_device_id imx_wm8960_dt_ids[]={

    633{.compatible ="fsl,imx-audio-wm8960",},

    634{/* sentinel */}

    635};

    636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

    637

    638staticstruct platform_driver imx_wm8960_driver ={

    639.driver ={

    640.name ="imx-wm8960",

    641.pm =&snd_soc_pm_ops,

    642.of_match_table = imx_wm8960_dt_ids,

    643},

    644.probe = imx_wm8960_probe,

    645.remove = imx_wm8960_remove,

    646};

    第632~635行的数组imx_wm8960_dt_ids就是imx-wm8960.c这个驱动文件的匹配表,此匹配表只有一个匹配值“fsl,imx-audio-wm8960”。如果在设备树中有哪个节点的compatible属性值与此相等,那么这个节点就会使用此驱动文件。

    第642行,wm8960采用了platform_driver驱动模式,关于platform_driver驱动后面会讲解。此行设置.of_match_table为imx_wm8960_dt_ids,也就是设置这个platform_driver所使用的OF匹配表。

    2、model属性

    model属性值也是一个字符串,一般model属性描述设备模块信息,比如名字什么的,比如:

    model = "wm8960-audio";

    3、status属性

    status属性看名字就知道是和设备状态有关的,status属性值也是字符串,字符串是设备的状态信息,可选的状态如表43.3.3.1所示:

    70c2dc9a5c973102c8b378dd30db95a5.png

    表43.3.3.1 status属性值表

    4、#address-cells和#size-cells属性

    这两个属性的值都是无符号32位整形,#address-cells和#size-cells这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定了子节点reg属性中地址信息所占用的字长(32位),#size-cells属性值决定了子节点reg属性中长度信息所占的字长(32位)。#address-cells和#size-cells表明了子节点应该如何编写reg属性值,一般reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg属性的格式一为:

    reg =

    每个“addresslength”组合表示一个地址范围,其中address是起始地址,length是地址长度,#address-cells表明address这个数据所占用的字长,#size-cells表明length这个数据所占用的字长,比如:

    示例代码43.3.3.2 #address-cells和#size-cells属性

    1 spi4 {

    2 compatible ="spi-gpio";

    3 #address-cells =<1>;

    4 #size-cells =<0>;

    5

    6 gpio_spi: gpio_spi@0 {

    7 compatible ="fairchild,74hc595";

    8 reg =<0>;

    9 };

    10};

    11

    12 aips3: aips-bus@02200000 {

    13 compatible ="fsl,aips-bus","simple-bus";

    14 #address-cells =<1>;

    15 #size-cells =<1>;

    16

    17 dcp: dcp@02280000 {

    18 compatible ="fsl,imx6sl-dcp";

    19 reg =<0x022800000x4000>;

    20 };

    21};

    第2,3行,节点spi4的#address-cells = <1>,#size-cells = <0>,说明spi4的子节点reg属性中起始地址所占用的字长为1,地址长度所占用的字长为0。

    第8行,子节点gpio_spi: gpio_spi@0的reg 属性值为<0>,因为父节点设置了#address-cells = <1>,#size-cells = <0>,因此addres=0,没有length的值,相当于设置了起始地址,而没有设置地址长度。

    第14,15行,设置aips3: aips-bus@02200000节点#address-cells = <1>,#size-cells = <1>,说明aips3: aips-bus@02200000节点起始地址长度所占用的字长为1,地址长度所占用的字长也为1。

    第19行,子节点dcp: dcp@02280000的reg属性值为<0x02280000 0x4000>,因为父节点设置了#address-cells = <1>,#size-cells = <1>,address= 0x02280000,length= 0x4000,相当于设置了起始地址为0x02280000,地址长度为0x40000。

    5、reg属性

    reg属性前面已经提到过了,reg属性的值一般是(address,length)对。reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,比如在imx6ull.dtsi中有如下内容:

    示例代码43.3.3.3 uart1节点信息

    323 uart1: serial@02020000 {

    324 compatible ="fsl,imx6ul-uart",

    325"fsl,imx6q-uart","fsl,imx21-uart";

    326 reg =<0x020200000x4000>;

    327 interrupts =<GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;

    328 clocks =clks IMX6UL_CLK_UART1_IPG>,

    329clks IMX6UL_CLK_UART1_SERIAL>;

    330 clock-names ="ipg","per";

    331 status ="disabled";

    332};

    上述代码是节点uart1,uart1节点描述了I.MX6ULL的UART1相关信息,重点是第326行的reg属性。其中uart1的父节点aips1: aips-bus@02000000设置了#address-cells = <1>、#size-cells = <1>,因此reg属性中address=0x02020000,length=0x4000。查阅《I.MX6ULL参考手册》可知,I.MX6ULL的UART1寄存器首地址为0x02020000,但是UART1的地址长度(范围)并没有0x4000这么多,这里我们重点是获取UART1寄存器首地址。

    6、ranges属性

    ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges是一个地址映射/转换表,ranges属性每个项目由子地址、父地址和地址空间长度这三部分组成:

    child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占用的字长。

    parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells确定此物理地址所占用的字长。

    length:子地址空间的长度,由父节点的#size-cells确定此地址长度所占用的字长。

    如果ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的I.MX6ULL来说,子地址空间和父地址空间完全相同,因此会在imx6ull.dtsi中找到大量的值为空的ranges属性,如下所示:

    示例代码43.3.3.4 imx6ull.dtsi文件代码段

    137 soc {

    138 #address-cells =<1>;

    139 #size-cells =<1>;

    140 compatible ="simple-bus";

    141 interrupt-parent =gpc>;

    142 ranges;

    ......

    1177}

    第142行定义了ranges属性,但是ranges属性值为空。

    ranges属性不为空的示例代码如下所示:

    示例代码43.3.3.5 ranges属性不为空

    1 soc {

    2 compatible ="simple-bus";

    3 #address-cells =<1>;

    4 #size-cells =<1>;

    5 ranges =<0x00xe00000000x00100000>;

    6

    7 serial {

    8 device_type ="serial";

    9 compatible ="ns16550";

    10 reg =<0x46000x100>;

    11 clock-frequency =<0>;

    12 interrupts =<0xA0x8>;

    13 interrupt-parent =ipic>;

    14 };

    15};

    第5行,节点soc定义的ranges属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为0x0,父地址空间的物理起始地址为0xe0000000。

    第6行,serial是串口设备节点,reg属性定义了serial设备寄存器的起始地址为0x4600,寄存器长度为0x100。经过地址转换,serial设备可以从0xe0004600开始进行读写操作,0xe0004600=0x4600+0xe0000000。

    7、name属性

    name属性值为字符串,name属性用于记录节点名字,name属性已经被弃用,不推荐使用name属性,一些老的设备树文件可能会使用此属性。

    8、device_type属性

    device_type属性值为字符串,IEEE 1275会用到此属性,用于描述设备的FCode,但是设备树没有FCode,所以此属性也被抛弃了。此属性只能用于cpu节点或者memory节点。imx6ull.dtsi的cpu0节点用到了此属性,内容如下所示:

    示例代码43.3.3.6 imx6ull.dtsi文件代码段

    54 cpu0: cpu@0 {

    55 compatible ="arm,cortex-a7";

    56 device_type ="cpu";

    57 reg =<0>;

    ......

    89};

    关于标准属性就讲解这么多,其他的比如中断、IIC、SPI等使用的标准属性等到具体的例程在讲解。

    43.3.4 根节点compatible属性

    每个节点都有compatible属性,根节点“/”也不例外,imx6ull-alientek-emmc.dts文件中根节点的compatible属性内容如下所示:

    示例代码43.3.4.1 imx6ull-alientek-emmc.dts根节点compatible属性

    14/{

    15 model ="Freescale i.MX6 ULL 14x14 EVK Board";

    16 compatible ="fsl,imx6ull-14x14-evk","fsl,imx6ull";

    ......

    148}

    可以看出,compatible有两个值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我们说了,设备节点的compatible属性值是为了匹配Linux内核中的驱动程序,那么根节点中的compatible属性是为了做什么工作的?通过根节点的compatible属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是“imx6ull”这颗SOC。Linux内核会通过根节点的compoatible属性查看是否支持此设备,如果支持的话设备就会启动Linux内核。接下来我们就来学习一下Linux内核在使用设备树前后是如何判断是否支持某款设备的。

    1、使用设备树之前设备匹配方法

    在没有使用设备树以前,uboot会向Linux内核传递一个叫做machine id的值,machine id也就是设备ID,告诉Linux内核自己是个什么设备,看看Linux内核是否支持。Linux内核是支持很多设备的,针对每一个设备(板子),Linux内核都用MACHINE_START和MACHINE_END来定义一个machine_desc结构体来描述这个设备,比如在文件arch/arm/mach-imx/mach-mx35_3ds.c中有如下定义:

    示例代码43.3.4.2 MX35_3DS设备

    613 MACHINE_START(MX35_3DS,"Freescale MX35PDK")

    614/* Maintainer: Freescale Semiconductor, Inc */

    615.atag_offset =0x100,

    616.map_io = mx35_map_io,

    617.init_early = imx35_init_early,

    618.init_irq = mx35_init_irq,

    619.init_time = mx35pdk_timer_init,

    620.init_machine = mx35_3ds_init,

    621.reserve = mx35_3ds_reserve,

    622.restart = mxc_restart,

    623 MACHINE_END

    上述代码就是定义了“Freescale MX35PDK”这个设备,其中MACHINE_START和MACHINE_END定义在文件arch/arm/include/asm/mach/arch.h中,内容如下:

    示例代码43.3.4.3 MACHINE_START和MACHINE_END宏定义

    #define MACHINE_START(_type,_name)

    static const struct machine_desc __mach_desc_##_type

    __used

    __attribute__((__section__(".arch.info.init"))) = {

    .nr = MACH_TYPE_##_type,

    .name = _name,

    #define MACHINE_END

    };

    根据MACHINE_START和MACHINE_END的宏定义,将示例代码43.3.4.2展开后如下所示:

    示例代码43.3.4.3 展开以后

    1staticconststruct machine_desc __mach_desc_MX35_3DS

    2 __used

    3 __attribute__((__section__(".arch.info.init")))={

    4 .nr = MACH_TYPE_MX35_3DS,

    5 .name ="Freescale MX35PDK",

    6 /* Maintainer: Freescale Semiconductor, Inc */

    7 .atag_offset =0x100,

    8 .map_io = mx35_map_io,

    9 .init_early = imx35_init_early,

    10 .init_irq = mx35_init_irq,

    11 .init_time = mx35pdk_timer_init,

    12 .init_machine = mx35_3ds_init,

    13 .reserve = mx35_3ds_reserve,

    14 .restart = mxc_restart,

    15}

    从示例代码43.3.4.3中可以看出,这里定义了一个machine_desc类型的结构体变量__mach_desc_MX35_3DS,这个变量存储在“.arch.info.init”段中。第4行的MACH_TYPE_MX35_3DS就是“Freescale MX35PDK”这个板子的machineid。MACH_TYPE_MX35_3DS定义在文件include/generated/mach-types.h中,此文件定义了大量的machineid,内容如下所示:

    示例代码43.3.4.3 mach-types.h文件中的machine id

    15 #define MACH_TYPE_EBSA110 0

    16 #define MACH_TYPE_RISCPC 1

    17 #define MACH_TYPE_EBSA285 4

    18 #define MACH_TYPE_NETWINDER 5

    19 #define MACH_TYPE_CATS 6

    20 #define MACH_TYPE_SHARK 15

    21 #define MACH_TYPE_BRUTUS 16

    22 #define MACH_TYPE_PERSONAL_SERVER 17

    ......

    287 #define MACH_TYPE_MX35_3DS 1645

    ......

    1000 #define MACH_TYPE_PFLA03 4575

    第287行就是MACH_TYPE_MX35_3DS的值,为1645。

    前面说了,uboot会给Linux内核传递machineid这个参数,Linux内核会检查这个machineid,其实就是将machineid与示例代码43.3.4.3中的这些MACH_TYPE_XXX宏进行对比,看看有没有相等的,如果相等的话就表示Linux内核支持这个设备,如果不支持的话那么这个设备就没法启动Linux内核。

    2、使用设备树以后的设备匹配方法

    当Linux内核引入设备树以后就不再使用MACHINE_START了,而是换为了DT_MACHINE_START。DT_MACHINE_START也定义在文件arch/arm/include/asm/mach/arch.h里面,定义如下:

    示例代码43.3.4.4 DT_MACHINE_START宏

    #define DT_MACHINE_START(_name, _namestr)

    static const struct machine_desc __mach_desc_##_name

    __used

    __attribute__((__section__(".arch.info.init"))) = {

    .nr = ~0,

    .name = _namestr,

    可以看出,DT_MACHINE_START和MACHINE_START基本相同,只是.nr的设置不同,在DT_MACHINE_START里面直接将.nr设置为~0。说明引入设备树以后不会再根据machineid来检查Linux内核是否支持某个设备了。

    打开文件arch/arm/mach-imx/mach-imx6ul.c,有如下所示内容:

    示例代码43.3.4.5 imx6ull设备

    208staticconstchar*imx6ul_dt_compat[] __initconst ={

    209"fsl,imx6ul",

    210"fsl,imx6ull",

    211NULL,

    212};

    213

    214 DT_MACHINE_START(IMX6UL,"Freescale i.MX6 Ultralite (Device Tree)")

    215.map_io = imx6ul_map_io,

    216.init_irq = imx6ul_init_irq,

    217.init_machine = imx6ul_init_machine,

    218.init_late = imx6ul_init_late,

    219.dt_compat = imx6ul_dt_compat,

    220 MACHINE_END

    machine_desc结构体中有个.dt_compat成员变量,此成员变量保存着本设备兼容属性,示例代码43.3.4.5中设置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat表里面有"fsl,imx6ul"和"fsl,imx6ull"这两个兼容值。只要某个设备(板子)根节点“/”的compatible属性值与imx6ul_dt_compat表中的任何一个值相等,那么就表示Linux内核支持此设备。imx6ull-alientek-emmc.dts中根节点的compatible属性值如下:

    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    其中“fsl,imx6ull”与imx6ul_dt_compat中的“fsl,imx6ull”匹配,因此I.MX6U-ALPHA开发板可以正常启动Linux内核。如果将imx6ull-alientek-emmc.dts根节点的compatible属性改为其他的值,比如:

    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll"

    重新编译DTS,并用新的DTS启动Linux内核,结果如图43.3.4.1所示的错误提示:

    daeeb8c2dc459fa817b7927510f163b6.png

    图43.3.4.1 系统启动信息

    当我们修改了根节点compatible属性内容以后,因为Linux内核找不到对应的设备,因此Linux内核无法启动。在uboot输出Startingkernel…以后就再也没有其他信息输出了。

    接下来我们简单看一下Linux内核是如何根据设备树根节点的compatible属性来匹配出对应的machine_desc,Linux内核调用start_kernel函数来启动内核,start_kernel函数会调用setup_arch函数来匹配machine_desc,setup_arch函数定义在文件arch/arm/kernel/setup.c中,函数内容如下(有缩减):

    示例代码43.3.4.6 setup_arch函数内容

    913void __init setup_arch(char**cmdline_p)

    914{

    915conststruct machine_desc *mdesc;

    916

    917 setup_processor();

    918 mdesc = setup_machine_fdt(__atags_pointer);

    919if(!mdesc)

    920 mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

    921 machine_desc = mdesc;

    922 machine_name = mdesc->name;

    ......

    986}

    第918行,调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首地址,也就是uboot传递给Linux内核的dtb文件首地址,setup_machine_fdt函数的返回值就是找到的最匹配的machine_desc。

    函数setup_machine_fdt定义在文件arch/arm/kernel/devtree.c中,内容如下(有缩减):

    示例代码43.3.4.7 setup_machine_fdt函数内容

    204conststruct machine_desc * __init setup_machine_fdt(unsignedint dt_phys)

    205{

    206conststruct machine_desc *mdesc,*mdesc_best =NULL;

    ......

    214

    215if(!dt_phys ||!early_init_dt_verify(phys_to_virt(dt_phys)))

    216returnNULL;

    217

    218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

    219

    ......

    247 __machine_arch_type = mdesc->nr;

    248

    249return mdesc;

    250}

    第218行,调用函数of_flat_dt_match_machine来获取匹配的machine_desc,参数mdesc_best是默认的machine_desc,参数arch_get_next_mach是个函数,此函数定义在定义在arch/arm/kernel/devtree.c文件中。找到匹配的machine_desc的过程就是用设备树根节点的compatible属性值和Linux内核中保存的所以machine_desc结构的. dt_compat中的值比较,看看那个相等,如果相等的话就表示找到匹配的machine_desc,arch_get_next_mach函数的工作就是获取Linux内核中下一个machine_desc结构体。

    最后在来看一下of_flat_dt_match_machine函数,此函数定义在文件drivers/of/fdt.c中,内容如下(有缩减):

    示例代码43.3.4.8 of_flat_dt_match_machine函数内容

    705constvoid* __init of_flat_dt_match_machine(constvoid*default_match,

    706constvoid*(*get_next_compat)(constchar*const**))

    707{

    708constvoid*data =NULL;

    709constvoid*best_data = default_match;

    710constchar*const*compat;

    711unsignedlong dt_root;

    712unsignedint best_score =~1, score =0;

    713

    714 dt_root = of_get_flat_dt_root();

    715while((data = get_next_compat(&compat))){

    716 score = of_flat_dt_match(dt_root, compat);

    717if(score >0&& score < best_score){

    718 best_data = data;

    719 best_score = score;

    720}

    721}

    ......

    739

    740 pr_info("Machine model: %s", of_flat_dt_get_machine_name());

    741

    742return best_data;

    743}

    第714行,通过函数of_get_flat_dt_root获取设备树根节点。

    第715~720行,此循环就是查找匹配的machine_desc过程,第716行的of_flat_dt_match函数会将根节点compatible属性的值和每个machine_desc结构体中. dt_compat的值进行比较,直至找到匹配的那个machine_desc。

    总结一下,Linux内核通过根节点compatible属性找到对应的设备的函数调用过程,如图43.3.4.2所示:

    6000b0d180f1333349250a4c676cce81.png

    图43.3.4.2 查找匹配设备的过程

    展开全文
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第七十章Linux WIFI驱动实验WIFI的使用已经很常见了,手机、平板、汽车等等,虽然...

    1)实验平台:正点原子Linux开发板

    2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南关注官方微信号公众号,获取更多资料:正点原子

    580a75c3b7e844cf8a3ce690c904e207.png

    第七十章Linux WIFI驱动实验

    WIFI的使用已经很常见了,手机、平板、汽车等等,虽然可以使用有线网络,但是有时候很多设备存在布线困难的情况,此时WIFI就是一个不错的选择。正点原子I.MX6U-ALPHA开发板支持USB和SDIO这两种接口的WIFI,本章我们就来学习一下如何在I.MX6U-ALPHA开发板上使用USB和SDIO这两种WIFI。

    70.1 WIFI驱动添加与编译

    正点原子的I.MX6U-ALPHA开发板目前支持两种接口的WIFI:USB和SDIO,其中USB WIFI使用使用的芯片为RTL8188EUS,SDIO接口的WIFI使用芯片为RTL8189FS,也叫做RTL8189FTV。这两个都是realtek公司出品的WIFI芯片。WIFI驱动不需要我们编写,因为realtek公司提供了WIFI驱动源码,因此我们只需要将WIFI驱动源码添加到Linux内核中,然后通过图形化界面配置,选择将其编译成模块即可。

    正点原子I.MX6U-ALPHA开发板默认会赠送一个RTL8188 USB WIFI,RTL8188 USB WIFI如图70.1.1所示:

    752287d452151b0725c078978a981f1d.png

    图70.1.1 RTL8188 USB WIFI

    另外,正点原子还有一款采用RTL8189FTV芯片的SDIO WIFI,如图70.1.2所示:

    dad8d7bfe21592a6d660b52ed8ff753c.png

    图70.1.2 RTL8188 SDIO WIFI

    70.1.1 向Linux内核添加WIFI驱动

    1、rtl81xx驱动文件浏览

    WIFI驱动源码已经放到了开发板光盘中,路径为:1、例程源码->5、模块驱动源码->1、RTL8XXX WIFI驱动源码-> realtek。realtek目录下就存放着RTL8188EUS和RTL8189FS这两个芯片的驱动源码,如图70.1.1.1所示:

    22b3d101374edb56e6c2392631705dac.png

    图70.1.1.1 rtl8xxx WIFI驱动

    其中rtl8188EUS下存放着RTL8188EUS驱动,RTL8189FS存放着RTL8189FS/FTV的驱动文件。Kconfig文件是WIFI驱动的配置界面文档,这样可以通过Linux内核图形化配置界面来选择是否编译WIFI驱动,Kconfig文件内容如下所示:

    示例代码70.1.1.1 Kconfig文件内容

    1 menuconfig REALTEK_WIFI

    2 tristate "Realtek wifi"

    3

    4if REALTEK_WIFI

    5

    6 choice

    7 prompt "select wifi type"

    8 default RTL9189FS

    9

    10 config RTL9189FS

    11 depends on REALTEK_WIFI

    12 tristate "rtl8189fs/ftv sdio wifi"

    13

    14 config RTL8188EUS

    15 depends on REALTEK_WIFI

    16 tristate "rtl8188eus usb wifi"

    17

    18 endchoice

    19 endif

    Makefile文件内容如下所示

    示例代码70.1.1.2 Makefile文件内容

    1 obj-$(CONFIG_RTL8188EUS)+= rtl8188EUS/

    2 obj-$(CONFIG_RTL8189FS)+= rtl8189FS/

    2、将rtl81xx驱动添加到Linux内核中

    将realtek整个目录拷贝到ubuntu下Linux内核源码中的drivers/net/wireless目录下,此目录下存放着所有WIFI驱动文件。拷贝完成以后此目录如图70.1.1.1所示:

    4ce918e99896eb510734895ae8a3e468.png

    图70.1.1.1 拷贝完成的wireless目录

    图70.1.1.1中框选出来的就是我们刚刚拷贝进来的realtek目录。

    3、修改drivers/net/wireless/Kconfig

    打开drivers/net/wireless/Kconfig,在里面加入下面这一行内容:

    source "drivers/net/wireless/realtek/Kconfig"

    添加完以后的Kconfig文件内容如下所示:

    示例代码70.1.1.3 drivers/net/wireless/Kconfig文件内容

    1 #

    2 # Wireless LAN device configuration

    3 #

    4

    5 menuconfig WLAN

    ......

    286 source "drivers/net/wireless/rsi/Kconfig"

    287 source "drivers/net/wireless/realtek/Kconfig"

    286

    289 endif # WLAN

    第287行就是添加到drivers/net/wireless/Kconfig中的内容,这样WIFI驱动的配置界面才会出现在Linux内核配置界面上。

    3、修改drivers/net/wireless/Makefile

    打开drivers/net/wireless/Makefile,在里面加入下面一行内容:

    obj-y += realtek/

    修改完以后的Makefile文件内容如下所示:

    示例代码70.1.1.4 drivers/net/wireless/Makefile文件内容

    1 #

    2 # Makefile for the Linux Wireless network device drivers.

    3 #

    4

    5 obj-$(CONFIG_IPW2100)+= ipw2x00/

    ......

    62 obj-$(CONFIG_CW1200)+= cw1200/

    63 obj-$(CONFIG_RSI_91X)+= rsi/

    64

    65 obj-y += realtek/

    第65行,编译realtek中的内容,至此,Linux内核要修改的内容就全部完成了。

    70.1.2 配置Linux内核

    在编译RTL8188和RTL8189驱动之前需要先配置Linux内核。

    1、配置USB支持设备

    配置路径如下:

    -> Device Drivers

    ->USB support

    ->Support for Host-side USB

    -> EHCI HCD (USB 2.0) support

    -> OHCI HCD (USB 1.1) support

    -> ChipIdea Highspeed Dual Role Controller

    -> [*] ChipIdea device controller

    -> [*] ChipIdea host controller

    2、配置支持WIFI设备

    配置路径如下:

    -> Device Drivers

    ->[*] Network device support

    ->[*] Wireless LAN

    -> IEEE 802.11 for Host AP (Prism2/2.5/3 and WEP/TKIP/CCMP)

    -> [*] Support downloading firmware images with Host AP driver

    -> [*] Support for non-volatile firmware download

    配置完如图70.1.2.1所示:

    a86307939e26db5d64111b2c728c1ab5.png

    图70.1.2.1 配置支持WIFI设备

    3、配置支持IEEE 802.11

    配置路径如下:

    -> Networking support

    -> -*- Wireless

    ->[*] cfg80211 wireless extensions compatibility

    -> Generic IEEE 802.11 Networking Stack (mac80211)

    配置完如图70.1.2.2所示:

    68235f0faa1d43d9c41e5cbadeb7e34d.png

    图70.1.2.2 IEE 802.11配置项

    配置好以后重新编译一下Linux内核,得到新的zImage,后面使用新编译出来的zImage启动系统。

    70.1.3 编译WIFI驱动

    执行"makemenuconfig"命令,打开Linux内核配置界面,然后按照如下路径选择将rtl81xx驱动编译为模块:

    -> Device Drivers

    -> Network device support (NETDEVICES [=y])

    -> Wireless LAN (WLAN [=y])

    -> Realtek wifi (REALTEK_WIFI [=m])

    -> rtl8189ftv sdio wifi

    -> rtl8188eus usb wifi

    配置结果如图70.1.3.1所示:

    8ab644c286f530270455efbad5b4283e.png

    图70.1.3.1 WIFI配置界面

    图70.1.3.1中的配置界面就是我们添加进去的WIFI配置界面,选中"rtl8189fs/ftv sdio wifi"和"rtl8188eus usb wifi",将其编译为模块。执行如下命令编译模块:

    makemodules -j12 //编译驱动模块

    编译完成以后就会在rtl8188EUS和rtl8189FS文件夹下分别生成8188eu.ko和8189fs.ko这两个.ko文件,结果如图70.1.3.2所示:

    1a6e0854d03c04f5c65cb01136696654.png

    图70.1.3.2 编译结果

    图70.1.3.2中的8188eu.ko和8189fs.ko就是我们需要的RTL8188EUS和RTL8189FS的驱动模块文件,将这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,命令如下:

    sudo cp 8189fs.ko /home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -rf

    sudo cp 8188eu.ko /home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -rf

    因为我们重新配置过Linux内核,因此也需要使用新的zImage启动,将新编译出来的zImage镜像文件拷贝到Ubuntu中的tftpboot目录下,命令如下:

    cp arch/arm/boot/zImage /home/zuozhongkai/linux/tftpboot/ -f

    然后重启开发板!!!

    70.1.4 驱动加载测试

    1、RTL8188 USB WIFI驱动测试

    重启以后我们试着加载一下8188eu.ko和8189fs.ko这两个驱动文件,首先测试一下RTL8188的驱动文件,将RTL8188 WIFI模块插到开发板的USB HOST接口上。进入到目录lib/modules/4.1.15中,输入如下命令加载8188eu.ko这个驱动模块:

    depmod //第一次加载驱动的时候需要运行此命令

    modprobe 8188eu.ko //加载驱动模块

    如果驱动加载成功的话如图70.1.4.1所示:

    0f20c2c5fd21583f1b0471e1d8638b35.png

    图70.1.4.1 RTL8188驱动加载成功

    输入"ifconfig-a"命令,查看wlanX(X=0….n)网卡是否存在,一般都是wlan0,除非板子上有多个WIFI模块在工作,结果如图70.1.4.2所示:

    730f8c908a38da19532fefe936a8860e.png

    图70.1.4.2 当前开发板所有网卡

    从图70.1.4.2中可以看出,当前开发板有一个叫做"wlan0"的网卡,这个就是RTL8188对应的网卡。

    2、RTL8189 SDIO WIFI驱动测试

    测试完RTL8188以后,再来测试一下RTL8189这个SDIO WIFI,因为I.MX6U-ALPHA开发板的SDIO WIFI接口与SD卡公用一个SDIO接口。因此SD卡和SDIO WIFI只能二选其一,一次只能一个工作,所以测试RTL8189 SDIO WIFI的时候需要拔插SD卡。SDIO WIFI接口原理图如图70.1.4.3所示:

    5f8791452647efe0e42f038f14ca0b63.png

    图70.1.4.3 SDIO WIFI接口

    测试开始之前要先将SD卡拔出,然后将RTL8189 SDIO WIFI模块插入到SDIO WIFI座子上,如图70.1.4.4所示:

    0d8b089cdc336535ae50f84d670b27d1.png

    图70.1.4.4 SDIO WIFI连接图

    SDIO WIFI与开发板连接好以后就可以测试了,输入如下命令加载8189fs.ko这个驱动模块:

    depmod //第一次加载驱动的时候需要运行此命令

    modprobe 8189eu.ko //加载驱动模块

    如果驱动加载成功的话如图70.1.4.5所示:

    3aa02f89088ad4d79900997be25534a7.png

    图70.1.4.5 RTL8189驱动加载成功

    从70.1.4.5可以看出,RTL8189 SDIO WIFI驱动加载成功,同样使用"ifconfig -a"命令查看一下是否有wlanX(X=0…n)网卡存在,如果有的话就说明RTL8189 SDIO WIFI驱动工作正常。

    不管是RTL8188 USB WIFI还是RTL8189 SDIO WIFI,驱动测试都工作正常,但是我们得能联网啊,不能联网的话要他有什么用呢?WIFI要想联网,需要移植一些其他第三方组件,否则无法连接路由器,接下来我们就移植这些第三方组件。

    70.2 wireless tools工具移植与测试

    70.2.1 wireless tools移植

    wirelesstools是操作WIFI的工具集合,包括一下工具:

    ①、iwconfig:设置无线网络相关参数。

    ②、iwlist:扫描当前无线网络信息,获取WIFI热点。

    ③、iwspy:获取每个节点链接的质量。

    ④、iwpriv:操作WirelessExtensions 特定驱动。

    ⑤、ifrename:基于各种静态标准命名接口。

    我们最常用的就是iwlist和iwconfig这两个工具,首先获取到相应的源码包,这里我们已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》iwlist_for_visteon-master.tar.bz2。将iwlist_for_visteon-master.tar.bz2拷贝到Ubuntu中前面创建的tool目录下,拷贝完成以后将其解压,生成iwlist_for_visteon-master文件夹。进入到iwlist_for_visteon-master文件夹里面,打开Makefile文件,修改Makefile中的CC、AR和RANLIB这三个变量,修改后的值如图70.2.1.1所示:

    c4e1f8ad1e4885a2e4c6d57b703b4463.png

    图70.2.1.1修改后的CC、AR和RANLIB值

    图70.2.1.1中CC、AR和RANLIB这三个变量为所使用的编译器工具,将其改为我们所使用的arm-linux-gnueabihf-xxx工具即可。修改完成以后就可以使用如下命令编译:

    makeclean //先清理一下工程

    make //编译

    编译完成以后就会在当前目录下生成iwlist、iwconfig、iwspy、iwpriv、ifrename这5个工具,另外还有很重要的libiw.so.29这个库文件。将这5个工具拷贝到开发板根文件系统下的/usr/bin目录中,将libiw.so.29这个库文件拷贝到开发板根文件系统下的/usr/lib目录中,命令如下:

    sudo cp iwlist iwconfig iwspy iwpriv ifrename /home/zuozhongkai/linux/nfs/rootfs/usr/bin/ -f

    sudo cp libiw.so.29 /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -f

    拷贝完成以后可以测试iwlist是否工作正常。

    70.2.2 wirelesstools工具测试

    这里我们主要测试一下iwlist工具,要测试iwlist工具,先测试一下iwlist工具能不能工作,输入iwlist命令,如果输出图70.2.2.1所示信息就表明iwlist工具工作正常。

    310054e50e21cbaf609da2b9707b1010.png

    图70.2.2.1 iwlist工具

    正式测试iwlist之前得先让WIFI模块工作起来。RTL8188或RTL8189都可以,以RTL8188 USB WIFI为例,先将RTL8188 WIFI模块插到开发板的USB HOST接口上,然后加载RTL8188驱动模块8188eu.ko,驱动加载成功以后在打开wlan0网卡,命令如下:

    modprobe 8188eu.ko //加载RTL8188驱动模块

    ifconfig wlan0 up //打开wlan0网卡

    wlan0网卡打开以后就可以使用iwlist命令查找当前环境下的WIFI热点信息,也就是无线路由器,输入如下命令:

    iwlist wlan0 scan

    上述命令就会搜索当前环境下的所有WIFI热点,然后将这些热点的信息信息答应出来,包括MAC地址、ESSID(WIFI名字)、频率、速率,信号质量等等,如图70.2.2.2所示:

    34ae88e10c60d969bff2756a44e5ebb6.png

    图70.2.2.2 扫描到的WIFI热点信息

    在扫描到的所有热点信息中找到自己要连接的WIFI热点,比如我要连接到"ZZK"这个热点上,这个WIFI热点信息如图70.2.2.3所示:

    f5c2561eb26b97e24c75bf2186cdf6bb.png

    图70.2.2.3 ZZK热点信息

    可以看出,"ZZK"这个热点信息已经被扫描到了,因此可以连接。要想连接到指定的WIFI热点上就需要用到wpa_supplicant工具,所以接下来就是移植此工具。

    70.3 wpa_supplicant移植

    70.3.1 libopenssl移植

    wpa_supplicant依赖于libopenssl,因此需要先移植libopenssl,libopenssl源码已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》openssl-1.1.1-stable-SNAP-20190915.tar.gz。将openssl源码压缩包拷贝到Ubuntu中前面创建的tool目录下,然后使用如下命令将其解压:

    tar -vxzf openssl-1.1.1-stable-SNAP-20190915.tar.gz

    解压完成以后就会生成一个名为openssl-1.1.1-stable-SNAP-20190915的目录,然后在新建一个名为"libopenssl"的文件夹,用于存放libopenssl的编译结果。进入到解压出来的openssl-1.1.1-stable-SNAP-20190915目录中,然后执行如下命令进行配置:

    ./config shared no-asm --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl

    配置成功以后会生成Makefile,打开Makefile,找到所有包含"-m64"的内容,一共两处分别为变量CNF_CFLAGS和CNF_CXXFLAGS,将这两个变量中的"-m64"删除掉,删除以后如图70.3.1.1所示:

    190badc24db9e57d53d318b0a8b3c0cb.png

    图70.3.1.1 删除掉"-m64"

    Makefile修改好以后使用如下命令编译并安装libopenssl:

    make CROSS_COMPILE=arm-linux-gnueabihf- -j12

    makeinstall

    编译安装完成以后的libopenssl目录内容如图70.3.1.2所示:

    33b414304d0acf53ddffb87e06cfbea5.png

    图70.3.1.2 编译并安装成功的libopenssl目录

    将图70.3.1.2中的lib目录是我们需要的,将lib目录下的所有文件拷贝到开发板根文件系统中的/usr/lib目录下,命令如下:

    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rf

    70.3.2 libnl库移植

    wpa_supplicant也依赖于libnl,因此还需要移植一下libnl库,libnl源码已经放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》libnl-3.2.23.tar.gz。将libnl源码压缩包拷贝到Ubuntu中前面创建的tool目录下,然后使用如下命令将其解压:

    tar -vxzf libnl-3.2.23.tar.gz

    得到解压完成以后会得到libnl-3.2.23文件夹,然后在新建一个名为"libnl"的文件夹,用于存放libnl的编译结果。进入到libnl-3.2.23文件夹中,然后执行如下命令进行配置:

    ./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/libnl/

    --host用于指定交叉编译器的前缀,这里设置为"arm-linux-gnueabihf",--prefix用于指定编译结果存放目录,这里肯定要设置为我们刚刚创建的libnl文件夹。配置完成以后就可以执行如下命令对libnl库进行编译、安装:

    make-j12 //编译

    makeinstall //安装

    编译安装完成以后的libnl目录如图70.3.2.1所示:

    2ee1614757f9c7966283e29d75715723.png

    图70.3.2.1 编译安装完成后的libnl目录

    我们需要图70.3.2.1中lib目录下的libnl库文件,将lib目录下的所有文件拷贝到开发板根文件系统的/usr/lib目录下,命令如下所示:

    sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rf

    70.3.3 wpa_supplicant移植

    接下来移植wpa_supplicant,wpa_supplicant源码我们已经放到了开发板光盘中,路径为:1、例程源码->7、第三方库源码->wpa_supplicant-2.7.tar.gz,将wpa_supplicant-2.7.tar.gz拷贝到Ubuntu中,输入如下命令进行解压:

    tar -vxzf wpa_supplicant-2.7.tar.gz

    解压完成以后会得到wpa_supplicant-2.7文件夹,进入到此文件夹中,wpa_supplicant-2.7目录内容如图70.3.3.1所示:

    b2a413f6d19836ef304d1f90cfab097c.png

    图70.3.3.1 wpa_supplicant-2.7目录

    进入到图70.3.3.1中的wpa_supplicant目录下,然后进行配置,wpa_supplicant的配置比较特殊,需要将wpa_supplicant下的defconfig文件拷贝一份并重命名为.config,命令如下:

    cd wpa_supplicant/

    cp defconfig .config

    完成以后打开.config文件,在里面指定交叉编译器、openssl、libnl库和头文件路径,设置如下:

    示例代码70.3.3.1 .config文件需要添加的内容

    1 CC = arm-linux-gnueabihf-gcc

    2

    3 #openssl库和头文件路径

    4 CFLAGS +=-I/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl/include

    5LIBS+=-L/home/zuozhongkai/linux/IMX6ULL/tool/libopenssl/lib-lssl

    -lcrypto

    6

    7#libnl库和头文件路径

    8 CFLAGS +=-I/home/zuozhongkai/linux/IMX6ULL/tool/libnl/include/libnl3

    9 LIBS +=-L/home/zuozhongkai/linux/IMX6ULL/tool/libnl/lib

    CC变量用于指定交叉编译器,这里就是arm-linux-gnueabihf-gcc,CFLAGS指定需要使用的库头文件路径,LIBS指定需要用到的库路径。编译wap_supplicant的时候需要用到openssl和libnl库,所以示例代码70.3.3.1中指定了这两个的库路径和头文件路径。上述内容在.config中的位置见图70.3.3.2:

    ceba2ebc379ae8a537c9fe8ed1d89f23.png

    图70.3.3.2 添加到.config中的内容

    .config文件配置好以后就可以编译wpa_supplicant了,使用如下命令编译:

    export PKG_CONFIG_PATH=/home/zuozhongkai/linux/IMX6ULL/tool/libnl/lib/pkgconfig:

    $PKG_CONFIG_PATH //指定libnl库pkgconfig包位置

    make -j12 //编译

    首先我们使用export指定了libnl库的pkgconfig路径,环境变量PKG_CONFIG_PATH保存着pkgconfig包路径。在tool/libnl/lib/下有个名为"pkgconfig"的目录,如图70.3.3.3所示:

    7b0be5818b1fc422186d18be6ed2d2c9.png

    图70.3.3.3 libnl的pkgconfig目录

    编译wpa_supplicant的时候是需要指定libnl的pkgconfig路径,否则会提示"libnl-3.0"或者"libnl-3.0.pc"找不到等错误。编译完成以后就会在本目录下生成wpa_supplicant和wpa_cli这两个软件,如图70.3.3.3所示:

    e516c7bc8b7bf0f8d2cdb6bb3bf96a07.png

    图70.3.3.3 编译出来的wpa_cli和wpa_supplicant文件

    将图70.3.3.3中的wpa_cli和wpa_supplicant这两个文件拷贝到开发板根文件系统的/usr/bin目录中,命令如下:

    sudo cp wpa_cli wpa_supplicant /home/zuozhongkai/linux/nfs/rootfs/usr/bin/ -f

    拷贝完成以后重启开发板!输入"wpa_supplicant -v"命令查看一下wpa_supplicant版本号,如果wpa_supplicant工作正常的话就会打印出版本号,如图70.3.3.4所示:

    1745bf8e3e17f885e7af222db588444f.png

    图70.3.3.4 wpa_supplicant版本号

    从图70.3.3.4可以看出, wpa_supplicant的版本号输出正常,说明wpa_supplicant移植成功,接下来就是使用wpa_supplicant将开发板的WIFI链接到路由器上,实现WIFI上网功能。

    70.4 WIFI联网测试

    不管是USB WIFI还是SDIO WIFI,联网的操作步骤如下所示:

    ①、插上WIFI模块,如果是板子集成的就不需要这一步。如果是SDIO WIFI的话确保WIFI所使用的SDIO接口没有插其他的模块,比如SD卡,防止其他模块对SDIO WIFI造成影响。

    ②、加载RTL8188或者RTL8189驱动模块。

    ③、使用ifconfig命令打开对应的无线网卡,比如wlan0或wlan1……

    ④、无线网卡打开以后使用iwlist命令扫描一下当前环境下的WIFI热点,一来测试一下WIFI工作是否正常。二来检查一下自己要连接的WIFI热点能不能扫描到,扫描不到的话肯定就没法连接了。

    当上述步骤确认无误以后就可以使用wpa_supplicant来将WIFI连接到指定的热点上,实现联网功能。

    70.4.1 RTL8188 USB WIFI联网测试

    首先测试一下RTL8188 USB WIFI联网测试,确保RTL8188能扫描出要连接的WIFI热点,比如我要连接"ZZK"这个WIFI,iwlist扫描到的此WIFI热点信息如图70.4.1.1所示:

    9622f5b0ef5ae495decf8652298bf260.png

    图70.4.1.1 ZZK WIFI热点

    要连接的WIFI热点扫描到以后就可以连接了,先在开发板根文件系统的/etc目录下创建一个名为"wpa_supplicant.conf"的配置文件,此文件用于配置要连接的WIFI热点以及WIFI秘密,比如我要连接到"ZZK"这个热点上,因此wpa_supplicant.conf文件内容如下所示:

    示例代码70.4.1.1 wpa_supplicant.conf文件内容

    1 ctrl_interface=/var/run/wpa_supplicant

    2 ap_scan=1

    3 network={

    4 ssid="ZZK"

    5 psk="xxxxxxxx"

    6}

    第4行,ssid是要连接的WIFI热点名字,这里我要连接的是"ZZK"这个WIFI热点。

    第5行,psk就是要连接的WIFI热点密码,根据自己的实际情况填写即可。

    注意,wpa_supplicant.conf文件对于格式要求比较严格,"="前后一定不能有空格,也不要用TAB键来缩进,比如第4行和5行的缩进应该采用空格,否则的话会出现wpa_supplicant.conf文件解析错误!最重要的一点!wpa_supplicant.conf文件内容要自己手动输入,不要偷懒复制粘贴!!!

    wpa_supplicant.conf文件编写好以后再在开发板根文件系统下创建一个"/var/run/wpa_supplicant"目录,wpa_supplicant工具要用到此目录!命令如下:

    mkdir /var/run/wpa_supplicant -p

    一切准备好以后就可以使用wpa_supplicant工具让RTL8188 USB WIFI连接到热点上,输入如下命令:

    wpa_supplicant -D wext -c /etc/wpa_supplicant.conf -i wlan0 &

    当RTL8188连接上WIFI热点以后会输出如图70.4.1.2所示的信息:

    3d7ba036f2f3a5d621254ff9b1bb47ea.png

    图70.4.1.2 连接成功

    从图70.4.1.2可以看出,当RTL8188连接到WIFI热点上以后会输出"wlan0: CTRL-EVENT-CONNECTED"字样。接下来就是最后一步了,设置wlan0的IP地址,这里使用udhcpc命令从路由器申请IP地址,输入如下命令:

    udhcpc -i wlan0 //从路由器获取IP地址

    IP地址获取成功以后会输出如图70.4.2.2所示信息:

    84321579cf4b7331926da55cc3180073.png

    图70.4.2.2 wlan0网卡WIFI地址获取成功

    从图70.4.2.2可以看出,wlan0的IP地址获取成功,IP地址为192.168.1.126。可以输入如下命令查看一下wlan0网卡的详细信息:

    ifconfigwlan0

    结果如图70.4.2.3所示:

    2c9682de76ecc01fe6bd9049e427c00d.png

    图70.4.2.3 wlan0网卡详细信息

    可以通过电脑ping一下wlan0的192.168.1.126这个IP地址,如果能ping通就说明RTL8188 USB WIFI工作正常。也可以直接在开发板上使用wlan0来ping一下百度网站,输入如下命令:

    ping -I 192.168.1.126

    -I是指定执行ping操作的网卡IP地址,我们要使用wlan0去ping百度网站,因此要通过"-I"指定wlan0的IP地址。如果WIFI工作正常的话就可以ping通百度网站,如图70.4.2.4所示:

    9accf43c15ec9ab239b233666abae180.png

    图70.4.2.4 百度网站ping成功

    至此RTL8188 USB WIFI我们就完全驱动起来了,大家就可以使用WIFI来进行网络通信了。

    70.4.2 RTL8189 SDIO WIFI联网测试

    RLT8189 SDIO WIFI的测试和RTL8188 USB WIFI的测试方法基本一致,如果插了SD卡的话先将SD卡从I.MX6U-ALPHA开发板上拔出,因为I.MX6U-ALPHA开发板的SD卡和SDIO WIFI公用一个SDIO接口。插入RTL8189 SDIO WIFI模块,然后加载RTL8189驱动,并且打开对应的wlan0(如果只有RTL8189一个WIFI的话)网卡,使用iwlist命令搜索要连接的WIFI热点是否存在,如果存在的话就可以连接了。

    RTL8189 SDIO WIFI同样使用wpa_supplicant来完成热点连接工作,因此同样需要创建/etc/wpa_supplicant.conf文件,具体过程参考70.4.1小节。一切准备就绪以后输入如下命令来完成WIFI热点连接:

    wpa_supplicant -Dnl80211 -c /etc/wpa_supplicant.conf -i wlan0 &

    注意红色字体,使用RTL8189的话应该使用"-Dnl80211",这里不要填错了!WIFI热点连接成功以后会输出如图70.4.2.1所示信息:

    9626e7daffe571340851b81e96d74e1f.png

    图70.4.2.1 RTL8189 SDIO WIFI连接成功

    使用udhcpc命令获取IP地址,命令如下:

    udhcpc -i wlan0

    IP地址获取过程如图70.4.2.2所示:

    65ed98e71cce977f13de13b9394a9c4b.png

    图70.4.2.2 udhcpc获取IP地址过程

    从图70.4.2.2可以看出,wlan0的IP地址为192.168.1.118,大家可以使用"ifconfigwlan0"查看一下wlan0网卡的详细信息。可以通过电脑ping一下192.168.1.118测试WIFI是否工作正常,或者在开发板上使用wlan0网卡ping一下百度网址来测试一下WIFI工作是否正常,输入如下命令:

    ping -I 192.168.1.118

    如果ping成功的话结果如图70.4.2.3所示:

    02a865ccdc95c4ac18a2952bccea8cdc.png

    图70.4.2.3 ping百度网站测试成功

    至此,如何在I.MX6U-ALPHA开发板上使用WIFI就全部讲解完了,包括USB WIFI和SDIO WIFI。其实不管是在I.MX6U上,还是在其他的SOC上,USB WIFI和SDIO WIFI的驱动都是类似的,大家可以参考本章教程讲RTL8188、RTL8189这两款WIFI的驱动移植到芯片或者开发板上。

    展开全文
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第六十二章Linux SPI驱动实验 上一章我们讲解了如何编写Linux下的I2C设备驱动,SPI也是...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第四十七章Linux并发与竞争 Linux是一个多任务操作系统,肯定会存在多个任务共同操作同...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第五十七章Linux MISC驱动实验misc的意思是混合、杂项的,因此MISC驱动也叫做杂项...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第六十三章Linux RS232/485/GPS驱动实验 串口是很常用的一个外设,在Linux下通常通过...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第十七章GPIO中断试验 中断系统是一个处理器重要的组成部分,中断系统极大的提高了CPU的...
  • 这不是开箱测评 ...受肺炎疫情的影响,历经千山万水的正点原子Linux开发板终于到了我的手上。当我拆开快递盒的那一刹那,我愣住了,我看到的是这样的盒子 发错快递了(心里犹如千万匹骏马爬过) ...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第四十六章Linux蜂鸣器实验 上一章实验中我们借助pinctrl和gpio子系统编写了LED灯驱动,...
  • 2020-06-18 正点原子linux学习笔记汇总软件安装方法一、下载源码编译tree安装1、下载源码http://mama.indstate.edu/users/ice/tree/2、解压3、在目录中sudo make4、安装 sudo make install二、apt-getgit安装sudo ...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第二十三章 DDR3实验I.MX6U-ALPHA开发板上带有一个256MB/512MB的DDR3内存芯片,一般...
  • 1)实验平台:正点原子Linux开发板 2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第五十六章Linux自带的LED灯驱动实验前面我们都是自己编写LED灯驱动,其实像LED灯...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第六十三章Linux RS232/485/GPS驱动实验 串口是很常用的一个外设,在Linux下通常通过...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第十二章官方SDK移植试验 在上一章中,我们参考ST官方给STM32编写的stm32f10x.h来自行...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子33.2.7 网络驱动修改1、I.MX6U-ALPHA开发板网络简介I.MX6UL/ULL内部有个以太网MAC...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子58.2 硬件原理图分析本章实验硬件原理图参考15.2小节即可。58.3实验程序编写本实验...
  • 正点原子Linux连载】第八章汇编LED灯试验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0`:---------:`第八章汇编LED灯试验8.1 I.MX6U GPIO详解8.1.2 I.MX6U IO命名 1)实验平台:正点原子阿尔法Linux...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子33.2.7 网络驱动修改1、I.MX6U-ALPHA开发板网络简介I.MX6UL/ULL内部有个以太网MAC...
  • 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 第十七章GPIO中断试验 中断系统是一个处理器重要的组成部分,中断系统极大的提高了CPU的执行效率,在学习
  • 第四十二章新字符设备驱动实验1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子经过前两章实验的实战操作,我们已经掌握了Linux字符...
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第七十章Linux WIFI驱动实验 WIFI的使用已经很常见了,手机、平板、汽车等等,虽然可以...
  • 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 第十五章按键输入试验 前面几章试验都是讲解如何使用I.MX6U的GPIO输出控制功能,I.MX6U的IO不仅
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第四十九章Linux按键输入实验 在前几章我们都是使用的GPIO输出功能,还没有用过GPIO输入...
  • 1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子第五十章Linux内核定时器实验定时器是我们最常用到的功能,一般用来完成定时功能,...
  • 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 第十章C语言版LED灯实验 第八章我们讲解了如何用汇编语言编写LED灯实验,但是实际开发过程中汇编用的很少
  • 1)实验平台:正点原子...3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 第十二章官方SDK移植试验 在上一章中,我们参考ST官方给STM32编写的stm32f10x.h来自行编写
  • 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 第十四章蜂鸣器试验 前几章试验中的驱动LED灯亮灭属于GPIO的输出控制,本章再巩固一下I.MX6U的GP
  • 1)资料下载:点击资料即可下载2)对正点原子Linux感兴趣的同学可以加群讨论:9354467413)关注正点原子公众号,获取最新资料更新第六十章Linux RTC驱动实验 RTC也就是实时时钟,用于记录当前系统时间,对于Linux系统而...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 823
精华内容 329
关键字:

正点原子linux

linux 订阅