Interfaces
Files: embedded (see button_serial.c) serial_cpp shelf_app (see shelf_button.cpp)
This week we’re diving deeper into communication between microcontrollers and computers.
Recitation
Since Amira doesn’t have her magnetic field detector from HTM(a)A 2016 (see week 12), I loaned her my hello world button board. But it needed a minor code update to be useful. I used Neil’s serial bit banging code once again, and made my board output a constant stream of characters on the FTDI header. When the button is not pressed it outputs '0'
, and when it is it outputs '1'
. I wasn’t able to make it, but I hear the live demo worked.
Exploration
I want to approach serial communication from a pretty low level, so I’m going to use a C or C++ library. Using OS-specific system calls (as is ultimately pretty much necessary) is a bit too deep, though, since I’d like to be able to write cross-platform code. From some quick googling it looks like the two most popular cross-platform C libraries are libserial and libserialport. They seem mostly equivalent in features, but libserialport works on Windows in addition to POSIX systems, and Homebrew has a tap for it. Decision made.
The Homebrew install worked fine, and I set up a quick test program in C++ based on an example I found on GitHub. (For those that are curious, I made an imported library target for libserialport in cmake. So that’s where I record include directories and linking steps.)
#include <iostream>
#include <libserialport.h>
void list_ports() {
int32_t i;
struct sp_port **ports;
sp_return error = sp_list_ports(&ports);
if (error == SP_OK) {
for (i = 0; ports[i]; i++) {
std::cout << "Found port: " << sp_get_port_name(ports[i]) << '\n';
}
sp_free_port_list(ports);
} else {
std::cout << "No serial devices detected\n";
}
}
int main() {
std::cout << "Searching for serial ports.\n";
list_ports();
return 0;
}
To my surprise it worked immediately. It found my board at /dev/cu.usbserial-AC01YB5P
, which I confirmed via ls /dev | grep serial
.
Now to listen to it.
#include <iostream>
#include <libserialport.h>
int main() {
const char* desired_port = "/dev/cu.usbserial-AC01YB5P";
constexpr uint32_t baud_rate = 115200;
constexpr uint32_t byte_buffer_size = 512;
char byte_buffer[byte_buffer_size];
struct sp_port *port;
std::cout << "Opening port " << desired_port << '\n';
sp_return error = sp_get_port_by_name(desired_port,&port);
if (error == SP_OK) {
error = sp_open(port, SP_MODE_READ);
if (error == SP_OK) {
sp_set_baudrate(port, baud_rate);
while (true) {
int bytes_waiting = sp_input_waiting(port);
if (bytes_waiting > 0) {
std::cout << bytes_waiting << " bytes in the buffer: ";
int byte_num = 0;
byte_num = sp_nonblocking_read(port, byte_buffer, byte_buffer_size);
bool button_pressed = false;
for (decltype(byte_num) i = 0; i < byte_num; ++i){
if (byte_buffer[i] == '1') {
button_pressed = true;
}
std::cout << byte_buffer[i];
}
if (button_pressed) {
std::cout << " the button is pressed!";
}
std::cout << std::endl;
}
}
// Execution can never make it here, but I'll close the port on principle.
sp_close(port);
} else {
std::cout << "Error opening serial device\n";
}
} else {
std::cout << "Error finding serial device\n";
}
return 0;
}
Now my computer can tell when I press the button.
An Impractical Application
I have a parametric shelf design from CNC week. Why not generate a new shelving unit every time I press the button?
In the video above I’m just reloading one of four generated SVG files (since the shelves at their biggest require four panels of material). But it’s also generating ShopBot code for each panel as well. So after pressing the button you’re fully ready to cut. All of this happens in less than the blink of an eye – the delay is just from Chrome being slow at reloading.
What I’m not doing is generating a 3d model of the shelves… It would be fun to do that in three.js, and communicate with my shelf app over a websocket. Unfortunately I don’t have time this week.