pwm驱动原理和代码实现

学这个pwm真是非常曲则,首先是看s3c2440的datasheet,全英文的,而且还有硬件的时序图(很多是硬件的工作原理,和软件控制不相关)。看了很久加上网上看了资料才把这个pwm弄通。当然,其中牵扯到了几个知识,基本都弄通了。后面会通过blog一一列出来。

第一个知识点:I/O映射和内存映射所牵扯到的知识点,包括统一编址和独立编址,以及linux下怎么对这两种方式编程,以及这两种方式下怎么访问外设。

第二个知识点:映射到内存哪里?怎么映射?所以就涉及到linux内核的内存分布问题,顺便也分析了几个内核内存分配函数的区别。

这里对几个涉及到的知识点不展开来分析,后面会详细讲解下。这里只对pwm的工作原理和驱动分析下。

我最开始有写个简单的峰鸣器驱动,不能调频率的:s3c2440 杂项驱动实现蜂鸣器里面用杂项设备驱动使峰鸣器工作,当然里面都是调用了s3c2440下提供的读写函数。这个对移植来说不是很好,我这篇blog是用通用的函数从底层一步步使pwm工作的。

首先是说下mini2440,我用的开发板是mini2440的,也就是s3c2440处理器。最开始我还不知道s3c2440是一款cpu,我在linux源码的 arch/arm平台中找到了s3c2440,然后我才找资料了解了下s3c2440处理器。其中主要的是了解s3c2440的I/O编址,s3c2440是统一编址其实就是内存映射了。

s3c2440提供了__raw_readl() 等函数来读写I/O,我在s3c2440系统自带的管脚宏和函数blog也分析过这些函数的源码(好像有点乱),我这里不用s3c2440提供的系统I/O操作函数,自己映射地址,用通用的ioread()系列函数来操作端口;

首先是pwm的工作原理,这个可以看下我转载的一篇blog,简明扼要的讲清楚了pwm的工作原理:pwm的工作原理;当然也可以看看芯片的datasheet,总之看懂了就感觉很容易了。下面直接上代码:

regAddr.h代码

 #ifndef __REG_ADDR_H__
 #define __REG_ADDR_H_

 /*
#define GPBCON ((volatile unsigned long*)0x56000010)
#define GPBDAT ((volatile unsigned long*)0x56000014)

#define TCFG0 ((volatile unsigned long *)0x51000000)
#define TCFG1 ((volatile unsigned long *)0x51000004)

#define TCON  ((volatile unsigned long *)0x51000008)

#define TCNTB0  ((volatile unsigned long *)0x5100000c)
#define TCMPB0  ((volatile unsigned long *)0x51000010)
#define TCNTO0  ((volatile unsigned long *)0x51000014)
 */

 #define GPBCON ((unsigned long)0x56000010)
 #define GPBDAT ((unsigned long)0x56000014)

 #define TCFG0 ((unsigned long)0x51000000)
 #define TCFG1 ((unsigned long)0x51000004)

 #define TCON  ((unsigned long)0x51000008)

 #define TCNTB0  ((unsigned long)0x5100000c)
 #define TCMPB0  ((unsigned long)0x51000010)
 #define TCNTO0  ((unsigned long)0x51000014)

 #endif

主要是用宏来定义用到的几个寄存器地址,注释了的是本来想用来直接操作数据的。不过后面的request_mem_region()函数用到的是unsigned long 的地址值,所以就换成下面的地址值;

pwm.h代码

 #ifndef __PWM_H__
 #define __PWM_H__

 #include "regAddr.h"

 #include<linux/init.h>
 #include<linux/module.h>
 #include<linux/cdev.h>
 #include<linux/errno.h>
 #include<linux/fs.h>
 #include<linux/device.h>
 #include<asm/io.h>
 #include<linux/ioport.h>

 #include <linux/kernel.h>
 #include <linux/delay.h>
 #include <linux/poll.h>
 #include <linux/interrupt.h>
 #include <mach/hardware.h>
 #include <plat/regs-timer.h>
 #include <mach/regs-irq.h>
 #include <asm/mach/time.h>
 #include <linux/clk.h>

 /*
 char devName[] = "yzh";
 dev_t major_num = 0;
 dev_t minor_num = 0;
 dev_t dev_num = -1;
 */
 #endif

主要是包含用到的头文件,以及全局变量(这几个全局变量放到了pwm.c文件中,方便调试),本来这个文件中还要声明使用到的函数,但因为主程序代码只有一个pwm.c文件,所以没必要搞这么复杂,如果有多个文件,各个文件都要相互访问各个文件中的函数时,就需要把函数声明放在该文件中;

pwm.c代码

 #include"pwm.h"

 char devName[] = "yzh_pwm";
 dev_t major_num;
 dev_t minor_num;
 dev_t dev_num;

 struct cdev* devp = NULL;

 //static unsigned int *gpbdat = NULL;
 //static unsigned int *gpbcon = NULL;
 void* gpbdat;
 void* gpbcon;

 //tcfg0 是一级8位预分频器,tcfg0是二级8位预分频器
 void* tcfg0; //PLCK/(prescale + 1)
 void* tcfg1; //PLCK/(prescale + 1)/(diviervalue)

 //1、tcon设置启动定时器,此时把tcmpb0、tcntb0分别装入内部寄存器tcmp0、tcnt0;
 //2、tcnt0开始减1,tcnt0的值可以通过tcnto0获取到。当tcnt0和tcmp0相等时,定时器的输出反转;
 //3、tcnt0继续减1,当tcnt0等于0时,定时器的输出再次反转,并触发定时器中断;
 //4、tcnt0为0时,tcon如果设置为自动加载(tcmpb0、tcntb0自动加载到tcmp0、tcnt0),则重复循环1~4步骤;
 void* tcon;
 void* tcntb0;
 void* tcmpb0;

void* map_addr(unsigned long start, unsigned long len, char *name)
 {
     if (!request_mem_region(start, len, name)){
         printk("in request_mem_region error, name:%s\n", name);
         return NULL;
     }
     return ioremap(start, len);
 }

// 对所有用到的寄存器地址进行映射
 int get_all_addr(void)
 {
 //  gpbdat = (unsigned int*)map_addr(GPBDAT, sizeof(unsigned int), "gpbdat");
 //  gpbcon  = (unsigned int *)map_addr(GPBCON,  sizeof(unsigned int), "gpbcon");

     gpbcon  = map_addr(GPBCON,  sizeof(unsigned int), "gpbcon");
     gpbdat  = map_addr(GPBDAT, sizeof(unsigned int), "gpbdat");
     tcfg0   = map_addr(TCFG0,  sizeof(unsigned int), "tcfg0");
     tcfg1   = map_addr(TCFG1,  sizeof(unsigned int), "tcfg1");
     tcon    = map_addr(TCON,  sizeof(unsigned int), "tcon");
     tcntb0  = map_addr(TCNTB0,  sizeof(unsigned int), "tcntb0");
     tcmpb0  = map_addr(TCMPB0,  sizeof(unsigned int), "tcmpb0");
     return 0;
 }

 int pwm_open(struct inode *inode, struct file* filp)
 {
     printk("in pwm_open!\n");
     return 0;
 }

//一般的峰鸣器,就是buzzer功能
void common_pwm(int start_stop)
 {
     unsigned int con, data;

     con = ioread8(gpbcon);
     con = con & (~3);
     con = con | 1;
     iowrite8(con, gpbcon);

     data = ioread8(gpbdat);

     if (!start_stop)
         data = data & (~1);
     else
         data = data | 1;

     iowrite8(data, gpbdat);
 }

//pwm寄存器的设置,这也是核心部分
int start_pwm(unsigned int cmd, unsigned long freq)
 {
     unsigned int con;
     unsigned int cfg0;
     unsigned int cfg1;
     unsigned int cnt_cmp = 0;
     unsigned int tcon_dat = 0;

     struct clk *clk_p;
     unsigned long pclk;

     //频率为0,普通的峰鸣器响
     if (0 == cmd){
         common_pwm(0);
         return 0;
     }

     //设置为tout0, pwm输出
     con = ioread32(gpbcon);
     con = con & (~3);
     con = con | 2;
     iowrite32(con, gpbcon);

     //设置tcfg0
     cfg0 = ioread32(tcfg0);
     cfg0 = cfg0 & (~0xff);
     cfg0 = cfg0 | (50 -1) ; //设置分频为50,因为: PCLK/(prescale + 1)
     iowrite32(cfg0, tcfg0);

     //设置tcfg1
     cfg1 = ioread32(tcfg1);
     cfg1 = cfg1 & (~0xf);
     cfg1 = cfg1 | 3;      //设置二级分频为 1/16;PCLK/(prescale + 1)/diviervalue
     iowrite32(cfg1, tcfg1); // === PCLK/(50)/(16)

     //获取pclk,用来设置cnt、cmp
     clk_p = clk_get(NULL, "pclk");
     pclk = clk_get_rate(clk_p);
     cnt_cmp = (pclk/50/16)/freq;

     //设置tcntb0和tcmp0
     iowrite32(cnt_cmp, tcntb0);
     iowrite32((cnt_cmp >> 1), tcmpb0);

     //设置tcon
     tcon_dat = tcon_dat & (~0x1f);
     tcon_dat = tcon_dat | 0xb;
     iowrite32(tcon_dat, tcon);

     //设置tcon自动加载tcnt tcmp
     tcon_dat = tcon_dat & (~2);
     iowrite32(tcon_dat, tcon);

     return 0;
 }

int pwm_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)
 {
     printk("in pwm_ioctl!\n");

     if (0 == arg)//如果arg为0,表示只有一个参数,则作为buzzer处理
         common_pwm(cmd);
     else
         start_pwm(cmd, arg);//arg作为freq

     return 0;
 }

 struct file_operations fops=
 {
     .owner = THIS_MODULE,
     .open  = pwm_open,
     .ioctl = pwm_ioctl,
 };

 static int __init pwm_init(void)
 {
     int ret;

     //struct class* myclass = NULL;

     printk("in pwm_init!\n");

     //dev_num = MKDEV(major_num, minor_num);

     ret = alloc_chrdev_region(&dev_num, 0, 1, devName);
         if (ret < 0){
             printk("alloc dev num failur!\n");
             return -EBUSY;
         }
         major_num = MAJOR(dev_num);
         minor_num = MINOR(dev_num);

     printk("major:%d, minor:%d, devnum:%d, devName:%s\n",
             major_num, minor_num, dev_num, devName);

     devp = cdev_alloc();
     cdev_init(devp, &fops);
     devp->owner = THIS_MODULE;
     ret = cdev_add(devp, dev_num, 1);
     if (ret){
         printk("Error %d adding cdev", ret);
         return -EINVAL;
     }

 // myclass = class_create(THIS_MODULE, devName);
 //            device_create(myclass, NULL, dev_num, NULL, devName);

     get_all_addr();//在这里调用,可以使驱动加载后就把一次性映射了地址。不能在open中调用

     return 0;
 }

 static void __exit pwm_exit(void)
 {
     printk("in pwm_exit!\n");
     cdev_del(devp);
     unregister_chrdev_region(dev_num, 1);
 }   

 module_init(pwm_init);
 module_exit(pwm_exit);
 MODULE_LICENSE("Dual BSD/GPL");

上面就是主函数pwm.c,有些注释了,一部分是因为想换个方式表达,一部分是因为mini2440是一块资源有限的设备,有些东西不具备(自动创建节点,好像就不具备)。还是比较简单的,就不详细唠叨了。

Makefile文件

 #####################################################
 ifneq ($(KERNELRELEASE),) 

     obj-m := pwm.o

 else 

 KERNELDIR := /home/yzh/work/s3c2440/linux/linux-2.6.32.2

 PWD:=$(shell pwd)   

 all:   

     make -C $(KERNELDIR) M=$(PWD) modules   

 clean:   

     rm -rf *.ko *.o *.mod.c *.mod.o *.symvers  modules* 

 endif

Makefile文件是通用的

main.c代码

 #include<stdio.h>
 #include<stdlib.h>
 #include<fcntl.h>
 #include<errno.h>

 int main(int argc, char *argv[])
 {
     int ret, fd, cmd;
     unsigned long arg;

     fd = open("/dev/yzh", O_RDWR);

     if (fd < 0){
         printf("open /dev/yzh  error, erron:%d!\n", errno);
         return -1;
     }

     if(argc == 1)
         cmd = arg = 0;
     else if(argc == 2){
         cmd = atoi(argv[1]);
         arg = 0;
     }else{
         cmd = atoi(argv[1]);
         arg = atol(argv[2]);
     }

     ret =ioctl(fd, cmd, arg);

     if (ret < 0){
         printf("ioctl error!\n");
         return -1;
     }
     return 0;
 }

这是测试代码(要交叉编译  arm-linux-gcc xxxx)

分两种:

1、buzzer功能

a、./a.out  1 开启buzzer  ######  b、./a.out 关闭buzzer;

2、pwm功能

a、./a.out 1 freq 以pclk/50/16/freq的频率工作的pwm  ######  b、./a.out 0 关闭pwm;

#####################################################################################################################

到这里所有的代码已经贴出来了,应该还是比较简单的。但是有两个问题:

1、不知道为什么在地址映射的时候,有时候会报错,映射不了。我昨晚调试了很久还是没用,今晚一加载就好了,我什么都没修改,我估计是s3c2440的资源有限,操作久了有些地址被占用了,映射不了。

2、pwm功能启动后,终端没用了,但是能一直工作,不知道这是不是个正常现象?我估计不是正常的,可我不知道怎么调试,因为没报任何错误或者警告。所以,如果知道的可以帮忙指点下。谢谢!!

其他的倒没有什么问题了,只是要注意不能自动创建节点,要手动创建: mknod /dev/yzh  c 253 0;参数 /dev/yzh就是设备文件,c表示字符设备,253是主设备号,0是次设备号。自动创建内核会崩溃,应该不是代码本身的问题的吧(感觉是mini2440不支持)。

转载地址:http://blog.csdn.net/yuzhihui_no1/article/details/47010967

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-22 21:30:29

pwm驱动原理和代码实现的相关文章

flume原理及代码实现

转载标明出处:http://www.cnblogs.com/adealjason/p/6240122.html 最近想玩一下流计算,先看了flume的实现原理及源码 源码可以去apache 官网下载 下面整理下flume的原理及代码实现: flume是一个实时数据收集工具,hadoop的生态圈之一,主要用来在分布式环境下各服务器节点做数据收集,然后汇总到统一的数据存储平台,flume支持多种部署架构模式,单点agent部署,分层架构模式部署,如通过一个负载均衡agent将收集的数据分发到各个子a

Java基础知识强化之集合框架笔记47:Set集合之TreeSet保证元素唯一性和比较器排序的原理及代码实现(比较器排序)

1. TreeSet保证元素唯一性和比较器排序的原理及代码实现(比较器排序) (1)Student.java: 1 package cn.itcast_07; 2 3 public class Student { 4 private String name; 5 private int age; 6 7 public Student() { 8 super(); 9 } 10 11 public Student(String name, int age) { 12 super(); 13 thi

从原理到代码:大牛教你如何用 TensorFlow 亲手搭建一套图像识别模块 | AI 研习社

从原理到代码:大牛教你如何用 TensorFlow 亲手搭建一套图像识别模块 | AI 研习社 PPT链接: https://pan.baidu.com/s/1i5Jrr1N 视频链接: https://v.qq.com/x/page/n0386utnrb0.html?start=492

DM8168 PWM驱动与测试程序

昨天把DM8168的Timer设置给摸了一遍,为写PWM的底层驱动做好了准备,现在就要进入主题了. dm8168_pwm.c: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> /* copy_to_user,copy_from_user */ #include <linux/miscdevice.h&

PHP网站安装程序的原理及代码

原文:PHP网站安装程序的原理及代码 原理: 其实PHP程序的安装原理无非就是将数据库结构和内容导入到相应的数据库中,从这个过程中重新配置连接数据库的参数和文件,为了保证不被别人恶意使用安装文件,当安装完成后需要修改安装文件. 步骤: 1.检查目录或文件的权限 2.修改或填加配置文件 3.检查配置文件正确性 4.导入数据库 5.锁定或删除安装文件 具体代码: 文件:由于只是展示原理,尽量让其简单化故用小Demo形式演示 install.html 为表单填写文件 doAction.php  为处理

潜在语义分析Latent semantic analysis note(LSA)原理及代码实现

文章参考:http://blog.sina.com.cn/s/blog_62a9902f0101cjl3.html Latent Semantic Analysis (LSA)也被叫做Latent Semantic Indexing(LSI),从字面上的意思理解就是通过分析文档去发现这些文档中潜在的意思和概念.假设每个词仅表示一个概念,并且每个概念仅仅被一个词所描述,LSA将非常简单(从词到概念存在一个简单的映射关系) 不幸的是,这个问题并没有如此简单,因为存在不同的词表示同一个意思(同义词),

DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解

DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解 @author:wepon @blog:http://blog.csdn.net/u012162613/article/details/43221829 本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Multilayer Perceptron,如果你想详细了解多层感知机算法,可以参考:UFLDL教程,或者参考本文第一部分的算法简介. 经详细注释的代码:放在我的gith

DeepLearning tutorial(4)CNN卷积神经网络原理简介+代码详解

DeepLearning tutorial(4)CNN卷积神经网络原理简介+代码详解 @author:wepon @blog:http://blog.csdn.net/u012162613/article/details/43225445 本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Convolutional Neural Networks (LeNet).经详细注释的代码和原始代码:放在我的github地址上,可下载. 一.CNN卷积神经网络原理

免费的Lucene 原理与代码分析完整版下载

Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的或者有限长度的数据,比如数据库,元数据等.非结构化数据则是不定长或者没有固定格式的数据,如图片,邮件,文档等.还有一种较少的分类为半结构化数据,如XML,HTML等,在一定程度上我们可以将其按照结构化数据来处理,也可以抽取纯文本按照非结构化数据来处理.非结构化数据又称为全文数据.,对其搜索主要有两种