4.3 驱动程序的系统调用接口
为对处于总线地址0x4000_0000的DSP板进行操作,首先应用open打开设备,该设备可以通过mknod建立(本例建立的是/dev/hpi)。然后mmap完成映射。
Int dev_hpi_open(str_HPI *ss)
{size_t length=1024;
int i;
if((*ss).hpi_number==0)
{
(*ss).hpi_fd=open("/dev/hpi",O_RDWR);
}
if((*ss).hpi_fd==-1)return -1;
(*ss).hpi_mmap_start =mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,((*ss).hpi_fd),0); //获得映射区内存的起始地址
return 0;
} //dev_hpi_open
mmap的作用是将文件内容映射到内存中。函数的原形及各参数定义如下:
*mmap(void *start,size_length,int prot,int flags,intfd,off_t offset)
start指向欲对应的内存地址,size-length的含义是要映射的量;prot代表映射区域的保护方式;flag会影响映射区域的各种特性;fd为文件描述符;offtoffset代表文件的偏移量,通常设置为零。
示例程序中的结构体变量ss用来总知各种变量。通过mmap可以获得映射后的内存地址,用(*ss).hpi_mmap_start表示。
一旦获得了这个起始地址,就能对0x4000_0000起始的总线地址进行操作,因为映射已经完成,对(*ss).hpi_mmap_start的操作就是对0x4000_0000起始的总线地址进行操作,而DSP板HPI接口的控制线正是在这个位置。这样就实现了物理地址和用户空间的转换。
4.4 用户程序接口部分
下面以HPI接口读写中最复杂的自增读方式用户程序为例说明用户接口程序的设计过程。要完成自增读的操作,对于HPI一侧,假设采用软件握手的方式。要完成的工作如下:
首先读HPIC以查询其中的HRDY位是否为1,如果为1则表示DSP中数据已经备妥。然后写HPIA以告诉DSP从什么位置开始进行自增读。接着将HPIC的FETCH位置1以刷新写入。再读HPIC以查询其中的HRDY位是否为1,如果为1则表示DSP中数据已经备妥。最后从HPID中读取数据。
对于ARM一侧,要对HPIC、HPID、HPIA寄存器进行读写必须满足HPI接口的定义,具体如下(以自增读为例):
读前半字节(高16位)时,HCNTL0=0、HCNTL1=1、HR/W=1、HHWRL=0。
读后半字节(低16位)时,HCNTL0=0、HCNTL1=1、HR/W=1、HHWRL=0。
从硬件的原理图可知,这些HPI的控制口线分别与ARM的一部分地址总线连接。具体为HCNTL0----A2、HCNTL1----A3、HR/W------A4、HHWIL-----A5。
宏定义过程如下:
#define HPIC_R_F(HPI_VA_BASE)*((unsigned long*)((HPI_VA_BASE)+0x00000004+DSPNUMBER))
//读HPIC第一半字
#define HPIC_R_S(HPI_VA_BASE)*((unsigned long*)((HPI_VA_BASE)+0x0000000C+DSPNUMBER))
//读HPIC第二半字
等等,只要改变在HPI_BA_BASE基础上增加的数字就可以获取对控制口线的操作。
在这里HPI_VA_BASE将由映射得到的用户空间虚拟地址代替,所以如果“自增模式读HPID第一半字”那么就可求满足前文提到的HCNTL0=0、HCNTL1=1、HR/W=1、HHWRL=0,也就是要满足地址位A2=0、A3=1、A4=1、A5=0,所以只要在HPI_VA_BASE的基础上加0x0000_0006就可以了。要注意的是ARM处理器的地址是32位。所以是加上0x0000_0006而不是0x0000_0018。
另外,还有二点需要说明:
通过改变宏定义中的DSPNUMBER常量可以控制地址总线A6、A7、A8。通过这3个总线组合并通过简单地址译码电路就可以完成对多块DSP板的读写。在硬件电路中可以定义为0。
A4(HR/W)同时还用做SN74LVTH16245的方向控制。读的时候A4=1,此时SN74LVTH16245的数据从A-->B;反之,则从B-->A。
下面给出程序中的自增读和注释部分:
int dev_hpi_auto1(str_HPI *ss)
{volatile unsigned long dsp_addr_hign_read_auto;//定义各种中间变量
volatile unsigned long dsp_addr_low_read_auto;
volatile unsigned long dsp_data_hign_read_auto;
volatile unsigned long dsp_data_low_read_auto;
volatile unsigned long dsp_add_temp;
int i;
volatile unsigned long data_length;
//---read hpic----the host polls the HPIC for HRDY=1
volatile unsigned long polLTEst;
polltest=HPIC_R_F((*ss).hpi_mmap_start);
while((polltest&0x00000008)!=0x00000008)
{polltest=HPIC_R_F((*ss).hpi_mmap_start);
}
dsp_add_temp=((*ss).hpi_dsp_add);//从应用程序传过来的参数,指明希望从DSP的哪一个地址读起
dsp_addr_low_read_auto=((dsp_add_temp)&0x0000ffff)+((dsp_add_temp)<<16); //完成数据转换
dsp_addr_hign_read_auto=((dsp_add_temp)&0xffff0000)+((dsp_add_temp)>>16);
//---write dsp s addr to HPIA
HPIA_W_F ((*ss).hpi_mmap_start)=(dsp_addr_hign_read_auto);
HPIA_W_S((*ss).hpi_mmap_start)=(dsp_addr_low_read_auto);
//--------write hpic----------to FETCH bit
HPIC_W_F((*ss).hpi_mmap_start)=0xfff8fff8;
HPIC_W_S((*ss).hpi_mmap_start)=0xfff8fff8;
//---read dsp s data from HPID,autoincrement mode
data_length=(*ss).hpi_dsp_data_length;//从应用程序传过来的参数,指明期望读取多少个字
for(i=0;i<=data_length;i++)
{//---read hpic----the host polls the HPIC for HRDY=1 again
polltest=HPIC_R_F((*ss).hpi_mmap_start);
while((polltest&0x00000008)!=0x00000008)
{polltest=HPIC_R_F((*ss).hpi_mmap_start);
}
dsp_data_hign_read_auto =HPID_R_F_A((*ss).hpi_mmap_start); //读第一个半字。
dsp_data_low_read_auto =HPID_R_S_A((*ss).hpi_mmap_start); //第二个个半字
{(*ss).buffer [(i)]=(dsp_data_hign_read_auto&oxffff0000)+(dsp_data_low_read_auto&0x0000ffff);
//数据拼接,放入结构体,回传给调用的用户程序。
}
}
} //dev_hpi_read_auto(str_HPI *ss)
5 结束语
本文通过一个实例说明了如何实现在Linux操作系统下ARM体系结构的处理器与DSP的数据通信。给出了接口部分的硬件处理和部分驱动程序。
在某款智能仪表的研发过程中,给出一个简单的地址译码电路对二块(或更多)DSP板进行交替读写,并以自增读方式进行操作,当ARM主频为180MHz,DSP主频为125MHz时,对DSP数据的读写速度可以达到每毫秒1k的32位字。
上一篇:基于RTLinux的实时控制系统