imx6ull应用开发基础
1.shell脚本
- .s格式的文件,第一行必须是#!/bin/bash;
- echo打印命令;
- read命令:读取命令行的输入;
- &&和||:命令1 && 命令2,表示命令1执行并且正确后命令2执行;命令1 || 命令2,表示命令1执行并且错误后命令2执行;
- test命令:查看文件是否存在、两个字符串是否相等、两个值比大小等;
- 中括号[]判断符,整体输出真或假;
- 默认变量:0表示命令行第一个参数./∗∗.sh;1-n表示第一个到底n个参数;#表示参数的数量;$@表示所有的参数;
- 判断:{if [表达式]:then (执行语句) fi};或者{if [表达式]:then (执行语句) else (执行语句) fi};或者{if [表达式]:then (执行语句) elif [表达式]:then (执行语句) else (执行语句) fi};
- 判断:case:{case $val in "值a") (执行语句) ;; "值b") (执行语句) ;; '*') (执行语句) ;; esac}
- 函数 函数名(){函数体};
- 循环:{while [表达式] do (执行语句) done};{for val in con1 con2 ... do (执行语句) done};{for((初始值; 限制值; 执行步长)) do (执行语句) done};
2.GCC
- 预处理:.c中的头文件找到、宏展开、编译开关展开,得到.i文件;
- 编译:得到汇编文件.s(语法错误在该过程中发现);
- 汇编:得到机器码文件.o;
- 链接:链接为一个应用程序;
3.Makefile文件及编写规则
4.文件IO
- 用到的函数:open、creat、write、read;
- 文件IO系统调用内部机制调用glibc写的系统接口访问Linux内核提供的服务(触发异常 )(文件句柄和文件install绑定的时候回产生一个task_struct结构体(每个进程都有一个),其里面有一个files成员,其里面有个fdt成员,其里面有个fd数组,对应进程的各个句柄对应的文件值);
- 两次读一个文件,各读各的,f_pos互相不影响,使用fd2=dup(fd1)是在fd数组中新建的句柄指针fd2指向被复制的数组fd1所以f_pos是一样的。(dup也可指定别的句柄(dup2(fd1,*)));
5.Framebuffer
- 控制LCD;
- freetype显示单个文字、显示旋转的文字、显示一行文字;
6.各种编码方式
ASCII编码、ANSI编码、字符集、unicode编码(UTF16-LE编码、UTF16-BE编码、UTF8编码)
7.读取输入设备信息
- 查询方式;
- 休眠唤醒方式;
- POLL/SELECT方式(定个闹钟,到时间了看一下);
- 异步通知方式(驱动程序主动告知app);
8.网络编程(类似文件传输有源\目的\长度)
- TCP(可靠、重传、有链接);
- 服务端
- fd=socket()
- bind(自己的ip,端口)
- listen(开始监测数据)
- accept(建立一条连接)
- send发送数据
- recv接收数据
- 客户端
- fd=socket()
- connect(目的)
- send发送数据
- recv接收数据
- UDP(不可靠、无连接);
- 服务端
- fd=socket()
- bind(自己的ip,端口)
- recvfrom/send_to
- 客户端
- fd=socket()
- connect(实际没有使用)
- send发送数据
- recv接收数据
9.多线程编程
进程的话是两个main函数,内部函数需要通信。线程的话是一个main函数,内部线程可以通过全景变量传递信息;
10.TTY体系中设备节点
- TTY/Terminal/Console/UART区别;
- TTY驱动程序框架(RXT TXT--UART--uart_driver--line_displine行规程);
11.串口应用程序编程
- ARM从高电平->低电平开始计时,保持1bit时间(开始位);
- ARM根据数据驱动TXD电平,bit[0]、bit[1]...bit7;
- (奇/偶校验位)数据位中为1的个数;
- (奇/偶校验位)数据位中为1的个数;
- ARM->高电平(停止位);
12.IIC应用编程
- 协议价绍(看裸机开发);
- SMBus协议(IIC的拓展);
- IIC系统重要的结构体:
- 控制器:I2C_Adapter(num第几个iic总线,xfer传输函数);
- 设备:I2C_Client(addr设备地址,adapter表示挂在哪个iic控制器上);
- 数据传输:I2C_msg(addr从设备地址,flag读还是写,buffer数据,len长度);
Linux内核中一般有了iic的驱动程序Adapter-DRV,提供了iic总线的读写能力,应用程序可以通过i2c-dev.c访问到Adapter-DRV进而控制iic总线;
- 指定IIC控制器:i2c-dev.c为每个I2C控制器都生成了一个设备节点/dev/i2c-0、/dev/i2c-1,使用open某个设备节点/dev/i2c-x,就是访问该I2C控制器下的设备;
- 指定IIC设备:通过ioctl指定iic设备的地址,具体使用ioctl(file,I2C_SLAVE,addreess)或者ioctl(file,I2C_SLAVE_FORCE,addreess)。
- 传输数据:使用SMBus协议方式:ioctl(file,I2C_SMBUS,&args)或者iic协议(file,I2C_RDWR,&rdwr);
imx6ull驱动开发基础
1.怎么写一个驱动程序
- /* 1.确定主设备号 */
- /* 2.定义自己的file_operation结构体 */
- /* 3.实现对应的open/read/write等函数,填入file_operation结构体 */
- /* 4.把file_operation结构体告诉内核:注册驱动程序 */
- /* 5.谁来注册驱动程序,需要有一个入口函数,安装驱动程序时,就回去调用这个入口函数 */
- /* 6.有入口函数就有出口函数:卸载驱动程序时就去调用这个出口函数 */
- /* 7.有入口函数就有出口函数:卸载驱动程序时需要将注册的驱动程序去除 */
2.普适GPIO引脚操作方法
- 时钟使能;
- 模式选择;
- 输入/输出;
- 值;
注:读写寄存器的时候不要影响到其他位;(读、置位、写)
2.1 AM335x和AM437x的GPIO操作方法
- AM335X有gpio0-gpio3;AM437有gpio0-gpio5;gpio0常使能、其他的通过PRCM模块来控制使能状态;
- PRCM:Power,Reset,and Clock Managenment
- 拿到一个开发板配置GPIO要看他的芯片手册中GPIO章节、PRCM章节和Control Module章节;
- 配置某个引脚工作于GPIO模式:
- 在原理图上找到pin number;
- 根据pin number确定芯片手册中的pin name;
- 根据pin name在芯片手册的Control Module章节确定该引脚的寄存器(基地址+偏移地址);
2.2 RK3288和RK3399的GPIO操作方法
- RK3288有gpio0-gpio8;RK3399有gpio0-gpio4;每组GPIO分abcd四个小组gpio(0-7);
- 每组GPIO都有Data RegisterI(输出用)、Data Direction Register、EXT Data Register(输入用);
- 使能GPIO时钟寄存器:CRU(Clock & Reset Unit)时钟和复位单元;
- 设置GPIO模式寄存器:对于gpio1-8:PMU(Power Managerment)电源管理单元、对于gpio0:GRF(General Register Files)通用寄存器文件;
2.3 imx6ull的GPIO操作方法
- 有5组GPIO,每组最多有32个引脚;
- 使能某一组gpio时钟:CCM(Clock Controller Module);
- 设置gpio引脚模式:IOMUXC(包括MUX_MODE和SW_PAD_CTL两个寄存器,分别用来选择模式和是否上拉、回环等等);
- 设置gpio寄存器:输入还是输出GPIO_GDIR(还有中断寄存器等)、值多少GPIO_DR、状态GPIO_PSR;
2.4 STM32MP157的GPIO操作方法
- 内部有两个Cortex-A7核,一个Cortex-M4核;
- 要先使能PLL(PLL4)时钟本身,才能使能GPIO时钟;
- 使能GPIO时钟:RCC_MP寄存器或者RCC_MC寄存器;
- 设置GPIOX_MODER寄存器配置工作为GPIO模式、输入/输出;
3.最简单的LED驱动程序
- app只能open/read/write/ioctl;
- app——file_operation结构体——访问硬件;
- file_operation结构体是驱动程序的核心;
- app和驱动程序通过copy_to_user和copy_from_user传输数据;
- 驱动程序不可以直接访问寄存器的物理地址,需要通过ioremap把物理地址映射为虚拟地址进行访问;
- 编写led驱动程序,其实就是file_operation结构体中的读写、出入口函数等;
- 编写led测试程序(作为app);
- 编写Makefile后make编译得到.ko文件和生成的app,复制到开发板,使用insmod *.ko导入驱动程序(rmmod name 是卸载驱动程序);
- ls /dev/(驱动文件里写的名字) -l 查看有无对应的设备节点;
- 运行测试app;
4.LED驱动程序框架
额外知识:
- dmesg可以查看内核打印的信息;
- MMU(Memory Manager Unit)内存管理单元两大功能:
- 地址映射:CPU发出同样的地址(虚拟地址),执行不同的APP时,访问的是物理地址,由MMU执行中间的转换;
- 权限保护:CPU发出的地址,要经过MMU审核之后才可以访问具体硬件;
LED驱动程序框架:
- 找到原理图LED的引脚;
- 看芯片手册找到GPIO的时钟寄存器使能;
- 看芯片手册找到GPIO的IO_MUX寄存器复用为GPIO;
- 看芯片手册找到GPIO内部方向寄存器设置输入/输出;
- 看芯片手册找到GPIO内部数据寄存器 设置输出值/读输入值;
- 在每个开发板的驱动程序board.c中写对应开发板的led控制逻辑,修改寄存器值;抽象出来一个led_operation结构体;
- 使用中间层驱动leddrv.c(类似字符驱动框架)根据每个开发板的led_operation结构体进行LED控制;
5.驱动设计的思想
面向对象、分层、分离
- file_operation调get_board_led_opr调get_led_resouce;
- file_operation实现从app到led驱动的第一层驱动;
- get_board_led_opr实现所有关于led的io的初始化和控制关于led的io输出高低电平;
- get_led_resouce实现初始化芯片资源的哪个引脚(LED的话就选择led对应的引脚);
6.驱动设计的进化,总线设备驱动模型

- 分配/设置/注册platform_device结构体:定义所用资源,指定设备名字;
- 分配/设置/注册platform_driver结构体:在其中的probe函数里,分配/设置/注册file_operations结构体,并从platform_device中确定所用硬件资源,指定platform_driver的名字;
7.使用总线设备驱动模型改造LED模板驱动程序
- board_A.c:在入口函数中注册platform_device结构体,在 platform_device结构体中指定使用哪个GPIO引脚。
- chip_demo_gpio.c:注册platform_driver结构体使用Bus/Dev/Drv模型,当有匹配的platform_device时,它的 probe函数就会被调用,从匹配的platform_device中得到获取资源、确定引脚并创建设备。
8.驱动进化:设备树的引入
- 设备树文件dts、语法(格式、属性、常用的节点);
- 编译dts文件为dtb文件,在服务器make dtbs编译或者是手工编译,也可以反编译;
- 板子上/sys/firmware/fdt文件,就是 dtb 格式的设备树文件;

9.使用设备树改造LED模板驱动程序
- 修改服务器内核中的设备树文件添加led节点(包括宏、compatible属性、哪个引脚);
- 在chip_gpio.c在platform_driver结构体中添加of_match_table表示支持设备树,其中compatible的值需要和设备树文件对应;
- 在probe函数中,dev.of_node取出设备节点,并读出pin属性值;
- 创建设备节点的/dev/*,后面实现init和ctl;
10.按键驱动程序开发
相关概念:
- APP读取按键的方式:查询方式(循环查询)、休眠-唤醒方式(中断判断或rtos)、poll方式(闹钟+中断)、异步通知方式(按键自动通知);
- 驱动的基本技能:中断、休眠、唤醒、poll等机制;
- APP开发的基本技能:阻塞、非阻塞、休眠、poll、异步通知;
查询方式写按键驱动:
- button_drv.c实现file_operation结构体;
- board_xxx.c实现button_operations结构体;
- app调用button_drv调用board_xxx,不同单板修改board_xxx.c;
11.Pinctrl子系统
相关概念:
- 之前驱动思路:资源和驱动在同一个文件(一层驱动)->资源使用platform_devive指定,驱动在platform_driver实现->资源使用设备树指定,驱动在platform_driver实现;
- 每次操作都要查看寄存器引脚,很繁琐,利用Pinctrl子系统简化这部分操作(由bsp工程师完成),
12.GPIO子系统
相关概念:
- 实现在设备树里指定GPIO引脚;
- 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值;
- 这样的驱动代码,将是单板无关的;
实现demo:
- 在命令行设置某个引脚(也提供了两个版本的代码供程序里使用):
- 在开发板目录:/sys/class/gpio下找到gpiochipxxx目录,进入后cat label查看该组gpio的基地址;
- 找到对应的gpiochipxxx,基地址xxx(可通过cat base查看)+io号就对应改组gpio的号码;
- 假设引脚号码为N,则在命令行可以配置方向和值等:
- echo N > /sys/class/gpio/export
- echo out > /sys/class/gpio/gpioN/direction
- echo 1 > /sys/class/gpio/gpioN/value
- echo N > /sys/class/gpio/unexport
12.Linux 系统对中断的处理
相关概念
- 对于耗时中断解决:1.分成上半部(硬件中断,无法处理其他中断)和下半部(软件中断,可以被中断打断,通过软件中断来实现);2.用内核线程(中断下半部作为一个线程,轮流执行);
- 硬件中断不能嵌套,软件中断可以;
- 软件中断是处理完硬件中断后顺便处理的;
- 为了避免中断下半部执行时间过长,内核有个worker线程,内部有个work queue会调度执行queue内的函数,所以当中断下半部需要执行的时间很长时,要先构造work结构体内部填充执行函数,在中断上半部执行完后把work放入work queue。
- 线程化中断:每个中断都创建一个内核线程,分配到多个cpu核上运行;
- 设备树里指定中断,irq_domain中有个xlate函数解析设备树中指定的irq_parent和interrapt,还有个map函数将硬件中断号映射为如软件中断号,会保存在palatform_device中,就可以使用request_irq注册这个中断处理函数了。
- 读取GPIO寄存器得到hwirq,根据hwirq得到之前映射的软件中断,既可以实现一个中断的硬件中断和软件中断的绑定。
13.编写使用中断的按键中断程序
对于GPIO按键,使用内核自带的驱动程序drivers/input/keyboard/gpio_key.c,只需要修改设备树指定的引脚及键值;

14.驱动程序的基石
1.休眠与唤醒
drv_read驱动程序中,读按键有值就返回,没有值就休眠,按键中断程序会唤醒app并把按键值传给app
2.POLL机制
中断+定时,检查有无数据没有数据则进入睡眠状态,如果还没有数据,休眠到时间后再去判断,超时退出;
内核帮忙实现了sys_poll,app调用sys_poll,内核函数调用驱动程序drv_poll(把线程放入work queue中,但不休眠,返回event状态,休眠在上层内核函数sys_poll中进行)。
3.异步通知
驱动程序发信号,app把自己的pid号给驱动程序;
使用kill_fasync(PID, SIGIO)发信号;
app事先注册一个信号处理函数signal(SIGIO, func),收到信号后执行func函数;

4.阻塞与非阻塞
驱动程序能获得应用程序open文件传入的O_RDWR或O_NONBLOCK;
5.定时器(属于软件中断)
按键抖动会频繁发生中断,每次按键中断我们都忘后延迟20ms,这20ms是超时时间,按键稳定后超时再执行按键中断函数;

6.中断下半部tasklet
软件中断中有个tasklet_action队列,里面存放tasklet任务结构体,硬件中断执行完会将该中断的下半部的tasklet放入队列中,内核线程会去执行;
7.工作队列
在内核线程中执行;
驱动程序构造work(含有执行函数func),然后把work放入队列,最后唤醒;
8.中断的线程化处理
使用request_threaded_irq替换原来的中断注册函数request_irq

9.mmap内存映射
基础知识:
- 用户态buffer和内核态buffer传数据要使用专门的函数,但当数据量大时就容易出现问题;
- 解决:mmap是把内核的buffer映射到用户态,让app在用户态直接读写内存;
- cache和buffer;
- cpu发出虚拟地址通过mmu内存管理单元找到对应的虚拟地址,具体的过程是通过页表映射实现;
代码编写:
应用程序编写函数格式可以在命令行执行“man 函数名”查看;

imx6ull驱动大全