Arduino SD Card Reader

I recently had a project that required having multiple files that were used to control an animatronics display. Each file was a scene for the display and the code simply would play 1 scene, pause, and then play the next scene.

I used the Adafruit MicroSD card breakout board. This assembly is inexpensive, works with in either 3V or 5V systems, and has an SPI interface. A standard Arduino SD library was used for access. I used the MicroSD card with both a Nano and ESP32.

The SD library supports FAT16/FAT32 file structures. So preparing the microSD card with the file used a Windows 10 computer with a SD reader. The card was formatted and the files were copied.

Accessing the files were simplified using the Arduino SD library. Using root (declared as File) that was pointed to the root directory (“/”) the openNextFile() method was used to get the next filename to use. When we were at the end of the directory, a rewindDirectory() was performed. The filename was then checked for the correct extension.

showFile = root.openNextFile();   // get next file
if (!showFile) {                  // end of directory start over
  root.rewindDirectory();         // beginning
  showFile = root.openNextFile(); // file
}
filename = showFile.name();       // check filename ends in ".BIN"
filename.trim();                  // remove whitespace
filename.toUpperCase();           // all upper case
if(filename.endsWith(".BIN")) {   // check for bin file
  break;    // found good file  
}
else {
  showFile.close();               // close unneeded file
}

Bytes were read from the file using the read() method. It should be noted that the documentation states that EOF is returned at the end of the file. Actually the number of bytes read is returned. When the value is zero (0), then the end of file was encountered.

I used the microSD card with both a Nano (5V) and ESP32 (3V3) and worked without any issues on both systems. For my application I had to read a small buffer (3 bytes) at a 30 frames per second rate (33.3 milli-seconds).

Finding Unknown Resistor Value using Voltage

In the world of sensing, there are many sensors that change their resistance value based on the environment. Knowing the sensor resistance provides a measurement of the environment being measured. These variable resistor sensors include:

  • Thermistor – a variable resistor that changes value with the surrounding temperature changes. There are two types: negative temperature coefficient (NTC) and positive temperature coefficient (PTC). The NTC thermistor decreases in value when the temperature increases the the PTC thermistor increases in value when the temperature increases.
  • Magneto Resistor – a variable resistor that changes value when a magnetic field is applied. When the magnetic fields increases, the resistance increased. When the magnetic field decreases, the resistance decreases.
  • Photoresistor – a variable resistor that changes value based on light energy. The photoresistor resistance decreases when light energy is increased and increases when light energy is decreased.
  • Humistor – a variable resistor that changes value based on humidity.
  • Force Sensitive Resistor – a variable resistor that changes values based on the force that is applied.

Thermistors are variable resistors that are more sensitive to temperature changes then a standard circuit resistor. The simple first order thermistor relationship between resistance and temperature is:

ΔR = kΔT, where
ΔR is change in resistance (in ohms)
Δ is change in temperature (in kelvin)
k is first-order temperature coefficient (in ohms/kelvin)

In general the first order approximation is only accurate over a limited temperature range. The Steinhart-Hart equation is a widely used third order approximation that improves accuracy to less than 0.02 oC over a much wider temperature range.

where
T is absolute temperature (in kelvins)
R is the resistance (in ohms)
a, b, and c are coefficients

NTC thermistors can also be characterized with the Β (beta) parameter equation, which is just a specialized case of the Steinhart-Hart equation.

where
T is absolute temperature in kelvins
T0 is 298.15 K (25 oC)
R0 is resistance at T0
R is the resistance

Having the B parameter and measuring the thermistor resistance, the temperature can be determined. But most embedded systems don’t measure the resistance directly. So the question is how do we measure the thermistor resistance. The answer is to use a voltage divider. Measuring the voltage divider voltage, which is common using analog to digital converters, gives us a way to get the thermistor resistance.

Remember that a voltage divider is two series resistors in this case connecting power and ground. The voltage between the two resistors is given by:

NTC Thermistor Voltage Divider

VOUT = VIN x (R1 / (R1 + R2))
R2 = R1 x (VIN/VOUT – 1), where
R2 = unknown thermistor resistance (in ohms)
R1 = known resistance (in ohms)
VIN = known input voltage (in V)
VOUT = measured voltage between resistors (in V)

Generally NTC thermistors have a nominal resistance at 25oC. Most common is either 10K or 100K ohms. When picking the known resistor R1, the value should match the nominal thermistor resistance, e.g. for a 100K thermistor, R1 should be 100K.

Using an embedded controller like an Arduino UNO or Nano, the code is very simple to convert the sensed input voltage to the thermistor resistance, to a temperature as shown in following code segment.

  // in setup, one time calculation
  Rinf = NTCRESISTOR * exp(-1*BETA/298.15);


  // in Arduino loop
  tempIn = analogRead(TEMPPIN);  // 0 to 1023 values

  // find thermistor R
  // SERIESRESISTOR = R1, 1023.0 = VIN, tempIn = VOUT
  Rth = SERIESRESISTOR * ((1023.0/tempIn) - 1);
  // calculate temp in K and convert to C
  tempC = BETA/(log(Rth/Rinf)) - 273.15;

One final item to consider with a voltage divider is the input impedance of the measuring device. To limit the impact of input impedance on circuit performance, generally you want the input impedance to be at least 10 times the value of R1 in the circuit above. The input impedance is in parallel with R1 so if the input impedance is only 100K, then the effective value of R1 in our circuit is only 50K, which greatly affects the measurement and calculations.

Op Amp Voltage Follower

One way to solve this problem is to use a voltage follower op amp circuit. This circuit provides unity gain (voltage divider Vout equal op amp Vout), has a low output impedance, and very large input impedance. It is important to select an op amp that has stable unity gain.

MOL-AU5016 MP3/WAV Player

I recently had a project where a client wanted to interface an Arduino Nano and ESP32S with MDFLY Electronics AU5016 Embedded SD/SDHC Card MP3/WAV Player Module. The player is controllable using either a digital or parallel interface. It’s features from the datasheet are:

AU5016 Assembly without Headers
  • High performance 32Bit CPU
  • High quality on-chip stereo DAC
  • Decodes MP3/WAV/APE audio format
  • Supports bitrate from 32Kbps to 320Kbps
  • Supports MicroSD/HC memory card up to 32GB
  • Low-power operation
  • Ultra-low background noise
  • TTL serial interface
  • Input voltage: 5VDC
  • Compact design

Some of the limitations of the AU5016 include a maximum of 200 tracks (files) stored/used on the self contained SD card, some of the digital interface commands are required to be repeated until the proper response is received, 5VDC only, and the datasheet is missing some key electrical properties (e.g. Busy IOH). The good stuff, standalone audio playback, works with multiple file types, high performance, and very compact.

I used the digital interface with the Arduino Nano and ESP32S. The digital interface uses start/stop protocol at a default 9600 baud 8N1. The signal levels are TTL (5V only). There is a discrete Busy signal when audio is playing. All of the commands are a single byte.

For this application the AU5016 contained the audio files on an SD card while the Nano/ESP32S SD card contained control information. The control and audio are time synchronized using a third party application. When the control starts the audio is also started. What clock drift there is between the processor and AU5016 is not noticeable in this application’s 2 minute window.

For the Arduino code interface I created an C++ object. This simplified using the AU5016 as typical in the OOP environment. Differences between the Nano and ESP32S was the object used SoftwareSerial on the Nano while the ESP32S used the HardwareSerial. Thankfully these objects have similar interfaces so the code changes were minimal. The object I provided implemented most of the AU5016 commands.

AU5016 Object Definition

// AU5016 object class
class AU5016 {
  public:
    AU5016(int uartNum, int RXPin, int TXPin, int BusyPin); // AU5016 object
    int StartTrack(int track); // starts playing track, error return
    void Stop(void);           // stops tracking playing
    int SetVolume(int level);  // sets volume level, returns volume level
    int VolumeUp(void);        // increases volume level + 1, returns volume level
    int VolumeDown(void);      // decreases volume level -1, returns volume level
    void Mute(void);           // mutes output
    int PlayPause(void);       // pasues play track, un-pauses track, error return
    int SetEQ(int EQ);         // sets EQ, returns error
    int RepeatMode(int Mode);  // sets repeat mode, return error
    int TrackAfterPower(int Mode);  // sets track to play after power on, return error
    int SetBusyStatus(int Mode);    // sets busy output active high or low, return error

  private:
    HardwareSerial *_AudioSerial;   // UART interface to AU5016
    SemaphoreHandle_t  _sema_v;     // multi task access control
    int _Busy;                      // busy pin
    int _audioSendCommand(int cmd); // send a command, return response
    int _audioGetResponse(void);    // get command response, return response
};

Hardware interfacing to the AU5016 for the Nano was simple since both devices are 5V. For the ESP32S, although in input voltage is 5V, the processor and it’s IO is actually 3.3V. I used level TXS0108E translator between the ESP32 and AU5016. Luckily the SD card used with both processors works at either 5V or 3.3V.

Issues

I only encountered one real issue working with the AU5016. I originally use a 32GB SDHC formatting FAT32 for the audio. I couldn’t get the AU5016 to recognize the card. The response was 0xAB, No Memory card. Working with MDFLY tech support they recommended working with a smaller SD card. So I tried a 2GB SD FAT16 and 8GB SDHC and both appeared to work.

Excel Selenium Element Test

During web scraping sometimes an expected element is not present. This recently happened during a web scraping project when the client input data was not correct causing the page to load with an error and didn’t have the expected data to scrape. The web scraping was being done in Excel using Selenium and Chrome browser. The error handling can be done a couple of different ways. I used the approach of assigning a webelement object to the expected element and then test if the object is Nothing.

Element Test Code

' class that has distance in miles
Set miClass = driver.FindElementByClass("mi", timeout:=0, Raise:=False)
If Not miClass Is Nothing Then
    {Element Present}
Else
    { Element not Present, Error Handling}

This project required loading almost 20,000 web pages. Sometimes the web page url failed to load. Although not directly tied to testing for the present of an element this error case still needed to be handled. For this case Excel VBA error handing is used. Multiple attempts are made to load the url. If the web page loads then the “attempt loop” is exited early. If the maximum number of attempts is reached, then error handling is used.

In an Excel VBA, On Error is used to control the error handing. Before making the web page loading attempt On Error GoTo and Exit For are used to manage the multiple attempts.

Web Page Loading Error handling

' attempt to get website
On Error GoTo tryAgain    ' if error GoTo tryAgain
For try = 1 To Attempts   ' number of attempts to try (const)
    ' get website
    driver.Get url        ' attempt to load web page, if error GoTo tryAgain
    Exit For              ' web page loaded exit For
tryAgain:
Next try                  ' next attempt
On Error GoTo 0           ' reset Error handling to stop on error
                    
If (try <> (Attempts + 1)) Then   ' Test if maximum attempts were reached
    {Page Loaded, Do Stuff}
Else
    {Page Didn't Load, Error Handling Stuff}

Calculating Moon Phase

Recently I published a Fitbit app that calculates the Moon phase and then displays the proper phase image and illumination value. The user can select a different date or simply increment the date using buttons. Some feedback I received was that the app was fast and they wanted to understand why?

First, the app performs all calculations on the watch. No phone resources are need No GPS. No Internet. The calculations are based on a dateNow date object (this object is settable by the user though the interface that is not part of this discussion).

The calculation is based on an article I found by SubsySTEMs while performing an Internet search. Their paper broke the Moon phase calculation into a series of steps, which was easy to follow and translated directly to JavaScript. The output of their calculation is the number of days since the last New Moon. From there the Moon phase is determined. There are eight Moon phases (in order from New):

  • New
  • Waning Crescent
  • Third Quarter
  • Waning Gibbous
  • Full
  • Waxing Gibbous
  • First Quarter
  • Waxing Crescent

Using the days since the last New Moon you can find the phase but beware, each phase isn’t just 1/8 of 29.53 days. Shown below is the JavaScript that implements the attached paper.

function calMoonPhase() {
  // calculate moon phase for date
  let month = dateNow.getMonth() + 1;
  let year = dateNow.getFullYear();
  if(month < 3) {
    year = year - 1;
    month = month + 12;
  }

  let a = Math.floor(year/100);
  let b = Math.floor(a/4);
  let c = 2 - a + b;
  let d = dateNow.getDate();
  let e = Math.floor(365.25*(year+4716));
  let f = Math.floor(30.6001 * (month + 1));
  let jd = c + d + e + f - 1524.5;
  let daysSinceNew = jd - 2451549.5;
  let newMoons = daysSinceNew / 29.53;

  <...Determine Moon phase...>
  <...Determine image to show...>
  <...Calculate illumination...>


}

Excel Selenium Web Scraping Error 33

I’ve done Chrome web scraping in both Python and Excel using Selenium on a Windows 10 platform. I have also web scraped in Python using BeatifulSoup4. A new client had a requirement to find the distance between two zip codes. The client had created an Excel worksheet with a matrix of zip codes (vertical) and cities (horizontal). I found a web site that calculates the distance between two zip codes in a web page. The zip codes are part of the web page URL I converted the cities to city airport zip codes so I just needed to read rows and columns to create the URL and the scrape the results. Easy right?

Error 33

I implemented the VBA code and got a runtime error 33 when opening the Chrome browser. I checked other Excel scrapers that I had written and they all failed. So something was wrong with my Excel environment. An Internet search indicated that the version of Chrome and the chromedriver were not the same. I remembered that I had created a directory for Selenium webdrivers and added it to the system path. I updated the driver to match the Chrome version and I still got the same runtime error. I then tried one of my Python scrapers and they worked! So my problem was with Excel. I continued my search and couldn’t find a resolution until….

Excel Selenium Environment

To setup the Excel environment you need to add SeleniumBasic library, which I already had and the version hadn’t changed since 2016. To add the library to your VBA you need to select the Selenium Type Libary (editor Tools > References…). As I re-read the installation instructions I found that the driver needs to be placed in the same directory as the SeleniumBasic library, which for me was not the webdriver directory I had already updated. That was the difference. Excel did not use the system path for the webdriver.

Once I updated the chromdriver in the SeleniumBasic directory very thing started working again. Watch out for Chrome updates. For my environment I need to update the driver in two locations – the system path directory (Python) and the SeleniumBasic directory (Excel). A good description on setting up Excel to work with Selenium can be found at myOnlineTrainingHub.

Sunrise/Sunset Fitbit App Released

Anyware, LLC Fitbit app for displaying the sunrise and sunset times were approved and published on the Fitbit Gallery.

The Sunrise/Sunset app displays the sunrise and sunset times for the users current location and time settings. The initial date displayed is the current date. The date display is a button that allows the user to select a different date for the sunrise and sunset data or the user can push the increment button to advance to the next date. This app uses the phone’s GPS and network connection to obtain the sunrise and sunset data. No user data is saved and only the user’s current location is transmitted to an API.

RTC DS3231 with Ardunio

Most embedded systems require some kind of real time clock for different timing events. Recently a client wanted a chicken coop controller. Central to the implementation is an Adafruit DS3231 Precision RTC Breakout. This board uses Maxim’s DS3231 Integrated Real Time Clock.

The DS3231 is an extremely accurate real-time clock with an integrated temperature compensated crystal oscillator and crystal. This device supports battery backup to maintain timekeeping when main power is interrupted. This device maintains seconds, minutes, hours, day, date, month, and year information that includes leap year corrections. The DS3231 also has two programmable time of day alarms that trigger an interrupt output when the specified time occurs. The DS3231 interface is I2C.

The chicken coop controller included an Ardunio UNO, Adafruit DS3231 precision real-time clock (RTC), Pololu 5V 1A regulator, 2-channel relay module, AC light dimmer (forward phase) module, 12V linear actuator, and a 12V battery. The Ardunio sketch was to:

  • At sunrise retract the linear actuator for 3 minutes.
  • 1-hour after sunrise turn on lights at 100% brightness
  • Leave lights at 100% brightness for 12.5 hours, then gradually dim over a 1-hour period.
  • At sunset extent the linear actuator for 3 minutes.

Looking at the requirements, all events except the dimming occur on the minute boundary (sunrise, sunset, actuator time, lights on full time). The DS3231 has two alarms. One alarm works down to the second and the other alarm works down to the minute. The need for an alarm to occur on seconds boundary is driven by the light dimming over a 1-hour period. The light dimming uses the 8-bit PWM control. Using all of the PWM control states (256) over 1-hour period has the PWM value updating every 14 seconds (255 down to 0). Since light dimming needs an alarm to occur every 14 seconds, alarm 1 was selected for light control and alarm 2 for actuator control.

The next alarm time is determined by adding the seconds, minutes, hours, and days to a time. For example if the next alarm is to occur in 3 minutes from now, 3 minutes is added to the current time. An add time function was developed to support this sketch. The time is added to the appropriate parameter and then rollover values are tested and updated. When adjusting months the code continues to loop through rollovers until all adjustments are made.

Function to Add time

// function to add time to input time
void addTime(ts *t, int secs, int mins, int hours, int days) {
  boolean adjustMonths = true;
  
  // add time then adjust for rollovers
  t->sec += secs;
  t->min += mins;
  t->hour += hours;
  t->mday += days; 

  // adjust date/time based on rollovers
  if (t->sec >= 60) {
    t->min += t->sec / 60;
    t->sec %= 60;
  }
  if (t->min >= 60) {
    t->hour += t->min / 60;
    t->min %= 60;
  }
  if(t->hour >= 24) {
    t->mday = t->hour / 24;
    t->hour %= 24;
  }

  // adjust months
  while(adjustMonths) {
    switch(t->mon) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        if (t->mday > 31) {
          t->mday -= 31;
          t->mon += 1;
          if (t->mon > 12) {
            t->mon = 1;
            t->year += 1;
          }
        }
        else
          adjustMonths = false;
        break;
      case 4:
      case 6:
      case 9:
      case 11:
        if (t->mday > 30) {
          t->mday -= 30;
          t->mon += 1;
        }
        else
          adjustMonths = false;
      case 2:
        if (t->year % 4 == 0) {   // leap year, don't worry about % 400
          if (t->mday > 29) {
            t->mday -= 29;
            t->mon += 1;
          }
          else
            adjustMonths = false;
        }
        else
          if (t->mday > 28) {
            t->mday -= 28;
            t->mon += 1;
          }
          else
            adjustMonths = false;
        break;
    }
  }
}

In setting the alarm the developer must enable all values (seconds, minutes, hours, date) up to the desired match (i.e. if alarm is on a hour value, then seconds, and minutes must also be enabled).

When the time matches the enabled alarm parameters then the DS3231 INT/ pin goes active low. Since the I2C Adruino library uses interrupts to communicate, this sketch polls this DS3231 pin instead of using an interrupt. During the polling each alarm is checked and the proper flag(s) is(are) set.

One final item with the DS3231 is the control register used to enable/disable alarms and set other parameters. This register is not readable. If the alarms or other controls are enabled at different times in the code, then a “shadow” register is needed to maintain the current control register state. For example is you are enabling alarm 2 you don’t know the current state of alarm 1 unless you maintain an internal variable in your code.

Finally the library select to support the DS3231 was ds3231FS by Petre Rodan. This library provided an interface to setting the alarms.

Fitbit Apps Published

Anyware, LLC Fitbit apps for Poker Hand ranks and Moon Phases were approved and published on the Fitbit Gallery.

Poker Hand app is designed as a simple player reminder of poker hand ranks while sitting at the table. The app is a simple scrolling list that displays poker hand ranks from highest to lowest. This app uses no user data.

The moon phases app displays the current moon phase and illumination percentage for the given date. The initial date displayed is the current date. The date display is a button that allows the user to select a different date for the moon phase, or the user can push the increment button to advance to the next date. This app uses no external resources and no user data is saved or transmitted.

The Node Method

In the next update to my book, AC/DC Principles and Applications, I plan to add a small section on the Node Method to Chapter Nine – Complex Network Analysis Techniques. Discussed in this chapter are Kirchoff’s voltage and current laws, superposition, Thevenin’s and Norton’s theorems. This post is an advance installment of the update.

The Node Method is a circuit analysis approach that uses the circuit element properties with Kirchoff’s voltage and current laws. This method helps to reduce analysis complexity as seen in the example.

Each element connection point is a circuit node. In using the Node Method, each node voltage is label with one node being selected as ground or 0 volts. Each node also has the current labeled showing the currents entering and leaving each node.

After all the node voltages and currents are labeled, KCL is written for each node. Based on Ohm’s Law, the current through a resistor is the voltage across the resistor divided by the resistance. The voltage across a resistor is the voltage difference between the terminals.

Using the set of KCL equations, solve for the missing information.

Node Method Steps

  1. Select a reference node to be 0 volts (ground). A good choice for this reference node is one that has the most connections.
  2. Label voltages and currents.
  3. Write KCL equations for each node that has an unknown voltage.
  4. Solve for missing values using the equations developed in step 3.

Example

Using Figure 9-3, what is the voltage drop for a 2Ω load resistor that is connected between a 12V closed-loop circuit (loop A) with a resistance of 4Ω and a 6V closed-loop circuit (loop B) with a resistance of 3Ω?

Write KCL
IL = I1 + I2
e/2Ω = (12V – e)/4Ω + (6V – e)/3Ω

Solve for node voltage e
6e/12Ω = 3(12V – e)/12Ω + 4(6V – e)/12Ω
6e/12Ω = (60V – 7e)/12Ω
13e/12Ω = 60V/12Ω
e = 60V/13
e = 4.6V
IL = 4.6V/2Ω
IL = 2.3A

This solution was simpler than creating the KVL for each loop, solving for I1, I2, and then IL, and then finding the node voltage e.