你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
Matlab控件回调函数编写与软件调试
 

一、引言

在完成了遥控原理Matlab程序实现、可视化界面设计与控件使用之后,本篇讲述第三大模块:控件回调函数的编写与软件的调试。

控件回调函数的编写与软件的调试是个实践性很强的工作,需要不断的实践和摸索,建议读者对照源代码Telecommand_demo.m阅读本篇,细细体会各控件之间的逻辑关系,并按照文中提示自己上机调试。

二、预备工作

在正式开始编写各控件的回调函数之前,应当首先完成如下预备工作:

1.程序初始化完善

程序初始化的工作,是在主函数Telecommand_demo中完成,需要在程序自动生成的源代码基础上增加一部分语句,完善程序的初始化功能。

完善后的主函数Telecommand_demo如下:

function varargout = Telecommand_demo(varargin)

global handles    % 设定句柄handles为全局变量

if nargin == 0

fig = openfig(mfilename,reuse);

set(fig,'Color',[1 1 0.76], Name, '遥控系统原理及工作过程演示'); 

% 背景颜色和图形名字的设定

handles = guihandles(fig);

guidata(fig, handles);

if nargout > 0

……    (该部分为程序自动生成,故此处省略)   

end

程序中有注释的部分均为添加的指令。其中,设定句柄为全局变量,以便控件之间信息的获取和传递,这是GUIDE程序设计中一项重要的技巧,将在下文重点讲述。

2.控件pushbutton_step回调函数pushbutton_step_Callback

运行此时的程序Telecommand_demo,其界面只提供单步执行自动演示退出程序三个指令选项,必须首先进入单步执行的界面进行回调函数的编写,故应当首先编写控件pushbutton_step 的回调函数pushbutton_step_Callback

显然pushbutton_step的回调函数功能很简单,即:显示单步执行的控件开始发送接收执行,隐藏控件单步执行自动演示退出程序。基本方法是:利用handles访问相应控件的句柄,并通过set语句进行相应属性的设置。

控件pushbutton_step回调函数编写如下:

function varargout = pushbutton_step_Callback(h, eventdata, handles, varargin)

set(handles.pushbutton_step, Visible, off, Userdata,[0 0 0 0 0 0 0])

% 单步执行控件隐藏,并给其属性Userdata赋初值[0 0 0 0 0 0 0]

set(handles.pushbutton_continous, Visible, off)   % 自动演示控件隐藏

set(handles.pushbutton_exit, Visible, off)        % 退出程序控件隐藏

set(handles.popupmenu_NO, Userdata,[0 0])  % 初始化控件popupmenu_NO的用户数值Userdata

set(handles.pushbutton_start, Visible, on)    % 显示控件开始发送接收执行

set(handles.pushbutton_send, Visible, on)

set(handles.pushbutton_receive, Visible, on)

set(handles.pushbutton_do, Visible, on)  

Matlab的命令窗口输入“help set”,系统将罗列出set语句的所有基本语法,其最为常用的句式是:SET(H,PropertyName,PropertyValue)H表示句柄,PropertyName表示属性名称,PropertyValue则为赋给PropertyName的属性值。这段程序显然就是通过句柄访问控件,并将属性值on(或off)赋给属性Visible,实现控件的隐藏及显示功能。

除此之外,还有将数组[0 0 0 0 0 0 0][0 0]分别赋给控件pushbutton_step和控件popupmenu_NO的属性Userdata(用户数据),主要用来判断是否已完成相应操作,这种设计思想将在下文中介绍。

3.控件pushbutton_start回调函数pushbutton_start_Callback

运行程序Telecommand_demo,在界面点击单步执行,即可进入单步执行的界面。按照软件控件的设计,此时只有控件显示开始,其余的控件(如:按钮、弹出式菜单)均点击不相应。故在控件pushbutton_start的回调函数下实现指令参数功能的选择,需要将设置控件PopupmenuEnable属性。

回调函数pushbutton_start_Callback语句较为单一,编写如下:

function varargout = pushbutton_start_Callback(h, eventdata, handles, varargin)

set(handles.popupmenu_NO, Enable, on)   % 实现6popupmenu控件的指令参数可选

set(handles.popupmenu_v, Enable, on)

set(handles.popupmenu_sum, Enable, on)

set(handles.popupmenu_trace, Enable, on)

set(handles.popupmenu_color, Enable, on)

set(handles.popupmenu_Head, Enable, on)

set(handles.pushbutton_start, Enable, off)   % 控件开始使能功能失效

三、控件回调函数编写与程序调试

预备工作完毕后,就可以进行单步执行界面中控件回调函数的编写与调试了。此时若运行程序Telecommand_demo,点击按钮单步执行将得到响应,可进入相应界面。

应当指出的是,回调函数的编写和程序的调试同步进行,回调函数的修改和功能的完善是需要反复调试而成,不可能一蹴而就。在完成这一部分时,请读者务必理解程序流程及相应子函数的调用过程。

1.遥控指令与Popupmenu回调函数

通过选择指令参数,构建出完整的遥控指令,这是通过Popupmenu控件和Edit Text控件实现。由于Edit Text控件只用来显示帧码和控制字,并不提供相应的回调函数,故只需要编写Popupmenu控件的回调函数。

在进行这一部分的设计时,需要考虑到以下四个方面:选择参数后形成的指令应和相应控制字是实时对应的关系;无序选择指令参数若应不影响指令的生成;应实时判断指令是否选择完毕,一旦选择完毕则控件显示方可以使用;指令参数不可重复选择。

按照以上的设计思想,控件popupmenu_Head的回调函数popupmenu_Head_Callback编写如下:

function varargout = popupmenu_Head_Callback(h, eventdata, handles, varargin)

global a_Head;   % a_HeadHnum设定为全局变量,以便各子函数均可调用

global Hnum;    % a_Head表示帧码序列,Hnum表示帧码长度

popupHead=get(handles.popupmenu_Head, Value);  % 获取用户选项

switch popupHead  % 判断用户选项并给帧码a_Head赋初值

case 1

   a_Head=[1 1 1 0 1 0 1 1 1 0 0 1 0 0 0 0];

   Hnum=length(a_Head);

case 2

 a_Head=[1 0 0 1 1 0 1 0 1 0 1 1 1 1 0 0 ...

   1 0 1 1 0 1 0 1 0 0 1 0 1 1 0 0];

   Hnum=length(a_Head);

end

set(handles.popupmenu_Head, Enable, off)         % 指令选择后使能功能失效,不可重选

set(handles.edit_zhenma, string,num2str(a_Head))    % 将帧码转化为字符串并送至帧码显示栏

OK=get(handles.pushbutton_step, Userdata);  

OK(1)=1;                    % 获取pushbutton_stepUserdata,并将下标为1的数值赋为1

set(handles.pushbutton_step, Userdata,OK)  % 更新pushbutton_stepUserdata

isOK                        % 调用函数isOK,判断是否已经完成指令参数的选择

function varargout = popupmenu_NO_Callback(h, eventdata, handles, varargin)

……

%--------------------------------------------------------------------------------------------

function isOK

% 判断是否完成指令参数的选择,若完成则出现控件显示

global handles;                            % handles为全局变量

global a1,global a2,global a3,global a4,   % 各参数全局变量定义

global a5,global a_Head,global Hnum,

global a;

a=[a_Head a1 a2 a3 a4 a5];                 % 形成遥控指令

OK=get(handles.pushbutton_step, Userdata);% 获取控件pushbutton_stepUserdata

n=sum(OK);                             % 计算数组所有元素的和

if n==6                                % 只有所有元素和为6时,表示所有参数选择完毕

set(handles.pushbutton_send, Enable, on, String, '显示', Userdata,a)

% 控件pushbutton_send变为显示指令,同时遥控指令a赋给Userdata供传递调用

end

如何实时判断指令是否已经选择完毕,是设计的难点。该程序提供的解决方法是:首先赋初值[0 0 0 0 0 0 0]给控件pushbutton_stepUserdata,然后每选择一个参数,则更新一次数据(帧码选择时将下标为1的数值赋为1,站点选择时将下标为2的数值赋为1,依次类推),每完成一个参数的选择则调用函数isOK,在函数isOK中算出更新后的数组所有元素之和,这样即可作出正确的判断。

这段程序中除了set语句,还有一个get语句的反复应用,其基本格式为get(H,PropertyName),常常用于控件相关属性的获取。这个语句和set语句一样重要,读者也要很好地理解并掌握。

参考注释,细细研究这段源代码,并通过运行,不难看出,考虑的四个方面的问题都得到很好的解决。

由于站点号、圈数、速度、颜色、轨迹的选择和各控件回调函数编程思想基本一致,故不作讲述,只给出相应的源代码及简单的注释。另外,请读者分析函数popupmenu_NO_Callbackpopupmenu_sum_Callback后思考:由于站点号和运动圈数共同形成控制字1,故只有当两者全部选择后控制字1才形成并显示,程序是如何解决这一问题的?

相应的源代码如下(读者可以参照函数popupmenu_Head_Callback的注释理解下面的程序):

function varargout = popupmenu_NO_Callback(h, eventdata, handles, varargin)

global a1;

global a2;

popupNo= get(handles.popupmenu_NO, Value);   % 获取用户选择的站点号

a1=dec2bin_m((popupNo-1),2);                     % 对站点号编码,形成a1

set(handles.popupmenu_NO, Enable, off)         

n=get(handles.popupmenu_NO, Userdata);       % 获取popupmenu_NOUserdata

n(1)=1;                                          % 对获取的数组进行更新

set(handles.popupmenu_NO, Userdata,n)           % 更新后的数组重新赋给属性Userdata

if sum(get(handles.popupmenu_NO, Userdata))==2  % 判断控制字1的两个选项是否选择完毕

set(handles.edit_kong1, string,num2str([a1 a2])) % 如果选择完毕则a1a2形成控制字1

set(handles.popupmenu_NO, Userdata,[0 0])

end

OK=get(handles.pushbutton_step, Userdata);         % 获取pushbutton_stepUserdata并更新

OK(2)=1;              

set(handles.pushbutton_step, Userdata,OK)

isOK                                         % 判断是否完成参数的选择

% ------------------------------------------

function varargout = popupmenu_sum_Callback(h, eventdata, handles, varargin)

global a1;

global a2;

popupsum=get(handles.popupmenu_sum,'Value');      %  获取运动圈数的选项

switch popupsum

case 1        % 对运动圈数进行编码,形成a2

    a2=dec2bin_m(18,6);

case 2

    a2=dec2bin_m(16,6);

case 3

    a2=dec2bin_m(14,6);

case 4

    a2=dec2bin_m(12,6);

case 5

    a2=dec2bin_m(10,6);

case 6

    a2=dec2bin_m(8,6);

case 7

    a2=dec2bin_m(6,6);

case 8

    a2=dec2bin_m(4,6);

case 9

    a2=dec2bin_m(2,6);

end

set(handles.popupmenu_sum, Enable, off)

n=get(handles.popupmenu_NO, Userdata);       % 获取popupmenu_NOUserdata

n(2)=1;                                          % 对获取的数组进行更新

set(handles.popupmenu_NO, Userdata,n)        % 更新后的数组重新赋给属性Userdata

if sum(get(handles.popupmenu_NO, Userdata))==2  % 判断控制字1的两个选项是否选择完毕

set(handles.edit_kong1, string,num2str([a1 a2]))     % 如果选择完毕则a1a2形成控制字1

set(handles.popupmenu_NO, Userdata,[0 0])

end

OK=get(handles.pushbutton_step, Userdata);         % 获取pushbutton_stepUserdata并更新

OK(3)=1;

set(handles.pushbutton_step, Userdata,OK)

isOK                                         % 判断是否完成参数的选择

% ---------------------------------------

function varargout = popupmenu_v_Callback(h, eventdata, handles, varargin)

popupv= get(handles.popupmenu_v, Value);        % 获取运动速度选项

global a3

switch popupv                                % 对运动速度进行编码

case 1

     a3=dec2bin_v(360,8);

case 2

     a3=dec2bin_v(-360,8);

case 3

     a3=dec2bin_v(180,8);

case 4

     a3=dec2bin_v(-180,8);

case 5

     a3=dec2bin_v(120,8);

case 6

     a3=dec2bin_v(-120,8);

case 7

     a3=dec2bin_v(60,8);

case 8

     a3=dec2bin_v(-60,8);

end

set(handles.edit_kong2, string,num2str(a3))      % 显示运动速度编码的控制字2

set(handles.popupmenu_v, Enable, off)

OK=get(handles.pushbutton_step, Userdata);    % 获取pushbutton_stepUserdata并更新

OK(4)=1;

set(handles.pushbutton_step, Userdata,OK)

isOK                                    % 判断是否完成参数的选择

% -------------------------------------------

function varargout = popupmenu_color_Callback(h, eventdata, handles, varargin)

global a4,

popupcolor=get(handles.popupmenu_color, Value);  % 获取小球颜色选项

switch popupcolor                             % 根据选项完成编码形成控制字3

case 1

    a4=[0 0 0 0 0 1 0 0];

case 2

    a4=[0 0 0 0 0 1 1 0];

case 3

    a4=[0 0 0 0 0 0 0 1];

case 4

    a4=[0 0 0 0 0 0 1 0];

end

set(handles.popupmenu_color, Enable, off)

set(handles.edit_kong3, string,num2str(a4))       % 显示控制字3

OK=get(handles.pushbutton_step, Userdata);      % 获取pushbutton_stepUserdata并更新

OK(5)=1;

set(handles.pushbutton_step, Userdata,OK)

isOK                                      % 判断是否完成参数的选择

% --------------------------------------------

function varargout = popupmenu_trace_Callback(h, eventdata, handles, varargin)

global a5,

popuptrace=get(handles.popupmenu_trace, Value);  % 获取运动轨迹的选项

switch popuptrace                             % 对运动轨迹进行编码

case 1

    a5(1:4)=dec2bin_m(10,4);

    a5(5:8)=dec2bin_m(10,4);

case 2

    a5(1:4)=dec2bin_m(10,4);

    a5(5:8)=dec2bin_m(5,4);

case 3

    a5(1:4)=dec2bin_m(5,4);

    a5(5:8)=dec2bin_m(10,4);

end

set(handles.edit_kong4, string,num2str(a5))        % 显示控制字4

set(handles.popupmenu_trace, Enable, off)

OK=get(handles.pushbutton_step,'Userdata');       % 获取pushbutton_stepUserdata并更新

OK(6)=1;

set(handles.pushbutton_step, Userdata,OK)

isOK                                       % 判断是否完成参数的选择

H=handles.axes1;

case 2

    H=handles.axes2;

case 3

    H=handles.axes3;

case 4

    H=handles.axes4;

end

axes(H)                               % 指定相应号的站点

pause(0.8)

call_plot(H,a_No,0,1)                 % 调用函数call_plot,绘制闪烁的信号灯    

ss=questdlg({'已解调出遥控指令!请重新发送以便校核!', '请按回车键重新发送!'},...

'遥控指令已解调出','好,重新发送','好,重新发送');

send;                                 % 调用函数sendreceive,重新发送、接收

pause(1.4);

receive

pause(1)

a=de_afpsk(p_afpsk,20,choice);    

pause(1.4)

ss=questdlg({'已解调出遥控指令!校核无误!', '请按回车键启动执行键!'},...

'遥控指令已解调出,校核无误',...

'好,启动执行键', '好,启动执行键');

pause(1)

set(handles.pushbutton_do, Enable, on)  % 将控件pushbutton_do使能属性Enable设置为on

end

……

function call_plot(H,No,x,y)

% 用于解调出的信号灯闪烁,以及绘制出相应轨迹

backcolor=[0.3 0.3 0.3];                   % 设置背景颜色的RGB

if length(x)==1;                           % 信号灯闪烁

        for i=1:2                              % 重复2次周期

        H1=plot(x,y, .y);                     % 绘制黄颜色的信号灯

    set(H1,MarkerSize,40),

    set_position(H,backcolor),                 % 控制界面状态保持不变

          pause(0.3)                           % 信号灯亮0.3

          H1=plot(x,y, . );                % 重新绘制信号灯,且颜色设置为和背景一样

          set(H1, MarkerSize,40, Color,backcolor),   % 信号灯熄灭

          set_position(H,backcolor),           % 控制界面状态保持不变

          pause(0.3)

        end

else

        plot(x,y, c: ),

end

set_position(H,backcolor)

function set_position(H,backcolor)

% 控制界面状态保持不变

axis([-1.3 1.3 -1.3 1.3]); 

set(H, XColor,backcolor, YColor,backcolor, Color,backcolor,...

    XTick,[], YTick,[])

这段程序除了调用已经调试成熟的de_afpskreadreceive外,还另外调用了函数call_plot来绘制信号灯,在call_plot中又调用set_position来保持Axes的界面。由于这两个功能需要反复使用,故编写成函数供调用,以便核心代码的阅读和维护。

同样,和上文类似,函数de_afpskreceive和函数AFPSKsend一样,都要作出类似的改动。请读者自行完成,并对可能出现的错误进行自己的判断和修改。

3pushbutton_do的回调函数pushbutton_do _Callback

在完成信号的接收解调并读取出遥控指令后,在回调函数pushbutton_do _Callback中直接调用函数control(No,sum,v,color,trace)即可。同时在整个遥控系统完成之后,对界面上的所有控件重新进行初始化。源代码如下:

function varargout = pushbutton_do_Callback(h, eventdata, handles, varargin)

set(handles.pushbutton_do, Enable, off); % pushbutton_do控件的使能属性设置为off

a_do=get(handles.pushbutton_do, Userdata);   % 获取读取遥控指令后的数据a_do

a_No=a_do(1);                           % 恢复遥控信息

a_sum=a_do(2);

a_v=a_do(3);

a_color=a_do(4:6);

a_trace=a_do(7:8);

pause(1)

control(a_No,a_sum,a_v,a_color,a_trace)     % 调用函数control执行遥控指令

reset_axes;                                 % 调用函数reset_axes,取消信号的显示

set(handles.popupmenu_Head, Value,1),      % 指令参数选择初始化

set(handles.popupmenu_NO, Value,1)

set(handles.popupmenu_sum, Value,1),

set(handles.popupmenu_v, Value,1)

set(handles.popupmenu_color, Value,1),

set(handles.popupmenu_trace, Value,1)

set(handles.popupmenu_tiaozhi, Value,1)

set(handles.edit_zhenma, string, ' '),      % 帧码、控制字显示取消

set(handles.edit_kong1, string, ' '),set(handles.edit_kong2, string, ' '),

set(handles.edit_kong3, string, ' '),set(handles.edit_kong4, string, ' '),

ss=questdlg({'遥控指令执行完毕!', '请按回车键回到初始化界面!'},...

'单步执行遥控指令完毕',...

'好,回到初始化界面!', '好,回到初始化界面!');

set(handles.pushbutton_step, Visible, on)     % 回到初始化界面

set(handles.pushbutton_continous, Visible, on)

set(handles.pushbutton_exit, Visible, on)

set(handles.pushbutton_start, Visible, off, Enable, on)

set(handles.pushbutton_send, Visible, off)

set(handles.pushbutton_receive, Visible, off)

set(handles.pushbutton_do, Visible, off)

……

function reset_axes

% 示波器信号不显示任何信号

global handles

color=[0.25 0.25 0.25];

axes(handles.axes)

plot(0,0)

set(handles.axes, XColor,color, YColor,color, Color,color, XTick,[], YTick,[]),

%需要对函数control作必要的改动:

switch No                          

case 1

    title('您选择的是1号站点!');

case 2

    title('您选择的是2号站点!');

case 3

    title('您选择的是3号站点!');

case 4

    title('您选择的是4号站点!');

end

这段程序中需要将title语句改为H=handles.axesX(如:case1时,H=handles.axes1case2时,H=handles.axes2,依次类推)。

同时,在语句h=line(Color,color,Marker,.,MarkerSize,40,EraseMode,xor)前,加上语句:

axes(H);

call_plot(H,No,x,y)

用来指定坐标轴,并在相应坐标轴上画出运动轨迹。

3.程序调试、编译和运行

至此,已经完成整个遥控系统过程的所有步骤,此时可以保存文件并开始调试。对于出现的错误提示信息进行相应的修改、完善,使之最终能够通过编译并顺利运行。

一般而言,在各控件回调函数中调用的已经调试好的函数时(如:sendreceiveNRZ_L AFPSK等),均需要在以下方面作出改动:增加语句“global handles”;涉及作图时,应当通过句柄来设定作图的区域(语句为“Axes(handles.axesX),plot(…)”),并对图形的属性(如背景颜色)进行重新设置;如有必要,在其他不符合程序逻辑的地方作出相应的改进。

除此之外,还有控件pushbutton_continous(连续执行)回调函数的编写。因为连续执行实际上就是上述过程在同一个控件中的集成,故不再讲述。感兴趣的读者可以参考源代码,参照上述过程自行编写。

4.控件回调函数设计小结

各控件的回调函数的编写和核心原理程序的设计相比,难度有所降低。其基本设计要点有:合理设计安排各控件之间的逻辑关系;掌握利用各控件的属性来进行数据的保存、传递;通过句柄访问控件并进行属性的设置、获取;会对不符合程序逻辑的地方作出改进和优化。

回调函数的编写,一个重要的技巧就是通过setget语句对控件的属性进行获取、设置,以及完成相关数据的传递、保存、获取,这一点在程序设计中得到了很好的体现,需要读者细细体会。

对于程序中需要反复调用的函数,需要将其另外编写成函数的形式,供主函数调用。这样,程序增强了可读性,方便用户对源代码的分析和维护。在本软件中,除了主函数和各控件生成的函数之外,自行编写的函数有:sendreceivebin2dec_mdec2bin_vdec2bin_mNRZ_LAFPSKde_afpskreadreset_axescontrolcall_plotset_position等,显然,这样简化了源代码,同时还增强了可读性和可维护性。

对于各控件回调函数的编写不可能一蹴而就,只能通过反复的调试、修改和运行才能够使程序通过编译,得以运行并不断得到完善。

四、结语

经过遥控原理Matlab程序实现、可视化界面设计与控件使用、控件回调函数编写与软件调试三个步骤,利用Matlab进行遥控模拟系统软件的设计得以圆满完成。

事实上,这是一个需要反复思考、反复琢磨、反复调试、反复摸索的过程,并不像文中所述的那样按部就班、一气呵成。所以读者在按照本文所讲述的步骤进行程序的调试时,一定会出现这样那样的问题,对于各种问题一定要按照Matlab给出的提示信息,动脑思考、动手实践,并按文中所给的思路加以解决。

 

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089