要操控蜂鸣器,首先我们需要找到这个器件的原理图,从而找到芯片中控制这个器件的管脚,于是我们可以先去开发板的电路原理图中搜索该器件,找到原理图:

可以看到FS4412开发板使用了其中一路PWM输出(PWM0,对应管脚为GPD0.0)接蜂鸣器。

PWM,脉宽调制器,就是一个输出脉冲宽度可以调整的硬件器件,它不仅脉冲宽度可调,频率也可以调整,核心部件是一个硬件定时器。PWM管脚默认输出高电平。

上图可以说明PWM工作原理,在时刻1设置计数值为160,比较值设置也为109,时刻2启动定时器,PWM输出低电平,计数器开始做减法,当计数值减到和比较值一致时,输出反转,当计数达到0,完成一次计数。形成一个波形,波形周期由计数值决定,占空比由比较值决定。我们可以通过调整这两个值使蜂鸣器发出特有频率的声音。

然后我们在芯片手册中找到pwm0内部结构图:

可以看到输入时钟为PCLK,经过八位预分频和第二次的再分频的时钟最终给到PWM0所对应的计数器0,TCNTB0是计数寄存器,用于控制PWM输出波形的频率,TCMPB0是比较寄存器,用于控制PWM输出波形的占空比。通过查看这些寄存器,并对他们进行配置,我们可以完成对PWM的驱动代码的硬件部分的编写。

首先再回顾以下我们linux字符设备驱动的编写框架:

1.实现入口函数 xxx_init() 和卸载函数 xxx_exit()
2.申请设备号 alloc_chrdev_region (与内核相关)
3.注册字符设备驱动 cdev_alloc,cdev_init,cdev_add (与内核相关)
4.利用 udev/mdev 机制创建设备文件(节点) class_create,device_create(与内核相关)
5.硬件部分初始化

从设备树获取硬件资源platform_get_resource()
        IO 资源映射 ioremap ,内核提供 gpio库函数(与硬件相关)
        注册中断(与硬件相关)
        初始化等待队列(与内核相关)
        初始化定时器(与内核相关)
6.构建 file_operation 结构(与内核相关)
7.实现操作硬件的方法 xxx_open,xxx_read,xxx_write…(与硬件相关)

首先我们将驱动的框架搭建好,再进行填充

头文件,查找对应的头文件我们可以使用多种方法,可以在内核源码目录建立索引使用ctags,进行搜索查找,或者使用soureinsight进行查找。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <asm/io.h>

定义变量来接收对应寄存器的映射地址,以及设备号设备名等等。

unsigned int *gpd0con;
unsigned int *tcfg0;
unsigned int *tcfg1;
unsigned int *tcon;
unsigned int *tcntb0;
unsigned int *tcmpb0;static struct resource *rescon;//接收设备树中状态寄存器资源
static struct resource *resdata;//接收设备树中数据寄存器资源
static dev_t devnum;//设备号
static char *name ="mybuzzer";//设备名unsigned int *gpx1con;
unsigned int *gpx1data;

声明一些函数,在我们的驱动和设备匹配成功后,会调用probe方法,在移除驱动时,会调用remove方法,所以我们对应的字符设备申请,注册,初始化,硬件资源获取,地址映射等都在probe种执行,remove种执行相应的注销销毁释放,解除映射等操作。

//实现platform_driverint my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args);//IO操作函数static struct cdev mycdev ;
static struct class * myclass;//方便多个相同设备管理
static struct device * mydevice;static struct file_operations myops={};

入口函数和卸载函数,不用再将硬件操作放在加载模块部分,这样更好体现了分离的思想,加载和卸载模块只需要进行驱动的注册和注销操作即可。

static int mod_init(void)
{return  platform_driver_register(&mydriver);  //平台驱动注册
}
static void mod_exit(void)
{platform_driver_unregister(&mydriver);        //平台驱动注销
}
module_init(mod_init);//注册
module_exit(mod_exit);
MODULE_LICENSE("GPL");//模块信息

定义平台驱动对象并初始化,设置匹配规则为对应的资源通过设备树进行获取,当然我们需要到相应的设备树dts文件中,进行修改,使我们的名字能够匹配上,并且寄存器的地址也要修改和我们芯片手册上地址一样才行,然后编译得到对应的dtb文件。

//定义platform_driver对象
struct of_device_id of_matches[]={{.compatible="fs,mybee"},           //修改匹配规则,从设备树  {},                                 //获取buzzer相关硬件信息
};
static struct platform_driver mydriver ={.probe = my_probe,.remove = my_remove,.driver = {.name = "mytest",               .of_match_table =  of_matches,  //通过设备树匹配},}; 

修改设备树文件内容,名字要和我们驱动代码中一样,才能进行匹配,寄存器地址要和芯片手册对应管脚位置一样。

获取硬件资源,由于这几个寄存器地址都是连续的所以我们只需要获取最开始的地址然后进行偏移即可,随后建立映射,字符设备驱动的注册等操作

int my_probe(struct platform_device *pdev)
{int ret;//通过设备树获取 硬件资源printk("match\n");rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);if(rescon==NULL){goto failed_getcon;}printk("%#x\n",rescon->start);gpd0con = ioremap(rescon->start,4);//建立映射resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);if(resdata==NULL){goto failed_getdata;}printk("%#x\n",resdata->start);tcfg0 = ioremap(resdata->start,4);tcfg1 = ioremap(resdata->start+4,4);tcon = ioremap(resdata->start+8,4);tcntb0 = ioremap(resdata->start+12,4);tcmpb0 = ioremap(resdata->start+16,4);//字符设备注册ret =  alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号if(ret!=0){goto failed_alloc;}cdev_init(&mycdev,&myops); //2.cdev初始化ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核if(ret!=0){goto failed_add;}printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));myclass = class_create(THIS_MODULE,"myclass");if(IS_ERR(myclass)){goto failed_class;}mydevice = device_create(myclass,NULL,devnum,NULL,"buzzer");if(IS_ERR(mydevice)){goto failed_device;}//硬件操作buzzer_init();buzzer_off();
#endif return 0;//对应的操作失败函数
failed_device:class_destroy(myclass);
failed_class:cdev_del(&mycdev);
failed_add:unregister_chrdev_region(devnum,1);
failed_alloc:return -1;
}

卸载时对应的操作,与我们在probe方法中进行的操作恰好相反,由地址映射,先接触映射,然后销毁设备,销毁类,删除字符设备,然后注销设备号。

int my_remove(struct platform_device *pdev)
{printk("driver remove\n");iounmap(gpx1con);//解除映射iounmap(gpx1data);device_destroy(myclass,devnum);//销毁设备class_destroy(myclass);//销毁类cdev_del(&mycdev);//删除字符设备unregister_chrdev_region(devnum,1);//注销设备号return 0;
}

硬件操作pwm初始化,这里的操作就按照芯片手册对应寄存器的设置进行相应的位操作,这里我们将管脚GPD0.0设置为功能2(TOUT_0),即PWM0的输出不上拉,驱动强度为最低级别。随后进行预分频,二分频等操作。

int buzzer_init(void)
{u32 tmp;tmp = readl(gpd0con);tmp &= ~0xf;tmp |= 0x2;writel(tmp,gpd0con);tmp = readl(tcfg0);tmp |= 0xff;writel(tmp,tcfg0);tmp = readl(tcfg1);tmp &= ~0xf;tmp |= 0x3;writel(tmp,tcfg1);writel(110,tcntb0);writel(110/2,tcmpb0);tmp = readl(tcon);tmp |= 0x1<<3;tmp |= 0x1<<1;writel(tmp,tcon);tmp = readl(tcon);tmp &= ~(0x1<<1);writel(tmp,tcon);return 0;
}

硬件操作,为应用层操作启动PWM,停止PWM提供方法。

int buzzer_on(void)
{u32 tmp;printk("buzzer_on\n");tmp = readl(tcon);tmp |= 0x1;writel(tmp,tcon);return 0;
}
int buzzer_off(void)
{u32 tmp;printk("buzzer_off\n");tmp = readl(tcon);tmp &= ~0x1;writel(tmp,tcon);return 0;
}

当应用层调用IO操作时就会调用到我们写好的底层驱动代码,实现相应的命令操作。

#define BUZZER_ON  _IO('B',1)//命令码
#define BUZZER_OFF _IO('B',2)static struct file_operations myops={.unlocked_ioctl = my_ioctl,
};long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args)
{switch (cmd){case BUZZER_ON:  buzzer_on(); break;case BUZZER_OFF: buzzer_off();break;default:  return -1;}return 0;
}

linux驱动开发:PWM驱动编写相关推荐

  1. Linux驱动开发 -- touch驱动注册

    Linux i2c驱动开发 – touch 驱动 文章目录 Linux i2c驱动开发 -- touch 驱动 前言 一.i2c 驱动框架 二.Linux的MODULE声明 1. MODULE相关声明 ...

  2. Linux驱动开发——串口设备驱动

    Linux驱动开发--串口设备驱动 一.串口简介 串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单.使用两条线即可实现双向通信,一条用于发送,一条用于 ...

  3. Linux嵌入式驱动开发02——驱动编译到内核

    文章目录 全系列传送门 make menuconfig图形化配置界面 1. 怎么进入到make menuconfig图形化界面? 2. make menuconfig图形化界面的操作 3. 退出 4. ...

  4. 解析Linux内核源码中数据同步问题丨C++后端开发丨Linux服务器开发丨Linux内核开发丨驱动开发丨嵌入式开发丨内核操作系统

    剖析Linux内核源码数据同步 1.pdflush机制原理 2.超级块同步/inode同步 3.拥塞及强制回写技术 视频讲解如下,点击观看: 解析Linux内核源码中数据同步问题丨C++后端开发丨Li ...

  5. STM32MP157驱动开发——SPI驱动

    STM32MP157驱动开发--SPI驱动 一.简介 1.SPI介绍 2.STM32MP1 SPI介绍 3. ICM-20608 简介 4.Linux下的SPI框架 二.驱动开发 1)IO 的 pin ...

  6. STM32F427库函数配置DMA驱动TIM5 PWM驱动WS2812B单总线全彩RGB

    STM32F427库函数配置DMA驱动TIM5 PWM驱动WS2812B单总线全彩RGB 系列文章传送门: STM32F4多路PWM DMA控制千颗WS2812/SK6812配置过程全解析 STM32 ...

  7. Linux驱动开发(从零开始编写一个驱动程序)

    1.系统整体工作原理 (1)应用层->API->设备驱动->硬件 (2)API:open.read.write.close等 (3)驱动源码中提供真正的open.read.write ...

  8. linux驱动开发音频设备驱动,linux驱动开发—基于Device tree机制的驱动编写

    摘要:媒介 Device Tree是一种用去描绘硬件的数据布局,类似板级描绘说话,发源于OpenFirmware(OF).正在现在遍及应用的kernel 2.6.x版本中,对分歧仄台.分歧硬件,往] ...

  9. Linux 设备驱动开发思想 —— 驱动分层与驱动分离

    前面我们学习I2C.USB.SD驱动时,有没有发现一个共性,就是在驱动开发时,每个驱动都分层三部分,由上到下分别是: 1.XXX 设备驱动 2.XXX 核心层 3.XXX 主机控制器驱动 而需要我们编 ...

  10. linux摄像头内核驱动开发,怎么在Linux下开发摄像头驱动

    无根之木不活,无基之楼不立,无论是学习哪个领域知识,基础是重中之重. 针对学习linux驱动,我们来仔细谈谈: 个人认为C语言和数据结构就是重中之重!Linux系统最优秀的地方就在于内核.无论是进程调 ...

最新文章

  1. 第17章 使用iSCSI服务部署网络存储
  2. 【原创 HadoopSpark 动手实践 6】Spark 编程实例与案例演示
  3. reset.css页面样式初始化
  4. oracle 日期6,EF 6与Oracle - 如何加入日期字段?
  5. Anaconda是什么?Anconda下载安装教程 - Python零基础入门教程
  6. python嵌套列表操作_python基础(list列表的操作,公共方法,列表嵌套,元祖)...
  7. 保姆级带你深入阅读NAS-BERT
  8. 人工智能(17)----人工智能视频监控 实用性有待提高
  9. mysql+8.0+新特性_MySQL 8.0备受瞩目的新特性大放送!
  10. Flutter进阶—质感设计之表单输入
  11. Mac OSX上卸载Anaconda
  12. SHFileOperation 文件拷贝、移动、删除等操作
  13. 稳定触发windows蓝屏的路径漏洞,不要用来整人!
  14. 一个电源工程师要学哪些软件?
  15. TEM测试常见问题及解答(五)
  16. 模拟信号数字化传输系统的设计与仿真分析说明书
  17. 计算机里的硬盘怎么隐藏,Win10系统如何隐藏“此电脑”中的某个本地磁盘
  18. 用html5画瀑布图,漂亮的Excel瀑布图,竟然如此简单~~
  19. 以下不属于大气数据计算机系统的传感器是,下列不属于地理信息技术的是 A. 遥感     B.传感器     C. 全球定位系统   D. 地理信息系统——青夏教育精英家教网——...
  20. Windows7安装mysql-压缩包方式

热门文章

  1. 用Python做单变量数据集的异常点分析
  2. 在C#中设置打印机纸张大小
  3. 2012网易校园招聘笔试题
  4. java foreach 空指针_foreach循环报NPE空指针异常
  5. C语言输入三角形三条边边长 算三角形面积
  6. 最近在做一些改变,想听听你的意见
  7. 深度学习论文: An Energy and GPU-Computation Efficient Backbone Network for Object Detection及其PyTorch
  8. RSRP RSRQ RSSI SNR的定义
  9. 简单笔记(rsrp/mbps/session/dialog/dbm)
  10. 分析界面,在全国公共资源交易平台使用java获取全国的招投标数据接口