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

上篇文章写了如何下载内核源码、编译源码的详细步骤,以及一个简单的官方demo编译,今天分享一下如何根据板子的引脚写自己控制GPIO进行高低电平反转。

想要控制GPIO之前要学会看自己的引脚分布图,我用的是鲁班猫RK3568,引脚分布图如下所示:

具体板子的引脚示意图可以在这里看:教程官网

1 通过shell命令进行GPIO控制

1.1 使用GPIO sysfs接口控制IO

bash 复制代码
#以下所有操作均需要打开管理者权限使用
#使能引脚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:

shell 复制代码
sudo apt install gpiod

具体使用一共就这几个接口:

设置GPIO1_A1为高电平:

shell 复制代码
gpioset 1 1=1

设置GPIO1_A1为低电平:

shell 复制代码
gpioset 1 1=0

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

2 通过代码来操控GPIO接口

2.1 通过GPIO 子系统设置引脚

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

c 复制代码
//设置GPIO1_A0为高电平
gpio_set_value(32,1);
//设置GPIO1_A0为低电平
gpio_set_value(32,0);

这种方式操作GPIO,就算直接拉高在拉低操作延时都会在500ns左右。

这是我写的部分代码,感兴趣的兄弟可以看一下:

pin_ctl.c:

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灯:

c 复制代码
#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的基地址为:

所以可以知道上述代码中地址宏定义:

c 复制代码
//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 更为推荐。

相关推荐
sukalot38 分钟前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot3 小时前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8282 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday2 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发
被遗忘的旋律.2 天前
Linux驱动开发笔记(十)——中断
linux·驱动开发·笔记
路溪非溪2 天前
Linux驱动如何向应用层提供sysfs操作接口
linux·arm开发·驱动开发
ShiMetaPi3 天前
操作【GM3568JHF】FPGA+ARM异构开发板 使用指南:蓝牙
arm开发·嵌入式硬件·fpga开发·rk3568
sukalot3 天前
window显示驱动开发—监视筛选器驱动程序(三)
驱动开发
墨染天姬3 天前
【android 驱动开发九】生产者-消费者模型
android·驱动开发
搞一搞汽车电子4 天前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车