写内核驱动其实和开发单片机没什么两样。这里用高通的一款路由器芯片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在闪烁