摘要
关于GPIO的使用,本文给出了多种操作方式,包括使用终端控制、系统IO(open、write等)控制、寄存器控制、内核态控制、cdev字符设备驱动控制、以及misc杂项设备驱动等,并且给出了简洁可用的代码参考。此外,本文还简要说明了工具链的安装、编译流程、nfs配置等。
另外,本文提到的led,您可能需要自己用面包板搭建电路,这很简单,只需要在想要控制的GPIO引脚与地(GND)之间连接一个led灯即可,请注意极性方向。并且,以下代码均在sipeed LicheeRV Nano上运行成功。
一、前期准备
1.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,很重要,请记住,在下方会用到
1.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编译器路径,等会儿会用到。
工具链的编译测试请自行参考上方参考链接中的说明哦。
1.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
查看。
1.4 命令alias(可选)
该部分是可选的,只是为了使用方便一点。
Ctrl
+ Alt
+ T
打开终端,输入:sudo gedit .bashrc
在打开的文件末尾补充以下:
duo端IP
、gcc编译器路径
请查看上方教程。
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
2 控制LED亮灭
2.1 命令控制与C程序
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
/// 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
2.2 驱动控制
简易代码
// 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
###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
2.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);
2.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);