Input Devices

The assignments for this week:

For this week, our instructor, Saheen suggested to use the accelerometer, ADXL345 as an input device for my final project. One of the main functions, for the reminder band would be to detect whether the bottle has been lifted up by the user to drink water. Here, I interface the ADXL345 accelerometer with the ATtiny1614 microcontroller.

ADXL345 Digital Acccelerometer

An accelerometer is a device that detects and measures both stationary (such as gravity) and moving (such as motion or vibration) accelerations. It gauges proper acceleration, which is the acceleration relative to freefall and what individuals and objects experience. For instance, when an accelerometer is stationary on the Earth's surface, it registers an acceleration due to gravity, which is directed upwards at approximately 9.81 m/s^2. Conversely, in free fall, where an object is descending towards the Earth's center at a rate of about 9.81 m/s^2, the accelerometer reads zero.
Conceptually, an accelerometer behaves as a damped mass on a spring. When the accelerometer experiences an acceleration, the mass is displaced to the point that the spring is able to accelerate the mass at the same rate as the casing. The displacement is then measured to give the acceleration.




There are many different ways to make an accelerometer.Some accelerometers use the piezoelectric effect-they contain microscopic crystal structures that get stressed by accelerative forces, which causes a voltage to be generated.



Another way to do it is by sensing changes in capacitance. If you have two microstructures next to each other, they have a certain capacitance between them. If an accelerative force moves one of the structures, then the capacitance will change. When we add a circuitry, it can convert from capacitance to voltage, and it will be an accelerometer.
The ADXL345 is a 3-axis accelerometer developed by Analog Devices. This sensor is designed to accurately measure acceleration in the X, Y, and Z axes, making it suitable for various applications. It is particularly popular in the fields of robotics, gaming, virtual reality, wearables, and IoT devices, among others.This accelerometer is capable of detecting both static acceleration, such as the force of gravity, and dynamic acceleration resulting from motion or vibrations. The ADXL345 has adjustable sensitivity, enabling it to cater to a range of applications with different acceleration requirements. The ADXL345 communicates with microcontrollers using either I2C or SPI interfaces, which makes it compatible with a wide range of development platforms, such as Arduino, Raspberry Pi Pico, and others.


Specifications of ADXL345

The ADXL345 is a triple-axis accelerometer with the following key specifications:
  • Axes: 3-axis (X, Y, Z)
  • Sensitivity: User-selectable, ±2g, ±4g, ±8g, or ±16g
  • Resolution: 10-bit or 13-bit, user-selectable
  • Output data rate (ODR): 0.10 Hz to 3200 Hz
  • Communication interfaces: I2C (up to 400 kHz) and SPI (4-wire and 3-wire, up to 5 MHz)
  • Supply voltage range: 2.0 V to 3.6 V
  • Current consumption: 23 µA to 130 µA, depending on output data rate and measurement mode
  • Operating temperature range: -40°C to +85°C
  • Package: 3 mm × 5 mm × 1 mm LGA
  • Pinout of ADXL345



  • GND: Ground connection for the module.
  • VCC: Power supply input, typically 3.3V, but it can accept a voltage range from 2.0V to 3.6V
  • CS (Chip Select): Used for SPI communication. When pulled low, it enables SPI communication; when pulled high, it switches to I2C communication.
  • INT1 (Interrupt 1): This pin can be configured to output interrupt signals for certain events like activity/inactivity detection, single/double-tap detection, or free-fall detection.
  • INT2 (Interrupt 2): This pin can be configured to output interrupt signals for certain events like activity/inactivity detection, single/double-tap detection, or free-fall detection.
  • SDO (Serial Data Out): Master In Slave Out (MISO) line for SPI communication.
  • SDA (Serial Data): Serial Data (I2C)/Serial Data Input (SPI 4-Wire)/Serial Data Input and Output (SPI 3-Wire).
  • SCL (Serial Clock): Serial Communications Clock. SCL is the clock for I2C, and SCLK is the clock for SPI.
  • ATtiny 1614



    Using the ATtiny1614 microcontroller with the ADXL345 accelerometer is a practical and efficient choice for a variety of reasons:
  • Sufficient Processing Power and Memory: The ATtiny1614 has a 16-bit AVR CPU with sufficient processing power to handle data from the ADXL345 accelerometer. It also has 16 KB of flash memory, 2 KB of SRAM, and 128 bytes of EEPROM, which are adequate for most sensor data processing and storage needs.
  • Low Power Consumption: Both the ATtiny1614 and ADXL345 are designed for low power consumption, making them suitable for battery-powered applications. The ATtiny1614 features various power-saving modes, and the ADXL345 can be configured to operate in a low-power mode as well.
  • Compact Size: The small form factor of the ATtiny1614 allows for compact designs, which is essential for portable or space-constrained projects. The ADXL345 is also available in a compact package, complementing the overall small footprint of the design.
  • Cost-Effective: Both the ATtiny1614 and ADXL345 are relatively inexpensive, making this combination a cost-effective solution for projects that require motion sensing.
  • Communication Protocols: The ADXL345 supports I2C and SPI communication protocols. The ATtiny1614 is capable of handling both I2C and SPI, allowing for flexible and reliable data communication between the accelerometer and the microcontroller. The ADXL345 accelerometer uses the I2C (Inter-Integrated Circuit) protocol for communication with microcontrollers. I2C is a synchronous, multi-master, multi-slave, packet-switched, single-ended, serial communication bus. It uses two lines: SDA (Serial Data Line): For sending and receiving data. SCL (Serial Clock Line): For synchronizing the data transfer. I2C allows multiple devices to be connected to the same bus, making it ideal for interfacing sensors like the ADXL345. Each device on the I2C bus has a unique address, and the master (ATtiny1614 in this case) can communicate with any device by addressing it directly.
  • Sufficient I/O Pins: The ATtiny1614 provides enough I/O pins to interface with the ADXL345 and other peripherals. This includes pins for power, ground, communication (SDA and SCL for I2C or MOSI, MISO, SCK, and CS for SPI), and possibly additional sensors or actuators.
  • Ease of Programming and Debugging: The ATtiny1614 supports UPDI (Unified Program and Debug Interface), making it straightforward to program and debug using modern tools. This simplifies the development process and helps in quickly iterating on the design.
  • Interfacing ADXL345 and ATtiny1614

  • Connect the VCC and GND pins of both the ATtiny1614 and the ADXL345 to the power supply and ground, respectively.
  • Connect the SDA (data) and SCL (clock) pins of the ATtiny1614 to the corresponding SDA and SCL pins of the ADXL345. These pins are used for I2C communication between the two devices.
  • Connect the INT1 and INT2 pins of the ADXL345 accelerometer to the ATtiny1614 pins(PA2 and PA3 respectively).These pins are interrupt outputs from the ADXL345, which can be configured to trigger an interrupt signal to the microcontroller under certain conditions. In the reminder band for the final project,it prompts the user to drink water at appropriate intervals based on their activity levels.


  • There was no footprint library available for the 1x 08 socket which is to be used for the accelerometer. These are the steps I followed to add a library:

    Using the Quentorres board

  • I am using the XIAO RP2040 of the Quentorres board to program the ATtiny1614 microcontroller using the Arduino IDE
  • To communicate with the ATtiny1614 and ADXL345, enabling data exchange and control commands, we can use the XIAO's UART (Serial) interface.


  • Schematic



    PCB design

    Once it is imported to KiCad editor, the components need to be arranged such that the connections don't cross each other.

    PCB milling

    Using the traces mentioned above, I milled the PCB in the Modela using the same procedures mentioned in Week 4 - Electronics Production week




    Soldering

    I collected the required components from the fablab inventory.

    Here again, soldering was done using the same procedures followed in Week 4 - Electronics Production week

    Connecting the boards

    The next task involves linking the new board to the Quentorres and subsequently integrating it into the system. Since Quentorres lacks a UPDI pin, we're utilizing an FTDI to UPDI converter available at our FabLab as an intermediary.



    Programming

    To blink the LED in the board
    
        /// ATtiny1614 Blink LED Example
    
        // Define the pin connected to the LED
        #define LED_PIN 8
        
        void setup() {
          // Initialize the LED pin as an output
          pinMode(LED_PIN, OUTPUT);
        }
        
        void loop() {
          // Turn the LED on
          digitalWrite(LED_PIN, HIGH);
          
          // Wait for 500 milliseconds (0.5 seconds)
          delay(500);
          
          // Turn the LED off
          digitalWrite(LED_PIN, LOW);
          
          // Wait for another 500 milliseconds
          delay(500);
        }
        
    This program is from the example for sensortest in Arduino IDE.
          #include  // Include the Wire library for I2C communication
            #include  // Include the Adafruit Sensor library
            #include  // Include the Adafruit ADXL345 library
            
            // Assign a unique ID to this sensor
            Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
            
            // Function to display sensor details
            void displaySensorDetails(void) {
              sensor_t sensor;
              accel.getSensor(&sensor);
              Serial.println("------------------------------------");
              Serial.print("Sensor:       "); Serial.println(sensor.name);
              Serial.print("Driver Ver:   "); Serial.println(sensor.version);
              Serial.print("Unique ID:    "); Serial.println(sensor.sensor_id);
              Serial.print("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" m/s^2");
              Serial.print("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" m/s^2");
              Serial.print("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" m/s^2");  
              Serial.println("------------------------------------");
              Serial.println("");
              delay(500);
            }
            
            // Function to display the data rate of the accelerometer
            void displayDataRate(void) {
              Serial.print("Data Rate:    "); 
              
              // Print the current data rate
              switch(accel.getDataRate()) {
                case ADXL345_DATARATE_3200_HZ:
                  Serial.print("3200 "); 
                  break;
                case ADXL345_DATARATE_1600_HZ:
                  Serial.print("1600 "); 
                  break;
                case ADXL345_DATARATE_800_HZ:
                  Serial.print("800 "); 
                  break;
                case ADXL345_DATARATE_400_HZ:
                  Serial.print("400 "); 
                  break;
                case ADXL345_DATARATE_200_HZ:
                  Serial.print("200 "); 
                  break;
                case ADXL345_DATARATE_100_HZ:
                  Serial.print("100 "); 
                  break;
                case ADXL345_DATARATE_50_HZ:
                  Serial.print("50 "); 
                  break;
                case ADXL345_DATARATE_25_HZ:
                  Serial.print("25 "); 
                  break;
                case ADXL345_DATARATE_12_5_HZ:
                  Serial.print("12.5 "); 
                  break;
                case ADXL345_DATARATE_6_25HZ:
                  Serial.print("6.25 "); 
                  break;
                case ADXL345_DATARATE_3_13_HZ:
                  Serial.print("3.13 "); 
                  break;
                case ADXL345_DATARATE_1_56_HZ:
                  Serial.print("1.56 "); 
                  break;
                case ADXL345_DATARATE_0_78_HZ:
                  Serial.print("0.78 "); 
                  break;
                case ADXL345_DATARATE_0_39_HZ:
                  Serial.print("0.39 "); 
                  break;
                case ADXL345_DATARATE_0_20_HZ:
                  Serial.print("0.20 "); 
                  break;
                case ADXL345_DATARATE_0_10_HZ:
                  Serial.print("0.10 "); 
                  break;
                default:
                  Serial.print("???? "); 
                  break;
              }  
              Serial.println(" Hz");  
            }
            
            // Function to display the range of the accelerometer
            void displayRange(void) {
              Serial.print("Range:         +/- "); 
              
              // Print the current range
              switch(accel.getRange()) {
                case ADXL345_RANGE_16_G:
                  Serial.print("16 "); 
                  break;
                case ADXL345_RANGE_8_G:
                  Serial.print("8 "); 
                  break;
                case ADXL345_RANGE_4_G:
                  Serial.print("4 "); 
                  break;
                case ADXL345_RANGE_2_G:
                  Serial.print("2 "); 
                  break;
                default:
                  Serial.print("?? "); 
                  break;
              }  
              Serial.println(" g");  
            }
            
            // Setup function runs once when the microcontroller starts
            void setup(void) {
              #ifndef ESP8266
                while (!Serial); // Wait for serial port to connect (for Leonardo/Micro/Zero)
              #endif
              Serial.begin(9600); // Initialize serial communication at 9600 baud rate
              Serial.println("Accelerometer Test"); Serial.println("");
              
              // Initialize the sensor
              if(!accel.begin()) {
                // If there's a problem detecting the ADXL345, check the connections
                Serial.println("Ooops, no ADXL345 detected ... Check your wiring!");
                while(1);
              }
            
              // Set the range to whatever is appropriate for your project
              accel.setRange(ADXL345_RANGE_16_G);
              // accel.setRange(ADXL345_RANGE_8_G);
              // accel.setRange(ADXL345_RANGE_4_G);
              // accel.setRange(ADXL345_RANGE_2_G);
              
              // Display some basic information on this sensor
              displaySensorDetails();
              
              // Display additional settings (outside the scope of sensor_t)
              displayDataRate();
              displayRange();
              Serial.println("");
            }
            
            // Loop function runs continuously after setup
            void loop(void) {
              // Get a new sensor event
              sensors_event_t event; 
              accel.getEvent(&event);
             
              // Display the results (acceleration is measured in m/s^2)
              Serial.print("X: "); Serial.print(event.acceleration.x); Serial.print("  ");
              Serial.print("Y: "); Serial.print(event.acceleration.y); Serial.print("  ");
              Serial.print("Z: "); Serial.print(event.acceleration.z); Serial.print("  ");Serial.println("m/s^2 ");
              delay(500); // Wait for 500 milliseconds before the next loop iteration
            }
            
          

    Testing the accelerometer on a bottle

    The 'z' values change indicating that the bottle has been lifted up to drink water.

    Testing step response


    From Neil's input class Water has a high dielectric constant (about 80), compared to air (about 1). This means the presence of water between the plates significantly increases the capacitance.
  • The copper strips placed on the bottle act as the conductive plates of a capacitor.
  • As the water level changes, the amount of water (dielectric) between the copper strips changes, affecting the capacitance. When the water level rises, more water (with a high dielectric constant) is between the strips, increasing the capacitance. When the water level falls, less water is present, decreasing the capacitance.
  • The code I used for testing this from Neil's notes for input week is here. This is the code used from Neil's notes that i wanted to use :
          //
    // hello.txrx.t1624.ino
    //
    // ATtiny1624 step response
    //
    // Neil Gershenfeld 11/14/21
    //
    // This work may be reproduced, modified, distributed,
    // performed, and displayed for any purpose, but must
    // acknowledge this project. Copyright is retained and
    // must be preserved. The work is provided as is; no
    // warranty is provided, and users accept all liability.
    //
    
    #define rxpin PIN_PA4 // receive pin
    #define txpin PIN_PA5 // transmit pin
    #define settle 100 // settle time
    #define samples 100 // number of samples to accumulate
    
    void setup() {
       Serial.begin(115200); // start serial
       pinMode(txpin,OUTPUT); // set transmit pin to output
       analogSampleDuration(5); // speed up ADC sampling
       analogReadResolution(12); // increase ADC resolution
       }
    
    void loop() {
       int32_t up,down;
       up = down = 0;
       noInterrupts(); // disable interrupts while measuring
       for (int i = 0; i < samples; ++i) {
          digitalWriteFast(txpin,HIGH); // charge up
          up += analogRead(rxpin); // read
          delayMicroseconds(settle); //settle
          digitalWriteFast(txpin,LOW); // charge down
          down += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          }
       interrupts(); // enable interrupts after measuring
       Serial.println(up-down); // send difference
       Serial.flush(); // finish communicating before measuring
       }
        

    However, this is for an ATtiny1624 and I had a board with ATtiny1614 done in the Input week where I had given a pinout for doing this test with a 1Mohm resistor between the pins. Chatgpt modified the code for ATtiny1614 as below:
          //
    // hello.txrx.t1614.ino
    //
    // ATtiny1614 step response
    //
    // Adapted by ChatGPT 05/23/24
    //
    // This work may be reproduced, modified, distributed,
    // performed, and displayed for any purpose, but must
    // acknowledge this project. Copyright is retained and
    // must be preserved. The work is provided as is; no
    // warranty is provided, and users accept all liability.
    //
    
    #define rxpin PIN_PA4 // receive pin (PA4 on ATtiny1614)
    #define txpin PIN_PA5 // transmit pin (PA5 on ATtiny1614)
    #define settle 100 // settle time
    #define samples 100 // number of samples to accumulate
    
    void setup() {
       Serial.begin(115200); // start serial
       pinMode(txpin, OUTPUT); // set transmit pin to output
       analogReadResolution(12); // increase ADC resolution
       }
    
    void loop() {
       int32_t up, down;
       up = down = 0;
       noInterrupts(); // disable interrupts while measuring
       for (int i = 0; i < samples; ++i) {
          digitalWrite(txpin, HIGH); // charge up
          up += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          digitalWrite(txpin, LOW); // charge down
          down += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          }
       interrupts(); // enable interrupts after measuring
       Serial.println(up - down); // send difference
       Serial.flush(); // finish communicating before measuring
       }
    
        
    From Chatgpt:
    Explanation of Changes:
    Pin Definitions: The pin definitions for rxpin and txpin are set to PIN_PA4 and PIN_PA5 respectively, as they are suitable for the ATtiny1614.
    analogSampleDuration: The analogSampleDuration function is not directly supported in standard Arduino for ATtiny1614. Instead, ensuring proper ADC resolution is sufficient.
    digitalWriteFast: Changed to digitalWrite since digitalWriteFast is not a standard function in all environments. You may need to use digitalWriteFast if you have the appropriate library; otherwise, digitalWrite should be adequate. Make sure you have the correct core and libraries for the ATtiny1614 when programming your board. You may need to use the MegaTinyCore for this purpose.

    This is the error I got in Arduino:

    In function 'check_valid_resolution', inlined from 'analogReadResolution.constprop' at C:\Users\DELL\AppData\Local\Arduino15\packages\megaTinyCore\hardware\megaavr\2.3.1\cores\megatinycore\wiring_analog.c:137:3, inlined from 'setup' at C:\Users\DELL\AppData\Local\Temp\.arduinoIDE-unsaved2024423-23584-1kq8zdi.11id\sketch_may23a\sketch_may23a.ino:23:24, inlined from 'main' at C:\Users\DELL\AppData\Local\Arduino15\packages\megaTinyCore\hardware\megaavr\2.3.1\cores\megatinycore\main.cpp:51:8: C:\Users\DELL\AppData\Local\Arduino15\packages\megaTinyCore\hardware\megaavr\2.3.1\cores\megatinycore\wiring_analog.c:124:7: error: call to 'badArg' declared with attribute error: badArg("analogReadResolution called with invalid argument - valid options are 8 or 10."); ^ lto-wrapper.exe: fatal error: C:\Users\DELL\AppData\Local\Arduino15\packages\DxCore\tools\avr-gcc\7.3.0-atmel3.6.1-azduino4b/bin/avr-gcc returned 1 exit status compilation terminated. c:/users/dell/appdata/local/arduino15/packages/dxcore/tools/avr-gcc/7.3.0-atmel3.6.1-azduino4b/bin/../lib/gcc/avr/7.3.0/../../../../avr/bin/ld.exe: error: lto-wrapper failed collect2.exe: error: ld returned 1 exit status exit status 1 Compilation error: exit status 1

    And finally, the corrected code:
      //
    // hello.txrx.t1614.ino
    //
    // ATtiny1614 step response
    //
    // Adapted by ChatGPT 05/23/24
    //
    // This work may be reproduced, modified, distributed,
    // performed, and displayed for any purpose, but must
    // acknowledge this project. Copyright is retained and
    // must be preserved. The work is provided as is; no
    // warranty is provided, and users accept all liability.
    //
    
    #define rxpin PIN_PA4 // receive pin (PA4 on ATtiny1614)
    #define txpin PIN_PA5 // transmit pin (PA5 on ATtiny1614)
    #define settle 100 // settle time
    #define samples 100 // number of samples to accumulate
    
    void setup() {
       Serial.begin(115200); // start serial
       pinMode(txpin, OUTPUT); // set transmit pin to output
       // Ensure ADC is configured correctly
       analogReadResolution(10); // set ADC resolution to 10 bits
       }
    
    void loop() {
       int32_t up, down;
       up = down = 0;
       noInterrupts(); // disable interrupts while measuring
       for (int i = 0; i < samples; ++i) {
          digitalWrite(txpin, HIGH); // charge up
          up += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          digitalWrite(txpin, LOW); // charge down
          down += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          }
       interrupts(); // enable interrupts after measuring
       Serial.println(up - down); // send difference
       Serial.flush(); // finish communicating before measuring
       }
    
    
    My instructor , Saheen changed the code as below to monitor the change in water level.
    
    #define rxpin PIN_PA4 // receive pin (PA4 on ATtiny1614)
    #define txpin PIN_PA6 // transmit pin (PA5 on ATtiny1614)
    #define settle 100 // settle time
    #define samples 100 // number of samples to accumulate
    int32_t waterLevel =0;
    void setup() {
       Serial.begin(115200); // start serial
       pinMode(txpin, OUTPUT); // set transmit pin to output
       // Ensure ADC is configured correctly
       analogReadResolution(10); // set ADC resolution to 10 bits
       }
    
    void loop() {
       int32_t up, down;
       up = down = 0;
       noInterrupts(); // disable interrupts while measuring
       for (int i = 0; i < samples; ++i) {
          digitalWrite(txpin, HIGH); // charge up
          up += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          digitalWrite(txpin, LOW); // charge down
          down += analogRead(rxpin); // read
          delayMicroseconds(settle); // settle
          }
       interrupts(); // enable interrupts after measuring
      //  Serial.println(up - down); // send difference
       int32_t diffrence = up-down;
       Serial.print(diffrence);
       waterLevel = map(diffrence,40500,72000,0,100);
       Serial.print("\t");
       Serial.print(waterLevel);
       Serial.println("%");
       Serial.flush(); // finish communicating before measuring
       }
    

    Problems faced

    A problem I faced was that I had initially planned on using a socket pin, and space was provided on the board for this purpose. However, only header pins were available in the lab, so I had to modify the board by sawing it to fit the header pins instead.
    PCB design
    PCB design

    Hero shots

    Input
    input

    Group assignment:

    Input devices' analog levels and digital signals



    Design Files

    kiCad files
    Png file
    Png file2