|
本帖最后由 huge 于 2015-2-19 01:14 編輯
本例程的下位機單片機程序和視頻詳見:http://www.zg4o1577.cn/bbs/dpj-31602-1.html 本來這次是想寫一個類似QQ尾巴的程序,不過好像有點難,所以就先緩一緩,做了個這個:寢室電量提示系統。大概流程:使用軟件(上位機)打開一個記錄有寢室電量信息的Access文件,查詢低于一定電量的寢室,再將這些寢室號或時間信息通過RS232接口發送到點陣LED上顯示;不使用時,點陣LED平時當作時鐘使用。
首先是上位機的編寫。和以往一樣,我還是選delphi平臺來編寫。
【程序界面布局】

整個程序分為串口和數據庫查詢兩部分。其中通信協議設置(均以十六進制發送、接收):
①PC發送數據包格式:
(1) 發送顯示的數據組數:BB+AF+發送數據的組數(H)+00+00+00+00+00++00+AF+EE ;
(2) 發送時間: BB+A2+14(20)+0C(12)+0B(11)+0A(10)+0D(13)+0E(14)+0B(11)+A2與月(0B)的異或+EE ;
(即2012-11-10 13:14:11)
(3) 發送寢室號: BB+A8+00+03+06+02+03+00+00+A8與“寢室號03620”的“6”(06H)異或+EE ;
②單片機應答數據包格式: BB+AA+00/01+00+00+00+00+00+00+AA+EE
(其中第三個數據00/01:00表示顯示成功;01表示顯示失敗)
【代碼】
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms,
Dialogs, ExtCtrls, SPComm, StdCtrls, sBevel, Grids, DBGrids,
dbcgrids, DB, ADODB, sSkinManager, SGridFunction, AdoConEd, sLabel,
ComCtrls,Menus,ScrollText, acTitleBar, ImgList,
acAlphaImageList, sCheckBox;
type
TForm1 = class(TForm)
Ports: TComboBox;
Baud: TComboBox;
Parity_bit: TComboBox;
Data_bits: TComboBox;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Senddisp: TButton;
Connect: TButton;
Stop_bit: TComboBox;
Label6: TLabel;
Comm1: TComm;
Image1: TImage;
Label7: TLabel;
Label8: TLabel;
Battery: TEdit;
Total: TEdit;
Image2: TImage;
StringGrid1: TStringGrid;
ADOQuery1: TADOQuery;
sSkinManager1: TsSkinManager;
ADOConnection1: TADOConnection;
Time: TsLabelFX;
Timer1: TTimer;
Time_settle: TsCheckBox;
MainMenu1: TMainMenu;
Database_set: TMenuItem;
Open_database: TMenuItem;
About: TMenuItem;
sBevel1: TsBevel;
Search: TButton;
Quit: TMenuItem;
ScrollText1: TScrollText;
sAlphaImageList1: TsAlphaImageList;
Timer2: TTimer;
procedure FormCreate(Sender: TObject);
procedure PortsChange(Sender: TObject);
procedure BaudChange(Sender: TObject);
procedure Parity_bitChange(Sender: TObject);
procedure Data_bitsChange(Sender: TObject);
procedure Stop_bitChange(Sender: TObject);
procedure ConnectClick(Sender: TObject);
procedure SenddispClick(Sender: TObject);
procedure BatteryKeyPress(Sender: TObject; var Key: Char);
procedure Timer1Timer(Sender: TObject);
procedure Open_databaseClick(Sender: TObject);
procedure AboutClick(Sender: TObject);
procedure SearchClick(Sender: TObject);
procedure QuitClick(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
procedure Comm1ReceiveData(Sender: TObject; Buffer: Pointer;
BufferLength: Word);
private
{ Private declarations }
procedure EnumComPorts(Ports: TStrings);
procedure SendString(const str:string);
function StrToHexStr(const S:string):string;
function HexStrToStr(const S:string):string;
public
{ Public declarations }
end;
var
Form1: TForm1;
SysTime:TSystemTime;
i,i0:Integer;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.EnumComPorts(Ports: TStrings); //獲取當前可用的串口
var
KeyHandle: HKEY;
ErrCode, Index: Integer;
ValueName, Data: string;
ValueLen, DataLen, ValueType: DWORD;
TmpPorts: TStringList;
Tmp4,Tmp5:TStringList;
begin
ErrCode:= RegOpenKeyEx(HKEY_LOCAL_MACHINE,'HARDWARE\DEVICEMAP\SERIALCOMM',0,KEY_READ,KeyHandle); if ErrCode <> ERROR_SUCCESS then
ShowMessage('打開串口列表的注冊表項出錯');
TmpPorts := TStringList.Create;
Tmp4:= TStringList.Create;
Tmp5 := TstringList.Create;
try
Index := 0;
repeat
ValueLen := 256;
DataLen := 256;
SetLength(ValueName, ValueLen);
SetLength(Data, DataLen);
ErrCode := RegEnumValue(KeyHandle, Index, PChar(ValueName),
Cardinal(ValueLen), nil, @ValueType, PByte(PChar(Data)), @DataLen);
if ErrCode = ERROR_SUCCESS then
begin
SetLength(Data, DataLen);
TmpPorts.Add(Trim(Data));
Inc(Index);
end
else if ErrCode <> ERROR_NO_MORE_ITEMS then
ShowMessage('打開串口列表的注冊表項出錯');
until (ErrCode <> ERROR_SUCCESS);
TmpPorts.Sort;
For Index:=0 To TmpPorts.Count-1 do
begin
if Length(TmpPorts[Index])<=4 then // 'COM3'
Tmp4.Add(TmpPorts[Index])
else Tmp5.Add(TmpPorts[Index]);
end;
Tmp4.AddStrings(Tmp5);
//Ports.Assign(TmpPorts);
Ports.Assign(Tmp4);
finally
RegCloseKey(KeyHandle);
TmpPorts.Free;
Tmp4.Free;
Tmp5.Free;
end;
end;
function TForm1.HexStrToStr(const S: string): string; //16進制字符串轉換成字符串
var
t:Integer;
ts:string;
M,Code:Integer;
begin
t:=1;
Result:='';
while t<=Length(S) do
begin
while (t<=Length(S)) and (not (S[t] in ['0'..'9','A'..'F','a'..'f'])) do
inc(t);
if (t+1>Length(S))or(not (S[t+1] in ['0'..'9','A'..'F','a'..'f'])) then
ts:='$'+S[t]
else
ts:='$'+S[t]+S[t+1];
Val(ts,M,Code);
if Code=0 then
Result:=Result+Chr(M);
inc(t,2);
end;
end;
procedure TForm1.SendString(const str: string);
begin
Comm1.WriteCommData(Pchar(str),Length(str));
end;
function TForm1.StrToHexStr(const S: string): string; //字符串轉換成16進制字符串
var
I:Integer;
begin
for I:=1 to Length(S) do
begin
if I=1 then
Result:=IntToHex(StrToInt(S[1]),2)
else Result:=Result+' '+IntToHex(StrToInt(S[I]),2);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);//初始化窗口
begin
Self.DoubleBuffered:= True;
ScrollText1.ScrollText:='------歡迎使用寢室電量提示系統 O(∩_∩)O~';
StringGrid1.Cells[0, 0]:='序號';
StringGrid1.Cells[1, 0]:='缺電寢室號';
StringGrid1.Cells[2, 0]:='剩余電量(度)';
EnumComPorts(Ports.Items);
Ports.ItemIndex:=0;
Comm1.CommName:=Ports.Text;
Baud.ItemIndex:=5;
Comm1.BaudRate:=StrToIntDef(Baud.Text,Comm1.BaudRate);
Parity_bit.ItemIndex:=2;
Comm1.Parity:=None;
Data_bits.ItemIndex:=3;
Comm1.ByteSize:=_8;
Stop_bit.ItemIndex:=0;
Comm1.StopBits:=_1;
end;
procedure TForm1.PortsChange(Sender: TObject); //端口選項
begin
Comm1.CommName:=Ports.Text;
end;
procedure TForm1.BaudChange(Sender: TObject); //波特率選項
begin
Comm1.BaudRate:=StrToIntDef(Baud.Text,Comm1.BaudRate)
end;
procedure TForm1.Parity_bitChange(Sender: TObject); //校驗位選項
begin
case Parity_bit.ItemIndex of
0:Comm1.Parity:=Even;
1:Comm1.Parity:=Mark;
2:Comm1.Parity:=None;
3:Comm1.Parity:=Odd;
4:Comm1.Parity:=Space;
end;
end;
procedure TForm1.Data_bitsChange(Sender: TObject); //數據位選項
begin
case Data_bits.ItemIndex of
0:Comm1.ByteSize:=_5;
1:Comm1.ByteSize:=_6;
2:Comm1.ByteSize:=_7;
3:Comm1.ByteSize:=_8;
end;
end;
procedure TForm1.Stop_bitChange(Sender: TObject); //停止位選項
begin
case Stop_bit.ItemIndex of
0:Comm1.StopBits:=_1;
1:Comm1.StopBits:=_1_5;
2:Comm1.StopBits:=_2;
end;
end;
procedure TForm1.ConnectClick(Sender: TObject); //連接串口按鈕事件
begin
if Connect.Caption='連接串口' then
begin
Comm1.StartComm;
Connect.Caption:='關閉串口';
Image1.Visible:=false;
Image2.Visible:=true;
Ports.Enabled:=false;
Baud.Enabled:=false;
Parity_bit.Enabled:=false;
Data_bits.Enabled:=false;
Stop_bit.Enabled:=false;
Senddisp.Enabled:=true;
end
else
begin
Comm1.StopComm;
Connect.Caption:='連接串口';
Image1.Visible:=true;
Image2.Visible:=false;
Ports.Enabled:=true;
Baud.Enabled:=true;
Parity_bit.Enabled:=true;
Data_bits.Enabled:=true;
Stop_bit.Enabled:=true;
Senddisp.Enabled:=false;
end;
end;
procedure TForm1.SenddispClick(Sender: TObject);
var
strdata,stryear1,stryear0,strmonth,strday,strhour,strminute,strsecond:string;
begin
if Time_settle.Checked=true then
begin
stryear1:=IntToHex(StrToInt(copy(IntToStr(SysTime.wYear),1,2)),2);
stryear0:=IntToHex(StrToInt(copy(IntToStr(SysTime.wYear),3,2)),2);
strmonth:=IntToHex(SysTime.wMonth,2);
strday:=IntToHex(SysTime.wDay,2);
strhour:=IntToHex(SysTime.wHour,2);
strminute:=IntToHex(SysTime.wMinute,2);
strsecond:=IntToHex(SysTime.wSecond,2);
strdata:=HexStrToStr('BBA2'+stryear1+stryear0+strmonth+strday+strhour+strminute+strsecond+IntToHex(SysTime.wMonth Xor $A2,2)+'EE');
if (trim(Total.Text) = '') or (trim(Total.Text) = '0') then
begin
SendString(HexStrToStr('BBAF01000000000000AFEE'));
//格式 BB+AF+發送數據的組數(H)+00+00+00+00+00++00+AF+EE
sleep(200);
SendString(strdata);
//格式 BB+A2+20+12+11+10+13+14+11+A2與月的異或+EE
Timer2.Enabled:=false;
end
else
begin
i0:=StrToInt(Total.Text);
SendString(HexStrToStr('BBAF'+IntToHex(i0+1,2)+'000000000000AFEE'));
//格式 BB+AF+發送數據的組數(H)+00+00+00+00+00++00+AF+EE
i:=1;
sleep(200);
SendString(strdata);
//格式 BB+A2+14(20)+0C(12)+0B(11)+0A(10)+0D(13)+0E(14)+0B(11)+A2與月(0B)的異或+EE
sleep(200);
Timer2.Enabled:=true;
end;
end
else
begin
if (trim(Total.Text) = '') or (trim(Total.Text) = '0') then
Application.MessageBox('數據無效,無法發送!','警告',MB_ICONWARNING)
else
begin
i0:=StrToInt(Total.Text);
SendString(HexStrToStr('BBAF'+IntToHex(i0,2)+'000000000000AFEE'));
//格式 BB+AF+發送數據的組數(H)+00+00+00+00+00++00+AF+EE
i:=1;
sleep(200);
Timer2.Enabled:=true;
end;
end;
end;
procedure TForm1.BatteryKeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in ['0'..'9',#8]) then Key:=#0;
end;
procedure TForm1.Timer1Timer(Sender: TObject); //顯示當前時間,每隔1s更新一次
begin
GetLocalTime(SysTime);
Time.Caption:=IntToStr(SysTime.wYear)+'年'+Format('%.2d',[SysTime.wMonth])+'月'+Format('%.2d',[SysTime.wDay])+'日 '+Format('%.2d',[SysTime.wHour])+':'+Format('%.2d',[SysTime.wMinute])+':'+Format('%.2d',[SysTime.wSecond]);
end;
procedure TForm1.Open_databaseClick(Sender: TObject);
var
constr: string;
begin
if ( constr <> '') then
ADOConnection1.ConnectionString := constr;
if (EditConnectionString(ADOConnection1)) then//調用數據源窗口,判斷是否連接成功
begin
ADOConnection1.Connected:=False;
ADOConnection1.Connected:=True;
constr := ADOConnection1.ConnectionString;
Search.Enabled:=true;
end
else
begin
constr := ADOConnection1.ConnectionString;
ADOConnection1.ConnectionString := '';
end;
end;

procedure TForm1.AboutClick(Sender: TObject);
begin
Application.MessageBox(' ☆ 寢室電量提示系統 ☆'+#13#13+
' Agent CopyRight@2012 '+#13+
' by hun ','關于',MB_OK);
end;
procedure TForm1.SearchClick(Sender: TObject);
begin
ADOConnection1.Connected:=true;
with ADOQuery1 do
begin
Close;
SQL.Clear;
SQL.Add('SELECT * FROM Sheet1 WHERE Battery <='+Battery.Text); // 查詢數據庫
Open;
end;
ShowQueryData(StringGrid1,ADOQuery1,0,1);
if HaveData(StringGrid1, 1, 1)=false then
Total.Text:='0'
else
Total.Text:=SetNumberFields(StringGrid1,0,1);
ADOConnection1.Connected:=false;
end;
procedure TForm1.QuitClick(Sender: TObject);
begin
if Application.MessageBox('您確定要退出嗎?','警告',MB_YESNO or MB_DEFBUTTON1)=IDYES then
begin
Application.Terminate;
end;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
begin
SendString(HexStrToStr('BBA8'+StrToHexStr(Format('%.5d',[StrToInt(StringGrid1.Cells[1, i])]))+'0000'+IntToHex(StrToInt('$'+StrToHexStr(copy(StringGrid1.Cells[1, i],Length(StringGrid1.Cells[1, i])-2,1))) Xor $A8,2)+'EE'));
//格式 BB+A8+00+03+06+02+03+00+00+A8與03620的06h異或+EE
i:=i+1;
if i>i0 then
Timer2.Enabled:=false;
end;
procedure TForm1.Comm1ReceiveData(Sender: TObject; Buffer: Pointer;
BufferLength: Word);
var
rbuf:array[0..10] of Byte;
begin
move(Buffer^,pchar(@rbuf)^,BufferLength);
if (rbuf[1]<>rbuf[9]) or (inttohex(rbuf[2],2)='01') then
Application.MessageBox('顯示失敗!','提示信息',MB_OK)
else
begin
if inttohex(rbuf[2],2)='00' then
Application.MessageBox('顯示成功!','提示信息',MB_OK)
else
Application.MessageBox('顯示失敗!','提示信息',MB_OK);
end;
end;
end.
【查詢效果】
在編寫下位機的過程中,發現端口的設置有點多余,所以又重新布局,于是誕生了精簡版:

|
|