0 引言
嵌入式系统广泛应用于生活中的各行各业,嵌入式软硬件复杂度也在不断增加,嵌入式系统开发与维护变得越来越复杂,然而嵌入式系统的开发与维护工具发展相对很滞后。为了提高嵌入式系统开发与维护的效率,发展嵌入式开发与维护工具是非常重要的。通过基于 Qt 平台开发各种开发维护工具,实现嵌入式开发与维护的平台化,是当前嵌入式开发和维护的趋势[1-2]。当一款新的设备出厂之后,后期维护成了人们越来越关注的问题,为了延长设备使用周期,节约生产成本,使设备创造更大的价值,设备的操作与维护越来越受到人们的重视。
每当设备出现问题之后,就会调试底层驱动,这时可能需要调整寄存器的设置。面对这个问题,当前国内外开发与维护人员的通常做法是直接在程序里面修改硬件寄存器的数据,然后重新编译程序,下载到设备,以此来检验设备运行情况[3-4]。但是这种方法比较麻烦,效率低下,不利于维护。为了提高开发人员开发和维护设备的效率,需要设计一个可视化工具,从而可以直接方便地对硬件内部寄存器进行修改与调试。这个工具拥有图形化界面,可以直接读写下位机硬件寄存器的数值,首先输入硬件寄存器要传入的物理地址(这个物理地址可以通过芯片手册确定,在驱动程序里面要通过映射为虚拟地址才能使用),然后根据要求来读写下位机任意硬件寄存器的数值。
1 系统设计框架
该系统采用嵌入式 Linux 操作系统作为开发的核心,包括三部分,分别为客户端图形化界面、服务器端和硬件设备。客户端用来与用户进行交互,如输入下位机硬件寄存器地址和数据,服务器端用来接收上位机客户端用户传来的数据或者向客户端发送数据,底层驱动用来操作硬件设备内部寄存器的数值。本系统采用 Linux 网络通信的方式连接上位机和下位机,使交互更加方便和高效。整个系统框架如图1所示。
图1 系统框架
2 Linux 网络通信设计
该网络通信系统包括服务器端和客户端两个部分。服务器端实现对数据的采集和发送,以及通过TCP 协议进行网络传输;客户端主要是接收服务器传输过来的数据并进行图形化显示。服务器端和客户端使用 TCP 协议进行网络通信的具体流程图如图2所示。
图2 TCP网络通信流程图
客户端和服务器端的交互过程如下:服务器端先初始化 socket,分配文件描述符;然后调用bind() 将套接字与本地IP 地址和端口绑定;接着调用 listen() 对端口进行监听,并设置监听队列的大小;继续调用accept()阻塞,等待客户端连接[5]。如果有客户端初始化一个socket()后调用 connect()向服务器端发送连接请求,若经过三次握手,则连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后调用 close() 关闭连接,一次交互结束。
2.1 服务器端设计
服务器端通过三次握手与客户端建立连接之后,将驱动端映射到内核空间的数据读取出来并且发送到客户端,或者将客户端要发送的数据接收到服务器端并发送给驱动端。
下位机服务器端的部分程序如下:
while(1){
//服务器阻塞,直到获得连接请求并建立连接
int socklis=accept(sock,(struct sockaddr *)&.clientAddr,
&.len);
if(socklis<0){
perror("accept")
exit(1);
}
While(1){// 清空数组
memset(buff,0,1024);
//从客户端接收数据
n=recv(socklis,buff,1024,0);
if(n<0){
perror("recv");
return —1
}
msgtype=*(unsigned int *)buff;
switch(msgtype){
case REGISTER_CTRL:{
structregister_ctrl regmsg;
structregister_info reg;
//内存拷贝
memcpy(&.regmsg,buff,sizeof(regmsg));
reg.addr=regmsg.addr;
reg.data=regmsg.data;
if(regmsg.cmd==1){
ioctl(fd_reg,REG_READ,);
printf("read reg:addr=%#x,data=%#x ",reg.
addr,reg.data);
//发送数据到客户端
send(socklis,&.reg,sizeof(reg),0);
}else if(regmsg.cmd==0){
printf("write reg:addr=%#x,data =%#x ",reg.addr,
reg.data);
ioctl(fd_reg,REG_WRITE,&.reg);
}
break;
}
}
}
}
2.2 客户端设计
为了满足人性化要求,本文设计了一个 Qt 客户端图形化界面,从而方便用户操作和读写数据。Qt 是一个跨平台的 C++ 图形用户界面应用程序框架,由挪威 Troll- Tech公司出品,目前包括 QtCreator、Qt Embedded 、Qt Designer等快速开发工具[6]。其中 Qt Creator 是一个全新的、完整的、轻量级的图形开发平台,它可以按照设计人员的意愿建立图形用户界面,随时进行显示和修改,具有良好的适应性,保证了不同平台之间设计的兼容性[7]。
在客户端图形化界面中,首先设计一个“寄存器地址”输入框,用来输入用户需要操作的下位机寄存器物理地址;然后设计一个“寄存器数据”输入框,用来显示从下位机读取的存储在该寄存器里面的数据,或者向下位机发送用户需要写入到该寄存器中的数据;接着在旁边设计一个“读”数据复选框和一个“写”数据复选框,用来发送“读”或者“写”命令;最后在界面下端设计一个“确定”按钮,来确认需要执行的操作。Qt 图形化界面如图3所示。该界面清晰明确、步骤简单、操作方便、简洁高效,大大缩短了用户查看和编辑下位机设备硬件寄存器中数据的时间,提高了用户调试和维护设备的效率。
图3 Qt图形化界面
在Qt中通过使用Linux系统调用中的 TCP协议实现客户端与服务器端的连接,并进行数据读取、发送以及显示,按下“确定”按钮,建立网络通信,开始进行数据的读取、发送和显示。
上位机客户端部分应用程序如下:
void MainWindow::on_send_clicked(){
struetregister_ctrl reg;
structregister_info reg_rsp;
reg.msgtype=REGISTER CTRL;
reg.addr=ui->addr->text().toUInt(NULL,16);
reg.data=ui->data->text().toUInt(NULL,16);
if(ui->read->isChecked()){
reg.cmd=1;
qDebug("read");
}
if(ui->write->isChecked()){
reg.cmd=0;
qDebug("write");
}
//发送数据到服务器端
send(m_fd_client,&.reg,sizeof(reg),0);
if(reg.cmd = =1){
//从服务器端接收数据
recv(m_fd_client,&.reg_rsp,sizeof(reg_rsp),0);
ui->data->setText(QString::number(reg_rsp.data,
16)):
}
}
3 底层驱动端设计
系统在运行时,外设的I/O 内存资源的物理地址是已知的,由硬件设计决定,但是 CPU 通常并没有为这些已知的外设I/O内存资源物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后根据映射所得到的核心虚拟地址范围通过访内指令访问这些I/O内存资源[8]。
嵌入式处理器访问外设都是以地址指针的形式访问,也就是说要想访问外设,必须知道这个外设的物理地址。在 Linux 系统中,不管是在用户空间还是内核空间,一律不允许直接访问外设的物理地址,要想访问需要提前将物理地址映射到内核虚拟地址或者用户虚拟地址上,将来程序访问用户虚拟地址或者内核虚拟地址就是在访问物理地址[9]。将Linux 系统4G虚拟地址空间划分如下,用户虚拟地址为0x00000000~0xBFFF FFFF(0G~3G),内核虚拟地址为0xC0000000~0xFFFF FFFF(3G~4G)[10]。一个物理地址可以有多个虚拟地址,一个虚拟地址不能对应多个物理地址。如果要将物理地址映射到内核虚拟地址上,可以使用ioremap() 函数;若要解除地址映射,可以使用iounmap() 函数。Linux 地址映射机制如图4所示。
图4 Linux 地址映射机制
驱动端部分程序如下:
static longreg_ioctl(struct file *file,unsigned int emd,unsignec long arg){
//定义内核缓冲区
structreg_info kreg;
unsigned long *gpiobase;
//拷贝用户缓冲区到内核
copy_to_user((struct reg_info *)arg,&kreg,sizeof
(kreg));
//将外设的物理地址映射到内核虚拟地址上
gpiobase=ioremap(kreg.addr,4);
//解析命令,操作硬件
switch(cmd){
case REG_READ:
kreg.data=*gpiobase;
copy_to_user((struct reg_info *)arg,&kreg,sizeof
(kreg));
break;
case REG_WRITE:
*gpiobase=kreg.data;
break;
}
//解除地址映射
iounmap(gpiobase);
return 0;
}
4 实验结果
本系统使用基于Cortex-A53 架构处理器的S5P6818 开发板上面的点阵LED 灯来验证实验结果。首先在下位机运行Linux 系统,点亮第一个LED灯,将第二个和第三个LED灯关闭。然后查看芯片手册,将物理地址为0xC001C000 的32位寄存器的bit[12] 、设为低电平,bit[11] 和bit[7] 设为高电平。对此,向该寄存器写入数据0x880,并选中右边的“写”复选框,点击“确认”按钮(如图5所示)后,发现开发板第一个LED 灯被点亮,验证成立,如图6所示。接着选中右边的“读”复选框,再次点击“确认”按钮,发现寄存器数据显示为0x880(如图7所示),因为读取的正是刚才写入到该寄存器的数值。
图5 向寄存器写数据
图6 点阵LED被点亮
图7 从寄存器读数据
结语
本文所设计的寄存器读写器工具,只需知道设备的硬件寄存器物理地址就能快速准确地读写任意硬件寄存器的数值,操作简单方便,快速高效,为设备操作与维护提供了一种有效的解决方案。特别是当系统逻辑比较复杂时,图形化界面的调试工具可以大大节省用户发现问题的时间,能够让用户方便快速地处理设备中出现的各种问题,从而更好地维护产品和设备。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !