%% fig-alt: Comparison chart of six popular MCUs showing ESP32 as high performance Wi-Fi option, ESP8266 as budget Wi-Fi choice, Arduino Nano as beginner-friendly classic, STM32 as industrial-grade performer, nRF52 as ultra-low power BLE specialist, and Raspberry Pi Pico as budget performance leader.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#7F8C8D', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
flowchart TB
subgraph HighPerf["High Performance"]
ESP32["ESP32<br/>240 MHz Dual Core<br/>Wi-Fi + BLE"]
STM32["STM32F4<br/>180 MHz Cortex-M4<br/>Industrial Grade"]
end
subgraph LowPower["Low Power Champions"]
nRF52["nRF52840<br/>64 MHz<br/>0.4µA Deep Sleep"]
ESP8266["ESP8266<br/>80 MHz<br/>Budget Wi-Fi"]
end
subgraph Budget["Budget Friendly"]
Pico["RP2040<br/>133 MHz Dual Core<br/>$1 Cost"]
Nano["Arduino Nano<br/>16 MHz<br/>Best for Learning"]
end
1625 Hardware Selection Optimizer
Choose Optimal IoT Hardware
1625.1 Selecting the Right Hardware
Choosing the right microcontroller for an IoT project involves balancing multiple competing requirements: processing power, connectivity, power consumption, operating environment, and cost. This interactive optimizer helps you explore the trade-offs and find the best MCU for your specific needs.
This interactive hardware selector allows you to specify your project requirements and visualize how different MCUs compare. Features include requirement matching, radar chart comparison, BOM generation, power consumption estimates, and development complexity indicators.
- Set your Processing Power requirement (low/medium/high)
- Select required Connectivity options (Wi-Fi, BLE, LoRa, Cellular, Zigbee)
- Choose your Power Source (battery, solar, mains)
- Select Operating Environment (indoor, outdoor, industrial)
- Adjust your Budget Range using the slider ($5-$100)
- View the Radar Chart comparing top 3 recommendations
- Review Recommendations with detailed reasoning
- Generate a Bill of Materials for your project
Show code
{
// ===========================================================================
// HARDWARE SELECTION OPTIMIZER - ENHANCED VERSION
// ===========================================================================
// Features:
// - Requirements inputs (Processing Level, Connectivity, Power Source, Environment, Budget)
// - MCU database with comprehensive specs (ESP32, ESP8266, Arduino Nano, STM32, nRF52, RPi Pico)
// - Scoring algorithm based on requirements match
// - Comparison table with radar chart
// - Top 3 recommendations with reasoning
// - Component BOM generator
// - Power consumption estimate
// - Development complexity indicator
//
// IEEE Color Palette:
// Navy: #2C3E50 (primary)
// Teal: #16A085 (secondary)
// Orange: #E67E22 (highlights)
// Gray: #7F8C8D (neutral)
// LtGray: #ECF0F1 (backgrounds)
// Green: #27AE60 (good)
// Red: #E74C3C (warnings)
// ===========================================================================
// ---------------------------------------------------------------------------
// CONFIGURATION
// ---------------------------------------------------------------------------
const config = {
width: 950,
height: 2200,
colors: {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
red: "#E74C3C",
yellow: "#F1C40F",
purple: "#9B59B6",
blue: "#3498DB",
pink: "#E91E63"
},
// MCU Database with comprehensive specifications
mcuDatabase: [
{
id: "esp32",
name: "ESP32",
manufacturer: "Espressif",
processingLevel: "high",
mips: 600,
clockMHz: 240,
cores: 2,
flashKB: 4096,
ramKB: 520,
powerActiveMA: 80,
powerSleepMA: 0.01,
powerDeepSleepUA: 10,
costUSD: 4.00,
boardCostUSD: 8.00,
connectivity: ["wifi", "ble"],
voltage: 3.3,
voltageRange: "2.2-3.6V",
gpioCount: 34,
adcChannels: 18,
adcResolution: 12,
pwmChannels: 16,
temperatureRange: "-40 to 85°C",
environments: ["indoor", "outdoor", "industrial"],
powerSources: ["battery", "solar", "mains"],
ecosystem: {
ide: ["Arduino IDE", "ESP-IDF", "PlatformIO", "MicroPython"],
libraries: 95,
community: 98,
documentation: 90,
learningCurve: "moderate"
},
developmentComplexity: 3,
color: "#E74C3C",
pros: ["Excellent Wi-Fi/BLE combo", "Large community", "Good processing power", "Rich peripherals"],
cons: ["Higher power consumption", "Complex deep sleep", "No native USB on basic variant"],
useCases: ["Smart home", "Wearables", "Industrial monitoring", "Asset tracking"],
additionalComponents: [
{ name: "Antenna (PCB)", cost: 0.50 },
{ name: "USB-UART Bridge", cost: 1.50 },
{ name: "3.3V Regulator", cost: 0.30 },
{ name: "Decoupling Caps", cost: 0.20 }
]
},
{
id: "esp8266",
name: "ESP8266",
manufacturer: "Espressif",
processingLevel: "medium",
mips: 80,
clockMHz: 80,
cores: 1,
flashKB: 4096,
ramKB: 80,
powerActiveMA: 70,
powerSleepMA: 0.02,
powerDeepSleepUA: 20,
costUSD: 2.00,
boardCostUSD: 4.00,
connectivity: ["wifi"],
voltage: 3.3,
voltageRange: "2.5-3.6V",
gpioCount: 17,
adcChannels: 1,
adcResolution: 10,
pwmChannels: 4,
temperatureRange: "-40 to 125°C",
environments: ["indoor", "outdoor"],
powerSources: ["battery", "solar", "mains"],
ecosystem: {
ide: ["Arduino IDE", "NodeMCU", "PlatformIO", "MicroPython"],
libraries: 90,
community: 95,
documentation: 85,
learningCurve: "easy"
},
developmentComplexity: 2,
color: "#3498DB",
pros: ["Very low cost", "Easy to use", "Great for Wi-Fi projects", "Mature ecosystem"],
cons: ["Limited GPIO", "Single ADC", "No Bluetooth", "Less RAM"],
useCases: ["Wi-Fi sensors", "Home automation", "IoT prototypes", "Weather stations"],
additionalComponents: [
{ name: "Antenna (PCB)", cost: 0.50 },
{ name: "USB-UART Bridge", cost: 1.50 },
{ name: "3.3V Regulator", cost: 0.30 },
{ name: "Decoupling Caps", cost: 0.15 }
]
},
{
id: "arduino_nano",
name: "Arduino Nano",
manufacturer: "Arduino/Microchip",
processingLevel: "low",
mips: 20,
clockMHz: 16,
cores: 1,
flashKB: 32,
ramKB: 2,
powerActiveMA: 19,
powerSleepMA: 0.0075,
powerDeepSleepUA: 0.1,
costUSD: 2.50,
boardCostUSD: 5.00,
connectivity: [],
voltage: 5.0,
voltageRange: "2.7-5.5V",
gpioCount: 22,
adcChannels: 8,
adcResolution: 10,
pwmChannels: 6,
temperatureRange: "-40 to 85°C",
environments: ["indoor", "outdoor"],
powerSources: ["battery", "solar", "mains"],
ecosystem: {
ide: ["Arduino IDE", "PlatformIO", "Atmel Studio"],
libraries: 100,
community: 100,
documentation: 98,
learningCurve: "easy"
},
developmentComplexity: 1,
color: "#27AE60",
pros: ["Easiest to learn", "Massive community", "5V logic", "Abundant tutorials"],
cons: ["No wireless", "Limited memory", "Slow processor", "No native USB"],
useCases: ["Education", "Simple sensors", "Motor control", "LED projects"],
additionalComponents: [
{ name: "USB-B Mini Cable", cost: 2.00 },
{ name: "Decoupling Caps", cost: 0.10 }
]
},
{
id: "stm32f4",
name: "STM32F446",
manufacturer: "STMicroelectronics",
processingLevel: "high",
mips: 225,
clockMHz: 180,
cores: 1,
flashKB: 512,
ramKB: 128,
powerActiveMA: 35,
powerSleepMA: 0.0011,
powerDeepSleepUA: 1.8,
costUSD: 6.00,
boardCostUSD: 15.00,
connectivity: [],
voltage: 3.3,
voltageRange: "1.7-3.6V",
gpioCount: 82,
adcChannels: 16,
adcResolution: 12,
pwmChannels: 17,
temperatureRange: "-40 to 105°C",
environments: ["indoor", "outdoor", "industrial"],
powerSources: ["battery", "solar", "mains"],
ecosystem: {
ide: ["STM32CubeIDE", "Keil MDK", "IAR", "PlatformIO"],
libraries: 88,
community: 85,
documentation: 92,
learningCurve: "steep"
},
developmentComplexity: 4,
color: "#9B59B6",
pros: ["Excellent performance", "Rich peripherals", "Low power modes", "Industry standard"],
cons: ["Steeper learning curve", "No integrated wireless", "Complex setup"],
useCases: ["Industrial control", "Motor drives", "Audio processing", "Medical devices"],
additionalComponents: [
{ name: "ST-Link Debugger", cost: 15.00 },
{ name: "Crystal Oscillator", cost: 0.50 },
{ name: "3.3V Regulator", cost: 0.40 },
{ name: "Decoupling Caps", cost: 0.30 }
]
},
{
id: "nrf52840",
name: "nRF52840",
manufacturer: "Nordic Semiconductor",
processingLevel: "medium",
mips: 64,
clockMHz: 64,
cores: 1,
flashKB: 1024,
ramKB: 256,
powerActiveMA: 5.3,
powerSleepMA: 0.0015,
powerDeepSleepUA: 0.4,
costUSD: 5.50,
boardCostUSD: 10.00,
connectivity: ["ble", "zigbee"],
voltage: 3.0,
voltageRange: "1.7-5.5V",
gpioCount: 48,
adcChannels: 8,
adcResolution: 12,
pwmChannels: 4,
temperatureRange: "-40 to 85°C",
environments: ["indoor", "outdoor"],
powerSources: ["battery", "solar"],
ecosystem: {
ide: ["nRF Connect SDK", "Segger Embedded Studio", "Zephyr", "Arduino"],
libraries: 80,
community: 82,
documentation: 90,
learningCurve: "moderate"
},
developmentComplexity: 3,
color: "#16A085",
pros: ["Ultra-low power", "Excellent BLE stack", "Thread/Zigbee support", "USB device"],
cons: ["No Wi-Fi", "Smaller community than ESP32", "Higher cost"],
useCases: ["Wearables", "Beacons", "Mesh networks", "Low-power sensors"],
additionalComponents: [
{ name: "Antenna (Chip)", cost: 1.00 },
{ name: "32.768kHz Crystal", cost: 0.30 },
{ name: "Decoupling Caps", cost: 0.20 }
]
},
{
id: "rp2040",
name: "Raspberry Pi Pico",
manufacturer: "Raspberry Pi Foundation",
processingLevel: "medium",
mips: 270,
clockMHz: 133,
cores: 2,
flashKB: 2048,
ramKB: 264,
powerActiveMA: 25,
powerSleepMA: 1.3,
powerDeepSleepUA: 180,
costUSD: 1.00,
boardCostUSD: 4.00,
connectivity: [],
voltage: 3.3,
voltageRange: "1.8-3.3V",
gpioCount: 26,
adcChannels: 3,
adcResolution: 12,
pwmChannels: 16,
temperatureRange: "-20 to 85°C",
environments: ["indoor"],
powerSources: ["mains", "battery"],
ecosystem: {
ide: ["Thonny", "Arduino IDE", "PlatformIO", "MicroPython", "CircuitPython"],
libraries: 85,
community: 90,
documentation: 95,
learningCurve: "easy"
},
developmentComplexity: 2,
color: "#E67E22",
pros: ["Incredibly cheap", "Good performance", "Native USB", "Excellent PIO", "Easy debugging"],
cons: ["No wireless on base model", "Poor deep sleep", "Limited ADC channels"],
useCases: ["Education", "USB devices", "State machines", "Audio", "HID devices"],
additionalComponents: [
{ name: "USB-C Cable", cost: 2.00 },
{ name: "Headers", cost: 0.50 }
]
}
],
processingLevels: {
low: { label: "Low", minMIPS: 0, maxMIPS: 50, description: "Simple sensors, data logging" },
medium: { label: "Medium", minMIPS: 50, maxMIPS: 200, description: "Connectivity, basic processing" },
high: { label: "High", minMIPS: 200, maxMIPS: 1000, description: "AI/ML edge, complex algorithms" }
},
powerSources: {
battery: { label: "Battery", icon: "🔋", requiresLowPower: true },
solar: { label: "Solar", icon: "☀️", requiresLowPower: true },
mains: { label: "Mains Power", icon: "🔌", requiresLowPower: false }
},
environments: {
indoor: { label: "Indoor", tempRange: "0 to 50°C", protection: "None required" },
outdoor: { label: "Outdoor", tempRange: "-20 to 60°C", protection: "Weather sealing" },
industrial: { label: "Industrial", tempRange: "-40 to 85°C", protection: "IP67/ATEX" }
}
};
// ---------------------------------------------------------------------------
// STATE MANAGEMENT
// ---------------------------------------------------------------------------
let state = {
// Requirements
requirements: {
processingLevel: "medium",
connectivity: [],
powerSource: "mains",
environment: "indoor",
budgetMax: 50
},
// Results
recommendations: [],
selectedMCU: null
};
// ---------------------------------------------------------------------------
// SCORING ALGORITHM
// ---------------------------------------------------------------------------
function calculateMCUScores() {
const results = [];
const { requirements } = state;
config.mcuDatabase.forEach(mcu => {
let score = 0;
let maxScore = 0;
let meetsRequirements = true;
let failReasons = [];
let matchReasons = [];
// 1. Processing Level Match (25 points)
maxScore += 25;
const procLevel = config.processingLevels[requirements.processingLevel];
if (mcu.mips >= procLevel.minMIPS && mcu.mips <= procLevel.maxMIPS * 2) {
if (mcu.processingLevel === requirements.processingLevel) {
score += 25;
matchReasons.push(`Perfect processing match (${mcu.mips} MIPS)`);
} else if (mcu.mips >= procLevel.minMIPS) {
score += 15;
matchReasons.push(`Adequate processing (${mcu.mips} MIPS)`);
}
} else if (mcu.mips < procLevel.minMIPS) {
failReasons.push(`Insufficient processing (${mcu.mips} < ${procLevel.minMIPS} MIPS)`);
meetsRequirements = false;
} else {
score += 10; // Overpowered but still works
matchReasons.push(`Exceeds processing needs (${mcu.mips} MIPS)`);
}
// 2. Connectivity Match (25 points)
maxScore += 25;
if (requirements.connectivity.length === 0) {
score += 25;
matchReasons.push("No specific connectivity required");
} else {
const hasAllConnectivity = requirements.connectivity.every(
conn => mcu.connectivity.includes(conn)
);
if (hasAllConnectivity) {
score += 25;
matchReasons.push(`Has required connectivity: ${requirements.connectivity.join(", ")}`);
} else {
const missing = requirements.connectivity.filter(c => !mcu.connectivity.includes(c));
failReasons.push(`Missing connectivity: ${missing.join(", ")}`);
meetsRequirements = false;
// Partial credit
const hasCount = requirements.connectivity.filter(c => mcu.connectivity.includes(c)).length;
score += Math.floor(25 * hasCount / requirements.connectivity.length);
}
}
// 3. Power Source Compatibility (20 points)
maxScore += 20;
if (mcu.powerSources.includes(requirements.powerSource)) {
const powerSourceConfig = config.powerSources[requirements.powerSource];
if (powerSourceConfig.requiresLowPower) {
// For battery/solar, prefer low power MCUs
if (mcu.powerActiveMA <= 30) {
score += 20;
matchReasons.push(`Excellent for ${requirements.powerSource} (${mcu.powerActiveMA}mA active)`);
} else if (mcu.powerActiveMA <= 50) {
score += 15;
matchReasons.push(`Good for ${requirements.powerSource} with sleep modes`);
} else {
score += 10;
matchReasons.push(`Usable with aggressive power management`);
}
} else {
score += 20;
matchReasons.push(`Compatible with ${requirements.powerSource}`);
}
} else {
failReasons.push(`Not recommended for ${requirements.powerSource}`);
meetsRequirements = false;
}
// 4. Environment Compatibility (15 points)
maxScore += 15;
if (mcu.environments.includes(requirements.environment)) {
score += 15;
matchReasons.push(`Rated for ${requirements.environment} use`);
} else {
failReasons.push(`Not rated for ${requirements.environment} environment`);
meetsRequirements = false;
}
// 5. Budget Fit (15 points)
maxScore += 15;
const totalCost = mcu.boardCostUSD + mcu.additionalComponents.reduce((sum, c) => sum + c.cost, 0);
if (totalCost <= requirements.budgetMax) {
const budgetEfficiency = 1 - (totalCost / requirements.budgetMax);
score += Math.floor(15 * (0.5 + 0.5 * budgetEfficiency));
matchReasons.push(`Within budget ($${totalCost.toFixed(2)} of $${requirements.budgetMax})`);
} else {
failReasons.push(`Over budget ($${totalCost.toFixed(2)} > $${requirements.budgetMax})`);
meetsRequirements = false;
}
// Calculate normalized score
const normalizedScore = (score / maxScore) * 100;
results.push({
...mcu,
meetsRequirements,
failReasons,
matchReasons,
score: normalizedScore,
totalCost: totalCost,
scores: {
processing: mcu.mips,
memory: mcu.ramKB,
powerEfficiency: 100 - (mcu.powerActiveMA / 100 * 100),
costEfficiency: 100 - (totalCost / 50 * 100),
connectivity: mcu.connectivity.length * 25,
ecosystem: (mcu.ecosystem.libraries + mcu.ecosystem.community) / 2
}
});
});
// Sort by score, prioritizing those meeting requirements
results.sort((a, b) => {
if (a.meetsRequirements !== b.meetsRequirements) {
return b.meetsRequirements - a.meetsRequirements;
}
return b.score - a.score;
});
state.recommendations = results;
return results;
}
// ---------------------------------------------------------------------------
// CREATE CONTAINER
// ---------------------------------------------------------------------------
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", `${config.width}px`)
.style("margin", "0 auto");
// ---------------------------------------------------------------------------
// REQUIREMENTS PANEL
// ---------------------------------------------------------------------------
const reqPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
reqPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">1</span> Project Requirements`);
const reqGrid = reqPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(200px, 1fr))")
.style("gap", "15px");
// Processing Level Selection
const procGroup = reqGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `1px solid ${config.colors.gray}`);
procGroup.append("label")
.style("display", "block")
.style("margin-bottom", "10px")
.style("color", config.colors.navy)
.style("font-weight", "700")
.style("font-size", "13px")
.text("Processing Power");
const procOptions = procGroup.append("div")
.style("display", "flex")
.style("flex-direction", "column")
.style("gap", "8px");
Object.entries(config.processingLevels).forEach(([key, level]) => {
const option = procOptions.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("cursor", "pointer")
.style("padding", "8px")
.style("background", state.requirements.processingLevel === key ? config.colors.teal + "20" : "transparent")
.style("border-radius", "6px")
.style("transition", "background 0.2s");
option.append("input")
.attr("type", "radio")
.attr("name", "processing")
.attr("value", key)
.property("checked", state.requirements.processingLevel === key)
.style("accent-color", config.colors.teal)
.on("change", function() {
state.requirements.processingLevel = key;
updateAll();
});
const labelDiv = option.append("div");
labelDiv.append("div")
.style("font-weight", "600")
.style("font-size", "12px")
.style("color", config.colors.navy)
.text(level.label);
labelDiv.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.text(level.description);
});
// Connectivity Options
const connGroup = reqGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `1px solid ${config.colors.gray}`);
connGroup.append("label")
.style("display", "block")
.style("margin-bottom", "10px")
.style("color", config.colors.navy)
.style("font-weight", "700")
.style("font-size", "13px")
.text("Required Connectivity");
const connOptions = connGroup.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "8px");
const connectivityTypes = [
{ id: "wifi", name: "Wi-Fi", color: config.colors.red },
{ id: "ble", name: "BLE", color: config.colors.blue },
{ id: "lora", name: "LoRa", color: config.colors.orange },
{ id: "cellular", name: "Cellular", color: config.colors.purple },
{ id: "zigbee", name: "Zigbee", color: config.colors.green }
];
connectivityTypes.forEach(conn => {
const label = connOptions.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "6px")
.style("cursor", "pointer")
.style("padding", "6px 12px")
.style("background", config.colors.lightGray)
.style("border-radius", "20px")
.style("font-size", "12px")
.style("transition", "all 0.2s")
.style("border", `2px solid transparent`);
const checkbox = label.append("input")
.attr("type", "checkbox")
.style("accent-color", conn.color)
.on("change", function() {
if (this.checked) {
state.requirements.connectivity.push(conn.id);
label.style("border-color", conn.color).style("background", conn.color + "20");
} else {
state.requirements.connectivity = state.requirements.connectivity.filter(c => c !== conn.id);
label.style("border-color", "transparent").style("background", config.colors.lightGray);
}
updateAll();
});
label.append("span")
.style("color", config.colors.navy)
.style("font-weight", "500")
.text(conn.name);
});
// Power Source Selection
const powerGroup = reqGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `1px solid ${config.colors.gray}`);
powerGroup.append("label")
.style("display", "block")
.style("margin-bottom", "10px")
.style("color", config.colors.navy)
.style("font-weight", "700")
.style("font-size", "13px")
.text("Power Source");
const powerOptions = powerGroup.append("div")
.style("display", "flex")
.style("flex-direction", "column")
.style("gap", "8px");
Object.entries(config.powerSources).forEach(([key, source]) => {
const option = powerOptions.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("cursor", "pointer")
.style("padding", "8px")
.style("border-radius", "6px");
option.append("input")
.attr("type", "radio")
.attr("name", "power")
.attr("value", key)
.property("checked", state.requirements.powerSource === key)
.style("accent-color", config.colors.orange)
.on("change", function() {
state.requirements.powerSource = key;
updateAll();
});
option.append("span")
.style("font-size", "16px")
.text(source.icon);
option.append("span")
.style("font-size", "12px")
.style("color", config.colors.navy)
.style("font-weight", "500")
.text(source.label);
});
// Environment Selection
const envGroup = reqGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `1px solid ${config.colors.gray}`);
envGroup.append("label")
.style("display", "block")
.style("margin-bottom", "10px")
.style("color", config.colors.navy)
.style("font-weight", "700")
.style("font-size", "13px")
.text("Operating Environment");
const envOptions = envGroup.append("div")
.style("display", "flex")
.style("flex-direction", "column")
.style("gap", "8px");
Object.entries(config.environments).forEach(([key, env]) => {
const option = envOptions.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("cursor", "pointer")
.style("padding", "8px")
.style("border-radius", "6px");
option.append("input")
.attr("type", "radio")
.attr("name", "environment")
.attr("value", key)
.property("checked", state.requirements.environment === key)
.style("accent-color", config.colors.green)
.on("change", function() {
state.requirements.environment = key;
updateAll();
});
const labelDiv = option.append("div");
labelDiv.append("div")
.style("font-weight", "600")
.style("font-size", "12px")
.style("color", config.colors.navy)
.text(env.label);
labelDiv.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.text(env.tempRange);
});
// Budget Slider
const budgetGroup = reqGrid.append("div")
.style("background", config.colors.white)
.style("padding", "15px")
.style("border-radius", "8px")
.style("border", `1px solid ${config.colors.gray}`)
.style("grid-column", "span 2");
const budgetHeader = budgetGroup.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "10px");
budgetHeader.append("label")
.style("color", config.colors.navy)
.style("font-weight", "700")
.style("font-size", "13px")
.text("Budget Range");
const budgetValue = budgetHeader.append("span")
.style("font-size", "18px")
.style("font-weight", "bold")
.style("color", config.colors.teal)
.text(`$${state.requirements.budgetMax}`);
const budgetSlider = budgetGroup.append("input")
.attr("type", "range")
.attr("min", 5)
.attr("max", 100)
.attr("value", state.requirements.budgetMax)
.style("width", "100%")
.style("height", "8px")
.style("cursor", "pointer")
.style("accent-color", config.colors.teal);
budgetSlider.on("input", function() {
state.requirements.budgetMax = +this.value;
budgetValue.text(`$${state.requirements.budgetMax}`);
updateAll();
});
const budgetLabels = budgetGroup.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("margin-top", "5px")
.style("font-size", "10px")
.style("color", config.colors.gray);
budgetLabels.append("span").text("$5");
budgetLabels.append("span").text("$50");
budgetLabels.append("span").text("$100");
// ---------------------------------------------------------------------------
// RADAR CHART COMPARISON
// ---------------------------------------------------------------------------
const radarPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
radarPanel.append("h3")
.style("margin", "0 0 5px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">2</span> Top 3 Recommendations Comparison`);
radarPanel.append("p")
.style("margin", "0 0 15px 0")
.style("color", config.colors.gray)
.style("font-size", "12px")
.text("Radar chart comparing the top recommended MCUs across six key dimensions.");
const radarSvg = radarPanel.append("svg")
.attr("viewBox", "0 0 900 380")
.attr("width", "100%");
const radarCenterX = 380;
const radarCenterY = 190;
const radarRadius = 140;
const radarG = radarSvg.append("g")
.attr("transform", `translate(${radarCenterX}, ${radarCenterY})`);
// Radar dimensions
const radarDimensions = [
{ key: "processing", label: "Processing", max: 600 },
{ key: "memory", label: "Memory", max: 520 },
{ key: "powerEfficiency", label: "Power Eff.", max: 100 },
{ key: "costEfficiency", label: "Cost Eff.", max: 100 },
{ key: "connectivity", label: "Connectivity", max: 100 },
{ key: "ecosystem", label: "Ecosystem", max: 100 }
];
const angleSlice = (Math.PI * 2) / radarDimensions.length;
// Draw radar grid
function drawRadarGrid() {
radarG.selectAll(".radar-grid").remove();
const gridG = radarG.append("g").attr("class", "radar-grid");
// Concentric circles
[0.2, 0.4, 0.6, 0.8, 1.0].forEach(level => {
gridG.append("circle")
.attr("r", radarRadius * level)
.attr("fill", "none")
.attr("stroke", config.colors.gray)
.attr("stroke-width", 0.5)
.attr("stroke-dasharray", "3,3");
});
// Axis lines and labels
radarDimensions.forEach((dim, i) => {
const angle = angleSlice * i - Math.PI / 2;
const x = Math.cos(angle) * radarRadius;
const y = Math.sin(angle) * radarRadius;
gridG.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", x)
.attr("y2", y)
.attr("stroke", config.colors.gray)
.attr("stroke-width", 1);
// Labels
const labelRadius = radarRadius + 25;
const labelX = Math.cos(angle) * labelRadius;
const labelY = Math.sin(angle) * labelRadius;
gridG.append("text")
.attr("x", labelX)
.attr("y", labelY)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("fill", config.colors.navy)
.attr("font-size", "11px")
.attr("font-weight", "600")
.text(dim.label);
});
}
drawRadarGrid();
// Radar paths group
const radarPathsG = radarG.append("g").attr("class", "radar-paths");
// Radar legend
const radarLegendG = radarSvg.append("g")
.attr("class", "radar-legend")
.attr("transform", "translate(680, 60)");
// ---------------------------------------------------------------------------
// RECOMMENDATION CARDS
// ---------------------------------------------------------------------------
const recPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
recPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">3</span> Detailed Recommendations`);
const recContainer = recPanel.append("div")
.attr("class", "rec-container");
// ---------------------------------------------------------------------------
// BOM GENERATOR
// ---------------------------------------------------------------------------
const bomPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
bomPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">4</span> Bill of Materials Generator`);
const bomContainer = bomPanel.append("div")
.attr("class", "bom-container");
// ---------------------------------------------------------------------------
// POWER CONSUMPTION ESTIMATE
// ---------------------------------------------------------------------------
const powerPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "15px")
.style("border", `2px solid ${config.colors.navy}`);
powerPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">5</span> Power Consumption Estimate`);
const powerContainer = powerPanel.append("div")
.attr("class", "power-container");
// ---------------------------------------------------------------------------
// DEVELOPMENT COMPLEXITY
// ---------------------------------------------------------------------------
const devPanel = container.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("border", `2px solid ${config.colors.navy}`);
devPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "18px")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.html(`<span style="background:${config.colors.navy};color:white;padding:4px 10px;border-radius:4px;">6</span> Development Complexity Indicator`);
const devContainer = devPanel.append("div")
.attr("class", "dev-container");
// ---------------------------------------------------------------------------
// UPDATE FUNCTIONS
// ---------------------------------------------------------------------------
function updateAll() {
calculateMCUScores();
updateRadarChart();
updateRecommendations();
updateBOM();
updatePowerEstimate();
updateDevComplexity();
}
function updateRadarChart() {
radarPathsG.selectAll("*").remove();
radarLegendG.selectAll("*").remove();
const top3 = state.recommendations.filter(m => m.meetsRequirements).slice(0, 3);
if (top3.length === 0) {
radarG.selectAll(".no-data").remove();
radarG.append("text")
.attr("class", "no-data")
.attr("text-anchor", "middle")
.attr("fill", config.colors.gray)
.attr("font-size", "14px")
.text("No MCUs meet all requirements");
return;
}
radarG.selectAll(".no-data").remove();
top3.forEach((mcu, idx) => {
// Calculate radar values (normalized to 0-1)
const values = radarDimensions.map(dim => {
let val = 0;
switch (dim.key) {
case "processing":
val = Math.min(mcu.mips / dim.max, 1);
break;
case "memory":
val = Math.min(mcu.ramKB / dim.max, 1);
break;
case "powerEfficiency":
val = Math.max(0, 1 - (mcu.powerActiveMA / 100));
break;
case "costEfficiency":
val = Math.max(0, 1 - (mcu.totalCost / 50));
break;
case "connectivity":
val = Math.min(mcu.connectivity.length / 4, 1);
break;
case "ecosystem":
val = (mcu.ecosystem.libraries + mcu.ecosystem.community) / 200;
break;
}
return Math.max(0.05, Math.min(1, val));
});
// Create path
const pathData = values.map((v, i) => {
const angle = angleSlice * i - Math.PI / 2;
const x = Math.cos(angle) * radarRadius * v;
const y = Math.sin(angle) * radarRadius * v;
return `${i === 0 ? "M" : "L"} ${x} ${y}`;
}).join(" ") + " Z";
radarPathsG.append("path")
.attr("d", pathData)
.attr("fill", mcu.color)
.attr("fill-opacity", 0.15)
.attr("stroke", mcu.color)
.attr("stroke-width", 2.5);
// Draw points
values.forEach((v, i) => {
const angle = angleSlice * i - Math.PI / 2;
const x = Math.cos(angle) * radarRadius * v;
const y = Math.sin(angle) * radarRadius * v;
radarPathsG.append("circle")
.attr("cx", x)
.attr("cy", y)
.attr("r", 5)
.attr("fill", mcu.color)
.attr("stroke", config.colors.white)
.attr("stroke-width", 2);
});
// Legend item
const lg = radarLegendG.append("g")
.attr("transform", `translate(0, ${idx * 45})`);
lg.append("rect")
.attr("width", 24)
.attr("height", 24)
.attr("rx", 4)
.attr("fill", mcu.color);
lg.append("text")
.attr("x", 32)
.attr("y", 12)
.attr("fill", config.colors.navy)
.attr("font-size", "14px")
.attr("font-weight", "700")
.attr("dominant-baseline", "middle")
.text(mcu.name);
lg.append("text")
.attr("x", 32)
.attr("y", 30)
.attr("fill", config.colors.gray)
.attr("font-size", "11px")
.text(`Score: ${mcu.score.toFixed(0)}%`);
});
}
function updateRecommendations() {
recContainer.selectAll("*").remove();
const top3 = state.recommendations.filter(m => m.meetsRequirements).slice(0, 3);
const notMeeting = state.recommendations.filter(m => !m.meetsRequirements);
if (top3.length === 0) {
recContainer.append("div")
.style("padding", "30px")
.style("text-align", "center")
.style("background", config.colors.red + "10")
.style("border-radius", "8px")
.style("border", `2px dashed ${config.colors.red}`)
.html(`<div style="font-size:24px;margin-bottom:10px;">⚠️</div>
<strong style="color:${config.colors.red}">No MCUs meet all requirements</strong><br>
<span style="font-size: 12px; color: ${config.colors.gray}">
Try relaxing constraints: increase budget, remove connectivity requirements, or choose less demanding environment</span>`);
return;
}
const recGrid = recContainer.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(280px, 1fr))")
.style("gap", "15px");
top3.forEach((mcu, idx) => {
const rank = idx + 1;
const medalColor = rank === 1 ? "#FFD700" : rank === 2 ? "#C0C0C0" : "#CD7F32";
const medalEmoji = rank === 1 ? "🥇" : rank === 2 ? "🥈" : "🥉";
const card = recGrid.append("div")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("padding", "20px")
.style("border", `3px solid ${mcu.color}`)
.style("position", "relative")
.style("cursor", "pointer")
.style("transition", "transform 0.2s, box-shadow 0.2s")
.on("mouseenter", function() {
d3.select(this).style("transform", "translateY(-4px)").style("box-shadow", `0 8px 20px ${mcu.color}40`);
})
.on("mouseleave", function() {
d3.select(this).style("transform", "translateY(0)").style("box-shadow", "none");
})
.on("click", function() {
state.selectedMCU = mcu;
updateBOM();
updatePowerEstimate();
updateDevComplexity();
});
// Rank badge
card.append("div")
.style("position", "absolute")
.style("top", "-15px")
.style("left", "20px")
.style("background", medalColor)
.style("color", rank === 2 ? config.colors.navy : config.colors.white)
.style("padding", "6px 14px")
.style("border-radius", "20px")
.style("font-size", "14px")
.style("font-weight", "bold")
.style("box-shadow", "0 2px 8px rgba(0,0,0,0.2)")
.text(`${medalEmoji} #${rank}`);
// Header
const header = card.append("div")
.style("margin-top", "15px")
.style("margin-bottom", "15px");
header.append("div")
.style("font-size", "22px")
.style("font-weight", "bold")
.style("color", mcu.color)
.text(mcu.name);
header.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.text(mcu.manufacturer);
// Score bar
const scoreContainer = card.append("div")
.style("margin-bottom", "15px");
scoreContainer.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("margin-bottom", "5px");
scoreContainer.select("div").append("span")
.style("font-size", "11px")
.style("color", config.colors.gray)
.text("Match Score");
scoreContainer.select("div").append("span")
.style("font-size", "14px")
.style("font-weight", "bold")
.style("color", mcu.color)
.text(`${mcu.score.toFixed(0)}%`);
const scoreBar = scoreContainer.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "6px")
.style("height", "10px")
.style("overflow", "hidden");
scoreBar.append("div")
.style("background", `linear-gradient(90deg, ${mcu.color}, ${mcu.color}cc)`)
.style("height", "100%")
.style("width", `${mcu.score}%`)
.style("transition", "width 0.5s ease-out");
// Key specs
const specsGrid = card.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr")
.style("gap", "10px")
.style("margin-bottom", "15px")
.style("padding", "12px")
.style("background", config.colors.lightGray)
.style("border-radius", "8px");
[
{ label: "Clock", value: `${mcu.clockMHz} MHz` },
{ label: "RAM", value: `${mcu.ramKB} KB` },
{ label: "Power", value: `${mcu.powerActiveMA} mA` },
{ label: "Cost", value: `$${mcu.totalCost.toFixed(2)}` }
].forEach(spec => {
const specItem = specsGrid.append("div");
specItem.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.style("text-transform", "uppercase")
.text(spec.label);
specItem.append("div")
.style("font-size", "14px")
.style("color", config.colors.navy)
.style("font-weight", "600")
.text(spec.value);
});
// Connectivity badges
if (mcu.connectivity.length > 0) {
const connRow = card.append("div")
.style("margin-bottom", "12px");
connRow.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.style("margin-bottom", "6px")
.style("text-transform", "uppercase")
.text("Connectivity");
const badges = connRow.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "6px");
mcu.connectivity.forEach(conn => {
const connType = connectivityTypes.find(c => c.id === conn);
badges.append("span")
.style("background", connType ? connType.color + "20" : config.colors.lightGray)
.style("color", connType ? connType.color : config.colors.navy)
.style("padding", "4px 10px")
.style("border-radius", "12px")
.style("font-size", "11px")
.style("font-weight", "600")
.style("text-transform", "uppercase")
.text(conn);
});
} else {
card.append("div")
.style("margin-bottom", "12px")
.style("padding", "8px")
.style("background", config.colors.yellow + "20")
.style("border-radius", "6px")
.style("font-size", "11px")
.style("color", config.colors.orange)
.text("⚡ No built-in wireless - requires external module");
}
// Match reasons
if (mcu.matchReasons.length > 0) {
const matchDiv = card.append("div")
.style("margin-bottom", "10px");
matchDiv.append("div")
.style("font-size", "10px")
.style("color", config.colors.green)
.style("font-weight", "700")
.style("margin-bottom", "6px")
.style("text-transform", "uppercase")
.text("Why It Matches");
mcu.matchReasons.slice(0, 3).forEach(reason => {
matchDiv.append("div")
.style("font-size", "11px")
.style("color", config.colors.navy)
.style("padding-left", "12px")
.style("margin-bottom", "3px")
.html(`<span style="color:${config.colors.green}">✓</span> ${reason}`);
});
}
// Use cases
card.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.style("border-top", `1px solid ${config.colors.lightGray}`)
.style("padding-top", "10px")
.style("margin-top", "10px")
.html(`<strong>Best for:</strong> ${mcu.useCases.slice(0, 3).join(", ")}`);
});
// Not meeting requirements section
if (notMeeting.length > 0) {
const notMeetingDiv = recContainer.append("div")
.style("margin-top", "20px")
.style("padding", "15px")
.style("background", config.colors.white)
.style("border-radius", "8px")
.style("border", `2px dashed ${config.colors.gray}`);
notMeetingDiv.append("div")
.style("font-size", "13px")
.style("font-weight", "700")
.style("color", config.colors.gray)
.style("margin-bottom", "12px")
.text("Not Meeting All Requirements:");
const notGrid = notMeetingDiv.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(250px, 1fr))")
.style("gap", "10px");
notMeeting.forEach(mcu => {
const item = notGrid.append("div")
.style("padding", "10px")
.style("background", config.colors.lightGray)
.style("border-radius", "6px")
.style("border-left", `4px solid ${mcu.color}`);
item.append("div")
.style("font-weight", "600")
.style("color", mcu.color)
.style("font-size", "13px")
.style("margin-bottom", "4px")
.text(mcu.name);
mcu.failReasons.forEach(reason => {
item.append("div")
.style("font-size", "11px")
.style("color", config.colors.red)
.html(`✗ ${reason}`);
});
});
}
}
function updateBOM() {
bomContainer.selectAll("*").remove();
const selectedMCU = state.selectedMCU || state.recommendations.find(m => m.meetsRequirements);
if (!selectedMCU) {
bomContainer.append("div")
.style("text-align", "center")
.style("padding", "20px")
.style("color", config.colors.gray)
.text("Select an MCU from recommendations to generate BOM");
return;
}
bomContainer.append("div")
.style("margin-bottom", "15px")
.style("padding", "10px")
.style("background", selectedMCU.color + "10")
.style("border-radius", "8px")
.style("border-left", `4px solid ${selectedMCU.color}`)
.html(`<strong style="color:${selectedMCU.color}">${selectedMCU.name}</strong> Bill of Materials`);
const bomTable = bomContainer.append("table")
.style("width", "100%")
.style("border-collapse", "collapse")
.style("font-size", "13px");
const bomHeader = bomTable.append("thead").append("tr")
.style("background", config.colors.navy)
.style("color", config.colors.white);
["Component", "Quantity", "Unit Price", "Subtotal"].forEach(col => {
bomHeader.append("th")
.style("padding", "12px")
.style("text-align", "left")
.style("font-weight", "600")
.text(col);
});
const bomBody = bomTable.append("tbody");
let totalCost = 0;
// Main MCU/Board
const boardRow = bomBody.append("tr")
.style("background", config.colors.lightGray);
boardRow.append("td").style("padding", "10px").style("font-weight", "600").text(`${selectedMCU.name} Development Board`);
boardRow.append("td").style("padding", "10px").text("1");
boardRow.append("td").style("padding", "10px").text(`$${selectedMCU.boardCostUSD.toFixed(2)}`);
boardRow.append("td").style("padding", "10px").style("font-weight", "600").text(`$${selectedMCU.boardCostUSD.toFixed(2)}`);
totalCost += selectedMCU.boardCostUSD;
// Additional components
selectedMCU.additionalComponents.forEach((comp, idx) => {
const row = bomBody.append("tr")
.style("background", idx % 2 === 0 ? config.colors.white : config.colors.lightGray);
row.append("td").style("padding", "10px").text(comp.name);
row.append("td").style("padding", "10px").text("1");
row.append("td").style("padding", "10px").text(`$${comp.cost.toFixed(2)}`);
row.append("td").style("padding", "10px").text(`$${comp.cost.toFixed(2)}`);
totalCost += comp.cost;
});
// Total row
const totalRow = bomBody.append("tr")
.style("background", config.colors.navy)
.style("color", config.colors.white);
totalRow.append("td").attr("colspan", 3).style("padding", "12px").style("font-weight", "700").style("text-align", "right").text("TOTAL:");
totalRow.append("td").style("padding", "12px").style("font-weight", "700").style("font-size", "16px").text(`$${totalCost.toFixed(2)}`);
// Volume pricing note
bomContainer.append("div")
.style("margin-top", "15px")
.style("padding", "12px")
.style("background", config.colors.teal + "10")
.style("border-radius", "8px")
.style("font-size", "12px")
.style("color", config.colors.teal)
.html(`<strong>💡 Volume Pricing:</strong> At 1000+ units, expect ~30-50% cost reduction. MCU chip only: $${selectedMCU.costUSD.toFixed(2)}`);
}
function updatePowerEstimate() {
powerContainer.selectAll("*").remove();
const selectedMCU = state.selectedMCU || state.recommendations.find(m => m.meetsRequirements);
if (!selectedMCU) {
powerContainer.append("div")
.style("text-align", "center")
.style("padding", "20px")
.style("color", config.colors.gray)
.text("Select an MCU from recommendations to see power estimates");
return;
}
const powerGrid = powerContainer.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(200px, 1fr))")
.style("gap", "15px")
.style("margin-bottom", "20px");
// Active power
const activeCard = powerGrid.append("div")
.style("background", config.colors.red + "10")
.style("padding", "15px")
.style("border-radius", "8px")
.style("text-align", "center");
activeCard.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.style("margin-bottom", "5px")
.text("Active Mode");
activeCard.append("div")
.style("font-size", "28px")
.style("font-weight", "bold")
.style("color", config.colors.red)
.text(`${selectedMCU.powerActiveMA} mA`);
activeCard.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.text(`@ ${selectedMCU.voltage}V = ${(selectedMCU.powerActiveMA * selectedMCU.voltage).toFixed(0)} mW`);
// Sleep power
const sleepCard = powerGrid.append("div")
.style("background", config.colors.orange + "10")
.style("padding", "15px")
.style("border-radius", "8px")
.style("text-align", "center");
sleepCard.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.style("margin-bottom", "5px")
.text("Light Sleep");
sleepCard.append("div")
.style("font-size", "28px")
.style("font-weight", "bold")
.style("color", config.colors.orange)
.text(`${(selectedMCU.powerSleepMA * 1000).toFixed(1)} µA`);
// Deep sleep power
const deepCard = powerGrid.append("div")
.style("background", config.colors.green + "10")
.style("padding", "15px")
.style("border-radius", "8px")
.style("text-align", "center");
deepCard.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.style("margin-bottom", "5px")
.text("Deep Sleep");
deepCard.append("div")
.style("font-size", "28px")
.style("font-weight", "bold")
.style("color", config.colors.green)
.text(`${selectedMCU.powerDeepSleepUA} µA`);
// Battery life calculator
const batteryCalc = powerContainer.append("div")
.style("background", config.colors.lightGray)
.style("padding", "20px")
.style("border-radius", "8px");
batteryCalc.append("div")
.style("font-weight", "700")
.style("color", config.colors.navy)
.style("margin-bottom", "15px")
.text("Battery Life Estimates (2000 mAh battery)");
const scenarios = [
{ name: "Always Active", activePercent: 100, description: "Continuous operation" },
{ name: "10% Duty Cycle", activePercent: 10, description: "Active 6 min/hour" },
{ name: "1% Duty Cycle", activePercent: 1, description: "Active 36 sec/hour" },
{ name: "Deep Sleep Mode", activePercent: 0, description: "Wake on interrupt only" }
];
const batteryMAh = 2000;
const scenarioGrid = batteryCalc.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(180px, 1fr))")
.style("gap", "10px");
scenarios.forEach(scenario => {
const avgCurrent = scenario.activePercent === 0
? selectedMCU.powerDeepSleepUA / 1000
: (selectedMCU.powerActiveMA * scenario.activePercent / 100) +
(selectedMCU.powerSleepMA * (100 - scenario.activePercent) / 100);
const hoursLife = batteryMAh / avgCurrent;
const daysLife = hoursLife / 24;
const yearsLife = daysLife / 365;
let lifeDisplay, lifeUnit;
if (yearsLife >= 1) {
lifeDisplay = yearsLife.toFixed(1);
lifeUnit = "years";
} else if (daysLife >= 1) {
lifeDisplay = daysLife.toFixed(0);
lifeUnit = "days";
} else {
lifeDisplay = hoursLife.toFixed(0);
lifeUnit = "hours";
}
const card = scenarioGrid.append("div")
.style("background", config.colors.white)
.style("padding", "12px")
.style("border-radius", "6px")
.style("text-align", "center");
card.append("div")
.style("font-size", "11px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.text(scenario.name);
card.append("div")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("color", config.colors.teal)
.style("margin", "5px 0")
.text(lifeDisplay);
card.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.text(lifeUnit);
card.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.style("margin-top", "5px")
.text(scenario.description);
});
}
function updateDevComplexity() {
devContainer.selectAll("*").remove();
const selectedMCU = state.selectedMCU || state.recommendations.find(m => m.meetsRequirements);
if (!selectedMCU) {
devContainer.append("div")
.style("text-align", "center")
.style("padding", "20px")
.style("color", config.colors.gray)
.text("Select an MCU from recommendations to see development complexity");
return;
}
const devGrid = devContainer.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(250px, 1fr))")
.style("gap", "20px");
// Complexity meter
const complexityCard = devGrid.append("div")
.style("background", config.colors.lightGray)
.style("padding", "20px")
.style("border-radius", "8px");
complexityCard.append("div")
.style("font-weight", "700")
.style("color", config.colors.navy)
.style("margin-bottom", "15px")
.text("Development Complexity");
const complexityLevels = ["Beginner", "Easy", "Moderate", "Advanced", "Expert"];
const complexityColors = [config.colors.green, config.colors.teal, config.colors.orange, config.colors.red, config.colors.purple];
const meterContainer = complexityCard.append("div")
.style("margin-bottom", "15px");
const meter = meterContainer.append("div")
.style("display", "flex")
.style("gap", "4px")
.style("height", "20px");
for (let i = 1; i <= 5; i++) {
meter.append("div")
.style("flex", "1")
.style("background", i <= selectedMCU.developmentComplexity ? complexityColors[selectedMCU.developmentComplexity - 1] : config.colors.white)
.style("border-radius", i === 1 ? "4px 0 0 4px" : i === 5 ? "0 4px 4px 0" : "0")
.style("border", `1px solid ${config.colors.gray}`);
}
meterContainer.append("div")
.style("text-align", "center")
.style("margin-top", "8px")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("color", complexityColors[selectedMCU.developmentComplexity - 1])
.text(complexityLevels[selectedMCU.developmentComplexity - 1]);
// Learning curve description
const curveMap = {
easy: "Quick start with Arduino-style coding. Abundant tutorials and examples available.",
moderate: "Some embedded systems knowledge helpful. Good documentation available.",
steep: "Requires understanding of embedded systems, registers, and toolchains."
};
complexityCard.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.style("line-height", "1.5")
.text(curveMap[selectedMCU.ecosystem.learningCurve]);
// IDE and Tools
const toolsCard = devGrid.append("div")
.style("background", config.colors.lightGray)
.style("padding", "20px")
.style("border-radius", "8px");
toolsCard.append("div")
.style("font-weight", "700")
.style("color", config.colors.navy)
.style("margin-bottom", "15px")
.text("Development Tools");
toolsCard.append("div")
.style("font-size", "11px")
.style("color", config.colors.gray)
.style("margin-bottom", "8px")
.style("text-transform", "uppercase")
.text("Supported IDEs");
const ideList = toolsCard.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "6px")
.style("margin-bottom", "15px");
selectedMCU.ecosystem.ide.forEach(ide => {
ideList.append("span")
.style("background", config.colors.white)
.style("padding", "4px 10px")
.style("border-radius", "12px")
.style("font-size", "11px")
.style("color", config.colors.navy)
.text(ide);
});
// Ecosystem scores
const scoresDiv = toolsCard.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr")
.style("gap", "10px");
[
{ label: "Libraries", value: selectedMCU.ecosystem.libraries, color: config.colors.teal },
{ label: "Community", value: selectedMCU.ecosystem.community, color: config.colors.orange },
{ label: "Documentation", value: selectedMCU.ecosystem.documentation, color: config.colors.purple }
].forEach(metric => {
const metricDiv = scoresDiv.append("div");
metricDiv.append("div")
.style("font-size", "10px")
.style("color", config.colors.gray)
.style("margin-bottom", "4px")
.text(metric.label);
const barBg = metricDiv.append("div")
.style("background", config.colors.white)
.style("height", "8px")
.style("border-radius", "4px")
.style("overflow", "hidden");
barBg.append("div")
.style("background", metric.color)
.style("height", "100%")
.style("width", `${metric.value}%`);
metricDiv.append("div")
.style("font-size", "11px")
.style("color", config.colors.navy)
.style("margin-top", "2px")
.text(`${metric.value}/100`);
});
// Getting started tip
devContainer.append("div")
.style("margin-top", "15px")
.style("padding", "15px")
.style("background", config.colors.teal + "10")
.style("border-radius", "8px")
.style("border-left", `4px solid ${config.colors.teal}`)
.style("font-size", "12px")
.style("color", config.colors.navy)
.html(`<strong>💡 Getting Started:</strong> For ${selectedMCU.name}, we recommend starting with
<strong>${selectedMCU.ecosystem.ide[0]}</strong>.
${selectedMCU.ecosystem.learningCurve === "easy"
? "Upload your first blink sketch within minutes!"
: selectedMCU.ecosystem.learningCurve === "moderate"
? "Check manufacturer examples for quick start guides."
: "Review the hardware abstraction layer documentation first."}`);
}
// ---------------------------------------------------------------------------
// INITIALIZE
// ---------------------------------------------------------------------------
updateAll();
return container.node();
}1625.2 Understanding Hardware Selection
1625.2.1 Key Selection Criteria
When choosing a microcontroller for IoT applications, consider these primary factors:
| Criterion | Description | Typical Range |
|---|---|---|
| Processing Power | Clock speed and MIPS | 16-240 MHz |
| Memory (RAM) | Working memory | 2-520 KB |
| Flash Storage | Program storage | 32 KB - 16 MB |
| Power Consumption | Active mode current | 5-100 mA |
| Cost | Unit price | $1-15 USD |
| Connectivity | Built-in wireless | Wi-Fi, BLE, LoRa |
1625.2.2 MCU Comparison Summary
This decision tree guides hardware selection based on primary requirements.
%% fig-alt: Decision tree for MCU selection showing decision points for Wi-Fi need, power constraints, processing requirements, and budget, leading to specific MCU recommendations including ESP32, ESP8266, nRF52, STM32, Arduino Nano, and Raspberry Pi Pico.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
flowchart TD
START[Select MCU] --> Wi-Fi{Need<br/>Wi-Fi?}
Wi-Fi -->|Yes| POWER{Battery<br/>Critical?}
POWER -->|Yes| BLE{Also Need<br/>BLE?}
BLE -->|Yes| ESP32[ESP32<br/>Wi-Fi + BLE]
BLE -->|No| ESP8266[ESP8266<br/>Budget Wi-Fi]
POWER -->|No| PERF{High<br/>Performance?}
PERF -->|Yes| ESP32_HP[ESP32<br/>Dual Core 240MHz]
PERF -->|No| ESP8266_2[ESP8266<br/>Cost Effective]
Wi-Fi -->|No| LOWPWR{Ultra-Low<br/>Power?}
LOWPWR -->|Yes| NRF52[nRF52840<br/>0.4µA Sleep]
LOWPWR -->|No| INDUST{Industrial<br/>Grade?}
INDUST -->|Yes| STM32[STM32F4<br/>Wide Temp Range]
INDUST -->|No| BUDGET{Budget<br/>Under $5?}
BUDGET -->|Yes| PICO[RP2040<br/>$1 Dual Core]
BUDGET -->|No| NANO[Arduino Nano<br/>Easy Learning]
style START fill:#2C3E50,color:#fff
style ESP32 fill:#E74C3C,color:#fff
style ESP8266 fill:#3498DB,color:#fff
style NRF52 fill:#16A085,color:#fff
style STM32 fill:#9B59B6,color:#fff
style PICO fill:#E67E22,color:#fff
style NANO fill:#27AE60,color:#fff
1625.2.3 Application-Specific Recommendations
| Application | Recommended MCU | Key Reason |
|---|---|---|
| Battery Wearable | nRF52840 | Ultra-low power, excellent BLE |
| Smart Home Hub | ESP32 | Wi-Fi + BLE, good processing |
| Industrial Sensor | STM32F4 | Reliable, wide temp range |
| Education Project | Arduino Nano | Huge community, simple |
| Cost-Sensitive | RP2040 | Best performance per dollar |
| Wi-Fi Sensor | ESP8266 | Cheap, proven, easy |
1625.3 What’s Next
Explore related hardware and design topics:
- Prototyping Hardware - Development boards and modules
- Reading a Spec Sheet - Understanding MCU datasheets
- Power Budget Calculator - Detailed battery life analysis
- Energy-Aware Considerations - Power optimization strategies
Interactive tool created for the IoT Class Textbook - HARDWARE-SEL-001