网站首页  词典首页

请输入您要查询的论文:

 

标题 Windows下I2C总线的GPIO模拟
范文

    邹应双

    

    

    摘要:简要介绍了I2C总线操作和基于Windows内核模式驱动的用户态I/O端口访问,分析了Windows平台下GPIO管脚模拟I2C总线的可行性,讲解了编程实现过程,连接I2C接口的安全芯片进行了验证。

    关键词:I2C总线;GPIO管脚;Windows;内核模式驱动

    中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2017)01-0100-04

    Abstract: I2C bus operations and user-mode I/O port access using Windows kernel mode driver are briefly introduced in this paper. And a feasibility analysis on simulating I2C bus with GPIO pins under Windows is made. Then we do a programming impelmentation to the simulating method, and verify the software by accessing an I2C-interfaced security chip.

    Key words: I2C bus; GPIO pins; Windows; kernel mode driver

    1 背景

    I2C總线是Philips公司推出的两线式串行总线,用于嵌入式系统连接各种低速外围设备,如RTC、EEPROM、传感器、安全芯片等。许多单片机、嵌入式芯片等都带有I2C主控器,其采用的操作系统如嵌入式Linux等均带有I2C驱动程序,编程中可直接使用。对于不含I2C主控器的芯片,为了满足定制系统设计需求,一般也有大量的GPIO管脚,可用于软件模拟I2C总线。

    Intel公司的X86系列CPU和配套的桥接芯片,除了用于桌面PC,还广泛用于网关、收银、视频等各种嵌入式服务器。这类服务器一般采用Windows Server操作系统,不带有I2C主控器或不提供I2C驱动程序。为了实现软件授权和保护,这类嵌入式服务器首先会选择管脚少、性价比高的I2C接口的安全芯片。为了满足这种需求,一种方法是采用USB转I2C的专用芯片,但这将增加硬件成本和软件复杂度。

    基于I2C总线的简单性,可选用桥接芯片的GPIO管脚来模拟I2C主控器。GPIO管脚通过X86的I/O指令即可完全控制;但普通应用程序在Windows下无法直接访问I/O端口,可通过内核模式驱动程序来实现。在Windows平台下通过GPIO管脚模拟I2C总线,将是一种简单有效、低成本的解决方法。本文就这个模拟过程进行探讨。

    2 I2C总线的读写

    I2C总线通过时钟和数据两根线即可实现完善的同步数据传输。当发送数据时,一个设备作为主机,另一个设备作为从机。主设备为数据传输产生时钟信号。I2C通讯协议要求在时钟线(SCL, Serial Clock Line)处于低电平时,数据线(SDA, Serial Data Line)才能变化。协议中每个从设备都有一个地址,会一直监视总线上的主设备要初始化数据传输时发出的地址并匹配。

    总线的工作流程如下:

    空闲:当总线上没有数据通讯发生时,SCL和SDA通过上拉电阻呈高电平。

    开始:SCL为高时,SDA由高变低,这时数据传输开始。

    地址:主设备发送地址信息,包含7位的从设备地址和1位的读写位(表明数据流的方向)。发送完一个字节后,从设备会发送一位的认可位(ACK)。

    数据:根据方向位,数据在主设备和从设备之间传输。数据一般以8位传输,高位在前。接收器上用一位的ACK表明一个字节收到了。传输可被终止或重新开始。

    停止:当SCL为高时,SDA由低变高,这时数据传输结束,总线重新进入空闲状态。

    一次完整的数据传输时序如图1所示。

    标准I2C总线的传输速率是100KHz,通过线与逻辑实现慢速设备等待。I2C总线的这些特性允许主设备的功能通过两个GPIO管脚模拟而实现。

    3 Windows下的I/O端口访问

    端口I/O指令允许X86 CPU与系统中的其他硬件设备通信。对于硬件设备的低层次直接控制,C函数_inp()和_outp()(用X86处理器的IN和OUT指令实现)允许从端口读入或向一个端口写。但在Windows应用程序中插入_inp()或者_outp(),将导致特权指令异常消息,并给出终止或调试出错应用程序的选择。Windows的体系结构决定了应用程序不能直接通过IN和OUT指令访问硬件。否则,应用程序可以关闭中断、破坏显示或驱动器等硬件设备,危及系统的稳定性。所以,通过内核模式驱动程序间接访问I/O端口是Windows下访问硬件资源的唯一途径。

    实现对I2C主控器的模拟,只需要简单的I/O访问即可实现。如果编写完整的内核模式的I2C驱动程序,将涉及复杂的、花费大量时间的Windows内核模式驱动驱动程序的开发和调试工作。编写最简驱动实现I/O端口访问,封装好用户态访问的接口,将I2C实现代码放在用户态,将极大地简化开发工作,同时增加二次开发利用的灵活性。

    这样通过内核模式驱动程序实现I/O访问的副作用是每一次I/O操作都要通过Windows的I/O子系统发送请求,需要花费数千个时钟周期。但这个时间成本和100KHz的慢速I2C的一个位周期相当,对于数据传输量不大的应用,在性能上可接受。

    4 编程实现

    本文的目标硬件平台为Intel Core i3-4330 CPU、Intel DH82H81桥接芯片、Maxim DS28C22安全芯片。桥片和安全芯片的连接如图2所示。

    本文的目標软件平台是64位的Windows Server 2008,开发平台是Windows 10 专业版,选用WDK(Windows Driver Kit) 7.1和Visual Studio 2015专业版。通过查询方式实现I2C读写,驱动层提供I/O端口访问功能,pioctl.dll库封装驱动成类似于IN/OUT指令的接口,i2c.dll实现I2C读写函数,提供给上层做应用开发。软件层次结构如图3所示。

    下面按自底向上的顺序简单介绍各层次的实现源码。

    4.1 内核模式驱动

    和Linux驱动的开发相比,Windows驱动开发的门槛要高一些,首先需安装WDK,了解其中的核心态函数,熟悉WDM、WDF等驱动程序框架。

    WDK中提供了大量的样例驱动供驱动开发者参考。考虑到我们的驱动只需提供X86的IN和OUT指令的访问接口,特选择WDK样例中源码最简单的src/general/ioctl/wdm为基础,命名为pioctl,并对驱动源码中的函数名等做适当重命名,添加上I/O端口访问的代码,即实现了本驱动。这个开发过程不需要对Windows驱动开发有较深入的了解。

    本驱动程序的驱动加载和卸载、设备打开和关闭等例程无新加代码,不是本文的重点,下面仅对I/O端口操作相关的代码做说明。

    NTSTATUS PioctlDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)

    {

    PIO_STACK_LOCATION irpSp;

    NTSTATUS ntStatus = STATUS_SUCCESS;

    ULONG inBufLength, outBufLength;

    ULONG dataBufSize;

    PULONG pIOBuffer;

    ULONG nPort;

    irpSp = IoGetCurrentIrpStackLocation(Irp);

    inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;

    outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

    switch (irpSp->Parameters.DeviceIoControl.IoControlCode)

    { // 检查用户态参数

    case IOCTL_PIOCTL_WRITE_PORT_ULONG:

    dataBufSize = sizeof(ULONG);

    if (inBufLength < (sizeof(ULONG) + dataBufSize)) {

    ntStatus = STATUS_INVALID_PARAMETER;

    goto End;

    }

    break;

    case IOCTL_PIOCTL_READ_PORT_ULONG:

    dataBufSize = sizeof(ULONG);

    if (inBufLength != sizeof(ULONG) || outBufLength < dataBufSize) {

    ntStatus = STATUS_INVALID_PARAMETER;

    goto End;

    }

    break;

    default:

    ntStatus = STATUS_INVALID_PARAMETER;

    goto End;

    }

    pIOBuffer = (PULONG)Irp->AssociatedIrp.SystemBuffer;

    nPort = *pIOBuffer; // I/O端口号

    switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )

    { // 判定I/O控制码

    case IOCTL_PIOCTL_READ_PORT_ULONG: // IND

    *(PULONG)pIOBuffer = READ_PORT_ULONG((PULONG)((ULONG_PTR)nPort));

    pIrp->IoStatus.Information = dataBufSize; // 读取的字节数

    break;

    case IOCTL_PIOCTL_WRITE_PORT_ULONG: // OUTD

    pIOBuffer++;

    WRITE_PORT_ULONG((PULONG)((ULONG_PTR)nPort), *(PULONG)pIOBuffer);

    Irp->IoStatus.Information = dataBufSize; // 写的字节数

    break;

    default:

    Irp->IoStatus.Information = 0;

    ntStatus = STATUS_INVALID_DEVICE_REQUEST;

    break;

    }

    End:

    Irp->IoStatus.Status = ntStatus;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return ntStatus;

    }

    4.2 用户态I/O端口读写接口

    为了应用程序的开发方便,实现了pioctl.dll库,以负责自动动态加载卸载驱动程序、提供I/O端口的用户态访问接口。

    1)DLL入口函数

    BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID)

    {

    switch (ul_reason_for_call)

    {

    case DLL_PROCESS_ATTACH:

    if (pioctl_init() < 0) // 利用SCM函数加载驱动; 打开设备文件

    return FALSE;

    break;

    case DLL_PROCESS_DETACH:

    pioctl_deinit(); // 关闭设备文件; 利用SCM函数卸载驱动

    break;

    }

    return TRUE;

    }

    2)端口访问函数

    PIOCTL_API unsigned long pioctl_inpd(unsigned short port)

    {

    ULONG PortNumber = (ULONG)port;

    ULONG Data;

    ULONG bytesReturned;

    BOOL bRc;

    bRc = DeviceIoControl(s_hDevice, (DWORD)IOCTL_PIOCTL_READ_PORT_ULONG,

    &PortNumber, sizeof(PortNumber), &Data, sizeof(Data), &bytesReturned, NULL);

    if (!bRc) {

    fprintf(stderr, "Error in DeviceIoControl : %d\n", GetLastError());

    return -1;

    }

    return Data;

    }

    pioctl_outpd()类似,不再列举出。

    4.3 I2C读写函数

    本文采用查询方式实现I2C读写函数。硬件上用DH82H81的GPIO8模拟SCL、GPIO15模拟SDA,封装成i2c.dll库。这两个管脚为专用GPIO脚,通过GP_IO_SEL和GP_LVL两个端口即可控制其I/O方向和电平值。下面以I2C操作中的启动和发送字节为例,讲解其实现。其他操作的实现过程类似,不再赘述。

    1)初始化函数

    #define SCL GPIO8

    #define SDA GPIO15

    void i2c_init(void)

    {

    gpioDir = pioctl_inpd(GP_IO_SEL) & ~((1 << SCL) | (1 << SDA)); // 0:OUTPUT

    pioctl_outpd(GP_IO_SEL, gpioDir); // 设置GPIOs的I/O方向

    gpioVal = pioctl_inpd(GP_LVL) | ((1 << SCL) | (1 << SDA));

    pioctl_outpd(GP_LVL, gpioVal); // 设置GPIOs的电平

    }

    2)端口的位操作函数

    int gpio_in(int gpio_num)

    { // 读取GPIOs输入脚

    if ((gpioDir & (1<

    gpioDir |= (1<

    pioctl_outpd(GP_IO_SEL, gpioDir);

    }

    return (pioctl_inpd(GP_LVL) & (1<

    }

    void gpio_out(int gpio_num, int level)

    { // 写GPIOs输出脚

    if ( gpioDir & (1<

    gpioDir &= ~(1<

    pioctl_outpd(GP_IO_SEL, gpioDir);

    }

    gpioVal = pioctl_inpd(GP_LVL) & ~(1<

    if (level)

    gpioVal |= 1<

    pioctl_outpd(GP_LVL, gpioVal); // 设置GPIO的电平

    }

    3)I2C读寫函数

    #define i2c_scl(level) gpio_out(SCL, level)

    #define i2c_sda(level) gpio_out(SDA, level)

    

    

    

    

    

    #define i2c_sda_in() gpio_in(SDA)

    void i2c_start(void)

    { // 当SCL为高电平时,SDA发生由高到低的跳变

    i2c_scl(1);

    i2c_sda(0);

    i2c_scl(0);

    }

    int i2c_send_data(unsigned char octet)

    { // 发送一个字节

    int i, ack;

    for(i=0x80; i>0; i>>=1) {

    i2c_sda(octet & i ? 1 : 0);

    i2c_scl(1);

    i2c_scl(0);

    }

    i2c_sda(1); // 发送器件释放SDA线

    i2c_scl(1);

    ack = i2c_sda_in(); // 讀取低电平有效的ACK位

    scl(0); // 实现了了ACK

    return (ack); // 返回ACK位

    }

    4.4 驱动加载和调试

    由于目标软件平台为64位系统,pioctl.sys驱动相应编译成64位,需要禁用Windows Server 2008的数字签名,编译的驱动才能加载。

    在pioctl.sys驱动中通过KdPrint()宏输出调试信息,通过WinDbg工具捕获调试信息,以和printf函数类似的方式调试代码。

    5 结束语

    本文基于GPIO管脚的I2C操作模拟方法,在图2所示的采用Windows Server 2008的目标平台上做测试,实现了和D28C22安全芯片的可靠通信,验证了本方法的正确性。该方法的原理可供类似的采用Intel X86方案的产品设计参考,以有效节省采用转接芯片的成本、降低软件开发的难度。

    这种用户态I/O端口访问的硬件模拟,花费较高的CPU时间成本,对于高速的简单I/O设备访问,有较大的局限性。但基于本方法,可在用户态将硬件操作代码调试好,再直接封装到内核模式驱动程序中,将极大地降低开发特定Windows设备驱动的难度。

    参考文献:

    [1] 田磊, 宋圆方. 基于Windows CE的IIC设备驱动的实现[J]. 西安邮电学院学报, 2008(1): 126-128.

    [2] 蔡纯洁, 邢武. PIC 16/17 单片机原理与实现[M]. 合肥: 中国科学技术大学出版社, 1997.

    [3] 保拉?汤姆林森. Windows NT/2000编程实践[M]. 北京: 中国电力出版社, 2001.

    [4] 张佩, 马勇, 董鉴源. 竹林蹊径——深入浅出Windows驱动开发[M]. 北京: 电子工业出版社, 2011.

随便看

 

科学优质学术资源、百科知识分享平台,免费提供知识科普、生活经验分享、中外学术论文、各类范文、学术文献、教学资料、学术期刊、会议、报纸、杂志、工具书等各类资源检索、在线阅读和软件app下载服务。

 

Copyright © 2004-2023 puapp.net All Rights Reserved
更新时间:2025/2/6 1:08:38