Linux LED设备驱动开发实践
LED驱动硬件访问机制
Linux设备驱动通过配置硬件寄存器实现控制。现代处理器采用MMU(内存管理单元)实现虚拟地址到物理地址的映射,提供内存保护和访问权限控制。32位系统虚拟地址空间为4GB,物理内存(如1GB DDR3)通过MMU映射到该空间。
地址映射函数
// 物理地址到虚拟地址映射
void __iomem *phy_to_virt(resource_size_t phys_addr, size_t region_size)
{
return __arch_ioremap(phys_addr, region_size, MT_DEVICE);
}
// 映射释放
void unmap_region(volatile void __iomem *virt_addr);
寄存器访问接口
// 读操作
uint8_t reg_read8(const volatile void __iomem *addr);
uint16_t reg_read16(const volatile void __iomem *addr);
uint32_t reg_read32(const volatile void __iomem *addr);
// 写操作
void reg_write8(uint8_t value, volatile void __iomem *addr);
void reg_write16(uint16_t value, volatile void __iomem *addr);
void reg_write32(uint32_t value, volatile void __iomem *addr);
LED硬件连接原理
LED0连接至GPIOI_0引脚,低电平点亮。需配置GPIOI_MODER为输出模式,通过GPIOI_BSRR寄存器控制电平状态。
驱动程序实现
#include <linux/io.h>
#include <linux/module.h>
#define DEV_MAJOR 202
#define DEV_NAME "led_ctrl"
enum led_state { LED_OFF, LED_ON };
/* 物理地址定义 */
#define GPIOI_BASE_ADDR 0x5000A000
#define GPIOI_MODER_OFFSET 0x00
#define GPIOI_BSRR_OFFSET 0x18
/* 虚拟地址指针 */
static void __iomem *vmoder_reg;
static void __iomem *vbsrr_reg;
void set_led(enum led_state state)
{
if (state == LED_ON)
reg_write32(1 << 16, vbsrr_reg); // 设置高16位使输出低电平
else
reg_write32(1, vbsrr_reg); // 设置低16位使输出高电平
}
static int dev_open(struct inode *inodep, struct file *filep)
{
return 0;
}
static ssize_t dev_write(struct file *filep, const char __user *buf,
size_t len, loff_t *offset)
{
char val;
if (copy_from_user(&val, buf, 1))
return -EFAULT;
set_led(val ? LED_ON : LED_OFF);
return len;
}
static struct file_operations fops = {
.open = dev_open,
.write = dev_write,
};
static int __init driver_init(void)
{
vmoder_reg = phy_to_virt(GPIOI_BASE_ADDR + GPIOI_MODER_OFFSET, 4);
vbsrr_reg = phy_to_virt(GPIOI_BASE_ADDR + GPIOI_BSRR_OFFSET, 4);
// 配置GPIO为输出模式
reg_write32((reg_read32(vmoder_reg) & ~0x3) | 0x1, vmoder_reg);
// 注册字符设备
register_chrdev(DEV_MAJOR, DEV_NAME, &fops);
return 0;
}
static void __exit driver_exit(void)
{
unmap_region(vmoder_reg);
unmap_region(vbsrr_reg);
unregister_chrdev(DEV_MAJOR, DEV_NAME);
}
module_init(driver_init);
module_exit(driver_exit);
MODULE_LICENSE("GPL");
用户空间测试程序
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd = open(argv[1], O_WRONLY);
char state = atoi(argv[2]);
write(fd, &state, 1);
close(fd);
return 0;
}
编译与测试流程
1. 驱动编译Makefile:
obj-m := led_drv.o
KERNEL_DIR := /path/to/kernel
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
2. 测试程序编译:
arm-linux-gnueabihf-gcc led_test.c -o ledtest
3. 加载驱动并测试:
# insmod led_drv.ko
# mknod /dev/ledctrl c 202 0
# ./ledtest /dev/ledctrl 1 # LED亮
# ./ledtest /dev/ledctrl 0 # LED灭