更多课程笔记请查看:【zynq裸机编程课程笔记合集】
http://www.corecourse.cn/forum.php?mod=viewthread&tid=29095
本节视频课程学习地址(需复制链接到浏览器才能打开):www.bilibili.com/video/BV1Ra411q7ww?p=6
课程大纲
1、什么是bsp(board support package)
BSP的全称为Board Support Package,中文意思是板级支持包,事实上对于Zynq来说,bsp的确切含义应该是硬件系统支持包,我们创建的BD文件添加的PS、以及添加的各种扩展IP,共同组成了基于PS的硬件系统,该硬件系统中包含的各个PS和PL的外设功能,Xilinx原厂都为用户提供了预先编写并验证好的驱动程序,以及实现各种常用功能时需要用到的函数,这些驱动程序以及常用函数组合在一起,共同实现了对该特定硬件系统的编程的支持,用户编程时,可以使用该支持包中提供的驱动和函数,来避免自己编写应用函数和基于寄存器读写的驱动。由于这些驱动程序中加了很多安全判断和兼容操作,所以,在对程序尺寸和运行效率要求不高的场合,推荐使用BSP提供的驱动和函数,而在对性能和程序尺寸有要求的场合,推荐自己编写基于寄存器读写的驱动。
2、如何实现对指定地址的读写操作
使用指针
如何实现对指定地址的读写呢,根据CPU的工作原理,每一项操作的本质,都是对指定地址进行数据的读写操作。所以从CPU编程角度来说,我们只需要知道每个寄存器的绝对地址,就可以使用指针的方式读取或写入该存储器/寄存器。例如对地址为0x00000020的寄存器进行读写,就可以使用下面的形式:
- 读寄存器:return (volatile u8 ) 0x00000020;
- 写寄存器:(volatile u8 ) 0x00000020 = 0x12;
使用IO读写函数
xilinx提供的基本的地址读写函数,这些函数位于xil_io.h文件中,如下所示:
Xil_In8(addr);
Xil_In16(addr);
Xil_In32(addr);
Xil_In64(addr);
Xil_Out8 (addr, data);
Xil_Out16(addr, data);
Xil_Out32(addr, data);
Xil_Out64(addr, data);
如果打开任意一个函数查看,会发现这些函数就是对指针操作的封装而已。如下所示:
static INLINE u32 Xil_In32(UINTPTR Addr)
{
return (volatile u32 ) Addr;
}
3、如何知道各个外设的硬件信息(寄存器地址,位功能)
经过上面的分析,我们了解到,CPU的编程就是对各个地址的存储器/寄存器进行读写操作 ,那么这些地址分别是多少呢?换句话说,如何知道哪个地址的数据能够对应代表什么功能,或者说,指定功能由哪个地址的存储器中的值控制呢?方法很多,最基础,最原始,也是最可靠的方法就是查看芯片的数据手册(datasheet)。
查看datasheet
这个方法对于任何的CPU架构的芯片都是通用的。不管是8051、还是Cortex-M3、以及高端的Cortex-A9、A53系列。Zynq 7000系列芯片,对应的手册叫做UG585,安装了Xilinx的文档向导(DocNav)后,就能在文档向导中找到这个文档。文档向导只要安装了vivado,就会自动被安装 ,不用再重新单独安装。在UG585的附录B中,有所有外设的每个寄存器的地址和功能描述。
使用BSP提供的驱动和硬件信息文件
在bsp工程中,Xilinx为每一个硬件功能都提供了描述其寄存器地址和位功能的.h文件,这类文件字母x开头,然后紧跟外设功能名,最后以_hw结尾。例如,对于GPIO,提供的该文件名为xgpiops_hw.h,对于串口(uart),提供的该文件名为xuartps_hw.h,对于SD/MMC外设控制器,提供的该文件名为xsdps_hw.h。
需要注意的是,SDK在生成BSP时,会仅针对系统中配置使能了的硬件生成硬件信息文件,对于没有配置使能的硬件,则可能不会生成硬件信息文件,例如我们开发流程课程中,因为没有使能SD/MMC外设和UART外设,所以在SDK中生成的LED_bsp下就找不到刚刚说的xsdps_hw.h和xuartps_hw.h。
4、如何实现程序中的延时
在程序中,少不了要进行延时处理,比如最简单的LED灯闪烁控制,就可以通过延时加翻转LED控制IO的方式来实现LED指定时长的亮灭。对精度要求很低的延时,可以使用死循环的方式实现,例如使用while、for循环等等。对精度要求较高的延时,可以使用BSP中提供的基于CPU心跳定时器的定时/延时函数,例如
微秒单位延迟:usleep(unsigned long useconds)
秒单位延迟:sleep(unsigned int seconds)
这些函数都包含在名为unistd.h的文件中。
这些延时函数在Xilinx的BSP中,都是根据CPU架构平台,使用CPU系统标配的滴答定时器实现的,所以延时精度相对与程序中写while死循环的方式延迟,要高得多。
另外,这种延时方式是跨平台通用的,在很多操作系统或者提供裸机bsp的平台上,都支持使用这些函数进行延时,所以,推荐大家在编写c程序时,需要用到延时的地方,优先使用这两个延时函数,而不是使用自己写的while死循环的方式,或者封装的其它延时函数名。
想要更高精度的延时方法,或者希望在延时期间还能让cpu做点其他事情,就可以使用定时器中断的方式了。使用这种方式,可以让指定的事件在指定的定时时间到来后去执行一次,而在没有到来的情况下,CPU可以处理别的事情。这种方式,后续我们讲解定时器的使用时候会讲到。
总结下来就是,对于精度要求不高,和对CPU利用率要求不高的场合,使用usleep、sleep函数是非常方便的,而且还能在大多数情况下跨平台移植,非常方便。
5、使用跨平台可移植的数据类型
什么是跨平台的数据类型,为什么要使用跨平台的数据类型?先给大家举个简单的例子。
8位数量类型,Xilinx的SDK提供的例子,使用的都是u8,对应的数据类型实际为unsigned char。使用u8定义一个变量i。就是 u8 i;
在Xilinx的SDK中这么定义,没问题,只要你的程序中任何一个头文件中包含了"xil_types.h"就能正常使用,比如在LED这个程序中,我们为了使用GPIO的寄存器地址信息,包含了xgpiops_hw.h这个头文件,而在xgpiops_hw.h这个文件中,又包含了"xil_types.h"这个头文件,所以我们在main函数中使用u8\u16\u32\u64\s8\s16\s32\s64来定义变量的类型,才会没有任何问题。但是当我们将这句简单的话放到其他的编译器中,就不行了,例如,我们放到intel的硬核或软核的软件开发环境,也就是Nios II 13.0 Software Build Tools for Eclipse中,就会报错。
会不会是nios ii eds比较垃圾,没有做这个定义呢,那我们再打开Cypress的FX3(USB3.0芯片)的开发环境,在这个里面我们同样来写这句话,也同样会报错。
也就是说,除了在Xilinx的SDK中,这个类型在其他很多开发环境中都没有定义。(确实是没有定义,不是我没有找到定义的头文件的位置,是确实没有定义,感兴趣的可以自己下来研究核实)所以当我们在Xilinx的SDK中写的程序移植到其他的开发环境中时,所有的变量定义都会失效,而让我们不得不自己手写该平台的这些变量类型的定义语句(使用typedef)。
那么有没有一种变量类型是在绝大多数开发环境中通用的呢?确实有,这个就是C99标准新增的标准头文件stdint.h中定义的各个类型。在stdint.h中,定义了各种常见类型的缩写关键词。
那么,这些类型定义,真的能够做到跨平台通用吗?这个确认是的,不信的话大家可以做个试验,在自己期望测试的开发环境中,写上下面这段代码。
include “stdint.h”,
uint8_t us8_type;
uint16_t us16_type;
uint32_t us32_type;
uint64_t us64_type;
int8_t s8_type;
int16_t s16_type;
int32_t s32_type;
int64_t s64_type;
然后看看能不能在你的编译环境中找到这些定义。或者修改你可以成功编译的程序里某个变量的类型声明为stdint.h中定义的类型,然后编译看看,看会不会报错。
基本上可以绝大多数开发环境都能很好的支持这些定义,所以使用stdint.h文件中定义的数据类型来定义变量,就能够实现跨平台便捷移植。推荐大家使用。
总结
好了,这节课程讲到这里,还没有讲解如何去点亮一个LED灯,只是介绍了C一些在Xilinx的SDK中编程的常见思路和规律。掌握这些规律,能够让你往具备独立开发能力的开发者的路上迈进,而不是去做一个简单的模仿者。万事俱备,下一节课,我们就来以GPIO为例,仔细看看我们该用怎么的思路和方法对各种硬件外设进行编程控制。