Fan Dank

From dankwiki

Let's fuck hard with some fans using an Arduino MEGA and a Heltec LoRa ESP32 v2. These will be used with my MO-RA3 to collect realtime data (fan RPMs, temperatures, etc.) and provide realtime control (fan PWM/RPM, RGB signals). We will also need a TXS0108E level shifter, a LM2596 buck converter, and six IRLB8721 logic level MOSFETs.

We use a 12V PWM+DRGB hub for the many fans of the MO-RA3. Only one fan's RPM will be reported (whichever one is plugged into the red fan hookup), so it's important that we use the same model throughout. The hub has a two-wire hookup for tach and PWM (12V and ground are provided through the SATA power hookup). Looking at the hub with the clear side oriented up, the left wire is the PWM input, and the right wire is the tach output.

The goal is to have nothing running between the case and the MO-RA3 except for the coolant tubing. No USB, no RGB, no 4-pin fan, no power. That way, I can spring my quick disconnects (Koolance QD4s) and boom, the two units are distinct. The MO-RA3 won't pull on any easily-loosened 9000AWG wires, and there will be no electrical signals between the two.


We'll want to report RPMs to the controlling host, and probably take PWM and RGB orders from it. We might use LoRa for this later. For now, we'll use MQTT over WiFi from the Heltec.

MQTT topics

  • mora3/pwm desired PWM, integer 0–255, published by controller, subscribed by Heltec
  • mora3/therm temperature in celsius, published by Heltec, subscribed by controller
  • mora3/rpm rpm extrapolated from the most recent second (i.e., multiplied by 60), Heltec->controller

Powering the system

We need 12V for our fans (Arctic P14 RGBs) and their RGB LEDs. This is accomplished with a SHNITPWR 12V AC adapter, plugged into a barreljack switch. This barreljack switch is then adapted to a 12V-only SATA plug (SATA normally carries 3.3V, 5V, and 12V). The SATA plug enters a 12V PWM+RGB hub. All fans are plugged directly into this hub, one port of which carries through tachometer readings.

A 12V plus ground pair are used from one of the hub's PWM hookups to drive a HiLetgo LM2596 buck converter. The LM2596 is configured to deliver 7.1V by adjusting a potentiometer. The output is displayed on the LM2596's LED. This is a safe output to drive both the Arduino and the Heltec. On the output side, we hook two barreljack pushbutton connectors up to the LM2596. One directly powers the Arduino. The other uses a microUSB adapter to power the Heltec.

We will *not* be powering the fans or LEDs from the Arduino directly. They draw far too much current, and Arduinos can't provide 12V power anyway (well, unless you dump 12V in at the power jack, and then drive from VIN, but you're gonna be voltage regulating that 12 down to 5, and why burn dinosaurs when there's no need?). Everything else is powered by the hub directly. We only need 5V for the tachometer and PWM signals, but we need send 12V PWM to the LEDs. We thus connect another ground to one side of the breadboard, where we'll hook it up to the MOSFETs.

Rotation count (RPMs)

Most case fans have a tachometer inside, using the third wire to send its signal. It will be strobed once for every two revolutions. If it is e.g. strobed 80 times within a second, then there were 160 revolutions in that second, and we can extrapolate to 9600RPM (truly an insane case fan; I know of no such monstrosities).

We'll need a digital input pin with interrupt support. On the Arduino Uno, this restricts us to pin 2 or 3. We use pin 2, plus the necessary resistor.



We set up a 25K PWM signal on pin 8. We can't use the standard Arduino PWM system, which operates at lower frequencies (490Hz and 980Hz). We operate directly on timer registers, using timer 4:

  const word PWM_FREQ_HZ = 25000;
  TCCR4A = 0;
  TCCR4B = 0;
  TCNT4  = 0;
  ICR4 = (F_CPU / PWM_FREQ_HZ) / 2;
  TCCR4A |= (1 << COM4A1) | (1 << WGM41);
  TCCR4B |= (1 << WGM43) | (1 << CS40);

This ICR4 value comes out to 320. We can then set the desired PWM duty (0–255) with:

  OCR4C = duty;


We use three PWM-capable digital pins: 9, 10, and 11 for blue, green, and red respectively. analogWrite() is sufficient to set the brightness. These pins are used to control logic level MOSFETs, which are also hooked up to ground and the 12V input.


We want the water temperature, and ideally also our own temperature on the microcontroller. The Uno's ATmega328P processor has an onboard temperature sensor:

int readTemp(void){
  while(bit_is_set(ADCSRA, ADSC)){
  return (ADCL | (ADCH << 8)) - 342;

Unlike the Uno, it appears that the MEGA has no way to get its own temperature, lame.

External (thermistor)

We're using an Alphacool Eiszapfen 17365 plug sensor. Its datasheet can be found here. This is a 10kΩ thermistor (resistance varies with temperature) with a β of 3435K and a nominal temperature of 25℃. To effect a better read, we'll use the 3.3V as our reference. This requires hooking 3.3V up to AREF and calling analogReference(EXTERNAL);. We pair it with a reference 10KΩ resistor, and hook it up to 3.3V, ground, and analog input pin A0.


All level shifter connections are to the MEGA or Heltec, and are specified there.


  • 5V pin goes to 10kΩ resistor, goes to tach signal, goes to level shifter VB
  • Pin 2 goes to hub's tach output
  • Pin A0 goes to thermistor signal
  • Pin 8 goes to hub's PWM input
  • 3.3V pin goes to 10kΩ resistor, goes to thermistor signal, goes to AREF
  • Pin 16 goes to level shifter B7
  • Pin 17 goes to MEGA pin 17
  • Pins 9, 10, and 11 go to MOSFET controls


  • 5V pin goes to level shifter OE
  • 3.3V pin goes to level shifter VA
  • Pin 36 goes to level shifter A7
  • Pin 17 goes to MEGA pin 17


  • Gate is connected to appropriate MEGA pin 9, 10, or 11
  • Drain is connected to appropriate hub R/G/B pin
  • Source is connected to ground


The Heltec publishes desired PWM levels to the MEGA by writing a single byte over the UART. This communicates a value 0–255. It ought be immediately applied by the MEGA. It is published once a second, and whenever a new value is read from MQTT.

The MEGA publishes measured RPM and water temperature levels, along with the currently applied PWM, to the Heltec over the UART. Values may be published in any order, at any time. These values are published in a human-readable form, one after another with no intervening content. They are of the form `Tf`, `Ri`, and `Pi`, where `f` is a float and `i` is an integer.


We can transmit from the Heltec to the Arduino directly; the 3.3V high logic level of the Heltec registers as high on the MEGA. Sending from the 5V MEGA directly to the Heltec will damage the latter. We instead go through a TXS0108E level shifter. The 5V line is taken to VB, while the 3.3V line is taken to VA. TX2 (pin 16) on the MEGA is taken to B7 on the level shifter. A7 goes to pin 36 on the Heltec, which is configured as RX2. The Heltec transmits from 17 directly to RX2 (pin 17) on the MEGA. Finally, the Heltec's 5V output is connected to OE on the level shifter.

Future work

  • This needs to get cleaned up, obviously. at a bare minimum, wires need be soldered into the Heltec.