Big restructuring of this code to make it more readable and better
modularized. Also switches to a simpler model: instead of a pong game, now the
host Android device initializes the microcontroller with an initial state, and
then calls on the microcontroller for updates. The microcontroller is just
used as a model/state engine for what the app will render. Barring bugs, this
microcontroller-side code should now be done.
Change-Id: I064963c7485d698a0764270224c99ab7f99d5677
diff --git a/apps/CtsVerifier/arduino-helper/arduino-helper.pde b/apps/CtsVerifier/arduino-helper/arduino-helper.pde
index 0c41388..7d56096 100644
--- a/apps/CtsVerifier/arduino-helper/arduino-helper.pde
+++ b/apps/CtsVerifier/arduino-helper/arduino-helper.pde
@@ -23,7 +23,7 @@
* sender in case data is lost and the fixed-byte messages
* get out of sync.
*/
-#define MESSAGE_SIZE 128
+#define MESSAGE_SIZE 128 // serial buffer size on TTL is 128 bytes
#define MESSAGE_DELIMITER 0xBEEF
#define MESSAGE_ESCAPE 0x2a
struct message {
@@ -62,23 +62,23 @@
uint16_t id;
uint16_t playfield_width;
uint16_t playfield_height;
- uint16_t paddle_width;
- uint16_t paddle_offset;
- uint16_t max_paddle_motion;
- uint8_t unused[MESSAGE_SIZE - 14];
+ uint16_t ball_x;
+ uint16_t ball_y;
+ uint16_t ball_radius;
+ uint16_t ball_velocity_x;
+ uint16_t ball_velocity_y;
+ uint8_t unused[MESSAGE_SIZE - 18];
};
#define OPCODE_PONG_BALL_STATE (OPCODE_RESET + 4)
struct pong_ball_state {
uint16_t opcode;
uint16_t id;
- uint16_t ball_x;
- uint16_t ball_y;
- uint8_t unused[MESSAGE_SIZE - 8];
+ uint8_t unused[MESSAGE_SIZE - 4];
};
struct wall_time_struct {
- uint32_t raw; // long == 4-byte on AVR
+ uint32_t raw;
uint8_t initialized;
uint8_t hours;
uint8_t minutes;
@@ -86,20 +86,47 @@
};
struct wall_time_struct WALL_TIME;
+/*
+ * Main ball-playing state record.
+ */
struct pong_state_struct {
uint16_t playfield_width;
uint16_t playfield_height;
- uint16_t paddle_width;
- uint16_t paddle_offset;
- uint16_t max_paddle_motion;
- uint16_t paddle_x;
- uint16_t last_ball_x;
- uint16_t last_ball_y;
+ int16_t ball_x;
+ int16_t ball_y;
+ int16_t ball_radius;
+ int16_t velocity_x;
+ int16_t velocity_y;
};
struct pong_state_struct PONG_STATE;
-void print_current_time() {
+/* This is a temporary buffer used by the message handler */
+struct message_buffer {
+ uint16_t count; // number of bytes read into the buffer
+ uint8_t buffer[MESSAGE_SIZE]; // contents of a 'struct message'
+};
+struct message_buffer MESSAGE_BUFFER;
+
+
+/*
+ * Clears all stateful values, including the wall clock time, current message
+ * data, and user/app state. Also clears the message handler's buffer. By
+ * "clear" we mean "memset to 0".
+ */
+void reset() {
+ memset(&WALL_TIME, 0, sizeof(WALL_TIME));
+ memset(&CURRENT_MESSAGE, 0, sizeof(CURRENT_MESSAGE));
+ memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
+ memset(&PONG_STATE, 0, sizeof(PONG_STATE));
+}
+
+
+/*
+ * Closes out a serial response, which involves sending a blank line in
+ * between messages. Also prints the current time, for funsies.
+ */
+void close_response() {
if (WALL_TIME.initialized) {
Serial.print("current_time=");
Serial.print(WALL_TIME.hours, DEC);
@@ -114,100 +141,126 @@
} else {
Serial.println("current_time=00:00:00");
}
+ Serial.print("\r\n");
}
+/*
+ * Opcode processor/handler.
+ */
void handle_current_message() {
- static uint16_t last_id;
+ static uint16_t last_id = 999999;
static struct setmode_pong* setmode_pong_msg;
- static struct pong_ball_state* pong_ball_state_msg;
- static uint16_t paddle_half_width;
- static uint16_t paddle_max;
- static uint16_t danger;
- static uint8_t invert;
- static uint16_t delta;
- if (CURRENT_MESSAGE.id == 0 || CURRENT_MESSAGE.id == last_id) {
+ if (CURRENT_MESSAGE.id == last_id) {
return;
}
last_id = CURRENT_MESSAGE.id;
switch (CURRENT_MESSAGE.opcode) {
- case OPCODE_SETMODE_PONG:
+ case OPCODE_SETMODE_PONG: // initialize ball animation state
memset(&PONG_STATE, 0, sizeof(PONG_STATE));
setmode_pong_msg = (struct setmode_pong*)(&CURRENT_MESSAGE);
PONG_STATE.playfield_width = setmode_pong_msg->playfield_width;
PONG_STATE.playfield_height = setmode_pong_msg->playfield_height;
- PONG_STATE.paddle_width = setmode_pong_msg->paddle_width;
- PONG_STATE.paddle_offset = setmode_pong_msg->paddle_offset;
- PONG_STATE.max_paddle_motion = setmode_pong_msg->max_paddle_motion;
-
- paddle_half_width = PONG_STATE.paddle_width / 2;
- paddle_max = PONG_STATE.playfield_width - paddle_half_width;
+ PONG_STATE.ball_x = setmode_pong_msg->ball_x;
+ PONG_STATE.ball_y = setmode_pong_msg->ball_y;
+ PONG_STATE.ball_radius = setmode_pong_msg->ball_radius;
+ PONG_STATE.velocity_x = setmode_pong_msg->ball_velocity_x;
+ PONG_STATE.velocity_y = setmode_pong_msg->ball_velocity_y;
Serial.println("message_type=setmode_pong_ack");
Serial.print("id=");
Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
+ Serial.print("ball_x=");
+ Serial.println(PONG_STATE.ball_x, DEC);
+ Serial.print("ball_y=");
+ Serial.println(PONG_STATE.ball_y, DEC);
+ Serial.print("ball_velocity_x=");
+ Serial.println(PONG_STATE.velocity_x, DEC);
+ Serial.print("ball_velocity_y=");
+ Serial.println(PONG_STATE.velocity_y, DEC);
+ Serial.print("playfield_width=");
+ Serial.println(PONG_STATE.playfield_width, DEC);
+ Serial.print("playfield_height=");
+ Serial.println(PONG_STATE.playfield_height, DEC);
+ Serial.print("ball_radius=");
+ Serial.println(PONG_STATE.ball_radius, DEC);
+ close_response();
break;
- case OPCODE_PONG_BALL_STATE:
- pong_ball_state_msg = (struct pong_ball_state*)(&CURRENT_MESSAGE);
- danger = pong_ball_state_msg->ball_x - PONG_STATE.paddle_x;
- invert = (danger < 0);
- danger *= invert ? -1 : 1;
- if (danger < paddle_half_width) {
- delta = 0;
- } else if (danger < PONG_STATE.playfield_width / 3) {
- delta = PONG_STATE.max_paddle_motion / 3;
- } else if (danger < PONG_STATE.playfield_width * 2 / 3) {
- delta = PONG_STATE.max_paddle_motion * 2 / 3;
- } else {
- delta = PONG_STATE.max_paddle_motion;
+ case OPCODE_PONG_BALL_STATE: // update a frame
+ /* This gets called once per update/refresh request from host. From the
+ * perspective of the AVR, we are running this animation in arbitrary
+ * time units. If host calls this at 10 FPS, we return data at 10 FPS.
+ * If it calls us at 100FPS, we return data at 100FPS. */
+ PONG_STATE.ball_x += PONG_STATE.velocity_x;
+ PONG_STATE.ball_y += PONG_STATE.velocity_y;
+
+ // all we do is bounce around the inside of the box we were given
+ if ((PONG_STATE.ball_x - PONG_STATE.ball_radius) < 0) {
+ PONG_STATE.velocity_x *= -1;
+ PONG_STATE.ball_x = PONG_STATE.ball_radius;
+ } else if ((PONG_STATE.ball_x + PONG_STATE.ball_radius) > PONG_STATE.playfield_width) {
+ PONG_STATE.velocity_x *= -1;
+ PONG_STATE.ball_x = PONG_STATE.playfield_width - PONG_STATE.ball_radius;
}
- delta *= invert ? 1 : -1;
- PONG_STATE.paddle_x += delta;
- if (PONG_STATE.paddle_x < paddle_half_width) {
- PONG_STATE.paddle_x = paddle_half_width;
- } else if (PONG_STATE.paddle_x > paddle_max) {
- PONG_STATE.paddle_x = paddle_max;
+
+ if ((PONG_STATE.ball_y - PONG_STATE.ball_radius) < 0) {
+ PONG_STATE.velocity_y *= -1;
+ PONG_STATE.ball_y = PONG_STATE.ball_radius;
+ } else if ((PONG_STATE.ball_y + PONG_STATE.ball_radius) > PONG_STATE.playfield_height) {
+ PONG_STATE.velocity_y *= -1;
+ PONG_STATE.ball_y = PONG_STATE.playfield_height - PONG_STATE.ball_radius;
}
Serial.println("message_type=pong_paddle_state");
Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id, DEC);
+ Serial.print("ball_x=");
+ Serial.println(PONG_STATE.ball_x, DEC);
+ Serial.print("ball_y=");
+ Serial.println(PONG_STATE.ball_y, DEC);
+ close_response();
+ break;
+
+ case OPCODE_RESET:
+ reset();
+ Serial.println("message_type=reset_ack");
+ Serial.print("id=");
Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.print("paddle_x=");
- Serial.println(PONG_STATE.paddle_x);
- Serial.println("");
+ close_response();
+ break;
+
+ case OPCODE_INIT_TIME:
+ // cast CURRENT_MESSAGE to our time struct to conveniently fetch
+ // out the current time
+ WALL_TIME.raw = ((struct init_time*)(&CURRENT_MESSAGE))->cur_raw_time;
+ WALL_TIME.initialized = 1;
+
+ Serial.println("message_type=init_time_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
+ break;
+
+ case OPCODE_CURRENT_TIME:
+ Serial.println("message_type=current_time_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
break;
default:
+ Serial.println("message_type=unknown_command_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
break;
}
}
-/* This is a temporary buffer used by the message handler */
-struct message_buffer {
- uint16_t count; // number of bytes read into the buffer
- uint8_t buffer[MESSAGE_SIZE]; // contents of a 'struct message'
-};
-struct message_buffer MESSAGE_BUFFER;
-
-/*
- * Clears all stateful values, including the wall clock time, current message
- * data, and user/app state. Also clears the message handler's buffer. By
- * "clear" we mean "memset to 0".
- */
-void reset() {
- memset(&WALL_TIME, 0, sizeof(WALL_TIME));
- memset(&CURRENT_MESSAGE, 0, sizeof(CURRENT_MESSAGE));
- memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
- memset(&PONG_STATE, 0, sizeof(PONG_STATE));
-}
-
/*
* Pumps the message processor. That is, this function is intended to be
@@ -226,16 +279,10 @@
static uint8_t cur_byte;
static uint16_t* cur_word;
static int8_t delimiter_index;
- static char buf[4];
+ static char buf[6];
while (Serial.available() > 0) { // keep going as long as we might have messages
cur_byte = (uint8_t)(Serial.read() & 0x000000ff);
MESSAGE_BUFFER.buffer[(MESSAGE_BUFFER.count)++] = cur_byte;
- Serial.print("booga ");
- Serial.print(itoa(MESSAGE_BUFFER.count, buf, 10));
- Serial.print(" ");
- Serial.print(itoa(Serial.available(), buf, 10));
- Serial.print(" ");
- Serial.println(itoa(cur_byte, buf, 10));
if (MESSAGE_BUFFER.count >= MESSAGE_SIZE) {
if ((*(uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)) != MESSAGE_DELIMITER) {
// whoops, we got out of sync with the transmitter. Scan current
@@ -251,10 +298,6 @@
}
}
}
- Serial.print("klaxon ");
- Serial.println(itoa(delimiter_index, buf, 10));
- Serial.print("klaxon ");
- Serial.println(itoa(*((uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)), buf, 10));
MESSAGE_BUFFER.count = 0;
if (delimiter_index >= 0) {
for (int i = delimiter_index + 2; i < MESSAGE_SIZE; ++i, ++(MESSAGE_BUFFER.count)) {
@@ -262,47 +305,16 @@
}
}
memset(MESSAGE_BUFFER.buffer + MESSAGE_BUFFER.count, 0, MESSAGE_SIZE - MESSAGE_BUFFER.count);
+ close_response();
} else {
memcpy(&CURRENT_MESSAGE, MESSAGE_BUFFER.buffer, MESSAGE_SIZE);
memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
- switch (CURRENT_MESSAGE.opcode) {
- case OPCODE_RESET:
- reset();
- return;
-
- case OPCODE_INIT_TIME:
- // cast CURRENT_MESSAGE to our time struct to conveniently fetch
- // out the current time
- WALL_TIME.raw = ((struct init_time*)(&CURRENT_MESSAGE))->cur_raw_time;
- WALL_TIME.initialized = 1;
-
- Serial.println("message_type=init_time_ack");
- Serial.print("id=");
- Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
-
- CURRENT_MESSAGE.id = 0;
- break;
-
- case OPCODE_CURRENT_TIME:
- Serial.println("message_type=current_time_ack");
- Serial.print("id=");
- Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
-
- CURRENT_MESSAGE.id = 0;
-
- default:
- // no-op -- actually means main loop will handle it
- break;
- }
}
}
}
}
+
/*
* Pumps the system wall clock. This checks the device's monotonic clock to
* determine elapsed time since last invocation, and updates wall clock time
@@ -320,7 +332,6 @@
if (millis() / 1000 != tmp_prev_millis) {
tmp_prev_millis = millis() / 1000;
- print_current_time();
}
if (WALL_TIME.initialized) {
@@ -348,15 +359,7 @@
* Standard Arduino loop-pump hook.
*/
void loop() {
- static uint16_t last_id = 0;
-
- // pump the clock and message processor
pump_clock();
pump_message_processor();
-
- // ignore any "system" messages (those with ID == 0) but dispatch app messages
- if ((last_id != CURRENT_MESSAGE.id) && (CURRENT_MESSAGE.id != 0)) {
- handle_current_message();
- }
- last_id = CURRENT_MESSAGE.id;
+ handle_current_message();
}