Adding an MCP23017 16 port IO expander to Arduino or Esp8266 or Attiny85 or……..

After I made this expander module, a ready made module
with this chip has become available. So I actually would advice anybody needing a 16 bit expander, to buy that one rather than build it. The module will cost you abt 1.50 euro, while the individual chip may set you back a euro or so.

I am not claiming that what I am describing here is earth shattering or trailblazing, because in fact it is very simple and no doubt has been done by many already. But sometimes what is simple for the one, is still a question mark for the other, so here is quick ‘how-to’ of adding 16 I/O ports to your microprocessor. This is especially handy when working with a chip like the ESP8266 that has only limited I/O
The MCP23017 is an I2C enabled 16 I/O port chip. That means that you only need 2 pins (yes with Vcc and ground it makes 4) to control the chip and the added advantage is that you can share I2C with various other devices as well.

The 16 I/O lines are divided into an 8 I/O PORT A and an 8 I/O PORT B. Both can be used as input as well as output. The chip also has 2 configurable interrupts (that I will not be using). The physical layout of the chip makes it quite easy to use it on a piece of strip board.

The circuit (at right) is rather simple. At a last moment I decided to leave out the pull up resistors so it would be more flexible to use together with other I/O devices. The 3 Address pins A0-A2 determine the I2C address that ranges from 0x20 (all pins on ground) to 0x27 (all pins on Vcc).
The chip  can take a Vcc from 2.7V to 5V and this is perfect for 3.3 Volt devices as  the modern arduino’s and the ESP8266 range of boards.

Using the chip in a program is fairly easy. There are good libraries available, but it might help if you know how to program the chip without a library.
In my case I have all  address lines tied to ground and therefore my I2C address is 0x20. Suppose I want to use all PORT A lines as outputs. I do that  as follows:

Wire.write(0x00); // IODIRA register
Wire.write(0x00); // set entire PORT A to output

For PORT B that  is rather similar:

Wire.write(0x01); // IODIRB register
Wire.write(0x00); // set entire PORT B to output

If we then want to send a specific value ‘X’ to that PORT A, we do that as follows

Wire.write(0x12); // address port A
Wire.write(X);  // value to send

‘X’ ofcourse is a byte value that determines whether we set a specific port HIGH or LOW.
If for instance ‘X’is ‘0’ that means we write a LOW to all PORT A outputs. If it is 255 that means we write a HIGH to all PORT A outputs.
To determine what value to send, consider the 8 I/O lines of PORT A as a byte in which the individual bits determine HIGH or LOW.
So if we only want to make PORTA.0 HIGH and the rest LOW, we write a binary value of 0b00000001 =1 to the A register. If we want to make PORTA.0 and PORTA.2 HIGH and the rest LOW we write a binary value of 0b00000101 = 5.
For PORT B it is similar:

Wire.write(0x13); // address PORT B
Wire.write(X);  // value to send

If we want to use PORT B (or PORT A for that matter) as input, we do that as follows:

Wire.write(0x13); // address PORT B
Wire.requestFrom(0x20, 1); // request one byte of data
byte; // store incoming byte into "input"

The byte “input” will vary between 0 and 255, in which the individual bits determine the input on the corresponding IO line. So if ‘input’  reads ‘3’  which in binary is 0b00000011, that means that both IO line 0 and 1  were HIGH and the rest LOW. (note: Wire.beginTransmission() and Wire.endTransmission() are only used for a Wire.write(), not or a or Wire.request().)

#include <Wire.h> // Wire.h
byte input=0;
void setup()
  Wire.begin(); // wake up I2C bus
  Wire.write(0x00); // IODIRA register
  Wire.write(0x00); // set entire PORT A as output
void loop()
  // read the inputs of bank B
  Wire.requestFrom(0x20, 1);;
  // now send the input data to bank A
  Wire.write(0x12); // address PORT A
  Wire.write(input);    // PORT A
  delay(100); // for debounce

That’s basically it if you want to do the addressing yourself. Using a library, such as the one from Adafruit, makes it much easier though as it has commands to write and read from individual IO lines. One of the example programs to read a single button, looks  for instance like this:

#include <Wire.h> // Wire.h
#include "Adafruit_MCP23017.h"

// Basic pin reading and pullup test for the MCP23017 I/O expander
// public domain!
// Connect pin #12 of the expander to Analog 5 (i2c clock)
// Connect pin #13 of the expander to Analog 4 (i2c data)
// Connect pins #15, 16 and 17 of the expander to ground (address selection)
// Connect pin #9 of the expander to 5V (power)
// Connect pin #10 of the expander to ground (common ground)
// Connect pin #18 through a ~10kohm resistor to 5V (reset pin, active low)
// Input #0 is on pin 21 so connect a button or switch from there to ground

Adafruit_MCP23017 mcp;

void setup() 
mcp.begin();      // use default address 0
mcp.pinMode(0, INPUT);
mcp.pullUp(0, HIGH);  // turn on a 100K pullup internally
pinMode(13, OUTPUT);  // use the p13 LED as debugging

void loop() {
// The LED will 'echo' the button
digitalWrite(13, mcp.digitalRead(0));

If you want to use more than one MCP23017 do that as follows:

#define addr1 0 //addr1 =A2 low , A1 low , A0 low =000
#define addr2 1 //addr 2 = A2 low , A1 low , A0 high =001 

Mind you that “0” is in fact 0x20 and ‘1’ is in fact 0x21

If you are using the Adafruit library with the ESP8266, you will encounter a compilation error signaling it cannot find <avr/pgmspace.h>. The solution for this is easy:

Open the cpp file of the library.


#include <avr/pgmspace.h>


#if (defined(__AVR__))
#include <avr/pgmspace.h>
#include <pgmspace.h>

You will find extensive information on how to use the mcp23017 here.


Solar powering an Attiny or Arduino with a capacitor, or just use AA? Part 1

If you have a processor in a remote location that you cannot or will not bring a psu cable to, you have several options of supplying it with energy.
Batteries come to mind and these could be rechargeable or not, or one can use a capacitor. After all, those store energy as well. To make a capacitor a viable option, obviously it needs to be a big one (in capacity, not especially in size), and one has the option of adding a solar cell to it.
As I wanted to feed an attiny that is somewhere in my garden and that is asleep most of the time and as I had some idle solar panels  (9 Volts, 2 Volts) those seemed  suitable to use, but as they do not provide much energy during the night, I needed to store that energy as well. As storage Nicads or even a LiPo came to mind. A Lipo I discarded as it takes some extra circuitry to do any decent charging. A nicad obviously needs some consideration in charging and discharging  as well, but they are more tolerant albeit that a deep discharge is usually not appreciated by them, nor is a constant charge.
Therefore I also considered using a “supercapacitor” I have a capacitor of 10.000uF but one can hardly call that a supercapacitor, but nowadays supercapacitors are readily available at modest prices. They would be easy to use in a charging circuit (basically a resistor would be enough) and they don’t mind frequent  charge and discharge. As their charge is  limited, obviously I would need a solar-cell, so the only thing I needed to see is if they would carry my application ‘through the night’

The Charge of a capacitor is expressed in Coulomb (symbol C).
this can be expressed as:

C=F * V  (where of course ‘F’ stands for the capacity in Farad)
Energy in Joule is expressed as:
Which is:
F*V*V  of F.V²
However, at a given voltage and capacity only half of the energy is available to charge the capacitor hence the energy in the capacitor will be:
J=½ * F *V²
and Joules can also be expressed as ‘Wattseconds’:
J=W *s

So if we calculate the number of Joules in the capacitor at 5.1 Volt:
J=0.5 * 1.5 * 5.1² = 19.5 J
Now obviously we will not all use those as the attiny kinda stops working at 1.8 Volt. If we calculate the number of Joules still left then we come to
J=0.5 * 1.5 * 1.8² = 2.43 J
So the amount of usable energy comes to 19.5-2.43= 17J (rounded down)

From an earlier project on  an attiny13 that was put to sleep only to awake every so many seconds, I found out that the current consumption was 5.5 uA at 5 Volt. Now obviously it will consume a bit less when the voltage drops, so lets assume 4uA at an average of 3.45 Volt (The average voltage being (5.1+1.8)/2=3.45 Volt).
We can calculate the average power consumption as V*I:
3.45 * 4*10⁻⁶ = 13.8 uWatt
as we have 17 Joules available and 17 joules being 17 Ws (Wattseconds) we can calculate the time:
s=17/(13.8 *10⁻⁶)
s=17/13.8  *10⁶
s=1.23 *10⁶
s=1230 000 sec (1,23 million seconds)
that is:
20.531 min, is
342 hrs is
14 days
Now this is just an approximation as there might be some current leakage. The processor will need to do something when awake, like flash a  led or send something, so it will probably not make the full 14 days, but that is not important as it is obviously enough to carry us through the night and probably an overcast day as well.

So how would this be if I used normal AA batteries or NiCad batteries?
As example for regular alkaline batteries  I will take the ubiquitous Philips  LR6. Fortunately someone else already calculated the energie stored in these batteries  at  1986 mAh at a discharge of 100mA. As the performance of these batteries is increasing when the discharge decreases, I will use 2000mAh for my calculations.
If one uses 3 of those to get to 4.5 Volts that will be a total of 9000mWh or 9 Wh.
Again, we will not be able to use this all as we have 1.8 Volt as lower limit, so we will be stuck with 3.6 Wh that we cant use, leaving  5.4 Watthour usable energy.
The average Voltage on the  processor will be:
(4.5+1.8)/2=3.15 Volts.
the average power consumption thus is:
3.15* 4*10⁻⁶ = 12.6uW
as we have 5.4 Wh available we can again calculate the time:
h=5.4/12.6 * 10⁶
h=0.428571 *10⁶
h=428571 hr
=17857 days
=592 months
=50 years
Wow. Why would anybody want to use anything else but LR6 batteries ?
Well for several reasons: 3 of those batteries take more space and eventhough they tend to perform well at low temperatures and low discharge rate, we all have experienced that Alkaline batteries left in a hardly used device tend to leak, even destroying that device by its corrosive leaking. Advantages and disadvantages of alkaline batteries are discussed here.
Nevertheless using alkaline batteries can be a good choice, but don’t forget to change them.

Using NiCad batteries gives a somewhat similar calculation. Suppose we use 3 cells of 1.2 Volt 2000mAh (or 2Ah).
Total energy will be 3.6*2= 7.2 Wh
useable energy will be (3.6-1.8)* 2=3.6 Wh
the average voltage we set at (3.6+1.8)/2=2.70 Volt
The average power consumption will be:
2.7* 4*10⁻⁶ = 10.8uW  (V*I)
The time will thus be:
h=(3.6/10.8) * 10⁶
=333333 hr
=38 yrs
Now if that only was true. My experience with  for instance solar garden lights (that often only have an 800mAh cell)  is that they usually dont last through the winter… and mind you, those are being recharged (which could contribute to their demise).
Nevertheless they may be a good choice.

In a following article I will discuss some practical examples


A capacitive soil humidity sensor: Part 3

Initially I imagined the probe as a sleek device with a wire coming out that one would stick in the soil, if necessary completely. This would mean that the circuit pcb would be an integral part of the PCB that  formed the plates, but as always, things go different in practice. For one thing , the piece of PCB I intended to use just wasnt long enough. Second, my PCB etching possibilites are temporarily impeded. Third, I wanted to add an LDR, meaning that I had to have some sort of  translucent cover.

I constructed the circuit on a small piece of stripboard:

IMG_20160225_163125 i2cprobe

With regard to the LDR, sure that is nonsense. If I want to add an LDR I  could just as well add it to my base station. But I am not adding it because I have to, I am adding it because I can and because I wanted to get some experience in programming an I2C slave. An LDR might not be so usefull but in future I may want another sensor, e.g.a sensor that reads if there is really water flowing from the irrigation tube.

Also I had decided on putting the capacitor plates back to back, but as I did not have double sided PCB I just used two pieces glued and  soldered(!) together.
So My BOM was rather simple:

  • 2 equal size pieces of PCB  Size depends on what you have, but do not make them too small. I used 12×3 cm.
  • 1 piece of 0.5-1.0 cm plastic for a baseboard. I used an old cutting board.
  • 1 clear/translucent cover, I used the lid of a whipped cream spray can.
  • 1 piece of  thin 4 wire cable, length depending on your need

pcbprobe2I glued the two piecesof pcb back to back. drilled a hole in all 4 corners and through soldered a piece of wire through each hole, thus anchoring the plates together. I removed some copper around the solder so it would become an island isolated from the rest of the plate. (See picture).

Soldering the plates together in the corners may not be necessary if you decide to electrically isolate them from the soil with e.g. shrink tube.

Eventually I will place an NTC on the plate as well after it is covered with Shrink tube. Esthetically it might be better to put the NTC under the shrink tube, but that could create an air pocket.


baseplateI made the base plate from a o.5 cm thick piece of soft plastic. Cut out a round shape  with a 5.5 cm diameter to fit the base of my  clear dome and  made a slit 3 x0.3 cm into that in which the PCB  fits snug. made a round hole for the connecting cable.




I soldered two wires on the top of the PCB, one on each side. Soldered wires on an NTC, insulated those, attached the NTC to the bottom of the PCB with the wires leading to the top and then covered the PCB with shrink wrap.

probeEventually the probe looks like this (picture)probe3

Andreas Spiess, discusses Capacitive sensors vs Resistive sensors in a youtube video.

A capacitive soil humidity sensor: Part 2

In a previous article I presented a simple way to read a capacitive moisture sensor with a simple RC generator.
In this post I will present a sensor with some added functionality that can be read through I2C. The circuit (figure) doesnt need much explanation: the RC generator we saw in the previous article and the two variable resistors in a voltage divide read by analogue inputs. The Attiny 45  is the heart, or rather the brains of the sensor. As the Attiny  functions as an I2C slave we will need the TinyWireS library. The library comes with some examples and one example was quite easy to rework to what I needed. The code is as follows.

#define I2C_SLAVE_ADDRES 0x4
#include <TinyWireS.h>//
#define TWI_RX_BUFFER_SIZE ( 16 )

volatile uint8_t i2c_regs[] =
// Tracks the current register pointer position
volatile byte reg_position;
const byte reg_size = sizeof(i2c_regs);

byte LDRvalue;
byte NTCvalue;
int MoistPulse;

 * This is called for each read request we receive, never put more than one byte of data (with TinyWireS.send) to the
 * send-buffer when using this callback
void requestEvent()
	// Increment the reg position on each read, and loop back to zero
	if (reg_position >= reg_size)
		reg_position = 0;

 * The I2C data received -handler
 * This needs to complete before the next incoming transaction (start, data, restart/stop) on the bus does
 * so be quick, set flags for long running tasks to be called from the mainloop instead of running them directly,
void receiveEvent(uint8_t howMany)
	if (howMany < 1) { // Sanity-check return; } if (howMany > TWI_RX_BUFFER_SIZE)
		// Also insane number

	reg_position = TinyWireS.receive();
	if (!howMany)
		// This write was only to set the buffer for next read
		i2c_regs[reg_position] = TinyWireS.receive();
		if (reg_position >= reg_size)
			reg_position = 0;

void setup()
	pinMode(1, INPUT);

void loop()
	 * This is the only way we can detect stop condition (
	 * it needs to be called in a very tight loop in order not to miss any (REMINDER: Do *not* use delay() anywhere, use tws_delay() instead).
	 * It will call the function registered via TinyWireS.onReceive(); if there is data in the buffer on stop.

void readSensors()
	LDRvalue = (analogRead(A2)) >>2; // max value is 1023/4=255 is 1 byte (FF) physical pin3=PB4=A2
        i2c_regs[0] = LDRvalue;
	NTCvalue = (analogRead(A3)) >>2; // max value is 1023/4=255 is 1 byte (FF) pin2=PB3=A3
	i2c_regs[1] = NTCvalue;
	//pulsepin= pin6=PB1
	MoistPulse = pulseIn(1, HIGH);
	i2c_regs[2] = highByte(MoistPulse); // or use = MoistPulse >> 8;
	i2c_regs[3] = lowByte(MoistPulse); // or use = MoistPulse & 0xFF;

I have called the pulseIn function with an integer rather than with a long. This means that you will have to choose a value for R3 that gives a reasonable range for the type of soil that you use. A range of say 0-200 uS is very reasonable. Once you have done that you can also add a timeout to the pulseIn function. This should be abt 2 times the pulselength you expect (but depends on the duty cycle). With regard to the two variable resistors, they are in pull up so their value is Rntc = Rseries/((1023/ADC) – 1));  For the NTC this could be substituted in a Steinhart Hart approximation

The code to call the values (and that is loaded onto the master arduino) is even simpler:

#include <Wire.h>
void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output

void loop() {
  for (byte i=0;i<4;++i){
  Serial.println(readRegister(i),HEX); // print the character
Serial.println(" ");

uint8_t readRegister(uint8_t regaddress)
	Wire.requestFrom(4, 1);


This code only prints out the values, you still need to combine the LSB and MSB from the cycle time and e.g. switch a pump based on the value. You could do that with this function:

int combine (byte lsbv, byte msbv)
	int value = msbv << 8;
	value = value | lsbv;
	//value= msbv<<8 | lsvb;//if you want to do it in one go
	return value;

Don’ t forget that the I2C lines need pull-up resistors of 4.7-10k.

In a next article I will present the construction of the probe itself.

A capacitive soil humidity sensor: Part 1

An automated plant/garden watering system is a popular application for  the Arduino and other microcontrollers. The humidity sensor that is often used is  often a resistance meter: 2 probes in the soil form a resistor and as part of a voltage divider that gives info on the amount of water in the soil.
The major shortcoming of these sensors is that since there is a current flowing, the probe is sensitive to electrolytic corrosion. Another shortcoming is that it doesnt really give information on the soil humidity, but more on the  ion concentration in the soil. Remember: pure water is a bad conductor, It are the ions in the water that make it a conductor.

To get around the corrosion, people have started to feed the probe with AC rather than DC current, but at best the circuits for this supply a pulsating DC current. Another  possibility to minimize corrosion is to  switch off  the current to the probe and only switch it on when a measurement is taken, say every 5  minutes.

All these measures help but even then, just due to the contact with moist soil, the metal on the probe will corrode and weather.
Another method is to encase the probes in plaster. Some people swear by it but I  think it is bothersome. Also the plaster encased probe has a lag as it will retain water  for some time after the  soil is already dry and it will still be dry for some time after the soil is already moist.

Capacitive measuring is a way to avoid these problems. With capacitive measurement the ‘plates’ of the capacitor can be electrically isolated from the soil and the soil in fact forms the dielectrum of the capacitor. Water makes a good dielectrum, whereas ions dont. Capacitive measuring therefore will have a better correlation with the actual amount of water in the soil than resistive measuring and, as said, ideally there will not be any corrosion.

Although the Arduino can measure capacity, it needs 3 pins for that. Also, it isnt really practical to have a long wire as part of your soil probe capacitor go to your Arduino as the wire and whether it is  straight or curly or in a loop, will act as a parasitic capacity and affect the measurement.
Putting your Arduino right onto your plantbed might also not be the best solution.

RC Oscillator With 74HCT14

A better way to do this is to introduce an RC oscillator in which the soil functions as a capacitor and the Arduino measures the frequency that comes out of the oscillator. More water  will increase the value of the ‘capacitor’ which usually will lower the frequency (or increase the cycle time) and that can be measured with the arduino.

An easy RC oscillator can be seen in the figure to the right. A 74HC14 or 74HCT14 Inverter Schmitt-trigger is all you need. For the HC version the Frequency is:
for the HCT version it is:

But in fact the frequency isn’t really that important because we are not making a frequency meter. We are only interested in changes of the frequency that relate to  dry, moist or wet soil. The workings of this type of Oscillator are explained here. and here. The theory behind the frequency calculation is explained here.

leydenjarI tested my set-up initially with a sort of Leyden Jar as a capacitor. Simply put I taped two pieces of alufoil on the outside of a glass jar, attached wires to it and tried if I could measure a change between an empty and a full jar. I started out using a 100k resistor, but only got a decent range after using a 2M2 resistor: an empty jar gave me 1uSec (maybe less, but I guess that was my lower limit) a half full jar around 50uS and a full jar about 100uS (measured with PulseIn). From using a known capacitor I knew I had to use the formula f=1/T=1/(0.67*R*C), but as said, neither the frequency or the actual capacitor value are of much importance as we are just looking for  changes.

Nevertheless the capacity of this jar (when full) could be calculated from
Now ofcourse this isn’t entirely correct as with PulseIn We only measured half a cycle, so in fact the Capacity is more likely to be 136pF, provided the dutycycle is 50% (some sources say it is 50% others say it is 33% with the space  2*mark).

byte pin = 8;
unsigned long duration;

void setup()
  pinMode(pin, INPUT);

void loop()
  duration = pulseIn(pin, HIGH);
  Serial.print("Time ");
  Serial.print(" usec ");
  Serial.print(" kHz ");
  Serial.println(" Hz");

Then it was time to do some garden field tests. There are several ways to construct a probe: two pieces of PCB at a distance from eachother, One piece of PCB with two plates etched onto it, or a piece of double sided PCB. With regerd to the latter, we tend to think of a capacitor as two plates with a dielectrum in between, but in fact it can also be two  plates with a dielectrum around it.

capacitive probe-textMy garden field test, using two insulated plates of PCB (picture) kinda gave similar results as with the Leyden Jar so I knew I had a decent design.

Yet, two plates is not really very handy: you have a loose wire connecting the plates and as that forms part of the capacitor it introduces stray capacitance, so I decided to go for either a double sided PCB, or a single sided PCB with two plates etched onto it and then place the oscillator on that PCB as well. Frankly, that idea isnt new as many of the (semi)DIY (e.g. the ‘Chirp’) or commercial capacitive probes follow that design as well.

Now it is always good  that before you make a PCB to think through what you want. As I was using only 1/6th of the 74HCT14, I was thinking maybe I could use the other gates as RC oscillators as well  for e.g. an LDR or NTC. Now ofcourse I immediately dismissed that again as I could just as well use an analog read of those and I would have to run extra cables that would all carry a frequency signal, no doubt those would interfere, but it got me thinking. The ‘Chirp’ has a big brother that has an I2C interface, if I would add that, I could add some other sensors (light, temperature)  to my probe. With just the capacitive sensor I would have to run 3 wires (+Vcc, signal, ground). With I2C 4 wires (+Vcc, SDA, SCL, Ground) would be enough for a number of sensors on my probe. Also, as I have various plantbeds, having a number of I2C probes wouldnt require  more inputs on my Arduino.

Obviously, adding I2C meant adding a microcontroller, preferably a cheap one, which easily brings one to the attiny25/45/85 series. If you have good eye sight and a steady hand, you could consider the soic version that is available from 30-70 cts. If you go for a DIP version, that is around 0.9-1 euro. Considering one can get an entire Pro Mini for about 1.20, obviously that is a narrow gap and I (or you) could consider giving each plantbed a totally dedicated  Pro Mini microprocessor that could take care of the irrigation as well.
Having said that, in the past, I have made a self contained system for plantbeds, just using 1 Opamp, so maybe I should just consider this I2C excercise a ‘learning excercise’

capacitive74HC14_attinyI came up with the following circuit. It is easy to recognise the RC oscillator. The cycle signal is fed to PB1 whereas the analog inputs on PB3 and PB4
A3= PB3=physical pin2;A2=PB4=physical pin3.
Pin PB2 is used as SCL signal and PB0 as SDA signal for the I2C interface.

If for any reason you do not have a 74HCx14 in your toolbox, there are other ways of constructing an RC generator. E.g. a 555 or a 74HC00. Even a single transistor could be used. With this type of oscillator the dutycycle approaches 50%


In a next article I will discuss how to implement the I2C software.

Simple EEPROM Module for Arduino or other microcontroller

256 EEPROMs come in handy if you want to store some data without losing it. Many microcontrollers come with some internal EEPROM, but as EEPROMs have a finite (though very large) number of writes before they start becoming faulty, my anal retentive character always had a problem with using that internal EEPROM.
There are other reasons too why you might want to use an external EEPROM: data logging in which you just want to swap an EEPROM rather than having to read out your microcontroller in the field.
Anyway, I just wanted to share a simple 5 minute build EEPROM module that is a bit simpler than a prior one i published.
1x 24LC256 EEPROM (or other size)
2x 4k7 resistors
stripboard 5 strips of 8
8 pin DIL IC socket (optional)

256pin I am using a 24LC256. That is a 256 kiloBIT EEPROM so in fact only a 32kiloBYTE EEPROM in my traditional way of thinking. These are not to expensive. Can be had for around 80 ct USD.
Although the board can also be used for smaller EEPROMS, like a 24C32, I would advise against that. If that is what you need, buy a 50 ct DS1307 RTC module that includes a 24C32 (which is actually 4kByte).

Anyway, the 24LC256 is an I2C EEPROM, which is pretty standard and which makes use easy
Pins A0, A1 and A2 select the I2C addresses, (A0=pin1, A1=pin1, A2=pin3).
The addressing is as follows

1 0 1 0 A2 A1 A0 x

So if you connect pins A0, A1, A2 to GND the I2C address will be 1010000 = 0x50 in hexadecimal.
If you connect them all to Vcc it will be 1010111=0x57.
As 0x50 is an address that is often used, I decided to connect A2 and A1 to ground and A0 to Vcc, which gives 0x51. It also made the design a tadd simpler.
Pin 7 is the ‘WriteProtect’ pin that needs to be pulled HIGH for ReadOnly and LOW for Read/Write. Obviously I connected it to ground.
The pull up resistors are both 4k7
You will find many EEPROM libraries in the Arduino Playground I tend to use the following code to read and write:

void writeData(int device, unsigned int addr, byte data)
// writes a byte of data 'data' to the chip at I2C address 'device', 
// in memory location 'add'
    if ( addr > 65535 )
        addr = addr | B00001000;
    Wire.write((int)(addr >> 8));   // left-part of pointer address
    Wire.write((int)(addr & 0xFF)); // and the right
byte readData(int device, unsigned int add)
// reads a byte of data from memory location 'add' in chip at I2C address 'device'
    byte result; // returned value
    Wire.beginTransmission(device); // these three lines set the pointer 
				    // position in the EEPROM
    Wire.write((int)(add >> 8));    // left-part of pointer address
    Wire.write((int)(add & 0xFF));  // and the right
    Wire.requestFrom(device, 1);    // now get the byte of data...
    result =;
    return result; // and return it as a result of the function readData

Special Notes if using the 24LC1025


This board can also be used for larger EEPROMS, but……… if you use it for the 24LC1025, you need to make a small adaptation. With this chip A2 MUST be tied to Vcc for it to operate.

The 24LC1025 has an internal addressing boundary limitation that is divided into two segments of 512K bits. Block select bit ‘B0’ to control access to each segment.

The addressing is determined by

1 0 1 0 B0 A1 A0 R/-W  

in which the ‘Block select bit is used to address the lower or higher 512k Block The chip thus has 2 different addresses. with the A0A1 selection as in my board those are:

1010101 =0x55

1010001 =0x51

For memory access from 0..65535 the I2C address for the Memory Chip is 0x51. If you want to access memory between 65536..131071 the Chip address is 0x55

You cannot write across the 65535..65536 byte boundary with breaking the operation into two write() calls. One to chip 0x51, the other to chip 0x55.

Adding Shiftregisters to an Attiny10

I have a problem: if I underuse a microcontroller, i feel uncomfortable. So if I see a project with an Arduino or standalone Atmega328 that uses only say 2 pins, I start thinking ‘Attiny’. But even then I would feel bad only using 2 pins of say an attiny85.

For a summer project for a friends daughter, she needed a number of LED’s to flash (on a costume). Well great for an Atmega328, after all that has a lot of pins, but then using an Atmega328 just to flash some LED’s? When it can do so much more? Plus, I had my bare Atmega328 chips already destined for other projects.

I had Attiny85 and Attiny13 chips, the latter being a great candidate… but that only has 4 pins to drive LED’s with, so i was thinking ‘Shift Register’. I still had some 595’s and 164’s.
But they need about 3 pins ‘underusing’ the Attiny13.
Anyway… I had an Attiny10, just one that I had been playing with.
Now I would not really suggest anyone to get these as even at aliexpress they are relatively expensive: I saw 10 for about 8 USD, while for about 1.60 USD one can already get a pro-mini. (Edit 2019: prices have come down. Now available for about 40 ct a piece if bought per 5). I might be anal retentive but I am not stupid. If you are lucky enough to frequent Japan though, they are available there for only 35 cts (eurocents).
Also they are in SOT23-6 which makes soldering difficult. And they cannot be programmed via ICSP like the Attiny85.
Mine however came from a guy who had bought a couple of them and gave me one to play with.
So, plenty of reasons to skip this chip, but as I had one, I saw it as a challenge to use it.
The circuit I used is pretty standard:
There are SOT23 to DIL adapters but they are kinda expensive plus that seemed defeating the purpose of using a small chip, so for testing and programming I etched a small board (I know, will probably never use it again) with a  clothes-pin as main ingredient (to keep the attiny10 in place). For the resistors I used sip packages.

Programming the Attiny10 is not done through ICSP but  through TPI. TPI programming can be done roughly via 3 ways:

  • A commercial AVR programmer (STK or AVRISP)
  • An FTDI break out board that has DTR CTS and RTS broken out. (Check links at end of program).
  • Your Arduino.

I opted for the latter and this is how to do that.

attiny10Connect the Attiny10 with digital pins D10-D13 as indicated in the circuit to the right.

Then you need the programming firmware. This has been developed by Keri Duprey. The latest version can be found here:

ATtiny4_5_9_10_20_40Programmer.ino or check here. The latter code does not program the Attiny20 or Attiny40, but solved some hickups in the former code

The former program can be used standalone or with a GUI that can be found here:
GUI: ATTiny4_5_9_10_20_40_Programmer.jar

The program  expects a hex file to be copied in the serial monitor. If you are using the GUI, it just lets you upload the hex file.

If you prefer to program the Attiny with an FTDI brak-out board, check this blog. Further interesting reading is found here. And here.

Or here

Using the Attiny’s Reset Pin without setting Fuse Bits

attiny13_reset The Attiny 13, 25, 45, 85 are charming little chips that as the name says it, are tiny. They are supposed to have 6 I/O pins but  pin number one  (PB5/ADC0) doubles as RESET pin and in order to use it as an I/O pin, one needs to set the proper fuses in the chip. That is not so difficult but the problem is that once that fuse is set, the chip cannot be reprogrammed  by SPI, but needs a High Voltage Programmer that first needs to reset  the specific fusebit again.
Though it was a bit unclear to me what the required Low Voltage is that the Reset pin needs for a Reset, it seems that it is lower than what is  generally interpreted as a ‘LOW’.
That potentially opens possibilities to use the range in between +Vcc and Vreset for input, without resetting the chip
To test this I used an Attiny13, hooked up an LED  and resistor to PB0 and connected the middle contact of a 25k variable resistor to Pin 1 and the outer contacts to Vcc and 0V respectively.
I then loaded the Attiny with the following program:

// Using the Reset pin as ADC0

const int Led = 0;
int x=0;
void setup() {
pinMode(Led, OUTPUT);

void loop() {

When the variable resistor is turned all the way up to the +Vcc rail, the LED flashes in a steady rhythm. When I turned down the variable resistor, the flashing frequency went up, i.e. a faster flashing LED.. as expected. This went on till the LED suddenly stopt flashing (as the RESET function kicked in).
It turns out that the point of reset was at  9K Ohm. Which is equal to 5 *(9/25)= 45/25=9/5=2.2 Volt.
That is generally not much different from what is considered a LOW and it is a bit higher than what I understood the Vreset to be.

As ofcourse you cant have a circuit  that is always on the brink of resetting, we need to build in some form of protection: something that keeps the voltage on pin 1 from hitting 2.2 Volt.
attiny13_reset2Let’s consider the following circuit. Suppose that the lowest resistance we  measure under the light circumstances we are  using is 1k. Then we know that the current through that 1 k must be minimally 2.2 mA to stay above the Reset voltage. Hence the total resistance of the LDR + the  Resistor must be  5/2.2=2.27k, hence the resistor must be not more than 1.27k. The closest E12 values are 1.2 k and 1.5 k and we should choose 1.2k to be safe.

Of course one can use the circuit with the resistor and LDR interchanged, but then  it is much harder to calculate a safe resistor as in darkness the value of the LDR may go up to several Mega Ohm, calling for a resistor that is in that same range.

Given the fact that the Reset level  is on 2.2 Volt, I have not bothered to try if it would work with digitalRead

Flashing an LED with Attiny13: Timer, Watchdog timer and Sleep


led_flasherI have written two articles on timers in one article I discussed timers in general and in the 2nd article I went more into a couple of practical examples.
As I recently started to use the Attiny13 a lot, I thought it would be good to write something specifically on  how to use timers in the Attiny13.
As usual a Led flasher is a good example and we will start using the Timer OverFlow register to achive our task but also give an example using the Watchdog Timer.

Before I go on, if you are using the well known Smeezekitty core for the Attiny13 you are in trouble. In order to correct the timing, John Smeezekitty uses the Timer0 Overflow vector in his core for the Attiny13. You may want to use this core. If you continue to use that core you may want to  make this modification as well.
Also, if you would want to adopt this for another Attiny chip, apart from changing some of the register names (like TIMSK rather than TIMSK0), you may find that the Timer0 OverFlow Vector is  used in a variety of other cores. For the Attiny85 for instance, you may encounter a ‘Vector_5’  error, which is the Attiny85’s version of the Attiny13’s ‘Vector_3’error. Vector5 and Vector3 are the Timer0 Overflow vectors for resp the Attiny85 and the Attiny13
The ATtiny13 datasheet lists the timer interrupts (and other interrupts) in §9.1.
In Table 9.1 we find the Timer/Counter Overflow interrupt vector.
It is listed as TIM0_OVF. We then know the Interrupt Service Routine (ISR) we need to call in our program is “TIM0_OVF_vect”

The next chapter with information on the behaviour of the timer is in §11.7.1, it says that in “normal mode” (WGM02:0 = 0), the counter counts up, and once it gets to the “top” (0xFF as it is an 8 bit timer) it resets back to zero and the TOV0 (Timer/Counter Overflow Flag) bit is set.
How we enable the Timer/Counter0 Overflow Interrupt is found in §11.9.6: it states that if you enable the Timer/Counter0 Overflow Interrupt by setting the TOIE0 bit to 1 in the TIMSK0 register and interrupts are enabled, whenever the TOV0 bit is set, the interrupt will occur.

The timer that we are using to generate an interrupt in the Attiny13 is an 8-bit register. Unless ‘prescaled’ (see later) it increments once per clock cycle.  As it is an 8-bit register its maximum value is 255, therefore once every 256 clock cycles the timer reaches its maximum value and resets back to zero.  When this occurs the Timer/Counter0 Overflow Interrupt will be generated, and we can can catch this in and Interrupt Service Routine (ISR) in our program, to respond to the generated interrupt, and flash a LED.

The Attiny13, knows 3 major frequencies: 9.6 MHz, 4.8 Mhz and 1.2 MHz.
The frequency of 1.2 MHz is obtained by dividing  the internal 9.6 MHz clockspeed by 8 by setting some fuses. Often, when sold, those fuses are set and your brand new Attiny13  is set to the 1.2 Mhz clockspeed.
So, how often does that Timer Interrupt Overflow occur then?
Well that is easy: at 1.2 MHz, the length of a clock cycle is 0.00083333333333333 ms. As the interrupt is set when the timer register reaches 255, that means there are 256 Clock cycles (remember, we are counting from 0). Thus the interrupt occurs every 0.213333333 milisecs which is 1000/0.213333333=4687.5 per second. (I used a bit of an extensive calculation here to  make it clear. Normally you would just divide the frequency by the register (1.2Mhz/256=4687.5)).  That is a bit fast to  see the led flashing.

The standard practice then is to add a counter in the ISR and toggle the LED once the counter reaches a certain number of interrupt overflows.  Earlier we calculated that the interrupt will be triggered approx 4688 (1.2 MHz *1/256) times per second. With an 8 bit counter variable we cannot even count to 4688: we can count a max of 256 interrupts. as the interrupts occur every 0.2133333 msecs that would be 54.613333248 msec, which is about 18 times a second. Still a bit fast for a flashing LED.
If we use a 16-bit variable instead: 65536/4688 gives us a maximum flash time of ~13 seconds. So that is one way of doing it, but there is another (better?) way…

Earlier I touched upon ‘prescaling’ the timer.  This slows down the increment of the timer counter by using 1 out of every so many clock cycles to increment the timer counter (see §12.1).   These prescale factors are found in the datasheet in §11.9.2, table 11-9.  The largest slow down or ‘prescale’ factor is 1024 (by setting CS02 and CS00 bits to 1 in the TCCR0B register).

If prescaling is set to1024, the timer register gets incremented 1172 times a second (1.2MHz/1024), and thus overflows 4.6 times per second. With an 8-bit variable to count the overflows we can count to about 56 seconds.

This gives the final code:

volatile byte counter = 0;// interrupt needs volatile variable

ISR(TIMER0_OVF_vect) {
   if (++counter > 5) {// interrupt occurs 4.6 times per second
      // Toggle Port B pin 3 output state
      PORTB ^= 1<<PB3;// Toggle Port PB3
      counter = 0; //reset the counter

void setup() {
   // Clear interrupts, just to make sure
   // Set up Port B pin 3 mode to output by setting the proper bit
   // in the DataDirectionRegisterB
    DDRB = 1<<DDB3;

   // set prescale timer to 1/1024th 
   TCCR0B |= (1<<CS02) | (1<<CS00);// set CS02 and CS00 bit in TCCR0B

   // enable timer overflow interrupt
   TIMSK0 |=1<<TOIE0;// left shift 1 to TOIE0 and OR with TIMSK
                     //  = set TOIE0 bit
   sei(); //start timer
  void loop() {
      // ISR handles the flashing so no code here

Sleeping.. with an alarm clock

With the LED flashing as in the program above, the Attiny13 doesnt do much more than waiting for the right amount of interrupts to pass by and then toggle the LED.
All that time it is using full energy. Now if only we could put it to sleep, only to wake up when the LED needs to be toggled,  theat would save energy consumption and thus extend  a possible battery life..
There are 3 sleep modes of which ‘Power down’ gives the deepest sleep and thus the least energy consumption. In order to wake from sleep the sleeping chip either needs an external interrupt, or it needs to be woken by the watchdog timer. The watchdog timer is described in chaper 8 of the Attiny13 datasheet. Its operation is quite simple: it needs bits set in the Watchdog Timer Control Register (WDTCR) to determine the time it will run and then  it needs to be enabled to start it. When the watchdog timer sends an interrupt, the WDT_vect ISR is called and executed and the program goes back to where it was put asleep.
As we only need to toggle an LED, that can be done in the WDT_vect ISR

ISR(WDT_vect) {
// Toggle PB3 output
PORTB ^= 1<<PB3;

void setup(){
// Set PB3 to output
DDRB = 1<<DDB3;
//set timer to 1 sec
WDTCR |= (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0);
// set timer to 0.5s
//WDTCR |= (0<<WDP3) | (1<<WDP2) | (0<<WDP1) | (1<<WDP0);

// Set watchdog timer in interrupt mode
WDTCR |= (1<<WDTIE);
WDTCR |= (0<<WDE);

sei(); // Enable global interrupts
void loop() {
// Everything is already handled in the ISR

This program will  let a led flash every second, or should you desire, flash within a second.
Once we have done that we can put the the  Attiny to sleep, only to be woken up by the Watchdog timer.
To handle the sleep mode we make use of the sleep.h header file that is part of the avr library. In the setup we set the sleepmode to Power Down. with that we in fact set two bits in the MCUCR register, SM[1:0] to 1 resp 0.
Then in the loop we do a call to sleep_mode()
The command sleep_mode() in fact makes a call to three routines:

sleep_enable(); \
sleep_cpu(); \
sleep_disable(); \

sleep_enable() sets the Sleep Enable bit in the MCUCR register
sleep_cpu() issues the SLEEP command
sleep_disable() clears the SE bit.
So what happens is that at the end of the setup routine the watchdog is set and the sleepmode is set. Upon entry in the loop the is put to sleep and kept in sleepmode. When a Watchdog interrupt occurs, the Attiny13 wakes up, the interrupt routine is carried out and the  program jumps back to where it was  put asleep. there it finds the sleepdisable macro that resets the Sleep Enable bit. This is in case  you want to perform more tasks. But as there aren’t any more tasks, the Attiny is brought right back to sleep again, till the next Watchdog interrupt

#include <avr/interrupt.h>
#include <avr/sleep.h>
ISR(WDT_vect) {
// Toggle Port B pin 3 output state
PORTB ^= 1<<PB3;

void setup(){
// Set up Port B as Input
DDRB = 0; // usually not necessary but it will save current
// Set Port B pin 3 mode back to output
DDRB = 1<<DDB3;

//set timer to 1 sec
WDTCR |= (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0);
// set timer to 0.5s
// WDTCR |= (1<<WDP2) | (1<<WDP0);
// set timer to 4 sec
// WDTCR |= (1<<WDP3);

// Set watchdog timer in interrupt mode
WDTCR |= (1<<WDTIE);
WDTCR |= (0<<WDE);
sei(); // Enable global interrupts

void loop() {
sleep_mode();   // go to sleep and wait for interrupt...

In order to save  even more energy, there is more we can do:  disable the ADC channels and set  all I/O lines to input (but when out of sleep you have to set back the one that toggles the LED).
In the setup routine we can switch the ADC off by the command:

cbi(ADCSRA,ADEN);    // switch Analog to Digitalconverter OFF


ADCSRA &= ~(1<<ADEN); //

Which is the same

With the commands:

ADCSRA &= ~(1<<ADEN);     //turn off ADC
ACSR |= _BV(ACD);         // disable analog comparator

I got the following results:
sleep: 6.6uA (0.0066mA)
non sleep: 3600 uA (3.6mA)
That is a factor 600. Of course this was with the LED unplugged as I was only interested in the consumption of the chip. The LED would add another 4-5 mA.
Disabling the analog input buffers with the command:

DIDR0 |= (1<< AIN1D)|(1 << AIN0D);//disable

didn’t change the power-consumption. It should be possible to go even lower with lower chip frequency (mine was 9.6Mhz) and lower voltage (mine was 5V). On 3.3 Volts the current went to 5uA. Mind you that also the use of the WDT ‘contributes significantly to the power consumption’ according to the datasheet.
If you are using an attiny85, it may pay to also issue the   power_all_disable(); command that sets the PRR, but the attiny13 doesnt have that. The Attiny13A does, but as the core used is thinking it is an Attiny13, the <avr/iotn13a.h> file is not loaded.
One can counter that by adding the following definitions:

#define PRR _SFR_IO8(0x25)
#define PRADC 0
#define PRTIM0 1

and for the BOD CONTROL Register

#define BODCR _SFR_IO8(0x30)
#define BPDSE 0
#define BPDS 1

However, activating the PRR register or closing the BOD did not change the power consumption of my attiny13A.

Note: if you are using the MicroCore for the attiny13 you may get a vector 8 error. That is because the WDT is already used to set millis(). You then need to disable that in the core_settings.h file. If you installed the core via the boards manager you are most likely to find it here: user\xxxxxxx\AppData\Local\Arduino15\packages\MicroCore\hardware\avr\1.0.2\cores\microcore

Attiny Motorboard


For a project I needed to drive a steppermotor, a servo and possibly a small regular DC motor. Piece of cake of course with an Arduino, but I am anal retentive where it comes to underusing an Arduino and as I had come by a couple of Attiny13A’s why not use those.attiny-stepper

A circuit is easily drawn up and doesn’t hold too many surprises. The chip of choice is the ULN2003 that can take 500mA per port. Driving a steppermotor for a while can make the chip unpleasantly hot and adding a heatsink is a wise precaution. My Steppermotor, the 55SI-25DAWC (1350 g/cm torque) has a 6 prong connector with the two midconnectors that receive the Motor Voltage on one side and the 4 coil connectors next to that, so that kinda dictated my design on K1. It is 36 Ohm at 12 Volt, so each coil uses 333mA.
The servo connector K2 is rather standard: Signal-Vcc-Ground. The DC motor connector K3 is fed by the open collector output of the ULN2003. The signal for that is shared with the Servo, as for now I probably wont be using both, but If I need both, I might be feeding it off of PB5, though that would mean resetting the fuses to disable the reset that is attached to pin1. It also means I need a high voltage programmer if I need to reprogram the chip.tinystepper-stripboard

A prototype is easily set up on a piece of stripboard. The interruption of the second strip between F and G is just there in case I want to add a jumper to switch between servo use and ULN2003 use on Output Q3. Rather than making K3 just a two prong connector, I decided to add an entire row that is connected to V++, so if I so wish, I can also use Q1-Q7 just for DC motors. It gives a bit more flexibility. As the Attiny13 has a max of 6 I/O pins and the ULN2003 has 7 drivers, one can always decide to use the drivers in parallel to drive bigger loads.

The Attiny13 has only 1 kByte available, but that is enough for a simple application with a servo and a steppermotor.
As my steppermotor is a rather old one already, I doubt if too many people have it. so my program is most likely no use for anybody else, but driving steppermotors is rather easy.
With the unipolar steppermotor that I use, there are 3 ways of driving it: Halfstepping, Full stepping or wave driving
Wave driving is the easiest: you just make each pin high in the right sequence.coils.jpg
For a unipolor motor that would be: 1a-2a-1b-2b. That would spin the motor in low torque because there is always a moment in which there is no coil magnetized.
Half stepping requires alternating between energising a single coil and two coils at the same time. This increases the angular resolution, but the torque fluctuates between utilizing a single coil and both coils. In principle though it means that you keep one coil magnetized while you already switch on the second one. So that would be:
Full stepping is when two coils are always energized at the same time. This allows the maximum torque of the motor to be used, at the expense of more current. This looks like:
For completeness sake: the 55SI-25DAWC can also be used as bipolar stepper motor just by not using the white wires. This gives more torque and less power consumption
The below program is just a quick illustration of how to drive a stepper in Full stepping mode. The program was written on the basis of the Smeezekitty core for the Attiny13. For the MCUDude microcore a small change is necessary, indicated in the program below:

                               |     |
 Hardware:             +5V     |     |
             ________   |      |     |     
      ___   |        |  |      |     |  ___  
 +5V-|___|--|RES  VCC|--+   B3-|I4 O4|-|___|-+Q3 brown
            |        |         |     |  ___  
        B3--|PB3  PB2|---------|I5 O5|-|___|-+Q2 yellow
                     |         |     |  ___  
          --|PB4  PB1|---------|I6 O6|-|___|-+Q4 blue
            |        |         |     |  ___  
         |--|GND  PB0|---------|I7 O7|-|___|-+Q1 red
            |________|         |     |       
             ATtiny13        |-|GNDCC|------- +12Volt white
                               |_____|    |__ +12Volt white
Funktioning: sequence: red-yellow-brown-blue
    Stepmotor drive sequence and encoding:
      Portbit      PB0   PB1   PB2   PB3   |            |
      Color         rd    bl    yl    bn   | Portbit    | Byte
      Step          O7    O6    O5    O4   | 3  2  1  0 |
         1          1      0    1     0    | 0  1  0  1 |  05
         2          0      0    1     1    | 1  1  0  0 |  12 / 0C
         3          0      1    0     1    | 1  0  1  0 |  10 / 0A
         4          1      1    0     0    | 0  0  1  1 |  03
If you would use D8-D11 on an Atmega328, you could use the same values 
for the Portbits, as D8-D11 is equivalent to PB0-PB3

 red    ------------+------------ brown
 yellow-------------+------------ blue
 #define delayTime  200// time between steps
  byte servoPin=4;
 void setup()
 DDRB=31; // set Port 0-5 as output
 void loop()
 stepper(3); //=90 degrees. The angle is 7.5 degr,=> 4 steps=30 degr.
 void stepper(byte turn)
 for (byte pos=0;pos<turn;pos++)
 PORTB=3; delay(delayTime);

The above program is just rough, to show how to drive a stepper and it misses some sophistication. Writing directly to PORTB is OK, but in this case it will also write a “0” to PB4 and PB5. PB5 is not much of a problem, but you may want to use PB4. In my case that is where I put my servo and that doesn’t really cause a problem as I do not use them at the same time.
If you want to avoid writing to PB4 and PB5, use a mask to only write to bit 0-3. The mask to do that is B00001111.
If you then want to set bits bits 0 and 2, go like this:
PORTB=(PORTB &~mask) | (B00000101);
For those who find this too cryptic:
it first ANDs the value of PORTB with NOT mask and OR’s the result with the value we want to write and assigns that back to PORTB.
So, suppose PORTB= 00010000 and we want to write 00000101 to it, we cannot assign that immediately because that would clear PB4.
However, if we do as described, it becomes:
PORTB=(PORTB & 11110000) | 00000101
PORTB=(00010000 & 11110000) | 00000101
PORTB=00010000 | 00000101
PORTB= 00010101
We have written our value and kept PB4
So, why cant we immediately OR PORTB with the value we want in stead of AND-ing it first?
Well because that might keep PB4 and PB5… but it also keeps PB3-PB0 unchanged if one of them already contained a ‘1’
Of course inverting the mask wouldn’t be necessary if we would define it already inverted, but it is common practice to do it as such

Writing to the Servo is quite easy:
add this function:

void pulseOut( byte pin, byte p){
//delayMicroseconds(300+p*(2500/180));// this is no longer allowed in the Microcore
delay(5);// so we use this

and call that like this:

for (byte pos=0;pos<180;pos++)

Full program here.  (backup)

High Torque mode uses more power than low Torque mode. In Low Torque mode each step in my case (12V, 36 Ohm coil resistance) takes 333mA. In high torque mode, each step takes 667mA (2×333.33).

In case you are wondering what project I needed it for: I need a doll to turn and nod its head.

Self build alertSmart-Electronics-28BYJ-48-5V-4-Phase-DC-Gear-Stepper-Motor-ULN2003-Driver-Board-for-Arduino

I enjoy building stuff, but it isn’t always the wisest thing to do. There is a ULN2003 based driver board, including a good steppermotor available for about 1.80 euro. That is a good deal. In my case, I just wanted the Attiny13 and ULN2003 driver to be on one PCB, I already had a spare ULN2003 and I had the steppermotor so that was a no brainer, But for anybody else wanting to drive a steppermotor, consider that cheap motor and driver.
My cost for just the driver section (chip, pins, socket), I estimate 45 ct (euro). Not sure what my stepper costed me 30 years ago, but a decent hobby stepper now will be between 1.60 euro and 5 euro, so the savings in self built (not even counting the solder and electricity use) are minimal to non existent. So only do it for fun, educational purposes or if you need something tailored like i did.

I have a 3d file for a motorflange and housing for the commercial board avaiable.

That trusty Attiny13
Finally I have to praise the Attiny13 for being extremely resilient. Due to some stupidity from my side, I connected PB3 with O4 instead of I4, So it received 12 Volt through a 36 ohm resistor and also tried to drive a 330mA motor coil. This situation lasted for at least a week in which it would sometimes be steering the stepper for hours, with me trying to find out why it wasn’t working optimally.
I only found out when I accidentally felt the chip was getting pretty hot.
I fixed the connection and the chip was still working (and the motor turned better). A week of 12 Volt on one of its pins and getting hot and still working great