sonicfirr 发表于 2022-3-10 13:10

【平头哥RVB2601创意应用开发】使用体验01 -- printf避坑

本帖最后由 sonicfirr 于 2022-3-10 13:10 编辑

<p>&nbsp; &nbsp; &nbsp; &nbsp;在使用RVB2601的过程中,免不了使用到printf()或是SDK提供的LOG输出,再加上本人参与了创意应用开发活动,为了完成比赛项目,逐一功能模块测试,在测试到实时内核任务管理时出现了一个小bug,通过研究源码成功避坑,因而决定写下此篇博文。</p>

<p>1、案例设计</p>

<p>&nbsp; &nbsp; &nbsp; 案例由HelloWorld&nbsp;Demo扩展而来,分析源码了解启动流程。程序是从startup.S开始,到SystemInit()初始化RAM、中断、时钟、DMA、系统节拍、SPIFlash等,再到pre_main(),然后跳转到main()函数。</p>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p>&nbsp; &nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;">&nbsp;</p>

<p>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;不过,SDK中pre_main()找到了两个,一个在chip_ch2601包,一个在aos包,这里我猜想一个是裸机版入口,一个是RTOS版入口。Demo默认是开启了控制台的,所以RTOS版的可能性最大,实际情况通过&ldquo;ps命令&rdquo;查看,也发现的确是有一个&ldquo;app_task&rdquo;进程在运行,说明入口函数是aos包中的版本。于是分析任务函数application_task_entry(),它调用了main函数进入了Demo的功能实现代码部分。</p>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p>&nbsp;</p>

<p class="imagemiddle" style="text-align: center;"></p>

<p>&nbsp;</p>

<p>2、创建任务</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;这里,本人偷点懒,没有去看手册,直接仿照app_task的格式,编写自己的任务,实现逻辑是:新建任务&ldquo;my_task&rdquo;,优先级高于启动任务&ldquo;app_task&rdquo;,my_task置位标志量flag,然后休眠,app_task运行判断flag置位则做串口输出,然后清零flag。</p>

<p>&nbsp;</p>

<pre>
<code>#define MY_TASK_STACK_SIZE 4096                //自建任务堆栈尺寸
#define MY_TASK_APP_PRI    8      //自建任务优先级
static aos_task_t my_task_handle;   //自建任务控制块(这里也不知道术语)
static int flag = 0;                //自建运行标志


static void my_task_entry(void *arg)
{
       
        while(1) {       
                LOGD(TAG, "My Task in running\n");
                flag = 1;
                LOGD(TAG, "flag: %d\n", flag);               
                aos_msleep(5000);
        }
}


int main(void)
{
    board_yoc_init();
    LOGD(TAG, "%s\n", aos_get_app_version());
    oled_init();       
//        test_timer_periodic();          //这里是测试定时器用的(本例无用)
        aos_task_new_ext(&amp;my_task_handle, "my_task", my_task_entry,
                     NULL, MY_TASK_STACK_SIZE, MY_TASK_APP_PRI);

    while (1) {
                if(flag == 1)        {LOGD(TAG, "Hello world! YoC\n"); flag = 0;}
                aos_msleep(1);            //入坑点,没有延时会卡死
    }

    return 0;
}</code></pre>

<p>&nbsp; &nbsp; &nbsp; &nbsp;所谓坑点,就是在main()函数的while循环中,如果没有加入延时,就会卡死,串口无输出,控制台命令也不起作用了。加了延时,哪怕只有1ms就可以保证运行正常。</p>

<p>&nbsp;</p>

<p>3、坑点分析</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;以本人使用RTOS的经验来猜想,这里很有可能是串口异步操作引起的(其实,当时先以为任务创建有问题,毕竟没有看手册,后来核对无误,才想到这里),于是追踪串口输出的实现过程一路看下去。</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;日志输出的宏&ldquo;LOGD&rdquo;等,都定义在ulog包的ulog.h中&mdash;&mdash;#define LOG(...) ulog(LOG_ALERT, &quot;AOS&quot;, ULOG_TAG, __VA_ARGS__),而ulog()函数则是定义在ulog.c中。函数没有细致分析,两个地方引起了本人注意:一是日志操作前申请了互斥量,二是日志输出也是调用printf实现的。所以本人还是去看printf的实现过程。</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;在newlib包的printf.c中,printf函数调用了_vsnprintf函数做格式化,然后使用了&ldquo;_out_char()&rdquo;做输出,_out_char()则调用了&ldquo;_putchar()&rdquo;,_putchar()调用了&ldquo;uart_putc()&rdquo;。</p>

<p>&nbsp;</p>

<pre>
<code>//位于newlib\v7.4.3\libc\stdio\printf.c,调用了_out_char() ----- by author
int printf(const char* format, ...)
{
va_list va;
va_start(va, format);
char buffer;
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}

//位于newlib\v7.4.3\libc\stdio\printf.c,调用了_putchar() ----- by author
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)buffer; (void)idx; (void)maxlen;
if (character) {
    _putchar(character);
}
}

//位于newlib\v7.4.3\libc\stdio\printf.c,调用了uart_putc() ----- by author
void _putchar(char character)
{
    uart_putc(character);
}</code></pre>

<p>&nbsp;</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;uart_putc()定义在aos包的console_uart.c中,它调用了&ldquo;hal_uart_send()&rdquo;,而hal_uart_send()则是驱动库hal_csi中定义的,看源码也是启动了互斥量。至此,代码追踪差不多了,可见串口且绑定控制台的独一性资源,系统是通过互斥量来申请并使用的。所以本人设计的任务中通过互斥量占用了串口,没有及时释放资源(这里猜想应该是,UART启用了DMA,所以产生了异步操作,任务休眠时,串口还没有来得及输出完毕,互斥量没有得到释放),而加入了延时后,资源得以释放,功能也就正常了。</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;看来在多任务使用串口时必须做好串口操作的时长预估,并设计好任务调度流程,以免再出现卡死。</p>

<p>&nbsp;</p>

<pre>
<code>//位于aos\v7.4.3\src\devices\console_uart.c中,调用了hal_uart_send() ----- by author
int uart_putc(int ch)
{
    if (g_console_handle == NULL) {
      return -1;
    }

    if (ch == '\n') {
      int data = '\r';
      if (!aos_irq_context()) {
            hal_uart_send(g_console_handle, &amp;data, 1, AOS_WAIT_FOREVER);
      } else {
            hal_uart_send_poll(g_console_handle, &amp;data, 1);
      }
    }

    if (!aos_irq_context()) {
      hal_uart_send(g_console_handle, &amp;ch, 1, AOS_WAIT_FOREVER);
    } else {
      hal_uart_send_poll(g_console_handle, &amp;ch, 1);
    }

    return 0;
}

//位于hal_csi\v7.4.3\csi2\uart.c中,启用了互斥量 ----- by author
int32_t hal_uart_send(uart_dev_t *uart, const void *data, uint32_t size, uint32_t timeout)
{
    int32_t ret = 0;
    unsigned int actl_flags = 0;

    if (uart == NULL) {
      return -1;
    }

    aos_mutex_lock(&amp;uart_list.tx_mutex, AOS_WAIT_FOREVER);
#ifdef UART_MODE_SYNC
    int32_t num;
    num = csi_uart_send(&amp;uart_list.handle, data, size, timeout);

    if (num != size) {
      return -1;
    }
#else
    ret = csi_uart_send_async(&amp;uart_list.handle, data, size);

    if (ret &lt; 0) {
      return -1;
    }

    ret = aos_event_get(&amp;uart_list.event_write_read, EVENT_WRITE, AOS_EVENT_OR_CLEAR, &amp;actl_flags, timeout);
    if (ret != 0) {
      return -1;
    }
#endif
    aos_mutex_unlock(&amp;uart_list.tx_mutex);

    return ret;
}</code></pre>

<p>&nbsp;</p>

freebsder 发表于 2022-3-10 22:26

<p>谢谢分享,得仔细</p>

wangerxian 发表于 2022-3-11 15:33

<p>一看就是经常研究底层代码的大佬,这个坑填上了!</p>

kit7828 发表于 2022-3-13 15:01

<p>感谢分享,标记下</p>

梦溪开物 发表于 2022-3-15 09:19

<p>CDK如何进入某一个函数查看具体内容内容?</p>

梦溪开物 发表于 2022-3-15 15:52

<p>YocTools与CDK是什么关系呢?</p>

fxyc87 发表于 2022-3-17 13:47

<p>分析的不错,</p>

fxyc87 发表于 2022-3-17 13:47

梦溪开物 发表于 2022-3-15 09:19
CDK如何进入某一个函数查看具体内容内容?

<p>https://bbs.eeworld.com.cn/thread-1196869-1-1.html</p>

<p>要手动生成符号</p>
页: [1]
查看完整版本: 【平头哥RVB2601创意应用开发】使用体验01 -- printf避坑