{"id":19682121,"url":"https://github.com/ssloy/penny","last_synced_at":"2026-03-06T08:01:55.517Z","repository":{"id":149309090,"uuid":"237815107","full_name":"ssloy/penny","owner":"ssloy","description":"3 servos, 10 dollars hexapod","archived":false,"fork":false,"pushed_at":"2020-02-23T00:24:55.000Z","size":20337,"stargazers_count":33,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-18T13:34:32.082Z","etag":null,"topics":["arduino","atmega","atmel","fun","hexapod","open-hardware","robot","robots"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssloy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null}},"created_at":"2020-02-02T18:19:44.000Z","updated_at":"2025-05-28T00:28:30.000Z","dependencies_parsed_at":"2023-06-06T10:31:34.634Z","dependency_job_id":null,"html_url":"https://github.com/ssloy/penny","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ssloy/penny","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssloy%2Fpenny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssloy%2Fpenny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssloy%2Fpenny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssloy%2Fpenny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssloy","download_url":"https://codeload.github.com/ssloy/penny/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssloy%2Fpenny/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30166859,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T07:56:45.623Z","status":"ssl_error","status_checked_at":"2026-03-06T07:55:55.621Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","atmega","atmel","fun","hexapod","open-hardware","robot","robots"],"created_at":"2024-11-11T18:09:44.508Z","updated_at":"2026-03-06T08:01:55.497Z","avatar_url":"https://github.com/ssloy.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Meet Penny#3, a 3 servo hexapod.\nPenny is a low budget (roughly ten bucks) hexapod. It can be a great one-weekend project to entertain yourself and your kids. Check for Penny dancing to funky music (youtube link):\n\n[![Penny dances](https://img.youtube.com/vi/quMe5CEoOok/0.jpg)](https://youtu.be/quMe5CEoOok)\n\nPenny avoids obstacles (youtube link):\n\n[![Penny dances](https://img.youtube.com/vi/f4vZZBQZLEU/0.jpg)](https://youtu.be/f4vZZBQZLEU)\n\nIn her current state, Penny only knows to walk; she can see (measure distance to) nearby obstacles. Her brains, however, are powerful enough to digest data from many other sensors, send me your suggestions!\n\nPenny is a tremendous fun!\n\n\u003cimg src=\"https://raw.githubusercontent.com/ssloy/penny/master/doc/A_playing_with_penny.jpg\" width=\"512\"/\u003e\n\n## Credits\nPenny has two elder sisters, [Penny](https://youtu.be/7Py03SH5DbE) and [Penny](https://youtu.be/PiVTC8JhZTQ). Note that I have no hardware contributions, all I did is to gather the information, assemble things and write the firmware. I want this wonderful robot to be easy to clone, therefore I created this repository. The original Penny#1 is created by [Jeremy Zimmer](https://www.robotshop.com/community/robots/show/penny). The wiring being cumbersome and cheapduino being discontinued, Dennis van Elteren has designed the motherboard that I also use. Thus Penny#2 was born. Here I present you Penny#3. While I have Dennis' sanction to publish his files, I failed to contact Jeremy. The software, however is distributed under the DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.\n\n# How to clone\n## Bill of materials\nPrinting the body costs next to nothing if you have a 3d printer. Here are the main things you need to build the bot:\n* The motherboard. You can either etch it by yourself, or you can check chinese factories, any normal day it costs ~10€ / 10 pcs (shipping included). Since the board is tiny, personally I appended it to another order, and it was free of charge for me. You can find an alternative like cheapduino or similar, because the schematics is very, very basic.\n* [SG90 9G micro servo, 3 * 1.47€ / piece](https://www.aliexpress.com/item/4000595327297.html)\n* [4x AAA battery holder, 1.34€ / piece](https://www.aliexpress.com/item/33049875634.html)\n* [ATMega8A-AU (QFP-32), 1€ / piece](https://www.aliexpress.com/item/32557093316.html)\n* [IR LED + IR phototransistor, 0.20€ / pair](https://www.aliexpress.com/item/32849824664.html)\n* [Electrolytic capacitor 1000uF 16V, 0.17€ / piece](https://www.aliexpress.com/item/32954075821.html)\n* [2n3904 transistor, 3 * 0.01€ / piece](https://www.aliexpress.com/item/32494899564.html)\n* You will need wires, heat shrink, screws, pin headers, few 0805 resistors and capacitors. All electronic components are listed in the [hardware/motherboard/BOM.html](https://github.com/ssloy/penny/blob/master/hardware/motherboard/BOM.html) file.\n\n**NB:** When bying 9g servos, do not forget that they come in different sizes, Penny is designed for the low profile plastic gears servos. It will work with other servos, but you might need to update SketchUp files for it. Moreover, metal gears are an overkill here.\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/9g-servos-comparison.jpg)\n\n## The body\nIt is quite straightforward, if you have a printer, just print it. You can find the files in the [hardware/body/](https://github.com/ssloy/penny/tree/master/hardware/body) folder. All the body parts are shown here:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/penny-body-print.jpg)\n\nMy printer is equipped with a 1mm nozzle, so the prints were completed in less than two hours. When assembled, it should look like this beast:\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/penny-body-model.jpg)\n\nM3 nylon screws are perfect for the assembly. Use nylon washers between moving parts and lock the nut by the method of your choice.\nPersonally I have locked the thread with a soldering iron:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/nylon-screws.jpg)\n\n## The motherboard\n### The brain\nThe motherboard itself is pretty basic. It has an ATMega8 microcontroller and the proximity sensor circuit, nothing else. The source files, the gerber files and the bill of materials can be found in the [hardware/motherboard/](https://github.com/ssloy/penny/tree/master/hardware/motherboard) folder. Here is a render of the gerber files:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/mb-gerber.png)\n\nAnd the part of the schematics with the brain:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/pcb-mcu.png)\n\nI recommend to solder the bare minimum to power up the processor, and to flash it to be sure that nothing is wrong with the delicate soldering. At this stage the motherboard looks like this:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/penny_motherboard_mcu.jpg)\n\n**N.B. ATMega8A datasheet specifies its operating voltage at 2.7-5.5V and absolute maximum 6V rating.\nA safe option is to power Penny with 4 NiMH 1.2V rechargeable batteries,\nbut I tried to run Penny with 4 standard alcaline batteries (6.4V in total) and it did not fry the brain.\nIf you are going this way, I did warn you. It is at your own risk!**\n\nHere is a photo of the motherboard fully populated (with an exception of IR led/phototransistors):\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/penny_motherboard.jpg)\n\n### The proximity sensor\nPenny has two eyes, each one is made of an infrared LED and a corresponding phototransistor.\nThe LED emits infrared light; this light propagates through the air and once it hits an object it is reflected back towards the phototransistor.\nIf the object is close, the reflected light will be stronger than if the object is further away.\nNote that while the infrared light is not visible by a human eye, some cameras may see it and show it on the recordings,\nit can be a handy tool for debugging Penny:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/ir-leds-on-camera.gif)\n\nThe schematics is very simple:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/pcb-proximity-sensor.png)\n\nWe power up two LEDs, when the phototransistors are not lit, the collector pins of Q3 and Q4 are tied to Vcc, and if the phototransistors\nsense the light, the voltage on the collector pins will go down.\nHere is the circuit test before I have installed it in the Penny's eyesockets:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/proximity-sensor.gif)\n\nNote that you may need to adjust the R6 resistor value.\n47 Ohms provides 55mA to the LEDs; some leds might need more or less current.\nFor example, I have scraped a couple of LEDs from a broken toy, and they work perfectly on 3mA (910 Ohms)!\n\nI recommend to assemble first the sensing unit on a breadboard without the LEDs.\nThen light up the LED with a CR2032 or a similar battery, pand put it against the phototransistor (I do not know the internal resistance of the battery, but I have never seen a LED fried from such an operation. Correct me if I am wrong).\nVerify that the voltage on the Q3 and Q4 collector pins drops as expected.\nOnce the sensing unit is okay, try to find a good resistor value for the LEDs to obtain the behaivour you see on the above video.\nNote that it is important to put a heatshrink around both the LEDs and the phototransistors to cut off parasitic lights.\nMoreover, with heatshrink it fits neatly into the eyesockets. When soldering the 2n3904, I recommend to solder first the center pin, and only then the side pins, otherwise it is too easy to create hard-to-remove solder bridges. Personally I find these little basterds harder to solder than the microcontroller itself (but I am bad at soldering!).\n\nIf you do to not have an oscilloscope, that is okay, you can do it with a couple of debugging LEDs, check the blue LEDs on the following video:\n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/proximity-sensor2.gif)\n\nFor more distant obstacles the LEDs will be less bright.\n\nIf you fail to assemble the proximity sensor, or simply dislike it, there are plenty of options:\n\n* You can use isf471 instead of the phototransistors and all the 2n3904 circuitry.\n* You can buy Sharp GP2Y0A21YK0F distance measuring units: \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/ssloy/penny/master/doc/GP2Y0A21YK0F.jpg\" width=\"320\"/\u003e\n* Or a basic binary proximity sensor based on a LM393 differential comparator: \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/ssloy/penny/master/doc/lm393.jpg\" width=\"320\"/\u003e\n\n# Firmware explained\nPenny can be programmed via arduino environment, but I find it quite obscure for such simple microcontrollers as ATMega8. Let us split the firmware comments into four parts: \n* [how to get the PWM working](https://github.com/ssloy/penny#pwm-generation)\n* [how Penny moves legs](https://github.com/ssloy/penny#movement-planner)\n* [gait sequences](https://github.com/ssloy/penny#gait-sequences)\n* [obstacle avoidance strategy](https://github.com/ssloy/penny#obstacle-detection)\n\n### PWM generation\nThe servos take a 50 Hz PWM signal; 1 ms minimum pulse width (0 deg), 2 ms maximum pulse width (90 deg). Penny has three servos, two of them are attached to a 16 bit timer (timer1), and the third one to a 8 bit timer (timer2). If I am not mistaken, arduino's Servo.h controls servomotors via software PWM, and I dislike that, therefore both timers are ticking in fast PWM mode.\n\nThe microcontroller ticks at 8 MHz, and the timer1 ticks at 1 MHz (prescaler 8), and ICR1 provides the TOP value (20000), thus it restarts every 20 ms, providing a correct 50 Hz signal. OCR1A and OCR1B registers control microsecond pulse widths for the left and right servos.\n\nThe problem comes with the center servo attached to a 8 bit timer2. It does not have a handy ICR1 analog, so the overflowing frequency is controlled via the prescaler only. There are no prescalers to approximate 50 Hz well enough, so here is an idea that lies somewhere inbetween a software and a hardware PWM:\n* we set timer2 to tick at prescaler 128, thus it overflows after 4.096 ms = 256 * 128/(8 * 10^6).\n* At the overflow we disable the timer2, so it is a one-shot pulse.\n* At the timer1 capture interrupt we re-arm the (one-shot) timer2.\n\n4 ms is superior to a 2 ms max pulse width we need to control, and is well inferior to the 20 ms re-arming beat. To sum up, let us say we want to put all three servos to the middle position (1500 μs pulse width). We need to do the following:\n```c\nOCR1A = 1500;    // left servo\nOCR1B = 1500;    // right servo\nOCR2  = 1500/16; // center servo\n```\n### Movement planner\n\nFirst of all, there are 6 important constants in the code:\n```c\nconst uint8_t  zero[3] = {45, 50, 40};     // zero position of the servo (degrees)\nconst uint8_t range[3] = {25, 25, 20};     // the servos are allowed to move in the zero[i] +- range[i] interval\n```\nThe `zero[3]` array provides the angles of all three servos corresponding to the neutral stance (left image below). Ideally, these angles are to be at 45° (midpoint of the servo range), but in practice if we set the servos to 45°, the legs won't be aligned due to descreet teeth position on the shaft. Next, `range[3]` specifies the admissible range. It means that the servo `i` is allowed to move in the `zero[i]-range[i]`to `zero[i]+range[i]` interval. \n\n![](https://raw.githubusercontent.com/ssloy/penny/master/doc/servos-pos-range.jpg)\n\nCurrent servo position (in degrees, 0°-90°) is supposed to be stored in the `uint8_t pos[3]` array. When calling `update_servo_timers()`, the timers are updated according to the array. The right image above corresponds to the `pos[i]=zero[i]+range[i]` for all three `i=0,1,2`.\n\nAll the movements are planned as constant speed. There are four auxiliary arrays for the movement planner: `pos_beg[3], pos_end[3], time_start[3]` and `duration[3]`. Let us suppose that we want to move the left servo only. All we need to do is:\n* copy `pos[0]` to `pos_beg[0]`, it marks the starting point of the movement;\n* set `pos_end[0]` to the desired position (still in degrees);\n* set `time_start[0]` to the current timestamp (milliseconds since the boot);\n* and, finally, set `duration[0]` (in seconds). That is, the speed will be `(pos_end[0]-pos_beg[0])/duration[0]` degrees/sec.\n\nThen in an endless loop I invoke `movement_planner()`, it sets the goal `pos[]` according to the plan, and `update_servo_timers()` to update the PWM generator according to the `pos[]` position.\n\n### Gait sequences\nNote that all movement planner variables are stored in 3-element arrays, thus the movements (including the speeds) can be independent one from another. \nDespite that, my current gait implementation uses synchronized movements of all three servos.\nLet us see how Penny goes forward. To advance, Penny repeats in a loop 4 steps.\nAll 4 steps are linear movements to following goal positions:\n* step 1: `{zero[0]-range[0], zero[1]-range[1], zero[2]+range[2]}`\n* step 2: `{zero[0]-range[0], zero[1]-range[1], zero[2]-range[2]}`\n* step 3: `{zero[0]+range[0], zero[1]+range[1], zero[2]-range[2]}`\n* step 4: `{zero[0]+range[0], zero[1]+range[1], zero[2]+range[2]}`\n\nWe can express that as a 2d array (4 triplets of goal positions):\n```c\nconst int8_t advance_sequence[4][3] = {{-1, -1,  1}, {-1, -1, -1}, { 1,  1, -1}, { 1,  1,  1}};\n```\nThis array tells us that the goal position of the servo `i` at the step `step` is `zero[i] + range[i]*advance_sequence[step][i]`.\nThen the following code makes Penny to go forward indefinitely:\n```c\n    uint8_t step = steps_per_sequence-1; // at the initialization stage the (previous) movement is considered to be complete, thus the next movement will be planned starting from the step 0\n    while (1) {\n        if (is_movement_finished()) {\n            step = (step + 1) % 4; // if previous movement is complete, then perform the next step; this variable loops as 0,1,2,3.\n            plan_next_movement(step, advance_sequence); // execute next movement\n        }\n        movement_planner(); // update the servos position according to the planning\n        _delay_ms(1);\n    }\n```\n\n### Obstacle detection\n\nRecall that our proximity sensor provides a voltage that we read throuh channels 4 and 5 of the ADC. To cut off eventual spikes in the readings (esp. knowing that the servos induce tons of noise), at each loop I update the variables `adc_left_eye` and `adc_right_eye` as a low-pass filter over the ADC readings:\n```c\n        adc_left_eye  = adc_left_eye *.99 + adc_read(5)*.01; // low-pass filter on the ADC readings\n        adc_right_eye = adc_right_eye*.99 + adc_read(4)*.01;\n```\nThe cutoff frequency can be set either by adjusting `_delay_ms()` inside the loop or by changing the `.99` and `1-.99` coefficients in the above code.\n\nThe presense of an obstacle is detected by a simple threshold over the ADC readings (recall that C does not have `bool` type so I use `uint8_t` instead):\n```c\n        uint8_t lobst = adc_left_eye  \u003c distance_threshold; // obstacle on the left?\n        uint8_t robst = adc_right_eye \u003c distance_threshold; // obstacle on the right?\n```\n\nThen at the end of each step of the current sequence I verify if there is an obstacle present and change the plans accordingly. It is as simple as that!\n\n```c\n        if (is_movement_finished()) {\n            if (!lobst \u0026\u0026 !robst) {\n                sequence = advance_sequence; // no obstacles =\u003e go forward\n            } else if (lobst \u0026\u0026 robst) {\n                sequence = retreat_sequence; // obstacles left and right =\u003e go backwards\n            } else if (lobst \u0026\u0026 !robst) {\n                sequence = turn_right_sequence; // obstacle on the left =\u003e turn right\n            } else if (!lobst \u0026\u0026 robst) {\n                sequence = turn_left_sequence; // obstacle on the right =\u003e turn left\n            }\n            step = (step + 1) % steps_per_sequence; // if previous movement is complete, then perform the next step\n            plan_next_movement(step, sequence); // execute next movement\n        }\n```\n\n# Wishlist\nAny contribution is welcome! Send me your ideas; here is a list of things that I'd like to see improved:\n\n### software:\n* Propose me an elegant way to have more natural, life-like movements. Right now it moves by a linear interpolations between keyframes, it would be great if the robot was less shaky. Probably, non-linear interpolation between the same keyframes with pre-computed accelerations?\n* Propose new strategies of obstacle detection. Current implementation is very basic and aims a good source code readability rather then WOW robot's behaviour.\n* I guess that it would be a good idea to port the code to the arduino environment for those who do not want to call avr-gcc directly (or for those who are afraid of meddling with AVR registers). If you can do it, send me a pull request or fork the repository.\n\n### hardware:\nIf you are a good soul willing to create a V2 of the motherboard, you are very welcome to do so. Here are the things that I'd like to be fixed/added/modified in the motherboard:\n* The main thing is the on/off switch to cut off servo motors power during flashing;\n* Remove the crystal, internal RC should be just fine;\n* Replace through-hole components by their SMD analogs;\n* Replace R6 with a potentiometer to adjust the proximity sensor more easily;\n* Propose good (small and foolproof) connectors instead of pin headers and optimize their placement;\n* IR LEDs pads are very hard to reach under the center servo. The only viable option with the V1 motherboard is to solder the wires;\n* Move a little bit the big capacitor. I had to incline it, otherwise the screw in center legs would destroy it;\n* Add test pads easy to access with an oscilloscope;\n* Add a couple of debugging LEDs;\n* Create good soldering points for unused ATMega8 pins for debugging purposes and further extension.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssloy%2Fpenny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssloy%2Fpenny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssloy%2Fpenny/lists"}