ECE 4160 – Fast Robots
The goal of this lab was to establish familiarity with the Arduino IDE as well as the Artemis board.
I started with installing the Arduino IDE and setting up the Artemis board, connecting it via USB to my computer.
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.
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.
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.
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.
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.
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.
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!!!
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
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
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
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.
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
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
In task 8 I discuss the differences between the two methods of data collection and transmission (immedietly sending vs batching).
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
In this method, data is first recorded on the Artemis board and transmitted afterward.
32030 - 31965 = 65 ms
65 ms = 0.065 s
150 messages / 0.065 s
= 2307.69 messages per second
40191 - 36364 = 3827 ms = 3.827 s
150 messages / 3.827 s
= 39.20 messages per second
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.
Each sample consists of a timestamp and a temperature value:
uint32_t): 4 bytesfloat): 4 bytesThis 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.