Arduino code for 25khz pwm fan

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
MonsignorBart
 
Posts: 27
Joined: Sun Dec 29, 2019 4:33 pm

Arduino code for 25khz pwm fan

Post by MonsignorBart »

Hi, I have an M4 airlift lite and my pwm fan isn't adjusting its speed. I have my arduino plugged into the same 12v source as the fan (but do use a 12v to 5v buck converter). As, I found out the hard way when I didn't have my arduino plugged into the same power source as the fan.

Here's some pwm code I found on the internet for the M4 Feather that compiles but it doesn't seem to work on my M4 airlift lite my fan doesn't adjust speed. Any idea what I'm missing? I did play with the numbers and tried to figure out out.

Code: Select all

// Adafruit feather m4 express: Set-up digital pin D12 and D13 to 25KHz variable duty cycle
// PWM frequency = (selected clock source / DIV) / (PRESCALER_DIV * (PERIOD + 1))
//                  ( GCLK_GENCTRL_SRC_DPLL0 / GCLK_GENCTRL_DIV(0) ) / (TC_CTRLA_PRESCALER_DIV2

#define PERIOD 2399
#define PWMPin 12
int led = 13;

void setup()
{

  pinMode(led, OUTPUT);    

  // Set up the generic clock (GCLK7) to clock timer TCC1
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(0) |       // Divide the clock source
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW "square wave"
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         //GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization

  // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
  // page 169
  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_BANNED |        // Enable the TCC0/TCC1 perhipheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0 and TCC1

  // Enable the peripheral multiplexer on pin D12
  PORT->Group[g_APinDescription[PWMPin].ulPort].PINCFG[g_APinDescription[PWMPin].ulPin].bit.PMUXEN = 1;
 
  // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
  // page 35
  // Set the D12 (PORT_PA22 TCC1/WO[6]) peripheral multiplexer to peripheral (EVEN port number) F(0x5): TCC1, Channel 0
  PORT->Group[g_APinDescription[PWMPin].ulPort].PMUX[g_APinDescription[PWMPin].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);

  TCC1->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV2 |        // Set prescaler to 2, 120MHz/2 = 60MHz
                    TCC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock

  TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;             // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC1->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

    //pwm resolution
    //log(PER.reg 2399) / log(2) = 2^11 steps (2048)
    TCC1->PER.reg = PERIOD;                          // Set-up the PER (period) register for 25KHz
  while (TCC1->SYNCBUSY.bit.PER);                    // Wait for synchronization

  TCC1->CC[2].reg = PERIOD;                          // D12??? all the way on
  while (TCC1->SYNCBUSY.bit.CC2);                    // Wait for synchronization

  TCC1->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC1
  while (TCC1->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

}

void changeDutyCycle(float newVal) {
    float fperiod = newVal * (PERIOD / 100.00);
    int period = constrain(fperiod, 0, PERIOD);
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
    //page 43-44 table 6-29
    TCC1->CCBUF[2].reg = period; //per can equal 0-2399 (+1) microseconds?
}


void loop()
{
  for (int ii = 0; ii <= 10; ii++)
  {
    changeDutyCycle(ii * 10);
    for (int xx = 0; xx < ii; xx++)
    {
      digitalWrite(led, HIGH);
      delay(500);
      digitalWrite(led, LOW);
      delay(500);
    }
    delay(5000);
  }
}

User avatar
Franklin97355
 
Posts: 23910
Joined: Mon Apr 21, 2008 2:33 pm

Re: Arduino code for 25khz pwm fan

Post by Franklin97355 »

What is the model number of your fan (or a link to the datasheet)?

User avatar
MonsignorBart
 
Posts: 27
Joined: Sun Dec 29, 2019 4:33 pm

Re: Arduino code for 25khz pwm fan

Post by MonsignorBart »

https://noctua.at/pub/media/wysiwyg/Noc ... _paper.pdf

It's a Noctua NF-A14 Industrial PPC-2000 IP67 PWM
https://noctua.at/en/nf-a14-industrialp ... cification

On page 6 it states

Code: Select all

PWM control input signal
As specified by Intel (c.f. “4-Wire Pulse Width Modulation (PWM) Controlled Fans”, Intel
Corporation September 2005, revision 1.3), the square wave type PWM signal has to be
supplied to the PWM input (pin 4) of the fan and must conform to the
following specifications:
• Target frequency: 25kHz, acceptable range 21kHz to 28kHz
• Maximum voltage for logic low: VIL=0,8V
• Absolute maximum current sourced: Imax=5mA (short circuit current)
• Absolute maximum voltage level: VMax=5,25V (open circuit voltage)
• Allowed duty-cycle range 0% to 100%
Thank you

User avatar
MonsignorBart
 
Posts: 27
Joined: Sun Dec 29, 2019 4:33 pm

Re: Arduino code for 25khz pwm fan

Post by MonsignorBart »

Found the issue. I'm not an expert by any means on this, but playing with stuff found the issue ended up being a few things. I came across code with TCC0 instead of TCC1 (very similar looking) and TCC0 worked after adding the below change, TCC1 did not.

Code: Select all

void changeDutyCycle(float newVal) {
    float fperiod = newVal * (PERIOD / 100.00);
    int period = constrain(fperiod, 0, PERIOD);
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
    //page 43-44 table 6-29
[b]    TCC1->CCBUF[2].reg = period; //per can equal 0-2399 (+1) microseconds?[/b]
}
I don't know what
TCC1->CCBUF[2].reg = period;
was supposed to do, but changing it to
TCC0->CC[0].reg = period; instead worked.

If anyone else comes across this in the future, nearly all the code I pasted came from this user/post at https://forum.arduino.cc/t/samd51-adafr ... m/593605/5
Secondly for PWM to work the Arduino must be powered by the same source as the fan. Most PWM fans are 12v and Arduino's 5v so that's an issue. You can use a 12v to 5v usb buck converter (but then you're constantly unplugging the arduino from laptop to plug in arduino to buck converter to test rinse and repeat) or get an adjustable dc step down converter and a 5.5mm x 2.1mm plug. Adafruit thankfully adds ability to turn on/off dc adapter and if it's over 7.5v it will automatically switch to using the dc plug for power. This lets one power the arduino by a 12v battery using the adjustable step-down converter while keeping it plugged into the laptop so you can see the serial display, change your code, without constant unplugging/replugging and since the fan and arduino are powered by the same 12v battery changing the PWM will work.

Here's the entire code that worked... and it does depend on the arduino & fan being powered by the same 12v battery using an adjustable dc step down to get 7.5v-9v to the dc plug of the arduino (do not send 12v into the arduino) and still being plugged into the laptop with the USB so you can change the fan speeds through the serial port while laptop is connected. I also noticed on my original code I was dealing with TCC1 and on the below code am working with TCC0. Don't ask me why I tried TCC0, but the below is working when using TCC0.

Code: Select all

// Adafruit feather m4 express: Set-up digital pin D12 and D13 to 25KHz variable duty cycle
// PWM frequency = (selected clock source / DIV) / (PRESCALER_DIV * (PERIOD + 1))
//                  ( GCLK_GENCTRL_SRC_DPLL0 / GCLK_GENCTRL_DIV(0) ) / (TC_CTRLA_PRESCALER_DIV2

#define PERIOD 2399
#define PWMPin 7

const byte numChars = 32;
char receivedChars[numChars]; // an array to store the received data
String inString = "";
float inputfloat = 0;
boolean newData = false;

void setup() 
{
  // Set up the generic clock (GCLK7) to clock timer TCC0 
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization  

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_BANNED |        // Enable the TCC0 peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[PWMPin].ulPort].PINCFG[g_APinDescription[PWMPin].ulPin].bit.PMUXEN = 1;
  
  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) E(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[PWMPin].ulPort].PMUX[g_APinDescription[PWMPin].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
  
  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV4 |        // Set prescaler to 8, 48MHz/8 = 6MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                 

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC0->PER.reg = 2399;                            // Set-up the PER (period) register 50Hz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
  
  TCC0->CC[0].reg = 2399;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

  Serial.begin(9600);
  while (!Serial);
  Serial.println("<Arduino is ready>");
}

void changeDutyCycle() {
  if (newData == true) {
    inString = receivedChars;
    float rawinputfloat = inString.toFloat();
    inString = "";
    inputfloat = constrain(rawinputfloat, 0.00, 100.00);
    float fperiod = inputfloat * (PERIOD / 100.00);
    int period = constrain(fperiod, 0, PERIOD);
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
    //page 43-44 table 6-29
    TCC0->CC[0].reg = period; //per can equal 0-2399 (+1) microseconds
  }
}

void loop()
{
  recvWithEndMarker();
  changeDutyCycle();
  showNewData();
}

void recvWithEndMarker() {
  static byte ndx = 0;
  const char endMarker = '\n';
  char rc;
  // if (Serial.available() > 0) {
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();
    if (rc != '\n') {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void showNewData() {
  if (newData == true) {
    Serial.print("This just in ... ");
    Serial.println(inputfloat);
    newData = false;
  }
}

Locked
Please be positive and constructive with your questions and comments.

Return to “Metro, Metro Express, and Grand Central Boards”