标签:
无标签
嵌入式开发人员必须选择一款处理器,以此决定合适的系统性能。Nios II处理器使用指令和数据存储器分离的存储器结构,具有灵活的结构可修改性,支持自定制指令。Nios II处理器支持片上调试,通过JTAG调试通道,可以实现指令单步、断点、连续运行等调试功能。使用系统开发工具将处理器、外设、存储器和I/O接口集成在单片FPGA中,定制自己设计的系统,并且对各种外围设备的实现提供了强大的支持平台;SOPC Builder系统开发工具可以自动生成组件以及连接组件的总线,quartus ii 软件开发工具可以完成功能模块设计、综合布线和仿真,Nios II ide软件开发工具提供嵌入式应用软件的开发环境和调试环境。所有软件开发任务编辑、构建、程序调试都能够在(IDE)下完成,从而简化了开发过程,降低了系统成本、复杂性以及功耗,缩短了产品上市周期。
组件的可定制性是嵌入式开发的一个特点,SOPC(System on Programmable Chip,片上可编程系统)是Altera公司提出的一种灵活、高效的SOC解决方案。它将处理器、存储器、I/O接口、DMA、定时器等系统设计需要的功能模块集成到一个PLD器件上,构建一个可编程的片上系统。
嵌入式应用软件都是运行在特定的硬件平台上的。我所使用的FPGA为EP1C6Q240C8,如果只是使用FPGA来实现一个串口通信,那么在Nios II IDE中使用Nios II device drivers来写一个串口通讯程序是容易实现的,但是我们的程序要保存一些数据到FLASH中,Nios II device drivers虽然提供了HAL和c库,但是没有提供可读写的文件系统,所以我们就选了提供文件系统支持的eCos嵌入式操作系统来实现所需要的功能。那么eCos怎么移植到nios II处理器上,又怎样在这样一个平台上开发串口通信程序呢!我把设计的过程分为三部分:硬件平台设计,eCos库的编译,串口通信程序开发和调试。
硬件平台设计
根据fpga型号和flash类型先要制作目标板。Quartus ii有一个命令mk_target_board是用来制作目标板的,参看该命令的使用帮助请参考Quartus ii安装路径document目录下的flash-program-guide.pdf。生成目标板工程后,启动quartus ii 5.0和sopc builder软件设计该目标板工程,sopc builder软件完成UART、FLASH、RAM组件的添加和编译。quartus ii 5.0软件进行综合布线,编译生成flash program file(一个sof文件)。
然后使用quartus ii 5.0和sopc Builder软件设计我们的应用工程,在sopc builder软件中选择我们自己设计的目标板。添加必需的组件和模块,串口通信需要的UART组件,保存数据的FLASH组件等。分配管脚、编译,生成FPGA硬件配置文件(也是sof文件)。Sopc Builder生成的ptf文件也是我们在后面配置ecos库的时候要用到的文件。
eCos库编译
eCos可以到Redhat的网站或者Nios Community论坛找到,Nios论坛的eCos是移植了的版本,而redhat网站上的还要自己移植到Nios II上。移植了的版本支持这些Altera的Avalon设备组件:Timer、UART、JTAG UART、Lan91C111、Ethernet Chip、LCD 16207 Panel、Compact Flash等,后面提到的eCos都是指移植了的版本。安装移植了的版本eCos需要一个条件,就是quartus II软件要满足版本需求,现在Nios论坛上的eCos已经开发到5.1版了。
完成eCos的安装后,就可以配置eCos了,打开“开始—>程序—>altera—>niosii development kit—> niosii sdk shell”,在niosii sdk shell中启动配置工具nios2configtool,命令如下:
nios2configtool --ptf=/ecos-c/info_aquire/niosii_c.ptf --cpu=cpu
参数niosii_c.ptf是我们在设计应用工程时生成的文件,cpu是使用Sopc Builder定制接口时添加Nios II处理器的名字。接着出现如下图形配置界面:

根据具体的UART芯片型号,从package目录选择相应芯片的驱动加入,如果需要的UART芯片驱动不存在,则可以参考已有的UART芯片驱动进行修改。
打开菜单“build->package”添加“serial device drivers”包和“FLASH device drivers”包,把我们需要的包添加完后,就可以编译eCos库了,打开菜单“build->library”编译生成eCos库,它包含include文件目录、lib文件目录和program_flash文件。然后可以使用Nios II IDE集成开发工具开发基于eCos嵌入式操作系统的串口通信程序。
串口通信程序开发和调试
1)配置开发环境
打开Nios II IDE软件,新建一个“advanced c/c++ project”工程,选择我们串口通信程序所在的文件夹的目录路径。然后在“build command”中输入我们的定制编译命令:
make INSTALL_DIR=/ecos-c/info_aquire/vehicle_install
这个目录/ecos-c/info_aquire/vehicle_install是我们的编译生成的eCos库所在的目录,不要和串口通信程序所在的目录混淆。
2) 串口通信程序设计
程序工作原理:从PC机发送一条十六进制格式的数据包到串口通讯程序,串口通讯程序的读数据线程首先对接收到的数据进行判断,如果接收到包头标志,则重新开始填充缓冲区,如果接收到包尾标志,则将缓冲区数据传递给处理数据线程对数据包进行处理。处理数据线程按照包的类型标志进行处理,如果在处理的过程中出现错误,则把错误的信息返回给PC,成功执行了则返回成功的对应标志让PC确认操作执行成功。
通讯包协议:
|
名称 |
类型 |
长度 |
描述 |
|
包头 |
Hex |
1 |
固定为0xff |
|
包类型 |
Hex |
1 |
|
|
数据部长度 |
Hex |
1 |
数据部的十六进制字节数 |
|
数据部 |
Hex |
0<=n<=256 |
变长的数据 |
|
CRC校验 |
Hex |
2 |
数据部的校验结果 |
|
包尾 |
Hex |
2 |
固定为<CR><LF>(回车、换行),字符为‘\r’,‘\n’;十六进制为0x0d,0x0a |
3)程序主要代码分析
定义包的类型,PKG_DATE和PKG_STATUS是接收包类型,ANSWER_OK和ANSWER_ERR是返回标志。
声明线程函数和线程句柄,program_recv是数据接收线程函数,program_deal是处理数据线程函数。
enum TPKG{PKG_DATE=0x50,PKG_STATUS=0x51 };
enum TANSWER{ANSWER_OK=0,ANSWER_ERR=1};
cyg_thread thread_s[2];
char stack[2][4096];
cyg_handle_t thread_recv,thread_deal;
cyg_thread_entry_t program_recv;
cyg_thread_entry_t program_deal;
下面函数是一个请求应答函数,当echo_type是ANSWER_OK时,使消息解析成功,且命令执行;当echo_type是ANSWER_ERR时,表示接收消息错误或者命令没有成功执行。
void request_answer(int echo_type,int pkg_type,int err_type,int err_val)
{
unsigned short ret;
const unsigned char *pb;
short hi,lo;
int nlen;
if(echo_type==ANSWER_OK){//成功返回
unsigned char chACK[]={
0xff,0x1c,0x01,pkg_type,0x00,0x00,'\r','\n'};
ret=checkcrc(&chACK[3],1);
hi=(ret>>8)&0xff;
lo=ret&0xff;
chACK[4]=lo;
chACK[5]=hi;
nlen=sizeof(chACK);
cyg_io_write( handle, chACK, &nlen );
} else if(echo_type==ANSWER_ERR) { //失败返回
unsigned char chNAK[]={0xff,0x1a,0x03,pkg_type,err_type,err_val,0x00,0x00,'\r','\n'};
ret=checkcrc(&chNAK[3],3);
hi=(ret>>8)&0xff;
lo=ret&0xff;
chNAK[6]=lo;
chNAK[7]=hi;
nlen=sizeof(chNAK);
cyg_io_write(handle, chNAK, &nlen);
}
}
定义数据接收线程和数据处理线程,接收线程接收数据,接收到了一个整包,就把这个包拷贝给全局数据缓冲区,然后由数据处理线程进行处理。
代码:
cyg_io_handle_t handle ;
unsigned char g_package[256];//传递给处理线程的全局数据缓冲区
int g_npkg=0; //缓冲区的字符数
static void program_recv( )
{
Cyg_ErrNo err;
//打开串口,返回串口的句柄给参数handle
err = cyg_io_lookup("/dev/uart_usb", &handle);
unsigned char buff;
unsigned char content[256];//临时缓冲区
int count="0",len;
while(1){
len=1;
//读一个字节,放到buff中
err=cyg_io_read(handle,(void*)&buff, &len);
if (ENOERR == err) {
if(buff==0xff){
count=0;
content[count++]=buff;
} else content[count++]=buff;
if((buff==0x0a)&&(content[count-2]==0x0d)){
g_npkg=0; //拷贝数据到全局数据缓冲区
while((count--)>0){
g_package[g_npkg]=content[g_npkg];
g_npkg++;
}
}
if(count>=255) { //防止意外发生,不能使数组越界
count%=256;
}
} // end if(ENOERR == err)
}//end while(1)
}
数据处理线程负责处理一个完整的数据包。
static void program_deal(cyg_addrword_t datat)
{
int delay;
while(1){
//全局数据缓冲区有数据,并且成功打开了串口则执行数据处理
if(g_npkg&&handle){
//首先通过checkcrc()进行crc的校验。
unsigned short ret="checkcrc"(&g_package[3],g_package[2]);
const unsigned char *pb=&g_package[3]+g_package[2];
short hi,lo;
int nlen;
hi=(ret>>8)&0xff;
lo=ret&0xff;
//比较校验后的结果,正确则处理包,错误则返回错误消息
if((lo==*pb)&&(hi==*(pb+1))){
//执行命令指定的动作………
} else{
request_answer(ANSWER_ERR,g_package[1],0,lo);
}
g_npkg=0;//不要忘了清空全局数据缓冲区
}
}