接上一个笔记目录 ,本笔记主要讲解FX3 固件代码的结构,有些内容需要读者先了解下USB的协议。
本节内容主要参考官方手册:FX3_Programmers_Manual。也参考了一些其他博主的资料,侵删!
固件需要的内存地址和范围:
5 FX3 固件结构
以 demo:SlaveFifoSync 的FX3 固件为例,demo位置:
\Cypress\EZ-USB FX3 SDK\1.3\firmware\slavefifo_examples\slfifosync
典型应用是 FPGA 通过 slave fifo接口连接到FX3 上。
5.1 固件代码组成
FX3 所有的固件程序都包含两部分(初始化代码和应用代码)
初始化代码:所有应用的初始化代码基本都是相同的
应用代码:每个应用的专用代码
如上图所示:
cyfx_gcc_stratup.s
启动代码,汇编语言
cyfxgpif2config.h
GPIF接口描述文件,由GPIF ii Designer生成,直接导入工程中使用,GPIF II Designer的工程是后缀.cyfx,编译生成.h文件。
cyfxtx.c 官方封装 的一些 库函数,直接调用。
cyfxslfifosync.c 固件程序的主要功能,实现线程的建立,对回调函数的处理,DMA通道的建立,控制传输的实现,对GPIFII的状态机加载等功能。程序入口main函数即在这个文件中。
本例中的流程主要就是在这个函数中实现的
cyfxslfifosync.h
对全局变量的定义以及对 cyfxbulklpauto.c中使用到的函数的声明。
cyfxbulklpdscr.c
USB设备描述符的定义
sensor.c/sensor.h:
本例子中配置的sensor的代码
5.2 初始化代码流程
下面这个流程图是上面这个流程图的举例,使用的一些函数也标出来了,有些流程手册说的有些不容易理解,不过本质是一样的,流程也没有乱,读者看的时候按照这个流程走就行,别纠结哪个函数是属于哪个流程的。
图5.2
5.2.1 CyU3PFirmwareEntry()
FX3 的固件入口 ,该函数定义在了 FX3 API 库中,对用户不可见。
该函数实现了一些初始化功能,比如初始化cache, mmu ,初始化一些堆栈空间等。
5.2.2 Tool Chain Initialization
将BSS 数据段的值清零。BSS 数据段保存了所有应用固件的未初始化数据。针对 ARM 处理器的 GUN GCC 编译器的工具链初始化函数示例如下(本代码位于 cyfx_gcc_startup.S )
在该函数中,执行了以下两个过程
A :清零 BSS 数据段
B: 将程序控制权转交给 main()函数
接下来的流程(函数)是进入main函数了, main函数是应用程序的入口,在 cyfxslfifosync.c 文件中定义。
5.2.3主函数 main
主函数main中执行了很多,上面的流程图中只标出了一部分
main中执行的流程有 4部分:
CyU3PDeviceInit>
CyU3PDeviceCacheControl>
CyU3PDeviceConfigureIOMatrix>
CyU3PKernelEntry
执行完CyU3PKernelEntry以后,函数会跳到CyFxApplicationDefine函数,执行用户函数的线程。
其中上图5.2 的 OS kernel Entry 是指RTOS(Real-Time Operating System) Kernel ,即RTOS 内核入口,上图5.2的OS timer Configuration 是指RTOS timer Configuration ,这两部分都属于主函数中的CyU3PKernelEntry
5.2.3.1 CyU3PDeviceInit 初始化
位于 main 函数中,如下图,CyU3PDeviceInit 中的 clkcfg 配置了时钟相关设置,中断控制器的初始化,还有 GCTL 和PLL的配置,具体的含义后续再补充
这里注意当设置外部晶振是 19.2Mh或者38.4Mhz的时候,主频是384mhz,当外部晶振是26M或者52Mhz的时候,主屏是416Mhz
当GPIF 跑32bit 100Mhz的速率的时候,主频必须是416Mhz,否则跑不动。
这个更改通过CyU3PDeviceInit() 中的setsysclk400=true 来进行,如果外部是19.2Mhz的,那么内部的频率是403.2mhz d
5.2.3.2 CyU3PDeviceCacheControl 缓存配置
配置缓存,器件包含 8K 字节的数据缓存区和 8K 字节的指令缓存区。在本例中,仅使能了指令缓存区。因为数据缓存区仅在需要 CPU 进行大量的数据存取工作的时候才能体现出效果,而本例是基于 DMA 的数据传输,当完成初始化后,CPU 基本处于旁路状态,无需进行大量的数据存取操作,因此不需要使能数据缓存
CyU3PDeviceCacheControl (CyTrue, CyFalse, CyFalse)函数的三个参数分别为指令缓存、数据
缓存、DMA 是否关联数据缓存的开关
5.2.3.3 CyU3PDeviceConfigureIOMatrix IO配置
IO 矩阵配置:所谓 IO 矩阵配置就是配置 GPIF 或串行外设接口的对应 IO 功能 。
这里注意,I2C 和SPI接口是复用的IO,因此只能二选一。
固件代码中还提到,如果使用GPIF 32bit的位宽,SPI不能使用。看结构框图这里应该是不冲突的不知道为何不能使用??????
5.2.3.4 CyU3PKernelEntry 设置进入RTOS 操作系统
进入RTOS操作系统,设置RTOS 定时器等
5.2.3.5 handle_fatal_error
代码中有些内容是 goto handle_fatal_error;
这意味着有错误发生或者某个操作失败了。此时,程序会跳转到handle_fatal_error
标签处执行。
但是DEMO中此处并没有执行什么,我个人认为开发的时候应当增加一些处理逻辑
比如:
handle_fatal_error:
CyU3PDebugPrint("handle_fatal_error ! \n", ); // 打印错误信息
CyU3PGpioSetValue(59, CyTrue); //点灯指示错误
CyU3PThreadSleep(1000); // 等待一段时间观察错误指示灯
CyU3PFreeHeaps();// 释放资源或进行清理工作
CyU3PDeviceReset(isWarmReset); // 最终,如果无法恢复,可以执行软件重启 ,这个函数的重启不会重新加载固件
5.2.3.6 ApplicationDefine 用户代码
接下来进入用户代码了,详见下章节。
5.3 用户代码
用户代码的执行分5部分,分别是 :
1)CyFxApplicationDefine
创建线程
2)SlFifoAppThread_Entry
应用进程实现具体应用相关的初始化操作,例如 GPIF II 和 USB 模块的配置。
在这个模块中,会执行很多函数 ,这个模块和后面的CyFxSlFifoApplnStart和CyFxSlFifoApplnStop是平级的
3)CyFxSlFifoApplnStart
收到 USB SET_CONFIGURATION 请求后,CyFxSlFifoApplnStart 设置 USB和 GPIF 互通所需的端点和 DMA 通道。
4)CyFxSlFifoApplnStop
USB 复位或者连接断开被检测到后,CyFxSlFifoApplnStop 函数被调用,关闭 USB 端
点、释放 DMA 通道。
5)用户回调函数
用于处理一些事务和USB协议,接受上位机的控制指令等。
在这之前需要首先创建用户线程
5.3.1 CyFxApplicationDefine 创建用户线程
当操作系统被调用以后,FX3 的库会调用 CyFxApplicationDefine()函数,在这个函数中,将
创建具体的应用线程
这里首先设置 线程堆栈的大小 ptr = CyU3PMemAlloc (CY_FX_SLFIFO_THREAD_STACK);
然后下面的CyU3PThreadCreate 就是创建一个线程:
&slFifoAppThread
:这是指向线程结构体的指针,用于存储线程的状态和控制信息。"21:Slave_FIFO_sync"
:线程的名称和ID,用于调试和识别。SlFifoAppThread_Entry
:线程的入口函数,一旦线程启动,就会执行这个函数。这里应该是线程的主要工作函数。0
:传递给线程入口函数的参数,这里没有传递任何参数。ptr
:指向分配给线程栈的内存的指针。CY_FX_GPIFTOUSB_THREAD_STACK
:线程栈的大小,确定了线程可以使用的最大栈空间。CY_FX_GPIFTOUSB_THREAD_PRIORITY
:线程的优先级,这里两次使用相同的值表示固定优先级。CYU3P_NO_TIME_SLICE
:线程调度的时间片,这里设置为CYU3P_NO_TIME_SLICE
表示线程不使用时间片轮转调度。CYU3P_AUTO_START
:这个参数决定线程是否立即启动,CYU3P_AUTO_START
表示创建线程后立即运行。
这里的前两个参数按照demo,自己创建就好了,第三个参数SlFifoAppThread_Entry 是你线程的入口函数,后续需要写线程的入口函数
剩下的一些参数仿照demo修改即可。
这里DEMO中如果创建线程失败则没有任何下文了,这里建议修改增加一些功能代码比如:
CyU3PDebugPrint("Failed to create thread! \n", ); // 打印错误信息
CyU3PGpioSetValue(59, CyTrue); //点灯指示错误
CyU3PThreadSleep(1000); // 等待一段时间观察错误指示灯
CyU3PFreeHeaps();// 释放资源或进行清理工作
CyU3PDeviceReset(isWarmReset); // 最终,如果无法恢复,可以执行软件重启 ,这个函数的重启不会重新加载固件
或者尝试重新创建线程....
这里的CY_FX_SLFIFO_THREAD_PRIORITY 是设置线程优先级,优先级分为0-31个,0是优先级最高的,驱动是优先级最高的线程,官方建议用户线程设置到8 或者更低的优先级
如果要增加线程,就调用函数:CyU3PThreadCreate
5.3.2 进入用户线程SlFifoAppThread_Entry
应用进程实现具体应用相关的初始化操作,例如 GPIF II 和 USB 模块的配置。
流程图如下:下图流程是一部分,slavefifo的demo还有一些其他的流程。
代码如下:
5.3.2.1 CyFxSlFifoApplnDebugInit 函数初始化UART相关代码
5.3.2.2 CyFxSlFifoApplnInit 应用初始化
流程如下
1)初始化P PORT block
2) 禁用同步GPIF的DLL
3) 加载GPIF的配置
这个配置有些是在GPIF ii工具中指定的,有些是在这里写的,比如GPIF II的时序工具中有些空满标志的参数值是在这里确定的(后续补充介绍),如下图:
4)加载GPIF 的状态机(后续补充解释)
5)初始化和配置GPIO
6) USB 接口初始化,首先是USB栈初始化
7)注册了一些USB 协议相关的回调函数
后续再介绍这些回调函数
8) 设置 USB 描述符,这是通过调用 USB 设置描述符来完成的。
9) 启用USB 连接
也就是说USB相关设置都配置好了,通过这个函数来开启和上位机的通信。
另外如果要重枚举,可以使用这个函数,先设置false,然后再true。
5.3.2.3 检测是否受到配置数据
这个用于打印的,如果收到了一个USB的配置事务,则会更新这个标志,然后打印收到了配置事务。
glIsApplnActive 是个标志信号,在函数CyFxSlFifoApplnStart 中设置true,在CyFxSlFifoApplnStop中设置false,默认上电是flase,其他代码有很多都调用了这个标志,相当于只有收到 上位机的 配置事务之后才会开始后面的操作
下图是SlFifoAppThread_Entry函数。即用户线程函数的最后的代码
5.3.3 CyFxSlFifoApplnStart
端点接收到ET_CONFIGURATION USB协议 事务的时候调用这个函数,就是说基于slave fifo的USB通信要开始启用了
这里面设置了端点的属性,配置
设置了DMA通道
更新了标志信号:glIsApplnActive
5.3.4 CyFxSlFifoApplnStop
当端 点收到RESET or DISCONNECT USB协议 事务的时候调用这个函数,相当于slave FIFO的USB通信停止。
这里的操作相对于CyFxSlFifoApplnStart 的就是断开端点,断开DMA通道等等
5.3.5 用户的其他回调函数
1)CyFxApplnUSBSetupCB
USB发送SETUP command 时该回调 函数将会被调用,这里的指令是 USB协议中的Vendor 指令。
2 )CyFxSlFifoApplnUSBEventCB
USB 事务的回调函数:举例说明:
3)CyFxSlFifoUtoPDmaCallback /CyFxSlFifoPtoUDmaCallback
DMA回调函数
本demo中,有两个数据通道分别为 U to P 和 P to U。每个数据通路上的 DMA 事件都会为之注册
一个 DMA 回调函数,当一个 DMA 的缓存中有数据时,该事件发生。该缓存中的数据可以使来
自 USB 输出 或者 GPIF II 套接字。在 DMA 回调函数中,该缓存中的数据被传递给 GPIF-II 套
接字或者 USB 输入端点。
4) CyFxApplnLPMRqtCB
低功耗 模式 回调函数,当返回CyTrue的时候,FX3进入低功耗模式。
5)其他,摘自数据手册:Getting Started with FX3 SDK 章节5.2.3
流程总结:(从主函数Main开始)