forked from gmenounos/kw1281test
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKwpCommon.cs
More file actions
209 lines (170 loc) · 6.22 KB
/
Copy pathKwpCommon.cs
File metadata and controls
209 lines (170 loc) · 6.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
using BitFab.KW1281Test.Interface;
using System;
using System.Diagnostics;
using System.Threading;
namespace BitFab.KW1281Test
{
public interface IKwpCommon
{
IInterface Interface { get; }
int WakeUp(byte controllerAddress, bool evenParity = false);
byte ReadByte();
/// <summary>
/// Write a byte to the interface and receive its echo.
/// </summary>
/// <param name="b">The byte to write.</param>
void WriteByte(byte b);
void ReadComplement(byte b);
}
class KwpCommon : IKwpCommon
{
public IInterface Interface { get; }
public int WakeUp(byte controllerAddress, bool evenParity)
{
// Disable garbage collection int this time-critical method
bool noGC = GC.TryStartNoGCRegion(1024 * 1024);
if (!noGC)
{
Log.WriteLine("Warning: Unable to disable GC so timing may be compromised.");
}
byte syncByte = 0;
const int maxTries = 3;
for (int i = 1; i <= maxTries; i++)
{
Thread.Sleep(300);
BitBang5Baud(controllerAddress, evenParity);
// Throw away anything that might be in the receive buffer
Interface.ClearReceiveBuffer();
Log.WriteLine("Reading sync byte");
try
{
syncByte = Interface.ReadByte();
break;
}
catch (TimeoutException)
{
if (i < maxTries)
{
Log.WriteLine("Retrying wakeup message...");
}
else
{
throw new InvalidOperationException("Controller did not wake up.");
}
}
}
if (noGC)
{
GC.EndNoGCRegion();
}
if (syncByte != 0x55)
{
throw new InvalidOperationException(
$"Unexpected sync byte: Expected $55, Actual ${syncByte:X2}");
}
var keywordLsb = Interface.ReadByte();
Log.WriteLine($"Keyword Lsb ${keywordLsb:X2}");
var keywordMsb = ReadByte();
Log.WriteLine($"Keyword Msb ${keywordMsb:X2}");
Thread.Sleep(25);
var complement = (byte)~keywordMsb;
WriteByte(complement);
var protocolVersion = ((keywordMsb & 0x7F) << 7) + (keywordLsb & 0x7F);
Log.WriteLine($"Protocol is KW {protocolVersion} (8N1)");
if (protocolVersion >= 2000)
{
ReadComplement(controllerAddress);
}
return protocolVersion;
}
public byte ReadByte()
{
return Interface.ReadByte();
}
public void WriteByte(byte b)
{
WriteByteAndDiscardEcho(b);
}
public void ReadComplement(byte b)
{
var expectedComplement = (byte)~b;
var actualComplement = Interface.ReadByte();
if (actualComplement != expectedComplement)
{
throw new InvalidOperationException(
$"Received complement ${actualComplement:X2} but expected ${expectedComplement:X2}");
}
}
/// <summary>
/// Send a byte at 5 baud manually to the interface. The byte will be sent as
/// 1 start bit, 7 data bits, 1 parity bit (even or odd), 1 stop bit.
/// https://www.blafusel.de/obd/obd2_kw1281.html
/// </summary>
/// <param name="b">The byte to send.</param>
/// <param name="evenParity">
/// False for odd parity (KWP1281), true for even parity (KWP2000).</param>
private void BitBang5Baud(byte b, bool evenParity)
{
const int bitsPerSec = 5;
long ticksPerSecond = Stopwatch.Frequency;
long ticksPerBit = ticksPerSecond / bitsPerSec;
long maxTick;
// Delay the appropriate amount and then set/clear the TxD line
void BitBang(bool bit)
{
while (Stopwatch.GetTimestamp() < maxTick)
;
if (bit)
{
Interface.SetRts(false);
Interface.SetBreak(false);
}
else
{
// On Windows (and maybe just non-USB cables), SetRts must be called before
// SetBreak or else it will cancel the break because it internally calls
// the Win32 SetCommState function, which reinitializes the port.
Interface.SetRts(true);
Interface.SetBreak(true);
}
maxTick += ticksPerBit;
}
bool parity = !evenParity; // XORed with each bit to calculate parity bit
Interface.SetDtr(false);
var startTick = maxTick = Stopwatch.GetTimestamp();
BitBang(false); // Start bit
for (int i = 0; i < 7; i++)
{
bool bit = (b & 1) == 1;
parity ^= bit;
b >>= 1;
BitBang(bit);
}
BitBang(parity);
BitBang(true); // Stop bit
Interface.SetDtr(true);
// Wait for end of stop bit
long stopTick;
while ((stopTick = Stopwatch.GetTimestamp()) < maxTick)
;
Log.WriteLine(
$"Wakeup duration: {(double)(stopTick - startTick) / ticksPerSecond} seconds");
}
/// <summary>
/// Write a byte to the interface and read/discard its echo.
/// </summary>
private void WriteByteAndDiscardEcho(byte b)
{
Interface.WriteByteRaw(b);
var echo = Interface.ReadByte();
if (echo != b)
{
throw new InvalidOperationException($"Wrote 0x{b:X2} to port but echo was 0x{echo:X2}");
}
}
public KwpCommon(IInterface @interface)
{
Interface = @interface;
}
}
}