cruelfox 发表于 2019-7-7 14:08

【CC1352P测评】rfEasyLinkTx运行过程简析

<div class='showpostmsg'><p>  SDK 带的例子里面 NoRTOS-rfEasyLinkTx 这个工程看起来是最简单的一个无线应用了,它只是间歇性地发送数据包。代码还算不复杂,所以成了我选择的第一个剖析例子。<br />
  根据 makefile 内容,除去库文件,算是工程自己的代码产生的目标文件一共有8个:<br />
<strong><span style="font-family:Courier">EasyLink_nortos.obj<br />
main_nortos.obj<br />
smartrf_settings.obj<br />
smartrf_settings_predefined.obj<br />
rfEasyLinkTx_nortos.obj<br />
CC1352P1_LAUNCHXL_fxns.obj<br />
ccfg.obj<br />
CC1352P1_LAUNCHXL.obj</span></strong><br />
它们也的确对应了工程目录里面的8个.c文件。从文件名看上去,这些文件除了执行代码之外还有相当部分是用来提供配置参数的。</p>

<p>&nbsp;</p>

<p>  main_nortos.c 这个文件很短,就包含一个短小的主函数:</p>

<pre>
<code class="language-cpp">int main(void)
{
    /* Call driver init functions */
    Board_initGeneral();
    /* Start NoRTOS */
    NoRTOS_start();
    /* Call mainThread function */
    mainThread(NULL);
    while (1);
}</code></pre>

<p>  main() 是被 localProgramStart() 函数调用的,后者在 SDK 提供的编译好的启动代码里面(意思是开发者不要随意去修改 localProgramStart() 函数),而且这个启动代码完成的事情并不仅仅是通常MCU的初始化代码那样初始化C运行库的环境,如前面我的帖子所述,它调用 SetupTrimDevice() 进行了硬件相关的处理,我们开发应用可以不理会它。<br />
  main() 函数的三个调用逻辑很清晰:一是把板子的硬件初始化;二是把 NoRTOS 环境启动;三是执行主线程,就是应用程序。</p>

<p>  <strong>(1)</strong> Board_initGeneral() 实际是换名字成 CC1352P1_LAUNCHXL_initGeneral(),这个函数在 CC1352P1_LAUNCHXL.c 里面:</p>

<pre>
<code class="language-cpp">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();
}</code></pre>

<p>  Power_init() 函数是 SDK 驱动程序库中的。尽管这个函数不需要参数,在 SDK 文档 driver 部分也没有叙述需要额外提供什么配置数据,根据本人的试验,它需要一个全局常量(结构) PowerCC26X2_config 提供配置信息。这个结构常量在工程文件 CC1352P1_LAUNCHXL.c 中提供了。</p>

<pre>
<code class="language-cpp">const PowerCC26X2_Config PowerCC26X2_config = {
    .policyInitFxn      = NULL,
    .policyFxn          = &amp;PowerCC26XX_standbyPolicy,
    .calibrateFxn       = &amp;PowerCC26XX_calibrate,
    .enablePolicy       = true,
    .calibrateRCOSC_LF= true,
    .calibrateRCOSC_HF= true,
};</code></pre>

<p>  接着调用的 PIN_init() 的调用是初始化引脚的,它使用了 CC1352P1_LAUNCHXL.c 中的 BoardGpioInitTable 数组作为参数:</p>

<pre>
<code class="language-cpp">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
};</code></pre>

<p>  这个初始化方式很独特,一次调用初始化若干个引脚,每个引脚的指定和设置包含在一个32-bit整数里面。但是我看这里还没有引脚功能复用的定义,只是指定了输入输出类型等基本信息。<br />
  在 SDK 的 source/drivers/pin/PINCC26XX.c 里面有 PIN_init() 的具体实现,还不光是设置I/O寄存器那么简单。<br />
  Board_initHook() 在 CC1352P1_LAUNCHXL_fxns.c 里面,包含了两个函数调用:initAntennaSwitch() 和 CC1352P1_LAUNCHXL_shutDownExtFlash(), 它们的实现也在同一个C文件当中。</p>

<p>  <strong>(2)</strong> NoRTOS_start() 就是 SDK 的库函数了。找出源代码来看,居然相当简单:</p>

<pre>
<code class="language-cpp">void NoRTOS_start()
{
    HwiP_enable();
}</code></pre>

<p>  我想大概是,毕竟没有多任务执行,就没有太多要初始化的,那么暂且就不管了。</p>

<p>  <strong>(3)</strong> mainThread() 在 rfEasyLinkTx_nortos.c 中,是本应用的主体。它调用的无线操作相关函数在 easylink/EasyLink_nortos.c 中实现。我整理了一下,主要有这几个:EasyLink_init(), EasyLink_setRfPower(), EasyLink_getAbsTime(), EasyLink_transmitAsync() 和 EasyLink_abort(). 这些函数再去调用 SDK RF driver 提供的函数。</p>

<p>&nbsp;</p>

<p>  分析一下 EasyLink_transmitAsync() 这个异步操作单数, 每当发送数据包的时候,mainThread 会执行 &nbsp; &nbsp; &nbsp; &nbsp;<br />
EasyLink_transmitAsync(&amp;txPacket, txDoneCb);<br />
第一个参数是结构指针,指向下面数据包描述类型的变量</p>

<pre>
<code class="language-cpp">typedef struct
{
      uint8_t dstAddr;            //!&lt;Destination address
      uint32_t absTime;                //!&lt; Absolute time to Tx packet (0 for immediate)
                                       //!&lt; Layer will use last SeqNum used + 1
      uint8_t len;                     //!&lt; Payload Length
      uint8_t payload;       //!&lt; Payload
} EasyLink_TxPacket;</code></pre>

<p>第二个参数是一个回调函数入口地址,当发送结束后会调用指定的函数。<br />
  在函数中实现发送操作的RF driver API调用是<br />
<span style="font-family:Courier">&nbsp; &nbsp; &nbsp; &nbsp; asyncCmdHndl = RF_postCmd(rfHandle, (RF_Op*)&amp;EasyLink_cmdPropTx,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RF_PriorityHigh, txDoneCallback, EASYLINK_RF_EVENT_MASK);</span><br />
这是在RF命令队列中加入了一个命令,由第二个参数指定命令的具体内容,它是属于面这个结构定义的类型(这是TI私有协议发送命令专用,其它命令会有所不同,不过都是从 rfc_radioOp_t 基本类型扩展而来)</p>

<pre>
<code class="language-cpp">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;                   </code></pre>

<p>EasyLink_cmdPropTx 这是一个全局变量,在 EasyLink_init() 中进行了初始化,于是在 EasyLink_transmitAsync() 被调用的时候仅改变其中和数据包有关的成员。在 smartrf_settings.c 中(以及 smartrf_settings_predefined.c 中还有个相同的)定义了一个设置,会被 EasyLink_init() 利用。</p>

<pre>
<code class="language-cpp">rfc_CMD_PROP_TX_t RF_cmdPropTx =
{
    .commandNo = 0x3801,
    .status = 0x0000,
    .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&amp;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*)&amp;xxx
};</code></pre>

<p>  命令格式的细节需要对无线部分深入了解才能掌握,目前用不到我就不深入分析了。比较有意思的是 RF_postCmd() 里面是如何实现的?这是库函数,可以看代码:</p>

<pre>
<code class="language-cpp">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(&amp;h-&gt;state.clkInactivity);
      /* Increment the sequence number and mask the value */
      RF_cmdQ.nSeqPost = (RF_cmdQ.nSeqPost + 1) &amp; N_CMD_MODMASK;
      /* Populate container with reset values */
      pCmd-&gt;pOp   = pOp;
      pCmd-&gt;ePri    = ePri;
      pCmd-&gt;pCb   = pCb;
      pCmd-&gt;ch      = RF_cmdQ.nSeqPost;
      pCmd-&gt;pClient = h;
      pCmd-&gt;bmEvent = (bmEvent | RFC_DBELL_RFCPEIFG_LAST_COMMAND_DONE_M) &amp; ~RF_INTERNAL_IFG_MASK;
      pCmd-&gt;pastifg = 0;
      pCmd-&gt;flags   = RF_CMD_ALLOC_FLAG;
      /* Cancel ongoing yielding */
      h-&gt;state.bYielded = false;
      /* Submit to pending command to the queue. */
      List_put(&amp;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-&gt;ch;
    }
    /* Exit critical section */
    HwiP_restore(key);
    /* Return with an error code */
    return(cmdHandle);
}</code></pre>

<p>  先给这个 RF 命令分配空间,把参数复制进去(注意 RF_Op* pOp 只是指针拷贝,命令的内容仍然在函数调用者那里),然后将命令插入队列(pending的列表),最后调 RF_triggDispatcher() 将命令发出去&hellip;&hellip;<br />
  如何发出去?在 SDK RF driver 的 RFCC26X2_multiMode.c 中还有相当多的代码在处理相关事务。要详细了解,就不得不去理解 HWI, SWI, RAT 这几样东西了&hellip;&hellip;感觉要掉入一个坑。作为评测活动,到此打住!</p>

<p>&nbsp;</p>

<p>  作为 CC1352 的应用开发者,不需要去了解到太底层的实现细节,用上层的协议有关的 API 就差不多了。这个 rfEasyTx 的例子从顶层看起来(就到 mainThread 实现这一级)还挺简单的。<br />
  但是,作为一个很简单的 demo, 要用到 8 个C源文件,也不能算精简了,想入门的看一看并非一目了然。EasyLink_nortos.c 算作是中间件,不能由开发者自己编写;smartrf_settings.c 和 smartrf_settings_predefined.c 是由 SmartRF Studio 软件生成的配置信息,都不是手工编写;CC1352P1_LAUNCHXL...前缀的文件是和开发板资源有关的,包含了很多实际上没有用到(编译过程中会被优化丢掉)的数据。若要做一个新项目以此为模板的开始的话,我觉得仍有点冗余。<br />
&nbsp;</p>

<p><br />
<strong><span style="color:#5e7384">此内容由EEWORLD论坛网友<span style="font-size:medium">cruelfox</span>原创,如需转载或用于商业用途需征得作者同意并注明出处</span></strong></p>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

okhxyyo 发表于 2019-7-7 22:24

<p><img height="28" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/grinning-face-with-smiling-eyes_1f601.png" width="28" />是不是可以自己弄一个模板?</p>

alan000345 发表于 2019-7-8 08:29

<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/onion/Onion--108.gif" width="50" />分享的内容很不错。</p>

1021256354 发表于 2019-12-13 10:22

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

cruelfox 发表于 2019-12-13 12:55

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

<p>没错,这个是私有协议。</p>

<p>CC1352也支持BLE,可以和手机通信。</p>

luoguangqian 发表于 2019-12-28 16:54

<p>编译出来的bin档有多大?</p>
页: [1]
查看完整版本: 【CC1352P测评】rfEasyLinkTx运行过程简析