PWM Drive principle and code implementation

Source: Internet
Author: User

This PWM is really very good, the first is to see s3c2440 datasheet, all in English, and there are hardware timing diagram (many of the hardware works, and software control is not related). Watched for a long time plus online to read the data before the PWM good grasp. Of course, it involves a few knowledge, basically all good grasp. The following will be listed through blog one by one.

The first point of knowledge: the knowledge points involved in I/O mapping and memory mapping, including unified addressing and stand-alone addressing, and how Linux can be programmed in both ways, and how to access peripherals in both ways.

Second point of Knowledge: where is it mapped to memory? How to map? So it involves the memory distribution of the Linux kernel, and by the way it analyzes the differences between several kernel memory allocation functions.

Here are a few of the knowledge points involved in the analysis, the following will be explained in detail. Here only the operating principle of PWM and the analysis of the drive.

I was the first to write a simple spike driver, can not adjust the frequency: s3c2440 Miscellaneous driver to implement the buzzer inside with miscellaneous equipment to drive the spike, of course, inside are called the read-write function provided under s3c2440. This is not very good for the transplant, I this blog is a general function from the bottom step to make PWM work.

The first is the next mini2440, I use the Development Board is mini2440, that is, s3c2440 processor. At first I didn't know s3c2440 is a CPU, I found the s3c2440 in the Linux source Arch/arm platform, then I found the information about the next s3c2440 processor. The main is to understand the s3c2440 I/O address, s3c2440 is a unified address is actually memory mapping.

S3C2440 provides __RAW_READL () and other functions to read and write I/O, I s3c2440 the system comes with the Tang Hong and function Blog also analyzed the source of these functions (it seems a bit messy), I do not use the s3c2440 provided by the system I/O operation function, self-mapping address, Using the Universal Ioread () series function to operate the port;

The first is the principle of PWM, this can look at my reprint of a blog, concise and clear the work principle of PWM: PWM work principle, of course, can also look at the chip datasheet, in short, it feels easy to understand. Directly below the code:

RegAddr.h Code

#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 *) 0x5100000 c) #define TCMPB0 (volatile unsigned long *) 0x51000010) #define TCNTO0 ((volatile unsigned long *) 0x51000014) */  #def Ine 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
The main use of macros to define the use of a few register addresses, comments are intended to be used to directly manipulate the data. However, the following request_mem_region () function uses the address value of unsigned long, so it is replaced by the address value below;


Pwm.h Code

#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
Mainly contains the used header file, As well as global variables (these global variables into the pwm.c file, easy to debug), this file is also declared to use the function, but because the main program code only a PWM.C file, so there is no need to complicate it, if there are multiple files, each file to each other to access the functions of each file, you need to put the function declaration in the text In a piece;


PWM.C Code

 #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 is a primary 8-bit prescaler, Tcfg0 is a two-stage 8-bit prescaler void* tcfg0; plck/(Prescale + 1) void* tcfg1; plck/(Prescale + 1)/(Diviervalue)//1, Tcon set the start timer, at this time tcmpb0, tcntb0 respectively loaded into the internal register tcmp0, tcnt0;//2, tcnt0 start minus 1, The value of the tcnt0 can be obtained through tcnto0. When the tcnt0 and tcmp0 are equal, the output of the timer reverses;//3, TCNT0 continues to subtract 1, when tcnt0 equals 0 o'clock, the output of the timer is reversed again and the timer interrupt is triggered;//4, tcnt0 is 0 o'clock, Tcon if set to auto Load (tcmpb0,   Tcntb0 automatically loaded into Tcmp0, tcnt0), repeat cycle 1~4 step; 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); }//mapping all used register addresses 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;      }//General, is the buzzer function void common_pwm (int start_stop) {unsigned int con, data;     con = Ioread8 (Gpbcon);     con = con & (~); con = Con |     1; Iowrite8 (Con, gpbcon);              data = Ioread8 (Gpbdat);     if (!start_stop) data = data & (to); else data = Data |         1; Iowrite8 (data, gpbdat); }//PWM register settings, which is also the kernelHeart Part 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;         The frequency is 0, the ordinary peak-ringing if (0 = = cmd) {common_pwm (0);     return 0;     }//Set to Tout0, PWM output con = ioread32 (Gpbcon);     con = con & (~); con = Con |     2;      Iowrite32 (Con, gpbcon);     Set Tcfg0 cfg0 = Ioread32 (TCFG0);     Cfg0 = cfg0 & (~0XFF); Cfg0 = Cfg0 | (50-1);     Set the crossover to 50 because: pclk/(Prescale + 1) iowrite32 (cfg0, tcfg0);     Set Tcfg1 CFG1 = Ioread32 (TCFG1);     CFG1 = cfg1 & (~0XF); CFG1 = Cfg1 |      3; Set the two-level divider to 1/16;pclk/(Prescale + 1)/diviervalue iowrite32 (CFG1, TCFG1);     = = = pclk/(50)/(16)//Get PCLK, used to set CNT, CMP clk_p = Clk_get (NULL, "PCLK");     PCLK = Clk_get_rate (clk_p);      cnt_cmp = (PCLK/50/16)/freq;     Set tcntb0 and Tcmp0 iowrite32 (cnt_cmp, tcntb0); Iowrite32 ((CNT_CMP >> 1), tcmpb0);     Set Tcon Tcon_dat = Tcon_dat & (~0x1f); Tcon_dat = Tcon_dat |     0XB;      Iowrite32 (Tcon_dat, Tcon);     Set Tcon auto-load tcnt tcmp tcon_dat = tcon_dat &      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)//If ARG is 0, indicating that there is only one parameter, then treat COMMON_PWM (cmd) as buzzer; else START_PWM (cmd, arg);//arg as 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 ();//Call here, you can make the driver after loading a one-time map of the address. Cannot call return 0 in open;     } 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");
Above is the main function pwm.c, some comments, part because want to express in a different way, partly because mini2440 is a resource-limited device, some things do not have (automatically create nodes, as if it does not have). is still relatively simple, it is not detailed nagging.


Makefile file

##################################################### 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
The makefile file is generic


MAIN.C Code

#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; }
This is the test code (to cross-compile ARM-LINUX-GCC xxxx)

Divided into two types:

1. Buzzer function

A,./a.out 1 Open buzzer ###### B,./a.out close buzzer;

2. PWM function

A./a.out 1 freq pwm ###### B,./a.out 0 with Pclk/50/16/freq frequency to switch off PWM;


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

All the code here has been posted, it should be relatively simple. But there are two problems:

1, do not know why in the address map, sometimes error, mapping can not. I debugged last night for a long time or useless, tonight a load is good, I have nothing to modify, I estimate is s3c2440 Resources Limited, operation for a long time some address is occupied, map not.

2, the PWM function starts, the terminal useless, but can always work, do not know this is not a normal phenomenon? I guess it's not normal, but I don't know how to debug it because I didn't report any errors or warnings. So, if you know can help pointing. Thank you!!

There is nothing wrong with the other, just pay attention to not automatically create nodes, to manually create: Mknod/dev/yzh C 253 0, parameter/dev/yzh is the device file, C for the character device, 253 is the main device number, 0 is the secondary device number. The automatic creation of the kernel crashes and should not be a problem with the code itself (it feels like mini2440 is not supported).


Reprint Address: http://blog.csdn.net/yuzhihui_no1/article/details/47010967


Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

PWM Drive principle and code implementation

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.