• 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
    ", 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!
    ");
         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!
    ");
     
         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!
    ");
     
         //dev_num = MKDEV(major_num, minor_num);
    
         ret = alloc_chrdev_region(&dev_num, 0, 1, devName);
             if (ret < 0){
                 printk("alloc dev num failur!
    ");
                 return -EBUSY;
             }
             major_num = MAJOR(dev_num);
             minor_num = MINOR(dev_num);
     
         printk("major:%d, minor:%d, devnum:%d, devName:%s
    ",
                 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! "); 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!
    ", 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!
    ");
             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


  • 相关阅读:
    chrome手动同步书签
    MySQL(5.6/5.7版本)卸载方法
    Windows 搭建IIS+PHP+MySQL环境
    按照innode删除结点
    wsl区分大小win10不区分大小写解决方案
    Docker容器里的centos疑难杂症
    [UGUI]源码调试和修改
    [UnityAPI]EditorWindow类 & Editor类
    [UnityAPI]Selection类
    [Lua]require
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/6867984.html
Copyright © 2020-2023  润新知