当前位置:首页 > 公众号精选 > 嵌入式云IOT技术圈
[导读]最近在优化公司的一款基于RT-Thread操作系统的液体探测仪产品。关于RT-Thread,我最开始用的是RT-Thread Nano,所以这款产品也是基于RT-Thread nano进行开发的,关于RT-Thread之前也写了一些文章。

最近在优化公司的一款基于RT-Thread操作系统的液体探测仪产品。关于RT-Thread,我最开始用的是RT-Thread Nano,所以这款产品也是基于RT-Thread nano进行开发的,关于RT-Thread之前也写了一些文章,如下:

RT-Thread编程高阶用法-函数扩展之$Sub$$与$Super$$

移植一个实时OS很难?那就手把手教你如何快速移植一个RT-Thread Nano吧!

在这个项目中就运用到了大彩串口屏,大彩串口屏是我们的优质合作厂家,他们的技术实力相当牛逼,所以在我们的产品上运用了很多年,质量相当可观!在很早以前我在公众号和CSDN博客就写过大彩串口屏使用的相关文章:

带串口屏显示的Bootloader

大彩串口屏是基于类似消息队列的机制来实现的:

详情可以看之前的文章或者参考大彩科技官方提供的文档。

我们的产品是基于多页面进行开发的,在每个页面上又有N多个按钮控件,用于实现界面的交互。

1、串口屏解析逻辑

1.1、STM32CubeMX配置

我在CubeMX上将串口配置为DMA模式,以便于高效的进行串口屏数据的处理和接收。

1.2、软件处理逻辑

串口接收数据结构:

#define HMI_LCD_U2_BUFFER_SIZE 100
typedef struct{
    uint8_t  HMI_LCD_U2_Buffer[HMI_LCD_U2_BUFFER_SIZE];
}HMI_LCD_HandleTypeDef;
extern HMI_LCD_HandleTypeDef HMI_LCD_Handler ;

暂时定义为以上规格,方便以后拓展和维护;串口屏中断处理函数实现:

/**
  * @brief This function handles USART2 global interrupt.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
    uint32_t i ;
    uint32_t uart2_dma_rxlen ;
    /*获取空闲中断*/
    if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_IDLE) != RESET)
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart2);
        HAL_UART_DMAStop(&huart2);
        /*获取此次接收到的数据长度*/
        uart2_dma_rxlen = HMI_LCD_U2_BUFFER_SIZE - (__HAL_DMA_GET_COUNTER(huart2.hdmarx));
        /*将数据丢进大彩科技实现的环形队列里*/
        for(i = 0; i < uart2_dma_rxlen; i++)
        {
            queue_push(HMI_LCD_Handler.HMI_LCD_U2_Buffer[i]);
        }
        /*重新打开DMA和空闲中断,进行新的一轮数据接收*/
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
        HAL_UART_Receive_DMA(&huart2, HMI_LCD_Handler.HMI_LCD_U2_Buffer, HMI_LCD_U2_BUFFER_SIZE);
    }

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

在主任务中,我定义了一个串口屏任务以及信号量方式用于处理和同步串口屏上报的数据,关于创建和使用任务,创建信号量以及使用信号量的方法可以参考RT-Thread官方文档:

为什么要采用RTOS多任务?

对于普通的项目来说,比如密码锁类项目,单独的一个传感器模块的开发,某些简单的仪器仪表等等,对于这类场景单一,业务需求也单一的项目来说,使用状态机或者事件驱动的方式就足以完成项目的基本功能了。

但是如果开发一个巨量代码的工程项目,项目可能设计到传感器数据读取、无线数据上传与接收、数据传输、UI实时刷新、算法处理等等,功能诸多还需要相互配合的情况下,那么如果还在用裸机的思想去完成,那么开发者一般会面临以下两个问题:

  • 设计思路过于复杂,光怎么想程序的设计思路就得想好久了
  • 设计下来的各个功能,要考虑相互配合的问题,实时性可能得不到要求

RTOS的多任务就可以解决对应的问题,它既能让项目开发起来思路清晰,方便易维护;同时RTOS也能保证整个产品运行的实时性。

为什么要采用RTOS信号量?

信号量,俗话说就是信号的数量,它是一种任务间传递系统可用资源的机制;举一个生产者与消费者的问题;也就是说消费者在消费了一个资源之前需要等待资源释放,生产者生产资源以后要即时去通知其它的消费者,简单的说就是凡事都要有个先来后到,所以信号量最常用的地方就是实现任务间同步。

以下是我写的串口屏任务以及信号量的同步处理:

/***************串口液晶屏解析任务*************/
rt_sem_t lcd_sem = RT_NULL;
#define HMI_LCD_THREAD_PRIORITY         5
#define HMI_LCD_THREAD_STACK_SIZE       512
#define HMI_LCD_THREAD_TIMESLICE        5
static rt_thread_t hmi_lcd_thread = RT_NULL;
/*串口液晶屏线程入口函数 */
static void hmi_lcd_thread_entry(void *parameter);
/***************串口液晶屏解析任务*************/

int main(void)
{
    rt_kprintf("create main_thread\n");
    //.....省略其它代码
    /*3、创建串口屏解析任务*/
    hmi_lcd_thread = rt_thread_create("lcd_thread",
                                      hmi_lcd_thread_entry, RT_NULL,
                                      HMI_LCD_THREAD_STACK_SIZE,
                                      HMI_LCD_THREAD_PRIORITY, HMI_LCD_THREAD_TIMESLICE);

    /* 如果获得线程控制块,启动这个线程 */
    if (hmi_lcd_thread != RT_NULL)
        rt_thread_startup(hmi_lcd_thread);
    //.....省略其它代码
    while (1)
    {
        rt_thread_mdelay(100);
    }
}

/*串口液晶屏线程入口函数 */
static void hmi_lcd_thread_entry(void *parameter)
{
    rt_kprintf("create lcd_thread\n");
    rt_err_t result;
    queue_reset();
    rt_thread_mdelay(500);
    /* 创建一个动态信号量,初始值是 0 */
    lcd_sem = rt_sem_create("lcd_sem", 0, RT_IPC_FLAG_FIFO);

    if (lcd_sem == RT_NULL)
    {
        rt_kprintf("create lcd_sem failed.\n");
        return ;
    }
    /*使能空闲中断,打开DMA接收*/
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    HAL_UART_Receive_DMA(&huart2, HMI_LCD_Handler.HMI_LCD_U2_Buffer, HMI_LCD_U2_BUFFER_SIZE);
    while (1)
    {
        /*获取LCD信号量*/
        result = rt_sem_take(lcd_sem, RT_WAITING_FOREVER);
        if (RT_EOK == result)
        {
            //进行消息处理
            ProcessMessage((PCTRL_MSG)lcd_handler_def.buffer, lcd_handler_def.size);
        }
    }
}

那么这个信号量是从哪里发来的呢?在大彩串口屏提供的tft_cmd_queue.c文件中,有一个queue_find_cmd的函数,这个函数的作用就是将接收的队列进行整合的过程,在整合完毕,确认数据是大彩串口屏协议规格的时候,即是一个完整的数据帧,我们就可以在这个位置发送信号量,主任务接收到信号量,此时就完成了数据同步的过程,主任务将我自己定义的拷贝数组拿出来后,放到消息处理函数ProcessMessage里,即完成一帧数据的解析和应用。

/*我自己定义的,用来拷贝完整的数据帧*/
typedef struct
{
    qsize  size;
    qdata buffer[CMD_MAX_SIZE] ;
} lcd_handler ;
extern lcd_handler lcd_handler_def ;
/*大彩科技实现的*/
qsize queue_find_cmd(qdata *buffer, qsize buf_len)
{
    qsize cmd_size = 0;
    qdata _data = 0;

    while(queue_size() > 0)
    {
        //取一个数据
        queue_pop(&_data);

        if(cmd_pos == 0 && _data != CMD_qhead) //指令第一个字节必须是帧头,否则跳过
        {
            continue;
        }

        if(cmd_pos < buf_len) //防止缓冲区溢出
            buffer[cmd_pos++] = _data;

        cmd_state = ((cmd_state << 8) | _data); //拼接最后4个字节,组成一个32位整数

        //最后4个字节与帧尾匹配,得到完整帧
        if(cmd_state == CMD_TAIL)
        {
            cmd_size = cmd_pos; //指令字节长度
            cmd_state = 0;  //重新检测帧尾巴
            cmd_pos = 0; //复位指令指针

            #if(CRC16_ENABLE)

            //去掉指令头尾EE,尾FFFCFFFF共计5个字节,只计算数据部分CRC
            if(!CheckCRC16(buffer + 1, cmd_size - 5)) //CRC校验
                return 0;

            cmd_size -= 2;//去掉CRC16(2字节)
            #endif
            /*发送队列*/
            lcd_handler_def.size = cmd_size ;
            memcpy(lcd_handler_def.buffer, buffer, cmd_size);
            /*给出一个信号量*/
            rt_sem_release(lcd_sem);
            queue_reset();
            return cmd_size;
        }
    }
    //没有形成完整的一帧
    return 0;
}

由于我的项目涉及多个页面以及多个按钮控件的交互,所以按钮控件的处理函数用得是最多的:

/*!
 *  \brief  按钮控件通知
 *  \details  当按钮状态改变(或调用GetControlValue)时,执行此函数
 *  \param screen_id 画面ID
 *  \param control_id 控件ID
 *  \param state 按钮状态:0弹起,1按下
 */
void NotifyButton(u16 screen_id, u16 control_id, u8  state)
{
    //TODO: 添加用户代码
  Menu_Select_Item(screen_id,control_id,state);
}

我是怎么来做的呢?根据之前写表驱动状态机的思维,将这个过程抽象成了一个框架结构,如下:

/*当前菜单*/
typedef enum
{
    WELCOME_PAGE = 0,
    MAIN_PAGE,
    SETTING_PAGE,
    BACKLIGHT_PAGE,
    VOLUME_PAGE,
    LANGUAGE_PAGE,
    TIMESET_PAGE,
    DB_UPDATE_PAGE,
    DATA_UP_PAGE,
    PASSWORD_PAGE,
    PASSWORD_START_PAGE,
    PROJECT_CONFIGUARE_PAGE,
    PROJECT_SELECT_PAGE,
    METAL_TEST_PAGE,
    NONMETAL_TEST_PAGE,
    USB_MODE_PAGE,
    RECOVERY_PAGE,
    DEVICE_TEST_PAGE,
    CREATE_NONMETAL_SAMPLE_LIB_PAGE1,
    CREATE_NONMETAL_SAMPLE_LIB_PAGE2,
    CREATE_NONMETAL_SAMPLE_LIB_PAGE3,
    CLEART_NONMETAL_SAMPLE_LIB_PAGE4,
    CREATE_NEW_ST_SAMPLE_LIB_PAGE,
    CLEAR_SAMPLE_LIB_PAGE,
    CLEAR_SAMPLE_LIB_CONFIRM_PAGE,
    CLEAR_DETECT_CONFIRM_PAGE
} OP_PAGE;

typedef void (*menu_op_func)(uint16_t, uint8_t);
typedef struct OP_STRUCT
{
    uint16_t op_menu ;     /*操作菜单*/
    menu_op_func opfun ;  /*带参数的操作方法*/
} OP_MENU_PAGE;

typedef struct
{
    /*界面操作游标*/
    uint16_t flow_cursor ;
} Cursor ;
extern Cursor Flow_Cursor ;


/*菜单初始化*/
void Menu_Init(void);
/*跳转到下一个菜单*/
void Menu_Jump(uint16_t screen_id);
/*菜单操作*/
void Menu_Select_Item(uint16_t screen_id, uint16_t control_id, uint8_t state);

然后一堆复制粘贴,就复用了以前写的一套代码:

基于事件型表驱动法菜单框架之小熊派简易气体探测器实战项目开发(上)

基于事件型表驱动法菜单框架之小熊派简易气体探测器实战项目开发(中)

TencentOS tiny危险气体探测仪产品级开发重磅高质量更新(Flash都快用完了!)

成功的将跳转表的思想运用到了串口屏多页面多按钮的交互菜单产品开发上:

Cursor Flow_Cursor ;

/*菜单操作表定义*/
static OP_MENU_PAGE g_opStruct[] =
{
    {WELCOME_PAGE,  NULL},           /*欢迎页面*/
    {MAIN_PAGE      , main_page_process},     /*主页面*/
  {SETTING_PAGE   , setting_page_process},   /*设置页面*/
  {BACKLIGHT_PAGE , backlight_page_process},  /*背光设置页面*/
  {VOLUME_PAGE  , volume_page_process},    /*音量设置页面*/
  {LANGUAGE_PAGE,NULL},
  {TIMESET_PAGE  , datetime_page_process},   /*日期时间设置页面*/
  {DB_UPDATE_PAGE,NULL},
  {DATA_UP_PAGE,NULL},
  {PASSWORD_PAGE,NULL},
  {PASSWORD_START_PAGE,NULL},
  {PROJECT_CONFIGUARE_PAGE,engineer_password_page_process}, /*工程配置页面密码输入*/
  {PROJECT_SELECT_PAGE,engineer_select_page_process}, /*工程选择页面*/
  {METAL_TEST_PAGE,metal_test_page_process},
  {NONMETAL_TEST_PAGE,nonmetal_test_page_process},
  {USB_MODE_PAGE,NULL},
  {RECOVERY_PAGE,recovery_page_process},
  {DEVICE_TEST_PAGE,NULL},
  {CREATE_NONMETAL_SAMPLE_LIB_PAGE1,NULL},
  {CREATE_NONMETAL_SAMPLE_LIB_PAGE2,NULL},
  {CREATE_NONMETAL_SAMPLE_LIB_PAGE3,NULL},
  {CLEART_NONMETAL_SAMPLE_LIB_PAGE4,NULL},
  {CREATE_NEW_ST_SAMPLE_LIB_PAGE,NULL},
  {CLEAR_SAMPLE_LIB_PAGE,NULL},
  {CLEAR_SAMPLE_LIB_CONFIRM_PAGE,NULL},
  {CLEAR_DETECT_CONFIRM_PAGE,NULL},
};


/*跳转到表所对应的页面*/
static int JUMP_Table(int16_t op, int16_t control_id, uint8_t state)
{
    assert(op >= sizeof(g_opStruct) / sizeof(g_opStruct[0]));
    assert(op < 0);
  if(g_opStruct[op].opfun != NULL)
   g_opStruct[op].opfun(control_id, state);
  else
   printf("current_page op is null!\n");
    return 0 ;
}

/*菜单初始化*/
void Menu_Init(void)
{
    current_screen_id = MAIN_PAGE ;
    Flow_Cursor.flow_cursor = current_screen_id ;
    SetScreen(Flow_Cursor.flow_cursor);
}

/*跳转到下一个菜单*/
void Menu_Jump(uint16_t screen_id)
{
    current_screen_id = screen_id ;
    Flow_Cursor.flow_cursor = current_screen_id ;
    SetScreen(Flow_Cursor.flow_cursor);
}

/*菜单选择项*/
void Menu_Select_Item(uint16_t screen_id, uint16_t control_id, uint8_t state)
{
    JUMP_Table(screen_id, control_id, state);
}

以背光调节为例,结合大彩科技的VisualTFT界面开发软件所画的UI如下:

根据大彩串口屏背光调节实现代码逻辑实现如下:

/*************按钮****************/
#define BACKLIGHT_SUB_BUTTON  1
#define BACKLIGHT_ADD_BUTTON  2
#define BACKLIGHT_BACK_BUTTON 8
/*************按钮****************/
#define BACKLIGHT_LEVEL    3
#define BACKLIGHT_LEVEL_TEXT  5

BackLight_Handler backlight_handler ;

//背光调节
void Backlight_level_set(uint8_t level)
{
    uint8_t text_buf[20] = {0};

    switch(level)
    {
    case 0:
        SetBackLight(200);
        break ;

    case 1:
        SetBackLight(160);
        break ;

    case 2:
        SetBackLight(120);
        break ;

    case 3:
        SetBackLight(80);
        break ;

    case 4:
        SetBackLight(40);
        break ;

    case 5:
        SetBackLight(0);
        break ;

    default:
        break ;
    }

    para_stroge.Backligh_level = level ;
    sprintf((char *)text_buf, "当前亮度:%d", level);
    SetTextValue(BACKLIGHT_PAGE, BACKLIGHT_LEVEL_TEXT, text_buf);
    AnimationPlayFrame (BACKLIGHT_PAGE, BACKLIGHT_LEVEL, level);
}

/*背光页面初始化*/
void backlight_page_init(void)
{
    backlight_handler.level = para_stroge.Backligh_level ;
    Menu_Jump(BACKLIGHT_PAGE);
}

/*背光页面操作*/
void backlight_page_process(uint16_t control_id, uint8_t state)
{
    switch(control_id)
    {
    case BACKLIGHT_SUB_BUTTON:
        (backlight_handler.level > 0) ? (backlight_handler.level--) : (backlight_handler.level = 0);
        Backlight_level_set(backlight_handler.level);
        break ;

    case BACKLIGHT_ADD_BUTTON:
        (backlight_handler.level < 5) ? (backlight_handler.level++) : (backlight_handler.level = 5);
        Backlight_level_set(backlight_handler.level);
        break ;

    case BACKLIGHT_BACK_BUTTON:
        WriteUserFlash(0x00000000, 1, ¶_stroge.Backligh_level);
        Menu_Jump(SETTING_PAGE);
        break ;

    default:
        break ;
    }
}

然后此时我们需要在菜单框架里将backlight_page_process注册到对应的区域,这样在对应的界面才能对相应的按钮事件进行一一关联:

由于代码涉及了一些产品的核心需求,故不能完全开放,但与大彩串口屏多界面交互的思想就是状态机+表驱动,这是任何一个项目都可以应用的。

2、腾讯云征文

本人受邀担任第一季【年度征文】活动的评审,朋友圈转发活动详情海报和我的个人评审海报并保留至活动截稿日 12月23 日,将截图发送给发送给云+社区小编(微信号:juxiaoka66)参与抽奖,将抽取5位读者每人一张腾讯视频月卡(云+社区定制卡面)活动详情链接:https://cloud.tencent.com/developer/article/1752258

往期精彩

RT-Thread编程高阶用法-函数扩展之$Sub$$与$Super$$

上海出差之行--领略外滩美景、RT-Thread总部之旅、嵌友面基、返程记录

移植一个实时OS很难?那就手把手教你如何快速移植一个RT-Thread Nano吧!

整理了很久之前在码云/Github/CSDN上收藏的嵌入式产品级项目分享开源

觉得本次分享的文章对您有帮助,随手点[在看]并转发分享,也是对我的支持。

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭