I’ve made a few comments regarding the limitations of C++ on IQ, however, I want to emphasize that you can write some pretty large programs using C++, you just need to keep within the constraints of the system and perhaps have a bias towards writing C code rather than C++.
A few years back I ported the (at that time) default IQ drive code to ROBOTC, you can find that in a repo here.
It was a pretty good reproduction of the default drive code with the exception that user programs cannot use the “exit” button on the brain.
When we first were testing C++ on IQ, I ported that project to the new SDK as a test to see where the limit of code size etc. was. that was about 18 months ago using very preliminary tools. Just to see where we ended up I recently brought that into VEXcode IQ, a few minor changes were needed (no dynamic memory, controller needs configuring in the graphical configuration) but it seems to compile and run ok. Here’s the code, it’s about 1500 lines.
code part 1, continued in next post
/*-----------------------------------------------------------------------------*/
/* */
/* Copyright (c) James Pearman */
/* 2016 */
/* All Rights Reserved */
/* */
/*-----------------------------------------------------------------------------*/
/* */
/* Module: iqDefaultCode.c */
/* Author: James Pearman */
/* Created: 31 Jan 2016 */
/* */
/* V1.00 31 Jan 2016 Initial release tested with V4.52 */
/* */
/* This version for VEXcode IQ 2.0.1, August 5 2020 */
/* */
/* */
/*-----------------------------------------------------------------------------*/
/* */
/* The author is supplying this software for use with the VEX IQ */
/* control system. This file can be freely distributed and teams are */
/* authorized to freely use this program , however, it is requested that */
/* improvements or additions be shared with the Vex community via the vex */
/* forum. Please acknowledge the work of the authors when appropriate. */
/* Thanks. */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); */
/* you may not use this file except in compliance with the License. */
/* You may obtain a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* */
/* The author can be contacted on the vex forums as jpearman */
/* or electronic mail using jbpearman_at_mac_dot_com */
/* Mentor for team 8888 RoboLancers, Pasadena CA. */
/* */
/*-----------------------------------------------------------------------------*/
/* */
/* Description: */
/* */
/* A simulation of the Driver Control program for VEX IQ written in ROBOTC. */
/* */
/* PORT1 Left Drive Motor (optional) */
/* PORT2 Bumper switch, stop PORT4 motor going reverse. Or any sensor. */
/* PORT3 Bumper switch, stop PORT4 motor going forward. Or any sensor. */
/* PORT4 Auxilary motor (BtnRUp & BtnRDown). Or any sensor. */
/* PORT5 Auxilary motor (BtnFUp & BtnFDown). Or any sensor. */
/* PORT6 Right Drive Motor (optional) */
/* */
/* PORT7 Left Drive Motor (optional) */
/* PORT8 Bumper switch, stop PORT10 motor going reverse. Or any sensor. */
/* PORT9 Bumper switch, stop PORT10 motor going forward. Or any sensor. */
/* PORT10 Auxilary motor (BtnLUp & BtnLDown). Or any sensor. */
/* PORT11 Auxilary motor (BtnEUp & BtnEDown. Or any sensor.) */
/* PORT12 Right Drive Motor (optional) */
/* */
/* Optional sensors */
/* Touch LED, enable/disable robot, any free port */
/* Color Sensor, enable/disable robot, any free port */
/* Gyro, reset robot to start orientation, any free port */
/* Sonar (distance), stop robot hitting objects, any free port */
/* */
/*-----------------------------------------------------------------------------*/
#include "iq_cpp.h"
using namespace vex;
// configuration menu items
typedef enum tConfigSelect {
kConfig_exit = 0,
kConfig_0 = 1,
kConfig_1 = 2,
kConfig_2 = 3,
kConfig_3 = 4,
kConfig_4 = 5,
kConfig_5 = 6,
kConfig_6 = 7,
kConfig_7 = 8
} tConfigSelect;
// joystick control modes
typedef enum tJoystickMode {
kJoystickTank,
kJoystickArcadeLeft,
kJoystickArcadeRight
} tJoystickMode;
// motor control direction
typedef enum tMotorDirection {
kNormal,
kReverse
} tMotorDirection;
// Motor data is collected together in a structure
typedef struct _IQMotor {
bool installed; ///< is motor installed
tMotorDirection direction; ///< reverse motor
vex::bumper *fwdTouch; ///< touch sensor port to stop motor going forwards
vex::bumper *revTouch; ///< touch sensor port to stop motor going reverse
float velocity; ///< calculated velocity
long lastEncoder;
long lastTime;
vex::motor &instance;
} IQMotor;
// Default setting for config page if they are not stored
// in memory
#define DRIVE_MODE_DEFAULT kJoystickTank
#define DRIVE_TURN_DEFAULT kNormal
#define DRIVE_FWD_DEFAULT kNormal
#define MOTOR_04_DEFAULT kNormal
#define MOTOR_05_DEFAULT kNormal
#define MOTOR_10_DEFAULT kNormal
#define MOTOR_11_DEFAULT kNormal
#define IQ_TICK_PER_REV 960 ///< raw encoder counts/rev
// Globals to hold state and sensor info
#define PORTNONE (-1)
// LCD rows
#define LCD_ROW1 1
#define LCD_ROW2 2
#define LCD_ROW3 3
#define LCD_ROW4 4
#define LCD_ROW5 5
#define LCD_ROW6 6
#define LCD_ROW7 7
#define LCD_ROW8 8
#define LCD_ROW9 9
#define LCD_ROW 9
// define in graphical config
//vex::controller Controller;
vex::timer T1;
vex::motor Motor1( vex::PORT1 );
vex::motor Motor6( vex::PORT6 );
vex::motor Motor7( vex::PORT7 );
vex::motor Motor12( vex::PORT12 );
vex::motor Motor4( vex::PORT4 );
vex::motor Motor5( vex::PORT5 );
vex::motor Motor10( vex::PORT10 );
vex::motor Motor11( vex::PORT11 );
vex::motor Motor2( vex::PORT2 );
vex::motor Motor3( vex::PORT3 );
vex::motor Motor8( vex::PORT8 );
vex::motor Motor9( vex::PORT9 );
vex::gyro robotGyroSensor(0);
vex::touchled robotTouchSensor(0);
vex::colorsensor robotColorSensor(0);
vex::sonar robotSonarSensor(0);
vex::bumper Bumper2( PORT2 );
vex::bumper Bumper3( PORT3 );
vex::bumper Bumper8( PORT8 );
vex::bumper Bumper9( PORT9 );
// Globals used by the code
tJoystickMode configDriveMode = DRIVE_MODE_DEFAULT;
tMotorDirection configDriveTurn = DRIVE_TURN_DEFAULT;
bool robotRunning = false;
bool robotEnabled = true;
bool sonarFront = true;
IQMotor IQMotors[ 12 ] = {
{ .instance = Motor1 },
{ .instance = Motor2 },
{ .instance = Motor3 },
{ .instance = Motor4 },
{ .instance = Motor5 },
{ .instance = Motor6 },
{ .instance = Motor7 },
{ .instance = Motor8 },
{ .instance = Motor9 },
{ .instance = Motor10 },
{ .instance = Motor11 },
{ .instance = Motor12 }
};
short robotTotalDevices;
const unsigned char logo[320] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0C,0x00,0x30,0x7F,0xFE,0x30,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,
0x1E,0x00,0x78,0xFF,0xFE,0x3C,0x07,0x80,0x00,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,
0x1F,0x00,0x78,0xFF,0xFE,0x3C,0x0F,0x00,0x00,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,
0x0F,0x00,0xF0,0xF0,0x00,0x1E,0x1E,0x00,0x00,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,
0x0F,0x80,0xF0,0xF0,0x00,0x1F,0x3E,0x00,0xF8,0x00,0xF8,0x00,0xF9,0xE0,0x3F,0x00,
0x07,0x81,0xE0,0xF0,0x00,0x0F,0xFC,0x07,0xFE,0x07,0xFF,0x03,0xFD,0xE0,0xFF,0xC0,
0x07,0xC1,0xE0,0xFF,0xF8,0x07,0xF8,0x0F,0x9F,0x0F,0x8F,0x87,0xCF,0xE1,0xF3,0xE0,
0x03,0xC3,0xC0,0xFF,0xFC,0x03,0xF0,0x1E,0x07,0x8F,0x07,0x8F,0x83,0xE3,0xC1,0xF0,
0x03,0xC3,0xC0,0xFF,0xFC,0x03,0xF0,0x1E,0x03,0x1E,0x03,0xCF,0x01,0xE3,0xC0,0xF0,
0x01,0xE7,0x80,0xF0,0x00,0x07,0xF8,0x1E,0x00,0x1E,0x03,0xCF,0x01,0xE3,0xC0,0xF0,
0x01,0xE7,0x80,0xF0,0x00,0x0F,0xFC,0x1C,0x00,0x1E,0x03,0xCF,0x01,0xE3,0xFF,0xE0,
0x00,0xF7,0x00,0xF0,0x00,0x1F,0x3E,0x1E,0x03,0x9E,0x03,0xCF,0x01,0xE3,0xC0,0x00,
0x00,0xFF,0x00,0xF0,0x00,0x3E,0x1F,0x1F,0x07,0x8F,0x07,0x87,0x83,0xE3,0xC0,0x70,
0x00,0x7E,0x00,0xFF,0xFE,0x7C,0x0F,0x0F,0xFF,0x0F,0xFF,0x87,0xFF,0xE1,0xFF,0xE0,
0x00,0x7E,0x00,0xFF,0xFF,0x78,0x07,0x87,0xFE,0x03,0xFE,0x03,0xFD,0xE0,0xFF,0xC0,
0x00,0x18,0x00,0x7F,0xFE,0x30,0x03,0x00,0xF0,0x00,0xF8,0x00,0xF0,0xC0,0x1F,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
const unsigned char *getLogo() {
return( logo );
}
/*-----------------------------------------------------------------------------*/
/* Draw the bitmap defined in the array above on the LCD */
/* Just a hardcoded hack for now, will improve later */
/*-----------------------------------------------------------------------------*/
void
drawBitmap( const unsigned char *bitmap )
{
int row, stripe, pix;
int p;
Brain.Screen.setPenColor( vex::colorType::white );
Brain.Screen.setFillColor( vex::colorType::white );
Brain.Screen.drawRectangle( 0, 10, 127, 24 );
Brain.Screen.setPenColor( vex::colorType::black );
// 24 rows
Brain.Screen.setAspectCompensation(false);
for(row=0;row<20;row++)
{
// 16 bytes each with 8 pixels
for(stripe=0;stripe<16;stripe++)
{
p = bitmap[ (row*16) + stripe ];
for(pix=0;pix<8;pix++)
{
if( ( p & 0x80 ) )
Brain.Screen.drawPixel( (stripe*8) + pix, (row+10) );
p <<= 1;
}
}
}
Brain.Screen.setAspectCompensation(true);
}
// Base address of EEPROM
unsigned long eepromPtr = 0x400AF000;
#define EEPROM_PAGE_REG 0x04
#define EEPROM_OFFSET_REG 0x08
#define EEPROM_RDWR_REG 0x10
#define EEPROM_RDWRI_REG 0x14
// The page we use, be very careful with this, don't change it !!
#define EEPROM_PAGE 0x15
/*-----------------------------------------------------------------------------*/
/* Initialize read or write */
/*-----------------------------------------------------------------------------*/
void
iqSettingsSetupPage()
{
unsigned long *p;
p = (unsigned long *)(eepromPtr+EEPROM_PAGE_REG);
*p = EEPROM_PAGE; // default page is 21
p = (unsigned long *)(eepromPtr+EEPROM_OFFSET_REG);
*p = 0; // offset 0
}
/*-----------------------------------------------------------------------------*/
/* Read one unsigned long from EEPROM */
/*-----------------------------------------------------------------------------*/
unsigned long
iqSettingsReadData()
{
unsigned long *p;
unsigned long data;
iqSettingsSetupPage();
p = (unsigned long *)(eepromPtr+EEPROM_RDWR_REG);
data = *p;
vex::console::write("r %08X\n", data);
return( data );
}
/*-----------------------------------------------------------------------------*/
/* Write one unsigned long to EEPROM */
/*-----------------------------------------------------------------------------*/
void
iqSettingsWriteData( unsigned long data )
{
unsigned long *p;
iqSettingsSetupPage();
p = (unsigned long *)(eepromPtr+EEPROM_RDWR_REG);
*p = data;
vex::console::write("w %08X\n", data);
}
/*-----------------------------------------------------------------------------*/
/* Simple integrity check on EEPROM page to check most locations are 0xFF */
/*-----------------------------------------------------------------------------*/
bool
iqSettingsIntegrityCheck()
{
volatile unsigned long *p;
int i;
bool fail = false;
// Do some simple checks on the EEPROM
iqSettingsSetupPage();
p = (unsigned long *)(eepromPtr+EEPROM_RDWRI_REG);
// ignore first location, we may have written bad data
volatile int tmp1;
unsigned long tmp = *p; tmp1 = tmp;
// all other locations should be 0xFF
for(i=0;i<8;i++) {
if( *p != 0xFFFFFFFF )
fail = true;
}
return( !fail );
}
/*-----------------------------------------------------------------------------*/
/* Masks for our config data */
/*-----------------------------------------------------------------------------*/
#define CODE_MASK 0xFFFF0000
#define CODE_IDENT 0xC0DE0000
#define MODE_MASK 0x00000003
#define TURN_MASK 0x00000004
#define M01_MASK 0x00000008
#define M04_MASK 0x00000010
#define M05_MASK 0x00000020
#define M10_MASK 0x00000040
#define M11_MASK 0x00000080
#define SONAR_MASK 0x00000100
/*-----------------------------------------------------------------------------*/
/* Store configuration settings */
/*-----------------------------------------------------------------------------*/
void
iqSettingsStore()
{
unsigned long data;
data = iqSettingsReadData();
switch(configDriveMode)
{
case kJoystickTank:
data = (data & ~MODE_MASK);
break;
case kJoystickArcadeLeft:
data = (data & ~MODE_MASK) | 0x00000001;
break;
case kJoystickArcadeRight:
data = (data & ~MODE_MASK) | 0x00000002;
break;
}
if( configDriveTurn == kNormal )
data &= ~TURN_MASK;
else
data |= TURN_MASK;
if( IQMotors[PORT1 ].direction == kNormal )
data &= ~M01_MASK;
else
data |= M01_MASK;
if( IQMotors[PORT4 ].direction == kNormal )
data &= ~M04_MASK;
else
data |= M04_MASK;
if( IQMotors[PORT5 ].direction == kNormal )
data &= ~M05_MASK;
else
data |= M05_MASK;
if( IQMotors[PORT10].direction == kNormal )
data &= ~M10_MASK;
else
data |= M10_MASK;
if( IQMotors[PORT11].direction == kNormal )
data &= ~M11_MASK;
else
data |= M11_MASK;
if( sonarFront )
data &= ~SONAR_MASK;
else
data |= SONAR_MASK;
iqSettingsWriteData( data );
}
/*-----------------------------------------------------------------------------*/
/* Never used so init the EEPROM word */
/*-----------------------------------------------------------------------------*/
void
iqSettingsInit( unsigned long data )
{
iqSettingsWriteData( data );
}
/*-----------------------------------------------------------------------------*/
/* Retrieve data word and setup configure options */
/*-----------------------------------------------------------------------------*/
void
iqSettingsRetrieve()
{
unsigned long data;
data = iqSettingsReadData();
if( (data & CODE_MASK) == CODE_IDENT ) {
//writeDebugStreamLine("Load %08X", data );
switch( data & MODE_MASK )
{
case 0:
configDriveMode = kJoystickTank;
break;
case 1:
configDriveMode = kJoystickArcadeLeft;
break;
case 2:
configDriveMode = kJoystickArcadeRight;
break;
}
configDriveTurn = (data & TURN_MASK) ? kReverse : kNormal;
IQMotors[PORT1 ].direction = (data & M01_MASK) ? kReverse : kNormal;
IQMotors[PORT6 ].direction = (data & M01_MASK) ? kReverse : kNormal;
IQMotors[PORT4 ].direction = (data & M04_MASK) ? kReverse : kNormal;
IQMotors[PORT5 ].direction = (data & M05_MASK) ? kReverse : kNormal;
IQMotors[PORT10].direction = (data & M10_MASK) ? kReverse : kNormal;
IQMotors[PORT11].direction = (data & M11_MASK) ? kReverse : kNormal;
sonarFront = (data & SONAR_MASK) ? false : true;
}
else {
if( iqSettingsIntegrityCheck() )
iqSettingsInit( CODE_IDENT );
}
}
/*-----------------------------------------------------------------------------*/
/* Reset configuration and save */
/*-----------------------------------------------------------------------------*/
void
iqResetToDefaults()
{
configDriveMode = DRIVE_MODE_DEFAULT;
configDriveTurn = DRIVE_TURN_DEFAULT;
IQMotors[ (short)PORT1 ].direction = DRIVE_FWD_DEFAULT;
IQMotors[ (short)PORT6 ].direction = DRIVE_FWD_DEFAULT;
IQMotors[ (short)PORT4 ].direction = MOTOR_04_DEFAULT;
IQMotors[ (short)PORT5 ].direction = MOTOR_05_DEFAULT;
IQMotors[ (short)PORT10 ].direction = MOTOR_10_DEFAULT;
IQMotors[ (short)PORT11 ].direction = MOTOR_11_DEFAULT;
sonarFront = true;
iqSettingsStore();
//playSound(soundTada);
}
/*-----------------------------------------------------------------------------*/
/* Control robot wheels */
/*-----------------------------------------------------------------------------*/
void
iqRunRobotDrive()
{
short leftDrive;
short rightDrive;
if( robotEnabled ) {
switch( configDriveMode )
{
case kJoystickTank:
leftDrive = Controller.AxisA.value();
rightDrive = Controller.AxisD.value();
break;
case kJoystickArcadeLeft:
leftDrive = Controller.AxisA.value() + Controller.AxisB.value();
rightDrive = Controller.AxisA.value() - Controller.AxisB.value();
break;
case kJoystickArcadeRight:
leftDrive = Controller.AxisD.value() + Controller.AxisC.value();
rightDrive = Controller.AxisD.value() - Controller.AxisC.value();
break;
}
// threshold
if( abs(leftDrive) < 10 ) leftDrive = 0;
if( abs(rightDrive) < 10 ) rightDrive = 0;
// flip drive direction based on configuration
leftDrive = (IQMotors[PORT1 ].direction == kNormal) ? leftDrive : -leftDrive;
rightDrive = (IQMotors[PORT6 ].direction == kNormal) ? rightDrive : -rightDrive;
// reverse left and right & flips signs
// Not sure I agree with this but it makes robot behave
// as per the default driver program.
// Robot front is now back.
if( configDriveTurn == kReverse ) {
int tmp = leftDrive;
leftDrive = -rightDrive;
rightDrive = -tmp;
}
// When moving reset a timer used to start gyro control
if( leftDrive || rightDrive )
T1.clear();
// Gyro sensor
if( robotGyroSensor.installed() ) {
// If stopped for more than 500mS then reorientate using gyro
if( T1.time() >= 500 ) {
int heading = robotGyroSensor.angle();
if( heading > 180 )
heading -= 360;
// small amount of hysteresis
if( abs( heading ) < 2 ) {
leftDrive = 0;
rightDrive = 0;
}
else
if( heading > 3 ) {
leftDrive = 30 + heading / 2;
rightDrive = -30 - heading / 2;
}
else
if( heading < -3 ) {
leftDrive = -30 + heading / 2;
rightDrive = 30 - heading / 2;
}
}
}
// Sonar sensor installed
// stop motors going forwards
if( robotSonarSensor.installed() ) {
// stop at 15cm
if( robotSonarSensor.distance(distanceUnits::mm ) < 150 )
{
if( (sonarFront && leftDrive > 0) || (!sonarFront && leftDrive < 0) )
leftDrive = 0;
if( (sonarFront && rightDrive > 0) || (!sonarFront && rightDrive < 0) )
rightDrive = 0;
}
}
}
else {
leftDrive = 0;
rightDrive = 0;
}
// Control drive motors.
if( IQMotors[ PORT1 ].installed )
Motor1.spin( fwd, leftDrive, rpm );
if( IQMotors[ PORT7 ].installed )
Motor7.spin( fwd, leftDrive, rpm );
if( IQMotors[ PORT6 ].installed )
Motor6.spin( fwd, -rightDrive, rpm ); // right is reversed
if( IQMotors[ PORT12 ].installed )
Motor12.spin( fwd, -rightDrive, rpm ); // right is reversed
}
/*-----------------------------------------------------------------------------*/
/* Control the four Auxiliary motors, ports 4, 5, 10 & 11 */
/*-----------------------------------------------------------------------------*/
void
iqRunRobotAux()
{
int m04Drive;
int m05Drive;
int m10Drive;
int m11Drive;
if( robotEnabled ) {
// Control the auxilary motors
m04Drive = ( Controller.ButtonRUp.pressing() * 100) - (Controller.ButtonRDown.pressing() * 100);
m05Drive = ( Controller.ButtonFUp.pressing() * 100) - (Controller.ButtonFDown.pressing() * 100);
m10Drive = ( Controller.ButtonLUp.pressing() * 100) - (Controller.ButtonLDown.pressing() * 100);
m11Drive = ( Controller.ButtonEUp.pressing() * 100) - (Controller.ButtonEDown.pressing() * 100);
m04Drive = (IQMotors[PORT4 ].direction == kNormal) ? m04Drive : -m04Drive;
m05Drive = (IQMotors[PORT5 ].direction == kNormal) ? m05Drive : -m05Drive;
m10Drive = (IQMotors[PORT10].direction == kNormal) ? m10Drive : -m10Drive;
m11Drive = (IQMotors[PORT11].direction == kNormal) ? m11Drive : -m11Drive;
// Deal with touch sensors
// stopping motor 4 and motor 10
if( IQMotors[ PORT4 ].fwdTouch != NULL ) {
if( m04Drive > 0 && IQMotors[ PORT4 ].fwdTouch->pressing() )
m04Drive = 0;
}
if( IQMotors[ PORT4 ].revTouch != NULL ) {
if( m04Drive < 0 && IQMotors[ PORT4 ].revTouch->pressing() )
m04Drive = 0;
}
if( IQMotors[ PORT10 ].fwdTouch != NULL ) {
if( m10Drive > 0 && IQMotors[ PORT10 ].fwdTouch->pressing() )
m10Drive = 0;
}
if( IQMotors[ PORT10 ].revTouch != NULL) {
if( m10Drive < 0 && IQMotors[ PORT10 ].revTouch->pressing() )
m10Drive = 0;
}
} // robotEnabled
else {
// Robot disabled
m04Drive = m05Drive = m10Drive = m11Drive = 0;
}
// Control Aux motors
if(IQMotors[ PORT4 ].installed)
Motor4.spin( fwd, m04Drive, rpm );
if(IQMotors[ PORT5 ].installed)
Motor5.spin( fwd, m05Drive, rpm );
if(IQMotors[ PORT10 ].installed)
Motor10.spin( fwd, m10Drive, rpm );
if(IQMotors[ PORT11 ].installed)
Motor11.spin( fwd, m11Drive, rpm );
}
As the code has been ported a couple of times, it’s not necessarily how I would write it from scratch now, it had to work with ROBOTC limitations originally. But I wanted to show that IQ can support more than just trivial code. The stats for this program are
so about 28k of code and still a reasonable amount of RAM remaining (around 2k) primarily because no events are being used. (installing even one event will use another 1.5k of memory, it’s been pulled out due to optimization by the linker)