Skip to content
Snippets Groups Projects
Commit 11e60d82 authored by bav6096's avatar bav6096
Browse files

inital commit

parent cef6674b
No related branches found
No related tags found
No related merge requests found
/*!
* @file Arduino.ino
*
* This Arduino sketch is designed for an Arduino Nano, with which two NeoPixel
* rings from Adafruit are connected. It is assumed that both rings have 16 LEDs
* each.
*
* This code uses Adafruit's Neopixel library, which is released under a GNU Lesser
* General Public License.
*
* Objective:
This sketch should make it possible to use the Arduino Nano to play animation on
the two connected NeoPixel rings. Since the memory of the Nano is very limited,
there is the functionality to load animations via the serial interface.
*
* Let the left NeoPixel ring be abbreviated to: lR
* Let the right NeoPixel ring be abbreviated to: rR
*
* Wiring:
Arduino pin 6 -> lR Data Input
Arduino pin 7 -> rR Data Input
Arduino pin 5V -> lR Power
Arduino pin 5V -> rR Power
Arduino pin GND -> lR Ground
Arduino pin GND -> rR Ground
*/
#include <Adafruit_NeoPixel.h>
#define MSG_SIZE 50 // Expected size of a received message in bytes.
#define BUF_SIZE 16 // Maximum number of frames that can be stored in either frame
// buffer.
#define L_PIN 6 // Arduino pin that is connected to the left NeoPixel ring.
#define R_PIN 7 // Arduino pin that is connected to the right NeoPixel ring.
#define PIXELS 16 // Number of pixels on either NeoPixel ring.
/*
* To make the serial communication a little more robust, all serial requests are
* answered by the Nano. Furthermore, the Nano only receives 50-bit messages and
* only sends 8-bit messages. The serial buffer is also emptied before each written
* message in order to be able to react to the following incoming message.
*
* There are two types of messages, data messages and instruction messages. Data
* messages are used to convey the animation frames, while instruction messages are
* used to convey instructions, which the Nano then executes.
*/
// Variables used for communication.
uint8_t msg[MSG_SIZE]; // Holds a received message.
int msg_size; // Determines how many bits of a message are received.
bool rcvd_msg; // Determines if all 50 bits of a message are received.
bool rcvg_animation; // Determines if an animation is currently been received.
int msgs_to_rcv; // If an animation is received: Determines the number of
// (data) messages that still have to be received.
/*
* Every animation consists of frames. A frame describes the colors of all 16
* pixels on one NeoPixel ring. Since two NeoPixel rings are connected to the
* Arduino, each animation consists of at least two frames, one frame for the left
* ring and one frame for the right ring. To play an animation, two frames are
* displayed in parallel. The term frame ensemble or ensemble is used to refer to
* these two frames that play in parallel.
*/
// Variables used for animation storage.
int values_per_frame; // Number of bytes that are needed to store an entire
// frame.
uint8_t ** l_frame_buffer; // Stores all animation frames for the left
// NeoPixel ring.
uint8_t ** r_frame_buffer; // Stores all animation frames for the right
// NeoPixel ring.
uint8_t num_ensembles; // Number of frame ensembles in the animation.
uint8_t num_iterations; // Number of times the animation is repeated.
unsigned long frame_time; // Delay in milliseconds between each displayed
// frame ensemble.
// Variables used for animation playback.
bool animation_loaded; // Determine if an animation is loaded.
bool playback; // Determine if animation should be played.
long start_time; // Time at which each individual frame ensemble
// started to be displayed.
uint8_t curr_ensemble; // Frame ensemble that is currently displayed.
uint8_t curr_iteration; // Animation iteration that is currently played.
// Variables used for NeoPixel ring.
Adafruit_NeoPixel l_pixels(PIXELS, L_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel r_pixels(PIXELS, R_PIN, NEO_GRB + NEO_KHZ800);
void setup()
{
msg_size = 0;
rcvd_msg = false;
rcvg_animation = false;
msgs_to_rcv = 0;
// Three color values (red, green, blue) ​​must be saved for each pixel.
values_per_frame = PIXELS * 3;
animation_loaded = false;
playback = false;
start_time = 0;
curr_ensemble = 0;
curr_iteration = 0;
init_frame_buffers();
l_pixels.begin();
r_pixels.begin();
Serial.begin(57600);
}
/*!
@brief Allocates memory for the two frame buffers.
@note The two frame buffers each have the structure of a 2D array in which
16 frames are stored. Each frame consists of 48 bytes of color
information. A total of 1536 bytes are allocated in this function.
*/
void init_frame_buffers()
{
l_frame_buffer = (uint8_t **) malloc(sizeof(uint8_t *) * BUF_SIZE);
r_frame_buffer = (uint8_t **) malloc(sizeof(uint8_t *) * BUF_SIZE);
for (int i = 0; i < BUF_SIZE; i++)
{
l_frame_buffer[i] = (uint8_t *) malloc(sizeof(uint8_t) * values_per_frame);
r_frame_buffer[i] = (uint8_t *) malloc(sizeof(uint8_t) * values_per_frame);
}
}
/*!
@brief Sends a 8-bit message via the serial connection. The serial buffer
is emptied beforehand in order to react to following incoming messages.
@param msg 8-bit message from Arduino.
@note This function is used to respond to incomming messages.
*/
void respond(uint8_t msg)
{
while (Serial.available() > 0) Serial.read(); // Flush serial buffer.
Serial.write(msg);
}
/*!
@brief This function sends the color information in the two frame buffers to the
NeoPixel rings. The frame ensembles are shown delayed depending on the
animation speed. In addition, the animation is repeated depending on the
desired iterations.
*/
void play_animation()
{
if (curr_ensemble < num_ensembles)
{
if (millis() > frame_time + start_time)
{
for (int i = 0; i < PIXELS; i++)
{
int pixel = i * 3;
uint8_t r;
uint8_t g;
uint8_t b;
r = l_frame_buffer[curr_ensemble][pixel + 0];
g = l_frame_buffer[curr_ensemble][pixel + 1];
b = l_frame_buffer[curr_ensemble][pixel + 2];
l_pixels.setPixelColor(i, l_pixels.Color(r, g, b));
r = r_frame_buffer[curr_ensemble][pixel + 0];
g = r_frame_buffer[curr_ensemble][pixel + 1];
b = r_frame_buffer[curr_ensemble][pixel + 2];
r_pixels.setPixelColor(i, r_pixels.Color(r, g, b));
}
l_pixels.show();
r_pixels.show();
start_time = millis();
curr_ensemble++;
}
}
if (curr_ensemble >= num_ensembles)
{
if (millis() > frame_time + start_time)
{
if (num_iterations != 0xFF) curr_iteration++; // Animation is looped indefenitly.
if (curr_iteration >= num_iterations)
{
reset_playback();
respond(0x4F); // Animation successfully played!
}
else curr_ensemble = 0;
}
}
}
/*!
@brief Executes the appropriate function depending on the instruction read
from a received instruction message.
*/
void handle_instruction()
{
switch ((char) msg[1])
{
case 'A':
load_animation();
break;
case 'B':
start_playback();
break;
case 'C':
pause_playback();
break;
case 'D':
resume_playback();
break;
case 'E':
reset_display();
break;
default:
respond(0xF2); // Instruction was not recognized.
break;
}
}
/*!
@brief Breaks down an instruction message into its individual components.
*/
void load_animation()
{
if (msg[2] % 2 != 0) respond(0xF7); // Unequal number of frames for the
// left and right NeoPixel ring.
else if (msg[2] * 0.5 > BUF_SIZE) respond(0xF0); // Animation has to many frame ensembles.
else if (msg[2] * 0.5 == 0x00) respond(0xF8); // Animation has no frame ensemble.
else if (msg[7] == 0x00) respond(0xF6); // Animation has no iteration.
else
{
reset_playback();
num_ensembles = msg[2] * 0.5;
frame_time = msg[3];
frame_time = (frame_time << 8) + msg[4];
frame_time = (frame_time << 8) + msg[5];
frame_time = (frame_time << 8) + msg[6];
num_iterations = msg[7];
animation_loaded = false;
rcvg_animation = true;
msgs_to_rcv = msg[2];
respond(0x0F); // Waiting for frames to be send.
}
}
/*!
@brief Starts animation playback.
*/
void start_playback()
{
if (animation_loaded)
{
playback = true;
start_time = millis() - frame_time;
curr_ensemble = 0;
respond(0x1F); // Animation playback has been started.
}
else respond(0xF1); // No animation is loaded.
}
/*!
@brief Pauses animation playback
*/
void pause_playback()
{
if (animation_loaded)
{
playback = false;
respond(0x5F); // Animation playback has been paused.
}
else respond(0xF1); // No animation is loaded.
}
/*!
@brief Resumes animation playback.
*/
void resume_playback()
{
if (animation_loaded && !playback)
{
playback = true;
start_time = millis() - frame_time;
respond(0x6F); // Animation playback has been resumed.
}
else if (playback) respond(0xF5); // Animation is playing.
else respond(0xF1); // No animation is loaded.
}
/*!
@brief Resets all animation playback parameters.
*/
void reset_playback()
{
playback = false;
start_time = 0;
curr_ensemble = 0;
curr_iteration = 0;
}
/*!
@brief Clears both NeoPixel rings.
*/
void reset_display()
{
l_pixels.clear();
r_pixels.clear();
l_pixels.show();
r_pixels.show();
respond(0x7F); // Displays have been cleared.
}
/*!
@brief Loads receiving data messages into one of the frame buffers.
@note Each animation consists of an even number of frames. All even frames in an
animation are stored in the left frame buffer and all odd frames in the
right one.
*/
void handle_data()
{
if (rcvg_animation && msgs_to_rcv > 0)
{
int frame = 0.5 * (2 * num_ensembles - msgs_to_rcv); // Data type int always rounds
// off a floating point number.
for (int i = 0; i < values_per_frame; i++)
{
if (msgs_to_rcv % 2 == 0) l_frame_buffer[frame][i] = msg[i + 1];
else r_frame_buffer[frame][i] = msg[i + 1];
}
msgs_to_rcv--;
if (msgs_to_rcv == 0)
{
rcvg_animation = false;
animation_loaded = true;
respond(0x3F); // Last frame successfully received.
}
else respond(0x2F); // Frame successfully received.
}
else respond(0xF3); // No data message expected.
}
/*!
@brief Responds to wrong formated message.
*/
void handle_wrong_format()
{
respond(0xF4); // Message has wrong format.
}
/*!
@brief Loades a received message form the serial buffer into memmory.
@note Data messages as well as instruction messages always have a length
of 50 bits.
*/
void read_serial()
{
while (Serial.available() > 0)
{
if (msg_size < MSG_SIZE)
{
msg[msg_size] = Serial.read();
msg_size++;
}
if (msg_size >= MSG_SIZE)
{
rcvd_msg = true;
break;
}
}
}
/*!
@brief First, checks if a message is received. If thats the case determines if the
message is a data or an instruction message and preecedes accordingly. If the
received message is neither one of the two, informs the communication partner.
Secondly, checks if a message is currently beeing send to the Nano. If thats
the case, writes the message to memory.
Thirdly, if an animation is loaded and if the animaiton should be played,
plays the loaded animation.
@note Data messages beginn and end with ascii encoded curling brackets.
Instruction messages beginn and end with ascii encoded square brackets.
*/
void loop()
{
if (rcvd_msg)
{
char startMarker = msg[0];
char endMarker = msg[MSG_SIZE - 1];
if (startMarker == '[' && endMarker == ']') handle_instruction();
else if (startMarker == '{' && endMarker == '}') handle_data();
else handle_wrong_format();
rcvd_msg = false;
msg_size = 0;
}
else if (Serial.available() > 0) read_serial();
else if (playback && animation_loaded) play_animation();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment