I mentioned in my introduction post that I've made a puzzle that I plan on using for my chemistry classes. This puzzle requires the student to determine the empirical formula from the percent composition of each element. The puzzle is in the form of a box with a sliding door, a control panel, and an On/Off switch. Insider are an Arduino Nano, a buck converter (with 9V battery), an LCD screen, a momentary switch button, a rotary encoder, a servo motor, and a RC522 RFID reader. I designed the box in Fusion 360 and printed out the parts on my Prusa Mk3 printer.
When the device is turned on, a default puzzle with an (relatively) easy solution is given. This on has a solution of C2H5NO.
The rotary encoder (the adjust knob) is used to change the value of each element. Pushing the rotary encoder button toggles between each element. Once the student is confident that they have the answer, they use the test button to attempt to open the lock (a 9g servo motor). A correct answer gives them an affirmative message and a satisfying swish sound from the servo motor. They can then slide the door on top open and see what's inside.
The cool part is that you can update the puzzle with a new problem by scanning a programed RFID card, which changes the screen and the variables in memory for the correct answer. The programming of this took a while and was very educational for me. Here's a card with a much more challenging problem programmed into the RFID memory.
The data is easy to program onto the cards. There's even an Android app you can use to copy the data onto the card. Here's what the ASCII data on the card looks like.
I'm pleased with how well it worked out and learned how to integrate several of the components thanks to DroneBot Workshop videos. I'm going to post the code for the Arduino (if anyone wants to see what I did) as a reply to this post because it is quite long.
Here's the Arduino code for those interested in such things. I'm not much of an expert at coding, but I posted lots of comment code to explain each library, variable, pin assignment, and instance.
// Libraries included
#include <LiquidCrystal_I2C.h> //Includes the I2C liquid crystal library
#include <string.h> // Includes the string library
#include <Servo.h> // Includes the servo library
#include <SPI.h> // Includes the Serial Peripheral Interface library
#include <MFRC522.h> // Includes the MFRC522 RFID library
// pin inputs and outputs
#define inputCLK 2 // Rotary encoder CLK input pin
#define inputDT 5 // Rotary encoder DT input pin
#define switchPin 3 // Rotary encoder SW (switch) input pin. The remaining pins on the rotary encoder are to ground and VCC.
#define testPin 4 // Test button pin. Other side is connected to ground.
#define servoPin 6 // Servo motor data pin (usually the third pin that isn't ground or VCC)
#define SS_Pin 10 // MFRC522 SPI SS (SDA) pin
#define RST_Pin 9 // MFRC522 SPI Reset (RST) pin
// The remaining pins on the MFRC522 are defined by the library: SPI MOSI D11, SPI MISO D12, SPI SCK D13
int currentStateCLK; // initializes the variable for the current state of the rotary encoder
int previousStateCLK; // initializes the variable for the previous state of the rotary encoder
int element; // initializes the variable for the element (C=0, H=1, N=2, O=3). Activated in updateElement function using button of rotary encoder
int CarboNum; // initializes the variable for the user input number of carbons
int HydroNum; // initializes the variable for the user input number of hydrogens
int NitroNum; // initializes the variable for the user input number of nitrogens
int OxyNum; // initializes the variable for the user input number of oxygens
int CarboCount; // initializes the variable for the count sent from the rotary encoder for carbon
int HydroCount; // initializes the variable for the count sent from the rotary encoder for hydrogen
int NitroCount; // initializes the variable for the count sent from the rotary encoder for nitrogen
int OxyCount; // initializes the variable for the count sent from the rotary encoder for oxygen
int period = 1000; // initializes the variable for the wait period in milliseconds during the loop
String line0 = "C: 40.66% H: 8.55%"; // initializes default value for line 0 of LCD display
String line1 = "N: 23.71% O: 27.08%"; // initializes default value for line 1 of LCD display
String line2 = ""; // initializes default value for line 2 of LCD display
String line3 = ""; // initializes default value for line 3 of LCD display
int CHNOsub[5] = {2, 5, 1, 1}; // initializes variable array with correct subscripts for C, H, N, and O. Default values will be changed via RFID
unsigned long time_now = 0; // initializes current time to 0 upon startup or reset
unsigned long LastElemDebounceTime = 0; // initializes comparative value for debounce of updateElement subroutine
unsigned long debounceDelay = 500; // initializes the debounce time for updateElement subroutine. Increase if elements are getting skipped when rotary encoder button is pushed.
// functions other than setup and loop
void updateElement(); // function to update which element is adjusted using rotary encoder
void AdjustCounter(); // function to update counter of an element
void test(); // function to test the results currently in memory and on the LCD screen
void updatePuzzle(); // function to update puzzle with RFID card
// Create instances of hardware processes
LiquidCrystal_I2C lcd(0x27, 20, 4); //Instantiates the LiquidCrystal_I2C for address 0x27 and 20 chars, 4 lines
MFRC522 mfrc522(SS_Pin, RST_Pin); // Instantiates MFRC522 with pin assignments
Servo myservo; // Instantiates a Servo object named "myservo" to control a servo motor
void setup() {
// put your setup code here, to run once:
// Set encoder pins as inputs
pinMode (inputCLK,INPUT);
pinMode (inputDT,INPUT);
pinMode (switchPin,INPUT_PULLUP);
// Set Test button as input
pinMode (testPin,INPUT_PULLUP);
// Setup Serial Monitor
Serial.begin (9600);
// Initialize SPI bus
SPI.begin();
// Initialize MFRC522 card
mfrc522.PCD_Init();
// Set up servo
myservo.attach(servoPin);
myservo.write(90);
// Setup LCD screen
lcd.init();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print(line0);
lcd.setCursor(0,1);
lcd.print(line1);
// Read the initial state of inputCLK
// Assign to previousStateCLK variable
previousStateCLK = digitalRead(inputCLK);
element = 0;
CarboNum = 0;
HydroNum = 0;
NitroNum = 0;
OxyNum = 0;
CarboCount = 0;
HydroCount = 0;
NitroCount = 0;
OxyCount = 0;
attachInterrupt(digitalPinToInterrupt(switchPin), updateElement, CHANGE); // establishes rotary encoder switch pin as an interrupt
attachInterrupt(digitalPinToInterrupt(inputCLK), AdjustCounter, CHANGE); // establishes rotary encoder dial as an interrupt
}
void loop() {
// put your main code here, to run repeatedly:
if(millis() >= time_now + period) {
time_now += period;
Serial.print("C:");
Serial.print(CarboNum);
Serial.print(" H:");
Serial.print(HydroNum);
Serial.print(" N:");
Serial.print(NitroNum);
Serial.print(" O:");
Serial.println(OxyNum);
String CNum = String(CarboNum);
String HNum = String(HydroNum);
String NNum = String(NitroNum);
String ONum = String(OxyNum);
String line2 = String("C: " + CNum + " H: " + HNum + " ");
String line3 = String("N: " + NNum + " O: " + ONum + " ");
lcd.setCursor(0,2);
lcd.print(line2);
lcd.setCursor(0,3);
lcd.print(line3);
}
CarboNum=CarboCount/2;
HydroNum=HydroCount/2;
NitroNum=NitroCount/2;
OxyNum=OxyCount/2;
test();
updatePuzzle();
}
void test() {
int testVal = digitalRead(testPin);
if (testVal == LOW) {
if (CarboNum == CHNOsub[0] && HydroNum == CHNOsub[1] && NitroNum == CHNOsub[2] && OxyNum == CHNOsub[3]) {
Serial.print("SUCCESS!!!");
lcd.setCursor(0,2);
lcd.print("Answer is correct!");
lcd.setCursor(0,3);
lcd.print(" ");
myservo.write(0);
delay(10000);
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print(" ");
} else {
Serial.print("FAILURE");
lcd.setCursor(0,2);
lcd.print("Answer is incorrect!");
lcd.setCursor(0,3);
lcd.print("Please try again.");
myservo.write(90);
delay(2000);
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print(" ");
}
}
}
void updateElement() {
int sensorVal = digitalRead(switchPin);
if (sensorVal == LOW) {
if ((millis() - LastElemDebounceTime) > debounceDelay) {
element++;
LastElemDebounceTime = millis();
if (element >3) {
element = 0;
}
}
}
}
void AdjustCounter() {
// Read the current state of inputCLK
currentStateCLK = digitalRead(inputCLK);
// If the previous and the current state of the inputCLK are different then a pulse has occured
if (currentStateCLK != previousStateCLK) {
// Nested loop ** If the inputDT state is different than the inputCLK state then the encoder is rotating counterclockwise
if (digitalRead(inputDT) != currentStateCLK) {
switch(element){
case 0:
CarboCount--;
if (CarboCount<0) {
CarboCount=0;
}
break;
case 1:
HydroCount--;
if (HydroCount<0) {
HydroCount=0;
}
break;
case 2:
NitroCount--;
if (NitroCount<0) {
NitroCount=0;
}
break;
case 3:
OxyCount--;
if (OxyCount<0) {
OxyCount=0;
}
break;
}
} else {
// Otherwise the encoder must be rotating clockwise
switch(element){
case 0:
CarboCount++;
if (CarboCount>199) {
CarboCount=199;
}
break;
case 1:
HydroCount++;
if (HydroCount>199) {
HydroCount=199;
}
break;
case 2:
NitroCount++;
if (NitroCount>199) {
NitroCount=199;
}
break;
case 3:
OxyCount++;
if (OxyCount>199) {
OxyCount=199;
}
break;
}
}
}
// Update previousStateCLK with the current state
previousStateCLK = currentStateCLK;
}
void updatePuzzle(){
// Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
//some variables we need
byte block;
byte len;
MFRC522::StatusCode status;
//-------------------------------------------
// Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
// Select one of the cards
if ( ! mfrc522.PICC_ReadCardSerial()) {
return;
}
Serial.println(F("**Card Detected:**"));
//-------------------------------------------
mfrc522.PICC_DumpDetailsToSerial(&(mfrc522.uid)); //dump some details about the card
//mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); //uncomment this to see all blocks in hex
//-------------------------------------------
byte carbonPer[18];
line0 = "";
line1 = "";
block = 1;
len = 18;
//------------------------------------------- GET Carbon Percentage
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 1, &key, &(mfrc522.uid)); //line 834 of MFRC522.cpp file
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, carbonPer, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Carbon Percentage
Serial.print(F("Carbon: "));
for (uint8_t i = 0; i < 10; i++)
{
Serial.write(carbonPer[i]);
line0 += (char)carbonPer[i];
}
Serial.println(" ");
// lcd.printstr(carbonPer);
//---------------------------------------- GET Hydrogen Percentage
byte hydrogenPer[18];
block = 2;
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 2, &key, &(mfrc522.uid)); //line 834
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, hydrogenPer, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Hydrogen Percentage
Serial.print(F("Hydrogen: "));
for (uint8_t i = 0; i < 10; i++ ) {
Serial.write(hydrogenPer[i] );
line0 += (char)hydrogenPer[i];
}
Serial.println(" ");
lcd.setCursor(0,0);
lcd.print(line0);
//---------------------------------------- GET Nitrogen Percentage
byte nitrogenPer[18];
block = 4;
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 4, &key, &(mfrc522.uid)); //line 834
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, nitrogenPer, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Nitrogen Percentage
Serial.print(F("Nitrogen: "));
for (uint8_t i = 0; i < 10; i++) {
Serial.write(nitrogenPer[i] );
line1 += (char)nitrogenPer[i];
}
Serial.println(" ");
//---------------------------------------- GET Oxygen Percentage
byte oxyPer[18];
block = 5;
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 5, &key, &(mfrc522.uid)); //line 834
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, oxyPer, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Oxygen Percentage
Serial.print(F("Oxygen: "));
for (uint8_t i = 0; i < 10; i++) {
Serial.write(oxyPer[i] );
line1 += (char)oxyPer[i];
}
Serial.println(" ");
lcd.setCursor(0,1);
lcd.print(line1);
//----------------------------------------
byte CSub[18];
String CSubAsc = "";
block = 8;
len = 18;
//------------------------------------------- GET Carbon Subscript
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 8, &key, &(mfrc522.uid)); //line 834 of MFRC522.cpp file
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, CSub, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Carbon Number
Serial.print(F("C: "));
for (uint8_t i = 0; i < 2; i++)
{
Serial.write(CSub[i]);
CSubAsc += (char)CSub[i];
}
Serial.println(" ");
CHNOsub[0] = CSubAsc.toInt();
Serial.println(CSubAsc);
Serial.println(CHNOsub[0]);
//----------------------------------------
byte HSub[18];
String HSubAsc = "";
block = 9;
len = 18;
//------------------------------------------- GET Hydrogen Subscript
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 9, &key, &(mfrc522.uid)); //line 834 of MFRC522.cpp file
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, HSub, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Hydrogen Number
Serial.print(F("H: "));
for (uint8_t i = 0; i < 2; i++)
{
Serial.write(HSub[i]);
HSubAsc += (char)HSub[i];
}
Serial.println(" ");
CHNOsub[1] = HSubAsc.toInt();
Serial.println(HSubAsc);
Serial.println(CHNOsub[1]);
//----------------------------------------
byte NSub[18];
String NSubAsc = "";
block = 10;
len = 18;
//------------------------------------------- GET Nitrogen Subscript
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 10, &key, &(mfrc522.uid)); //line 834 of MFRC522.cpp file
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, NSub, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Nitrogen Number
Serial.print(F("N: "));
for (uint8_t i = 0; i < 2; i++)
{
Serial.write(NSub[i]);
NSubAsc += (char)NSub[i];
}
Serial.println(" ");
CHNOsub[2] = NSubAsc.toInt();
Serial.println(NSubAsc);
Serial.println(CHNOsub[2]);
//----------------------------------------
byte OSub[18];
String OSubAsc = "";
block = 12;
len = 18;
//------------------------------------------- GET Oxygen Subscript
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 12, &key, &(mfrc522.uid)); //line 834 of MFRC522.cpp file
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentication failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
status = mfrc522.MIFARE_Read(block, OSub, &len);
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Reading failed: "));
Serial.println(mfrc522.GetStatusCodeName(status));
return;
}
//PRINT Oxygen Number
Serial.print(F("O: "));
for (uint8_t i = 0; i < 2; i++)
{
Serial.write(OSub[i]);
OSubAsc += (char)OSub[i];
}
Serial.println(" ");
CHNOsub[3] = OSubAsc.toInt();
Serial.println(OSubAsc);
Serial.println(CHNOsub[3]);
Serial.println(F("\n**End Reading**\n"));
delay(1000); //change value if you want to read cards faster
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
//*****************************************************************************************//
The cool part is that you can update the puzzle with a new problem by scanning a programed RFID card, which changes the screen and the variables in memory for the correct answer. The programming of this took a while and was very educational for me. Here's a card with a much more challenging problem programmed into the RFID memory.
The data is easy to program onto the cards. There's even an Android app you can use to copy the data onto the card. Here's what the ASCII data on the card looks like.
I was wondering why you didn't make up an SD card with all of the data and eliminate the physical RFID cards. You could then just use a random number to select the next problem and keep working.
Was it because you wanted to phase in harder problems as the student went on ?
Anything seems possible when you don't know what you're talking about.
@will The simple answer is that I had bought a pack of RFID readers and was looking for something to do with them. This was as much a self-learning exercise as it was a project for developing teaching tools.
@will The simple answer is that I had bought a pack of RFID readers and was looking for something to do with them.
LOL, I understand completely 🙂
This was as much a self-learning exercise as it was a project for developing teaching tools.
Well it turned out great. It's a really good mix of 3D design and printing, the Arduino and a nice assortment of useful components well integrated into a practical and operational end product. And it looks good too.
Well done !
Anything seems possible when you don't know what you're talking about.
ReplyQuote
We use cookies on the DroneBot Workshop Forums to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.