DRV8301 with 3 phase motor

Hello, I am an undergraduate student majoring in robotics.

I am trying to implement FOC using a 3-phase BLDC motor, a DRV8301, and an Arduino board.

The problem is that the SPI communication between the DRV8301 and the Arduino board is not working, so I am facing difficulties.

In the Arduino IDE, it appears that only the setup function is being executed, and neither the loop function nor the FOC_ISR function is running.

Has anyone solved this issue?

Below text is the code I used.

#include <SPI.h>
#include <DueTimer.h>
#include <math.h>

// -- Constant Definitions --
#define TWO_PI      6.28318530718

// -- DRV8301 Pin Definitions --
#define CS_PIN      10

// -- DRV8301 PWM Pin Definitions --
#define INH_A       5
#define INL_A       6
#define INH_B       7
#define INL_B       8
#define INH_C       9
#define INL_C       11

// -- DRV8301 En_Gate Pin Definition --
#define EN_GATE     12

// -- DRV8301 Current Sensing Pin Definitions --
#define SO1_PIN     A0
#define SO2_PIN     A1

// -- DRV8301 EMF Sensing Pin Definitions --
#define EMF_A       A2
#define EMF_B       A3
#define EMF_C       A4

// -- nFAULT Pin Definition (for monitoring) --
#define NFAULT_PIN  2

// FOC update period (10kHz -> dt = 100 µs)
const float dt = 0.0001; // 100 µs

// Motor Parameters
const float polePairs = 6.0; // e.g., for a 12-pole motor, polePairs = 6

// Constants and Variables for PI Control
volatile float targetSpeedRPM = 200.0;  // Target speed (RPM)
volatile float phaseCorrection = 0.0;     // Phase correction from PI control (radian/update)
volatile float currentPhase = 0.0;        // Current electrical angle (radian)
volatile float measuredSpeedRPM = 0.0;    // Measured speed (RPM)
volatile float speedIntegral = 0.0;       // PI integral term
const float Kp = 0.01;
const float Ki = 0.001;

// Dead Time (e.g., for 12-bit resolution, 7 counts ≒ approx 170 ns)
const uint32_t deadTimeCounts = 6;

// Motor Power Control Scaling (0.0 ~ 1.0)
volatile float motorScaling = 0.5;

// Interrupt Flag
volatile bool focFlag = false;

// Variables to store ADC measurements
volatile uint16_t adcSO1 = 0, adcSO2 = 0;
volatile uint16_t adcEMF_A = 0, adcEMF_B = 0, adcEMF_C = 0;

/////////////////////// SPI Related Functions ///////////////////////
void writeDRV8301Register(uint8_t regAddr, uint16_t data) {
  uint16_t frame = (0 << 15) | ((regAddr & 0xF) << 11) | (data & 0x7FF);
  
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(CS_PIN, LOW);
  SPI.transfer16(frame);
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();
}

uint16_t readDRV8301Register(uint8_t regAddr) {
  uint16_t frame = (1 << 15) | ((regAddr & 0xF) << 11);
  uint16_t data = 0;
  
  // First transaction: send command
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(CS_PIN, LOW);
  SPI.transfer16(frame);
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();

  delay(5);
  
  // Second transaction: dummy read
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(CS_PIN, LOW);
  data = SPI.transfer16(0x0000);
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();
  
  return (data & 0x07FF);
}

/////////////////////// DRV8301 Initialization Function ///////////////////////
void initDRV8301() {
  // Example settings based on datasheet
  writeDRV8301Register(2, 0x068); // Control Register 1
  delay(5);
  writeDRV8301Register(3, 0x020); // Control Register 2
  delay(10);
  
  uint16_t cr1Val = readDRV8301Register(2);
  delay(5);
  uint16_t cr2Val = readDRV8301Register(3);
  Serial.print("CR1: 0x"); Serial.println(cr1Val, HEX);
  Serial.print("CR2: 0x"); Serial.println(cr2Val, HEX);
  Serial.println("DRV8301 initialization complete.");
}

/////////////////////// Fault Status Monitoring Function ///////////////////////
void checkFaultStatus() {
  uint16_t status1 = readDRV8301Register(0);
  uint16_t status2 = readDRV8301Register(1);
  
  Serial.print("Status Reg1: 0x"); Serial.println(status1, HEX);
  Serial.print("Status Reg2: 0x"); Serial.println(status2, HEX);
  
  if (status1 & (1 << 0)) Serial.println("Fault: PVDD Undervoltage");
  if (status1 & (1 << 1)) Serial.println("Fault: DVDD Undervoltage");
  if (status1 & (1 << 2)) Serial.println("Fault: GVDD Undervoltage");
  if (status1 & (1 << 3)) Serial.println("Fault: GVDD Overvoltage");
  if (status1 & (1 << 4)) Serial.println("Fault: Overtemperature Warning");
  if (status1 & (1 << 5)) Serial.println("Fault: Overtemperature Shutdown");
}

/////////////////////// Function to Set PWM Frequency ///////////////////////
void setPWMFrequency_All(uint32_t freq) {
  const uint32_t resolution = 4096;  // 12-bit PWM
  uint32_t pwmClock = freq * resolution;
  uint32_t divisor = 84000000UL / pwmClock;
  if (divisor < 1) divisor = 1;
  if (divisor > 255) divisor = 255;
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(divisor);
  
  Serial.print("PWM Frequency: ~");
  Serial.print((float)84000000UL / (divisor * (float)resolution));
  Serial.println(" Hz");
}

/////////////////////// Arduino PWM Initialization Function ///////////////////////
void initPWM() {
  analogWriteResolution(12);
  setPWMFrequency_All(10000);  // 10kHz
  Serial.println("PWM initialization complete.");
}

/////////////////////// Arduino ADC Initialization Function ///////////////////////
void initADC() {
  analogReadResolution(12);
  analogReference(AR_DEFAULT);
  Serial.println("ADC initialization: 12-bit, 3.3V reference");
}

/////////////////////// Sensor Transformation Functions ///////////////////////
// Clarke Transformation: 3-phase -> 2-axis (alpha, beta)
void clarkeTransform(float a, float b, float c, float &alpha, float &beta) {
  alpha = a;
  beta = (a + 2*b) / sqrt(3.0);
}

// Park Transformation: (alpha, beta) -> (d, q) (using rotor angle theta)
void parkTransform(float alpha, float beta, float theta, float &d, float &q) {
  d =  alpha * cos(theta) + beta * sin(theta);
  q = -alpha * sin(theta) + beta * cos(theta);
}

// Inverse Park Transformation: (d, q) -> (alpha, beta)
void inverseParkTransform(float d, float q, float theta, float &alpha, float &beta) {
  alpha = d * cos(theta) - q * sin(theta);
  beta  = d * sin(theta) + q * cos(theta);
}

// Inverse Clarke Transformation: (alpha, beta) -> 3-phase (a, b, c)
void inverseClarkeTransform(float alpha, float beta, float &a, float &b, float &c) {
  a = alpha;
  b = (-alpha + sqrt(3.0)*beta) / 2.0;
  c = (-alpha - sqrt(3.0)*beta) / 2.0;
}

/////////////////////// Simple Sensorless Rotor Angle Estimation Function ///////////////////////
// Estimates rotor angle using back-EMF signals (example: based on atan2)
// In actual implementation, ADC voltage conversion and calibration are required
float estimateRotorAngle(uint16_t emfA, uint16_t emfB, uint16_t emfC) {
  float vA = (float)emfA;
  float vB = (float)emfB;
  float vC = (float)emfC;
  // Calculate two-phase equivalent (simplified)
  float alpha = vA - 0.5 * (vB + vC);
  float beta  = (sqrt(3.0)/2.0) * (vB - vC);
  float angle = atan2(beta, alpha);
  if(angle < 0) angle += TWO_PI;
  return angle;
}

/////////////////////// PI Control Update Function ///////////////////////
void updateSpeedPI(float measuredSpeed) {
  float error = targetSpeedRPM - measuredSpeed;
  speedIntegral += error * dt;
  
  // Simple anti-windup: Limit the integral term
  if(speedIntegral > 1000) speedIntegral = 1000;
  if(speedIntegral < -1000) speedIntegral = -1000;
  
  phaseCorrection = Kp * error + Ki * speedIntegral;
}

/////////////////////// Fault Safety Handling Function inside FOC ISR ///////////////////////
void handleFaultInISR() {
  // Check the nFAULT pin and set PWM output to 0 if a fault occurs
  if(digitalRead(NFAULT_PIN) == LOW) {
    // Fault detected: set all PWM outputs to 0
    analogWrite(INH_A, 0);
    analogWrite(INL_A, 0);
    analogWrite(INH_B, 0);
    analogWrite(INL_B, 0);
    analogWrite(INH_C, 0);
    analogWrite(INL_C, 0);
  }
}

/////////////////////// FOC Interrupt Service Routine (10kHz) ///////////////////////
void FOC_ISR() {
  // 1. ADC Sampling (current and back-EMF measurements)
  adcSO1 = analogRead(SO1_PIN);
  adcSO2 = analogRead(SO2_PIN);
  adcEMF_A = analogRead(EMF_A);
  adcEMF_B = analogRead(EMF_B);
  adcEMF_C = analogRead(EMF_C);
  
  // 2. Fault status check and safety handling (set PWM to 0 on fault)
  handleFaultInISR();
  
  // 3. Estimate rotor angle using back-EMF measurements
  float rotorAngle = estimateRotorAngle(adcEMF_A, adcEMF_B, adcEMF_C);
  
  // 4. Open-loop based electrical angle update (combining back-EMF estimation and PI correction)
  float electricalSpeed = (targetSpeedRPM / 60.0) * TWO_PI * polePairs;
  float phaseIncrement = electricalSpeed * dt + phaseCorrection;
  currentPhase += phaseIncrement;
  if(currentPhase >= TWO_PI) currentPhase -= TWO_PI;
  
  // 5. Simplified speed measurement (based on phaseIncrement)
  measuredSpeedRPM = (phaseIncrement / dt) * (60.0 / (TWO_PI * polePairs));
  if(isnan(measuredSpeedRPM)) measuredSpeedRPM = targetSpeedRPM;
  
  // 6. PI control update
  updateSpeedPI(measuredSpeedRPM);
  
  // 7. Calculate 3-phase PWM duty cycles (using back-EMF and corrected currentPhase)
  float dutyA = (sin(currentPhase) + 1.0) / 2.0;
  float dutyB = (sin(currentPhase + TWO_PI/3.0) + 1.0) / 2.0;
  float dutyC = (sin(currentPhase + 2.0*TWO_PI/3.0) + 1.0) / 2.0;
  
  uint32_t pwmA = (uint32_t)(dutyA * 4095);
  uint32_t pwmB = (uint32_t)(dutyB * 4095);
  uint32_t pwmC = (uint32_t)(dutyC * 4095);
  
  // 8. Apply deadtime correction
  uint32_t pwmA_high = (pwmA > deadTimeCounts) ? pwmA - deadTimeCounts : 0;
  uint32_t pwmA_low  = (4095 - pwmA > deadTimeCounts) ? (4095 - pwmA) - deadTimeCounts : 0;
  uint32_t pwmB_high = (pwmB > deadTimeCounts) ? pwmB - deadTimeCounts : 0;
  uint32_t pwmB_low  = (4095 - pwmB > deadTimeCounts) ? (4095 - pwmB) - deadTimeCounts : 0;
  uint32_t pwmC_high = (pwmC > deadTimeCounts) ? pwmC - deadTimeCounts : 0;
  uint32_t pwmC_low  = (4095 - pwmC > deadTimeCounts) ? (4095 - pwmC) - deadTimeCounts : 0;
  
  // Apply motor power scaling
  pwmA_high = (uint32_t)(pwmA_high * motorScaling);
  pwmA_low  = (uint32_t)(pwmA_low * motorScaling);
  pwmB_high = (uint32_t)(pwmB_high * motorScaling);
  pwmB_low  = (uint32_t)(pwmB_low * motorScaling);
  pwmC_high = (uint32_t)(pwmC_high * motorScaling);
  pwmC_low  = (uint32_t)(pwmC_low * motorScaling);
  
  // 9. Update 6 PWM outputs
  analogWrite(INH_A, pwmA_high);
  analogWrite(INL_A, pwmA_low);
  analogWrite(INH_B, pwmB_high);
  analogWrite(INL_B, pwmB_low);
  analogWrite(INH_C, pwmC_high);
  analogWrite(INL_C, pwmC_low);
  
  focFlag = true;
}

/////////////////////// Serial Input Processing ///////////////////////
void processSerialInput() {
  if(Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    input.trim();
    if(input.startsWith("SPEED")) {
      float spd = input.substring(6).toFloat();
      targetSpeedRPM = spd;
      Serial.print("New target speed: ");
      Serial.println(targetSpeedRPM);
    } else if(input.startsWith("CORR")) {
      float corr = input.substring(5).toFloat();
      phaseCorrection = corr;
      Serial.print("New phase correction: ");
      Serial.println(phaseCorrection);
    } else if(input.startsWith("POWER")) {
      float powerVal = input.substring(6).toFloat();
      if(powerVal < 0.0) powerVal = 0.0;
      if(powerVal > 1.0) powerVal = 1.0;
      motorScaling = powerVal;
      Serial.print("New motor scaling: ");
      Serial.println(motorScaling);
    }
  }
}

/////////////////////// Monitor nFAULT Status ///////////////////////
void checkNfaultStatus() {
  int nfaultState = digitalRead(NFAULT_PIN);
  if(nfaultState == LOW) {
    Serial.println("[FAULT] nFAULT LOW: Fault detected!");
  } else {
    Serial.println("[OK] nFAULT HIGH: No fault.");
  }
}

/////////////////////// setup ///////////////////////
void setup() {
  Serial.begin(115200);
  while(!Serial);
  
  // Configure pins
  pinMode(INH_A, OUTPUT); pinMode(INL_A, OUTPUT);
  pinMode(INH_B, OUTPUT); pinMode(INL_B, OUTPUT);
  pinMode(INH_C, OUTPUT); pinMode(INL_C, OUTPUT);
  pinMode(EN_GATE, OUTPUT);
  pinMode(CS_PIN, OUTPUT);
  pinMode(NFAULT_PIN, INPUT_PULLUP);
  
  Serial.println("Pin configuration completed.");
  delay(500);
  
  // Immediately set EN_GATE to HIGH to enable DRV8301 (as per datasheet)
  digitalWrite(EN_GATE, LOW);
  delay(500);
  
  digitalWrite(CS_PIN, HIGH);
  Serial.println("[Setup] EN_GATE enabled and SPI pins configured.");
  delay(500);
  
  SPI.begin();
  Serial.println("[Setup] SPI initialization complete.");
  checkNfaultStatus();
  delay(500);
  
  initDRV8301();
  Serial.println("[Setup] DRV8301 initialization complete.");
  checkNfaultStatus();
  delay(500);
  
  initPWM();
  Serial.println("[Setup] PWM initialization complete.");
  delay(500);
  
  initADC();
  Serial.println("[Setup] ADC initialization complete.");
  checkNfaultStatus();
  delay(500);
  
  digitalWrite(EN_GATE, HIGH);
  delay(500);

  // Initialize variables
  speedIntegral = 0.0;
  phaseCorrection = 0.0;
  currentPhase = 0.0;
  motorScaling = 1.0;
  
  // Start 10kHz interrupt (100 µs period)
  // For testing, the period can be reduced if there are issues with the ISR load
  Timer3.attachInterrupt(FOC_ISR).start(100);

  Serial.println("- - - - - - - - - - - - - -");
  Serial.println("[Setup] Setup complete. Starting loop.");
  Serial.println("- - - - - - - - - - - - - -");
}

/////////////////////// loop ///////////////////////
void loop() {
  processSerialInput();
  
  static unsigned long lastFaultCheck = 0;
  if(millis() - lastFaultCheck > 500) {
    lastFaultCheck = millis();
    Serial.println("------ Fault Status Check ------");
    checkNfaultStatus();
    checkFaultStatus();
    Serial.println("-------------------------------");
  }
  
  if(focFlag) {
    focFlag = false;
    Serial.print("Target RPM: "); Serial.print(targetSpeedRPM);
    Serial.print("  Measured RPM: "); Serial.print(measuredSpeedRPM);
    Serial.print("  Phase Correction: "); Serial.println(phaseCorrection);
  }
  
  delay(10);
}

Hi @Sagum0 , welcome to SimpleFOC!

You’re not really using SimpleFOC library so I am not sure how much help we can be :slight_smile:

We have some code here for working with the DRV8301: GitHub - simplefoc/Arduino_SimpleFOC_DRV8301_Support_Library: Arduino DRV8301 library support for SimpleFOC, it also can use alone without SimpleFOC library

You may find it helpful, but I guess you’ll have to adapt it to your code…

Hi yeong-u, I have worked with the DRV8301 before and the SPI is indeed really finicky. So briefly, this IC uses the SPI mode 1 (CPOL = 0, CPHA = 1), so make sure your SPI config is correct.

Second, the datasheet specifies that the driver can support SPI clock of up to 10Mhz, but I have only had successes with the IC when SPI is below 6Mhz, prefereably below 1Mhz.

Third, there is this thing called the delay time between nCS events and SPI transactions. Basically, after pulling nCS low, you would have to give it some delay before start sending SPI. The datasheet says that the minimum delay would be around 50ns, but shame on TI because that’s not true at all. So I programmed the IC using STM32 HAL library, and I had to give the delay of around 10 miliseconds after pulling nCS low, my code would look something like:
HAL_GPIO_WritePin(nCS_port, nCS_pin, GPIO_PIN_LOW);
HAL_Delay(10);
HAL_SPI_TransmitReceive(…);
HAL_Delay(10);
HAL_GPIO_WritePin(nCS_port, nCS_pin, GPIO_PIN_HIGH);

I have tested communication with the IC for several delay intervals, and it worked stably above 5ms. Below that, the IC might treat your SPI data as invalid. I have gone crazy for weeks before finding out this, and I think this might be the case for you too.

In your code, try:
digitalWrite(CS_PIN, LOW);
delay(10);
SPI.beginTransaction(…);
SPI.transfer16(…);
delay(10);
digitalWrite(CS_PIN, HIGH);

Hope this helps because I went insane with this IC too.