1、linu终端输入下面命令安装
git clone https://e.coding.net/weidongshan/linux_course/linux_basic_develop.git
2、
进入到GIT仓库目录
cd /D/abc/doc_and_source_for_mcu_mpu
在doc_and_source_for_mcu_mpu目录下,执行以下命令获得资料的最新版本。
git pull origin
1、hello驱动程序步骤
file_operations
结构体(字符设备驱动的核心)#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include static int major;static int hello_open (struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return size;
} static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return size;
}static int hello_release (struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 1. create file_operations */
static const struct file_operations hello_drv = {.owner = THIS_MODULE,.read = hello_read,.write = hello_write,.open = hello_open,.release = hello_release,
};/* 2. register_chrdev *//* 3. entry function */
static int hello_init(void)
{major = register_chrdev(0, "100ask_hello", &hello_drv);return 0;
}/* 4. exit function */
static void hello_exit(void)
{unregister_chrdev(major, "100ask_hello");
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
用户与内核传数据和创建设备文件驱动程序
#include "asm/cacheflush.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include #include static struct class *hello_class; //定义一个类型,用于创建设备
static int major; //主设备号
static unsigned char hello_buf[100]; //内核数据缓存static int hello_open (struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{unsigned long len = size > 100 ? 100 : size;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);copy_to_user(buf, hello_buf, len); //将内核数据发送给用户return len;
}static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{unsigned long len = size > 100 ? 100 : size;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);copy_from_user(hello_buf, buf, len);return len;
}static int hello_release(struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 1. create file_operations */
static const struct file_operations hello_drv = {.owner = THIS_MODULE,.read = hello_read,.write = hello_write,.open = hello_open,.release = hello_release,
};/* 2. register_chrdev *//* 3. entry function */
static int hello_init(void)
{ //申请设备号,只能进行注测主设备号0-255个设备,次设备号全部被占用major = register_chrdev(0, "100ask_hello", &hello_drv); //0代表自动寻找设备号,hello_class = class_create(THIS_MODULE, "hello_class"); //创建一个类型if (IS_ERR(hello_class)) {printk("failed to allocate class\n");return PTR_ERR(hello_class);}//创建一个名字为hello的设备device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}/* 4. exit function */
static void hello_exit(void)
{device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "100ask_hello");
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
应用
#include
#include
#include
#include
#include
#include /* 写: ./hello_test /dev/xxx 100ask* 读: ./hello_test /dev/xxx*/
int main(int argc, char **argv)
{int fd;int len;char buf[100];if (argc < 2){printf("Usage: \n");printf("%s [string]\n", argv[0]);return -1;}// openfd = open(argv[1], O_RDWR);if (fd < 0){printf("can not open file %s\n", argv[1]);return -1;}if (argc == 3){// writelen = write(fd, argv[2], strlen(argv[2])+1);printf("write ret = %d\n", len);}else{// readlen = read(fd, buf, 100);buf[99] = '\0';printf("read str : %s\n", buf);}// closeclose(fd);return 0;
}
1、驱动程序:提供能力,不提供策略
非阻塞(查询)
阻塞(休眠-唤醒)
poll(定个闹钟)
异步通知
妈妈怎么知道卧室里小孩醒了?
时不时进房间看一下: 查询方式
进去房间陪小孩一起睡觉,小孩醒了会吵醒她: 休眠-唤醒
不累,但是妈妈干不了活了
妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟: poll 方式
妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈: 异步通知
2、中断的引入
驱动程序
#include "asm-generic/errno-base.h"
#include "asm/cacheflush.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include static struct class *hello_class;
static struct cdev hello_cdev;
static dev_t dev;static unsigned char hello_buf[100];static int hello_open (struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{unsigned long len = size > 100 ? 100 : size;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);copy_to_user(buf, hello_buf, len);return len;
}static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{unsigned long len = size > 100 ? 100 : size;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);copy_from_user(hello_buf, buf, len);return len;
}static int hello_release (struct inode *node, struct file *filp)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 1. create file_operations */
static const struct file_operations hello_drv = {.owner = THIS_MODULE,.read = hello_read,.write = hello_write,.open = hello_open,.release = hello_release,
};/* 2. register_chrdev *//* 3. entry function */
static int hello_init(void)
{int ret;// register_chrdev//参数一:分配设备号成功后用来存放分配到的设备号,分配结束后要把主设备号提取出来(major = MAJOR(devno);)因为主设备号会变所以要将变得重新赋值//参数二:起始的次设备号,一般为0//参数三:count:申请的设备数量,从起始设备号累加。如果在创建一个设备,主设备号跟前一个一样,次设备号为1的设备,依然可以访问,就是同一个设备。//参数四:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号ret = alloc_chrdev_region(&dev, 0, 2, "hello"); //自动注册设备if (ret < 0) {printk(KERN_ERR "alloc_chrdev_region() failed for hello\n");return -EINVAL;}cdev_init(&hello_cdev, &hello_drv); //将hello_drv与hello_cdev链接起来 ret = cdev_add(&hello_cdev, dev, 2);//将设备号添加到设备结构体if (ret){printk(KERN_ERR "cdev_add() failed for hello\n");return -EINVAL;}hello_class = class_create(THIS_MODULE, "hello_class"); if (IS_ERR(hello_class)) {printk("failed to allocate class\n");return PTR_ERR(hello_class);}//创建一名字为hello的设备 device_create(hello_class, NULL, dev, NULL, "hello"); /* /dev/hello */return 0;
}/* 4. exit function */
static void hello_exit(void)
{device_destroy(hello_class, dev);class_destroy(hello_class);//unregister_chrdev(major, "100ask_hello");cdev_del(&hello_cdev);unregister_chrdev_region(dev, 2);
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
app
#include
#include
#include
#include
#include
#include /* 写: ./hello_test /dev/xxx 100ask* 读: ./hello_test /dev/xxx*/
int main(int argc, char **argv)
{int fd;int len;char buf[100];if (argc < 2){printf("Usage: \n");printf("%s [string]\n", argv[0]);return -1;}// openfd = open(argv[1], O_RDWR);if (fd < 0){printf("can not open file %s\n", argv[1]);return -1;}if (argc == 3){// writelen = write(fd, argv[2], strlen(argv[2])+1);printf("write ret = %d\n", len);}else{// readlen = read(fd, buf, 100);buf[99] = '\0';printf("read str : %s\n", buf);}// closeclose(fd);return 0;
}
1、GPIO子系统
在开发板上执行如下命令查看已经在使用的GPIO状态:
# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-15, parent: platform/soc:pin-controller@50002000, GPIOA:gpio-10 ( |heartbeat ) out logpio-14 ( |shutdown ) out higpiochip1: GPIOs 16-31, parent: platform/soc:pin-controller@50002000, GPIOB:gpio-26 ( |reset ) out hi ACTIVE LOWgpiochip2: GPIOs 32-47, parent: platform/soc:pin-controller@50002000, GPIOC:gpiochip3: GPIOs 48-63, parent: platform/soc:pin-controller@50002000, GPIOD:
怎么确定GPIO引脚的编号?方法如下:
① 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:(这个后面的数是起始地址,不跟上面那个查到的组数一样)
② 然后进入某个gpiochipXXX目录,查看文件label的内容,就可以知道起始号码XXX对于哪组GPIO
那么GPIO4_14的号码是96+14=110,可以如下操作读取按键值:
[root@100ask:~]# echo 110 > /sys/class/gpio/export // gpio_request
[root@100ask:~]# echo in > /sys/class/gpio/gpio110/direction // gpio_direction_input
[root@100ask:~]# cat /sys/class/gpio/gpio110/value // gpio_get_value
[root@100ask:~]# echo 110 > /sys/class/gpio/unexport // gpio_free
GPIO子系统函数有新、老两套:
descriptor-based | legacy |
---|---|
获得GPIO | |
gpiod_get | gpio_request |
gpiod_get_index | |
gpiod_get_array | gpio_request_array |
devm_gpiod_get | |
devm_gpiod_get_index | |
devm_gpiod_get_array | |
设置方向 | |
gpiod_direction_input | gpio_direction_input |
gpiod_direction_output | gpio_direction_output |
读值、写值 | |
gpiod_get_value | gpio_get_value |
gpiod_set_value | gpio_set_value |
释放GPIO | |
gpio_free | gpio_free |
gpiod_put | gpio_free_array |
gpiod_put_array | |
devm_gpiod_put | |
devm_gpiod_put_array |
2、中断函数
2.1 使用中断的流程
在驱动程序里使用中断的流程如下:
确定中断号
注册中断处理函数,函数原型如下:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
在中断处理函数里
2.2 函数细节
request_irq函数的第1个参数是中断号,可以根据GPIO函数获得中断号:
int gpio_to_irq(unsigned int gpio);
int gpiod_to_irq(const struct gpio_desc *desc);
request_irq函数的第2个参数是函数指针:
enum irqreturn {IRQ_NONE = (0 << 0),IRQ_HANDLED = (1 << 0),IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev);
request_irq函数的第3个参数有如下取值:
(上升沿触发、下降沿触发…)
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008#define IRQF_SHARED 0x00000080
request_irq函数的第4个参数是中断的名字,可以在执行cat /proc/interrupts
的结果里查看。
request_irq函数的第5个参数是给中断处理函数使用的。
2.3 代码
driver
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include struct gpio_desc{ //定义一个中断结构体int gpio;int irq;char *name;int key;struct timer_list key_timer;
} ;static struct gpio_desc gpios[2] = { //中断结构体赋初值{131, 0, "gpio_100ask_1", 1,},{132, 0, "gpio_100ask_2", 2,},
};/* 主设备号 */
static int major = 0;
static struct class *gpio_class;/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;struct fasync_struct *button_fasync;#define NEXT_POS(x) ((x+1) % BUF_LEN)static int is_key_buf_empty(void)
{return (r == w);
}static int is_key_buf_full(void)
{return (r == NEXT_POS(w));
}static void put_key(int key)
{if (!is_key_buf_full()){g_keys[w] = key;w = NEXT_POS(w);}
}static int get_key(void)
{int key = 0;if (!is_key_buf_empty()){key = g_keys[r];r = NEXT_POS(r);}return key;
}static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{/* data ==> gpio */// struct gpio_desc *gpio_desc = from_timer(gpio_desc, t, key_timer);struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio); //读取引脚 0或1//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);key = (gpio_desc->key) | (val<<8); //key值用来放在环形数组中,用key第八位看按键是否按下put_key(key);//放入环形数组wake_up_interruptible(&gpio_wait);kill_fasync(&button_fasync, SIGIO, POLL_IN);
}/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);int err;int key;if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait,!is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4);return 4;
}static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{unsigned char ker_buf[2];int err;if (size != 2)return -EINVAL;err = copy_from_user(ker_buf, buf, size);if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))return -EINVAL;gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);return 2;
}static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);poll_wait(fp, &gpio_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}static int gpio_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}/* 定义自己的file_operations结构体 */
static struct file_operations gpio_key_drv = {.owner = THIS_MODULE,.read = gpio_drv_read,.write = gpio_drv_write,.poll = gpio_drv_poll,.fasync = gpio_drv_fasync,
};static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//jiffies是全局变量 mod_timer(&gpio_desc->key_timer, jiffies + HZ/5); //消抖,每次抖动时长小于(HZ/5)*Tms,都会推迟进入中断服务程序return IRQ_HANDLED;
}/* 在入口函数 */
static int __init gpio_drv_init(void)
{int err;int i;int count = sizeof(gpios)/sizeof(gpios[0]);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);for (i = 0; i < count; i++){ gpios[i].irq = gpio_to_irq(gpios[i].gpio); //调用函数获得中断号//定时器初始化函数 参数一:定时器时间 参数二:中断服务程序 参数三:传入中断服务程序的参数setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);//timer_setup(&gpios[i].key_timer, key_timer_expire, 0);gpios[i].key_timer.expires = ~0;add_timer(&gpios[i].key_timer);err = request_irq(gpios[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]); //注册中断}/* 注册file_operations */major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv); /* /dev/gpio_desc */gpio_class = class_create(THIS_MODULE, "100ask_gpio_key_class");if (IS_ERR(gpio_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "100ask_gpio_key");return PTR_ERR(gpio_class);}device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio"); /* /dev/100ask_gpio */return err;
}/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit gpio_drv_exit(void)
{int i;int count = sizeof(gpios)/sizeof(gpios[0]);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(gpio_class, MKDEV(major, 0));class_destroy(gpio_class);unregister_chrdev(major, "100ask_gpio_key");for (i = 0; i < count; i++){free_irq(gpios[i].irq, &gpios[i]);del_timer(&gpios[i].key_timer);}
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(gpio_drv_init);
module_exit(gpio_drv_exit);MODULE_LICENSE("GPL");
app
#include
#include
#include
#include
#include
#include
#include
#include static int fd;/** ./button_test /dev/100ask_button0**/
int main(int argc, char **argv)
{int val;struct pollfd fds[1];int timeout_ms = 5000;int ret;int flags;int i;/* 1. 判断参数 */if (argc != 2) {printf("Usage: %s \n", argv[0]);return -1;}/* 2. 打开文件 */fd = open(argv[1], O_RDWR | O_NONBLOCK);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}for (i = 0; i < 10; i++) {if (read(fd, &val, 4) == 4)printf("get button: 0x%x\n", val);elseprintf("get button: -1\n");}flags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);while(1){if (read(fd, &val, 4) == 4)printf("get button: 0x%x\n", val);elseprintf("while get button: -1\n");}close(fd);return 0;
}