三、控件回调函数编写与程序调试
预备工作完毕后,就可以进行“单步执行”界面中控件回调函数的编写与调试了。此时若运行程序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_Head和Hnum设定为全局变量,以便各子函数均可调用
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_step的Userdata,并将下标为1的数值赋为1
set(handles.pushbutton_step, 'Userdata',OK) % 更新pushbutton_step的Userdata
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_step的Userdata
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_step的Userdata,然后每选择一个参数,则更新一次数据(帧码选择时将下标为1的数值赋为1,站点选择时将下标为2的数值赋为1,依次类推),每完成一个参数的选择则调用函数isOK,在函数isOK中算出更新后的数组所有元素之和,这样即可作出正确的判断。
这段程序中除了set语句,还有一个get语句的反复应用,其基本格式为get(H,‘PropertyName’),常常用于控件相关属性的获取。这个语句和set语句一样重要,读者也要很好地理解并掌握。
参考注释,细细研究这段源代码,并通过运行,不难看出,考虑的四个方面的问题都得到很好的解决。
由于站点号、圈数、速度、颜色、轨迹的选择和各控件回调函数编程思想基本一致,故不作讲述,只给出相应的源代码及简单的注释。另外,请读者分析函数popupmenu_NO_Callback和popupmenu_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_NO的Userdata
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])) % 如果选择完毕则a1、a2形成控制字1
set(handles.popupmenu_NO, 'Userdata',[0 0])
end
OK=get(handles.pushbutton_step, 'Userdata'); % 获取pushbutton_step的Userdata并更新
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_NO的Userdata
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])) % 如果选择完毕则a1、a2形成控制字1
set(handles.popupmenu_NO, 'Userdata',[0 0])
end
OK=get(handles.pushbutton_step, 'Userdata'); % 获取pushbutton_step的Userdata并更新
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_step的Userdata并更新
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_step的Userdata并更新
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_step的Userdata并更新
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; % 调用函数send、receive,重新发送、接收
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_afpsk、read、receive外,还另外调用了函数call_plot来绘制信号灯,在call_plot中又调用set_position来保持Axes的界面。由于这两个功能需要反复使用,故编写成函数供调用,以便核心代码的阅读和维护。
同样,和上文类似,函数de_afpsk、receive和函数AFPSK和send一样,都要作出类似的改动。请读者自行完成,并对可能出现的错误进行自己的判断和修改。
(3)pushbutton_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.axes1;case2时,H=handles.axes2,依次类推)。
同时,在语句h=line(‘Color’,color,‘Marker’,‘.’,‘MarkerSize’,40,‘EraseMode’,‘xor’)前,加上语句:
axes(H);
call_plot(H,No,x,y)
用来指定坐标轴,并在相应坐标轴上画出运动轨迹。
3.程序调试、编译和运行
至此,已经完成整个遥控系统过程的所有步骤,此时可以保存文件并开始调试。对于出现的错误提示信息进行相应的修改、完善,使之最终能够通过编译并顺利运行。
一般而言,在各控件回调函数中调用的已经调试好的函数时(如:send、receive、NRZ_L、 AFPSK等),均需要在以下方面作出改动:增加语句“global handles”;涉及作图时,应当通过句柄来设定作图的区域(语句为“Axes(handles.axesX),plot(…)”),并对图形的属性(如背景颜色)进行重新设置;如有必要,在其他不符合程序逻辑的地方作出相应的改进。
除此之外,还有控件pushbutton_continous(连续执行)回调函数的编写。因为连续执行实际上就是上述过程在同一个控件中的集成,故不再讲述。感兴趣的读者可以参考源代码,参照上述过程自行编写。