最近在调试一块S3C6410的核心板。为了验证核心板扩展槽上的所有接口,LD特地设计了一个扩展底板,上面遍布红红绿绿的LED。软件上用总线读写和GPIO来控制这些灯。观察LED的亮灭,该亮的亮,该灭的灭,说明接口没有问题,否则就需要拿给硬件检查了。
考虑到既有总线读写,又有大量的GPIO控制,写驱动程序会相当麻烦,所以使用了《WinCE6.0中应用程序如何直接访问物理空间》中介绍的方法,直接在测试程序中实现总线访问和GPIO控制。测试程序写的还算顺利,一会儿就搞定了。不过,有些IO的控制有点奇怪,总是莫名其妙的被修改。《ARM-WinCE5.0-寄存器读写工具》中曾提到过GPIO被修改的原因和解决办法。不过,在这里似乎都解释不通。因为现在只有测试应用程序能够修改GPIO,不存在被别的程序修改的可能。难道是代码写的有问题?代码已经简单到不能再简单了,如下。
#include
"
stdafx.h
"
#include <windows.h>
#include
"
s3c6410.h
"
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)
LPVOID GetVirtual(DWORD dwPhyBaseAddress, DWORD dwSize)
{
LPVOID pVirtual;
VIRTUAL_COPY_EX_DATA vced;
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);
return pVirtual;
}
int _tmain(
int argc, _TCHAR* argv[])
{
volatile
PS3C6410_GPIO_REG pGPIO1;
pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,
sizeof(S3C6410_GPIO_REG));
pGPIO1->GPBCON =
0x22222222;
printf(
"
\r\npGPIO1->GPBCON(%08x)
", pGPIO1->GPBCON);
pGPIO1->GPBCON &= (~(
0xFFFF <<
16));
printf(
"
-->(%08x)
", pGPIO1->GPBCON);
return
0;
}
预想的执行结果应该是pGPIO1->GPBCON(02222222)-->(00002222)而实际结果却是pGPIO1->GPBCON(02222222)-->(00000000),不仅仅是高16位被清零了,所有位都被清零!
跟LD一起调试时,愣是没找到原因。回来的路上,忽然想到可能是编译器优化的问题。不过,马上又把它给否了——已经加了volatile啊。难道volatile不起效?不太可能吧?
到家以后,用IDA工具看了下测试程序的汇编代码,果不其然,跟预想的不一样,代码如下。
.
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
pGPIO1->GPBCON &= (~(0xFFFF << 16));被优化得无影无踪,仅剩STRH R5, [R3,#0x22],而R5此时此刻为0x00000000。原因似乎是找到了,确实是编译优化的问题,但却更让人不解,这里的volatile为什么不起作用?在TCC8901上,一直都这么用,从没出现过这问题。差别在哪里呢?
后来对比了TCC8901的BSP,有了进一步的发现,它定义GPIO结构体的代码如下。
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
而S3C6410定义GPIO的结构体的代码如下。
typedef
struct
{
UINT32 GPACON;
//
000
UINT32 GPADAT;
//
004
UINT32 GPAPUD;
//
008
UINT32 GPACONSLP;
//
00c
UINT32 GPAPUDSLP;
//
010
UINT32 PAD1[
3];
//
014~01f
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!!!)。
int _tmain(
int argc, _TCHAR* argv[])
{
PS3C6410_GPIO_REG pGPIO1;
pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,
sizeof(S3C6410_GPIO_REG));
pGPIO1->GPBCON =
0x22222222;
printf(
"
\r\npGPIO1->GPBCON(%08x)
", pGPIO1->GPBCON);
pGPIO1->GPBCON &= (~(
0xFFFF <<
16));
printf(
"
-->(%08x)
", pGPIO1->GPBCON);
return
0;
}
问题的原因似乎更明朗了。但好似还是有点问题。因为在结构体中添加volatile前,下面这段代码的执行也是正确的。
int _tmain(
int argc, _TCHAR* argv[])
{
volatile
S3C6410_GPIO_REG* pGPIO1;
pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,
sizeof(S3C6410_GPIO_REG));
pGPIO1->GPBCON =
0x22222222;
printf(
"
\r\npGPIO1->GPBCON(%08x)
", pGPIO1->GPBCON);
pGPIO1->GPBCON &= (~(
0xFFFF <<
16));
printf(
"
-->(%08x)
", pGPIO1->GPBCON);
return
0;
}
可以看到,与问题代码唯一的不同就是"volatile PS3C6410_GPIO_REG pGPIO1;”变成"volatile S3C6410_GPIO_REG* pGPIO1; ” ,"PS3C6410_GPIO_REG”和"S3C6410_GPIO_REG*”不是等价的吗?通常更习惯用“PS3C6410_GPIO_REG”,这也有问题么。
问题还没有结束,在结构体中添加volatile前,下面这段代码也能正确运行。
int _tmain(
int argc, _TCHAR* argv[])
{
volatile PS3C6410_GPIO_REG pGPIO1;
pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,
sizeof(S3C6410_GPIO_REG));
pGPIO1->GPBCON =
0x22222222;
printf(
"
\r\npGPIO1->GPBCON(%08x)
", pGPIO1->GPBCON);
pGPIO1->GPBCON = pGPIO1->GPBCON & (~(
0xFFFF
<<
16
));
printf(
"
-->(%08x)
", pGPIO1->GPBCON);
return
0;
}
这段代码与问题代码的唯一区别pGPIO1->GPBCON &= (~(0xFFFF << 16));变成pGPIO1->GPBCON = pGPIO1->GPBCON & (~(0xFFFF << 16));
难道这也有区别?那为什么会有这个区别呢?
纳闷…纠结…晕…
看来现在还不能给这个问题做一个全面的总结,只有以下几点教训:
用volatile时得多留些心眼,有时用了volatile也不一定管用;
对于MCU专用寄存器结构体的定义,最好内部加上volatile;
在定义寄存器结体指针变量时,尽量使用形如S3C6410_GPIO_REG*的方式,慎用PS3C6410_GPIO_REG;
在对MCU寄存器进行位运算时,尽量使用 a = a & b;的方式,慎用a &=b;
WinCE6.0中应用程序直接访问物理空间有隐患,谨慎使用;
VS2005的编译器优化很强大,必须小心优化过度,但不也能因噎废食;
只有弄清楚优化规则的来龙去脉,才能避免写出被编译器优化得面目全非的代码;
PS:疑惑还没有彻底解决,有知道的TX请明示,感兴趣的TX也请留言讨论,看晕了的TX请多包涵。祝各位节日快乐!