In many old-fashioned automotive motor control systems, PWM command communication is still preferred over LIN or CAN. Even for in-lab application debugging, it is very convenient to use PWM command communication to control the motor placed in a test-bench or in an acoustic or thermal chamber, especially when the BDM debugger or the SCI/RS232 communication is not available.
PWM control is usually specified on the OEM level and it is not provided as a public standard. Nevertheless, adjustable software driver can be created to help the PWM command signal to be detected. The following code has been developed for S12ZVM, but it can be easily adopted to fit in whatever device with a timer input capture feature.
Hardware specification may be different for many cases and the implementation is the matter of proper design case-by-case and may be a very easy task for a skilled designer. However, software design can be abstracted and reused for different hardware implementation. In order to help you use the PWM control signal, the following description is provided.
The example provided is targeting NXP MagniV S12ZVML devices, but may be used along with any device accordingly. The S12ZVML MCU contains LIN physical layer which will be used as PWM input in our case.
PWM Signal Specification
Since there is no public standard specification on PWM signal, let's target a general case with adjustable parameters. Since the PWM control signal is being replaced by LIN communication, the specification may be based on 5.5V to 18V voltage range with nominal 12V level. PWM frequency varies from 10 Hz to 1 kHz (or more), but it really depends on the requirements of the application, compatibility, etc. Duty-cycle range of the PWM signal might be from 15% to 90% from stand-by to full speed. Hysteresis at low duty-cycle range (between 10 and 15%) prevents the system from periodic on/off switching at the lowest duty-cycle. Duty-cycles outside of this range may be identified as not valid and appropriate action may be taken (e.g. full speed command to an engine cooling fan).
The signal definition should consider some specific values to help with the signal reading. Following values are defined:
|noSignalLevel||No signal detected below this duty cycle|
|noSignalOutput||Output on "no signal" detected|
|lowSignalOutput||Output on "low signal" detected|
|hystLowSignalOff||Low level of the hysteresis. Smaller duty-cycle means "low signal" state, higher duty-cycle enters the hysteresis range (min speed or stand-by)|
|hystLowSignalOn||High level of the hysteresis. Smaller duty-cycle means "hysteresis area: min speed or stand-by", higher duty-cycle means "run" in the linear signal range, with the speed given by the duty-cycle.|
|linearSignalOutputMin||Low level of the linear mode|
|linearSignalLevelMax||High level of the linear mode|
Considering normal operation in stand-by mode, the duty-cycle is between noSignalLevel and hystLowSignalOff. In order to add some distortion-proof feature, the duty-cycle can go up to the hystLowSignalOn and the stand-by mode would be still detected. In order to engage the "run" state, the duty-cycle has to exceed the hystLowSignalOn value and the linear area is reached. Corresponding output is calculated to meet the linearSignalOutputMin at the hystLowSignalOn value and linearSignalOutputMax at the linearSignalLevelMax. System keeps the output at linearSignalOutputMax for duty-cycles higher than linearSignalLevelMax. When slowing down, reaching the hystLowSignalOn sets the output to linearSignalOutputMin and keeps this output until the hystLowSignalOff is reached. This way, the hysteresis feature is provided in order to prevent motors from randomly switch on and off in case the PWM signal is noisy. Lowering the duty-cycle below the hystLowSignalOff, zero output is set, which represents the stand- by mode. Duty-cycles below the noSignalLevel are treated as not valid and noSignalOutput is engaged (which can be set to zero or to max, according to the application requirements).
Requirements on Timer
In order to correctly detect PWM signal frequency and duty-cycle, hardware timer with input capture feature and rising/falling edges detection shall be used. Timing and chaining of multiple timer channels may be needed to increase the frequency range or duty-cycle resolution. Following example will be simplified to use just one 16-bit timer channel (timer module TIM16B4CV3 used in S12ZVML128). The input of the timer shall be routed to an input pin, on which the PWM signal will be sensed. Integrated LIN physical layer module of S12ZVML128 device can be used for signal conditioning since the signal voltage range is up to 18V. In the example, LINPhy module has to be enabled and the LPRXD0 signal is routed to the timer T0 input capture channel 3 by setting "T0IC3RR1-0" of the MODRR2 register to 0b01. Input capture and timer overflow interrupts are used to detect correct or faulty signal.
Requirements on Application Software
PWM signal detection is called within the Timer Input Capture interrupt. This way, the duty-cycle is calculated immediately. Signal lost event is detected within the Timer overflow interrupt. It is recommended that the detected duty-cycle is filtered by a low-pass filter (moving average or IIR), which is usually called within some real-time interrupt routine, such as PWM reload or ADC end-of-scan interrupt. Reading of the output value can be done anytime within the code, e.g. within a speed loop to read the demanded speed command.
Frequency and Duty-cycle Detection
Frequency of PWM signal is given by the time between two consecutive rising edges or two falling edges. The duty-cycle is a ratio between the time of high-level state over the entire period, as indicated below. The key is to capture the time of the rising edge tR1, change the detection mode to the falling edge to capture the time of the falling edge tF1, again change the detection mode to rising edge, etc. This detection can be easily done within the input capture timer interrupt. The time values captured are then used to calculate "High" and "Period" values. Frequency and duty-cycle is calculated based on the following equations.
The timer settings should follow requirements on frequency range and duty-cycle resolution. It is recommended that the timer settings allow detecting frequencies slightly beyond the required frequency range.
Let's consider an example of:
|Limit||Specified frequency limits||Detected frequency||Detected period|
|Minimal PWM frequency||20 Hz||19.5 Hz||0.051282 s|
|Maximal PWM frequency||1000 Hz||1010 Hz||0.00099 s|
Based on these limits, Timer settings can be calculated, considering the MCU clock settings and prescalers:
For example, if the fbus = 50 MHz and the TimerPrescaler = 50, MinPeriodTicks for MaxDetectedFreq = 1010 Hz equals 495 ticks of the timer. The duty-cycle resolution at 1010 Hz is then 1/495, which is 0.202%. MaxPeriodTicks is key information for the timer settings since it needs to fit in the 16bit range with no overflow. In this case, the number of ticks is 25641 for 19.5Hz, which is suitable for 16-bit range with no issues. If the detected period (number of timer ticks) is outside of this range, the algorithm should go to the noSignalLevel state with defined noSignalOutput.
Finally, if the PWM signal is lost or corrupted, timer overflow event occurs and corresponding interrupt is called. Within the interrupt routine, the entire algorithm should be reset to wait for another rising edge, while turned in the noSignalLevel state with defined noSignalOutput.
Selecting Detected State
Based on the PWM signal condition, frequency and duty-cycle detected, the algorithm state is selected. Following states are defined to help users with state identification:
|PWM_NoSignalDetect||PWM signal is not detected or the PWM period is outside of the range||noSignalOutput, usually zero or full command (according to the application requirements)|
|PWM_LowSignalDetect||PWM signal is detected, the duty-cycle is within the noSignalLevel and hystLowSignalOn or hystLowSignalOff (depends on the hysteresis)||lowSignalOutput, usually zero/stand-by|
|PWM_LinearCtrlDetect||PWM signal is detected, the duty-cycle is within the hystLowSignalOn and linearSignalLevelMax|
output = (dutyCycle * linearSignalSlope + linearSignalOffset) << linearSignalNshift,
from linearSignalOutputMin to linearSignalOutputMax
|PWM_HighSignalDetect||PWM signal is detected, the duty-cycle is higher than linearSignalLevelMax||linearSignalOutputMax|
Linear Output Calculation
Linear output calculation is an easy way of interpolating two endpoints by a linear equation. In order to calculate the coefficients (slope and offset) and to improve the fixed point range, shifting is implemented as well.
The algorithm is designed as a set of static functions with a structure of settings as a parameter to all the interfaces. This way, the same functionality can be used in multiple instances.
/* PWM Control data structure */
pwmControlStatus_t flags; //Control status bits
pwmControlConfig_t config; //Configuration
unsigned int period; //Modulation period
unsigned int dutyCycle; //dutyCycle
unsigned int hystDutyCycle; //previous hysteresis of duty cycle
unsigned int overflowCntr; //timer overflow counter
tFrac16 outputValue; //Output value
unsigned int ControlInputClass; //Classification of the input signal
The input-output characteristics are defined by a set of parameters as discussed earlier using the following structure:
/* No signal definition */
tFrac16 noSignalLevel; //Level of the duty cycle for NoSignal detection
tFrac16 noSignalOutput; //Output when NoSignal is detected
/* Low signal definition */
tFrac16 lowSignalOutput; //Output when LowSignal is detected
/* Linear signal definition */
tFrac16 hystLowSignalOff; //LowSignal-to-NoSignal hysteresis level
tFrac16 hystLowSignalOn; //NoSignal-to-LowSignal hysteresis level
tFrac16 linearSignalLevelMax; //Level of the duty cycle for LinearSignal saturation
tFrac16 linearSignalOutputMin; //Output at hystLowSignalOn
tFrac16 linearSignalOutputMax; //Output at linearSignalLevelMax
tFrac16 linearSignalSlope; // ((linearSignalOutputMax - linearSignalOutputMin)/(hystLowSignalOn - linearSignalLevelMax)))*2^(-linearSignalNshift)
tFrac16 linearSignalOffset; // (linearSignalOutputMax - (linearSignalSlope * linearSignalLevelMax))*2^(-linearSignalNshift)
tU16 linearSignalNshift; // scaling shift of the linear curve
/* timing settings */
unsigned int minPeriod; //minimal period of the signal to be detected, shorter period leads to PWM_NoSignalDetect
unsigned int maxPeriod; //maximal period of the signal to be detected, longer period leads to PWM_NoSignalDetect
More details on the structures and values are described in the header file in attachment.
There is a set of exported functions to be integrated into the application.
/* Exported function headers */
extern void PWMControlInit(pwmControl_t * data); //Clears all the internal accumulators and prepars the algorithm for usage
extern unsigned int PWMControlUpdate(unsigned int pin, pwmControl_t * data); //To be called within the timer input capture interrupt, updates the internal values and calculates the PWM frequency and duty-cycle
extern tFrac16 PWMControlGetOutputValue(pwmControl_t * data); //To be called where necessary by the application to get the output value
extern void PWMControlTimerOverflow(pwmControl_t * data); //To be called within the timer overflow interrupt to indicate that the signal is lost
The PWM control source and header files should be included in the project to be compiled. The project uses NXP Automotive Math and Motor Control Library Set (AMMCLib version v1.1.17 or higher), which needs to be added to the project as well. For more information, please visit https://www.nxp.com/support/developer-resources/run-time-software/automotive-software-and-tools/automotive-math-and-motor-control-library-set:AUTOMATH_MCL?lang=en&lang_cd=en&
The next step is to include the header file in the main application source code:
In order to provide necessary settings and to handle the output, the following global variables are recommended:
// PWM Control data
pwmControl_t pwmControlData; //PWM input control data structure
static tBool pwmControlEnabled = false; // Off by default
Device initialization should include clock settings, timer, and input pin routing settings accordingly. For S12ZVML device, the configuration of TIM, PIM and LIN modules is following:
Within the initialization part of the application, the following code should be placed to initialize the PWM control. Please consider the values as an example only.
// PWM control initial settings
pwmControlData.config.minPeriod = 495; //Period of the max frequency
pwmControlData.config.maxPeriod = 25641; //Period of the min frequency
pwmControlData.config.noSignalLevel = FRAC16(0.05); //No signal detected below this duty cycle
pwmControlData.config.noSignalOutput = FRAC16(0); //Output on ""no signal"" detected
pwmControlData.config.lowSignalOutput = FRAC16(0); //Output on ""low signal"" detected
pwmControlData.config.hystLowSignalOff = FRAC16(0.1); //Low level of the hysteresis. Smaller duty cycle means ""low signal"", higher duty cycle means ""switch on""
pwmControlData.config.hystLowSignalOn = FRAC16(0.15); //High level of the hysteresis. Smaller duty cycle means ""min speed or off"", higher duty cycle means ""switch on and linear""
pwmControlData.config.linearSignalLevelMax = FRAC16(0.85); //High level of the linear mode
pwmControlData.config.linearSignalOutputMin = FRAC16(0.116666666666667); //Output within the hysteresis mode
pwmControlData.config.linearSignalOutputMax = FRAC16(0.833333333333333); //Output at the linearSignalLevelMax
pwmControlData.config.linearSignalSlope = FRAC16(0.511904761904762); //Linear signal slope = (linearSignalOutputMax - linearSignalOutputMin) / (linearSignalLevelMax - hystLowSignalOn) * 2^(-linearSignalNshift)
pwmControlData.config.linearSignalOffset = FRAC16(-0.018452380952381); //Linear signal offset = (linearSignalOutputMin - (linearSignalOutputMax - linearSignalOutputMin) / (linearSignalLevelMax - hystLowSignalOn) * hystLowSignalOn) * 2^(-linearSignalNshift)
pwmControlData.config.linearSignalNshift = 1; //Linear signal shift
pwmControlData.config.f16pwmInputMA.u16NSamples = 5; //Moving average filter settings 2^n samples
The configuration can be generated using an Excel sheet in the attachment. Content of the cell "D34" can be copied-pasted in the code. Name of the structure can be changed in "H1" cell. Orange-colored cells are customizable, while grey-colored cells use formulas to calculate output.
Timer input capture interrupt routine should call the PWM control "Update" routine and switch the input capture mode between the rising edge and falling edge detection. In the case of S12ZVML device, the timer interrupt example follows:
INTERRUPT void TIM0chan3_ISR(void)
// Read PWM input control pin and update status
// Read the PWM Control output
pwmControlData.outputValue = PWMControlGetOutputValue(&pwmControlData);
// Toggle edge detection
TIM0TCTL4_EDG3A = 1;
TIM0TCTL4_EDG3B = 0;
TIM0TCTL4_EDG3A = 0;
TIM0TCTL4_EDG3B = 1;
// Clear interrupt flag
TIM0TFLG1 = TIM0TFLG1_C3F_MASK; //TIM0TFLG1_C0F_MASK | TIM0TFLG1_C1F_MASK | TIM0TFLG1_C2F_MASK | TIM0TFLG1_C3F_MASK;
Timer overflow interrupt routine shall implement PWM control "Overflow" handler as follows (again, for S12ZVML device). Output value updater should be called as well to update the output value.
INTERRUPT void TIM0overflow_ISR(void)
// Detect PWM input control period overflow (ultra low frequencies
pwmControlData.outputValue = PWMControlGetOutputValue(&pwmControlData);
// Clear interrupt flag
TIM0TFLG2 = TIM0TFLG2_TOF_MASK;
Finally, within the application task, the PWM control data can be accessed and used in the application. For example, based on the ControlInputClass, the input signal processing may be enabled or disabled and the output value can be used as the required speed value of an engine cooling fan.
// PWM Control
// If no signal is detected, enable manual switch (using FreeMASTER)
if(pwmControlData.ControlInputClass == PWM_NoSignalDetect)
if(pwmControlEnabled == true)
cntrState.usrControl.switchAppOnOff = 0;
pwmControlEnabled = false;
pwmControlEnabled = true;
// If PWM input control enabled, update the demanded speed
//If the linear or high signal is detected, switch the app on
if(pwmControlData.ControlInputClass >= PWM_LinearCtrlDetect)
cntrState.usrControl.switchAppOnOff = 1;
//Store the demanded speed
drvFOC.pospeControl.wRotElReq = pwmControlData.outputValue;
//else switch off the app...
cntrState.usrControl.switchAppOnOff = 0;
//...and if a fault is indicated, clear the fault
if(cntrState.state == fault)
cntrState.usrControl.switchFaultClear = true;
Example code is provided for S12ZVML-MINIKIT. The code is a modified devkit SW, which is available here (and related AN5327 here). The FreeMASTER project file is included with added PWM Control subblock. If there is no PWM signal detected, the application can be controlled via FreeMASTER on/off button. If the PWM signal is detected (even with a duty-cycle below the minimal value), the application listens and reacts as defined by the signal definition.
PWM control is still being used as one of the control signals to control auxiliary electric drives in automotive. Even if being replaced by LIN or CAN communication, there are still facilities which can benefit from software drivers enabling such a feature.
In this document, a software driver is provided to be integrated within the application. The example is provided for S12ZVML devices, however, it can be easily adopted to any device which meets the hardware requirements necessary for such PWM control signal detection.