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