GPIO子系统与内核定时器的应用(LED灯为列)

一,主要的实现功能

        1.通过自主注册设备驱动,并未3个LED灯注册对应的设备文件,每一个设备文件控制对用的LED灯。2.对控制LED灯的寄存器进行设置时,采用GPIO子系统,而不是采用寄存器地址的映射,消除了当硬件环境改变时,驱动代码无法正常执行。3.用户空间代码手动控制灯的熄灭逻辑。4.使用内核定时器使LED灯自主一闪一灭。!!!当想要手动控制灯的熄灭时,必须先将内核定时器代码先注销,因为共用一个驱动。

二,应用层代码

        主要给用户提供一个操作界面,通过终端命令获取控制灯的亮闪,与内核定时器无关。

#include "user.h"
int main(int argc, char const *argv[])
{
    while (1)
    {
        printf("请选择你要进行的操作1.<点灯> 0.<熄灯>:");
        scanf("%d", &my_select);
        if (my_select == 1) // 点灯
        {
            printf("选择你要操作的灯 1<LED1>,2<LED2>,3<LED3>:");
            scanf("%d", &key);
            if (key == 1) // 操作LED1
            {
                fd_led1 = open("/dev/myled0", O_RDWR);
                if (fd_led1 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led1, LED_ON);
            }
            else if (key == 2) // 操作LED2
            {
                printf("LED2\n");
                fd_led2 = open("/dev/myled1", O_RDWR);
                if (fd_led2 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led2, LED_ON);
            }
            else if (key == 3) // 操作LED3
            {
                printf("LED3\n");
                fd_led3 = open("/dev/myled2", O_RDWR);
                if (fd_led3 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led3, LED_ON);
            }
        }
        else if (my_select == 0) // 熄灯
        {
            printf("选择你要操作的灯 1<LED1>,2<LED2>,3<LED3>:");
            scanf("%d", &key);
            if (key == 1) // 操作LED1
            {
                fd_led1 = open("/dev/myled0", O_RDWR);
                if (fd_led1 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led1, LED_OFF);
            }
            else if (key == 2) // 操作LED2
            {
                fd_led2 = open("/dev/myled1", O_RDWR);
                if (fd_led2 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led2, LED_OFF);
            }
            else if (key == 3) // 操作LED3
            {
                fd_led3 = open("/dev/myled2", O_RDWR);
                if (fd_led3 < 0)
                {
                    printf("myled%d设备文件打开失败\n", key - 1);
                    return 0;
                }
                ioctl(fd_led3, LED_OFF);
            }
        }
    }
    close(fd_led1);
    close(fd_led2);
    close(fd_led3);
    return 0;
}

三,驱动代码

        1.入口代码,包括自主注册驱动,初始化内核定时器,GPIO子系统节点信息的获取和资源申请

static int __init mycdev_init(void)
{
    int i = -1;
    /**定时器初始化**/
    //设置定时阈值
    mytimer.expires=jiffies+HZ;//设置定时1s  # define HZ     CONFIG_HZ
    //完成定时器的初始化 参1:操作的定时器对象 参2:定时器到期时执行的回调函数 参3:标志0即可
    timer_setup(&mytimer,mytimer_handler, 0); 
    //注册定时器到内核并启用
    add_timer(&mytimer);

    /*****LED灯的驱动安装流程*****/
    // 1.安装驱动获取mojor设备号
    printk("LED灯驱动安装\n");
    major = register_chrdev(0, "myled", &fops); // 系统动态申请主设备号
    if (major < 0)
    {
        printk("主设备号申请失败\n");
        return major;
    }
    printk("主设备号申请成功:major=%d\n", major);

    // 3.向上提交目录
    cls = class_create(THIS_MODULE, "myled");
    if (IS_ERR(cls))
    {
        printk("向上提交目录失败\n");
        return PTR_ERR(cls);
    }
    printk("向上提交目录成功\n");
    /*
    cls=class_create(THIS_MODULE,"mychrdev1");
    cls=class_create(THIS_MODULE,"mychrdev2");
    */

    // 4.向上提交设备节点信息,分别为每一个灯创建设备文件

    for (i = 0; i < 3; i++)
    {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(dev))
        {
            printk("向上提交设备信息myled%d失败\n", i);
            return PTR_ERR(dev);
        }
        printk("向上提交设备信息myled%d成功", i);
    }

    all_led_init();
    return 0;
}

       

int all_led_init(void)
{
    // 1.解析设备树节点信息通过名字
    dnode = of_find_node_by_name(NULL, "myleds");
    if (dnode == NULL)
    {
        printk("设备树节点解析失败\n");
        return -ENOMEM;
    }
    printk("设备树节点解析成功\n");

    // 2.通过节点信息获取GPIO编号
    // 2.1申请LED1对应资源
    gpiono_led1 = gpiod_get_from_of_node(dnode, "led1", 0, GPIOD_OUT_LOW, NULL);
    if (IS_ERR(gpiono_led1))
    {
        printk("LED1资源申请失败\n");
        return -PTR_ERR(gpiono_led1);
    }
    printk("LED1资源申请成功\n");
    // 2.2申请LED1对应资源
    gpiono_led2 = gpiod_get_from_of_node(dnode, "led2", 0, GPIOD_OUT_LOW, NULL);
    if (IS_ERR(gpiono_led2))
    {
        printk("LED2资源申请失败\n");
        return -PTR_ERR(gpiono_led2);
    }
    printk("LED2资源申请成功\n");
    // 2.3申请LED1对应资源
    gpiono_led3 = gpiod_get_from_of_node(dnode, "led3", 0, GPIOD_OUT_LOW, NULL);
    if (IS_ERR(gpiono_led3))
    {
        printk("LED3资源申请失败\n");
        return -PTR_ERR(gpiono_led1);
    }
    printk("LED3资源申请成功\n");
    return 0;
}

        2.出口代码,资源的释放,回收等)

                 注意回收和注销的顺序:1.先关闭stm32上运行的硬件灯,2.将申请的资源释放 3.销毁自主创建的设备文件,目录。4.最后销毁驱动。

static void __exit mycdev_exit(void)
{
    // 关灯
    gpiod_set_value(gpiono_led1, 0);
    gpiod_set_value(gpiono_led2, 0);
    gpiod_set_value(gpiono_led3, 0);
     //注销定时器
    del_timer(&mytimer);
    // 释放申请的LED资源
    gpiod_put(gpiono_led1);
    gpiod_put(gpiono_led2);
    gpiod_put(gpiono_led3);
    // 销毁设备信息
    device_destroy(cls, MKDEV(major, 0));
    device_destroy(cls, MKDEV(major, 1));
    device_destroy(cls, MKDEV(major, 2));
    // 销毁目录
    class_destroy(cls);
    // 注销字符设备驱动
    unregister_chrdev(major, "myled");

}

        3.手动控制LED灯逻辑代码函数ioctl

int mycdev_open(struct inode *inode, struct file *file)
{
    // 1.通过struct inode找到设备号
    // 2.根据设备号使用MINOR函数找到次设备号
    // 3.将次设备号放入到struct file文件中private_date中
    led_minor = MINOR(inode->i_rdev);
    file->private_data = (void *)led_minor;
    printk("data=%x\n",(unsigned int)file->private_data);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

    // 向用户空间读取拷贝
    if (size > sizeof(kbuf)) // 用户空间期待读取的大小内核满足不了,那就给内核支持的最大大小
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) // 拷贝失败
    {
        printk("copy_to_user filed\n");
        return ret;
    }
    return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    // 获取struct file结构体中的private_data中存储的次设备号
    unsigned int led_x = 0;
    led_x = (unsigned int )file->private_data;
    printk("led_x=%x",led_x);
    switch (led_x)
    {
    case 0:          // 打开的是LED1设备文件
        printk("文件myled0打開\n");
        switch (cmd) // 判断是开灯还是熄灯
        {
        case LED_ON: // 开灯
            printk("LED1开\n");
            gpiod_set_value(gpiono_led1,1);
            break;
        case LED_OFF: // 熄灯
            gpiod_set_value(gpiono_led1,0);
            break;
        }
        break;
    case 1:          // 打开的是LED2设备文件
        switch (cmd) // 判断是开灯还是熄灯
        {
        case LED_ON: // 开灯
        printk("LED2开\n");
            gpiod_set_value(gpiono_led2,1);
            break;
        case LED_OFF: // 熄灯
            gpiod_set_value(gpiono_led2,0);
            break;
        }
        break;
    case 2:          // 打开的是LED3设备文件
        switch (cmd) // 判断是开灯还是熄灯
        {
        case LED_ON: // 开灯
        printk("LED3开\n");
            gpiod_set_value(gpiono_led3,1);
            break;
        case LED_OFF: // 熄灯
            gpiod_set_value(gpiono_led3,0);
            break;
        }
        break;
    }
    return 0;
}

        4.内核定时器自动控制灯的亮灭代码

                主要在于执行函数内部调用定时器,达到一个无限循环的效果

void mytimer_handler(struct timer_list *timer)
{
    //控制灯亮灭
    gpiod_set_value(gpiono_led1,!gpiod_get_value(gpiono_led1));
    gpiod_set_value(gpiono_led2,!gpiod_get_value(gpiono_led2));
    gpiod_set_value(gpiono_led3,!gpiod_get_value(gpiono_led3));
    //再次启动定时器
    mod_timer(timer,jiffies+HZ);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
定时器结合GPIO实验的软件模块主要分为以下几个部分: 1. 定时器配置:包括定时器模式设置、预分频器设置、计数器初始值设置等。 2. GPIO配置:包括GPIO口模式设置、引脚方向设置、引脚输出值设置等。 3. 中断配置:包括中断使能、中断优先级设置、中断处理程序编写等。 4. 主程序:包括等待定时器中断触发、中断处理程序中GPIO输出状态设置等。 对于定时器结合GPIO的实验,需要根据具体的硬件平台和实验需求来进行相应的软件模块设计和编程。下面是一个简单的示例程序,仅供参考: ```c #include <reg52.h> sbit LED = P1^0; // 定义LED引脚为P1.0 sbit KEY = P3^2; // 定义按键引脚为P3.2 void InitTimer0() // 定时器配置函数 { TMOD &= 0xF0; // 清除定时器0模式位 TMOD |= 0x01; // 设置为模式1,16位自动重载定时器 TH0 = 0x3C; // 定时器计数值设为0x3C50,时钟频率为12MHz TL0 = 0x50; ET0 = 1; // 允许定时器0中断 EA = 1; // 允许总中断 TR0 = 1; // 启动定时器0 } void InitGPIO() // GPIO配置函数 { LED = 0; // LED引脚输出低电平 KEY = 1; // 按键引脚上拉 } void InterruptTimer0() interrupt 1 // 定时器0中断处理程序 { LED = !LED; // LED引脚翻转 } void main() // 主程序 { InitTimer0(); // 定时器配置 InitGPIO(); // GPIO配置 while (1) { if (KEY == 0) // 检测按键是否按下 { LED = 1; // LED引脚输出高电平 } } } ``` 上述示例程序中,定时器0被配置为模式1,计数值为0x3C50,时钟频率为12MHz,中断使能后每隔约5ms触发一次中断。中断处理程序中LED引脚被翻转,即每次中断时LED引脚状态翻转一次。主程序中检测按键引脚是否为低电平,如果按键被按下,LED引脚输出高电平,否则LED引脚维持低电平。 定时器结合GPIO实验的软件模块需要根据具体的实验需求进行相应的修改和优化,确保实验的正确性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值