本文为转载,参考链接地址

https://blog.csdn.net/li_wen01/article/details/80137566

https://www.cnblogs.com/lsh123/p/7354573.html

DeviceIoControl 将控制代码直接发送到指定的设备驱动程序,使相应的设备执行相应的操作。

这种通信方式,就是驱动程序和应用程序自定义一种IO控制码,然后调用DeviceIoControl函数,IO管理器会产生一个MajorFunction 为IRP_MJ_DEVICE_CONTROL(DeviceIoControl函数会产生此IRP),MinorFunction 为自己定义的控制码的IRP,系统就调用相应的处理IRP_MJ_DEVICE_CONTROL的派遣函数,你在派遣函数中判断MinorFunction ,是自定义的控制码你就进行相应的处理。

BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

hDevice [in]
    需要执行操作的设备句柄。该设备通常是卷,目录,文件或流,使用 CreateFile 函数打开获取设备句柄。具体的见备注
dwIoControlCode [in]
    操作的控制代码,该值标识要执行的特定操作以及执行该操作的设备的类型,有关控制代码的列表,请参考备注。每个控制代码的文档都提供了lpInBuffernInBufferSizelpOutBuffernOutBufferSize参数的使用细节。
lpInBuffer [in, optional]
    (可选)指向输入缓冲区的指针。这些数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不需要输入数据的操作,则此参数可以为NULL。
nInBufferSize [in]
    输入缓冲区以字节为单位的大小。单位为字节。
lpOutBuffer [out, optional]
    (可选)指向输出缓冲区的指针。这些数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不返回数据的操作,则此参数可以为NULL。
nOutBufferSize [in]
    输出缓冲区以字节为单位的大小。单位为字节。
lpBytesReturned [out, optional]
    (可选)指向一个变量的指针,该变量接收存储在输出缓冲区中的数据的大小。如果输出缓冲区太小,无法接收任何数据,则GetLastError返回ERROR_INSUFFICIENT_BUFFER,错误代码122(0x7a),此时lpBytesReturned是零。
    如果输出缓冲区太小而无法保存所有数据,但可以保存一些条目,某些驱动程序将返回尽可能多的数据,在这种情况下,调用失败,GetLastError返回ERROR_MORE_DATA,错误代码234lpBytesReturned指示接收到的数据量。您的应用程序应该再次使用相同的操作调用DeviceIoControl,指定一个新的起点。
    如果lpOverlapped为NULL,则lpBytesReturned不能为NULL。 即使操作没有返回输出数据并且lpOutBuffer为NULL,DeviceIoControl也会使用lpBytesReturned。在这样的操作之后,lpBytesReturned的值是没有意义的。
    如果lpOverlapped不为NULL,则lpBytesReturned可以为NULL。 如果此参数不为NULL并且操作返回数据,则在重叠操作完成之前,lpBytesReturned是无意义的。要检索返回的字节数,请调用GetOverlappedResult,如果hDevice与I / O完成端口相关联,则可以检索通过调用GetQueuedCompletionStatus返回的字节数。
lpOverlapped [in, out, optional]
    (可选)指向OVERLAPPED结构的指针,
  如果在未指定FILE_FLAG_OVERLAPPED的情况下打开hDevice,则忽略lpOverlapped
  如果使用FILE_FLAG_OVERLAPPED标志打开hDevice,则该操作将作为重叠(异步)操作执行。在这种情况下,lpOverlapped必须指向包含事件对象句柄的有效OVERLAPPED结构。 否则,该功能将以不可预知的方式失败。
    对于重叠操作,DeviceIoControl会立即返回,并在操作完成时通知事件对象。 否则,该功能在操作完成或发生错误之前不会返回。

返回值:
    如果操作成功完成,DeviceIoControl将返回一个非零值。

如果操作失败或正在等待,则DeviceIoControl返回零。 要获得扩展的错误信息,请调用GetLastError。

一. 先谈一下这个定义IO控制码 ,其实可以看作是一种通信协议。

看看CTL_CODE原型:

  #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
  )

   可以看到,这个宏四个参数,自然是一个32位分成了4部分,高16位存储设备类型,14~15位访问权限,2~13位操作功能,最后0,1两位就是确定缓冲区是如何与I/O和文件系统数据缓冲区进行数据传递方式,最常见的就是METHOD_BUFFERED。

自定义CTL_CODE:

  #define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

  IOCTL_Device_Function:生成的IRP的MinorFunction

  DeviceType:设备对象的类型。设备类型可参考:http://blog.csdn.net/liyun123gx/article/details/38058965

  Function :自定义的IO控制码。自己定义时取0x800到0xFFF,因为0x0到0x7FF是微软保留的。

  Method :数据的操作模式。

METHOD_BUFFERED:缓冲区模式

METHOD_IN_DIRECT:直接写模式

METHOD_OUT_DIRECT:直接读模式

METHOD_NEITHER :Neither模式

Access:访问权限,可取值有:

FILE_ANY_ACCESS:表明用户拥有所有的权限

FILE_READ_DATA:表明权限为只读

FILE_WRITE_DATA:表明权限为可写

也可以 FILE_WRITE_DATA | FILE_READ_DATA:表明权限为可读可写,但还没达到FILE_ANY_ACCESS的权限。

继续介绍这个缓冲区数据传递方式Method:

  Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:
  #define METHOD_BUFFERED                0  
  #define METHOD_IN_DIRECT               1  
  #define METHOD_OUT_DIRECT              2  
  #define METHOD_NEITHER                  3

(1)如果使用METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。

  METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲。

METHOD_BUFFERED方式(借图):

  (2)如果使用METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。
这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
 
  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问
  8METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式(借图):

  (3)如果使用METHOD_NEITHER方式,"其他方式",虽然通信的效率提高了,但是不够安全。驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。

  METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,

  驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。
  由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

  METHOD_NEITHER方式(借图):

  

二 .定义驱动设备名,符号链接名
       定义好了IO控制码CTL_CODE,第二步驱动程序还要准备驱动设备名和符号链接名。

    关于在Ring0层中要设置驱动设备名的同时还要设置符号链接名的原因,是因为只有符号链接名才可以被用户模式下的应用程序识别。

    windows下的设备是以"\Device\[设备名]”形式命名的。例如磁盘分区的c盘,d盘的设备名称就是"\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 当然也可以不指定设备名称。                               如果IoCreateDevice中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。例如"\Device\00000001"。\Device\[设备名],不容易记忆,通常符号链接可以理解为设备的别名,更重要的是设备名,只能被内核模式下的其他驱动所识别,而别名可以被用户模式下的应用程序识别,例如c盘,就是名为"c:"的符号链接,其真正的设备对象是"\Device\HarddiskVolume1”,所以在写驱动时候,一般我们创建符号链接,即使驱动中没有用到,这也算是一个好的习惯吧。

    驱动中符号链接名是这样写的
    L"\\??\\HelloDDK" --->\??\HelloDDK

    或者
    L"\\DosDevices\\HelloDDK"--->\DosDevices\HelloDDK

    在应用程序中,符号链接名:
    L"\\\\.\\HelloDDK"-->\\.\HelloDDK

    DosDevices的符号链接名就是??, 所以"\\DosDevices\\XXXX"其实就是\\??\\XXXX

1

2

3

4

#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"

//设备与设备之间通信

#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"

//设备与Ring3之间通信

三.将符号链接名与设备对象名称关联 ,等待IO控制码

    驱动程序要做的最后一步,先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink将符号链接名与设备对象名称关联 ,大功告成,等待IO控制码。

    

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

    //创建设备对象名称

RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);

//创建设备对象

Status = IoCreateDevice(DriverObject,NULL,

    &DeviceObjectName,

    FILE_DEVICE_UNKNOWN,

    0, FALSE,

    &DeviceObject);

if (!NT_SUCCESS(Status))

{

    return Status;

}

//创建设备连接名称

RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);

//将设备连接名称与设备名称关联

Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);

if (!NT_SUCCESS(Status))

{

    IoDeleteDevice(DeviceObject);

    return Status;

四.应用程序获取设备句柄,发送IO控制码。

    驱动程序铺垫打理好之后,应用程序就可以由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个DeviceHandle发送控制码了。

    先看看这两个函数:

   

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

BOOL WINAPI DeviceIoControl(

  _In_         HANDLE hDevice,       //CreateFile函数打开的设备句柄

  _In_         DWORD dwIoControlCode,//自定义的控制码

  _In_opt_     LPVOID lpInBuffer,    //输入缓冲区

  _In_         DWORD nInBufferSize,  //输入缓冲区的大小

  _Out_opt_    LPVOID lpOutBuffer,   //输出缓冲区

  _In_         DWORD nOutBufferSize, //输出缓冲区的大小

  _Out_opt_    LPDWORD lpBytesReturned, //实际返回的字节数,对应驱动程序中pIrp->IoStatus.Information。

  _Inout_opt_  LPOVERLAPPED lpOverlapped //重叠操作结构指针。同步设为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计

);

HANDLE CreateFile(

  LPCTSTR lpFileName,                         //打开的文件名

  DWORD dwDesiredAccess,                    //访问权限

  DWORD dwShareMode,                      //共享模式

  LPSECURITY_ATTRIBUTES lpSecurityAttributes,   //安全属性

  DWORD dwCreationDisposition,               //文件存在与不存在时的文件创建模式

  DWORD dwFlagsAndAttributes,                //文件属性设定(隐藏、只读、压缩、指定为系统文件等)

  HANDLE hTemplateFile                       //文件副本句柄

);

  

  最后总结一下DeviceIoControl的通信流程:

    1.驱动程序和应用程序自定义好IO控制码 (CTL_CODE宏 四个参数,32位,4部分,存储设备类型,访问权限,操作功能,缓冲区数据传递方式(四种))

    2.驱动程序定义驱动设备名,符号链接名, 将符号链接名与设备对象名称关联 ,等待IO控制码(IoCreateDevice,IoCreateSymbolicLink)

    3.应用程序由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个设备句柄发送控制码给派遣函数。

  源代码:

  BufferedIO.h

  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#pragma once

#include <ntifs.h>

#define CTL_SYS \

    CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)

#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"

//设备与设备之间通信

#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"

//设备与Ring3之间通信

VOID DriverUnload(PDRIVER_OBJECT DriverObject);

NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);

NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);

  

BufferedIO.c

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

#include "BufferedIO.h"

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)

{

    NTSTATUS Status = STATUS_SUCCESS;

    PDEVICE_OBJECT  DeviceObject = NULL;

    UNICODE_STRING  DeviceObjectName;

    UNICODE_STRING  DeviceLinkName;

    ULONG           i;

    //   栈

    //   堆

    //   全局(global Static Const)

    DriverObject->DriverUnload = DriverUnload;

    //创建设备对象名称

    RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);

    //创建设备对象

    Status = IoCreateDevice(DriverObject,NULL,

        &DeviceObjectName,

        FILE_DEVICE_UNKNOWN,

        0, FALSE,

        &DeviceObject);

    if (!NT_SUCCESS(Status))

    {

        return Status;

    }

    //创建设备连接名称

    RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);

    //将设备连接名称与设备名称关联

    Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);

    if (!NT_SUCCESS(Status))

    {

        IoDeleteDevice(DeviceObject);

        return Status;

    }

    //设计符合我们代码的派遣历程

    for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)

    {

        DriverObject->MajorFunction[i] = PassThroughDispatch;   //函数指针

    }

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;

    return Status;

}

//派遣历程

NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject,PIRP Irp)

{

    Irp->IoStatus.Status = STATUS_SUCCESS;     //LastError()

    Irp->IoStatus.Information = 0;             //ReturnLength

    IoCompleteRequest(Irp, IO_NO_INCREMENT);   //将Irp返回给Io管理器

    return STATUS_SUCCESS;

}

NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)

{

    NTSTATUS Status;

    ULONG_PTR Informaiton = 0;

    PVOID InputData = NULL;

    ULONG InputDataLength = 0;

    PVOID OutputData = NULL;

    ULONG OutputDataLength = 0;

    ULONG IoControlCode = 0;

    PIO_STACK_LOCATION  IoStackLocation = IoGetCurrentIrpStackLocation(Irp);  //Irp堆栈  

    IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;

    InputData  = Irp->AssociatedIrp.SystemBuffer;

    OutputData = Irp->AssociatedIrp.SystemBuffer;

    InputDataLength  = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;

    OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;

    switch (IoControlCode)

    {

    case CTL_SYS:

    {

        if (InputData != NULL&&InputDataLength > 0)

        {

            DbgPrint("%s\r\n", InputData);

        }

        if (OutputData != NULL&&OutputDataLength >= strlen("Ring0->Ring3") + 1)

        {

            memcpy(OutputData, "Ring0->Ring3"strlen("Ring0->Ring3") + 1);

            Status = STATUS_SUCCESS;

            Informaiton = strlen("Ring0->Ring3") + 1;

        }

        else

        {

            Status = STATUS_INSUFFICIENT_RESOURCES;   //内存不够

            Informaiton = 0;

        }

        break;

    }

    default:

        break;

    }

    Irp->IoStatus.Status = Status;             //Ring3 GetLastError();

    Irp->IoStatus.Information = Informaiton;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);  //将Irp返回给Io管理器

    return Status;                            //Ring3 DeviceIoControl()返回值

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

    UNICODE_STRING  DeviceLinkName;

    PDEVICE_OBJECT  v1 = NULL;

    PDEVICE_OBJECT  DeleteDeviceObject = NULL;

    

    RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);

    IoDeleteSymbolicLink(&DeviceLinkName);

    DeleteDeviceObject = DriverObject->DeviceObject;

    while (DeleteDeviceObject != NULL)

    {

        v1 = DeleteDeviceObject->NextDevice;

        IoDeleteDevice(DeleteDeviceObject);

        DeleteDeviceObject = v1;

    }

}

  

IO.cpp

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

// 缓冲区IO.cpp : 定义控制台应用程序的入口点。

//

#include "stdafx.h"

#include <windows.h>

#define DEVICE_LINK_NAME    L"\\\\.\\BufferedIODevcieLinkName"

#define CTL_SYS \

    CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)

int main()

{

    HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,

        GENERIC_READ | GENERIC_WRITE,

        FILE_SHARE_READ | FILE_SHARE_WRITE,

        NULL,

        OPEN_EXISTING,

        FILE_ATTRIBUTE_NORMAL,

        NULL);

    if (DeviceHandle==INVALID_HANDLE_VALUE)

    {

        return 0;

    }

    char BufferData = NULL;

    DWORD ReturnLength = 0;

    BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,

        "Ring3->Ring0",

        strlen("Ring3->Ring0")+1,

        (LPVOID)BufferData,

        0,

        &ReturnLength,

        NULL);

    if (IsOk == FALSE)

    {

        int LastError = GetLastError();

        if (LastError == ERROR_NO_SYSTEM_RESOURCES)

        {

            char BufferData[MAX_PATH] = { 0 };

            IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,

                "Ring3->Ring0",

                strlen("Ring3->Ring0") + 1,

                (LPVOID)BufferData,

                MAX_PATH,

                &ReturnLength,

                NULL);

            if (IsOk == TRUE)

            {

                printf("%s\r\n", BufferData);

            }

        }

    }

    if (DeviceHandle != NULL)

    {

        CloseHandle(DeviceHandle);

        DeviceHandle = NULL;

    }

    printf("Input AnyKey To Exit\r\n");

    getchar();

    return 0;

}

应用程序与驱动程序通信 DeviceIoControl相关推荐

  1. windows驱动开发7:应用程序和驱动程序的通信

    应用程序和驱动程序的通信 一.基础介绍 1.1 设备与驱动的关系 设备由驱动去创建,访问一个设备,是首先得访问驱动.如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的. 驱动程序和系统其他组 ...

  2. exchange邮件中继服务器搭建,使用 Exchange 2010 传输中继应用程序服务器 SMTP 通信...

    使用 Exchange 2010 传输中继应用程序服务器 SMTP 通信 05/13/2016 本文内容 适用于: Exchange Server 2010 SP2, Exchange Server ...

  3. 微信小程序组件间通信(二)

    2019独角兽企业重金招聘Python工程师标准>>> 一.微信小程序中通过事件,实现子组件向父组件中传递数据或操作 注:子组件向父组件中传递通过事件传递操作 通过事件参数对象det ...

  4. 如何在基于 Silverlight 的本地应用程序之间实现通信

    http://msdn.microsoft.com/zh-cn/library/dd833075(VS.95).aspx 在两个Silverlight应用间数据通信(包括与Flash通信) 如何在基于 ...

  5. Java21-day12【网络编程(网络编程入门(ip地址、端口、协议、InetAddress)、UDP通信程序、TCP通信程序)】

    视频+资料[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg   提取码:zjxs] Java基础--学习笔记(零起点打开java世界的大门)--博 ...

  6. Smart200控制两台V90伺服,绝对定位和速度控制,有屏程序,PN通信。 注释清楚

    Smart200控制两台V90伺服,绝对定位和速度控制,有屏程序,PN通信. 注释清楚,PDF调试说明,易懂. YID:4630644263796376TB

  7. 驱动开发(一)——(单片机程序、Linux应用程序与驱动程序分析)

    文章目录 前言 157准备工作 配置交叉编译链 编译内核 编译解压glibc 单片机程序 应用程序 驱动程序 三者的关系 前言 学习资料,跟的韦东山老师的视频,大家可以上百问网下载资料 百问网. 我使 ...

  8. Smart200控制两台V90伺服,绝对定位和速度控制,有屏程序,PN通信

    Smart200控制两台V90伺服,绝对定位和速度控制,有屏程序,PN通信 注释清楚,PDF调试说明,易懂 YID:6930644263796376

  9. 蓝牙串口通信java_Java程序与串口通信的实现及通信原码-全网最详细,一步一步教会...

    RS-232(ANSI/EIA-232标准)是IBM-PC及其兼容机上的串行连接标准.RS-422(EIA RS-422-AStandard)是Apple的Macintosh计算机的串口连接标准.RS ...

最新文章

  1. 项目经理生存现状(漫画)
  2. TypeError: Cannot read property 'gc' of undefined 使用百度地图报错
  3. Android之父卸任意味着什么?
  4. 得到例会听后感悟_20190507_重和远
  5. 47. Leetcode 107 - 二叉树的层次遍历 ii (二叉树-二叉树遍历)
  6. linux动态链接库
  7. Web使用热敏打印小票(IE环境)
  8. MATLAB如何把图片显示在GUI上
  9. 推荐系统--矩阵分解(2)
  10. 开启企业级市场转型之路 群晖亮出安全“杀手锏”
  11. ieda中快捷搜索_快捷指令(07)早上好(三)播报当日日程安排。
  12. Python 开源电子书资源
  13. JS 获取宽,高(ie未测)
  14. 分组码--原理,码率,软硬判决,编码增益
  15. 需求分析中系统参与者的概念和确定
  16. python绘制对数函数_python中如何画对数函数图?
  17. matlab编程之求向量的模
  18. java事件适配器_java 事件适配器
  19. ex绅士_非凡绅士联盟...
  20. Redis工具类封装

热门文章

  1. mysql error 1118_mysql数据库插入数据错误Error Code- 1118
  2. 在线视频直播类app软件如何开发?
  3. 51单片机学习知识点
  4. 表数据操作(插入数据、修改数据、删除数据)
  5. 下载W ndows桌面,《如何制作WndowsMoble手机的桌面主题.doc
  6. 在ubuntu下使用有道词典
  7. oracle 函数属性 parallel_enable
  8. 安娜堡计算机工程光学专业,美国排名前12的电气电子工程专业研究生院校
  9. 一个生动的例子让你理解Linux的Shell外壳
  10. 运筹与决策(三)求解线性规划、运输问题和0-1整数规划问题