PWM pulse width modulation (1)

1. PWM mode

The pulse width modulation mode can generate a signal whose frequency is determined by the TIMx_ARR registers and the duty cycle is determined by the TIMx_CCRx registers .

PWM is essentially a timer, the ARR register is filled with a maximum value, the counter counts from 0 to ARR, and then jumps to 0 to start counting again.

A threshold is stored in the CCRx register. When the value in the counter is less than CCRx, the PWM outputs a low level, and when it is greater than CCRx, it outputs a high level.


2. Use

The backlight of LCD, the tone of buzzer, the brightness of LED, etc. can be adjusted.

In the S5PV210, the features of the PWM timer are as follows:

  • There are 5 PWM timers, of which, Timer0 to Timer3 each have a PWM output Pin.
  • The clock input source is APB-PCLK. When the clock is initialized, I set PCLK to 66.7MHz, and achieve the expected tick cycle through a series of frequency divisions.
  • Each tick of the PWM will generate an internal interrupt , and we can add the functions that need to be processed in the interrupt handler.

3. Internal process


Take Timer0 as an example (XpwmTOUT0):

The input clock is PCLK, which first goes through an 8Bit prescaler, and then performs a frequency division (1/1, 1/2, 1/4, 1/8, 1/16), and then enters the core part of the The control unit (Control Logic0) finally outputs the PWM waveform xPWMTOUT0. Among them, the core part is the part of Control Logic in the figure.

PWM Cycle

Let's first look at a simple PWM cycle, as shown below:

First, the part from position 2 to position 5 in the above figure becomes a PWM cycle. List what was done at each moment in turn.

1. Set register TCNTBn=159 (50+109), TCMPBn=109, set manual-update on, at this time, the value in TCNTBn is copied to TCNTn, and the value in TCMPBn is copied to TCMPn.

2. Set the manual-update off, and set the start bit to indicate the start of timing. At this time, TCNTn starts to count down and subtracts one every tick, namely: 159, 158, 157...

3. When the value in TCNTn decreases from 159 to 109, that is: when the value of TCNTn == the value of TCMPn, the level jumps from 0 to 1.

4. When TCNTn continues to decrement to 0, an internal interrupt is generated.

5. After another tick, if auto-reload is set at this time, TCMPBn and TCNTBn are reloaded into TCNTn and TCMPn to start a new PWM cycle.

The above involves several key points raised again :

1. When we set auto-reload, TCMPBn and TCNTBn will be reloaded into TCNTn and TCMPn only when the value of TCNTn is decremented to 0.

2. At the beginning, after we set TCMPBn and TCNTBn, we can manually load TCMPBn and TCNTBn into TCNTn and TCMPn only by turning on manual-update, and then we turn off manual-update.

3. At the beginning of the PWM cycle, the default initial level is low, but this can be modified through registers.

4. When TCNTn == TCMPn, the level jumps, so we can modify the high-level duty cycle by modifying the value of TCMPBn.


Take a look at the following picture, which describes the double buffer mechanism of PWM:

The process and principle are similar to the above, focusing on the two consecutive settings at the beginning:

1. Set TCNTBn=3, TCMPBn=1 for the first time, and set the manual update to 1, so that TCNTn=TCNTBn=3, TCMPn=TCMPBn=1

2. Set TCNTBn=2, TCMPBn=0 for the second time, and set the manual update to 0. In this way, although the PWM cycle has not yet started, the value of the second PWM cycle has been set. This is the so-called double buffer mechanism.

3. At the beginning of the second PWM cycle, the auto-reload is set to 0, so after the second PWM is over, the reload will not continue, and it is over.

key register

The following all take Timer1 as an example.

divide by two

TCFG0,0xE250_0000

Prescaler from 1 to 255 can be performed.

TCFG1,0xE250_0004

Can be divided by 1/1, 1/2, 1/4, 1/8, 1/16, or directly select SCLK_PWM.

TCNTBn and TCMPBn

TCNTB1, 0xE250_0018

TCMPB1, 0xE250_001C

control register

TCON, 0xE250_0008

 

example

The following example is to control the buzzer to sound through the PWM waveform output by Timer1. There are two control methods. Through the ioctl operation in the user space and the echo operation in the file system, the adjustable parameters include the frequency, loudness, and frequency of the buzzer. duration.

Note: The frequency of the buzzer is the output frequency after PCLK frequency division, and the loudness is the duty cycle of the PWM.

/*
 * Character device driver for buzzer
 * Two ways to turn on the buzzer
 * Method 1: Operate /dev/my_beep through ioctl, code example: test/beep_test.c
 * Method 2: Operation via file system:
 * step1: Write parameters to the frequency, volume, and sec files in the /sys/devices/virtual/my_beep/my_beep path
 * step2: echo 1 > /sys/devices/virtual/my_beep/my_beep/do_beep, to start the buzzer
 */

#include <linux/delay.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>

#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/map.h>
#include <mach/irqs.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-core.h>
#include <plat/gpio-cfg.h>
#include <plat/gpio-cfg-helpers.h>
#include <plat/regs-timer.h>
#include <plat/clock.h>

#define SOUND_FREQENCY_BASE  667

volatile int *CLK_GATE_IP3 = NULL;
static dev_t devno;
static struct cdev *beep_dev;
static struct class *beep_class;
static struct device *beep_class_dev;
static unsigned long pclk;
static int beep_irq;
static unsigned long beep_cnt = 0;

static int fs_beep_freq;
static int fs_beep_volume;
static long fs_beep_sec;

static int beep_on(unsigned int snd_volume, unsigned int snd_freq, unsigned long beep_sec)
{
    unsigned long tcon;
    unsigned long tcfg0;
    unsigned long tcfg1;
    unsigned long tcntb1;
    unsigned long tcmpb1;
    unsigned long freq;

    /*clear relate bits*/
    tcon = __raw_readl(S3C2410_TCON);
    tcfg0 =__raw_readl(S3C2410_TCFG0);
    tcfg1 =__raw_readl(S3C2410_TCFG1);

    tcfg0 &= ~0xFF;
    __raw_writel(tcon, S3C2410_TCFG0);
    tcfg1 &= ~(0xF << 4);
    __raw_writel(tcon, S3C2410_TCFG1);
    tcon &= ~(0xF << 8);
    __raw_writel(tcon, S3C2410_TCON);


    /*Timer Input Clock Frequency = PCLK / ( {prescaler value + 1} ) / {divider value}
     *set prescaler    = 1
     *set divider = 2
     *so , input clock frequency = 66.7MHz / 2 / 2 = 16.675MHz
     */
    tcfg0 |= 0x01;
    __raw_writel(tcfg0, S3C2410_TCFG0);
    tcfg1 |= 1 << 4;
    __raw_writel(tcfg1, S3C2410_TCFG1);

    /*enable auto-reload, set TCNTB1 & TCMPB1, then disable manual-update*/
    tcon |= 1 << 11;
    tcntb1 = 101 * SOUND_FREQENCY_BASE * (10 - snd_freq);
    tcmpb1 = snd_volume * SOUND_FREQENCY_BASE * (10 - snd_freq);
    freq = pclk/4/(tcntb1+1);
    beep_cnt = freq * beep_sec;
    printk("beep frequency = %ldHz \n" , freq);
    __raw_writel(tcon, S3C2410_TCON);
    __raw_writel(tcntb1, S3C2410_TCNTB(1));
    __raw_writel(tcmpb1, S3C2410_TCMPB(1));

    /*enable manual-update and then disable manual-update, let TCNTB1 and TCMPB1 load to TCNT1 and TCMP1*/
    tcon |= 1 << 9;
    __raw_writel(tcon, S3C2410_TCON);
    tcon &= ~(1 << 9);
    __raw_writel(tcon, S3C2410_TCON);

    / * start timer * /
    tcon |= 1 << 8;
    __raw_writel(tcon, S3C2410_TCON);

    return 0;
}

static int beep_off(void)
{
    unsigned long tcon;
    tcon = __raw_readl(S3C2410_TCON);
    tcon &= ~(1 << 8);
    tcon &= ~(1 << 9);
    __raw_writel(tcon, S3C2410_TCON);

    return 0;
}

static int beep_dev_open(struct inode *inode, struct file *file)
{
    /* There is nothing to do in the open function, here we print the frequency of PCLK (pwm input clock) */
    struct clk *clk_p;
    clk_p = clk_get(NULL, "pclk");
    pclk = clk_get_rate(clk_p);
    printk("pclk rate : %ldHz\n", pclk);

    return 0;
}

static int beep_dev_release(struct inode *inode, struct file *file)
{
    /*stop beep*/
//    beep_off();
    return 0;
}

/*
 * snd_lvl: 1 ~ 100
 * snd_freq: 0 ~ 9
 */
static int beep_dev_ioctl(struct file *file, unsigned int beep_on_off, unsigned int (*beep_args)[])
{
    unsigned int snd_lvl = (*beep_args)[0];
    unsigned int snd_freq = (*beep_args)[1];
    unsigned long beep_sec = (*beep_args)[2];

    if(beep_on_off > 0)
    {
        if(snd_lvl < 1 || snd_lvl > 100)
        {
            printk("error: sound volume should from 1 ~ 100 !\n");
            return -1;
        }
        if(snd_freq < 0 || snd_freq > 9)
        {
            printk("error: sound frequency level should from 0 ~ 9 !\n");
            return -1;
        }
        if(beep_sec <= 0)
        {
            printk("error: beep seconds should > 0 !\n");
            return -1;
        }
        printk("snd_lvl = %d, snd_freq= %d\n", snd_lvl, snd_freq);
        beep_on(snd_lvl, snd_freq, beep_sec);
    }
    else
        beep_off();

    return 0;
}

static irqreturn_t my_beeq_irq_handler(int irq,void *dev_id,struct pt_regs *regs)
{
    beep_cnt--;
    int tint_cstat = __raw_readl(S3C64XX_TINT_CSTAT);
    tint_cstat |= 1 << 6;
    __raw_writel(tint_cstat, S3C64XX_TINT_CSTAT);
    if(beep_cnt == 0)
        beep_off();
    return (IRQ_HANDLED);
}

static size_t show_beep_frequency(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value:0~9)\n", fs_beep_freq);
    ret = strlen (buf) + 1;
    return ret;
}

static size_t store_beep_frequency(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int freq = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(freq < 0 || freq > 9)
    {
        printk("error: sound frequency level should from 0 ~ 9 !\n");
        return -1;
    }
    fs_beep_freq = freq;

    return len;
}
static DEVICE_ATTR(frequency, 0666, show_beep_frequency, store_beep_frequency);

static size_t show_beep_volume(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value:1~100)\n", fs_beep_volume);
    ret = strlen (buf) + 1;
    return ret;
}

static size_t store_beep_volume(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int volume = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(volume < 1 || volume > 100)
    {
        printk("error: sound volume should from 1 ~ 100 !\n");
        return -1;
    }
    fs_beep_volume = volume;

    return len;
}
static DEVICE_ATTR(volume, 0666, show_beep_volume, store_beep_volume);

static size_t show_beep_sec(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t ret = 0;
    sprintf(buf, "%d (value: > 0)\n", fs_beep_sec);
    ret = strlen (buf) + 1;
    return ret;
}

static size_t store_beep_sec(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    unsigned long sec = simple_strtoull(buf, NULL, 10);
    if(sec <= 0)
    {
        printk("error: beep seconds should > 0 !\n");
        return -1;
    }
    fs_beep_sec = sec;

    return len;
}
static DEVICE_ATTR(sec, 0666, show_beep_sec, store_beep_sec);

static size_t fs_do_beep(struct device *dev, struct device_attribute *attr, char *buf, size_t len)
{
    int is_beep = (unsigned int)simple_strtoull(buf, NULL, 10);
    if(is_beep > 0)
    {
        if(fs_beep_freq < 0 || fs_beep_freq > 9
            || fs_beep_volume < 1 || fs_beep_volume > 100
            || fs_beep_sec < 1)
        {
            printk("error: condition not match! \n");
            return len;
        }
        struct clk *clk_p;
        clk_p = clk_get(NULL, "pclk");
        pclk = clk_get_rate(clk_p);

        beep_on(fs_beep_volume, fs_beep_freq, fs_beep_sec);
    }
    return len;
}
static DEVICE_ATTR(do_beep, 0222, NULL, fs_do_beep);

static struct file_operations beep_ops =
{
    .owner = THIS_MODULE,
    .open = beep_dev_open,
    .release = beep_dev_release,
    .unlocked_ioctl = beep_dev_ioctl
};

static int tq210_beep_init(void)
{
    int major;
    unsigned long tint_cstat;

    /* Make sure the PWM clock gating is on */
    CLK_GATE_IP3 = (int *)ioremap(0xE010046C, 4);

    *CLK_GATE_IP3 |= 1 << 23;

    /* set gpio control register */
    s3c_gpio_cfgpin (S5PV210_GPD0 (1), S3C_GPIO_SFN (2));
    /* Do not use pull-up and pull-down resistors */
    s3c_gpio_setpull(S5PV210_GPD0(1), S3C_GPIO_PULL_NONE);

    /* The following is to create and add a character device */
    if(alloc_chrdev_region(&devno, 0, 1, "my_tq210_beep") < 0)
    {
        printk("alloc_chrdev_region 'my_beep' fail ! \n");
        return -1;
    }

    major = MAJOR(devno);
    beep_dev = kmalloc(sizeof(struct cdev), GFP_KERNEL);
    cdev_init(beep_dev, &beep_ops);
    beep_dev->owner = THIS_MODULE;

    if(cdev_add(beep_dev, devno, 1) < 0)
    {
        printk("cdev_add 'beep_dev' fail ! \n");
        return -1;
    }

    /*Create device node*/
    beep_class = class_create(THIS_MODULE, "my_beep");
    beep_class_dev = device_create(beep_class, NULL, MKDEV(major, 0), NULL, "my_beep", 0);
    if (device_create_file(beep_class_dev, &dev_attr_frequency) < 0)
    {
        printk("error: create attr frequency error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_volume) < 0)
    {
        printk("error: create attr volume error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_sec) < 0)
    {
        printk("error: create attr seconds error!\n");
        return -1;
    }
    if (device_create_file(beep_class_dev, &dev_attr_do_beep) < 0)
    {
        printk("error: create attr do_beep error!\n");
        return -1;
    }

    /* Enable interrupt, request interrupt */
    tint_cstat = __raw_readl(S3C64XX_TINT_CSTAT);
    tint_cstat |= 1 << 1;
    tint_cstat |= 1 << 6;
    __raw_writel(tint_cstat, S3C64XX_TINT_CSTAT);

    beep_irq = request_irq(IRQ_TIMER1, my_beeq_irq_handler, IRQF_DISABLED, "my_beep", NULL);
    if(beep_irq < 0)
    {
        printk("request timer 1 irq (no = %d) fail!\n", IRQ_TIMER1);
        return -1;
    }
    printk("my tq210 beep init finish! \n");

     return 0;
}

static void tq210_beep_exit(void)
{
    cdev_del (beep_dev);
    kfree(beep_dev);
    unregister_chrdev_region(devno, 1);
    device_unregister(beep_class_dev);
    class_destroy(beep_class);
    free_irq(beep_irq, NULL);
}

module_init(tq210_beep_init);
module_exit(tq210_beep_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("chuck_huang / [email protected]");
MODULE_DESCRIPTION("PWM for beep driver");

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
    if (argc != 5)
    {
        printf("arg format:\n\
            argv[1]: 0-> sound off / 1-> sound on\n\
            argv[2]: sound volume, from 1 to 100\n\
            argv[3]: sound frequency level, from 0 to 9\n\
            argv[4]: beep secounds\n\
            e.g.: beep_test 1 50 5 2\n");
        return -1;
    }
    int fd;

    int beep_args[3];
    beep_args[0] = atoi(argv[2]);
    beep_args[1] = atoi(argv[3]);
    beep_args[2] = atoi(argv[4]);
    fd = open("/dev/my_beep", O_RDWR);

    if(fd < 0)
    {
        printf("open /dev/my_beep error ! \n");
        return -1;
    }
    
    /*
    * ioctl (*file, int cmd, int (*beep_args)[2])
    * cmd: 0-> sound off / 1-> sound on
    * (*beep_args)[0] : snd_lvl : from 1 to 100
    * (*beep_args)[1] : snd_freq : from 1 to 10
    * (*beep_args)[2] : beep_sec : from 1 to 10
    */
    ioctl(fd, atoi(argv[1]), &beep_args);
    close(fd);

    return 0;
}


















































































Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325731298&siteId=291194637