Building the 4-bay ThinkNAS with a Smart Fan Controller

Building the 4-bay ThinkNAS with a Smart Fan Controller

A few months ago, a friend showed me a 3D print model for a ThinkCentre NAS. I was instantly intrigued and wanted to build it myself.

ThinkNAS - 4x HDD NAS enclosure for Lenovo M920q - Free 3D Print Model - MakerWorld
Download this free 3D print file designed by Fisz. ThinkNAS - it's a compact, 4 HDD bay enclosure, compatible with Lenovo ThinkCentre M920q (and M920x / M720x) mini PC.Ideal for Truenas or Proxmox, XCP-NG etc.It's using 120mm fan at the back to cool all the stuff inside :) Size: 16.8cm (width) x 19.6cm (height) x 25.1cm (depth) Boost MeBoost for free, if you like the project. It'll help me build more fancy stuff :)Thank you ❤️ Used parts:Lenovo ThinkCentre M920q PC obviously :)M920q compatible PCIe riser [LINK]ASM1166 PCIe to 6x SATA Controller [LINK] if you want use NVMe version check below:If you want to use ASM1166 NVMe version, to free up the PCIe slot (so you can connect better network card or other stuff), you'll need NVMe extender like this: [LINK], and also ASM1166 NVMe controller [LINK]SATA Male to female adapter ("backplane") [LINK]SATA cables (50cm will be enough)USB powered FAN controller [LINK] or [LINK2]5525 12V 6A power supply - for supplying power to disks [LINK]5525 to SATA splitter - from power supply to HDD/FAN(s) [LINK]120mm FAN - I've used Arctic P12 PWMScrews:6-32*6mm screws for ThinkNAS case and for installing HDD disks into caddy [LINK]M3*10mm screws for installing backplane to NAS case [LINK]M3*20mm screws for installing handle with HDD caddyFor rear fan it's recommended to install some fan grill.: https://makerworld.com/en/models/1332105-screwless-120mm-fan-grill-with-distance-no-whine#profileId-1370582  PCIe SATA Controller ASM1166 bracket compatible with Lenovo M920q (and others) is here: https://makerworld.com/en/models/1246251-lenovo-m720q-m920q-hdd-controller-bracket-asm1166#profileId-1267786  Be sure to print it at least with PETG filament, so it'll be more heat resistant, than PLA. If you're searching for smaller, 2-bay version - you can find it here: https://makerworld.com/en/models/1280680-thinknas-2x-hdd-enclosure-for-lenovo-m920q#profileId-1308483 Even bigger 6-bay version: https://makerworld.com/en/models/1737570-thinknas-6x-hdd-nas-enclosure-for-lenovo-m920q#profileId-1846272

I don't have a 3D printer, so I asked the same friend to print it for me. In the meantime, I ordered all the extra materials I needed for it. I also noticed that the model designer recommends using a USB-A-powered fan controller for a 120mm fan that can be installed in the case and should run all the time. I found that a little annoying because I do not want the fan to spin all the time, even when it is not needed, which led me to craft a (somewhat) smart fan controller.

Bill of Materials


The bill of materials lists everything I used to build my ThinkNAS, and it is split into materials needed for the base model and additional materials needed for building the smart fan controller.

Materials needed for the base model

Click to expand/collapse the Bill of Materials for the base model
ℹ️
Links marked with * are affiliate links, and I may receive a small commission at no extra cost to you.
  • Lenovo ThinkCentre M720q Tiny
  • The 3D printed ThinkCentre NAS case itself [LINK]
  • M720q riser card [LINK]*
  • ASM1166-based PCIe to 6x SATA controller [LINK]*
  • 4x SATA male to female adapters [LINK]*
  • 4x SATA cables [LINK]*
  • DC 5525 12V 6A power supply [LINK]*
    • Alternatively to be absolutely sure that the power supply delivers enough (peak) power for four HDDs, choose a 12V power supply with ~10A [LINK]*
  • DC 5525 to 4x SATA power splitter [LINK]*
  • 120mm 4-pin PWM fan [LINK]*
  • USB-A Fan Controller [LINK]*
    • If you decide to build the smart fan controller below, then you won't need this
  • 3D printed fan grill to prevent cables from obstructing the fan [LINK]
  • 3D printed bracket for the PCIe SATA card [LINK]
  • 6-32x6mm screws for putting the case together and installing HDD disks into the caddies [LINK]*
  • M3x10mm screws for installing SATA adapters in the NAS case [LINK]*
  • M3x25mm screws for installing handles to the HDD caddies [LINK]*
    • Alternatively M3x22mm screws, but I have not tested this size specifically (see below for more information)
  • M5x12mm self-tapping screws for installing the fan in the case [LINK]*
  • Optionally: 3D printed 2.5" SSD to 3.5" HDD adapter, if you need to install SSDs in the NAS case (printing it in PETG is enough, ABS, as the print profile suggests, is not really needed) [LINK]

Additional materials for the smart fan controller

ℹ️
Links marked with * are affiliate links, and I may receive a small commission at no extra cost to you.
  • ESP32-C3 SuperMini [LINK]*
  • AHT20 temperature sensor module [LINK]*
  • MT3608 DC-DC boost converter module [LINK]*
  • USB-A to USB-C cable [LINK]*
    • Preferably with USB data lines (D+/D-) to allow for reading the serial output and reflashing code from the ThinkCentre, but not necessarily needed
    • Those 10-20cm cables that often come free with various electronic devices usually lack the data lines, but will still power the microcontroller
  • Any double-sided tape for securing the fan controller inside the case
    • You probably have something lying around; otherwise, this is what I used (20mm wide, 1mm thick): [LINK]*
You do not need to buy the USB-A Fan Controller listed in the materials for the base model, if you decide to build the smart fan controller.

Building the "dumb" NAS case


I'll skip most of the assembly of the base NAS case itself, as it was pretty unspectacular and mostly involved following the manual provided by the model designer. The images in the manual were sometimes taken from too short a distance, so you can't really see where a component is supposed to go. Despite that, it was still quite straightforward to assemble the "backplane" (what the model designer calls the part where the SATA adapters are installed and which is inserted into the outer case) and screw everything together. The truly interesting part is replacing the USB fan controller recommended by the model designer with a smart one.

I ordered all the parts listed in the original bill of materials provided by the model designer, only to notice that the listed screw length for installing the caddy handles was too short. The model designer recommends M3x20mm screws, but you should use longer ones. I used M3x25mm because those are more readily available than any between 20 and 25 mm, but they are slightly too long. If they are fully screwed in, they stick out slightly on the other side of the handle, but screwing them in almost all the way so they do not, or only slightly, stick out works without any problems. I imagine that something like M3x22mm is the sweet spot.

The model designer also recommends installing a fan grill to prevent cables from jamming the fan. I forgot to have this part printed at first, and it annoyed me very much during the initial installation. The problem was that I'd already disassembled the old setup, and waiting for the part to continue building was infeasible, so I needed to make it work somehow. The temporary fix was to use an absurd number of cable ties to secure all the cables so they did not obstruct the fan. In the meantime, I had a fan grill printed and installed it a few weeks later.

Here are some time lapses of the prints:

Printing time lapses
0:00
/0:51

Outer case of the ThinkNAS

0:00
/0:38

Inner part of the ThinkNAS ("backplane") that is inserted into the outer case

0:00
/0:02

The SATA adapters are installed into these four parts which are then installed into the inner part of the case

0:00
/0:08

Two caddies

0:00
/0:08

Even more caddies

0:00
/0:04

Four caddy handles

0:00
/0:05

2.5" SSD to 3.5" HDD Adapter for the boot SSD

0:00
/0:04

Fan grill

Smart Fan Controller


While researching how to make a temperature-controlled or "smart" fan controller, I came up with a few different ways. The first idea was to use a fan header splitter to duplicate the CPU fan control signals for the fan inside the NAS case. This method may work for other SFF computers and 3D models, but it doesn't work with the M720q without a lot of tinkering. The problem is that the CPU fan header on this machine has a nonstandard layout where some pins are switched, so a standard 4-pin PWM fan header does not work here. I also thought that this approach was suboptimal because the CPU temperature could differ from the actual temperature inside the case.

After abandoning the previous idea, I found the KlausMu/esp32-fan-controller GitHub project, which uses an ESP32 and a few other components to make a temperature-controlled fan controller that can connect to Home Assistant via MQTT. I didn't need the Home Assistant features, and I'm quite happy with every device that does not need a Wi-Fi connection, so I looked into how I could approach this project using that as inspiration.

I've worked with a few temperature sensors in previous projects (maybe I'll write about them in future blog posts, stay tuned!), and the AHT20 seemed like a good candidate, as it's accurate enough for my use case and cheap. I also had an ESP32-C3 SuperMini and some MT3608 DC-DC boost converter modules lying around, so I started soldering all the components together. (Maybe not the smartest idea to solder everything before testing it on a breadboard, but it worked out mostly fine for this project.)

The following ESP32-C3 SuperMini pinout diagrams may help with the wiring later to show which wire connects to which pin of the microcontroller.

ESP32-C3 SuperMini Pinout Diagram (seen from above). Source: https://www.imiconsystem.com/product/esp32-c3-super-mini/
ESP32-C3 SuperMini Pinout Diagram (seen from below). Source: https://randomnerdtutorials.com/getting-started-esp32-c3-super-mini/

I used the following wiring for the fan controller, but as it later turned out, although it works, it is not ideal.

Wiring diagram of my build (see paragraph below for recommended pin assignments)

I only noticed this after soldering everything together, but there's a small blue LED on GPIO8 of the ESP32-C3 SuperMini. If you use my code and wiring, this LED will blink every time the microcontroller communicates with the temperature sensor because it's connected to the default SDA pin GPIO8. If this annoys you, use different pins than GPIO8 (and GPIO9) for I²C and different pin assignments in the code below. For example, GPIO0 and GPIO1 should work without any problems, as these pins do not have any secondary assignments like the blue LED on GPIO8. I also connected the PWM pin of the fan to GPIO3 of the microcontroller. Luckily, this pin does not need to be changed, like the one for SDA. So, if you want to build this fan controller as well, I recommend the following wiring:

Recommended wiring diagram

All the other connections are self-explanatory: the AHT20 is connected to 3.3V and ground of the microcontroller, the input side of the MT3608 is connected to 5V and ground of the microcontroller, and the output side of the MT3608 is connected to 12V and ground of the fan. I also isolated most of the pins with budget-brand Kapton tape, as at that point in the project I was unsure whether there would be exposed metal inside the assembled case that could short out the circuit. The assembled fan controller should look something like this (if you decide to plaster the components with Kapton tape as well):

The programming of the microcontroller was straightforward. I struggled a little to install firmware on it the first time, but then I found out that you need to hold the BOOT button (or manually pull GPIO9 low) on the device while connecting it to the computer to allow flashing. Otherwise, the microcontroller will continuously connect and disconnect from the computer every second.

I also recently started using PlatformIO for my microcontroller projects instead of the Arduino IDE because it offers more flexibility and makes working with multiple source files possible, at the cost of not having an "everything just works out of the box" experience. The code is still quite minimal and should work without prior experience.

If you do not already have PlatformIO installed, search the VSCode extension store for "PlatformIO" and install the extension "PlatformIO IDE" (more information on the PlatformIO website). Create a new project in PlatformIO, overwrite the existing platformio.ini with the one below, and paste the two code files config.h and main.cpp into the src/ folder. There are some settings in config.h that you can change, such as the pin assignments for SDA and SCL or toggling serial logging.

If you do not want to install PlatformIO, you can also create a new Arduino IDE project and paste in smart_fan_controller.ino (which is essentially just a merged config.h and main.cpp file). Then you need to install the esp32 board manager (link to tutorial) and select the board type Nologo ESP32C3 Super Mini. You can also change the previously mentioned settings with this approach at the top of the .ino file.

platformio.ini
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nologo_esp32c3_super_mini]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
board = nologo_esp32c3_super_mini
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps = adafruit/Adafruit AHTX0@^2.0.5

platformio.ini

config.h
// ----------------------
// --- Debug Settings ---
// ----------------------

// set to 1 to enable serial output
#define DEBUG 1

#if DEBUG
    #define D_begin(...) Serial.begin(__VA_ARGS__)
    #define D_waitForSerial() while (!Serial) { delay(100); }
    #define D_print(...) Serial.print(__VA_ARGS__)
    #define D_println(...) Serial.println(__VA_ARGS__)
    #define D_printf(...) Serial.printf(__VA_ARGS__)
    #define D_write(...) Serial.write(__VA_ARGS__)
#else
    #define D_begin(...)
    #define D_waitForSerial()
    #define D_print(...)
    #define D_println(...)
    #define D_printf(...)
    #define D_write(...)
#endif // DEBUG

// -----------------------
// --- Pin Definitions ---
// -----------------------

// pin for PWM fan control
#define PIN_PWM 3

// pin for SDA
#define PIN_SDA 8

// pin for SCL
#define PIN_SCL 9

// --------------------
// --- PWM Settings ---
// --------------------

// PWM frequency in Hz
#define PWM_FREQUENCY 25000

// PWM resolution in bits
#define PWM_RESOLUTION 8

config.h

main.cpp
#include <Arduino.h>
#include <Adafruit_AHTX0.h>

#include "config.h"

Adafruit_AHTX0 aht;

float get_temperature() {
    sensors_event_t aht_humidity, aht_temperature;

    aht.getEvent(&aht_humidity, &aht_temperature);

    return aht_temperature.temperature;
}

void setup() {
    D_begin(115200);
    D_waitForSerial();

    ledcAttach(PIN_PWM, PWM_FREQUENCY, PWM_RESOLUTION);

    Wire.begin(PIN_SDA, PIN_SCL);
    aht.begin(&Wire);
}

void loop() {
    // request the current temperature from the AHT20
    float case_temp = get_temperature();

    uint8_t duty_cycle = 0;

    // if PWM resolution is set to 8bit, ledcWrite uses an 8bit duty cycle value (0-255)
    // currently implemented temperature steps are:
    // <27°C (no cooling), <30°C, <40°C, <50°C and >=50°C
    if (case_temp < 27.0f) {
        duty_cycle = 0;
    } else if (case_temp < 30.0f) {
        duty_cycle = 50;
    } else if (case_temp < 40.0f) {
        duty_cycle = 80;
    } else if (case_temp < 50.0f) {
        duty_cycle = 100;
    } else {
        duty_cycle = 150;
    }

    // set PWM
    ledcWrite(PIN_PWM, duty_cycle);

    // print the current temp and fan duty cycle to serial, if enabled
    D_printf("Current case temperature: %.2f°C\nSet fan to %u\n", case_temp, duty_cycle);

    delay(5000);
}

main.cpp

smart_fan_controller.ino
#include <Arduino.h>
#include <Adafruit_AHTX0.h>

// ----------------------
// --- Debug Settings ---
// ----------------------

// set to 1 to enable serial output
#define DEBUG 1

#if DEBUG
    #define D_begin(...) Serial.begin(__VA_ARGS__)
    #define D_waitForSerial() while (!Serial) { delay(100); }
    #define D_print(...) Serial.print(__VA_ARGS__)
    #define D_println(...) Serial.println(__VA_ARGS__)
    #define D_printf(...) Serial.printf(__VA_ARGS__)
    #define D_write(...) Serial.write(__VA_ARGS__)
#else
    #define D_begin(...)
    #define D_waitForSerial()
    #define D_print(...)
    #define D_println(...)
    #define D_printf(...)
    #define D_write(...)
#endif // DEBUG

// -----------------------
// --- Pin Definitions ---
// -----------------------

// pin for PWM fan control
#define PIN_PWM 3

// pin for SDA
#define PIN_SDA 8

// pin for SCL
#define PIN_SCL 9

// --------------------
// --- PWM Settings ---
// --------------------

// PWM frequency in Hz
#define PWM_FREQUENCY 25000

// PWM resolution in bits
#define PWM_RESOLUTION 8

// -----------------------

Adafruit_AHTX0 aht;

float get_temperature() {
    sensors_event_t aht_humidity, aht_temperature;

    aht.getEvent(&aht_humidity, &aht_temperature);

    return aht_temperature.temperature;
}

void setup() {
    D_begin(115200);
    D_waitForSerial();

    ledcAttach(PIN_PWM, PWM_FREQUENCY, PWM_RESOLUTION);

    Wire.begin(PIN_SDA, PIN_SCL);
    aht.begin(&Wire);
}

void loop() {
    // request the current temperature from the AHT20
    float case_temp = get_temperature();

    uint8_t duty_cycle = 0;

    // if PWM resolution is set to 8bit, ledcWrite uses an 8bit duty cycle value (0-255)
    // currently implemented temperature steps are:
    // <27°C (no cooling), <30°C, <40°C, <50°C and >=50°C
    if (case_temp < 27.0f) {
        duty_cycle = 0;
    } else if (case_temp < 30.0f) {
        duty_cycle = 50;
    } else if (case_temp < 40.0f) {
        duty_cycle = 80;
    } else if (case_temp < 50.0f) {
        duty_cycle = 100;
    } else {
        duty_cycle = 150;
    }

    // set PWM
    ledcWrite(PIN_PWM, duty_cycle);

    // print the current temp and fan duty cycle to serial, if enabled
    D_printf("Current case temperature: %.2f°C\nSet fan to %u\n", case_temp, duty_cycle);

    delay(5000);
}

I haven't yet extensively tested the implemented temperature steps and corresponding PWM duty cycles for the fan. They're set to values that make sense to me and seem to work well in my case but may need a bit of tweaking. If I find better values, I'll update the code snippets above and add a notice at the top of the post.

After programming the microcontroller, the fan controller could be installed in the case. I used double-sided tape on the back of the boost converter to stick it underneath the "backplane". This way, the temperature sensor hovers perfectly behind the HDDs and in front of the fan. Your mileage may vary depending on how you soldered your components together, how long your wires are, etc.

Now came the installation of the boot SSD and the HDDs. This step was really frustrating because there was some wiggle room between the caddies and the slots they slide through. The SATA adapters also seemed to be ever so slightly misaligned with the caddies. This made it quite hard to tell whether an inserted SSD/HDD was actually fully connected to the SATA adapter in the back of the case. The best method appeared to be wiggling the caddy a little to feel the SATA adapter in the back while inserting it into a slot. To make full contact with the SATA adapter, a small bit of force may be needed. After some trial and error, the HDDs were successfully connected to the adapters, but they still felt a little loose, and I could not be bothered to improve this anymore. The HDDs have stayed in their slots, though, so it seems to hold for the time being.

Finished build with an external HDD. Only slots 2 and 3 have disks installed here, that is why the caddies in 1 and 4 stick out a little bit.

The assembled ThinkNAS looks very clean, and I have yet to encounter any issues since putting it together. There have been no overheating problems yet, though that will truly be put to the test when the next modded Minecraft server is started ;)

The old HDD cage I used before building the ThinkNAS

The finished build is a night-and-day difference compared to the HDD cage from before. The HDD cage was actually quite practical. It had five 3.5" slots and space to install a 120mm fan in the back, but it was really ugly.
(I sadly do not have a picture of the assembled HDD cage build, only of the cage itself.)

Total Project Costs


The total price, excluding printing costs, comes out to about $53.31 or 47,33€. This price includes items from the previous HDD cage build that I could reuse and some things I already had lying around, like SATA cables and MT3608 DC-DC boost converters. I also used some AliExpress gimmicks (e.g., coins) to save a bit of money, so it may not be representative if you decide to buy all components now. The prices for the screws assume the complete ordered supply is used, but there are a lot left over. If you account only for the screws that were actually used (or you already have similar screws to copy this build), it is a few bucks cheaper.

Cost breakdown
product ppu_usd usd_eur ppu_eur units price_usd price_eur
M720q riser card 8,73 0,8932 7,797636 1 8,73 7,797636
PCIe SATA Controller 18,38 0,8868 16,299384 1 18,38 16,299384
4x SATA adapter 2,13 0,8659 1,844367 1 2,13 1,844367
SATA cable 1,06 0,8864 0,939584 4 4,24 3,758336
DC 5525 power supply 6,53 0,8932 5,832596 1 6,53 5,832596
DC 5525 SATA power splitter 5,01 0,8932 4,474932 1 5,01 4,474932
12V PWM fan 2,68 0,8636 2,314448 1 2,68 2,314448
6-32x6mm screws 1,06 0,8659 0,917854 1 1,06 0,917854
M3x10mm screws 0,38 0,8659 0,329042 1 0,38 0,329042
M3x25mm screws 0,40 0,8614 0,344560 1 0,4 0,344560
M5x12mm screws 0,47 0,8640 0,406080 1 0,47 0,406080
ESP32-C3 SuperMini 1,26 0,9793 1,233918 1 1,26 1,233918
5x MT3608 module 1,75 0,9497 1,661975 0,2 0,35 0,332395
AHT20 module 0,47 0,8534 0,401098 1 0,47 0,401098
USB-A USB-C cable 0,38 0,8506 0,323228 1 0,38 0,323228
double-sided tape 0,84 0,8614 0,723576 1 0,84 0,723576
TOTAL $53.31 47,33€

I actually thought the total costs would be a little lower before I calculated this breakdown, but it's still much more affordable than "real" NAS systems, and the finished build looks really nice :)

Final Thoughts


The finished ThinkNAS looks really good, and it's definitely a nice upgrade compared to the HDD cage I had before. There were some frustrating aspects of this build, including the wrong screw size listed for the HDD caddy handles, having to wiggle the HDDs/SSDs into the SATA adapters to make them connect properly, and forgetting to print a fan grill (which is entirely my fault). Other than that, if you have an M720q or M920q ThinkCentre in your homelab and are planning to hook up some disks, I can recommend this build!