738|2

53

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

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

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

由于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(×tamp, 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,不知道有没有更好的办法实现

 

 

 

最新回复

用0.5ms的定时器,效果会更好,这个是真的   详情 回复 发表于 2023-11-24 07:26
点赞 关注
 
 

回复
举报

6893

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

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

点评

就是直接写个定时器程序,回调函数中什么语句都不写,CPU占用也是出奇的高,难道是linux的定时器冗余代码太多了吗?这么吃CPU,真是无语。。。  详情 回复 发表于 2023-11-24 08:38
 
 
 

回复

53

帖子

0

TA的资源

一粒金砂(中级)

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

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

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

开源项目 更多>>
    随便看看
    查找数据手册?

    EEWorld Datasheet 技术支持

    相关文章 更多>>
    关闭
    站长推荐上一条 1/10 下一条
    福禄克有奖直播:高精度测温赋能电子制造 报名中!
    直播时间:2025年2月28日(周五)上午10:00
    直播主题:高精度测温赋能电子制造
    小伙伴们儿快来报名直播吧~好礼等你拿!

    查看 »

     
    EEWorld订阅号

     
    EEWorld服务号

     
    汽车开发圈

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表