{"id":16692639,"url":"https://github.com/platisd/moltoduino","last_synced_at":"2025-05-15T19:32:05.340Z","repository":{"id":145722105,"uuid":"52436212","full_name":"platisd/moltoduino","owner":"platisd","description":"Add programmable cores and enable HIL testing","archived":false,"fork":false,"pushed_at":"2018-03-07T08:21:38.000Z","size":228,"stargazers_count":2,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-03T13:38:50.843Z","etag":null,"topics":["arduino","arduino-shield","hardware-in-the-loop","test-driven-development"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/platisd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-02-24T11:08:25.000Z","updated_at":"2018-04-24T18:33:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"9a07b480-cb82-45d7-8697-023963dcaf53","html_url":"https://github.com/platisd/moltoduino","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/platisd%2Fmoltoduino","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/platisd%2Fmoltoduino/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/platisd%2Fmoltoduino/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/platisd%2Fmoltoduino/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/platisd","download_url":"https://codeload.github.com/platisd/moltoduino/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254407447,"owners_count":22066249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","arduino-shield","hardware-in-the-loop","test-driven-development"],"created_at":"2024-10-12T16:27:59.262Z","updated_at":"2025-05-15T19:32:05.326Z","avatar_url":"https://github.com/platisd.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Moltoduino\nAdd programmable cores to your project and enable Hardware In the Loop (HIL) testing.\n\n![Stacked Moltoduinos](https://i.imgur.com/Wbx2Yex.jpg)\n\n## What?\nMoltoduino is an Arduino shield that enables the stacking of multiple ATMega328P microcontrollers. The microcontroller pins are broken out in two ways:\n* Outwards, to enable their arbitrary connection to different components or other pins via cables.\n* Inwards, to enable their connection to the respective pin of the bottommost Arduino via jumpers.\n\nThe firmware on the Moltoduino cores can be easily flashed by flipping a switch that sets them in programming mode and then using the bottommost Arduino as an ISP programmer. You can read more about the project on [platis.solutions](https://platis.solutions/blog/2018/02/22/moltoduino-extra-cores-hil-testing/).\n\n## Why?\nMoltoduino is essentially a barebones Arduino UNO in the form of a shield that can be easily reprogrammed. So, what can you do with it?\n\n* Parallelize time-consuming or time-critical functionality (e.g. reading sensor data, playing sound)\n* Have a dedicated microcontroller for computationally intensive operations (e.g. Fast Fourier Transform)\n* Enable Hardware In the Loop (HIL) testing\n* Get additional resources, such as I/O pins, external interrupts, Serial port, I2C bus etc\n* Save physical space needed for applications that require a more than one microcontrollers (e.g. by stacking them instead placing them next to each other)\n\n## How?\nMoltoduino was initially created with the purpose of providing a stackable pin extension solution for Arduino boards. During development, another use case emerged which involved the shield being used for HIL testing.\n\nThe picture below illustrates how the pins of the shield's ATMega328P are broken out, highlighting them with **green**. In **red** one can find the bottom Arduino's pins. It becomes apparent that pins that follow the Arduino numbering convention can be easily connected via jumpers (e.g. D1 with D1) regardless of which specific Arduino board is in place (i.e. Mega or Uno). Furthermore, the shield's pins are also broken on the sides of the board so to be accessible when multiple Moltoduinos or other shields are stacked on top.\n\n![Moltoduino pin grouping](https://i.imgur.com/xR7Q34B.jpg)\n\n### Pin extension\nAttaching a Moltoduino shield is more or less equivalent to placing an Arduino Uno on top of another Uno or Mega board. The cool part is that it doesn't take up any horizontal real estate. You program them to perform an individual task in parallel or connect the Arduinos together via Serial, I2C or otherwise to implement complex functionality.\n\nThe pins facing outward can interface with the external components while the inward facilitate connection to the bottom Arduino, especially via I2C or Serial.\n\n### HIL testing\nMoltoduino can be utilized to provide a _hobby-grade_ [HIL testing](http://www.hil-simulation.com/home/hil-testing.html) solution for your embedded project. Having two microcontrollers that are easily connected to each other it is possible to conduct HIL simulations. This is achieved by one microcontroller running the production code and the other running the HIL test which generates input for the system under test and reads its output.\n\nSuch a HIL test fixture allows you to automate system-level testing on your project by mimicking real-world input. This can be a difficult and laborious process, where the tester would have to manually provide the environment input and check the output so to verify the system. Check out the relevant [code examples](#code-examples) to see how a HIL implementation could look like.\n\n### Programming Moltoduino\n1. Have all switches (there is one on each Moltoduino), in the \"operation\" position instead of \"programming\" mode\n2. In the Arduino IDE, upload the **ArduinoISP** sketch to the \"bottom\" Arduino\n3. Open the sketch to upload to the Nth Moltoduino on the Arduino IDE\n4. Make sure that \"Arduino as ISP\" is chosen under **Tools** :arrow_right: **Programmer**\n5. Choose the Arduino Uno as the target board, under **Tools** :arrow_right: **Board** :arrow_right: **Arduino/Genuino UNO**\n6. Put the switch in the \"programming\" position, on the Moltoduino(s) you wish to program\n7. (If not previously done) Burn the bootloader, under **Tools** :arrow_right: **Burn bootloader**\n8. Upload sketch using programmer, under **Sketch** :arrow_right: **Upload sketch using programmer**\n9. Put the switch back in the \"operation\" position, otherwise uploading to the bottom Arduino will not be possible\n\n## Code examples\nFor the code examples the [Smartcar shield](http://plat.is/smartcar) library will be used. You can easily download it via your Arduino IDE library manager.\n\n### Controlling a Smartcar via UART\nIn this scenario, a [Smartcar](http://plat.is/smartcar) is being controlled via UART (e.g. an HC-06 Bluetooth dongle) and we want to verify its behavior. The HIL simulation will be running on an Arduino Mega while the system under test firmware will be uploaded onto a Moltoduino that is stacked on top.\n\n**System under test**\n\nThe following code should be straight forward. Depending on the UART input the Smartcar should change speed and direction. We are steering the Smartcar with a servo motor while the speed is controlled by a brushed motor. We will be sending commands via UART and reading the PWM steering signal as well as the brushed motor control signals in order to verify the Smartcar's intended behavior.\n\n```cpp\n#include \u003cSmartcar.h\u003e\n\nconst int SERVO_PIN = 3;\nconst int FORWARD_PIN = 8;\nconst int BACKWARD_PIN = 7;\nconst int THROTTLE_PIN = 6;\nconst int fSpeed = 70; // 70% of the full speed forward\nconst int bSpeed = -70; // 70% of the full speed backward\nconst int lDegrees = -50; // Degrees to turn left\nconst int rDegrees = 50; // Degrees to turn right\n\n// Initialize the car, that uses a servo motor for steering\n// and a brushed DC motor to pins 8,7 (direction) and 6 (PWM) for throttling\nCar car(useServo(SERVO_PIN), useDCMotor(FORWARD_PIN, BACKWARD_PIN, THROTTLE_PIN));\n\nvoid setup() {\n  Serial.begin(9600);\n  car.begin(); // Initialize the car using the encoders and the gyro\n}\n\nvoid loop() {\n  handleInput();\n}\n\nvoid handleInput() { // Handle serial input if there is any\n  if (Serial.available()) {\n    char input = Serial.read();\n    switch (input) {\n      case 'l': // Turn counter-clockwise going forward\n        car.setSpeed(fSpeed);\n        car.setAngle(lDegrees);\n        break;\n      case 'r': // Turn clock-wise\n        car.setSpeed(fSpeed);\n        car.setAngle(rDegrees);\n        break;\n      case 'f': // Go ahead\n        car.setSpeed(fSpeed);\n        car.setAngle(0);\n        break;\n      case 'b': // Go back\n        car.setSpeed(bSpeed);\n        car.setAngle(0);\n        break;\n      default: // If you receive something that you don't know, just stop\n        car.setSpeed(0);\n        car.setAngle(0);\n    }\n  }\n}\n```\n\n**HIL simulation**\n\nIn this HIL test we need to simulate the input to the system (i.e. UART commands) and then observe its output (i.e. pin states and PWM signal) to verify production code's correct behavior. Moreover, jumpers are used to enable interaction between the system under test and the HIL simulation. The jumper placement is illustrated in the table below.\n\n| HIL simulation | System Under Test | Purpose                           |\n| :----:         |:----:             |:----:                             |\n| TX             |  RX               | Transmit UART commands to the SUT |\n| 3              |  3                | Read servo motor's PWM signal     |\n| 7              |  7                | Read motor control pin            |\n| 8              |  8                | Read motor control pin            |\n\n```cpp\n// Simulation pin configuration\nconst int STEERING_PIN = 3;\nconst int FORWARD_PIN = 8; // Car goes forward when set HIGH\nconst int BACKWARD_PIN = 7; // Car goes backward when set HIGH\nconst int THROTTLE_PIN = 6;\n// UART commands\nconst char CAR_FORWARD = 'f';\nconst char CAR_BACKWARD = 'b';\nconst char CAR_LEFT = 'l';\nconst char CAR_RIGHT = 'r';\nconst char CAR_STOP = 's';\n// Expected steering degrees\nconst int LEFT = -50;\nconst int RIGHT = 50;\nconst int STRAIGHT = 0;\n// Other variables and structures\nenum Throttle {\n  FORWARD,\n  BACKWARD,\n  STOPPED,\n};\n\nbool isThrottle(Throttle expectedThrottle) {\n  Throttle actualThrottle = STOPPED;\n  if (digitalRead(FORWARD_PIN) \u0026\u0026 !digitalRead(BACKWARD_PIN)) {\n    actualThrottle = FORWARD;\n  } else if (!digitalRead(FORWARD_PIN) \u0026\u0026 digitalRead(BACKWARD_PIN)) {\n    actualThrottle = BACKWARD;\n  }\n\n  return actualThrottle == expectedThrottle;\n}\n\nbool isSteering(int expectedAngle) {\n  int pwm = pulseIn(STEERING_PIN, HIGH);\n  // Map the PWM signal to a 0 to 180 scale\n  const int MIN_PWM = 540;\n  const int MAX_PWM = 2390;\n  int measuredAngle = map(pwm, MIN_PWM, MAX_PWM, 0, 180);\n  // Offset the angle by 90 to get an angle between -90 and 90\n  measuredAngle -= 90;\n  int absoluteDelta = expectedAngle \u003e measuredAngle ?\n                      expectedAngle - measuredAngle : measuredAngle - expectedAngle;\n  // We are expecting some error in these measurements so let's define an acceptable error margin\n  const int ERROR_MARGIN = 5;\n\n  return absoluteDelta \u003c ERROR_MARGIN;\n}\n\nvoid runSmartcarHIL(const char* testName, const char command, Throttle throttle, const int steering) {\n  Serial.print(\"RUNNING: \");\n  Serial.println(testName);\n  // Send command\n  Serial.print(\"Sending command: \");\n  Serial.print(command);\n  Serial.flush(); // Make sure we have finished sending\n  // Wait a bit to make sure the command has been processed\n  const unsigned long TEST_DELAY = 100;\n  delay(TEST_DELAY);\n  String result = isThrottle(throttle) \u0026\u0026 isSteering(steering) ? \"PASSED\" : \"FAILED\";\n  Serial.print(\"\\nRESULT: \");\n  Serial.println(result);\n  Serial.println(\"----\");\n}\n\nvoid goForward_test() {\n  auto UARTcommand = CAR_FORWARD;\n  auto expectedThrottle = FORWARD;\n  auto expectedSteering = STRAIGHT;\n  runSmartcarHIL(__func__, UARTcommand, expectedThrottle, expectedSteering);\n}\n\nvoid goBackward_test() {\n  auto UARTcommand = CAR_BACKWARD;\n  auto expectedThrottle = BACKWARD;\n  auto expectedSteering = STRAIGHT;\n  runSmartcarHIL(__func__, UARTcommand, expectedThrottle, expectedSteering);\n}\n\nvoid turnLeft_test() {\n  auto UARTcommand = CAR_LEFT;\n  auto expectedThrottle = FORWARD;\n  auto expectedSteering = LEFT;\n  runSmartcarHIL(__func__, UARTcommand, expectedThrottle, expectedSteering);\n}\n\nvoid turnRight_test() {\n  auto UARTcommand = CAR_RIGHT;\n  auto expectedThrottle = FORWARD;\n  auto expectedSteering = RIGHT;\n  runSmartcarHIL(__func__, UARTcommand, expectedThrottle, expectedSteering);\n}\n\nvoid stop_test() {\n  auto UARTcommand = CAR_STOP;\n  auto expectedThrottle = STOPPED;\n  auto expectedSteering = STRAIGHT;\n  runSmartcarHIL(__func__, UARTcommand, expectedThrottle, expectedSteering);\n}\n\nvoid setup() {\n  pinMode(FORWARD_PIN, INPUT);\n  pinMode(BACKWARD_PIN, INPUT);\n  pinMode(STEERING_PIN, INPUT);\n  Serial.begin(9600);\n  Serial.println(\"====================\");\n  Serial.println(\"Starting HIL test suite\");\n  Serial.println(\"====================\");\n\n  goForward_test();\n  goBackward_test();\n  turnLeft_test();\n  turnRight_test();\n  stop_test();\n}\n\nvoid loop() {\n}\n```\n\n### Controlling a light based on ultrasound sensor distance measurements\nIn this scenario, an `HC-SR04` ultrasound sensor is being used to measure distances. When an object is detected close enough a light turns on. Yet again, the HIL simulation will be running on an Arduino Mega while the system under test firmware will be uploaded onto a Moltoduino that is stacked on top.\n\n**System under test**\n\nBelow you can find a simplified implementation of the typical use case in which a light needs to be turned on when an object is detected at a nearby distance otherwise it should be off. The `HC-SR04` class of [Smartcar shield](http://plat.is/smartcar) library is utilized to conduct the measurements.\n\n```cpp\n#include \u003cSmartcar.h\u003e\n\nSR04 sensor;\nconst int TRIGGER_PIN = 2;\nconst int ECHO_PIN = 3;\nconst int LIGHT_PIN = 9;\nconst int MIN_OBSTACLE_DISTANCE = 15; // In centimeters\n\nvoid setup() {\n  sensor.attach(TRIGGER_PIN, ECHO_PIN);\n  pinMode(LIGHT_PIN, OUTPUT);\n}\n\nvoid loop() {\n  auto distance = sensor.getDistance();\n  // If there is an obstacle nearby turn the light on\n  if (distance \u003e 0 \u0026\u0026 distance \u003c MIN_OBSTACLE_DISTANCE) {\n    digitalWrite(LIGHT_PIN, HIGH);\n  } else {\n    digitalWrite(LIGHT_PIN, LOW);\n  }\n  delay(100);\n}\n```\n\n**HIL simulation**\n\nIn this HIL test we need to simulate the `HC-SR04` ultrasound sensor's input to the system and then observe its output (i.e. the pin that controls the state of the light). Particularly, we observe (via an interrupt) the incoming signals from the trigger pin of the system under test, an event which designates that a measurement has been initiated. Next, we simulate the sensor input by calculating and producing a pulse of equal length to the one that would have been generated, should there have been an actual sensor and an object. Finally, we verify the digital pin that controls the light is in the appropriate state.\n\nJumpers are used to enable interaction between the system under test and the HIL simulation. The jumper placement is illustrated in the table below.\n\n| HIL simulation | System Under Test | Purpose              |\n| :----:         |:----:             |:----:                |\n| 2              |  2                | Read trigger signal  |\n| 3              |  3                | Generate echo pulse  |\n| 9              |  9                | Read light pin state |\n\n```cpp\nconst int TRIGGER_PIN = 2;\nconst int ECHO_PIN = 3;\nconst int LIGHT_PIN = 9;\n\nvolatile unsigned long echoDuration = 0;\n\nvoid generateEcho() {\n  if (echoDuration == 0) {\n    // If no echo duration has been set or set to 0\n    // then return immediately as no pulse is to be generated\n    return;\n  }\n  digitalWrite(ECHO_PIN, LOW); // Set LOW first for cleaner signal\n  delayMicroseconds(3);\n  // Simulate the echo pulse of an HC-SR04 sensor\n  digitalWrite(ECHO_PIN, HIGH);\n  delayMicroseconds(echoDuration);\n  digitalWrite(ECHO_PIN, LOW);\n}\n\n/**\n   Accordig to the HC-SR04 datasheet (https://goo.gl/b22dp3) the formula to\n   determine the distance in centimeters is: cm = uSec / 58\n   We need to calculate the length of the pulse in microseconds\n   that we has to be generated to simulate a specific distance in cm.\n*/\nvoid setEchoPulseDurationFor(unsigned int cm) {\n  noInterrupts();\n  echoDuration = cm * 58;\n  interrupts();\n}\n\nvoid runUltrasoundHIL(const char* testName, int obstacleDistance, int expectedLEDState) {\n  Serial.print(\"RUNNING: \");\n  Serial.println(testName);\n  setEchoPulseDurationFor(obstacleDistance);\n  const unsigned long TEST_DELAY = 300;\n  delay(TEST_DELAY); // Wait a bit for a new measurement to start\n  String result = digitalRead(LIGHT_PIN) == expectedLEDState ? \"PASSED\" : \"FAILED\";\n  Serial.print(\"\\nRESULT: \");\n  Serial.println(result);\n  Serial.println(\"----\");\n}\n\nvoid whenObstacleNear_lightTurnsOn_test() {\n  auto obstacleDistance = 10;\n  auto expectedLEDState = HIGH;\n  runUltrasoundHIL(__func__, obstacleDistance, expectedLEDState);\n}\n\nvoid whenObstacleFar_lightTurnsOff_test() {\n  auto obstacleDistance = 15;\n  auto expectedLEDState = LOW;\n  runUltrasoundHIL(__func__, obstacleDistance, expectedLEDState);\n}\n\nvoid whenNoObstacle_lightTurnsOff_test() {\n  auto obstacleDistance = 0;\n  auto expectedLEDState = LOW;\n  runUltrasoundHIL(__func__, obstacleDistance, expectedLEDState);\n}\n\nvoid setup() {\n  pinMode(LIGHT_PIN, INPUT);\n  pinMode(TRIGGER_PIN, INPUT);\n  pinMode(ECHO_PIN, OUTPUT);\n  // Setup interrupt for trigger pin which goes HIGH when a measurement begins\n  attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), generateEcho, FALLING);\n  Serial.begin(9600);\n  Serial.println(\"====================\");\n  Serial.println(\"Starting HIL test suite\");\n  Serial.println(\"====================\");\n\n  whenObstacleNear_lightTurnsOn_test();\n  whenObstacleFar_lightTurnsOff_test();\n  whenNoObstacle_lightTurnsOff_test();\n}\n\nvoid loop() {\n}\n```\n\n## Bill Of Materials\n| Quantity | Value            | Device                                    | Package                   |\n|----------|----------------- |-------------------------------------------|---------------------------|\n| 1        | Moltoduino PCB   | ARDUINO_R3_ICSP                           | ARDUINOR3_ICSP            |\n| 1        | ATMEGA328P_PDIP  | ATMEGA328P_PDIP                           | DIL28-3                   |\n| 1        | BC547            | TRANSISTOR_NPNBC547                       | TO-92-AMMO                |\n| 3        | 100nf            | DIP-CERAMIC-DISC-100NF-50V-Y5V(D5.0MM)    | CERAMIC-2.54              |\n| 1        | 150Ω             | DIP-RES-1K-5%-1/4W(PR-D2.3XL6.5MM)        | PR-D2.3XL6.5MM            |\n| 1        | 16MHZ            | DIP-CRYSTAL-16MHZ-18PF-30PPM-50R(HC49US)  | HC49US                    |\n| 1        | 1K               | DIP-RES-1K-5%-1/4W(PR-D2.3XL6.5MM)        | PR-D2.3XL6.5MM            |\n| 2        | 22pf             | DIP-CERAMIC-DISC-22PF-50V-10%-NPO(D4.0MM) | CERAMIC-2.54              |\n| 1        | 2x16p-2.54       | DIP-BLACK-MALE-HEADER-VERT(2X16P-2.54)    | H2X16-2.54                |\n| 1        | 2x6p-2.54        | DIP-BLACK-MALE-HEADER(2X6P-2.54)          | H2X6-2.54                 |\n| 2        | 4.7K             | DIP-RES-4.7K-5%-1/4W(PR-D2.3XL6.5MM)      | PR-D2.3XL6.5MM            |\n| 4        | 7p-2.54-90d      | DIP-BLACK-MALE-HEADER(7P-2.54-90D)        | H7-2.54-90D-MALE          |\n| 1        | AD595AQ-DIP14    | 74HC4066(DIP14)                           | DIP14-2.54-17.24X6.35MM   |\n| 1        | SK-12D01         | DIP-TOGGLE-SWITCH-ON-ON(3+2P-8.8X4.6MM)   | SW5-2.0-8.8X4.4X4.7MM-90D |\n| 1        | SR302DC          | DIP-LED-RED-RED(2P-D3MM)                  | LED2-2.54-D3.0MM          |\n| 1        | TS-1101F         | DIP-BUTTON-FRONT-WHITE(2P-3.4X6MM)        | SW2-3.4X6.0X3.55MM-90D    |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplatisd%2Fmoltoduino","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplatisd%2Fmoltoduino","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplatisd%2Fmoltoduino/lists"}