Pwm的使用与设备树下使用gpio

前言

本文为该系列文章第二篇。第一篇地址请点这里

PWM使用

Cv180X/CV181X有4组PWM(0-3),每组PWM有4路PWM,一共16路PWM(0-15,16 channels)。例如在duo256上:第0组PWM里有PWM0,PWM1,PWM2,PWM3;第1组PWM里有PWM4,PWM5,PWM6,PWM7,以此类推。另外,实际每组PWM内也会有第二种命名方式,例如第0组PWM内,有PWM0,PWM1,PWM2,PWM3,对应上面duo256中第0组PWM里有PWM0,PWM1,PWM2,PWM3;第1组PWM内,也有PWM0,PWM1,PWM2,PWM3,对应上面duo256中第1组PWM里有PWM4,PWM5,PWM6,PWM7,以此类推。

理解上述情况后,查看duo256的结构图:

看到左下角的PWM5,按照上面的说法,PWM5属于第1组PWM。PWM5在GPIOA-17引脚上,这个引脚默认功能并不是PWM,所以要引脚复用,将该引脚的功能改为PWM。

查看SG2002的TRM手册,在第八章可以找到该引脚的复用功能描述:

复用引脚

上图中,我们要注意的信息首先是寄存器的地址0x0300_1044,其次是右边的功能序号,例如,0是UART0_RX,3是GPIOA[17],2是PWM。我们要先对寄存器地址用ioremap映射,再用writel写入功能序号,例如要启用PWM功能,就写入2。

可以使用命令duo-pinmux -r查看某个引脚的功能。该命令的具体使用方法请查看参考链接

将引脚的PWM功能启用后,现在开始操作PWM的寄存器。首先要找到这组PWM的基地址,在上面提到,PWM5属于第1组PWM,查看SG2002的TRM手册可以找到基地址为0x03061000

pwm基地址

然后要找到PWM各个寄存器的地址偏移(Address Offset):

pwm地址偏移

在上图中,看到PWM0和PWM1,读者可能会存在疑惑,PWM0和PWM1到底是什么呢?这就是上面提到的第二种命名方式,例如对于duo256上的PWM5,就是第1组PWM中的PWM1(PWM4是第1组PWM的PWM0)。

综上所述,我们需要的寄存器是:HLPERIOD1,PERIOD1,POLARITY,PWMSTART,PWM_OE,将基地址加上地址偏移就是对应寄存器的地址。

PWM工作方式

PERIOD1用于设置频率,PWM 的时钟源为 100MHz,用100MHz除以要输出的频率即为PERIOD1,例如要输出1MHz,则PERIOD1=100MHz/1MHz=100,HLPERIOD1用于设置低电平占比,若要低电平占比为75%,则HLPERIOD1=100乘75%=75。在此配置下去测引脚,应该能测到的电压为:3.33V * 25%=0.8325V。

POLARITY用于设置PWM输出模式,PWM5是第1组PWM的PWM1,若要设置为连续输出模式,则应在(1+8)位设置0。

PWM_OE用于使能PWM输出,PWMSTART设置PWM开始输出。

具体代码如下:

//pwm_kernel.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/uaccess.h"
#include "linux/io.h"
#include "linux/gpio.h"

#define FMUX_GPIO_REG_IOCTRL_UART0_RX 0x03001044
#define PWM1_REG_BASE 0x03061000
#define PWM1_HLPERIOD (PWM1_REG_BASE+0x008)
#define PWM1_PERIOD (PWM1_REG_BASE+0x00c)
#define PWM1_PWMSTART (PWM1_REG_BASE+0x044)
#define PWM1_MODE (PWM1_REG_BASE+0x040)
#define PWM1_OE (PWM1_REG_BASE+0x0d0)


#define GPIOA_DIRECTION (0x03020000+0x004)
#define GPIOA_VALUE  (0x03020000+0x000)
unsigned int *vir_mux;
unsigned int *vir_gpioA17_direction;
unsigned int *vir_gpioA17_value;

unsigned int *pwm1_hlperiod;
unsigned int *pwm1_period;
unsigned int *pwm1_start;
unsigned int *pwm1_mode;
unsigned int *pwm1_oe;
#define gpio_num 17


ssize_t pwm_read(struct file *file, char __user *user_buf, size_t size, loff_t * loff){
    return 0;
}

ssize_t pwm_write(struct file *file, const char __user *user_buf, size_t size, loff_t * loff){
    char kernel_buf[64]={0};
    printk("pwm_write\n");
    if(copy_from_user(kernel_buf,user_buf,size)!=0) {
        printk("copy_from_user error\n");
        return -1;
    }else {
        if (kernel_buf[0]=='1') {
            // *vir_gpioA17_value|=(1<<gpio_num);
        }else if(kernel_buf[0]=='0') {
            // *vir_gpioA17_value&=~(1<<gpio_num);
        }
        return 0;
    }
}

int pwm_open(struct inode *inode, struct file *file){
    printk("pwm_open\n");
    return 0;
}

int pwm_release(struct inode *inode, struct file *file){
    return 0;
}


struct file_operations pwm_fops={
    .owner=THIS_MODULE,
    .write=pwm_write,
    .read=pwm_read,
    .open=pwm_open,
    .release=pwm_release
};

static struct miscdevice pwm_dev={
        .minor=MISC_DYNAMIC_MINOR,
        .fops=&pwm_fops,
        .name="pwm_led_dev"
};

static int __init pwm_init(void){
    printk("pwm_init\n");
    if(misc_register(&pwm_dev)<0){
        printk("misc_register error\n");
        return -1;
    } else{
        printk("misc_register succeed\n");
        vir_mux=ioremap(FMUX_GPIO_REG_IOCTRL_UART0_RX,4);
        pwm1_hlperiod=ioremap(PWM1_HLPERIOD,4);
        pwm1_period=ioremap(PWM1_PERIOD,4);
        pwm1_start=ioremap(PWM1_PWMSTART,4);
        pwm1_mode=ioremap(PWM1_MODE,4);
        pwm1_oe=ioremap(PWM1_OE,4);
        // vir_gpioA17_direction=ioremap(GPIOA_DIRECTION,4);
        // vir_gpioA17_value=ioremap(GPIOA_VALUE,4);
        if(vir_mux==NULL){
            printk("ioremap error\n");
            return -1;
        } else{
            printk("ioremap succeed\n");
        }
        writel(2,vir_mux);
        writel(50,pwm1_hlperiod);
        writel(100,pwm1_period);
        *pwm1_mode&=~(1<<(1+8));
        *pwm1_oe|=(1<<1);
        *pwm1_start|=(1<<1);
        // *vir_gpioA17_direction|=(1<<gpio_num);
        // *vir_gpioA17_value&=~(1<<gpio_num);
        return 0;
    }
}

static void __exit pwm_exit(void){
    printk("pwm_exit\n");
    misc_deregister(&pwm_dev);
    iounmap(vir_mux);
    iounmap(pwm1_hlperiod);
    iounmap(pwm1_period);
    iounmap(pwm1_start);
    iounmap(pwm1_mode);
    iounmap(pwm1_oe);
    // iounmap(vir_gpioA17_direction);
    // iounmap(vir_gpioA17_value);
}

MODULE_LICENSE("GPL");
module_init(pwm_init);
module_exit(pwm_exit);
export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-musl-
obj-m +=pwm_kernel.o
kdir=/home/cunjiang/milkv/duo_256m/linux_sdk/duo-buildroot-sdk/linux_5.10
all:
	make -C $(kdir) M=$(shell pwd) modules

clean:
	rm -rf *.o *.ko
//app.c

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

int main(int argc,char *argv[]){
    int fd= open("/dev/pwm_led_dev",O_RDWR);
    if(fd<0){
        perror("open error");
        return -1;
    } else{
        write(fd,argv[1],sizeof(argv[1]));
        close(fd);
        return 0;
    }
}

二、设备树控制GPIO

在duo-buildroot-sdk/build/boards/cv181x/cv1812cp_milkv_duo256m_sd/dts_riscv/cv1812cp_milkv_duo256m_sd.dts文件中添加节点:

led_gpioA14:led_gpioA14{
		compatible = "my_led";
		led_gpio=<&porta 14 GPIO_ACTIVE_LOW>;
	};

重新编译镜像并烧录到存储卡,插入duo256并上电,使用以下命令可以看到节点:

cd /proc/device-tree/

//platform_led.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/platform_device.h"
#include "linux/of.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/uaccess.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"

int gpio_num;

int led_open(struct inode *inode, struct file *file){
    printk("led_open\n");
    return 0;
}

ssize_t led_write(struct file *file, const char __user *user_buf, size_t size, loff_t * loff){
    char kernel_buf[64]={0};
    printk("led_write\n");
    if(copy_from_user(kernel_buf,user_buf,size)!=0) {
        printk("copy_from_user error\n");
        return -1;
    }else {
        if (kernel_buf[0]=='1') {
            gpio_set_value(gpio_num,1);
        }else if(kernel_buf[0]=='0') {
            gpio_set_value(gpio_num,0);
        }
        return 0;
    }
}

static struct file_operations led_fops={
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

struct miscdevice led_miscdevice={
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &led_fops,
    .name = "led_misc"
};


int led_probe(struct platform_device *platform_device){
    printk("led_probe\n");
    printk("compatible is %s\n",(char *)of_find_property(platform_device->dev.of_node,"compatible",NULL)->value);
    gpio_num=of_get_named_gpio(platform_device->dev.of_node,"led_gpio",0);
    if(gpio_num<0) {
        printk("of_get_named_gpio error\n");
        return -1;
    }

    printk("led gpio is %d\n",gpio_num);
    gpio_request(gpio_num,NULL);
    gpio_direction_output(gpio_num,0);

    if(misc_register(&led_miscdevice)<0) {
        printk("misc_register error\n");
        return -1;
    }else {
        printk("misc_register succeed\n");
        return 0;
    }

}

const struct of_device_id of_led_table[]={
    {
        .compatible = "my_led",
    },
    {},
};

static struct platform_driver led_driver={
    .probe = led_probe,
    .driver = {
        .owner = THIS_MODULE,
        .name = "led_driver",
        .of_match_table =of_led_table
    }
};

static int __init led_init(void) {
    printk("led_init\n");
    if(platform_driver_register(&led_driver)<0) {
        printk("platform_driver_register error\n");
        return -1;
    }
    printk("platform_driver_register succeed\n");
    return 0;

}

static void __exit led_exit(void) {
    printk("led_exit\n");
    gpio_free(gpio_num);
    misc_deregister(&led_miscdevice);
    platform_driver_unregister(&led_driver);
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);
# Makefile
export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-musl-
obj-m +=platform_led.o
kdir=/home/cunjiang/milkv/duo_256m/linux_sdk/duo-buildroot-sdk/linux_5.10
all:
	make -C $(kdir) M=$(shell pwd) modules

clean:
	rm -rf *.o *.ko
//app.c

//
// Created by cunjiang on 24-3-10.
//
#include "stdio.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[]){
    int fd= open("/dev/led_misc",O_RDWR);
    if(fd<0){
        perror("open error");
        return -1;
    } else{
        write(fd,argv[1],sizeof(argv[1]));
        close(fd);
        return 0;
    }
}
cc=riscv64-unknown-linux-musl-gcc
var=app.o
app:$(var)
	$(cc) $^ -o app -static

%.o:%.c
	$(cc) -c $< -o $@ -static

clean:
	rm -rf *.o
2 Likes