目录
1 需求确定的重要性
书接上回,由于之前做过一个案子经过是这样的:
Step 1:
客户给我发来一个压缩包,里面十几个文件。
Step 2:
我辛辛苦苦看明白所有文档,这才搞清楚客户的需求到底是啥。
Step 3:
将需求整理为文档,发给客户(不是邮件只相当于口头沟通)。
Step 4:
客户口头回复就按照这个做一个版本。(由于客户当时前忙别的案子,没有一一对需求项)
Step 5:
按照需求一顿抡,并调试完成,交付给客户。
Step 6:
第二天,我以为客户可以打款了。结果客户重新给了一份明确的需求过来,看似和我们做的大差不差,但应用层几乎得重做一遍。我当时就直接爆粗了,然并卵,客户就是上帝,还得按照需求改。
被这样坑过一次后,后续做案子我都必须要求有明确的需求。明确的需求拿到之前可以提前动,但都是一些必须用到的功能可以先做,应用层次的东西则暂缓。
这次的案子也是这样,要得比较急,但是明确的应用功能需求没有明确。
2 提炼必须功能
上回说到这个案子是用串口控制设备,串口收发数据通道已经打通。
而具体内容,就需要通讯协议承载,而设备是现有的这个协议也不会变化。
3 继续干
设备通讯协议如上图所示,先来解决校验和帧尾字段。
3.1 校验和帧尾添加
实现一个简单的数据包封装功能,它计算输入缓冲区的校验和,并在缓冲区末尾添加校验和和结束标志。
代码如下:
type
SendBuffer = array[1..200] of Byte;
function TForm1.PackCheckAndEnd(InputDa:SendBuffer;InputLen:Integer):SendBuffer;
var
I:Integer;
sum:Byte;
begin
sum := 0;
for I := 1 to InputLen do
begin
sum := sum + InputDa[I];
end;
sum := not sum; //取反操作
sum := sum + 1;
InputDa[InputLen+1] := sum;
InputDa[InputLen+2] := $7d; // $7d就是0x7d
Result := InputDa;
end;
代码解释:
- 初始化变量: sum 初始化为 0,用于累加输入缓冲区中的所有字节值。
- 计算校验和:使用 for 循环遍历输入缓冲区 InputDa 的有效部分(从索引 1 到 InputLen)。在每次循环中,累加当前字节的值到 sum 中。
(3)取反并加 1
(4)添加校验和和结束标志:
将计算得到的校验和值 sum 写入到 InputDa 的 InputLen+1 位置。
在 InputDa 的 InputLen+2 位置添加一个结束标志 $7D。
(6)返回结果,函数返回修改后的 InputDa 缓冲区。
3.2 组包并发送
实现数据包的构建和发送功能,它首先初始化数据包,并在其中添加起始标志。接着调用 PackCheckAndEnd 函数计算校验和并添加结束标志,最后将数据包发送出去并通过 Memo2 显示发送的数据。
代码如下:
function TForm1.TestSendCmd(test:string):Boolean;
var
Bytes: SendBuffer;
MySendStr: string;
DataLen:Integer;
I:Integer;
str:string;
begin
Result := True;
Bytes[1] := $7b;
DataLen := 2;
{添加组包代码}
Bytes := PackCheckAndEnd(Bytes,DataLen);
DataLen := DataLen + 2;
for I := 1 to DataLen do
begin
str := str + IntToHex(Bytes[I] , 2) + ' ';
end;
Memo2.Lines.Add(chr($d) + chr($a) + '发送数据:' + str);
// 发送数据
SetLength(MySendStr,DataLen);
Move(Bytes,Pchar(MySendStr)^,DataLen);
Form1.Comm1.WriteCommData(PChar(MySendStr),DataLen);
end;
代码解释:
(1)初始化变量:
• Result 初始化为 True,表示默认发送成功。
(2)初始化数据包:
• Bytes[1] := $7B; 在 Bytes 数组的第一个位置添加起始标志 $7B。
• DataLen := 2; 设置初始数据长度为 2(包括起始标志)。
(3)添加组包代码:
• 这里应该有添加数据到 Bytes 数组的代码,但示例中这部分被注释掉了。
• 你可以在这里添加自己的数据,例如 Bytes[2] := SomeValue;。
(4)计算校验和并添加结束标志:
• 调用 PackCheckAndEnd 函数计算校验和并添加结束标志 $7D。
• DataLen := DataLen + 2; 更新数据包长度,增加校验和和结束标志所占的空间。
(5)构建十六进制字符串:
• 使用 for 循环遍历 Bytes 数组的有效部分,并使用 IntToHex 函数将每个字节转换为十六进制字符串。
• 将转换后的字符串拼接起来,并添加到 Memo2 控件中。
(6)发送数据:
• 使用 SetLength 函数设置 MySendStr 的长度为 DataLen。
• 使用 Move 函数将 Bytes 数组的内容复制到 MySendStr。
• 调用 Comm1.WriteCommData 方法发送数据。
3.3 串口接收调整
上回做的串口接收,是接收并打印字符串,由于协议是十六进制包,需要调整。
实现串口接收到数据时的处理功能,它首先判断是否处理接收到的数据,然后遍历接收到的数据缓冲区,将每个字节转换为十六进制字符串,并将结果显示在 Memo2 控件中。这样可以监控串口通信过程中的数据传输情况。
代码如下:
procedure TForm1.Comm1ReceiveData(Sender: TObject; Buffer: Pointer;
BufferLength: Word);
var
str:string;
I: Word;
ByteValue: Byte;
P: PByte;
begin
if s_com then
begin
application.ProcessMessages;
str := '';
P := PByte(Buffer);
for I := 0 to BufferLength - 1 do
begin
ByteValue := P^;
str := str + IntToHex(ByteValue , 2) + ' ';
Inc(P);
end;
Memo2.Lines.Add(datetimetostr(now) + chr($0d) + chr($0a) + str);
end;
end;
代码解释:
(1)处理消息:
• application.ProcessMessages; 这行代码用于处理窗口的消息队列,确保 GUI 的响应性。
(2)初始化变量:
• str := ''; 初始化字符串 str 为空字符串。
• P := PByte(Buffer); 将 Buffer 指针转换为 PByte 指针,方便访问缓冲区中的字节。
(3)构建十六进制字符串:
• 使用 for 循环遍历缓冲区中的每个字节。
• 使用 P^ 获取当前指针位置的字节值,并存储在 ByteValue 变量中。
• 使用 IntToHex 函数将 ByteValue 转换为十六进制字符串,并拼接到 str 字符串中。
• 使用 Inc(P); 增加指针,使其指向下一个字节。
(4)显示接收到的数据:
• 使用 DateTimeToStr(Now) 获取当前时间,并将其与转换后的十六进制字符串一起添加到 Memo2 控件中。
3.4 十六进制发送和接收测试
现阶段由于还没有拿到设备,我将串口线的发送和接收直接短接,可初步测试数据发送和接收效果。效果如下图:
欲知后事如何,请看下回道来。
【创作不易,欢迎转载,转载请注明出处】
如果大家对相关文章感兴趣,可以关注公众号“嵌入式毛哥”,持续分享嵌入式干货,不限于疑难故障分析解决、算法共享、开发设计思想等,志在帮助更多人在嵌入式行业发光发热。