搞嵌入式开发却不会写上位机?来了解下小白的福音Micro-Lab(多个案例带你快速入门)...

    技术2022-07-10  138

    在工作中,以什么样的方式向领导汇报工作最直接高效呢?当然是图形界面!图形界面更好表达一个程序设计的逻辑思维,一目了然,本次介绍的Micro-Lab出自风媒科技-赵工之手,赵工在前两年也出版本过一本技术书籍<<STM32物联网实战教程>>,将自己毕生的开发经验汇集成册并开源pdf,并在电子发烧友等论坛推出了自己的30天入门物联网的免费教程。

    1、什么是Micro-Lab?

    Micro-Lab可以称得上是迄今为止最棒的嵌入式调试工具,在调试过程中遇到的所有痛点,在这里都得以解决,并引入了很多新的功能,如串口/网络示波器,串口/网络指令编程,革命性的事件驱动型上位机生成器——组态画布等数十个功能,无论是在使用手感上还是功能实用方面都是绝对一流,可以说Micro-Lab重新定义了嵌入式调试工具,同时也成为行业工具的标杆。(当前介绍引自码云:https://gitee.com/fengmeitech/Micro-Lab),来看看具体长什么样吧:

    功能很丰富,基本上常见的调试功能都已经集成了,但这篇文章的重点是怎么用Micro-Lab与MCU进行交互。

    2、Micro-Lab图形界面设计与使用

    2.1 设计基本图形界面

    切换到组态画布页面,然后手动将左边相关控件拖放到中间控件放置区域,选中其中一个控件时,可以通过右边更改控件的属性,和玩串口屏是类似的操作,即使完全没有用过,不看说明文档也能快速上手。

    当控件布局完毕以后,在控件放置区域点击鼠标右键选择Run,这时候就会弹出刚刚画好的界面,如下:

    在MCU与Micro-Lab建立通信之前我们要先了解三大控件类的用途:

    控制组件

    控制组件目前有按键、开关、滑动条、旋钮,意思就是通过Micro-Lab去操作这些控件,Micro-Lab就会下发对应的协议指令给MCU,MCU收到对应的指令后,则根据SDK提供的接口去处理不同的事务。

    显示组件

    显示组件目前有进度球、电池、仪表盘、时钟、指南针、点阵屏、数码管、LED、飞行仪、刻度计、文本、时间日期这些组件,当组态画图布局了对应组件,MCU往Micro-Lab发送对应的协议指令,Micro-Lab接收到对应的协议指令后进行数据解析,将数据展示在显示组件上。

    图表组件

    图表控件目前仅有曲线波形,用于展示波形数据。

    该项目还在持续成长中,相信将来会有越来越多的控件加入。

    2.2 MCU与MicroLab建立通信

    2.2.1 下载SDK

    界面设计完毕,接下来需要下载SDK,将SDK移植到MCU上,让MCU和Micro-Lab建立通信,建立通信目前有两种方式,一种是串口通信,还有另一种是网络通信。

    下载SDK的方法如下,在组态画布控件布局区域鼠标右键,然后选择Download MCU SDK

    接下来会跳转到码云,点击下载SDK包,SDK包提供了stm32f103有关组态画布的例程,sdk则是与Micro-Lab通信的协议代码,关于怎么使用这个Micro-Lab以及如何移植到MCU上,up主赵工也在bibi上开了视频专门讲解,感兴趣的可以去看看,地址如下:

    https://space.bilibili.com/281029341?from=search&seid=2143076487604252529

    2.2.1 将SDK移植到小熊派上

    这里我直接用小熊派自带的光强传感器例程进行移植

    (1)将SDK包拷贝到工程中

    (2)在Keil中包含SDK包

    (3)修改工程

    当然这个发送字节的接口也可以改成TCP/IP传输,下面再结合智能小车控制的例程进行修改。

    (3)使用Micro-Lab

    1、包含相应的头文件并导入外部event变量

    2、初始化组态画布

    3、调用更新画布接口发送数据到上位机

    这里我用的是数码管控件,简单来说明下updateCanvas这个接口:

    /**  * brief : update Canvas`components state with serialport/net  * parameter:  *      componenttype  : component type  *      componentnumber: component number  *      data           : data that to canvas  *      datalen        : data lenth  * ret: none  */ void updateCanvas(COMPONENT_TYPE componenttype, unsigned short componentnumber, char * data, unsigned short datalen) {     static char tbuffer[TBUFFERSIZE];     static short size;     packProtocol(ORGANIZATION, SECTION, DATAPOINT, componenttype, componentnumber, NONE_MSG, data, datalen, tbuffer, &size);     sendBytes(tbuffer, size); }

    其中componenttype表示控件类型,sdk中用枚举进行描述,对应如下:

    /* component type */ typedef enum {     NONE_COMPONENT =    -1,     /*define ctrol component*/     PUSHBUTTON     =    1,      //push button     SWITCHBUTTON   =    2,      //switch     SLIDER         =    3,      //slider     DIAL           =    4,      //dial     /*define show component*/     WATERLEVER     =    1001,   //water level     BATTERY        =    1002,   //battery power     CARDASHBOARD   =    1003,   //car dashboard     CLOCK          =    1004,   //clock with hour, minute, second hands     COMPASS        =    1005,   //compass     LEDDOT         =    1006,   //LED dot matrix     LCDNUMBER      =    1007,   //lcd number     LEDSTATE       =    1008,   //LED state     PLANEDASHBOARD =    1009,   //plane dashboard     TEMPMETER      =    1010,   //temperature meter     LABEL          =    1011,   //text label     LCDDATETIME    =    1012,   //datetime lcdnumber     /*define chart component*/     WAVECHART      =    2001,   //wave chart     /*KEY & MOUSE*/     KEYBOARD       =    9001,     MOUSE          =    9002 }COMPONENT_TYPE;

    对应的这些数值,比如1001、1002,其实就是画布上的属性区域的type值,例如我用的是数码管控件,对应的type值为1007。

    componentnumber表示的是画布中控件的序号,比如创建了第一个数码管控件,那么索引值为0,第二个则为1,以此类推。

    data表示显示在控件上数据

    datalen表示数据长度

    (4)运行结果

    将移植后的工程编译下载后,设置好Micro-Lab串口参数,然后打开串口,运行画布即可看到数据已经上传上来了:

    3、Micro-Lab按钮组件控制开发板上灯亮灭

    3.1 设计基本图形界面

    然后设置按钮按下和释放的事件值

    这里设置按下为0,释放为1。

    3.2 移植SDK,添加处理接口

    以下只写关键的代码部分,其余部分请下载完整工程查看。

    在串口接收中断中添加处理函数:processBytes

    void USART1_IRQHandler(void) {   /* USER CODE BEGIN USART1_IRQn 0 */     if(RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))     {         __HAL_UART_CLEAR_IDLEFLAG(&huart1);         HAL_UART_DMAStop(&huart1);         cmd_parse_typedef.no_rx_count  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数         cmd_parse_typedef.rx_count =  CMD_STR_SIZE - cmd_parse_typedef.no_rx_count; //总计数减去未传输的数据个数,得到已经接收的数据个           processBytes((char *)cmd_parse_typedef.cmd_buffer, cmd_parse_typedef.rx_count);         HAL_UART_Receive_DMA(&huart1,cmd_parse_typedef.cmd_buffer,CMD_STR_SIZE);//重新打开DMA接收           cmd_parse_typedef.BufferReady = 1 ;     }   /* USER CODE END USART1_IRQn 0 */   HAL_UART_IRQHandler(&huart1);   /* USER CODE BEGIN USART1_IRQn 1 */   /* USER CODE END USART1_IRQn 1 */ }

    这里我用的是串口1的接收,采用空闲中断+DMA的方式接收。

    在主函数中,处理接收到的数据,根据触发添加做自己想做的事情。

    int main(void) {     /* USER CODE BEGIN 1 */     /* USER CODE END 1 */     /* MCU Configuration--------------------------------------------------------*/     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */     HAL_Init();     /* USER CODE BEGIN Init */     /* USER CODE END Init */     /* Configure the system clock */     SystemClock_Config();     /* USER CODE BEGIN SysInit */     /* USER CODE END SysInit */     /* Initialize all configured peripherals */     MX_GPIO_Init();     MX_DMA_Init();     MX_USART1_UART_Init();     /* USER CODE BEGIN 2 */     //初始化画布     initCanvas(events);     /* USER CODE END 2 */     /* Infinite loop */     /* USER CODE BEGIN WHILE */     while (1)     {         /* USER CODE END WHILE */         /* USER CODE BEGIN 3 */         //如果串口数据接收完毕了,开始处理串口数据         if(1 == cmd_parse_typedef.BufferReady)         {             cmd_parse_typedef.BufferReady = 0 ;             ///遍历事件             for(char i = 0; i < CANVASEVENTLIST_SIZE; ++i)             {                 //如果当前SDK包含对应的控件事件,则处理                 if(events[i].componenttype != NONE_COMPONENT)                 {                     //当前componenttype为按钮                     if(PUSHBUTTON == events[i].componenttype)                     {                         //当前画布的Index为0                         if(events[i].componentnumer == 0)                         {                             //当前触发了按下,点灯                             if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                             {                                 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);                             }                             //当前触发了释放,灭灯                             else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                             {                                 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);                             }                         }                     }                     //清除事件                     events[i].componenttype = NONE_COMPONENT;                     events[i].componentnumer = 0;                     events[i].componentmsgtype = NONE_MSG;                     events[i].msglen = 0;                 }             }         }         /* USER CODE END 3 */     } }

    其它控制控件也是类似的操作方法,非常简单。

    连接好Micro-Lab后,打开串口,运行画布,鼠标按下button,开发板上的LED点亮,释放鼠标,则LED灯熄灭。

    4、Micro-Lab按钮组件控制坦克小车

    由于坦克小车是基于ESP8266 WIFI无线,所以我们就需要通过Micro-Lab的网络TCP/IP的模式与WIFI坦克小车进行通信,控制流程如下,详细代码见文末,在后台回复关键字获取。

    4.1 Micro-Lab端的设置

    4.2 小车端程序流程(基于STM32F103ZET6)

    /**   * @brief  The application entry point.   * @retval int   */ int main(void) {     /* USER CODE BEGIN 1 */     /* USER CODE END 1 */     /* MCU Configuration--------------------------------------------------------*/     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */     HAL_Init();     /* USER CODE BEGIN Init */     /* USER CODE END Init */     /* Configure the system clock */     SystemClock_Config();     /* USER CODE BEGIN SysInit */     /* USER CODE END SysInit */     /* Initialize all configured peripherals */     MX_GPIO_Init();     MX_TIM1_Init();     MX_USART1_UART_Init();     MX_USART2_UART_Init();     MX_TIM3_Init();     /* USER CODE BEGIN 2 */     HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);     //setServoAngle(6);     ESP8266_Init();     //初始化画布     initCanvas(events);     printf("正在配置 ESP8266 ......\n" );     ESP8266_Rst();     while(!ESP8266_AT_Test());     while(!ESP8266_Net_Mode_Choose(STA_AP));     while(!ESP8266_JoinAP(User_ESP8266_ApSsid, User_ESP8266_ApPwd));     while(!ESP8266_Enable_MultipleId(DISABLE));     while(!ESP8266_Link_Server(enumTCP, User_ESP8266_TcpServer_IP, User_ESP8266_TcpServer_Port, Single_ID_0));     while(!ESP8266_UnvarnishSend());     //使能空闲中断     __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);     printf("配置 ESP8266 完毕\n");     //配置完毕,打开指示灯,代表此时已经连接服务器成功     HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);     /* USER CODE END 2 */     /* Infinite loop */     /* USER CODE BEGIN WHILE */     while (1)     {         /* USER CODE END WHILE */         /* USER CODE BEGIN 3 */         ESP8266_Data_Handler();     }     /* USER CODE END 3 */ }

    接收到Micro-Lab发送来的数据后,调用SDK进行协议解析,然后解析得到的指令调用不同的小车控制逻辑:

    //ESP8266接收数据处理 void ESP8266_Data_Handler(void) {     ESP8266_ReceiveString(ENABLE);     if ( strEsp8266_Fram_Record .InfBit .FramFinishFlag )     {         //接收到数据闪烁灯         HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);         strEsp8266_Fram_Record .Data_RX_BUF [ strEsp8266_Fram_Record .InfBit .FramLength ] = '\0';         for(int i = 0 ; i < strEsp8266_Fram_Record .InfBit .FramLength ; i++)             printf("%x ", strEsp8266_Fram_Record .Data_RX_BUF[i]);         printf("\r\n");         //处理Canvas发来的数据         processBytes(strEsp8266_Fram_Record .Data_RX_BUF, strEsp8266_Fram_Record .InfBit .FramLength);         //执行事件处理         Car_Event_Handler();         //清楚串口缓存区         memset(strEsp8266_Fram_Record.Data_RX_BUF, 0, strlen(strEsp8266_Fram_Record.Data_RX_BUF));     } }

    小车事件处理:

    //小车事件处理 void Car_Event_Handler(void) {     ///遍历事件     for(int i = 0; i < CANVASEVENTLIST_SIZE; ++i)     {         //如果当前SDK包含对应的控件事件,则处理         if(events[i].componenttype != NONE_COMPONENT)         {             //当前componenttype为按钮             if(PUSHBUTTON == events[i].componenttype)             {                 //小车前进                 if(events[i].componentnumer == 0)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_go();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }                 //小车后退                 if(events[i].componentnumer == 1)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_back();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }                 //小车左转                 if(events[i].componentnumer == 2)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_left();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }                 //小车右转                 if(events[i].componentnumer == 3)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_right();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }                 //小车瞄准                 if(events[i].componentnumer == 4)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_aim();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }                 //小车发射                 if(events[i].componentnumer == 5)                 {                     if(PUSHBUTTON_PRESS == events[i].componentmsgtype)                         car_shot();                     else if(PUSHBUTTON_RELEASE == events[i].componentmsgtype)                         car_stop();                 }             }             //清除事件             events[i].componenttype = NONE_COMPONENT;             events[i].componentnumer = 0;             events[i].componentmsgtype = NONE_MSG;             events[i].msglen = 0;         }     } }

    运行效果:

    实际演示效果:

    Micro-Lab还在不断的成长中,相信未来会有越来越多好玩的功能,敬请期待!

    5、案例下载

    公众号后台回复:Micro-Lab 即可获取本节所有程序案例以及Micro-Lab软件的下载链接。

    往期精彩

    STM32在线升级OTA,看这一篇就够啦~

    第10期 | ringbuff,通用FIFO环形缓冲区实现库

    ESP8266实战贴:使用HTTP POST请求上传数据到公有云OneNet

    会C/C++就可以开发Linux/Android应用程序?替代传统串口屏的Yoxios了解一下!

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

    Engineer-Bruce_Yang 认证博客专家 嵌入式硬件 单片机 arm开发 本科毕业于华南理工大学,现美国卡罗尔工商管理硕士研究生在读,曾就职于世界名企伟易达、联发科技等,多年嵌入式产品开发经验,在智能玩具、安防产品、平板电脑、手机开发有丰富的实战开发经验,现任深圳市云之手科技有限公司副总经理、研发总工程师。
    Processed: 0.016, SQL: 9