Tesla Model 3 Rear Drive Unit Hacking
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Well , yeah that solved that lol. Only thing is now we are not saving the can mappings.
I'm going to need a hacksaw
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
So sendBuffer is 11 bytes long and any len over 8 is rejected. So len can at most be 8, hence writing to sendBuffer[i+2] is sendBuffer[7+2] is sendBuffer[9]. That looks fine.
Also sendBuffer[len+2] is sendBuffer[10], the last byte. So that is fine also. What about your data[] object?
EDIT: found the problem:
Besides the naughty indentation the "l" object is destroyed after the final "}" and the Stm32Can object reuses its memory. So any calls to LinBus will operate on Can memory. Instantiate LinBus l on main() scope and you're fine
Also sendBuffer[len+2] is sendBuffer[10], the last byte. So that is fine also. What about your data[] object?
EDIT: found the problem:
Code: Select all
if (hwRev == HW_TESLAM3)
{
DigIo::gate_ps_en.Set();
uDelay(200000);
TeslaModel3::Initialize();
LinBus l(UART4, 19200);
lin=&l;
linM3.SetLinInterface(lin);
}
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Oh! stopped reading on page 33 it seems 
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Do you want saveable CAN mappings on both CAN1 and CAN2?Jack Bauer wrote: ↑Mon Aug 25, 2025 2:38 pm Well , yeah that solved that lol. Only thing is now we are not saving the can mappings.
If only CAN1 it should work if you leave everything in vanilla state
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Just CAN1. Sadly no luck so far even on latest libopeninv. Same on the webinterface. I can map fine now and data is sent correctly but on power cycle all mapping is lost.
I'm going to need a hacksaw
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
CAN1_BLKNUM back at 2 and FLASH_PAGE_SIZE back at 1024?
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Yep as far as I can tell am back to "standard".
I'm going to need a hacksaw
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
So I am very very frustrated right now tbh. Five years of struggle is now coming down to this. I'm gonna walk away before I say or do something stupid. The core OI FOC runs that tesla motor so friggin sweet it sounds oem.....
I'm going to need a hacksaw
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
1. Start openocd so that is connects to target
2. In Makefile CPPFLAGS change -Os to -Og and rebuild
3. Start debugging in CodeBlocks with F8
4. Set break point in parm_save() and check whether flash_erase_page() is called (it shouldn't)
Also check which address the parameters are saved to (return value of GetFlashAddress()
Likewise in canmap.cpp
You can dump said address by telnetting into openocd
and then dumping
Of course you need to use the address obtained when debugging canmap.cpp minus 0x08000000
Attach it here
2. In Makefile CPPFLAGS change -Os to -Og and rebuild
3. Start debugging in CodeBlocks with F8
4. Set break point in parm_save() and check whether flash_erase_page() is called (it shouldn't)
Also check which address the parameters are saved to (return value of GetFlashAddress()
Likewise in canmap.cpp
You can dump said address by telnetting into openocd
Code: Select all
telnet localhost 4444Code: Select all
flash read_bank 0 can_and_parameters.bin 0x1F800Attach it here
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
Re: Tesla Model 3 Rear Drive Unit Hacking
I've been making progress of sorts with the CAN mapping issue (thanks for the STM32 board loan Damien).
The good news is that the M3_DU_experimental branch has working CAN mapping and param/map saving. This works via the ESP8266 serial web interface or openinverter_can_tool.
The bad news is I have not been able to replicate the failures as described above with any branch of the MG_DU or M3_DU code. The only fix the M3_DU_experimental branch required was to the LIN initialisation.
My M3_DU_experimental branch has binaries available which can be upgraded via CAN or serial. I have tested these thoroughly with opmode in "Off".
Some notes:
The good news is that the M3_DU_experimental branch has working CAN mapping and param/map saving. This works via the ESP8266 serial web interface or openinverter_can_tool.
The bad news is I have not been able to replicate the failures as described above with any branch of the MG_DU or M3_DU code. The only fix the M3_DU_experimental branch required was to the LIN initialisation.
My M3_DU_experimental branch has binaries available which can be upgraded via CAN or serial. I have tested these thoroughly with opmode in "Off".
Some notes:
- OpenInverter CAN Tool needs to be upgraded to v0.4.1 to have "can clear" and "can import" work properly. I released that version this morning.
- I spent much of last week chasing down a horrible LIN checksum bug that causes random data loss. The code has been tested with a LIN oil pump simulator that verifies correct operation of the integration. So much work to change a ">" to ">=".
- Please use the v1.3 CAN bootloader if you plan on using more than one OI board on the same CAN bus. You'll thank me later.
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Thank you Dave. I have the inverter installed in the drive unit in the V50 so I'll leave the software alone for now until I either need to remove the inverter or have another setup for bench tests. Right now I'm working on a mostly hardware modification to implement a Terminal 30 / Terminal 15 system in the M3 board and all future revs of OI boards. The basic premise being the inverter has a permanent 12v and a switched 12v feed just like most oems implement. Once "woken" by T15 the inverter will stay awake if T15 remains on but once T15 is removed will shut itself down in a controlled manner after a preset period if certain conditions are met. e.g. HV below xV , rpm=0 , direction = N etc.
Edit : this will be for the forthcoming V3 board which hopefully Iwill be able to send to a few beta testers as well as myself.
Edit : this will be for the forthcoming V3 board which hopefully Iwill be able to send to a few beta testers as well as myself.
I'm going to need a hacksaw
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
Re: Tesla Model 3 Rear Drive Unit Hacking
Fair enough.
So what should be the fail-safe behaviour of the inverter be then? Are we assuming the worst case of the switched 12V going off as we are driving along or that it remains under control of a VCU?
So what should be the fail-safe behaviour of the inverter be then? Are we assuming the worst case of the switched 12V going off as we are driving along or that it remains under control of a VCU?
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
At the moment I'm working on the hw side. There will need to be a sw component and a decision tree as of what to do but as a minimum I would envisage something like the following on loss of T15 :
1)Throttle to 0
2)Direction to N
3)Opmode to OFF as soon as RPM < some value.
Once Opmode is OFF we wait for either return of T15 or after HV < some value and some timer expires we shut off.
1)Throttle to 0
2)Direction to N
3)Opmode to OFF as soon as RPM < some value.
Once Opmode is OFF we wait for either return of T15 or after HV < some value and some timer expires we shut off.
I'm going to need a hacksaw
Re: Tesla Model 3 Rear Drive Unit Hacking
I guess we should do a little failure mode analysis. Are the contactors provided with KL30 ? Then we should switch all three by the OI board. That turned out to be a big bug for me with the SDU. Some events to be propably considered: HV fuse blown, 12V fuse contactor blown, emergency button pressed, CAN signal time out (BMS or Throttle) . Fail save behaviour would be different based on the failure cause. For everything except HV off throttle to zero and standard recuperation would be propably the best way to go.davefiddes wrote: ↑Mon Sep 01, 2025 9:27 pm Fair enough.
So what should be the fail-safe behaviour of the inverter be then? Are we assuming the worst case of the switched 12V going off as we are driving along or that it remains under control of a VCU?
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Got my hands on a front drive unit so will grab a capture of the gate driver config spi asap.
I'm going to need a hacksaw
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
Re: Tesla Model 3 Rear Drive Unit Hacking
So, I've run into a bit of a problem. Got my test inverter rigged so it's happy to actually start generating PWM and hooked up a Tesla resolver. The stock code works fine but as soon as I apply the hack changes to get a 8.8kHz encoder running things start to go a bit weird. CAN SDO in particular stops. Looking at the changes more closely and the reason is obvious...they speed up the interrupt frequency from 4.4kHz to 8.8kHz. This works but leaves insufficient time for the rest of the system to function and things start breaking.
I think that the fix is going to be to refactor the encoder to use TIM6 or TIM7TIM5 to generate the exciter rather than GPIO toggling in the main control interrupt handler. I can't remember all of the ins and outs of how the encoder works so some investigation will be required. It may have HW implications so I wouldn't rush to produce a Rev3 board.
I think that the fix is going to be to refactor the encoder to use TIM6 or TIM7TIM5 to generate the exciter rather than GPIO toggling in the main control interrupt handler. I can't remember all of the ins and outs of how the encoder works so some investigation will be required. It may have HW implications so I wouldn't rush to produce a Rev3 board.
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
Thanks Dave. A, about a month away from a V3 board order anyway so no panic. That does help explain some of the weird behaviour.
Edit : The other option here is to stay with 4.4KHz resolver and dial back the exciter amplitude until it stops clipping?
Edit : The other option here is to stay with 4.4KHz resolver and dial back the exciter amplitude until it stops clipping?
I'm going to need a hacksaw
- johu
- Site Admin
- Posts: 6969
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 455 times
- Been thanked: 1771 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
It even ups the IRQ frequency from 8.8 to 17.6 kHz, each PWM period consists of an upcounting and a downcounting part to achieve symmetrical PWM. With a repetition counter of 0 you get an interrupt for both parts.
There's a good bit of hardware logic involved in how things work now, not sure if there'd be enough resources left to optimize it. Basically TIM3_CH4 is used to trigger the ADC conversion at a certain time after the exciter edge. Perhaps something could be achieved by generating the exciter edge with another channel of TIM3. Never looked into it as it just worked fine at 4.4 kHz
There's a good bit of hardware logic involved in how things work now, not sure if there'd be enough resources left to optimize it. Basically TIM3_CH4 is used to trigger the ADC conversion at a certain time after the exciter edge. Perhaps something could be achieved by generating the exciter edge with another channel of TIM3. Never looked into it as it just worked fine at 4.4 kHz
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
Re: Tesla Model 3 Rear Drive Unit Hacking
My thought was to get a timer (TIM5 I think) to generate the exciter square wave at 8.8kHz. This would be aligned with the current PWM interrupt generating the required output with no software input. I think the frequency and alignment means that it would have to be a separate timer rather than a spare channel on an existing timer. The encoder ADC capture would then function as it currently does but on every other cycle. The resolverSampleDelay might need some re-measuring and tweaking to work in this mode but it shouldn't need much.
This is similar to the way I have the TMS320F28377D set up in my C2000 inverter code. Unfortunately I've not really thought about STM32 timers for a couple of years and all the detailed knowledge seems to have leaked out of my brain. Need a bit of time to re-learn how things work and try some experiments.
I did have a cheeky thought about asking if we could use a PLL to frequency double the exciter output to achieve the same result with HW only.
This is similar to the way I have the TMS320F28377D set up in my C2000 inverter code. Unfortunately I've not really thought about STM32 timers for a couple of years and all the detailed knowledge seems to have leaked out of my brain. Need a bit of time to re-learn how things work and try some experiments.
I did have a cheeky thought about asking if we could use a PLL to frequency double the exciter output to achieve the same result with HW only.
Re: Tesla Model 3 Rear Drive Unit Hacking
Here is what Grok said in reply to your last postn
It sounds like you're working on a resolver excitation setup for a motor control application on an STM32, syncing it with existing PWM generation (likely from an advanced timer like TIM1) and tweaking ADC sampling for an encoder or feedback loop. Your approach with a dedicated timer like TIM5 for the 8.8 kHz square wave makes sense—general-purpose timers like TIM5 can handle this independently, and separating it avoids conflicts with multichannel PWM configs on shared timers. Aligning it hardware-wise without CPU intervention is key for determinism, especially if you're drawing parallels to the ePWM sync chain in the TMS320F28377D.
I'll outline a refreshed walkthrough on STM32 timer setup based on standard practices, focusing on CubeMX/HAL for quick prototyping (since you mentioned re-learning). This assumes a common STM32 family like F4 or G4 with a typical APB1 clock (e.g., 84-100 MHz), but adjust for your specific MCU's clock tree—use SystemClock_Config() or CubeMX to verify. If you share the exact STM32 model, I can refine this further.
### Generating the 8.8 kHz Square Wave with TIM5
Use TIM5 in PWM mode (mode 1 or 2) with 50% duty cycle for a clean square wave. This is more reliable than pure toggle mode for frequency control, as it leverages the auto-reload for periodicity.
1. **Clock and Prescaler/Period Calculation**:
- Timer frequency formula: \( f = \frac{\text{TIM_CLK}}{(\text{PSC} + 1) \times (\text{ARR} + 1)} \)
- For 8.8 kHz output, aim for an update event at 8.8 kHz. Assuming TIM_CLK = 84 MHz (common APB1):
- Set PSC = 0 (no prescaling for precision).
- ARR = (84e6 / 8.8e3) - 1 ≈ 9545 (fine-tune for exact division; use higher PSC if ARR overflows 16-bit max of 65535).
- For duty cycle 50%, set CCRx = ARR / 2 ≈ 4772.
- If your clock is different (e.g., 100 MHz), recalculate: ARR = (100e6 / 8.8e3) - 1 ≈ 11363, CCR = 5681.
2. **CubeMX Configuration**:
- Enable TIM5, set Clock Source to Internal Clock.
- Channel 1 (or whichever pin): PWM Generation CH1.
- Mode: PWM Mode 1 (active high until CCR match).
- Set PSC, ARR, and Pulse (CCR) as above.
- Output: Enable on a GPIO pin (e.g., PA0 for TIM5_CH1).
3. **HAL Code Example** (in main.c or a timer init function):
```c
TIM_HandleTypeDef htim5;
TIM_OC_InitTypeDef sConfigOC = {0};
void MX_TIM5_Init(void) {
htim5.Instance = TIM5;
htim5.Init.Prescaler = 0; // Adjust based on your calc
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 9545; // ARR for ~8.8 kHz at 84 MHz
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim5);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 4772; // 50% duty
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1);
// GPIO init for output pin (e.g., via CubeMX)
__HAL_TIM_ENABLE(&htim5);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1);
}
```
This will output the square wave immediately on start. For toggle mode alternative (simpler but less flexible): Set OCMode to TIM_OCMODE_TOGGLE, and the output flips on every CCR match—frequency is then TIM_CLK / (PSC+1) / (2 * (CCR+1)).<grok:render card_id="cedfde" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">5</argument>
</grok:render><grok:render card_id="8db12e" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">7</argument>
</grok:render><grok:render card_id="8b81a6" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">10</argument>
</grok:render>
### Aligning with PWM Interrupt (Timer Synchronization)
To keep the excitation wave phase-locked with your PWM (no software polling), use master-slave mode. Assume your PWM is on TIM1 (common for motor control); make it the master to trigger/reset TIM5 (slave). This mirrors the sync chain in C2000 ePWM modules.
1. **CubeMX Setup**:
- For Master (PWM timer, e.g., TIM1): Combined Channels > PWM Generation, and Master/Slave Mode > Enable. Trigger Event > Update Event (TRGO on counter overflow).
- For Slave (TIM5): Master/Slave Mode > Enable. Slave Mode > Reset Mode (resets slave counter on master trigger) or Gated Mode (slave runs only during master active period). Trigger Source > ITR0 (internal connection from TIM1; check RM for your MCU's ITR mappings).
- This ensures TIM5 restarts/aligns with TIM1's PWM cycle.
2. **HAL Code Snippet** (add to init):
```c
// Master (TIM1 PWM setup assumed already done)
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // Or TIM_TRGO_OCx for specific channel
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
// Slave (TIM5)
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; // Align by resetting on master trigger
sSlaveConfig.InputTrigger = TIM_TS_ITR0; // From TIM1
HAL_TIM_SlaveConfigSynchro(&htim5, &sSlaveConfig);
```
Start both timers; the slave will sync automatically. If your PWM frequency differs from 8.8 kHz, adjust slave PSC/ARR to divide/multiply accordingly. For phase shift, preload a counter value on sync.<grok:render card_id="9b60f4" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">15</argument>
</grok:render><grok:render card_id="5616b5" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">16</argument>
</grok:render><grok:render card_id="63c457" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">17</argument>
</grok:render>
### ADC Capture on Every Other Cycle
Assuming your current ADC is triggered by a timer event (e.g., TIMx_TRGO or CC event), modify it to half-rate for "every other cycle." Hardware options avoid software tweaks:
1. **Direct Timer Trigger Adjustment**:
- If ADC is triggered by the excitation timer's update event, set the trigger to a compare channel (CC) at ARR (full cycle) instead of update (which is every cycle). But for every other: Chain another timer or use a divider.
- Better: Use the master PWM timer's TRGO at half rate (e.g., if PWM is 17.6 kHz, divide by 2), or configure ADC external trigger with a prescaler if available (some ADCs have EXTSEL with dividers).
2. **CubeMX/HAL for Timer-Triggered ADC**:
- ADC Mode: Regular channel, External Trigger Conversion > Rising Edge, External Trigger Source > Timer x Trigger Out event (e.g., from TIM5_TRGO).
- For every other: Set the triggering timer's ARR to double (for half freq), or use a separate basic timer (e.g., TIM6) slaved to TIM5 with PSC=1 (div by 2).
```c
// Example: TIM5 TRGO triggers ADC1 every cycle; for every other, slave TIM6 to TIM5 and use TIM6_TRGO
ADC_InitTypeDef ADC_InitStruct = {0};
ADC_InitStruct.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T5_TRGO; // Or T6_TRGO for divided
HAL_ADC_Init(&hadc1);
HAL_ADC_Start(&hadc1); // Or with IT/DMA for capture
```
Re-measure resolverSampleDelay post-setup—scope the excitation vs. ADC EOC flag or a GPIO toggle in ADC IRQ. If using DMA for burst captures, ensure buffer alignment with the reduced rate.<grok:render card_id="c384de" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">27</argument>
</grok:render><grok:render card_id="d597be" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">28</argument>
</grok:render><grok:render card_id="daf66e" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">30</argument>
</grok:render>
This should get you experimenting quickly—start with CubeMX for visual config, then tweak registers directly if needed (e.g., TIM5->ARR). If the square wave is approximating a resolver exciter (usually sine), consider DMA to DAC for better accuracy later. Let me know if you hit snags or need code for a specific STM32!
It sounds like you're working on a resolver excitation setup for a motor control application on an STM32, syncing it with existing PWM generation (likely from an advanced timer like TIM1) and tweaking ADC sampling for an encoder or feedback loop. Your approach with a dedicated timer like TIM5 for the 8.8 kHz square wave makes sense—general-purpose timers like TIM5 can handle this independently, and separating it avoids conflicts with multichannel PWM configs on shared timers. Aligning it hardware-wise without CPU intervention is key for determinism, especially if you're drawing parallels to the ePWM sync chain in the TMS320F28377D.
I'll outline a refreshed walkthrough on STM32 timer setup based on standard practices, focusing on CubeMX/HAL for quick prototyping (since you mentioned re-learning). This assumes a common STM32 family like F4 or G4 with a typical APB1 clock (e.g., 84-100 MHz), but adjust for your specific MCU's clock tree—use SystemClock_Config() or CubeMX to verify. If you share the exact STM32 model, I can refine this further.
### Generating the 8.8 kHz Square Wave with TIM5
Use TIM5 in PWM mode (mode 1 or 2) with 50% duty cycle for a clean square wave. This is more reliable than pure toggle mode for frequency control, as it leverages the auto-reload for periodicity.
1. **Clock and Prescaler/Period Calculation**:
- Timer frequency formula: \( f = \frac{\text{TIM_CLK}}{(\text{PSC} + 1) \times (\text{ARR} + 1)} \)
- For 8.8 kHz output, aim for an update event at 8.8 kHz. Assuming TIM_CLK = 84 MHz (common APB1):
- Set PSC = 0 (no prescaling for precision).
- ARR = (84e6 / 8.8e3) - 1 ≈ 9545 (fine-tune for exact division; use higher PSC if ARR overflows 16-bit max of 65535).
- For duty cycle 50%, set CCRx = ARR / 2 ≈ 4772.
- If your clock is different (e.g., 100 MHz), recalculate: ARR = (100e6 / 8.8e3) - 1 ≈ 11363, CCR = 5681.
2. **CubeMX Configuration**:
- Enable TIM5, set Clock Source to Internal Clock.
- Channel 1 (or whichever pin): PWM Generation CH1.
- Mode: PWM Mode 1 (active high until CCR match).
- Set PSC, ARR, and Pulse (CCR) as above.
- Output: Enable on a GPIO pin (e.g., PA0 for TIM5_CH1).
3. **HAL Code Example** (in main.c or a timer init function):
```c
TIM_HandleTypeDef htim5;
TIM_OC_InitTypeDef sConfigOC = {0};
void MX_TIM5_Init(void) {
htim5.Instance = TIM5;
htim5.Init.Prescaler = 0; // Adjust based on your calc
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 9545; // ARR for ~8.8 kHz at 84 MHz
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim5);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 4772; // 50% duty
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1);
// GPIO init for output pin (e.g., via CubeMX)
__HAL_TIM_ENABLE(&htim5);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1);
}
```
This will output the square wave immediately on start. For toggle mode alternative (simpler but less flexible): Set OCMode to TIM_OCMODE_TOGGLE, and the output flips on every CCR match—frequency is then TIM_CLK / (PSC+1) / (2 * (CCR+1)).<grok:render card_id="cedfde" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">5</argument>
</grok:render><grok:render card_id="8db12e" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">7</argument>
</grok:render><grok:render card_id="8b81a6" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">10</argument>
</grok:render>
### Aligning with PWM Interrupt (Timer Synchronization)
To keep the excitation wave phase-locked with your PWM (no software polling), use master-slave mode. Assume your PWM is on TIM1 (common for motor control); make it the master to trigger/reset TIM5 (slave). This mirrors the sync chain in C2000 ePWM modules.
1. **CubeMX Setup**:
- For Master (PWM timer, e.g., TIM1): Combined Channels > PWM Generation, and Master/Slave Mode > Enable. Trigger Event > Update Event (TRGO on counter overflow).
- For Slave (TIM5): Master/Slave Mode > Enable. Slave Mode > Reset Mode (resets slave counter on master trigger) or Gated Mode (slave runs only during master active period). Trigger Source > ITR0 (internal connection from TIM1; check RM for your MCU's ITR mappings).
- This ensures TIM5 restarts/aligns with TIM1's PWM cycle.
2. **HAL Code Snippet** (add to init):
```c
// Master (TIM1 PWM setup assumed already done)
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // Or TIM_TRGO_OCx for specific channel
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
// Slave (TIM5)
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; // Align by resetting on master trigger
sSlaveConfig.InputTrigger = TIM_TS_ITR0; // From TIM1
HAL_TIM_SlaveConfigSynchro(&htim5, &sSlaveConfig);
```
Start both timers; the slave will sync automatically. If your PWM frequency differs from 8.8 kHz, adjust slave PSC/ARR to divide/multiply accordingly. For phase shift, preload a counter value on sync.<grok:render card_id="9b60f4" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">15</argument>
</grok:render><grok:render card_id="5616b5" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">16</argument>
</grok:render><grok:render card_id="63c457" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">17</argument>
</grok:render>
### ADC Capture on Every Other Cycle
Assuming your current ADC is triggered by a timer event (e.g., TIMx_TRGO or CC event), modify it to half-rate for "every other cycle." Hardware options avoid software tweaks:
1. **Direct Timer Trigger Adjustment**:
- If ADC is triggered by the excitation timer's update event, set the trigger to a compare channel (CC) at ARR (full cycle) instead of update (which is every cycle). But for every other: Chain another timer or use a divider.
- Better: Use the master PWM timer's TRGO at half rate (e.g., if PWM is 17.6 kHz, divide by 2), or configure ADC external trigger with a prescaler if available (some ADCs have EXTSEL with dividers).
2. **CubeMX/HAL for Timer-Triggered ADC**:
- ADC Mode: Regular channel, External Trigger Conversion > Rising Edge, External Trigger Source > Timer x Trigger Out event (e.g., from TIM5_TRGO).
- For every other: Set the triggering timer's ARR to double (for half freq), or use a separate basic timer (e.g., TIM6) slaved to TIM5 with PSC=1 (div by 2).
```c
// Example: TIM5 TRGO triggers ADC1 every cycle; for every other, slave TIM6 to TIM5 and use TIM6_TRGO
ADC_InitTypeDef ADC_InitStruct = {0};
ADC_InitStruct.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T5_TRGO; // Or T6_TRGO for divided
HAL_ADC_Init(&hadc1);
HAL_ADC_Start(&hadc1); // Or with IT/DMA for capture
```
Re-measure resolverSampleDelay post-setup—scope the excitation vs. ADC EOC flag or a GPIO toggle in ADC IRQ. If using DMA for burst captures, ensure buffer alignment with the reduced rate.<grok:render card_id="c384de" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">27</argument>
</grok:render><grok:render card_id="d597be" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">28</argument>
</grok:render><grok:render card_id="daf66e" card_type="citation_card" type="render_inline_citation">
<argument name="citation_id">30</argument>
</grok:render>
This should get you experimenting quickly—start with CubeMX for visual config, then tweak registers directly if needed (e.g., TIM5->ARR). If the square wave is approximating a resolver exciter (usually sine), consider DMA to DAC for better accuracy later. Let me know if you hit snags or need code for a specific STM32!
-
davefiddes
- Posts: 375
- Joined: Mon Jan 18, 2021 12:39 pm
- Location: Edinburgh, Scotland, UK
- Has thanked: 149 times
- Been thanked: 195 times
Re: Tesla Model 3 Rear Drive Unit Hacking
Some good progress I think. I've managed to hook up TIM8 to output a 8.8kHz square wave on PC6. This is synchronised to the main PWM TIM1 which leaves the existing resolver sampling intact.
To set the scene this is what the stock stm32-sine firmware looks like: The yellow trace is R106 at the input to the exciter amp. The cyan trace is PD2/EXC_OUT (this is the same signal). The green trace is the exciter winding on the resolver. The purple trace is the cosine phase on the resolver.
You can see the time difference between the rising edge of the exciter PWM and the peak of the exciter output is 153 uSec (dotted yellow line cursor).
Looking at the modified firmware: The yellow trace is PC6 attached to TIM8_CH1. The cyan trace is PD2/EXC_OUT. The green trace is the exciter winding on the resolver. The purple trace is the cosine phase on the resolver.
You can see the exciter is running at 8.8kHz (yellow/green/purple) but the main loop (cyan) is running at 4.4kHz. You'll have to trust me but the PWM is running at 8.8kHz in both cases.
The amplitude of the exciter and resolver phases is substantially lower at 8.8kHz (the Y scale is different between the two traces). I think this is what we desire.
The resolver sample point is in a trough of the exciter rather than the peak (see cursor). This will mess up the syncofs but other than that should still deliver reliable angles I think. I don't understand why a higher frequency results in an exciter output 180 degrees out of phase.
Thus far I've just used an uncalibrated steel nut as my resolver rotor. I think the next step is to get the resolver mounted in my motor and try to get a plot of the "angle" as I rotate the motor. Thoughts?
To set the scene this is what the stock stm32-sine firmware looks like: The yellow trace is R106 at the input to the exciter amp. The cyan trace is PD2/EXC_OUT (this is the same signal). The green trace is the exciter winding on the resolver. The purple trace is the cosine phase on the resolver.
You can see the time difference between the rising edge of the exciter PWM and the peak of the exciter output is 153 uSec (dotted yellow line cursor).
Looking at the modified firmware: The yellow trace is PC6 attached to TIM8_CH1. The cyan trace is PD2/EXC_OUT. The green trace is the exciter winding on the resolver. The purple trace is the cosine phase on the resolver.
You can see the exciter is running at 8.8kHz (yellow/green/purple) but the main loop (cyan) is running at 4.4kHz. You'll have to trust me but the PWM is running at 8.8kHz in both cases.
The amplitude of the exciter and resolver phases is substantially lower at 8.8kHz (the Y scale is different between the two traces). I think this is what we desire.
The resolver sample point is in a trough of the exciter rather than the peak (see cursor). This will mess up the syncofs but other than that should still deliver reliable angles I think. I don't understand why a higher frequency results in an exciter output 180 degrees out of phase.
Thus far I've just used an uncalibrated steel nut as my resolver rotor. I think the next step is to get the resolver mounted in my motor and try to get a plot of the "angle" as I rotate the motor. Thoughts?
- Jack Bauer
- Posts: 3831
- Joined: Wed Dec 12, 2018 5:24 pm
- Location: Ireland
- Has thanked: 75 times
- Been thanked: 696 times
- Contact:
Re: Tesla Model 3 Rear Drive Unit Hacking
That looks good Dave. Nice work. Another of my many screups on the V1 board was not updating the values of the resistors in the 3 stage filter to the exciter amp hence the low amplitude. The values are much too hgh for 8.8KHz. Correct values for 8.8khz attached.
I'm going to need a hacksaw
- manny
- Posts: 165
- Joined: Sun Jan 23, 2022 4:15 pm
- Location: Netherlands
- Has thanked: 38 times
- Been thanked: 125 times
Re: Tesla Model 3 Rear Drive Unit Hacking
The filter to make a sine wave out of a square wave has a phase delay.
At 4400 hz the phase delay is -164.5° so that's (1/4400)/360*164.5 = 103.85us
At 8800 hz the phase delay is -206.3° so that's (1/8800)/360*206.3 = 65.11us
this is from the center of the squarewave, but on your scope the measurement is form the leading edge. To compensate we need to add a 1/4 wavelength.
At 4400 hz this is 56.8 us + 103.85us = 160.65us
At 8800 hz this is 28.4 us + 65.11us = 93.51us
so the sample point at 8800 hz should be 93.5 us after the rising edge.
with changed filter this will be: 28.4 + 55 = 83.4 us
This data is from simulation so assuming ideal components.
At 4400 hz the phase delay is -164.5° so that's (1/4400)/360*164.5 = 103.85us
At 8800 hz the phase delay is -206.3° so that's (1/8800)/360*206.3 = 65.11us
this is from the center of the squarewave, but on your scope the measurement is form the leading edge. To compensate we need to add a 1/4 wavelength.
At 4400 hz this is 56.8 us + 103.85us = 160.65us
At 8800 hz this is 28.4 us + 65.11us = 93.51us
so the sample point at 8800 hz should be 93.5 us after the rising edge.
with changed filter this will be: 28.4 + 55 = 83.4 us
This data is from simulation so assuming ideal components.
[DRIVING] Citroen Saxo electrique
- Volvo ERAD motor, XC90 inverter/DCDC (custom OI board)
- PSA battery (50kWh)
- Foccci and MG ZS charger 6.6kW
- Zombie VCU