📝本文概览
本文将介绍PetaLinux驱动开发中的Pinctrl子系统和GPIO子系统的相关知识,并借助Zynq开发板完成一个点灯实验。
📚参考视频
正点原子嵌入式Linux驱动开发:点击观看
ALINX Zynq MPSoC Linux驱动开发:点击观看
前者更适合理论学习,后者更适合实操。
1. Pinctrl子系统
从Vivado的PS配置界面可以看出,每个MIO引脚都有多种复用选项,因此如果需要使用某个MIO引脚,需要pinctrl子系统进行相应的配置。
pinctrl子系统在设备树中配置。
构建PetaLinux工程并执行petalinux-build
编译后,可以在[project_dir]/components/plnx_workspace/device-tree/device-tree
目录中找到设备树的相关文件:
设备树的包含结构如下:
1 2 3 4 5
| #include "zynq-7000.dtsi" #include "pcw.dtsi"
#include "system-user.dtsi"
|
其中,system-user.dtsi
文件位于另一个目录[project_dir]/project-spec/meta-user/recipes-bsp/device-tree/files
中,这是用户添加自定义设备树节点的地方。
不同厂商的开发板有不同的pinctrl配置方法,这是因为它们的底层驱动实现存在差异。
构建PetaLinux工程并编译后,可以在[project_dir]/build/tmp/work-shared/zynq-generic-7z100/kernel-source
目录中找到内核源码。
在内核源码kernel-source/Documentation/devicetree/bindings/pinctrl/xlnx,zynq-pinctrl.yaml
文件中,可以找到Zynq pinctrl的标准配置示例:
UART1 Pinctrl配置示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| examples: - | #include <dt-bindings/pinctrl/pinctrl-zynq.h> pinctrl0: pinctrl@700 { compatible = "xlnx,zynq-pinctrl"; reg = <0x700 0x200>; syscon = <&slcr>;
pinctrl_uart1_default: uart1-default { mux { groups = "uart1_10_grp"; function = "uart1"; };
conf { groups = "uart1_10_grp"; slew-rate = <0>; power-source = <IO_STANDARD_LVCMOS18>; };
conf-rx { pins = "MIO49"; bias-high-impedance; }; conf-tx { pins = "MIO48"; bias-disable; }; }; };
uart1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart1_default>; };
|
关于mux和conf的配置该yaml文件中已经解释得很清楚了,比如groups、function的取值有哪些、引脚的配置有哪些、mux和conf中必须包含的内容等等,这里介绍一下这个示例的配置:
pinctrl0节点:在zynq-7000.dtsi
中已定义基本信息(如compatible属性),用户只需在system-user.dtsi
中通过&pinctrl0
追加即可,示例这里是为了体现完整性。
配置结构:
mux
节点:配置引脚复用功能
conf
节点:配置引脚电气特性
conf-rx/conf-tx
:针对特定引脚的专门配置
引脚分组
每个外设都有多个可选的引脚组合(grp),以UART1为例:
- MIO8-9:
uart1_0_grp
- MIO12-13:
uart1_1_grp
- ······
- MIO48-49:
uart1_10_grp
示例中选择MIO48-49作为UART1,因此使用uart1_10_grp
。
conf参数含义:
slew-rate
:引脚转换速率(0=慢速,1=快速),影响信号的上升和下降时间
power-source
:I/O电平标准
MIO48-49属于bank1,通常对应手册中的bank501。有效值为1,2,3,4,这里使用宏定义的方式,相关的宏定义在dt-bindings/pinctrl/pinctrl-zynq.h
中,所以前面include了这个文件:1 2 3 4
| #define IO_STANDARD_LVCMOS18 1 #define IO_STANDARD_LVCMOS25 2 #define IO_STANDARD_LVCMOS33 3 #define IO_STANDARD_HSTL 4
|
bias-high-impedance
:高阻抗偏置
bias-disable
:禁用上下拉
一个外设可以具有多种引脚配置状态,这里示例中uart1只有一种配置,即default,它指向的就是pinctrl中的pinctrl_uart1_default。假如有多种配置,比如:
1 2 3 4 5 6
| &i2c0 { pinctrl-names = "default", "gpio", "sleep"; pinctrl-0 = <&i2c0_pins_default>; pinctrl-1 = <&i2c0_pins_gpio>; pinctrl-2 = <&i2c0_pins_sleep>; };
|
对应的pinctrl节点中则需要定义三种状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| &pinctrl0 { i2c0_pins_default: i2c0-default { mux { }; conf { }; } i2c0_pins_gpio: i2c0-gpio { } i2c0_pins_sleep: i2c0-sleep { } }
|
驱动程序可以根据实际需要在不同状态间切换:
default:正常工作模式,通常在驱动初始化时请求
sleep:低功耗模式,设备进入睡眠状态时使用
gpio:通用GPIO模式,需要将引脚作为GPIO使用时切换
现在假设使用MIO0-MIO3控制LED,则需要在system-user.dtsi
中添加如下配置:
LED Pinctrl配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/pinctrl/pinctrl-zynq.h>
/include/ "system-conf.dtsi" / { led_gpio { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_led_default>; led-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>, <&gpio0 1 GPIO_ACTIVE_HIGH>, <&gpio0 2 GPIO_ACTIVE_HIGH>, <&gpio0 3 GPIO_ACTIVE_HIGH>; }; };
&pinctrl0 { pinctrl_led_default: led-default { mux { function = "gpio0"; groups = "gpio0_0_grp", "gpio0_1_grp", "gpio0_2_grp", "gpio0_3_grp"; }; conf { groups = "gpio0_0_grp", "gpio0_1_grp", "gpio0_2_grp", "gpio0_3_grp"; pins = "MIO0", "MIO1", "MIO2", "MIO3"; slew-rate = <0>; power-source = <IO_STANDARD_LVCMOS33>; bias-disable; }; }; };
|
这里相比于之前说的多了led-gpios这一个属性,这就属于接下来要讲的gpio子系统的内容了。
2. GPIO子系统
正点原子Linux开发手册提到:
2.1 GPIO设备树配置格式
GPIO在设备树中的配置格式相当固定,详细说明可以在内核文档中找到:
kernel-source/Documentation/devicetree/bindings/gpio/gpio.txt
基本语法:
1
| [name]-gpios = <&gpiox offset flags>;
|
参数解释:
[name]
:自定义名称
&gpiox
:GPIO控制器节点引用(Zynq-7000中所有MIO都在gpio0中)
offset
:表示GPIO编号,即gpio0的第几个I/O
flags
:配置标志,可以在kernel-source/include/dt-bindings/gpio/gpio.h
中查看,可以使用或逻辑组合,比如配置高电平有效且上拉则flags为GPIO_ACTIVE_HIGH | GPIO_PULL_UP
2.2 GPIO子系统API
GPIO相关的API文档在kernel-source/Documentation/driver-api/gpio/consumer.rst
和kernel-source/Documentation/driver-api/gpio/board.rst
中。
使用API需包含linux/gpio/consumer.h头文件,常用的API如下。
2.2.1 获取GPIO描述符
1
| struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)
|
dev
参数可以从device_create中得到(后面编写内核驱动代码时会提到)。
con_id
就是前面设备树定义中[name]-gpios的[name],比如aaa-gpios,con_id就是”aaa”。
flags
用于指定方向和初始值,可选值如下:
flags可选值
· GPIOD_ASIS 或 0 表示不初始化 GPIO,方向必须稍后使用专用函数设置。
· GPIOD_IN 初始化 GPIO 为输入.
· GPIOD_OUT_LOW 初始化 GPIO 为输出,且输出值为 0.
· GPIOD_OUT_HIGH 初始化 GPIO 为输出,且输出值为 1.
· GPIOD_OUT_LOW_OPEN_DRAIN 与 GPIOD_OUT_LOW 相同,但使用开漏输出方式。
· GPIOD_OUT_HIGH_OPEN_DRAIN 与 GPIOD_OUT_HIGH 相同,但使用开漏输出方式。
如果有多个GPIO,可以分开定义:
1 2
| led0-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; led1-gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
|
然后分别获取描述符:
1 2
| led0 = gpiod_get(device, "led0", GPIOD_OUT_HIGH); led1 = gpiod_get(device, "led1", GPIOD_OUT_HIGH);
|
也可以像前面led-gpios一样一次性定义多个gpio,此时则可以使用以下函数:
1
| struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx,enum gpiod_flags flags)
|
可以看到,多了一个参数idx,代表获取第几个gpio,比如:
1 2
| led0 = gpiod_get_index(device, "led", 0, GPIOD_OUT_HIGH); led1 = gpiod_get_index(device, "led", 1, GPIOD_OUT_HIGH);
|
但这样还是每次只控制一个gpio,如果想同时控制多个gpio可以使用数组的方式:
1
| struct gpio_descs *gpiod_get_array(struct device *dev,const char *con_id,enum gpiod_flags flags)
|
参数和gpiod_get一样,只是返回的结构体不一样:
1 2 3 4 5
| struct gpio_descs { struct gpio_array *info; unsigned int ndescs; struct gpio_desc *desc[]; }
|
返回的结构体中包含了一个gpio_desc类型的结构体数组。
2.2.2 GPIO方向配置
如果前面gpiod_get没有设置方向和初值,则可以使用以下函数设置:
1 2 3 4 5
| int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)
|
参数说明:
desc
:之前获取的GPIO描述符
value
:输出初值(0或1)
2.2.3 GPIO读写操作
1 2
| int gpiod_get_value(const struct gpio_desc *desc); void gpiod_set_value(struct gpio_desc *desc, int value);
|
1 2 3 4 5 6 7 8
| int gpiod_get_array_value(unsigned int array_size, struct gpio_desc **desc_array, struct gpio_array *array_info, unsigned long *value_bitmap); int gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, struct gpio_array *array_info, unsigned long *value_bitmap);
|
仔细观察可以发现,前面三个参数刚好来自gpiod_get_array返回的结构体。
value_bitmap是位图,位图的第N位为0/1,对应的gpio_descs-> desc[N]为0/1。
使用示例:
1 2 3 4 5 6 7 8 9
| struct gpio_descs *my_gpio_descs = gpiod_get_array(device, "led", GPIOD_OUT_LOW);
unsigned long my_gpio_value_bitmap = 5; gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc, my_gpio_descs->info, &my_gpio_value_bitmap);
|
2.2.4 释放GPIO描述符
单个GPIO
1
| void gpiod_put(struct gpio_desc *desc);
|
GPIO数组
1
| void gpiod_put_array(struct gpio_descs *descs);
|
3. 点灯实验
3.1 硬件设计配置
在Vivado Block Design中完成基础系统配置:
- 外设接口启用:打开SD卡、以太网、串口并根据开发板手册选择对应的MIO引脚
- 电平设置:配置Bank 0和Bank 1的电平(对应板子手册的Bank500/501电平)
由于我所使用的开发板只有4个PL LED,没有PS LED,因此通过EMIO来控制LED,位宽为4。
将GPIO_O进行Make External操作:
注意:如果使用MIO控制LED,则不需要Make External和引脚绑定操作
在I/O Planning中将导出的GPIO信号绑定到PL LED引脚。
完成以上配置后:
- 生成XSA文件
- 构建PetaLinux工程
- 执行编译操作
3.2 设备树配置
由于使用的是EMIO方式,不涉及引脚复用,所以不需要配置pinctrl子系统,只需要配置GPIO子系统即可。
修改system-user.dtsi
文件:
1 2 3 4 5 6 7 8 9 10 11
| #include <dt-bindings/gpio/gpio.h> /include/ "system-conf.dtsi"
/ { led_gpio{ led-gpios = <&gpio0 54 GPIO_ACTIVE_HIGH>, <&gpio0 55 GPIO_ACTIVE_HIGH>, <&gpio0 56 GPIO_ACTIVE_HIGH>, <&gpio0 57 GPIO_ACTIVE_HIGH>; }; };
|
Zynq-7000系列中,MIO占据了gpio0的前54个编号(0-53),EMIO从编号54开始。
3.3 Linux驱动程序开发
3.3.1 字符设备驱动模板
以下是一个简单的字符设备驱动模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
| #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h>
#define xxx_COUNT 1 #define xxx_NAME "xxx" static struct xxx_device{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; } xxx;
static int xxx_open(struct inode *inode, struct file *filp) { return 0; }
static int xxx_release(struct inode *inode, struct file *filp) { return 0; }
static ssize_t xxx_write(struct file *filp, const char *buf, size_t count, loff_t *ppos) { int retval; unsigned char databuf[x];
retval = copy_from_user(databuf, buf, count); if(retval < 0) { printk("copy_from_user error\r\n"); return -EFAULT; }
return 0; }
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int retval = 0; unsigned char databuf[x];
retval = copy_to_user(buf, databuf, count); if(retval < 0) { printk("copy_to_user error\r\n"); return -EFAULT; }
return 0; }
static const struct file_operations xxx_fops = { .owner = THIS_MODULE, .open = xxx_open, .release = xxx_release, .write = xxx_write, .read = xxx_read };
static int __init xxx_init(void) { int retval;
retval = alloc_chrdev_region(&xxx.devid, 0, xxx_COUNT, xxx_NAME); if (retval < 0) { printk("fail to register devid\r\n"); goto fail_devid; }
xxx.cdev.owner = THIS_MODULE; cdev_init(&xxx.cdev, &xxx_fops); retval = cdev_add(&xxx.cdev, xxx.devid, xxx_COUNT); if (retval < 0) { printk("fail to add cdev\r\n"); goto fail_cdev; }
xxx.class = class_create(THIS_MODULE, xxx_NAME); if(IS_ERR(xxx.class)){ printk("fail to create device class\r\n"); retval = PTR_ERR(xxx.class); goto fail_class; }
xxx.device = device_create(xxx.class, NULL, xxx.devid, NULL, xxx_NAME); if(IS_ERR(xxx.device)){ printk("fail to create device\r\n"); retval = PTR_ERR(xxx.device); goto fail_device; } return 0;
fail_device: class_destroy(xxx.class); fail_class: cdev_del(&xxx.cdev); fail_cdev: unregister_chrdev_region(xxx.devid, xxx_COUNT); fail_devid: return retval; }
static void __exit xxx_exit(void) { device_destroy(xxx.class, xxx.devid); class_destroy(xxx.class); cdev_del(&xxx.cdev); unregister_chrdev_region(xxx.devid, xxx_COUNT); }
module_init(xxx_init); module_exit(xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xxx");
|
对于特定的驱动,只需要在该模板上修改增删即可。
3.3.2 LED驱动实现
对于特定的LED控制驱动,只需要在通用模板上进行以下修改:
- 去除read操作:LED只需要写入控制,不需要读取状态
- init函数:添加GPIO描述符的获取操作
- exit函数:添加GPIO资源的释放操作
- write函数实现:根据用户数据控制对应LED
LED驱动完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/gpio/consumer.h>
#define LED_COUNT 1 #define LED_NAME "led"
static struct led_device{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct gpio_descs *led_gpios; } led;
static int led_open(struct inode *inode, struct file *filp) { return 0; }
static int led_release(struct inode *inode, struct file *filp) { return 0; }
static ssize_t led_write(struct file *filp, const char *buf, size_t count, loff_t *ppos) { int retval; unsigned char databuf[1]; unsigned long led_bitmap;
retval = copy_from_user(databuf, buf, count); if(retval < 0) { printk("copy_from_user error\r\n"); return -EFAULT; }
led_bitmap = databuf[0] == 0 ? 1 : databuf[0] == 1 ? 2 : databuf[0] == 2 ? 4 : databuf[0] == 3 ? 8 : 0; gpiod_set_array_value(led.led_gpios->ndescs, led.led_gpios->desc, led.led_gpios->info, &led_bitmap);
return 0; }
static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .write = led_write, };
static int __init led_init(void) { int retval;
retval = alloc_chrdev_region(&led.devid, 0, LED_COUNT, LED_NAME); if (retval < 0) { printk("fail to register devid\r\n"); goto fail_devid; }
led.cdev.owner = THIS_MODULE; cdev_init(&led.cdev, &led_fops); retval = cdev_add(&led.cdev, led.devid, LED_COUNT); if (retval < 0) { printk("fail to add cdev\r\n"); goto fail_cdev; }
led.class = class_create(THIS_MODULE, LED_NAME); if(IS_ERR(led.class)) { printk("fail to create device class\r\n"); retval = PTR_ERR(led.class); goto fail_class; }
led.device = device_create(led.class, NULL, led.devid, NULL, LED_NAME); if(IS_ERR(led.device)) { printk("fail to create device\r\n"); retval = PTR_ERR(led.device); goto fail_device; }
led.device->of_node = of_find_node_by_path("/led_gpio"); if(led.device->of_node == NULL) { printk("fail to find node\r\n"); retval = -EINVAL; goto fail_findnd; } led.led_gpios = gpiod_get_array(led.device, "led", GPIOD_OUT_LOW); if(IS_ERR(led.led_gpios)) { printk("fail to get gpio array\r\n"); retval = PTR_ERR(led.led_gpios); goto fail_gpio; }
printk("LED driver loaded successfully\r\n"); return 0;
fail_gpio: fail_findnd: device_destroy(led.class, led.devid); fail_device: class_destroy(led.class); fail_class: cdev_del(&led.cdev); fail_cdev: unregister_chrdev_region(led.devid, LED_COUNT); fail_devid: return retval; }
static void __exit led_exit(void) { gpiod_put_array(led.led_gpios); device_destroy(led.class, led.devid); class_destroy(led.class); cdev_del(&led.cdev); unregister_chrdev_region(led.devid, LED_COUNT); printk("LED driver unloaded\r\n"); }
module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("raiset"); MODULE_DESCRIPTION("LED GPIO Driver for PetaLinux");
|
3.4 用户空间应用程序
📱应用程序作用
用户空间应用程序负责与内核驱动交互,通过标准的文件操作接口(open/write/close)实现LED控制。
LED控制应用程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h>
#define FILE_NAME "/dev/led"
int main(int argc, char **argv) { int fd; int ret; unsigned char databuf[1];
if(argc != 2) { printf("Usage: %s <led_number>\r\n", argv[0]); printf(" led_number: 0-3 (LED0-LED3)\r\n"); return -1; }
fd = open(FILE_NAME, O_RDWR); if(fd < 0) { printf("Error: cannot open file %s\r\n", FILE_NAME); printf("Please check if the driver is loaded: sudo insmod led.ko\r\n"); return -1; }
databuf[0] = (unsigned char)atoi(argv[1]); if(databuf[0] > 3) { printf("Warning: LED number should be 0-3, got %d\r\n", databuf[0]); }
ret = write(fd, databuf, 1); if(ret < 0) { printf("Error: cannot write to file %s\r\n", FILE_NAME); close(fd); return -1; }
printf("LED%d controlled successfully\r\n", databuf[0]);
ret = close(fd); if(ret < 0) { printf("Error: cannot close file %s\r\n", FILE_NAME); return -1; }
return 0; }
|
⚙️主要功能:
- 参数验证:检查命令行参数是否正确
- 设备文件操作:打开
/dev/led
设备文件
- 数据传输:将LED编号写入驱动
3.5 交叉编译
也可以参考第4节使用petalinux-create的方法,这样无需配置交叉编译器,也不需要自己写Makefile。
由于开发主机(x86/x64)和目标开发板(ARM)架构不同,驱动和应用程序需要在主机上使用交叉编译器进行编译,然后将目标文件传输到开发板上运行。
3.5.1 生成SDK
在PetaLinux工程目录下执行以下命令:
1 2
| petalinux-build --sdk petalinux-package --sysroot
|
执行成功后,在<项目路径>/images/linux/
目录下会生成一个sdk
文件夹。
3.5.2 配置交叉编译环境
按照上面的终端提示,执行以下命令加载交叉编译环境:
1
| source /home/raiset/Project/peta_prj/zynq_test/images/linux/sdk/environment-setup-cortexa9t2hf-neon-xilinx-linux-gnueabi
|
验证环境变量是否设置成功:
1 2
| echo $CROSS_COMPILE echo $CC
|
每次打开新的终端都需要重新source一下环境配置文件。
3.6 Makefile编写
3.6.1 驱动模块Makefile
驱动Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| obj-m := led.o
LINUX_KERNEL_SRC := /home/raiset/Project/peta_prj/zynq_test/build/tmp/work/zynq_generic_7z100-xilinx-linux-gnueabi/linux-xlnx/6.1.5-xilinx-v2023.1+gitAUTOINC+716921b6d7-r0/linux-zynq_generic_7z100-standard-build
ARCH := arm
SRC := $(shell pwd)
all: $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(LINUX_KERNEL_SRC) M=$(PWD) modules
clean: rm -f *.o *.mod.o *.mod.c *.order *.symvers *.ko *.mod
.PHONY: all clean
|
需要修改的参数如下:
3.6.2 应用程序Makefile
应用程序Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| CC := $(CC)
CFLAGS += -Wall -O2 LDFLAGS +=
TARGET := led_test
SRCS := led_test.c
OBJS := $(SRCS:.c=.o)
all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(LDFLAGS) -o $@ $^
%.o: %.c $(CC) $(CFLAGS) -c -o $@ $<
clean: rm -f $(OBJS) $(TARGET)
.PHONY: all clean
|
这里只需要修改TARGET
、SRCS
即可。
3.7 上板测试与验证
3.7.1 文件传输
将编译好的led.ko
和应用程序传输到开发板上,可以使用网络传输,也可以直接复制到SD卡的文件系统下:
3.7.2 驱动加载与测试
在开发板上执行以下命令加载驱动:
加载驱动时会执行led_init
函数中的内容,初始化设备并创建设备文件。
如果需要查看设备树信息,可以进入/proc/device-tree
目录:
某些设备树属性值可能显示为乱码,这是二进制数据的正常表现。
3.7.3 LED控制测试
测试LED0(第一个灯):
可以看到最左边的LED亮起,说明驱动工作正常。
测试LED1(第二个灯):
依此类推,运行sudo ./led_test 1
时亮第2个灯,这和驱动中的led_write
函数定义的逻辑一致。
4. 使用petalinux-create创建用户模块和应用程序
运行以下命令创建用户模块和应用程序:
1 2
| petalinux-create -t modules --name ledmodule --enable petalinux-create -t apps --name ledapp --enable
|
在project_dir/project-spec/meta-user/recipes-modules/ledmodule/files
目录下可以找到ledmodule.c文件,在该文件中编写驱动代码。
在project_dir/project-spec/meta-user/recipes-apps/ledapp/files
目录下可以找到ledapp.c文件,在该文件中编写应用程序代码。
之后运行petalinux-build
命令编译工程,该驱动会自动加入到内核中,上板后使用sudo ledapp 0
即可测试。
这种方法虽然简单,但是如果频繁修改代码,需要重新build工程,效率较低,适合后期项目固定时使用。使用交叉编译器可以随时将编译好的文件传过去测试,无需更改工程。