实战!手把手教你如何编写一个Linux驱动并写一个支持物联网的LED演示demo

Linux_嵌入式 专栏收录该内容
7 篇文章 1 订阅

目录

一.开发环境

二. 准备工作:

1. 创建一个项目工程目录

2. 创建输出与目标目录

3.头文件目录

4. 建立源代码src目录

5. 使用git管理你的项目

三.编写LED驱动

三.一 准备工作

三.二 init实现

三.四 exit实现

三.五 make实现

三. 六 open实现

三.七 write实现

三.八 read实现

三.九 ioctl实现

四、编写测试APP

五. 物联网

六.在make添加支持

七. 最终演示


一.开发环境

开发板:

核心板:TQ210CoreB

底板:TQ210 V4

CPU:s5pv210

内核:Linux_kernel_3.0.8

交叉编译系统环境:

操作系统:ubnutu16.04

编译器:arm-embedsky-linux-gnueabi工具链 4.4.6

所需知识点:

若你是刚入门的学生不太看得懂原理图与芯片手册,请先看下这篇文章中针对电路原理与芯片文档这块的教程:详细介绍如何读懂STM32开发板电路原理图以及芯片文档和开发手册,并编写一个测试程序:点亮一个LED灯

本文还需要对Linux设备管理器有一定的了解,否则你开发时只知道调这些函数,但不知道内核态发生了什么,所以建议大家学一下相关知识点:Linux嵌入式开发_主设备号与次设备号详解Linux驱动开发_设备文件系统详解

本文使用GIT来管理项目,GIT方面的教程:关于Git这一篇就够了

内核开发基本知识:Linux内核开发_内核模块

针对位的高级应用:c语言位操作的高级应用

HTTP方面:

Http响应码含义

HTTP协议下GET与POST的区别

HTTP请求报头中各个字段的含义

开源字符处理类库:CharString类 拆分自自己研发的web服务器中的类库

这些知识点我都在别的文章中有详细的解释,若对单片机/嵌入式体系基础较差的同学可以看一看。

本篇教程较为类GNU/LINUX风格,从目录体系到项目管理,都会以类GNU/LINUX风格编写

即便是一个小demo我们也要用git来管理,这是为了加深大家对git的了解与认识,也为将来的工作做准备。

请大家先看完上面的知识在继续学习本篇知识。

二. 准备工作:

工欲善其事必先利其器,当我们开发一个项目工程时,需要构建好项目体系,这样便于我们后面的开发,也让我们的项目变得可维护性更高一点

1. 创建一个项目工程目录

mkdir moudul && cd moudul

2. 创建输出与目标目录

arch、output

这两个目录将用于目标文件的输出以及中间文件的输出目录。

在arch目录下在新建一个arm的目录,因为我们的板子是arm架构,所以新建一个目录用来存放arm的目标文件,这样的体系是源于Linux内核目录体系

mkdir arch && mkdir arch/arm
mkdir output

3.头文件目录

在建立一个头文件目录,同时在include目录下创建一个目录TQ210_LED,代表工程类型,然后在这个目录下创建两个子目录:device、app,用来存放驱动/app的头文件

mkdir include
mkdir include/TQ210_LED
mkdir include/TQ210_LED/device
mkdir include/TQ210_LED/app

4. 建立源代码src目录

目录结构与头文件目录一致

mkdir src
mkdir src/TQ210_LED
mkdir src/TQ210_LED/device
mkdir src/TQ210_LED/app

好了到此我们的工程体系已经建立完成

beis@ubuntu:~/moudul$ tree
.
├── arch
│   └── arm
├── include
│   └── TQ210_LED
│       ├── app
│       └── device
├── output
└── src
    └── TQ210_LED
        ├── app
        └── device

目录结构分配的非常清晰合理,当我们新增别的模块时,只需要按照这个规范在src与include下建立不同的类型目录就可以了

若别的架构只需要在arch下建立对应的架构目录即可。

目录体系完成之后我们在使用GIT来管理我们的项目

5. 使用git管理你的项目

git init

三.编写LED驱动

在src/TQ210_LED/device目录下新建一个.c文件和在include/TQ210_LED/device目录下新建一个.h文件

touch src/TQ210_LED/device/TQ210_LED_device.c
touch include/TQ210_LED/device/TQ210_LED_device.h

然后使用vim打开.c文件我们就可以开始编写驱动模块啦

vim src/TQ210_LED/device/TQ210_LED_device.c

现在我们打开电路原理图,这是开发步骤的必须的第一步,因为只有看原理图才知道电路的结构。

在原理图中找到LED这一块并放大

从原理图中可以得知LED1、LED2都接在GPC端口上。

 vdd表示器件内部工作电压的符号,vdd5v的意思就是至少需要5伏电压才能让此器件工作,不过这个我们一般不用关系,这个一般由PCB板设计者们已经完成了,输入源端已经设定好电阻之类的器件器件来控制电流通过,包括板子使用的电流模式。

 R开头的这样的电路符号一般都是电阻名字,1K=1000欧姆,也就是说当电压流过时它会吸收掉1000欧姆的电压。

  这是一个放大三极管,用于放大电流的,型号是S8050,符号是Q,Q1代表一号放大三极管。

从这里可以看到LED1和LED2分别接在GPC0号端口上,位为3与4

这里我教大家如何通过GPC0_4这个标识来找到芯片手册里的对应标志位

我们打开芯片手册,先看下GPIO的框架图

它由两部分组成 OFF PART(断电部分)和ALILVE PART(带电部分)

两者分别为,睡眠模式与非睡眠模式,也就是说这个GPIO框架支持睡眠模式与非睡眠模式,若进入睡眠模式则整个框架内部时钟将停止工作,等待其它中断将其唤醒,同时在睡眠模式下是能够保证其GPIO内部寄存器的值的。

它挂载在APB总线上,并且APB没有挂接到RCC这样的时钟总线上,不像STM32是挂接在这个总线上,若不先开启它则APB总线不会工作,这是STM32出于低功耗的设计。

接着我们找到GPC端口的描述                                        

 

这里建议大家在打开PDF时使用CTRL+F去搜索GPC0能快速找到对应的说明页

按照单片机的开发的经验来说,我们可以知道若想让一个GPC口工作,必须使其设置为OUTPUT模式

同时可以看到DAT寄存器的介绍

DAT说明:

当端口配置为输入端口时,对应位为引脚状态当端口配置为输出端口时,引脚状态与对应位相同当端口配置为功能引脚时,将读取未定义的值。

也就是说CON对应端口是输出时,DAT对应的就是状态值,给1即高电平,给0即低电平。

在三星s5pv210中每个GPIO口由CON和DAT组成,CON是控制状态,DAT是输入输出状态。

并且我们可以看到GPC0CON的端口地址为:0xE0200060

GPC0DAT的端口地址为:0XE0200064 相差四字节。

一个32位机器上的int的大小。

基本上硬件以及地址信息我们都知道了,那么就是正式写代码开始进行开发了。

三.一 准备工作

在你的src/device目录下新建一个.c的文件

touch src/TQ210_LED/device/TQ210_device_led.c

同时在创建头文件:

touch include/TQ210_LED/device/TQ210_device_led.c

然后使用你喜欢的编辑工具开始写代码吧!

首先在.c文件中包含基本头文件:

#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <asm/irq.h>
#include <mach/hardware.h>
#include <plat/regs-serial.h>
#include <mach/regs-gpio.h>
#include <asm/uaccess.h>

基本信息:

//author
MODULE_AUTHOR("Stephen Zhou");
MODULE_LICENSE("GPL");

init与exit:

static int __init led_init(void){

}

static void __exit led_exit(void){

}

module_init(led_init);
module_exit(led_exit);

我们在增加一些基本信息:

这些信息用来存储针对设备处理器用的名字,这些在之前的文件系统详解中都有详细说过

先定义存放/dev下的名字,这个是给udev看的:

//led one and two name
#define LED_ONE_NAME "TQ210_LED_ONE"
#define LED_TWO_NAME "TQ210_LED_TWO"

定义内核模块表中的名字,以及sysfs的名字,这个是给内核和sysfs文件系统看的

//led name in kernel
#define LED_KERNEL_NAME         "TQ210_LED"
#define LED_SYSFS_CLASS_NAME    "TQ210_LED_CLASS"

led数量,这个是给我们程序自己看的

//led device number max
#define LED_NUMBER_MAX 2

在写几个针对位操作的函数:

//set or get gpic bit value
#define SET_GPIC(GPIC_ADDRESS,VALUE,OPE)                                *GPIC_ADDRESS OPE VALUE
#define SET_GPIC_STATE(GPIC_ADDRESS,LED1_VALUE,LED2_VALUE,OPE)          *GPIC_ADDRESS OPE (LED1_VALUE | LED2_VALUE)
#define GET_GPIC(GPIC_ADDRESS,VALUE)                                    *GPIC_ADDRESS & VALUE

//led gpic port
#define LED1_GPIC_BIT(VALUE) (VALUE << 12)
#define LED2_GPIC_BIT(VALUE) (VALUE << 16)
#define LED1_GPIC_DAT_BIT(VALUE) (VALUE << 3)
#define LED2_GPIC_DAT_BIT(VALUE) (VALUE << 4)

属性宏:

//device gpic address
#define GPIC0_CON_ADDRESS 0xE0200060
#define GPIC0_DAT_ADDRESS 0xE0200064
#define GPIC_ADDRESS_FORMAT 16

//state
#define LED_STATE_ON  "ON"
#define LED_STATE_OFF "OFF"

好了,接下来我们去实现init函数

三.二 init实现

在实现init函数之前我们在.c文件中申请几个全局变量,用来存储不同的属性:

存储led名字:

char LED_NAME[][256] = {{LED_ONE_NAME},{LED_TWO_NAME}};

存储con与dat地址:

volatile unsigned long* GPIC0_address = NULL;
volatile unsigned long* GPIC0_dat     = NULL;

存储内核fd与类(sysfs)fd:

//drive struct
static int                         led_kernel_fd                                 = 0;                           //kernel struct fd
static struct class*               led_device_file_class                         = NULL;                        //sysfs fd
static struct device*              led_device_class_son[LED_NUMBER_MAX]          = {NULL};       

除此之外还需要一个结构体:

struct file_operations

这个结构体就是用来存储文件函数指针的,write、open等函数实现

为此我们先将write、open先定义出来,什么都不做,后面我们在实现:

//open
static int led_open(struct inode* inode,struct file* file){

}

//write
static ssize_t led_write(struct file* file,const char __user* buf,size_t count,loff_t* ppos){

}

//read
static ssize_t led_read(struct file* file,char __user* buf,size_t count,loff_t* ppos){

}

//ioctl
static long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){

}

注意根据内核版本不同,在内核部分的write、read函数原型不同,可以根据自己开发板子使用的内核版本来查一下。

我们把fops结构体定义出来:

//drive struct
static struct file_operations led_drive_fops = {

                .owner          = THIS_MODULE,
                .open           = led_open,
                .write          = led_write,
                .read           = led_read,
                .unlocked_ioctl = led_ioctl,
};

里面的THIS_MODULE是一个地址,这个会在预编译期间被编译器替换为当前模块的地址,也就是说它指向当前模块。

init函数的目的:

将设备注册到内核并注册到类文件系统,将udev注册到dev dir

第一步注册到内核结构体中

注意在内核模块中我们尽量多打日志,利用printk函数,便于我们调试

因为内核态是不能用GDB这些来调试的。

led_kernel_fd = register_chrdev(0,LED_KERNEL_NAME,&led_drive_fops);
        if(led_kernel_fd < 0){
                printk("TQ210_LED[ERROR]: register_chrdev - %d\n",led_kernel_fd);
                return -1;
}

第二步 注册到sysfs/sys/class dir,申请一个父节点

led_device_file_class = class_create(THIS_MODULE,LED_SYSFS_CLASS_NAME);

第三步注册子设备到类sysfs中,作为子节点

注意编译器是c99,我们不能在代码部分声明变量,所以在开头加一个i的定义

 int i = 0;      //for c99 
 for(; i < LED_NUMBER_MAX; ++i){
                led_device_class_son[i] = device_create (led_device_file_class,NULL,MKDEV(led_kernel_fd,i),NULL,LED_NAME[i]);
                if(unlikely(IS_ERR(led_device_class_son[i]))) {
                        printk("TQ210_LED[ERROR]: register son device\n");
                        return -2;
                }
        }

最后一步将物理地址映射到内核虚拟地址:

操作系统出于alu地址随机化保护原因,是不能直接访问物理地址的,需要先转化为虚拟地址。

 GPIC0_address = (volatile unsigned long*)ioremap(GPIC0_CON_ADDRESS,GPIC_ADDRESS_FORMAT);
 GPIC0_dat     = (volatile unsigned long*)ioremap(GPIC0_DAT_ADDRESS,GPIC_ADDRESS_FORMAT);

最后打印一下表示我们成功初始化init了。

printk("TQ210_LED[SUCCESS]:init\n");
return 0;

在实现一下exit函数:

三.四 exit实现

先释放子类节点,注意这里的释放是有顺序之分的

因为我们在注册的时候是先注册到内核-sysfs-sysfs子节点这样的一个流程。

在设备文件详解里我说过它的寻找方式,当我们open一个/dev下的文件时,先调用中断的do_sys_open,然后在调用do_filp_open,来从文件系统中获取节点,VFS中,因为/dev目录下的文件是存在于磁盘上的,但是这个文件都在VFS的文件描述结构体中又注册,因为VFS是管理磁盘的,然后在调用open_namei函数来根据name在VFS中获取指向这个文件的指针。

最后通过这个指针可以找到这个文件指向哪个open、write等,最后会发现通过它里面的文件指针找到的是sysfs,udev只负责根据class目录下的结构把文件注册到VFS的磁盘/DEV目录下。

然后sysfs里的节点指向内核中的节点,所以当我们想删除一个节点的话,如果先删除的是内核里的节点的话,你在去删除sysfs里的节点会发现出现段错误。

这个原因大概是因为找到sysfs节点时,linux会判断指向的内核指针是否有效,来确认这是否是一个正常的设备。

所以我们怎么注册的,就怎么反着来释放。

先释放子节点

int i = 0;      //for c99

for(; i < LED_NUMBER_MAX; ++i){
                device_unregister(led_device_class_son[i]);
}

释放父节点

class_destroy(led_device_file_class);

关闭io映射

iounmap(GPIC0_address);
iounmap(GPIC0_dat);

删除内核里的模块信息

 unregister_chrdev(led_kernel_fd,LED_KERNEL_NAME);

在打印一行log

 printk("TQ210_LED[SUCCESS]:exit\n");

在打印时非常建议大家在前面加上标识符,这样在输出log时使用grep能更清楚看到你的日志。

到这里你的雏形驱动已经完成了,你编译好之后在使用insmod命令安装的话就会看到/dev目录下对应的节点。

当使用sysfs注册时,sysfs会自动通知udev的。

现在开始写make,回到顶层目录下

三.五 make实现

内核模块的make写法已经在之前的linux内核模块文章中说过了

这里交叉编译的话记得修改KDIR还有CROSS_COMPILE的变量值就可以了

ifneq ($(KERNELRELEASE),)
        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.o
else
        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4
all:
        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-
        mv ./src/TQ210_LED/device/*.mod.c               ./output
        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LED
clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean
        rm ./output/*
endif

然后你在make一下,就可以在arch/arm/TQ210_LED目录下看到你的ko文件了。

完成初步之后就需要git保存一下啦

git add .
git commit -m "one"

那么接下来回到驱动文件下实现open函数

三. 六 open实现

首先我们可以利用MINOR取的当前操作的子节点

 int son_id = MINOR(inode->i_rdev);

然后利用switch来对不同节点做不同的处理,这样就不用写多个驱动文件了

同一驱动类型,非常建议这么做。

switch(son_id){

                        case 0: //led one
          
                        break;

                        case 1: //led two

                        break;

                        default:
                                printk("TQ210_LED[ERROR]: error son device number\n");
                                return -1;
                        break;

                }

首先利用我们写好的位函数,把led开启成输出模式

首先是先清空,在开启,这样方便进行位运算,从而不被其他位影响。

case 0: //led one
        SET_GPIC(GPIC0_address,~LED1_GPIC_BIT(0xf),&=);         //clear
        SET_GPIC(GPIC0_address,LED1_GPIC_BIT(0x1),|=);          //output
break;

case 1: //led two
        SET_GPIC(GPIC0_address,~LED2_GPIC_BIT(0xf),&=);         //clear
        SET_GPIC(GPIC0_address,LED2_GPIC_BIT(0x1),|=);          //output
break;

针对初学者,可能对位还不是特别懂,这里我给大家拆一下,详细说一下这个步骤

 把宏展开给大家看下,以LED1举例

*GPIC0_address &= ~((0xf<<(12));

上面原理图说过,CON3[15:12]是设置模式的,0xf的二进制是1111,左移12位是1111000000000000,然后在与原位做与运算,与运算特点:两位同时为1则为1,不相同则为0

然后这里取反就是0000111111111111,与CON3做与运算:

假设CON3是1011000000001000

1011000000001000

——————————

00001111111111110

——————————

0000000000001000

可以看到非常巧妙的利用与特点,没有修改其它位把我们想要设置的位给清空了。

第二个也与之一样,在你对这些含糊不清的时候,请拿起笔来自己运算,让自己清楚才是真正明白了。

*GPIC0_address |= ((0x1<<(12));

左移12位:0001000000000000 ,或运算的特点:当一个bit位为1,则为1,所以不用做取反运算了。

0000000000001000

——————————

0001000000000000

——————————

0001000000001000

也在没有修改其它位的情况下完成了。

open实现完整代码:

static int led_open(struct inode* inode,struct file* file){

                /* open device and init */

                //get son device number
                int son_id = MINOR(inode->i_rdev);

                switch(son_id){

                        case 0: //led one
                                SET_GPIC(GPIC0_address,~LED1_GPIC_BIT(0xf),&=);         //clear
                                SET_GPIC(GPIC0_address,LED1_GPIC_BIT(0x1),|=);          //output
                        break;

                        case 1: //led two
                                SET_GPIC(GPIC0_address,~LED2_GPIC_BIT(0xf),&=);         //clear
                                SET_GPIC(GPIC0_address,LED2_GPIC_BIT(0x1),|=);          //output
                        break;

                        default:
                                printk("TQ210_LED[ERROR]: error son device number\n");
                                return -1;
                        break;

                }

                printk("TQ210_LED[SUCCESS]:open\n");
                return 0;
}

三.七 write实现

获取子节点

 int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

这里我们利用一个函数从用户态往内核态拿一下参数,记得失败打log

char val = 0;
if(copy_from_user(&val,buf,count)){ printk("TQ210_LED[ERROR]:get user variable\n"); return -1; }

然后实现:

就是判断写入的是1则亮,0则灭,位运算在open已经仔细说过了,这里就不展开说了。

我们这里的操作要对dat寄存器,上面开头也说过了。

switch(son_id){

                        case 0: //led one
                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED1_GPIC_DAT_BIT(1),LED2_GPIC_DAT_BIT((GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1)))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED1_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }
                                printk("TQ210_LED[MSG]:led one\n");
                        break;

                        case 1: //led two
                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED2_GPIC_DAT_BIT(1),LED1_GPIC_DAT_BIT(GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED2_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }
                                printk("TQ210_LED[MSG]:led two\n");
                        break;

                        default:
                                printk("TQ210[ERROR]:can't write device number\n");
                        break;

}

最后打印一下:

 printk("TQ210_LED[SUCCESS]:write\n");
 return 0;

完整实现:

static ssize_t led_write(struct file* file,const char __user* buf,size_t count,loff_t* ppos){

                //write device

                //get son device number
                int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

                //get user variable to kernel variablei
                char val = 0;
                if(copy_from_user(&val,buf,count)){ printk("TQ210_LED[ERROR]:get user variable\n"); return -1; }

                switch(son_id){

                        case 0: //led one
                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED1_GPIC_DAT_BIT(1),LED2_GPIC_DAT_BIT((GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1)))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED1_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }
                                printk("TQ210_LED[MSG]:led one\n");
                        break;

                        case 1: //led two
                                if(val == 1){ SET_GPIC_STATE(GPIC0_dat,LED2_GPIC_DAT_BIT(1),LED1_GPIC_DAT_BIT(GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1))),|=); }else{ SET_GPIC_STATE(GPIC0_dat,~LED2_GPIC_DAT_BIT(1),(GET_GPIC(GPIC0_dat,0)),&=); }
                                printk("TQ210_LED[MSG]:led two\n");
                        break;

                        default:
                                printk("TQ210[ERROR]:can't write device number\n");
                        break;

                }

                printk("TQ210_LED[SUCCESS]:write\n");
                return 0;

}

三.八 read实现

read实现也很简单,就是利用位运算去读位的值,唯一用到的就是用户态向内核态传递参数的函数:copy_to_user

完整代码:

static ssize_t led_read(struct file* file,char __user* buf,size_t count,loff_t* ppos){

                //read led state

                int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

                int BIT = 0;
                char on[2]  = LED_STATE_ON;
                char off[3] = LED_STATE_OFF;
                switch(son_id){

                        case 0:
                                BIT = GET_GPIC(GPIC0_dat,LED1_GPIC_DAT_BIT(1));
                                if(BIT){

                                        if(copy_to_user((char*)buf,&on,sizeof(on))) return -EFAULT;

                                }else{
                                        if(copy_to_user((char*)buf,&off,sizeof(off))) return -EFAULT;
                                }
                        break;

                        case 1:
                                BIT = GET_GPIC(GPIC0_dat,LED2_GPIC_DAT_BIT(1));
                                if(BIT){

                                        if(copy_to_user((char*)buf,&on,sizeof(on))) return -EFAULT;
                                        return 2;

                                }else{
                                        if(copy_to_user((char*)buf,&off,sizeof(off))) return -EFAULT;
                                        return 3;
                                }
                        break;

                        default:
                                printk("TQ210[ERROR]:can't write device number\n");
                                return -1;
                        break;

                }

                printk("TQ210_LED[SUCCESS]:read\n");
                return 0;
}

三.九 ioctl实现

ioctl的实现的话,linux是有要求的,ioctl思想是利用参数来获取对应属性,实现对应功能。

linux内核是利用命令码实现这些,开发者利用switch case来对不同的命令码进行不同的实现

在内核里一个命令码是这样的:

________________________________________

| 设备类型  | 序列号 |  方向 | 数据尺寸  |

|----------|--------|------|--------     |

| 8 bit   |  8 bit   | 2 bit |8~14 bit |

|----------|--------|------|------------ |

linux内核也提供了一些实现宏定义

//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送

例子:

#define MEM_IOC_MAGIC 'm' //定义类型
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

linux也提供了一些判断参数是否有效的宏函数

_IOC_NR()    读取基数域值 (bit0~ bit7)
_IOC_TYPE    读取魔数域值 (bit8 ~ bit15)
_IOC_SIZE    读取数据大小域值 (bit16 ~ bit29)
_IOC_DIR     获取读写属性域值 (bit30 ~ bit31)

我的定义:

//ioctl
#define MEMDEV_IOC_MAGIC  's'
#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
#define MEMDEV_IOC_MAXNR  3

依旧取子节点

int par = 0;
int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

判断类型是否有效:

 if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
                        return -EINVAL;

判断参数是否有效

 if(_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
                        return -EINVAL;

在判断读写属性是否有效

 if (_IOC_DIR(cmd) & _IOC_READ){ if(access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }
                else if(_IOC_DIR(cmd) & _IOC_WRITE) { if (access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }

这里利用了一个access_ok是一个宏,用来判断参数的读写是否有效。

接下来就是具体实现啦

只需要判断cmd的属性就可以了。

 switch(son_id){

                        case 0:
                                switch(cmd){

                                        case MEMDEV_IOCPRINT:   //Print information
                                                printk("TQ210_LED1 demo to stephen zhou\n");
                                        break;

                                        case MEMDEV_IOCGETDATA: //Get parameters
                                                return __put_user(par,(int*) arg);
                                        break;

                                        case MEMDEV_IOCSETDATA: //Set parameters
                                                return __get_user(par,(int*) arg);
                                        break;

                                        default:
                                                return -EINVAL;
                                        break;
                                }
                        break;

                        case 1:
                                switch(cmd){

                                        case MEMDEV_IOCPRINT:   //Print information
                                                printk("TQ210_LED2 demo to stephen zhou\n");
                                        break;

                                        case MEMDEV_IOCGETDATA: //Get parameters
                                                return __put_user(par,(int*) arg);
                                        break;

                                        case MEMDEV_IOCSETDATA: //Set parameters
                                                return __get_user(par,(int*) arg);
                                        break;

                                        default:
                                                return -EINVAL;
                                        break;
                                }
                        break;
}

最后别忘记打印:

 printk("TQ210_LED[SUCCESS]:ioctl\n");
 return 0;

完整代码:

static long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){

                int par = 0;
                //get son id    
                int son_id = MINOR(file->f_dentry->d_inode->i_rdev);

                //cmd su or err
                if(_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
                        return -EINVAL;
                if(_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
                        return -EINVAL;
                if (_IOC_DIR(cmd) & _IOC_READ){ if(access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }
                else if(_IOC_DIR(cmd) & _IOC_WRITE) { if (access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))){ return -EFAULT; } }

                switch(son_id){

                        case 0:
                                switch(cmd){

                                        case MEMDEV_IOCPRINT:   //Print information
                                                printk("TQ210_LED1 demo to stephen zhou\n");
                                        break;

                                        case MEMDEV_IOCGETDATA: //Get parameters
                                                return __put_user(par,(int*) arg);
                                        break;

                                        case MEMDEV_IOCSETDATA: //Set parameters
                                                return __get_user(par,(int*) arg);
                                        break;

                                        default:
                                                return -EINVAL;
                                        break;
                                }
                        break;

                        case 1:
                                switch(cmd){

                                        case MEMDEV_IOCPRINT:   //Print information
                                                printk("TQ210_LED2 demo to stephen zhou\n");
                                        break;

                                        case MEMDEV_IOCGETDATA: //Get parameters
                                                return __put_user(par,(int*) arg);
                                        break;

                                        case MEMDEV_IOCSETDATA: //Set parameters
                                                return __get_user(par,(int*) arg);
                                        break;

                                        default:
                                                return -EINVAL;
                                        break;
                                }
                        break;
                }

                printk("TQ210_LED[SUCCESS]:ioctl\n");
                return 0;
}

好了,这里open、write、read、ioctl都已经实现啦,那么我们在写一个简单的app测试一下吧

四、编写测试APP

这里我写了一个闪烁的app代码

在src/app目录下新建一个TQ210_app_led.c的文件,用于用户态的程序

包含基本头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "../../../include/TQ210_LED/device/TQ210_device_led.h"

这里我写了一个基本的打印函数

void print(char* led1_state,char* led2_state){
        system("clear");
        printf("/*****************************************************************************\n");
        printf("*                                                                            *\n");
        printf("*  TQ210 LED的demo演示                                                       *\n");
        printf("*  Copyright (C) 2021 StephenZhou                                            *\n");
        printf("*                                                                            *\n");
        printf("*----------------------------------------------------------------------------*\n");
        printf("*  Device         : State                                                    *\n");
        printf("*----------------------------------------------------------------------------*\n");
        if(strcmp(led1_state,"ON") == 0){ printf("*  TQ210_LED_ONE  : ON                                                             *\n"); }
        else{ printf("*  TQ210_LED_ONE  : OFF                                                        *\n"); }
        printf("*----------------------------------------------------------------------------*\n");
        if(strcmp(led2_state,"ON") == 0){ printf("*  TQ210_LED_TWO  : ON                                                             *\n"); }
        else{ printf("*  TQ210_LED_TWO  : OFF                                                        *\n"); }
        printf("*----------------------------------------------------------------------------*\n");
        printf("*  Change History :                                                          *\n");
        printf("*  <Date>     | <Version> | <Author>       | <Description>                   *\n");
        printf("*----------------------------------------------------------------------------*\n");
        printf("*  2020/5/25 | 1.0.0.0   | StephenZhou      | LED Demo                       *\n");
        printf("*----------------------------------------------------------------------------*\n");
        printf("*                                                                            *\n");
        printf("*****************************************************************************/\n");

}

第一步就是声明基本变量,然后就按照open、write的方式写就可以了,注释我写的非常清楚

就是先open打开,然后read读取状态,把状态打印出来,并且调用ioctl来打印基本信息,在使用write写入状态。

int main(int argc,char **argv){

        /* Two LEDs flash each other and print the status */
        int cmd = 0,arg = 0,val = 0;
        char led1_state[4] = {0},led2_state[4] = {0};
        //1. open udev device
        int led_fd1 = open(LED_DEV_ONE_NAME,O_RDWR);
        int led_fd2 = open(LED_DEV_TWO_NAME,O_RDWR);
        if(led_fd1 == -1 || led_fd2 == -1){
                printf("error:can't open led device\n");
                return -1;
        }

        //2. print msg to kernel ioctl
        cmd = MEMDEV_IOCPRINT;
        if(ioctl(led_fd1,cmd,&arg) == -1) { printf("error:can't ioctl for led1\n"); return -1; }
        cmd = MEMDEV_IOCPRINT;
        if(ioctl(led_fd2,cmd,&arg) == -1) { printf("error:can't ioctl for led2\n"); return -1; }

        //3. wink
        while(1){

                //led one wink led two close
                val = 1;
                if(write(led_fd1,&val,sizeof(val)) == -1){ printf("error:can't write for led1\n"); return -1; }
                val = 0;
                if(write(led_fd2,&val,sizeof(val)) == -1){ printf("error:can't write for led2\n"); return -1; }

                //clear string
                memset(led1_state,0,sizeof(led1_state));
                memset(led2_state,0,sizeof(led2_state));

                //read led state
                if(read(led_fd1,led1_state,sizeof(led1_state)) == -1){ printf("error:cant't read for led1\n"); return -1; }
                if(read(led_fd2,led2_state,sizeof(led2_state)) == -1){ printf("error:cant't read for led2\n"); return -1; }
                print(led1_state,led2_state);

                sleep(3);

                //led two wink led one close
                val = 1;
                if(write(led_fd2,&val,sizeof(val)) == -1){ printf("error:can't write for led1\n"); return -1; }
                val = 0;
                if(write(led_fd1,&val,sizeof(val)) == -1){ printf("error:can't write for led2\n"); return -1; }

                //clear string
                memset(led1_state,0,sizeof(led1_state));
                memset(led2_state,0,sizeof(led2_state));

                //read led state
                if(read(led_fd1,led1_state,sizeof(led1_state)) == -1){ printf("error:cant't read for led1\n"); return -1; }
                if(read(led_fd2,led2_state,sizeof(led2_state)) == -1){ printf("error:cant't read for led2\n"); return -1; }
                print(led1_state,led2_state);

                sleep(3);

        }

        return 0;
}                                                                                                                                                                                                                                                  

我们在把app文件添加到make里,用交叉编译器

以下是我修改后的make

我使用了一些mv命令来把生成的临时文件以及目标文件都放到固定目录中。

ifneq ($(KERNELRELEASE),)
        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.o
else
        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4
all:
        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-
        arm-embedsky-linux-gnueabi-gcc ./src/TQ210_LED/app/TQ210_app_led.c -o TQ210_app_led
        mv ./src/TQ210_LED/device/*.o                   ./output
        mv *.symvers                                    ./output
        mv *.order                                      ./output
        mv ./src/TQ210_LED/device/*.mod.c               ./output
        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LED
        mv TQ210_app_led                                ./arch/arm/TQ210_LED
clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean
        rm ./output/*
        rm ./arch/arm/TQ210_LED/*
        rm ./arch/arm/network/*
endif

这个时候你在make一下就能在arch/arm/TQ210_LED目录下看到app和.ko文件了。

beis@ubuntu:~/moudul/arch/arm/TQ210_LED$ tree
.
├── TQ210_app_led
└── TQ210_device_led.ko

0 directories, 2 files

最后别忘记git一下

git add .
git commit -m "two"

到这一步基本一个demo就已经写完了,那么接下里我们让它物联网

写一个server服务器来控制它

以下代码是我以前写过web ui的方法实现的。

五. 物联网

在src目录下新建一个network目录,在此目录新建一个server.c文件,同理include目录也是一样

这部分如果你对http开发以及网站开发没有开发经验的话可以跳过,这里我是自己写的服务器是为了让大家更清楚了解物联网原理,可以去看下我关于http协议的讲解,以及我开源的http解析代码

mkdir src/network
touch src/network/server.c
mkdir include/network
touch include/network/server.h

我们在写一个html用来展示前端,其就post的提交

代码很简单,存放在src/network目录下

先在.h文件里把协议以及头文件定义出来

#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "../../include/TQ210_LED/device/TQ210_device_led.h"

//server config
#define PORT                    8081
#define BACKLOG                 10

//http
#define STATE_OK                "HTTP/1.0 200 OK\r\n"
#define SERVER_TYPE             "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"

//file
#define INDEX_NAME              "./index.html"

//http ops
#define STATE_GET_INDEX         1
#define STATE_BUTTON_FOO        2
#define STATE_200               3

//led
#define LED_1                   1
#define LED_2                   2
#define LED_UP                  3
#define LED_DOWN                4

思路就是post提交给服务器,服务器拿到post数据然后判断数据信息来执行对应的功能

这里使用了form的表格实现了这一功能

<html>
<body>
<form action = "" method = "post">
                 <button name="foo" value="LED1_Light">LED1_点亮</button>
</form>
<form action = "" method = "post">
       <button name="foo" value="LED1_Ext">LED1_熄灯</button>
</form>
<form action = "" method = "post">
                 <button name="foo" value="LED2_Light">LED2_点亮</button>
</form>
<form action = "" method = "post">
       <button name="foo" value="LED2_Ext">LED2_熄灯</button>
</form>
</body>
</html>

server.c部分

首先包含头文件:

#include "../../include/network/server.h"

在写一个解析http协议的head代码:

这一部分是用来获取报文头的,便于服务器判断请求参数是什么

int GetHead(char* buff,char* t){

        if(buff == NULL || t == NULL){
                return -1;
        }
        int str_len = strlen(buff);
        int i = 0;
        for(; i<str_len ;++i){

                if(buff[i] == '\r'){
                        break;
                }

                t[i] = buff[i];

        }

        return 0;

}

在写一个函数用来获取报文尾部,因为post提交的话文本会在报文体的尾部

int GetEnd(char* buff,char* t){
        if(buff == NULL || t == NULL){
                return -1;
        }

        int i = strlen(buff);
        int count = 0;
        int d = 0;
        int y = 0;
        for(;i>0;--i){
                if(buff[i] == '\n'){
                        break;
                }
                ++count;
        }

        d = strlen(buff) - count+1;
        for(;buff[d] != '\0';++d){
                t[y++] = buff[d];

        }

        return 0;
}

然后在写一个获取HTTP状态的函数,这一部分主要利用解析到的文本头来判断http客户端执行了什么操作,然后返回给我们,便于我们做对应的操作

int GET_HTTP_STATE(char* buff){
        
        if(buff == NULL){  
                return 0;
        }

        char Head[256] = {0};
        GetHead(buff,Head);
        if(Head == NULL){ return 0; }
        
        if(strcmp(Head,"GET / HTTP/1.1") == 0){
                
                return STATE_GET_INDEX;
    
        }
    
        char ff[256] = {0};
        if(strcmp(Head,"POST / HTTP/1.1") == 0){
                          
                return STATE_BUTTON_FOO;

        }

        return STATE_200;

}

然后就是led操作的函数,这个就不用多说了,非常简单

int Led_Ops(int LED_INDEX,int STATE){

        int fd  = 0;
        int val = 0;

        if(LED_INDEX == LED_1){

                fd = open(LED_DEV_ONE_NAME,O_RDWR);
                if(fd == -1) { return -1; }
        }
        
        if(LED_INDEX == LED_2){
                fd = open(LED_DEV_TWO_NAME,O_RDWR);
                if(fd == -1) { return -1; }
        }

        if(STATE == LED_UP){
                val = 1;
                write(fd,&val,sizeof(val));
        }

        if(STATE == LED_DOWN){
                val = 0;
                write(fd,&val,sizeof(val));
        }

        close(fd);

}

最后就是exec的事件函数,根据返回的状态执行对应的操作,最后也要给http客户端进行反馈,200告诉它我们完成了工作,其次post提交之后我们也要返回页面,因为http客户端会显示服务器返回的数据。

int Http_Exec(int state,int fd,char* buff){

        char rt[2*1024] = {0};
        if(state == STATE_GET_INDEX){
                strcat(rt,STATE_OK);
                strcat(rt,SERVER_TYPE);
                strcat(rt,index_body);
                send(fd,rt,strlen(rt),0);
        }

        if(state == STATE_BUTTON_FOO){
                char fun[256] = {0};
                GetEnd(buff,fun);
                strcat(rt,STATE_OK);
                strcat(rt,SERVER_TYPE);
                strcat(rt,index_body);
                send(fd,rt,strlen(rt),0);
                if(strcmp(fun,"foo=LED1_Light") == 0){
                        Led_Ops(LED_1,LED_UP);
                }
                if(strcmp(fun,"foo=LED1_Ext") == 0){
                        Led_Ops(LED_1,LED_DOWN);
                }
                if(strcmp(fun,"foo=LED2_Light") == 0){
                        Led_Ops(LED_2,LED_UP);
                }
                if(strcmp(fun,"foo=LED2_Ext") == 0){
                        Led_Ops(LED_2,LED_DOWN);
                }

        }

        if(state == STATE_200){
                strcat(rt,STATE_OK);
                strcat(rt,SERVER_TYPE);
                send(fd,rt,strlen(rt),0);
        }

        return 0;
}

然后就是main函数

先初始化tcp,这里我写的不是多线程,是单线程的,也就是说同一时间只能有一个http客户端响应。

 int listenfd, connectfd;
        struct sockaddr_in server, client;
        socklen_t addrlen;

        if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
                perror("[SERVER_ERROR] socket\n");
                return -1;
        }

        int opt = 1;
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        bzero(&server, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        server.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){
                perror("[SERVER_ERROR] bind\n");
                return -1;
        }

        addrlen = sizeof(client);

        FILE* fp = fopen(INDEX_NAME,"r");
        if(fp == NULL){
                perror("[SERVER_ERROR] index.html file\n");
                return -1;
        }

把index.html读取出来,返回给客户端

fread(index_body,sizeof(index_body),1,fp);

然后while循环监听并执行事件

 while(1){

                if(listen(listenfd, BACKLOG) == -1){
                        perror("[SERVER_ERROR] listen\n");
                        return -1;
                }

                if((connectfd = accept(listenfd, (struct sockaddr *)&client, &addrlen)) == -1){
                        perror("[SERVER_ERROR] accept\n");
                        return -1;
                }

                char buff[1024] = {0};
                recv(connectfd,buff,1024,0);
                //printf("%s\n",buff);
                Http_Exec(GET_HTTP_STATE(buff),connectfd,buff);
                close(connectfd);
        }

最后的关闭tcpfd

 close(listenfd);
 return 0;

六.在make添加支持

arm-embedsky-linux-gnueabi-gcc ./src/network/server.c -o server
mv server                                       ./arch/arm/network
cp ./src/network/index.html                     ./arch/arm/network

好了到这一步就彻底写完啦。

完整的make:

ifneq ($(KERNELRELEASE),)
        obj-m  := ./src/TQ210_LED/device/TQ210_device_led.o
else
        KDIR := /home/beis/TQ/opt/EmbedSky/TQ210/Kernel_3.0.8_TQ210_for_Linux_v2.4
all:
        $(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-embedsky-linux-gnueabi-
        arm-embedsky-linux-gnueabi-gcc ./src/TQ210_LED/app/TQ210_app_led.c -o TQ210_app_led
        arm-embedsky-linux-gnueabi-gcc ./src/network/server.c -o server
        mv ./src/TQ210_LED/device/*.o                   ./output
        mv *.symvers                                    ./output
        mv *.order                                      ./output
        mv ./src/TQ210_LED/device/*.mod.c               ./output
        mv ./src/TQ210_LED/device/*.ko                  ./arch/arm/TQ210_LED
        mv TQ210_app_led                                ./arch/arm/TQ210_LED
        mv server                                       ./arch/arm/network
        cp ./src/network/index.html                     ./arch/arm/network
clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean
        rm ./output/*
        rm ./arch/arm/TQ210_LED/*
        rm ./arch/arm/network/*
endif

git保存

git add .
git commit -m "three"

七. 最终演示

使用你的方式在把arch/arm下两个目录的文件全部传输到开发板

我用的是dropbear

然后ssh登入进去执行。

我们是ssh登入进去的,所以很多命令都在/sbin目录下

加载模块:

/sbin/insmod TQ210_device_led.ko

然后查看一下日志

可以看到成功加载。

然后我们在运行app demo演示:

在看一下server演示:

因是GIF所以比较模糊,大家可以自行测试

这里是github的项目地址:https://github.com/beiszhihao/Internet-of-things-project

日后所有基于物联网的项目我都会开源在这个仓库中

欢迎大家star

相关推荐
<p> <strong><span style="font-size:20px;color:#FF0000;">本课程演示的<span>是一套基于SSM框架实现的酒店管理系统,</span>主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。</span></strong> </p> <p> <br /> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">1. 包含:<span style="color:#FFFF00;background-color:#FF0000;">项目源码、项目文档、数据库脚本、软件工具</span>等所有资料</span></strong></span> </p> <p> <span style="color:#FF0000;font-size:18px;"><strong><span>2. 手把手的带你从零开始部署运行本套系统</span></strong></span> </p> <p> <span style="color:#FF0000;font-size:18px;"><strong><span>3. 该项目附带的源码资料可作为毕设使用</span></strong></span> </p> <p> <span style="color:#FF0000;font-size:18px;"><strong><span>4. 提供技术答疑和远程协助指导</span></strong></span> </p> <p> <strong><span><span style="font-size:18px;"><br /> </span></span><span><span style="font-size:20px;">技术实现:</span></span></strong> </p> <p> <strong><span style="color:#494429;font-size:18px;">1. 后台框架:Spring、SpringMVC、MyBatis</span></strong> </p> <p> <strong><span style="color:#494429;font-size:18px;">2. UI界面:BootStrap、JSP、jQuery</span></strong> </p> <p> <strong><span style="color:#494429;font-size:18px;">3. 数据库:MySQL</span></strong> </p> <p> <strong><span style="color:#494429;font-size:18px;"><br /> </span></strong> </p> <p> <span style="color:#FF0000;"><strong><strong><span><strong><span style="font-size:20px;color:#000000;"></span></strong></span></strong></strong><strong><strong><span><strong><span style="font-size:20px;color:#000000;"></span></strong></span></strong></strong><strong><strong><span><strong><span style="font-size:20px;color:#000000;"></span></strong></span></strong></strong></span> </p> <p> <span style="font-size:20px;color:#494429;"><strong>项目截图</strong></span><span style="font-size:20px;"><strong>:</strong></span> </p> <p> <strong><span style="font-size:18px;">1)系统登陆界面</span></strong> </p> <p> <img src="https://img-bss.csdn.net/202003160644304457.png" alt="" /> </p> <p> <span style="color:#262626;"><strong><strong><span style="color:#FF0000;"><span style="font-size:18px;color:#000000;"></span></span></strong></strong></span><strong><strong><span style="color:#FF0000;"><strong><span style="font-size:18px;color:#000000;"><strong><span>2)客房预订</span></strong></span></strong></span></strong></strong> </p> <p> <strong><strong><span style="color:#FF0000;"><strong><span style="font-size:18px;color:#000000;"><strong><span><img src="https://img-bss.csdn.net/202003160644376426.png" alt="" /><br /> </span></strong></span></strong></span></strong></strong> </p> <p> <strong><strong><span style="color:#FF0000;"><strong><span style="font-size:18px;color:#000000;"><strong><span><strong><span>3)住宿登记</span></strong></span></strong></span></strong></span></strong></strong> </p> <p> <strong><strong><span style="color:#FF0000;"><strong><span style="font-size:18px;color:#000000;"><strong><span><strong><span><img src="https://img-bss.csdn.net/202003160644464949.png" alt="" /><br /> </span></strong></span></strong></span></strong></span></strong></strong> </p> <p> <strong><span style="font-size:18px;">4)旅客管理</span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202003160644535071.png" alt="" /><br /> </span></strong> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">更多Java毕设项目请关注【毕设系列课程】<a href="https://edu.csdn.net/lecturer/2104">https://edu.csdn.net/lecturer/2104</a></span></strong></span> </p>
<p> <span> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476"><span style="font-size:14px;"> </span></span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> 人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。 </p> <p class="ql-long-24357476"> <br /> </p> <p class="ql-long-24357476"> 本课程以简洁易懂的语言手把手你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; </p> <p> <span></span> </p> <p> <span style="color:#FF9900;"><span><span> </span></span></span> </p> <p class="ql-long-24357476"> <span style="background-color:#FFFFFF;color:#E53333;">【限时福利】</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476">1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包!</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476"><span>2)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。</span><br /> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476">---------------------------------------------------------------</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476" style="color:#E53333;">99元=掌握Linux必修知识+社群答疑+讲师社群分享会+700元编程礼包。</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476" style="color:#E53333;"><br /> </span> </p> <p style="font-size:11pt;color:#494949;"> <span> <img alt="" src="https://img-bss.csdn.net/202002140604337221.png" /></span> </p>
<p> <strong><span style="font-size:16px;color:#337FE5;"><b><a target="_blank" href="https://edu.csdn.net/bundled/detail/308"></a><a target="_blank" href="https://edu.csdn.net/bundled/detail/308"><span> </span></a></b></span></strong> </p> <p class="ql-long-39788408" style="font-size:11pt;color:#494949;"> <strong><b><strong><a class="ql-link ql-size-12 ql-author-39788408" href="https://edu.csdn.net/bundled/detail/298" target="_blank">[本课程属于AI完整学习路线套餐,该套餐已“硬核”上线,点击立即学习!]</a></strong> </b></strong> </p> <p class="ql-long-39788408" style="font-size:11pt;color:#494949;"> <br /> </p> <p> <strong><span style="font-size:16px;color:#337FE5;"><img src="https://img-bss.csdnimg.cn/202011090216454206.png" alt="" /><br /> </span></strong> </p> <p> <strong><span style="font-size:16px;color:#337FE5;"><br /> </span></strong> </p> <p> <strong><span style="font-size:16px;color:#337FE5;">【为什么要学习深度学习和计算机视觉?】</span></strong> </p> <p> <span style="font-family:"background-color:#FFFFFF;">AI人工智能现在已经成为人类发展中最火热的领域。而计算机视觉(CV)是AI中最热门,也是落地最多的一个应用方向<span style="font-family:"background-color:#FFFFFF;">(人脸识别,自动驾驶,智能安防,车牌识别,证件识别)</span>。</span><span style="font-family:"background-color:#FFFFFF;">所以基于人工智能的计算视觉行业必然会诞生大量的工作和创业的机会。如何能快速的进入CV领域,同时兼备理论基础和实战能力,就成了大多数学习者关心的事情,而这门课就是因为这个初衷而设计的。<br /> </span> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <br /> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span style="font-size:16px;color:#337FE5;"><strong>【讲师介绍】</strong></span> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span style="font-size:16px;color:#337FE5;"><strong>CH<strong><span style="font-family:"color:#222226;font-size:16px;background-color:#FFFFFF;font-weight:700;">ARLIE 老师</span></strong></strong></span> </p> 1、人工智能算法科学家<br /> 2、深圳市海外高层次人才认定(孔雀人才)<br /> 3、美国圣地亚哥国家超算中心博士后<br /> 4、加利福尼亚大学圣地亚哥全奖博士<br /> 5、参与美国自然科学基金(NSF)及加州能源局 (CEC)资助的392MW IVANPAH等智慧电网项目<br /> 6、21篇国际期刊文章(sci收录17篇),总引用接近1000<br /> 7、第一作者发明专利11份<br /> <p> <br /> </p> <p class="ql-long-24357476" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span style="font-family:"color:#337FE5;"><span><span style="font-size:16px;"><strong>【推荐你学习这门课的理由:</strong></span><span style="color:#E53333;font-size:16px;"><strong>知识体系完整+丰富学习资料】</strong></span></span></span> </p> <p class="ql-long-24357476" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span class="ql-author-24357476" style="font-family:""></span> </p> <p class="MsoNormal" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> 1、本课程总计9大章节,是一门系统入门计算机视觉的课程,未来将持续更新。 </p> <p class="MsoNormal" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span>2</span>、<span>课程从计算机视觉理论知识出发,理论结合实战手把手实战代码实现(霍夫变换与模板匹配,</span><span>AlexNet OCR</span><span>应用</span><span>,VGG</span><span>迁移学习,多标签分类算法工程)</span> </p> <p class="MsoNormal" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span>3</span>、<span>带你了解最前沿技术,</span><span>各类型算法的优点和缺点,掌握数据增强,</span><span>Batchnormalization, Dropout</span><span>,迁移学习等优化技巧,搭建实用的深度学习应用模型</span> </p> <p class="MsoNormal" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <span>4</span>、学习完后,你将具有深度学习与计算视觉的项目能力,比如大学生学完可以具备独立完成机器视觉类毕业设计的能力,在求职过程中可以体系化的讲解机器视觉核心知识点,初步达到人工智能领域机器视觉工程师的水平 </p> <span style="color:#222226;font-family:PingFangSC-Regular, "font-size:14px;background-color:#FFFFFF;"></span> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <br /> </p> <p class="ql-long-24357476" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="color:#337FE5;font-size:16px;">【学完后我将达到什么水平?】</span></strong> </p> <p class="ql-long-24357476"> <span>1、<span style="font-family:"">零基础入门计算视觉,学习掌握并应用从经典图像处理到深度学习分类任务的要点知识</span></span> </p> <p class="ql-long-24357476"> <span>2、<span style="font-family:"">掌握数据增强,迁移学习等优化技巧,搭建实用的深度学习应用模型</span></span> </p> <p class="ql-long-24357476"> <span>3、<span style="font-family:"">学习完课程,可以独立应用多个经典算法和深度学习算法</span></span> </p> <p class="ql-long-24357476"> <span>4、<span style="font-family:"">以</span><span style="font-family:"">大学毕业设计,面试找工作为目标,</span><span style="font-family:"">手把手带大家编程,即使没有太多计算视觉的背景知识也可以循序渐进完成课程,获得实战项目的经验</span></span> </p> <p class="ql-long-24357476"> <br /> </p> <p class="ql-long-24357476"> <span style="color:#337FE5;"><b><span style="background-color:#FFFFFF;color:#337FE5;"><span style="font-size:16px;color:#337FE5;">【面向人群】</span></span></b></span> </p> <p class="ql-long-24357476"> <span>1、对AI感兴趣,想要系统学习计算机视觉的学员</span> </p> <p class="ql-long-24357476"> <span>2、<span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">需要毕业设计的大学生</span></span> </p> <p class="ql-long-24357476"> <span>3、<span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">做图像分析或相关数据分析的研究生</span></span> </p> <p class="ql-long-24357476"> <span>4、<span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">准备面试计算视觉和深度学习岗位的应聘者</span></span> </p> <p class="ql-long-24357476"> <span>5、<span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">希望在项目中引入计算视觉</span><span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">/</span><span style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;">深度学习技术的开发人员</span></span> </p> <p class="ql-long-24357476"> <br /> </p> <p class="ql-long-24357476"> <b><span style="font-family:"font-size:16px;background-color:#FFFFFF;color:#337FE5;"><span style="font-size:16px;color:#337FE5;">【课程知识体系图</span><span style="font-size:16px;color:#337FE5;">】</span></span></b> </p> <p class="ql-long-24357476"> <span><b><img src="https://img-bss.csdnimg.cn/202007140746422581.png" alt="" /></b></span> </p> <p class="ql-long-24357476"> <span><b><br /> </b></span> </p> <p class="ql-long-24357476"> <span style="font-size:16px;color:#337FE5;"><b>【实战项目】</b></span> </p> <p class="ql-long-24357476"> <b><img src="https://img-bss.csdnimg.cn/202007150352244062.png" alt="" /><img src="https://img-bss.csdnimg.cn/202007150517376530.png" alt="" /></b> </p> <p class="ql-long-24357476"> <br /> </p>
<p> <span style="color:#FF0000;"><strong>更好的应用,更少的代码!</strong></span><span><br /> <br /> </span> </p> <p> <span style="font-size:16px;color:#000000;">SwiftUI是苹果主推的下一代用户界面搭建技术,具有声明式语法、实时生成界面预览等特性,可以为苹果手机、苹果平板、苹果电脑、苹果电视、苹果手表五个平台搭建统一的用户界面。<br /> <br /> </span> </p> <p> <span style="font-size:16px;color:#000000;">SwiftUI是一种创新、简单的iOS开发中的界面布局方案,可以通过Swift语言的强大功能,在所有的Apple平台上快速构建用户界面。 仅使用一组工具和API为任何Apple设备构建用户界面。<br /> <br /> SwiftUI具有易于阅读和自然编写的声明式Swift语法,可与新的Xcode设计工具无缝协作,使您的代码和设计**同步。自动支持动态类型、暗黑模式、本地化和可访问性,意味着您的**行SwiftUI代码已经是您编写过的非常强大的UI代码了。</span> </p> <p> <br /> </p> <p> <span style="font-size:16px;color:#000000;">【课程特点】</span> </p> <p> <span style="font-size:16px;color:#FF0000;">1、196节大容量课程:包含了SwiftUI的大部分知识点,详细讲解SwiftUI的方方面面;<br /> </span> </p> <p> <span style="font-size:16px;color:#FF0000;">2、15个超级精彩的实例:包含美食、理财、健身、育、电子商务等各行业的App实例;</span> </p> <p> <span style="font-size:16px;color:#000000;">3、创新的学模式:手把手您SwiftUI用户界面开发技术,一看就懂,一学就会;</span> </p> <p> <span style="font-size:16px;color:#000000;">4、贴心的操作提示:让您的眼睛始终处于操作的焦点位置,不用再满屏找光标;</span> </p> <p> <span style="font-size:16px;color:#000000;">5、语言简洁精练:瞄准问题的核心所在,减少对思维的干扰,并节省您宝贵的时间;</span> </p> <p> <span style="font-size:16px;color:#000000;">6、视频短小精悍:即方便于您的学习和记忆,也方便日后对功能的检索;</span> </p> <p> <span style="font-size:16px;color:#000000;">7、齐全的学习资料:提供所有课程的源码,在Xcode 11 + iOS 13环境下测试通过;</span> </p> <p> <span style="font-size:16px;color:#000000;"><br /> </span> </p> <p> <span style="font-size:16px;color:#000000;"><img src="https://img-bss.csdn.net/202001220124599122.png" alt="" /><br /> </span> </p> <p> <span style="font-size:16px;color:#000000;"><img src="https://img-bss.csdn.net/202001220125126694.png" alt="" /><br /> </span> </p>
<p> <span style="color:#E53333;font-family:"font-size:24px;background-color:#FFFFFF;"><strong>本页面购买不发书!!!仅为视频课购买!!!</strong></span><strong><span style="font-size:24px;"></span></strong> </p> <p style="font-family:"color:#3D3D3D;font-size:16px;background-color:#FFFFFF;"> <strong><span style="color:#E53333;font-size:18px;">请务必到</span></strong><strong><span style="color:#E53333;font-size:18px;"><a href="https://edu.csdn.net/bundled/detail/59?utm_source=banner">https://edu.csdn.net/bundled/detail/59</a> 下单</span></strong><strong><span style="color:#E53333;font-size:18px;">,方可购买课程+图书。</span></strong> </p> <p style="font-family:"color:#3D3D3D;font-size:16px;background-color:#FFFFFF;"> <strong><span style="color:#E53333;font-size:18px;"><br /> </span></strong> </p> <p style="font-family:"color:#3D3D3D;font-size:16px;background-color:#FFFFFF;"> 本页面,仅为观看视频页面,如需购买图书,请<span style="font-family:"">务必到</span><a href="https://edu.csdn.net/bundled/detail/49?utm_source=banner"></a><a href="https://edu.csdn.net/bundled/detail/59?utm_source=banner">https://edu.csdn.net/bundled/detail/59</a><span style="font-family:"">下单购买。</span> </p> <p style="font-family:"color:#3D3D3D;font-size:16px;background-color:#FFFFFF;"> <br /> </p> <p> 本课程由浅入深的讲授人工智能、机器学习、深度学习的原理和实现,尤其会重点介绍搜索引擎和自然语言处理等热门技术,不但会用生动的例子帮助学员理解理论知识,还会手把手详细示范动手实践环节,让你能够亲自实现一个智能问答系统。 </p>
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值