Sunday, October 24, 2021

An easy way of improving the randomness of the random function in Arduino code

 Here is how to create two random numbers from 1 to 5 on an Arduino:

x = random (1,6) ;

y = random (1,6) ;

Don't be tricked by the 6. The actual output ranges from 1 to 5 in these examples. Anyway.

If you don't use the function randomSeed(iSeed)you will get the same sequence of random numbers every time you run your program. This is because random actually generates a pseudo random number, which means it loops through all available answers and then loops back to the beginning again. 

So randomSeed(iSeed)is used to start in a different place in the loop every time you run the program. iSeed  must vary to change the starting point of random

Often randomSeed is called in the setup part of the Arduino program.

But how can you choose a different iSeed every time you run the program? An often suggested method is to read a floating analog input. "Floating" means it is not connected to anything, so electrical fields wafting in from your hand or cell phone or brain move the electrons on the circuit board track which connects to the input. The moving electrons produce a tiny current and the tiny current produces a tiny change in voltage, which you read using the function  ???

 You can improve the random variation of voltage at A0 (for example) simply by adding a long wire to it!

Here's the board with no connection to A0:


And here are the variations in voltage...

A0, rand seed: 265, min 265, max 265, delta 0
A0, rand seed: 263, min 263, max 265, delta 2
A0, rand seed: 261, min 261, max 265, delta 4
A0, rand seed: 259, min 259, max 265, delta 6
A0, rand seed: 258, min 258, max 265, delta 7
A0, rand seed: 257, min 257, max 265, delta 8
A0, rand seed: 256, min 256, max 265, delta 9
A0, rand seed: 255, min 255, max 265, delta 10
A0, rand seed: 254, min 254, max 265, delta 11
A0, rand seed: 254, min 254, max 265, delta 11
A0, rand seed: 253, min 253, max 265, delta 12
A0, rand seed: 252, min 252, max 265, delta 13
A0, rand seed: 251, min 251, max 265, delta 14
A0, rand seed: 251, min 251, max 265, delta 14
A0, rand seed: 251, min 251, max 265, delta 14
A0, rand seed: 250, min 250, max 265, delta 15
A0, rand seed: 250, min 250, max 265, delta 15
A0, rand seed: 249, min 249, max 265, delta 16
A0, rand seed: 249, min 249, max 265, delta 16
A0, rand seed: 248, min 248, max 265, delta 17
A0, rand seed: 247, min 247, max 265, delta 18
A0, rand seed: 248, min 247, max 265, delta 18
A0, rand seed: 247, min 247, max 265, delta 18
A0, rand seed: 246, min 246, max 265, delta 19
A0, rand seed: 247, min 246, max 265, delta 19

... going from 246 to 265.

Here is the Arduino with a long wire connected to A0:


And here are the variations in voltage (with a long wire)...

A0, rand seed: 48, min 48, max 48, delta 0
A0, rand seed: 59, min 48, max 59, delta 11
A0, rand seed: 67, min 48, max 67, delta 19
A0, rand seed: 81, min 48, max 81, delta 33
A0, rand seed: 93, min 48, max 93, delta 45
A0, rand seed: 133, min 48, max 133, delta 85
A0, rand seed: 193, min 48, max 193, delta 145
A0, rand seed: 249, min 48, max 249, delta 201
A0, rand seed: 308, min 48, max 308, delta 260
A0, rand seed: 369, min 48, max 369, delta 321
A0, rand seed: 431, min 48, max 431, delta 383
A0, rand seed: 488, min 48, max 488, delta 440
A0, rand seed: 537, min 48, max 537, delta 489
A0, rand seed: 577, min 48, max 577, delta 529
A0, rand seed: 612, min 48, max 612, delta 564
A0, rand seed: 629, min 48, max 629, delta 581
A0, rand seed: 799, min 48, max 799, delta 751
A0, rand seed: 747, min 48, max 799, delta 751
A0, rand seed: 325, min 48, max 799, delta 751
A0, rand seed: 310, min 48, max 799, delta 751
A0, rand seed: 297, min 48, max 799, delta 751
A0, rand seed: 288, min 48, max 799, delta 751
A0, rand seed: 278, min 48, max 799, delta 751
A0, rand seed: 273, min 48, max 799, delta 751
A0, rand seed: 268, min 48, max 799, delta 751


...going from 48 to 799, clearly an improvement.

Remember the analog signal is not the random number, but how to make the seed for the random number generator function, random().

Here  is the code I used to do the test above:

/*
 * Adding a wire to a floating analog input can give a better range
 * of random seeds.
 */

// The setup() method runs once, when the sketch starts
void setup()   {                
    Serial.begin (9600) ;
}

// the loop() method runs over and over again,
// as long as the Arduino has power

int iMin = 1024 ;
int iMax = 0 ;
int iLoopResetCount = 0 ;

void loop()                     
{
    int iVal = analogRead (A0) ;
    if (iVal < iMin) {
        iMin = iVal ;
    }
    if (iVal > iMax) {
        iMax = iVal ;
    }
    
    Serial.print ("A0, rand seed: ") ;
    Serial.print (iVal) ;
    Serial.print (", min ") ;
    Serial.print (iMin) ;
    Serial.print (", max ") ;
    Serial.print (iMax) ;
    Serial.print (", delta ") ;
    Serial.print (iMax-iMin) ;
    Serial.println() ;
    
    delay(500);     

    iLoopResetCount+=1 ;
    
    if (iLoopResetCount == 25) {
       iLoopResetCount=0 ;
       Serial.println("loop reset") ;
       iMin = 1024 ;
       iMax = 0 ;
    }
}

And here is how in practice you use randomSeed:

/*
 * Move a servo to random positions every 10 seconds.
 */

// The setup() method runs once, when the sketch starts
void setup()   {                
    Serial.begin (9600) ;

    int iSeed = analogRead (A0) ;
    randomSeed(iSeed);
    Serial.print ("Seed = ") ;
    Serial.println (iSeed) ;
}

// the loop() method runs over and over again,
// as long as the Arduino has power
void loop()                     
{
    // Move a servo randomly between 0 and 180 degrees
    int iServoDegrees = random(0,181) ;    
    Serial.print ("Would move the servo to ") ;
    Serial.print (iServoDegrees) ;
    Serial.println (" degrees ") ;
    delay(10000);     // 10000ms = 10 sec
}





 

 

.

 


 

 


Friday, October 8, 2021

Automatically creating a CRecordset derived class from an Access table

 There used to be Wizard in the Visual Studio IDE which wrote the CPP and H files of a CRecordset derived class just by pointing at a table in an Access database. No more, or I can't find it in the zillion options of the IDE, or I'm not good at searching on line. Anyway. In the end I decided that, since I was going to have to do the work on many tables I may as well write the wizard myself, in C# to give me more practice in that language. 

Here's the overview of the application, RecSecMaker:


I'm now really glad I wrote it. When a database table has more than 3 or 4 columns doing this stuff by hand becomes long winded and error prone. Now (cliche warning) in just a few clicks the whole class is written for me immediately and automatically.

Here is an example of the output of the program, first the H file:

#pragma once

#include <afxdb.h>
class CCustomersRecSet: public CRecordset
{
public:
    CCustomersRecSet(CDatabase* pdb = NULL);
    int m_iCustomerID;
    //(System.Int32)
    CString m_csCompanyName;
    //(System.String)
    CString m_csContactFirstName;
    //(System.String)
    CString m_csContactLastName;
    //(System.String)
    CString m_csAddress1;
    //(System.String)
    CString m_csAddress2;
    //(System.String)
    CString m_csAddress3;
    //(System.String)
    CString m_csPostalCode;
    //(System.String)
    CString m_csTitle;
    //(System.String)
    CString m_csPhoneNumber;
    //(System.String)
    CString m_csFaxNumber;
    //(System.String)
    CString m_csEmailAddress;
    //(System.String)
    CString m_csNotes;
    //(System.String)
    BOOL m_bAcceptsSpam;
    //(System.Boolean)
    CString m_csWEBAddress;
    //(System.String)
    BYTE m_iSexId;
    //(System.Byte)
    int m_iCountryId;
    //(System.Int32)
    CString m_csContactMiddleName;
    //(System.String)
    CString m_csAddress4;
    //(System.String)
    int m_iCompanyID;
    //(System.Int32)

    virtual CString GetDefaultSQL();
    virtual void DoFieldExchange(CFieldExchange* pFX);
    virtual CString GetDefaultConnect();
};

And here is the CPP file:

#include "stdafx.h"
#include "CustomersRecSet.H"

CCustomersRecSet::CCustomersRecSet(CDatabase* pdb /*= NULL*/) : CRecordset(pdb)
{
    m_nFields = 20;
    m_nDefaultType = dynaset;
}

CString CCustomersRecSet::GetDefaultSQL()
{
    CString SqlString = L"SELECT * FROM Customers";
    return SqlString ;
}

void CCustomersRecSet::DoFieldExchange(CFieldExchange* pFX)
{
    pFX->SetFieldType(CFieldExchange::outputColumn);
    RFX_Int(pFX, _T("[CustomerID]"), m_iCustomerID);
    //(System.Int32)

    RFX_Text(pFX, _T("[CompanyName]"), m_csCompanyName);
    //(System.String)

    RFX_Text(pFX, _T("[ContactFirstName]"), m_csContactFirstName);
    //(System.String)

    RFX_Text(pFX, _T("[ContactLastName]"), m_csContactLastName);
    //(System.String)

    RFX_Text(pFX, _T("[Address1]"), m_csAddress1);
    //(System.String)

    RFX_Text(pFX, _T("[Address2]"), m_csAddress2);
    //(System.String)

    RFX_Text(pFX, _T("[Address3]"), m_csAddress3);
    //(System.String)

    RFX_Text(pFX, _T("[PostalCode]"), m_csPostalCode);
    //(System.String)

    RFX_Text(pFX, _T("[Title]"), m_csTitle);
    //(System.String)

    RFX_Text(pFX, _T("[PhoneNumber]"), m_csPhoneNumber);
    //(System.String)

    RFX_Text(pFX, _T("[FaxNumber]"), m_csFaxNumber);
    //(System.String)

    RFX_Text(pFX, _T("[EmailAddress]"), m_csEmailAddress);
    //(System.String)

    RFX_Text(pFX, _T("[Notes]"), m_csNotes);
    //(System.String)

    RFX_Bool(pFX, _T("[AcceptsSpam]"), m_bAcceptsSpam);
    //(System.Boolean)

    RFX_Text(pFX, _T("[WEBAddress]"), m_csWEBAddress);
    //(System.String)

    RFX_Byte(pFX, _T("[SexId]"), m_iSexId);
    //(System.Byte)

    RFX_Int(pFX, _T("[CountryId]"), m_iCountryId);
    //(System.Int32)

    RFX_Text(pFX, _T("[ContactMiddleName]"), m_csContactMiddleName);
    //(System.String)

    RFX_Text(pFX, _T("[Address4]"), m_csAddress4);
    //(System.String)

    RFX_Int(pFX, _T("[CompanyID]"), m_iCompanyID);
    //(System.Int32)

}

CString CCustomersRecSet::GetDefaultConnect()
{
    // You'll have to change this for your own situation, this is a guide...
    CString csDriver = L"MICROSOFT ACCESS DRIVER (*.mdb)";
    CString csConnect;
    csConnect.Format(L"ODBC;DRIVER={%s};DSN='';DBQ=%s", csDriver.GetString(), theApp.GetProgDbFullFileName().GetString());
    return csConnect;
}

The program automatically declares fields of the correct type based on the type of the Access column, and calls the appropriate RFX_ macro, also based on the type of the Access column.

One of the main parts of the program is how the Access database is interrogated, I do that in a class called CTableDetails.

// CTableDetails: A class which reads the details of a particular table, the schema.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.OleDb;
using System.Data.Common;
using System.Diagnostics;
using System.Windows.Forms;

namespace RecSecMaker
{
    // This class contains all I want to know about the format of a column (field) in a database table...
    public struct AccessFieldData_t
    {
        public AccessFieldData_t (string sColName, Type TheType, int iColSize)
        {
            m_sColName = sColName ; // "SAPCODE"
            m_ColType = TheType;  // is this a string or a double or...? System.Int16 for example
            m_iColSize = iColSize ;  // How big is the field in bytes, length of the string if it is a string
        }
        public string m_sColName;
        public Type m_ColType;
        public int m_iColSize;
    };

    public class CTableDetails
    {
        public readonly string m_sTableName; // "Birthdays"

        private List<AccessFieldData_t> m_ListOfFields = null;

        // How many columns are in the table?
        public int GetNumFields ()
        {
            if (m_ListOfFields == null)
            {
                return 0;
            }

            return m_ListOfFields.Count;
        }

        // Get details about a particular column or field
        public AccessFieldData_t GetFieldDataByIndex (int i)
        {
            return m_ListOfFields[i];
        }

        // Construct the object from the database filename and the table inside that filename
        public CTableDetails (string sMdbFullFileName, string sTableName)
        {
            m_sTableName = sTableName;
            try
            {
                // Use using so we don't have to dispose of it...
                using (OleDbConnection ADbConnection = DbHelpers.OpenDbConnection(sMdbFullFileName))
                {
                    // These three lines are just to get hold of the table...
                    DbCommand DbCmd = ADbConnection.CreateCommand();
                    DbCmd.CommandText = "select * from " + sTableName + " where 1 = 0";
                    DbCmd.CommandType = CommandType.Text;

                    // The Reader will give us the table...
                    DbDataReader Reader = DbCmd.ExecuteReader();

                    // Now we can get how the columns are specified...
                    DataTable Schema = Reader.GetSchemaTable();

                    // Get ready to store information on each column...
                    m_ListOfFields = new List<AccessFieldData_t>();

                    foreach (DataRow row in Schema.Rows)
                    {
                        // Get hold of the three things I am interested in...
                        AccessFieldData_t TempData = new AccessFieldData_t(row.Field<string>("ColumnName"), row.Field<Type>("DataType"), row.Field<int>("ColumnSize"));

                        // Save the column details
                        m_ListOfFields.Add(TempData);
                    }

                    foreach (AccessFieldData_t Dat in m_ListOfFields)
                    {
                        Debug.WriteLine("{0}, {1}, {2}", Dat.m_sColName, Dat.m_ColType, Dat.m_iColSize);
                    }
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("Exception in database: " + e.Message);
            }

        }
    }
}

I did not know how to do this before writing the program, so I learned something and built something useful.

I thought about writing about an article RecSecMaker for stackoverflow, but hell, the loops you have to jump through just to impart information! And I thought about GITHub, but life is too short.

So if anyone wants the EXE or the sources just contact me. All offered as is with no guarantees and no support.