概述
文章目录
- 经典的输入输出设备
- PCF8591
- 操作字
- 功能描述
- 读写源码
- 输入/输出原语
- 读操作
- 写操作
- CPU和外设交互的方式
- 忙等I/O
- 输出源码
- 输入后输出
- 中断
- 中断开销
- 中断的过程
- ARM7响应中断
- ARM7结束中断
- 中断的开销
- 中断源码
- 管态,异常和陷阱
- 异常
- 陷阱
- 管态
作品首发于个人博客
www.thedreamfish.cn
经典的输入输出设备
输入/输出设备通常都有模拟或非电组件。显然我们可以意识到cpu通过读写寄存器与设备的通信,这些设备通常有下面这些寄存器。
- 数据寄存器:保存设备待处理或已处理的数据。
- 状态寄存器:提供设备运行的各种状态寄存器。
PCF8591
这里我们使用在51单片机中学习的PCF8591进行举例,通过对其操作字进行说明
操作字
- PCF8591是具有I2C总线借口的8位AD/DA转换芯片,内部为单一电源供电(2.5~6V),典型值为5V,CMOS工艺。PCF8591有4路AD输入,属逐次比较型,内含采样保持电路;1路8位DA输出,内含DAC数据寄存器。AD/DA转换的最大速率约为11KHz。
- 在IICa总线中,器件地址必须是起始条件后作为第一个字节发送。发送给PCF8591的第二个字节被存储在控制寄存器,用于控制寄存器的功能。发送给PCF8591的第三个字节被存储到DAC数据寄存器。并使用片上D/A转换成相应的模拟电压。
- 一个A/D转换周期总是开始于发送一个有效读模式地址给PCF8591之后。A/D转换周期在应答时钟脉冲的后沿被触发。操作分四步:
- 发送地址字节,选择该器件。
- 发送控制字节,选择相应通道。
- 重新发送地址字节,选择该器件。
- 接收目标通道的数据。
功能描述
- I2C总线系统中的每一片PCF8591通过发送有效地址到该器件来激活。改地址包括固定部分和可编程部分。可编程部分必须根据地址A0 A2 A2来设置。在I2C总线协议中,地址必须是骑士条件后作为第一个字节发送。地址字节的最后一位用于设置以后数据传输方向的读/写位。
-
发送到PCF8591的第二个字节将被存储在控制寄存器。用于控制器件功能。
-
具体的寄存器配置(指令)就不详细介绍,总结如下:
adc功能配置,先后发送0x90、0x40+channel(取值0、1、2、3,总共有四个通道)、0x91即可;
dac功能配置,先后发送0x90、0x40、user_data(8bit)即可;
-
读写源码
import smbus
import time
bus = smbus.SMBus(1)
#check your PCF8591 address by type in 'sudo i2cdetect -y -1' in terminal.
def setup(Addr):
global address
address = Addr
def read(chn): #channel
if chn == 0:
bus.write_byte(address,0x40)
if chn == 1:
bus.write_byte(address,0x41)
if chn == 2:
bus.write_byte(address,0x42)
if chn == 3:
bus.write_byte(address,0x43)
bus.read_byte(address) # dummy read to start conversion
return bus.read_byte(address)
def write(val):
temp = val
temp = int(temp) # change string to integer
bus.write_byte_data(address, 0x40, temp)
if __name__ == "__main__":
setup(0x48)
while True:
print 'AIN0 = ', read(0)
print 'AIN1 = ', read(1)
tmp = read(0)
tmp = 5*(tmp/255)
write(tmp)
# time.sleep(0.3)
输入/输出原语
微处理器能够通过两种途径为输入输出提供编程支持:I/O指令和内存映射I/O。我们早先学习的X86为输入输出提供了(in和out),这些指令使得I/O设备提供了单独的地址空间。
而我们在ARM系统中,最普遍的方法是通过内存映射-即使提供I/O指令的CPU也能实现内存映射。这样是为每一个I/O设备提供了内存地址。程序使用一般的CPU读写命令来与设备通信。
读操作
#define DEV1 0x100
int peek(char *location){
return *location;
}
DEV1 EQU OX100
LDR r1,#DEV1
LDR r0,[r1]
写操作
#define DEV1 0x100
void poke(char *location, char new){
(*location)=new;
}
DEV1 EQU 0X100
LDR r1,#DEV1;
LDR r0,#8
STR r0,[r1]
CPU和外设交互的方式
忙等I/O
在程序中使用设备的最简单的方法是忙等I/O。我们知道如果cpu对一台设备执行多重操作,比如像输出设备写若干字符,他必须等待前一个操作结束后,才可以进入下一个操作。所以我们永远不可能在第一个字符串写完前就开始写第二个字符,外设永远不会进行响应。因此通过查询状态寄存器来询问设备是否空闲,这极其重要
输出源码
#define out_char 0X100
#define out_status 0x101
char *mystring = "helloworld";
char *current_char;
current_char=mystring;
while (current_char = '/0'){
poke (out_char,*current_char);
poke(out_status,1);//打开输出设备
while (peek(out_status)!=0);//外设状态寄存器为1时表示正在写
current_char++;
}
输入后输出
//当新字符被读取时,输入设备状态为1,读取后,设置为0,就可以开始新的读取
//写字符,将输出状态设置为1,启动,为0时才可以再一次输出
#define in_data 0x100
#define in_status 0x101
#define out_data 0x110
#define out_status ox111
while (1){
while (peek(in_status)==0){ //读取状态寄存器
tmp= (char)peek(in_data);
}
poke(out_data,char);
poke(out_status,1);
while (peek(out_status)!=0);
}
中断
我们可以发现,使用忙等将会造成cpu陷入一种极其麻烦的状态,cpu不得不花费大量的精力去不断查询寄存器的状态,无法去处理其他可能更重要的事情。为了使得cpu可以在这一过程中控制其他的I/O设备,或者决定下一个发送到设备的输出数据或处理最后一个接受输入时,进行计算操作。
笔者在参加电子设计竞赛中就遇到过,如果不对ADC/DAC进行中断处理,同时不使用使用DMA采样技术,不仅会使得cpu在运行中出现奇怪的delay延时问题,还会导致采样率精度不高。尤其是在使用DAC输出时,只有使用中断和dma处理后,我们才可以发现原来这样可以极大程度的减少。
显然有些时候,中断虽然具备实时性,快速反应的一系列优点,但是我们知道实际上我们不能总是打断cpu的工作,高频次的打断cpu工作,会破坏流水线等一系列工作的稳定性。
中断开销
中断的过程
-
cpu在指令的开始会检查未决的中断。它响应优先级最高的中断。
-
设备在接受中断应答信号后,会向cpu发送中断向量。
-
cpu用向量作为索引在中断向量表中查找中断服务程序的地址。并保护中断现场,保存cpu寄存器的状态。
-
软件将会驱动保存其他cpu状态,之后会执行设备需要的操作,最后执行中断
-
cpu会恢复pc和其他自动保存的状态,返回被中断的代码继续执行。
ARM7响应中断
- 保存适当的pc值。
- 将cpsr复制到spsr
- 强制cpsr中的位记录下中断
- 强制pc指向中断向量
ARM7结束中断
- 恢复pc的正确值
- 用spsr恢复cpsr
- 清除中断禁用标志
中断的开销
中断导致分支的损失。需要额外的时钟周期来应答中断,中断程序需要不断保护和恢复那些不被中断自动保存的寄存器。硬件查找中断和查找中断向量所需要的时间不能由程序员决定,
中断源码
/*
字符串io_buff保存那些已经读入但是未能写出的字符队列。
当新字符被读取时,输入设备状态为1,读取后,设置为0,就可以开始新的读取
写字符,将输出状态设置为1,启动,为0时才可以再一次输出
我们已经具有下面这些函数
*/
#define buf_size 8
void add_char(char x);
char remove_char();
bool enpty();
bool full();
#define in_data 0X100
#defien in_status 0x101
#define out_data 0x110
#define out_status 0x101
void input_hander(){
char tmp;
tmp=peek(in_data);
add_char(tmp);
poke (in_status,0);
if (nchar()==1){
poke(out_data,remove_char());
poke(out_status,1);
}
}
如果io_buff有字符在等待,输出设备可以自行开启一个输出设备
否则必须有输入设备在其中断程序中启动输出设备。
//输出一个字符串后触发中断
void output_handere(){
if (!empty()){
poke(out_data,remove_char());
poke(out_atatus,1);
}
}
//另一种写法
void input hander(){
achar=peek(in_data);
where=1
poke(in_status,0);
}
int main(){
while(1){
if (where){
poke(out_data,achar);
poke(out_status,1);
where=0;
}
}}
//1.输入输出的速度可能不一样
//前台参与了工作
管态,异常和陷阱
异常
异常是一种内部可以检测的错误。一个经典的例子是0做除数,未定义的Resets指令和非法的内存访问。异常必须有优先级,因为一个操作可能会产生很多异常,异常的向量好通常由体系结构预先定义;被用于索引异常处理程序表。
陷阱
软件中断,通常会进入管态,如果用户和管态之间的接口没有被仔细设计,用户程序可能偷偷进入管态进行破坏。
管态
程序通常运行在用户态,管态拥有更高的特权。例如,允许动态更改内存单元的物理地址。此模式下,cpsr后五位均设置为1。
最后
以上就是仁爱万宝路为你收集整理的嵌入式设计输入输出I/O设备的典型方法与问题-忙等和中断经典的输入输出设备输入/输出原语CPU和外设交互的方式的全部内容,希望文章能够帮你解决嵌入式设计输入输出I/O设备的典型方法与问题-忙等和中断经典的输入输出设备输入/输出原语CPU和外设交互的方式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复