分享一个多线程异步串口通讯库
编辑时间:2017-05-14 作者:H.green 浏览量:3207 来源:cart论坛

 前阵子在找界面库HTMLayout时认识了aardio,小巧、不用安装、不带库,这正是我需要的。因为之前做过几年电工,知道在工控领域很多地方还在用串口、以太网的,于是决定通过写一个串口通讯库来学习aardio。    这个串口库纯aardio代码,读、写线程独立,可以实现全双工,设计成类似C#串口类的形式(方法+属性)。使用了3个标准库util.metaProperty、thread.event、thread.table。在写的过程中主要是在纯函数跟API数据类型上面吃了苦头。因为水平有限,错误在所难免,欢迎指正。

主程序

import win.ui;
import win.ui.menu;
import win.ui.statusbar;
import win.reg;
import port;    //串口通讯库
/*DSG{{*/
mainForm = win.form(text="UartTest";right=599;bottom=399)
mainForm.add(
bthOpen={cls="button";text="打开串口";left=13;top=206;right=150;bottom=253;dl=1;dt=1;z=13};
btnClrCounter={cls="button";text="计数清零";left=13;top=322;right=150;bottom=369;dl=1;dt=1;z=16};
btnClrRecv={cls="button";text="清空显示";left=255;top=159;right=322;bottom=183;dl=1;dt=1;z=18};
btnSend={cls="button";text="发送数据";left=13;top=264;right=150;bottom=311;dl=1;dt=1;z=15};
chkRecvHex={cls="checkbox";text="16进制接收";left=162;top=164;right=247;bottom=182;dl=1;dt=1;z=17};
chkSendHex={cls="checkbox";text="16进制发送";left=162;top=8;right=247;bottom=26;dl=1;dt=1;z=19};
combobox1={cls="combobox";left=68;top=27;right=139;bottom=53;dl=1;dt=1;edge=1;items={};mode="dropdownlist";z=4};
combobox2={cls="combobox";left=68;top=61;right=139;bottom=87;dl=1;dt=1;edge=1;items={};mode="dropdownlist";z=6};
combobox3={cls="combobox";left=68;top=95;right=139;bottom=121;dl=1;dt=1;edge=1;items={};mode="dropdownlist";z=8};
combobox4={cls="combobox";left=68;top=128;right=139;bottom=154;dl=1;dt=1;edge=1;items={};mode="dropdownlist";z=10};
combobox5={cls="combobox";left=68;top=162;right=139;bottom=188;dl=1;dt=1;edge=1;items={};mode="dropdownlist";z=12};
groupbox1={cls="groupbox";text="初始化";left=13;top=7;right=150;bottom=197;dl=1;dt=1;edge=1;z=2};
richedit1={cls="richedit";left=162;top=27;right=588;bottom=152;dl=1;dr=1;dt=1;edge=1;hscroll=1;multiline=1;vscroll=1;wrap=1;z=1};
richedit2={cls="richedit";left=162;top=184;right=588;bottom=369;db=1;dl=1;dr=1;dt=1;edge=1;hscroll=1;multiline=1;vscroll=1;wrap=1;z=14};
static1={cls="static";text="串口号";left=21;top=33;right=63;bottom=53;dl=1;dt=1;transparent=1;z=3};
static2={cls="static";text="波特率";left=21;top=66;right=63;bottom=86;dl=1;dt=1;transparent=1;z=5};
static3={cls="static";text="数据位";left=21;top=99;right=63;bottom=119;dl=1;dt=1;transparent=1;z=7};
static4={cls="static";text="停止位";left=21;top=132;right=63;bottom=152;dl=1;dt=1;transparent=1;z=9};
static5={cls="static";text="校验位";left=21;top=165;right=63;bottom=185;dl=1;dt=1;transparent=1;z=11}
)
/*}}*/

var sp = port.SerialPort(); //创建一个串口对象(可以在这里指定串口参数,如果不指定则使用默认值)
var flag = false;   //串口打开标志
var nRead = 0;  //已读字节
var nWrite = 0; //已写字节

//串口号
var reg = win.regReader("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM\");   //获取当前计算机的串口列表
if(reg)
{
    for(name,value,t in reg.eachValue()) 
    {
        mainForm.combobox1.add(value);
    }
    
    if(mainForm.combobox1.count)
        mainForm.combobox1.selIndex = 1;
}


//波特率
mainForm.combobox2.add("300");
mainForm.combobox2.add("600");
mainForm.combobox2.add("1200");
mainForm.combobox2.add("2400");
mainForm.combobox2.add("4800");
mainForm.combobox2.add("9600");
mainForm.combobox2.add("14400");
mainForm.combobox2.add("19200");
mainForm.combobox2.add("38400");
mainForm.combobox2.add("56000");
mainForm.combobox2.add("57600");
mainForm.combobox2.add("115200");
mainForm.combobox2.selIndex = 6;

//数据位
mainForm.combobox3.add("4");
mainForm.combobox3.add("5");
mainForm.combobox3.add("6");
mainForm.combobox3.add("7");
mainForm.combobox3.add("8");
mainForm.combobox3.selIndex = 5;

//停止位
mainForm.combobox4.add("1");
mainForm.combobox4.add("1.5");
mainForm.combobox4.add("2");
mainForm.combobox4.selIndex = 1;

//校验位
mainForm.combobox5.add("None");
mainForm.combobox5.add("Odd");
mainForm.combobox5.add("Even");
mainForm.combobox5.add("Mark");
mainForm.combobox5.add("Space");
mainForm.combobox5.selIndex = 1;


//状态栏
status = win.ui.statusbar(mainForm);
status.addItem( "Status: Ports Closed",250);
status.addItem( "RX:0",100);
status.addItem( "TX:0",100);

//打开或关闭串口
mainForm.bthOpen.oncommand = function(id,event){
    try
    {
        if(!flag)
        {
            //如果串口参数不需频繁改变,也可以在创建串口对象时指定。所有的参数都必须在打开串口前设置
            sp.PortName = mainForm.combobox1.text;
            sp.BaudRate = tonumber(mainForm.combobox2.text);
            sp.DataBits = tonumber(mainForm.combobox3.text);
            sp.StopBits = mainForm.combobox4.selIndex - 1;
            sp.Parity = mainForm.combobox5.selIndex - 1;
            sp.NotifyhWnd = mainForm.hwnd;  //设置接收串口消息的窗口句柄
            
            if( sp.Open()==0 )
            {
                flag = true;
                mainForm.bthOpen.text = "关闭串口";
                mainForm.statusbar.setText( "Status: " + mainForm.combobox1.text + " Opened,"
                                                       + mainForm.combobox2.text + ","
                                                       + mainForm.combobox5.text + ","
                                                       + mainForm.combobox3.text + ","
                                                       + mainForm.combobox4.text, 1 );
            }
        }
        else
        {
            sp.Close();
            flag = false;
            mainForm.bthOpen.text = "打开串口";
            mainForm.statusbar.setText( "Status: Port Closed", 1 );
        }
    }
    catch(e)
    {
        mainForm.msgboxErr(e);
    }
}

//发送数据
mainForm.btnSend.oncommand = function(id,event){
    if(mainForm.chkSendHex.checked)
        sp.WriteHex(mainForm.richedit1.text);
    else
        sp.Write(mainForm.richedit1.text);
}

//发送、接收字节计数清零
mainForm.btnClrCounter.oncommand = function(id,event){
    nRead = 0;
    nWrite = 0;
    mainForm.statusbar.setText( "RX:" + nRead, 2 );
    mainForm.statusbar.setText( "TX:" + nWrite, 3 );
}

//清空接收区显示
mainForm.btnClrRecv.oncommand = function(id,event){
    mainForm.richedit2.text = "";
}

//发送区右键菜单
mainForm.richedit1.wndproc = function(hwnd,message,wParam,lParam){
    if( message == 0x205/*_WM_RBUTTONUP*/ ){  
        owner.popMenu();
    }
}

//接收区右键菜单
mainForm.richedit2.wndproc = function(hwnd,message,wParam,lParam){
    if( message == 0x205/*_WM_RBUTTONUP*/ ){  
        owner.popMenu();
    }
}

//窗口消息循环,处理字符串的效率会比较低
var HighByte = 0;   //声明一个变量缓存汉字的高位字节,避免1个汉字拆分成“两半”
mainForm.wndproc = function(hwnd,message,wParam,lParam){
    select( message ) 
    {
        case 0x8801/*_UWM_COMM_SENDEDBYTES*/
        {
            nWrite += wParam;   //wParam存放已发送的字节数,lParam存放要发送的字节数,if(wParam != lParam) ...
            mainForm.statusbar.setText( "TX:" + nWrite, 3 );
        }
        case 0x8701/*_UWM_COMM_RXCHAR*/
        {
            if(mainForm.chkRecvHex.checked)  //16进制显示,不足2位在前面补0,并用空格隔开
            {
                mainForm.richedit2.appendText(..string.format("%02X ", lParam ));
            }
            else  //字符显示,分两种情况:1、汉字的显示   2、英文数字等字符的显示
            {
                if(HighByte)
                {
                    mainForm.richedit2.appendText( ..string.pack({HighByte;lParam}) );  //取出缓存的高位字节,组成一个完整的汉字
                    HighByte = 0;
                }
                else
                {
                    if(lParam>127)  //如果大于127,认为收到了汉字的高位字节,先缓存起来
                        HighByte = lParam;
                        //这里可能要启动一个定时器,如果一定时间内收不到后续的低位字节,则直接显示高位字节
                    else
                        mainForm.richedit2.appendText( ..string.pack(lParam) ); //英文数字等字符的显示
                }
            }
            
            nRead++;
            mainForm.statusbar.setText( "RX:" + nRead, 2 );
        }
        case 0x8901/*_UWM_COMM_FATALERR*/
        {
            mainForm.msgboxErr("后台线程已退出,必须关闭串口!");
        }
    }
}

mainForm.show() 
return win.loopMessage(
/**
    function(msg)   //如果不指定sp.NotifyhWnd,消息将发到主线程
    {
        if( msg.message == 0x8701/*_UWM_COMM_RXCHAR*/ )
            io.print( "串口接收到数据", msg.wParam, msg.lParam);
    }
**/
);


串口库实在太长了,见附件吧。

UartTest.zip


来说两句吧