Firmware

The firmware image for the Skittle PNP is to run on an STM32F103C8T6 microcontroller with an ARM Cortex-M CPU. The peripheral-access libraries come from the open source project libopencm3. The application is built on FreeRTOS, an open source real-time operating system.

Tasks

The entrypoint to the system is the main function, which initializes the hardware peripherals, synchronization structures, and kicks off the 3 tasks.

task_uart

A UART interrupt is set up to add each character received to a queue. The UART task, in turn, blocks until there is an item in the queue, and, when an item is received, stores it in a buffer until a newline character \n is reached, at which point the whole buffer is written to a "message buffer" to be interpreted by the GCode task.

task_gcode

GCode is the protocol that the high-level software uses to control the firmware. An example of this might be:

G28 - Return to origin 
G01 Z1400 - Move z-axis down 
M401 -  Actuate solenoid 
G01 Z1000 - Move z-axis up 
G01 X400 Y836 - Move to (400, 836) 
G01 Z1400 - Move z-axis down 
M400 - De-actuate solenoid (turn off) 
G01 Z1000 - Move z-axis up 
G28 - Return to origin

The full list of commands implemented is:
Code Description
G01 X___ Y___ Z___ - Linear movement to (X, Y, Z)
G28 [X] [Y] [Z] - Move to origin
- If any of X, Y, or Z are present, it will only move to the minimum in that axis
G400/1 - ​Disable or enable the stepper motors to allow for manually moving the X and Y stage
- This is incredibly useful in testing out things like limit switches
M400/1 - Disable or enable the solenoid, which controls whether or not the end effector is generating suction
M410/1 - Open or close the feeder mechanism, used for reloading the tray of candy
The command is fed into the task as a string. It is then interpreted and converted to a struct which contains the information in a more consumable format. This struct is then written to a queue and picked up by the "motion" task.

task_motion

The motion task is the final stage in the process: it converts actuation requests into movements. The task dequeues commands from the GCode task and then decides which sub-function to invoke to create the correct movement. For example, the linear motion command (G01) has a dedicated sub-function which gets called whenever the motion task receives a struct with the G01 tag.

Libraries

The above tasks are responsible for the processing pipeline that allows for software to control the mechanical and electrical components, but it wouldn't be possible to achieve without the use of additional libraries to help keep the main code clean, extensible, and debug-able. 

The majority of the libraries used are from libopencm3, which provides APIs for interacting with the microcontroller peripherals like USART, GPIO, and hardware timers. These libraries are the basis for the other libraries which control application-specific things. This code is often referred to as the BSP, or board support package. There are a number of BSP libraries that were written for this project. 

PWM

The PWM library wraps a number of functions from the timer library from libopencm3. It initializes the requested timer peripheral (i.e. TIM1, TIM3, etc) as a PWM timer with an output channel connected to a specific IO pin on the microcontroller. There is also a function that controls the duty cycle of the PWM waveform. It provides functionality required by the Servo library. 

Servo

The servo library has two functions: servo_init and servo_move. It uses the PWM library to control a servo connected to the microcontroller.

Stepper

The stepper library uses GPIO to control stepper motors. It has functions for initializing the correct pins, setting the direction of the stepper, and "stepping" the stepper.

Next Steps

If there were more time, I would have loved to take advantage of the timer's "one-pulse mode," which allows for precise control of a pulse output on a GPIO pin using a timer. Currently, the timing of the pulses is controlled using "while" loops with that just delay for a specific number of cycles. This took a lot of tweaking and calibrating, and often had to be recalibrated. It was especially difficult because the two steppers we used weren't exactly the same and thus required different delays in between pulses. Using the hardware timers would've allowed the system to be more predictable and would've allowed the pulses to be more configurable.