2022-11-12

Dehumidifier Hack

I hate lights in my room while I sleep and we’ve got a couple appliances that love to give some feedback that they are on.

Annoying lights

Although it’s not that bright, it still annoys the shit out of me so at some point I got the bright idea that I could probably swap out the leds with similar diode. I popped the top off and with the help of some solder wick and 2 1N4148 diodes, the design flaw was corrected.

Here’s the board with one of the swapped diodes and an original led. Mid swap

And with all leds swapped out. All swapped

Pure bliss. Finished

2022-09-30

The AVR Toolchain

One of the goals I had when I started getting into the digital side of electronics was to get a good grasp of the GNU toolchain and some of the avr tools. The arduino platform is nice but it doesn’t really give you a good grasp of what’s actually happening so here’s a primer from an attiny perspective and some of the common tools used to understand what’s in the final binary.

There’s already a great overview of the main avr toolchain), but this article provides some examples built against a simple binary to wrap your head around it all.

Tools

All of these examples are build with the attiny25 family in mind and uses a binary from a really dumb program in , loop.c:

int main(void) {
    int i = 0;
    while(i < 10000) {
        i++;
    }
}

This was compiled to the loop binary using avr-gcc -g -mmcu=avr25 -o loop loop.c

At a high level, avr-libc provides a standard c library that can be compiled with gcc. The tools here are mostly provided through binutils and you would normally use make to stitch it all together.

Objdump

This is the swiss army knife to picking apart object files. Typically used to take a look at the disassembly:

> avr-objdump -m avr25 -S loop

loop:     file format elf32-avr

Disassembly of section .text:

00000000 <main>:
int main(void) {
   0:	cf 93       	push	r28
   2:	df 93       	push	r29
   4:	00 d0       	rcall	.+0      	; 0x6 <L0^A>

00000006 <L0^A>:
   6:	cd b7       	in	r28, 0x3d	; 61
   8:	de b7       	in	r29, 0x3e	; 62

0000000a <.Loc.1>:
    int i = 0;
   a:	1a 82       	std	Y+2, r1	; 0x02
   c:	19 82       	std	Y+1, r1	; 0x01

0000000e <.Loc.2>:
    while(i < 10000) {
   e:	05 c0       	rjmp	.+10     	; 0x1a <.L2>

00000010 <.L3>:
        i++;
  10:	89 81       	ldd	r24, Y+1	; 0x01
  12:	9a 81       	ldd	r25, Y+2	; 0x02
  14:	01 96       	adiw	r24, 0x01	; 1
  16:	9a 83       	std	Y+2, r25	; 0x02
  18:	89 83       	std	Y+1, r24	; 0x01

0000001a <.L2>:
    while(i < 10000) {
  1a:	89 81       	ldd	r24, Y+1	; 0x01
  1c:	9a 81       	ldd	r25, Y+2	; 0x02
  1e:	80 31       	cpi	r24, 0x10	; 16
  20:	97 42       	sbci	r25, 0x27	; 39
  22:	b4 f3       	brlt	.-20     	; 0x10 <.L3>
  24:	80 e0       	ldi	r24, 0x00	; 0
  26:	90 e0       	ldi	r25, 0x00	; 0

00000028 <.Loc.5>:
    }
  28:	0f 90       	pop	r0
  2a:	0f 90       	pop	r0
  2c:	df 91       	pop	r29
  2e:	cf 91       	pop	r28
  30:	08 95       	ret

The -S flag attempts to intermix the original source code along with the assembly.

The output of this dump is a little confusing at first but let’s break it down. Looking at the counter increment code:

# This is the address in the elf file and the associated debugging symbol <.L3>
00000010 <.L3>:

# We're looking at the increment instruction here
        i++;

# 10 is the address, 89 81 is the actual data.
# objdump translates this to ldd	r24, Y+1 for an attiny.
  10:	89 81       	ldd	r24, Y+1	; 0x01
  12:	9a 81       	ldd	r25, Y+2	; 0x02
  14:	01 96       	adiw	r24, 0x01	; 1
  16:	9a 83       	std	Y+2, r25	; 0x02
  18:	89 83       	std	Y+1, r24	; 0x01

Hexdump

Probably not that useful but shows a raw view of the file.

1 byte per line dump:

> hexdump -v -e '1/1 "%02x " "\n"' loop
7f
45
4c
46
01
01
01
... a lot more is dumped

Od - Octal Dump

Way simpler to use than hexdump.

No address: -An and just text: -a.

> od -An -a loop
del   E   L   F soh soh soh nul nul nul nul nul nul nul nul nul
stx nul   S nul soh nul nul nul nul nul nul nul   4 nul nul nul
84  bs nul nul  em nul nul nul   4 nul  sp nul stx nul   ( nul
cr nul  ff nul soh nul nul nul   t nul nul nul nul nul nul nul
... once again, lots more dumped

Size

Easy way to see the sizes of each section.

> avr-size --format=SysV loop
loop  :
section          size      addr
.text              50         0
.data               0   8388704
.comment           36         0
.debug_aranges     32         0
.debug_info        85         0
.debug_abbrev      78         0
.debug_line        91         0
.debug_frame       52         0
.debug_str         95         0
Total             519

Objcopy

Elf files contain a bunch of unnecessary sections that would be a waste to store in our limited flash space on the target, like all the debugging sections and comments. This command is similar to strip but also encodes the file from elf to various formats.

Intel hex

Most microcontrollers code uploads use an ihex file format rather than an elf file.

Building ihex from an elf:

> avr-objcopy -j .text -j .data -O ihex loop loop.hex && cat loop.hex
:10000000CF93DF9300D0CDB7DEB71A82198205C037
:1000100089819A8101969A83898389819A81803125
:100020009742B4F380E090E00F900F90DF91CF9172
:02003000089531
:00000001FF

Binary

You can take a look at the raw binary if hex is annoying to look at. Not that this is really useful outside of educational purposes.

# first dump to a binary file
> avr-objcopy -O binary --only-section=.text main test.bin

# Now display the binary for one instruction per line, `xxd` can handle the conversion:
> xxd -b -c 2 test.bin
00000000: 00001110 11000000  ..
00000002: 00010101 11000000  ..
00000004: 00010100 11000000  ..
00000006: 00010011 11000000  ..
00000008: 00010010 11000000  ..
0000000a: 00010001 11000000  ..
0000000c: 00010000 11000000  ..
0000000e: 00001111 11000000  ..
00000010: 00001110 11000000  ..
00000012: 00001101 11000000  ..
00000014: 00001100 11000000  ..
00000016: 00001011 11000000  ..
00000018: 00001010 11000000  ..
0000001a: 00001001 11000000  ..
0000001c: 00001000 11000000  ..
0000001e: 00010001 00100100  .$
00000020: 00011111 10111110  ..
00000022: 11001111 11100101  ..
00000024: 11010010 11100000  ..
00000026: 11011110 10111111  ..
00000028: 11001101 10111111  ..
0000002a: 00000010 11010000  ..
0000002c: 00000100 11000000  ..
0000002e: 11101000 11001111  ..
00000030: 10010000 11100000  ..
00000032: 10000000 11100000  ..
00000034: 00001000 10010101  ..
00000036: 11111000 10010100  ..
00000038: 11111111 11001111  ..

AVR GCC

This is the main compiler I use when working with avr chips.

Supported architectures

Wiki

Standards

C has published new standard for the language over time. New standards bring new features or datatypes. For an example of one I like, c99 introduced boolean datatypes. There are a number of different standards, as of 2022 theres c89, gnu89, c94, c99, gnu99, c11, gnu11, c17, gnu17, c2x, and gnu2x. I’m not sure why each release has a GNU equivalent or the history behind that, but there are differences, mostly a couple keywords and macros.

Note use gnu* standards for arduino, I’ve seen some cases in the SpencerKonde ATtiny core where the asm keyword is used.

Linking

The avr-gcc compiler not only has many AVR ISAs compilation support, it also provides the linker with defaults. This means that the architecture flag -m needs to always be specified.

For AVRs, the interrupt table starts at 0x000. Dumping the table for an attiny85, compiled with -mmcu=attiny85:

> avr-objdump -S  -m avr25 loop
...
00000000 <__vectors>:
   0:	0e c0       	rjmp	.+28     	; 0x1e <__ctors_end>
   2:	15 c0       	rjmp	.+42     	; 0x2e <__bad_interrupt>
   4:	14 c0       	rjmp	.+40     	; 0x2e <__bad_interrupt>
   6:	13 c0       	rjmp	.+38     	; 0x2e <__bad_interrupt>
   8:	12 c0       	rjmp	.+36     	; 0x2e <__bad_interrupt>
   a:	11 c0       	rjmp	.+34     	; 0x2e <__bad_interrupt>
   c:	10 c0       	rjmp	.+32     	; 0x2e <__bad_interrupt>
   e:	0f c0       	rjmp	.+30     	; 0x2e <__bad_interrupt>
  10:	0e c0       	rjmp	.+28     	; 0x2e <__bad_interrupt>
  12:	0d c0       	rjmp	.+26     	; 0x2e <__bad_interrupt>
  14:	0c c0       	rjmp	.+24     	; 0x2e <__bad_interrupt>
  16:	0b c0       	rjmp	.+22     	; 0x2e <__bad_interrupt>
  18:	0a c0       	rjmp	.+20     	; 0x2e <__bad_interrupt>
  1a:	09 c0       	rjmp	.+18     	; 0x2e <__bad_interrupt>
  1c:	08 c0       	rjmp	.+16     	; 0x2e <__bad_interrupt>

Compare this to the Arduino Uno’s ATmega328p, compiled with -mmcu=atmega328p:

> avr-objdump -S  -m avr5 loop
...
00000000 <__vectors>:
   0:	0c 94 34 00 	jmp	0x68	; 0x68 <__ctors_end>
   4:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
   8:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
   c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  10:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  14:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  18:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  1c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  20:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  24:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  28:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  2c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  30:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  34:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  38:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  3c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  40:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  44:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  48:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  4c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  50:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  54:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  58:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  5c:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  60:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>
  64:	0c 94 3e 00 	jmp	0x7c	; 0x7c <__bad_interrupt>

Avrdude

Pretty slick tool to read and write memory on your avr chip.

The Arduino platform has a pretty good integration with these tools. I found it to be helpful to turn the ide verbosity up to read the avrdude commands that are run. There’s a config file that comes from whatever core you are using that might be useful.

The following examples are based on an attiny85 connected to a Sparkfun tinyIsp programmer plugged into a usb port.

Upload an ihex file:

# -U [memory(flash/eeprom)]:[w/r]:[input/output file]:[i stands for intel hex]
avrdude -v -c usbtiny -B8 -p attiny85 -U flash:w:hello.hex:i

Dump the flash:

avrdude -v -c usbtiny -B8 -p attiny85 -U flash:r:dump.hex:i
avr-objdump -sSd -m avr25 dump.hex

Terminal mode to dump part of eeprom:

avrdude -v -c usbtiny -B8 -p attiny85 -t
>>> dump eeprom 0 16

Reading | ################################################## | 100% 0.02s

0000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

Changing the clock speed from 8Mhz internal to 1Mhz internal. Note that the F_CPU macro in your code needs to match the fuse value. Use a fuse calculator for these. Setting the fuses:

avrdude -v -c usbtiny -B8 -p attiny85 -t
>>> dump lfuse

Reading | ################################################## | 100% 0.00s

0000  e2                                                |.               |

>>> write lfuse 0 0x62

Info: Writing 1 bytes starting from address 0x00

Writing | ################################################## | 100% 0.01s

>>> read lfuse

Reading | ################################################## | 100% 0.00s

0000  62                                                |b               |

2022-08-10

GCC Optimization Levels

Optimization levels are really weird.

Using the function and compiling for an attiny85, ie --mmcu=attiny85.

int main(void) {
    int i = 0;
    while(i < 10000) {
        i++;
    }
}

We can disassemble with avr-objdump -sS -m avr25 led. As a disclaimer, I’m not pretending I know what any of this means.

Using -Os ie optimize for code side, it looks like the entire function is optimized away!

> avr-objdump -sS -m avr25 led
...
00000030 <main>:
{
    int i = 0;
    while(i < 10000) {
        i++;
    }
}
  30:	90 e0       	ldi	r25, 0x00	; 0
  32:	80 e0       	ldi	r24, 0x00	; 0
  34:	08 95       	ret

And by comparison, using -O0, fastest compile time and the gcc default:

00000030 <main>:
// #include "wait.h"

int main(void) {
  30:	cf 93       	push	r28
  32:	df 93       	push	r29
  34:	00 d0       	rcall	.+0      	; 0x36 <L0^A>

00000036 <L0^A>:
  36:	cd b7       	in	r28, 0x3d	; 61
  38:	de b7       	in	r29, 0x3e	; 62

0000003a <.Loc.1>:
    int i = 0;
  3a:	1a 82       	std	Y+2, r1	; 0x02
  3c:	19 82       	std	Y+1, r1	; 0x01

0000003e <.Loc.2>:
    while(i < 10000) {
  3e:	05 c0       	rjmp	.+10     	; 0x4a <.L2>

00000040 <.L3>:
        i++;
  40:	89 81       	ldd	r24, Y+1	; 0x01
  42:	9a 81       	ldd	r25, Y+2	; 0x02
  44:	01 96       	adiw	r24, 0x01	; 1
  46:	9a 83       	std	Y+2, r25	; 0x02
  48:	89 83       	std	Y+1, r24	; 0x01

0000004a <.L2>:
    while(i < 10000) {
  4a:	89 81       	ldd	r24, Y+1	; 0x01
  4c:	9a 81       	ldd	r25, Y+2	; 0x02
  4e:	80 31       	cpi	r24, 0x10	; 16
  50:	97 42       	sbci	r25, 0x27	; 39
  52:	b4 f3       	brlt	.-20     	; 0x40 <.L3>
  54:	80 e0       	ldi	r24, 0x00	; 0
  56:	90 e0       	ldi	r25, 0x00	; 0

00000058 <.Loc.5>:
    }
}
  58:	0f 90       	pop	r0
  5a:	0f 90       	pop	r0
  5c:	df 91       	pop	r29
  5e:	cf 91       	pop	r28
  60:	08 95       	ret
00000030 <main>:
// #include "wait.h"

int main(void) {
  30:	cf 93       	push	r28
  32:	df 93       	push	r29
  34:	00 d0       	rcall	.+0      	; 0x36 <L0^A>

00000036 <L0^A>:
  36:	cd b7       	in	r28, 0x3d	; 61
  38:	de b7       	in	r29, 0x3e	; 62

0000003a <.Loc.1>:
    int i = 0;
  3a:	1a 82       	std	Y+2, r1	; 0x02
  3c:	19 82       	std	Y+1, r1	; 0x01

0000003e <.Loc.2>:
    while(i < 10000) {
  3e:	05 c0       	rjmp	.+10     	; 0x4a <.L2>

00000040 <.L3>:
        i++;
  40:	89 81       	ldd	r24, Y+1	; 0x01
  42:	9a 81       	ldd	r25, Y+2	; 0x02
  44:	01 96       	adiw	r24, 0x01	; 1
  46:	9a 83       	std	Y+2, r25	; 0x02
  48:	89 83       	std	Y+1, r24	; 0x01

0000004a <.L2>:
    while(i < 10000) {
  4a:	89 81       	ldd	r24, Y+1	; 0x01
  4c:	9a 81       	ldd	r25, Y+2	; 0x02
  4e:	80 31       	cpi	r24, 0x10	; 16
  50:	97 42       	sbci	r25, 0x27	; 39
  52:	b4 f3       	brlt	.-20     	; 0x40 <.L3>
  54:	80 e0       	ldi	r24, 0x00	; 0
  56:	90 e0       	ldi	r25, 0x00	; 0

00000058 <.Loc.5>:
    }
}
  58:	0f 90       	pop	r0
  5a:	0f 90       	pop	r0
  5c:	df 91       	pop	r29
  5e:	cf 91       	pop	r28
  60:	08 95       	ret

With -O1:

00000030 <main>:
// #include "wait.h"

int main(void) {
  30:	80 e1       	ldi	r24, 0x10	; 16
  32:	97 e2       	ldi	r25, 0x27	; 39

00000034 <.L2>:
    int i = 0;
    while(i < 10000) {
  34:	01 97       	sbiw	r24, 0x01	; 1

00000036 <.LVL2>:
  36:	f1 f7       	brne	.-4      	; 0x34 <.L2>

00000038 <.Loc.8>:
        i++;
    }
}
  38:	90 e0       	ldi	r25, 0x00	; 0
  3a:	80 e0       	ldi	r24, 0x00	; 0

0000003c <.LVL3>:
  3c:	08 95       	ret

And finally with -O2 and -O3 we are back to optimizing everything away:

00000030 <main>:
int main(void) {
    int i = 0;
    while(i < 10000) {
        i++;
    }
}
  30:	90 e0       	ldi	r25, 0x00	; 0
  32:	80 e0       	ldi	r24, 0x00	; 0
  34:	08 95       	ret

So I guess the TLDR here is try and test them all?

The case where the entire function is optimized away seems funny to me, what if you hand rolled a sleep with a big loop? Wouldn’t that be grand if it just disappeared?

2022-07-21

Thermometer

Why buy a $10 thermometer when you can spend $100 and build one yourself in 15-20 hours?

Thermometer

Caitlin kept saying the thermostat was off so I build this to find out. As a bonus, it also tells me my house is too humid so now I have to also figure that out. Fun.

I was stubborn and wanted to use an attiny85 regardless of whether it was a good idea or not. 5 pins isn’t enough to control the display so I used a shift register as an IO expander to talk to the 1602 led display using SPI.

Prototype

The temp/humidity sensor originally was an aht11 but apparently that unit is extremely sensitive to timing and I couldn’t quite get it right with the attiny. I switched to an AM2301B and that used i2c for communication and worked really well.

First try I soldered everything backwards but I eventually got it right.

Board

Board2

Board3

Power Up

The packing took forever and I ended up having to buy multiple project boxes to get the right size.

Packaging

Assembly

All done!

Final

2022-07-16

Bench Setup

Office

2022-07-15

7 Segment LED Counter

This was a funny project where I tried to build a simple 7 segment led counter without using a microcontroller. Each digit is controlled separately using 4026’s and a sub 15 ms clock controlling which led is displayed, targeting a digit using a mux. 15 ms was chosen because it’s faster than I could notice the counter changing, kinda like controlling led brightness using pwm. The counter ticks up based on a second slower clock or a button.

I isolated the clocks into separate modules using 555 timers, both of which took forever to solder. One was in a fixed astable mode, the other a toggle-able monostable button debouncer or a bi-stable latch.

Ran out of diodes on this one…

LED Counter

2022-07-10

Simple Oscillator

Oscillator

Probably the first circuit I tried to solder together. An astable oscillator that was so astable the timing was all over the place!