Cortex digital IO ports and the pneumatics driver

I’m going to try and explain why we sometimes have problems with the pneumatics firing and why different programming options are behaving differently. To understand what’s going on some background knowledge is needed on how the digital IO ports are configured by firmware and what features the specific micro controller in the cortex has.

To start, lets look at the simplified schematic of a digital IO port from the cortex user manual.


This shows the 1k ohm series resistor that’s used as short and ESD protection, a capacitor connects the IO port to ground to help with noise suppression and a weak pullup resistor is shown inside the cortex. A weak pullup is a resistor with a fairly large value, it can be overridden externally by using resistors of significantly lower values, for example, if the weak pullup was 100k then a pulldown of 10K would force the pin to a value of 0.3v, essentially grounding the input.

Next lets look at the details of the digital IO port inside the cortex (specifically inside the STM32 user processor).


This looks much more complex, I’m showing the path an input signal would take if the port were programmed as an input in blue. The path if programmed as an output port is in red. On the input path I outlined two resistors, a weak pullup and a weak pulldown. These resistors have a switch in series that allow them to be disabled. I won’t say too much more about this schematic, you can ask questions later if necessary.

There are several ways this port (called a GPIO port which means general purpose input/output port) can be programmed by the firmware, a summary of this is shown in the following table.


Again this looks complex but the pertinent details are as follows.

There are four different ways the port can be programmed as an input.

  1. An analog input (not every port can do this)
  2. A floating input (both pullup and pulldown resistors are turned off)
  3. An input with the weak pullup enabled.
  4. An input with the weak pulldown enabled.

When programmed as an output there are also different choices to be made.

  1. The frequency response, 2MHz, 10MHz or 50MHz. Lower frequencies are chosen to suppress noise and limit slew rate of the edges.
  2. Push/pull mode or open drain (also known as open collector). This determines if the signal is both driven high and low by turning on transistors or only driven low with the expectation that an external resistor will be used to drive the output high.
  3. Normal or alternate function. Alternate function mode is selected when the port is used for things like a UART or I2C interface. In this situation the signal comes from that special function block inside the micro-controller rather than the output register.

When the micro-controller is reset, the port is programmed as a floating input, the condition I highlighted in yellow above. This is a safe condition but is not very useful unless the port is dedicated to be an input and a known circuit used to drive it externally. The most typical selection for an input port would be to program it with the weak pullup enabled, this is the situation shown in the cortex user manual above.

The final schematic I want to show as background is the pneumatics driver cable (the cable that goes between the cortex and pneumatics solenoid). Now this schematic is just conceptual, I don’t have any pneumatics at home and have never pulled apart a cable to reverse engineer it, however, it is conceptually correct even though the exact detail may be different.


The control signal from the cortex output port drives (perhaps through a series resistor) the base of a transistor (which may be a MOSFET, I don’t know). A logic high turns the transistor on allowing current to flow and energize the external solenoid, a logic low turns the transistor off. Notice that there is a pulldown resistor from the transistor base to ground, this makes sure the transistor is off when there is no control input.

Firmware startup

When the firmware on the user processor starts, there are a series of steps performed to configure the digital IO ports. Firmware will nearly always assume that ports are inputs unless specified otherwise, this is because configuring a port as an output and driving into something not designed to be driven is generally a bad idea. Each programming option has slightly different behavior, I will outline what happens for each one below, however, one thing to mention first is that the master processor has control over reset of the user cpu, this is important to remember because it means that often the first part of the firmware reset sequence can happen multiple (usually three) times.


  1. processor is reset
  2. processor has set all GPIO ports as inputs that are floating
  3. RobotC configures all GPIO as inputs with weak pullups
  4. wait until master processor communications is established ~ 3 seconds when tethered
  5. Check for user program and run if found
  6. Configure GPIO ports to the user program
  7. If the competition template is used then a 2 second delay
  8. pre_auton runs


  1. processor is reset
  2. processor has set all GPIO ports as inputs that are floating
  3. EasyC configures all GPIO ports to the user program
  4. wait until master processor communications is established ~ 3 seconds when tethered
  5. initialize runs


  1. processor is reset
  2. processor has set all GPIO ports as inputs that are floating
  3. ConVEX configures all GPIO ports to the definitions in the board.h file (the default is input with weak pullup)
  4. vexUserSetup is called, GPIO can be reconfigured to the user program requirements here.
  5. 2 second delay to match ROBOTC competition template
  6. vexUserInit runs

So ignoring ConVEX for now, the big difference between ROBOTC and EasyC is that EasyC leaves all GPIO as floating inputs until reconfigured by the user program. ROBOTC always configures GPIO as inputs with pullup resistors first, the user code will run a few seconds later and reconfigure the ports as outputs if necessary. For a pneumatics port, EasyC starts with the port as a floating input and then changes it to be an output, ROBOTC starts with the port as a floating input, changes it to an input with a pullup and then (later on) changes that port to an output. If reset happens more than once then the first part of this sequence will happen multiple times, my measurements show that in addition to power on reset there are two subsequent master processor initiated reset sequences.

To understand why I’m highlighting the input port configuration we can look at the difference between these when connected to the conceptual solenoid driver.


When configured with a weak pullup, we have ~1V (the exact number is unknown) at the base of the transistor. When configured as a floating input we have something close to 0V. The transistor will sometimes turn on when drive with 1V, it will not when at 0V.

This is the root cause of the problem, configuring the GPIO as an input with a pullup can often cause the pneumatic solenoid to activate. ROBOTC will allow this to happen, EasyC does not.

In the next post I will show some examples of this sequence as oscilloscope traces so give me a couple of hours before commenting.


I made some measurements to illustrate the startup sequences I proposed in post #1. Lets start with ROBOTC, the test code I used is as follows.

#pragma config(Sensor, dgtl1,  pneumatics,     sensorDigitalOut)
#pragma config(Sensor, dgtl2,  test_out,       sensorDigitalOut)
//*!!Code automatically generated by 'ROBOTC' configuration wizard               !!*//

task main()
    SensorValue pneumatics ] = 1;
    SensorValue pneumatics ] = 0;
    SensorValue pneumatics ] = 1;

        SensorValue test_out ]   = 1;
        SensorValue test_out ]   = 0;

When the user code runs I’m toggling the pneumatics output so I can see where we are in the startup sequence, I then go into an infinite loop and toggle a second digital output, again just so I can tell what’s happening in the code when using an oscilloscope.

I don’t have any pneumatics at home, however, based on data I colledted last year for this post;

we know the approximate load presented by the driver and can simulate that with a single pulldown resistor of about 20k. All these tests were done with the cortex tethered to the joystick using the USB cable, last years data was probably running the cortex without a joystick so the timing of user code running is a little different.

So here is the first scope capture.


The yellow trace is digital port 1, the blue trace is digital port 2. It’s easy to see where the user code starts running about 3.5 seconds afer power on. Notice how the blue trace is pulled high to 3.3V almost immediately after power on by it’s weak pullup resistor, however, the yellow trace is held at about 1V due to the pulldown resistor of the pneumatics driver.

Now a closer look at the first few hundred milliseconds.


Notice that digital output 1 starts at 0V, rises to 1V as ROBOTC configured it as an input with pullup. This is repeated three times as the master processor forces the user cpu to reset.

Now the EasyC version of the first scope trace.


EasyC does not configure the port as an input with pullup, it actually goes from floating input to output set to logic 0 but it’s almost impossible to see that in the scope trace. The pneumatics do not fire.

ConVEX has a little more flexibility in it’s configuration. The default behavior is very similar to ROBOTC except that the port only exists in the input with pullup condition for a very short amount of time before being reconfigured as an output (usually set to logic 1 but that can changed as well). Here is the ConVEX startup in it’s default configuration.


You can see the same multiple reset condition as was visible in the ROBOTC trace, however, the reconfiguration happens much more quickly.

The final trace is with ConVEX reconfigured to behave the same way as EasyC, initial configuration is floating input followed by output set to logic 0.


So what can we do with all of this information.

Unfortunately in terms of ROBOTC there really is nothing to be done. ROBOTC needs to first run the virtual machine followed by user code that may be different each time. It has to make a decision early in the startup sequence on how to configure the IO ports and has chosen a way that is the most common for an embedded system.

EasyC, ConVEX and PROS all have the advantage that the program that is loaded and runs is the only program. PROS seems to do more or less the same as ConVEX, all ports are inputs with pullup, as with ConVEX reconfiguration from this state can happen much quicker than robots so may or may not cause the pneumatics to actually fire, I haven’t tested this and, as I said before, component tolerance means that the actual voltage in the driver will change slightly and not be enough to turn the transistor on.

That was a lot of information, hopefully at least someone reading will learn something from it.


Thank you for the information.

We just got a new cortex, and the problem does not exist anymore. Would this be because of a different resistor combination?

I don’t know if anything has changed in the various revisions of cortex to the IO ports and I have never tested a large enough sample of cortex/solenoid drivers to know exactly what the difference is. The firmware behavior does not change with the hardware, I was using a RevA4 unit to get the scope traces but with a simulated pneumatics driver.


This is great stuff. My kids are using RobotC, so I guess the lesson here is to be sure that a pneumatic firing will not affect anything important on start up.

I wonder if there’s anything the RobotC people can do in the future to prevent this, or is this something inherent to the way RobotC will always work.

1 Like

Does anyone know if the newer versions of RobotC prevent this from happening? The problem is that the pneumatics could fire on Cortex power up. Anyone know if that has been fixed? :confused:

I doubt anything has been changed, I don’t think they really consider it a bug.


That’s disappointing. I would think, at the very least, they should have a little pop-up window that warns you about this possibility when you configure your ports for solenoids. :frowning:

I have a question regarding RobotC. Upon startup, if I keep the hand-valve in the closed position, would the piston fire once I opened the valve?
Also, would the port properly initialize if I unplug the solenoid?


Well, it could be argued that it’s a hardware issue not a software issue. Not all solenoid drivers do this.

If the air is turned off then the solenoid will still fire but the pistons will not actuate, this is what most teams do anyway.


I was running the same version of robotC (3.62) for my Toss Up and Skyrise robot. Both use pneumatics. On startup for Toss Up, all my pneumatics would fire (including the ones on the hanging device, not just the catapult), which is why I used a switch. For my Skyrise robot, I forgot to put a switch on it, but it didn’t fire at all on startup. The only difference was the Vexnet 2.0 keys, which I don’t know if that was the one that changed this behavior.

I seem to recall a Toss Up team having trouble with their catapult shooting on startup. If I remember correctly, they said it shot it sometimes and sometimes not, as though it might have been a nearly random thing on startup. They had no idea why it did that - they just suffered with it. If the kids/teachers don’t know this could happen, it seems to me it could be hazardous, especially as the SkyRise robots are getting so big.

It truly can get dangerous. I got smacked in the face once with my catapult, and it was really close to hitting my eye. It was also due to human error, since I forgot to turn the switch off. Another reason I kept the switch was because I didn’t want to repump the minimal air I just lost in the spaz.

Well I am also suffering from this for some time, while our robot has three pumps, the air fires at start up can cause it to lose air pressure and even hurt someone…

One weird thing is that we have several identical robots running the same code, but the air fire happens in different pumps (e.g one in the gripper another one in the arm). How could this happen?

Does anyone know how to eliminate (or at least minimize) this issue? I am currently using the latest robotC, but this issue is still there.

As far as I know, the only way to (still) deal with this problem is to place one of those blue-knobbed shutoff valves between your air tanks and your solenoids. At the beginning of a match, you keep the blue-knobbed shutoff turned off. You then turn on the Cortex and allow things to settle (maybe about 10 seconds?). Then you turn on the valve so the solenoids + pistons can receive air.

If you don’t have such a blue-knobbed shutoff valve, I think you might still be able to buy them here, when and if they are in stock:
(Be careful when buying such valves from outside dealers: you want to make sure they are identical to what Vex sells.)

1 Like

Thanks for your reply! I already have such valves attached to the air tanks plug wire. If I attach another one I doubt that the children would be confused of with one to turn on before competition… May I ask what are you using for the plug of air tank so that it can be inflated but won’t leak air?

If you already have a shutoff valve you should not be having the problem you are describing. Your system should look like:

Air Tank --> Tire Pump Fitting --> Shutoff Valve --> Rest of system

If you fill your tank with air and have the shutoff valve closed when you turn your cortex on, there is no way for pressure to get to your pistons. During the first few seconds of your Cortex turning on you will most likely hear the solenoids clicking a few times. Once the Cortex is fully booted, the solenoids will no longer be clicking and you can open your shutoff valve. Then you are ready to go.

1 Like

We have the Schrader valve (which came with the pneumatics kit) at one end and the fitting that feeds to the pneumatic tubing at the other.

FIXED as of next ROBOTC firmware.

1 Like