title: Break a big file into smaller files

text:
# Question
Title: Break a big file into smaller files
I had a really big `.ino` file and I decided to break it down into sub-files.

So I had the `.ino` file, the `globals.h` file and the `functions.h` file.

I moved all the global vars and functions to their respective files. This is what is going on inside:

```
//.ino
#include "globals.h"
#include "functions.h"

//globals.h
*Has header guard
*All variables here are extern
*Contains function prototypes - the definition is in functions.h
*Contains objects (like softWareSerial)

//functions.h
#include "globals.h"
*contains function definitions

```

However, my code will not work. I get a huge list of errors, but the last of which is about `SoftwareSerial` (in `globals.h`)

```
'SoftwareSerial' does not name a type; did you mean 'SoftwareSerial_h'?

```

Isn'e the code structure that I posted previously valid?

EDIT: I ask here and not on StackOverflow for example, because the IDE has symo idiosyncracies, like it first loads the `.ino` and then all the other files in an alphabetical manner.

EDIT 2: I followed user's `Edgar Bonet` advice (thank you). I reached somewhere, but I get `multiple definition` errors now. This is the pseudocode to show to display my code structure:

```
    //string_handling_functions.h
    HEADER_GUARD
    #include <SoftwareSerial.h> //used later. If this call happened in main .ino (even before the call to this header file, code would not work)
    #include <model_definitions.h> //used in the function definition in the .cpp file
    byte clear_Buffer(bool, SoftwareSerial*);

    //string_handling_functions.cpp
    #include <Arduino.h>
    #include "string_handling_functions.h"
    FUNCTION DEFINITION GOES HERE

    //model_definitions.h
    HEADER_GUARD
    #defines
    SoftwareSerial object_definition
    Other Objects Definitions (libs called in main .ino file)
    const char example_var PROGMEM = {0};

    //main_ino.ino
    #include "string_handling_functions.h"

```

I get these type of errors now:

```
\sketch\string_handling_functions.cpp.o (symbol from plugin): In function `GSM_Module_SW_Serial':
(.text+0x0): multiple definition of `GSM_Module_SW_Serial'
\sketch\main_code.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

```

All errors are `multiple definitions`. The items that fall victim to this error are objects and arrays.

Some extra questions:

1. All the vars in `.h` files have to be `extern`?
2. Can they have a value in the header file? (eg `extern char value = {"0"};`)

**EDIT 3**: After compying to all the rules, I had to create many files (`.h` and `.cpp`) to group everything together. These are the last errors that I get, and they have to do with `SoftwareSerial`:

```
In file included from \sketch_general_vars.cpp:2:0:
model_definitions.h:498:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial Ter_1_SS_RXOnly (TER_1_RX_PIN, TER_1_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial
model_definitions.h:499:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial Ter_2_SS_RXOnly (TER_2_RX_PIN, TER_2_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial
model_definitions.h:536:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial GSM_Module_SW_Serial(GSM_RX_PIN, GSM_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial

exit status 1
'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?

```

When I `#include <SoftwareSerial.h>` in that file, then so many errors pop up. Its a cat and mouse game.

First of all, the previous Object calls now get `multiple definitios` errors, for example `multiple definition of` GSM\_Module\_SW\_Serial'\`.

Secondly, I get countless of these messages (not only for `READ_GPS`): `ino:1817: undefined reference to` READ\_GPS'\`

`READ_GPS` is defined in `globals_non_model.h`, defined in `globals_non_model.cpp`, included in `string_handling_functions.h`. This is then included in the main `ino` (`#include "string_handling_functions.h"`)

# Answer
This is not the right way to split a big .ino file. You should instead think in terms of functional units, i.e. software “modules” that address specific concerns. For example, if a subset of your code is intended to manage an hyperdrive, then you could move that to a dedicated “hyperdrive” module:

```
// hyperdrive.h -- module interface
* variable declarations (all extern, no initializers)
* function declarations (i.e. prototypes only)
* class definitions

```

```
// hyperdrive.cpp -- module implementation
#include <Arduino.h>     // needed if you use the Arduino API
#include "hyperdrive.h"  // to check consistency with the public interface
* variable definitions (without extern, with initializers when relevant)
* function definitions (with bodies)
* class method definitions

```

```
// main.ino
#include "hyperdrive.h"
* code of main program

```

**Edit 1**: Answering EDIT 2 of the question.

> All errors are `multiple definitions`

This typically happens when you define a variable in a header (.h) file: the variable gets defined as many times as that header is included somewhere. You should not do that. The only things you should ever define in a header are *types*, which includes *classes*. Everything else (functions and variables) should only be *declared* in the header, and *defined* in the accompanying .cpp file.

> All the vars in `.h` files have to be `extern`?

Yes. Without the `extern` keyword, you would have a variable *definition*, and variable definitions belong to the accompanying .cpp file. The `extern` keyword is needed for writing a variable *declaration*.

> Can \[variables\] have a value in the header file?

No. An initial value belongs to the variable *definition*, not to its declaration.

**Edit 2**: Answering EDIT 3 of the question.

> ```
> model_definitions.h:498:1: error: 'SoftwareSerial' does not name a type
>
> ```

If the header file references types or objects that are defined elsewhere, it needs to have the relevant `#include` lines. In this case you need:

```
#include <SoftwareSerial.h>

```

> ```
> multiple definition of `GSM_Module_SW_Serial'
>
> ```

This line:

```
SoftwareSerial GSM_Module_SW_Serial(GSM_RX_PIN, GSM_TX_PIN);

```

is a variable definition. At the risk of repeating myself: *you should not put variable definitions in header files*. The header should contain only the declaration:

```
extern SoftwareSerial GSM_Module_SW_Serial;

```

whereas the definition should be in the corresponding .cpp file.
> 4 votes

# Answer
If you split the file into multiple .ino files, then the compiler will concatenate them all into one long .ino file before it compiles. It will put the one that matches the name of the project first and the rest in alphabetical order. This can cause problems if things end up being used in that long file before they are defined.

You can start the .ino file names with numbers or something and control the order that they get included in, or you can move that code into .h and .cpp files and include it that way. Header files will be included in the order you include them in the code.
> 3 votes

# Answer
I had the same problem. This solution is wrong but I tried all types of solutions and eventually had the same problem. What I did was place the extracted code in another file and labeled it .ino in the same folder as the original file and it worked. Someday they might add the .insert"filename" command. It worked great in the assembler world.
> 2 votes

# Answer
> I moved all the global vars and functions to their respective files. This is what is going on inside:

As Delta\_G said, the concatenated file is the main file first (the one with the name of the project) followed by the other files in alphabetic order. This may move global vars to be declared after they are used in other files. I would have left the global vars in the main file.

However as Edgar\_Bonet said in his answer, it would probably be better to make the whole thing modular.

---

See my answer How the IDE organizes things \- it explains it in more detail.

> 'SoftwareSerial' does not name a type; did you mean 'SoftwareSerial\_h'?

Things like includes of libraries therefore have to go in the main file - putting them in sub files breaks the inclusion of those libraries, hence that error message.
> 1 votes

# Answer
You need to treat the .ino file as a **project file**. Just put in that the includes for things like software serial. That triggers the IDE to include the appropriate libraries. Don't put any other code in it. Put declarations into .h files and definitions (ie. variable values and functions) into .cpp files.

If you do it that way make sure you do the usual C and C++ convention. Make forward declarations of functions, in case you attempt to use a function before its definition is found. These would normally go into .h files.

> Some extra questions:
>
> All the vars in .h files have to be extern?

If you don't want multiple declaration errors you should be **declaring** but not **defining** variables in your .h files. So yes, they should be extern.

> Can they have a value in the header file? (eg extern char value = {"0"};)

No - because you will be including it multiple times, probably. In fact you can't have a value on an extern, it doesn't make sense. Using extern means it is defined elsewhere.

---

Put the declarations in the .h files with extern in front of them. Then they have to be defined somewhere (ie. given a value). You could make a .cpp file specifically for that purpose.

Did you read the link I gave? That goes into it in some detail.

---

> ```
> //.ino
> #include "globals.h"
> #include "functions.h"
>
> //globals.h
> *Has header guard
> *All variables here are extern
> *Contains function prototypes - the definition is in functions.h
> *Contains objects (like softWareSerial)
>
> ```

The includes for SoftwareSerial need to go into the .ino file because that is what the IDE scans for, to see what libraries to link into your project.

Just put the (includes for the) libraries into the .ino file, nothing else.

---

Header guards just stop multiple copies of the same .h file being included in the same compilation unit. They don't stop multiple .h files being included into different compilation units (multiple .cpp files). So they may not achieve much here.
> 1 votes

---
Tags: compile, compilation-errors, header
---


# Question
Title: Can't install to arduino mega 2560 using arduino-cli, but can using IDE
I have a MKS GEN L v1.0 3dprinter board that works fine uploading using the IDE. But I need to install to it using the arduino-cli.

For some reason, the command `arduino-cli board list` says that the board has an unknown FQBN. Which is fine, I guess. I guess I don't know how to compile & upload without the FQBN.

The options I need selected (which work in the IDE) are:

* Board: Arduino/Genuino Mega or Mega 2560
* Processor: ATmega2560
* Programmer: AVRISP mkII

I don't understand why these options aren't clearly available in the arduino-cli.

I ran these commands: `arduino-cli core install arduino:avr and arduino-cli core install arduino:megaavr`

I ran  `arduino-cli board list`. It says /dev/ttyUSB0, serial, Serial Port (USB), "Unknown" for FQBN....

I run `arduino-cli compile Marlin`. It says "unknown FQBN".

So how do I install using the same parameters in the IDE?

# Answer
I would guess, that the arduino-cli doesn't recognize your boards FQBN, since it is not a genuine Arduino board. My Arduino Nano clones (with the CH340 USB-Serial-chip) aren't recognized as Arduino Nanos either. For me that's the same with the Arduino IDE.

Though, since you already know what board you have, you can find the FQBN yourself by looking through the list in the output of

```
arduino-cli board listall

```

or if you want to narrow the list down a bit:

```
arduino-cli board listall avr

```

There you will find the line with the Megas FQBN:

```
Arduino Mega or Mega 2560        arduino:avr:mega

```

Then you need to know, which Serial interface/COM port your Arduino has. This is done analog to the Arduino IDE: Check the list of connected boards via

```
arduino-cli board list

```

If you cannot identify it directly from the list, you might want to disconnect and connect it again, to see, what entry in this list reappears again.

Then compile and upload can be done like this (be sure to input your own COM port/Serial interface, if it differs from this):

```
arduino-cli compile --fqbn arduino:avr:mega
arduino-cli upload --fqbn arduino:avr:mega --port /dev/ttyUSB0

```

while being in the sketch folder.
> 3 votes

---
Tags: arduino-mega, arduino-cli
---


# Question
Title: Expose two HID Joystick devices with single RP2040 board (Waveshare Pi Pico Zero)
I want to use a Waveshare Pi Pico Zero to connect two standard NES controllers to a computer over a single USB port.

I'm using the `Waveshare RP2040 Zero` board definition from https://github.com/earlephilhower/arduino-pico which seems to provide a `Joystick` HID library that was compatible with this Teensy targeted NES\_to\_USB sketch. However, that only works for a single NES controller.

I found Arduino-USB-HID-RetroJoystickAdapter that supports two joysticks or NES controllers using a single board using the MHeironimus ArduinoJoystickLibrary. Unfortunately it seems that particular Joystick library only supports ATmega32u4 based Arduino boards (e.g. Leonardo, Pro Micro).

## Ask

A solution for exposing two USB HID Joystick devices on a single Pi Pico Zero board. First prize is a drop-in library like MHeironimus ArduinoJoystickLibrary that is compatible with https://github.com/earlephilhower/arduino-pico's RP2040 core. Happy to hear of more DIY solutions too, not sure if I'll be able to follow though :)

# Answer
> 0 votes
By selecting the Adafruit TinyUSB USB stack option (Tools-\>USB Stack-\>Adafruit TinyUSB) of the `arduino-pico` core instead of the default (Pico SDK) the USB HID descriptors can be configured directly to expose two HID gamepads.

The disadvantage of this approach is that the easy to use preconfigured devices (e.g. `Joystick`, `Mouse` and `Keyboard`) are not configured, but this gives you the option to use the full flexibility of TinyUSB to configure USB devices. While I've used it with my Waveshare Pi Pico Zero board it should work with any Arduino board that Adafruit TinyUSB supports.

Here is a test sketch that sets up a HID device with two gamepads similar to the "Hey how about two players?" example in https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/. I modified the Adafruit TinyUSB hid\_gamepad.ino example by adding a second Gamepad report and shortening the "demo" loop to save space:

```
/*********************************************************************
 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 Copyright (c) 2021 NeKuNeKo for Adafruit Industries
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

#include "Adafruit_TinyUSB.h"

// NB NB!!! Select "Adafruit TinyUSB" for USB stack

// HID report descriptor using TinyUSB's template
uint8_t const desc_hid_report[] =
{
  TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)), // First gamepad report-id 1
  TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(2))  // Second gamepad report-id 2
};

// USB HID object. For ESP32 these values cannot be changed after this declaration
// desc report, desc len, protocol, interval, use out endpoint
Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_NONE, 2, false);

// Report payload defined in src/class/hid/hid.h
// - For Gamepad Button Bit Mask see  hid_gamepad_button_bm_t
// - For Gamepad Hat    Bit Mask see  hid_gamepad_hat_t
hid_gamepad_report_t   gp[2]; // Two gamepad descriptors

void setup()
{
#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
  // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040
  TinyUSB_Device_Init(0);
#endif

  Serial.begin(115200);
  usb_hid.begin();

  // wait until device mounted
  while( !TinyUSBDevice.mounted() ) delay(1);
  Serial.println("Adafruit TinyUSB HID multi-gamepad example");
}

uint8_t gp_i = 0;

void loop()
{
  if ( !usb_hid.ready() ) return;

  Serial.print("Testing gamepad nr: ");
  Serial.println(gp_i);

  // Reset buttons
  Serial.println("No pressing buttons");
  gp[gp_i].x       = 0;
  gp[gp_i].y       = 0;
  gp[gp_i].z       = 0;
  gp[gp_i].rz      = 0;
  gp[gp_i].rx      = 0;
  gp[gp_i].ry      = 0;
  gp[gp_i].hat     = 0;
  gp[gp_i].buttons = 0;
  // gp_i + 1 is the HID report-id, i.e. 1 for first gamepad and 2 for the
  // second, as defined in desc_hid_report[] above.
  usb_hid.sendReport(gp_i + 1, &gp[gp_i], sizeof(gp[gp_i]));
  delay(2000);

  // Random touch
  Serial.println("Random touch");
  gp[gp_i].x       = random(-127, 128);
  gp[gp_i].y       = random(-127, 128);
  gp[gp_i].z       = random(-127, 128);
  gp[gp_i].rz      = random(-127, 128);
  gp[gp_i].rx      = random(-127, 128);
  gp[gp_i].ry      = random(-127, 128);
  gp[gp_i].hat     = random(0,      9);
  gp[gp_i].buttons = random(0, 0xffff);
  usb_hid.sendReport(gp_i + 1, &gp[gp_i], sizeof(gp[gp_i]));
  delay(2000);

  // select the other gamepad
  gp_i = (gp_i + 1) % 2;
}

```

## Linux HID Malarkey

For some reason the Linux kernel by default ignores multiple reports of the same type of HID device. I.e. a device with a mouse, keyboard and joystick device reports are no problem, but if a device has multiple of the same HID reports, say, two gamepads, Linux will only see the first instance. To enable multiple instances the `HID_QUIRK_MULTI_INPUT` setting must be enabled for the specific USB device.

These instructions worked on Ubuntu 22.04, you may need to adjust them for your distro.

After programming the example sketch I could see the ID of my Pi Pico USB device with \`lsusb:

```
$ lsusb
...
Bus 001 Device 011: ID 239a:cafe Adafruit RP2040 Zero
...

```

Note down the USB device identifier (`239a:cafe`) and create a file in `/etc/modprobe.d` to configure the `usbhid` module:

```
$ echo "options usbhid quirks=0x239a:0xcafe:0x040" | sudo tee /etc/modprobe.d/adafruit_hid_quirk.conf

$ sudo update-initramfs -u

# Requires a reboot to take effect, e.g.:

$ sudo shutdown -r now

```

The `0x239a:0xcafe` is from the `lsusb` output and `0x040` is the magic number to enable `HID_QUIRK_MULTI_INPUT` mode. We need `update-initramfs` since `usbhid` is usually loaded too early in the boot process for the settings from `/etc/modprobe.d` to take effect without it being in the `initrd`.

Once you have rebooted you can check if the `usbhid` setting took effect:

```
$ cat /sys/module/usbhid/parameters/quirks
0x239a:0xcafe:0x040,(null),(null),(null)

```

and you should see two joystick devices after programming the sketch:

```
ls /dev/input/js*
/dev/input/js0  /dev/input/js1

```

## Testing

An easy way to test is to navigate to https://gamepad-tester.com/. After a short period you should see something like:

You should see Player1's values toggle and then Player2's values until you unplug your pico.

---
Tags: usb, joystick, hid, raspberrypi-pico
---


# Question
Title: Developing Standalone Arduino GUI Controller
I am rather new to using an Arduino. As a part of my Graduate-Level Chemistry research, I am trying to further the development of a cheap DIY, open-source, Flow Chemistry Syringe pump system.

The device utilizes Stepper Motors, Step Motor control boards, an Arduino Uno, and a 3D printed Pump frame. Simple code was previously developed using the Arduino IDE. I have expanded on this a bit to allow for more customization (syringe size, mostly). The limitation here, is that the Serial Monitor inputs will be taxing to the users. I need to develop a simple GUI that allows for all of the inputs I need (syringe size, flow (step) rate, start and stop functions, etc.). I need this gui to be a standalone application that can be easily shared (no need to download anything extra, no installations, deploy-able on at LEAST Win and Mac).

Our labs goal is to develop a system that is easy to use and distribute, reducing the costs associated with using Flow Chemistry. I have a decent understanding of Arduino IDE, and I am willing to learn whatever I need to make this work.

I have seen a few things about Python, and 'Processing', but they seem to require the simultaneous use of Arduino IDE which would make deployment too complicated.

How should I approach this? What language do I need to learn? What advice do you have?

# Answer
> 1 votes
The Uno is connected to the PC with a Serial interface (emulated over USB). The program on the PC only sees the Serial interface. It is not important, if there is really an Arduino at the other end. Nearly every programmung language can be used for interfacing a Serial interface, each gives advantages and disadvantages. But you will not have a single portable program file, that you can start on different OS without installing anything. Either you need to compile the program for each OS individually, or you can use a portable programming language, where you first need to install it's framework on the OS (for example python).

I would propose a different solution (that I'm currently also working on): Depending on your lab situation you could extend your project with a network interface, either Wifi or LAN. Then you can present a webinterface to the user. When writing HTML/javascript websites, it is easy to have them display correctly on most mayor browsers.

That way the GUI is really independent from the OS. For adding the network connection, you could use a LAN shield, Wifi shield, or directly change the microcontroller from an Arduino Uno to an ESP board, which has Wifi build in and is also has more computation power and memory.

The webinterface can be interactive by the use of javascript (since you cannot run php or similar on the Arduino or ESP).

Wether you want to use a companion application, that you write for the PC, or a webinterface is up to you and depends on your exact lab situation.

# Answer
> 0 votes
I have a similar project ahead of me. While it is quite but obvious to be able to read the output voltages using arduino, I was wondering if we could create a python file which will be saved as an executable, but have in it, code that will run the original arduino cc file to read the averaged voltages from the pins. I tried using pyqt5 and/or tkinter but I am having thread issues in that the gui hangs after a few seconds of activity.(I used pyserial library here) It will be really helpful if someone can help me out here.

---
Tags: arduino-uno
---


# Question
Title: Communicating via Serial Monitor but not able to get comms via Terminal outside of Arduino IDE
I am using Arduino Nano 33 BLE. I am able to communicate via Serial Monitor within Arduino IDE, but not able to communicate via Terminal, outside of Arduino IDE. I am using Teraterm, do I need to do any additional setting?

No error messages. It does work on the Serial Monitor of Arduino IDE at 115200 baud rate with the default settings ("SERIAL\_8N1"). The serial port Setup on Teraterm is the following:

`Speed: 115200 Data: 8 bit Parity: none. Stop bits: 1 bit. Flow control: none`

The thing is that it does what it has to do writing one command that is one letter, I mean, in my Arduino sketch if you write "$" on Serial Monitor it does a reset, and through Teraterm if you write "$" It does a reset as well,.. but on Teraterm if I type a command longer than one letter It doesn't even show what I type on Teraterm and doesn't recognize.

# Answer
> 1 votes
In TeraTerm the local echo is off by default. So you won't see anything while you type something. Here are the settings:

1. First connect to the serial port.

2. Then go to `Setup`-\>`Terminal`.

3. Based on your code select what you are expecting to receive at the end of a transmission from Arduino and what you need to send at the end of your command to Arduino to tell it is the end of the command. Usually, it can be Carriage Return (CR: 0x0D) or Line Feed (LF: 0x13), or both.

Also, check `Local echo`. That will print whatever you are typing on the terminal.

4. Then `Setup`-\>`Serial port...`.

5. And configure your serial port accordingly.

# Answer
> 0 votes
What you call a “terminal” is actually a *terminal emulator*, i.e. a computer program intended to emulate an old school computer terminal. CRT-based terminals were in turn designed to emulate still older “hard-copy terminals”, like the venerable ASR-33. A hard-copy terminal (also called “teletypewriter”) is basically a typewriter fitted with a serial port. Whatever you type is sent along the wire. Keystrokes are sent *immediately*: these devices did not have memory (which was hugely expensive at the time) and could not buffer your input. The Teraterm program you are using inherits from this history: like a teletypewriter, it sends your keystrokes immediately.

The Arduino serial monitor, in contrast, does not transmit as you type. Instead, you type a line of text and, when you hit “Send”, the whole line is sent at once, at the full speed of the serial port.

This makes a significant difference in terms of transmission timing. With Teraterm, the Arduino program sees long pauses (which depend on your typing speed) between successive characters. With the serial monitor, successive characters are spaced by roughly 87 µs. If your Arduino code properly buffers its input until it receives an end-of-line, then it will work equally well with Teraterm and with the serial monitor. If, on the other hand, your code gets impatient and times out while you are typing, then it will not get multi-character commands you type on Teraterm.

Note that, if you want your Arduino program to be friendly to terminal users, you may want to echo the characters you receive, as local echo on the terminal is usually not on by default. More importantly, you may want to handle the backspace key (which can send either ASCII BS or ASCII DEL) to let the user fix their typos. See how this is done in this very simple command-line interpreter.

---
Tags: error, terminal
---


# Question
Title: Arduino Freezes (probably because of I2C) - Need help to write robust script
Arduino Pro mini 3.3V stucks few hours after startup. After repowered, it begins to stuck within a few seconds. If I wait long enough before turning it on it stucks little later for example now it lasted 45 seconds before stuck. Whenever it is stuck SCL pin is high and SDA pin is low so I suspect somethin about I2C is wrong. There is an 60 seconds countdown at start and the stuck usually happens there, you will see it is an extremely simple for loop. There are SHT31 sensor, 128x64 SH1106 driven OLED screen, DS3231 clock module, hc-sr501PIR sensor, LDR, Encoder and a button on the project. Only first three works with I2C and the code for those devices are very short, they are in "start(), updatescreen(), getTime(), getDew()" functions in the long script below so I hope it is not hard to diagnose the problem. I have done some experiments. I isolated SHT31 and OLED, both caused freeze even though all other devices were disconnected (I added blink code to their example test sketch and the blinking stopped after few seconds which means stuck). OLED screen works when it is connected with jumpers outside the PCB so I even suspected the PCB but it is so simple that it shouldn't cause problems. The weird thing is that this device worked perfectly for 2 days before it crushed for the first time.

Schematic:

PCB (some mistakes were fixed later):

Real images:

Test script (the same freezing problem occurs but easier to read):

```
#include <U8g2lib.h>
#include <Wire.h>

U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
void setup() {
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB14_tr);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, 20);
    u8g2.print(F("Hello World!"));
  } while (u8g2.nextPage());
  delay(1000);
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);
}

```

Test script 2 (this time for sht31, the same freezing problem occurs but easier to read):

```
#include <Wire.h>
#include "ClosedCube_SHT31D.h"

ClosedCube_SHT31D sht3xd;
void setup() {
  Wire.begin();
  sht3xd.begin(0x44);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

```

Full Script:

```
#include "Wire.h"
#include "ClosedCube_SHT31D.h"
#include <DS3231.h>
#include "LowPower.h"
#include <EEPROM.h>
#include <U8g2lib.h>

#define PIR 2
#define ENCODER_BUTTON 3
#define ENC_A 4
#define ENC_B 5
#define FAN_MAIN_REL 8
#define FAN_SECOND_REL 9
#define BUZZER 10
#define LIGHT_REL 11
#define LDR_PWR 12
#define LDR_READ A0
#define FREE_RUN_BUTTON A2
#define CLOCK_INT A3

volatile bool PIR_flag, TIM1_flag, But_flag, FreeRun_flag, Settings_flag;
uint8_t Weather_flag;  // Weather_flag is special, it is set/reset by a regular function, 0: Dew is under threshold, 1: Dew is over DewThres but less than DewThresExtreme, 2: Dew is over DewThresExtreme
volatile int16_t encVal;

// Variables:
float Temp, Hum, Dew;
bool DewTrigEnabled;
uint8_t FanMotTrigEnabled;  // for fan relay
bool LightRelEnabled;       // for light relay
uint16_t waitDur;           // Setting 1
uint16_t Runtime;           // Setting 2
float DewThres;             // Setting 3
float DewThresExtreme;
uint16_t FreeRunDur;  // Setting 5
bool FreeRunStatus;
bool DNDenabled;
uint8_t DND_SH;  // DND start Hour
uint8_t DND_SM;  // DND start Minute
uint8_t DND_FH;  // DND finish Hour
uint8_t DND_FM;  // DND finish Minute
uint16_t counter;
uint8_t hour, minute;
bool LDRenabled;
uint16_t LDRThres;
bool screen_awake = true;  // screen starts "on"
uint8_t FanRelayStatus;
const uint8_t screenTime = 60;  // Screen On time without motion (seconds)
const float DewUSratio = 0.98;  // Determines the dew point which the FAN turns off if it was triggered by the Dew in the first place
const uint8_t phaseLim = 120;   // Determines LDR check rate if light is off but LDR is enabled, each phase corresponds to 5 seconds.

ClosedCube_SHT31D sht3xd;
RTClib myRTC_1;
DS3231 myRTC_2;
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

void swCounter(bool turn_on, uint32_t cnt = 0) {
  if (turn_on) {
    counter = cnt;
    TCNT1 = 0;                            // TIM1 counter value
    TCCR1B |= (1 << CS12) | (1 << CS10);  // Start timer by setting prescaler to 1024
  } else
    TCCR1B &= ~(1 << CS12) | ~(1 << CS10);  // Stop TIM1
}

void setup() {
  LoadPinsAndEEPROM();

  Wire.begin();

  // Temp&Hum Sensor:
  sht3xd.begin(0x44);

  // Clock Module:
  myRTC_2.setA2Time(
    0, 0, 0,
    0b01110000, false, false, false);
  myRTC_2.turnOnAlarm(2);

  myRTC_2.setA1Time(
    0, 0, 0xFF, 0,
    0b00001110, false, false, false);
  myRTC_2.turnOffAlarm(1);
  myRTC_2.checkIfAlarm(1);  // clear Alarm 1 flag

  sht3xd.heaterEnable();
  // OLED Screen:
  u8g2.begin();
  u8g2.setFont(u8g2_font_lubB12_tr);
  for (int i = 60; i > 0; i--) {
    u8g2.firstPage();
    do {
      u8g2.drawStr(6, 25, "by Prince");
      u8g2.setCursor(58, 55);
      u8g2.print(i);
      if (i == 30) sht3xd.heaterDisable();
    } while (u8g2.nextPage());
    delay(1000);
  }
  u8g2.setDrawColor(2);

  //TIM1 Configuration:
  TCCR1A = 0;
  TCCR1B = 0;
  TIMSK1 |= (1 << OCIE1A);  // Output Compare A Match Interrupt Enable
  OCR1A = 39061;            // Output Compare A
  TCCR1B |= (1 << WGM12);   // CRC (auto reset timer when it reaches OCR1A)
  TCNT1 = 0;                // TIM1 counter value

  // Attach interrupts (PinChanges and INTpin)
  attachInterrupt(digitalPinToInterrupt(PIR), ISR_PIR, RISING);
  attachInterrupt(digitalPinToInterrupt(ENCODER_BUTTON), ISR_ENCODER_BUTTON, FALLING);
  PCICR |= (1 << PCIE1);                      // A0-A6 group can create interrupt
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // A2 and A3 pins can create interrupt
  PCMSK2 |= (1 << PCINT20) | (1 << PCINT21);  // D4 and D5 pins can create interrupt

  Beep(1, 100, 0);
  PIR_flag = false;
  But_flag = false;
}

void loop() {
  checkButtons();  // After execution it will return.
  if (!getDNDstatus()) {
    getDew();  // sets flag accordingly

    if (PIR_flag) {
      if (FanMotTrigEnabled == 1) {
        TIM1_flag = true;  // Update screen initially
        screenPwr(1);

        unsigned long lastMotion;
        uint8_t step = 1;
        if (waitDur <= 5) step = 2;  // PIR_flag is already true at this point
        else swCounter(1, waitDur);

        for (step = step; step <= 2; step++) {  // 1: waiting, 2: armed, 3: Running
          if (step == 2) {
            (waitDur >= 90) ? counter = 60 : counter = 30;  // Deciding how long ahead it will check for motion
            swCounter(1, counter);
            PIR_flag = false;
            updateScreen(step);
          }
          while (counter > 0) {  // Counter Loop
            if (checkButtons()) return;
            if (PIR_flag) {
              PIR_flag = false;
              lastMotion = millis();
              if (step >= 2) {    // if it's armed or Running and Motion is detected
                if (step == 2) {  // if it's armed and Motion is detected
                  if (FanRelayStatus != 2) swFanRelays(1);
                  step = 3;  // Motion..
                  updateScreen(step);
                }
                swCounter(1, Runtime);  // Restart interval
              }
            }
            if (TIM1_flag) {
              TIM1_flag = false;
              if (getDNDstatus()) return;
              if (step == 1 && (millis() - lastMotion) / 1000 >= screenTime) return;
              getDew();
              DewControl(step != 3);  // in case Dew increases while on wait
              updateScreen(step);
            }
          }
        }
      } else if (FanMotTrigEnabled != 1) {  // Mot trig is disabled but there is motion
        PIR_flag = false;
        if (FanMotTrigEnabled == 2) swFanRelays(0);
        RunScreen(false);
      }
    }

    DewControl(true);
    Sleep(true);

  } else {  // if it's DND times:
    if (PIR_flag) {
      PIR_flag = false;
      RunScreen(true);
    }
    if (getDNDstatus()) Sleep(false);  // DnD status is rechecked because it might have been stuck in RunScreen by the user when DND is over.
  }
}

void DewControl(bool cmd1) {  // 0: cannot turn the fan "off" but only "on" 1: can both turn "on" and "off"
  if (Weather_flag == 0 && cmd1) swFanRelays(0);
  else if (Weather_flag == 1) swFanRelays(1);
  else if (Weather_flag == 2) swFanRelays(2);
}

void RunScreen(bool cmd1) {  // 0: dont return based on DND, 1: return if not in DND times anymore (prevents being stuck in DND if screen is on)
  TIM1_flag = true;          // Update screen initially
  screenPwr(1);
  swCounter(1, screenTime);  // Fixed Run screen time
  while (counter > 0) {
    if (checkButtons()) return;
    if (TIM1_flag) {
      if (getDNDstatus()) {
        if (FanRelayStatus) return;
      } else {
        if (cmd1) return;
        DewControl(true);
      }
      getDew();
      updateScreen(0);  // Idle or Dew

      if (PIR_flag) {
        counter = screenTime;
        PIR_flag = false;
      }
      TIM1_flag = false;
    }
  }
  LightRel_IfNeeded(0);
}

void getDew() {
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);  // used to be a static var
  Temp = result.t;
  Hum = result.rh;

  float a_func = log(Hum / 100) + 17.625 * Temp / (243.04 + Temp);
  Dew = (243.04 * a_func) / (17.625 - a_func);

  if (!DewTrigEnabled || (Dew <= DewThres * DewUSratio) || (Dew < DewThres && FanRelayStatus == 0)) Weather_flag = 0;
  else if (Dew < DewThresExtreme) Weather_flag = 1;
  else Weather_flag = 2;
}

bool getDNDstatus() {  // check if hours and minutes are in the interval
  if (!DNDenabled) return false;

  updateTime();
  uint16_t currentmin = hour * 60 + minute;

  uint16_t localDNDstart = DND_SH * 60 + DND_SM;
  uint16_t localDNDfinish = DND_FH * 60 + DND_FM;

  if (localDNDstart < localDNDfinish) return (currentmin >= localDNDstart && currentmin < localDNDfinish);
  else return (currentmin >= localDNDstart || currentmin < localDNDfinish);
}

void updateTime() {
  if (myRTC_2.checkIfAlarm(2)) {
    DateTime now;  // used to be a static var
    now = myRTC_1.now();
    hour = now.hour();
    minute = now.minute();
  }
}

void swFanRelays(uint8_t cmd1) {  // 0: turn off, 1: turn on
  if (FanRelayStatus == cmd1) return;
  bool PIR_flag_original = PIR_flag;
  if (cmd1 == 0) {
    digitalWrite(FAN_MAIN_REL, LOW);
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 0;
  } else if (cmd1 == 1) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    if (FanRelayStatus != 2) {
      digitalWrite(FAN_SECOND_REL, HIGH);
      delay(500);
    }
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 1;
  } else if (cmd1 == 2) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    digitalWrite(FAN_SECOND_REL, HIGH);
    FanRelayStatus = 2;
  }
  delay(100);
  if (!PIR_flag_original) PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;
}

void LightRel_IfNeeded(bool cmd1) {  // 0: turn OFF Light Relay, 1: turn ON Light Relay
  static bool LightRelStatus;
  bool LDRresult;
  static uint8_t phase;  // regularly check light level in case brightness changes in the room over time.

  switch (cmd1) {
    case 0:
      if (!LightRelStatus) return;
      LightRelStatus = false;
      phase = 0;
      digitalWrite(LIGHT_REL, LOW);
      delay(100);
      PIR_flag = false;
      But_flag = false;
      FreeRun_flag = false;
      break;

    case 1:
      if (!LightRelStatus && phase == 0) {
        if (LDRenabled) {
          digitalWrite(LDR_PWR, HIGH);
          delay(10);
          LDRresult = (analogRead(LDR_READ) > LDRThres);  // Darkness --> High resistance --> High voltage on LDR --> TRUE              Lit --> Low resistance --> Low voltage on LDR --> FALSE
          digitalWrite(LDR_PWR, LOW);
        } else LDRresult = true;

        if (LDRresult) {
          LightRelStatus = true;
          digitalWrite(LIGHT_REL, HIGH);
          delay(100);
          PIR_flag = false;
          But_flag = false;
          FreeRun_flag = false;
        }
      }
      phase++;
      if (phase > phaseLim) phase = 0;  // default value is 120 which means 10 minutes
      break;
  }
}

void Sleep(bool cmd1) {  // 0: Sleep normal 1: Sleep without turning off the Fan
  if (cmd1 == 0) swFanRelays(0);
  LightRel_IfNeeded(0);
  screenPwr(0);
  updateTime();
  PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;

  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}

void updateScreen(uint8_t cmd1) {  // 0: idle or Dew Trig, 1: Detected, 2: Waiting, 3: Armed, 4: Mot Trig, 5: Free run dur
  String info;
  LightRel_IfNeeded(1);  // Regularly called because brigthness of the room info is updated every phase*5/60 minutes
  updateTime();

  switch (cmd1) {
    case 0:
      if (FanRelayStatus == 0) info = "Idle";
      else if (FanRelayStatus == 1) info = "Dew";
      else info = "OverDew";
      break;
    case 1:
      info = "Waiting";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 2:
      info = "Armed";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 3:
      info = "Motion";
      if (Weather_flag == 1) info += "&Dew";
      else if (Weather_flag == 2) info += "&OverDew";
      break;
    case 4:
      info = "<";
      info += String(counter / 60 + 1);
      info.remove(info.indexOf('.'));
      info += " mins";
      break;
  }

  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_6x10_mr);  // u8g2_font_haxrcorp4089_tn
    u8g2.setCursor(0, 7);
    u8g2.print(info);

    u8g2.setCursor(99, 7);
    if (hour < 10) u8g2.print("0");
    u8g2.print(hour);
    u8g2.print(":");
    if (minute < 10) u8g2.print("0");
    u8g2.print(minute);

    u8g2.drawLine(0, 10, 128, 10);

    u8g2.setFont(u8g2_font_fub20_tn);
    u8g2.setCursor(0, 38);
    if (Temp < 10 && Temp >= 0) u8g2.print("0");
    u8g2.print(Temp, 1);
    u8g2.setCursor(0, 64);
    u8g2.print(Hum, 1);
    u8g2.setCursor(71, 61);
    if (Dew < 10) u8g2.print("0");
    u8g2.print(Dew, 1);
    if (Dew < DewThres) u8g2.drawFrame(70, 38, 58, 26);
    else u8g2.drawBox(71, 39, 56, 24);

    u8g2.setFont(u8g2_font_lubB12_tr);
    u8g2.drawStr(55, 30, "*C");
    u8g2.drawStr(55, 64, "%");

    if (FreeRunStatus == true) u8g2.drawStr(81, 30, "FREE");
    else if (getDNDstatus()) u8g2.drawStr(82, 30, "DND");

  } while (u8g2.nextPage());
}

void screenPwr(bool cmd1) {
  if (cmd1 == screen_awake) return;

  switch (cmd1) {
    case 0:
      u8g2.setPowerSave(1);
      u8g2.clear();
      screen_awake = false;
      break;

    case 1:
      u8g2.setPowerSave(0);
      swCounter(0);
      screen_awake = true;
      break;
  }
}

void FreeRun() {
  Beep(1, 500, 0);
  FreeRun_flag = false;
  FreeRunStatus = true;
  getDew();
  swFanRelays(1);
  swCounter(1, FreeRunDur);
  updateScreen(4);
  screenPwr(1);
  while (counter > 0) {
    if (But_flag) {
      FreeRunStatus = false;
      Settings();
      return;
    }
    if (FreeRun_flag) {
      Beep(1, 100, 0);
      delay(1000);
      if (!digitalRead(FREE_RUN_BUTTON)) {  // if the user holds the button down
        swFanRelays(0);
        Beep(2, 100, 100);
        while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // For the user to remove hand
        delay(500);                                        // prevent debouncing
        FreeRun_flag = false;
        break;
      } else {
        FreeRun_flag = false;
        if (FanRelayStatus == 1) swFanRelays(2);
        else if (FanRelayStatus == 2) swFanRelays(1);
      }
    }
    if (TIM1_flag) {
      TIM1_flag = false;
      if (getDNDstatus()) break;
      getDew();
      updateScreen(4);
    }
  }
  DewControl(true);
  FreeRunStatus = false;
  PIR_flag = false;  // Ignore the motion while in "Free Run" mode
}

bool checkButtons() {
  if (FreeRun_flag) {
    if (!getDNDstatus()) {
      FreeRun();
      return 1;
    } else {  // if Free Run is pressed during DND, deny Free Run
      Beep(3, 50, 50);
      FreeRun_flag = false;
    }
  } else if (But_flag) {
    Beep(1, 100, 0);
    delay(1000);  // If button is held down
    if (!digitalRead(ENCODER_BUTTON)) {
      Settings();
      return 1;
    }
    But_flag = false;
  }
  return 0;
}

void Beep(uint8_t rep, uint8_t dur_act, uint8_t dur_pass) {  // repetition, duration of beeping, duration of pauses
  for (int i = 0; i < rep; i++) {
    digitalWrite(BUZZER, HIGH);
    delay(dur_act);
    digitalWrite(BUZZER, LOW);
    if (i < (rep - 1)) delay(dur_pass);
  }
}

void LoadPinsAndEEPROM() {

#define FanMotTrigEnabled_Address 0
#define waitDur_Address 1
#define Runtime_Address 3
#define DewTrigEnabled_Address 5
#define DewThres_Address 6
#define DewThresExtreme_Address 10
#define LightRelEnabled_Address 14
#define LDRenabled_Address 15
#define LDRThres_Address 16
#define DND_Enabled_Address 18  // DND enabled
#define DND_SH_Address 19       // DND start Hour
#define DND_SM_Address 20       // DND start Minute
#define DND_FH_Address 21       // DND finish Hour
#define DND_FM_Address 22       // DND finish Minute
#define FreeRunDur_Address 23

  EEPROM.get(FanMotTrigEnabled_Address, FanMotTrigEnabled);
  EEPROM.get(waitDur_Address, waitDur);
  EEPROM.get(Runtime_Address, Runtime);
  EEPROM.get(DewTrigEnabled_Address, DewTrigEnabled);
  EEPROM.get(DewThres_Address, DewThres);
  EEPROM.get(DewThresExtreme_Address, DewThresExtreme);
  EEPROM.get(LightRelEnabled_Address, LightRelEnabled);
  EEPROM.get(LDRenabled_Address, LDRenabled);
  EEPROM.get(LDRThres_Address, LDRThres);
  EEPROM.get(DND_Enabled_Address, DNDenabled);
  EEPROM.get(DND_SH_Address, DND_SH);
  EEPROM.get(DND_SM_Address, DND_SM);
  EEPROM.get(DND_FH_Address, DND_FH);
  EEPROM.get(DND_FM_Address, DND_FM);
  EEPROM.get(FreeRunDur_Address, FreeRunDur);

  // DEBUG:
  /*
  FanMotTrigEnabled = 1;
  waitDur = 10;
  Runtime = 10;
  DewTrigEnabled = true;
  DewThres = 18.0;
  DewThresExtreme = 19.0;
  LightRelEnabled = true;
  LDRenabled = true;
  LDRThres = 512;
  DNDenabled = false;
  DND_SH = 23;
  DND_SM = 45;
  DND_FH = 0;
  DND_FM = 2;
  FreeRunDur = 30;
*/
  pinMode(PIR, INPUT);
  pinMode(LDR_READ, INPUT);
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);

  pinMode(CLOCK_INT, INPUT_PULLUP);  // Datasheet states pullup resistor is necessary
  pinMode(FREE_RUN_BUTTON, INPUT_PULLUP);
  pinMode(ENCODER_BUTTON, INPUT_PULLUP);

  pinMode(FAN_MAIN_REL, OUTPUT);
  pinMode(FAN_SECOND_REL, OUTPUT);
  pinMode(LIGHT_REL, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(LDR_PWR, OUTPUT);
}

void ISR_PIR() {
  PIR_flag = true;
}

void ISR_ENCODER_BUTTON() {
  But_flag = true;
}

ISR(PCINT1_vect) {
  if (!digitalRead(FREE_RUN_BUTTON)) FreeRun_flag = true;  // Button has pullup resistor thus "!" is used.
}

ISR(TIMER1_COMPA_vect) {
  TIM1_flag = true;
  counter -= 5;
  if (counter <= 0) swCounter(0);
}

// Horrible Settings Codes xd:
ISR(PCINT2_vect) {  // Rotary Encoder Interrupt
  static bool aLastState;
  bool aState = digitalRead(ENC_A);
  bool bState = digitalRead(ENC_B);

  if (aState != aLastState) {
    if (bState != aState) {
      encVal++;
    } else {
      encVal--;
    }
    aLastState = aState;
  }
}

void ButDebounce(bool cmd1) {  // 0: Encoder But, 1: Free Run
  if (cmd1) {
    while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // Wait until button is released
    delay(200);
    FreeRun_flag = false;
  } else {
    while (!digitalRead(ENCODER_BUTTON)) delay(100);  // Wait until button is released
    delay(200);
    But_flag = false;
  }
}

String formatTime(int input, bool type = true) {  // type = true means input is seconds, if false it means input is minutes
  int hours = input / 3600;                       // Get the number of hours
  int minutes = (input % 3600) / 60;              // Get the number of minutes
  int remainingSeconds = input % 60;              // Get the number of seconds

  if (!type) {
    hours = minutes;
    minutes = remainingSeconds;
    remainingSeconds = 0;
  }

  // Create a formatted string in the format "hour:minute:second"
  String formattedTime = "";

  if (hours < 10) {
    formattedTime += "0";  // Add leading zero for hours
  }
  formattedTime += String(hours) + ":";

  if (minutes < 10) {
    formattedTime += "0";  // Add leading zero for minutes
  }
  formattedTime += String(minutes) + ":";

  if (remainingSeconds < 10) {
    formattedTime += "0";  // Add leading zero for seconds
  }
  formattedTime += String(remainingSeconds);

  return formattedTime;
}

uint8_t setEncVal(uint16_t target, uint8_t inc1, uint8_t inc2, uint16_t inc3, uint8_t lim1, uint8_t lim2) {
  uint16_t a = 0;
  uint8_t b = 0;
  while (a < target) {
    if (b < lim1) a += inc1;
    else if (b < lim2) a += inc2;
    else a += inc3;
    b++;
  }
  return b;
}

void Settings() {} // removed because of character limit but I don't run this function before stuck anyway

```

# Answer
> 1 votes
It sounds like over ambitious pull up resistors. They should total about 3K on each bus. I will take a SWAG and say the I2C driver fet is drawing to much current and heating up. When it does that its resistance increase thereby delivering less current. Get your resistors correct and I believe it will work just fine.

If you have freeze spray chill them and they will probably start working until they warm up.

If it was left on for any period of time it is possible you damaged one of the ICs which is no longer driving the bus properly. If you can check it with a scope. If not swat parts.

# Answer
> 1 votes
Are you sure you are using the latest I2C library? Up until halfway through 2020, the 'official' I2C library had a known failure mode that would cause the I2C buss to hang. See my 'Paynters Palace' post on this subject for additional info.

Frank

---
Tags: i2c, arduino-pro-mini, oled
---


# Question
Title: Issues using mini microphone module off AliExpress
I have been trying to get a mini microphone module with an inbuilt microphone module I got from AliExpress to my Arduino to confirm if it is working. It was a four pin microphone with pins labeled V, L, and two G pins. The V represents voltage in and the L represents audio output positive.

Here is the link to the microphone: https://www.aliexpress.com/item/1005004277353392.html?spm=a2g0o.productlist.main.23.61e36f2fBMHvFk&algo\_pvid=15b05576-cd73-4eb7-a476-4503b8a706a7&algo\_exp\_id=15b05576-cd73-4eb7-a476-4503b8a706a7-11&pdp\_npi=3%40dis%21USD%210.54%210.39%21%21%21%21%21%40211bf3f716869249882567444d07ba%2112000028601598840%21sea%21NG%214345576938&curPageLogUid=plaZEQWq7SRB

However, when I ran the code to check whether it works the value does not change that much, Is there something I'm doing wrong (I'm a beginner btw) or is the microphone not just working?

My code

```
const int audioPin = A0; // Analog input pin connected to the microphone module output

void setup() {
  Serial.begin(9600); // Initialize the Serial Monitor
}

void loop() {
  int audioValue = analogRead(audioPin); // Read the analog value from the microphone module

  Serial.println(audioValue); // Print the analog value to the Serial Monitor

  // Add a small delay to control the rate of readings
  delay(10);
}

```

I connected the output (L) to A0 and connected both the G pins to the arduino ground, I connected the V pin to the 3.3V pin on the Arduino Uno.

# Answer
> 1 votes
Have you checked to see if it is a hardware problem? It could be the fact that it is just off AliExpress. Usually, the display images are better than the actual product and if this is true, you can see a few bad soldering connections. I might want to go over some of them with a soldering iron.

---
Tags: arduino-uno, analogread, audio, analog
---


# Question
Title: How to get around passing a variable into an ISR
Okay so you aren't able to pass a variable into in ISR. This is causing problems for me. I'm using a rotary encoder, and I need it to be connected to an interrupt pin and running a ISR. When using this method, no pulses are ever skipped and the knob works great.

Currently I have the ISR setup like this:

```
void vhISR()
{
  // this checks the PINE register for a 1 or 0 on the 5th bit (or 6th)
  // I'm bit shifting the result so that variable is either true or false for comparison, not 16/0 or 32/0
  rotCurrentA = (PINE & B00010000) >> 4;
  rotB = (PINE & B00100000) >> 5;

  if (rotCurrentA != rotLastA)
    {
      if (rotCurrentA != rotB) // cw
        {
          voltageHigh += knobResolution;
          rotLastA = rotCurrentA;
        }
      else // ccw
        {
          voltageHigh -= knobResolution;
          rotLastA = rotCurrentA;
        }
    }
}

```

The code simply varies voltageHigh by 0.1V increments. The problem is this: I need to do this same 0.1 variations on several other variables, depending on which one I select. I need to increase/decrease a voltageLow, a timer, and a resistance, all that need +/- 0.1 increments (but on different ranges. For instance, voltLow is somewhere between 0.1 & 5, and VH is somewhere between 6 & 10). Normally what I would do is just pass the variable I needed by reference into the function and have the function increment/decrement that variable....but an ISR is not a normal function and you cannot pass a variable into it.

How do I get around this? I'm not sure if that's enough information to go off of let me know.

# Answer
> 8 votes
I would follow Delta\_G's advice, and write an ISR that counts the steps and does nothing more. As a general rule, you want to do as little as possible within an ISR. I would even avoid floating point and just count the raw number of steps:

```
// Count of rotational steps updated in the ISR. Must be unsigned in
// order to avoid signed overflow, which is undefined behavior.
volatile uint16_t rotation_count;

void vhISR()
{
    static uint8_t rotLastA;
    uint8_t rotCurrentA = (PINE >> 4) & 1;
    uint8_t rotB = (PINE >> 5) & 1;

    if (rotCurrentA != rotLastA)
        return;
    if (rotCurrentA != rotB) // cw
        rotation_count++;
    else // ccw
        rotation_count--;
    rotLastA = rotCurrentA;
}

// Return the number of rotational steps since the last call. Should be
// called often enough to avoid overflowing an int16_t.
int16_t get_rotation()
{
    static uint16_t last_count;

    // Avoid a data race: rotation_count should not be modified while we
    // are reading it.
    noInterrupts();
    uint16_t count_copy = rotation_count;
    interrupts();
    int16_t delta = count_copy - last_count;  // this is signed!
    last_count = count_copy;
    return delta;
}

```

Note that the raw count is an unsigned number which can roll over modulo 216. The value returned by `get_rotation()`, however, is a signed number which, owing to the rules of modular arithmetic, is *immune* to the rollover of the raw count. As long as you call `get_rotation()` often enough (more often than every 32767 steps), the returned value will always be correct.

Then, in your main program, you can use this to update whatever variable you want:

```
const float knobResolution = 0.1;
float voltageHigh, voltageLow, resistance;

// The variable that the user has selected for changing.
enum {VOLTAGE_HIGH, VOLTAGE_LOW, RESISTANCE} selection;

void loop()
{
    // Set a pointer to the selected variable.
    float *selected_var;
    switch (selection) {
    case VOLTAGE_HIGH:
        selected_var = &voltageHigh;
        break;
    case VOLTAGE_LOW:
        selected_var = &voltageLow;
        break;
    case RESISTANCE:
        selected_var = &resistance;
        break;
    }

    // Update the selected variable.
    *selected_var += get_rotation() * knobResolution;
}

```

Note here that the `selection` enum is probably redundant with `selected_var`. You could use the latter to keep track of the user selection, then you would not need the `switch`/`case`.

# Answer
> 2 votes
> Okay so you aren't able to pass a variable into in ISR ...

Not as such, because the ISR is triggered by hardware, and therefore you don't pass arguments to it.

However there is nothing stopping you having a global variable which the ISR can access (preferably declared `volatile`) so the ISR can change its behaviour depending on what the global variable holds.

---
Tags: isr
---


title: ESP8266 Wifi connection problem

text:
# Question
Title: ESP8266 Wifi connection problem
I have a Feather Huzzah, aka ESP8266, that I am trying to connect to the Wifi.

One major thing is, that my landlord decided to use the worst Wifi SSID and PW.

The SSID contains an ä and a whitespace, and the PW an @.

I scanned successfully the Wifi with the board and the prewritten code and the Wifi shows up.

Now I am using the Wifi client basic, just to check if any connection can established.

I tried to write all special characters in Unicode, but still no change.

Here is my code:

```
/*
This sketch sends a string to a TCP server, and prints a one-line response.
You must run a TCP server in your local network.
For example, on Linux you can use this command: nc -v -l 3000
*/

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#ifndef STASSID
#define STASSID "XXX XXäXX"      //original SSID
#define STASSID "XXX XX\u00e4XX" //Unicode SSID
#define STAPSK "XXX@XXX"        //Orginal PW
#define STAPSK "XXX@XXX"        //Unicode PW
#endif

const char* ssid = STASSID;
const char* password = STAPSK;

const char* host = "192.168.1.1";
const uint16_t port = 3000;

ESP8266WiFiMulti WiFiMulti;

void setup() {
  Serial.begin(115200);

  // We start by connecting to a WiFi network
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, password);

  Serial.println();
  Serial.println();
  Serial.print("Wait for WiFi... ");

  while (WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  delay(500);
}

void loop() {
  Serial.print("connecting to ");
  Serial.print(host);
  Serial.print(':');
  Serial.println(port);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;

  if (!client.connect(host, port)) {
    Serial.println("connection failed");
    Serial.println("wait 5 sec...");
    delay(5000);
    return;
  }

  // This will send the request to the server
  client.println("hello from ESP8266");

  // read back one line from server
  Serial.println("receiving from remote server");
  String line = client.readStringUntil('\r');
  Serial.println(line);

  Serial.println("closing connection");
  client.stop();

  Serial.println("wait 5 sec...");
  delay(5000);
}

```

Thank you for any help, and greetings from Sweden.

# Answer
I don't have enough reputation to comment, otherwise this would be a comment, but you could ask your landlord to change the password. If he says no, you could say one of your devices can't type that character.
> 1 votes

# Answer
Start with a known simple SSID, and try this:

```
#define STASSID "mywifi" // For example this is the known SSID that connects
... ... ...
const char ssid[] = "mywifi";

```

If that works, then you can replace the `char` with hex.

```
const char ssid[] = {0x6D, 0x79, 0x77, 0x69, 0x66, 0x69};

```

If that works then you can replace the hex bytes with your actual SSID. And in the place of `ä` use those two hex bytes as jsotola mentioned, `0xC3` and `0xA4`.

---

Edit 1:

From the comment, it looks like the receiver never connects to the WiFi. In the receiver hardware, try something simple and see if it gets connected at all or not.

```
#include <ESP8266WiFi.h>

void setup()
{
  Serial.begin(115200);
  Serial.println();

  WiFi.begin("network-name", "pass-to-network");

  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {}

```

Check what output you get.
> 0 votes

---
Tags: esp8266, wifi
---


title: arduino nano fried (TWICE!!)

text:
# Question
Title: arduino nano fried (TWICE!!)
Having the skill to be stupid has managed to fry not one but 2 Arduino nano boards in the same place (I think it is because this part that smoke came out from of has a lump on it that was not there before). I have heard that Arduino boards are open source, but I have not found anything explaining which part I have fried. Therefore, I don't know the function, so I cannot replace it. It would be greatly appreciated if anyone could point out the part name or a link to a shop with it in stock so I could purchase it or give me a copy of the PCB schematics to make my own Arduino. I think the part is called the "usb diode" Thanks

# Answer
From the several Arduino Nano schematics I've found, the "fried" part you identify as the USB diode, might be a MBR0520 Schottky diode or a similar diode with a low forward voltage drop. These parts are polarized and should be replaced in the same orientation as the original part.

There is no guarantee that this is the only damaged component on your Nano.
> 1 votes

---
Tags: arduino-nano, hardware
---


# Question
Title: How to ignore garbage values in serial communication between arduino and python
I have `arduino uno`. I am trying to send and receive serial data from arduino to python. I have usb to uart converter. I have connected its `tx` to `rx` of Arduino and `rx` to `tx` of Arduino and `gnd` is connected to `gnd`. I have connected this usb to my laptop where I have written below python script:

```
import serial

ser = serial.Serial(port='COM6')

res = ser.read(10)
print(bytes.hex(res))
data = b"\x7E\x7E\xAA\x03\xAB\x77"
ser.write(data)

ser.close()

```

In above code, I am first reading the serial data, once the data is received, I sent out some hex packets. Below is the Arduino code:

```
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  const byte message[] = {0xAA, 0xBB, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x03 };

  Serial.write(message, sizeof message);
  Serial.println("data sent");
  delay(1000);

  char buffer[6];
  if (Serial.available() > 0)
  {
    int size = Serial.readBytesUntil('\x77', buffer, 6);
    for (int i = 0 ; i < size ; i++)
    {
      Serial.println(buffer[i], HEX);
    }
  }
}

```

In above code, I am first sending out the hex packets (which is received fine on python side) and then it waits for serial data. Once serial data is available to read, it reads it unitll `\x77` and prints out all data. This is received on serial console of Arduino:

You will notice that it has received the data but there are some extra `FF` being received. Why have we recived these `FF`. Also in the python code, we know we are sending data of length 6 so accordingly we have defined `char buffer[6]` in arduino but what if we have to receive data which we do not what length it will be. Please help. Thanks

# Answer
> 4 votes
The effect that you see is type promotion, in the C++ standard called "integral promotion", which happens implicitly. Values of type `char` are cast to `int`. Commonly, and in your case too, the `char` is by default signed.

The values `0xAA` and `0xAB` have a set most significant bit, which commonly designates a *negative* value in two's complement. This holds true for your case. And so these negative values are cast to the same negative values, but in full width of an `int`. In your case, an `int` has 32 bits.

Hex `0xAA` as (by default signed) `char` is `-86` decimal. And `-86` decimal as (by definition signed) `int` is `0xFFFFFFAA` hex.

Printed as hex, all bits are shown. So you see all these `FF`.

The solution is to mask only the interesting lower 8 bits:

```
    {
      Serial.println(buffer[i] & 0xFF, HEX);
    }

```

---
Tags: arduino-uno, serial, python, serial-data
---


# Question
Title: My Led blinks rapidly at the reset and stop blinking instantly
So ive been trying to make a program to test if the pins are working correctly but all it does is make my led blink rapidly on reset. the point of the program is that all my pins are connected to the same led, so blink each one of them once and send the number of the blinked pin to the serial. but the led just doesnt blink and the program gets stuck at the start of pwmoutputtest.

boolean byteinarray(byte array[], byte pbit){
  for(int i=0; i<6; i++){
    if(array[i]==pbit){
      return true;
    }
  }
  return false;
}

void outputtest(){
  for(byte i=2;i<13;i++) {
    pinMode(i, OUTPUT);
  }

  for(byte i=2;i<14;i++){
    if (byteinarray(pwm, i)==false){
      Serial.println(i);
      digitalWrite(i, HIGH);
      delay(1000);
      digitalWrite(i, LOW);
      delay(1000);
    }
  }
}

void analogoutputtest(){
  for(byte i=0;i<5;i++) {
    pinMode("A"+i, OUTPUT);
  }

  for(int i=A0;i<A5;i++){
    Serial.println(i);
    digitalWrite(i, HIGH);
    delay(1000);
    digitalWrite(i, LOW);
    delay(1000);
  }
}

void pwmoutputtest (){
  for(int i=0; i<6; i++) {
    pinMode(pwm[i], OUTPUT);
  }

  for(int i=0; i<6; i++) {
    Serial.println(pwm[i]);
    for(int j=0; i<200; i++){
      analogWrite(pwm[i], j);
      delay(5);
    }
    analogWrite(pwm[i], 0);
  }
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  outputtest();
  pwmoutputtest();
}```
</code></pre>

# Answer
> 1 votes
You have connected all the pins together. Then you are setting one pin HIGH (5V) at a time while all others are LOW (0V, aka ground).

So basically you are creating a short each time you set a pin HIGH. The current won't flow through the LED to ground (which would light up the LED), but the current flows through the pins, that are LOW, to ground. And since you didn't mention any current limiting resistors, you are straining the pin output hardware with this massively. If you do this too much, the pin output hardware will fail at some point, making the pin unusable for digital IO.

Instead of keeping the other pins LOW, you need to keep them at high impedance , which means configuring them as INPUT. That would be a function like this (not tested):

```
void outputtest(){
  for(byte i=2;i<13;i++) {
    pinMode(i, INPUT);
  }

  for(byte i=2;i<14;i++){
    if (byteinarray(pwm, i)==false){
      Serial.println(i);
      pinMode(i, OUTPUT);
      digitalWrite(i, HIGH);
      delay(1000);
      digitalWrite(i, LOW);
      pinMode(i, INPUT);
      delay(1000);
    }
  }
}

```

---
Tags: arduino-uno
---


# Question
Title: How to converte EPOCH time into time and date on Arduino?
I have a question on how to convert EPOCH timestamps, which I receive as char\*, into `DD.MM.YYYY` and `HH:MM:SS` format separately?

Here is more information on my Arduino project: The Arduino is receiving through an ESP8266 module three different EPOCH timestamps from the OpenWeatherMap API for sunrise, sunset and the current date. I want these 3 pieces of information to be displayed on a 1,3" OLED display, the date in `DD.MM.YYYY` and the other two timestamps in `HH:MM` format.

I was looking in several time libraries like TimeLib and RTC for a solution, but I wasn't able to find one.

That's my code fragment on receiving the timestamps:

```
DynamicJsonBuffer jsonBuffer(4096);
JsonObject& root = jsonBuffer.parseObject(client);

const char* date = root["dt"];
const char* sunrise = root["sys"]["sunrise"];
const char* sunset = root["sys"]["sunset"];

```

How to convert those timestamps?

# Answer
> 3 votes
Epoch are seconds from 1970-01-01. Get it from JSON as unsigned long. They are a number in the returned JSON (no `"`)

from https://openweathermap.org/ doc:

`"sys":{"country":"JP","sunrise":1369769524,"sunset":1369821049},`

---

to convert it to string with TimeLib.h:

```
#include <TimeLib.h>
#include <ArduinoJson.h>

void setup() {

  Serial.begin(115200);

  const char* json = "{\"country\":\"JP\",\"sunrise\":1369769524,\"sunset\":1369821049}";

  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(json);

  unsigned long t = root["sunrise"];

  char buff[32];
  sprintf(buff, "%02d.%02d.%02d %02d:%02d:%02d", day(t), month(t), year(t), hour(t), minute(t), second(t));

  Serial.println(buff);
}

void loop() {
}

```

# Answer
> 0 votes
I'm was having a similar challenge with my custom Wifi library `m_wifi..cpp` for the ESP32 ( see here )

After reading and googling I ended up coding the solution below:

```
ESP32Time rtc(0);
long int epochTime;
...

rtc.setTime( epochTime  );
Serial.println( rtc.getDateTime(true) );

```

---
Tags: time, convert
---


title: Speedometer for car

text:
# Question
Title: Speedometer for car
I want to build a speedometer for a car using Arduino UNO. For the display, I'm using an LCD screen that will be located behind the steering wheel. For context, I'm building the speedometer for a Tesla, which does not have a speedometer behind the steering wheel. Because this speedometer is for a car, and for driving purposes, I need the speedometer to be very accurate. I am planning to calculate the current speed of the car using a GPS module, but not sure **which GPS module would have the best accuracy but is not too expensive?** I am also open to other methods that may be available to measure current speed.

# Answer
I built a GPS speedometer for an old Corvette using the TinyGPSPlus library with a GY-NEO6MV2 NEO-6M with an Arduino Uno. http://www.carols62.com/speedo Speed readings were very accurate.
> 0 votes

---
Tags: arduino-uno, lcd, gps, speed
---


# Question
Title: connecting 4 scales to microcontroller for use as dog force plate
I want to build a system to measure the force distribution between the 4 legs of a dog. I plan to use this method:

http://www.rehabtools.org/scales.html

This setup seems to be for 1 scale. I want to use 1 scale for each leg, so that adds up to 4 scales. I assume I need 4 amplifiers, but can I connect those 4 to one microcontroller?

I currently have the Arduino Uno coming, will that work?

The connection from the 4 legs of the scale to the load amplifier I understand. But how do I go about having the 4 readings and then getting them in the software program?

Any advice would be greatly appreciated.

# Answer
If you follow the links in the tutorial you linked to, you will find

The library uses a bit-banging method (`pulseIn()`) for communicating with the amplifier. This method can work with *any* of the Arduino digital pins. In the example sketch you have the instantiation:

```
HX711 scale(3, 2);

```

where the numbers (3, 2) tell the library the pins you want to use.

Given all this, you should be able to interface four identical scales simply by using four separate pairs of pins. For example:

```
HX711 scale1(3, 2);
HX711 scale2(5, 4);
HX711 scale3(7, 6);
HX711 scale4(9, 8);

```

The adapt the example sketch to read all the scales.
> 1 votes

---
Tags: arduino-uno
---


# Question
Title: does ATtiny85 handles the size of the libraries mentioned below `<MD_Parola.h>` and `<MD_MAX72xx.h>`
The ATtiny85 microcontroller has very limited resources, including program memory (flash) and RAM. It has 8KB of flash memory and only 512 bytes of RAM.

does ATtiny85 handles the size of the libraries mentioned below `<MD_Parola.h>` and `<MD_MAX72xx.h>`

what microcontroller would you recommend?

https://www.arduinolibraries.info/libraries/md\_max72-xx

https://reference.arduino.cc/reference/en/libraries/md\_parola/

# Answer
> 3 votes
It is very simple to test.

1. Install the ATTinyCore in IDE
2. Install the MD\_Parola library in Library Manager. It will install the MD\_MAX72xx library too
3. Go to IDE Examples menu, search for the MD\_Parola library examples and select the Parola\_HelloWorld example
4. select the ATTiny85 in the Tools menu
5. hit Verify

output:

```
avr/bin/ld: region `text' overflowed by 11820 bytes
collect2: error: ld returned 1 exit status
exit status 1
Error compiling for board ATtiny45/85 (Optiboot)

```

which means it will not fit.

If you select Arduino Uno in the Tools menu and Verify you get:

```
Sketch uses 20692 bytes (64%) of program storage space. Maximum is 32256 bytes.
Global variables use 288 bytes (14%) of dynamic memory, leaving 1760 bytes for local variables. Maximum is 2048 bytes.

```

---
Tags: attiny85
---


title: SSD1306 OLED LCD Library Errors

text:
# Question
Title: SSD1306 OLED LCD Library Errors
Recently i bought an "Serial 128X64 OLED LCD Display SSD1306".

I searched for available libraries and tried to find the best one to use before actually experimenting with this device.

I came across a hackaday article mentioning a "HIGH SPEED SSD1306 LIBRARY"

So that drew my attention and got on with it. This is the link to the afforementioned library for the Arduino DUE.

DayOLED\_SSD1306

Right from the beginning i had some trouble with the wiring. I wired 3.3 volts to input, GND, RES, DC, CS pins are wired according to the library example.. But the library contains no info on how to wire D0 and D1 pins. Since this OLED works both I2C and SPI, i thought maybe these two pins are ommited.

So i hooked it up, load up the example code and got compilation errors.

Here are the errors:

```
C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp: In member function 'virtual void DayOLED_SSD1306::drawPixel(int16_t, int16_t, uint16_t)':

C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp:114:14: error: 'swap' was not declared in this scope

     swap(x, y);

              ^

C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp: In member function 'virtual void DayOLED_SSD1306::drawFastHLine(int16_t, int16_t, int16_t, uint16_t)':

C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp:598:16: error: 'swap' was not declared in this scope

       swap(x, y);

                ^

C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp: In member function 'virtual void DayOLED_SSD1306::drawFastVLine(int16_t, int16_t, int16_t, uint16_t)':

C:\Users\User1\Documents\Arduino\libraries\DayOLED_SSD1306-master\DayOLED_SSD1306.cpp:666:16: error: 'swap' was not declared in this scope

       swap(x, y);

```

Anybody got a clue what is going on with the swap() function? Maybe it's deprecated?

Anyone has managed to get this library working? Or maybe has managed to get this Display working even with another library?

# Answer
I've had good success with the u8g2 library for the small I2C OLEDs. u8g2 on GitHub
> 1 votes

# Answer
I have managed to get this somewhat working with these 4 libraries: #include \<SPI.h\>, #include \<Wire.h\>, #include \<Adafruit\_GFX.h\>, #include \<Adafruit\_SSD1306.h\>. These should all be in the include library in the ide. Hope this helps!
> 1 votes

---
Tags: arduino-due, lcd, library
---


# Question
Title: Adafruit_SSD1306.cpp fatal error: pgmspace.h: No such file or directory using SSD1306 on Arduino Nano RP2040
I am currently trying to get a SBC-OLED01 display to run under my Arduino Nano RP2040. I use the official example from Adafruit, attached is the code.

```
/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x64 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 };

void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}

```

I downloaded the optimized Adafruit GFX library from github and installed it as described. As board I use Arduino Nano RP2040 from Arduino Mbed OS in V3.4.1.

Now when I compile the code, I get the following error:

```
[...]Arduino\libraries\Adafruit_SSD1306\Adafruit_SSD1306.cpp:42:10: fatal error: pgmspace.h: No such file or directory
#include <pgmspace.h>
          ^~~~~~~~~~~~
compilation terminated.

exit status 1

Compilation error: exit status 1

```

Unfortunately, I have not found a solution on the net how to solve the problem, which is why I have now written this question. I tried the "normal" GFX library from the package manager instead of the optimized one, with the same result. I guess the error is in the Adafruit\_SSD1306 library, so it doesn't make any difference anyway.

# Answer
What is happening is, in the library you have included, another library is included. There are 2 solutions: delete the `#include pgmspace.h` within one of those libraries or just get `pgmspace.h`. Hope this helps!
> 2 votes

---
Tags: adafruit, ssd1306, arduino-nano-rp2040
---


# Question
Title: Modbus rtu server library which logs the requests
Kindly seek help. In my project I want to ‘fake’ a smart meter via Modbus rtu server for a solis inverter.

Somehow the solis realizes that I am faking him, so I am wondering which Modbus rtu server library you can recommend to allow me seeing what registers the solis is querying? As of now I am using Emilianov Modbus-8266 library to run the rtu server/slave and platform is an esp32 with max3485.

---

So let me elaborate. Mine Solis simply sucks during low-light conditions. It can not decide if it shall charge or discharge, resulting in heavy oscialltion, jumping from feeding to much into grid and then taking to much from grid - peaks are ranging from +800Watt to -800Watt when it starts oscillating.

On the com-port you are true - here the solis acts as slave. But on its RS485 meter-port I understand the Solis acts as master and the smart meter is the slave.

My goal is to fake the smart-meter.

So topology would be:

Solis(master)-----(slave)ESP32(master)-----(slave)smartmeter

So I am looking for a solution that any read-request from the Solis is transparently passed-through to the smart-meter. But I want one register intercpeted (most likely total-active-power, which to mine understanding is the only value the solis want to bring back to zero in its control-loop).

As of now I can read the Solis and ther Seplos BMS via ESP32/RS485 successfully. I even have a running slave which I thought shall be good enough to get accpeted as fake-smart-meter, but it is not. The solis is asking for more register then my fake-solution is providing. hence mine ask, which ESP32 slave/server modbus library can print any request its arriving.

If my view is incorrect, kindly get me corrected.

# Answer
Try looking at this as a starting point

https://github.com/straga/Smart-Meter-Gateway

My approach has been different but will come back to this if my current approach fails.
> 1 votes

---
Tags: datalogging, modbus
---


# Question
Title: Using interrupts as flags/latches without any ISR
I've got an Arduino Mega periodically running an (~400ms) operation that is sensitive to repeatable timing, so I don't ever want it to be interrupted. But, I'd like to be able to use an interrupt flag from one of the built-in peripherals (PCINT, comparator, etc.) to check afterward if an external event (say, a pin going high momentarily) ever happened during the operation.

That is, it'd be nice if I could just treat one of the interrupt flags as a kind of async hardware latch that I can check (and reset) at my next earliest convenience instead of having my code interrupted.

It wouldn't be hard to add a discrete latch IC, but I was hoping I could get away with just using what was already built into the AVR without adding more hardware (and using an extra pin to reset the latch).

Looking at the documentation for each of the peripherals, it was unclear whether the flags will still be set when the interrupts themselves are disabled. I saw a mention someplace of an "EMPTY\_INTERRUPT" macro, but I'm guessing that still generates an interrupt vector with a single "return" instruction, so I'd still be losing cycles to the interrupt preamble, etc.

Can this be done without any ISR being called at all? Or do I need to add more hardware? I'm not sure wrapping the long-running task in noInterrupts()/interrupts() is a choice unless that also doesn't interfere with UART receiving. Thanks!

# Answer
> 6 votes
The answer to your question is: yes, you can use an interrupt flag even if the interrupt itself is disabled. The flag is raised whenever the peripheral detects the relevant event. You can check it at your convenience.

As for resetting the flag, the way to do it varies from flag to flag. Most are reset by writing a 1 bit to it (yes, it is kind of backwards, but that's how it works). Others are reset by removing the condition that raised them in the first place (e.g. the USART Data Register Empty interrupt). You will have to check the datasheet for the specific interrupt flag you are interested in.

That being said, I would question your idea that you don't want your code to be interrupted at all. As Nick Gammon writes in a comment, the minimum time to execute an ISR is 2.625 µs. This is about 0.0007% of the process period. If your operation is so time-sensitive that it cannot tolerate this kind of variation, then it cannot run on a Mega *at all*. The ceramic resonator that clocks a Mega has a typical accuracy of about 0.1%, which is 150 time worse than the effect of a minimal interrupt. Even if you calibrate this oscillator against an atomic clock, in only a few hours its frequency will drift past 0.0007%.

In addition, if you want the code to work with no interrupts, you will have to disable them explicitly with `noInterrupts()`. You will then loose the Arduino timing function `millis()`, `delay()`, etc. And you will not be able to receive data from the UART unless you write your own code that polls the “receive complete” UART interrupt flag and then grabs the data from the UART data register.

# Answer
> 2 votes
> I'm not sure wrapping the long-running task in noInterrupts()/interrupts() is a choice ...

That's right. Turning off interrupts would stop the timing/delay working so you wouldn't be able to time your 200 µs (unless you use delayMicroseconds).

> Each exposure is only a couple hundred microseconds, so even 2.625µs of periodic jitter would increase the exposure time by at least 1%.

I think you might be better off using a hardware timer to time the exposure, rather than a loop. I generated VGA signals in a post here where I wanted the horizontal and vertical sync to have no jitter whatsoever. The timers can be set to turn on, or off, an output pin, so that could be used to control your shutter. Then it would be completely precise.

> I didn't know the UART interrupts the CPU ...

Incoming (or indeed outgoing) serial communications causes interrupts. The timers cause interrupts. In particular, if you haven't disabled it, Timer 0 generates interrupts which are counted so that millis() and micros() can function. You can disable interrupts but then timing exact intervals becomes problematic. My suggestion of using the timer hardware will give the most reliable and repeatable performance, in the same way that my VGA signal generation was completely without jitter.

Because of the timer interrupts, if you haven't disabled interrupts, some of your exposures may have the timer interrupt in the middle (making them longer) and some not, or at least, a different number of interrupts. This will make the exposure time non-precise.

---

Another approach would be to turn off interrupts for the exposure only (ie. your 200 µs, not the 400 ms you mention), and then use delayMicroseconds() to time the exposure. Then when interrupts are back on the closure of a switch (an external interrupt) could be processed by a normal ISR. This might add slight differences to the time *between* exposures, but the exposure time itself would be constant.

Even with interrupts off, an external interrupt is "remembered" and would be processed when interrupts are enabled again.

---

This is turning into an XY problem somewhat. It would have been better to mention your exact requirements (the photography part) rather than launching into questions about using interrupts as latches.

---

# Example code

To demonstrate how you can do this with timers, I have written some sample code:

```
// Example of strobing a flash FRAMES_TO_TAKE times to take pictures
// The time between each flash is in TIME_BETWEEN_SEQUENCES
// The flash "on" time is in STROBE_ON_TIME
//
// For Atmega328P (Arduino Uno and similar)
// and Atmega2560 (Arduino Mega)

// Author: Nick Gammon
// Date: 4 July 2023

const byte FRAMES_TO_TAKE = 12;
const byte CPU_CLOCK_SPEED = 16;  // MHz

const unsigned long TIME_BETWEEN_SEQUENCES = 20000;  // µs
const unsigned long STROBE_ON_TIME = 200;  // µs
const byte PRESCALER = 64;

// set correct output pin
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
  const byte FLASH_PIN = 10;  // OC1B (Output Compare Timer 1: B side) - Atmega328P
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  const byte FLASH_PIN = 12;  // OC1B (Output Compare Timer 1: B side) - Atmeg12560
#else
  #error Processor not supported
#endif

volatile byte count;  // how many times it fired

// Timer 1 Compare "B" ISR vector
// count up to the required number of exposures, then stop
ISR (TIMER1_COMPB_vect)
  {
  if (++count > FRAMES_TO_TAKE)
    {
    // stop timer 1
    TCCR1A = 0;
    TCCR1B = 0;
    } // end if number of frames reached

  }  // end of TIMER1_COMPB_vect

// does one sequence of FRAMES_TO_TAKE shots and then stops
void startSequence ()
{
  // stop timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TCCR1A = bit (WGM10) | bit (WGM11);  // fast PWM - top at OCR1A
  TCCR1B = bit (WGM12) | bit (WGM13);  //  (mode 15)

  TCCR1A |= bit (COM1B1);   // Set OC1B during duty cycle

  OCR1A = ((TIME_BETWEEN_SEQUENCES * CPU_CLOCK_SPEED) / PRESCALER) - 1;  // total cycle time
  OCR1B = ((STROBE_ON_TIME * CPU_CLOCK_SPEED) / PRESCALER) - 1;          // duty cycle time
  count = 0;

  TCNT1 = 0;    // reset timer count

  TIFR1   = bit (OCF1B);    // clear "compare B" flag
  TIMSK1  = bit (OCIE1B);   // interrupt on compare B match
  TCCR1B |= bit (CS10) | bit (CS11);     // start timer with prescaler of 64
} // end of startSequence

void setup ()
  {
  // stop timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  // set up pin so we can toggle it
  pinMode (FLASH_PIN, OUTPUT);
  digitalWrite (FLASH_PIN, LOW);

  // for debugging etc.
  Serial.begin (115200);
  }  // end of setup

void loop ()
  {
  startSequence ();
  delay (20);
  Serial.print ("But does it get goat's blood out?\n");
  delay (1000);
   }  // end of loop;

```

---

# Code explanation

What this code does is use Timer 1 to time the intervals you require. Based on what you posted earlier, I have assumed:

* 12 exposures in a "session" (FRAMES\_TO\_TAKE) - you said "a dozen or so"
* Exposures are to be 20 ms apart (TIME\_BETWEEN\_SEQUENCES) - so a session is 240 ms
* Exposure time is to be 200 µs (STROBE\_ON\_TIME) - you said "a couple of hundred microseconds"
* You want to do other stuff like serial comms, checking switches etc.

The function `startSequence` initiates the timer which has a period of 20 ms and a duty cycle of 200 µs. At the completion of a duty cycle the ISR is called which counts duty cycles. At the end of the nominated number (12) the timer is turned off.

The output pin (pin 12 on the Mega, pin 10 on the Uno) is turned on during the duty cycle (ie. for 200 µs and off the rest of the time). Because this is done in hardware there is no jitter at all. In my demo I am doing serial comms to show that the intervals are exactly correct, despite the interrupts caused by the comms.

---

# Logic analyser output

The logic analyser screenshot shows that the intervals are exactly correct (to within a fraction of a percent). In any case the intervals are consistent with each other.

This second screenshot shows that there are 12 pulses in that interval.

I have used a prescaler of 64 to get a nice long interval that can be timed (without overflowing the 16-bit counter size). This gives a granularity of 64 clock cycles (4 µs).

---

# Timer 1 cheat sheet

---

---
Tags: interrupt, isr
---