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