cancel
Showing results for 
Search instead for 
Did you mean: 

Can USB firmware or hardware cause a PWM signal to fall to zero, then back to its previous value without warning?

ESpra.1
Senior

I'm working on a device that uses Pulse-Width-Modulation to control a DRV2605L LRA/ERM haptic feedback motor driver (datasheet found here: https://www.ti.com/lit/ds/symlink/drv2605l.pdf?ts=1683824689205&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FDRV2605L)

The devices uses two DRV2605L, each connected to an STM32L552ZET6Q microcontroller via their own I2C bus (I2C1 & I2C2) and a PWM line (TIM1 Channel 1 & Channel 2 respectively). The DRV2605Ls are configured via I2C to take a PWM signal from their respective TIM1 channels and constantly drive an LRA haptic motor at a strength depending on the PWM signal. I'm unsure of precisely why, but the strength of the motor's vibrations are inversely proportional to the PWM signal (i.e. lower duty cycle means higher vibration strength).

The way the device works is as follows: instructions are sent from a PC to the STM32L5 via USB. These instructions contain which channel from TIM1 is having its duty cycle adjusted, and what the duty cycle is being adjusted to. The DRV2605Ls are constantly monitoring the duty cycle of their respective PWM inputs and adjusting their motors' vibration strength accordingly. The PC is constantly receiving data from another device to figure out how strong the LRA motor's vibration needs to be, and is constantly updating that information via USB.

below are the changes I made to the usb receive function.

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  uint8_t len = (uint8_t) *Len;
  memset(buffer, '\0', 64);
  memcpy(buffer, Buf, len);
  if((char)buffer[0] == 'z')
  {
	  TR_comCheck();
  }
  else
  {
	  key = buffer[0];
	  command = buffer[1];
	  unit = buffer[2];
	  data1 = buffer[3];
	  data2 = buffer[4];
  }
  memset(Buf, '\0', len);
  flag = 1;
  return (USBD_OK);
  /* USER CODE END 6 */
}

instructions are sent as uint8_t arrays of a key, command, unit, data1 and data2, though data2 isn't relevant to the haptic sections of the device. key is a randomly generated and used to double check that the command was received properly. command is used to select an action (initialize DRV2605Ls, double-check device connection, etc). unit is used to determine which specific drv2605 to talk to. data1 sets the new duty cycle.

while (1)
  {
	  if(flag == 1)
	  {
 
		  echoCommandBack();
		  switch(command)
		  {
		  case(0x01):			//Move Servo motors
				  moveServoMotor();
				  break;
		  case(0x02):
				  writeHapticPWM();
				  break;
                  }
                  flag = 0;
        }
}
 
void writeHapticPWM()
{
	//int value = 0;
	int levelOfStrength = 0;
	switch(unit)
	{
	case(0x00):
			//value = drvM;
 
			if(LRA1->status != DRV2605_ACTIVE)
			{
				LRA1->status = DRV2605_ACTIVE;
				LRA2->status = DRV2605_STANDBY;
				DRV2605_PWM_Output(LRA2, 65);
			}
			levelOfStrength = selectHapticStrength(LRA1, data1);
			if(levelOfStrength == 0)
			{
				printf("ERROR: LRA PWM = 0");
			}
			if(levelOfStrength != LRA1->currentPosition)
			{
				DRV2605_PWM_Output(LRA1, levelOfStrength);
			}
			break;
	case(0x01):
 
			if(LRA2->status != DRV2605_ACTIVE)
			{
				LRA2->status = DRV2605_ACTIVE;
				LRA1->status = DRV2605_STANDBY;
				DRV2605_PWM_Output(LRA1, 65);
			}
			levelOfStrength = selectHapticStrength(LRA2, data1);
			if(levelOfStrength == 0)
			{
				printf("ERROR: LRA PWM = 0");
			}
			if(levelOfStrength != LRA2->currentPosition)
			{
				DRV2605_PWM_Output(LRA2, levelOfStrength);
			}
			break;
	case(0x02):
			LRA1->status = DRV2605_STANDBY;
			LRA2->status = DRV2605_STANDBY;
			DRV2605_PWM_Output(LRA1, 65);
			DRV2605_PWM_Output(LRA2, 65);
			break;
	default:
		return;
	}
 
	usb_ACK();
}
 
void DRV2605_PWM_Output(struct DRV *drv, int output)
{
	TIM_HandleTypeDef *Timer = drv->timer;
	uint32_t channel = drv->channel;
	switch(channel)
	{
	case(TIM_CHANNEL_1):
		Timer->Instance->CCR1 = output;
		drv->currentPosition = output;
		break;
	case(TIM_CHANNEL_2):
		Timer->Instance->CCR2 = output;
		drv->currentPosition = output;
		break;
	}
}

So, a transmission of [randomkey], 0x02,0x00,0x2D translates to: adjust channel 1's duty cycle to 45.

This is what showed me the problem: Every once and a while, one of the PWM channels' duty cycle will rapidly drop to what looks like zero, then quickly bounce back to its original value. This only occurs when the PC is constantly sending new instructions for what the LRA vibration strength should be. Additionally, I have not seen this behaviour when I send a single instruction and nothing else after.

All of this so far seems to be pointing me towards the constant USB communications being the problem, though I'm not certain how. I'm still researching and testing, but I wanted to see if anyone had any potential insights.

5 REPLIES 5
Bob S
Principal

If only one of the 2 PWM signals drops to zero, then the timer itself is still running. Could be (temporary) corruption of that channel's CCR register or the GPIO config for that pin (changed from AltFunc to input/output). Both seem kinda unlikely, but can't tell.

Is it always the same PWM channel that goes out? When it drops to zero, does it "come back" after one complete PWM period? If so, perhaps you are updating the CCR register with a value that the CNT has already passed.

How are you configuring the timer, starting it and updating the duty cycle? Show you code (please don't post images/screen shots, copy/paste code into here using the code "</>" button).

BTW - I suspect your "lower duty cycle means higher vibration strength" issue is related to using PWM mod 1 vs. mode 2 in the CCMRx register, which basically basically invert the PWM polarity.

ESpra.1
Senior

I checked the mode 1 v mode 2 thing, and last I saw that wasn't happening. I think it's something related to the DRV board I'm using. I'll get the rest to here soon. It can happen to either of them, but never at the same time, which is weird. And again, the weird thing is that this only occurs at times of high USB traffic

Besides everything what BobS said above (i.e. show relevant portions of code, take the PWM waveform using LA and show the relevant portion, etc.), this particular thing:

> If so, perhaps you are updating the CCR register with a value that the CNT has already passed.

Here the question is: do you have CCRx preload enabled? (Read out and check in TIMx_CCMRx.OCxPE).

JW

I checked this before, and yes it is enabled. I checked in the SFRs in debug mode to confirm. Unfortunately, I might not be able to get the rest of the info for a little while since I'm occupied with something else at the moment.

Okay, I've added code. I'm still occupied with something else, but I should be able to get the rest of the info soon