Saturday, April 17, 2021

0000 0000 0101 1101

DIP8 converter ATTiny13 to PFS154

The ATTiny13 chip has been the mainstay of many of my projects for the last 5 years or so, but lately I have been straying a bit towards the PFS154, particularly now that I have a working programmer and a barely working knowledge of programming the chip.


One barrier to the transition (and there have been many) is the different pin layouts of the chips - like...very different!

In this blog and video I am going to attempt to "re-wire" a DIP8 adapter so that I can use the padauk PFS154 chip in projects designed for AVR chips. That is, I'm going to need to make sure that VCC and GND can be re-routed appropriately as follows.

The process started with a "double-decker" idea, and after a couple of false starts (but no smoke), I was able to achieve the following "FrankenChip" which performed fading duties as desired.


There was only one unexpected outcome, which was the PWM signal being able to "source" (perhaps OUTPUT and LOW?) for the connected LED. More experiments required I think to fully understand that side-effect (see video).

So at the moment this seems like a nice option to use existing projects but with a new chip, at least until I decide to re-imagine some projects, then it will be interesting to see which chip I design the PCB around. Watch this space!




Saturday, April 10, 2021

0000 0000 0101 1100

Servos and Attiny chips

A while ago PileOfStuff was coding a model railway track switch with the aim of semi-automating the process using a servo and a microcontroller.

He started with an ATTiny85 and was using one of my programming PCBs that I sent to him for that purpose. At one point of frustration in the project he abandoned the ATTiny85 in favour of an Arduino Nano and THREW THE PROGRAMMING PCB AWAY!


Well, here in Tasmania the locals went wild! To redress the balance, I was naturally "forced" to spend hours at the bench attempting to reproduce the servo requirements of a model railway track switch with an ATTiny85 using the discarded PCB as a starting point.

The conventional wisdom concerning servo jitter is as follows:

  • buy a more expensive servo you cheapskate!
  • put a decent size (e.g. 470uF) electrolytic capacitor across VCC and GND close to the servo
  • put a toroidal iron core in the circuit for the servo wires to loop around to suppress transients
  • use code suitable for the chip (e.g. this code for ATTiny85)
  • after sweeping the servo, detach the servo to stop jitter
  • adjust time/delays/ISR/something-else™ to "finesse" the jitter
  • twist servo wires together to suppress transients
  • have a separate power supply for the servo (common ground)
  • set the fuse bits to change the clock speed of the Attiny85
  • do the following at some point (?!)
    • cli(); // disable interrupts
    • digitalWrite(ServoPin, HIGH);
    • // use micros() for delay
    • digitalWrite(ServoPin, LOW);
    • sei(); // enable interrupts
  • don't use an ATTiny85

Of all of those options (and I tried most) - I settled on the following approach.

  • Make sure that the ATTiny85 is running 8MHz internal clock (burn bootloader)
  • Slap a 470uF capacitor close to the servo (common practise for me anyway)
  • Use this library for the ATTiny85
  • After each servo sweep use detach and then re-attach the servo
  • Have a separate power supply for the servo

I will admit that there may be a possible problem with detaching the servo for some applications, as the servo may need to resist torque changes depending on the use. In this case, the servo is horizontally deployed with no external impressed forces, it should stay put. Also, a sleeping ATTiny85 doesn't fight back when you change the position anyway, and this thing mostly sleeps!

The setup works fine, and the code is as follows:


/*
   Attiny85 based servo code to adjust then switch tracks on a model
   railway. μC is asleep until button is pressed. If a short press
   (adjusted by variable "buttondelay") then the track just switches. If
   a longpress then user can adjust left and right "limits" for the
   movement.

   The EEProm is employed to "permanently" keep track (pun intended) of
   the right and left positions for the servo. When the adjustments are
   made they are stored and can be retrieved when powered up again.


   OneCircuit  www.onecircuit.blogspot.com
   Sunday 4 April  17:42:57 AEST 2021

*/

// Servo_ATTinyCore.h is part of the ATTinyCore family
// found at https://github.com/SpenceKonde/ATTinyCore
#include <Servo_ATTinyCore.h>

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <EEPROM.h>

Servo myservo;         // create servo object

int potpin = A1;       // potentiometer adjustment pin

int theaverage = 0;    // average pot readings to reduce servo "jitter"
int leftpos = 256;     // default left position (256 = 45°/180*1023)
int rightpos = 767;    // default right position (767 = 135°/180*1023)
int buttondelay = 600; // longpress delay
int leftbutton = PB4;  // pins for button connection
int rightbutton = PB3;
volatile boolean leftpress = false;  // which button pressed
volatile boolean rightpress = false;
boolean eepromwritten = false;       // have we used EEProm?

int ledpin = PB1;      // debug LED for button pushing indication

/*
  to combat "jitter" read the analog pin 25 times and
  then average the result - this should give the reading
  some "weight" which adds stability
*/
int readpin() {
  for (int i = 0; i < 25; i++) {
    theaverage = theaverage + analogRead(potpin);
  }
  theaverage = theaverage / 25;
  return theaverage;
}

// button pressed! ...but which one?
ISR(PCINT0_vect) {
  if (digitalRead(leftbutton)) leftpress = true;
  else if (digitalRead(rightbutton)) rightpress = true;
}

// do we have values stored? First EEProm location is 
// "0" for no and "1" for yes
boolean checkeeprom() {
  int writ = 0;
  boolean used = false;
  EEPROM.get(0, writ);
  if (writ == 0) used = false;
  if (writ == 1) used = true;
  return used;
}

// standard sleep routines - goodnight μC
void sleep() {

  GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
  PCMSK |= _BV(PCINT3) | _BV(PCINT4);     // Use PB3/4 as interrupt pins
  ADCSRA &= ~_BV(ADEN);                   // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT3) | _BV(PCINT4);    // Turn off the interrupt pins
  sleep_disable();                        // Clear SE bit
  ADCSRA |= _BV(ADEN);                    // ADC on
  delay(50);                              // setlle
  sei();                                  // Enable interrupts
}

/* 
  map the ADC (0-1023) result to degrees (0-180)
  detaching at the end is a contentious way of
  removing "jitter", but "sleep" effectively
  detaches anyway
*/
void movetheservo(int movepos) {
  myservo.attach(0);
  movepos = map(movepos, 0, 1023, 0, 180);
  myservo.write(movepos);
  delay(600);                     // generous movement time, adjust as you wish
  myservo.detach();
}

void setup() {
  pinMode(potpin, INPUT);
  pinMode(ledpin, OUTPUT);        // debug LED to output
  eepromwritten = checkeeprom();
  
  if (eepromwritten) {            // if data is present, recall it
    EEPROM.get(2, leftpos);
    EEPROM.get(4, rightpos);
    movetheservo(rightpos);       // or left, you choose?!
  }
  else if (!eepromwritten) {
    movetheservo(rightpos);       // default if no EEProm
  }
  delay(100);                     // settle time, get ready!
}

// left button pressed, short press = change
// tracks, long press = adjust servo position
void goleftbutton() {
  // debug LED flashing
  digitalWrite(PB1, HIGH);
  delay(50);
  digitalWrite(PB1, LOW);
  delay(50);
  digitalWrite(PB1, HIGH);
  delay(50);
  digitalWrite(PB1, LOW);
  delay(buttondelay);
  while (digitalRead(leftbutton) == HIGH) {
    theaverage = readpin();
    leftpos = theaverage;
    movetheservo(leftpos);
    EEPROM.put(2, leftpos);
    if (!eepromwritten) {
      EEPROM.put(0, 1);
      eepromwritten = true;
    }

  }
  movetheservo(leftpos);
}

// right button pressed, short press = change
// tracks, long press = adjust servo position
void gorightbutton() {
  // debug LED flashing
  digitalWrite(PB1, HIGH);
  delay(50);
  digitalWrite(PB1, LOW);
  delay(buttondelay);
  while (digitalRead(rightbutton) == HIGH) {
    theaverage = readpin();
    rightpos = theaverage;
    movetheservo(rightpos);
    EEPROM.put(4, rightpos);
    if (!eepromwritten) {
      EEPROM.put(0, 1);
      eepromwritten = true;
    }
  }
  movetheservo(rightpos);
}

// sleep little one, and if button pressed then 
// check which one and react accordingly
void loop() {
  sleep();
  if (leftpress) {
    goleftbutton();
    leftpress = false;     // reset button press
  }
  else if (rightpress) {
    gorightbutton();
    rightpress = false;    // reset button press
  }
}

Sketch uses 2712 bytes (33%) of program storage space. Maximum is 8192 bytes.

Global variables use 48 bytes (9%) of dynamic memory, leaving 464 bytes for local variables. Maximum is 512 bytes.

Tasmanian honour restored?






Saturday, April 3, 2021

0000 0000 0101 1011

Why PFS154? Part two...

In a previous blog I celebrated the unlocking of (not one, not two, but...) three independent PWM channels on the Pesky PFS154 Padauk microcontroller. This post will be about the code journey to that point. On the AVR version (ATTiny13) the process was as follows.

1. Choose lower and upper range values for PWM ramping
2. Generate LFSR random numbers
3. Adjust the PWM of each channel
4. Wait a bit
5. Rinse and repeat

The process for the PFS154 is exactly the same, excepting we have three channels and there are some changes to how this is coded and compiled under the FreePDK SDCC toolchain.

I was originally dreading this project because way back when I first achieved a working padauk programmer, I grabbed the FreePDK examples from github, compiled the code (success) and plugged in an LED (failure). No fading as expected.

I eventually (weeks of coding later) "fixed" it by trawling through the back alleys of the interwebs using Chinese to English translation services, reading and re-reading the datasheet and then used my patented "trial and error and error and error and error..." method of programming.

The original FadeLED code (not working):

  PWMG1C = (uint8_t)(PWMG1C_ENABLE | PWMG1C_INVERT_OUT | PWMG1C_OUT_PA4 | PWMG1C_CLK_IHRC);
  PWMG1S = 0x00;                  // No pre-scaler

Without going into the torturous process of elimination, I ended up chucking out the whole "(uint8_t)(PWMG...blah blah" approach (which relies on various *.h files scattered about) and went with the direct accessing of registers in binary. This is my tried and tested preferred approach which, despite the zealots, makes the most sense to me as it is straight from the datasheet.

The code that worked:

  PWMG1C = 0b10000111; // see datasheet


  PWMG1S = 0b00000000; // see datasheet

Once one PWM channel was behaving itself (ramping up and down gently), I turned my attention to the other two channels promised in the datasheet.

Honestly it was a bit of a "cut and paste" hit job and took only a few minutes. The sight of those little LEDs pulsing away in unity was quite a nice reward for all of that brow crinkling.

It was only days later that I realised I got a little lucky as each PWM is matched to a different pin depending on the binary number thrown to PWMG*C where * is either 0,1 or 2. So for instance if I throw PWMG2C = 0b10000111 then the datasheet shows the following "1"s highlighted:

So looking at the table above and the pinout, 0b10000111 enables the PWM (bit 7), 0b10000111 chooses PA3/pin5 (bits 3-1) and 0b10000111 chooses IHRC (Internal High RC oscillator) as the clock source (bit 0). See highlighting.

Similarly for PWMG0C and PWMG1C using 0b10000111 selects PA0 and PA4 as PWM outputs respectively. Clear as mud!

Sidenote: I threw in a PFS173 at this point to see if the chips are interchangeable. They are not - for although the code compiled OK, the registers for the PFS173 for PWM are different to the PFS154 - great!

The last hurdle was to see if the original code from the ATTiny13 candle project would just port across - and sure enough apart from the odd formatting/syntax issue (bool vs boolean as an example), not only did the old code compile for the PFS154, but also it uploaded and produced awesome candlely-goodness on the breadboard.


/*
 -----------------------------------------------------------------
 Description: A fake candle <sigh> running on a PFS154 padauk μC
 connected to 3 leds via three channels, one running fast, one
 medium and one slow.
 
 Author: OneCircuit                    Date: 02/04/2021
 www.onecircuit.blogspot.com
 -----------------------------------------------------------------

*/

#include <stdint.h>
#include <stdlib.h>
#include <pdk/device.h>
#include "auto_sysclock.h"
#include "delay.h"
#include <stdbool.h>

#define LED4_BIT 4
#define LED0_BIT 0
#define LED3_BIT 3

uint16_t myrand = 2901;  // happy birthday

// global variables randomised later for flickering, using the
// "waveslow" and "wavefast" arrays
uint8_t slowcounter = 0;
uint8_t medcounter = 0;
uint8_t fastcounter = 0;
uint8_t slowstart = 0;
uint8_t slowend = 0;
uint8_t medstart = 0;
uint8_t medend = 0;
uint8_t faststart = 0;
uint8_t fastend = 0;
uint8_t faster = 0;

uint8_t waveslow[] = {50, 100, 170, 200};
uint8_t wavemed[] = {40, 120, 140, 220};
uint8_t wavefast[] = {40, 80, 150, 240};

// booleans to keep track of "fading up" or "fading down"
// in each of the slow and fast cycles
bool fastup = true;
bool slowup = true;
bool medup = true;

void mydelay(uint8_t counter) {

  for (uint8_t thiscount = 0; thiscount <= counter; thiscount++) {
    _delay_ms(1);
  }

}

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 getnewslow() {
  slowstart = gimmerand(waveslow[0], waveslow[1]);
  slowend = gimmerand(waveslow[2], waveslow[3]);
}

void getnewmed() {
  medstart = gimmerand(wavemed[0], wavemed[1]);
  medend = gimmerand(wavemed[2], wavemed[3]);
}


// initialise a new fast cycle including the new speed of cycle
void getnewfast() {
  faststart = gimmerand(wavefast[0], wavefast[1]);
  fastend = gimmerand(wavefast[2], wavefast[3]);
  faster = gimmerand(1, 4);
}

// Main program
void main() {

  // Initialize hardware
  // Set LED as output (all pins are input by default)
  PAC |= (1 << LED4_BIT) | (1 << LED0_BIT) | (1 << LED3_BIT);

  // see datasheet
  PWMG1DTL = 0x00; 
  PWMG1DTH = 0x00;
  PWMG1CUBL = 0xff; 
  PWMG1CUBH = 0xff;
  PWMG1C = 0b10100111;
  PWMG1S = 0b00000000;

  PWMG0DTL = 0x00;   
  PWMG0DTH = 0x00;
  PWMG0CUBL = 0xff;
  PWMG0CUBH = 0xff;
  PWMG0C = 0b10100111;
  PWMG0S = 0b00000000;

  PWMG2DTL = 0x00; 
  PWMG2DTH = 0x00;
  PWMG2CUBL = 0xff;
  PWMG2CUBH = 0xff;
  PWMG2C = 0b10100111;
  PWMG2S = 0b00000000;

  getnewfast();
  getnewslow();
  getnewmed();
  slowcounter = slowstart;
  fastcounter = faststart;
  medcounter = medstart;

  // Main processing loop
  while (1) {

    // ramp up slow
    if (slowup) {
      slowcounter++;
      if (slowcounter > slowend) { // ramp finished so switch boolean
        slowup = !slowup;
      }
    }
    else {
      // ramp down slow
      slowcounter--;
      if (slowcounter < slowstart) { // ramp finished so switch boolean
        slowup = !slowup;
        getnewslow();
      }
    }

    // ramp up med
    if (medup) {
      medcounter++;
      if (medcounter > medend) { // ramp finished so switch boolean
        medup = !medup;
      }
    }
    else {
      // ramp down med
      medcounter--;
      if (medcounter < medstart) { // ramp finished so switch boolean
        medup = !medup;
        getnewmed();
      }
    }


    // ramp up fast
    if (fastup) {
      fastcounter = fastcounter + faster;
      if (fastcounter > fastend) { // ramp finished so switch boolean
        fastup = !fastup;
      }
    }
    else {
      // ramp down fast
      fastcounter = fastcounter - faster;
      if (fastcounter < faststart) { // ramp finished so switch boolean
        fastup = !fastup;
        getnewfast();
      }
    }
    // delay + a re-purposed random for ramp speeds
    mydelay(6 + faster);

    PWMG1DTL = slowcounter & 255;
    PWMG1DTH = slowcounter;
    PWMG0DTL = fastcounter & 255;
    PWMG0DTH = fastcounter;
    PWMG2DTL = medcounter & 255;
    PWMG2DTH = medcounter;

  }
}

// Startup code - Setup/calibrate system clock
unsigned char _sdcc_external_startup(void) {

  // Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.
  // The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.
  // Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
  AUTO_INIT_SYSCLOCK();

  // Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.
  // The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.
  // Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h
  AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);

  return 0;   // Return 0 to inform SDCC to continue with normal initialization.
}

Next I think that I will make up a PCB and shoehorn the beast into a jar with a Solar Panel running the show. Can't wait!




Saturday, March 27, 2021

0000 0000 0101 1010

Why PFS154? Part one...

Those who have been here for the (long and torturous) journey with Padauk chips have seen the following milestones (millstones?) much like episodes like in the sitcom "Friends":

        a) The one where I fry many PCBs (fail)
        b) The one where I use my one working programmer to...er...blink an LED (semi-success)
        c) The one where I make an unworkable "old man" version of the programmer (fail)
        d) The one where I "baked" working programmers in a cheap oven (for the win)
        e) The one where I blink a bright LED via a transistor

And if you have seen all of that and are shaking your head about the stupidity and waste, you may be asking yourself the obvious question - but why?

I have of course learned a great deal along the way and there is no doubt (in my mind at least) that this project has given more than it has taken, but a cloud still remains over the whole endeavour when on the shelf I have so many idle Padauk chips - what is the actual end game?

Well, for one, despite their low cost the major goal all along has been getting access to the lovely specs of these chips on the assumption they are not full of tiny socks (long story).

In particular the PFS154, which looks similar to my AVR buddy the ATTiny13, is interesting in that by comparison it is a bit of a muscly fellow, at around 20% of the cost. There's no ADC (grrr), a "feature" we will explore at a later time, but otherwise as Paul Keating might say "That's a beautiful set of numbers!".


First impressions are that the many PWM channels might be of some use in the long-running candle project. More channels surely means a better simulation of the real deal?

The ATTiny13 has two PWM channels (8 bit) and so all previous work on this project has been limited to these specs. I have in the past configured three-in-one SMD 5050 LED such that two of the three LEDs are attached to a "slow rise/fall" channel and one LED for a "fast rise/fall" channel using a three-in-one SMD 5050 LED.

The result is a pretty good random flickering effect (peak intensity shown below in yellow). The plot is from 1000 points based on the code and then outputted to Serial on an Arduino Nano. The data was copied to a spreadsheet to make a pretty picture.



For three channels the result is quite different, with what I think is more natural "smoother" variation.



Some work still to be done:

1. Non-linear ramping (low pass filter or software?)

2. Separate ramping gradient for medium and fast ramping (e.g. different AND variably adjusted rates per ramp)

3. "Guttering" code - a natural phenomena whereby a candle will almost go out and then relight, for instance in response to a breeze.

4. Porting code in part or in entirety to assembly for efficiency.

5. Running the PFS154 at the lowest clock speed

6. Sleeping PFS154 between PWM adjustments?

The code itself was ported pretty much intact from an early ATTiny13-based version. I later developed that version into more efficient (smaller, faster) assembly code and as well wound the clock speed of the microcontroller down to around 128kHz. Is any of this possible also on the PFS-154? Watch this space.



Saturday, March 20, 2021

0000 0000 0101 1001

Attiny Bootloaders

Bootloaders are mysterious beasts - I have "bricked" many chips through clumsy setting and flashing of fuses on various micro-controllers. For instance, if you set the clock to an external 8Mhz crystal and then burn the bootloader, you will need an external 8Mhz crystal to either change the clock or to upload code. Makes sense in hindsight, right?

This "oversight" became such a regular occurrence that I ordered, and have used quite a few times, a high voltage fuse fixer (HVPP/HSVP programmer) which has brought back to life many an abused µC.

You can build your own HVPP if you wish, by just following these excellent instructions.

A large part of the confusion on my behalf arose early in my AVR dabbling when I bought a device marketed as a "programmer"

I had also at that time bought some dirt cheap DIP8 ATTiny13 chips so I was super keen to make a ... blinking led circuit? Loading up the famous blinky sketch on the Arduino IDE I plugged in the ATTiny13 into the cradle and then nudged the USB cable into a port on my computer and...

Due to not understanding the whole correct orientation of the chip thing, the ATTiny13 was literally smoking, the "programmer" was fried, and then the computer involuntarily shutdown.* Not the most auspicious start to my AVR programming life.

Very soon after this scarring experience I discovered the Arduino ISP sketch, found out how to hook up an Arduino to an ATTiny for burning fuses and programming, and forgot about the whole "cheap so-called programmer smoking up the room" episode.

Until recently that is! In order to conjure up topics for this blog and channel I literally grab a random component - and so I was fishing about for inspiration and pulled out one of these tiny DIP8 programmers from the buckets. Oh no! There were flashbacks including of course the well known smell of burning components (which was prophetic).

Nonetheless - given it is a few years on and perhaps I could be a bit wiser (?!), I plugged in an ATTiny85 (the correct way), and loaded up some blinky goodness, then pushed "compile and upload" only to be greeted with amused silence from the Arduino IDE and some distinctly non-blinking of LEDs.

So here I am again (with the benefit of hindsight and experience?) seeing if I can usefully employ these devices. Crucial to the process is the idea that this little board does not have any USB interface and so we need to use a "virtual USB" protocol encapsulated in a bootloader. Which came first, the chicken or the bootloader?

Most of the work in this area has been done by the "MicroNucleus" team. The plan for this little board is thus to:

1. Load the Micronucleus bootloader to a "vanilla" ATTiny85 using USB-ISP
2. Program the ATTiny85 directly from the board using the Micronucleus bootloaders V-USB protocol
3. Try to upgrade/load the bootloader and program using the board only
4. Try to extend the bootloader from ATTiny85 to ATTiny13

See below for the video of these steps and their various successes.


*postscript: I blew up another one in the exact same manner making this post/video <sigh>


Saturday, March 13, 2021

0000 0000 0101 1000

Rowing Clock

I've been giving the rowing machine a bit of a hammering over the last year or so. The plan is to shed my perpetual winter plumage (aka fat) and trim down to a point where I don't have to continually invest in a whole new wardrobe.

The problem (as a numbers guy) has been the ... er ... numbers on the machine. It's a comprehensive readout that comes with the machine and it successfully distracts me away from the business of proper form and zen-like concentration on the actual rowing. I'm always chasing the numbers.

After a couple of visits to the physiotherapist to confirm that I am old and fat and shouldn't be chasing numbers I switched off the readout and used instead my phone countdown to measure the time rowed. It worked semi-well until my weird brain started to panic and as I was sweating away I became distracted through wondering if the damn phone was set correctly, and whether or not I was going to be rowing until Christmas.

So what I needed was a visual way of seeing the rowing but without the actual numbers. Inspired by a pileofstuff video I planned out a little 3D printed box full of goodies including an LED Ring, a 4-digit 7-segment display (TM1637), rotary encoder and buzzer as follows.

I also used a spreadsheet to draw the pattern I had imagined, just to make the coding part of the project a little easier to construct.
The code was in several sections:
  1. Set time to row
  2. Countdown to rowing start
  3. Countdown to rowing finish
  4. Warmdown
  5. Shutdown
#include <OneButton.h>
#include <Rotary.h>
#include <TM1637Display.h>
#include <FastLED.h>
#include "LowPower.h"

#define NUM_LEDS 16
#define DATA_PIN 7
#define CLK 11
#define DIO 10
#define buzzer 9

uint32_t intervaldelay = 0;

CRGB leds[NUM_LEDS];

volatile byte rowingmins = 1;
byte minmins = 1;
byte maxmins = 180;
volatile byte cw = 0;
volatile byte clicked = 0;
byte buzzdelay = 200;

TM1637Display display(CLK, DIO);
Rotary r = Rotary(3, 2);
OneButton button(4, true);

void setup() {

  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(20);
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
  pinMode(buzzer, OUTPUT);
  button.attachClick(singleclick);
  display.setBrightness(4);
  setupcolour(0, 0, 0, 0);
}

void setupcolour(byte numgreen, byte numyellow, byte numred, byte numoff) {
  byte whichlight = 0;
  byte endlight = numgreen;
  for (byte countgreen = 0; countgreen < endlight; countgreen++) {
    leds[countgreen] = 0x00CC00;
    whichlight++;
  }
  endlight = whichlight + numyellow;
  for (byte countyellow = whichlight; countyellow < endlight; countyellow++) {
    leds[countyellow] = 0xFF9900;
    whichlight++;
  }
  endlight = whichlight + numred;
  for (byte countred = whichlight; countred < endlight; countred++) {
    leds[countred] = 0xCC0000;
    whichlight++;
  }
  endlight = whichlight + numoff;
  for (byte countblack = whichlight; countblack < endlight; countblack++) {
    leds[countblack] = 0x000000;
    whichlight++;
  }
  FastLED.show();
}

void precount() {

  for (byte showcount = 0; showcount <= 16; showcount++)  {
    setupcolour(showcount, 0, 0, 16 - showcount);
    delay(80);
  }

  setupcolour(0, 0, 0, 16);
  delay(500);

  setupcolour(16, 0, 0, 0);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(12, 0, 0, 4);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(8, 0, 0, 8);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(8000);

  setupcolour(4, 0, 0, 12);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(3, 0, 0, 13);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(2, 0, 0, 14);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  setupcolour(1, 0, 0, 15);
  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);
  delay(1000);

  tone(buzzer, 2000);
  delay(buzzdelay);
  noTone(buzzer);

}

void warmdown() {

  for (byte downcount = 1; downcount < 4; downcount++) {
    setupcolour(0, 0, 16, 0);
    tone(buzzer, 2000);
    delay(buzzdelay);
    noTone(buzzer);
    delay(250);
    setupcolour(0, 0, 0, 16);
    delay(250);
  }

  for (byte warmcount = 16; warmcount >= 0; warmcount -= 1) {
    setupcolour(0, warmcount, 0, 16 - warmcount);
    delay(4000);
  }
}

ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if ((result == DIR_CW) && (rowingmins < 180)) {
    rowingmins++;
  }
  else if ((result == DIR_CCW) && (rowingmins > 1)) {
    rowingmins--;
  }
}

void singleclick() {
  clicked = true;
}

void settime() {

  while (!clicked) {
    button.tick();
    display.showNumberDec(rowingmins);
  }
  PCICR = 0; //disable pin change interrupts

}

void loop() {

  settime();
  precount();
  display.clear();
  intervaldelay = rowingmins * 60000 / 32;

  for (byte countdown = 16; countdown > 0; countdown--) {
    setupcolour(countdown - 1, 1, 0, 16 - countdown);
    delay(intervaldelay);
    setupcolour(countdown - 1, 0, 1, 16 - countdown);
    delay(intervaldelay);
  }
  warmdown();
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);

}


Next I needed to prototype the setup on a breadboard to make sure that all the components (and code) were working as expected.

Finally I soldered up the whole shebang and shoehorned the lot into the 3D enclosure. The only addition in the end was a little ice-blue LED on the side to indicate that the box is "live" just in case I forget to turn in off as I have my post-workout heart attack.

It works exactly as needed and it was nice to build such a useful project with so many different aspects coming together to make the final result.









Saturday, March 6, 2021

0000 0000 0101 0111

CD4073 three input AND gate

I watch a bit of YouTube (shock) including the "Missionary Bush Pilot". Ryan the pilot is meticulous with his preparation and flying, and the scenery of course is dazzling, but most of all I am in awe of his ability to keep up a high quality narration as he goes about his piloting. My videos by comparison are replete with "ums" and "ahs" and all I'm doing is soldering a few LEDs together!

One really cool part of Ryan's preparation is the use of a rocker switch array which is his checklist.

It's a good design, but seems to lack the required number of LEDs to be truly epic. So recently I pulled out a CD4073 three-input AND gate from the buckets and decided to use it to emulate a checklist style panel.

I don't fly a plane, but I do ride a motorcycle - so this one could be about boots, helmet, gloves, tyre pressure, battery charge, headlight and maybe attitude!

If all the switches are "green" to go, then an eighth switch could be activated to actually start the bike/plane.

Firstly, I needed to check how multiple AND gates might work together, so I used iCircuit to test a possible configuration involving only two input AND gates.  Then I added a transistor NOT gate and a transistor switch indicate red or green for go.

Switches open, red light
Switches closed, green light

On the breadboard it works fine - should I strap it to the cowling/screen?