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!
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)
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 objectint potpin = A1; // potentiometer adjustment pinint 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 delayint leftbutton = PB4; // pins for button connectionint rightbutton = PB3;
volatile boolean leftpress =false; // which button pressedvolatile 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*/intreadpin() {
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;
elseif (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 μCvoid 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?!
}
elseif (!eepromwritten) {
movetheservo(rightpos); // default if no EEProm
}
delay(100); // settle time, get ready!
}
// left button pressed, short press = change// tracks, long press = adjust servo positionvoid 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 positionvoid 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 accordinglyvoid loop() {
sleep();
if (leftpress) {
goleftbutton();
leftpress =false; // reset button press
}
elseif (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.
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.
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 3uint16_t myrand =2901; // happy birthday// global variables randomised later for flickering, using the// "waveslow" and "wavefast" arraysuint8_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 cyclesbool fastup =true;
bool slowup =true;
bool medup =true;
voidmydelay(uint8_t counter) {
for (uint8_t thiscount =0; thiscount <= counter; thiscount++) {
_delay_ms(1);
}
}
uint16_tgimmerand(uint16_t small, uint16_t big) {
myrand ^= (myrand <<13);
myrand ^= (myrand >>9);
myrand ^= (myrand <<7);
return abs(myrand) %23* (big - small) /23+ small;
}
voidgetnewslow() {
slowstart = gimmerand(waveslow[0], waveslow[1]);
slowend = gimmerand(waveslow[2], waveslow[3]);
}
voidgetnewmed() {
medstart = gimmerand(wavemed[0], wavemed[1]);
medend = gimmerand(wavemed[2], wavemed[3]);
}
// initialise a new fast cycle including the new speed of cyclevoidgetnewfast() {
faststart = gimmerand(wavefast[0], wavefast[1]);
fastend = gimmerand(wavefast[2], wavefast[3]);
faster = gimmerand(1, 4);
}
// Main programvoidmain() {
// 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 loopwhile (1) {
// ramp up slowif (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 medif (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 fastif (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 clockunsignedchar_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);
return0; // 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!
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 a 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.
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.
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>
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.
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.
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?