个人成就
- 发布了53篇内容
- 获得了1次赞同
- 获得了2次收藏
个人简介
擅长领域
暂时没有设置哦~
-
什么是USB的描述符?
USB只是一个总线,只提供一个数据通路而已。USB总线驱动程序并不知道一个设备具体如何操作,有哪些行为。具体的一个设备实现什么功能,要由设备自己来决定。那么,USB主机是如何知道一个设备的功能以及行为呢?这就要通过描述符来实现了。那么什么是USB的描述符呢?其实就是一些传递的协议信息,比如设备的类型、厂商ID、产品ID、端点情况、版本号等信息。
既然描述符是协议信息,那么不同的版本也会有所不同,比如USB1.1协议定义的标准描述符有设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符、类特殊描述符以及厂商自定义的描述符。那么USB2.0协议中又增加了两个新的标准描述符有设备限定符描述符和其他速度配置描述符。随着USB协议版本的提升,大家知道都是为了提升速度和可靠度,让用户有更快更高效的体验。那么USB1.1是全速设备,现在我们一起看下USB1.1协议定义的描述符吧。
一个USB设备只有一个设备描述符。设备描述符里决定了该设备有多少种配置,每种配置都有一个配置描述符;而在每个配置描述符中又决定了该配置里有多少个接口,每个接口都有一个接口描述符;在接口描述符里又定义了该接口有多少个端点,每个端点都有一个端点描述符;端点描述符定义了端点的大小、类型等。如果有类特殊描述符,它跟在相应的接口描述符之后。由此可以看出,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,接下来是配置描述符,再下来是接口描述符,最下面是端点描述符。在主机获取描述符时,首先获取设备描述符,接着再获取配置描述符,然后根据配置描述符中的配置集合的总长度,一次将配置描述符、接口描述符、类特殊描述符(如果有)、端点描述符一次读回。对于字符串描述符,是单独获取的。主机通过发送获取字符串描述符的请求以及描述符的索引号、语言ID来获取对应的字符串描述符。
l 设备描述符主要记录的信息有:设备所使用的USB协议版本号、设备类型、端点0的最大包大小、厂商ID(VID)和产品ID(PID)、设备版本号、厂商字符串索引、产品字符串索引、设备序列号索引、可能的配置数等。
l 配置描述符主要记录的信息有:配置所包含的接口数、配置的编号、供电方式、是否支持远程唤醒、电流需求量等。
l 接口描述符主要记录的信息有:接口的编号、接口的端点数、接口所使用的类、子类、协议等。
l 端点描述符主要记录的信息有:端点号及方向、端点的传输类型、最大包长度、查寻时间间隔等。
l 字符串描述符主要是提供一些方便人们阅读的信息,它不是必需的。
说了半天,也许你还没搞清楚到底设备、配置、接口、端点等这些是什么东西。不要急,这些东西的确是有点晕人。特别是刚接触时,这么多的内容很容易让人搞混,或者似乎是懂了,然后再想想,似乎又没懂……这些所说的设备,就是一个实实在在的USB设备,例如一个USB鼠标。设备有一个设备地址,USB主机依靠这个设备地址来访问设备。而在设备内部还会分的更细。它会分出一些端点出来,例如端点0、端点1等。就是说,如果USB主机要和USB设备通信,光有设备地址是不够的,还需要一个端点地址。有了设备地址和端点地址,就能准确地对端点发送和读取数据了。好比你要去找8号教学楼的605教室,8号楼就是设备地址,而605教室就是端点地址。而配置和接口,是为了更方便地管理端点而抽象出来的概念。一个设备可以有多个配置,但是同一时刻只能有一个配置有效。每个配置下又可以有多个接口。当我们需要不同的功能时,只要选择不同的配置即可。拿刚才的教学楼来说,我们可以把它分成两个配置:平时上课用和考试用。考试用时,全部的教室都拿来作考场(即该配置下只有一个接口,接口下有很多端点---教室);而平时上课用时,分成两类(即该配置下有两个接口,每个接口下有一些端点---教室):教师休息室和上课的课室。教师休息室和课室是不能共用的(这在USB中也是如此,同一个端点号不能出现在同一个配置下的两个或者更多个不同的接口中)。但是平时用来做课室或者休息室的教室,考试时都可以拿来作考场(这在USB中也是如此,同一个端点号可用在不同的配置中)。具有多个接口并由接口来实现功能的设备把它叫做USB复合设备,例如一个USB音频设备,它具有一个音频控制接口,另外还可能具有一到多个音频流或MIDI流接口。在主机端会把USB复合设备的每个接口当作一个功能设备来看待。像常见的USB鼠标、U盘等,通常是单一的设备,即一个设备下只有一个配置描述符、一个接口描述符。
总结一下:由端点构成一个接口(或者反过来说,接口是端点的集合),由接口又构成一个配置(反过来说,配置是接口的集合),再由配置构成一个设备(设备是配置的集合)。学习USB,一定要把这些关系理清楚了,才能按照需要构造出一个合格的USB设备。如果一个设备的各种描述符成功返回了,那么可以说已经成功了大半。相反,只要描述符出现一点问题,哪怕只是一个bit的错误,都可能造成设备无法识别或者无法正常工作。
-
程序的链接过程和存储区解读
大家好,我们张飞实战电子黄忠老师,今天我们讲解程序的链接和存储区的解读。
根据C语言的特点,每一个源程序生成的目标代码将包含源程序所需要表达的所有信息和功能。有些时候很有必要从这些段中来分析实际使用情况和改进空间。目标代码中各段生成情况如下:
1、代码段(Code)
代码段由程序中各个函数产生,函数的每一个语句将最终经过编译和汇编生成二进制机器代码(具体生成哪种体系结构的机器代码由编译器决定)
2、只读数据段(RO Data)
只读数据段由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将该数据放入只读的部分中。C语言的一些语法将生成只读数据段。
① 只读全局变量
例如:定义全局变量 const char a[100]={“ABCDEFG”};
这个是生成大小为100个字节的只读数据区,并使用字符串“ABCDEFG”初始化。如果定义的时候没有指定大小,那么根据初始化的字符串长度生成相应大小的只读数据段。
② 只读局部变量
例如:在函数内部定义的只读变量 const char b[100]={“9876543210”};
③ 程序中使用的常量
例如:在程序中使用printf(“information n”),其中“information n”是字符串常量, 编译器会自动把常量放入只读数据区。
注意:上面两个变量定义,定义100个大小的区域,但是只初始化前面几个字节,实际后面的字节没有初始化,但是在程序中也不能写,实际是没有任何用处的。所以定义只读的时候需要做完全的初始化。
3、读写数据段(RW Data)
读写数据段表示了在目标文件中一部分可以读也可以写的数据区,在某些场合它们又被称为已初始化数据段。这部分是属于程序中的静态区域。
①已初始化全局静态变量
在函数外部定义的全局的变量,并且初始化。(static是限制作用域的)
②已初始化局部静态变量
在函数中定义的由static定义并且已经初始化的数据或者数组。
注意:定义的变量要有初始化才会在读写数据区。
4、未初始化数据段(BSS)
这个段也属于静态数据区。但是没有初始化,所以在目标文件中会有标识,而不会真正称为目标文件中的一个段,这个段会在运行时产生,所以它的大小不会影响目标文件的大小。
比如上面这个图就是通常我们编译后获得的。当你的方案选型是一个空间很小的处理器的时候很有必要了解这些存储的区域都存的是什么,方便处理冗余或者修改方案。
上面我们了解了程序对应的存储空间,程序是怎么对号入座到这些存储区的呢?一起来看下吧,也没想象的那么神秘。
我们每一个C语言源程序(*.c)经过编译生成目标文件(.o),目标文件就包含前面我们说的代码段、只读数据段、读写数据段。未初始化数据段、堆和栈不会占用目标文件的空间。
那么可执行程序是由各个目标文件经过链接而成,链接就是把各个目标文件的代码段、只读数据段、读写数据段经过了重新的排列组合。
需要注意的是未初始化数据段是怎么样的,在链接的过程中,链接器可以得到未初始化数据段的大小,它也是各个目标文件的各个未初始化数据段之和,但是这个段是不影响可执行程序大小的。在C语言使用的角度,读写数据段和未初始化数据段都是可读写的。实质上,在目标文件中未初始化数据段和读写数据段的区别也在于此,读写数据段占用目标文件的容量,而未初始化数据段只是一个标识,不需要占用实际的空间。
在链接过程之前,各个源文件生成目标文件相互没有关系。在链接之后,各目标文件函数和变量可以相互调用和访问,从而被联系在一起。比如函数调用,链接过程就是要有函数调用的地方还需要找到真正的函数定义才可以完成链接,链接器会根据需要根据实际的情况修改编译器生成的机器代码,完成正确的跳转。全局变量的访问也是同理。
再来了解下链接过程中常见的错误:
1、符号未找到
(1) 只要符号被声明,编译就可以通过,但是在链接过程中符号必须具有具体的实现才可以成功链接。
(2) 由于数据仅能在文件内部使用(static),导致符号未定义错误。
2、符号重定义
(1) 在多个文件中定义全局的同名函数和变量(static的重名了是正确的)。
(2) 在头文件中定义已经初始化数据,在头文件被多个文件包含的时候,将发生错误。同样在头文件中也不应该定义只读数据段的常量。
再有在头文件中不应该使用静态的变量,无论它有没有初值,这样虽然不会引起链接错误,但是在各个源文件中各自产生变量,不但占用空间,而且在逻辑上是不对的,也违背了头文件的使用原则。
从C语言程序设计的角度,不应该在头文件中定义变量或者函数。对于函数,在头文件中只是声明,需要在源文件中定义;对于变量,无论何种性质,最好的方式是在C语言的源文件中定义,在头文件中使用extern声明使用。
编译,链接后面就是执行了,后面我会跟大家再分享程序运行过程,这个其实都是C语言定的一些规则,只要守规则就会顺利完成想要实现的结果。
-
51单片机DIY抽奖-技术分享(三)
PCB设计
前情提要:上一篇我介绍了我做这个小产品的原理图设计思路,通过对原理图部分的设计说明,大家可能觉得没什么东西,当你动手实现了整个过程,再回过头去看,其实收货还是比想象的多,每一次的历练都不是徒劳的,当你收获多个小产品的经验后,有一天你接手一个大项目就不会感觉束手无措,会感觉自己平时的经验都能用的上了,话不多说,下面就分享我的PCB设计过程,PCB设计首先就是定义板框,我这里想到抽奖的小产品,那么模样也不能差太多了吧,话说我从网上搜了下抽奖转盘图片,常规我们看到的都是类似下面这种吧?
那设计开始吧,板框设计,由于这是一个实验品,不需要做成像平时我们见到的那么大吧,所以我只需要自定义一个能把所有元器件放下的就可以了。
板框实现了,就是把原理图导入到PCB了,接下来就是实现PCB的设计实现过程了。在这里呢,我主要分享我的实现过程,还有过程中我碰到的一些小问题,我会给大家分享出来,做个提示,如果碰到同样问题,那就可以少走弯路了。
具体实现细节大家可以持续关注下,后续我会把实现的过程视频分享给大家,互相学习嘛,有需要借鉴的就可以少走弯路,有我需要改进的欢迎大家来跟我交流哈。
那继续看下PCB设计前应该做的事情吧,首先就是规则设置了,无规矩不成方圆,这个也同理了,所以在布局前先把规则设置好,后面所有的事情做起来就只要守规矩就好了,否则会做出警示了。那么设置规则有没有讲究呢,就请跟着我的设置来看下我常规的经验,这个只是我个人常规使用,也可以根据自己实际情况调整的。
接下来一起看下规则设置:
先是间距设置,常规我设置线的间距(线和线,线和过孔等的距离)是0.2mm,这是经验值;然后我会设置敷铜间距,单独添加间距规则设置敷铜的间距,这个我设置的0.3mm,根据实际情况来定,这个也是我的经验值。
2、接下来就是线宽了,线宽推荐值我不变,最小线宽设置为0.2mm,最大设置为2mm,一般情况下布线常规线宽就是推荐值,电源和地线会用大点,所以这里规则要先设置好。
3、接着就是设置过孔,对于过孔来说,有内径大小和外径大小,都有相应的最小值、推荐值和最大值,在这里我只改变内径最小值(0.3mm)和外径的最小值(0.6mm),其他就是默认就可以了。
4、接下来就是我会设置下敷铜连接方式,单独新建规则来设置过孔的连接方式,常规默认是十字连接,这里我设置为直连就可以了。
5、后面的设置就是孔到孔的间距、最小阻焊的间距、丝印到阻焊的间距、丝印到丝印的间距、元件的间距,这些我一般都会全部设置为0,主要靠自己布局把握了。
主要的规则设置就上面这些了,具体的可以在实际过程中根据需求再设置就好了,规则设置好方便布局和走线的规范性,做事还是规范点好,不然真的好麻烦啊。
规则设置好了,就可以开始布局了,布局的讲究除了看起来合理,产品出来用起来顺手,还要考虑走线等等,真的不是随便摆摆就好了,我只是分享我的思路,还是那句,方法很多,可以借鉴感觉别人优势的地方。
既然像转盘转起来的感觉,我做的是个电子产品,有转起来的感觉就可以了,那么就把这个圆盘做了一圈的LED,然后通过程序来实现循环点亮,逐渐加快速度,同时把两位数码管(LED组成)数据循环显示(速度快无法分辨当前实际数值),两位数码管就放在上面正中间了。
由于是USB供电,所以要在端口处(最下面),既然上面显示,那么USB供电上面就可以放置两个按键,一个开始,一个停止。是不是简单多了。也可以通过按键的长按或者组合实现调整数据等操作,所以这里两个按键就够用了。
因为这个产品元器件不多,所以除了蜂鸣器我放在底层,其他都放在顶层,这样对于走线也方便。
针对模块化,我们先来看下,
第一部分就是单片机部分了,单片机连接了数码管驱动芯片以及蜂鸣器部分。
那么先把单片机涉及的部分放在中间,模块化布局可以把原理图放置在平行界面,当选中模块后,对应的PCB模块部分就可以选中来组合了,下图中所示:
那么针对前面说到的数码管显示放在上面,按键放在下面,自然就是数码管驱动芯片放在显示和按键中间合理了吧,又有和单片机的连接,所以看下图:
那么剩下的就是蜂鸣器部分了,一起看下显然就是放在单片机左侧了,然而我说了选择放在底层,所以合理放置就可以了。
布局就这样实现了,是不是挺简单的,接下来就是走线了,走线根据实际情况要不断的调整,顶层无法走了需要打过孔来走底层。这是最繁琐的一个过程了,也是考验细致和耐心的过程。具体走线细节可以关注后面的视频,这个看的过程中如果感兴趣还是需要实践下的,不实际操作看了真的没什么效果。
那么走线就不具体说了,走线完成还有敷铜,DRC检测,出图等很多流程,这里就不详细说了,主要在于设计过程的分享,还有注意事项,最后还有一项需要提醒的是泪滴,这个是对焊接时焊盘连接线的保护,还是要注意下的。
最终效果图看下吧:
对于抽奖DIY的PCB设计部分就这些,是不是看起来也不难呢。其实实现起来真的没你想的那么简单,每个实践过程都会有很大收获的,后续会分享我的程序实现过程以及效果。都会经历不同的坎坷啊。当你遇到不同的问题的时候慢慢解决了才是经验的积累过程。这个抽奖DIY小产品PCB设计就完成了,大家可以看下我的实现有没有需要改进的地方呢?随时欢迎交流啊,或者有更好玩的产品一起探讨实现啊,每个都有每个的特点,都能学到不一样的知识点。如果你能在我这有所启发是最好了,分享和交流的过程就是成长的过程。后面我会陆续更新,下面一篇就是对程序的实现了,感谢大家对我的持续关注,还希望能给出更多的意见和建议啊。
-
51单片机DIY抽奖-技术分享(二)
1 原理图设计
前情提要:上一篇我介绍了我做这个小产品的想法和准备工作,其实很有动手的必要,有时候不要觉得简单就不做,每一个产品实现的过程都不是一帆风顺的,都会增加不同的经验,我是深有体会的。话不多说,只要能让大家想起来我的需求和想法就好了,现在就是实现过程了,先来做原理图设计了。
看我的标题明确的说了DIY抽奖,所以对这个数码管显示数字部分我用的LED(红色---目的就是压降低),一圈旋转的灯就是黄绿蓝三色切换,一共用了18个。
我只做了两位显示,最大到99,有需要做三位以及四位都是可以添加的。我这里就是实现了两位的,主要功能实现了,其他都不问题了是吧。
我选择了USB供电,USB供电进来我加了一个保险丝(9V/200mA),起到一个保护作用。
因为我要实现的功能相对简单,主要靠编程实现效果,所以电路设计就比较容易实现了,但是还是需要想下用最少的资源实现最强的效果了。
接下来看下单片机模块的实现,先看下我设计的电路图:
选用了8脚单片机,电源端加了一个滤波电容,一个储能电容,这个没什么问题。然后就是一个蜂鸣器,我想当抽奖停止一次就通过蜂鸣器唱歌,然后有一个蜂鸣器端口,可以通过PWM输出不同的频率来实现歌曲的调子。这个还要研究下歌曲呢,谱子啥的,瞬间感觉自己都高能了。
右边部分就是I2C接口了,连接的按键显示芯片的端口。下面两个是串口通讯口,这里是单片机下载程序用的。这几个端口的10R电阻是防震荡作用的。
接下来我们看下按键显示模块的实现,先上图吧:
图中可以很清晰的看出显示的段和位部分,这里的100R电阻是对LED限流用的,这个电阻可以根据实际情况调节的。I2C端口的4.7K电阻是接口需求,这个可以查阅相关I2C标准,我就不多说了。下面这两个电容是电源端的滤波电容和储能电容。按键由于是共阴设计,所以加一个电阻是限流作用了。这一部分就设计完成了,是不是还挺简单的。
接下来就剩下的是蜂鸣器部分了,我选用的是无源压电式蜂鸣器,先上图看下设计思路吧:
电源端的电容就是滤波电容和储能电容了,上面这个10R电阻就是限流作用了,因为选用的是压电式无源蜂鸣器,所以下面这个R15就是为蜂鸣器放电用的了,因为我是要给蜂鸣器PWM实现唱歌,所以BEEP就是单片机给出的相当于信号了,所以下面加了一个三极管,R16作用就是限流电阻了,R17就是对三极管结电容加速关断的作用了。这样蜂鸣器部分就设计完成了。
对于抽奖DIY的原理图设计部分就这些,是不是看起来很简单呢。其实好玩的在后面呢,PCB设计像转盘一样的抽奖样式,程序实现效果。都要动一番心思的。我实现了这样的一个小产品,大家可以看下我的原理图部分有没有需要改进的地方呢?随时欢迎交流啊,或者有更好玩的产品一起探讨实现啊,每个都有每个的特点,都能学到不一样的知识点。如果你能在我这有所启发是最好了,分享和交流的过程就是成长的过程。后面我会陆续更新,下面一篇就是对PCB的实现了,感谢大家对我的持续关注,还希望能给出更多的意见和建议啊。
-
单片机C语言程序的存储区域解读
在C语言代码(文本文件)形成可执行程序(二进制文件),需要经过编译-汇编-链接三个阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,链接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。
C语言编写的程序经过编译-链接后,将形成一个统一文件,它由几个部分组成。在程序运行时又会产生其他几个部分,各个部分代表了不同的存储区域:
1、代码段(Code或Text)
代码段由程序中执行的机器代码组成。在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。
2、只读数据段(RO data)
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。
3、已初始化读写数据段(RW data)
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。
4、未初始化数据段(BSS)
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。
5、堆(heap)
堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后回收内存。
6、栈(stack)
栈内存只在程序运行时出现,在函数内部使用的变量、函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。
C语言目标文件的内存布局如下图:
代码段、只读数据段、读写数据段、未初始化数据段属于静态区域,而堆和栈属于动态区域。代码段、只读数据段和读写数据段将在连接之后产生,未初始化数据段将在程序初始化的时候开辟,而堆和栈将在程序的运行中分配和释放。
C语言程序分为映像和运行时两种状态。在编译-链接后形成的映像中,将只包含代码段(Text)、只读数据段(RO Data)和读写数据段(RW Data)。在程序运行之前,将动态生成未初始化数据段(BSS),在程序的运行时还将动态形成堆(Heap)区域和栈(Stack)区域。
一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment)。如果不详细区分,可以统称为段。
每一个源文件生成的目标代码有了这些段的区分,那么每个段就能表示出源文件想要表达的信息和功能。下一篇我们一起深入分析下在这些段中是怎么体现的源码的信息的。