CKS-Shield-1-Test Arduino Sketch¶
This Arduino sketch exercises the standard hardware of the CKS-1-Shield board. It provides a console interface for reading inputs and triggering outputs, and a set of asynchronous state machines for generating test motions.
Console Interface¶
-
static void
parse_user_input
(int argc, char *argv[])¶ Process an input message tokenized from a line of console input. New commands may be defined by adding additional clauses into the if-else structures. The message is passed in argv, an array of argc pointers to strings of, one per token.
-
void
poll_console_input
(unsigned long elapsed)¶ Polling function to process messages arriving over the serial port. Each iteration through this polling function processes at most one character. It records the input message line into a buffer while simultaneously dividing it into ‘tokens’ delimited by whitespace. Each token is a string of non-whitespace characters, and might represent either a symbol or an integer. Once a message is complete, parse_user_input() is called. The argument elapsed contains the number of microseconds elapsed since last update.
Motion Generators¶
-
bool
running
= true¶ Global flag to enable the autonomous demo motions.
-
void
poll_servos
(unsigned long interval)¶ Polling function to update the servo motion generator state machine. This should be called as frequently as possible. The argument interval provides the number of microseconds elapsed since the last update.
-
void
poll_beeps
(unsigned long interval)¶ Polling function to update the tone generator state machine. This should be called as frequently as possible. The argument interval provides the number of microseconds elapsed since the last update.
-
void
poll_motors
(unsigned long interval)¶ Polling function to update the DC motor motion generator state machine. This should be called as frequently as possible. The argument interval provides the number of microseconds elapsed since the last update.
Full Source Code¶
The full code is all in one file CKS-Shield-1-Test.ino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | /// @file CKS-Shield-1-Test.ino
/// @brief Arduino program for demonstrating and testing the CKS Shield 1 hardware.
/// @copyright No copyright, 2019, Garth Zeglin. This file is explicitly placed in the public domain.
/****************************************************************/
// Library imports.
#include <Servo.h>
/****************************************************************/
/**** Hardware pin assignments **********************************/
/****************************************************************/
// The following pin assignments correspond to the hardware on the CKS Shield 1 Rev A board.
// More hardware details can be found at https://courses.ideate.cmu.edu/16-223/f2019/text/resources/CKS-1-Shield.html
// Analog inputs.
const int PHOTO1_PIN = A0; /// photo-interrupter input, value decreases when object present
const int PHOTO2_PIN = A1; /// photo-interrupter input, value decreases when object present
const int SWITCH1_PIN = A2; /// active-low switch input
const int SWITCH2_PIN = A3; /// active-low switch input
const int SWITCH3_PIN = A4; /// active-low switch input
const int SWITCH4_PIN = A5; /// active-low switch input
// If the optional ADXL335 accelerometer is used, it replaces switches 3,4,5.
// N.B. If the optional I2C interface is used, A4 and A5 are not available.
const int ACCELX_PIN = A3;
const int ACCELY_PIN = A4;
const int ACCELZ_PIN = A5;
// Digital outputs. D0 and D1 are reserved for use as serial port RX/TX.
// Please note that many of these pins have alternate connections or may be used
// as GPIO; please refer to the documentation and specific configuration of your
// board.
const int RX0_PIN = 0; /// Serial receive (normally from USB, optionally from TTL Serial connector)
const int TX0_PIN = 1; /// Serial transmit(normally to USB, optionally to TTL Serial connector)
const int SERVO4_PIN = 2; /// servo PWM output
const int MOSFET1_PIN = 3; /// active-high MOSFET driver output
const int SERVO3_PIN = 4; /// servo PWM output
const int MOTA_PWM1_PIN = 5; /// DRV8833 dual H-bridge AIN1 (motor A, pin 1)
const int MOTA_PWM2_PIN = 6; /// DRV8833 dual H-bridge AIN2 (motor A, pin 2)
const int SERVO2_PIN = 7; /// servo PWM output
const int SERVO1_PIN = 8; /// servo PWM output
const int MOTB_PWM1_PIN = 9; /// DRV8833 dual H-bridge BIN1 (motor B, pin 1)
const int MOTB_PWM2_PIN = 10; /// DRV8833 dual H-bridge BIN2 (motor B, pin 2)
const int MOSFET2_PIN = 11; /// active-high MOSFET driver output
const int LED1_PIN = 12; /// active-low LED on "LED1" connector, or GPIO
const int ONBOARD_LED_PIN = 13; /// active-high LED on Arduino
// If using a WS2801 digital LED strand on the SPI output, the following will displace other usage:
const int STRAND_DATA_PIN = 11; /// WS2801 LED strand data output (yellow wire on strand) (SPI MOSI)
const int STRAND_CLK_PIN = 13; /// WS2801 LED strand clock output (green wire on strand) (SPI SCK)
/****************************************************************/
/**** Global variables and constants ****************************/
/****************************************************************/
// The baud rate is the number of bits per second transmitted over the serial port.
const long BAUD_RATE = 115200;
Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;
bool running = true; ///< Global flag to enable the autonomous demo motions.
/****************************************************************/
/**** Standard entry points for Arduino system ******************/
/****************************************************************/
/// Standard Arduino initialization function to configure the system. This
/// function is called once after reset to initialize the program.
void setup()
{
// configure the actuator pins as soon as possible.
pinMode(MOSFET1_PIN, OUTPUT);
pinMode(MOSFET2_PIN, OUTPUT);
pinMode(MOTA_PWM1_PIN, OUTPUT);
pinMode(MOTA_PWM2_PIN, OUTPUT);
pinMode(MOTB_PWM1_PIN, OUTPUT);
pinMode(MOTB_PWM2_PIN, OUTPUT);
pinMode(ONBOARD_LED_PIN, OUTPUT);
pinMode(LED1_PIN, OUTPUT);
digitalWrite(MOSFET1_PIN, LOW);
digitalWrite(MOSFET2_PIN, LOW);
digitalWrite(MOTA_PWM1_PIN, LOW);
digitalWrite(MOTA_PWM2_PIN, LOW);
digitalWrite(MOTB_PWM1_PIN, LOW);
digitalWrite(MOTB_PWM2_PIN, LOW);
digitalWrite(ONBOARD_LED_PIN, LOW);
digitalWrite(LED1_PIN, HIGH);
servo1.attach(SERVO1_PIN);
servo2.attach(SERVO2_PIN);
servo3.attach(SERVO3_PIN);
servo4.attach(SERVO4_PIN);
// initialize the Serial port for the user debugging console
Serial.begin(BAUD_RATE);
// send a message as a diagnostic
Serial.println(__FILE__ " awake.");
}
/****************************************************************/
/// Standard Arduino polling function to handle all I/O and periodic processing.
/// This function is called repeatedly as fast as possible from within the
/// built-in library to poll program events. This loop should never be allowed
/// to stall or block so that all tasks can be constantly serviced.
void loop()
{
// The timestamp in microseconds for the last polling cycle, used to compute
// the exact interval between output updates.
static unsigned long last_update_clock = 0;
// Read the microsecond clock.
unsigned long now = micros();
// Compute the time elapsed since the last poll. This will correctly handle wrapround of
// the 32-bit long time value given the properties of twos-complement arithmetic.
unsigned long interval = now - last_update_clock;
last_update_clock = now;
// Begin the polling cycle.
poll_console_input(interval);
poll_servos(interval);
poll_beeps(interval);
poll_motors(interval);
}
/****************************************************************/
/****************************************************************/
// Configure the control lines for one half of a DRV8833 driver.
// @param pwm Motor PWM value ranging from -255 to 255, with zero representing stopped.
// @param IN1_PIN Integer pin value for either AIN1 or BIN1.
// @param IN2_PIN Integer pin value for either AIN2 or BIN2.
static void set_motor_pwm(int pwm, int IN1_PIN, int IN2_PIN)
{
if (pwm < 0) { // reverse speeds
analogWrite(IN1_PIN, -pwm);
digitalWrite(IN2_PIN, LOW);
} else { // stop or forward
digitalWrite(IN1_PIN, LOW);
analogWrite(IN2_PIN, pwm);
}
}
/****************************************************************/
// Wrapper on strcmp for clarity of code. Returns true if strings are
// identical.
static int string_equal(char *str1, const char str2[])
{
return !strcmp(str1, str2);
}
/****************************************************************/
/// Process an input message tokenized from a line of console input.
/// New commands may be defined by adding additional clauses into the if-else structures.
/// @param argc number of argument tokens
/// @param argv array of pointers to strings, one per token
static void parse_user_input(int argc, char *argv[])
{
// Interpret the first token as a command symbol.
char *command = argv[0];
/* -- process zero-argument commands --------------------------- */
if (argc == 1) {
if (string_equal(command, "run")) {
running = true;
}
else if (string_equal(command, "stop")) {
running = false;
}
else if (string_equal(command, "inputs")) {
Serial.println("Raw sensor values:");
Serial.print("A0: "); Serial.println(analogRead(A0));
Serial.print("A1: "); Serial.println(analogRead(A1));
Serial.print("A2: "); Serial.println(analogRead(A2));
Serial.print("A3: "); Serial.println(analogRead(A3));
Serial.print("A4: "); Serial.println(analogRead(A4));
Serial.print("A5: "); Serial.println(analogRead(A5));
}
else {
Serial.println("unrecognized command.");
}
}
/* -- process one-argument commands --------------------------- */
else if (argc == 2) {
long value = atol(argv[1]);
// Set the onboard LED on or off.
if (string_equal(command, "led")) {
digitalWrite(ONBOARD_LED_PIN, value);
}
else if (string_equal(command, "ma")) {
set_motor_pwm(value, MOTA_PWM1_PIN, MOTA_PWM2_PIN);
}
else if (string_equal(command, "mb")) {
set_motor_pwm(value, MOTB_PWM1_PIN, MOTB_PWM2_PIN);
}
else if (string_equal(command, "beep")) {
switch(value) {
case 1: tone(MOSFET1_PIN, 440, 50); break;
case 2: tone(MOSFET2_PIN, 660, 50); break;
}
}
else {
Serial.println("unrecognized single-argument command.");
}
}
/* -- process two-argument commands --------------------------- */
else if (argc == 3) {
long value1 = atol(argv[1]);
long value2 = atol(argv[2]);
if (string_equal(command, "sv")) {
switch(value1) {
case 1: servo1.write(value2); break;
case 2: servo2.write(value2); break;
case 3: servo3.write(value2); break;
case 4: servo4.write(value2); break;
}
}
else {
Serial.println("unrecognized two-argument command.");
}
}
else {
Serial.println("unrecognized command format.");
}
}
/****************************************************************/
/// Polling function to process messages arriving over the serial port. Each
/// iteration through this polling function processes at most one character. It
/// records the input message line into a buffer while simultaneously dividing it
/// into 'tokens' delimited by whitespace. Each token is a string of
/// non-whitespace characters, and might represent either a symbol or an integer.
/// Once a message is complete, parse_user_input() is called.
///
/// @param elapsed number of microseconds elapsed since last update
void poll_console_input(unsigned long elapsed)
{
const int MAX_LINE_LENGTH = 80; // The maximum message line length.
const int MAX_TOKENS = 10; // The maximum number of tokens in a single message.
static char input_buffer[MAX_LINE_LENGTH]; // buffer for input characters
static char *argv[MAX_TOKENS]; // buffer for pointers to tokens
static int chars_in_buffer = 0; // counter for characters in buffer
static int chars_in_token = 0; // counter for characters in current partially-received token (the 'open' token)
static int argc = 0; // counter for tokens in argv
static int error = 0; // flag for any error condition in the current message
(void) elapsed; // no-op to suppress compiler warning
// Check if at least one byte is available on the serial input.
if (Serial.available()) {
int input = Serial.read();
// If the input is a whitespace character, end any currently open token.
if (isspace(input)) {
if (!error && chars_in_token > 0) {
if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
else {
input_buffer[chars_in_buffer++] = 0; // end the current token
argc++; // increase the argument count
chars_in_token = 0; // reset the token state
}
}
// If the whitespace input is an end-of-line character, then pass the message buffer along for interpretation.
if (input == '\r' || input == '\n') {
// if the message included too many tokens or too many characters, report an error
if (error) Serial.println("excessive input error");
// else process any complete message
else if (argc > 0) parse_user_input(argc, argv);
// reset the full input state
error = chars_in_token = chars_in_buffer = argc = 0;
}
}
// Else the input is a character to store in the buffer at the end of the current token.
else {
// if beginning a new token
if (chars_in_token == 0) {
// if the token array is full, set an error state
if (argc == MAX_TOKENS) error = 1;
// otherwise save a pointer to the start of the token
else argv[ argc ] = &input_buffer[chars_in_buffer];
}
// the save the input and update the counters
if (!error) {
if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
else {
input_buffer[chars_in_buffer++] = input;
chars_in_token++;
}
}
}
}
}
/****************************************************************/
// Demo state machine to create a pattern of motion on the servos.
/// Countdown for the polling timer, in microseconds.
long servo_timer = 0;
/// Interval between updates, in microseconds. Defaults to 20 Hz.
long servo_interval = 50000;
/// Servo demo motion frame counter.
long servo_frame = 0;
/// Polling function to update the servo motion generator state machine. This should be called as frequently as possible.
/// @param interval number of microseconds elapsed since last update
void poll_servos(unsigned long interval)
{
servo_timer -= interval;
if (servo_timer < 0) {
servo_timer += servo_interval;
if (running) {
servo_frame = servo_frame + 1;
// create a series of wave-like movements across all four servos
int servo = (servo_frame / 20) % 4;
int direction = (servo_frame / 80) % 2;
int angle = (servo_frame % 20) * 9;
if (direction) angle = 180 - angle;
switch(servo) {
case 0: servo1.write(angle); break;
case 1: servo2.write(angle); break;
case 2: servo3.write(angle); break;
case 3: servo4.write(angle); break;
}
}
}
}
/****************************************************************/
// Demo state machine to create a pattern of beeps.
/// Countdown for the polling timer, in microseconds.
long beep_timer = 0;
/// Interval between updates, in microseconds. Defaults to once every pi seconds.
long beep_interval = 3141592;
/// Beep melody position.
int beep_note = 0;
/// Beep melody table. Pitches are in Hz.
const int beep_freq[] = {262, 294, 330, 349, 392, 440, 494, 523, 0};
/// Polling function to update the tone generator state machine. This should be called as frequently as possible.
/// @param interval number of microseconds elapsed since last update
void poll_beeps(unsigned long interval)
{
beep_timer -= interval;
if (beep_timer < 0) {
beep_timer += beep_interval;
if (running) {
int freq = beep_freq[beep_note];
tone(MOSFET1_PIN, freq, 25);
beep_note = beep_note + 1;
if (beep_freq[beep_note] == 0) beep_note = 0;
}
}
}
/****************************************************************/
// Demo state machine to create a pattern of motion on the motors.
/// Countdown for the polling timer, in microseconds.
long motor_timer = 0;
/// Interval between updates, in microseconds. Defaults to 20 Hz.
long motor_interval = 50000;
/// Motor demo motion frame counter.
long motor_frame = 0;
/// Polling function to update the DC motor motion generator state machine. This should be called as frequently as possible.
/// @param interval number of microseconds elapsed since last update
void poll_motors(unsigned long interval)
{
motor_timer -= interval;
if (motor_timer < 0) {
motor_timer += motor_interval;
if (running) {
motor_frame = motor_frame + 1;
// create a series of wave-like movements across both motors
int phase_a = (motor_frame % 50);
int phase_b = (motor_frame % 70);
int pwm_a = map(phase_a, 0, 50, -255, 255);
int pwm_b = map(phase_b, 0, 70, -255, 255);
set_motor_pwm(constrain(pwm_a, -255, 255), MOTA_PWM1_PIN, MOTA_PWM2_PIN);
set_motor_pwm(constrain(pwm_b, -255, 255), MOTB_PWM1_PIN, MOTB_PWM2_PIN);
}
}
}
/****************************************************************/
|