Scott Zitek - Fab Academy 2014

Input devices

Week 10 Assignment

The assignment for this week is measure something: add a sensor to a microcontroller board that you've designed and read it. There are hello world example circuits to measure magnetic fields, temperature, light, sound, acceleration, orientation, vibration and more. I chose to build the transmit-receive version of the step response circuit. Variations of this circuit can be used to measure resistance, capacitance, inductance, position, pressure, proximity, tilt, acceleration, humidity, touchpad, multitouch, etc.


Making the circuit board

For a circuit that can measure so many things, the transmit-receive step response circuit appeared surprisingly simple. The hello.txrx.45 board uses an ATtiny45 8-pin microcontroller, 3 resistors, 1 capacitor, and some connectors. It doesn't get much simpler than that.

  1. Since I was redesigning the circuit board, I wanted to add at least one new feature. However, the ATtiny45 microcontroller has only 8 pins so it has a really limited number of I/O pins and all were in use. I remembered Neil telling us that the pins used for the In System Programmer (ISP) could be used for other purposes when the ISP was not connected.

  2. I noticed that ATtiny45 pins PB0 and PB1 were only used for In System Programmer (ISP) MISO and MOSI connections. I read through the data sheet for the ATtiny45 and decided to try to connect status indicator LEDs to PB0 and PB1.
  3. The example hello_txrx_45.png circuit.


  4. I used the freeware version of EagleCAD to draw up a schematic for the transmit-receive step response circuit and added current limiting resistors and LEDs for the two status indicator outputs. This helped me better understand the overall circuit by breaking it down into the different sub-parts of the circuit.
  5. EagleCAD schematic of transmit-receive step response circuit with two status indicator LEDs.


  6. I then used EagleCAD to layout the circuit board and route the traces.
  7. EagleCAD schematic of transmit-receive step response circuit with two status indicator LEDs.


  8. Once again, I started with a lot of trial and error. The autoroute feature doesn’t seem to help much at all. So I narrowed it down to the following approach:

  9. It didn't take me very long to solder the circuit. I am definitely getting faster with practice. I only had to watch the orientation of the ATtiny (pin 1) and the LEDs (polarity).

  10. Programming

    1. Using the ATtiny45 data sheet and referencing the internet, I "reverse engineered" the example C program called "hello.txrx.45.c". As I did so, Neil's quick explanation during lecture of how the program worked started to make sense.

    2. I added additional documentation to the program and added code to control the two LED status indicators.
      //
      // hello.txrx.led.45.c
      //
      // step response transmit-receive hello-world
      //    9600 baud FTDI interface
      //
      // Neil Gershenfeld
      // 11/6/11
      //
      // (c) Massachusetts Institute of Technology 2011
      // Permission granted for experimental and personal use;
      // license for commercial sale available from MIT.
      //
      // Modified by Scott Zitek 4/5/2014
      // to add more comments and two status indicator LEDs
      
      #include < avr/io.h >
      #include < util/delay.h >
      
      #define output(directions,pin) (directions |= pin) // set port direction for output
      #define set(port,pin) (port |= pin) // set port pin
      #define clear(port,pin) (port &= (~pin)) // clear port pin
      #define pin_test(pins,pin) (pins & pin) // test for port pin
      #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
      #define bit_delay_time 100 // bit delay for 9600 with overhead
      #define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
      #define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
      #define settle_delay() _delay_us(100) // settle delay
      #define char_delay() _delay_ms(10) // char delay
      #define nloop 100 // loops to accumulate
      
      #define serial_port PORTB
      #define serial_direction DDRB
      #define serial_pin_out (1 << PB2)
      #define transmit_port PORTB
      #define transmit_direction DDRB
      #define transmit_pin (1 << PB4)
      
      //added to configure LED connected to ATtiny45 pin PB0
      #define led1_port PORTB	
      #define led1_direction DDRB
      #define led1_pin (1 << PB0)
      
      //added to configure LED connected to ATtiny45 pin PB1
      #define led2_port PORTB
      #define led2_direction DDRB
      #define led2_pin (1 << PB1)
      
      void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
         //
         // send character in txchar on port pin
         //    assumes line driver (inverts bits)
         //
         // start bit
         //
         clear(*port,pin);
         bit_delay();
         //
         // unrolled loop to write data bits
         //
         if bit_test(txchar,0)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,1)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,2)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,3)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,4)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,5)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,6)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         if bit_test(txchar,7)
            set(*port,pin);
         else
            clear(*port,pin);
         bit_delay();
         //
         // stop bit
         //
         set(*port,pin);
         bit_delay();
         //
         // char delay
         //
         bit_delay();
         }
      
      int main(void) {
         //
         // main
         //
         static unsigned char count;
         static uint16_t up,down;
      
         // Added to create variable to store the result of up value minus down value
         
         static uint16_t value;
      
         //
         // set clock divider to /1
         //
         CLKPR = (1 << CLKPCE);
         CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
         //
         // initialize output pins
         //
         set(serial_port, serial_pin_out);
         output(serial_direction, serial_pin_out);
         clear(transmit_port, transmit_pin);
         output(transmit_direction, transmit_pin);
         
         // Initialize LED1 status indicator
         clear(led1_port, led1_pin);
         output(led1_direction, led1_pin);
      
         // Initialize LED2 status indicator
         clear(led2_port, led2_pin);
         output(led2_direction, led2_pin);
      
         //
         // init A/D
         //
      
         // Data sheet Pg134-135  ADMUX is the ADC Multiplexer Selection Register.
         // REFS2=0, REFS1=0,and REFS0=0 sets "Vcc used as voltage...... 
         // reference, disconnected from PB0 (AREF)"
         // ALDAR=0 sets ADC presentation for right adjust - see data sheet pg137 
         // MUX3=0, MUX2=0, MUX1=1, and MUX0=1 - this sets input channel
         // for Single ended-input ADC3 (PB3). Positive Differential 
         // input, Negative Differential input, and gain N/A.
         // ADCSRA is the ADC control and status register A
         // see data sheet pg136. prescaler select bits 1,1,1 set ADC prescaler for 
         // division factor 128. 
         // simplifies by basically truncating 10-bit ADC to 8-bit ADC result.
      
         ADMUX = (0 << REFS2) | (0 << REFS1) | (0 << REFS0) // Vcc ref
            | (0 << ADLAR) // right adjust
            | (0 << MUX3) | (0 << MUX2) | (1 << MUX1) | (1 << MUX0); // PB3
         ADCSRA = (1 << ADEN) // enable
            | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
         //
         // main loop
         //
         while (1) {
            //
            // accumulate
            //
            up = 0;
            down = 0;
            for (count = 0; count < nloop; ++count) { 
               //
               // settle, charge
               //
               settle_delay();
               set(transmit_port, transmit_pin);
               //
               // initiate conversion
               //
               ADCSRA |= (1 << ADSC);
               //
               // wait for completion
               //
               while (ADCSRA & (1 << ADSC))
                  ;
               //
               // save result
               //
               up += ADC;
               //
               // settle, discharge
               //
               settle_delay();
               clear(transmit_port, transmit_pin);
               //
               // initiate conversion
               //
               ADCSRA |= (1 << ADSC);
               //
               // wait for completion
               //
               while (ADCSRA & (1 << ADSC))
                  ;
               //
               // save result
               //
               down += ADC;
               }
      //
      // Control LED status indicators
      //
      // Subtract "down" value from "up" value and store the result in "value"
      // "value" is the number displayed by the example Python program.
      
      value = up - down;
      
      // Energize LED1 when the value is above a predefined value (e.g. 5000)
      
      if (value > 5000){
         set(led1_port, led1_pin); // Execute if condition is met
      } else{
         clear(led1_port, led1_pin); // Execute if condition is not met
      }
      
      // Energize LED2 when the value is above a predefined value (e.g. 7000)
      
      if (value > 7000){
         set(led2_port, led2_pin); // Execute if condition is met
      } else{
         clear(led2_port, led2_pin); // Execute if condition is not met
      }
      
            //
            // send framing
            //
            put_char(&serial_port, serial_pin_out, 1);
            char_delay();
            put_char(&serial_port, serial_pin_out, 2);
            char_delay();
            put_char(&serial_port, serial_pin_out, 3);
            char_delay();
            put_char(&serial_port, serial_pin_out, 4);
            //
            // send result
            //
      
            // "up" and "down" are both unsigned 16-bit integers.
            // so we must send upper and lower bytes of each as separate characters.
      
            put_char(&serial_port, serial_pin_out, (up & 255));
            char_delay();
            put_char(&serial_port, serial_pin_out, ((up >> 8) & 255));
            char_delay();
            put_char(&serial_port, serial_pin_out, (down & 255));
            char_delay();
            put_char(&serial_port, serial_pin_out, ((down >> 8) & 255));
            char_delay();
            }
         }
      
      
    3. Using avr-gcc in Ubuntu, I compiled the c program to hex
               sudo make -f hello.txrx.led.45.make
      
    4. I used avrdude and an ISP to write the program to the ATtiny45. NOTE: I did not perform the typical step of writing the fuse settings to the ATtiny because there is no external crystal the default settings should work.
               sudo make -f hello.txrx.45.led.make program-avrisp2
      


    Testing

    1. I soldered copper foil to the end of the circuit's transmit wire and another piece of copper foil to the circuit's receive wire.

    2. I used the example Python program and the FTDI USB to serial cable to communicated with the circuit and display the analog information.
               python hello.txrx.45.py /dev/ttyUSB0
      

      Completed transmit-receive step response circuit with both status indicator LEDs illuminated and Python readout program running in the background.



    3. I experimented to see what affected the value from the circuit.
      • Size of foil pieces
      • Distance between foil pieces
      • Proximity of objects to foil.
      • Material properties of object near foil
      • Etc.

    4. I set up a little experiment to see if I could detect the difference between mill and water. I put the transmit and receive foil pieces on the outside of a plastic cup and documented the readout for different quantities of each liquid.
    5. Plastic cup with transmit and receive foil pieces attached.


    6. A video of the circuit sensing the water level and controlling the status LEDs can be seen here. Notice that the first LED illuminates when the water level reaches the red line on the cup. The second LED illuminates when the water level reaches the blue line on the cup.

    7. I could not detect a significant difference between the milk and water but the results indicated that there was a relationship between liquid level and the readout value.
    8. Results of testing detection of milk and water at various levels. Notice, it did not detect changes well once liquid level was above foil.


    9. I also tested the circuit for measuring pressure. I placed a piece of soft 1.5 inch thick foam between the copper foil pieces connected to the transmit and receive wires. When I pressed down on the TX-foil/foam/Rx-foil sandwich using a cup, the circuit readout value increased proportionally. I was able to use one LED to indicate "medium" pressure and the other LED to indicate "high" pressure.
    10. Testing a pressure sensor based upon the step response circuit.


  11. A video of the circuit sensing the pressure and controlling the status LEDs can be seen here

  12. Files


    Back to index