← Back to labs

Lab 1: Artemis and Bluetooth

ECE 4160 – Fast Robots

Lab1A

The goal of this lab was to establish familiarity with the Arduino IDE as well as the Artemis board.

Prelab

I started with installing the Arduino IDE and setting up the Artemis board, connecting it via USB to my computer.

Blink

First I test Blink found in File->Examples->01.Basics. We can observe the blue light is toggled on an off at a regular interval.

Blink demo on the Artemis board

Serial

Next I test Example4_Serial found in File -> Examples -> Apollo3. The program allows the user to send a message to the board which it then echos back, ensuring the serial communication is working properly.

Serial demo on the Artemis board

Temperature

Here I test the temperature sensor using the file Example2_analogRead from File -> Examples -> Apollo3. I place my thumb on the sensor during the test and we can see the temperature slowly rise due to the heat from my finger.

Temperature demo on the Artemis board

Microphone

Finally I test the microphone using Example1_MicrophoneOutput found under File -> Examples -> PDM. Testing the microphone I say "Test" repeatedly to view how the output changes with sound input.

Microphone demo on the Artemis board

Lab1B

Before starting this part of the Lab I had to ensure I had the proper version of python and pip installed. I then created a virtual environment and read through the codebase to understand the functions that I would have to use.

Configuration

Next in order to set up the Artemis/Bluetooth connection, I installed the ArduinoBLE library burned the example code to my board. I then updated the config file with my MAC adress after printing it to the serial monitor.

Lab Tasks

Task 1: ECHO

Here I send the ECHO command from the computer to the Artemis board along with a string. The ECHO command causes the Artemis board to send back the exact same string. This allows us to verify that communication is properly working in both directions.

Arduino Code:


    case ECHO:

            char char_arr[MAX_MSG_SIZE];

            // Extract the next value from the command string as a character array
            success = robot_cmd.get_next_value(char_arr);
            if (!success)
                return;

            tx_estring_value.clear();
            tx_estring_value.append("Robot says -> ");
            tx_estring_value.append(char_arr);
            tx_estring_value.append("!!!");
            tx_characteristic_string.writeValue(tx_estring_value.c_str());

            
            break;
      

Python Code:


        #1
        ble.send_command(CMD.ECHO, "Artemis")
        s = ble.receive_string(ble.uuid['RX_STRING'])
        print(s)
      

With output:


        Robot says -> Artemis!!!
      

Task 2: SEND_THREE_FLOATS

Here I send over 3 floats to the Artemis board which then extracts and prints the 3 values.

Arduino Code:


        case SEND_THREE_FLOATS:
            /*
             * Your code goes here.
             */
            float float1, float2, float3;

            success = robot_cmd.get_next_value(float1);
            if (!success)
                return;

            success = robot_cmd.get_next_value(float2);
            if (!success)
                return;

            success = robot_cmd.get_next_value(float3);
            if (!success)
                return;

            Serial.print("Three Floats: ");
            Serial.print(float1);
            Serial.print(", ");
            Serial.print(float2);
            Serial.print(", ");
            Serial.println(float3);


            break;
      

Python Code:


        #2
        ble.send_command(CMD.SEND_THREE_FLOATS, "2.0|-15.5|4.7")
      

Output:


        Three Floats: 2.00, -15.50, 4.70
      

Task 3: GET_TIME_MILLIS

In task 3 I send a command to request the current time in milliseconds from the Artemis board. It then returns this time as a string. This was a new command so I had to update it in the Artemis code as well as the cmd_types file in python.

Python Code:


        #3
        ble.send_command(CMD.GET_TIME_MILLIS, "")
      

Arduino Code:


        case GET_TIME_MILLIS: 

            int time;
            time = (int)millis();

            tx_estring_value.clear();
            tx_estring_value.append("T:");
            tx_estring_value.append(time);
            tx_characteristic_string.writeValue(tx_estring_value.c_str());

            Serial.print("Sent back: ");
            Serial.println(tx_estring_value.c_str());

            break;
      

Output:


        T: 36073
      

Task 4: Recieve Time Back

In this task I receive the time string sent back from the Artemis board in task 3 and print it to the console using a callback function.

Python Code:


        #4
        ble.send_command(CMD.GET_TIME_MILLIS, "")
        s = ble.receive_string(ble.uuid['RX_STRING'])
        print(s)
      

Output:


        T: 44305.000
      

Task 5: Send Multiple Time Values

Now instead of sending a single time value, I send over multiple for a period of about 3 seconds. This then allowed me to count the number of messages send over this time to calculate the effective baud rate. I edited the GET_TIME_MILLIS function to encorperate this functionality. The variable time_length can be adjusted to change the duration of the test.

Arduino Code:


        case GET_TIME_MILLIS:
            unsigned long t;
            unsigned long t_start;
            unsigned long last;
            last = 0;
            t = millis();
            t_start = t;

            int num_messages;
            num_messages = 0;

            for (int i = 0; i < time_length; i++) {


                t = millis();                
                tx_estring_value.clear();
                tx_estring_value.append(t);   
                tx_characteristic_string.writeValue(
                tx_estring_value.c_str()
                );
                num_messages++;
            }

            
            Serial.println(num_messages); 

            break;
      

Pyhton Code:


        def string_notification_handler(uuid, byte_array):
        msg = ble.bytearray_to_string(byte_array)

        if msg.startswith("T:"):
            t = int(msg[2:])
            print(f"Time (ms): {t}")

        ble.start_notify(
            ble.uuid['RX_STRING'],
            string_notification_handler)


        ble.send_command(CMD.GET_TIME_MILLIS, "")
      

Partial Output (Since it is too long):


        Time (ms): 28305
        Time (ms): 28305
        Time (ms): 28305
        Time (ms): 28305
        Time (ms): 28362
        Time (ms): 28428
        Time (ms): 28428
        Time (ms): 28486
        Time (ms): 28486
        Time (ms): 28486
        Time (ms): 28541
      

Partial Output (Since it is too long):

The last time value sent was 31248. 31248 - 28305 = 2943. 87 messages were sent in this time so we can calculate the rate that messages were sent. (87 messages) / (2.943 seconds of time elapsed)= 29.5616717635 messages per second.

Task 6: Batch Method

Now instead of sending all the time values immedietly over, I switch to using a batch method where I first record all the values and store them in an array on the Artemis board. After this I loop through the array and send all the values over to the computer. A callback function then extracts all the values. I had to make a gloabl array to store the time and separated the functionality of recording and sending in two differnt commands. The GET_TIME_MILLIS handled recording while a new SEND_TIME_DATA command handled sending the data.

Arduino Code:


          case GET_TIME_MILLIS:
            
            unsigned long t;
            unsigned long t_start;
            unsigned long last;
            

            last = 0;
            t = millis();
            
        
            t_start = t;

            int num_messages;
            num_messages = 0;



            for (int i = 0; i < time_length; i++) {


                t = millis();
                time_arr[i] = (int)t;
      
                
                num_messages++;
            }

            Serial.println(num_messages); 

         

            Serial.println("Sent Time");
        

            break;
        
      

        case SEND_TIME_DATA:

            for (int i = 0; i < time_length; i++) {

                int time;
                time = time_arr[i];
                tx_estring_value.clear();
                tx_estring_value.append(time);   
                tx_characteristic_string.writeValue(tx_estring_value.c_str());
                //delay(100);                  

                        
                //Serial.println("Running SEND_TIME_DATA");

            }

            

            break;
      

Python Code:


        nums = []


        def collect_array_nums(uuid, byte_array):
            msg = ble.bytearray_to_string(byte_array)
            nums.append(msg)
            #print(f"Time (ms): {msg}")

        ble.start_notify(
            ble.uuid['RX_STRING'],
            collect_array_nums)

        ble.send_command(CMD.GET_TIME_MILLIS, "")

        ble.send_command(CMD.SEND_TIME_DATA, "")

        print(nums)
        print(len(nums))
      

Output:


        ['36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36073', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36074', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075', '36075']
150
      

Task 7: Time and Temp

In this task I create an additional array to store temperature readings along with the time readings. Again I used the batch method and first record the values for both temperature and time. After this I loop through both arrays in a new command called GET_TEMP_READINGS and send the values to the comptuer. Then the notification handler parses the strings and populates the data into two lists.

Python Code:


        time_reading = []
        temp_reading = []

        def collect_array_temps(uuid, byte_array):
            msg = ble.bytearray_to_string(byte_array)
            substring_time = msg.partition(':')[0]
            substring_temp = msg.partition(':')[2]
            time_reading.append(substring_time)
            temp_reading.append(substring_temp)
            #print(f"Time (ms): {msg}")

        ble.start_notify(
            ble.uuid['RX_STRING'],
            collect_array_temps)

        ble.send_command(CMD.GET_TIME_MILLIS, "")

        ble.send_command(CMD.GET_TEMP_READINGS, "")

        print(time_reading)
        print(len(time_reading))

      

Arduino Code:


        case GET_TIME_MILLIS:
            
            unsigned long t;
            unsigned long t_start;
            unsigned long last;
        
            last = 0;
            t = millis();
            
        
            t_start = t;

            int num_messages;
            num_messages = 0;



            for (int i = 0; i < time_length; i++) {


                t = millis();
                time_arr[i] = (int)t;
                temp_arr[i] = getTempDegF();       
                
                num_messages++;
            }

            Serial.println(num_messages); 

     
            Serial.println("Sent Time");
   

            
            

            break;
      

        case GET_TEMP_READINGS:

            //t = millis();
            int a;
            a = millis();

            for (int i = 0; i < time_length; i++) {

                int time;
                int temp;
                time = time_arr[i];
                temp = temp_arr[i];

                tx_estring_value.clear();
                tx_estring_value.append(time);   
                tx_estring_value.append(":");   
                tx_estring_value.append(temp);   
                tx_characteristic_string.writeValue(tx_estring_value.c_str());
                //delay(100);
                //Serial.println("Running GET_TEMP_READINGS");

            }
            int b;
            b = millis();
            Serial.println("This");
            Serial.println(a);
            Serial.println(b);

            

            break;
      

Output (from python cell):


        ['31965', '31966', '31966', '31966', '31967', '31967', '31967', '31967', '31968', '31968', '31968', '31968', '31968', '31969', '31969', '31969', '31969', '31970', '31970', '31970', '31970', '31971', '31976', '31976', '31976', '31976', '31977', '31977', '31977', '31977', '31978', '31978', '31978', '31978', '31978', '31979', '31979', '31979', '31979', '31980', '31980', '31980', '31980', '31981', '31986', '31986', '31986', '31986', '31987', '31987', '31987', '31987', '31988', '31988', '31988', '31988', '31988', '31989', '31989', '31989', '31989', '31990', '31990', '31990', '31990', '31991', '31996', '31996', '31996', '31996', '31997', '31997', '31997', '31997', '31998', '31998', '31998', '31998', '31998', '31999', '31999', '31999', '31999', '32000', '32000', '32000', '32000', '32001', '32006', '32006', '32006', '32006', '32007', '32007', '32007', '32007', '32008', '32008', '32008', '32008', '32009', '32009', '32009', '32009', '32009', '32010', '32010', '32010', '32010', '32011', '32016', '32016', '32016', '32016', '32017', '32017', '32017', '32017', '32018', '32018', '32018', '32018', '32019', '32019', '32019', '32019', '32019', '32020', '32020', '32020', '32020', '32021', '32026', '32026', '32026', '32026', '32027', '32027', '32027', '32027', '32028', '32028', '32028', '32028', '32029', '32029', '32029', '32029', '32029', '32030']
        150
      

Task 8: Conclusions

In task 8 I discuss the differences between the two methods of data collection and transmission (immedietly sending vs batching).

Data Throughput Comparison

Method 1: Send Data Immediately

In this method, data is transmitted as soon as it is measured on the Artemis board.


        31248 - 28305 = 2943 ms = 2.943 s

        87 messages / 2.943 s
        = 29.56 messages per second
        

Method 2: Store Data First, Then Send

In this method, data is first recorded on the Artemis board and transmitted afterward.

Recording Time


        32030 - 31965 = 65 ms
        65 ms = 0.065 s

        150 messages / 0.065 s
        = 2307.69 messages per second
        

Transmission Time


        40191 - 36364 = 3827 ms = 3.827 s

        150 messages / 3.827 s
        = 39.20 messages per second
        

Discussion

The first method sends data immediately after it is recorded, allowing the host computer to receive measurements with minimal latency. This approach is useful for real-time applications, such as robot control, where timely data is critical.

The second method prioritizes recording speed by batching data on the Artemis board before transmission. This allows data to be recorded extremely quickly (over 2000 samples per second), but requires waiting until all data has been collected before it can be accessed. Transmission speed was also slightly faster due to the separation of recording and sending.

If accurate, high-frequency sampling is required and real-time access is not necessary, the second method is preferable. The performance difference becomes most significant when a large number of samples are collected.

Memory Constraints

Each sample consists of a timestamp and a temperature value:

  • Timestamp (uint32_t): 4 bytes
  • Temperature (float): 4 bytes

This results in 8 bytes per sample. Given that the Artemis board has 384 kB of RAM:


        384,000 bytes / 8 bytes per sample = 48,000 samples
        

Therefore, approximately 48,000 samples can be stored before running out of memory.