AN4299 · Application noteHow to improve conducted noise robustness for touch sensing applications on STM32 MCUs
Download .pdfOpen in the full document

Effect of V DD level and V IH range

Effect of VDD level and VIH range

As explain in the previous section, the absolute value of any measurement is impacted by VDD and VIH. The effect increases when VDD increases, and decreases when VDD decreases.

The figure below describes these effects on measures.

Figure 24. VDD and VIH effect on a measure

Frequency hopping

Another way to increase the noise immunity is to use the frequency-hopping algorithm. The charge-transfer frequency at the end of all groups acquisition can be changed by updating CTPH, CTPL, and DIV TSC registers fields. This spreads the charge-transfer frequency. Two, three or more frequency values can be used.

An example of this algorithm is given below. It can be used to update CTPH and CTPL at the end of each complete sensors acquisition. Cautions must be taken as the acquisition waveform must respect the criteria described previously:

  • At TSC init stage, in MX_TSC_Init:
    • Define for this algorithm
      // TSC Frequency Hopping
      #define FREQ_VALUES     3
      typedef struct{
          int div;
          int ctph;
          int ctpl;
      }tsc_setup_t;
      tsc_setup_t FreqHopp[FREQ_VALUES]={0}; 
      
      TSC_HandleTypeDef htsc;
      TSC_HandleTypeDef htsc_freqhopp;
      
      #define CTPH_TO_INT(a)    ((uint32_t)( (a) >> 28) + 1)   // From 1 to 16
      #define INT_TO_CTPH(a)    ((uint32_t)( (a-1) << 28))     // From 1 to 16
      #define CTPL_TO_INT(a)    ((uint32_t)( (a) >> 24) + 1 )  // From 1 to 16
      #define INT_TO_CTPL(a)    ((uint32_t)( (a-1) << 24))     // From 1 to 16
      #define PRESC_TO_INT(a)   ((uint32_t)( (a) >> 12) + 1)   // 1/2/4/8/16/32/64/128
      #define INT_TO_PRESC(a)   ((uint32_t)( (a-1) << 12))     // 1/2/4/8/16/32/64/128
    • Choose DIV, CTPH, and CTPL to get a stable measure on all sensors. These parameters are linked to the board layout, sampling capacitors and serial resistors. See Active shield and section 'Charge transfer period tuning' in the application note Tuning a touch sensing application on MCUs (AN4316).
      OptimumTscCfgCTPH=x;    //With:
      OptimumTscCfgCTPL=x;    //x = 1 to 16
      OptimumTscCfgDIV=y;     //y = 1 to 128 (power of 2)
      
    • Call TSC init
      ...
      htsc.Init.CTPulseHighLength = INT_TO_CTPH(OptimumTscCfgCTPH);
      htsc.Init.CTPulseLowLength = INT_TO_CTPL(OptimumTscCfgCTPL);
      htsc.Init.SpreadSpectrum = DISABLE;
      ...
      htsc.Init.PulseGeneratorPrescaler = INT_TO_PRESC(OptimumTscCfgDIV);
      ...
      if (HAL_TSC_Init(&htsc) != HAL_OK)
      ...
    • Compute the three frequencies with this type of code sequence
      for(int f=0; f<FREQ_VALUES; f++){
        if(f!=0){
      	if(OptimunTscCfgCTPH+1 <=16 && OptimunTscCfgCTPL+1 <=16){
      	  OptimunTscCfgCTPH++;
      	  OptimunTscCfgCTPL++;
      	}else{
      	  // We assume CTPH <= CTPL
      	  OptimunTscCfgDIV  *= 2;
      	  OptimunTscCfgCTPH  = 1 + (OptimunTscCfgCTPH/2);
      	  OptimunTscCfgCTPL  = 1 + (OptimunTscCfgCTPL/2);
      	}
        }
        FreqHopp[f].div  = OptimunTscCfgDIV;
        FreqHopp[f].ctph = OptimunTscCfgCTPH;
        FreqHopp[f].ctpl = OptimunTscCfgCTPL;
      }
  • During while(1) loop
    while (1)
    {
      // Process TSC acquisition
      if(TSCAcqDone != 0){
        TSCAcqDone = 0;
        // Get TSC/TSL keys status
        TSL_obj_GroupProcess(&MyObjGroup);
        // Process DXS
        …
        // Process ECS algo
        …
        // activate led if key is press
        …
        //FTB, or Fast Transient Burst immunity: wait for 15ms
        …
        // We move to next frequency
        MoveToNextFreq();
        // After last bank acquisition we restart all bank acquisition
        TSCidxGroup = 0;
        TSCAcqDone = 0;
        TSL_acq_BankConfig(TSCidxGroup);
        TSL_acq_BankStartAcq_IT();
      }else{
        __WFI();
      }
    }
  • API used to change frequency after each acquisition
    * Api
    void MoveToNextFreq(void){
      static int f=0;
      f++;
      f %= FREQ_VALUES;
    
      // Change TSC frequency DIV divider
      htsc.Init.PulseGeneratorPrescaler = INT_TO_PRESC(FreqHopp[f].div);
      htsc.Init.CTPulseHighLength = INT_TO_CTPH(FreqHopp[f].ctph);
      htsc.Init.CTPulseLowLength  = INT_TO_CTPL(FreqHopp[f].ctpl);
    
      // Re Init TSC IP with New parameters...
      HAL_TSC_Init(&htsc);
      return;
    }

Important: The following points must be carefully checked:
  • Measures on all sensors must be equal when all frequencies are used: when moving from F0 to F1, no changes must be observed on any sensor, from measure point of view.
  • The frequencies must be far enough of each other, not a multiple.
  • The same number of acquisitions at each frequency must be performed. Instead of three samples, it is recommended to use four samples to validate a touch detection. Two acquisitions at the two frequencies are performed.

Cs and Rs

Increasing Cs, sampling capacitor, increases the acquisition time but decreases the conducted-noise effects.

The configuration Rskey = 10 kΩ and Cskey = 47 nF gives good results on the immunity to conducted-noise.

See section 'Hardware timing' of the application note Tuning a touch sensing application on MCUs (AN4316) for more details Rskey and Cskey.

See section 'Hardware timing' of the application note Guidelines for designing touch sensing applications with projected sensors (AN4313) for more details Rsshield and Csshield.

Noise or ripple measurement on VDD

On application using the STM32 general purpose ADC, the VIN ripple can be measured and the user can decide to stop touch sensing acquisition. The same measurement can be done on VDD side (see the figure below).
Figure 25. VIN ripple measurement on VDD
Regarding VDD decoupling, additional 10 nF and 1 nF capacitors can be used. This decoupling method spreads the decoupling capacitor impedance and increases the decoupling noise rejection range.
Figure 26. Decoupling capacitors on VDD
Figure 27. ESR behavior

Using DXS (detection exclusion system)

TSL provides a way to avoid multiple detections. This feature is activated by setting USE_DXS as shown below.
/*============================================================================*/
/* Detection Exclusion System (DXS)                                           */
/*============================================================================*/

/** @defgroup Common_Parameters_DXS 12 - DXS
  * @{ */

/** Detection Exclusion System (0=No, 1=Yes)
*/
#define TSLPRM_USE_DXS (1)

/** @} Common_Parameters_DXS */

Using TSL stateID sensors status

Usually all sensors are in TSL_STATEID_RELEASE state. When a sensor is pressed, its state moves to TSL_STATEID_DETECT.

Unusual system behavior can be detected using SensorNbInStateId API as shown below.
/**
* @brief  Count Sensor in stateId
* @param  StateId Sensor state id, can NOT be OR-ed.
* @retval Sensor number in StateId state
*/
int SensorNbInState(TSL_StateId_enum_T StateId){
  int   cntStateId=0,cntTKey=0;
  // Parse TKey sensors
#if TSLPRM_TOTAL_TOUCHKEYS > 0
  for (cntTKey=0;cntTKey<TSLPRM_TOTAL_TOUCHKEYS;cntTKey++){
    if(MyTKeys[cntTKey].p_Data->StateId == StateId){
      cntStateId++;
    }
  }
#endif
  // Parse LinRots sensors
#if TSLPRM_TOTAL_LINROTS > 0
  for (cntTKey=0;cntTKey<TSLPRM_TOTAL_LINROTS;cntTKey++){
    if(MyLinRots[cntTKey].p_Data->StateId == StateId){
      cntStateId++;
    }
  }
#endif
  return cntStateId;
}

Inside the main loop, sensors not in RELEASE state are counted and the acquisition is stopped if more than n sensors are not in a stable state. This may occur in case of incoming noise or when hands touch all sensors simultaneously.

The algorithm detailed below, added to DXS, improves the system noise immunity.
#define NOISE_COMPUTE_WINDOWS 10      /* Use to monitor noise */
#define NOISE_COMPUTE_THRESHOLD 20       /* Threshold level to decide when noise is too high */
#define NOISE_SUSPEND_DELAY 5000         /* Suspend delay when we detect noise (5s) */
#define NOISE_SENSOR_NOT_IN_RELEASE 3   /* Suspend acquisition when sensors are not 
in release state */
while (1)
{
if(TSCAcqDone != 0 && WaitEndOfNoiseDuration == 0){
  TSCAcqDone = 0;
  
  // Get TSC/TSL keys status
  TSL_obj_GroupProcess(&MyObjGroup);

  // Point to DXS first object
  // Process ECS algo
  // Application sensor processing
  // For FTB test, wait for 15ms
  // Handle Frequency Hopping

  // Handle noise on application level
  nbSensorInRelease = SensorNumberInState(TSL_STATEID_RELEASE);
  
  // Handle next acquisition
  if((((TSLPRM_TOTAL_TOUCHKEYS+TSLPRM_TOTAL_LINROTS)-nbSensorInRelease)
 >= NOISE_SENSOR_NOT_IN_RELEASE)){
	// Noise detect, we suspend sensors acquisition
	WaitEndOfNoiseDuration = NOISE_SUSPEND_DELAY;
  }else{
	// After last bank acquisition we restart all bank acquisition
	TSCidxGroup = 0;
	TSCAcqDone = 0;
	TSL_acq_BankConfig(TSCidxGroup);
	TSL_acq_BankStartAcq_IT();
  }
}else{
  __WFI();
  // We assume only 1ms tick interrupt occurs
  if(WaitEndOfNoiseDuration > 0) {
	WaitEndOfNoiseDuration--;
	if(WaitEndOfNoiseDuration==0){
	  nbSensorInRelease = 0;
	  WaitEndOfNoiseDuration = 0;
	  // We restart all acquisition
	  TSCidxGroup = 0;
	  TSCAcqDone = 0;
	  TSL_acq_BankConfig(TSCidxGroup);
	  TSL_acq_BankStartAcq_IT();
    }
  }
}

Using noise level measurement on delta

The GetNoiseLevel API can be used to get median noise level on all channels and decide to stop system when a value is too high. This API must be combined with the StateNbInSate one.

/**
* @brief  Get noise level
* @param  None
* @retval mean noise level
*/
int phaseMeasurementNoise=0;
int GetNoiseLevel(void){
	int meanNoiseLevel=0, noiseLevelTKEYS=0, noiseLevelLROT=0;
	uint32_t id = 0, channel=0, div=0;

	//Reset array
	if(phaseMeasurementNoise==0){
#if TSLPRM_TOTAL_TOUCHKEYS > 0
		for (channel=0;channel<TSLPRM_TOTAL_TOUCHKEYS;channel++){
			for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
				NoiseTKEYS[channel][id] = 0;
			}
		}
#endif
#if TSLPRM_TOTAL_LINROTS > 0
		for (channel=0;channel<TSLPRM_TOTAL_LINROTS;channel++){
			for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
				NoiseLROT[channel][id] = 0;
			}
		}
#endif
	}
	phaseMeasurementNoise++;
	//Start to measure noise, level.....
#if TSLPRM_TOTAL_TOUCHKEYS > 0
	// Parse TKey sensors
	for (channel=0;channel<TSLPRM_TOTAL_TOUCHKEYS;channel++){
		NoiseTKEYS[channel][phaseMeasurementNoise%NOISE_COMPUTE_WINDOWS]
=MyTKeys[channel].p_ChD->Delta;
	}
#endif
#if TSLPRM_TOTAL_LINROTS > 0
	// Parse LinRots sensors
	for (channel=0;channel<TSLPRM_TOTAL_LINROTS;channel++){
		NoiseLROT[channel][phaseMeasurementNoise%NOISE_COMPUTE_WINDOWS]
=MyLinRots[channel].p_ChD->Delta;
	}
#endif

	// Compute noise inside the rolling windows
#if TSLPRM_TOTAL_TOUCHKEYS > 0
	for (channel=0;channel<TSLPRM_TOTAL_TOUCHKEYS;channel++){
		for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
			noiseLevelTKEYS += NoiseTKEYS[channel][id];
		}
	}
	noiseLevelTKEYS = abs(noiseLevelTKEYS);
	noiseLevelTKEYS /= NOISE_COMPUTE_WINDOWS;
	div += TSLPRM_TOTAL_TOUCHKEYS; // Will be used to compute noise mean level
#endif
#if TSLPRM_TOTAL_LINROTS > 0
	for (channel=0;channel<TSLPRM_TOTAL_LINROTS;channel++){
		for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
			noiseLevelLROT += NoiseLROT[channel][id];
		}
	}
	noiseLevelLROT = abs(noiseLevelLROT);
	noiseLevelLROT /= NOISE_COMPUTE_WINDOWS;
	div += TSLPRM_TOTAL_LINROTS; // Will be used to compute noise mean level
#endif

// For debug purpose
//#if TSLPRM_TOTAL_TOUCHKEYS > 0
//	for (channel=0;channel<TSLPRM_TOTAL_TOUCHKEYS;channel++){
//                printf("Sensor TKEY %02d Delta(s) :",channel);
//		for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
//			printf(" %4d",NoiseTKEYS[channel][id]);
//		}
//      printf("\n");
//	}
//#endif
//#if TSLPRM_TOTAL_LINROTS > 0
//	for (channel=0;channel<TSLPRM_TOTAL_LINROTS;channel++){
//                printf("Slider LROT %02d Delta(s) :",channel);
//		for (id=0;id<NOISE_COMPUTE_WINDOWS;id++){
//			printf(" %4d",NoiseLROT[channel][id]);
//		}
//      printf("\n");
//	}
//#endif

	// mean noise level take into account keys and sliders
	meanNoiseLevel = (noiseLevelTKEYS + noiseLevelLROT)/div;
	printf("==> Noise ..... noiseLevelTKEYS %d noiseLevelLROT %d [meanNoiseLevel %d]\n",
noiseLevelTKEYS,noiseLevelLROT,meanNoiseLevel);

	return meanNoiseLevel;
}

Inside the main loop, noise is measured at the end of all groups acquisition. It is important to get the noise value only when all sensors are in RELEASE state.

The algorithm detailed below, added to DXS, improves the system noise immunity.
#define NOISE_COMPUTE_WINDOWS  10 /* Use to monitor noise */
#define NOISE_COMPUTE_THRESHOLD  20 /* Threshold level to decide when noise is too high */
#define NOISE_SUSPEND_DELAY  5000 /* Suspend delay when we detect noise (5s) */
#define NOISE_SENSOR_NOT_IN_RELEASE  3 /* Suspend acquisition when sensors are not
in release state */
while (1)
{
if(TSCAcqDone != 0 && WaitEndOfNoiseDuration == 0){
  TSCAcqDone = 0;
  
  // Get TSC/TSL keys status
  TSL_obj_GroupProcess(&MyObjGroup);

  // Point to DXS first object
  // Process ECS algo
  // Application sensor processing
  // For FTB test, wait for 15ms
  // Handle Frequency Hopping

  // Handle noise on application level
  nbSensorInRelease = STMTBOX_SensorNumberInState(TSL_STATEID_RELEASE);
  // We compute noise level only if all sensors are in Release state
  if(nbSensorInRelease==(TSLPRM_TOTAL_TOUCHKEYS+TSLPRM_TOTAL_LINROTS)){
	noiseLevel = STMTBOX_GetNoiseLevel();
	if (noiseLevel >= NOISE_COMPUTE_THRESHOLD)
	  noiseIsDetected = 1;
	else
	  noiseIsDetected = 0;
  }
  
  // Handle next acquisition
  if((((TSLPRM_TOTAL_TOUCHKEYS+TSLPRM_TOTAL_LINROTS)-nbSensorInRelease) 
>= NOISE_SENSOR_NOT_IN_RELEASE) || (noiseIsDetected)){
	// Noise detect, we suspend sensors acquisition
	WaitEndOfNoiseDuration = NOISE_SUSPEND_DELAY;
  }else{
	// After last bank acquisition we restart all bank acquisition
	TSCidxGroup = 0;
	TSCAcqDone = 0;
	TSL_acq_BankConfig(TSCidxGroup);
	TSL_acq_BankStartAcq_IT();
  }
}else{
  __WFI();
  // We assume only 1ms tick interrupt occurs
  if(WaitEndOfNoiseDuration > 0) {
	WaitEndOfNoiseDuration--;
	if(WaitEndOfNoiseDuration==0){
	  nbSensorInRelease = 0;
	  WaitEndOfNoiseDuration = 0;
	  noiseIsDetected = 0;
	  // We restart all acquisition
	  TSCidxGroup = 0;
	  TSCAcqDone = 0;
	  TSL_acq_BankConfig(TSCidxGroup);
	  TSL_acq_BankStartAcq_IT();
    }
  }
}

Using the internal ADC to detect noise on VDD

Examples of software reading the ADC value are available in STM32CubeMX deliveries. Windowing can be used to trig the interrupts. Under the ADC interrupt routine, WaitEndOfNoiseDuration must be set to match with the algorithm detailed in the previous section.

// Noise detect, we suspend sensors acquisition
	WaitEndOfNoiseDuration = NOISE_SUSPEND_DELAY;

Examples:

  • ./STM32Cube_FW_F0_Vx/Projects/STM32F091RC-Nucleo/Examples/ADC/ADC_AnalogWatchdog/readme.txt
  • ./STM32Cube_FW_F0_Vx/Projects/STM32F072RB-Nucleo/Examples_LL/ADC/ADC_MultiChannelSingleConversion/readme.txt