-
One More Thing
02/04/2021 at 17:48 • 0 commentsI guess I was not quite done with the Think-a-Tron world just yet. This additional project was inspired by a part and was finished in under three days. Check it out here: Think-a-Tron Mini.
And here is a photo of my complete Think-a-Tron family.
-
Wrapping Up
01/28/2021 at 16:14 • 0 commentsThe hardware and software are finished and there is now an easy way to create trivia cards. Here is a video of Think-a-Tron 2020 in action.
Users read the trivia question from a card and "lock-in" their answers via the labeled push buttons. The card is then placed into the slot and the ? button pressed to tell Think-a-Tron to determine the correct answer. After a little thinking time (revealing the "man in the machine") the correct answer choice is displayed on the 5x7 pixel display and the players scores are updated. The answer selection buttons will turn green for a correct choice and red for a wrong pick. Pressing the ? again will reset Think-a-Tron for the next question. Pressing and holding the ? button for more than 5 seconds will reset the scores to 0.
When I look back at my goals for this project I think that I have accomplished at least two of the three.
- I want to build something that is clearly a Think-a-Tron derivative. I'll let you be the judge, but I THINK there is a pretty good resemblance.
I would like to maintain the 60's mainframe vibe of the original. I don't think I nailed this one. Think-a-Tron 2020 doesn't scream 60's computer to me. Mind you I'm not unhappy with the look. I get a more 60's alien sci-fi computer vibe. I'm OK with this.
Have some fun along the way. You'll have to take my word for this one, but putting Think-a-Tron 2020 together was a blast.
-
Trivially Making Question Cards
01/28/2021 at 03:15 • 0 commentsI'm relatively new to Python, but I'm beginning to appreciate how quickly you can get things done with it. What I thought might take a few days turned out to be more like a few hours.
So the application works with one sheet of Avery 5371 business cards at a time. You simply key in ten questions, choices, and answers then press the Save button. Three files will be created.
- A text file with your data so that you can Load it back in to make changes.
['Humans and chimpanzees share roughly how much DNA?', 'Roughly how long does it take for the sun’s light to reach Earth ?', 'At what temperature are Celsius and Fahrenheit equal?', 'What modern-day country was Marie Curie born in?', 'How many hearts do octopuses have?', 'Apollo 15 was the first moon mission to carry a lunar rover?', 'On the periodic table, what symbol stands for silver?', 'Mercury is the only planet in the solar system without a moon.', 'What was the name of the first man-made satellite launched by the Soviet Union in 1957?', 'What is the biggest planet in our solar system?'] ['10%', '8 minutes', '-40', 'Hungary', 'One', '', 'Si', '', 'Sputnik 1', 'Uranus'] ['52%', '8 hours', '0', 'Poland', 'Two', '', 'Ag', '', 'Echo 1', 'Jupiter'] ['98%', '8 days', '32', 'Czechoslovakia', 'Three', '', 'Na', '', 'Explorer 1', 'Neptune'] ['', '', '', '', '', '', '', '', '', ''] ['', '', '', '', '', '', '', '', '', ''] [3, 1, 1, 2, 3, 4, 2, 5, 1, 2]
- A PDF of the questions and choices nicely formatted one per card.
- A PDF of the ten QR codes laid out to align with the correct questions when two sided printing.
It would be pretty easy to setup a trivia night with this program.
I did run into one issue creating this program. I'm using the ReportLab PDF Library to layout the cards (which works great by the way). My intention was to use the corresponding reportlab-qrcode library to generate the QR codes on the fly. I had thoughts or obfuscating the "payload" of each QR Code since if you look very closely at the QR Codes for simple "A", "B", "C", "T", and "F" payloads you can in fact distinguish between them.
Unfortunately the QR Code library I'm using on the ESP32 side would not read the generated codes. I tried many different variations but, in the end, decided to employ the QR Code images that I have been using all along. These images were created online at QR Code Generator.
- A text file with your data so that you can Load it back in to make changes.
-
It Worked!
01/26/2021 at 02:03 • 0 commentsThe ADS1115 did the trick. Not only did it get around my "one pin short" issue, the analog reads are now rock solid too.
I redid the "shield" incorporating the ADS1115 and everything works really well now. I consider the hardware to be officially complete at this point. I'm still tweaking the software a bit but that's pretty close too.
I have also started to write a Python script that will make the creation of trivia question cards, well, trivial. I see the light at the end of the tunnel.
-
For Want of a Pin the Project Remains Thus Far Unfinished
01/23/2021 at 21:30 • 0 commentsI'm coming down to the wire, literally, for my Think-a-Tron 2020 project. The anticipation is killing me but I have a couple of small problems. For one I'm a pin short. I thought I was OK based on the documentation I had on hand. This was my plan.
ESP32-CAM PIN Think-a-Tron 2020 Connection GPIO 0 Not Available Camera XCLK GPIO 1 Trigger Sound Effect GPIO 2 Player 1 Answer Buttons (ADC) GPIO 3 NeoPixels (all 45 of them) GPIO 4 Camera LED GPIO 12 Score Counters SDI GPIO 13 Score Counters SCLK GPIO 14 Player 2 Answer Buttons (ADC) GPIO 15 Score Counters LOAD GPIO 16 ? Button *** The problem is that GPIO 16 is in fact used by the Camera although this is not documented anywhere I could find. Any use of GPIO 16 causes the camera to crash when capturing an image (sigh).
The other issue is that the analog readings that I am getting from the player inputs suck. Even averaging multiple analog reads sometimes results in the wrong button being "selected".
So I have ordered a 16 bit high precision 4 channel I2C IIC Analog-to-Digital Converter based on the ADS1115 PGA ADC converter chip (Amazon).
So in a couple of days I'll see if I can convert two pins for an I2C bus into 3 inputs: Player1, Player2, and ? buttons. Hopefully I can kill two problems with one piece of silicon.
-
Wire Wrangling
01/21/2021 at 17:17 • 0 commentsSo with everything together I realized that I had to deal with a lot of wires.
If I were doing this again I would definitely put more thought into the whole wiring thing. But moving forward I need to try to to tame this mess. I decided to create a "shield" for the ESP32-CAM that would at least position the correct headers on the side that the wires were coming from.
Again if I could afford the shipping I would have made a PCB. As it is I'm getting lots of practice doing point-to-point wiring on proto boards for this project. I printed a card with the expected connections (which I assure you do line up with the pins when you don't have photo perspective issues). Here is the result.
A little better I think. I don't mind the showing what happens behind the curtains. So that's pretty much it for the hardware. Now to marry all of my little test sketches into a proper program for Think-a-Tron 2020.
-
A Second Sneak Peek
01/17/2021 at 14:56 • 0 commentsWhile I was assembling the various pieces of Think-a-Tron 2020 it occurred to me that a wrap-around console look might be cool. This is what I ended up with.
-
Core Console
01/17/2021 at 02:46 • 0 commentsThe heart of Think-a-Tron 2020 consists of three main parts: the camera QR code reader section, the display, and the question (?) console with speaker.
The ESP32-CAM is mounted in a rectangular box forming the main part of the core section.
You can see there is a mounting slot for the ESP32-CAM and a guide to hold the question card (with QR code) at the correct distance for the camera to be able to read it. The top for this section has a slot that lines up with the guide to accept the card.
I had some buck converters lying around so I used one to power the project.
The power jack supplies the input voltage to the converter, with the output of 5V and GND connected o a small power rail that I made with headers and perf board. I mounted this with a small 3D printed bracket that I glued to the back.
I decided that I want to add a sound effect to the project so I purchased a cheap solid state recording and playback module (ISD1820 Sound Voice Recording Playback Module with Mic Sound Audio Microphone - Amazon).
I attached some jumper wires to the VCC, GND, and P-L connectors. Whatever you record on the device (which can hold about 10 seconds of sound) will play back so long as the P-L pin is held high).
There is a slot for the sound module on the opposite side of the card holder.
Run the jumpers along the left had side and out through the hole at the back as can be seen above. I secured the module with a piece of two sided tape.
I printed a small console to hold a single button and the speaker for the sound module.
I attached the printed speaker grill to the console front with a few dabs of glue. There are slots to mount the speaker, which I secured in place with some electrical tape. I attached leads to the push button common and NO pins.
Attach the speaker to the sound module and run the push button cables along the side and out the back the same as was done with the sound module. Attach the button panel to the console. Glue the console to the main part of the core section.
I printed a stand to hold the 5x7 NeoPixel display. I modified the wiring for the display case slightly so that the wires exited straight back instead of down.
Insert the display with case into the stand and glue the assembly to the top of main core section.
With everything assembled I wired the core unit up for a test.
Mounting the ESP32-CAM this way makes it easy to access the headers and more importantly the reset button. I wrote an little "integration" test to exercise the components that I just added to the core unit.
#include "Adafruit_NeoPixel.h" #include "ESPino32CAM.h" #include "ESPino32CAM_QRCode.h" // Camera objects. ESPino32CAM cam; // Image capture object ESPino32QRCode qr; // Image decoding object // Set the camera pins. #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 // Think-a-Tron Controls. #define FLASH 4 #define SOUND 14 #define QUESTION 15 // 5x7 LED Array. #define NUM_LEDS 35 #define LEDS_PIN 2 Adafruit_NeoPixel matrix(NUM_LEDS, LEDS_PIN, NEO_GRB + NEO_KHZ800); int A[35] = {0,0,1,1,1,1,1, 0,1,0,0,1,0,0, 1,0,0,0,1,0,0, 0,1,0,0,1,0,0, 0,0,1,1,1,1,1}; int B[35] = {1,1,1,1,1,1,1, 1,0,0,1,0,0,1, 1,0,0,1,0,0,1, 1,0,0,1,0,0,1, 0,1,1,0,1,1,0}; int C[35] = {0,1,1,1,1,1,0, 1,0,0,0,0,0,1, 1,0,0,0,0,0,1, 1,0,0,0,0,0,1, 0,1,0,0,0,1,0}; int T[35] = {1,0,0,0,0,0,0, 1,0,0,0,0,0,0, 1,1,1,1,1,1,1, 1,0,0,0,0,0,0, 1,0,0,0,0,0,0}; int F[35] = {1,1,1,1,1,1,1, 1,0,0,1,0,0,0, 1,0,0,1,0,0,0, 1,0,0,1,0,0,0, 1,0,0,0,0,0,0}; int Q[35] = {0,1,0,0,0,0,0, 1,0,0,0,0,0,0, 1,0,0,0,1,0,1, 1,0,0,1,0,0,0, 0,1,1,0,0,0,0}; String lastResult = ""; void setup() { // Define the sound pin. pinMode(SOUND, OUTPUT); digitalWrite(SOUND, LOW); // Set sound off. // Serial debugging. Serial.begin(115200); Serial.println("QR Code Reader"); // Define the flash pin. pinMode(FLASH, OUTPUT); digitalWrite(FLASH, LOW); // Turn off the flash. // Define the question button pin. pinMode(QUESTION, INPUT_PULLUP); // Configure the camera pins. camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_VGA; config.jpeg_quality = 4; config.fb_count = 1; // Setup the camera. esp_err_t err = esp_camera_init(&config); // Initialize the camera. if (err != ESP_OK) { Serial.printf("Camera start failed with error 0x%x", err);//Informa erro se a câmera não for iniciada corretamente delay(1000); ESP.restart(); // Reboot the ESP } // Initialize the decoding object. qr.init(&cam); sensor_t *s = cam.sensor(); s->set_framesize(s, FRAMESIZE_CIF); s->set_whitebal(s, true); // Setup the 5x7 LED Array. matrix.begin(); matrix.show(); // Initialize all pixels to 'off'. showLetter(Q); // Announce. Serial.println(); Serial.println("Think-a-Tron 2020 Ready."); } void loop(){ unsigned long pv_time = millis(); // Captura an image. digitalWrite(FLASH, HIGH); // Turn on the flash. for (int i = 0; i < 10; i++) { if (digitalRead(QUESTION) == LOW) { showLetter(Q); lastResult = ""; } delay(40); } camera_fb_t *fb = cam.capture(); digitalWrite(FLASH, LOW); // Turn off the flash. if (!fb) { Serial.println("Image capture failed."); return; } // Attempt to read a QR code from the image. dl_matrix3du_t *rgb888, *rgb565; if (cam.jpg2rgb(fb, &rgb888)) { rgb565 = cam.rgb565(rgb888); } cam.clearMemory(rgb888); cam.clearMemory(rgb565); dl_matrix3du_t *image_rgb; if (cam.jpg2rgb(fb, &image_rgb)) { cam.clearMemory(fb); qrResoult res = qr.recognition(image_rgb); // Decodes the image containing the data. if (res.status) { // If you can decode the image it shows the data on the screen. String result = res.payload; // Variable to show the data contained in the QR Code. if (result != lastResult) { lastResult = result; if (res.payload == "A") { showRandom(); showLetter(A); } else if (res.payload == "B") { showRandom(); showLetter(B); } else if (res.payload == "C") { showRandom(); showLetter(C); } else if (res.payload == "T") { showRandom(); showLetter(T); } else if (res.payload == "F") { showRandom(); showLetter(F); } else { showLetter(Q); } } Serial.println(); Serial.println(result); // Shows data on the serial monitor. } else { // If you do not wait to receive code. Serial.println(); Serial.println("Waiting for code."); } } cam.clearMemory(image_rgb); // Delete image to receive a new image. } // Display the letter passed on the 5x7 LED Array. void showLetter(int letter[]) { for (int i = 0; i < NUM_LEDS; i++) { if (letter[i] == 1) { matrix.setPixelColor(i, 32, 32, 32); } else { matrix.setPixelColor(i, 0, 0, 0); } } matrix.show(); } // Show a random changing pattern on the 5x7 LED Array. void showRandom() { // Trigger the sound. digitalWrite(SOUND, HIGH); for (int count = 0; count <110; count++) { for (int i = 0; i < NUM_LEDS; i++) { if (random(3) == 0) { matrix.setPixelColor(i, 32, 32, 32); } else { matrix.setPixelColor(i, 0, 0, 0); } } matrix.show(); delay(50); } digitalWrite(SOUND, LOW); }
And here is what that test looks like.
I'm coming down to the wire. I just have to incorporate the player one and two panels and the hardware and software will be done.
-
Sneak Peek
01/12/2021 at 02:04 • 0 commentsWhile there is still a lot of wiring and integration work to do, I'd like to share a photo of what is probably the final design for Think-a-Tron 2020.
-
Ready Player One
01/08/2021 at 19:17 • 0 commentsWith all of the little bits sorted, I just finished the first major subassembly for Think-a-Tron 2020, the Player Console.
You'll notice something new in the picture above. A two digit seven-segment display that will be used to keep track of the player's score. I made five of the NeoPixel push button switches I talked about in my last log entry. The only change I made was to incorporate the switch leads into my header jack.
So from top to bottom we have:
- +5V
- Led In
- Led Out
- GND
- Switch Common
- Switch NO
I used a perf board with headers to help sort out the wiring. This Player Module also has the four 2K resistors used to determine which button is pressed. Point-to-point wiring was tight but doable. If I could get PCBs cheaply (it's the shipping to Canada that kills me), I would have gone that route.
I created a small slotted clamp to hold the Player Module perf board and attached it with two sided tape (or glue) to the back of a panel that I printed to mount the switches and display.
I added headers to the back of the seven-segment display (a Robojax Whmxe 595-2 74HC595 Driving 2 Digit 0.5in Seven Segment Display for Arduino - from Amazon) and mounted the display in the center rectangle with the raised edges. The front of the display should end up being flush with the front face of the panel. The switches are pushed in from the front of the panel and held in place with the tabs you can see in the second picture below.
I slid the perf board into the holder and began plugging in the switches.
Then I wired the Player Console to an Arduino Nano for a test.
Connect:
From To Player Module VCC Arduino +5V Player Module LEDS In Arduino D6 Player Module GND Arduino GND SS Display VCC Player Module VCC (Second Header) SS Display GND Player Module GND (Second Header) SS Display SDI Arduino D9 SS Display SCLK Arduino D8 SS Display LOAD Arduino D7 With everything attached mount the Player Panel onto the printed console seen below.
And the final result can be seen below running a small test sketch. The buttons are programmed to light when pressed and act as radio buttons so the only the last button pressed is illuminated. The counter was programmed to increment on each button press.
#include "FastLED.h" #include <ShiftRegister74HC595.h> #define SDI 9 #define SCLK 8 #define LOAD 7 #define DIGITS 2 // create shift register object (number of shift registers, data pin, clock pin, latch pin) ShiftRegister74HC595 sr (DIGITS, SDI, SCLK, LOAD); int value,digit1,digit2,digit3,digit4; uint8_t digits[] = {B11000000, //0 B11111001, //1 B10100100, //2 B10110000, //3 B10011001, //4 B10010010, //5 B10000010, //6 B11111000, //7 B10000000, //8 B10010000 //9 }; #define NUM_LEDS 5 #define LEDS_PIN 6 CRGB leds[NUM_LEDS]; int buttons = 0; int A = 0; int B = 1; int C = 2; int T = 3; int F = 4; int countA = 0; int countB = 0; int countC = 0; int countT = 0; int countF = 0; int score = 0; void setup() { Serial.begin(115200); Serial.println("Single LED"); pinMode(A5, INPUT_PULLUP); FastLED.addLeds<NEOPIXEL, LEDS_PIN>(leds, NUM_LEDS); clearAnswers(); FastLED.show(); } void loop() { buttons = analogRead(5); Serial.println(buttons); if (buttons <= 24 && buttons >= 04) countA++; if (buttons <= 86 && buttons >= 66) countB++; if (buttons <= 134 && buttons >= 114) countC++; if (buttons <= 177 && buttons >= 157) countT++; if (buttons <= 215 && buttons >= 195) countF++; if (countA > 3) {showAnswer(A); FastLED.show(); waitForButtonUp(); score++; countA = 0; countB = 0; countC = 0; countT = 0; countF = 0;} if (countB > 3) {showAnswer(B); FastLED.show(); waitForButtonUp(); score++; countA = 0; countB = 0; countC = 0; countT = 0; countF = 0;} if (countC > 3) {showAnswer(C); FastLED.show(); waitForButtonUp(); score++; countA = 0; countB = 0; countC = 0; countT = 0; countF = 0;} if (countT > 3) {showAnswer(T); FastLED.show(); waitForButtonUp(); score++; countA = 0; countB = 0; countC = 0; countT = 0; countF = 0;} if (countF > 3) {showAnswer(F); FastLED.show(); waitForButtonUp(); score++; countA = 0; countB = 0; countC = 0; countT = 0; countF = 0;} showScore(score, 0); delay(20); } void waitForButtonUp() { while (analogRead(5) < 300) { delay(50); } } void clearAnswers() { for(int i = 0; i < NUM_LEDS; i++) { leds[i] = CRGB::Black; } } void showAnswer(int answer) { clearAnswers(); leds[answer] = CRGB::White; } void showScore(int score1, int score2) { digit2=score1 % 10 ; digit1=(score1 / 10) % 10 ; //Send them to 7 segment displays uint8_t numberToPrint[]= {digits[digit2],digits[digit1]}; sr.setAll(numberToPrint); //digit4=score2 % 10 ; //digit3=(score2 / 10) % 10 ; //Send them to 7 segment displays //uint8_t numberToPrint2[]= {digits[digit4],digits[digit3]}; //sr.setAll(numberToPrint2); }
Player two will be ready soon.