电子爱好者应该不会对“上位机”这个词感到陌生,毕竟或多或少有过接触。但若是说到上位机的开发的话,大家就不一定熟悉了。很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的需求吗?不,究其原因,国内相关开发资料和例子不足,所以初学者在学习过程中几乎不会接触到相关内容。

  近来刚好手上有个小东西需要做个上位机,在C#、Matlab、QT、Labview之间徘徊许久之后最终选择了Python,继而了解了一些相关知识,在此分享一些我整理的经验,希望能够帮助后来者。

从一个按键开始

一个简单的demo

  Python作为一种脚本语言,一大优势就是可以方便地调用各种各样的库。比如,可以使用Qt的一些组件,在Python中调用Qt~~~~可以参考这个官方示例

  例子里面的核心代码如下:

@Slot()
def say_hello():
 print("Button clicked, Hello!")

# Create the Qt Application
app = QApplication(sys.argv)
# Create a button, connect it and show it
button = QPushButton("Click me")
button.clicked.connect(say_hello)
button.show()
# Run the main Qt loop
app.exec_()

  首先,定义了一个函数say_hello来打印输出信息,“@Slot()”是一个装饰器,表明该函数是一个“槽”,对此概念不了解的童鞋需要补一点QT的知识。然后,创建了一个QT应用、创建了一个按钮控件、将函数say_hello连接到按键、显示按钮控件,最后,启动QT主循环。

  创建应用和启动主循环可以说是调用QT组件的“套路”,关于button的操作则示范了一个操作空间的大概流程。此外,作为按钮控件,say_hello可以理解为button的“事件函数”,每发生一次按钮按下的事件,函数就被执行一次。

明确我们的需求

  基于官方的demo,我们可以进行一些修改来创建自己的应用。当然,首先要明确需求,我们的上位机应当能够对下位机进行控制,就以LED的控制为例子把,上位机具有一个按键,按一下板载LED(单个)亮起,再按一下熄灭,同时,按键上最好能够通过颜色或文字显示LED当前的状态。

改造demo

  我们把槽函数修改如下:

led = "ON"
# @Slot()是一个装饰器,标志着这个函数是一个slot(槽)
@Slot()
def led_toggle():
    '''按下按钮,翻转LED状态'''
    global led
    if led == "ON":
        led = "OFF"
        button.setText("LED OFF!")
    else :
        led = "ON"
        button.setText("LED ON!")
    print(led)

  在定义函数前我们创建了一个叫led的字符串并指向“ON”,在函数内部我们声明led为全局的(python默认指定为局部变量),并对led的内容进行判断,如果其指向的内容为“ON”,修改它以指向“OFF”,并通过setText这一方法设置按键上的文字为“LED OFF!”。不为“ON”时的操作基本也是这样。

  修改之后demo的运行效果如下:(按下后变为LED ON!/LED OFF!)

P1.jpg

  修改后的demo的完整代码在这里

建立桥梁——串口和JSON

serial库的简单使用

  在单片机上,串口是极常用的通信接口,上位机对单片机的控制可以基于串口来进行,python中控制串口需要使用serial库。

  serial库下的Serial方法可以创建并返回一个串口对象,使用的例子如下:

ser = serial.Serial('COm10',115200,timeout=0.5) #打开指定串口

  该方法默认设置数据为“8N1”格式,一般指定com口、波特率和超时时间(单位:ms)即可。

  (com端口自己开设备管理器看)

  有了串口对象就可以发数据了,写入的例子如下:

ser.write(b"hello serial!")

  b表示将字符串以字节(bytes)形式编码并发送。

json库的简单使用

  json是一种常用的、跨平台的数据交换格式,对于我们这个“控制一个LED的亮灭”的需求,使用一个键值对就可以解决。

  例如,我们首先创建一个data_on来存源数据。

data_on  = { 'led' : 0 }

  关于值的意义,我们可以规定0表示LED亮、1表示LED灭。

  然后,把源数据打包为json字符串。

json_led_on  = json.dumps(data_on, sort_keys=True, separators=(',', ':'))

  类似的,可以创建一个内容为“{"LED":1}”的json字符串。

  串口发送json的测试用例在这里

完整的上位机

  虽然说我们已经能够通过串口发数据了,但是自己指定串口号还是有些麻烦,这里提供一个搜索并返回可用串口列表的程序

  加上这个,再合并一下代码,我们控制LED的简易上位机就完成了,代码在这里

  当然,下位机还等着写呢。

下位机的处理

  在这篇文章中,我对在Keil里面使用jansson库处理JSON的方法进行了一些讲解,这次的使用没有超过原来的范围,不赘述了。

  核心业务部分的代码如下:

void Task_LEDControl()
{
    uint16_t len = 0;
    int led = 0;
    char json_RX_buffer[400] =  {'\0'};
    
    json_t *led_raw;
    json_error_t error;

    if(usart1_rx_ok == 1)
    {
        usart1_rx_ok = 0;
        len = usart1_rx_count;
        usart1_rx_count = 0;
        
        for(uint16_t i=0;i<len;i++) {
            json_RX_buffer[i] = usart1_rx_buffer[i];
        }
        led_raw = json_loads(json_RX_buffer, JSON_ENCODE_ANY, &error);
        json_unpack(led_raw,"{s:i}","led",&led);
        
        if(led == 0) {
            Board_LED_ON();
            printf("led on!\n");
        }
        else {
            Board_LED_OFF();
            printf("led off!\n");
        }
        
        json_delete(led_raw);    //删除json对象
        memset(json_RX_buffer,0x00,sizeof(json_RX_buffer)); //清空数组
    }
}

  完整工程在这里

  最终的演示效果如下:

demo演示.gif

(PS. 由于作者水平有限,再加上编写时尽量追求“新手友好”而不是代码量或效率的最优,所以很多地方可能并不简洁优雅,这一点还请原谅)


ngHackerX86
22 声望24 粉丝

000000