Learning this PWM is really very song, first of all is to see s3c2440 datasheet, all English, but also have a hardware sequence diagram (many is the working principle of the hardware, and software control is irrelevant). Read for a long time plus the Internet 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 knowledge point is the knowledge points involved in I/O mapping and memory mapping, including unified address and independent address, and how to program these two ways under Linux, and how to access peripherals in both ways.
Second knowledge point: Map to memory where. How to map. So it involves the memory distribution of the Linux kernel, and also analyzes the differences of several kernel memory allocation functions.
Here a few related to the knowledge point does not expand to analyze, later will explain in detail. Here only on the operating principle of PWM and drive analysis.
I started to write a simple buzzer driver, can not adjust the frequency of: s3c2440 Miscellaneous Drive Implementation of the beeper with miscellaneous device drivers to work, of course, inside are called the s3c2440 provided by the read-write function. This is not very good for the transplant, I this blog is a general-purpose function from the bottom step to make the PWM work.
The first is the next mini2440, I used the Development Board is mini2440, that is s3c2440 processor. At first I did not know s3c2440 is a CPU, I am in the Linux source Arch/arm platform to find the s3c2440, and then I find information to understand the next s3c2440 processor. One of 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 system with the Tang Hong and function Blog also analyzed the source code of these functions (as if a bit messy), I do not have to s3c2440 provide the system I/O operation functions, mapping their own address, Use the Universal Ioread () series function to operate the port;
The first is the working principle of PWM, this can look at my reprinted a blog, concise and clear the working principle of PWM: PWM work principle; Of course, you can also see the chip datasheet, in short, it feels very easy to understand. The following direct code:
RegAddr.h Code
#ifndef __reg_addr_h__ #define __REG_ADDR_H_/* #define GPBCON ((volatile unsigned) long*) #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 ((v Olatile unsigned long *) 0x5100000c) #define tcmpb0 (volatile unsigned long *) 0x51000010) #define tcnto0 (vol. Atile unsigned long *) 0x51000014)/#define GPBCON ((unsigned long) 0x56000010) #define GPBDAT ((unsigned long) 0x5600 0014) #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 Tcnt O0 ((unsigned long) 0x51000014) #endif
The main use of a macro to define the use of several register addresses, annotated is intended to be used to directly manipulate the data. However, the following request_mem_region () function uses the address value of unsigned long, so replace it with 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
Basically contains the header file used, and global variables (these global variables are placed in the Pwm.c file for easy debugging), originally this file is also to declare the use of functions, but because the main program code only a PWM.C file, so there is no need to make such a complex, 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 Parts;
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 8-bit prescaler frequency converter, tcfg0 is a two-level 8-bit prescaler-frequency converter void* tcfg0; plck/(Prescale + 1) void* tcfg1; plck/(Prescale + 1)/(Diviervalue)//1, Tcon set up the start timer, then the tcmpb0, tcntb0 loaded into the internal register tcmp0, tcnt0,//2, TCNT0 began to reduce 1, The value of the tcnt0 can be obtained by tcnto0. When the tcnt0 and tcmp0 are equal, the output of the timer is reversed;//3, TCNT0 continues to reduce 1, when tcnt0 equals 0 o'clock, the timer output reverses again and triggers the timer interrupt;//4, tcnt0 to 0 o'clock, Tcon if set to automatically load (tcmpb0,
tcntb0 automatically load to Tcmp0, tcnt0), repeat cycle 1~4 steps; 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); ///maps 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;
The//General buzzer is the buzzer function 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 & (~);
Else data = Data |
1;
Iowrite8 (data, gpbdat);
}//PWM register setting, which is also the core 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;
Frequency is 0, ordinary buzzer ring if (0 = cmd) {common_pwm (0);
return 0;
//Set to Tout0, PWM output con = ioread32 (Gpbcon);
con = con & (~3); 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 two-level crossover for 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 up 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 & (~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)//If ARG is 0, indicates only one parameter, then as buzzer processing common_pwm (CMD);
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); The GET_ALL_ADDR ()//is called here to enable the driver to load and then map the address once.
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, partly because they want to be expressed in a different way, partly because mini2440 is a resource-limited equipment, some things do not have (automatically create nodes, as if they do not have). Or relatively simple, not verbose 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 Vers 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, operating at pclk/50/16/freq frequencies;
############################################################################################################### ######
All the code here has been posted, it should be relatively simple. But there are two questions:
1, do not know why in the address mapping, sometimes the error, can not map. 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 addresses are occupied, mapping.
2, the PWM function starts, the terminal is useless, but can work all the time, 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, you can help to guide. Thank you..
The other is not a problem, just to note that the node can not be automatically created, to manually create: Mknod/dev/yzh C 253 0; The parameter/dev/yzh is the device file, C indicates the character device, 253 is the main device number, 0 is the minor number. The automatic creation of the kernel crashes, it should not be the problem of the code itself (the feeling is mini2440 not supported).
Reprint Address: http://blog.csdn.net/yuzhihui_no1/article/details/47010967