管脚连接模块与GPIOGPIOGPIOGPIO——基于LPC2103LPC2103LPC2103LPC2103(arm7arm7arm7arm7内核)的讲解注:该内容适用于大部分arm7arm7arm7arm7内核的芯片ByByByBy:小飞胡ArmArmArmArm芯片是一种32323232位的高性能单片机(暂且可以这样称呼吧),相比传统的51515151单片机其体积更小,性能更高,管脚也更多。ArmArmArmArm芯片的管脚一般都有好几十个,有的甚至上百个,而且这么多的管脚还不止一种功能,一般都是有3333、4444种功能复用的,但这些功能不能乱用,必须得有专门的部件对他们进行管理,在不同的时刻对所需的管脚选择所需的功能,这个部件就叫做管脚连接模块。GPIOGPIOGPIOGPIO(GeneralGeneralGeneralGeneralPurposePurposePurposePurposeI/OportsI/OportsI/OportsI/Oports)是通用输入输出口的简称,,,,是管脚最常用的功能,负责管理它的模块叫做GPIGPIGPIGPIOOOO模块。本文会详细地讲述管脚连接模块的作用与用法,并着重讲述管脚最常用的GPIOGPIOGPIOGPIO功能,最后会以一个实例进行分析以使大家对其理解更加深刻。管脚连接模块管脚连接模块的寄存器一般写作PINSELxPINSELxPINSELxPINSELx,其中xxxx可以是0000、1111、2222等等,不同系列的芯片写法可能有出入,但其道理都是一样的,都是对管脚的功能进行选择。本文我就以恩智浦公司产的LPC2103LPC2103LPC2103LPC2103芯片为例进行讲解,你可以根据本文的方法对其他芯片进行操作。先看一下管脚连接模块的寄存器,因为单片机的核心就是对寄存器进行配置,所以把每个模块的寄存器配置方法搞懂了,基本就可以使用了:寄存器的各位的功能如下表所示:(只取了PINSEL0的一部分内容,讲懂原理即可,相信你很容易就能理解)由图观之,一个管脚一般是按四种功能进行预制的,也就是说一个管脚最多只能有四种功能可选,四种功能就要用2个二进制位才能完全对应,LPC2103有32个IO口也就是需要64个二进制位才能完全控制每个管脚的每个功能,所以管脚连接模块的寄存器也刚好是2个32位的寄存器,恰好64个二进制位,是不是很妙?比如我如果想让P0.0管脚作为GPIO口使用,那我只需要对PINSEL0的最低2位赋值00即可,如果我想让P0.0管脚作为TXD0功能使用,我只需要对PINSEL0寄存器的最低两位赋值01即可,其他功能以此类推。注意:保留的功能不能使用,也就是说如果我对PINSEL0的最低2位赋值11是没有意义的。现在清楚管脚连接模块的使用方法了吧,是不是很简单?我们只需要选择所需的管脚功能,并对管脚连接模块的寄存器写值就完成了管脚的功能设定,Soeasy!其他类型的arm7芯片也是这样用的,无非有的腿多些有的腿少些,大同小异,如果你学会了骑自行车去郊游,难道还能不会骑自行车去上班?所以学习要抓住内在本质,掌握它的使用要领,就可以以不变应万变。如果你的老板非要让你用另一款arm7但你只用过你自己的板子,你能说不会用吗,如果是的,那你只有卷铺盖走人了。现在来看看这几个例子:1.设置P0.0为GPIO功能PINSEL0=PINSEL&0XFFFFFFFC;//设置P0.0为GPIO,其他功能不可用2.设置P0.0为TXD0功能PINSEL0=(PINSEL0&0XFFFFFFFC)|0x01//设置P0.0为TXD0,其他功能不可用3.将P0.8、P0.9设置为TXD1、RXD1功能PINSEL0=0X00050000;(1)或PINSEL0=0X0516;(1)其实(1)(2)两句是等价的,(2)中0x0516是将0101左移16位,比(1)直观。但要注意:(3)中的写法可以将P0.8、P0.9连接到UART1,可是会改变其他管脚的功能设置,为了不更改原先的引脚功能设置,可以采取“读取——修改——回写”的方式进行,即先读取寄存器值,然后进行逻辑“与”、“或”操作,再回写到此寄存器。如:PINSEL0=(PINSEL0&0XFFF0FFFF)|(0X0516);以上就是管脚连接模块的基本内容了,推荐使用例3中的写法,因为这种写法只对需要的管脚进行了改动,不会影响到其他管脚的作用,以后的学习中还会见到很多这样的写法。下边开始讲解GPIO功能模块。GPIOGPIOGPIOGPIO功能模块的使用当管脚被管脚连接模块选中GPIO功能时,有4个基本的寄存器用于控制GPIO的使用,其名称和功能依次为:IOPIN:IOPIN:IOPIN:IOPIN:反映当前IOIOIOIO的状态IOSETIOSETIOSETIOSET:用于口线置1111,读回IOSETIOSETIOSETIOSET则反映当前IOIOIOIO口设定状态IODIR:IODIR:IODIR:IODIR:用于设置IOIOIOIO口是输出还是输入IOCLRIOCLRIOCLRIOCLR:用于口线置零下边是这几个寄存器的详细内容:注意:虽然讲解时这些寄存器直接写作IOPIN,IOSET,IODIR,IOCLRIOPIN,IOSET,IODIR,IOCLRIOPIN,IOSET,IODIR,IOCLRIOPIN,IOSET,IODIR,IOCLR等,但写程序时必须得带上IOIOIOIO组号,以LPC2103LPC2103LPC2103LPC2103为例,它只有一组IOIOIOIO口即P0.0~P0.31P0.0~P0.31P0.0~P0.31P0.0~P0.31,它的IOIOIOIO组号就是0000,写程序时这些寄存器就得写成IO0PIN,IO0SET,IO0CLR,IO0DIRIO0PIN,IO0SET,IO0CLR,IO0DIRIO0PIN,IO0SET,IO0CLR,IO0DIRIO0PIN,IO0SET,IO0CLR,IO0DIR,以后的学习中还会有很多这样的寄存器,希望到时能知道是怎么回事。下边分别说明这四个寄存器的作用与用法。IOPIN:管脚值寄存器,也就是说只要管脚被设置为GPIO,不管它是做输出还是输入,IOPIN都显示当前GPIO的值,这里写的它是只读,其实是写错了,这个寄存器是个可读可写的寄存器。还有一点要注意:IOPIN只反映那些被设置为GPIO功能的管脚,对于被设置为其他功能的管脚,IOPIN中的对应位的值是无意义的。IOSET:输出置位寄存器,当某管脚被配置为GPIOGPIOGPIOGPIO输出模式时,可使用该寄存器使该位输出高电平。写入1使其输出高电平,写入0无效,无效的意思就是说如果IOSET的某位被写过1了,你再往该位写个0,IOSET的该位值还是1不变,对应管脚还是输出高电平。该寄存器可读可写,读它时一般是为了取反某位,这个下边会说到。IODIR:GPIO方向控制寄存器。当某管脚被设置为GPIO时,可使用该位设置该管脚是作为输出还是作为输入。写1111是输出,写0000是输入。IOCLR:GPIO输出清零寄存器,当某管脚被设置为GPIOGPIOGPIOGPIO时,可使用该寄存器使该管脚输出低电平,写入1111使对应管脚输出低电平并清零IOSETIOSETIOSETIOSET寄存器中相应的位而写入0000无效。该寄存器是只写寄存器,也就是说无法从该寄存器读出值。下边这段话非常重要,应该好好思考并理解。当往某个寄存器只有写入特定逻辑的值才有效,而写入反逻辑的值无效时(如只有写入1111有效而写入0000无效,或者只写入0000有效而写入1111无效),只操作其中某一位或者某些位时,可以不必关心其他位的状态,即不必读回原来的值再进行与或操作后回写,可以径直修改需要的位而不必担心影响其他管脚的状态。一般情况下,有这种特性的寄存器,均会有置位和清零寄存器成对出现,如IOSETIOSETIOSETIOSET和IOCLR.IOCLR.IOCLR.IOCLR.使用示例:1.1.1.1.设置P0.0P0.0P0.0P0.0为输出模式PINSEL0=0X00000000;PINSEL0=0X00000000;PINSEL0=0X00000000;PINSEL0=0X00000000;//P0.0//P0.0//P0.0//P0.0选择GPIOGPIOGPIOGPIO功能IO0DIR=0X00000001;IO0DIR=0X00000001;IO0DIR=0X00000001;IO0DIR=0X00000001;////////设置P0.0P0.0P0.0P0.0为输出模式2.2.2.2.GPIOGPIOGPIOGPIO读写操作程序将会读取P0.7~P0.4P0.7~P0.4P0.7~P0.4P0.7~P0.4脚值,然后从P0.3~P0.0P0.3~P0.0P0.3~P0.0P0.3~P0.0输出bak=IO0PIN;bak=IO0PIN;bak=IO0PIN;bak=IO0PIN;////////读取引脚上的值IO0CLR=0X0000000F;IO0CLR=0X0000000F;IO0CLR=0X0000000F;IO0CLR=0X0000000F;////////将P0.0~P0.3P0.0~P0.3P0.0~P0.3P0.0~P0.3输出0000IO0SET=(bak&0X000000F0)4;//IO0SET=(bak&0X000000F0)4;//IO0SET=(bak&0X000000F0)4;//IO0SET=(bak&0X000000F0)4;//设置P0.0~P0.3P0.0~P0.3P0.0~P0.3P0.0~P0.3输出(为1111的位输出1111)3.3.3.3.取反P0.0P0.0P0.0P0.0的输出方法是程序先从IO0SET读取当前输出寄存器的值,而不是去读引脚上的电平值(即不去读IO0PIN),然后判断P0.0的输出是高电平还是低电平,再控制输出取反。If((IO0SET&0X00000001)==0)IO0SET=0X00000001;elseIO0CLR=0x00000001;会灵活使用上边这几种用法,编写一般的与GPIOGPIOGPIOGPIO相关的程序已经足够了,但是对某些比较特殊的程序还是会有问题,主要的一种情况就是:通过IOSRTIOSRTIOSRTIOSRT和IOCLRIOCLRIOCLRIOCLR可以设置IOIOIOIO口的高低状态,可以同时置位////拉低某些选定的管脚,但是不能同时将某些管脚拉高而将某些管脚拉低。这段话说的是什么意思呢,说的就是比如我想让已被选为GPIO的0到7这8个管脚在某一时刻同时输出0xaa,也就是输出10101010,而不管这8个管脚之前是什么值,假设这8个管脚之前的值是01010101,我如果用IOSET和IOCLR进行这个过程时,就要分2步进行,即先用IOSET把01010101中的1、3、5、7位拉高,再用IOCLR把0、2、4、6位拉低,这样才完成要求,但在这个执行过程中会出现极短暂的一个时间这8个管脚上的值都是11111111,这个状态在IOSET把1、3、5、7位拉高后出现的,虽然时间很短,但对于某些非常灵敏的器件还是一种干扰甚至产生错误。所以要想一种方法可以同时把某几个管脚的值同时改变为所需要的值,这里就要用到IO0PIN.仅需要执行IO0PIN=(IO0PIN&0XFFFFFF00)|0X000000AA;即可。注意操作时要先“读取——修改——回写”,不然就会影响到其他管脚的状态,这点要注意。实验例程这里给出两个实例,一个是GPIO的输出实验,具体是实现一个蜂鸣器鸣叫;一个是GPIO的输入实验,具体是实现一个按键检测。第一个:GPIO输入,蜂鸣器鸣叫#includeconfig.h//包含与开发板相关的硬件宏定义/*延时子程序*/voiddelay(){U16i;for(i=