【平头哥RVB2601创意应用开发】使用体验01 -- printf避坑
[复制链接]
本帖最后由 sonicfirr 于 2022-3-10 13:10 编辑
在使用RVB2601的过程中,免不了使用到printf()或是SDK提供的LOG输出,再加上本人参与了创意应用开发活动,为了完成比赛项目,逐一功能模块测试,在测试到实时内核任务管理时出现了一个小bug,通过研究源码成功避坑,因而决定写下此篇博文。
1、案例设计
案例由HelloWorld Demo扩展而来,分析源码了解启动流程。程序是从startup.S开始,到SystemInit()初始化RAM、中断、时钟、DMA、系统节拍、SPIFlash等,再到pre_main(),然后跳转到main()函数。
不过,SDK中pre_main()找到了两个,一个在chip_ch2601包,一个在aos包,这里我猜想一个是裸机版入口,一个是RTOS版入口。Demo默认是开启了控制台的,所以RTOS版的可能性最大,实际情况通过“ps命令”查看,也发现的确是有一个“app_task”进程在运行,说明入口函数是aos包中的版本。于是分析任务函数application_task_entry(),它调用了main函数进入了Demo的功能实现代码部分。
2、创建任务
这里,本人偷点懒,没有去看手册,直接仿照app_task的格式,编写自己的任务,实现逻辑是:新建任务“my_task”,优先级高于启动任务“app_task”,my_task置位标志量flag,然后休眠,app_task运行判断flag置位则做串口输出,然后清零flag。
#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(&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;
}
所谓坑点,就是在main()函数的while循环中,如果没有加入延时,就会卡死,串口无输出,控制台命令也不起作用了。加了延时,哪怕只有1ms就可以保证运行正常。
3、坑点分析
以本人使用RTOS的经验来猜想,这里很有可能是串口异步操作引起的(其实,当时先以为任务创建有问题,毕竟没有看手册,后来核对无误,才想到这里),于是追踪串口输出的实现过程一路看下去。
日志输出的宏“LOGD”等,都定义在ulog包的ulog.h中——#define LOG(...) ulog(LOG_ALERT, "AOS", ULOG_TAG, __VA_ARGS__),而ulog()函数则是定义在ulog.c中。函数没有细致分析,两个地方引起了本人注意:一是日志操作前申请了互斥量,二是日志输出也是调用printf实现的。所以本人还是去看printf的实现过程。
在newlib包的printf.c中,printf函数调用了_vsnprintf函数做格式化,然后使用了“_out_char()”做输出,_out_char()则调用了“_putchar()”,_putchar()调用了“uart_putc()”。
//位于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[1];
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);
}
uart_putc()定义在aos包的console_uart.c中,它调用了“hal_uart_send()”,而hal_uart_send()则是驱动库hal_csi中定义的,看源码也是启动了互斥量。至此,代码追踪差不多了,可见串口且绑定控制台的独一性资源,系统是通过互斥量来申请并使用的。所以本人设计的任务中通过互斥量占用了串口,没有及时释放资源(这里猜想应该是,UART启用了DMA,所以产生了异步操作,任务休眠时,串口还没有来得及输出完毕,互斥量没有得到释放),而加入了延时后,资源得以释放,功能也就正常了。
看来在多任务使用串口时必须做好串口操作的时长预估,并设计好任务调度流程,以免再出现卡死。
//位于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, &data, 1, AOS_WAIT_FOREVER);
} else {
hal_uart_send_poll(g_console_handle, &data, 1);
}
}
if (!aos_irq_context()) {
hal_uart_send(g_console_handle, &ch, 1, AOS_WAIT_FOREVER);
} else {
hal_uart_send_poll(g_console_handle, &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(&uart_list[uart->port].tx_mutex, AOS_WAIT_FOREVER);
#ifdef UART_MODE_SYNC
int32_t num;
num = csi_uart_send(&uart_list[uart->port].handle, data, size, timeout);
if (num != size) {
return -1;
}
#else
ret = csi_uart_send_async(&uart_list[uart->port].handle, data, size);
if (ret < 0) {
return -1;
}
ret = aos_event_get(&uart_list[uart->port].event_write_read, EVENT_WRITE, AOS_EVENT_OR_CLEAR, &actl_flags, timeout);
if (ret != 0) {
return -1;
}
#endif
aos_mutex_unlock(&uart_list[uart->port].tx_mutex);
return ret;
}
|