{"id":427,"date":"2025-11-16T14:40:11","date_gmt":"2025-11-16T14:40:11","guid":{"rendered":"http:\/\/www.amartkh.com\/store\/?p=427"},"modified":"2025-11-16T14:42:16","modified_gmt":"2025-11-16T14:42:16","slug":"nice-circuit-thats-a-4x4x4-led-cube-4-levels-x-16-columns","status":"publish","type":"post","link":"http:\/\/www.amartkh.com\/store\/2025\/11\/16\/nice-circuit-thats-a-4x4x4-led-cube-4-levels-x-16-columns\/","title":{"rendered":"Nice circuit \u2014 that\u2019s a 4\u00d74\u00d74 LED cube (4 levels \u00d7 16 columns)."},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Nice circuit \u2014 that\u2019s a 4\u00d74\u00d74 LED cube (4 levels \u00d7 16 columns). Below I give a complete Arduino UNO sketch you can upload, plus wiring notes and how to adapt pin mapping if your physical wiring is different.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Important wiring notes before the code<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The sketch assumes <strong>columns<\/strong> are connected individually to Arduino pins (through current-limiting resistors). Each column controls 4 LEDs (one per level).<\/li>\n\n\n\n<li>Each <strong>level (layer)<\/strong> is switched by an NPN transistor (or MOSFET) to <em>sink<\/em> the cathodes of that whole level. That means: to <em>light a LED<\/em>, set the corresponding column pin <strong>HIGH<\/strong> and turn <strong>ON<\/strong> (LOW for NPN base via resistor) the transistor for that layer to sink current \u2014 OR invert that logic if you wired it differently. I include a variable to change active logic if needed.<\/li>\n\n\n\n<li><strong>Use current-limiting resistors<\/strong> on columns (typically 220\u2013470 \u03a9) \u2014 one resistor per column is common.<\/li>\n\n\n\n<li>Avoid using Arduino pins 0 and 1 for normal IO if you need serial debugging (they are RX\/TX). The mapping below <em>does<\/em> use 0 and 1 for layers as an example; change them if you want serial.<\/li>\n\n\n\n<li>If you want a more robust design use transistors\/MOSFETs for layer switching and make sure the transistor base\/gate resistor and common ground are correct.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<figure class=\"wp-block-image size-large\"><img fetchpriority=\"high\" decoding=\"async\" width=\"872\" height=\"1024\" src=\"http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-872x1024.jpg\" alt=\"\" class=\"wp-image-428\" srcset=\"http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-872x1024.jpg 872w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-255x300.jpg 255w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-768x902.jpg 768w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-1308x1536.jpg 1308w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-1320x1550.jpg 1320w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n-600x705.jpg 600w, http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n.jpg 1744w\" sizes=\"(max-width: 872px) 100vw, 872px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Suggested pin mapping (change as needed)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I picked a mapping that uses 16 Arduino pins for columns and 4 for layers. <strong>If your wiring is different, edit the <code>cols[]<\/code> \/ <code>layers[]<\/code> arrays accordingly.<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Columns 0..15  -&gt; Arduino pins\nconst uint8_t cols&#91;16] = {\n  2, 3, 4, 5,    \/\/ columns 0..3  (row 0 x=0..3)\n  6, 7, 8, 9,    \/\/ columns 4..7  (row 1)\n  10,11,12,13,   \/\/ columns 8..11 (row 2)\n  A0,A1,A2,A3    \/\/ columns 12..15 (row 3)\n};\n\n\/\/ Layers z=0..3 -&gt; Arduino pins (these control transistors)\nconst uint8_t layers&#91;4] = { A4, A5, 0, 1 };\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to keep Serial (USB) for debugging, <strong>do not<\/strong> use pins <code>0<\/code> and <code>1<\/code>. Replace them with other free pins or reassign some column pins to free up digital pins for layers.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Sketch<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This sketch provides:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>setVoxel(x,y,z, on)<\/code> and <code>clearVoxel<\/code> helpers<\/li>\n\n\n\n<li>A frame buffer <code>cube[z][y]<\/code> (4 planes \u00d7 4 rows, each byte uses 4 LSB bits for 4 columns)<\/li>\n\n\n\n<li>Multiplexing by scanning layers quickly<\/li>\n\n\n\n<li>A few demo animations (fill, rotate, rain). You can add more.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Upload to Arduino UNO as-is (but update <code>cols<\/code>\/<code>layers<\/code> to match your wiring).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/*\n  4x4x4 LED cube Arduino sketch\n  - Adjust pin mapping in cols&#91;] and layers&#91;] to match wiring\n  - Columns should have current-limiting resistors (220-470 ohm)\n  - Layers are switched by transistors (NPN to GND). If you use PNP\/high-side, invert logic.\n*\/\n\nconst uint8_t cols&#91;16] = {\n  2, 3, 4, 5,\n  6, 7, 8, 9,\n  10,11,12,13,\n  A0,A1,A2,A3\n};\n\nconst uint8_t layers&#91;4] = { A4, A5, 0, 1 };\n\n\/\/ If your layer transistors turn ON with HIGH, set LAYER_ACTIVE = HIGH\n\/\/ If transistors ON with LOW (common NPN sink), set LAYER_ACTIVE = LOW\nconst uint8_t LAYER_ACTIVE = LOW;\nconst uint8_t LAYER_INACTIVE = (LAYER_ACTIVE == LOW) ? HIGH : LOW;\n\n\/\/ Frame buffer: cube&#91;z]&#91;y] -&gt; 4 planes z=0..3 each with 4 rows y=0..3. Bits 0..3 represent x=0..3\nuint8_t cube&#91;4]&#91;4];\n\nconst unsigned int layerRefreshMicros = 2000; \/\/ microseconds per layer (tweak for brightness\/refresh)\nunsigned long lastAnimMs = 0;\nconst unsigned long animInterval = 120; \/\/ ms between animation steps\n\n\/\/ ------------------------------------------------------------------\n\/\/ Helper functions\n\/\/ ------------------------------------------------------------------\nvoid setColumnOutput(int idx, bool high) {\n  digitalWrite(cols&#91;idx], high ? HIGH : LOW);\n}\n\nvoid activateLayer(int z) {\n  \/\/ deactivate all layers\n  for (int i = 0; i &lt; 4; ++i) digitalWrite(layers&#91;i], LAYER_INACTIVE);\n  \/\/ activate requested\n  digitalWrite(layers&#91;z], LAYER_ACTIVE);\n}\n\nvoid deactivateAllLayers() {\n  for (int i = 0; i &lt; 4; ++i) digitalWrite(layers&#91;i], LAYER_INACTIVE);\n}\n\n\/\/ set voxel (x in &#91;0..3], y in &#91;0..3], z in &#91;0..3])\nvoid setVoxel(uint8_t x, uint8_t y, uint8_t z, bool on) {\n  if (x &gt; 3 || y &gt; 3 || z &gt; 3) return;\n  if (on) cube&#91;z]&#91;y] |= (1 &lt;&lt; x);\n  else    cube&#91;z]&#91;y] &amp;= ~(1 &lt;&lt; x);\n}\n\nvoid clearCube() {\n  for (int z = 0; z &lt; 4; ++z)\n    for (int y = 0; y &lt; 4; ++y)\n      cube&#91;z]&#91;y] = 0;\n}\n\nvoid fillCube() {\n  for (int z = 0; z &lt; 4; ++z)\n    for (int y = 0; y &lt; 4; ++y)\n      cube&#91;z]&#91;y] = 0x0F; \/\/ lower 4 bits on\n}\n\n\/\/ Send the buffer to hardware by multiplexing layers\n\/\/ This function spends time refreshing layers; call it often in loop to maintain persistence\nvoid refreshDisplay(unsigned long totalMillis) {\n  unsigned long end = millis() + totalMillis;\n  while (millis() &lt; end) {\n    for (int z = 0; z &lt; 4; ++z) {\n      \/\/ set columns pins according to cube&#91;z]\n      for (int col = 0; col &lt; 16; ++col) {\n        uint8_t x = col % 4;\n        uint8_t y = col \/ 4;\n        bool on = (cube&#91;z]&#91;y] &amp; (1 &lt;&lt; x)) != 0;\n        \/\/ column HIGH lights LED when its layer is active (assumes columns drive anodes HIGH)\n        setColumnOutput(col, on);\n      }\n      \/\/ activate layer\n      activateLayer(z);\n      \/\/ hold for a short time for brightness\n      delayMicroseconds(layerRefreshMicros);\n      \/\/ quickly turn off before next layer to prevent ghosting while switching columns\n      deactivateAllLayers();\n    }\n  }\n}\n\n\/\/ ------------------------------------------------------------------\n\/\/ Some demo routines\n\/\/ ------------------------------------------------------------------\nvoid demoFillFlash() {\n  fillCube();\n  refreshDisplay(200);\n  clearCube();\n  refreshDisplay(200);\n}\n\nvoid demoRisingTowerStep() {\n  \/\/ simple animation: raise a 4x4 plane upward\n  static int level = 0;\n  clearCube();\n  for (int y = 0; y &lt; 4; ++y) {\n    for (int x = 0; x &lt; 4; ++x) setVoxel(x, y, level, true);\n  }\n  level = (level + 1) &amp; 3;\n}\n\nvoid demoRotateSquare() {\n  \/\/ rotating pattern across layers\n  static int step = 0;\n  clearCube();\n  if (step % 2 == 0) {\n    \/\/ diagonal plane pattern\n    for (int z = 0; z &lt; 4; ++z) {\n      for (int i = 0; i &lt; 4; ++i) setVoxel((i + z) &amp; 3, i, z, true);\n    }\n  } else {\n    for (int z = 0; z &lt; 4; ++z) {\n      for (int i = 0; i &lt; 4; ++i) setVoxel((i + 1 + z) &amp; 3, i, z, true);\n    }\n  }\n  step++;\n}\n\n\/\/ rain demo\nvoid demoRainStep() {\n  \/\/ shift all voxels down one z level and random top bits\n  for (int z = 3; z &gt; 0; --z) for (int y = 0; y &lt; 4; ++y) cube&#91;z]&#91;y] = cube&#91;z-1]&#91;y];\n  \/\/ new top layer random bits\n  for (int y = 0; y &lt; 4; ++y) cube&#91;0]&#91;y] = (uint8_t)(random(0,16)); \/\/ random 4-bit pattern\n}\n\n\n\/\/ ------------------------------------------------------------------\n\/\/ Setup and loop\n\/\/ ------------------------------------------------------------------\nvoid setupPins() {\n  for (int i = 0; i &lt; 16; ++i) pinMode(cols&#91;i], OUTPUT);\n  for (int i = 0; i &lt; 4; ++i) pinMode(layers&#91;i], OUTPUT);\n\n  \/\/ ensure all columns LOW and layers inactive at start\n  for (int i = 0; i &lt; 16; ++i) digitalWrite(cols&#91;i], LOW);\n  deactivateAllLayers();\n}\n\nvoid setup() {\n  \/\/ If you want Serial for debugging, avoid using pins 0 and 1 for layers!\n  Serial.begin(115200);\n  randomSeed(analogRead(A5));\n\n  setupPins();\n  clearCube();\n  lastAnimMs = millis();\n}\n\nuint8_t demoMode = 0;\n\nvoid loop() {\n  \/\/ quick refresh while waiting for animation timing\n  refreshDisplay(20); \/\/ refresh for 20ms continuously to keep persistence\n\n  \/\/ animation timed separately\n  if (millis() - lastAnimMs &gt; animInterval) {\n    lastAnimMs = millis();\n\n    switch (demoMode) {\n      case 0:\n        demoFillFlash();\n        demoMode = 1;\n        break;\n      case 1:\n        demoRisingTowerStep();\n        break;\n      case 2:\n        demoRotateSquare();\n        break;\n      case 3:\n        demoRainStep();\n        break;\n    }\n\n    \/\/ cycle demoMode every few seconds\n    static int stepCount = 0;\n    stepCount++;\n    if (stepCount &gt; 30) {\n      stepCount = 0;\n      demoMode = (demoMode + 1) % 4;\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What to change if things don\u2019t match your wiring<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Pin arrays<\/strong>: edit <code>cols[]<\/code> and <code>layers[]<\/code> to reflect your exact wiring.<\/li>\n\n\n\n<li><strong>Active layer polarity<\/strong>: set <code>LAYER_ACTIVE<\/code> to <code>HIGH<\/code> if a HIGH on the transistor gate\/base activates the layer (e.g., P-channel\/P-MOSFET high-side). For NPN sinking style (typical), <code>LAYER_ACTIVE = LOW<\/code> is correct if you use an NPN base resistor tied to the Arduino pin and the transistor is wired to pull the cathodes to ground.<\/li>\n\n\n\n<li><strong>Column drive logic<\/strong>: the code sets a column pin HIGH to turn a LED ON (when that layer is active). If your wiring is inverted (columns sink and layers source), invert <code>setColumnOutput<\/code> calls or wire differently.<\/li>\n\n\n\n<li><strong>Brightness \/ flicker<\/strong>: tweak <code>layerRefreshMicros<\/code>. Smaller values = higher effective refresh speed and brighter display but more CPU time spent refreshing. For smoother animations, keep it low (1\u20133000 \u00b5s) and keep <code>refreshDisplay()<\/code> running frequently.<\/li>\n\n\n\n<li><strong>Avoid pins 0\/1<\/strong> if you want Serial. Move layers to other pins and remap columns accordingly.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Nice circuit \u2014 that\u2019s a 4\u00d74\u00d74 LED cube (4 levels \u00d7 16 columns). Below I give a complete Arduino UNO sketch you can upload, plus wiring notes and how to adapt pin mapping if your&hellip;<\/p>\n","protected":false},"author":1,"featured_media":428,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-427","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-projects"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"http:\/\/www.amartkh.com\/store\/wp-content\/uploads\/2025\/11\/581675608_2346176912482655_6528649808955474607_n.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/posts\/427","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/comments?post=427"}],"version-history":[{"count":2,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/posts\/427\/revisions"}],"predecessor-version":[{"id":430,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/posts\/427\/revisions\/430"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/media\/428"}],"wp:attachment":[{"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/media?parent=427"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/categories?post=427"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.amartkh.com\/store\/wp-json\/wp\/v2\/tags?post=427"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}