树莓派--LED驱动例程(01)

编写驱动程序前需要移植树莓派内核以获取内核源码,选择树莓派3B(CPU:BCM2709),其IO空间的起始地址为0x3f000000(BCM2708是0x20000000),加上GPIO的偏移量0x2000000,实际GPIO的物理地址是从0x3f200000开始的。在此基础上,再进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。

目录

1 LED驱动代码

1.1 头文件导入

1.2 常量定义

1.3 文件打开状态

1.4 GPIO操作函数

1)bcm2835_gpio_fsel()

2)bcm2835_gpio_set()

3)bcm2835_gpio_clr()

1.5 文件操作函数

1)leds_open()

2)leds_ioctl()

3)leds_release()

1.6 文件操作结构体

1.7 混杂设备结构体

1.8 模块初始化和清理

1)leds_init

2)leds_exit

1.9 模块信息和入口/出口函数

总结

在虚拟机上分别编译,将模块、测试程序拷贝至树莓派,插入模块后运行测试程序。

sudo insmod xxx.ko
sudo ./led_test 1 打开LED
sudo ./led_test 0 关闭LED

1 LED驱动代码

整个程序基于Linux内核模块编写,主要用于控制树莓派上的GPIO26号引脚,来控制一个LED灯的开关。

1.1 头文件导入

#include <linux/miscdevice.h>    
#include <linux/delay.h>    
#include <asm/irq.h>    
#include <linux/kernel.h>    
#include <linux/module.h>    
#include <linux/init.h>    
#include <linux/mm.h>    
#include <linux/fs.h>    
#include <linux/types.h>    
#include <linux/delay.h>    
#include <linux/moduleparam.h>    
#include <linux/slab.h>    
#include <linux/errno.h>    
#include <linux/ioctl.h>    
#include <linux/cdev.h>    
#include <linux/string.h>    
#include <linux/list.h>    
#include <linux/pci.h>    
#include <asm/uaccess.h>    
#include <asm/atomic.h>    
#include <asm/unistd.h>    
#include <asm/io.h>    
#include <asm/uaccess.h>    
#include <linux/ioport.h>

这些头文件定义了模块开发所需的各种宏和函数。

1.2 常量定义

#define PIN 26 // GPIO26 引脚号
#define BCM2835_GPSET0 0x001c  
#define BCM2835_GPFSEL0 0x0000  
#define BCM2835_GPCLR0 0x0028  
#define BCM2835_GPIO_FSEL_OUTP 1  
#define BCM2835_GPIO_BASE 0x3f200000

使用宏定义 GPIO 的一些控制寄存器和物理地址,PIN 26即我们需要控制的引脚。GPIO_BASE是树莓派的 GPIO 寄存器基地址,GPSET0设置 GPIO 输出寄存器的偏移,GPFSEL0为GPIO 功能选择寄存器的偏移,使用GPCLR0清除 GPIO 输出寄存器的偏移,GPIO_FSEL_OUTP用于设置 GPIO 为输出模式的值。

1.3 文件打开状态

int open_state = 0; // 文件打开状态

使用变量open_state跟踪设备文件是否已经打开。

1.4 GPIO操作函数

1)bcm2835_gpio_fsel()

int bcm2835_gpio_fsel(uint8_t pin, uint8_t mode)    
{
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);    
    volatile uint32_t * bcm2835_gpio_fsel = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10);    
    uint8_t shift = (pin % 10) * 3;    
    uint32_t value = mode << shift;    
    *bcm2835_gpio_fsel = *bcm2835_gpio_fsel | value;    
  
    printk("fsel address: 0x%lx : %x\n", (long unsigned int)bcm2835_gpio_fsel, *bcm2835_gpio_fsel);    
  
    return 0;    
}

这个函数用于配置指定 GPIO 引脚的功能(如输入或输出),pin是 GPIO 引脚号,mode是模式(这里通常是输入或输出),具体步骤:

  • 使用ioremap将物理地址BCM2835_GPIO_BASE映射到虚拟地址空间;
  • 计算 GPIO 功能选择寄存器的地址;
  • 设置对应引脚的功能为输出。

2)bcm2835_gpio_set()

int bcm2835_gpio_set(uint8_t pin)    
{
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);    
    volatile uint32_t * bcm2835_gpio_set = bcm2835_gpio + BCM2835_GPSET0/4 + pin/32;    
    uint8_t shift = pin % 32;    
    uint32_t value = 1 << shift;    
    *bcm2835_gpio_set = *bcm2835_gpio_set | value;    
    
    printk("set address: 0x%lx : %x\n", (long unsigned int)bcm2835_gpio_set, *bcm2835_gpio_set);    
  
    return 0;    
}

这个函数用于设置指定 GPIO 引脚的输出电平为高电平,类似地,它先映射物理地址,然后计算具体的寄存器地址,最后将指定引脚置为高电平,点亮 LED

3)bcm2835_gpio_clr()

int bcm2835_gpio_clr(uint8_t pin)    
{
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);    
    volatile uint32_t * bcm2835_gpio_clr = bcm2835_gpio + BCM2835_GPCLR0/4 + pin/32;    
    uint8_t shift = pin % 32;    
    uint32_t value = 1 << shift;    
    *bcm2835_gpio_clr = *bcm2835_gpio_clr | value;    
      
    printk("clr address: 0x%lx : %x\n", (long unsigned int)bcm2835_gpio_clr, *bcm2835_gpio_clr);    
  
    return 0;    
}

该函数用于清除指定 GPIO 引脚的输出电平,将其置为低电平,熄灭 LED

1.5 文件操作函数

1)leds_open()

static int leds_open(struct inode *inode, struct file *filp)    
{
    if(open_state == 0)      
    {      
        open_state =  1;      
        printk("Open file suc!\n");      
        return 0;      
    }      
    else      
    {      
        printk("The file has opened!\n");      
        return -1;      
    }      
}

这是设备文件的打开函数,它检查设备文件是否已经打开,如果没有,则标记为打开并返回成功;否则提升该设备文件已经被打开。

2)leds_ioctl()

static long leds_ioctl(struct file*filp, unsigned int cmd, unsigned long arg)    
{
    switch(cmd)      
    {      
        case 0:      
            bcm2835_gpio_clr(PIN);    
            printk("LED OFF!\n");    
            break;      
        case 1:      
            bcm2835_gpio_set(PIN);    
            printk("LED ON!\n");    
            break;      
        default:      
            return -EINVAL;      
    }      
    return 0;    
}

这是ioctl操作函数,用于处理用户空间发送的控制命令,cmd是用户传递的命令,可以控制 LED 的开关,具体地:

  • 'cmd = 0'   关闭LED(低电平);
  • 'cmd = 1'   打开LED(高电平)。

3)leds_release()

static int leds_release(struct inode *inode, struct file *filp)    
{
    if(open_state == 1)      
    {      
        open_state =  0;      
        printk("close file suc!\n");      
        return 0;      
    }      
    else      
    {      
        printk("The file has closed!\n");      
        return -1;      
    }      
}

这是设备文件的关闭函数,它将open_state重置为 0,表示设备文件已经关闭。

1.6 文件操作结构体

static const struct file_operations leds_fops = {    
    .owner = THIS_MODULE,    
    .open = leds_open,    
    .unlocked_ioctl = leds_ioctl,    
    .release = leds_release,    
};

该结构体将上面定义的文件操作函数绑定到设备驱动程序上,内核通过这个结构体知道如何处理打开、关闭和控制设备文件的请求。

1.7 混杂设备结构体

static struct miscdevice misc = {    
    .minor = MISC_DYNAMIC_MINOR,    
    .name = "my_leds",    
    .fops = &leds_fops,    
};

这个结构体定义了一个混杂设备(misc device),它是一种特殊类型的字符设备,主要用于实现一些不属于任何特定分类的设备,使用MISC_DYNAMIC_MINOR可以让系统自动分配次设备号。

1.8 模块初始化和清理

1)leds_init

static int __init leds_init(void)    
{
    int ret;    
    // 注册混杂设备    
    ret = misc_register(&misc);    
    // 配置功能选择寄存器为输出    
    bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);    
    // 设置输出电平为高电平,LED亮    
    bcm2835_gpio_set(PIN);    
    printk("ledsinit.\n");    
    return ret;    
}

这是模块的初始化函数,当模块加载时,它会执行以下操作:

  • 注册混杂设备,以使设备文件出现在/dev目录下;
  • 配置 GPIO26 为输出模式;
  • 将 GPIO26 的电平设置为高(LED 点亮)。

2)leds_exit

static void leds_exit(void)    
{
    // LED灭    
    bcm2835_gpio_clr(PIN);    
    // 注销混杂设备    
    misc_deregister(&misc);            
    printk("leds_exit\n");    
}

这是模块的清理函数,当模块卸载时,它会执行以下操作:

  • 将 GPIO26 的电平设置为低(LED 熄灭);
  • 注销混杂设备,移除/dev/my_leds设备文件。

1.9 模块信息和入口/出口函数

module_init(leds_init);    
module_exit(leds_exit);    
    
MODULE_AUTHOR("***");    
MODULE_LICENSE("GPL");

这段代码指定了模块的入口和出口函数,并提供了作者信息和许可证类型,module_init和 module_exit分别标记了模块加载和卸载时的执行函数,MODULE_LICENSE 表示该模块使用 GPL 许可证发布,这对 Linux 内核模块开发非常重要。

总结

该程序是一个简单的内核模块驱动程序,用于控制树莓派上的一个 LED,即通过'/dev/my_leds'设备文件控制 GPIO26 引脚的高低电平,从而控制 LED 的开关。

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: roc-rk3568-pc是一款基于RK3568芯片的台式电脑板,它能够提供高性能、低功耗、多媒体处理以及AI加速等特性。roc-rk3568-pc实现了PC级性能和功能,可以运行Linux和Android操作系统,同时支持多种显示接口和存储接口。 例如,roc-rk3568-pc支持双频WiFi和蓝牙,可以通过HDMI、DP、VGA等接口连接显示器,还可以使用USB3.0、SATA、NVMe等接口连接存储设备。此外,roc-rk3568-pc还配备了四个USB 2.0接口、两个USB 3.0接口、一个千兆网口、一个SPI Flash等。 对于开发者和爱好者来说,roc-rk3568-pc提供了完整的软件开发包(SDK),包括操作系统、编译器、驱动程序、应用程序等。同时,roc-rk3568-pc还有丰富的软件生态系统和社区支持,大大降低了开发门槛和开发成本。 总之,roc-rk3568-pc是一款高性能、全功能、易开发的台式电脑板,适用于各种应用场景,例如家庭娱乐、远程办公、物联网等。 ### 回答2: ROC-RK3568-PC例程是一个基于ROC-RK3568平台设计的针对PC设备的系统应用程序。该例程主要提供了一个使用ROC-RK3568平台的PC设备的示例,用户可通过该例程学习ROC-RK3568平台的基本应用,相应的硬件配置和软件编程技术。 针对ROC-RK3568-PC例程,用户需要掌握一定的嵌入式系统开发技术。首先,用户需要了解ROC-RK3568平台的基本知识,并安装相应的开发环境和编译器。在编写程序的过程中,用户需要熟悉C/C++语言和Linux操作系统,同时掌握设备驱动和应用程序的编写方法,用于实现安装、媒体播放、网络通信、GUI开发等功能。 ROC-RK3568-PC例程的主要任务是提供一个基本的嵌入式系统应用程序示例,让开发者通过学习和修改代码,快速掌握ROC-RK3568平台的开发和应用。该例程的编写需要开发者对ROC-RK3568平台熟悉到一定程度,需要有一定的软件编码能力和嵌入式系统设计经验。同时,用户还需要具备较好的沟通能力和团队协作能力,与其他成员共同完成开发任务。 总的来说,ROC-RK3568-PC例程是一个有实际应用价值的例程,对于想要深入了解嵌入式系统开发的开发者,学习和掌握该例程的编写技术是非常有益的。 ### 回答3: ROC-RK3568-PC例程是指基于ROC-RK3568芯片的PC开发板的样例程序。ROC-RK3568是一款高性能的处理器芯片,可以广泛应用于智能家居、工业控制、远程监控等领域。ROC-RK3568-PC是采用该芯片设计的一款开发板,可以为开发者提供丰富的硬件接口,并支持多种操作系统,如Ubuntu、Android等。 ROC-RK3568-PC例程提供了各种各样的代码示例和应用程序,可以帮助开发者更好地理解和使用ROC-RK3568芯片,实现自己的应用程序。例如,样例程序包括了基于Qt的UI界面设计、GPIO控制、I2C读写、串口通信、网络通信、摄像头采集等功能。此外,还提供了蓝牙配对、TCP/IP通信、远程控制等应用程序,可以帮助开发者快速搭建自己的应用系统。 对于需要使用ROC-RK3568芯片进行开发的开发者来说,ROC-RK3568-PC例程是一个非常有用的参考,可以加快开发进度,提高开发效率。自定义应用程序时,只需要根据自己的需求进行修改或者扩展即可。ROC-RK3568-PC例程提供了良好的参考和基础,为开发者快速实现自己所需的功能提供了便利。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值