前言
本文为该系列文章第二篇。第一篇地址请点这里。
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各个寄存器的地址偏移(Address Offset):
在上图中,看到PWM0和PWM1,读者可能会存在疑惑,PWM0和PWM1到底是什么呢?这就是上面提到的第二种命名方式,例如对于duo256上的PWM5,就是第1组PWM中的PWM1(PWM4是第1组PWM的PWM0)。
综上所述,我们需要的寄存器是:HLPERIOD1,PERIOD1,POLARITY,PWMSTART,PWM_OE,将基地址加上地址偏移就是对应寄存器的地址。
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