Categories
Arduino CircuitPython Microcontroller Photography Raspberry Pi Pico RTOS

Geotagging Photos

I found a good Instructable, GeoTagging With a Standalone GPS Unit & GeoSetter : 6 Steps – Instructables, to walk through geotagging photos with a standalone GPS device and the GeoSetter application. As a result of reading through the 14 year old Instructable, I landed on a somewhat standard CSV format that GPSBabel understands. As a result, the C++ code was updated in the GitHub repository, richteel/gps_tracker: GPS Tracker and data logger (github.com) to use the ‘Universal csv with field structure in the first line’ format. The first line of the CSV file contains the following headers: utc_d,utc_t,lat,lon,alt,head,speed.

  • utc_d: UTC date
  • utc_t: UTC time
  • lat: Latitude
  • lon: Longitude
  • alt: Elevation in meters
  • head: Heading in degrees
  • speed: Speed in meters per second

See: GPSBabel 1.9.0:Universal csv with field structure in first line (unicsv)

GPSBabel Screenshot
GPSBabel screen showing the ‘Universal csv with field structure in the first line’ format selected

GPS Logger

The GPS Logger that I’m discussing, was one I designed and built. The GPS Logger is Open Source, with the case, PCB, schematic, and code are included in the GitHub repository. The code, which is currently being used is the C++ code in the folder, Arduino/gps_tracker. (The CircuitPython code may be updated shortly as well, but for now, it is best to use the C++ code.) GPSBabel is used to translate the CSV file from the GPS Logger into a GPX file, that GeoSetter may be used to geotag photos.

Software Used

First a note on the time setting on the camera. Previously, I would set the date and time to the local date time, however I frequently would forget to update when using the camera, which was problematic when switching between daylight saving time and standard time. I decided this time to simply set to UTC. I’m not certain if this will be a good solution, but we shall see.

The first step is to translate the GPS Logger CSV file into a GPX file, using GPSBabel.

The next steps are done using GeoSetter. My camera produces JPG files, so I have only set the values for JPEG.

GeoSetter File Settings – JPEG
GeoSetter Data Preferences Settings
GeoSetter Map Settings
GeoSetter Misc Settings

Once the settings have been edited, then it is possible to load the GPX file with GPS data and geotag files in a folder. Start by selecting all the photos in the photos panel to the left.

GeoSetter Application with all photos in the photos panel selected

To geotag the selected photos, go to the menu and select, Edit > Synchronize with GPS Data Files…

GeoSetter Menu – Edit > Synchronize with GPS Data Files…

A dialog will open, with options for synchonizing the photos and GPS information. I have selected the “Synchronize with a Directory containing Data Files:” option. Not sure why, but I needed to uncheck the “Request Time Zone by using Webservice” option for the geotagging to work.

GeoSetter – Synchronize with GPS Data Files dialog window

A dialog showing the number of matched files is displayed. If no matches are found, then you will be prompted to try again or cancel. In this example, all 20 images were matched with GPS data, so the “Yes” option is selected.

GeoSetter – Synchronize with GPS Data Files confirmation dialog

An additional confirmation dialog will be displayed asking if you wish to save the assigned track(s) to the current directory. Select the desired option.

The photos will be geotagged and the positions will now be displayed on the map.

GeoSetter showing a map with the locations of the photos

The photos are not saved at this point. You will need to select Edit > Save Changes, from the menu to write the EXIF information to the photos.

Once the photos have been saved, you may view the photos in Windows Photo Viewer to show that the location information is saved with the photos.

Windows default photo viewer showing that the location infromation is saved in the EXIF information of the photo

Alternatively, you may look at the file properties to see the EXIF information.

File Properties – Details, showing GPS data
Categories
Arduino CircuitPython Microcontroller Raspberry Pi Pico RTOS

Revisiting the GPS Logger

I’m on vacation/holiday this month, but before I left, I paid the GPS Logger project a visit and rewrote the code in C++, using the Arduino IDE and FreeRTOS to make it a bit more responsive to user input. The result is a nearly polished project that I’m very happy with, but I plan to reorganize the GitHub, project files, at richteel/gps_tracker: GPS Tracker and data logger (github.com), and provide a few different patterns for implementing such a project. While I like using FreeRTOS, it is not for everyone, particularly those just getting started with microcontroller projects. I also wish to revisit CircuitPython and see about implementing the FreeRTOS version of Python. I believe the CircuitPython implementation is still an alpha version so I may not be able to do anything with it yet, but may give it a go.

Here are some photos of the GPS Logger in action.

TeelSys GPS v0.5 C++
Date Time (UTC)
01/06/2024 07:54:29
Local (EST)
01//06/2024 02:54:29
Screen 1 of 6 – Main Time
Lat: 17.9689 N Long: 102.6152 E Alt: 600.72 ft Speed: 1.52 mph Head: 0.0 deg
Screen 2 of 6 – Location Information
Satellites: 5 GPS Fix: Yes Fix Quality: GPS 3D Quality: 3D GPS Enabled: Yes
Screen 3 of 6 – GPS Information
Battery: 3.94 V Battery: 69.94 % Status: Discharging
Screen 4 of 6 – Battery
Card: Present Type: SDHC/SDXC Total: 14.621 Gb Free: 14.620 Gb Files: 11
Screen 5 of 6 – SD Card Information
Memory - Heap Free: 154920 b 65.42% Freq: 133.00 MHz
Screen 6 of 6 – Memory

Why is the GPS Logger needed when photos on the phone are Geotagged or the phone is capable of this function?

While this functionality may be available with apps on the phone, it is not always possible to get tracks off of the phone easily to use with an application such as GeoSetter for photos taken with a dedicated camera, such as the Sony DSC-WX500. I want to be able to use the data gathered from the logger and geotag photos from cameras, which do not have a GPS. I also want to create maps, showing the tracks that we took, while on vacation. For both of these reasons, a dedicated GPS Logger is a good option. The file format of the current version is a CSV file, which may or may not work with the GeoSetter application, but I should be able to convert the data into a format that will be usable.

Categories
Arduino Raspberry Pi Pico RTOS

Progress

Well, it has been a productive week. Much progress has been made since getting to the root problem with FreeRTOS and WiFi on the Raspberry Pi Pico.

Like the phoenix rising from the ashes, I’ve been pulling in the code from my previous attempts and have the been piecing them together. Progress has been very quick. Too bad I did not dig into the library earlier, but honestly, I may not have hit on the root cause without some considerable investigating. Thankfully GitHub user, GUVWAF, had submitted a fix for the root cause around the same time I had submitted the pull request, so it was not necessary for me to dig into the libraries much more. I’m very thankful to Earle and Maximilian reviewing my pull request and promptly replying to my comments. Even though my pull request was not needed, I learned a great deal from the effort with Earle and Maximilian’s support.

I’m hopeful that I will be pushing some code to my SpeechTimer GitHub repository some time this week. I may not have all the features hammered out, but hope to be close. I will be working on some of the more difficult aspects, which include using the remote and providing a web interface for controlling the timer. The reason these are the more difficult aspects, is not so much in writing the code, but figuring out the best way to control the timer using the remote.

Update 11 December 2023

The project files are now live on GitHub at https://github.com/richteel/SpeechTimer. There are two things missing so far, but it a minimal usable project. The timer is controllable through the IR Remote. It has three predefined timers for typical Toastmasters meeting activities.

  • 5 to 7 Minutes for Speeches
  • 1 to 2 Minutes for Table Topics
  • 2 to 3 Minutes for Evaluations

There is a fourth timer that may be set using the numbers on the remote and the left and right buttons to change from minimum (left) to maximum (right) times.

The two missing features are the web user interface and the remote unit. I plan to work on the web interface first, but may work on the remote unit. I’m thinking about two different architectures for the web interface and need to decide which one I want to use. One is easy to implement, but it is only available on the same subnet as the timer. The other could control the clock from anywhere, but is a bit more difficult to implement.

Categories
Arduino Raspberry Pi Pico RTOS

Raspberry Pi Pico W RTOS and Wi-Fi

It has been a few months since I posted last, but I’ve had a few frustrating months working on the Speech Timer project with a Raspberry Pi Pico W, RTOS, and Wi-Fi. It does not help that I can only work on this project for about 4 to 8 hours a week. This post will go into a few of the issues that I’ve run into and hoping to get the community to help me narrow down the issue.

Starting this project, I used CircuitPython to get the project up and running. It went fairly smoothly, but I ran into some issues that made it not such a good solution. Firstly, the program ran fine most of the time, but after some time, the heap would run out of enough contiguous space and would crash. This issue was still prevalent with garbage collection being called frequently and other optimizations to reduce heap fragmentation. I then turned my attention to C++ as it is possible to create more rock solid code than CircuitPython and the behavior is more predicable. Another reason for looking into C++ is I wanted to use RTOS to help make the user interaction more responsive.

The first problems to show up were with the IRremote library but most of the issues were a result of how the library is built. Once those limitations were mostly understood, it was possible to have the IR Remote working reliably.

The next issue came with the Wi-Fi connection. I still have not sorted it out, but still trying. I can get Wi-Fi working just fine if the FreeRTOS library is not included, but as soon as it is added, the Raspberry Pi Pico W attempts to connect to Wi-Fi, then locks up.

Here is some information on my setup:

  • BOARD: Raspberry Pi Pico W
  • IDE: Arduino IDE 2.2.1
  • CORE: Raspberry Pi Pico W core written by Earle F. Philhower, III
  • PROGRAMMING METHOD: Picoprobe

I made some changes to the WiFiMulti library to allow clearing the AP list and print out some status messages to let me know where things seem to be falling apart.

Below is the modified WiFiMulti.h file.

/*
WiFiMulti.h - Choose best RSSI and connect
Copyright (c) 2022 Earle F. Philhower, III

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

Modified by Ivan Grokhotkov, January 2015 - esp8266 support
*/

#pragma once

#include <list>
#include <stdint.h>
#include "wl_definitions.h"

class WiFiMulti {
public:
WiFiMulti();
~WiFiMulti();

bool addAP(const char *ssid, const char *pass = nullptr);

void clearAPList();

uint8_t run(uint32_t to = 10000);

private:
struct _AP {
char *ssid;
char *pass;
};
std::list<struct _AP> _list;
};

Below is the modified WiFiMulti.cpp file.

/*
WiFiMulti.cpp - Choose best RSSI and connect
Copyright (c) 2022 Earle F. Philhower, III

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

Modified by Ivan Grokhotkov, January 2015 - esp8266 support
*/

#include "WiFi.h"
#include <string.h>
#include <algorithm>

WiFiMulti::WiFiMulti() {
}

WiFiMulti::~WiFiMulti() {
while (!_list.empty()) {
struct _AP ap = _list.front();
_list.pop_front();
free(ap.ssid);
free(ap.pass);
}
}

bool WiFiMulti::addAP(const char *ssid, const char *pass) {
struct _AP ap;
if (!ssid) {
return false;
}
ap.ssid = strdup(ssid);
if (!ap.ssid) {
return false;
}
if (pass) {
ap.pass = strdup(pass);
if (!ap.pass) {
free(ap.ssid);
return false;
}
} else {
ap.pass = nullptr;
}
DEBUGV("[WIFIMULTI] Adding: '%s' %s' to list\n", ap.ssid, ap.pass);
_list.push_front(ap);
return true;
}

void WiFiMulti::clearAPList() {
while (!_list.empty()) {
struct _AP ap = _list.front();
_list.pop_front();
free(ap.ssid);
free(ap.pass);
}
}


uint8_t WiFiMulti::run(uint32_t to) {
struct _scanAP {
char *ssid;
char *psk;
uint8_t bssid[6];
int rssi;
};
std::list<struct _scanAP> _scanList;

// If we're already connected, don't re-scan/etc.
if (WiFi.status() == WL_CONNECTED) {
return WL_CONNECTED;
}

DEBUGV("[WIFIMULTI] Rescanning to build new list of APs\n");
int cnt = WiFi.scanNetworks();
if (!cnt) {
return WL_DISCONNECTED;
}

// Add all matching ones to the scanList
for (int i = 0; i < cnt; i++) {
for (auto j = _list.begin(); j != _list.end(); j++) {
if (!strcmp(j->ssid, WiFi.SSID(i))) {
_scanAP itm;
itm.ssid = j->ssid;
itm.psk = j->pass;
WiFi.BSSID(i, itm.bssid);
itm.rssi = WiFi.RSSI(i);
_scanList.push_front(itm);
}
}
}
// Sort by RSSI using C++ lambda magic
_scanList.sort([](const struct _scanAP & a, const struct _scanAP & b) {
return a.rssi > b.rssi;
});
for (auto j = _scanList.begin(); j != _scanList.end(); j++) {
DEBUGV("[WIFIMULTI] scanList: SSID: '%s' -- BSSID: '%02X%02X%02X%02X%02X%02X' -- RSSI: %d\n", j->ssid,
j->bssid[0], j->bssid[1], j->bssid[2], j->bssid[3], j->bssid[4], j->bssid[5], j->rssi);
}

// Attempt to connect to each (will be in order of decreasing RSSI)
for (auto j = _scanList.begin(); j != _scanList.end(); j++) {
DEBUGV("[WIFIMULTI] Connecting to: SSID: '%s' -- BSSID: '%02X%02X%02X%02X%02X%02X' -- RSSI: %d\n", j->ssid,
j->bssid[0], j->bssid[1], j->bssid[2], j->bssid[3], j->bssid[4], j->bssid[5], j->rssi);
uint32_t start = millis();
if (j->psk) {
WiFi.begin(j->ssid, j->psk, j->bssid);
} else {
WiFi.beginBSSID(j->ssid, j->bssid);
}
while (!WiFi.connected() && (millis() - start < to)) {
Serial1.print("^");
delay(5);
}
Serial1.print("\n");
if (WiFi.status() == WL_CONNECTED) {
Serial1.println("Connected");
return WL_CONNECTED;
}
}

// Failed at this point...
Serial1.println("Failed to Connect");
return WiFi.status();
}

The Arduino Sketch contains the following files:

  • sketch_dec02a.ino – Main Sketch file
  • Clk_SdCard.h – Header file for SD Card functions
  • Clk_SdCard.cpp – Code file for SD Card functions
  • Clk_Wifi.h – Header file for Wi-Fi functions
  • Clk_Wifi.cpp – Code file for Wi-Fi functions
  • config.h – Structure for configuration file
  • DbgPrint.h – Helper file for Serial statements
  • Defines.h – Pin definitions, programming mode, and debug flag
  • StructsAndEnums.h – Various structures and enumerations

The sketch_dec2a.ino file is shown below. All other files are included in the code.zip file in the “sketch_dec02a” folder. The zip file contains two other folders. The “lib” folder holds the two WiFiMulti library files, that may be copied into the library location. On my Windows machine, the location is “C:\Users\user\AppData\Local\Arduino15\packages\rp2040\hardware\rp2040\3.6.1\libraries\WiFi\src”. The other folder is “SDCard” and contains a sample config.txt file to be edited with your Wi-Fi configuration information and copied to the micro SD Card.
NOTE: The card reader needs to be connected to the pins defined in Defines.h, or the pin definitions need to be modified to match your setup.

/*****************************************************************************

* FreeRTOS Setup *
*****************************************************************************/
#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#define xPortGetCoreID get_core_num

/*****************************************************************************
* Project Files *
*****************************************************************************/
#include "DbgPrint.h" // Serial helpers
#include "Defines.h" // Pin definitions, programming mode, and debug flag
#include "config.h" // Structures for the configuration file
#include "Clk_SdCard.h" // Higher level SD Card functions
#include "Clk_Wifi.h" // Higher level Wi-Fi functions

/*****************************************************************************
* Other Includes *
*****************************************************************************/
#include <map>

/*****************************************************************************
* GLOBALS *
*****************************************************************************/
// Flags to make certain that required initialization is complete before tasks start
bool setup_complete = false;

// WiFi global settings
char last_ipaddress[16] = "";
WiFiMode_t last_WiFiMode = WIFI_OFF;

// Config global settings
unsigned long configLoadMillis = 0;
unsigned long last_configLoadMillis = 0;

// Module Objects
Clk_SdCard clockSdCard = Clk_SdCard();
Clk_Wifi clockWifi = Clk_Wifi();

/*****************************************************************************
* MAPPINGS *
*****************************************************************************/
// Mapping for string lookup
std::map<WiFiMode_t, const char *> wifiModeName{ { WIFI_OFF, "WiFi Off" }, { WIFI_STA, "Station Mode" }, { WIFI_AP, "Soft-AP Mode" }, { WIFI_AP_STA, "Station + Soft-AP Mode" } };

/*****************************************************************************
* INO FUNCTIONS *
*****************************************************************************/
void debugMessage(const char *message) {
Dbg_printf("%s\t%d\t%d\n", message, xPortGetCoreID(), rp2040.getFreeHeap());
}

/*****************************************************************************
* TASKS *
*****************************************************************************/
// void checkWiFi(void *param) {
void checkWiFi() {
// Make certain that setup has completed
while (!setup_complete) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}

int loopCount = 0;

while (1) {
Dbg_print("."); // Print ... while looping so we know that the loop is running
// If the SD Card was inserted/reinserted, use the new configuration to connect to the Wi-Fi AP
if (configLoadMillis != last_configLoadMillis) {
Dbg_println("START: Restart Wi-Fi with new Config");
clockWifi.begin(&clockSdCard.sdCardConfig);
Dbg_println("Wi-Fi Begin Completed");
last_configLoadMillis = configLoadMillis;
Dbg_println("END: Restart Wi-Fi with new Config");
}

// Set the IP Address property
clockWifi.hasIpAddress();

// If the IP Address has changed, print the IP Addess
if (strcmp(last_ipaddress, clockWifi.ipAddress) != 0) {
Dbg_println("START: Update IP Address");
strcpy(last_ipaddress, clockWifi.ipAddress);
Dbg_printf("IP Address has changed to %s\n", last_ipaddress);
Dbg_println("END: Update IP Address");
}

// If the Wi-Fi Mode has changed, print debug message
if (last_WiFiMode != clockWifi.wifiMode) {
Dbg_println("START: Print Debug Statement");
last_WiFiMode = clockWifi.wifiMode;
Dbg_printf("Wi-Fi has changed to %s\n", wifiModeName[last_WiFiMode]);
Dbg_println("END: Print Debug Statement");
}

loopCount++;
// Print message with stack usage once every ten loops
if (loopCount > 10) {
debugMessage("checkWiFi");
loopCount = 0;
}
vTaskDelay(250 / portTICK_PERIOD_MS);
}
}

/*****************************************************************************
* SETUP *
*****************************************************************************/
void setup() {
Dbg_begin(115200);

// Wait for the serial port to connect
while (DEBUG && !(Serial || Serial1)) {
delay(1); // wait for serial port to connect.
}

// Print 5 blank lines on startup
for (int i = 0; i < 5; i++) {
Dbg_println("");
}

clockSdCard.begin();
// NOTE: The SD Card needs to be inserted with the Config.txt file or the code will
// never attempt to connect to Wi-Fi. In the final version, there will be a task to
// check the SD Card and reload the configuration file.
if (clockSdCard.isCardPresent()) {
configLoadMillis = millis();
}

Dbg_printf("sdcard_init_complete = %s\n", configLoadMillis != last_configLoadMillis ? "true" : "false");

debugMessage("-----------------------------");

debugMessage("END: setup()");
setup_complete = true;
}

/*****************************************************************************
* LOOP *
*****************************************************************************/
void loop() {
// The core written by Earle F. Philhower, III, requires that the Wi-Fi be
// accessed from Core 0, therefore it needs to run in the loop.
checkWiFi();
}

The code typically hangs with the following output.

START: Reading Config File

0. MySSID_One Password1

1. MySSID_Two Password2

2. MySSID_Three Password3

FINISHED: Reading Config File

sdcard_init_complete = true

----------------------------- 0 165684

END: setup() 0 165684

.START: Restart Wi-Fi with new Config

------^^------ WIFI: Added AP MySSID_One ------^^------

------^^------ WIFI: Added AP MySSID_Two ------^^------

------^^------ WIFI: Added AP MySSID_Three ------^^------

Connecting WiFi...

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Once in a great while the code runs properly, but if it failed to connect, the second attempt most likely will hang.

START: Reading Config File

0. MySSID_One Password1

1. MySSID_Two Password2

2. MySSID_Three Password3

FINISHED: Reading Config File

sdcard_init_complete = true

----------------------------- 0 165684

END: setup() 0 165684

.START: Restart Wi-Fi with new Config

------^^------ WIFI: Added AP MySSID_One ------^^------

------^^------ WIFI: Added AP MySSID_Two ------^^------

------^^------ WIFI: Added AP MySSID_Three ------^^------

Connecting WiFi...

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Failed to Connect



Failed to connect to Wi-Fi. Trying again...

------^^------ WIFI: Added AP MySSID_One ------^^------

------^^------ WIFI: Added AP MySSID_Two ------^^------

------^^------ WIFI: Added AP MySSID_Three ------^^------

Connecting WiFi...

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Then once in a while it will connect just fine.

START: Reading Config File

0. MySSID_One Password1

1. MySSID_Two Password2

2. MySSID_Three Password3

FINISHED: Reading Config File

sdcard_init_complete = true

----------------------------- 0 165684

END: setup() 0 165684

.START: Restart Wi-Fi with new Config

------^^------ WIFI: Added AP MySSID_One ------^^------

------^^------ WIFI: Added AP MySSID_Two ------^^------

------^^------ WIFI: Added AP MySSID_Three ------^^------

Connecting WiFi...

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Connected



WiFi connected, SSID: MySSID_One, IP address: 192.168.1.45

Connected in 8015 ms

------^^------ WIFI: Starting Station Mode ------^^------

Wi-Fi Begin Completed

END: Restart Wi-Fi with new Config

START: Update IP Address

IP Address has changed to 192.168.1.45

END: Update IP Address

START: Print Debug Statement

Wi-Fi has changed to Station Mode

END: Print Debug Statement

..........checkWiFi 0 164684

...........checkWiFi 0 164684

...........checkWiFi 0 164684

...........checkWiFi 0 164684

...........checkWiFi 0 164684

...........checkWiFi 0 164684

.......

If you have any thoughts on what may be happening, please comment to let me know. Also check the discussion board on Earle’s core library on GitHub at https://github.com/earlephilhower/arduino-pico/discussions?discussions_q=RTOS. I plan to post a comment there and point to this post.

Thank you in advance for any help on this issue.

UPDATE 3 December 2023

I had found that the delay statement in the WiFIMulti.ccp file was causing the issue with FreeRTOS. I modified it with the blink without delay example and the code worked. I created a pull request for the change and hope it will become part of the code base in the future so hopefully no one else will run into this issue.

UPDATE 4 December 2023

It seems that it was dumb luck yesterday that made me think the issue was with the delay statement. I had created a pull request for the change and fortunately, Earle F. Philhower, III pushed back a bit and stated that it did not make sense that delay would be causing an issue. I was doing some troubleshooting with him and Maximilian Gerhardt, then it appeared that my fix was no longer working. I had forgot that I had modified the fix to handle a rollover in millis, which was really unnecessary. When I removed that segment, the timing loop replacement for delay behaved exactly the same way. I then modified the code to use delay and remove the timing loop and put the if block back in and it worked. It appears that having only a delay in the main loop is the cause of the issue. If anyone has an idea why, please feel free to submit a comment or better yet go to the pull request and add a comment there. The link to the pull request is https://github.com/earlephilhower/arduino-pico/pull/1878.

Original block of code.

while (!WiFi.connected() && (millis() - start < to)) {
    delay(5);
}

My original modification.

while (!WiFi.connected() && (millis() - start < to)) {
	// Replaced delay(5); with the following to prevent
	// failure when using RTOS
	unsigned long tWifiDelayForRtos_start = millis();
	while (millis() - tWifiDelayForRtos_start <= 5) {
		if (millis() < tWifiDelayForRtos_start) {
			// millis wrapped around to zero. Rare to hit this
			// issue, but it will do this every 49 days.
			tWifiDelayForRtos_start = millis();
		}
	}
}

Modification without the if block. Behaved the same as the original code.

while (!WiFi.connected() && (millis() - start < to)) {
	// Replaced delay(5); with the following to prevent
	// failure when using RTOS
	unsigned long tWifiDelayForRtos_start = millis();
	while (millis() - tWifiDelayForRtos_start <= 5) {
		;
	}
}

Modification with delay. Note that all the other code in while loop is unnecessary as it does nothing useful. It is only needed to keep the code running, but why?

while (!WiFi.connected() && (millis() - start < to)) {
	// Replaced delay(5); with the following to prevent
	// failure when using RTOS
	unsigned long tWifiDelayForRtos_start = millis();
	if (millis() < tWifiDelayForRtos_start) {
		// millis wrapped around to zero. Rare to hit this
		// issue, but it will do this every 49 days.
		tWifiDelayForRtos_start = millis();
	}
	delay(5);
}

UPDATE 5 December 2023

Finally got to the bottom of this issue. There was a check-in of new code around the same time as I posted about this issue. The change was in the FreeRTOS code in the file variantHooks.cpp. The call to portENABLE_INTERRUPTS(); was moved from before the call to vTaskPreemptionEnable(nullptr) to after the call. That change addressed the exact problems that I was seeing.

Glad to see that this is finally addressed. I think this may be related to some other issues that I experienced, but not certain. Now I can get back to the project and hopefully wrap it up soon.

If you are interested, the commit for the actual fix is d2461a1.

Categories
Arduino CircuitPython Raspberry Pi Pico Software Development

September Update

Another month has gone by and I am still working on the Speech Timer project. I was able to develop a solution with CircuitPython but was not happy with the end product. Perhaps it would be possible to get to a more stable version but even with all the RAM available on the Raspberry Pi Pico W, I would still run into issues with the heap becoming fragmented and not having enough free space to allocate additional space to server web pages reliably. I tried many of the tips to resolve the issue including calling garbage collection periodically. These changes helped the code run a bit longer but still ran into issues. I decided to rewrite the code in C++, using the Arduino IDE and wanted to be able to debug my code on the Raspberry Pi Pico while it was running as I ran into an issue when I changed the configuration to disable serial debugging output by not calling Serial.begin().

I made a Picoprobe and attempted to debug my code, but things were not as straight forward as they could have been, which resulted in many hours waisted.

I had followed the instructions in the getting-started-with-pico.pdf guide and used the Windows Pico SDK, but could not get debugging to work in Visual Code or the Arduino IDE. I was nearly ready to setup another PC with Linux and give that a go as I had seen folks saw that just works with no issues. I’m running Windows, which I assumed was part of the issue. I saw another Windows user who was very frustrated as well and stating that it seemed they were the only ones testing/using this in Windows. They eventually got it working although they could not figure out the root cause. Unfortunately I too got it working and have no clear indication of what the root issue was but I have an idea that there is an issue with the Windows setup scripts that needs to be addressed. If someone has Visual Code and/or Arduino IDE installed before installing the SDK, they will have issues. This may not be the fault of the SDK, it could be with the Visual Code and Arduino IDE setup but I suspect it is indeed the Pico SDK setup on Windows that is the issue.

Out of frustration, I reinstalled the Windows Pico SDK and checked Visual Code and the Arduino IDE. Magically, Visual Studio code worked but the Arduino IDE still failed. I then uninstalled the Arduino IDE, then restarted the PC. Once the PC restarted, I deleted the C:\Users\<user id>\AppData\Local\Arduino15 and C:\Program Files\Arduino IDE folders to clear out anything that was already configured. I then reinstalled the Arduino IDE and tried again and the Arduino IDE worked too.

There really is a problem with the Pico SDK but it will be difficult to fix unless someone can figure out what changed from the problem install and the fixed install. Unfortunately, I did not capture anything before so I cannot tell what went wrong.

In the end, once debugging was working, I was able to step through my code and found the issue in about 2 minutes. Too bad the debugging fiasco caused several days of wasted time trying to get the debugging working.

Categories
Arduino Raspberry Pi Pico

Raspberry Pi Pico with Arduino IDE

The Raspberry Pi Pico may be programmed in the Arduino IDE. There are three board libraries available but I found that the one written by Earle F. Philhower, III works best. Below are the steps that I took to get the example blink sketch loaded on the Raspberry Pi Pico.

  1. In the Arduino IDE, open the Boards Manager
  2. Type “Pico” in the search box
  3. If the “Arduino Mbed OS RP2040 Boards” is installed, click the “Remove” button to uninstall it
  4. If the  “Raspberry Pi Pico/RP 2040” is not installed, click the “Install” button to install it
Boards manager in the Arduino IDE
  1. Connect the Raspberry Pi Pico to the PC through the USB Port
  2. In the Arduino IDE menu, select the “Raspberry Pi Pico” board by going to Tools > Board > Raspberry Pi RP2040 Boards(3.2.0) (in Sketchbook) > Raspberry Pi Pico
Selecting the Raspberry Pi Pico board
  1. In the Arduino IDE menu, select Tools > Port from the menu
    • If this is the first time connecting the Raspberry Pi Pico to the PC, select UF2 Board
First time programming, select UF2 Board
    • If this is not the first time, then a list of COM Ports are available. Open the Device Manager to see the available COM Ports and determine which one is the Pico board. You may unplug the Pico Board, wait for the Device Manager to refresh with one less COM Port, then plug the Pico board back in. Note, which new COM Port appears, that will be the one to select in the Arduino IDE.
Device Manager showing the Ports
Port, select the correct COM Port
  1. Open the example “Blink” sketch from the menu File > Examples > 01. Basics > Blink
  2. Click the “Upload” button in the Arduino IDE to load the sketch onto the Pico board
    NOTE: You may see several warnings about whitespace. These warnings may be ignored
Successfully uploaded sketch
  1. You should see the LED on the Pico board flashing once the sketch is uploaded

Hopefully, this gets you up and running. It is always a good idea to run the example blink program first when configuring a new board. It lets you know right away if things are working as expected. Once that works, then move onto your code.

Categories
Android Arduino Meshtastic Microcontroller Raspberry Pi Pico

Meshtastic Serial

I wanted to see about connecting a Raspberry Pi Pico to a LillyGo TTGO T-Beam v1.1 device. I noticed that Meshtastic supports serial communications, so I decided to give it a go to see how it worked.

There are several serial modes but the ones that seem the most useful are TXTMSG and PROTO. First attempt will be with the TXTMSG Mode as that seems straight forward. Once the TXTMSG Mode is working, I will look into how to use the PROTO Mode.

Wiring

We need to connect the grounds between the two devices, then connect the transmit (TX) from one to the receive (RX) of the other device. Below is a table showing the connections used in my setup.

T-BeamPico
RX pin 13TX pin 1 (GP0)
TX pin 14RX pin 2 (GP1)
GNDGND
T-Beam and Pico wiring
Wiring between T-Beam and Raspberry Pi Pico

Meshtastic Setup

Meshtastic firmware was installed using the Web Installer at https://flasher.meshtastic.org/. The T-Beam came with Meshtastic preinstalled. You may need to use another method to install the firmware if the Web Installer does not work.

Meshtastic Web Installer
Meshtastic Web Installer

T-Beam TEXTMSG Mode

Once Meshtastic has been installed on the T-Beam device and connected to the Android or Apple application, go to the Module Settings to setup the serial connection on the T-Beam device. The Module Settings is accessed by clicking on the kebab menu (aka three vertical dots menu) and selecting “Module Settings”.

kebab menu
Kebab Menu
Module Settings menu item
Module Settings menu item

Once the Module settings are displayed, scroll down to the “Serial Config” section and set the following items.

  • Serial enabled: turn on
  • RX: Set it to the T-Beam pin number for receive, which is 13 in my setup.
  • TX: Set it to the T-Beam pin number for transmit, which is 14 in my setup.
  • Serial baud rate: May leave it at the default setting or set it to “BAUD_38400”. I think it is best to set it as the default baud rate may change in other versions. I believe I read that it did change in the past.
  • Serial mode: Set it to TEXTMSG
  • Once everything is set, click the “Send” button.
Serial Configuration
Serial Configuration

Pico Arduino Code

The Pico code is written in C++ using the Arduino IDE. It is necessary to configure use the Pico Board provided by Earle F. Philhower, III. First, add the URL, https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json, to the Additional Boards Manager URLs by going to File > Preferences in the menu.

Arduino Preferences Menu Item
Arduino Preferences Menu Item
Arduino IDE Preferences
Arduino IDE Preferences

Click the icon to the left of the “Additional boards manager URLs” entry. Add the URL to the a new line in the textbox and click the “OK” button.

Additional Boards Manager URLs
Additional Boards Manager URLs

Open the boards manager by clicking on the boards manager icon, type “Pico” in the search textbox, and install the board, Raspberry Pi Pico/RP2040 by Earle F. Philhower, III.

Boards Manager
Boards Manager

Once the board is installed, you may select it from the boards dropdown selection in the IDE, when the Pico is connected to the PC.

Pico selected in the boards drop-down list
Raspberry Pi Pico selected in the boards drop-down list
/*
  Sample code to allow the Pico to act as a serial bridge between the PC and the Meshtastic device.

  Data sent to the Pico using the Arduino Serial Monitor, PuTTY, or other terminal software is sent
  to the Meshtastic device over the Pico UART0/Serial1 connection. Any data received from the Meshtastic
  device to the Pico is relayed to the PC over the Pico's serial over USB connection.

  REFERENCES:
    - https://meshtastic.org/docs/settings/moduleconfig/serial
    - https://github.com/earlephilhower/arduino-pico/discussions/210
*/

void setup() {
  // PC to Pico
  Serial.begin(9600);
  // Pico to Meshtastic device
  Serial1.begin(38400);
  while (!Serial)
    ;  // Serial is via USB; wait for enumeration
}

void loop() {
  // If data is received from the Meshtastic device, send it to the PC over the USB connection
  if (Serial1.available()) {
    String receiveMessage = Serial1.readString();
    Serial.print("Message received on Serial1 is:  ");
    Serial.println(receiveMessage);  // Send to serial monitor
  }

  // If data is received from the PC, send it to the Meshtastic Device
  while (Serial.available()) {
    int inByte = Serial.read();
    Serial1.write(inByte);
  }
}

Upload the code to the Raspberry Pi Pico. Once the code is loaded, open the serial monitor and type some text and hit enter. The message will be received on the other node(s).

Sending message from PC
Sending message from PC
Message received on other node
Message received on other node

Sending a message from another node, will be received and shown in the serial terminal.

Sending message from another node
Sending message from another node
Receiving message on PC
Receiving message on PC

Now the simple TEXTMSG is working, we can try to get the PROTO working. The PROTO mode is interesting as it may be possible to configure the Meshtastic device, and query it for additional information. I will look into the PROTO Mode in the near future.

Categories
Arduino

All Electronics LCD-101 (256×128 LCD) with Arduino – Part 2

I posted about the All Electronics LCD-101 display in February 2018. The other day, I noticed that they were back in stock, so I decided to dust off where I last left off with the display. I had designed an I2C backpack with a contrast voltage driver for the display and modified the test Arduino code that I had to work with the backpack. The code was incomplete, so I needed to pick up where I left off, start over, or look at extending another library. I did look at the U8g2 library (https://github.com/olikraus/U8g2_Arduino) but it does not work well with the display. It may be because it is expecting the display to be 240 x 128 rather than 256 x 128.

LCD Pins

Pin No. Symbol Level Function JP2 Pin I2C bit
1 FG 0V Frame Ground 1 N/A
2 Vss(Gnd) 0V Ground 1 N/A
3 Vdd(Vcc) +5V Power supply voltage for logic and LCD 2 N/A
4 Vo Operating voltage for LCD (variable) N/A N/A
5 /RES H/L Reset signal 3 A0
6 /RD H/L Read signal 4 A1
7 /WR H/L Write signal 5 A2
8 /CS H/L Chip select signal 6 A3
9 A0 H/L Data type select signal 7 A4
10 DB0 H/L Display data bit 0 8 B0
11 DB1 H/L Display data bit 1 9 B1
12 DB2 H/L Display data bit 2 10 B2
13 DB3 H/L Display data bit 3 11 B3
14 DB4 H/L Display data bit 4 12 B4
15 DB5 H/L Display data bit 5 13 B5
16 DB6 H/L Display data bit 6 14 B6
17 DB7 H/L Display data bit 7 15 B7

 

A0 /CS /WR /RD /RES
I2C A4 I2C A3 I2C A2 I2C A1 I2C A0 Binary MASK HEX Decimal Valid Op Valid Result Function/Note
X X X X 0 11110 0x1E 30 OR 11110 Reset
X 0 0 0 1 01111 0x0F 15 AND 00001 Invalid State
X 0 1 1 1 01111 0x0F 15 AND 00111 No Operation
X 1 X X 1 01001 0x09 9 AND 01001 No Operation
0 0 0 1 1 00011 0x03 3 EQUAL/XOR 00011 Display data and parameter write
0 0 1 0 1 00101 0x05 5 EQUAL/XOR 00101 Status flag read
1 0 0 1 1 10011 0x13 19 EQUAL/XOR 10011 Command write
1 0 1 0 1 10101 0x15 21 EQUAL/XOR 10101 Display data and cursor address read

 

 

A0 /CS /WR /RD /RES Mask Valid Result
A4 A3 A2 A1 A0 Binary HEX Decimal Valid Op Binary HEX Decimal Function/Note
X X X X 0 11110 0x1E 30 OR 11110 0x1E 30 Reset
X 0 0 0 1 01111 0x0F 15 AND 00001 0x01 1 Invalid State
X 0 1 1 1 01111 0x0F 15 AND 00111 0x07 7 No Operation
X 1 X X 1 01001 0x09 9 AND 01001 0x09 9 No Operation
0 0 0 1 1 00011 0x03 3 EQUAL/XOR 00011 0x03 3 Display data and parameter write
0 0 1 0 1 00101 0x05 5 EQUAL/XOR 00101 0x05 5 Status flag read
1 0 0 1 1 10011 0x13 19 EQUAL/XOR 10011 0x13 19 Command write
1 0 1 0 1 10101 0x15 21 EQUAL/XOR 10101 0x15 21 Display data and cursor address read

 

12 June 2022 Update

I had a question about this display, so I have updated the GitLab repository at https://gitlab.com/richteel/LCD-101/ to include the I2C Backpack that I designed for the LCD with a negative charge pump for the contrast and I2C or SPI interface for Arduino and Raspberry Pi. You may order the black PCB from Oshpark at https://oshpark.com/shared_projects/LwLCCjaW.

The schematic for the backpack is in the LCD-101/LCD-101 2020/I2C Backpack folder and is named I2C Backpack.pdf.

Categories
Arduino

Magnetic Card Reader (TTL) with Arduino – Part II

<- Part 1

Introduction

In the first part, I did some troubleshooting to fix an issue with the particular card readers that I have on hand. In part two, I will go over some code changes which allow the card reader to work without needing to add a capacitor to the data line and allowing the Arduino Nano to support two card readers.

I submitted an issue/comment to the magstripelib library in GitHub and was promptly received replies from Yoan Tournade and Carlos Rodrigues. Yoan suggested using INPUT_PULLUP on the interrupt pins may help. I modified the call to pinMode to use INPUT_PULLUP rather than INPUT but it made no difference. I suspect that there would be no difference as the card reader is pulling the pin up to the +5V rail so it is unnecessary to do in internal to the Arduino. It was worth a shot and I appreciated the suggestion.

Carlos commented that it is best to use an interrupt on the data pin as it reduces the amount of time spent in the clock interrupt handler.  This is true but the I decided to rewrite the code to read the data pin inside the clock interrupt to solve two problems for my particular instance.

  • Eliminate the need for the capacitor on the data line
  • Allow an Uno or Nano to be able to read two card readers

I had planned to fork Carlos’s original code and make some changes to have him pull back in if he felt they added to the magstripelib but felt that the changes that I wanted to make serve a different goal from Carlos’s code. The changes that I made are for a very small audience of users if any at all so I chose to simply include modified header and class files into the Arduino project.

On to the Code

The following code is available on GitHub at https://github.com/richteel/MagCardReader.

Allowing the Arduino Nano to read two card readers was the real motivation for making the changes to the code. It did present a challenge that I had not anticipated. I had to figure out how to use the same interrupt code to read from a different data pin depending on this pin which fired the interrupt.

I wanted to be able to support 2 card readers on one Arduino Nano as the card readers that I have can be setup to read track 1 or track 2. The cover holding the reader head may be removed. The cover contains two sets of notches which allow the head to be in one of two positions. Since I have two readers, I configured one for track 1 and the other for track 2.

Below is a list of some of the code changes made from the magstripelib.

  • Change class name from MagStripe to MagCardRead
  • Added volatile variable to hold the current data pin number
  • Modified the constructor to allow the assigning of the card detect, strobe (aka clock), and data pins for the reader
  • Removed the data interrupt handler
  • Read the data pin in the strobe interrupt handler

MagCardRead.h

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#ifndef MAGCARDREAD_H
#define MAGCARDREAD_H

#include 

// Default pins used by init
// The data and strobe pins depend on the board model...
#if defined(__AVR_ATmega32U4__)
// Arduino Leonardo and Arduino Micro:
#  define MAGSTRIPE_RDT 3  /* data pin (yellow) */
#  define MAGSTRIPE_RCL 2  /* strobe pin (orange) */
#else
// Arduino Uno and Arduino Mega:
#  define MAGSTRIPE_RDT 2  /* data pin (yellow) */
#  define MAGSTRIPE_RCL 3  /* strobe pin (orange) */
#endif

#  define MAGSTRIPE_CLS 4  /* card present pin (green) */

// Cards can be read in one of these possible ways...
enum ReadDirection { READ_UNKNOWN, READ_FORWARD, READ_BACKWARD };

class MagCardRead {
  public:
    MagCardRead(uint8_t cardDetectPin = MAGSTRIPE_CLS, uint8_t strobePin = MAGSTRIPE_RCL, uint8_t dataPin = MAGSTRIPE_RDT);

    // Initialize the library (attach interrupts)...
    void begin(uint8_t track);

    // Deinitialize the library (detach interrupts)...
    void stop();

    // Check if there is a card present for reading...
    bool available();

    // Read the data from the card as ASCII...
    short read(char *data, uint8_t size);

    // The direction of the last card read...
    enum ReadDirection read_direction();

  private:
    uint8_t _cardDetect_pin;
    uint8_t _strobe_pin;
    uint8_t _data_pin;

    uint8_t _track;
    enum ReadDirection direction;

    void reverse_bits();
    bool verify_parity(uint8_t c);
    bool verify_lrc(short start, short length);
    short find_sentinel(uint8_t pattern);
    short decode_bits(char *data, uint8_t size);
};


#endif  /* MAGCARDREAD_H */


/* vim: set expandtab ts=4 sw=4: */

MagCardRead.cpp

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#include "MagCardRead.h"

#include 

// Enough bits to read any of the three tracks...
#define BIT_BUFFER_LEN 800


// Variables used by the interrupt handlers...
static volatile bool next_bit = 0;                       // next bit to read
static volatile unsigned char bits[BIT_BUFFER_LEN / 8];  // buffer for bits being read
static volatile short num_bits = 0;                      // number of bits already read
static volatile uint8_t vdata_pin = 0;                   // data pin to be read by interrupt


// Manipulate the bit buffer...
static void bits_set(short index, bool bit);
static bool bits_get(short index);

// The interrupt handlers...
static void handle_strobe(void);

MagCardRead::MagCardRead(uint8_t cardDetectPin, uint8_t strobePin, uint8_t dataPin)
{
  _cardDetect_pin = cardDetectPin;
  _strobe_pin = strobePin;
  _data_pin = dataPin;

  direction = READ_UNKNOWN;
}

void MagCardRead::begin(uint8_t track)
{
  _track = track;

  pinMode(_cardDetect_pin, INPUT_PULLUP);
  pinMode(_strobe_pin, INPUT_PULLUP);
  pinMode(_data_pin, INPUT_PULLUP);

  // Reading is more reliable when using interrupts...
  attachInterrupt(digitalPinToInterrupt(_strobe_pin), handle_strobe, FALLING);  // strobe pin
}


void MagCardRead::stop()
{
  detachInterrupt(digitalPinToInterrupt(_strobe_pin));
}


bool MagCardRead::available()
{
  vdata_pin = _data_pin;
  return digitalRead(_cardDetect_pin) == LOW;
}


short MagCardRead::read(char *data, unsigned char size)
{
  // Fail if no card present...
  if (!available()) {
    return -1;
  }

  // Empty the bit buffer...
  num_bits = 0;
  next_bit = 0;

  // Wait while the data is being read by the interrupt routines...
  while (available()) {}

  // Decode the raw bits...
  short chars = decode_bits(data, size);
  direction = READ_FORWARD;

  // If the data looks bad, reverse and try again...
  if (chars < 0) {
    reverse_bits();
    chars = decode_bits(data, size);
    direction = READ_BACKWARD;
  }

  // The card could not be read successfully...
  if (chars < 0) {
    direction = READ_UNKNOWN;
  }

  return chars;
}


enum ReadDirection MagCardRead::read_direction()
{
  return direction;
}


void MagCardRead::reverse_bits()
{
  for (short i = 0; i < num_bits / 2; i++) {
    bool b = bits_get(i);

    bits_set(i, bits_get(num_bits - i - 1));
    bits_set(num_bits - i - 1, b);
  }
}


bool MagCardRead::verify_parity(unsigned char c)
{
  unsigned char parity = 0;

  for (unsigned char i = 0; i < 8; i++) { parity += (c >> i) & 1;
  }

  // The parity must be odd...
  return parity % 2 != 0;
}


bool MagCardRead::verify_lrc(short start, short length)
{
  unsigned char parity_bit = (_track == 1 ? 7 : 5);

  // Count the number of ones per column (ignoring parity bits)...
  for (short i = 0; i < (parity_bit - 1); i++) {
    short parity = 0;

    for (short j = i; j < length; j += parity_bit) {
      parity += bits_get(start + j);
    }

    // Even parity is what we want...
    if (parity % 2 != 0) {
      return false;
    }
  }

  return true;
}


short MagCardRead::find_sentinel(unsigned char pattern)
{
  unsigned char bit_accum = 0;
  unsigned char bit_length = (_track == 1 ? 7 : 5);

  for (short i = 0; i < num_bits; i++) { bit_accum >>= 1;                               // rotate the bits to the right...
    bit_accum |= bits_get(i) << (bit_length - 1);  // ...and add the current bit

    // Stop when the start sentinel pattern is found...
    if (bit_accum == pattern) {
      return i - (bit_length - 1);
    }
  }

  // No start sentinel was found...
  return -1;
}


short MagCardRead::decode_bits(char *data, unsigned char size) {
  short bit_count = 0;
  unsigned char chars = 0;
  unsigned char bit_accum = 0;
  unsigned char bit_length = (_track == 1 ? 7 : 5);

  short bit_start = find_sentinel(_track == 1 ? 0x45 : 0x0b);
  if (bit_start < 0) {  // error, start sentinel not found
    return -1;
  }

  for (short i = bit_start; i < num_bits; i++) { bit_accum >>= 1;                                 // rotate the bits to the right...
    bit_accum |= (bits_get(i) << (bit_length - 1)); // ...and add the current bit bit_count++; if (bit_count % bit_length == 0) { if (chars >= size) {  // error, the buffer is too small
        return -1;
      }

      // A null means we reached the end of the data...
      if (bit_accum == 0) {
        break;
      }

      // The parity must be odd...
      if (!verify_parity(bit_accum)) {
        return -1;
      }

      // Remove the parity bit...
      bit_accum &= ~(1 << (bit_length - 1));

      // Convert the character to ASCII...
      data[chars] = bit_accum + (_track == 1 ? 0x20 : 0x30);
      chars++;

      // Reset...
      bit_accum = 0;
    }
  }

  // Turn the data into a null-terminated string...
  data[chars] = '\0';

  if (data[chars - 2] != '?') {  // error, the end sentinel is not in the right place
    return -1;
  }

  // Verify the LRC (even parity across columns)...
  if (!verify_lrc(bit_start, chars * bit_length)) {
    return -1;
  }

  return chars;
}


static void bits_set(short index, bool bit)
{
  volatile unsigned char *b = &bits[index / 8];
  unsigned char m = 1 << (index % 8);

  *b = bit ? (*b | m) : (*b & ~m);
}


static bool bits_get(short index)
{
  return bits[index / 8] & (1 << (index % 8)); } static void handle_strobe() { // Avoid a crash in case there are too many bits (garbage)... if (num_bits >= BIT_BUFFER_LEN) {
    return;
  }

  next_bit = !digitalRead(vdata_pin);

  bits_set(num_bits, next_bit);
  num_bits++;
}


/* vim: set expandtab ts=4 sw=4: */

MagCardReader.ino

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#include "MagCardRead.h"


#define pinCardDetect1 4
#define pinStrobe1 2
#define pinData1 5

#define pinCardDetect2 6
#define pinStrobe2 3
#define pinData2 7

// Visual feedback when the card is being read...
static const byte READ_LED = 8;  //13
static const byte ERROR_LED = 9; //12

//MagCardRead card;
//MagCardRead card(pinCardDetect);
MagCardRead card(pinCardDetect1, pinStrobe1, pinData1);
MagCardRead card2(pinCardDetect2, pinStrobe2, pinData2);

/*
   Track 3 is the one that can contain the most characters (107).
   We add one more to accomodate the final '\0', as the data is a C string...
*/
static const byte DATA_BUFFER_LEN = 108;
static char data[DATA_BUFFER_LEN];


void readCard(MagCardRead crd) {
  // Don't do anything if there isn't a card present...
  if (!crd.available()) {
    return;
  }

  // Show that a card is being read...
  digitalWrite(READ_LED, HIGH);
  Serial.println("-- Read Completed --");

  // Read the card into the buffer "data" (as a null-terminated string)...
  short chars = crd.read(data, DATA_BUFFER_LEN);

  // Show that the card has finished reading...
  digitalWrite(READ_LED, LOW);

  // If there was an error reading the card, blink the error LED...
  if (chars < 0) {
    digitalWrite(ERROR_LED, HIGH);
    delay(250);
    digitalWrite(ERROR_LED, LOW);
    Serial.println("Read Error...");
    Serial.println(chars);

    return;
  }

  Serial.println("Good Read...");
  Serial.print("Read Direction: ");
  //Serial.println(crd.read_direction());
  Serial.println(crd.read_direction() == 1 ? "Forward" : crd.read_direction() == 2 ? "Backward" : "Unknown");


  // Send the data to the computer...
  Serial.println(data);
}

void setup()
{
  pinMode(READ_LED, OUTPUT);
  pinMode(ERROR_LED, OUTPUT);

  // The card data will be sent over serial...
  Serial.begin(9600);

  // Initialize the library for reading track 2...
  card.begin(1);
  card2.begin(2);

  // Start with the feedback LEDs off...
  digitalWrite(READ_LED, LOW);
  digitalWrite(ERROR_LED, LOW);
}


void loop()
{
  readCard(card);
  readCard(card2);
}


/* EOF - MagCardRead.ino */

Conclusion

Most applications with card readers and Arduinos should make use of the magstripelib library by Carlos Rodrigues. If you have a project that uses one of the card readers mentioned in the original blog post then add a capacitor to the data line as mentioned in the original post. If for some reason, you cannot add a capacitor as mentioned or you need to read from two readers, then the code mentioned here may be a good solution.

Categories
Arduino

Magnetic Card Reader (TTL) with Arduino

Introduction

I was cleaning up and found a project I abandoned about 20 years ago. I was attempting to read magnetic strip cards using the PC Parallel Port. I had done it previously with a different card reader but I had some trouble with these two particular units. I wanted to test them before I decided to get rid of them so I broke out an Arduino Nano and found the magstripelib library written by Carlos Rodrigues on GitHub. I could not get the readers to work with the code so I looked at the libraries but found no issue with them. I then found an issue with the hardware that was rather easy to solve.

The Magnetic Card Readers

I purchased the card readers from either All Electronics or Electronic Goldmine. (Both places are great for obtaining low cost surplus electronics.) The readers are made by Tokin and have a part number of MCS-131P-3. The PCB contains 1550-B014-00-1 and YEC YE-34V N markings.

Card Reader Left Side View   Card Reader Top View   Card Reader Right Side View

I had recalled that there was an issue with the connector cable on the readers that I wanted to resolve. The red wire is connected to ground and the brown wire is connected to Vcc (+5V). This was verified by checking with an ohm meter. Additionally, there is a card sense connection but there is no wire. With a simple modification, we can switch the wires and add a new wire for the card sense connection. While we are at it, We will make the readers breadboard friendly by changing the connector to a 5 pin male header.

Modifying the Magnetic Card Reader

Step 1 – remove the PCB

Use a plastic tool or very carefully use a screwdriver to remove the hot glue holding the PCB in place and carefully slide the card out of the reader.

Card Reader Right Side View   Plastic Tool   Separating PC Board

Once the card is free, you can see that the board layout is made to accommodate the correct connections but there was an effort made to switch the ground and Vcc wires.

Separated PC Board   Close-up of the wires connecting to the PCB

Step 2 – Remove the strain relief zip-tie

Cut the zip-tie and remove.

   

Step 3 – Switch the red and brown wires

Unsolder the red and brown wires, switch them, and solder in place.

   

Step 4 – Add the card sense wire

Cut a length of stranded 26 AWG wire, strip the ends and solder to the card-sense connection.

Step 5 – Add the strain relief

Use a zip-tie to reapply the strain relief to the wire connector using the two holes in the PCB.

             

Step 6 – Reinstall the PCB

Carefully return the PCB to the plastic assembly by reinserting into the slot.

Step 7 – Remove the old connector

Cut off the old connector.

Step 8 – Prep the wires

Strip the ends of the wire and tin with solder.

Step 9 – Cut Heat-Shrink Tubing

Cut 5 pieces of 1/16″ heat-shrink to about 1/4″ in length and one or two pieces of 3/16″ heat-shrink tubing about 3/4″ in length.

Step 10 – Prepare male header pins

Cut a single-row male header to 5 positions.

Step 11 – Solder wires to header

Tin the short pins with solder. Slide the 3/16″ heat-shrink piece(s) over all of the wires together. Solder each wire to the tinned header pins by first sliding a 1/16″ heat-shrink tubing over each wire.

Step 12 – Shrink the heat-shrink tubing

You may want to use a breadboard to keep the pins aligned while heating the tubing as the plastic may become pliable and allow the pins to move. Heat the tubing to fix them in place.

Testing with the Arduino

Wire up the Arduino

The wiring will depend on the processor on the Arduino. If you are uncertain as to which processor your Arduino has, check out the Wikipedia article at https://en.wikipedia.org/wiki/List_of_Arduino_boards_and_compatible_systems.

The Arduino Nano uses the following wiring.

Wire Color Function Arduino Pin
Brown Ground GND
Red Vcc 5V
Green Card Detect 4
Orange Strobe 3
Yellow Data 2

Load the example sketch

Connect the Arduino to the PC and start the Arduino IDE and add the magstripelib library by going to the menu Tools > Manage Libraries… Search for MagStrip and install the library by Carlos Rodrigues.

Find and Install MagStripe Library

Load the MagStripe example by going to the menu File > Examples > MagStripe > MagStripeReader.

Make certain the correct board and port are selected then upload the code to the Arduino. Once the code is loaded, open the serial monitor and run a card through the reader to see if there is any data read. I did not receive any data so I added some lines of code to let me know that there was an issue. Below is the modified code. The modified code let me know that the card was detected and it attempted to read it but ran into an issue decoding the data.

/*
 * MagStripeReader - Read data from a magnetic stripe card (track 1, 2 or 3).
 *
 * Copyright (c) 2010 Carlos Rodrigues <cefrodrigues@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


#include 


// Visual feedback when the card is being read...
static const byte READ_LED = 13;
static const byte ERROR_LED = 12;

MagStripe card;

/*
 * Track 3 is the one that can contain the most characters (107).
 * We add one more to accomodate the final '\0', as the data is a C string...
 */
static const byte DATA_BUFFER_LEN = 108;
static char data[DATA_BUFFER_LEN];


void setup()
{
  pinMode(READ_LED, OUTPUT);
  pinMode(ERROR_LED, OUTPUT);
  
  // The card data will be sent over serial...
  Serial.begin(9600);
  
  // Initialize the library for reading track 2...
  card.begin(2);

  // Start with the feedback LEDs off...
  digitalWrite(READ_LED, LOW);
  digitalWrite(ERROR_LED, LOW);
}

 
void loop()
{
  // Don't do anything if there isn't a card present...
  if (!card.available()) {
    return;
  }
  
  // Show that a card is being read...
  digitalWrite(READ_LED, HIGH);
  
  // Read the card into the buffer "data" (as a null-terminated string)...
  short chars = card.read(data, DATA_BUFFER_LEN);
  
  // Show that the card has finished reading...
  digitalWrite(READ_LED, LOW);
  Serial.println("-- Read Completed --");
  
  // If there was an error reading the card, blink the error LED...
  if (chars < 0) {
    digitalWrite(ERROR_LED, HIGH);
    delay(250);
    digitalWrite(ERROR_LED, LOW);
    Serial.println("Read Error...");
    Serial.println(chars);

    return;
  }
  
  Serial.println("Good Read...");
  Serial.print("Read Direction: ");
  //Serial.println(card.read_direction());
  Serial.println(card.read_direction()==1 ? "Forward" : card.read_direction()==2 ? "Backward" : "Unknown");

  // Send the data to the computer...
  Serial.println(data);
}


/* EOF - MagStripeReader.ino */

The result was always a failed read no mater which card was read.

-- Read Completed --
Read Error...
-1

Troubleshooting

As mentioned in the introduction, I first focused on the code to see if I could find something that was amiss. I also attempted to switch the strobe and clock wires to see if I had identified them incorrectly. I then tried using an Arduino Uno to see if the Nano was a problem but I still could not successfully read a card. I then turned my attention to the hardware. The first thing that I did was to use my Tenma 72-8705 oscilloscope to look at the strobe and data lines. I noted that it looked like the data line had narrow spikes on some clock edges. I did not think much of it at the time.


Channel 1 is Strobe and Channel 2 is Data

I decided to break out the Saleae Logic Analyzer to capture the strobe and data lines and manually decode the data to see if the readers are working properly. I saw the same spikes on the data line which occurs with the rising clock edge.


Channel 0 is Strobe, Channel 1 is Data, and Channel 2 is Card Sense

Since the spikes occur on a rising clock edge, it really should not be an issue as the data is only valid on a falling clock edge. I exported the captured data into a CSV file and looked at it in Excel. I was able to decode the data and verified that the data was reading the card properly. To know that the data was read properly, it was necessary to read some of the information from Magtek.

I took another look at the code and see that the data values are being flipped from the previous value. I was unable to determine what the width of the spikes are but I suspect that the spikes may flip the data value in the code but are not wide enough to raise the hardware interrupt on the Arduino when it returns to +5V. If this is the case then it is understandable why there are issues reading the cards. This could be fixed using hardware or software. I took the hardware approach by connecting a 0.01uF capacitor between the data line and Vcc. Once the capacitor was added to remove the spikes from the data line, the code was able to read the cards successfully.

Going Further

It may be possible to resolve the issue observed with this particular card reader by removing the interrupt from the data pin and reading the data value on the clock interrupt instead. This in turn would free up the hardware interrupt used for the data pin and allow any digital pin to be used for the data line. This may allow the reading of two track from readers which read multiple tracks or allow the use of more than one card reader.

Part II ->