驱动开发(2)|鲁班猫rk3568简单GPIO波形操控

发布于:2025-05-30 ⋅ 阅读:(17) ⋅ 点赞:(0)

   上篇文章写了如何下载内核源码、编译源码的详细步骤,以及一个简单的官方demo编译,今天分享一下如何根据板子的引脚写自己控制GPIO进行高低电平反转。
想要控制GPIO之前要学会看自己的引脚分布图,我用的是鲁班猫RK3568,引脚分布图如下所示:
在这里插入图片描述
具体板子的引脚示意图可以在这里看:教程官网

1 通过shell命令进行GPIO控制

1.1 使用GPIO sysfs接口控制IO

#以下所有操作均需要打开管理者权限使用
#使能引脚GPIO1_A0
echo 32 > /sys/class/gpio/export

#设置引脚为输入模式
echo in > /sys/class/gpio/gpio32/direction
#读取引脚的值
cat /sys/class/gpio/gpio32/value

#设置引脚为输出模式
echo out > /sys/class/gpio/gpio32/direction
#设置引脚为低电平
echo 0 > /sys/class/gpio/gpio32/value
#设置引脚为高电平
echo 1 > /sys/class/gpio/gpio32/value

#复位引脚
echo 32 > /sys/class/gpio/unexport

这里需要注意的是,设置引脚模式为输出模式之后才能对引脚高低电平进行设置,其他的没什么好主意的,多敲几遍啥都懂了。至于官方给的计算引脚的位置,我根本没看,因为图中都给你算好了,文章开头的引脚分布图中的编号一列就为引脚具体的值。

1.2 使用libgpiod控制IO

首先要下载libgpio:

sudo apt install gpiod

具体使用一共就这几个接口:
在这里插入图片描述

设置GPIO1_A1为高电平:

gpioset 1 1=1

设置GPIO1_A1为低电平:

gpioset 1 1=0

这里的gpioset后面第一个1是GPIO的组号,因为是GPIO1所以为1,我想换个板子一共五组(GPIO0-GPIO4),所以范围是0-4。第二个是索引号,具体计算方式可以参照下图:
在这里插入图片描述

2 通过代码来操控GPIO接口

2.1 通过GPIO 子系统设置引脚

直接使用gpio_set_value,这种方式与1.1类似,很简单,也是直接看引脚途中的编号就可以:

//设置GPIO1_A0为高电平
gpio_set_value(32,1);
//设置GPIO1_A0为低电平
gpio_set_value(32,0);

这种方式操作GPIO,就算直接拉高在拉低操作延时都会在500ns左右。
这是我写的部分代码,感兴趣的兄弟可以看一下:
pin_ctl.c:

#include "pin_ctl.h"

void set_ce_high()
{
    gpio_set_value(GPIO_A1, data & 0x01);
}
static void set_pinA_value(u8 data, int signal)
{
    if(signal == 0)
    {
        //如果是上电时序和下电时序0
        gpio_set_value(GPIO_A1, data & 0x01);
    }
    else
    {
        //不是上电时序,是选择喷头
        gpio_set_value(GPIO_A1, data & 0x01);
        gpio_set_value(GPIO_A2, (data >> 1) & 0x01);
        gpio_set_value(GPIO_A3, (data >> 2) & 0x01);
        gpio_set_value(GPIO_A4, (data >> 3) & 0x01);
        gpio_set_value(GPIO_A5, (data >> 4) & 0x01);
        gpio_set_value(GPIO_A6, (data >> 5) & 0x01);
        gpio_set_value(GPIO_A7, (data >> 6) & 0x01);

    }
}
static void set_pinD_value(u8 data, int signal)
{
    if(signal == 0)
    {
        //如果是上电时序和下电时序0
        gpio_set_value(GPIO_D1, data & 0x01);
    }
    else
    {
        //不是上电时序
        gpio_set_value(GPIO_D1, data & 0x01);
        gpio_set_value(GPIO_D2, (data >> 1) & 0x01);
        gpio_set_value(GPIO_D3, (data >> 2) & 0x01);
        gpio_set_value(GPIO_D4, (data >> 3) & 0x01);

    }

}
static void set_pinS_value(u8 data, int signal)
{
    if(signal == 0)
    {
        //如果是上电时序和下电时序0
        gpio_set_value(GPIO_S1, data & 0x01);
    }
    else
    {
        //不是上电时序
        gpio_set_value(GPIO_S1, data & 0x01);
        gpio_set_value(GPIO_S2, (data >> 1) & 0x01);
        gpio_set_value(GPIO_S3, (data >> 2) & 0x01);
        gpio_set_value(GPIO_S4, (data >> 3) & 0x01);

    }

}


// 上电函数
static void power_on_sequence(void)
{
    gpio_set_value(GPIO_VL, 1);
    msleep(TPO_MS);
    
    gpio_set_value(GPIO_VPK, 1);
    gpio_set_value(GPIO_VPC, 1);
    // 参考书上最小0.5us
    //udelay(TW_US);
    udelay(TW_US);
    
    gpio_set_value(GPIO_CE, 1);
    // 6. 等待tn时间(5us),参考书上最小5us
    udelay(TN_US);
    
    gpio_set_value(GPIO_CK, 1);

    //1111->0x0F
    set_pinD_value(0x0F, 0);
    gpio_set_value(GPIO_SH, 1); 
    //1111111->0x7F
    set_pinA_value(0x7F, 0);
    //1111->0x0F
    set_pinS_value(0x0F, 0);

    printk(KERN_INFO "Full power sequence completed\n");
}
//下电函数
static void power_off_sequence(void)
{
    //下电顺序S->A->SH->D->CH->5us->CE->0.5/1us->VPC->VPK->1ms->VL
    set_pinS_value(0x00, 0);
    set_pinA_value(0x00, 0);
    gpio_set_value(GPIO_SH, 0);
    set_pinD_value(0x00, 0);
    gpio_set_value(GPIO_CK, 0);

    udelay(TN_US);
    gpio_set_value(GPIO_CE, 0);
    udelay(TW_US);
    gpio_set_value(GPIO_VPK, 0);
    gpio_set_value(GPIO_VPC, 0);

    msleep(TPO_MS);
    gpio_set_value(GPIO_VL, 0);


}
void SetPinDValue(u8 data)
{
	(data & 0x01) == 1 ? set_hig(5):set_low(5);
	((data >> 1) & 0x01) == 1 ? set_hig(6):set_low(6);
	((data >> 2) & 0x01) == 1 ? set_hig(7):set_low(7);
	((data >> 3) & 0x01) == 1 ? set_hig(8):set_low(8);

}

// 打印时序
static void print_sequence(void)
{
    //上电顺序
    //CE->CK->D1-D4->SH->A1-A7->S1-S4
    //选择第一个喷头(A1-A7)
    set_pinA_value(0x07, 1);
    gpio_set_value(GPIO_CK, 0);
    ndelay(50);
    gpio_set_value(GPIO_CE, 1);
    ndelay(50);
    gpio_set_value(GPIO_CK, 1);
    ndelay(10);
    set_pinD_value(0x0F, 1);
    ndelay(40);


}

static int ck_thread_func(void *data)
{
    while (!kthread_should_stop()) 
    {
        //ck信号测试
        //set_ck_one_cycle();

    }
    
    printk(KERN_INFO "Power CK thread is stopping...\n");
    return 0;
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("limingzhao");
MODULE_DESCRIPTION("inkjet enable pro");
//上电时序信号
EXPORT_SYMBOL(power_on_sequence);
//下电时序信号
EXPORT_SYMBOL(power_off_sequence);
//设置引脚
EXPORT_SYMBOL(set_pinA_value);
EXPORT_SYMBOL(set_pinD_value);
EXPORT_SYMBOL(set_pinS_value);
//测试程序pin_s_test
EXPORT_SYMBOL(pin_s_test);
EXPORT_SYMBOL(pin_s_test1);
EXPORT_SYMBOL(pin_s_test2);



2.2 直接操作硬件寄存器

这是官方给的示例代码,功能是点亮板子上的一个led灯:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define DEV_NAME            "led_chrdev"
#define DEV_CNT                 (1)

#define GPIO0_BASE (0xfdd60000)
//每组GPIO,有2个寄存器,对应32个引脚,每个寄存器负责16个引脚;
//一个寄存器32位,其中高16位都是使能位,低16位对应16个引脚,每个引脚占用1比特位
#define GPIO0_DR_L (GPIO0_BASE + 0x0000)
#define GPIO0_DR_H (GPIO0_BASE + 0x0004)
#define GPIO0_DDR_L (GPIO0_BASE + 0x0008)
#define GPIO0_DDR_H (GPIO0_BASE + 0x000C)

static dev_t devno;
struct class *led_chrdev_class;

struct led_chrdev {
	struct cdev dev;
	unsigned int __iomem *va_dr; 	// 数据寄存器,设置输出的电压
	unsigned int __iomem *va_ddr; 	// 数据方向寄存器,设置输入或者输出

	unsigned int led_pin; // 偏移
};

static int led_chrdev_open(struct inode *inode, struct file *filp)
{	
	unsigned int val = 0;
	struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev,dev);
	filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);

	printk("open\n");
	//设置输出模式
	val = ioread32(led_cdev->va_ddr);
	val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));
	val |= ((unsigned int)0X1 << (led_cdev->led_pin));
	iowrite32(val,led_cdev->va_ddr);
	
	//输出高电平
	val = ioread32(led_cdev->va_dr);
	val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));
	val |= ((unsigned int)0x1 << (led_cdev->led_pin));
	iowrite32(val, led_cdev->va_dr);

	return 0;
}

static int led_chrdev_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_chrdev_write(struct file *filp, const char __user * buf,
				size_t count, loff_t * ppos)
{
	unsigned long val = 0;
	char ret = 0;

	struct led_chrdev *led_cdev = (struct led_chrdev *)filp->private_data;
	printk("write \n");
	get_user(ret, buf);
	val = ioread32(led_cdev->va_dr);
	printk("val = %lx\n", val);
	if (ret == '0'){
		val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));
		val &= ~((unsigned int)0x01 << (led_cdev->led_pin));   /*设置GPIO引脚输出低电平*/
	}
	else{
		val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));
		val |= ((unsigned int)0x01 << (led_cdev->led_pin));    /*设置GPIO引脚输出高电平*/
	}
	iowrite32(val, led_cdev->va_dr);
	printk("val = %lx\n", val);
	return count;
}

static struct file_operations led_chrdev_fops = {
	.owner = THIS_MODULE,
	.open = led_chrdev_open,
	.release = led_chrdev_release,
	.write = led_chrdev_write,
};

static struct led_chrdev led_cdev[DEV_CNT] = {
	{.led_pin = 7}, 	//偏移,高16引脚,GPIO0_C7
};

static __init int led_chrdev_init(void)
{
	int i = 0;
	dev_t cur_dev;

	printk("led_chrdev init (lubancat2  GPIO0_C7)\n");
	
	led_cdev[0].va_dr   = ioremap(GPIO0_DR_H, 4);	 //
	led_cdev[0].va_ddr  = ioremap(GPIO0_DDR_H, 4);	 // 

	alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);

	led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");

	for (; i < DEV_CNT; i++) {
		cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
		led_cdev[i].dev.owner = THIS_MODULE;

		cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);

		cdev_add(&led_cdev[i].dev, cur_dev, 1);

		device_create(led_chrdev_class, NULL, cur_dev, NULL,
			      DEV_NAME "%d", i);
	}

	return 0;
}

module_init(led_chrdev_init);

static __exit void led_chrdev_exit(void)
{
	int i;
	dev_t cur_dev;
	printk("led chrdev exit (lubancat2  GPIO0_C7)\n");
	
	for (i = 0; i < DEV_CNT; i++) {
		iounmap(led_cdev[i].va_dr); 		// 释放模式寄存器虚拟地址
		iounmap(led_cdev[i].va_ddr); 	// 释放输出类型寄存器虚拟地址
	}

	for (i = 0; i < DEV_CNT; i++) {
		cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);

		device_destroy(led_chrdev_class, cur_dev);

		cdev_del(&led_cdev[i].dev);

	}
	unregister_chrdev_region(devno, DEV_CNT);
	class_destroy(led_chrdev_class);

}

module_exit(led_chrdev_exit);

MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");

具体源代码位置:led_cdev.c
这种方式操作GPIO,直接拉高在拉低操作延时在160ns左右。
这里如果兄弟们想封装自己的GPIO接口,可以通过官方文档去查找:引脚说明
简单来说就是将寄存器分为了高位寄存器和低位寄存器,其中A、B两组为地位寄存器,C和D为高位寄存器,查看Rockchip_RK3568_TRM_Part1_V1.3-20220930P.PDF可以知道GPIO0的基地址为:
在这里插入图片描述
GPIO1、GPIO2、GPIO3、GPIO4的基地址为:
在这里插入图片描述
所以可以知道上述代码中地址宏定义:

//GPIO0
#define GPIO0_BASE (0xfdd60000)
//AB用这俩
#define GPIO0_DR_L (GPIO0_BASE + 0x0000)
#define GPIO0_DDR_L (GPIO0_BASE + 0x0008)
//CD用这俩
#define GPIO0_DR_H (GPIO0_BASE + 0x0004)
#define GPIO0_DDR_H (GPIO0_BASE + 0x000C)

的出处,明显可以看到这是使用的GPIO0,至于引脚设置,可以看这里:
在这里插入图片描述
具体出处及来源,看这里5.4.2.1. 定义GPIO寄存器物理地址

总结

  本文主要分享了GPIO控制的四种方式,shell两种控制方式,和使用代码控制的两种方式,重点要说的是如果你追求极致性能和对硬件有较高的控制要求,ioread32/iowrite32 可能更合适。反之,若追求易用性和代码的可维护性,gpio_set_value 更为推荐。


网站公告

今日签到

点亮在社区的每一天
去签到