CD4013 Flip Flop
SR Latch |
SR Flip Flop |
Each week I will feature a circuit based on one or more of the electronic parts I have ordered then forgotten about over the years.
SR Latch |
SR Flip Flop |
After recent arrivals in the ESP32 adapter and relay department I decided to put together a circuit that allows for remote switching of decent current (e.g. 10A).
The circuit is similar to the one shown below (with the ESP32 playing the part of the switch):
The high signal drives the mosfet (a BS170) which opens the relay and releases all the ergs for the LED. Soldered up it looks quite good (don't look underneath), excepting that the 5V to 3.3V DC converter (SSP7603) looks a little ugly with all the capacitors stuck onto it like barnacles!
More or less standard OTA code makes this easy to program over the air, and I was pretty chuffed to be able to change the frequency of two separate GPIO pins without actually wiring up the ESP32 to a computer.
#include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> const char* ssid = "MyWifiConnection"; const char* password = "MyWifiPassword"; const char* host = "Blinky OTA32"; unsigned long ledtime = 0; unsigned long relaytime = 0; unsigned long currentMillis = 0; unsigned long led_delay = 3000; unsigned long relay_delay = 500; boolean ledstate = false; boolean relaystate = false; #define led_pin 5 #define relay_pin 4 void setup() { pinMode(led_pin, OUTPUT); digitalWrite(led_pin, LOW); pinMode(relay_pin, OUTPUT); digitalWrite(relay_pin, LOW); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { // could put debug stuff in here } ArduinoOTA.setHostname(host); ArduinoOTA.onError([](ota_error_t error) { (void)error; ESP.restart(); }); ArduinoOTA.begin(); // timing markers currentMillis = millis(); ledtime = currentMillis; relaytime = currentMillis; } // loop() visits here for actual code void dostuff() { // fake multi-tasking routines with blinking! currentMillis = millis(); if (currentMillis - ledtime >= led_delay) { if (ledstate) { digitalWrite(led_pin, LOW); ledstate = false; } else { digitalWrite(led_pin, HIGH); ledstate = true; } ledtime = currentMillis; } if (currentMillis - relaytime >= relay_delay) { if (relaystate) { digitalWrite(relay_pin, LOW); relaystate = false; } else { digitalWrite(relay_pin, HIGH); relaystate = true; } relaytime = currentMillis; } } void loop() { ArduinoOTA.handle(); dostuff(); }
Probably the only interesting thing about the code above is the use of millis() to fake multitask - I love that technique and it works really well on these devices.
On a smaller device (e.g. ATTiny13) I'd probably use interrupts and count the ticks to activate the GPIOs when a coded amount is reached. The ESP32 has a LOT of flash overhead though, so I took the lazy way this time!
I'm hoping I can continue to refine this idea and maybe use these guys around the place for remote sensing and control. Watch this space!
I have had it in mind to start to know the Padauk microcontrollers a little better, and in particular the PFS154 and the PMS150C. It has been such a journey so far, with the concentration of effort mostly into the making of the open source programmer.
In the "still to do" basket are:
When a ULN2003 stepper motor controller and 28BYJ-48 combo landed in the mailbag I decided that I might be able to start on points 1 & 2 (and a bit 3) above.
The prospect of making the feature challenged (but cheap!) one-time programmable PMS150C drive a stepper motor reminds me very much of humans driving big machinery - such as here in Australia where tiny figures are dwarfed by Haul Pack trucks.
My plan was to make some stepper code work with Arduino (and appropriate libraries), then transfer the code to the very familiar ATTiny13, and finally the PFS154 (because it's multi-programmable, so testing is less stressful). The ultimate goal of the exercise is to use the one time programmable PMS150C.
In the end I only threw away two fried PMS150C chips, so I'm pretty happy about that!
1. 1KW OTP program memory2. 64 Bytes data RAM3. One hardware 16-bit timer4. One hardware 8-bit timers with PWM generator5. One general purpose comparator6. Support fast wake-up7. Every IO pin can be configured to enable wake-up function8. 6 IO pins with optional drive/sink current and pull-high resistor9. Clock sources: internal high RC oscillator and internal low RC oscillator10. Eight levels of LVR reset ~ 4.0V, 3.5V, 3.0V, 2.75V, 2.5V, 2.2V, 2.0V, 1.8V11. One external interrupt pin
1. One processing unit operating mode2. 79 powerful instructions3. Most instructions are 1T execution cycle4. Programmable stack pointer to provide adjustable stack level5. Direct and indirect addressing modes for data access. Data memories are available for use as an index pointer of Indirect addressing mode6. Separated IO space and memory space
But first - what's with that four input stepper motor?
Getting an Arduino Uno to light em up and move the stepper was not problem - you simply install this library and then borrow the code from the examples given.
#include <Stepper.h> const int stepsPerRevolution = 200; Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11); void setup() { myStepper.setSpeed(60); Serial.begin(9600); } void loop() { Serial.println("clockwise"); myStepper.step(stepsPerRevolution); delay(500); Serial.println("counterclockwise"); myStepper.step(-stepsPerRevolution); delay(500); }
The stepper motor datasheet recommends the following sequence to turn the shaft:
Therefore instead of using the stepper library for the ATTiny13 version, I made an array of appropriate data and then stepped through it either backwards or forwards for "random" dancing.
const byte thesteps[] = { // ATTiny13 pinout // // _________ // / | // 1--|RESET VCC|--8 // 2--|PB3 PB2|--7 // 3--|PB4 PB1|--6 // 4--|GND PB0|--5 // |__________| // IN4,IN3,IN2,IN1 // PB4,PB3,PB2,PB1 // pins 3, 2, 7, 6
0b00011100, 0b00011000, 0b00011010, 0b00010010, 0b00010110, 0b00000110, 0b00001110, 0b00001100 }; byte numberofsteps = 0; uint16_t myrand = 2901; // happy birthday uint16_t gimmerand(uint16_t small, uint16_t big) { myrand ^= (myrand << 13); myrand ^= (myrand >> 9); myrand ^= (myrand << 7); return abs(myrand)%23*(big-small)/23+small; } void setup() { DDRB = 0b00011110; // drive pins as output } void loop() { numberofsteps = gimmerand(4, 40); delay(10); if ((numberofsteps%2) == 0) { for (int rotate = 0; rotate < numberofsteps; rotate++) { for (byte clocker = 0; clocker < 8; clocker++) { PORTB = thesteps[clocker]; delay(1); } } } else { for (int rotate = 0; rotate < numberofsteps; rotate++) { for (byte clocker = 7; clocker > 0; clocker--) { PORTB = thesteps[clocker]; delay(1); } } } }
Problems emerged when I "translated" this to the PFS154 and then to the PMS150C. I think the majority of the issues were wrapped up in the variable data types. I eventually simplified most of these to 0-255 by using the uint8_t data type.
The data types "int", "char", "byte" and even "uint16_t" were all too problematic for the PMS150C (although the PFS154 code was pretty much the same as the ATTiny13 code).
The change of data types also meant that I had to "tweak" the code, in particular the "random" number generator, quite a bit!
The resultant randomness is pretty awful, but that's a problem for another time (perhaps when I learn how to use the chip's comparator as an ADC).
#include <pdk/device.h> #include <delay.h> #include <stdint.h> #include "auto_sysclock.h" #include <stdlib.h> #include <stdbool.h> // important for booleans char thesteps[] = { // PMS150C pinout // // _________ // / | // 1--|VCC GND|--8 // 2--|PA7 PA0|--7 // 3--|PA6 PA4|--6 // 4--|PA5 PA3|--5 // |__________| // IN 4, IN3, IN 2, IN 1 // PA6, PA7, PA0, PA4 // pins 3, 2, 7, 6 0b11000001, 0b11000000, 0b11010000, 0b01010000, 0b01010001, 0b00010001, 0b10010001, 0b10000001 }; uint8_t numberofsteps = 0; // change these as you please, my focus in this // project was not the "purity" of randomness uint8_t myrand = 201; uint8_t xorpoly1 = 0b10001011; uint8_t xorpoly2 = 0b10111010; uint8_t throwspanner = 61; uint8_t spannercount = 0; uint8_t gimmerand(uint8_t small, uint8_t large) { bool carry = false; if (spannercount > throwspanner) { myrand = myrand ^ xorpoly2; myrand--; spannercount = 0; } if (((myrand) >> 7) & 1) { carry = true; } myrand = myrand << 1; if (carry) { myrand = myrand ^ xorpoly1; } return (myrand/((255)/(large-small))+small); } bool isEven(uint8_t n) // faster than n%2==0? { return (!(n & 1)); } void main() { PAC = 0b11111001; // output pins while (1) { // change these for "speed" of dancing numberofsteps = gimmerand(4, 40); _delay_ms(10); if (isEven(numberofsteps)) { for (uint8_t rotate = 0; rotate < numberofsteps; rotate++) { for (uint8_t clocker = 0; clocker < 8; clocker++) { PA = thesteps[clocker]; PA = PA | 0b00100000; // turns on LED _delay_ms(1); } } } else { for (uint8_t rotate = 0; rotate < numberofsteps; rotate++) { for (uint8_t clocker = 7; clocker > 0; clocker--) { PA = thesteps[clocker]; PA = PA | 0b00001000; // turns on LED _delay_ms(1); } } } } }
Amazingly the PMS150C (and the PFS154) do have an extra two "proper" GPIOs to play with after this program is installed (the ATTiny13's RESET pin can be used as a "weak" GPIO if needed), so you'll see blinking LEDS to indicate clockwise and counterclockwise dancing.
Lately I have been seeing a lot (A LOT!) of videos about "proper" connections. It's amazing that for years you might only rarely hear of a particular technical word like FERRULE, and then suddenly you can't watch a day of YouTube without it cropping up all over the place!
The one that spurred me into action was a lovely video (as always) from Great Scott, and indeed I have suffered some problems associated with dodgy soldered joints. Thus I put in an order and waited.
After the usual Pandelay I received a bunch of ferrules and a crimper, and so I am starting to replace some earlier compromised connections with these little fellas to increase the life and the stability of the devices on which they rely.