-
1Components I Used
Below are links to the components I used, or equivalent parts. (Amazon links are through my amazon associates account and are only components I have tried and tested).
- ( 1 ) 5.5v 1.5F Super Capacitor:
- ( 1 ) 53x30 5v Solar Panel:
- Amazon
- Also search Ebay
- ( 1 ) Attiny85:
- ( 1 ) 8 Pin IC Socket:
- ( 1 ) 5v6 Zener Diode
- ( 2 ) 1N5817 Schottky Diode:
- ( 1 ) 4x6cm Proto Board
- ( 5 ) 6x6mm Tactile switch:
- ( 2 ) LED:
- ( 11 ) Resistors:
- ( 1 ) 20 Ohm
- ( 2 ) 10 k
- ( 1 ) 20 k
- ( 1 ) 100 k
- ( 1 ) 51.1 k
- ( 1 ) 30 k
- ( 1 ) 15 k
- ( 1 ) 4.7 k
- ( 1 ) 3.9 M
- ( 1 ) 1 M
- ( 3 ) 100nf capacitor:
- ( 1 ) 9 or 10 pin header:
- ( 1 ) Slide Switch:
- Wires
-
2Assembly
I would strongly recommend building the device on a breadboard first and flashing/testing the code before soldering together. Here is the schematic:
ADC values in code for buttons, as well as VCC voltage may vary depending on values/tolerances of resistors you use.
In this prototype I used different value resistors than what is posted on schematic.
The Button resistors are pretty close to what I used, but I don't remember the exact values used, so in the Arduino sketch you will most likely have to adjust these values.
The VCC voltage divider has been changed in schematic to save power.
The working prototype is using a 100k to VCC, and 22k to GND for VCC adc voltage divider. In the schematic I changed that to 3.9M and 1M, with a 100nf capacitor.
If you use the 100/22k, the code should work "out of the box" for this, as well I know it works.
I am going to test the higher level resistors and post on whether or not it works reliably, so until then probably just safer to use 100/22k.
Using the provided schematic, here is the order I did everything:
- First make sure the Super Capacitor is at 0v, if its not then drain it with a resistor. Place and solder it on the protoboard.
- Place and solder buttons
- Place and solder IC socket
- Place and solder pin header and slide switch
- Place and solder LEDs
- Now begin placing and soldering resistors, diodes, and capacitors.
- Once all those connections are made, use jumper wires to finish off connections.
- Finally, on the back side, use two short jumper wires to attach solar cell, making sure to have the polarity correct. I attached one end, then used some 3mm double stick foam tape to prevent the solar cell from moving or shorting anything out on back, then once it was placed soldered the other end.
A PCB made for this project has been ordered, and once I receive them I will make an assembly video.
-
3Flashing Attiny85
PDOH * Set PB2 Output HIGH * PD0L * Set PB2 Output LOW *
I used Arduino Uno as ISP to flash device.
- Download ino file from this project page files or my github.
- Install Tinycore for attiny85: https://github.com/SpenceKonde/ATTinyCore
- "Burn Bootloader"
- using ISCP to attiny85:
- Board: ATTinyCore: Attiny25/45/85(No Bootloader)
- BOD: Disabled
- Chip: Attiny85
- Clock Source: 1mhz Internal
- Save EEPROM: retained
- LTO: Enabled
- millis()/micros: Enabled
- Timer 1 Clock: CPU frequency
- Board: ATTinyCore: Attiny25/45/85(No Bootloader)
- Flash Attiny85 using "Upload Using Programmer".
If the ADC values for buttons are correct, you should be able to navigate menu. I think i used a multimeter to get the exact voltage ratios for each button press.
If some buttons aren't working, you will have to adjust in sketch and reflash.
-
4Using The Device
Menus and Button Actions:
Menu 0 = No LED,
Menu 1 = LED 1,
Menu 2 = LED 0,
Menu 3 = LED 1 & LED 0
- Main Menu Buttons:
- Top Left Button: "<", Menu Left
- Top Right Button: ">", Menu Right
- Center Button: "T", Select Menu Choice:
- Menu 0: Run
- Menu 1: Byte Mode
- Menu 2: Load From EEPROM
- Menu 3: Save to EEPROM
- Bottom Left Button: "S", Blink Program Bytes
- Bottom Right Button: "R", Run Program
- Menu 0 (Run) Buttons:
- "<" Menu Left
- ">" Menu Right
- "S", Main Menu
- "R", Run Selected Program:
- Menu 0: Blink Entire Program
- Menu 1: Blink Voltage
- Menu 2: Serial Read/Load
- Menu 3: Empty
- Menu 1 (Byte Mode) Buttons:
- "<", Program Byte Left
- ">", Program Byte Right
- "T", Edit Byte:
- BIT MODE BUTTONS:
- "<", Bit Position Left.
- ">", Bit Position Right
- "T", Toggle bit
- "S", Byte Mode
- "R", Blink Byte
- BIT MODE BUTTONS:
- "S", Main Menu
- "R", Blink Byte
- Menu 2 (Load Program From EEPROM) Buttons:
- "<", EEPROM Slot Left
- ">", EEPROM Slot Right
- "T", Load Selected Slot
- "S", Main Menu
- "R", No Function
- Menu 3 (Save Program To EEPROM) Buttons:
- "<", EEPROM Slot Left
- ">", EEPROM Slot Right
- "T", Load Selected Slot
- "S", Main Menu
- "R", No Function
- Main Menu Buttons:
-
5Programming With TGRK
After entering Byte Mode on device (Menu 1), we can navigate through each byte in the program using the ">" and "<" buttons.
Pressing the "T" button will enter Bit Mode so we can edit the selected byte.
Using the Keyword table found on my GitHub, or on this projects files section, we can start writing programs in TGRK.
The program examples here will be written using the keyword names, which can be used to lookup the binary number for that keyword. Programming this way is very tedious, so I cannot imagine someone wanting to write a super long program. If we wrote a program that used all 127 bytes available, that would be 1016 individual bits that would be need to be set individually!
I will do my best here to describe all the keywords and what they do. There is definitively room for improvement with a lot of things with this device and interpreter, but as of now it is functional, and seems to be working better than I originally expected it would.
Registers:
These are 8 variables (R1-R8), that can store a number in. TGRK uses int8_t 8 bit integers, with any negative number being a keyword, and any positive number 0-127 is an integer. We don't want any negativity in our interpreter, so if a number calculation ever goes negative, it gets set to 0. Why did I do it this way? I really don't know.
Using R1 as an example, to set the variable:
R1 <value byte>
The value can be an integer 0-127, for example, lets set it to the answer to the Ultimate Question of Life, The Universe, and Everything:
R1 42
The value can be another register, in this instance setting R1's value to that of R2's value:
R1 R2
An EEPROM read byte, setting R1's value to the value of EEPROM address 3:
R1 ER 3
A pin's value:
R1 PDIU [PDIU = PB2, either 1 or 0 for HIGH or LOW] R1 PAIX [PAIX = ADC1, 2 digit analog voltage value: 36 = 3.6v] R1 PAXX [PAXX = VCC, 2 digit analog voltage value: 36 = 3.6v]
Doing math on a register looks like this:
R1 ADD 2 [R1 = R1 + 2] R1 SUB 2 [R1 = R1 - 2] R1 MUL 2 [R1 = R1 * 2] R1 DIV 2 [R1 = R1 / 2, Result is 0 if R1 is 0]
Math is done in the order it is written, in this example, using the standard order of operations, this math should add up to 16, but here the result is 24. Something to keep in mind when doing math.
R1 10 [R1 = 10] R1 SUB 2 ADD 4 DIV 2 MUL 2 [10 - 2 + 4 / 2 * 4 = 24]
We can do math on the value we are adding to our register:
R1 R2 ADD 16 DIV 2 [R1 = (R2 + 16 / 2)]
We don't have any floating point numbers in TGRK, only positive integers 0-127. Doing division that would result in a decimal will round down to the nearest integer:
R1 3 [R1 = 3] R1 / 2 [3 / 2 = 1]
Symbols:
We have already covered the math keywords in the above section. The two main symbols to be used will be SEP, and FUNC. I originally had SEP portrayed as " : " and FUNC as " # ", but when I switched all keywords to macros in the sketch, I had to use a different format.
SEP is used to define an expression block, and FUNC is used to define a function block:
[SEP] KEYWORD KEYWORD KEYWORD KEYWORD [SEP] Fx [FUNC] FUNCTION BLOCK [FUNC]
The function blocks are necessary for a function to work properly, but the expression separator is mainly important when doing loops or doing a conditional execution. We will get to conditionals in a moment.
AND and OR are used when joining conditions:
Cx VALUE1 VALUE2 AND Cx VALUE1 VALUE2 [following expression block only executes if both conditions are true] Cx VALUE1 VALUE2 OR Cx VALUE1 VALUE2 [following expression block executes if one condition is true]
Conditionals:
These keywords are used to compare two values, if the condition is true, the rest of the expression block is executed, if the condition is false, it is skipped. In this example we are checking if R1 is greater than 2. It is, so the expression would be executed:
R1 64 [Cxx][value1][value2][code to execute if true][SEP: ends the expression block]
Using the same value of 64 for R1, lets go through all the conditions:
CG R1 2 [ 64 > 2, True] CL R1 2 [ 64 < 2, False] CE R1 2 [ 64 == 2, False] CNE R1 2 [ 64 != 2, True] CGE R1 2 [ 64 >= 2, True] CLE R1 2 [ 64 <= 2, False]
Using the OR and AND keywords:
CG R1 2 AND CL R1 127 [ 64 > 2 && 64 < 127 , True] CL R1 2 OR CE R1 127 [ 64 < 2 || 64 == 127 , False] CE R1 2 AND CNE R1 127 [ 64 == 2 && 64 != 127, False] CNE R1 2 OR CGE R1 127 [ 64 != 2 || 64 >= 127, True] CGE R1 2 AND CLE R1 127 [ 64 >= 2 && 64 <= 127, True] CLE R1 2 OR CG R1 127 [ 64 <= 2 || 64 > 127, False]
Functions:
There are spaces for 8 functions, F1-F8. The function must be declared before calling it.
Declaring a function, using F1 as our function keyword, and code for multiplying R1 times 2.
F1 FUNC R1 MUL 2 FUNC
Calling a function:
F1
Functions can be nested up to 7 or 8 times... I can't remember which. But here is a ridiculous example of this being done.
R1 0 F1 FUNC R1 ADD 1 FUNC F2 FUNC R1 ADD 2 F1 FUNC F3 FUNC R1 ADD 3 F2 FUNC F4 FUNC R1 ADD 4 F3 FUNC F5 FUNC R1 ADD 5 F4 FUNC F6 FUNC R1 ADD 6 F5 FUNC F7 FUNC R1 ADD 7 F6 FUNC F8 FUNC R1 ADD 8 F7 FUNC F8 [All these functions should add to R1 until it is 36]
The FUNC keyword also acts as an expression separator. For example running this conditional inside a function, we don't have to put the SEP keyword at end of expression. The function will just be skipped if condition is False:
F1 FUNC CG R1 R2 R1 SUB 1 FUNC R1 21 R2 5 F1 [Condition is True this time, function runs] R2 36 F1 [Condition is False this time, function block gets skipped]
All of our registers (variables) are global in TGRK, so there is no need for parameters or return statements.
And that's about it for functions, let's move on to loops...
Loops:
There are 10 keywords for doing a loop in TGRK. Why 10? Well, there is the regular loop ( LP ), the keyword for looping a function (LPF) and also a keywords to loop the amount of times of any of the 8 register values.
The regular loop:
SEP [code to loop] LP SEP Fx FUNC [code to loop] LP FUNC
When TGRK reads the "LP" keyword, it reads backwards in the program until it reaches one of the following "SEP", "FUNC", or program address 0. This allows us to write extremely short programs if we want, or have functions or expression blocks that will keep looping until a condition is met.
Here is an example of one of those "extremely short programs". In fact it is only 1 byte long, consisting of only the LP keyword:
LP
This program will do nothing but keep the program in an infinite loop. Lets write a slightly longer program. In this program, if R1 (default starting value is 0) is less than 126 we add 1 and loop. This program will count from 0 to 127 then end:
CL R1 127 R1 ADD 1 LP
How about adding a loop into a function? In this program if R1 is greater than 2, and we call F1, it will keep subtracting 1 from R1 until it equals 2.
F1 FUNC CG R1 2 R1 SUB 1 LP FUNC R1 127 F1
This program checks the value of R1. If it is greater than 64, it keeps subtracting from R1 until it is equal to 64, if it is less than 24, it keeps adding to R1 until it equals 24. I am not sure of the use case for this program, but it is an example of using looped expressions inside of a function. When loop is called in a conditional expression block, and the condition is false, it is skipped like the rest of the expression.
F1 FUNC CG R1 64 R1 SUB 1 LP SEP CL R1 24 R1 ADD 1 LP SEP FUNC R1 4 F1
Function Loops:
If we have conditions within a function, we will need the SEP keyword in there. In that case we cannot use LP to loop the function, because TGRK will scan back until it reaches SEP, then start interpreting from there forward. That is where the LPF keyword comes in. LPF will scan back until hitting the FUNC block keyword. So if called within a function, it will start at the beginning of the function.
R1 127 F1 FUNC CG R1 0 R1 SUB 1 LP SEP CL R1 127 R1 ADD 1 LP SEP LPF FUNC F1
This function counts down from 127 to 0 then counts back up to 127 repeatedly.
Register Loops:
Register loops allow us to loop a function or expression block as many times as the value of the register we are looping. In this example we are using LR1 to loop [R2 ADD 1] 45 times (the value of R1).
R1 45 R2 0 SEP R2 ADD 1 LR1
Sleep Keywords:
There are two different types of sleep keywords, S1-S8, which is just a delay (1-8 seconds), and D0-D8, which puts the device in low power mode 1-8 minutes, or indefinitely until the "R" button is pressed.
L0H S1 * Delay 1 second * L0L D0 * Low power mode until R button * L0H S1 * Delay 1 second * L0H D8 * Low power mode for 8 min * LP
Pin and LED Control Keywords:
The following keywords are used to interact with PB2: PDOH, PDOL, PDIU, PDID, PAIX.
PB2 as output:
* Set PB2 as output HIGH * PDOH * Set PB2 as output LOW * PDOL
PB2 as input:
* Set PB2 as input PULLUP and read value into R1 * R1 PDIU * Set PB2 as input NO PULLUP and read value into R1 * R1 PDID
When calling PB2 as an input, it is accessible just like a register, or any other value, except it will either be a 1 for HIGH or a 0 for LOW.
PB2 (ADC1/A1) as analog input:
* Set PB2 as analog input, read value into R1 * R1 PAIX
PAIX will return a value from 0-53 corresponding to the voltage (53 = 5.3v, 26 = 2.6v, etc). The reference voltage for reading PAIX is default, or VCC. Therefore care must be taken to not read voltages above VCC.
PAXX returns the save voltage format, a 2 digit number 0-53. Except this is the internal reference reading VCC through voltage divider. This should be "fairly accurate" measurement of supercapacitor voltage within 200mV.
R1 PAXX
LED control:
This section is pretty straightforward, the following keywords control the 2 LED's:
* LED 0 ON * L0H * LED 0 OFF * L0L * LED 1 ON * L1H * LED 1 OFF * L1L
Buttons:
The following keyword waits for a button press:
BG
and saves it into the "BV" keyword, which can be accessed like a register:
* Set R1 to result of button press * R1 BV * Check if button press was button 3, if so, turn on both LED's * CE BV 3 L0H L1H
BV will always be a value between 1 and 4. A "0" means no button was pressed, and if button 5 (button R) was pressed, the interpreter will quit.
Blink Byte:'
This keyword will blink out the binary value of the following byte in program.
* Blink out R1's value * BB R1 * Blink out EEPROM address 27's value * BB ER 27 * Blink out 127 in binary * BB 127
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.