摘要
关于GPIO的使用,本文给出了多种操作方式,包括使用终端控制、系统IO(open、write等)控制、寄存器控制、内核态控制、cdev字符设备驱动控制、以及misc杂项设备驱动等,并且给出了简洁可用的代码参考。此外,本文还简要说明了工具链的安装、编译流程、nfs配置等。
另外,本文提到的led,您可能需要自己用面包板搭建电路,这很简单,只需要在想要控制的GPIO引脚与地(GND)之间连接一个led灯即可,请注意极性方向。并且,以下代码均在sipeed LicheeRV Nano上运行成功。
补充:
- 由于本人技术水平仍不足,如有疏漏与错误之处,敬请谅解。
- 由于本人目前仍在读研,时间紧张,文章写得或许过于简略,如有不懂的地方可以在评论区提出,我尽力解答。
- 本文为系列文章的第一篇,关于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端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
-
duo256m_login用于通过ssh登陆duo;
-
duo256m_gcc用于编译c文件,例如:
duo256m_gcc app.c -o app
-
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 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);