什么是GPIO? GPIO(General-Purpose IO ports)即通用IO口。
在嵌入式系统中常常有数量众多,但是结构却比较简单的外部设备/电路,对这些设备/电路,有的需要CPU为之提供控制手段,有的则需要被CPU用做输入信号。
而且,许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了,比如控制某个LED灯亮与灭;或者通过获取某个管脚的电平属性来达到判断外围设备的状态。对这些设备/电路的控制,使用传统的串行口或并行口都不合适,所以在微控制器芯片上一般都会提供一个“通用可编程IO接口”,即GPIO。接口至少有两个寄存器,即“通用IO控制寄存器”与“通用IO数据寄存器”。数据寄存器的各位都直接引到芯片外部,而对这种寄存器中每一位的作用,即每一位的信号流通方向,则可以通过控制寄存器中对应位独立地加以设置,比如可以设置某个管脚的属性为输入、输出或其他特殊功能。
在实际的MCU中,GPIO是有多种形式的。比如,有的数据寄存器可以按照位寻址,有些却不能按照位寻址,这在编程时就要加以区分。比如传统的8051系列,就区分成可位寻址和不可位寻址两种寄存器。另外,为了使用方便,很多MCU的 GPIO接口除去两个标准寄存器必须具备外,还提供上拉寄存器,可以设置IO的输出模式是高阻,还是带上拉的电平输出,或者不带上拉的电平输出。这使得在电路设计中,外围电路就可以简化不少。
All the GPIO pins can be reconfigured to provide alternate functions, SPI,PWM, I²C and so.
GPIOs are used in:
GPIO capabilities may include:
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.
All the GPIO pins can be reconfigured to provide alternate functions, SPI,PWM, I²C and so.
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种传输模式:
开发者可根据具体设备使用的是哪种模式来实现之,我们项目种的这块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===================(1)关于PWM:脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。
PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。
电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。
在树莓派上,可以通过对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()
|
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<//从高位到低位依次准备数据进行发送 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) 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 ) 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,有时可以起到减少系统开销的效果。