嵌入式字符设备驱动——ULN2003步进电机驱动程序实现

之前分享了字符设备驱动程序的实现——hello驱动,是不涉及硬件操作的,我说过要给大家分享一篇涉及硬件操作的字符设备驱动程序的实现,今天周末休息,就把我之前挖的坑给大家填上,本来我打算先给大家分享一个最简单的涉及硬件操作的设备驱动程序的实现——按键/LED驱动的,把这个步进电机的驱动程序留给你们后面先自己做一下,想了想其实都是最基本的,就直接上步进电机吧,下面我们开始学习步进电机驱动程序的实现吧!!

字符设备驱动程序实现的步骤

这个我之前讲过了,再温习一遍

1.确定主设备号,一般设置major = 0,让内核进行自动分配
2.定义一个属于自己的file_operations结构体,这个结构体内定了我们要实现的功能函数
3.实现file_operations结构体内定义的功能函数
4.把file_operations结构体告诉内核:register_chrdev()
5.注册驱动程序,再入口函数中注册,安装设备驱动时,会首先调用这个函数
6.出口函数,卸载驱动程序时,会调用这个函数,在出口函数中卸载驱动,unregister_chrdev()
7.创建设备信息,比如设备节点,设备名称:class_create(), device_create(),便于对设备进行操作

开发板与ULN2003步进电机

这里我用的是stm32mp157开发板,操作系统内核为Linux5.4,给我们预留了一些可供我们使用的GPIO引脚,我这里是GPIOA5、GPIOA13、GPIOD8、GPIOC3,你的开发板肯定和我的不一样,但是只要有四个引脚可用你用就可以了。

ULN步进电机的原理图如下所示,步进电机的驱动原理是依次给IN1->IN2->IN3->IN4输入高电平实现正转,依次给IN4->IN3->IN2->IN1输入高电平实现反转。

现在开发板和步进电机的原理图已经给大家介绍了,依次按照引脚顺序完成硬件连接。我这里的顺序为GPIOA5——IN1,GPIOA13——IN2,GPIOD8——IN3,GPIOC3——IN4。

STM32MP157开发板的GPIO操作函数

首先STM32MP157开发板是双核的,A7和M4,一般A7是用来嵌入式开发的,M4用来做单片机裸机开发,两个核所对应的GPIO的寄存器地址是不一样的,但位定义一样。下面我以其中一个GPIOA5引脚为例来介绍STM32MP157的GPIO的操作,你的开发板怎么操作去参看它的芯片手册即可,原理都是一样的。

1.首先我们需要使能时钟

通过芯片手册可以看到时钟寄存器为RCC_PLL4CR,地址偏移为0x894,在芯片寄存器内存分配章节我找到它的基地址为0x50000000,所以就能得到RCC_PLL4CR的寄存器地址为0x50000000+0x894。从下面可以看出赋值1时使能。

我这里把RCC、GPIOA、GPIOC、GPIOD的基地址都给出来后面我就不介绍了

2.使能所用的引脚

GPIOA5属于GPIOA里的第十个引脚,我们需要使能GPIOA,从下面可以看出RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28,将此寄存器的第一位使能就使能了GPIOA了。

3.设置GPIOA5的模式

模式设置寄存器为GPIOA_MODER,它的地址为GPIOA_MODER地址:0x50002000 + 0x00。设置GPIOA5为输出模式,即设置bit[11:10] = 0b01。

4.设置GPIOA5的输出电平

上面已经设置了GPIOA5为输出模式了,现在要能给它设置高低电平了,STM32MP157中有一个直接写寄存器。GPIOA_BSRR,地址为0x50002000 + 0x18。

掌握了GPIO的操作之后我们就可以开始写步进电机的驱动程序啦!!

步进电机驱动程序实现

我们按照字符设备驱动程序实现的步骤来一步一步写:

1.主设备号

static int major = 0;//声明主设备号,让内核自动分配

2.定义file_operations结构体

static struct file_operations mtr_drv_fops = {.owner = THIS_MODULE,.open = mtr_drv_open,.write = mtr_drv_write,.release = mtr_drv_close,
};

3.实现file_operations结构体中的功能函数

static int mtr_drv_open(struct inode *node, struct file *file){//我们在打开函数中实现对GPIO引脚的初始化操作//首先设置使能时钟*RCC_PLL4CR |= (1<<0);//使能GPIOA、GPIOD、GPIOC*RCC_MP_AHB4ENSETR |= ((1<<0) + (1<<2) + (1<<3));//设置为输出模式//设置GPIOA5、GPIOA13*GPIOA_MODER &= ~((3<<10) + (3<<26));*GPIOA_MODER |= ((1<<10) + (1<<26));//设置GPIOC3*GPIOC_MODER &= ~(3<<6);*GPIOC_MODER |= (1<<6);//设置GPIOD8*GPIOD_MODER &= ~(3<< 16);*GPIOD_MODER |= (1<<16);return 0;
}static ssize_t mtr_drv_write(struct file *file, const char __user *user, size_t size, loff_t *offset){//在写函数中实现对GPIO的输出高电平//从应用程序中得到指令写入到引脚char kbuf;copy_from_user(&kbuf, user, 1);switch(kbuf){//依次赋高电平case 1:*GPIOC_BSRR = (1 << 19);*GPIOD_BSRR = (1 << 24);*GPIOA_BSRR = (1 << 29);*GPIOA_BSRR = (1 << 5);break;}case 2:*GPIOC_BSRR = (1 << 19);*GPIOA_BSRR = (1 << 21);        *GPIOD_BSRR = (1 << 24);*GPIOA_BSRR = (1 << 13);break;case 3:*GPIOA_BSRR = (1 << 21);      *GPIOA_BSRR = (1 << 29);*GPIOD_BSRR = (1 << 24);*GPIOC_BSRR = (1 << 3);break;case 4:*GPIOA_BSRR = (1 << 21);        *GPIOA_BSRR = (1 << 29);*GPIOC_BSRR = (1 << 19);*GPIOD_BSRR = (1 << 8);break;default:break;}return 0;
}static int mtr_drv_close(struct inode *node, struct file *file){return 0;
}

4.入口函数,注册file_operations

static int __init mtr_drv_init(void){printk("%s %s LINE %d\n", __FUNCTIN__, __FILE__, __LINE__);if(!RCC_PLL4CR){//如果没有映射则映射,已经映射了就不再映射了//映射ioremap()硬件物理地址//时钟寄存器地址映射RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);//使能GPIO寄存器地址映射RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);//设置GPIOA工作方式寄存器地址映射GPIOA_MODER = ioremap(0x50002000 + 0x00, 0x18);//设置GPIOC工作方式寄存器地址映射GPIOC_MODER = ioremap(0x50004000 + 0x00, 4);//设置GPIOD工作方式寄存器地址映射GPIOD_MODER = ioremap(0x50005000 + 0x00, 4);//设置GPIOA写寄存器地址映射GPIOA_BSRR = ioremap(0x50002000 + 0x18, 0x18);//设置GPIOC写寄存器地址映射GPIOC_BSRR = ioremap(0x50004000 + 0x18, 4);//设置GPIOD写寄存器地址映射GPIOD_BSRR = ioremap(0x50005000 + 0x18, 4);}//注册file_operations结构体major = register_chrdev(0, "mtr_drv", &mtr_drv_fops);//设备名mtr_drv_class = class_create(THIS_MODULE, "mtr_drv");device_create(mtr_drv_class, NULL, MKDEV(major, 0), NULL, "mtr_drv0");//设备节点return 0;
}

5.出口函数

static void __exit mtr_drv_exit(void){iounmap(RCC_PLL4CR);iounmap(RCC_MP_AHB4ENSETR);iounmap(GPIOA_MODER);iounmap(GPIOC_MODER);iounmap(GPIOD_MODER);iounmap(GPIOA_BSRR);iounmap(GPIOC_BSRR);iounmap(GPIOD_BSRR);device_destroy(mtr_drv_class, MKDEV(major, 0));class_destroy(mtr_drv_class);unregister_chrdev(major, "mtr_drv");return;
}

6.其他补充

//告诉内核谁是入口、出口函数
module_init(mtr_drv_init);
module_exit(mtr_drv_exit);
MODULE_LICENSE("GPL");

还有一些声明:

static struct class* mtr_drv_class;
//寄存器声明
static volatile unsigned int *RCC_PLL4CR;
static volatile unsigned int *RCC_MP_AHB4ENSETR;
static volatile unsigned int *GPIOA_MODER;
static volatile unsigned int *GPIOC_MODER;
static volatile unsigned int *GPIOD_MODER;
static volatile unsigned int *GPIOA_BSRR;
static volatile unsigned int *GPIOC_BSRR;
static volatile unsigned int *GPIOD_BSRR;

到这里整个步进电机的驱动程序就完成了。

在Ubuntu中用交叉编译工具编译成hello_drv.ko文件,就可以在开发板上运行.

insmod mtr_drv.ko:加载安装驱动程序
ls /dev/:查看设备节点,看是否有我们安装的hello设备节点
cat /proc/devices:查看所有的设备文件,包括主设备号和设备名
lsmod:查看已经加载的设备驱动
rmmod mtr_drv:卸载驱动

测试程序和完整驱动程序的源码我都放在公众号中了,需要的关注公众号回复“步进电机驱动程序”即可获取。有什么问题可以在下方留言,我看到之后会回复你。

我是河边小乌龟爬,学习嵌入式软件开发路上的一名小学生,欢迎大家相互交流哇。公众号:河边小乌龟爬。QQ群名称:嵌入式软件开发群:1004953094。

嵌入式字符设备驱动——ULN2003步进电机驱动程序实现相关推荐

  1. linux内核创建字符节点,Tiny6410学习ing—(四)、嵌入式Linux内核驱动进阶—(7)、高级字符设备驱动(自动创建节点)—#931...

    按照国嵌的视频教程上来说的,最后就是-自动创建设备文件! 其实我感觉以前完全可以直接是手动创建了设备文件,然后就可以直接讲述自动创建设备文件,为啥非要拖到最后来讲述,我也就不清楚了!! 不管了,写完收 ...

  2. 嵌入式linux驱动之———字符设备驱动(一)

    一.简介: 在Linux内核驱动中,字符设备是最基本的设备驱动.字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以子为单位的.比如串口在进行收发数据时就是一个字节一个字节进行的. ...

  3. 【正点原子MP157连载】第二十二章 新字符设备驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  4. linux设备驱动程序jd,Linux设备驱动程序学习(基于2440的GPIO字符设备驱动)

    基于2440的GPIO字符设备驱动及应用程序是针对2440型号的底板的驱动及测试应用程序,详细情况请见底板的PCB图. S3C2440提供130 路复用的IO口线,分为如下端口进行管理: - Port ...

  5. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

  6. Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

    字符设备模拟pipe的驱动程序 让我们用一个"pipe"的设备驱动去结束简单字符设备吧(这里所说的pipe并非标准的pipe只是模拟了一个从一端写入从另一端写入的设备) 测试代码1 ...

  7. 《Linux设备驱动程序》学习2—高级字符设备驱动ioctl

    今天进入<Linux设备驱动程序>第六章高级字符设备驱动程序操作的学习,学习的过程和简单字符设备驱动程序的学习是一样的,看书,看程序,然后就是看Tek的博客笔记.依然tek的博客中对于这一 ...

  8. linux下camera驱动分析_《Linux设备驱动程序》(五)——字符设备驱动(下)

    上一节介绍了字符驱动中的一些概念,这一节我们将会基于系统内存编写一个字符设备驱动,加深对上一节中的概念的理解. 本节主要学会的内容: 字符设备注册 对设备节点进行cat和echo操作 驱动设计 编写驱 ...

  9. 嵌入式linux字符设备驱动

    1. 我们需要先调用register_chrdev_region()或 alloc_chrdev_region()来向系统申请设备号 int register_chrdev_region( dev_t ...

  10. 字符设备驱动0:一个简单但完整的字符设备驱动程序

    参考: linux设备驱动程序之简单字符设备驱动 [很详细,必看]http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html //在驱动 ...

最新文章

  1. 多目标跟踪(MOT)入门
  2. python软件安装-学python安装的软件总结
  3. 科大星云诗社动态20210819
  4. linux如何生成so文件,新人问个问题,莫见笑:关于如何生成so文件,大家多多捧场啊...
  5. 使用String.intern()减少内存使用
  6. java remove(index)_方法removeElementAt(int index)在Java中做什么?
  7. 二叉树题目----5 平衡二叉树 AND 根据二叉树创建字符串
  8. 量数据导出Excel 之 多重影分身之术
  9. 19年全国数学建模比赛A题代码(简单的迭代思想)
  10. leetcode—25.链表排序题目leetcode总结
  11. C++程序设计课程主页-2014级
  12. matlab输出 inf,为什么输出的是-inf,不应该是个值吗
  13. NOI 1.11(02)二分法求函数的零点
  14. 学习搜狗workflow心路历程(1)Windows版本的环境搭建
  15. java画星星_用Java 做一个星星图案
  16. 网站被降权的6种处理方法
  17. mysql union update_MYSQL:union, 以及常用函数
  18. VoLTE、VoWiFi和VoIP有什么不同?
  19. 应用程序日志管理工具
  20. Python 编码规范 PEP8

热门文章

  1. 思科模拟器Cisco Packet Tracer的中文安装 [含安装包]
  2. 虚拟主机安装php,php网站怎么安装到虚拟主机
  3. 多元函数微分法及其应用
  4. 腾讯程序员不寻常的三年
  5. word文本框文字垂直居中_如何在Microsoft Word中的页面上垂直居中放置文本
  6. python从excel读取数据用matplotlib画平面折线图
  7. 情侣天气推送升级简单版 项目上传github实现定时自动推送教程
  8. 机器人控制器编程实践指导书旧版-实践一 LED灯(数字量)
  9. Word目录怎么自动生成?Word文档怎么自动生成目录列表
  10. Matlab 导入数据操作