具体实施方式
下面根据附图和实施例对本发明作进一步详细说明:
如图1所示,是超声诊断设备的硬件框图,其中系统启动引导工具,操作系统内核,根文件系统,应用程序和用户存储数据存放在NANDFLASH存储器中,每个组件有自己固定的存储区域,存储区域的划分参见图2。而FPGA配置数据存放在单独的SPI FLASH存储器中,这样更方便FPGA的配置和使用。当然,FPGA配置数据也可以像其他组件一样存放在左侧的NAND FLASH存储器中,只是在使用时需要CPU先从中读出,然后配置给FPGA使用。
超声诊断设备的可更新组件通常可以包括系统启动引导工具bootloader、操作系统内核、根文件系统、应用程序、用户存储空间、FPGA配置数据等。如下面的枚举定义所示,实际使用时还可以根据需要包含更多的内容(如DSP,CPLD等模块的程序)。
enum TARGET_ID /**<更新组件标识ID*/
{
TARGET_NONE=0,
BOOTLOADER, /**<系统启动引导工具*/
KERNEL, /**<操作系统内核*/
ROOT_FS, /**<根文件系统*/
APP, /**<应用程序*/
USER_FS, /**<用户存储空间*/
FPGA_CONFIGURE_DATA, /**<FPGA配置数据*/
TARGET_NUM
};
该代码定义了一个TARGET_ID枚举类型,括号内包含了软件所有可以更新的组件,每个组件对应一个固定的ID,组件总数为TARGET_NUM。每个组件都有自己对应的更新数据(文件压缩包或者二进制镜像文件数据)。
如图3所示,本发明将所有待更新组件的更新数据制作成一个固定格式的更新包文件,从而达到一次完成所有更新的目的。该更新包也可只包含某个特定的待更新组件。整个更新包文件共分为四个部分,第一部分是CRC校验码。将更新包内所有的更新组件对应的更新数据按32位分割并相异或得到。保存在文件开始处,在更新时重新计算并与该值比较,如果两个结果不同,则说明更新包在传输过程中发生错误或者被人为修改,应放弃更新,重新获取更新包文件。第二部分是文件头记录部分。记录下待更新的机器代码和待更新的组件总数。另有两个字节的空间保留,可用来存储其他需要的数据。第三部分是更新组件记录部分。分别记录下各待更新组件的ID和更新数据的大小。更新组件ID由上面代码所述的TARGET_ID枚举值决定。如果存储值不在TARGET_ID所设定的范围内,应当拒绝更新。第四部分是各更新组件的数据。各块数据的大小在第三部分中的对应记录中存储。保证各块更新数据不会发生存储错误。
如图4所示,更新包检测程序运行在待更新的超声诊断设备上,通常表现为系统软件的一个独立的命令工具。该工具程序运行后循环探测是否有USB设备插入,若探测到,则挂载该USB设备到超声诊断设备上。判断该USB设备中是否有符合格式的更新包文件。若无更新包文件,则返回继续探测USB设备插入。若有满足格式的更新包文件,则启动系统更新过程,进入软件更新阶段。
更新工具程序检测到可用的更新包之后即可启动系统更新过程。启动后,用户可选择要使用的更新包文件。接着程序读取更新包文件的内容。然后程序分别判断设备标识ID和待更新目标组件ID是否合理,若二者均符合要求,则重新计算CRC校验码并与更新包文件内的CRC校验码比对。相同时表示更新包没有异常,随后执行更新过程,否则放弃更新,并返回对应的错误标识码。
如图5所示,执行更新过程需要先关闭应用程序,然后擦除存储器上待更新位置的数据,最后写入更新数据。更新程序执行过程中,超声诊断设备的界面上有进度提示信息,并警示用户此时不可执行其他操作或切断电源,否则可能导致系统无法正常完成更新过程,或更新后不能正常工作。整个过程执行完毕后,返回成功执行的标识码。设备标识判断、待更新组件判断和CRC校验码判断由统一的更新包检测程序完成。更新包检测程序首先打开更新包文件;然后读出设备标识,并与当前机器的实际标识对比,若不一致则中止更新过程,返回设备标识错误信息;若前者成功,再读出待更新组件的标识(该标识可能有多个),并分别判断是否属于TARGET_ID中定义的组件标识,若有不一致者,则中止更新过程,返回更新组件标识错误信息;若前者成功,再重新计算文件的CRC校验码,并与更新文件中保存的CRC校验码对比,若不一致则中止更新过程,返回CRC校验码错误信息。若前面三个判断均成功的话,则开始执行更新过程。
如图6所示,执行更新过程需首先关闭当前超声诊断设备的应用程序,并显示提示信息,告知用户更新正在进行,请勿关闭电源。然后根据更新文件中更新组件标识分别执行对应的更新程序,从而分别完成各不同组件的更新过程。图6中显示了系统所有组件的更新过程,实际使用中不一定全部包括6个组件的更新。使用时只需将要更新的组件制作在更新文件中,在系统更新过程中,软件会判断更新文件内包含的组件,并执行所包含组件对应的更新过程。
更新应用程序组件的过程如下。
第一步,由CPU中运行的操作系统打开USB外设中的整个更新文件,并映射到内存空间中以便操作。
int fd=open(filename,O_RDONLY);
//open是linux下打开文件或设备的函数;filename是更新文件名,由更新操作者输入;O_RDONLY表示以只读方式打开。fd代表打开的文件的描述符。
void*update_pack=mmap(NULL,len,PROT_READ,MAP_SHARED,fd,0);
//mmap是内存映射函数,将一个文件映射到内存空间中;NULL表示由系统寻找一个足够大小的空间映射目标文件;len为更新文件的长度,以字节为单位;PROT_READ表示映像后的内存可读;MAP_SHARED表示对内存映像文件所做的修改都将被保存到对应的文件中;fd代表之前打开的文件的描述符,表示将它代表的文件进行映射;0表示从文件的开头进行映射。update_pack是一个指针,指向映射了更新文件的内存地址。使用mmap的好处是可以使用指针和memcpy等内存操作函数,而不必使用open/read/write等IO函数,方便使用。
第二步,从映射后的内存中读出应用程序组件的更新数据,并保存到一个临时文件中(这样可以避免占用过多内存)。
FILE*fp=fopen(″application.tar.gz″,″w″);
//fopen是打开一个特定文件的函数;application.tar.gz是临时文件名,此名称可自己决定,.tar.gz表示这是一个压缩文件包,因为应用程序组件的更新数据是一个压缩后的文件;w表示打开的文件可写入,若文件不存在则自动创建,若已存在则清空,等待写入新内容。fp是一个文件类型指针,指向该打开的临时文件。
fwrite(base,buflen,1,fp);
//fwrite是执行写入的函数;base是一个指针,指向应用程序组件在内存映像文件中的保存地址,该地址由图3所示的更新文件中的更新组件记录部分来记录,是由update_pack指向的地址加上更新文件中应用程序组件之前的各组件的大小以及文件头大小得到(参见图3);buflen表示应用程序更新组件的大小,同样由图3所示的更新包文件中的更新组件记录部分决定;1表示将base开头的buflen字节数据写入1次到fp指向的文件中;fp是文件类型指针,指向之前打开的临时文件,表示写入到该指针指向的文件中。
第三步,删除系统中保存应用程序的区域。
rm-r/usr/local/app
//rm是linux删除文件或目录的命令;-r表示递归的删除一个目录,即删除目录及其下的所有子目录和文件;/usr/local/app是保存应用程序的位置;执行后将删除app目录及其下的所有内容。
第四步,将保存在临时文件中的更新数据读出,并拷贝到系统中保存应用程序的区域。
tar xvzf application.tar.gz;
//tar是linux处理压缩文件的命令;xvzf表示解压缩后面的文件,并在解压时将包内文件列表显示出来;application.tar.gz是要解压缩的文件名。解压后将在当前目录下自动生成一个application目录,保存解压后的数据。
cp-rapplication/*/usr/local/app/
//cp是linux拷贝文件或目录的命令;-r表示递归的拷贝一个目录,即拷贝目录及其下的所有子目录和文件;application/*是源目录,即待拷贝的目录;/usr/local/app是目标目录,即拷贝后放置的目录。执行后会将application/*下的所有目录、子目录和文件拷贝到/usr/local/app/目录下。/usr/local/app/目录就是我们保存应用程序数据的目录。
第五步,删除临时文件。
rm-r application/
//递归的删除application/目录,该目录是之前解压缩时自动生成的目录。unlink (″application.tar.gz″);
//unlink是linux删除文件的函数,执行后删除临时文件application.tar.gz。第六步,输出完成信息,返回重新启动应用程序的标识。
printf(″Application Refresh Done!\n″);
//printf是C/C++输出信息到标准输出(显示器)的命令,将后面引号内的信息输出到显示器上,提示用户应用程序组件更新完成。
return SCUS_APP_RESTART;
//return是程序返回语句;SCUS_APP_RESTART是程序定义的宏,表示一个唯一固定的常数,代表需重新启动应用程序的含义,更新结果验证程序接到该返回值后会重新启动应用程序。
更新系统内核组件的过程如下:
第一步,由CPU中运行的操作系统打开USB外设中的整个更新文件,并映射到内存空间中以便操作。
int fd=open(filename,O_RDONLY);
void*update_pack=mmap(NULL,len,PROT_READ,MAP_SHARED,fd,0);
//open函数打开更新文件;mmap函数将文件映射到内存空间中,update_pack指向映射后的内存地址。
第二步,从映射后的内存空间读出内核组件的更新数据,并保存到一个临时文件中。
FILE*fp=fopen(″kernel.img″,″w″);
fwrite(base,buflen,1,fp);
//fopen打开一个临时文件kernel.img,w表示文件可写入,kernel.img是自定义的一个文件名;fwrite函数将内核的更新数据写到kernel.img中,base指针指向内核更新数据在内存映像文件中的保存地址,该地址由update_pack指向的地址加上更新文件中内核之前的组件的大小以及文件头大小得到;buflen是内核组件更新数据的长度,以字节为单位,由更新文件中更新组件记录部分记录。执行后内核更新数据被写入到临时文件kernel.img中。
第三步,擦除NAND FLASH中保存当前内核的区域,由于当前内核已经运行于内存中,因此可以将保存在NAND FLASH上的内核文件擦除。
flash_eraseall/dev/mtd2;
//flash_eraseall命令是mtd-utils工具提供的,mtd-utils是linux下专门用于操作FLASH等芯片的工具包。下载mtd-utils-1.0.0.tar.gz软件包,编译后即产生flash_eraseall、nandwrite等可直接执行的命令文件,将这两个文件拷贝到目录/sbin/下,即可在需要时直接使用。/dev/mtd2是linux下的一个设备,对应于图1中NAND FLASH上的一块分区,分区地址是0x00100000-0x00300000(关于地址参加图2所示关于NAND FLASH的分区划分)。二者的对应关系是在编译系统内核的时候设定的,编译内核时在linux/arch/下找到所使用CPU的配置文件,在内部添加上设备同地址的对应关系即可。比如采用型号为AT91SAM9261的arm芯片,则只需在/linux/arch/arm/mach-at91/board-sam9261ek.c中将ek_nand_partition结构数组的第三个元素修改为
{
.name=″Linux Kernel″,//分区名字标识
.offset=1024*1024,//偏移量1M,即从地址0x00100000开始
.size =2*1024*1024,//大小2M字节
}
这样编译内核后,就将设备/dev/mtd2同0x00100000-0x00300000区域对应起来。擦除/dev/mtd2就表示擦除NAND FLASH上地址为0x00100000-0x00300000的一块分区,也即系统内核的保存位置。其他设备和分区的对应只需按上面方式修改对应的数组元素即可。第四步,将临时文件中的更新数据写入到NAND FLASH中保存内核数据的指定区域。
nandwrite/dev/mtd2kernel.img;
//nandwrite命令也是mtd-utils工具提供的(见第三步),是写入数据到NAND FLASH中的命令;/dev/mtd2是要写入的设备,对应于保存内核数据的区域;kerne.img是要写入的文件,即将该文件的数据写入的/dev/mtd2中,该文件保存着要更新的内核组件数据。
第五步,删除临时文件。
unlink(″kernel.img″);
//unlink是linux删除文件的函数,执行后删除临时文件kernel.img。
第六步,输出完成信息,返回重启操作系统的标识。
printf(″Kernel Refresh Done!\n″);
//输出信息,提示用户内核组件更新完成。
return SCUS_SYS_REBOOT;
//返回SCUS_SYS_REBOOT表示需重新启动操作系统,更新结果验证程序接到该返回值后会重新启动操作系统。
更新FPGA配置数据的过程需要根据FPGA配置数据的实际保存位置来定。如果FPGA配置数据保存在普通的外部存储器上(如图1所示),则其更新方法与系统内核的更新方法类似。但目前更多的超声诊断设备产品将FPGA配置数据放置在单独的一片SPI FLASH存储器中,这样更方便FPGA的配置和使用,也是推荐采用的方法。此时NAND FLASH中对应保存FPGA配置数据的分区将保留空白,而改由单独一片SPIFLASH来保存(如图1所示)。此时更新过程如下:
第一步,由CPU中运行的操作系统打开USB外设中的整个更新文件,并映射到内存空间中以便操作。使用mmap的好处是可以使用指针和memcpy等内存操作函数,而不必使用open/read/write等IO函数,方便使用。
int fd=open(filename,O_RDONLY);
void*update_pack=mmap(NULL,len,PROT_READ,MAP_SHARED,fd,0);
//open函数打开更新文件;mmap函数将文件映射到内存空间中,update_pack指向映射后的内存地址。
第二步,从映射后的内存中读出FPGA配置数据组件的更新数据,并保存到一个临时文件中,这样可避免占用过多内存。
FILE*fp=fopen(″fpga_configure.dat″,″w″);
fwrite(base,buflen,1,fp);
//fopen打开一个临时文件fpga_configure.dat,w表示文件可写入,若文件不存在则自动创建,若已存在则清空文件,fpga_configure.dat是自定义的一个文件名;fwrite函数将FPGA配置数据的更新数据写到fpga_configure.dat中,base指针指向FPGA配置更新数据在内存映像文件中的保存地址,该地址由update_pack指向的地址加上更新文件中FPGA配置数据之前的各组件的大小以及文件头大小得到;buflen是FPGA配置数据组件更新数据的长度,以字节为单位,由更新文件中更新组件记录部分记录。执行后FPGA配置更新数据被写入到临时文件fpga_configure.dat中。
第三步,打开并使能SPI接口。
int spi_fd=open(/dev/spi0,O_RDWR|O_SYNC);
//open是linux打开设备或文件的函数,/dev/spi0是一个SPI总线接口,该接口同SPI FLASH相连,O_RDWR表示打开后SPI总线接口可读可写,O_SYNC表示以同步的方式打开,即对接口设备的写入操作会马上写入,不会缓冲。spi_fd代表打开后的接口的描述符。
ioctl(spi_fd,IOCTL_CMD_SET_SPI_EN,LOW);
//ioctl是linux执行I/O操作的函数,通过它对设备进行控制。spi_fd是之前打开的SPI接口的描述符;IOCTL_CMD_SET_SPI_EN是一个宏定义的32位整数,代表一个使能SPI设备的命令;LOW表示将SPI接口的使能引脚电平拉低。执行时通过将SPI总线接口的使能引脚电平拉低,从而使能该SPI总线接口。使能后才能对该接口进行读写操作。
第四步,关闭SPI FLASH的写保护状态。
write_en(SPI_FLASH_CMD_WREN);
//write_en函数表示写使能,即去除写保护,SPI_FLASH_CMD_WREN是一个宏定义的32位整数,代表去除写保护的命令。write_en又调用下面这个函数将该32位的命令按照特定的时序每次一位的传下去。
ioctl(spi_fd,IOCTL_CMD_SET_SPI_D,bit);
//该ioctl函数操作spi_fd总线接口;IOCTL_CMD_SET_SPI_D同样是一个命令,表示设置SPI总线的数据输出端口;bit是要设置的位(0或1),依次表示32位命令SPI_FLASH_CMD_WREN的每一位。
//执行后,SPI FLASH接收到SPI_FLASH_CMD_WREN命令,即自动去除写保护状态,可以执行擦除、写入。
第五步,擦除SPI FLASH上的已有数据。
bulk_erase(SPI_FLASH_CMD_BE);
//erase_all函数表示擦除SPI FLASH上所有数据;SPI_FLASH_CMD_BE是一个宏定义的32位整数,代表块擦除的命令。bulk_erase又调用下面这个函数将该32位的命令按照特定的时序每次一位的传下去。
ioctl(spi_fd,IOCTL_CMD_SET_SPI_D,bit);
//该ioctl函数操作spi_fd总线接口;IOCTL_CMD_SET_SPI_D同前一步中一样是设置SPI总线的数据输出端口的命令;bit是要设置的位,依次表示32位命令SPI_FLASH_CMD_BE的每一位。
//执行后,SPI FLASH接收到SPI_FLASH_CMD_BE命令,即自动将所有位置为1,从而擦除全部数据。
第六步,将保存在临时文件中的更新数据读出,并通过SPI总线写到SPIFLASH上。
FILE*fp=fopen(fpga_configure.dat,″r″))
//fopen打开文件fpga_configure.dat;r表示可读;fp指向打开后的文件。
spi_write(fp,len,SPI_FLASH_CMD_PP);
//spi_write函数表示往SPI FLASH上写数据;fp指向要写入的文件的地址;len是要写入的文件的长度,以字节为单位;SPI_FLASH_CMD_PP是一个宏定义的32位整数,代表页编程的命令,一次写入一页(一般为256字节或512字节)。spi_write又调用下面这个函数将SPI_FLASH_CMD_PP命令和fp指向的文件的所有数据每次一位的传下去。
ioctl(spi_fd,IOCTL_CMD_SET_SPI_D,bit);
//该ioctl函数操作spi_fd总线接口;IOCTL_CMD_SET_SPI_D同前一步中一样是设置SPI总线的数据输出端口的命令;bit是要设置的位,先是32位的SPI_FLASH_CMD_WRITE命令,然后是fp指向的文件的每一位数据。
//执行后,SPI FLASH先接收到SPI_FLASH_CMD_PP命令,随即将后面接收到的数据按页编程的方式写入到SPI FLASH中。该写入的数据就是待更新的FPGA的配置数据。
第七步,关闭SPI总线接口,删除临时文件。
close(spi_fd);
//close是linux关闭设备或文件的命令;执行后即关闭spi_fd所代表的SPI总线接口。
unlink(″fpga_configure.dat″);
//unlink是linux删除文件的函数,执行后删除临时文件fpga_configure.dat。
第八步,输出完成信息,返回需关闭电源再重启的标识。
printf(″FPGA Configure Data Refresh Done!\n″);
//输出信息,提示用户FPGA配置数据更新完成。
return SCUS_POWER_RESET;
//返回SCUS_POWER_RESET表示需要关闭电源重启超声诊断设备,更新结果验证程序接收到该返回值后会提示用户需关闭电源,然后重新启动超声诊断设备。必须关闭电源重启是因为FPGA是在上电启动时读取SPI FLASH中的配置数据,完成配置的。
其他三个组件(系统启动引导工具、根文件系统、用户存储空间)的更新方法均类似于系统内核的更新方法,不再详述。
如图7所示,更新过程中最后启动更新结果验证程序,判断更新过程的返回值是否正确,不正确输出错误信息并返回。如果更新程序返回值正确,则需根据返回值类型判断应当重启应用程序还是重启系统。这个是由更新的组件类型决定的,通常更新应用程序时只需重新启动应用程序即可,而更新系统或与内核相关的部分则需重新启动系统。更新FPGA则需要重新加电运行。判定重启类型后,即可执行重启过程,重启成功后返回相应标识码。
本领域技术人员不脱离本发明的实质和精神,可以有多种变形方案实现本发明,以上所述仅为本发明较佳可行的实施例而已,并非因此局限本发明的权利范围,凡运用本发明说明书及附图内容所作的等效结构变化,均包含于本发明的权利范围之内。