{"id":3694,"date":"2021-09-14T14:00:49","date_gmt":"2021-09-14T21:00:49","guid":{"rendered":"https:\/\/www.cloudacm.com\/?p=3694"},"modified":"2021-09-10T06:24:51","modified_gmt":"2021-09-10T13:24:51","slug":"esp-32-flight-datalogger-turning-code-into-blocks","status":"publish","type":"post","link":"https:\/\/www.cloudacm.com\/?p=3694","title":{"rendered":"ESP-32 Flight Datalogger &#8211; Turning Code into Blocks"},"content":{"rendered":"<p>Developing firmware for hardware typically is not a ground up method. Many AVR and ESP projects that exist have been developed using pre-developed code. This can be the main code base or libraries that provide support. Libraries are easier to adopt into a project because they can be defined and called when needed. Code base reuse doesn&#8217;t offer this. Much of it is specific for the application it was developed for. As a result, the code base will need to be edited. Turning the code base into blocks of routines can provide the developer reuse code that can be adopted for other purposes.<\/p>\n<p>Determining which code base to develop as the foundation depends on what function all others will revolve around. Since the process used on the ESP32-Cam module project will be a super loop and linear in nature, the primary purpose of logging GPS data will be that foundation. So this can can be called the primary function. The code base will be example code demonstrating the GPS module, so the TinyGPS++ example was used as the code base. When code is run, there will be the standard setup function followed by the main loop function. Instead of placing all of the code in either of these functions, sub functions will be created that serve a specific purpose.<\/p>\n<p>Here is an example, see code &#8220;GPS_SoftwareSerial_TinyGPS++_Ver9.ino&#8221;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/** \r\n\r\nGPS_SoftwareSerial_TinyGPS++_Ver9.ino\r\nBase Results from Altitude Readings\r\nJuly 1st, 2021 - 22:07\r\n\r\n**\/\r\n\r\n\r\n\/\/ Libraries\r\n#include &lt;TinyGPS++.h&gt;\r\n#include &lt;SoftwareSerial.h&gt;\r\n\r\n\r\n\/\/ Variables and Constants\r\nTinyGPSPlus gps;\t\/\/ The TinyGPS++ object\r\nSoftwareSerial SoftSerial(8, 7);\t\/\/ The serial connection to the GPS device\r\nunsigned long last = 0UL;\t\/\/ For stats that happen every 5 seconds\r\nstatic const double SixtyAcres_LAT = 47.702875, SixtyAcres_LON = -122.137535;\r\nint PadSeconds;\r\nint PadMinutes;\r\nint PadDays;\r\nint PadMonths;\r\n\r\n\r\n\/\/ Routines and Subroutines\r\n\r\n\r\n\/\/ Hardware Initialization Routine\r\nvoid HardwareInit()\r\n{\r\n  Serial.begin(115200);\r\n  SoftSerial.begin(9600);\r\n} \r\n\r\n\r\n\/\/ GPS Reading Fault Routine\r\nvoid GPSReadingFault()\r\n{\r\n    if (gps.charsProcessed() &lt; 10)\r\n      Serial.println(F(\"WARNING: No GPS data.  Check wiring.\"));\r\n}\r\n\r\n\r\n\/\/ Printout Header Routine\r\nvoid PrintHeader()\r\n{\r\n  Serial.println(F(\"Lat,Long,Date,Time,MPH,Course,Altitude,Home,Bearing,Cardinal\"));\r\n}\r\n\r\n\r\n\/\/ Printout Location Routine\r\nvoid PrintOutLocation()\r\n{\r\n    Serial.print(gps.location.lat(), 6);\r\n    Serial.print(F(\",\"));\r\n    Serial.print(gps.location.lng(), 6);\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\/\/ Printout Date Routine\r\nvoid PrintOutDate()\r\n{\r\n    PadMonths = gps.date.month();\r\n      if (PadMonths &lt; 10)\r\n        {Serial.print(F(\"0\"));}\r\n    Serial.print(gps.date.month());\r\n    Serial.print(F(\"\/\"));\r\n    PadDays = gps.date.day();\r\n      if (PadDays &lt; 10)\r\n        {Serial.print(F(\"0\"));}\r\n    Serial.print(gps.date.day());\r\n    Serial.print(F(\"\/\"));\r\n    Serial.print(gps.date.year());\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\r\n\/\/ Printout Time Routine\r\nvoid PrintOutTime()\r\n{\r\n    Serial.print(gps.time.hour());\r\n    Serial.print(F(\":\"));\r\n    PadMinutes = gps.time.minute();\r\n      if (PadMinutes &lt; 10)\r\n        {Serial.print(F(\"0\"));}\r\n    Serial.print(gps.time.minute());\r\n    Serial.print(F(\":\"));\r\n    PadSeconds = gps.time.second();\r\n      if (PadSeconds &lt; 10)\r\n        {Serial.print(F(\"0\"));}\r\n    Serial.print(gps.time.second());\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\/\/ Printout Speed Routine\r\nvoid PrintOutSpeed()\r\n{\r\n    Serial.print(gps.speed.mph());\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\/\/ Printout Course Routine\r\nvoid PrintOutCourse()\r\n{\r\n    Serial.print(gps.course.deg());\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\/\/ Printout Altitude Routine\r\nvoid PrintOutAltitude()\r\n{\r\n    Serial.print(gps.altitude.feet());\r\n    Serial.print(F(\",\"));\r\n}\r\n\r\n\r\n\/\/ Reference Bearing Routine\r\nvoid BearingReference()\r\n{\r\n      \r\n      double distanceToSixtyAcres =\r\n        TinyGPSPlus::distanceBetween(\r\n          gps.location.lat(),\r\n          gps.location.lng(),\r\n          SixtyAcres_LAT, \r\n          SixtyAcres_LON);\r\n      double courseToSixtyAcres =\r\n        TinyGPSPlus::courseTo(\r\n          gps.location.lat(),\r\n          gps.location.lng(),\r\n          SixtyAcres_LAT, \r\n          SixtyAcres_LON);\r\n      \r\n      Serial.print(distanceToSixtyAcres\/1609, 6);\r\n      Serial.print(F(\",\"));\r\n      Serial.print(courseToSixtyAcres, 6);\r\n      Serial.print(F(\",\"));\r\n      Serial.print(TinyGPSPlus::cardinal(courseToSixtyAcres));\r\n}\r\n\r\n\r\n\/\/ Inbound Serial Data Processing\r\nvoid InboundSerialData()\r\n{\r\n  while (SoftSerial.available() &gt; 0)\r\n    gps.encode(SoftSerial.read());\r\n\r\n  if (gps.altitude.isUpdated())\r\n  {\r\n    PrintOutLocation();\r\n    PrintOutDate();\r\n    PrintOutTime();\r\n    PrintOutSpeed();\r\n    PrintOutCourse();\r\n    PrintOutAltitude();\r\n    BearingReference();\r\n    Serial.println();   \r\n  }\r\n\r\n  else if (millis() - last &gt; 5000)\r\n  {\r\n    if (gps.location.isValid())\r\n    GPSReadingFault();\r\n    last = millis();\r\n  }\r\n}\r\n\r\n\r\n\r\nvoid setup()\r\n{\r\n  HardwareInit();\r\n  PrintHeader();\r\n}\r\n\r\nvoid loop()\r\n{\r\n  InboundSerialData();\r\n}\r\n\r\n<\/pre>\n<p>The code starts with a title comment section followed by Libraries then Variables and Constants. Underneath that are the Routines and Subroutines. The routines follow this hierarchy. Setup routine calls the Hardware Initialization routine, this is only run once. Then the PrintHeader routine outputs the row containing the descriptions of each column. The super loop routine calls the InboundSerialData routine which then outputs the readings from the GPS module. It loops through this every time there is a data stream from the GPS module.<\/p>\n<p>If the objective were to store the results on microSD media, then a PrintResults routine would need to be created. The rest of the code base can be reused.<\/p>\n<p>Here is another example, see code &#8220;Adafruit INA219 Current Sensor_Ver6.ino&#8221;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/** \r\n\r\nAdafruit INA219 Current Sensor_Ver6.ino\r\nCompleted Blocking of Code\r\nJune 30th, 2021 - 07:52\r\n\r\n**\/\r\n\r\n\r\n\/\/ Libraries\r\n#include &lt;Wire.h&gt;\r\n#include &lt;Adafruit_INA219.h&gt;\r\n\r\n\r\n\/\/ Variables and Constants\r\nAdafruit_INA219 ina219(0x40);\r\n    \/\/ Initializes I2C communication with the Adafruit_INA219 device address 0x40\r\nuint32_t currentFrequency;\r\nfloat shuntvoltage = 0;\r\nfloat busvoltage = 0;\r\nfloat current_mA = 0;\r\nfloat loadvoltage = 0;\r\nfloat power_mW = 0;\r\n\r\n\r\n\/\/ Routines and Subroutines\r\n\r\n\r\n\r\n\/\/ Get Voltage Sensor Value Routine\r\nvoid GetVoltSensorValues()\r\n{\r\n  shuntvoltage = ina219.getShuntVoltage_mV();\r\n  busvoltage = ina219.getBusVoltage_V();\r\n  current_mA = ina219.getCurrent_mA();\r\n  power_mW = ina219.getPower_mW();\r\n  loadvoltage = busvoltage + (shuntvoltage \/ 1000);\r\n}\r\n\r\n\r\n\/\/ Printout Header Routine\r\nvoid PrintHeader()\r\n{\r\n  Serial.println(\"Bus Voltage,Shunt Voltage,Load Voltage,Current,Watts\");\r\n}\r\n\r\n\r\n\/\/ Printout Results Routine\r\nvoid PrintOut()\r\n{\r\n  Serial.print(busvoltage); Serial.print(\",\");\r\n  Serial.print(shuntvoltage); Serial.print(\",\");\r\n  Serial.print(loadvoltage); Serial.print(\",\");\r\n  Serial.print(current_mA); Serial.print(\",\");\r\n  Serial.print(power_mW); Serial.println(\"\");\r\n}\r\n\r\n\r\n\/\/ Timer Delay Routine\r\nvoid TimerDelay()\r\n{\r\n  delay(1000);\r\n}\r\n\r\n\r\n\/\/ Hardware Initialization Routine\r\nvoid HardwareInit()\r\n{\r\n  pinMode(LED_BUILTIN, OUTPUT);\r\n  Serial.begin(115200);\r\n  delay(10);\r\n}  \r\n  \r\n  \r\n\/\/ Fault Detetection and Alerting Routine\r\nvoid FaultDetect()\r\n{\r\n  if (! ina219.begin()) {\r\n    digitalWrite(LED_BUILTIN, HIGH);\r\n    while (1) { delay(10); }\r\n  }\r\n} \r\n\r\n\r\n\/\/ Sensor Calibraion Scaling Routine\r\nvoid SensorCalibration()\r\n{\r\n  ina219.setCalibration_16V_400mA();\r\n}\r\n\r\n\r\n\/\/ Clear Fault Alert Routine\r\nvoid ClearFaultAlert()\r\n{\r\n  digitalWrite(LED_BUILTIN, LOW);\r\n}\r\n\r\n\r\n\r\n\/\/ Setup Routine\r\nvoid setup() \r\n{\r\n  HardwareInit();\r\n  FaultDetect();\r\n  SensorCalibration();\r\n  ClearFaultAlert();\r\n  PrintHeader();\r\n}\r\n\r\n\r\n\/\/ Looping Routine\r\nvoid loop() \r\n{\r\n  GetVoltSensorValues();\r\n  PrintOut();\r\n  TimerDelay();\r\n}\r\n\r\n<\/pre>\n<p>Each routine is called either once or in a loop. It&#8217;s easier to know what the program is going to do. There is little need for excessive comments as the functions are self explanatory. Again, if serial print commands were to be replaced with microSD memory write commands, the 2 functions PrintHeader and PrintOut would only need to be edited.<\/p>\n<p>When editing base code from a third party, it is important to test the code as is to get a baseline. From that point moving forward, each edit should be detailed in the title comment section and a sequential version saved. No matter how simple the edit may seem, the code revision should be retested to validate it. Further edits should follow this same pattern. A good rule of thumb for editing is this process. Create and test a baseline. Create a title comments version. Next create a libraries version, followed by a variable and constants section. Now the editing can start to focus on routine revisions. Eventually the code base will be organized and each of the sections can be modified, reused, or removed with less impact on the entire code base.<\/p>\n<p>It may seem a painstaking process. However, it is far less cumbersome than scouring through several copies of code base for a peppered edit that broke the project. There have been many abandonded projects due to poor version controls.<\/p>\n<p>One other benefit to code development that is object orientated is any late stage changes that may be introduced can be easily adopted. This gives a level of agility that would not exist otherwise. The next section will cover how additional sensors were added to the ESP32-Cam project midway through its firmware development stage.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Developing firmware for hardware typically is not a ground up method. Many AVR and ESP projects that exist have been developed using pre-developed code. This can be the main code base or libraries that provide support. Libraries are easier to adopt into a project because they can be defined and called when needed. Code base reuse doesn&#8217;t offer this. Much of it is specific for the application it was developed for. As a result, the code base will need to&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.cloudacm.com\/?p=3694\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12,3],"tags":[],"class_list":["post-3694","post","type-post","status-publish","format-standard","hentry","category-esp32-cam","category-rd"],"_links":{"self":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3694","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3694"}],"version-history":[{"count":24,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3694\/revisions"}],"predecessor-version":[{"id":3790,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3694\/revisions\/3790"}],"wp:attachment":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3694"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3694"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3694"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}