从单片机开发到linux内核驱动

举报
似曾相识 发表于 2022/01/14 23:22:50 2022/01/14
【摘要】 写内核驱动其实和开发单片机没什么两样。

写内核驱动其实和开发单片机没什么两样。这里用高通的一款路由器芯片QCA4531与常见的单片机STM32做对比。前者通常跑嵌入式linux系统,后者通常跑裸机或者简单的实时操作系统。
那么用这两款芯片分别实现控制一个GPIO口,难度差距有多大呢?我感觉差不多。
下面就边实现边分析
首先,拿到任何一款产品,要想很好的使用它,只有一个办法,那就是看产品的说明书。因为产品的说明书是产品的开发者写的,没有人比产品的开发者更了解产品了。对于芯片而言,其说明书就是芯片手册。所以,看懂芯片手册就能用好产品。
比如,我们想去控制这两款芯片的GPIO,那我们就是翻它们的芯片手册。
1、STM32中想要控制一个GPIO,从芯片手册中我们了解到,只需要配置相应的寄存器就可以了,它们包括:端口配置寄存器(用来配置端口的输入输出模式)、数据输出寄存器,当然还有配置相应的时钟线(STM32和51不同,为了降低功耗,可以关闭部分设备的时钟)。
2、QCA4531中想要控制一个GPIO,又需要做什么呢,其实和其它任何芯片一样,也是配置相应的寄存器,包括:输出使能寄存器、引脚输出值寄存器。

STM32代码

#define PERIPH_BASE      		((unsigned int)0x40000000)
#define APB2PERIPH_BASE  		(PERIPH_BASE + 0x00010000)
#define GPIOC_BASE       		(APB2PERIPH_BASE + 0x1000)
#define GPIOC_CRL 	 	 		*(unsigned int*)(GPIOC_BASE+0x00)
#define GPIOC_ODR 	 	 		*(unsigned int*)(GPIOC_BASE+0x0C)
#define AHBPERIPH_BASE   		(PERIPH_BASE + 0x20000)
#define RCC_BASE 		 		(AHBPERIPH_BASE + 0x1000)
#define RCC_APB2ENR 	 		*(unsigned int*)(RCC_BASE+0x18)

void delay(u32 i)
{
	while(i--);
}

int main()
{
	RCC_APB2ENR |= 1<<4;				//时钟使能
	GPIOC_CRL &= ~( 0x0F<< (4*0));		//配置引脚模式
	GPIOC_CRL |= (3<<4*0);
	
	while(1)
	{
		GPIOC_ODR  |= (1 << 0);			//GPIOC0 置高
		delay(0xFFFFF);
		GPIOC_ODR  &= ~(1 << 0);		//GPIOC0 置低
		delay(0xFFFFF);
	}
}

嵌入式linux代码

#include <sys/types.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <memory.h>

#define GPIO_BASE 0x18040000

void * map_base;
FILE *f;
int n, fd;
volatile unsigned int * GPIO_OUT;

int main(int argc,char *argv[])
{
    if((fd=open("/dev/mem",O_RDWR|O_SYNC))==-1){
    	return(-1);
    }

    map_base=mmap(0,0xff,PROT_READ|PROT_WRITE,MAP_SHARED,fd,GPIO_BASE);		//将硬件寄存器地址映射到内存
    GPIO_OUT = (volatile unsigned int *) (map_base + 8);

    while(1){
        *GPIO_OUT |= (1 << 0);			//GPIO0 置高
        sleep(1);
        *GPIO_OUT &= ~(1 << 0);			//GPIO0 置低
        sleep(1);
    }
    close(fd);
    munmap(map_base,0xff);				//解除映射关系
    return 0;
}

发现没,在单片机和嵌入式linux中控制GPIO是一样简单的,就是配置一下相应的寄存器。

进化。。。


1、在STM中,有大量的寄存器(STM32寄存器的规模可不是传统8位单片机能够比拟的),程序员很难记得住每一个寄存器的名称、地址和作用,那么ST公司就想出了一个办法——库函数。即,使用函数调用的方式去代替直接操作硬件寄存器。好处:程序员只需要调用这些函数就行了,而不需要再去记那些枯燥的寄存器地址了。
实例代码

int main()
{
	LED_Init();
	while(1)
	{
		GPIO_SetBits(LED_PORT,GPIO_Pin_0);		//打开 LED
		delay(0xFFFFF);
		GPIO_ResetBits(LED_PORT,GPIO_Pin_0);	//关闭 LED
		delay(0xFFFFF);
	}
}

2、 那么在嵌入式Linux中,为什么要将操作硬件的函数以内核驱动的形式嵌入到内核中呢,直接操作不也可以吗?这就牵扯到linux中的用户态与内核态的作用,linux中为什么要有用户态与内核态呢?直接都是用户态不行吗?切来切去的还麻烦。当然不行,因为上升到复杂的操作系统,系统中有大量的用户程序,如果每一个用户程序都能很轻松的直接操作硬件资源(包括I/O读写、内存读写等),那么一个操作系统一天不知道要崩溃多少回(如果程序员一不小心将不适当的内容写到了不该写的地方,就很容易导致系统崩溃)。所以操作系统就设计了内核态与用户态,凡是涉及到IO读写、内存分配等硬件资源的操作时,往往不允许直接操作,而是通过一种叫系统调用的过程,让程序陷入到内核态运行,以保证系统的安全可靠。所以,顺理成章,操作系统要分内核态与用户态,操作硬件资源需要放在内核态。所以我们要把操作硬件的驱动放到内核——内核驱动。那么,怎么做呢,其实很简单,编写内核驱动有着相同的套路,无非就是module_init()、module_exit()等一系列函数的调用,文件操作结构体file_operations中open、write、read、ioctl、close等一系列函数的具体实现。怎么实现呢,其实思路清晰很简单,open函数就是对设备的初始化,本例中将GPIO的模式配置代码放入open函数中就OK了,write函数中放入操作GPIO输出寄存器的代码就OK了,read中放入读取GPIO寄存器值的代码,ioctl中也放入操作GPIO输出寄存器的代码。很轻松就实现了内核驱动的编写,不是吗?
实例代码

#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>

//define registers
volatile unsigned long *GPIO_OUT;
volatile unsigned long *GPIO_OE;

/**********************  基本定义 *******************************/
//内核空间缓冲区定义
#if 0
#define KB_MAX_SIZE 20
#define kbuf [KB_MAX_SIZE]
#endif

//初始化函数必要资源定义
//用于初始化函数当中
//device number;
dev_t dev_num;
//struct dev
struct cdev leddrv_cdev;

//auto "mknode /dev/leddrv c dev_num minor_num"
struct class *leddrv_class = NULL;
struct device *leddrv_device = NULL;

/**************** 结构体 file_operations 成员函数 *****************/
//open
static int leddrv_open(struct inode *inode, struct file *file)
{
    printk("leddrv drive open.\n");
    //配置模式省略
    return 0;
}

//close
static int leddrv_close(struct inode *inode, struct file *file)
{
    printk("leddrv drive close...\n");
    return 0;
}

//read
static ssize_t leddrv_read(struct file *file, char __user *buffer, size_t len, loff_t *pos)
{
    int ret_v = 0;
    printk("leddrv drive read...\n");
    return ret_v;
}

//write
static ssize_t leddrv_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset)
{
    int ret_v = 0;
    printk("leddrv drive write...\n");
    return ret_v;
}

//unlocked_ioctl
static int leddrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret_v = 0;
    printk("leddrv drive ioctl cmd = %d, arg = %d\n", cmd, arg);
    if (arg == 0)
        *GPIO_OUT &= ~(1 << cmd);
    else if (arg == 1)
        *GPIO_OUT |= (1 << cmd);

    return ret_v;
}

/***************** 结构体: file_operations ************************/
//struct
static const struct file_operations leddrv_fops = {
    .owner = THIS_MODULE,
    .open = leddrv_open,
    .release = leddrv_close,
    .read = leddrv_read,
    .write = leddrv_write,
    .unlocked_ioctl = leddrv_ioctl,
};

/*******************  functions: init , exit**********************/
//条件值变量,用于指示资源是否正常使用
unsigned char init_flag = 0;
unsigned char add_code_flag = 0;

//init
static __init int leddrv_init(void)
{
    int ret_v = 0;
    printk("leddrv drive init...\n");

    //函数alloc_chrdev_region主要参数说明:
    //参数2: 次设备号
    //参数3: 创建多少个设备
    if ((ret_v = alloc_chrdev_region(&dev_num, 0, 1, "leddrv")) < 0)
    {
        goto dev_reg_error;
    }
    init_flag = 1; //标示设备创建成功

    printk("The drive info of leddrv:\nmajor: %d\nminor: %d\n",
           MAJOR(dev_num), MINOR(dev_num));

    cdev_init(&leddrv_cdev, &leddrv_fops);
    if ((ret_v = cdev_add(&leddrv_cdev, dev_num, 1)) != 0)
    {
        goto cdev_add_error;
    }

    leddrv_class = class_create(THIS_MODULE, "leddrv");
    if (IS_ERR(leddrv_class))
    {
        goto class_c_error;
    }

    leddrv_device = device_create(leddrv_class, NULL, dev_num, NULL, "leddrv");
    if (IS_ERR(leddrv_device))
    {
        goto device_c_error;
    }
    printk("auto mknod success!\n");

    GPIO_OUT = (volatile unsigned long *)ioremap(0x18040008, 4);

    //如果需要做错误处理,请:goto leddrv_error
    add_code_flag = 1;
    //----------------------  END  ---------------------------//

    goto init_success;

dev_reg_error:
    printk("alloc_chrdev_region failed\n");
    return ret_v;

cdev_add_error:
    printk("cdev_add failed\n");
    unregister_chrdev_region(dev_num, 1);
    init_flag = 0;
    return ret_v;

class_c_error:
    printk("class_create failed\n");
    cdev_del(&leddrv_cdev);
    unregister_chrdev_region(dev_num, 1);
    init_flag = 0;
    return PTR_ERR(leddrv_class);

device_c_error:
    printk("device_create failed\n");
    cdev_del(&leddrv_cdev);
    unregister_chrdev_region(dev_num, 1);
    class_destroy(leddrv_class);
    init_flag = 0;
    return PTR_ERR(leddrv_device);

//-------------------- 请在此添加您的错误处理内容 ------------------//
leddrv_error:
    add_code_flag = 0;
    return -1;
    //---------------------          END         --------------------//

init_success:
    printk("leddrv init success!\n");
    return 0;
}

//exit
static __exit void leddrv_exit(void)
{
    printk("leddrv drive exit...\n");

    if (add_code_flag == 1)
    {
        //--------------   请在这里释放您的程序占有的资源   -----------//
        printk("free your resources...\n");

        iounmap(GPIO_OUT);

        printk("free finish\n");
        //-----------------------     END      --------------------//
    }

    if (init_flag == 1)
    {
        //释放初始化使用到的资源
        cdev_del(&leddrv_cdev);
        unregister_chrdev_region(dev_num, 1);
        device_unregister(leddrv_device);
        class_destroy(leddrv_class);
    }
}

/**************** module operations************************/
//module loading
module_init(leddrv_init);
module_exit(leddrv_exit);

//some infomation
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("from Jafy");
MODULE_DESCRIPTION("leddrv drive");
/*********************  The End ***************************/

编写应用程序测试

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

#define LED_ON 0
#define LED_OFF 1

char str_dev[] = "/dev/leddrv";

int main(int argc, char *argv[])
{
    int fd;
    if (argc != 2)
    {
        printf(" %s <on|off> to turn on or off LED.\n", argv[0]);
        return -1;
    }

    fd = open(str_dev, O_RDWR | O_NONBLOCK); 
    if (fd < 0)
    {
        printf("can't open %s \n", str_dev);
        return -1;
    }

    while (1)
    {
        ioctl(fd, atoi(argv[1]), 0);
        printf("led_%d is off\n", atoi(argv[1]));
        usleep(50000);
        ioctl(fd, atoi(argv[1]), 1);
        printf("led_%d is on\n", atoi(argv[1]));
        usleep(50000);
    }

    return 0;
}

运行结果
1、加载内核驱动模块

root@Shuncom:~# insmod /tmp/leddrv.ko

内核日志

[75774.330000] leddrv drive init...
[75774.330000] The drive info of leddrv:
[75774.330000] major: 251
[75774.330000] minor: 0
[75774.340000] auto mknod success!
[75774.340000] leddrv init success!

2、执行应用程序

root@Shuncom:~# led_app 0
led_0 is off
led_0 is on
led_0 is off
led_0 is on
led_0 is off
led_0 is on
...

内核日志

root@Shuncom:~# led_app 0
led_0 is off
led_0 is on
led_0 is off
led_0 is on
led_0 is off
led_0 is on
...

同时看到LED在闪烁

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。