转:rt-thread下finsh工作流程概述-使用串口驱动
(2015-02-28 09:43:05)
标签:
itrttfinsh |
分类: 工作自侃 |
以下内容摘自wiki百科
目的以及术语约定
本文讨论基于stm32f10x BSP。
本文目的:
-
通过finsh的源码研究如何使用串口驱动
-
分析,当我们在putty等终端软件上输入命令时,这个交互过程,RTT都执行了那些函数调用
-
stm32的串口不足讨论
*终端*
windows里自带的超级终端软件,或者其他串口软件,比如putty,Secure CRT等等。
finsh的大致工作流程
先来看 startup.c main函数
#ifdef RT_USING_FINSH finsh_system_init(); finsh_set_device("uart1"); #endif
finsh_system_init函数
这个函数会初始化finsh组件,包括一些finsh变量以及相关数据结构。
然后它会创建一个线程,代码如下:
result = rt_thread_init(&finsh_thread, "tshell", finsh_thread_entry, RT_NULL, &finsh_thread_stack[0], sizeof(finsh_thread_stack), FINSH_THREAD_PRIORITY, 10);if (result == RT_EOK) rt_thread_startup(&finsh_thread);
可以看到,线程函数是finsh_thread_entry,在下一节中我们将分析它具体工作流程。
finsh_set_device函数
void finsh_set_device(const char* device_name) { rt_device_t dev = RT_NULL;RT_ASSERT(shell != RT_NULL); dev = rt_device_find(device_name); if (dev != RT_NULL && rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) == RT_EOK) { if (shell->device != RT_NULL) { rt_device_close(shell->device); } shell->device = dev; rt_device_set_rx_indicate(dev, finsh_rx_ind); } else { rt_kprintf("finsh: can not find device:%s\n", device_name); } }
这个函数为finsh组件设置使用的串口,从这个函数中我们可以总结出,如何使用串口设备。
-
调用rt_device_find使用设备的字符串名字查找设备,得到设备数据结构指针
-
调用rt_devcie_open打开设备
* 对finsh来说,还使用了rt_device_set_rx_indicate函数设置了一个回调函数,它的作用我们后面会讨论
到这里设备就被打开了。
stm32f10x/serail.c:
if (device->rx_indicate != RT_NULL) { rt_size_t rx_length;rx_length = uart->int_rx->read_index > uart->int_rx->save_index ? UART_RX_BUFFER_SIZE - uart->int_rx->read_index + uart->int_rx->save_index : uart->int_rx->save_index - uart->int_rx->read_index; device->rx_indicate(device, rx_length); }
上面计算得到rx_length,然后触发回调函数,也就是前面的finsh_rx_ind函数,即实际执行的是
fins_rx_ind(device, rx_length)
而finsh_rx_ind的源码如下:
static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size) { RT_ASSERT(shell != RT_NULL);rt_sem_release(&shell->rx_sem); return RT_EOK; }
这个函数里只是简单的释放信号量。也就是说,当串口硬件上接收到一个字节,就会调用finsh_rx_ind函数来释放一个信号量。
finsh线程函数的工作流程概述
前面已经说明,finsh的线程函数是 finsh_thread_entry,代码如下:
void finsh_thread_entry(void* parameter) { char ch;shell->echo_mode = 1; finsh_init(&shell->parser); rt_kprintf(FINSH_PROMPT); while (1) { if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue; while (rt_device_read(shell->device, 0, &ch, 1) == 1) { #ifdef FINSH_USING_HISTORY if (finsh_handle_history(shell, ch) == RT_TRUE) continue; #endif if (ch == '\r') { char next; if (rt_device_read(shell->device, 0, &next, 1) == 1) ch = next; else ch = '\r'; } else if (ch == '\t') { finsh_auto_complete(&shell->line[0]); shell->line_position = strlen(shell->line); continue; } else if (ch == 0x7f || ch == 0x08) { if (shell->line_position != 0) { rt_kprintf("%c %c", ch, ch); } if (shell->line_position <= 0) shell->line_position = 0; else shell->line_position --; shell->line[shell->line_position] = 0; continue; } if (ch == '\r' || ch == '\n') { shell->line[shell->line_position] = ';'; #ifdef FINSH_USING_HISTORY finsh_push_history(shell); #endif if (shell->line_position != 0) finsh_run_line(&shell->parser, shell->line); else rt_kprintf("\n"); rt_kprintf(FINSH_PROMPT); memset(shell->line, 0, sizeof(shell->line)); shell->line_position = 0; break; } if (shell->line_position >= FINSH_CMD_SIZE) shell->line_position = 0; shell->line[shell->line_position] = ch; ch = 0; if (shell->echo_mode) rt_kprintf("%c", shell->line[shell->line_position]); shell->line_position ++; shell->use_history = 0; } } }
函数主体依然是一个while(1)循环,这是显然的,因为finsh要不停的监听终端上输入。
if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue;
即,如果串口上没有收到任何数据,并且串口缓冲区中也无数据,即shell→rx_sem信号量的值为0,那么这个函数会使finsh线程休眠,RTT内核会执行其他线程。
当串口收到数据,串口终端调用回调函数finsh_rx_ind函数来释放信号量,这会唤醒finsh线程,rt_sem_take函数会执行完毕,继续执行接下来的代码。
接下来的代码调用rt_device_read函数从串口数据缓冲池中读取一个字节。
然后判断所读取的到这个字节。
(1) 如果是'\r',即表示用户按下了回车键,再调用rt_device_read函数来读取一个字节,如果读到,则这将更新读到的字节,一般情况下,这个函数会返回0,即没有读到新的字节。
(2) 如果是'\t',即表示用户按下了TAB键,则调用finsh_auto_complete函数,这个函数做自动补全操作,也就是根据当前已输入的字符串,从finsh内部已注册的函数/变量中查找匹配字符串,如果找到则会在终端上自动补全。
(3) 如果是0x7f或者0x08 说明:查ascii码表可知,0x08 表示按下了backspace键,【0x7f表示按下了DEL键,这个不对劲,如何知道当我们按下了键盘按键时,串口都收到了什么数据呢?】 这表示用户期望删除已经输入的字符串,根据测试结果,发送”\0x08 \0x08”,可以实现退格。
(4) 如果收到了'\r'或者'\n',则表示用户按下了回车,希望处理这个命令,那么finsh_run_line函数被执行,这个函数会从从finsh已注册的函数/变量中匹配当前从终端里获取的字符串,如果匹配到,则执行对应的函数(若字符串为函数名)或者打印变量的值(若字符串为已变量)。
(5) 回显字符,也就是将刚才从串口接收到终端发送的字符发送到终端软件上显示出来。这就是说,我们在终端软件上输入字符,并且可以看到我们输入的字符,实际上是板子上的串口重新发回来显示的。
然后回到(1),重复这个过程。
串口驱动使用总结
上一节对finsh线程函数的工作流程分析,我们就已经学习了如何使用串口驱动。这里大致做一个总结。
(1)执行rt_device_open函数打开相应的驱动,
(2)使用rt_device_read函数读取串口数据。
由于串口是一种低速的串行设备,我们并不知道什么时候会收到串口数据,因此在这个函数之前,先使用了信号量函数,rt_sem_take来获取信号量,这样的好处是,如果没有串口数据时,线程会被挂起,当串口数据到来时,线程会被唤醒继续执行。
实际上对于串行设备来说,信号量的方式是必要的,并且最常使用的方式。这也是操作系统书籍里介绍的生产者和消费者问题的具体例子。
关于生产者和消费者问题的介绍,请参考操作系统理论书籍,这里推荐《操作系统设计与实现》。
(3)向串口写入数据可以调用rt_device_write函数,在上面finsh的线程代码中,这个函数是在rt_kprintf中调用的。