duo256m的多种GPIO使用方式及代码

摘要

关于GPIO的使用,本文给出了多种操作方式,包括使用终端控制、系统IO(open、write等)控制、寄存器控制、内核态控制、cdev字符设备驱动控制、以及misc杂项设备驱动等,并且给出了简洁可用的代码参考。此外,本文还简要说明了工具链的安装、编译流程、nfs配置等。
另外,本文提到的led,您可能需要自己用面包板搭建电路,这很简单,只需要在想要控制的GPIO引脚与地(GND)之间连接一个led灯即可,请注意极性方向。并且,以下代码均在sipeed LicheeRV Nano上运行成功。
补充:

  1. 由于本人技术水平仍不足,如有疏漏与错误之处,敬请谅解。
  2. 由于本人目前仍在读研,时间紧张,文章写得或许过于简略,如有不懂的地方可以在评论区提出,我尽力解答。
  3. 本文为系列文章的第一篇,关于GPIO的使用,显然还缺少了 设备树的控制,该部分将在本系列文章的第二篇补充。

0.前期准备

0.1 设置nfs

参考链接

nfs可以实现电脑端与duo文件互传。

#以下命令在电脑端执行
ifconfig										#查看电脑端IP,很重要,请记住,在下方会用到
sudo apt-get install nfs-kernel-server    		#安装nfs-kernel-server
sudo apt-get install gedit						#安装gedit编辑器
sudo mkdir /home/cunjiang/nfs					#创建电脑端nfs文件夹,该文件夹位置可以自行设置
sudo gedit /etc/exports							#打开配置文件
/home/cunjiang/nfs *(rw,sync,no_root_squash)	#在文件末尾添加
# 在/etc/exports文件末尾添加
/home/cunjiang/nfs *(rw,sync,no_root_squash)	
/etc/init.d/nfs-kernel-server restart			#重启 nfs 服务
#以下命令在duo端执行
mkdir /mnt/nfs/												  #创建duo端nfs文件夹,该文件夹位置可以自行设置
mount -t nfs -o nolock 电脑端IP:/home/cunjiang/nfs /mnt/nfs/	#挂载nfs
ifconfig												      #查看duo端IP,很重要,请记住,在下方会用到

0.2 安装编译工具链

参考链接

#以下命令在电脑端执行
sudo apt-get install wget git make						#安装依赖工具
git clone https://github.com/milkv-duo/duo-examples.git	#获取工具链
cd duo-examples											#进入文件夹
source envsetup.sh										#加载编译环境

/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/路径下即为编译所需的工具链,通常使用的是riscv64-unknown-linux-musl-gcc,请在该文件所在文件夹右键打开终端并使用命令pwd查看该gcc编译器路径,等会儿会用到。

工具链的编译测试请自行参考上方参考链接中的说明哦。

0.3 预编译

参考链接

依次在电脑终端执行以下命令:

export ARCH=riscv

export CROSS_COMPILE=/home/cunjiang/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-

cp /home/cunjiang/milkv/duo-buildroot-sdk/build/boards/cv181x/cv1812cp_milkv_duo256m_sd/linux/cvitek_cv1812cp_milkv_duo256m_sd_defconfig /home/cunjiang/milkv/duo-buildroot-sdk/linux_5.10/arch/riscv/configs/cvitek_cv1812cp_milkv_duo256m_sd_defconfig

make -C /home/cunjiang/milkv/duo_256m/duo-buildroot-sdk/linux_5.10/ cvitek_cv1812cp_milkv_duo256m_sd_defconfig

make -C /home/cunjiang/milkv/duo_256m/duo-buildroot-sdk/linux_5.10

上方的路径请根据自己的实际情况修改。

验证

创建helloworld.c文件,并写入以下代码:

//helloworld.c

#include "linux/init.h"
#include "linux/module.h"

static int __init helloworld_init(void) {
    printk("helloworld\n");
    return 0;
}

static void __exit helloworld_exit(void) {
    printk("goodbye\n");
}

MODULE_LICENSE("GPL");
module_init(helloworld_init);
module_exit(helloworld_exit);

创建Makefile文件,注意:kdir请根据自己的实际情况修改路径

# Makefile

export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-musl-
obj-m +=helloworld.o
kdir=/home/cunjiang/milkv/duo_256m/duo-buildroot-sdk/linux_5.10
pwd=$(shell pwd)
all:
	make -C $(kdir) M=$(pwd) modules

将以上两个文件放入同一文件夹,使用make命令编译,然后将编译出来的helloworld.ko文件传到板子上,使用insmod helloworld.ko加载模块,使用rmmod helloworld.ko卸载模块,最后使用dmesg查看。

0.4 命令alias(可选)

该部分是可选的,只是为了使用方便一点。

Ctrl + Alt+ T打开终端,输入:

sudo gedit .bashrc

在打开的文件末尾补充以下:

duo端IPgcc编译器路径请查看上方教程。

duo端IP示例:192.168.123.456 补充:若板子无网口,则duo端IP为192.168.42.1

gcc编译器路径示例:/home/cunjiang/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc

alias duo256m_login='ssh root@duo端IP'
alias duo256m_gcc='gcc编译器路径/riscv64-unknown-linux-musl-gcc'
function duo256m_scp(){
	scp $1 root@duo端IP:/mnt/nfs
}

保存并关闭文件,然后在终端输入:

source .bashrc
  1. duo256m_login用于通过ssh登陆duo;

  2. duo256m_gcc用于编译c文件,例如:

    duo256m_gcc  app.c -o app
    
  3. duo256m_scp用于传输文件,例如:

duo256m_scp app

1.控制LED亮灭

1.1.1 命令控制

ls /sys/class/gpio/
echo 494 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio494/direction
echo 1 > /sys/class/gpio/gpio494/value
echo 0 > /sys/class/gpio/gpio494/value

GPIO表格

GPIO name GPIO pin GPIO number Notes
GPIOA14 19 494
GPIOA15 20 495
GPIOA16 16 496
GPIOA17 17 497
GPIOA22 24 502
GPIOA23 21 503
GPIOA24 22 504
GPIOA25 25 505
GPIOA26 27 506
GPIOA27 26 507
GPIOA28 1 508
GPIOA29 2 509

1.1.2 C程序

/// led.c



#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd=open("/sys/class/gpio/export",O_WRONLY);
    if(fd==-1) {
        printf("export open error\n");
        return -1;
    }
    write(fd,"494",sizeof("494"));
    close(fd);

    fd=open("/sys/class/gpio/gpio494/direction",O_RDWR);
    if(fd==-1) {
        printf("direction open error\n");
        return -1;
    }
    write(fd,"out",sizeof("out"));
    close(fd);

    fd=open("/sys/class/gpio/gpio494/value",O_RDWR);
    if(fd==-1) {
        printf("value open error\n");
        return -1;
    }


    int i=20;
    while (i--) {
        i%2==0?write(fd,"1",sizeof("1")):write(fd,"0",sizeof("0"));
        sleep(1);
    }


    close(fd);


    return 0;
}

# Makefile




cc=/home/cunjiang/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc
var=led.o

led:$(var)
	$(cc) $^ -o led -static

%.o:%.c
	$(cc) -c $< -o $@ -static

clean:
	rm -rf *.o

1.2 驱动控制

1.2.1 简易代码

// led_kernel.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/gpio.h"


int gpio_num=494;

static int __init led_init(void) {
    printk("led_init\n");
    gpio_request(gpio_num,NULL);
    gpio_direction_output(gpio_num,0);
    gpio_set_value(gpio_num,1);
    return 0;
}

static void __exit led_exit(void) {
    printk("led_exit\n");
    gpio_free(gpio_num);
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);
# Makefile
export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-musl-
obj-m +=led_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

1.2.2 misc代码

参考链接

//led_kernel.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/gpio.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/uaccess.h"

int gpio_num=494;

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;
    }
}

int led_open(struct inode *inode, struct file *file){
    printk("led_open\n");
    gpio_request(gpio_num,NULL);
    gpio_direction_output(gpio_num,0);
    return 0;
}

int led_release(struct inode *inode, struct file *file){
    printk("led_release\n");
    gpio_free(gpio_num);
    return 0;
}

static struct file_operations led_fops={
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release
};

static struct miscdevice led_dev={
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &led_fops,
    .name = "led_dev"
};


static int __init led_init(void) {
    printk("led_init\n");
    if(misc_register(&led_dev)<0) {
        printk("misc_register error\n");
        return -1;
    }else {
        printk("misc_register succeed\n");
        return 0;
    }
}

static void __exit led_exit(void) {
    printk("led_exit\n");
    misc_deregister(&led_dev);
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);
// app.c
//
// Created by cunjiang on 24-2-28.
//
#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_dev",O_RDWR);
    if(fd<0) {
        perror("open error");
        return -1;
    }else {
        write(fd,argv[1],sizeof(argv[1]));
        close(fd);
        return 0;
    }
}
# app Makefile
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

1.3 操作寄存器控制

该部分通过寄存器方式控制led,仅led_kernel.c作了更改,其余文件不变。

参考链接

在SG2002的寄存器(TRM)手册,可以看到具体操作顺序:先配置DDR寄存器设置GPIO作为输入还是输出,再配置DR寄存器设置高低电平。

//led_kernel.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/gpio.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/uaccess.h"
#include "linux/io.h"

#define GPIO494_DDR (0x03020000+0x000)
#define GPIO494_DR (0x03020000+0x004)
unsigned int *vir_gpio494_ddr;
unsigned int *vir_gpio494_dr;


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);
            *vir_gpio494_dr|=(1<<14);
        }else if(kernel_buf[0]=='0') {
            //gpio_set_value(gpio_num,0);
            *vir_gpio494_dr&=~(1<<14);
        }
        return 0;
    }
}

int led_open(struct inode *inode, struct file *file){
    printk("led_open\n");
    // gpio_request(gpio_num,NULL);
    // gpio_direction_output(gpio_num,0);
    return 0;
}

int led_release(struct inode *inode, struct file *file){
    printk("led_release\n");
    //gpio_free(gpio_num);
    return 0;
}

static struct file_operations led_fops={
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release
};

static struct miscdevice led_dev={
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &led_fops,
    .name = "led_dev"
};


static int __init led_init(void) {
    printk("led_init\n");
    if(misc_register(&led_dev)<0) {
        printk("misc_register error\n");
        return -1;
    }else {
        printk("misc_register succeed\n");

        vir_gpio494_ddr=ioremap(GPIO494_DDR,4);
        vir_gpio494_dr=ioremap(GPIO494_DR,4);
        if (vir_gpio494_ddr==NULL ||vir_gpio494_dr==NULL) {
            printk("ioremap error\n");
            return -1;
        }else {
            printk("ioremap succeed\n");
        }
        *vir_gpio494_ddr|=(1<<14);
        *vir_gpio494_dr|=(1<<14);
        return 0;
    }
}

static void __exit led_exit(void) {
    printk("led_exit\n");
    misc_deregister(&led_dev);
    iounmap(vir_gpio494_ddr);
    iounmap(vir_gpio494_dr);
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);

1.4 字符设备cdev代码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/gpio.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/uaccess.h"
#include "linux/io.h"
#include "linux/cdev.h"
#include "linux/device.h"

#define GPIO494_DDR (0x03020000+0x000)
#define GPIO494_DR (0x03020000+0x004)
unsigned int *vir_gpio494_ddr;
unsigned int *vir_gpio494_dr;

dev_t dev_id;
struct class *class;
struct device *device;
struct cdev cdev={
    .owner = THIS_MODULE
};



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);
            *vir_gpio494_dr|=(1<<14);
        }else if(kernel_buf[0]=='0') {
            //gpio_set_value(gpio_num,0);
            *vir_gpio494_dr&=~(1<<14);
        }
        return 0;
    }
}

int led_open(struct inode *inode, struct file *file){
    printk("led_open\n");
    // gpio_request(gpio_num,NULL);
    // gpio_direction_output(gpio_num,0);
    vir_gpio494_ddr=ioremap(GPIO494_DDR,4);
    vir_gpio494_dr=ioremap(GPIO494_DR,4);
    if (vir_gpio494_ddr==NULL ||vir_gpio494_dr==NULL) {
        printk("ioremap error\n");
        return -1;
    }else {
        printk("ioremap succeed\n");
    }
    *vir_gpio494_ddr|=(1<<14);
    *vir_gpio494_dr|=(1<<14);
    return 0;
}

int led_release(struct inode *inode, struct file *file){
    printk("led_release\n");
    //gpio_free(gpio_num);
    return 0;
}

static struct file_operations led_fops={
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release
};


static int __init led_init(void) {
    printk("led_init\n");
    if(alloc_chrdev_region(&dev_id,0,1,"led_chr_device")<0) {
        printk("alloc_chrdev_region error\n");
        return -1;
    }else {
        printk("alloc_chrdev_region succeed\n");
        printk("major_id: %d, minor_id: %d\n", MAJOR(dev_id), MINOR(dev_id));
        cdev_init(&cdev,&led_fops);
        cdev_add(&cdev,dev_id,1);
        class=class_create(THIS_MODULE,"led_chr_class");
        device=device_create(class,NULL,dev_id,NULL,"led_dev");
        return 0;
    }
}

static void __exit led_exit(void) {
    printk("led_exit\n");
    cdev_del(&cdev);
    device_destroy(class,dev_id);
    class_destroy(class);
    iounmap(vir_gpio494_ddr);
    iounmap(vir_gpio494_dr);
}

MODULE_LICENSE("GPL");
module_init(led_init);
module_exit(led_exit);

1 Like

In the schematic of duo256, among other functions of the LED pin, there is PWM10. The datasheet and the duo pin configuration utility also mentions this function, however, the same datasheet states, there are only for PWMs in this chip, which sounds controversial to me… When I tried setting up pwm using registers in my program, it failed (segfault error or something).

Good evening. I spent some time researching pwm after reading your reply a few days ago and now I think I have solved your problem. I will write a document on the specific process and code on Saturday and Sunday, and then I will send the link to the document to you :grinning:

1 Like