The first part of this week is mostly reading and understanding what's happening within the ATTinys. I'll go over what I got from the chapters of this quite long datasheet.
PORTAand
PORTB.
SPHand
SPLfor the stack pointer, and
SREGfor the global AVR status.
EEARHand
EEARLfor EEPROM addressing,
EEDRfor the EEPROM data to read/write,
EECRfor controlling the write/read access mode of EEPROM, and finally
GPIORx(x=0,1,2) for general-purpose input/output registers.
CKSELfor the type of clock to use,
SUTfor startup timing, and
CKDIV8for the initial state of the prescaler (frequency divided by 8 by default).
CKSELfor the type of clock to use,
OSCCALfor oscillator calibration, and
CLKPRfor the clock prescaler.
MCUCRfor controlling the type of sleep mode when using
SLEEP(), and
PRRfor further power reduction abilities, as well as
DIDR0for disabling digital input on analog pins.
MCUSRfor flags describing which source triggered the reset (watchdog, external, brown-out or power-on), and
WDTCSRfor watchdog timer control and status.
0x00(reset, by default) to
0x10.
MCUCRMCU control register with two last bits for mode of the external interruption on pin INT0 (level = low or any level, edge = on falling or rising edge), and
GIMSK,
GIFRand
PCMSK0/1for enabling some interruptions (beyond register
SREGfrom Chapter 4).
PORTA = (1 << PA1) | (1 << PA2); DDRA = (1 << DDA1);). Need for synchronization after switching direction (i.e. needs
_NOP()or something else before reading from port after direction change). List of alternate functions for ports including analog functions, interrupt functions, USI, and SPI on port A; crystal, interrupt, clock output, debug wire and reset on port B. Port diagrams separating PORTXn, DDRXn and PINXn for data write/read, mode, and reading/toggling value.
MCUCRwith global pull-up disable bit,
PORTA,
DDRAand
PINAand similar counterparts for B
fclk_ext < fclk_I/O/2.5.
TCCRnx(x=A,B,C and n=0 for 8-bits and n=1 for 16-bits registers) for the timer-counter controls (compare-match output modes, and waveform generation modes);
TCNTnfor direct read/write access to the timer counter;
OCRnxfor the value to compare and match (for interruptions and/or waveform generation);
TIMSKnfor timer interruption masks, and
TIFRnfor interruption flags / states. Finally,
GTCCRprovide synchronization bits for the counter/timer and their prescaler.
USICRfor enabling interrupts, setting both wire and clock modes,
USISRfor flags, line status and counter value,
USIDRwhere the transfer data is stored / received, and
USIBRas read buffer.
ACSRfor control and status to enable, select modes of the comparator, and read interrupt flags,
ADCSRBfor using a specific pin of the ADC instead of AIN1, and
DIDR0to disable digital input buffers for AIN1/0 and thus reduce power consumption if not needing digital input functions.
ADC=1024 VIN/VREFgoing from GND to Vref, Unipolar Differential Conversion
ADC=1024 × GAIN × (VPOS - VNEG) / VREFwith GAIN either 1 or 20 (36dB), or Bipolar Differential Comparison
ADC=512 × GAIN × (VPOS - VNEG) / VREF. Finally, ADC can be used to read temperature using an on-chip temperature sensor using ADC8 channel.
ADMUXfor selecting the voltage reference, the combination of analog inputs, and the gain to use;
ADCSRAfor enabling the ADC, starting conversions, enabling auto-trigger as well as interruptions and the corresponding flag, and selecting the ADC clock prescaler;
ADCL/Hcontaining the 10-bits conversion result;
ADCSRBfor selecting the auto-trigger source, result alignment and bipolar input mode (defaults to unipolar); and
DIDR0for disabling digital input on ADC-able pins.
SELFPRGENbit for programming; Fuse High Byte as
RSTDISBL,DWEN,SPIEN,WDTON,EESAVE,BODLEVEL1/2/3for disabling external reset, enabling debugwire, enabling program download, setting whether watchdog timer is always on, whether EEPROM stay beyond programming, and the Brown-Out level; Fuse Low Byte as
CKDIV8,CKOUT,SUT1,SUT0,CKSEL3/2/1/0for dividing the clock frequency by 8, enabling clock output pin, selecting the startup timing, as well as the type of clock. Finally, the Device Signature Byte can be used to figure out which device we're dealing with (ATTIny 24/44/84).
I first decided to test led and switches on my first board. Finding the correct constants is obvious given the datasheet (although it looked all completely mysterious before I had read it). In this case, I am using the two pins on the left side before the last at the bottom, i.e. PB2 for the led and PA7 for the switch.
The output corresponds to setting
DDRB = 1 << PB2; and then outputting the desired voltage bit (low = 0, or high = 1) with
PORTB = 1 << PB2;.
The input from the switch is connected to ground when not pressing, and disconnected when pressing (counterintuitive to me).
This means that we want to use a pullup resistor, which we activate with
PORTA = 1 << PA7; (without changing DDRA since default is input).
Now, whenever we press the button, the reading from
PINA will be 0, and by default it will be
1 << PA7.
I tested multiple variants (modes) of connecting the switch and the led:
#include <avr/io.h> #include <util/delay.h> #include <avr/pgmspace.h> typedef enum { ON_OFF, STATEFUL, BLINKING, CHANGING, PULSING } SwitchMode; int main(void) { // // main // static unsigned char switch_curr, switch_last; static unsigned char state; static unsigned char counter; static unsigned char i; SwitchMode mode = PULSING; // // set clock divider to /1 // CLKPR = (1 << CLKPCE); CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0); // // initialize output pins // PORTA = (1 << PA7); // enable pull-up for switch input DDRA = 0; // all inputs on port A PORTB = (1 << PB2); // enable output for led DDRB = (1 << PB2); // synchronize _delay_ms(1); // initial state switch_curr = switch_last = PINA & (1 << PA7); state = switch_curr; // // main loop // while (1) { // get switch state switch_curr = PINA & (1 << PA7); switch(mode){ case ON_OFF: // use switch to decide whether to have led on or off state = switch_curr; break; case BLINKING: // just toggle state state = !state; _delay_ms(100); break; case CHANGING: // toggle but change the delay from counter state = !state; if(counter >= 100) counter = 0; else counter += 1; for(i = 0; i < counter; ++i) _delay_ms(1); break; case PULSING: // toggle but change the delay from counter state = !state; // change off-timing if(state){ if(counter > 200) counter = 0; else counter += 5; for(i = 0; i < counter; ++i) _delay_us(100); } break; case STATEFUL: default: // use change of state to invert led state if(switch_curr != switch_last && !!switch_last) state = !state; break; } switch_last = switch_curr; // led output if(state) PORTB = 1 << PB2; // PORTB |= (1 << PB2); // led on else PORTB = 0; // &= ~(1 << PB2); // led off // wait a bit if(mode == PULSING) _delay_ms(1); else _delay_ms(1); } }
The recitation session over USB programming was very interesting and triggered my interest. I thus looked for tutorials beyond what Mike Specter had talked about and found the long detailed work of Andrew Mao who used an ATMega16U2 with Hardware USB (for easy programming over USB) to program his board to be a basic keyboard. One idea that went through my head was to play pong with the mouse, and potentially use the keyboard for extra inputs and/or outputting results and help information. This is beyond the class, but an interesting project nonetheless. I only went up to verifying that I can have the USB board run with hardware USB enabled, proving that something can be done.
For the board, I decided to use similar components to the ones of Andrew as a first attempt. The one component we do not have in stock is the 16 MHz crystal. At first, I asked Gavin and eventually we found out that there were extra crystals in the EDS stock, some of which were 16 MHz ones. I looked for the model on digikey and found the reference PLE SRMP49 which corresponds to a 16MHz crystal in series resonance. At first, I was slightly suprised since all crystals I had seen explanations for had a load capacitance that had to be matched "in parallel", and had made my design following this assumption (similarly to Andrew and Mike). Seemingly, multiple crystal circuit types exists including the more common "parallel resonance" and this less common "series resonance" that does not require any extra capacitor. However, at the same time, one of my colleague who I had asked for a 16 MHz crystal answered that he had a defective Arduino Uno I could harvest the crystal from. As a first attempt, I am going with the Arduino one since it's proven to work.
The second difference was the LED which, although very similar, looks slightly different. I found the reference CLV1A-FKB for the part in our stock and it seems that the orientation is slightly different. I ended up with the wrong orientation initially when populating the components, but easily swapped the corresponding resistor for the red led as I understood the correct orientation. This was my first personal usage of the heated tweezer, and it was quite fun and easy!For the Eagle schematics, I wanted to use the USB pad, which was supposed to be in an extra SparkFun library. Eagle has a way to download these extra libraries and includes the "SparkFun" component library, or ... so I thought. Unfortunately, the library it downloads is actually not up to date. I had to download the one from the github repository to find the correct USB pad component in SparkFun-Connectors.lbr. At this point, I traced and cut the PCB. No major issue.
The soldering was less tricky than I thought. As suggested on some tutorials, I positioned the ATMega part first and then taped it to ensure I could more precisely align it before fully fixing it and soldering its pads. I eventually had solder connect multiple pads as expected and I tried to use the wick without any result again... I still can't use it propertly! However, this time I tried the slurping hot desoldering gun next to the tweezer and this is now my favorite desoldering tool. It does exactly what I want (and even slightly more intensely than I'd like it to, but removing too much is fine since adding solder back is much easier). I then tried to test the board by plugging it straight into a computer. Most computers I tried would trigger the green led showing that power was available, but they would not detect it as it should have (as DFU device). Debugging time!
The first check was whether anything was shorted. Nothing seemingly was.
The second check was whether the power was distributed correctly and it seemed to be (5V between VCC and GND).
Then, I decided to check the clock signal (if it was possible to see it in action) using an oscilloscope. I didn't know whether the crystal was working since it could well have been the reason the Arduino I took it from was not working. That's where I saw that ... I saw nothing. I then probed the capacitors with the multimeter to check that my stacked 10 pF capacitors were summing correctly to 20 pF. As I was told later, measuring the capacitance cannot be done with a multimeter unless you extract the component since the remaining parts of the circuits influence the measured capacitance. However, this did show me the first problem which was that my capacitors were several of magnitude wrong. Indeed, I had picked 10 uF ones instead of the picofarad ones I should have!
I removed the stacked capacitors next to the crystal using the hot tweezer, and then checked again with the correct ones. The signal was now as expected! The only unexpected part was that the voltage to ground was only ~1.3V. This could have been an issue with power, but as it turns out, this is what I also measured on another Arduino UNO, so I assume this is how they work.
At this point, it was sadly not yet recognized as DFU device and I suspected that the data pins were not connecting. I decided to create a small USB pad extension (quickest milling done so far!). For this board, I designed it the simplest way with Gimp and using the trace of the previous board as reference for the pad size. The connection was done using a 4-pin header which I soldered on both sides.
At this point, the board shows up as DFU device! The rest is for another time.