0

关于投票
D12的USB C51驱动程序开发

浅入深开展应用D12的USB C51驱动程序开发

USB设备启动流程:

1.USB设备接入USB口,发出连接USB命令
2.
主机发出读设备描述符两次。

3.
主机根据设备描述符——厂商ID、产品ID,启动相应设备驱动程序。

4.
设备驱动程序初始化USB设备:

  a.读设备描述符

  b.读配置描述符

  c.选择接口、端点(管道),确定传输方式

D12固件驱动:

//指向外部D12访问地址
#define D12_COMMAND (*(unsigned char xdata *)0x8000)
#define D12_DATA (*(unsigned char xdata *)0x0000)

D12_COMMAND = 0xf3;D12_DATA = 0x0e;D12_DATA = 0x03;//初始化频率 12MHz
D12_COMMAND = 0xd0;D12_DATA = 0x80;//
设置地址 0 使能

D12_COMMAND = 0xf3;D12_DATA = 0x1e;//
连接主机

D12_COMMAND = 0xf4;//
读中断寄存器

if(D12_DATA & 0x01)//
收到 SETUP

{
D12_COMMAND = 0x40;//
OUT 最后状态

if(D12_DATA & 0x20)
{
SETUP_read();
HandleSetup();
}
}
D12缓冲区

unsigned char SetupBuf[8];

void SETUP_read(void)
{
    unsigned char i;
    unsigned char * j;
    j = SetupBuf;
    D12_COMMAND = 0xf0;//
读标准控制码

    *j = D12_DATA;
    *(j+1) = D12_DATA;
    for(i=0;i<8;i++)
    {
       *(j + i) = D12_DATA;
    }
    D12_COMMAND = 0xf1;//
应答SETUP,使能( OUT 缓冲区、使能 IN 缓冲区)命令

    D12_COMMAND = 0xf2;//
OUT 缓冲区

}

D12缓冲区

void USB_submit(void)
{
    unsigned char i;
    D12_COMMAND = 0xf0;//
写缓冲区

    D12_DATA = 0x00;
    D12_DATA = XmtBuff.bLength;

    for(i=0;i<16;i++)
    {
        D12_DATA = *(XmtBuff.p++);
        if (!--XmtBuff.bLength)
        {
            break;
        }
    }
    D12_COMMAND = 0xfa;//
设置 IN 缓冲区有效

}


 
先介绍一下D12个管脚功能

1. D12的命令/数据切换有两种方式
 a. ALE 接地,由A0进行命令/数据切换,A0 1 时可以向D12写命令,A0 0 时可以向D12写数据。这时可以将A0P2口一个管脚(这里是A0P27),通过读写外部数据时,发出的地址来将A0 1/ 0

 b. A0 接高电平,由ALE进行命令/数据切换,将D12ALE51ALE(D12CS_N进行片选),当读写外部数据时,P0口发出奇数地址时,可向D12写入一个命令;P0口发出偶数地址时,可向D12读写数据。

2. D12SUSPEND
  当不能对D12操作时,不要忘了检查SUSPEND状态。如果SUSPEND为高表示D12已挂起,可将其置低,并发出恢复命令,唤醒主机。

3. D12INT_N
  可以说INT_N总是有效(由中断模式设置有关),一般D12挂起和忙时无效。

4. D12DMA传输有关管脚
  DMREQ 51发出DMA请求,51可以DMA要求读写数据

  DMACK_N 51D12确认DMA操作,D12完成DMA操作

  EOT_N 51D12发出结束DMA操作,同时要求DMACK_N置低,并发出读或写的动作。

引脚参数定义:

sbit D12_suspend=P1^0;
sbit D12_int_n=P1^1;
sbit D12_eot_n=P1^2;
sbit D12_DMAck_n=P1^3;
sbit D12_DMAreq=P1^4;


面对主机USB设备首先要处理的是主机标准控制请求。

下面代码可作为开发USB设备面对主机的模板。

[1] 处理主机标准控制请求:

unsigned char ENDPOINT_A0_FIFO[8]
//
判断输入的是SETUP请求,并将其读入缓冲区ENDPOINT_A0_FIFO

...
if((ENDPOINT_A0_FIFO[0] & 0b01100000)==0x00)
{
if (ENDPOINT_A0_FIFO[1] <= 0x0C)
{
(*StandardFunctionTable[ENDPOINT_A0_FIFO[1]])();
return;
}
}
...

const void (* StandardFunctionTable[])(void)=
{
GetStatus,ClearFeature,USB_Reserved,SetFeature,
USB_Reserved,SetAddress,GetDescriptor,SetDescriptor,
GetConfiguration,SetConfiguration,GetInterface,
SetInterface,SynchFrame
};

void GetStatus(void)
{
switch (ENDPOINT_A0_FIFO[0])
   {
case (0b10000000)://
返回设备状态

         //
发送两个字节数据:第一字节D1 1支持远程唤醒、为0不支持远程唤醒,D0 1设备自己供电、为0 USB总线供电,其它位为0;第二字节为0

break;
   case (0b100000001)://
返回接口状态

//
发送两个字节数据:第一字节为0;第二字节为0

break;
   case (0b10000010)://
返回端点状态

//
发送两个字节数据:第一字节D01端点处于暂停,否则D00,其它位为0;第二字节为0

break;
   }
}

void ClearFeature(void)
{
    if ((ENDPOINT_A0_FIFO[0] ==0b00000000)
        && (ENDPOINT_A0_FIFO[2] == 1) &&
           ( !ENDPOINT_A0_FIFO[3]))
        {
            //
清除设备远程唤醒功能

            return;
        }
    if ((ENDPOINT_A0_FIFO[0] ==0b00000001)
        && (ENDPOINT_A0_FIFO[2] == 0) &&
           ( !ENDPOINT_A0_FIFO[3]))
        {
            //
清除设备接口特殊功能 ENDPOINT_A0_FIFO[4] 为接口号

            return;
        }
    if ((ENDPOINT_A0_FIFO[0] == 0b00000010)
&& (ENDPOINT_A0_FIFO[2] == 0) &&
           (!ENDPOINT_A0_FIFO[3])) 
        {
            //
清除设备端点暂停功能 ENDPOINT_A0_FIFO[4] D7为端点方向,D3~D0为端点号

      return;
       }
}

void USB_Reserved(void)
{
//
只发确认信息。

}

void SetFeature(void)
{
if ((ENDPOINT_A0_FIFO[0] ==0b00000000)
&& (ENDPOINT_A0_FIFO[2] == 1) &&
( !ENDPOINT_A0_FIFO[3]))
{
//
设置设备远程唤醒功能

return;
}
if ((ENDPOINT_A0_FIFO[0] ==0b00000001)
&& (ENDPOINT_A0_FIFO[2] == 0) &&
( !ENDPOINT_A0_FIFO[3]))
{
//
设置设备接口特殊功能 ENDPOINT_A0_FIFO[4] 为接口号

return;
}
if ((ENDPOINT_A0_FIFO[0] == 0b00000010)
&& (ENDPOINT_A0_FIFO[2] == 0) &&
(!ENDPOINT_A0_FIFO[3]))
{
//
设置设备端点暂停 ENDPOINT_A0_FIFO[4] D7为端点方向,D3~D0为端点号

return;
}
}void SetAddress(void)
{
    if (ENDPOINT_A0_FIFO[0] == 0b00000000)
    {
        //
保存USB地址
0x80 | ENDPOINT_A0_FIFO[2];
    }
}
/*
注:

    SetAddress
请求实际可分成三个阶段。第一阶段,Setup包被送至设备,第二个可有无的阶段,数据在设备与主机之间传送,第三阶段,状态信息在主机与设备之间传送。

   
数据与状态传送的方向要看是主机发数据给设备还是设备发数据给主机。状态的传送方向总是与数据传送方向是相反的,如果没有数据传输阶段则状态由设备传向主机的。

    Setup
包传送以后的两个阶段的地址保持与Setup包传送阶段一致。USB设备只有在Status阶段过后才能改变设备地址。

*/

void GetDescriptor(void)
{
     if ( (ENDPOINT_A0_FIFO[0] != 0b10000000 DEVICE)) && 
         (ENDPOINT_A0_FIFO[0] != 0b10000001  INTERFACE)) &&
         (ENDPOINT_A0_FIFO[0] != 0b10000010  ENDPOINT)))        
    {
            return;
    }
    switch (ENDPOINT_A0_FIFO[3]) 
    {
        case 1 :
            //
发送设备描述表。 发送数据
<= ENDPOINT_A0_FIFO[6,7]
            break;
        case 2 :
            //
发送配置 [,接口(1),端点(1),接口(2),端点(2),...,,厂商等] 描述表

            break;
        case 3 :
            switch(ENDPOINT_A0_FIFO[2])
            {
            case 0x00 :
                //
发送字符串0描述表

                break;
            case 0x01 :
                //
发送字符串1描述表

               break;
            default   :
                return;
            }
            break;
        case 4 :
            //
发送接口描述表

            break;
        case 5 :
            //
发送端点描述表

            break;
    }
}

//设备描述表
const char USB_DEVICE_DEscriptOR[]=

  UCHAR bLength 
  UCHAR bDescriptorType 
  USHORT bcdUSB 
  UCHAR bDeviceClass 
  UCHAR bDeviceSubClass 
  UCHAR bDeviceProtocol 
  UCHAR bMaxPacketSize0 
  USHORT idVendor 
  USHORT idProduct 
  USHORT bcdDevice 
  UCHAR iManufacturer 
  UCHAR iProduct 
  UCHAR iSerialNumber 
  UCHAR bNumConfigurations 
};

//配置描述表
const char USB_CONFIGURATION_DEscriptOR[]=

  UCHAR bLength 
  UCHAR bDescriptorType 
  USHORT wTotalLength 
  UCHAR bNumInterfaces 
  .
  .
  UCHAR iConfiguration 
  UCHAR bmAttributes 
  UCHAR MaxPower 
};
//
接口描述表

const char USB_INTERFACE_DEscriptOR[]=

  UCHAR bLength 
  UCHAR bDescriptorType 
  UCHAR bInterfaceNumber 
  UCHAR bAlternateSetting 
  UCHAR bNumEndpoints 
  UCHAR bInterfaceClass 
  UCHAR bInterfaceSubClass 
  UCHAR bInterfaceProtocol 
  UCHAR iInterface 
};

//端点描述表
const char USB_ENDPOINT_DEscriptOR[]=

  UCHAR bLength 
  UCHAR bDescriptorType 
  UCHAR bEndpointAddress 
  UCHAR bmAttributes 
  USHORT wMaxPacketSize 
  UCHAR bInterval 
};

//字符串0描述表
const char USB_STRING_DEscriptOR[]=
{
  UCHAR bLength 
  UCHAR bDescriptorType 
  WCHAR bLangID[1] 
..
}

//字符串1描述表
const char USB_STRING_DEscriptOR[]=
{
  UCHAR bLength 
  UCHAR bDescriptorType 
  WCHAR bString[1] 
}


在发送配置 [,接口(1),端点(1),接口(2),端点(2),...,,厂商等] 联合描述表时,各描述表的先后顺序可随意,主机USBD根据描述表类型标识区分各种分描述表。

描述表类型:

USB_DEVICE_DEscriptOR_TYPE                0x01
USB_CONFIGURATION_DEscriptOR_TYPE        0x02
USB_STRING_DEscriptOR_TYPE                0x03
USB_INTERFACE_DEscriptOR_TYPE            0x04
USB_ENDPOINT_DEscriptOR_TYPE              0x05


//
标准设备描述表
const char USB_DEVICE_DEscriptOR[]=

  0x12, //
描述表长18字节

  0x01, //
设备描述表类型

  0x10,0x01, //
此设备与描述表兼容的USB设备说明版本号(BCD
)
  0x00, //
设备类码

  0x00, //
子类码

  0x00, //
协议码

  0x08, //
端点0的最大包大小(8,16,32,64为合法值
)
  0xb4,0x04,//
厂商ID(USB标准付值
)
  0x70,0x63,//
产品ID(由厂商付值
)
  0x01,0x00,//
设备发行号(BCD
)
  0x01,//
描述厂商信息的字串描述表索引

  0x02,//
描述产品信息的字串描述表索引

  0x00,//
描述设备序列号信息的字串描述表索引(不支持设为
0)
  0x01,//
可能的设置描述表数

};

USB设备类码(UNKNOWN设为0):

USB_DEVICE_CLASS_RESERVED           0x00
USB_DEVICE_CLASS_AUDIO              0x01
USB_DEVICE_CLASS_COMMUNICATIONS     0x02
USB_DEVICE_CLASS_HUMAN_INTERFACE    0x03
USB_DEVICE_CLASS_MONITOR            0x04
USB_DEVICE_CLASS_PHYSICAL_INTERFACE 0x05
USB_DEVICE_CLASS_POWER              0x06
USB_DEVICE_CLASS_PRINTER            0x07
USB_DEVICE_CLASS_STORAGE            0x08
USB_DEVICE_CLASS_HUB                0x09
USB_DEVICE_CLASS_VENDOR_SPECIFIC    0xFF

系统分类: 接口电路
用户分类: 驱动开发
标签: 无标签
来源: 转贴
发表评论 阅读全文(206) | 回复(0)

0

关于投票
volatile关键字
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用该关键字的例子如下:
int volatile nVint;

  当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

例如:

volatile int i="10";
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作

int b = i;
  volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
系统分类: 嵌入式
用户分类: 技术类
标签: 无标签
来源: 转贴
发表评论 阅读全文(115) | 回复(0)

0

关于投票
[转]Unicode编码规范
[转]Unicode编码规范
 
 

先从ASCII说起。ASCII是用来表示英文字符的一种编码规范,每个ASCII

字符占用1个字节(8bits)

因此,ASCII编码可以表示的最大字符数是256,其实英文字符并没有那么多,一般只用前128个(最高位为0),其中包括了控制字符、数字、大小写字母和其他一些符号 。

而最高位为1的另128个字符被成为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其他符号

这种字符编码规范显然用来处理英文没有什么问题 。(实际上也可以用来处理法文、德文等一些其他的西欧字符,但是不能和英文通用),但是面对中文、阿拉伯文之类复杂的文字,255个字符显然不够用

于是,各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312-80”,它是和ASCII兼容的一种编码规范,其实就是利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。

但是这个方法有问题,最大的问题就是,中文文字没有真正属于自己的编码,因为扩展ASCII码虽然没有真正的标准化,但是PC里的ASCII码还是有一个事实标准的(存放着英文制表符),所以很多软件利用这些符号来画表格。这样的软件用到中文系统中,这些表格符就会被误认作中文字,破坏版面。而且,统计中英文混合字符串中的字数,也是比较复杂的,我们必须判断一个ASCII码是否扩展,以及它的下一个ASCII是否扩展,然后才“猜”那可能是一个中文字 。

总之当时处理中文是很痛苦的。而更痛苦的是GB2312是国家标准,台湾当时有一个Big5编码标准,很多编码和GB是相同的,所以……,嘿嘿。

这时候,我们就知道,要真正解决中文问题,不能从扩展ASCII的角度入手,也不能仅靠中国一家来解决。而必须有一个全新的编码系统,这个系统要可以将中文、英文、法文、德文……等等所有的文字统一起来考虑,为每个文字都分配一个单独的编码,这样才不会有上面那种现象出现。

于是,Unicode诞生了。

Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。

以目前常用的UCS-2为例,它可以表示的字符数为2^16=65535,基本上可以容纳所有的欧美字符和绝大部分的亚洲字符 。

UTF-8的问题后面会提到 。

在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的解决了 。

但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。

我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了 。

另一个更加严重的问题是,C语言使用'\0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉 。

于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF 。

UTF= UCS Transformation Format UCS转换格式

它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16 。

其中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容 。

UTF-8有点类似于Haffman编码,它将Unicode编码为00000000-0000007F的字符,用单个字节来表示;

00000080-000007FF的字符用两个字节表示

00000800-0000FFFF的字符用3字节表示

因为目前为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字节表示一个字符。

在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类可以对UTF编码进行处理。

下面说说中文的问题。

由于历史的原因,在Unicode之前,一共存在过3套中文编码标准。

GB2312-80,是中国大陆使用的国家标准,其中一共编码了6763个常用简体汉字。Big5,是台湾使用的编码标准,编码了台湾使用的繁体汉字,大概有8千多个。HKSCS,是中国香港使用的编码标准,字体也是繁体,但跟Big5有所不同。

这3套编码标准都采用了两个扩展ASCII的方法,因此,几套编码互不兼容,而且编码区间也各有不同

因为其不兼容性,在同一个系统中同时显示GB和Big5基本上是不可能的。当时的南极星、RichWin等等软件,在自动识别中文编码、自动显示正确编码方面都做了很多努力 。

他们用了怎样的技术我就不得而知了,我知道好像南极星曾经以同屏显示繁简中文为卖点。

后来,由于各方面的原因,国际上又制定了针对中文的统一字符集GBK和GB18030,其中GBK已经在Windows、Linux等多种操作系统中被实现。

GBK兼容GB2312,并增加了大量不常用汉字,还加入了几乎所有的Big5中的繁体汉字。但是GBK中的繁体汉字和Big5中的几乎不兼容。

GB18030相当于是GBK的超集,比GBK包含的字符更多。据我所知目前还没有操作系统直接支持GB18030。


谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:

问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?

我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?

问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。

0、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。

GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。

从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。

有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。

这里还有一些细节:

GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。

在DBCS中,GB内码的存储格式始终是big endian,即高位在前。

GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。

2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。

根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。

在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。

目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。

IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。

3、UCS-2、UCS-4、BMP

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。

group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

4、UTF编码

UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

读者可以用记事本测试一下我们的编码是否正确。

UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。

5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

Windows就是使用BOM来标记文本文件的编码方式的。

6、进一步的参考资料
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。

系统分类: 自由话题
用户分类: 我的空间
标签: 无标签
来源: 转贴
发表评论 阅读全文(113) | 回复(0)

1

关于投票
USB接口的基础理论知识(转帖)
 USB接口的基础理论知识


 USB的重要关键字:

1、端点:位于USB设备或主机上的一个数据缓冲区,用来存放和发送USB的各种数据,每一个端点都有惟一的确定地址,有不同的传输特性(如输入端点、输出端点、配置端点、批量传输端点)

2、帧:时间概念,在USB中,一帧就是1MS,它是一个独立的单元,包含了一系列总线动作,USB将1帧分为好几份,每一份中是一个USB的传输动作。

3、上行、下行:设备到主机为上行,主机到设备为下行

下面以一问一答的形式开始学习吧:

问题一:USB的传输线结构是如何的呢?

答案一:一条USB的传输线分别由地线、电源线、D+、D-四条线构成,D+和D-是差分输入线,它使用的是3.3V的电压(注意哦,与CMOS的5V电平不同),而电源线和地线可向设备提供5V电压,最大电流为500MA(可以在编程中设置的,至于硬件的实现机制,就不要管它了)。

问题二:数据是如何在USB传输线里面传送的

答案二:数据在USB线里传送是由低位到高位发送的。

问题三:USB的编码方案?

答案三:USB采用不归零取反来传输数据,当传输线上的差分数据输入0时就取反,输入1时就保持原值,为了确保信号发送的准确性,当在USB总线上发送一个包时,传输设备就要进行位插入***作(即在数据流中每连续6个1后就插入一个0),从而强迫NRZI码发生变化。这个了解就行了,这些是由专门硬件处理的。

问题四:USB的数据格式是怎么样的呢?

答案四:和其他的一样,USB数据是由二进制数字串构成的,首先数字串构成域(有七种),域再构成包,包再构成事务(IN、OUT、SETUP),事务最后构成传输(中断传输、并行传输、批量传输和控制传输)。下面简单介绍一下域、包、事务、传输,请注意他们之间的关系。

(一)域:是USB数据最小的单位,由若干位组成(至于是多少位由具体的域决定),域可分为七个类型:

1、同步域(SYNC),八位,值固定为0000 0001,用于本地时钟与输入同步

2、标识域(PID),由四位标识符+四位标识符反码构成,表明包的类型和格式,这是一个很重要的部分,这里可以计算出,USB的标识码有16种,具体分类请看问题五。

3、地址域(ADDR):七位地址,代表了设备在主机上的地址,地址000 0000被命名为零地址,是任何一个设备第一次连接到主机时,在被主机配置、枚举前的默认地址,由此可以知道为什么一个USB主机只能接127个设备的原因。

4、端点域(ENDP),四位,由此可知一个USB设备有的端点数量最大为16个。

5、帧号域(FRAM),11位,每一个帧都有一个特定的帧号,帧号域最大容量0x800,对于同步传输有重要意义(同步传输为四种传输类型之一,请看下面)。

6、数据域(DATA):长度为0~1023字节,在不同的传输类型中,数据域的长度各不相同,但必须为整数个字节的长度

7、校验域(CRC):对令牌包和数据包(对于包的分类请看下面)中非PID域进行校验的一种方法,CRC校验在通讯中应用很泛,是一种很好的校验方法,至于具体的校验方法这里就不多说,请查阅相关资料,只须注意CRC码的除法是模2运算,不同于10进制中的除法。

(二)包:由域构成的包有四种类型,分别是令牌包、数据包、握手包和特殊包,前面三种是重要的包,不同的包的域结构不同,介绍如下

1、令牌包:可分为输入包、输出包、设置包和帧起始包(注意这里的输入包是用于设置输入命令的,输出包是用来设置输出命令的,而不是放据数的)

其中输入包、输出包和设置包的格式都是一样的:

SYNC+PID+ADDR+ENDP+CRC5(五位的校验码)

(上面的缩写解释请看上面域的介绍,PID码的具体定义请看问题五)

帧起始包的格式:

SYNC+PID+11位FRAM+CRC5(五位的校验码)

2、数据包:分为DATA0包和DATA1包,当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0包和DATA1包交替发送,即如果第一个数据包是 DATA0,那第二个数据包就是DATA1。但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATA0,格式如下:

SYNC+PID+0~1023字节+CRC16

3、握手包:结构最为简单的包,格式如下

SYNC+PID

(注上面每种包都有不同类型的,USB1.1共定义了十种包,具体请见问题五)

(三)事务:分别有IN事务、OUT事务和SETUP事务三大事务,每一种事务都由令牌包、数据包、握手包三个阶段构成,这里用阶段的意思是因为这些包的发送是有一定的时间先后顺序的,事务的三个阶段如下:

1、令牌包阶段:启动一个输入、输出或设置的事务

2、数据包阶段:按输入、输出发送相应的数据

3、握手包阶段:返回数据接收情况,在同步传输的IN和OUT事务中没有这个阶段,这是比较特殊的。

事务的三种类型如下(以下按三个阶段来说明一个事务):

1、 IN事务:

令牌包阶段——主机发送一个PID为IN的输入包给设备,通知设备要往主机发送数据;

数据包阶段——设备根据情况会作出三种反应(要注意:数据包阶段也不总是传送数据的,根据传输情况还会提前进入握手包阶段)

1) 设备端点正常,设备往入主机里面发出数据包(DATA0与DATA1交替);

2) 设备正在忙,无法往主机发出数据包就发送NAK无效包,IN事务提前结束,到了下一个IN事务才继续;

3) 相应设备端点被禁止,发送错误包STALL包,事务也就提前结束了,总线进入空闲状态。

握手包阶段——主机正确接收到数据之后就会向设备发送ACK包。



2、 OUT事务:

令牌包阶段——主机发送一个PID为OUT的输出包给设备,通知设备要接收数据;

数据包阶段——比较简单,就是主机会设备送数据,DATA0与DATA1交替

握手包阶段——设备根据情况会作出三种反应

1)设备端点接收正确,设备往入主机返回ACK,通知主机可以发送新的数据,如果数据包发生了CRC校验错误,将不返回任何握手信息;

2) 设备正在忙,无法往主机发出数据包就发送NAK无效包,通知主机再次发送数据;

3) 相应设备端点被禁止,发送错误包STALL包,事务提前结束,总线直接进入空闲状态。



3、SETUT事务:

令牌包阶段——主机发送一个PID为SETUP的输出包给设备,通知设备要接收数据;

数据包阶段——比较简单,就是主机会设备送数据,注意,这里只有一个固定为8个字节的DATA0包,这8个字节的内容就是标准的USB设备请求命令(共有11条,具体请看问题七)

握手包阶段——设备接收到主机的命令信息后,返回ACK,此后总线进入空闲状态,并准备下一个传输(在SETUP事务后通常是一个IN或OUT事务构成的传输)

(四)传输:传输由OUT、IN、SETUP事务其中的事务构成,传输有四种类型,中断传输、批量传输、同步传输、控制传输,其中中断传输和批量转输的结构一样,同步传输有最简单的结构,而控制传输是最重要的也是最复杂的传输。

1、中断传输:由OUT事务和IN事务构成,用于键盘、鼠标等HID设备的数据传输中

2、批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输。

3、同步传输:由OUT事务和IN事务构成,有两个特殊地方,第一,在同步传输的IN和OUT事务中是没有返回包阶段的;第二,在数据包阶段所有的数据包都为DATA0

4、控制传输:最重要的也是最复杂的传输,控制传输由三个阶段构成(初始设置阶段、可选数据阶段、状态信息步骤),每一个阶段可以看成一个的传输,也就是说控制传输其实是由三个传输构成的,用来于USB设备初次加接到主机之后,主机通过控制传输来交换信息,设备地址和读取设备的描述符,使得主机识别设备,并安装相应的驱动程序,这是每一个USB开发者都要关心的问题。

1、初始设置步骤:就是一个由SET事务构成的传输

2、可选数据步骤:就是一个由IN或OUT事务构成的传输,这个步骤是可选的,要看初始设置步骤有没有要求读/写数据(由SET事务的数据包阶段发送的标准请求命令决定)

3、 状态信息步骤:顾名思义,这个步骤就是要获取状态信息,由IN或OUT事务构成构成的传输,但是要注意这里的IN和OUT事务和之前的INT和OUT事务有两点不同:

1) 传输方向相反,通常IN表示设备往主机送数据,OUT表示主机往设备送数据;在这里,IN表示主机往设备送数据,而OUT表示设备往主机送数据,这是为了和可选数据步骤相结合;

2) 在这个步骤里,数据包阶段的数据包都是0长度的,即SYNC+PID+CRC16

除了以上两点有区别外,其他的一样,这里就不多说

(思考:这些传输模式在实际***作中应如何通过什么方式去设置?)

问题五:标识码有哪些?

答案五:如同前面所说的标识码由四位数据组成,因此可以表示十六种标识码,在USB1.1规范里面,只用了十种标识码,USB2.0使用了十六种标识码,标识码的作用是用来说明包的属性的,标识码是和包联系在一起的,首先简单介绍一下数据包的类型,数据包分为令牌包、数据、握手包和特殊包四种(具体分类请看问题七),标识码分别有以下十六种:

令牌包 :

0x01 输出(OUT)启动一个方向为主机到设备的传输,并包含了设备地址和标号

0x09 输入 (IN) 启动一个方向为设备到主机的传输,并包含了设备地址和标号

0x05 帧起始(SOF)表示一个帧的开始,并且包含了相应的帧号

0x0d 设置(SETUP)启动一个控制传输,用于主机对设备的初始化

数据包 :

0x03 偶数据包(DATA0),

0x0b 奇数据包(DATA1)

握手包:

0x02 确认接收到无误的数据包(ACK)

0x0a 无效,接收(发送)端正在忙而无法接收(发送)信息

0x0e 错误,端点被禁止或不支持控制管道请求

特殊包 0x0C 前导,用于启动下行端口的低速设备的数据传输

问题六:USB主机是如何识别USB设备的?

答案六:当USB设备插上主机时,主机就通过一系列的动作来对设备进行枚举配置(配置是属于枚举的一个态,态表示暂时的状态),这这些态如下:

1、接入态(Attached):设备接入主机后,主机通过检测信号线上的电平变化来发现设备的接入;

2、供电态(Powered):就是给设备供电,分为设备接入时的默认供电值,配置阶段后的供电值(按数据中要求的最大值,可通过编程设置)

3、缺省态(Default):USB在被配置之前,通过缺省地址0与主机进行通信;

4、地址态(Address):经过了配置,USB设备被复位后,就可以按主机分配给它的唯一地址来与主机通信,这种状态就是地址态;

5、配置态(Configured):通过各种标准的USB请求命令来获取设备的各种信息,并对设备的某此信息进行改变或设置。

6、挂起态(Suspended):总线供电设备在3ms内没有总线***作,即USB总线处于空闲状态的话,该设备就要自动进入挂起状态,在进入挂起状态后,总的电流功耗不超过280UA。

问题七:刚才在答案四提到的标准的USB设备请求命令究竟是什么?

答案七:标准的USB设备请求命令是用在控制传输中的“初始设置步骤”里的数据包阶段(即DATA0,由八个字节构成),请看回问答四的内容。标准USB设备请求命令共有11个,大小都是8个字节,具有相同的结构,由5 个字段构成(字段是标准请求命令的数据部分),结构如下(括号中的数字表示字节数,首字母bm,b,w分别表示位图、字节,双字节):

bmRequestType(1)+bRequest(1)+wvalue(2)+wIndex(2)+wLength(2)

各字段的意义如下:

1、bmRequestType:D7D6D5D4D3D2D1D0

D7=0主机到设备

=1设备到主机;

D6D5=00标准请求命令

=01 类请求命令

=10用户定义的命令

=11保留值

D4D3D2D1D0=00000 接收者为设备

=00001 接收者为设备

=00010 接收者为端点

=00011 接收者为其他接收者

=其他 其他值保留

2、bRequest:请求命令代码,在标准的USB命令中,每一个命令都定义了编号,编号的值就为字段的值,编号与命令名称如下(要注意这里的命令代码要与其他字段结合使用,可以说命令代码是标准请求命令代码的核心,正是因为这些命令代码而决定了11个USB标准请求命令):

0) 0 GET_STATUS:用来返回特定接收者的状态

1) 1 CLEAR_FEATURE:用来清除或禁止接收者的某些特性

2) 3 SET_FEATURE:用来启用或激活命令接收者的某些特性

3) 5 SET_ADDRESS:用来给设备分配地址

4) 6 GET_DEscriptOR:用于主机获取设备的特定描述符

5) 7 SET_DEscriptOR:修改设备中有关的描述符,或者增加新的描述符

6) 8 GET_CONFIGURATION:用于主机获取设备当前设备的配置值(注同上面的不同)

7) 9 SET_CONFIGURATION:用于主机指示设备采用的要求的配置

8) 10 GET_INTERFACE:用于获取当前某个接口描述符编号

9) 11 SET_INTERFACE:用于主机要求设备用某个描述符来描述接口

10) 12 SYNCH_FRAME:用于设备设置和报告一个端点的同步帧

以上的11个命令要说得明白真的有一匹布那么长,请各位去看书吧,这里就不多说了,控制传输是USB的重心,而这11个命令是控制传输的重心,所以这11个命令是重中之重,这个搞明白了,USB就算是入门了。

问题八:在标准的USB请求命令中,经常会看到Descriptor,这是什么来的呢?

回答八:Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。它的作用就是通过如问答节中的命令***作来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作,所以描述符也是十分重要的部分,要好好掌握。标准的描述符有5种,USB为这些描述符定义了编号:

1——设备描述符

2——配置描述符

3——字符描述符

4——接口描述符

5——端点描述符

上面的描述符之间有一定的关系,一个设备只有一个设备描述符,而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。这间描述符是用一定的字段构成的,分别如下说明:

1、设备描述符

struct _DEVICE_DEscriptOR_STRUCT

{

BYTE bLength; //设备描述符的字节数大小,为0x12

BYTE bDescriptorType; //描述符类型编号,为0x01

WORD bcdUSB; //USB版本号

BYTE bDeviceClass; //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型

//0x00不是在设备描述符中定义的,如HID

BYTE bDeviceSubClass; //usb分配的子类代码,同上,值由USB规定和分配的

BYTE bDeviceProtocl; //USB分配的设备协议代码,同上

BYTE bMaxPacketSize0; //端点0的最大包的大小

WORD idVendor; //厂商编号

WORD idProduct; //产品编号

WORD bcdDevice; //设备出厂编号

BYTE iManufacturer; //描述厂商字符串的索引

BYTE iProduct; //描述产品字符串的索引

BYTE iSerialNumber; //描述设备序列号字符串的索引

BYTE bNumConfiguration; //可能的配置数量

}

2、配置描述符

struct _CONFIGURATION_DEscriptOR_STRUCT

{

BYTE bLength; //设备描述符的字节数大小,为0x12

BYTE bDescriptorType; //描述符类型编号,为0x01

WORD wTotalLength; //配置所返回的所有数量的大小

BYTE bNumInterface; //此配置所支持的接口数量

BYTE bConfigurationVale; //Set_Configuration命令需要的参数值

BYTE iConfiguration; //描述该配置的字符串的索引值

BYTE bmAttribute; //供电模式的选择

BYTE MaxPower; //设备从总线提取的最大电流

}



3、字符描述符

struct _STRING_DEscriptOR_STRUCT

{

BYTE bLength; //设备描述符的字节数大小,为0x12

BYTE bDescriptorType; //描述符类型编号,为0x01

BYTE SomeDescriptor[36]; //UNICODE编码的字符串

}

4、接口描述符

struct _INTERFACE_DEscriptOR_STRUCT

{

BYTE bLength; //设备描述符的字节数大小,为0x12

BYTE bDescriptorType; //描述符类型编号,为0x01

BYTE bInterfaceNunber; //接口的编号

BYTE bAlternateSetting;//备用的接口描述符编号

BYTE bNumEndpoints; //该接口使用端点数,不包括端点0

BYTE bInterfaceClass; //接口类型

BYTE bInterfaceSubClass;//接口子类型

BYTE bInterfaceProtocol;//接口所遵循的协议

BYTE iInterface; //描述该接口的字符串索引值

}

5、端点描述符

struct _ENDPOIN_DEscriptOR_STRUCT

{

BYTE bLength; //设备描述符的字节数大小,为0x12

BYTE bDescriptorType; //描述符类型编号,为0x01

BYTE bEndpointAddress; //端点地址及输入输出属性

BYTE bmAttribute; //端点的传输类型属性

WORD wMaxPacketSize; //端点收、发的最大包的大小

BYTE bInterval; //主机查询端点的时间间隔

}

系统分类: 接口电路
用户分类: 技术类
标签: 无标签