友善之臂Tiny4412的嵌入式实验(二):交叉编译环境配置与驱动开发入门
1. 交叉编译环境搭建
嵌入式开发最核心的概念就是交叉编译——在PC上编写代码,然后编译成能在ARM开发板上运行的二进制程序。这就像在中国写好了菜谱,然后送到美国的厨房去做菜,需要确保那边的厨具和食材都能对得上。
1.1 安装必备工具链
首先确保你的Ubuntu系统已经安装了基础开发工具。打开终端(Ctrl+Alt+T),执行以下命令更新软件源并安装必备组件:
sudo apt update
sudo apt install build-essential
sudo apt install ubuntu-make
这些基础包包含了gcc、g++、make等核心开发工具。验证安装是否成功:
gcc --version
g++ --version
make --version
如果看到版本信息输出,说明基础环境已经就绪。我刚开始玩嵌入式的时候,曾经因为漏装这些基础包折腾了一整天,编译时各种莫名其妙的错误,所以这一步千万别跳过。
1.2 获取交叉编译工具链
友善之臂官方提供了专门的交叉编译工具链。你可以从官网下载,或者使用我提供的百度网盘链接(提取码:yccy)。下载完成后,你会得到一个名为arm-linux-gcc-4.6.4-arm-x86_64.tar.bz2的压缩包。
将下载的文件放到桌面,然后在终端中进入桌面目录:
cd ~/Desktop
给压缩包添加执行权限并解压:
sudo chmod 777 arm-linux-gcc-4.6.4-arm-x86_64.tar.bz2
tar -jxvf arm-linux-gcc-4.6.4-arm-x86_64.tar.bz2
解压后会得到一个gcc-4.6.4的目录,这就是我们的交叉编译工具链。
1.3 配置环境变量
为了让系统能找到我们的交叉编译工具,需要将其路径添加到环境变量中。创建工具链的安装目录:
sudo mkdir /usr/local/arm
sudo chmod 777 /usr/local/arm
将解压得到的gcc-4.6.4目录复制到刚创建的目录中:
sudo cp -r gcc-4.6.4 /usr/local/arm/
接下来编辑系统环境变量配置文件:
sudo gedit /etc/profile
在文件末尾添加以下两行内容:
export PATH=$PATH:/usr/local/arm/gcc-4.6.4/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/arm/gcc-4.6.4/lib
第一行添加执行程序路径,第二行添加库文件路径。保存退出后,使用source命令使配置立即生效:
source /etc/profile
1.4 验证安装结果
现在来验证交叉编译工具链是否安装成功:
arm-none-linux-gnueabi-gcc -v
如果看到输出类似"gcc version 4.6.4"的版本信息,恭喜你!交叉编译环境已经配置成功。我第一次看到这个输出时简直欣喜若狂,毕竟这意味着终于可以开始真正的嵌入式开发了。
2. 驱动开发基础入门
驱动开发是嵌入式Linux中最有意思的部分,它让你能够直接与硬件对话。就像学会了一门外语,可以直接和当地人交流一样。
2.1 Linux驱动模型理解
Linux驱动本质上是一个内核模块,它可以动态加载和卸载。与普通应用程序不同,驱动运行在内核空间,具有更高的权限,但也更容易导致系统崩溃。
驱动的主要作用包括:
- 设备抽象:为应用程序提供统一的设备访问接口
- 硬件操作:直接读写硬件寄存器
- 中断处理:响应硬件中断事件
- 数据传输:管理设备与内存之间的数据流动
在开始编写驱动前,建议先查看Tiny4412的原理图,找到LED对应的GPIO引脚。通常LED会连接到GPX2系列的引脚上,但具体要看你的板子版本。
2.2 第一个LED驱动编写
创建一个名为led_driver.c的文件,输入以下代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "led_demo"
#define CLASS_NAME "led"
static int major_num;
static struct class* led_class = NULL;
static struct device* led_device = NULL;
static void __iomem *GPX2CON;
static void __iomem *GPX2DAT;
static int led_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "LED device opened\n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "LED device closed\n");
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case 0: // LED off
writel(readl(GPX2DAT) & ~(1 << arg), GPX2DAT);
break;
case 1: // LED on
writel(readl(GPX2DAT) | (1 << arg), GPX2DAT);
break;
}
return 0;
}
static struct file_operations fops = {
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int __init led_init(void)
{
// 动态申请设备号
major_num = register_chrdev(0, DEVICE_NAME, &fops);
if (major_num < 0) {
printk(KERN_ALERT "Failed to register device\n");
return major_num;
}
// 创建设备类
led_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(led_class)) {
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(led_class);
}
// 创建设备节点
led_device = device_create(led_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);
if (IS_ERR(led_device)) {
class_destroy(led_class);
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(led_device);
}
// 映射GPIO寄存器
GPX2CON = ioremap(0x11000C40, 0x20);
GPX2DAT = GPX2CON + 0x04;
// 配置GPIO为输出模式
writel((readl(GPX2CON) & ~(0xF << 0)) | (0x1 << 0), GPX2CON);
printk(KERN_INFO "LED driver initialized\n");
return 0;
}
static void __exit led_exit(void)
{
iounmap(GPX2CON);
device_destroy(led_class, MKDEV(major_num, 0));
class_destroy(led_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "LED driver removed\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple LED Driver for Tiny4412");
这个驱动实现了最基本的LED控制功能。它创建了一个字符设备,应用程序可以通过ioctl系统调用来控制LED的亮灭。
2.3 编写Makefile
创建一个对应的Makefile来编译这个驱动:
obj-m := led_driver.o
KDIR := /path/to/your/kernel/source
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
注意要将KDIR替换为你实际的内核源码路径。Tiny4412通常使用Linux 3.5内核,你需要先编译好内核源码。
3. 实战编译与测试
理论学得再多不如动手实践一次。让我们完整走一遍从编译到测试的整个流程。
3.1 编译内核模块
在终端中进入驱动源码所在目录,执行编译命令:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
如果一切顺利,你会看到生成了一个led_driver.ko文件。这就是我们的内核模块文件。第一次编译时可能会遇到各种依赖问题,需要确保内核源码配置正确。
3.2 加载驱动模块
将编译好的ko文件拷贝到开发板上,可以使用U盘、SD卡或者网络传输。在开发板上执行:
insmod led_driver.ko
使用dmesg命令查看内核日志,应该能看到"LED driver initialized"的输出。还可以检查/dev目录下是否出现了led_demo设备节点:
ls /dev/led_demo
3.3 编写测试应用程序
创建一个简单的测试程序来验证驱动是否正常工作:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main()
{
int fd = open("/dev/led_demo", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 闪烁LED
for (int i = 0; i < 5; i++) {
ioctl(fd, 1, 0); // 点亮LED
sleep(1);
ioctl(fd, 0, 0); // 熄灭LED
sleep(1);
}
close(fd);
return 0;
}
使用交叉编译工具编译这个测试程序:
arm-none-linux-gnueabi-gcc -o led_test led_test.c
将生成的可执行文件拷贝到开发板上运行,你应该能看到LED开始闪烁。这一刻的成就感是无与伦比的——你终于让硬件听从你的指令了!
4. 常见问题与调试技巧
驱动开发过程中难免会遇到各种问题,这里分享一些我踩过的坑和解决方法。
4.1 编译常见错误
头文件找不到:确保内核源码路径正确,并且已经配置好内核。可以尝试先编译内核本身,确保内核编译环境正常。
权限问题:加载驱动需要root权限,确保你在开发板上使用sudo或者直接以root用户操作。
版本不匹配:驱动模块必须与运行的内核版本完全匹配,否则无法加载。使用uname -r查看开发板内核版本,然后用相同版本的内核源码进行编译。
4.2 调试技巧
printk调试:在内核代码中添加printk语句是最直接的调试方法。通过dmesg可以查看输出信息。
使用/proc文件系统:可以通过/proc/interrupts查看中断信息,/proc/iomem查看内存映射情况。
硬件排查:如果软件层面找不到问题,别忘了检查硬件连接。用万用表测量GPIO引脚电压,确保硬件电路正常工作。
逐步验证:从最简单的代码开始,逐步添加功能。先确保能正确加载模块,再添加设备节点创建,最后实现具体的硬件操作。
记得我第一次写驱动时,花了三天时间才发现是硬件接触不良,所以硬件问题永远不能忽视。
更多推荐

所有评论(0)