在工作中,以什么样的方式向领导汇报工作最直接高效呢?当然是图形界面!图形界面更好表达一个程序设计的逻辑思维,一目了然,本次介绍的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软件的下载链接。
往期精彩
第10期 | ringbuff,通用FIFO环形缓冲区实现库
ESP8266实战贴:使用HTTP POST请求上传数据到公有云OneNet