TCC89x的GPIO使用解说

     
最近在调试一块S3C6410的核心板。为了验证核心板扩展槽上的所有接口,LD特地设计了一个扩展底板,上面遍布红红绿绿的LED。软件上用总线读写和GPIO来控制这些灯。观察LED的亮灭,该亮的亮,该灭的灭,说明接口没有问题,否则就需要拿给硬件检查了。

//=====================================================================
//TITLE:
//    TCC89x的GPIO使用解说
//AUTHOR:
//    norains
//DATE:
//    Friday  05-November-2010
//Environment:
//    Windows CE 6.0
//    Telechips TCC8900
//=====================================================================

     
考虑到既有总线读写,又有大量的GPIO控制,写驱动程序会相当麻烦,所以使用了《WinCE6.0中应用程序如何直接访问物理空间》中介绍的方法,直接在测试程序中实现总线访问和GPIO控制。测试程序写的还算顺利,一会儿就搞定了。不过,有些IO的控制有点奇怪,总是莫名其妙的被修改。《ARM-WinCE5.0-寄存器读写工具》中曾提到过GPIO被修改的原因和解决办法。不过,在这里似乎都解释不通。因为现在只有测试应用程序能够修改GPIO,不存在被别的程序修改的可能。难道是代码写的有问题?代码已经简单到不能再简单了,如下。

 

#include “stdafx.h”
#include <windows.h>
#include “s3c6410.h”

   
 当接触一款新的CPU之时,如果想了该这款CPU,那么最简单就是从GPIO开始。对于已经并不是新品的TCC89x系列来说,自然也是如此。那么,现在我们就开始,看看如何操作这个GPIO吧。

typedef struct {
    LPVOID    pvDestMem;
    DWORD    dwPhysAddr;
    DWORD    dwSize;
} VIRTUAL_COPY_EX_DATA,*PVIRTUAL_COPY_EX_DATA;

 

#define IOCTL_VIRTUAL_COPY_EX CTL_CODE (FILE_DEVICE_UNKNOWN,3333,METHOD_BUFFERED,FILE_ANY_ACCESS)

     Telechips为了简化代码,特意定义了一个GPIO的结构体,其定义如下:

LPVOID GetVirtual(DWORD dwPhyBaseAddress, DWORD dwSize)
{
    LPVOID pVirtual;
    VIRTUAL_COPY_EX_DATA vced;

typedef struct _GPIO{ volatile unsigned int GPADAT; // 0x000 R/W
0x00000000 GPA Data Register volatile unsigned int GPAEN; // 0x004 R/W
0x00000000 GPA Output Enable Register volatile unsigned int GPASET; //
0x008 W – OR function on GPA Output Data volatile unsigned int GPACLR;
// 0x00C W – BIC function on GPA Output Data volatile unsigned int
GPAXOR; // 0x010 W – XOR function on GPA Output Data volatile unsigned
int GPACD0; // 0x014 W 0x55555555 Driver strength Control 0 on GPA
Output Data volatile unsigned int GPACD1; // 0x018 W 0x00000000 Driver
strength Control 1 on GPA Output Data volatile unsigned int GPAPD0; //
0x01C W 0x55555555 Pull-Up/Down function on GPA Output Data volatile
unsigned int GPAPD1; // 0x020 W 0x00000000 Pull-Up/Down function on GPA
Output Data volatile unsigned int GPAFN0; // 0x024 W 0x00000000 Port
Configuration on GPA Output Data volatile unsigned int GPAFN1; // 0x028
W 0x00000000 Port Configuration on GPA Output Data volatile unsigned int
GPAFN2; // 0x02C W 0x00000000 Port Configuration on GPA Output Data
volatile unsigned int GPAFN3; // 0x030 W 0x00000000 Port Configuration
on GPA Output Data volatile unsigned int NOTDEFINE0[3]; // 0x034-0x03C
Reserved //略 }GPIO, *PGPIO;

    if(dwPhyBaseAddress & 0xFFF)
    {
        return NULL;
    }

 

    vced.dwPhysAddr = dwPhyBaseAddress>>8;
    pVirtual = VirtualAlloc(0, dwSize, MEM_RESERVE, PAGE_NOACCESS);
    vced.pvDestMem = pVirtual;
    vced.dwSize = dwSize;
    KernelIoControl(IOCTL_VIRTUAL_COPY_EX, &vced, sizeof(vced), NULL, NULL, NULL);

   
 大家都知道,寄存器是32bit,而结构体的每一项恰好也是声明为32bit,这样的好处就是,如果结构体的指针指向GPIO寄存器的初始地址,那么结构体的成员就能够和寄存器一一对应。

    return pVirtual;
}

 

int _tmain(int argc, _TCHAR* argv[])
{
    volatile PS3C6410_GPIO_REG pGPIO1;

   
 现在的问题就是,我们该如何获取GPIO寄存器的初始地址?查看DataSheet,如图:

    pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO, sizeof(S3C6410_GPIO_REG));

威尼斯官方网站登录 1
 

    pGPIO1->GPBCON = 0x22222222;
    printf(“rnpGPIO1->GPBCON(%08x)”, pGPIO1->GPBCON);    
    pGPIO1->GPBCON &= (~(0xFFFF << 16));
    printf(“–>(%08x)”, pGPIO1->GPBCON);
    
    return 0;
}

    
可以得知GPIO寄存器的初始地址为0xF0102000。但这个地址我们不能直接使用,比如下面这句代码就会出错:

      预想的执行结果应该是pGPIO1->GPBCON(02222222)–>(00002222)而实际结果却是pGPIO1->GPBCON(02222222)–>(00000000),不仅仅是高16位被清零了,所有位都被清零!

PGPIO g_pGPIO = reinterpret_cast< PGPIO >(0xF0102000);

     
跟LD一起调试时,愣是没找到原因。回来的路上,忽然想到可能是编译器优化的问题。不过,马上又把它给否了——已经加了volatile啊。难道volatile不起效?不太可能吧?

 

     
到家以后,用IDA工具看了下测试程序的汇编代码,果不其然,跟预想的不一样,代码如下。

   
那么,我们该如何获取这寄存器的地址呢?这时候,就需要借助于Telechips的BSP下面的tcc_allocbaseaddress函数了。该函数传入GPIO的硬件地址,然后返回一个当前进程可使用的已经印射好的虚拟内存地址。使用方式很简单,如:

.text:00011000 wmain                                   ; CODE XREF: mainCRTStartupHelper+8Cp
.text:00011000                                         ; DATA XREF: .pdata:00014000o
.text:00011000
.text:00011000 var_24          = -0x24
.text:00011000 var_20          = -0x20
.text:00011000 var_1C          = -0x1C
.text:00011000 var_18          = -0x18
.text:00011000 var_14          = -0x14
.text:00011000 var_10          = -0x10
.text:00011000
.text:00011000                 STMFD   SP!, {R4,R5,LR}
.text:00011004                 SUB     SP, SP, #0x18
.text:00011008                 MOVL    R3, 0x7F0080
.text:00011010                 STR     R3, [SP,#0x24+var_14]
.text:00011014                 MOV     R3, #1
.text:00011018                 MOV     R2, #0x2000
.text:0001101C                 MOV     R1, #0x940
.text:00011020                 MOV     R0, #0
.text:00011024                 BL      VirtualAlloc
.text:00011028                 MOV     R4, R0
.text:0001102C                 LDR     R0, =0x223414
.text:00011030                 MOV     LR, #0x940
.text:00011034                 MOV     R5, #0
.text:00011038                 MOV     R3, #0
.text:0001103C                 MOV     R2, #0xC
.text:00011040                 ADD     R1, SP, #0x24+var_18
.text:00011044                 STR     R4, [SP,#0x24+var_18]
.text:00011048                 STR     LR, [SP,#威尼斯官方网站登录,0x24+var_10]
.text:0001104C                 STR     R5, [SP,#0x24+var_20]
.text:00011050                 STR     R5, [SP,#0x24+var_24]
.text:00011054                 BL      KernelIoControl
.text:00011058                 STR     R4, [SP,#0x24+var_1C]
.text:0001105C                 LDR     R1, =0x22222222
.text:00011060                 LDR     R3, [SP,#0x24+var_1C]
.text:00011064                 LDR     R2, [SP,#0x24+var_1C]
.text:00011068                 LDR     R0, =aPgpio1Gpbcon08
.text:0001106C                 STR     R1, [R3,#0x20]
.text:00011070                 LDR     R1, [R2,#0x20]
.text:00011074                 BL      printf
.text:00011078                 LDR     R3, [SP,#0x24+var_1C]
.text:0001107C                 LDR     R2, [SP,#0x24+var_1C]
.text:00011080                 LDR     R0, =a08x
.text:00011084                 STRH    R5, [R3,#0x22]
.text:00011088                 LDR     R1, [R2,#0x20]
.text:0001108C                 BL      printf
.text:00011090                 MOV     R0, #0
.text:00011094                 ADD     SP, SP, #0x18
.text:00011098                 LDMFD   SP!, {R4,R5,PC}
.text:00011098 ; End of function wmain

g_pGPIO = (PGPIO)tcc_allocbaseaddress((unsigned int)&HwGPIO_BASE);

      pGPIO1->GPBCON &= (~(0xFFFF <<
16));被优化得无影无踪,仅剩STRH    R5,
[R3,#0x22],而R5此时此刻为0x00000000。原因似乎是找到了,确实是编译优化的问题,但却更让人不解,这里的volatile为什么不起作用?在TCC8901上,一直都这么用,从没出现过这问题。差别在哪里呢?

 

     
后来对比了TCC8901的BSP,有了进一步的发现,它定义GPIO结构体的代码如下。

   
不过这里需要注意的是,tcc_allocbaseaddress只能在驱动函数中使用。如果你不幸地放在了应用程序,那么等待你的依然会是个错误。那么对于应用程序而言,是不是就束手无措,无法操作GPIO了呢?非也,Telechips其实也考虑到了这点。只不过,这时候需要借助于KernelIoControl函数,代码如下所示:

typedef struct _GPIO{
    volatile unsigned int    GPADAT;                 //   0x000  R/W  0x00000000  GPA Data Register 
    volatile unsigned int    GPAEN;                  //   0x004  R/W  0x00000000  GPA Output Enable Register 
    volatile unsigned int    GPASET;                 //   0x008  W  –  OR function on GPA Output Data 
    volatile unsigned int    GPACLR;                 //   0x00C  W  –  BIC function on GPA Output Data 
    volatile unsigned int    GPAXOR;                 //   0x010  W  –  XOR function on GPA Output Data 
    volatile unsigned int    GPACD0;                 //   0x014  W  0x55555555  Driver strength Control 0 on GPA Output Data 
    volatile unsigned int    GPACD1;                 //   0x018  W  0x00000000  Driver strength Control 1 on GPA Output Data 
    volatile unsigned int    GPAPD0;                 //   0x01C  W  0x55555555  Pull-Up/Down function on GPA Output Data 
    volatile unsigned int    GPAPD1;                 //   0x020  W  0x00000000  Pull-Up/Down function on GPA Output Data 
    volatile unsigned int    GPAFN0;                 //   0x024  W  0x00000000  Port Configuration on GPA Output Data 
    volatile unsigned int    GPAFN1;                 //   0x028  W  0x00000000  Port Configuration on GPA Output Data 
    volatile unsigned int    GPAFN2;                 //   0x02C  W  0x00000000  Port Configuration on GPA Output Data 
    volatile unsigned int    GPAFN3;                 //   0x030  W  0x00000000  Port Configuration on GPA Output Data 
    volatile unsigned int    NOTDEFINE0[3];          //     0x034-0x03C     Reserved 
    volatile unsigned int    GPBDAT;                 //   0x040  R/W  0x00000000  GPB Data Register 
    volatile unsigned int    GPBEN;                  //   0x044  R/W  0x00000000  GPB Output Enable Register 
    volatile unsigned int    GPBSET;                 //   0x048  W  –  OR function on GPB Output Data 
    volatile unsigned int    GPBCLR;                 //   0x04C  W  –  BIC function on GPB Output Data 
    volatile unsigned int    GPBXOR;                 //   0x050  W  –  XOR function on GPB Output Data 
    volatile unsigned int    GPBCD0;                 //   0x054  W  0x55555555  Driver strength Control 0 on GPB Output Data 
    volatile unsigned int    GPBCD1;                 //   0x058  W  0x00000000  Driver strength Control 1 on GPB Output Data 
    volatile unsigned int    GPBPD0;                 //   0x05C  W  0x55555555  Pull-Up/Down function on GPB Output Data 
    volatile unsigned int    GPBPD1;                 //   0x060  W  0x00000000  Pull-Up/Down function on GPB Output Data 
    volatile unsigned int    GPBFN0;                 //   0x064  W  0x00000000  Port Configuration on GPB Output Data 
    volatile unsigned int    GPBFN1;                 //   0x068  W  0x00000000  Port Configuration on GPB Output Data 
    volatile unsigned int    GPBFN2;                 //   0x06C  W  0x00000000  Port Configuration on GPB Output Data 
    volatile unsigned int    GPBFN3;                 //   0x070  W  0x00000000  Port Configuration on GPB Output Data 
    volatile unsigned int    NOTDEFINE1[3];          //      0x074-0x07C     Reserved

//申请一段虚拟内存 PVOID pVirtualAddress = ::VirtualAlloc( 0, dwSize,
MEM_RESERVE, PAGE_READWRITE); //初始化传入的形参 UidMem uidMem;
uidMem.dwHwBaseAddress = dwPhysicalAddress >> 8;
uidMem.dwAllocBaseAddress = (DWORD)pVirtualAddress; uidMem.dwSize =
dwSize;
//通过KernelIoControl和底层通信,让虚拟内存映射到传入形参中设定的物理地址
::KernelIoControl(IOCTL_HAL_VIRTUALCOPY, &uidMem, sizeof(uidMem),
NULL, NULL, NULL); // GPIO结构指针指向映射完毕的虚拟内存。 PGPIO
g_pGPIO = (PGPIO) pVirtualAddress;

      而S3C6410定义GPIO的结构体的代码如下。

 

typedef struct
{
    UINT32 GPACON;        // 000
    UINT32 GPADAT;        // 004
    UINT32 GPAPUD;        // 008
    UINT32 GPACONSLP;    // 00c

 
     初始地址获取完毕之后,我们就可以对成员变量进行操作了。

    UINT32 GPAPUDSLP;    // 010
    UINT32 PAD1[3];        // 014~01f

//将GPIOD9设为为GPIO功能 BITCLR(g_pGPIO->GPDFN1,Hw4|Hw5|Hw6|Hw7);
//设置为输出模式 BITSET(g_pGPIO->GPDEN,Hw9); //输出为HIGH
BITSET(g_pGPIO->GPDSET,Hw9);

    UINT32 GPBCON;        // 020
    UINT32 GPBDAT;        // 024
    UINT32 GPBPUD;        // 028
    UINT32 GPBCONSLP;    // 02c

 

    UINT32 GPBPUDSLP;    // 030
    UINT32 PAD2[3];        // 034~03f

     
可以看到,在TCC8901的结构体定义中都有volatile,而S3C6410中是没有的。也许这就是原因,结构体内部必须有volatile才行?定义结构体指针变量时的volatile作用域有限,并没有起到预想的作用——阻止编译器的优化。后来测试发现,如果结构体内部有volatile,定义结构体指针变量时省去volatile都行。示例代码如下,执行结果也是正确的(前提:在结构体定义中UINT32 GPBCON前增加了volatile!!!)。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章