嵌入式Linux中的LED驱动控制(以野火STM32MP157开发板为例)

news/2024/11/17 4:58:51/文章来源:https://www.cnblogs.com/fxzq/p/18197921

在嵌入式Linux系统中,由于从硬件到软件都是自己定制的,所以很多时候需要对自己定义的设备编写驱动程序。本例就以野火STM32MP157开发板为例,讨论如何控制开发板上三个LED的亮灭。

先来看一下LED部分的电路原理图,如下所示。

从上图中可以看到,三个RGB颜色的二极管采用共阳接法, 因此在给STM32MP157的PA13、PG2、PB5端口输出高电平时,三个发光管匀熄灭,输出低电平时,三个发光管匀点亮。

下面给出了完整的驱动程序代码,文件名为led.c。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
//以下定义总线及寄存器的物理地址
#define AHB4_PERIPH_BASE (0x50000000)
#define RCC_BASE (AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_GPIOENA (RCC_BASE + 0xA28)
#define GPIOA_BASE (AHB4_PERIPH_BASE + 0x2000)
#define GPIOA_MODER (GPIOA_BASE + 0x0000)
#define GPIOA_OTYPER (GPIOA_BASE + 0x0004)
#define GPIOA_OSPEEDR (GPIOA_BASE + 0x0008)
#define GPIOA_PUPDR (GPIOA_BASE + 0x000C)
#define GPIOA_ODR (GPIOA_BASE + 0x0014)
#define GPIOA_BSRR (GPIOA_BASE + 0x0018)
#define GPIOG_BASE (AHB4_PERIPH_BASE + 0x8000)
#define GPIOG_MODER (GPIOG_BASE + 0x0000)
#define GPIOG_OTYPER (GPIOG_BASE + 0x0004)
#define GPIOG_OSPEEDR (GPIOG_BASE + 0x0008)
#define GPIOG_PUPDR (GPIOG_BASE + 0x000C)
#define GPIOG_ODR (GPIOG_BASE + 0x0014)
#define GPIOG_BSRR (GPIOG_BASE + 0x0018)
#define GPIOB_BASE (AHB4_PERIPH_BASE + 0x3000)
#define GPIOB_MODER (GPIOB_BASE + 0x0000)
#define GPIOB_OTYPER (GPIOB_BASE + 0x0004)
#define GPIOB_OSPEEDR (GPIOB_BASE + 0x0008)
#define GPIOB_PUPDR (GPIOB_BASE + 0x000C)
#define GPIOB_ODR (GPIOB_BASE + 0x0014)
#define GPIOB_BSRR (GPIOB_BASE + 0x0018)
//以下定义时钟控制寄存器名称
static void __iomem *RCC_MP_AHB4ENSETR;
static void __iomem *GPIO_MODER_PA;
static void __iomem *GPIO_OTYPER_PA;
static void __iomem *GPIO_OSPEEDR_PA;
static void __iomem *GPIO_PUPDR_PA;
static void __iomem *GPIO_ODR_PA;
static void __iomem *GPIO_BSRR_PA;
static void __iomem *GPIO_MODER_PB;
static void __iomem *GPIO_OTYPER_PB;
static void __iomem *GPIO_OSPEEDR_PB;
static void __iomem *GPIO_PUPDR_PB;
static void __iomem *GPIO_ODR_PB;
static void __iomem *GPIO_BSRR_PB;
static void __iomem *GPIO_MODER_PG;
static void __iomem *GPIO_OTYPER_PG;
static void __iomem *GPIO_OSPEEDR_PG;
static void __iomem *GPIO_PUPDR_PG;
static void __iomem *GPIO_ODR_PG;
static void __iomem *GPIO_BSRR_PG;
//申明一个字符型设备结构体led_dev
struct led_dev
{dev_t devid;                  //设备号struct cdev chrdev;           //字符设备结构体struct class *class;         //类结构体struct device *device;     //设备结构体
};
//定义一个led_dev类型的结构体,名称为led
struct led_dev led;
//实现open函数,为file_oprations结构体成员函数
static int led_open(struct inode *inode, struct file *filp)
{ unsigned int tmp;//以下使能GPIOA、GPIOB、GPIOG端口时钟tmp = ioread32(RCC_MP_AHB4ENSETR);tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0);iowrite32(tmp, RCC_MP_AHB4ENSETR);//把led结构体保存在file结构体的私有变量中filp->private_data = &led;return 0;
}
//实现write函数,为file_oprations结构体成员函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{unsigned char value;unsigned long n;n = copy_from_user(&value, buf, cnt);    //从应用空间获取值switch(value)        //根据应用空间的值判断具体操作
       {case 0:            //全部点亮三个LEDiowrite32(0x20000000, GPIO_BSRR_PA);iowrite32(0x200000, GPIO_BSRR_PB);iowrite32(0x40000, GPIO_BSRR_PG);break;case 1:            //点亮红色LEDiowrite32(0x20000000, GPIO_BSRR_PA);break;case 2:            //点亮绿色LEDiowrite32(0x40000, GPIO_BSRR_PG);break;case 3:            //点亮蓝色LEDiowrite32(0x200000, GPIO_BSRR_PB);break;case 4:            //熄灭红色LEDiowrite32(0x2000, GPIO_BSRR_PA);break;case 5:            //熄灭绿色LEDiowrite32(0x20, GPIO_BSRR_PB);break;case 6:            //熄灭蓝色LEDiowrite32(0x04, GPIO_BSRR_PG);break;case 7:            //全部熄灭三个LEDiowrite32(0x2000, GPIO_BSRR_PA);iowrite32(0x20, GPIO_BSRR_PB);iowrite32(0x04, GPIO_BSRR_PG);break;default:          //全部熄灭iowrite32(0x2000, GPIO_BSRR_PA);iowrite32(0x20, GPIO_BSRR_PB);iowrite32(0x04, GPIO_BSRR_PG);break;} return 0;
}
//实现release函数,为file_oprations结构体函数
static int led_release(struct inode *inode, struct file *filp)
{unsigned int tmp;//以下禁能GPIOA、GPIOB、GPIOG端口时钟tmp = ioread32(RCC_MP_AHB4ENSETR);tmp &= ~0x43;iowrite32(tmp, RCC_MP_AHB4ENSETR);return 0;
}
//定义一个file_oprations类型的结构体,名为led_dev_fops,包含上述申明的成员函数
static struct file_operations led_dev_fops = {.owner = THIS_MODULE,.open = led_open,               //指定open函数成员.write = led_write,             //指定write函数成员.release = led_release,         //指定release函数成员
};
//初始化函数,此处为驱动模块的入口函数
static int __init led_init(void)
{unsigned int tmp;//以下实现各个寄存器的地址映射RCC_MP_AHB4ENSETR = ioremap(RCC_MP_GPIOENA, 4);GPIO_MODER_PA = ioremap(GPIOA_MODER, 4);GPIO_OTYPER_PA = ioremap(GPIOA_OTYPER, 4);GPIO_OSPEEDR_PA = ioremap(GPIOA_OSPEEDR, 4);GPIO_PUPDR_PA = ioremap(GPIOA_PUPDR, 4);GPIO_ODR_PA = ioremap(GPIOA_ODR, 4);GPIO_BSRR_PA = ioremap(GPIOA_BSRR, 4);GPIO_MODER_PB = ioremap(GPIOB_MODER, 4);GPIO_OTYPER_PB = ioremap(GPIOB_OTYPER, 4);GPIO_OSPEEDR_PB = ioremap(GPIOB_OSPEEDR, 4);GPIO_PUPDR_PB = ioremap(GPIOB_PUPDR, 4);GPIO_ODR_PB = ioremap(GPIOB_ODR, 4);GPIO_BSRR_PB = ioremap(GPIOB_BSRR, 4);GPIO_MODER_PG = ioremap(GPIOG_MODER, 4);GPIO_OTYPER_PG = ioremap(GPIOG_OTYPER, 4);GPIO_OSPEEDR_PG = ioremap(GPIOG_OSPEEDR, 4);GPIO_PUPDR_PG = ioremap(GPIOG_PUPDR, 4);GPIO_ODR_PG = ioremap(GPIOG_ODR, 4);GPIO_BSRR_PG = ioremap(GPIOG_BSRR, 4);//以下使能GPIOA、GPIOB、GPIOG端口时钟tmp = ioread32(RCC_MP_AHB4ENSETR);tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0);iowrite32(tmp, RCC_MP_AHB4ENSETR);//以下把GPIOA、GPIOB、GPIOG端口配置为输出、上位模式tmp = ioread32(GPIO_MODER_PA);tmp &= ~(0x3 << 26);tmp |= (0x1 << 26);iowrite32(tmp, GPIO_MODER_PA);tmp = ioread32(GPIO_MODER_PB);tmp &= ~(0x3 << 10);tmp |= (0x1 << 10);iowrite32(tmp, GPIO_MODER_PB);tmp = ioread32(GPIO_MODER_PG);tmp &= ~(0x3 << 4);tmp |= (0x1 << 4);iowrite32(tmp, GPIO_MODER_PG);tmp = ioread32(GPIO_PUPDR_PA);tmp &= ~(0x3 << 26);tmp |= (0x1 << 26);iowrite32(tmp, GPIO_PUPDR_PA);tmp = ioread32(GPIO_PUPDR_PB);tmp &= ~(0x3 << 10);tmp |= (0x1 << 10);iowrite32(tmp, GPIO_PUPDR_PB);tmp = ioread32(GPIO_PUPDR_PG);tmp &= ~(0x3 << 4);tmp |= (0x1 << 4);iowrite32(tmp, GPIO_PUPDR_PG);//以下设定GPIOA、GPIOB、GPIOG端口初始值iowrite32(0x2000, GPIO_BSRR_PA);iowrite32(0x20, GPIO_BSRR_PB);iowrite32(0x04, GPIO_BSRR_PG);//以下禁能GPIOA、GPIOB、GPIOG端口时钟tmp = ioread32(RCC_MP_AHB4ENSETR);tmp &= ~0x43;iowrite32(tmp, RCC_MP_AHB4ENSETR);//申请主设备号if(alloc_chrdev_region(&led.devid, 0, 1, "led") < 0){printk("Couldn't alloc_chrdev_region!\r\n");return -EFAULT;}led.chrdev.owner = THIS_MODULE;//绑定前面申明的file_oprations类型的结构体到字符设备cdev_init(&led.chrdev, &led_dev_fops);//填充上面申请到的主设备号到字符设备if(cdev_add(&led.chrdev,led.devid, 1) < 0){printk("Couldn't add chrdev!\r\n");return -EFAULT;}//创建类  led.class = class_create(THIS_MODULE, "led_dev");//根据创建的类生成一个设备节点led.device = device_create(led.class, NULL, led.devid, NULL, "led");return 0;
}
//退出函数,此处为驱动模块的出口函数
static void __exit led_exit(void)
{//以下实现各个寄存器的解除映射
    iounmap(RCC_MP_AHB4ENSETR);iounmap(GPIO_MODER_PA);iounmap(GPIO_OTYPER_PA);iounmap(GPIO_OSPEEDR_PA);iounmap(GPIO_PUPDR_PA);iounmap(GPIO_BSRR_PA);iounmap(GPIO_MODER_PB);iounmap(GPIO_OTYPER_PB);iounmap(GPIO_OSPEEDR_PB);iounmap(GPIO_PUPDR_PB);iounmap(GPIO_BSRR_PB);iounmap(GPIO_MODER_PG);iounmap(GPIO_OTYPER_PG);iounmap(GPIO_OSPEEDR_PG);iounmap(GPIO_PUPDR_PG);iounmap(GPIO_BSRR_PG);//删除字符设备cdev_del(&led.chrdev);//释放主设备号unregister_chrdev_region(led.devid, 1);//销毁设备节点device_destroy(led.class, led.devid);//销毁类class_destroy(led.class);
}
module_init(led_init);            //模块入口申明
module_exit(led_exit);            //模块出口申明
MODULE_LICENSE("GPL");            //GPL协议申明

以上程序在操作端口电平时使用了BSRR寄存器,如果要操作ODR寄存器,则上述代码中的led_write函数可换成如下的形式即可。

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{unsigned char value;unsigned int tmp;unsigned long n;n = copy_from_user(&value, buf, cnt);    //从应用空间获取值switch(value)            //根据应用空间的值判断具体操作
  {case 0:                        //全部点亮三个LEDtmp = ioread32(GPIO_ODR_PA);tmp &= ~0x2000;iowrite32(tmp, GPIO_ODR_PA);tmp = ioread32(GPIO_ODR_PB);tmp &= ~0x20;iowrite32(tmp, GPIO_ODR_PB);tmp = ioread32(GPIO_ODR_PG);tmp &= ~0x04;iowrite32(tmp, GPIO_ODR_PG);break;case 1:                        //点亮红色LEDtmp = ioread32(GPIO_ODR_PA);tmp &= ~0x2000;iowrite32(tmp, GPIO_ODR_PA);break;case 2:                        //点亮绿色LEDtmp = ioread32(GPIO_ODR_PG);tmp &= ~0x04;iowrite32(tmp, GPIO_ODR_PG);break;case 3:                        //点亮蓝色LEDtmp = ioread32(GPIO_ODR_PB);tmp &= ~0x20;iowrite32(tmp, GPIO_ODR_PB);break;case 4:                        //熄灭红色LEDtmp = ioread32(GPIO_ODR_PA);tmp |= 0x2000;iowrite32(tmp, GPIO_ODR_PA);break;case 5:                        //熄灭绿色LEDtmp = ioread32(GPIO_ODR_PB);tmp |= 0x20;iowrite32(tmp, GPIO_ODR_PB);break;case 6:                        //熄灭蓝色LEDtmp = ioread32(GPIO_ODR_PG);tmp |= 0x04;iowrite32(tmp, GPIO_ODR_PG);break;case 7:                        //全部熄灭三个LEDtmp = ioread32(GPIO_ODR_PA);tmp |= 0x2000;iowrite32(tmp, GPIO_ODR_PA);tmp = ioread32(GPIO_ODR_PB);tmp |= 0x20;iowrite32(tmp, GPIO_ODR_PB);tmp = ioread32(GPIO_ODR_PG);tmp |= 0x04;iowrite32(tmp, GPIO_ODR_PG);break;default:                        //全部熄灭tmp = ioread32(GPIO_ODR_PA);tmp |= 0x2000;iowrite32(tmp, GPIO_ODR_PA);tmp = ioread32(GPIO_ODR_PB);tmp |= 0x20;iowrite32(tmp, GPIO_ODR_PB);tmp = ioread32(GPIO_ODR_PG);tmp |= 0x04;iowrite32(tmp, GPIO_ODR_PG);break;}return 0;
}

驱动程序属于内核态程序,所以还需要配套一个Makefile文件,其内容如下。

KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := led.o
all:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

其中的第一行指定了开发板的内核源代码位置,若放到了其他路径可自行修改。

以下是测试用的应用程序代码,文件名为app.c。

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int fd;unsigned char val = 0;fd = open("/dev/led", O_RDWR);if( fd < 0 )printf("can`t open\n");if( argc != 3 ){printf("Usage :\n");printf("%s <all|red|green|blue> <on|off>\n", argv[0]);return 0;}if(strcmp(argv[1], "all") == 0){if(strcmp(argv[2], "on") == 0)val = 0;elseval = 7;}else if(strcmp(argv[1], "red") == 0){if(strcmp(argv[2], "on") == 0)val = 1;elseval = 4;}else if(strcmp(argv[1], "green") == 0){if(strcmp(argv[2], "on") == 0)val = 2;elseval = 6;}else if(strcmp(argv[1], "blue") == 0){if(strcmp(argv[2], "on") == 0)val = 3;elseval = 5;}write(fd, &val, 1);close(fd);return 0;
}

完成后,先执行make命令编译驱动程序,若成功会生成名为led.ko的驱动模块文件。然后对应用程序进行交叉编译,执行“arm-linux-gnueabihf-gcc app.c -o app”即可。

完成后,把编译生成的驱动模块文件led.ko和应用程序文件app一起拷贝到NFS共享目录下 。然后在开发板上执行“insmod led.ko”,把模块插入到内核中,可执行lsmod命令查看一下是否加载成功,并查看一下/dev目录是否已经生成了设备节点文件led。此外,还可以执行“cat /proc/devices”查看一下led设备的主设备号。

以上都正常后,就可以执行应用程序来进行测试了。输入“./app all on”并回车,可看到开发板上三个LED全部点亮,如下图所示。

再输入“./app all off”并回车,可看到它们又全部熄灭,如下图所示。

还可以执行其他命令,来点亮或熄灭单独的LED,比如,单独点亮红色的LED,可执行命“./app red on”,熄灭执行命令“./app red off”等等,以此类推进行检查验证,此处就不再赘述了。

关于驱动程序的具体原理,会在后面再单独作讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708883.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BUUctf xor

0x01 关于xor xor,即为计算机中的异或计算,相同为0,不同为1。 下面是关于异或加密的四个定理A ^ 0 = A A ^ A = 0 (A ^ B) ^ C = A ^ (B ^ C) (B ^ A) ^ A = B ^ 0 = B // 明文 B;密码 A观察可知,经历异或加密后的密文,再次进行异或算法即可得到明文。 0x02 题解 先丢进…

解决VSCode中Debug和运行路径不一致的

哈喽,大家好,我是木头左!背景介绍 在Visual Studio Code(简称VSCode)中进行开发时,经常需要使用到调试(Debug)功能。然而,有时候会发现,当尝试调试程序时,程序的运行路径与预期不符。这通常会导致程序无法正确读取文件或访问资源,从而影响调试过程。为了解决这个问…

DockerDesktop安装指南以及Windows下WSL2和 Hyper-V相关问题追查

文章原创不易,转载请注明来源 ,谢谢! 一、 问题 周末在家,给自己的老的台式机安装DockerDesktop。 电脑配置是处理器 Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz 3.30 GHz机带 RAM 16.0 GB (15.9 GB 可用)系统类型 64 位操作系统, 基于 x64 的处理器版本 Windows 10 专业版…

软件设计原则—接口隔离原则

B类需要方法1好处是b类继承A类后就有了方法1的功能,问题是B类被迫有了它不使用的方法2 这个其实是根据方法的职责细分接口,只需要依赖其中一个接口就可以了客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。 下面看一个例子来理解接口隔…

科翼阁:网赚广告套路千千万,美食沙雕占大半

在网赚的世界里,有70%是新手,20%是刚上手不久的,但这类人能坚持在网赚这个行业持续发展的机率只有10%,所以只有10%的能在网赚这个行业中进行持久战的,也有只这类人能真正在网赚中赚到大钱的人,网赚需要坚持,中途退出的人绝对无法在网赚这行中得到赢利。在国外,由于网赚…

关于 双向不循环列表的创建、插入、删除、遍历、检索、销毁

双向循环链表公式双向不循环链表代码 #include <stdio.h> #include <stdlib.h> #include <string.h>//宏定义一个数据域 #define DATA_LEN 60//双向不循环链表的节点定义 typedef struct double_link_list {//数据域char data[DATA_LEN]; // 数据…

软件设计原则—依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。 简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。下面看一个例子来理解依赖倒转原则:组装电脑 现要组装一台电脑,需要配件cpu,硬盘,…

关于没有熔断降级导致服务重启问题

场景 1.k8s微服务触发重启 容器配置的健康检查采用actuator curl 127.0.0.1:8080/actuator/health 2.容器重启钩子回调curl -X POST http://127.0.0.1:8080/actuator/shutdown 最终原因是因为调用第三方服务,超时设置3秒,重试3次,三方服务挂起导致tomcat连接池占满,健康检查…

stm32 bootloader的app中断向量偏移设置,HAL库

如何设置Hal库的中断向量偏移看前几篇的 stm32f103c8t6 HAL库更改中断向量表(app部分) - 这一切足够了 - 博客园 (cnblogs.com)我这里bootloader的APP开始地址就是0x08006000,中断向量偏移0x00006000 设置完成之后编译mdk,将生成的bin文件使用ymodem写入0x08006000中 这里设…

c语言程序实验————实验报告九

c语言程序实验————实验报告九实验项目名称: 实验报告8 字符串处理函数 实验项目类型:验证性 实验日期:2024 年 5 月 16 日一、实验目的 1.掌握定义函数的方法 2.掌握函数调用、实参与形参的对应关系、参数的传递方式 3.掌握函数的嵌套调用和递归调用的方法 4.掌握全局变…

Flink精确消费一次

在大数据计算里面,计算引擎是处于承上启下的作用,对上承接数据源,对下承接各种各种数据库,比如mysql、oracle。对于任何数据计算来说要想精确消费一次,就需要支持事务或者幂等,我们最常见的支持事务的就是单点的oracle、mysql数据库,那么Flink作为分布式计算引擎,是如何…

电子传输系统安全-进展1

实验二 电子传输系统安全-进展1 上周任务完成情况完成了上学期电子公文传输系统的重新调试通过 部署了bouncycastle 学习了bouncycastle 将jar包添加到依赖项本周计划将上学期电子公文传输系统重新调试通过 部署bouncycastle 学习bouncycastle 将jar包添加到依赖项参考链接Boun…