使用F1C200S从零制作掌机之点灯大法

本文详述如何在Linux环境下,利用F1C200S芯片进行嵌入式硬件开发,通过字符设备驱动实现点灯操作。涵盖了设备号、设备节点的概念,讲解了register_chrdev函数注册字符设备,以及通过设备树配置LED驱动,包括pinctrl和gpio子系统的应用。同时,介绍了平台设备驱动在无设备树和有设备树情况下的使用,最后展示了如何配置内核启用GPIO LEDs并进行控制。
摘要由CSDN通过智能技术生成

驱动知识点

Linux驱动之字符设备驱动框架与示例模板

https://blog.csdn.net/Wang_XB_3434/article/details/131739858

设备号

https://blog.csdn.net/qq_53221728/article/details/129464604

Linux设备主要分为字符设备、块设备、网络设备

字符设备:能够像字节流一样被访问且没有缓冲是按顺序访问的设备,当对字符设备发出读写请求,相应的IO操作立即发生。Linux系统中很多设备都是字符设备,绝大多数的设备都是字符设备。比如LED、按键、键盘、串口、传感器、LCD。字符设备驱动通过字符设备文件来访问。
块设备:按照数据块来访问且有缓冲是具有随机访问能力的设备,比如访问硬盘就不是一个字节一个字节的访问,而是直接访问数据块,数据块的大小是固定的但是不同的系统不一样。比如内存、磁盘、SD卡、U盘。块设备驱动通过块设备文件来访问。
网络设备:网络设备由Linux的网络子系统驱动,负责数据包的发送和接收,而不是面向流设备,因此在Linux系统文件系统中网络设备没有节点。访问网络设备不通过文件,通过套接字(网络通信地址)访问。

在Linux内核源码中,使用结构体dev_t类型来定义设备号。实际上dev_t类型为32位的unsigned int类型(在Linux内核源码中可以进行跟踪)。其中高12位作为存储主设备号,低20位作为存储次设备号。

可以在/proc/devices文件中查询哪些主设备号以及被使用了。

内核中提供了操作设备号的宏

MAJOR(设备号);//通过设备号获取主设备号
MINOR(设备号);//通过设备号获取次设备号
MKDEV(主设备号,次设备号);//通过主设备号和次设备号构造设备号

实际上在Linux内核中提供了两种方法可以进行分配主设备号。分别为静态申请设备号和动态分配设备号。

静态申请

  • 选择一个内核中未被使用的主设备号(在/proc/devices中查看)。

  • 根据设备个数分配次设备号,一般从0开始。

  • 使用宏构造完整的设备号。

  • 调用函数register_chrdev_region向系统申请。

  • 不再使用设备号需要注销,通过函数unregister_chrdev_region实现注销。

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

#define CDD_MAJOR 220  //主设备号
#define CDD_MINOR 0  //起始次设备号
#define CDD_COUNT 1  //设备号个数

dev_t dev;  //设备号

int cdd_init(void)  //加载函数
{
	int ret;

	//构造设备号
	dev = MKDEV(CDD_MAJOR, CDD_MINOR);

	// 静态申请设备号
	ret = register_chrdev_region(dev, CDD_COUNT, "cdd_demo");
	if(ret<0){
		printk("register_chrdev_region failed!\n");
		return ret;
	}

	printk("register_chrdev_region success!\n");

	return 0;
}


void cdd_exit(void) //卸载函数
{
	unregister_chrdev_region(dev, CDD_COUNT); //注销设备号
}

//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);

MODULE_LICENSE("GPL");

动态申请

就是向系统申请一个设备号,系统自动分配一个没有使用过的设备号。
使用函数alloc_chrdev_region向系统申请。

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


#define CDD_MINOR 0  //起始次设备号
#define CDD_COUNT 1  //设备号个数

dev_t dev;  //设备号

int cdd_init(void)  //加载函数
{
	int ret;

	// 动态态申请设备号
	ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd_demo");
	if(ret<0){
		printk("register_chrdev_region failed!\n");
		return ret;
	}

	printk("register_chrdev_region success!\n");
	printk("major number:%d\n",MAJOR(dev));
	
	return 0;
}

void cdd_exit(void)  //卸载函数
{
	unregister_chrdev_region(dev, CDD_COUNT);  //注销设备号
}

//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);

MODULE_LICENSE("GPL");

设备节点

https://blog.csdn.net/qq_41483419/article/details/129250200

手动创建

mknod /dev/test c 242 0

自动创建

创建类,在类下创建设备。

驱动点灯

https://whycan.com/p_72912.html

寄存器分析:使用Allwinner_F1C600_User_Manual_V1.0.pdf

image-20240315144550915

image-20240315143858176

image-20240315144850688

这里使用E2引脚,则n=4,板子使用LicheePi Nano

image-20240412134357446

image-20240412134428228

需要配置的寄存器

image-20240412134803411

image-20240412134826501

板卡没有LED灯,这里调试飞线连接1个发光二极管和电阻。

实现一、register_chrdev 函数注册字符设备

lec.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

#define DEV_MAJOR	200		/* 主设备号 */
#define DEV_NAME    "led"    /* 设备名 */
#define PE_CFG0_REG 0x01C20800 + 4 * 0x24 //PE配置寄存器 A:0 B:1 C:2 ....
#define PE_DATA_REG 0x01C20800 + 4 * 0x24 + 0x10 //PE数据寄存器 A:0 B:1 C:2 ....
#define PIN_N 2 // 第2个引脚
#define N (PIN_N % 8 * 4)   //引脚x : x % 8 * 4

//volatile unsigned int *gpio_con = NULL;
//volatile unsigned int *gpio_dat = NULL;
static void __iomem *gpio_con = NULL;
static void __iomem *gpio_dat = NULL;

/*打开设备*/
static int led_open (struct inode *node, struct file *filp)
{
    if (gpio_con) {     /*打开成功*/
    	return 0;
    } else {
   		return -1;
    }
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{ 
    return 0; 
} 

/*写设备*/
static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
	int ret;
    unsigned char val;     
    unsigned int temp_val;
    
    ret = copy_from_user(&val, buf, 1);
    if(ret < 0)  /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
	{
		printk(KERN_ERR "copy_from_user fail \n"); 
		return -EFAULT;
	}
	
	temp_val = readl(gpio_dat);
    if (val)
    {
   		//*gpio_dat |= (1 << PIN_N);//引脚0设置1
   		temp_val |= (1 << PIN_N);//引脚0设置1
    }
    else
    {
    	//*gpio_dat &= ~(1 << PIN_N);//引脚0设置0
    	temp_val &= ~(1 << PIN_N);//引脚0设置0
    }
    writel(temp_val, gpio_dat);
 
    return 0; 
}

/*关闭设备*/
static int led_release (struct inode *node, struct file *filp)
{
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations myled_oprs = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/* 设备初始化 */ 
static int myled_init(void)
{
    int ret;
    unsigned int val;
    
    ret = register_chrdev(DEV_MAJOR, DEV_NAME, &myled_oprs);
	if (ret < 0) {
		printk(KERN_ERR "register chrdev failed\n");
		return -EIO;
	}

    //gpio_con = (volatile unsigned int *)ioremap(PE_CFG0_REG, 1);
    //gpio_dat = (volatile unsigned int *)ioremap(PE_DATA_REG, 1);
    //gpio_dat = gpio_con + 4;  //数据寄存器 指针+4是移动了4*4=16个字节 原来是0x24 现在是0x34

    //*gpio_con &= ~(7 << N);  //7=111 取反000 0:2设置000 默认是0x7=111 失能
    //*gpio_con |=  (1 << N);  //设置输出 0:2设置001

    //*gpio_dat &= ~(1 << PIN_N);  //第0个引脚初始化设置0

    gpio_con = ioremap(PE_CFG0_REG, 4);
    gpio_dat = ioremap(PE_DATA_REG, 4);
    		
    val = readl(gpio_con);
    val &= ~(7 << N);
    val |=  (1 << N);
    writel(val, gpio_con);
    	
    val = readl(gpio_dat);
    val &= ~(1 << PIN_N);
    writel(val, gpio_dat);
    printk("init!\r\n");
    
    return 0;
}


static void __exit myled_exit(void)
{
	/* 注销字符设备驱动 */ 
	iounmap(gpio_con);
	iounmap(gpio_dat);
	unregister_chrdev(DEV_MAJOR, DEV_NAME);
	printk("exit!\r\n");
}
//注册模块加载函数
module_init(myled_init);
//卸载模块加载函数
module_exit(myled_exit);
//开源信息
MODULE_LICENSE("GPL");
//作者
MODULE_AUTHOR("wangxinchen");

Makefile

ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := led.o
else
PWD:= $(shell pwd)

KDIR := /home/wang/workplace/code/linux-5.2
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
	rm -rf .*.cmd *.o *.mod.c  .tmp_versions *.mod *.symvers *.order
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.mod *.symvers *.order
endif

led_test.c

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

/* ledtest /dev/led_1 on
 * ledtest /dev/led_1 off
 * 
 */
int main(int argc, char **argv)
{
	int fd;
	int a;
	unsigned char val = 1;
	if (argc != 3)
	{
		printf("Usage :\n");
		printf(&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值