SDK 带的例子里面 NoRTOS-rfEasyLinkTx 这个工程看起来是最简单的一个无线应用了,它只是间歇性地发送数据包。代码还算不复杂,所以成了我选择的第一个剖析例子。
根据 makefile 内容,除去库文件,算是工程自己的代码产生的目标文件一共有8个:
EasyLink_nortos.obj
main_nortos.obj
smartrf_settings.obj
smartrf_settings_predefined.obj
rfEasyLinkTx_nortos.obj
CC1352P1_LAUNCHXL_fxns.obj
ccfg.obj
CC1352P1_LAUNCHXL.obj
它们也的确对应了工程目录里面的8个.c文件。从文件名看上去,这些文件除了执行代码之外还有相当部分是用来提供配置参数的。
main_nortos.c 这个文件很短,就包含一个短小的主函数:
int main(void)
{
/* Call driver init functions */
Board_initGeneral();
/* Start NoRTOS */
NoRTOS_start();
/* Call mainThread function */
mainThread(NULL);
while (1);
}
main() 是被 localProgramStart() 函数调用的,后者在 SDK 提供的编译好的启动代码里面(意思是开发者不要随意去修改 localProgramStart() 函数),而且这个启动代码完成的事情并不仅仅是通常MCU的初始化代码那样初始化C运行库的环境,如前面我的帖子所述,它调用 SetupTrimDevice() 进行了硬件相关的处理,我们开发应用可以不理会它。
main() 函数的三个调用逻辑很清晰:一是把板子的硬件初始化;二是把 NoRTOS 环境启动;三是执行主线程,就是应用程序。
(1) Board_initGeneral() 实际是换名字成 CC1352P1_LAUNCHXL_initGeneral(),这个函数在 CC1352P1_LAUNCHXL.c 里面:
void CC1352P1_LAUNCHXL_initGeneral(void)
{
Power_init();
if (PIN_init(BoardGpioInitTable) != PIN_SUCCESS) {
/* Error with PIN_init */
while (1);
}
/* Perform board-specific initialization */
Board_initHook();
}
Power_init() 函数是 SDK 驱动程序库中的。尽管这个函数不需要参数,在 SDK 文档 driver 部分也没有叙述需要额外提供什么配置数据,根据本人的试验,它需要一个全局常量(结构) PowerCC26X2_config 提供配置信息。这个结构常量在工程文件 CC1352P1_LAUNCHXL.c 中提供了。
const PowerCC26X2_Config PowerCC26X2_config = {
.policyInitFxn = NULL,
.policyFxn = &PowerCC26XX_standbyPolicy,
.calibrateFxn = &PowerCC26XX_calibrate,
.enablePolicy = true,
.calibrateRCOSC_LF = true,
.calibrateRCOSC_HF = true,
};
接着调用的 PIN_init() 的调用是初始化引脚的,它使用了 CC1352P1_LAUNCHXL.c 中的 BoardGpioInitTable 数组作为参数:
const PIN_Config BoardGpioInitTable[] = {
CC1352P1_LAUNCHXL_PIN_RLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* LED initially off */
CC1352P1_LAUNCHXL_PIN_GLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* LED initially off */
CC1352P1_LAUNCHXL_PIN_BTN1 | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS, /* Button is active low */
CC1352P1_LAUNCHXL_PIN_BTN2 | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS, /* Button is active low */
CC1352P1_LAUNCHXL_SPI_FLASH_CS | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MIN, /* External flash chip select */
CC1352P1_LAUNCHXL_UART0_RX | PIN_INPUT_EN | PIN_PULLDOWN, /* UART RX via debugger back channel */
CC1352P1_LAUNCHXL_UART0_TX | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL, /* UART TX via debugger back channel */
CC1352P1_LAUNCHXL_SPI0_MOSI | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI master out - slave in */
CC1352P1_LAUNCHXL_SPI0_MISO | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI master in - slave out */
CC1352P1_LAUNCHXL_SPI0_CLK | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI clock */
CC1352P1_LAUNCHXL_DIO28_RF_24GHZ | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */
CC1352P1_LAUNCHXL_DIO29_RF_HIGH_PA | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */
CC1352P1_LAUNCHXL_DIO30_RF_SUB1GHZ | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */
PIN_TERMINATE
};
这个初始化方式很独特,一次调用初始化若干个引脚,每个引脚的指定和设置包含在一个32-bit整数里面。但是我看这里还没有引脚功能复用的定义,只是指定了输入输出类型等基本信息。
在 SDK 的 source/drivers/pin/PINCC26XX.c 里面有 PIN_init() 的具体实现,还不光是设置I/O寄存器那么简单。
Board_initHook() 在 CC1352P1_LAUNCHXL_fxns.c 里面,包含了两个函数调用:initAntennaSwitch() 和 CC1352P1_LAUNCHXL_shutDownExtFlash(), 它们的实现也在同一个C文件当中。
(2) NoRTOS_start() 就是 SDK 的库函数了。找出源代码来看,居然相当简单:
void NoRTOS_start()
{
HwiP_enable();
}
我想大概是,毕竟没有多任务执行,就没有太多要初始化的,那么暂且就不管了。
(3) mainThread() 在 rfEasyLinkTx_nortos.c 中,是本应用的主体。它调用的无线操作相关函数在 easylink/EasyLink_nortos.c 中实现。我整理了一下,主要有这几个:EasyLink_init(), EasyLink_setRfPower(), EasyLink_getAbsTime(), EasyLink_transmitAsync() 和 EasyLink_abort(). 这些函数再去调用 SDK RF driver 提供的函数。
分析一下 EasyLink_transmitAsync() 这个异步操作单数, 每当发送数据包的时候,mainThread 会执行
EasyLink_transmitAsync(&txPacket, txDoneCb);
第一个参数是结构指针,指向下面数据包描述类型的变量
typedef struct
{
uint8_t dstAddr[8]; //!< Destination address
uint32_t absTime; //!< Absolute time to Tx packet (0 for immediate)
//!< Layer will use last SeqNum used + 1
uint8_t len; //!< Payload Length
uint8_t payload[EASYLINK_MAX_DATA_LENGTH]; //!< Payload
} EasyLink_TxPacket;
第二个参数是一个回调函数入口地址,当发送结束后会调用指定的函数。
在函数中实现发送操作的RF driver API调用是
asyncCmdHndl = RF_postCmd(rfHandle, (RF_Op*)&EasyLink_cmdPropTx,
RF_PriorityHigh, txDoneCallback, EASYLINK_RF_EVENT_MASK);
这是在RF命令队列中加入了一个命令,由第二个参数指定命令的具体内容,它是属于面这个结构定义的类型(这是TI私有协议发送命令专用,其它命令会有所不同,不过都是从 rfc_radioOp_t 基本类型扩展而来)
struct __RFC_STRUCT rfc_CMD_PROP_TX_s {
uint16_t commandNo;
uint16_t status;
rfc_radioOp_t *pNextOp;
ratmr_t startTime;
struct {
uint8_t triggerType:4;
uint8_t bEnaCmd:1;
uint8_t triggerNo:2;
uint8_t pastTrig:1;
} startTrigger;
struct {
uint8_t rule:4;
uint8_t nSkip:4;
} condition;
struct {
uint8_t bFsOff:1;
uint8_t :2;
uint8_t bUseCrc:1;
uint8_t bVarLen:1;
} pktConf;
uint8_t pktLen;
uint32_t syncWord;
uint8_t* pPkt;
} __RFC_STRUCT_ATTR;
EasyLink_cmdPropTx 这是一个全局变量,在 EasyLink_init() 中进行了初始化,于是在 EasyLink_transmitAsync() 被调用的时候仅改变其中和数据包有关的成员。在 smartrf_settings.c 中(以及 smartrf_settings_predefined.c 中还有个相同的)定义了一个设置,会被 EasyLink_init() 利用。
rfc_CMD_PROP_TX_t RF_cmdPropTx =
{
.commandNo = 0x3801,
.status = 0x0000,
.pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
.startTime = 0x00000000,
.startTrigger.triggerType = 0x0,
.startTrigger.bEnaCmd = 0x0,
.startTrigger.triggerNo = 0x0,
.startTrigger.pastTrig = 0x0,
.condition.rule = 0x1,
.condition.nSkip = 0x0,
.pktConf.bFsOff = 0x0,
.pktConf.bUseCrc = 0x1,
.pktConf.bVarLen = 0x1,
.pktLen = 0x1E, // SET APPLICATION PAYLOAD LENGTH
.syncWord = 0x930B51DE,
.pPkt = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx
};
命令格式的细节需要对无线部分深入了解才能掌握,目前用不到我就不深入分析了。比较有意思的是 RF_postCmd() 里面是如何实现的?这是库函数,可以看代码:
RF_CmdHandle RF_postCmd(RF_Handle h, RF_Op* pOp, RF_Priority ePri, RF_Callback pCb, RF_EventMask bmEvent)
{
/* Assert */
DebugP_assert(h != NULL);
DebugP_assert(pOp != NULL);
/* Local pointer to a radio commands */
RF_CmdHandle cmdHandle = (RF_CmdHandle)RF_ALLOC_ERROR;
/* Enter critical section */
uint32_t key = HwiP_disable();
/* Try to allocate container */
RF_Cmd* pCmd = RF_cmdAlloc();
/* If allocation failed */
if (pCmd)
{
/* Stop inactivity clock if running */
ClockP_stop(&h->state.clkInactivity);
/* Increment the sequence number and mask the value */
RF_cmdQ.nSeqPost = (RF_cmdQ.nSeqPost + 1) & N_CMD_MODMASK;
/* Populate container with reset values */
pCmd->pOp = pOp;
pCmd->ePri = ePri;
pCmd->pCb = pCb;
pCmd->ch = RF_cmdQ.nSeqPost;
pCmd->pClient = h;
pCmd->bmEvent = (bmEvent | RFC_DBELL_RFCPEIFG_LAST_COMMAND_DONE_M) & ~RF_INTERNAL_IFG_MASK;
pCmd->pastifg = 0;
pCmd->flags = RF_CMD_ALLOC_FLAG;
/* Cancel ongoing yielding */
h->state.bYielded = false;
/* Submit to pending command to the queue. */
List_put(&RF_cmdQ.pPend, (List_Elem*)pCmd);
/* Trigger dispatcher HWI if there is no running command */
RF_triggDispatcher();
/* Return with the command handle as success */
cmdHandle = pCmd->ch;
}
/* Exit critical section */
HwiP_restore(key);
/* Return with an error code */
return(cmdHandle);
}
先给这个 RF 命令分配空间,把参数复制进去(注意 RF_Op* pOp 只是指针拷贝,命令的内容仍然在函数调用者那里),然后将命令插入队列(pending的列表),最后调 RF_triggDispatcher() 将命令发出去……
如何发出去?在 SDK RF driver 的 RFCC26X2_multiMode.c 中还有相当多的代码在处理相关事务。要详细了解,就不得不去理解 HWI, SWI, RAT 这几样东西了……感觉要掉入一个坑。作为评测活动,到此打住!
作为 CC1352 的应用开发者,不需要去了解到太底层的实现细节,用上层的协议有关的 API 就差不多了。这个 rfEasyTx 的例子从顶层看起来(就到 mainThread 实现这一级)还挺简单的。
但是,作为一个很简单的 demo, 要用到 8 个C源文件,也不能算精简了,想入门的看一看并非一目了然。EasyLink_nortos.c 算作是中间件,不能由开发者自己编写;smartrf_settings.c 和 smartrf_settings_predefined.c 是由 SmartRF Studio 软件生成的配置信息,都不是手工编写;CC1352P1_LAUNCHXL...前缀的文件是和开发板资源有关的,包含了很多实际上没有用到(编译过程中会被优化丢掉)的数据。若要做一个新项目以此为模板的开始的话,我觉得仍有点冗余。
此内容由EEWORLD论坛网友cruelfox原创,如需转载或用于商业用途需征得作者同意并注明出处
分享的内容很不错。
这个RF只能用在模块间吧,手机蓝牙间应该不能通信《》
编译出来的bin档有多大?