-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
KwpCommon.cs
206 lines (171 loc) · 6.19 KB
/
KwpCommon.cs
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
using BitFab.KW1281Test.Interface;
using System;
using System.Diagnostics;
using System.Runtime;
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.");
}
var protocolVersion = 0;
Interface.ReadTimeout = (int)TimeSpan.FromSeconds(2).TotalMilliseconds;
try
{
const int maxTries = 3;
for (var i = 1; i <= maxTries; i++)
{
try
{
protocolVersion = WakeUpNoRetry(controllerAddress, evenParity);
break;
}
catch (Exception ex)
{
Log.WriteLine(ex.Message);
if (i < maxTries)
{
Log.WriteLine("Retrying wakeup message...");
Thread.Sleep(TimeSpan.FromSeconds(1));
}
else
{
throw new InvalidOperationException("Controller did not wake up.");
}
}
}
}
finally
{
if (GCSettings.LatencyMode == GCLatencyMode.NoGCRegion)
{
GC.EndNoGCRegion();
}
Interface.ReadTimeout = Interface.DefaultTimeoutMilliseconds;
}
return protocolVersion;
}
private int WakeUpNoRetry(byte controllerAddress, bool evenParity)
{
Thread.Sleep(300);
BitBang5Baud(controllerAddress, evenParity);
// Throw away anything that might be in the receive buffer
Interface.ClearReceiveBuffer();
Log.WriteLine("Reading sync byte");
var syncByte = Interface.ReadByte();
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}");
var protocolVersion = ((keywordMsb & 0x7F) << 7) + (keywordLsb & 0x7F);
Log.WriteLine($"Protocol is KW {protocolVersion} (8N1)");
if (protocolVersion >= 2000)
{
BusyWait.Delay(25); // The EDC15 ECU needs a longer delay before writing the keywordMsb complement
}
else
{
BusyWait.Delay(10); // Supposed to be 25 but communicating with the UART takes a few ms
}
var complement = (byte)~keywordMsb;
WriteByte(complement);
if (protocolVersion >= 2000)
{
ReadComplement(
Utils.AdjustParity(controllerAddress, evenParity));
}
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;
const long msPerBit = 1000 / bitsPerSec - 3;
b = Utils.AdjustParity(b, evenParity);
BitBang(false); // Start bit
for (int i = 0; i < 8; i++)
{
bool bit = (b & 1) == 1;
BitBang(bit);
b >>= 1;
}
BitBang(true); // Stop bit
BusyWait.Delay(msPerBit);
return;
// Delay the appropriate amount and then set/clear the TxD line
void BitBang(bool bit)
{
BusyWait.Delay(msPerBit);
Interface.SetBreak(!bit);
}
}
/// <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 false
if (echo != b)
{
throw new InvalidOperationException($"Wrote 0x{b:X2} to port but echo was 0x{echo:X2}");
}
#endif
}
public KwpCommon(IInterface @interface)
{
Interface = @interface;
}
}
}