[经验分享] 【玄铁杯第三届RISC-V应用创新大赛】LicheePi 4A 6.定时器模拟PWM输出

乘简   2023-11-23 17:04 楼主

由于licheepi4a边上16个gpio中没有提供pwm引脚,所以只能用定时器来模拟,先来考虑一下可行性,拿一块24兆的51单片机来举例,因为24M单片机,每秒的时钟周期为24000000,pwm宽度为4096的话,那么其频率为24000000/4096=5859HZ,那么,想用定时器来模拟pwm的话,宽度与频率只能降低,那么频率应该只要在100hz左右就能满足需求,因为我们使用的交流电也才50hz,假如我使用1个1ms定时器,就是每秒执行1000次回调函数,即然要降低要求的话,肯定不能像单片机那样有4096的宽度下还能达到5859hz的频率,那么可以降低到10的话,正好可以调制出100hz的频率,如果用此pwm驱动电机转的话,有10种不同的速度可以选择,当然,我这里使用0.5ms的定时器,效果会更好,呵呵。。。

下面上驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/irq.h>


// GPIO号,GPIO1_5
#define GPIO1_5 453

/*------------------ 字符设备内容 ----------------------*/
#define GPIO_NAME1_5 "gpio1_5"
#define GPIO_CNT (1)

/*------------------ 设备数据结构体 ----------------------*/
struct gpio_dev_t
{
	dev_t devid;			  // 设备号
	struct cdev cdev;		  // cdev
	struct class *class;	  // 类
	struct device *device;	  // 设备
	struct device_node *nd;	  // 设备节点
	int irq_num;			  // 中断号
	int gpio;				  // 数据接收引脚
};
struct gpio_dev_t gpio_dev; // 设备数据结构体

// 按键初始化
static int gpio_init(void)
{
	int res;

	/* 申请 GPIO 资源 */
	gpio_dev.gpio = GPIO1_5;
	res = gpio_request(gpio_dev.gpio, GPIO_NAME1_5);
	if (res)
	{
		pr_err("key dev: Failed to request gpio\n");
		return res;
	}

	/* 将 GPIO 设置为输出模式 */
	gpio_direction_output(gpio_dev.gpio, 1);

	return 0;
}

// 打开设备
static int gpio_open(struct inode *inode, struct file *filp)
{
	/* 将设备数据设置为私有数据 */
	filp->private_data = &gpio_dev;

	printk("gpio_open\n");
	return 0;
}

static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
    int ret = 0;
	// struct gpio_dev_t *gpio_dev = filp->private_data;
 
	char dat;
	ret = copy_from_user(&dat, buf, sizeof(dat));
	if(ret != 0) {
        return -1;
    }
	//printk("write data %d,%d\n",dat, (int)count);

	if(dat=='1'){
		gpio_set_value(gpio_dev.gpio, 1);
	}else{
		gpio_set_value(gpio_dev.gpio, 0);
	}
	
    return 0;
}

// 关闭/释放设备
static int gpio_release(struct inode *inode, struct file *filp)
{
	int res = 0;
	printk("gpio_release\n");

	return res;
}

/* 设备操作函数结构体 */
static struct file_operations gpio_ops = {
	.owner = THIS_MODULE,
	.open = gpio_open,
	.write = gpio_write,
	.release = gpio_release,
};

// 注册字符设备驱动
static int gpio_register(void)
{
	int ret = -1; // 保存错误状态码

	/* GPIO 中断初始化 */
	ret = gpio_init();

	/* 1、创建设备号 */
	/* 采用动态分配的方式,获取设备编号,次设备号为0 */
	/* 设备名称为 GPIO_NAME1_5,可通过命令 cat /proc/devices 查看 */
	/* GPIO_CNT 为1,只申请一个设备编号 */
	ret = alloc_chrdev_region(&gpio_dev.devid, 0, GPIO_CNT, GPIO_NAME1_5);
	if (ret < 0)
	{
		pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", GPIO_NAME1_5, ret);
		goto fail_region;
	}

	/* 2、初始化 cdev */
	/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
	gpio_dev.cdev.owner = THIS_MODULE;
	cdev_init(&gpio_dev.cdev, &gpio_ops);

	/* 3、添加一个 cdev */
	/* 添加设备至cdev_map散列表中 */
	ret = cdev_add(&gpio_dev.cdev, gpio_dev.devid, GPIO_CNT);
	if (ret < 0)
	{
		pr_err("fail to add cdev \r\n");
		goto del_unregister;
	}

	/* 4、创建类 */
	gpio_dev.class = class_create(THIS_MODULE, GPIO_NAME1_5);
	if (IS_ERR(gpio_dev.class))
	{
		pr_err("Failed to create device class \r\n");
		goto del_cdev;
	}

	/* 5、创建设备,设备名是 GPIO_NAME1_5 */
	/*创建设备 GPIO_NAME1_5 指定设备名,*/
	gpio_dev.device = device_create(gpio_dev.class, NULL, gpio_dev.devid, NULL, GPIO_NAME1_5);
	if (IS_ERR(gpio_dev.device))
	{
		goto destroy_class;
	}

	return 0;

destroy_class:
	device_destroy(gpio_dev.class, gpio_dev.devid);
del_cdev:
	cdev_del(&gpio_dev.cdev);
del_unregister:
	unregister_chrdev_region(gpio_dev.devid, GPIO_CNT);
fail_region:
	gpio_free(gpio_dev.gpio);
	return -EIO;
}

// 注销字符设备驱动
static void gpio_unregister(void)
{
	/* 1、删除 cdev */
	cdev_del(&gpio_dev.cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(gpio_dev.devid, GPIO_CNT);
	/* 3、注销设备 */
	device_destroy(gpio_dev.class, gpio_dev.devid);
	/* 4、注销类 */
	class_destroy(gpio_dev.class);

	/* 释放 IO */
	gpio_free(gpio_dev.gpio);
}

// 驱动入口函数
static int __init gpio_driver_init(void)
{
	pr_info("gpio_driver_init\n");
	return gpio_register();
}

// 驱动出口函数
static void __exit gpio_driver_exit(void)
{
	pr_info("gpio_driver_exit\n");
	gpio_unregister();
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);

/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("乘简");

应用代码pwm.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>

timer_t timerid;
int fd;//文件指针
int pwm_width=0;//pwm宽度
int pwm_low=0;//低电平时间
int pwm_count=0;//计数器
int pwm_start=0;//1为开启,0为不开启,不开启时为高电平
char pwm_value=0;//当前gpio的值

long get_system_time_microsecond()
{
    struct timeval timestamp = {};
    if (0 == gettimeofday(&timestamp, NULL))
        return timestamp.tv_sec * 1000000 + timestamp.tv_usec;
    else
        return 0;
}

//设为低电平
void setLow(){
    if(pwm_value != '0'){
        pwm_value = '0';
        write(fd, &pwm_value, 1);//设置电平
    }
}

//设为高电平
void setHigh(){
    if(pwm_value != '1'){
        pwm_value = '1';
        write(fd, &pwm_value, 1);//设置电平
    }
}

//开启pwm
void setStart(){
    setHigh();//设置高电平
    pwm_start = 1;//开启pwm
}

//关闭pwm
void setClose(){
    pwm_start = 0;
}

void timer_handler(int signo, siginfo_t *info, void *v)
{
    if(pwm_start){
        if(pwm_count < pwm_low){//计算器小于所设定的低电平时间
            setLow();
        }else{
            setHigh();
        }
        pwm_count = ++pwm_count % pwm_width;//计算器+1
    }else{//没开启
        if(pwm_value != 0){//不为高电平
            pwm_count=0;//计算器
            setHigh();
            pwm_value=0;
        }
    }
}

int main(int argc, char *argv[])
{
    struct sigevent sev;
    struct itimerspec its;
    struct sigaction sa;

    if(argc!=3){
        printf("./pwm 5 3\n");
        return -1;
    }

    pwm_width = atoi(argv[1]);
    pwm_low = atoi(argv[2]);
    if(pwm_low > pwm_width)pwm_low = pwm_width;

    fd=open("/dev/gpio1_5", O_RDWR);
    if(fd<0){
        printf("can't open file %s",argv[1]);
        return -1;
    }

    // 设置信号处理函数
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = timer_handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
        printf("sigaction() failed\n");
        return 1;
    }

    // 创建定时器
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &timerid;
    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
        printf("timer_create() failed\n");
        return 1;
    }

    // 设置定时器
    its.it_value.tv_sec = 0;
    its.it_value.tv_nsec =    500000;//500us
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 500000;
    if (timer_settime(timerid, 0, &its, NULL) == -1) {
        printf("timer_settime() failed\n");
        return 1;
    }

    setStart();

    // 等待定时器事件
    while (1) {
        sleep(1);
    }

    close(fd);
    return 0;
}

执行效果测试,输入

sudo ./app 10 0

此时量GPIO1_5的电压为1.8V

sudo ./app 10 2

此时量电压为1.44V

sudo ./app 10 5 

电压为0.9V

sudo ./app 10 9

电压为0.17V

sudo ./app 10 10

此时为0V

 

但这种办法,会占用18%左右的CPU,不知道有没有更好的办法实现

1700730421191.png  

 

 

本帖最后由 乘简 于 2023-11-23 17:07 编辑

回复评论 (2)

用0.5ms的定时器,效果会更好,这个是真的

点赞  2023-11-24 07:26
引用: Jacktang 发表于 2023-11-24 07:26 用0.5ms的定时器,效果会更好,这个是真的

就是直接写个定时器程序,回调函数中什么语句都不写,CPU占用也是出奇的高,难道是linux的定时器冗余代码太多了吗?这么吃CPU,真是无语。。。

点赞  2023-11-24 08:38
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复