二哲科技 发表于 2023-9-16 15:46:46

【开源】重磅!基于迪文屏的DE-RTOS第一篇Shell

本帖最后由 二哲科技 于 2023-9-16 15:49 编辑

1、介绍

迪文屏的开发者非常的多,但是迪文没有一个很好的生态系统,像国产RTThread一样,拥有多种组件,多种传感器,能够快速的实现产品开发,为此我设计了一款基于C51的DE-RTOS,希望能成为迪文的操作系统之一。
该系统是参考RTThread的文件结构,也为了后续方便移植RTThread的各种组件和软件包,RTThread的系统框架如下图所示:


2、设计难点
目前基于C51的操作系统不多,RTX51系列是不开源的,所以C51的任务跳转也是比较难实现的,我也在找各种方法,后续还准备设计一款类似STM32CubeMX的图形配置软件,可以直接通过图形化进行外设、组件和软件包的配置,这个软件周期也会比较长,需要先完善DE-RTOS之后再去设计图形化配置软件。下面是STM32CubeMX的软件界面。



3、设计
1)代码设计
需要设计好文件框架,我是直接参考RTThread的文件框架,同时添加上一些迪文和自己的内容,文件框架如下图所示:



接下来就是移植Shell,在移植Shell的时候,其实就移植了一部分的RTThread,这部分移植还是挺复杂的,需要对串口数据进行FIFO的处理,然后再进行解码,根据解码的结果去调用合适的函数。调用函数用到了函数指针,这样就可以根据字符串的内容去执行相关函数,同时还可以传递数据。
大概贴一下Shell函数解析的代码。
void shell_task_entry()
{
    static uint8_t frist_flag = 0;
    char ch = 0;
    int8_t ret = 0;

    if(frist_flag == 0)
    {
      frist_flag = 1;
      shell.echo_mode = 1;
      printf(FINSH_PROMPT);
    }


    //while(1)
    {
      
      ch = de_hw_console_getchar();
      if(ch == 0)
      {
            return;
      }
            
      // printf("%c", ch);

      /*
         * handle control key
         * up key: 0x1b 0x5b 0x41
         * down key: 0x1b 0x5b 0x42
         * right key:0x1b 0x5b 0x43
         * left key: 0x1b 0x5b 0x44
         */
      if (ch == 0x1b)
      {
            shell.stat = WAIT_SPEC_KEY;
            return;
      }
      else if (shell.stat == WAIT_SPEC_KEY)
      {
            if (ch == 0x5b)
            {
                shell.stat = WAIT_FUNC_KEY;
                return;
            }

            shell.stat = WAIT_NORMAL;
      }
      else if (shell.stat == WAIT_FUNC_KEY)
      {
            shell.stat = WAIT_NORMAL;

            if (ch == 0x41) /* up key */
            {
#ifdef FINSH_USING_HISTORY
                /* prev history */
                if (shell.current_history > 0)
                  shell.current_history --;
                else
                {
                  shell.current_history = 0;
                  return;
                }

                /* copy the history command */
                memcpy(shell.line, &shell.cmd_history,
                     FINSH_CMD_SIZE);
                shell.line_curpos = shell.line_position = strlen(shell.line);
                shell_handle_history(&shell);
#endif
                return;
            }
            else if (ch == 0x42) /* down key */
            {
#ifdef FINSH_USING_HISTORY
                /* next history */
                if (shell.current_history < shell.history_count - 1)
                  shell.current_history ++;
                else
                {
                  /* set to the end of history */
                  if (shell.history_count != 0)
                        shell.current_history = shell.history_count - 1;
                  else
                        return;
                }

                memcpy(shell.line, &shell.cmd_history,
                     FINSH_CMD_SIZE);
                shell.line_curpos = shell.line_position = strlen(shell.line);
                shell_handle_history(&shell);
#endif
                return;
            }
            else if (ch == 0x44) /* left key */
            {
                if (shell.line_curpos)
                {
                  printf("\b");
                  shell.line_curpos --;
                }

                return;
            }
            else if (ch == 0x43) /* right key */
            {
                if (shell.line_curpos < shell.line_position)
                {
                  printf("%c", shell.line);
                  shell.line_curpos ++;
                }

                return;
            }
      }

      /* received null or error */
      if (ch == '\0' || ch == 0xFF) return;
      /* handle tab key */
#ifdef FINSH_USING_SYMTAB
      else if (ch == '\t')
      {
            int i;
            /* move the cursor to the beginning of line */
            for (i = 0; i < shell.line_curpos; i++)
                printf("\b");

            /* auto complete */
            shell_auto_complete(&shell.line);
            /* re-calculate position */
            shell.line_curpos = shell.line_position = strlen(shell.line);

            return;
      }
#endif
      /* handle backspace key */
      else if (ch == 0x7f || ch == 0x08)
      {
            /* note that shell.line_curpos >= 0 */
            if (shell.line_curpos == 0)
                return;

            shell.line_position--;
            shell.line_curpos--;

            if (shell.line_position > shell.line_curpos)
            {
                int i;

                de_memmove(&shell.line,
                           &shell.line,
                           shell.line_position - shell.line_curpos);
                shell.line = 0;

                printf("\b%s\b", &shell.line);

                /* move the cursor to the origin position */
                for (i = shell.line_curpos; i <= shell.line_position; i++)
                  printf("\b");
            }
            else
            {
                printf("\b \b");
                shell.line = 0;
            }

            return;
      }

      /* handle end of line, break */
      if (ch == '\r' || ch == '\n')
      {
#ifdef FINSH_USING_HISTORY
            shell_push_history(&shell);
#endif

#ifdef FINSH_USING_MSH
            
            shell_screen_append(0, shell.line, shell.line_position);
            if (cmd_is_used() == DE_TRUE)
            {
                if (shell.echo_mode)
                  printf("\r\n");
                cmd_exec(shell.line, shell.line_position);
            }
            else
#else
            printf("\r\n");
#endif
            {
#ifndef FINSH_USING_MSH_ONLY
                /* add ';' and run the command line */
                shell.line = ';';

                if (shell.line_position != 0)
                  finsh_run_line(&shell.parser, shell.line);
                else
                  if (shell.echo_mode) printf("\n");
#endif
            }
            printf(FINSH_PROMPT);
            memset(shell.line, 0, sizeof(shell.line));
            shell.line_curpos = shell.line_position = 0;
            return;
      }

      /* it's a large line, discard it */
      if (shell.line_position >= FINSH_CMD_SIZE)
            shell.line_position = 0;

      /* normal character */
      if (shell.line_curpos < shell.line_position)
      {
            int i;

            de_memmove(&shell.line,
                     &shell.line,
                     shell.line_position - shell.line_curpos);
            shell.line = ch;
            if (shell.echo_mode)
                printf("%s", &shell.line);

            /* move the cursor to new position */
            for (i = shell.line_curpos; i < shell.line_position; i++)
                printf("\b");
      }
      else
      {
            shell.line = ch;
            if (shell.echo_mode)
                printf("%c", ch);
      }

      ch = 0;
      shell.line_position ++;
      shell.line_curpos++;
      if (shell.line_position >= FINSH_CMD_SIZE)
      {
            /* clear command line */
            shell.line_position = 0;
            shell.line_curpos = 0;
      }
    } /* end of device read */
}

然后就是判断执行什么函数,如果输入内容错误,则直接返回错误提示。
void cmd_exec(char *cmd_line, uint8_t cmd_len)
{
    if(cmd_len > 0)
    {
      uint8_t i = 0;
      char cmd_line_temp = {0};

      memcpy(cmd_line_temp, cmd_line, cmd_len);
      for(i = 0;i < cmd_cnt;i++)
      {
            if(memcmp(cmd_list.name, cmd_line_temp, strlen(cmd_list.name)) == 0 && cmd_len >= strlen(cmd_list.name))
            {
                cmd_list.function(cmd_line_temp);
                //printf("ret:%d\r\n", cmd_list.function(cmd_line_temp));
                break;
            }
      }
      if(i == cmd_cnt)
      {
            cmd_error(cmd_line_temp);
      }
    }
}


同时我还设计了控制屏幕控件的函数,可以不用阅读【T5L_DGUSII_应用开发指南】就可以对控件进行操作,目前只设计了文本和基本图形中的矩形,后续会完善剩下的,代码如下所示:
void base_graph_rectangle_fill_set(uint16_t addr, base_graph_param_t base_graph_param_temp[], uint8_t len)
{
    uint8_t cmd = {0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x03, 0x20, 0xAA, 0xAA, 0xFF, 0x00};
    uint8_t i = 0;

    if(len > 24)    //一次最多24个数据
      len = 24;

    //图形数量
    cmd = len;

    for(i = 0;i < len;i++)
    {
      cmd = base_graph_param_temp.start_x / 256;
      cmd = base_graph_param_temp.start_x % 256;
      cmd = base_graph_param_temp.start_y / 256;
      cmd = base_graph_param_temp.start_y % 256;
      cmd = base_graph_param_temp.end_x / 256;
      cmd = base_graph_param_temp.end_x % 256;
      cmd = base_graph_param_temp.end_y / 256;
      cmd = base_graph_param_temp.end_y % 256;
      cmd = base_graph_param_temp.color / 256;
      cmd = base_graph_param_temp.color % 256;
    }

    cmd = 0xFF;
    cmd = 0x00;

    sys_write_vp(addr, cmd, (len * 10 + 6) / 2);
   
}

//文本变量设置
void text_display_value_set(uint16_t addr, uint16_t offset, char *text_data, uint16_t text_len)
{
    sys_write_vp(addr + offset, text_data, text_len);
}

//文本描述设置
void text_display_describe_set(uint16_t addr, uint32_t mask, uint16_t value_addr, int16_t x, int16_t y)
{
    if(mask & TEXT_MASK_VALUE_ADDR == TEXT_MASK_VALUE_ADDR)
    {
      sys_write_vp(addr, (uint8_t *)&value_addr, 1);
    }
}


最后还有一个触摸的回调,通过注册按键、添加按键回调来实现按键的触发,保证了系统的分层调用关系。代码如下:
void touch_init(uint16_t touch_addr)
{
    touch_address = touch_addr;
}

void touch_insert(uint16_t touch_value, touch_function_t touch_function_temp)
{
    touch_function = touch_function_temp;
    touch_values = touch_value;
    touch_function_cnt++;
}

//用于产生按钮点击扫描信号,放在1ms的定时器中调用即可
void touch_tick()
{
        #define BTN_SCAN_PERIOD                100   //按钮点击事件的扫描周期,单位ms
        static idata uint8_t tick = 0;
       
        tick++;
        if(tick == BTN_SCAN_PERIOD)
        {
                tick = 0;
                is_btn_scan = 1;//产生一个按钮点击扫描信号
        }
}

//按钮点击事件扫描并处理
void touch_handler()
{
        uint16_t btn_val;
    uint8_t i = 0;
       
        if(is_btn_scan == 0)//是否有扫描信号,没有的话就啥也不做,直接返回
                return;
        is_btn_scan = 0;//清除
       
        //1.开始检查是否有按钮按下
        sys_read_vp(touch_address, (uint8_t*)&btn_val, 1);
        if(btn_val == 0)
                return;
       
        //2.按钮点击事件处理
        for(i = 0;i < touch_function_cnt;i++)
    {
      if(btn_val == touch_values)
      touch_function();
    }

        //3.清除按键值
        btn_val = 0;
        sys_write_vp(touch_address, (uint8_t*)&btn_val, 1);
}



2)界面设计
界面设计就是简单的显示Shell的输入和输出,不过也用到了一些新技术,通过修改变量地址来实现翻页,这样就非常方便,界面素材只有三张图。



一共就两个界面,首页和显示页,有技术难度的在显示页,可以实现自动翻页和自动换行功能,主要是通过代码实现的,具体如下:
#define PAGE_LINE_MAX   17//一页最多十七行

uint8_t page_total = 0;         //总页数
uint8_t page_current = 0;       //当前页数
uint8_t last_page_lines = 0;    //最后一页的行数
uint8_t last_page_length = 0;   //最后一页的字符数
uint16_t page_addr = 0x2000;

void display_page_status()
{
    char page_temp = {0};

    page_temp = (page_current + 1) / 10 + '0';
    page_temp = (page_current + 1) % 10 + '0';
    page_temp = '/';
    page_temp = (page_total + 1) / 10 + '0';
    page_temp = (page_total + 1) % 10 + '0';
    text_display_value_set(0x1100, 0, page_temp, 3);
}

void shell_screen_append(uint8_t mode, char *line_data, uint8_t line_len)
{
    char line_temp = {0};
    char line_cnt = 0;
    uint8_t i = 0;
    uint8_t current_line_cnt = 0;

    if(mode == 0)
    {
      memcpy(line_temp, "msh >", 5);
      line_cnt = 5;
    }
    if(page_current != page_total)
    {
      page_current = page_total;
      text_display_describe_set(0x1200, TEXT_MASK_VALUE_ADDR, page_addr + page_current * 0x200, 0, 0);
    }
   
    for(i = 0;i < line_len;i++)
    {
      line_temp = line_data;
    }

    current_line_cnt = (line_cnt / 25) + 1;
    last_page_lines += (line_cnt / 25) + 1;

    if(last_page_lines > PAGE_LINE_MAX)
    {
      page_total++;
      page_current = page_total;
      last_page_lines = current_line_cnt;
      last_page_length = 0;
      text_display_describe_set(0x1200, TEXT_MASK_VALUE_ADDR, page_addr + page_current * 0x200, 0, 0);
      display_page_status();
    }

    if(line_cnt % 2 == 1)
    {
      if(line_cnt % 25 < 23)
      {
            line_temp = ' ';
            line_temp = '\r';
            line_temp = '\n';
      }
      else if(line_cnt % 25 == 24)
      {
            line_temp = ' ';
      }
    }
    else
    {
      if(line_cnt % 25 < 24)
      {
            line_temp = '\r';
            line_temp = '\n';
      }
    }
//    printf("\r\n%s %d\r\n", line_temp, line_len);
    text_display_value_set(page_addr + page_current * 0x200, last_page_length / 2, line_temp, line_cnt / 2);
    last_page_length += line_cnt;
}

void page_next()
{
    // printf("page_next");
    if(page_current < page_total)
    {
      page_current++;
      text_display_describe_set(0x1200, TEXT_MASK_VALUE_ADDR, page_addr + page_current * 0x200, 0, 0);
      display_page_status();
    }
}

void page_last()
{
    // printf("page_last");
    if(page_current > 0)
    {
      page_current--;
      text_display_describe_set(0x1200, TEXT_MASK_VALUE_ADDR, page_addr + page_current * 0x200, 0, 0);
      display_page_status();
    }
}


4、结论
C51移植Shell还是相对麻烦的,因为有些变量是没有的,所以需要通过别的方法来实现。好在整体功能都实现了,后续DE-RTOS希望有更多的开发者一起参与,来完善迪文的操作系统生态,我也会好好维护和升级DE-RTOS。
开源地址:https://gitee.com/erzhekeji/dwin_dertos.git
界面工程:
演示视频:https://www.bilibili.com/video/BV14h4y1A7wd/

leozhongwang 发表于 2023-9-16 21:16:39

好东西必须顶一个。

xiangliverygood 发表于 2023-9-19 09:07:20

这个必须顶!
页: [1]
查看完整版本: 【开源】重磅!基于迪文屏的DE-RTOS第一篇Shell