标签:linux驱动 pwm工作原理 pwm驱动 s3c2440驱动
学这个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* endifMakefile文件是通用的
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
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:linux驱动 pwm工作原理 pwm驱动 s3c2440驱动
原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/47010967