GPIO

Def-what?        

         什么是GPIO? GPIO(General-Purpose IO ports)即通用IO口。

        在嵌入式系统中常常有数量众多,但是结构却比较简单的外部设备/电路,对这些设备/电路,有的需要CPU为之提供控制手段,有的则需要被CPU用做输入信号。

         而且,许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了,比如控制某个LED灯亮与灭;或者通过获取某个管脚的电平属性来达到判断外围设备的状态。对这些设备/电路的控制,使用传统的串行口或并行口都不合适,所以在微控制器芯片上一般都会提供一个“通用可编程IO接口”,即GPIO。接口至少有两个寄存器,即“通用IO控制寄存器”与“通用IO数据寄存器”。数据寄存器的各位都直接引到芯片外部,而对这种寄存器中每一位的作用,即每一位的信号流通方向,则可以通过控制寄存器中对应位独立地加以设置,比如可以设置某个管脚的属性为输入、输出或其他特殊功能。

          在实际的MCU中,GPIO是有多种形式的。比如,有的数据寄存器可以按照位寻址,有些却不能按照位寻址,这在编程时就要加以区分。比如传统的8051系列,就区分成可位寻址和不可位寻址两种寄存器。另外,为了使用方便,很多MCU的 GPIO接口除去两个标准寄存器必须具备外,还提供上拉寄存器,可以设置IO的输出模式是高阻,还是带上拉的电平输出,或者不带上拉的电平输出。这使得在电路设计中,外围电路就可以简化不少。

     

Usage

All the GPIO pins can be reconfigured to provide alternate functions, SPI,PWM, I²C and so.

GPIOs are used in:

Capabilities

GPIO capabilities may include:

  • GPIO pins can be configured to be input or output
  • GPIO pins can be enabled/disabled
  • Input values are readable (typically high=1, low=0)
  • Output values are writable/readable
  • Input values can often be used as IRQs (typically for wakeup events)

      GPIO peripherals vary quite widely. In some cases, they are very simple, a group of pins that can be switched as a group to either input or output. In others, each pin can be set up flexibly to accept or source different logic voltages, with configurabledrive strengths and pull ups/downs. The input and output voltages are typically, though not universally, limited to the supply voltage of the device with the GPIOs on and may be damaged by greater voltages.

      A GPIO pin's state may be exposed to the software developer through one of a number of different interfaces, such as amemory mapped peripheral, or through dedicated IO port instructions.

      Some GPIOs have 5 V tolerant inputs: even when the device has a low supply voltage (such as 2 V), the device can accept 5 V without damage.


How-to-use?

All the GPIO pins can be reconfigured to provide alternate functions, SPI,PWM, I²C and so.

1. 用GPIO模拟SPI协议的实现[转]:

refer:http://www.cnblogs.com/cute/archive/2011/05/17/2048645.html

//begin===================

一、 SPI协议概括

SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200.

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)。

(1)SDO     – 主设备数据输出,从设备数据输入

(2)SDI      – 主设备数据输入,从设备数据输出

(3)SCLK     – 时钟信号,由主设备产生

(4)CS        – 从设备使能信号,由主设备控制

其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个SPI设备成为可能。

接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过SDO 线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8位数据的传输。

要注意的是,SCK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。

在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。

最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。

AT91RM9200的SPI接口主要由4个引脚构成:SPICLK、MOSI、MISO及 /SS,其中SPICLK是整个SPI总线的公用时钟,MOSI、MISO作为主机,从机的输入输出的标志,MOSI是主机的输出,从机的输入,MISO 是主机的输入,从机的输出。/SS是从机的标志管脚,在互相通信的两个SPI总线的器件,/SS管脚的电平低的是从机,相反/SS管脚的电平高的是主机。在一个SPI通信系统中,必须有主机。SPI总线可以配置成单主单从,单主多从,互为主从。

SPI的片选可以扩充选择16个外设,这时PCS输出=NPCS,说NPCS0~3接4-16译码器,这个译码器是需要外接4-16译码器,译码器的输入为NPCS0~3,输出用于16个外设的选择。

详细的SPI规范可参考SPI协议。

 

二、 GPIO模拟SPI的实现

   下面将结合本人项目中的经验来详细描述如何用GPIO来模拟SPI协议

项目中要求实现一块LCD为ssd1815br1的驱动,它与BB的通信使用SPI协议,由于BB上SPI总线已使用完, 因此考虑使用GPIO来模拟实现。

GPIO对应SPI引脚的关系如下:

(1)SDO     – GPIO0   (BB到LCD的数据线)

(2)SDI      – 无,因为暂时不需要BB接收来自LCD的数据

(3)SCLK     – GPIO1

(4)CS        – 接地, 使LCD一直处于使能状态。

 

接下来就是要实现SPI的协议了, SPI有4种传输模式:

用GPIO模拟SPI协议的实现[转] - 枫 - 枫之家 

开发者可根据具体设备使用的是哪种模式来实现之,我们项目种的这块LCD的模式为CPOL=1, CPHA=1.

具体实现如下:

#define SPI_DATA    GPIO0

#define SPI_CLK     GPIO1

void spi_write(char data)

{

   int8 i = 7;

   uint8 mask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

  

   for(; i >= 0; i--) {

   gpio_out(SPI_CLK, GPIO_LOW_VALUE);

   gpio_out(SPI_DATA, ((data & mask[i]) >> i));

  

   spi_delay(10);  

  

   gpio_out(SPI_CLK, GPIO_HIGH_VALUE);

    spi_delay(10);

}

}

实际上模拟SPI是很简单的事情,只要对照SPI传输模式的时序图来模拟就行了。需要注意的是一定要有个等待时间,以使数据在数据线上稳定下来,并使设备端有时间取数据。刚开始调试的时候可以适当把等待时间延长一点,当调通了SPI后在降下等待时间。

   我写的等待时间如下:

   #define spi_delay(delay)   \

   { \

       register uint32 i = 0; \

       while(i < delay) { \

            __asm{ \

                 NOP; \

                 NOP; \

                 NOP; \

                 NOP; \

              }; \

              i -= 4; \

         } \

     }

//end===================



2. 用GPIO模拟pwm协议的实现[转]:

       (1)关于PWM:脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波占空比调制用来对一个具体模拟信号的电平进行编码。

               PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。

               电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。

                pwm在LED中的应用:在LED控制中PWM作用于电源部分,脉宽调制的脉冲频率通常大于100Hz,人眼就不会感到闪烁。
       (2)GPIO 模拟PWM(PWM的一个优点是从处理器到 被控系统信号都是数字形式的,无需进行 数模转换!让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响。):

       在树莓派上,可以通过对GPIO的编程来实现PWM。

创建一个 PWM 实例:

1
p = GPIO.PWM(channel, frequency)

启用 PWM:

1
p.start(dc)   # dc 代表占空比(范围:0.0 <= dc >= 100.0)

更改频率:

1
p.ChangeFrequency(freq)   # freq 为设置的新频率,单位为 Hz

更改占空比:

1
p.ChangeDutyCycle(dc)  # 范围:0.0 <= dc >= 100.0

停止 PWM:

1
p.stop()

注意,如果实例中的变量“p”超出范围,也会导致 PWM 停止。

以下为使 LED 每两秒钟闪烁一次的示例:

1
2
3
4
5
6
7
8
9
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup( 12 , GPIO.OUT)
  
p = GPIO.PWM( 12 , 0.5 )
p.start( 1 )
input ( '点击回车停止:' )   # 在 Python 2 中需要使用 raw_input
p.stop()
GPIO.cleanup()

以下为使 LED 在亮/暗之间切换的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup( 12 , GPIO.OUT)
  
p = GPIO.PWM( 12 , 50 # 通道为 12 频率为 50Hz
p.start( 0 )
try :
     while 1 :
         for dc in range ( 0 , 101 , 5 ):
             p.ChangeDutyCycle(dc)
             time.sleep( 0.1 )
         for dc in range ( 100 , - 1 , - 5 ):
             p.ChangeDutyCycle(dc)
             time.sleep( 0.1 )
except KeyboardInterrupt:
     pass
p.stop()
GPIO.cleanup()


3. 用GPIO模拟i2c协议的实现[转]:

refer:http://www.cnblogs.com/aceheart/articles/3307477.html

refer:http://blog.csdn.net/zhenwenxian/article/details/8466886

(1)I2C总线的通信过程(见图4-8)主要包含三个主要阶段:起始阶段、数据传输阶段和终止阶段。

1. 起始阶段

在I2C总线不工作的情况下,SDA(数据线)和SCL(时钟线)上的信号均为高电平。如果此时主机需要发起新的通信请求,那么需要首先通过SDA和SCL发出起始标志。当SCL为高电平时,SDA电平从高变低,这一变化表示完成了通信的起始条件。

在起始条件和数据通信之间,通常会有延时要求,具体的指标会在设备厂商的规格说明书中给出。

2. 数据传输阶段

I2C总线的数据通信是以字节(8位)作为基本单位在SDA上进行串行传输的。一个字节的传输需要9个时钟周期。其中,字节中每一位的传输都需要一个时钟周期,当新的SCL到来时,SCL为低电平,此时数据发送方根据当前传输的数据位控制SDA的电平信号。如果传输的数据位为"1",就将SDA电平拉高;如果传输的数据位为"0",就将SDA的电平拉低。当SDA上的数据准备好之后,SCL由低变高,此时数据接收方将会在下一次SCL信号变低之前完成数据的接收。当8位数据发送完成后,数据接收方需要一个时钟周期以使用SDA发送ACK信号,表明数据是否接收成功。当ACK信号为"0"时,说明接收成功;为"1"时,说明接收失败。每个字节的传输都是由高位(MSB)到低位(LSB)依次进行传输。

I2C总线协议中规定,数据通信的第一个字节必须由主机发出,内容为此次通信的目标设备地址和数据通信的方向(读/写)。在这个字节中,第1~7位为目标设备地址,第0位为通信方向,当第0位为"1"时表示读,即后续的数据由目标设备发出主机进行接收;当第0位为"0"时表示写,即后续的数据由主机发出目标设备进行接收。在数据通信过程中,总是由数据接收方发出ACK信号。

3. 终止阶段

当主机完成数据通信,并终止本次传输时会发出终止信号。当SCL 是高电平时,SDA电平由低变高,这个变化意味着传输终止。

下面给出了模拟I2C总线进行读写的伪代码,用以说明如何使用GPIO实现I2C通信:

复制代码
#define SDA 254                         //定义SDA所对应的GPIO接口编号  
#define SCL 255                         //定义SCL所对应的GPIO接口编号  
#define OUTP 1                          //表示GPIO接口方向为输出  
#define INP 0                           //表示GPIO接口方向为输入  
/* I2C起始条件 */  
int i2c_start()  
{  
//初始化GPIO口  
set_gpio_direction(SDA, OUTP);          //设置SDA方向为输出  
set_gpio_direction (SCL, OUTP);         //设置SCL方向为输出  
         set_gpio_value(SDA, 1);                //设置SDA为高电平  
set_gpio_value(SCL, 1);                 //设置SCL为高电平  
delay();                            //延时  
//起始条件  
set_gpio_value(SDA, 0);                 //SCL为高电平时,SDA由高变低  
delay();  
}  
/* I2C终止条件 */  
void i2c_stop()  
{  
set_gpio_value(SCL, 1);  
set_gpio_direction(SDA, OUTP);  
set_gpio_value(SDA, 0);  
delay();  
set_gpio_value(SDA, 1);             //SCL高电平时,SDA由低变高  
}  
/*   
I2C读取ACK信号(写数据时使用)  
返回值 :0表示ACK信号有效;非0表示ACK信号无效  
*/  
unsigned char i2c_read_ack()  
{  
unsigned char r;  
set_gpio_direction(SDA, INP);           //设置SDA方向为输入  
set_gpio_value(SCL,0);              // SCL变低  
r = get_gpio_value(SDA);                //读取ACK信号  
delay();  
set_gpio_value(SCL,1);              // SCL变高  
delay();  
return r;  
}  
/* I2C发出ACK信号(读数据时使用) */  
int i2c_send_ack()  
{  
set_gpio_direction(SDA, OUTP);          //设置SDA方向为输出  
set_gpio_value(SCL,0);              // SCL变低  
set_gpio_value(SDA, 0);             //发出ACK信号  
delay();  
set_gpio_value(SCL,1);              // SCL变高  
delay();  
}  
/* I2C字节写 */  
void i2c_write_byte(unsigned char b)  
{  
int i;  
set_gpio_direction(SDA, OUTP);          //设置SDA方向为输出  
for (i=7; i>=0; i--) {  
set_gpio_value(SCL, 0);             // SCL变低  
delay();  
set_gpio_value(SDA, b & (1<<i));        //从高位到低位依次准备数据进行发送  
set_gpio_value(SCL, 1);             // SCL变高  
delay();  
}  
i2c_read_ack();                 //检查目标设备的ACK信号  
}  
/* I2C字节读 */  
unsigned char i2c_read_byte()  
{  
int i;  
unsigned char r = 0;  
set_gpio_direction(SDA, INP);           //设置SDA方向为输入  
for (i=7; i>=0; i--) {  
set_gpio_value(SCL, 0);         // SCL变低  
delay();  
r = (r <<1) | get_gpio_value(SDA);      //从高位到低位依次准备数据进行读取  
set_gpio_value(SCL, 1);         // SCL变高  
delay();  
}  
i2c_send_ack();                 //向目标设备发送ACK信号  
return r;  
}  
/*  
I2C读操作  
addr:目标设备地址  
buf:读缓冲区  
len:读入字节的长度  
*/  
void i2c_read(unsigned char addr, unsigned char* buf, int len)  
{  
int i;  
unsigned char t;  
i2c_start();                        //起始条件,开始数据通信  
//发送地址和数据读写方向  
t = (addr << 1) | 1;                    //低位为1,表示读数据  
i2c_write_byte(t);  
//读入数据  
for (i=0; i<len; i++)  
buf[i] = i2c_read_byte();  
i2c_stop();                     //终止条件,结束数据通信  
}  
/*  
I2C写操作  
addr:目标设备地址  
buf:写缓冲区  
len:写入字节的长度  
*/  
void i2c_write (unsigned char addr, unsigned char* buf, int len)  
{  
int i;  
unsigned char t;  
i2c_start();                        //起始条件,开始数据通信  
//发送地址和数据读写方向  
t = (addr << 1) | 0;                    //低位为0,表示写数据  
i2c_write_byte(t);  
//写入数据  
for (i=0; i<len; i++)  
i2c_write_byte(buf[i]);  
i2c_stop();                     //终止条件,结束数据通信  
}  

2)I2C总线的通信过程GPIO模拟I2C程序实现

        I2C是由Philips公司发明的一种串行数据通信协议,仅使用两根信号线:SerialClock(简称SCL)和SerialData(简称SDA)。I2C是总线结构,1个Master,1个或多个Slave,各Slave设备以7位地址区分,地址后面再跟1位读写位,表示读(=1)或者写(=0),所以我们有时也可看到8位形式的设备地址,此时每个设备有读、写两个地址,高7位地址其实是相同的。
       I2C数据格式如下:
无数据:SCL=1,SDA=1;
开始位(Start):当SCL=1时,SDA由1向0跳变;
停止位(Stop):当SCL=1时,SDA由0向1跳变;
数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数据,不可随意改变SDA;


当SCL保持为0时,SDA上的数据可随意改变;
地址位:定义同数据位,但只由Master发给Slave;
应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;
否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。


当数据为单字节传送时,格式为:
开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。
当数据为一串字节传送时,格式为:
开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。


     需要注意的是:
1,SCL一直由Master控制,SDA依照数据传送的方向,读数据时由Slave控制SDA,写数据时由Master控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反。
2,开始位“Start”和停止位“Stop”,只能由Master来发出。
3,地址的8位传送完毕后,成功配置地址的Slave设备必须发送“ACK”。否则否则一定时间之后Master视为超时,将放弃数据传送,发送“Stop”。
4,当写数据的时候,Master每发送完8个数据位,Slave设备如果还有空间接受下一个字节应该回答“ACK”,Slave设备如果没有空间接受更多的字节应该回答“NACK”,Master当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时Master放弃数据传送,发送“Stop”。
5,当读数据的时候,Slave设备每发送完8个数据位,如果Master希望继续读下一个字节,Master应该回答“ACK”以提示Slave准备下一个数据,如果Master不希望读取更多字节,Master应该回答“NACK”以提示Slave设备准备接收Stop信号。
6,当Master速度过快Slave端来不及处理时,Slave设备可以拉低SCL不放(SCL=0将发生“线与”)以阻止Master发送更多的数据。此时Master将视情况减慢或结束数据传送。

7,I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收据定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
       在实际应用中,并没有强制规定数据接收方必须对于发送的8位数据做出回应,尤其是在Master和Slave端都是用GPIO软件模拟的方法来实现的情况下,编程者可以事先约定数据传送的长度,不发送ACK,有时可以起到减少系统开销的效果。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值