GCC Code Coverage Report


Directory: ./
File: firmware/libfirmware/pt2001/src/pt2001.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 0.0% 0 0 298
Functions: 0.0% 0 0 22
Branches: 0.0% 0 0 52
Decisions: 0.0% 0 - 50

Line Branch Decision Exec Source
1 /*
2 * @file pt2001.cpp
3 *
4 * The NXP PT2001 is a programmable gate driver IC for precision solenoid control applications.
5 *
6 * Useful wires:
7 * 5v, 3(3.3v), Gnd, 12v, VccIO(3v) SPI, DRVEN, RSTB
8 *
9 * For MC33816 vs PT2000 differences see
10 * https://www.nxp.com/docs/en/application-note/AN5203.pdf
11 *
12 * @date May 3, 2019
13 * @author Andrey Belomutskiy, (c) 2012-2020
14 * @author Matthew Kennedy
15 */
16
17 #include <rusefi/pt2001.h>
18 #include <rusefi/arrays.h>
19
20 #include <PT2001_LoadData.h>
21
22 const int MC_CK = 6; // PLL x24 / CLK_DIV 4 = 6Mhz
23
24 const int MAX_SPI_MODE_A_TRANSFER_SIZE = 31; //max size for register config transfer
25
26 enum {
27 CODE_RAM1,
28 CODE_RAM2,
29 DATA_RAM
30 };
31 enum {
32 REG_MAIN,
33 REG_CH1,
34 REG_CH2,
35 REG_IO,
36 REG_DIAG
37 };
38
39 static bool validateChipId(uint16_t id) {
40 return (id >> 8) == 0x9D;
41 }
42
43 void Pt2001Base::setupSpi() {
44 select();
45 // Select Channel command
46 send(0x7FE1);
47 // Common Page
48 send(0x0004);
49
50
51 // Configure SPI command
52 send(0x3901);
53 // Mode A + Watchdog timer full
54 //send(0x001F);
55 send(0x009F); // + fast slew rate on miso
56 deselect();
57 }
58
59 uint16_t Pt2001Base::readId() {
60 select();
61 send(0xBAA1);
62 uint16_t ID = recv();
63 deselect();
64 return ID;
65 }
66
67 // Read a single word in Data RAM
68 uint16_t Pt2001Base::readDram(MC33816Mem addr) {
69 uint16_t addrInt = static_cast<uint16_t>(addr);
70
71 select();
72 // Select Channel command, Common Page
73 send(0x7FE1);
74 send(0x0004);
75 // read (MSB=1) at data ram x9 (SCV_I_Hold), and 1 word
76 send((0x8000 | addrInt << 5) + 1);
77 uint16_t readValue = recv();
78
79 deselect();
80 return readValue;
81 }
82
83 // Update a single word in Data RAM
84 void Pt2001Base::writeDram(MC33816Mem addr, uint16_t data) {
85 uint16_t addrInt = static_cast<uint16_t>(addr);
86
87 select();
88 // Select Channel command, Common Page
89 send(0x7FE1);
90 send(0x0004);
91 // write (MSB=0) at data ram x9 (SCV_I_Hold), and 1 word
92 send((addrInt << 5) + 1);
93 send(data);
94
95 deselect();
96 }
97
98 static uint16_t dacEquation(float current) {
99 /*
100 Current, given in A
101 I = (DAC_VALUE * V_DAC_LSB - V_DA_BIAS)/(G_DA_DIFF * R_SENSEx)
102 DAC_VALUE = ((I*G_DA_DIFF * R_SENSEx) + V_DA_BIAS) / V_DAC_LSB
103 V_DAC_LSB is the DAC resolution = 9.77mv
104 V_DA_BIAS = 250mV
105 G_DA_DIFF = Gain: 5.79, 8.68, [12.53], 19.25
106 R_SENSE = 10mOhm soldered on board
107 */
108 return ((current * 12.53f * 10) + 250.0f) / 9.77f;
109 }
110
111 void Pt2001Base::setTimings() {
112 setBoostVoltage(getBoostVoltage());
113
114 // Convert mA to DAC values
115 writeDram(MC33816Mem::Iboost, dacEquation(getBoostCurrent()));
116 writeDram(MC33816Mem::Ipeak, dacEquation(getPeakCurrent()));
117 writeDram(MC33816Mem::Ihold, dacEquation(getHoldCurrent()));
118
119 // in micro seconds to clock cycles
120 writeDram(MC33816Mem::Tpeak_off, (MC_CK * getTpeakOff()));
121 writeDram(MC33816Mem::Tpeak_tot, (MC_CK * getTpeakTot()));
122 writeDram(MC33816Mem::Tbypass, (MC_CK * getTbypass()));
123 writeDram(MC33816Mem::Thold_off, (MC_CK * getTholdOff()));
124 writeDram(MC33816Mem::Thold_tot, (MC_CK * getTHoldTot()));
125 writeDram(MC33816Mem::Tboost_min, (MC_CK * getTBoostMin()));
126 writeDram(MC33816Mem::Tboost_max, (MC_CK * getTBoostMax()));
127
128 // HPFP solenoid settings
129 writeDram(MC33816Mem::HPFP_Ipeak, dacEquation(getPumpPeakCurrent()));
130 writeDram(MC33816Mem::HPFP_Ihold, dacEquation(getPumpHoldCurrent()));
131 writeDram(MC33816Mem::HPFP_Thold_off, MC_CK * getPumpTholdOff());
132 writeDram(MC33816Mem::HPFP_Thold_tot, MC_CK * getPumpTholdTot());
133 }
134
135 void Pt2001Base::setBoostVoltage(float volts) {
136 // Sanity checks, Datasheet says not too high, nor too low
137 if (volts > 72.0f) {
138 onError("DI Boost voltage setpoint too high");
139 return;
140 }
141 if (volts < 10.0f) {
142 onError("DI Boost voltage setpoint too low");
143 return;
144 }
145
146 // There's a 1/32 divider on the input, then the DAC's output is 9.77mV per LSB. (1 / 32) / 0.00977 = 3.199 counts per volt.
147 uint16_t data = (volts * 3.25f) + 1.584f;
148 writeDram(MC33816Mem::Vboost_high, data+1);
149 writeDram(MC33816Mem::Vboost_low, data-1);
150 // Remember to strobe driven!!
151 }
152
153 bool Pt2001Base::checkFlash() {
154 select();
155
156 // ch1
157 // read (MSB=1) at location, and 1 word
158 send((0x8000 | 0x100 << 5) + 1);
159 if (!(recv() & (1<<5))) {
160 deselect();
161 return false;
162 }
163
164 // ch2
165 // read (MSB=1) at location, and 1 word
166 send((0x8000 | 0x120 << 5) + 1);
167
168 if (!(recv() & (1<<5))) {
169 deselect();
170 return false;
171 }
172
173 deselect();
174 return true;
175 }
176
177 void Pt2001Base::clearDriverStatus(){
178 // Note: There is a config at 0x1CE & 1 that can reset this status config register on read
179 // otherwise the reload/recheck occurs with this write
180 // resetting it is necessary to clear default reset behavoir, as well as if an issue has been resolved
181 setupSpi(); // ensure on common page?
182 select();
183 send((0x0000 | 0x1D2 << 5) + 1); // write, location, one word
184 send(0x0000); // anything to clear
185 deselect();
186 }
187
188 uint16_t Pt2001Base::readStatus(int reg) {
189 setupSpi(); // ensure on common page?
190 select();
191 send((0x8000 | reg << 5) + 1);
192 uint16_t driverStatus = recv();
193 deselect();
194 return driverStatus;
195 }
196
197 uint16_t Pt2001Base::readDriverStatus() {
198 return readStatus(0x1D2);
199 }
200
201 static bool checkUndervoltVccP(uint16_t driverStatus){
202 return (driverStatus & (1<<0));
203 }
204
205 static bool checkUndervoltV5(uint16_t driverStatus){
206 return (driverStatus & (1<<1));
207 }
208
209 // static bool checkOverTemp(uint16_t driverStatus){
210 // return (driverStatus & (1<<3));
211 // }
212
213 static bool checkDrivenEnabled(uint16_t driverStatus){
214 return (driverStatus & (1<<4));
215 }
216
217 void Pt2001Base::enableFlash() {
218 select();
219 send(0x2001); //ch1
220 send(0x0018); //enable flash
221 send(0x2401); //ch2
222 send(0x0018); // enable flash
223 deselect();
224 }
225
226 void Pt2001Base::periodicCallback() {
227 // todo: read status/diag via SPI
228 }
229
230 void Pt2001Base::downloadRam(int target) {
231 uint16_t memory_area = 0; // memory area
232 uint16_t start_address = 0; // start address
233 uint16_t codeWidthRegAddr = 0; // code width register address
234 uint16_t size = 0; // size of RAM data
235 uint16_t command = 0; // command data
236 const uint16_t *RAM_ptr = nullptr; // pointer to array of data to be sent to the chip
237
238 //Why Again? For Every time, just in case?
239 setupSpi();
240
241 switch(target) // selects target
242 {
243 case CODE_RAM1:
244 memory_area = 0x1;
245 start_address = 0;
246 codeWidthRegAddr = 0x107;
247 RAM_ptr = PT2001_code_RAM1;
248 // todo: use efi::size?
249 size = sizeof(PT2001_code_RAM1) / 2;
250 break;
251
252 case CODE_RAM2:
253 memory_area = 0x2;
254 start_address = 0;
255 codeWidthRegAddr = 0x127;
256 RAM_ptr = PT2001_code_RAM2;
257 // todo: use efi::size?
258 size = sizeof(PT2001_code_RAM2) / 2;
259 break;
260
261 case DATA_RAM: // ch1 only?
262 memory_area = 0x4;
263 start_address = 0;
264 RAM_ptr = PT2001_data_RAM;
265 // todo: use efi::size?
266 size = sizeof(PT2001_data_RAM) / 2;
267 break;
268 // optional, both data_rams with 0x3, writes same code to both
269 default:
270 break;
271 }
272
273 // Chip-Select high
274 select();
275
276 if (target != DATA_RAM)
277 {
278 command = codeWidthRegAddr << 5; // control width register address
279 command |= 1; // number of words to follow
280 send(command); // sends code_width command
281 send(size); // sends size (Code Width)
282 }
283
284 // Select Channel command
285 send(0x7FE1);
286 // RAM1, RAM2, or Common Page (Data RAM)
287 send(memory_area);
288
289 // "Command" of starting address
290 // up to 0x03FE of code ram
291 // up to 0x0080 of data ram
292 command = start_address << 5;
293 send(command); // sends start address command
294
295 sendLarge(RAM_ptr, size);
296 deselect();
297 }
298
299 void Pt2001Base::downloadRegister(int r_target) {
300 uint16_t r_start_address = 0; // start address
301 uint16_t r_size = 0; // size of configuration data
302 uint16_t r_command = 0; // command data
303 uint16_t remainder_size = 0; // remainder size
304 const uint16_t *reg_ptr = nullptr; // pointer to array of data to be sent to the chip
305
306 switch(r_target) // selects target
307 {
308 case REG_CH1: // channel 1 configurations
309 r_start_address = 0x100;
310 reg_ptr = PT2001_ch1_config;
311 r_size = efi::size(PT2001_ch1_config);
312 break;
313
314 case REG_CH2: // channel 2 configurations
315 r_start_address = 0x120;
316 reg_ptr = PT2001_ch2_config;
317 r_size = efi::size(PT2001_ch2_config);
318 break;
319
320 case REG_DIAG: // diagnostic configurations
321 r_start_address = 0x140;
322 reg_ptr = PT2001_diag_config;
323 r_size = efi::size(PT2001_diag_config);
324 break;
325
326 case REG_IO: // IO configurations
327 r_start_address = 0x180;
328 reg_ptr = PT2001_io_config;
329 r_size = efi::size(PT2001_io_config);
330 break;
331
332 case REG_MAIN: // main configurations
333 r_start_address = 0x1C0;
334 reg_ptr = PT2001_main_config;
335 r_size = efi::size(PT2001_main_config);
336 break;
337
338 default:
339 break;
340 }
341
342 //for location < size(remainder?)
343 // is location == 0? or past max xfer, send command + expected size
344 // if location = max xfer
345 //
346 // retrieve data, send it, increase pointer
347 // increase
348
349 if (r_size > MAX_SPI_MODE_A_TRANSFER_SIZE) //if size is too large, split into two sections ... MULTIPLE sections..
350 {
351 remainder_size = r_size - MAX_SPI_MODE_A_TRANSFER_SIZE; // creates remaining size
352 r_size = MAX_SPI_MODE_A_TRANSFER_SIZE; // sets first size
353 }
354
355 r_command = r_start_address << 5; // start address
356 r_command += r_size; // number of words to follow
357
358 select(); // Chip
359
360 send(r_command); // sends address and number of words to be sent
361
362 sendLarge(reg_ptr, r_size);
363
364 if (remainder_size > 0) // if remainder size is greater than 0, download the rest
365 {
366 r_start_address += r_size; // new start address
367 r_command = r_start_address << 5; // start address
368 r_command += remainder_size; // number of words to follow
369
370 send(r_command); // sends address and number of words to be sent
371 sendLarge(reg_ptr + r_size, remainder_size);
372 }
373 deselect();
374 }
375
376 // void initMc33816() {
377 // //
378 // // see setTest33816EngineConfiguration for default configuration
379 // // Pins
380 // if (!isBrainPinValid(engineConfiguration->mc33816_cs) ||
381 // !isBrainPinValid(engineConfiguration->mc33816_rstb) ||
382 // !isBrainPinValid(engineConfiguration->mc33816_driven)) {
383 // return;
384 // }
385 // if (isBrainPinValid(engineConfiguration->mc33816_flag0)) {
386 // efiSetPadMode("mc33816 flag0", engineConfiguration->mc33816_flag0, getInputMode(PI_DEFAULT));
387 // }
388
389 // chipSelect.initPin("mc33 CS", engineConfiguration->mc33816_cs /*, &engineConfiguration->csPinMode*/);
390
391 // // Initialize the chip via ResetB
392 // resetB.initPin("mc33 RESTB", engineConfiguration->mc33816_rstb);
393 // // High Voltage via DRIVEN
394 // driven.initPin("mc33 DRIVEN", engineConfiguration->mc33816_driven);
395
396
397 // spiCfg.ssport = getHwPort("hip", engineConfiguration->mc33816_cs);
398 // spiCfg.sspad = getHwPin("hip", engineConfiguration->mc33816_cs);
399
400 // // hard-coded for now, just resolve the conflict with SD card!
401 // engineConfiguration->mc33816spiDevice = SPI_DEVICE_3;
402
403 // driver = getSpiDevice(engineConfiguration->mc33816spiDevice);
404 // if (driver == NULL) {
405 // // error already reported
406 // return;
407 // }
408
409 // spiStart(driver, &spiCfg);
410
411 // addConsoleAction("mc33_stats", showStats);
412 // addConsoleAction("mc33_restart", mcRestart);
413 // //addConsoleActionI("mc33_send", sendWord);
414
415 // initMc33816IfNeeded();
416 // }
417
418 const char * mcFaultToString(McFault fault) {
419 switch (fault) {
420 case McFault::NoComm:
421 return "NoComm";
422 case McFault::NoFlash:
423 return "NoFlash";
424 case McFault::UnderVoltageAfter:
425 return "UnderVoltageAfter";
426 case McFault::flag0:
427 return "flag0";
428 case McFault::UnderVoltage5:
429 return "UnderVoltage5";
430 case McFault::Driven:
431 return "Driven";
432 case McFault::UnderVoltage7:
433 return "UnderVoltage7";
434 default:
435 return "TODO";
436 }
437 return "TODO";
438 }
439
440 void Pt2001Base::shutdown() {
441 setDriveEN(false); // ensure HV is off
442 setResetB(false); // turn off the chip
443 }
444
445 bool Pt2001Base::restart() {
446 bool flag0before = false;
447 bool flag0after = false;
448
449 // Start with everything off
450 shutdown();
451 deselect();
452
453 if (getVbatt() < 8) {
454 onError("GDI not Restarting until we see VBatt");
455 return false;
456 }
457
458 // Wait for chip to reset, then release reset and wait again
459 sleepMs(1);
460 setResetB(true);
461 sleepMs(1);
462
463 // Flag0 should be floating - pulldown means it should read low
464 flag0before = readFlag0();
465
466 acquireBus();
467 setupSpi();
468
469 clearDriverStatus(); // Initial clear necessary
470 status = readDriverStatus();
471 if (checkUndervoltV5(status)) {
472 onError(McFault::UnderVoltage5);
473 shutdown();
474 releaseBus();
475 return false;
476 }
477
478 uint16_t chipId = readId();
479 if (!validateChipId(chipId)) {
480 onError(McFault::NoComm);
481 shutdown();
482 releaseBus();
483 return false;
484 }
485
486 downloadRam(CODE_RAM1); // transfers code RAM1
487 downloadRam(CODE_RAM2); // transfers code RAM2
488 downloadRam(DATA_RAM); // transfers data RAM
489 downloadRegister(REG_MAIN); // download main register configurations
490
491 // current configuration of REG_MAIN would toggle flag0 from LOW to HIGH
492 flag0after = readFlag0();
493 if (flag0before || !flag0after) {
494 if (errorOnUnexpectedFlag()) {
495 onError(McFault::flag0);
496 shutdown();
497 releaseBus();
498 return false;
499 }
500 }
501
502 downloadRegister(REG_CH1); // download channel 1 register configurations
503 downloadRegister(REG_CH2); // download channel 2 register configurations
504 downloadRegister(REG_IO); // download IO register configurations
505 downloadRegister(REG_DIAG); // download diag register configuration
506
507 setTimings();
508
509 // Finished downloading, let's run the code
510 enableFlash();
511
512 // give it a moment to take effect
513 sleepMs(10);
514
515 if (!checkFlash()) {
516 onError(McFault::NoFlash);
517 shutdown();
518 releaseBus();
519 return false;
520 }
521
522 clearDriverStatus();
523 sleepMs(5);
524
525 status = readDriverStatus();
526 if (checkUndervoltVccP(status)) {
527 onError(McFault::UnderVoltage7);
528 shutdown();
529 releaseBus();
530 return false;
531 }
532
533 // Drive High Voltage
534 setDriveEN(true); // driven = HV
535 sleepMs(10); // Give it a moment
536 status = readDriverStatus();
537 if (!checkDrivenEnabled(status)) {
538 onError(McFault::Driven);
539 shutdown();
540 releaseBus();
541 return false;
542 }
543
544 status = readDriverStatus();
545 if (checkUndervoltVccP(status)) {
546 onError(McFault::UnderVoltageAfter); // Likely DC-DC LS7 is dead!
547 shutdown();
548 releaseBus();
549 return false;
550 }
551
552 onError(McFault::None);
553
554 releaseBus();
555 return true;
556 }
557