DSP

TI NDK应用开发过程中的一点经验及改进

2019-07-13 11:50发布

最近在TI的处理器上做软件开发,项目需要网络通讯功能,而在TI的处理器上做网络编程只能使用TI自家的NDK,除非是非常专业的选手,否则用户几乎没有其他选择。 
本文假设设计者熟悉TI的集成开发环境Code Composer Studio v6,因此一些基本的工程、项目、软件包的配置说明将被略过,若有需要了解请移步TI官网查询。 
本人项目中使用的处理器是TI C6457 DSP,软件包有NDK v2.24.03.35,SYS/BIOS v6.42.02.29。其中NDK需要SYS/BIOS的支持,因此SYS/BIOS是必选项。

一.NDK的配置

1.系统配置

NDK使用SYS/BIOS的Clock模块对NDK内部的工作进行计时驱动,这一模块的实例在NDK中被设计为动态创建得到,虽然NDK自带一套不依赖于系统动态内存管理功能的内存管理单元,并且有自己的堆内存,但Clock的实例却不能在其中创建,只能在系统的全局对中创建。因此SYS/BIOS中的内存堆必须分配足够的大小,且必须将SYS/BIOS的动态内存分配功能使能,否则编译工具将无法产生正确的Clock动态创建代码,而程序也无法正常启动NDK服务。

2.模块配置

按项目需求,需要使用芯片的网络接口设备,因此NDK配置选择了EMAC模块;又因为主要使用UDP协议通信,所以添加了IP模块;其他的TELNET、HTTP、FTP等上层服务一概不选。

3.底层驱动配置

底层驱动分别需要EMAC驱动和NIMU Ethernet驱动,另外CSL库是必须的,它们都可以在针对C64PLUS芯片的MCSDK中找到。这个软件包集合可以安装CSL、PDK、MCSDK三个包,CSL包不必说了,PDK中可以找到EMAC驱动,NIMU Ethernet驱动在MCSDK包中,后两个最好用CCS打开工程自己编译一下,然后需要手动将它们(包括CSL包)的lib文件及include路径添加到自己的工程中。其他的配置略过。

二.基于NDK的部分UDP编程经验

1.基于NDK的socket编程代码必须在Task中执行,原因有:

(1).NDK的启动、初始化以及后台守护任务都在Task中执行; 
(2).socket编程需要文件描述符的支持,SYS/BIOS实现了一个简化版的文件描述符,但必须在Task中打开(用fdOpenSession)和关闭(用fdCloseSession)。

2.使用socket编程的用户Task不可一直占用CPU的执行时间,必须以合适的时间间隔自动阻塞,原因有:

(1).SYS/BIOS的Tasks并不是以分片时间自动调度执行,而是按优先级自动重入执行,对相同优先级的Tasks仅在当前执行的Task通过结束任务或者阻塞来主动放弃执行时间后,等待执行的Task才有机会获得执行时间。 
(2).NDK的守护任务在完成初始化后即会自动降低Task优先级至最低的IDLE Task级,若用户Task执行在高于IDLE的优先级,且一直不通过阻塞来主动放弃执行时间的话,则NDK的数据报队列状态将一直得不到更新,从而导致用户Task的socket函数无法正常接收和发送数据报。

3.setsockopt应谨慎改变NDK的自定义属性SOL_BLOCKING,否则socket API recv和recvfrom无法正常接收数据报,原因目前未明。

4.recv、recvfrom、send、sendto函数的查询模式实现

这些函数虽然自带阻塞功能,但在一些需要使用查询的应用中并不合适,而多Task的方案又较为复杂,增加了程序设计的难度。实际上可以为它们的flag参数传递MSG_DONTWAIT来防止函数进入阻塞状态,此时这些函数将返回工作失败状态,用fdError()查询可得错误状态码为EWOULDBLOCK。另外在查询模式下,需要编程者手动调整Task的执行时间,或者按后面的方法对NDK进行一点小的改进。

5.在连接以Windows作为操作系统的PC机的网络接口时sendto函数有时会返回错误状态

这是因为Windows系统服务在网络接口连接后会断断续续的发送一些报文,这些报文会暂时占据NDK的数据报队列,导致查询模式下的sendto函数失败,此时fdError()会返回ENOBUFS状态。这种情况下,将Task阻塞一小段时间,再sendto就会成功了。

三.适应执行时间紧张应用场景的NDK小改进

正如上文所说,在查询模式下需要编程者手动调整Task的执行时间,以预留足够的时间给NDK守护任务用于刷新数据报队列,这在应用执行时间比较紧张的情况下将带来额外的调试任务和难度。 
为解决这个问题,可考虑将NDK的守护任务执行函数在用户主Task中按合适的时间频度调用,但这可能引入更多的Task间的互斥同步编程,且可能与守护任务的执行上下文产生冲突,因此有必要采用更安全的做法。 
我的做法是:在调用socket通信函数(recv、recvfrom、send、sendto等)发现NDK资源不足时,临时提高一次NDK守护任务的优先级,在守护任务执行完一次刷新任务后,又将自身的优先级降回IDLE。这需要对NDK的源代码做一点小更改,并重新编译。 
涉及的NDK源码更改为: /* (NDK安装目录)/packages/ti/ndk/netctrl/netctrl.c中的NetScheduler函数前添加自定义的API函数 */ static int netStartTaskPri = 0; static HANDLE hTaskNetService = 0; void NC_tmpRaiseNetServicePri(void) { if (hTaskNetService) { TaskSetPri( hTaskNetService, netStartTaskPri ); } } /* 更改NetScheduler函数的实现代码 */ static void NetScheduler( uint const SerilCnt, uint const EherCnt) { register int fEvents; /* Set the Scheduler priority */ register HANDLE hTaskSelf = hTaskNetService = TaskSelf();/* 获取和保存守护Task的句柄 */ netStartTaskPri = TaskSetPri( hTaskSelf, SchedulerPriority );/* 设置守护Task优先级为SchedulerPriority(默认为IDLE级),并保存原来的优先级 */ /* * 说明:NetScheduler所在的Task在执行NetScheduler之前执行的是NDK的初始化任务,默认情况下该Task的 * 优先级高于用户Task的优先级,直到初始化任务完毕后,在NetScheduler函数中才将自身优先级降为IDLE。 * 因此上面的代码中将原来的优先级和Task句柄一并保存,在自定义的API函数NC_tmpRaiseNetServicePri中 * 即可恢复守护Task的优先级,从而使守护Task从用户Task中重入并得以执行。 */ /* Enter scheduling loop */ while( !NetHaltFlag ) { .../* 原循环代码省略 */ /* 检查守护Task的优先级,并根据需要重设优先级至SchedulerPriority */ if (TaskGetPri( hTaskSelf ) != SchedulerPriority) { TaskSetPri( hTaskSelf, SchedulerPriority ); } } } /* (NDK安装目录)/packages/ti/ndk/inc/netctrl/netctrl.h中添加自定义API函数的声明 */ _extern void NC_tmpRaiseNetServicePri(void);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
用法示例: SOCKET s; struct sock_addr_in from; int fromLen; ... int ret = recvfrom(s, recvBuff, recvBytes, MSG_DONTWAIT, (struct sock_addr*)&from, &fromLen); if (ret == INVALID_SOCKET) { ret = fdError(); if (ret == EWOULDBLOCK || ret == ENOBUFS) { NC_tmpRaiseNetServicePri(); } } ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12