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()系列函数来操作port;
首先是pwm的工作原理,这个能够看下我转载的一篇blog。简明扼要的讲清楚了pwm的工作原理:pwm的工作原理;当然也能够看看芯片的datasheet,总之看懂了就感觉非常easy了。
以下直接上代码:
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
pwm驱动原理和代码实现的更多相关文章
- flume原理及代码实现
转载标明出处:http://www.cnblogs.com/adealjason/p/6240122.html 最近想玩一下流计算,先看了flume的实现原理及源码 源码可以去apache 官网下载 ...
- linux驱动编写(pwm驱动)【转】
本文转载自:https://blog.csdn.net/feixiaoxing/article/details/79889240 pwm方波可以用来控制很多的设备,比如它可以被用来控制电机.简单来说, ...
- Openwrt:基于MT7628/MT7688的PWM驱动
前言 MT7628/MT7688的PWM驱动相关资料较少,官方的datasheet基本也是一堆寄存器,啃了许久,终于嚼出了味道.由于PWM存在IO口复用的问题,所以要提前配置好GPIO的工作方式,不然 ...
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- Java Base64加密、解密原理Java代码
Java Base64加密.解密原理Java代码 转自:http://blog.csdn.net/songylwq/article/details/7578905 Base64是什么: Base64是 ...
- Base64加密解密原理以及代码实现(VC++)
Base64加密解密原理以及代码实现 转自:http://blog.csdn.net/jacky_dai/article/details/4698461 1. Base64使用A--Z,a--z,0- ...
- Android中Input型输入设备驱动原理分析<一>
话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反正这个是没变的,在android的底层开发中对于Linux的基本驱动程序设计还是没变的,当然Android底层机制也 ...
- AC-BM算法原理与代码实现(模式匹配)
AC-BM算法原理与代码实现(模式匹配) AC-BM算法将待匹配的字符串集合转换为一个类似于Aho-Corasick算法的树状有限状态自动机,但构建时不是基于字符串的后缀而是前缀.匹配 时,采取自后向 ...
- Java基础知识强化之集合框架笔记47:Set集合之TreeSet保证元素唯一性和比较器排序的原理及代码实现(比较器排序:Comparator)
1. 比较器排序(定制排序) 前面我们说到的TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列. 但是如果需要实现定制排序,比如实现降序排序,则要通过比较器排序(定制排序)实 ...
随机推荐
- python 时间、日期、时间戳的转换
在实际开发中经常遇到时间格式的转换,例如: 前端传递的时间格式是字符串格式,我们需要将其转换为时间戳,或者前台传递的时间格式和我们数据库中的格式不对应,我们需要对其进行转换才能与数据库的时间进行匹配等 ...
- Leetcode 424.替换后的最长重复字符
替换后的最长重复字符 给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次.在执行上述操作后,找到包含重复字母的最长子串的长度. 注意:字符串长度 和 ...
- Xshell设置登录会话
新建会话 点击用户登录验证输入账号密码 如果是公钥登录,选择pubulic key登录
- PHP下mysql驱动概述
Overview of the MySQL PHP drivers 什么是API? 一 个应用程序接口(Application Programming Interface的缩写),定义了类,方法,函数 ...
- 【java基础 15】java代码中“==”和equals的区别
导读:昨夜闲来无事,和贾姑娘聊了聊java基础,然后就说到了这个"=="和equals的问题,我俩都是以前了解过,也常用这个,但是,昨天说到的时候,又乱了,什么比较地址值,什么判断 ...
- iOS--app自定义相册--给图片重写exif数据-定义相册时间戳
1.Exif简介 可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据. Exif可 ...
- 自定义AlertView(Swift)
MyAlertView.swift // Pop Up Styles enum MyAlertViewStyle: Int { case success case error case notice ...
- javaweb学习总结(九)—— 通过Servlet生成验证码图片(转)
(每天都会更新至少一篇以上,有兴趣的可以关注)转载自孤傲苍狼 一.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类,如下:
- Scrapy学习-16-动态网页技术
Selenium浏览器自动化测试框架 简介 Selenium 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样. 支持的浏览器包括IE(7, 8, ...
- yii加载自带验证码的方法
Yii的源码包里面是自带有验证码的相关类的,因此在使用验证码的时候无需再加载外部验证码类来助阵了.下面本文将介绍一下如何在项目中加载Yii自带的验证码功能. 具体分三步: (1)在需要加载验证码的co ...