[原创] 【CC1352P测评】rfEasyLinkTx运行过程简析

cruelfox   2019-7-7 14:08 楼主

  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原创,如需转载或用于商业用途需征得作者同意并注明出处

回复评论 (5)

是不是可以自己弄一个模板?

玩板看这里: https://bbs.eeworld.com.cn/elecplay.html EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!
点赞  2019-7-7 22:24

分享的内容很不错。

点赞  2019-7-8 08:29

这个RF只能用在模块间吧,手机蓝牙间应该不能通信《》

点赞  2019-12-13 10:22
引用: 1021256354 发表于 2019-12-13 10:22 这个RF只能用在模块间吧,手机蓝牙间应该不能通信《》

没错,这个是私有协议。

CC1352也支持BLE,可以和手机通信。

点赞  2019-12-13 12:55

编译出来的bin档有多大?

点赞  2019-12-28 16:54
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复