/// <summary> /// Writes a SmcSettings object as text to the specified streamwriter. /// </summary> /// <param name="settings">The settings to read from.</param> /// <param name="sw">The file to write to.</param> /// <param name="device">The device that these settings came from.</param> public static void save(SmcSettings settings, StreamWriter sw, Smc device) { sw.WriteLine("# Pololu Simple Motor Controller G2 settings file."); sw.WriteLine("# " + Smc.documentationUrl); sw.WriteLine("product: " + Smc.productIdToShortModelString(device.productId)); // [Add-new-settings-here] writeInputMode(sw, settings.inputMode); writeMixingMode(sw, settings.mixingMode); writeSerialMode(sw, settings.serialMode); writeBool(sw, "enable_i2c", settings.enableI2C); writeU32(sw, "serial_device_number", settings.serialDeviceNumber); writeBool(sw, "crc_for_commands", settings.crcForCommands); writeBool(sw, "crc_for_responses", settings.crcForResponses); writeBool(sw, "uart_response_delay", settings.uartResponseDelay); writeBool(sw, "use_fixed_baud_rate", settings.useFixedBaudRate); writeU32(sw, "fixed_baud_rate", settings.fixedBaudRateBps); // Channel settings writeChannelSettings(sw, "rc1", false, settings.rc1); writeChannelSettings(sw, "rc2", false, settings.rc2); writeChannelSettings(sw, "analog1", true, settings.analog1); writeChannelSettings(sw, "analog2", true, settings.analog2); // Motor settings writeU32(sw, "pwm_period_factor", settings.pwmPeriodFactor); writeBool(sw, "motor_invert", settings.motorInvert); writeBool(sw, "coast_when_off", settings.coastWhenOff); writeU32(sw, "speed_update_period", settings.speedUpdatePeriod); writeMotorLimits(sw, "forward", settings.forwardLimits); writeMotorLimits(sw, "reverse", settings.reverseLimits); writeU32(sw, "current_limit", settings.currentLimit); writeU32(sw, "current_offset_calibration", settings.currentOffsetCalibration); writeU32(sw, "current_scale_calibration", settings.currentScaleCalibration); // Advanced settings writeU32(sw, "min_pulse_period", settings.minPulsePeriod); writeU32(sw, "max_pulse_period", settings.maxPulsePeriod); writeU32(sw, "rc_timeout", settings.rcTimeout); writeU32(sw, "consec_good_pulses", settings.consecGoodPulses); writeS32(sw, "vin_scale_calibration", settings.vinScaleCalibration); writeBool(sw, "temp_limit_gradual", settings.tempLimitGradual); writeU32(sw, "over_temp_complete_shutoff_threshold", settings.overTempCompleteShutoffThreshold); writeU32(sw, "over_temp_normal_operation_threshold", settings.overTempNormalOperationThreshold); writeU32(sw, "low_vin_shutoff_timeout", settings.lowVinShutoffTimeout); writeU32(sw, "low_vin_shutoff_mv", settings.lowVinShutoffMv); writeU32(sw, "low_vin_startup_mv", settings.lowVinStartupMv); writeU32(sw, "high_vin_shutoff_mv", settings.highVinShutoffMv); writeBool(sw, "disable_safe_start", settings.disableSafeStart); writeBool(sw, "ignore_pot_disconnect", settings.ignorePotDisconnect); writeBool(sw, "ignore_err_line_high", settings.ignoreErrLineHigh); writeBool(sw, "never_sleep", settings.neverSleep); writeU32(sw, "command_timeout", settings.commandTimeout); }
/// <summary> /// Compares this object to another to see if they have the same values. /// </summary> public override bool Equals(object x) { SmcSettings s = x as SmcSettings; if (s == null) { return(false); } return(this.convertToStruct().Equals(s.convertToStruct())); }
/// <summary> /// Writes a SmcSettings object as text to the specified file. /// </summary> /// <param name="settings">The settings to read from.</param> /// <param name="filename">The file to write to.</param> /// <param name="device">The device that these settings came from.</param> public static void save(SmcSettings settings, string filename, Smc device) { using (FileStream settingsFile = File.Open(filename, FileMode.Create)) { using (StreamWriter sw = new StreamWriter(settingsFile)) { save(settings, sw, device); } } }
/// <summary> /// Sends a new set of settings to the device, to be written to flash. /// This takes about 26 ms. /// </summary> public unsafe void setSmcSettings(SmcSettings settings) { SmcSettingsStruct settingsStruct = settings.convertToStruct(); try { controlTransfer(0x40, (byte)SmcRequest.SetSettings, 0, 0, &settingsStruct, (UInt16)sizeof(SmcSettingsStruct)); } catch (Exception exception) { throw new Exception("There was an error writing settings to the device.", exception); } }
/// <summary> /// Changes the settings for a specified channel. /// </summary> /// <param name="settings">The settings of the device. This object will be modified.</param> /// <param name="channel">Specifies the channel to change.</param> /// <param name="channelSettings">The new settings for the channel.</param> public static void setChannelSettings(this SmcSettings settings, SmcChannel channel, SmcChannelSettings channelSettings) { switch (channel) { case SmcChannel.Analog1: settings.analog1 = channelSettings; break; case SmcChannel.Analog2: settings.analog2 = channelSettings; break; case SmcChannel.Rc1: settings.rc1 = channelSettings; break; case SmcChannel.Rc2: settings.rc2 = channelSettings; break; default: throw new Exception("Unknown Channel: " + channel.ToString()); } }
/// <summary> /// Gets the settings for the specified channel. /// </summary> /// <param name="settings">The settings of the device.</param> /// <param name="channel">Specifies what channel to fetch.</param> /// <returns>The settings of the specified channel.</returns> public static SmcChannelSettings getChannelSettings(this SmcSettings settings, SmcChannel channel) { switch (channel) { case SmcChannel.Analog1: return(settings.analog1); case SmcChannel.Analog2: return(settings.analog2); case SmcChannel.Rc1: return(settings.rc1); case SmcChannel.Rc2: return(settings.rc2); default: throw new Exception("Unknown Channel: " + channel.ToString()); } }
/// <summary> /// Fixes certain things about a setttings object so that it doesn't make the device /// do something invalid. /// For each thing that gets fixed, a warning is added to the warnings list that is passed in. /// </summary> /// <param name="newSettings">The settings to fix.</param> /// <param name="warnings">A list of warnings. This function will add items to the list.</param> /// <param name="productId">The product ID of the device these settings will be used for.</param> /// <param name="firmwareVersion">The firmware version of the device these settings will be used for. /// If unknown, this argument should be 0.</param> public static void fixSettings(SmcSettings newSettings, List <string> warnings, UInt16 productId, UInt16 firmwareVersion) { if (productId == 0) { throw new Exception("Internal error: an invalid product ID was passed to fixSettings."); } if (newSettings.productId != productId) { throw new Exception( "These settings are for a different device. " + "The settings are for the " + Smc.productIdToShortModelString(newSettings.productId) + ", " + "not the " + Smc.productIdToShortModelString(productId) + "."); } // TODO: change the messages here to use present tense and future tense (like the Tic and Jrk). if (newSettings.overTempCompleteShutoffThreshold < newSettings.overTempNormalOperationThreshold) { warnings.Add( "The over-temperature complete shutoff threshold was " + "lower than the over-temperature normal operation threshold. " + "Both settings will be set to " + Smc.temperatureToString(newSettings.overTempCompleteShutoffThreshold) + " so the motor will shut off at that temperature."); newSettings.overTempNormalOperationThreshold = newSettings.overTempCompleteShutoffThreshold; } if (newSettings.lowVinStartupMv < newSettings.lowVinShutoffMv) { if (newSettings.lowVinShutoffMv + 500 > UInt16.MaxValue) { newSettings.lowVinStartupMv = UInt16.MaxValue; } else { newSettings.lowVinStartupMv = (UInt16)(newSettings.lowVinShutoffMv + 500); } warnings.Add("The Low VIN Startup voltage was lower than the Low VIN Shutoff voltage (" + newSettings.lowVinShutoffMv / (decimal)1000 + " V). " + "The Low VIN Startup voltage will be changed to " + newSettings.lowVinStartupMv / (decimal)1000 + " V."); } if (newSettings.highVinShutoffMv < newSettings.lowVinStartupMv) { newSettings.highVinShutoffMv = (new SmcSettings(productId).highVinShutoffMv); warnings.Add("The High VIN Shutoff voltage was lower than the Low VIN Startup voltage (" + newSettings.lowVinStartupMv / (decimal)1000 + " V). " + "The High VIN Shutoff voltage will be changed to " + newSettings.highVinShutoffMv / (decimal)1000 + " V."); } if (newSettings.vinScaleCalibration > 1500) { newSettings.vinScaleCalibration = 1500; warnings.Add("The VIN scale calibration was too high. It will be changed to 1500."); } if (newSettings.currentScaleCalibration > 20000) { newSettings.currentScaleCalibration = 20000; warnings.Add("The current scale calibration was too high. It will be changed to 20000."); } // Prevent the channel scaling values from being out of order (it's okay if they are equal) foreach (SmcChannel channel in Smc.channels) { SmcChannelSettings cs = newSettings.getChannelSettings(channel); if (cs.errorMin > cs.inputMin || cs.inputMin > cs.inputNeutralMin || cs.inputNeutralMin > cs.inputNeutralMax || cs.inputNeutralMax > cs.inputMax || cs.inputMax > cs.errorMax) { warnings.Add("The scaling values for " + channel.name() + " are out of order. They will be reset to their default settings."); SmcChannelSettings defaults = SmcChannelSettings.defaults(channel); cs.errorMin = defaults.errorMin; cs.inputMin = defaults.inputMin; cs.inputNeutralMin = defaults.inputNeutralMin; cs.inputNeutralMax = defaults.inputNeutralMax; cs.inputMax = defaults.inputMax; cs.errorMax = defaults.errorMax; } } fixMotorLimits(newSettings.forwardLimits, "forward", warnings); fixMotorLimits(newSettings.reverseLimits, "reverse", warnings); }
/// <summary> /// Parses a saved configuration file and returns a SmcSettings object. /// </summary> /// <param name="warnings">A list of warnings. Whenever something goes /// wrong with the file loading, a warning will be added to this list. /// The warnings are not fatal; if the function returns it will return /// a valid SmcSettings object. /// </param> /// <param name="sr">The file to read from.</param> public static SmcSettings load(StreamReader sr, List <String> warnings) { // For now, we just ignore the 'product' line in the settings file. // Note: In the next version of this software, we probably want to do things // differently here. We would not have a productId argument. We would get the // product from the settings file itself and use that to populate the default settings. // We would have a "fix_and_change_product" function that takes care of switching the // settings over to the new product (and firmware version) and fixing any issues in // it at the same time. var map = new Dictionary <string, string>(); int lineNumber = 0; char[] separators = new[] { ':' }; while (!sr.EndOfStream) { lineNumber++; string line = sr.ReadLine(); if (line.Length == 0 || line.StartsWith("#")) { continue; } string[] parts = line.Split(separators); // Make sure we have the right number of parts and make sure the first part does not // contain invalid characters that also make our later error messages about it be // confusing. if (parts.Length != 2 || parts[0].Contains(" ") || parts[0].Contains("\"")) { string hint = ""; if (line.StartsWith("<!--")) { hint = " Settings files saved from original Simple Motor Controller software are not supported."; } throw new Exception("Line " + lineNumber + " has an invalid format." + hint); } map[parts[0]] = parts[1].Trim(); } UInt16 productId = Smc.shortModelStringToProductId(map["product"]); if (productId == 0) { throw new Exception("Invalid product name: \"" + map["product"] + "\"."); } SmcSettings settings = new SmcSettings(productId); foreach (var item in map) { string value = item.Value; string key = item.Key; switch (key) { case "product": // Already processed. break; case "input_mode": settings.inputMode = parseInputMode(value); break; case "mixing_mode": settings.mixingMode = parseMixingMode(value); break; case "serial_mode": settings.serialMode = parseSerialMode(value); break; case "enable_i2c": settings.enableI2C = parseBool(key, value); break; case "serial_device_number": settings.serialDeviceNumber = parseByte(key, value); break; case "crc_for_commands": settings.crcForCommands = parseBool(key, value); break; case "crc_for_responses": settings.crcForResponses = parseBool(key, value); break; case "uart_response_delay": settings.uartResponseDelay = parseBool(key, value); break; case "use_fixed_baud_rate": settings.useFixedBaudRate = parseBool(key, value); break; case "fixed_baud_rate": settings.fixedBaudRateBps = parseU32(key, value); break; case "rc1_alternate_use": settings.rc1.alternateUse = parseAlternateUse(key, value); break; case "rc1_invert": settings.rc1.invert = parseBool(key, value); break; case "rc1_scaling_degree": settings.rc1.scalingDegree = parseByte(key, value); break; case "rc1_error_min": settings.rc1.errorMin = parseU16(key, value); break; case "rc1_input_min": settings.rc1.inputMin = parseU16(key, value); break; case "rc1_input_neutral_min": settings.rc1.inputNeutralMin = parseU16(key, value); break; case "rc1_input_neutral_max": settings.rc1.inputNeutralMax = parseU16(key, value); break; case "rc1_input_max": settings.rc1.inputMax = parseU16(key, value); break; case "rc1_error_max": settings.rc1.errorMax = parseU16(key, value); break; case "rc2_alternate_use": settings.rc2.alternateUse = parseAlternateUse(key, value); break; case "rc2_invert": settings.rc2.invert = parseBool(key, value); break; case "rc2_scaling_degree": settings.rc2.scalingDegree = parseByte(key, value); break; case "rc2_error_min": settings.rc2.errorMin = parseU16(key, value); break; case "rc2_input_min": settings.rc2.inputMin = parseU16(key, value); break; case "rc2_input_neutral_min": settings.rc2.inputNeutralMin = parseU16(key, value); break; case "rc2_input_neutral_max": settings.rc2.inputNeutralMax = parseU16(key, value); break; case "rc2_input_max": settings.rc2.inputMax = parseU16(key, value); break; case "rc2_error_max": settings.rc2.errorMax = parseU16(key, value); break; case "analog1_alternate_use": settings.analog1.alternateUse = parseAlternateUse(key, value); break; case "analog1_pin_mode": settings.analog1.pinMode = parsePinMode(key, value); break; case "analog1_invert": settings.analog1.invert = parseBool(key, value); break; case "analog1_scaling_degree": settings.analog1.scalingDegree = parseByte(key, value); break; case "analog1_error_min": settings.analog1.errorMin = parseU16(key, value); break; case "analog1_input_min": settings.analog1.inputMin = parseU16(key, value); break; case "analog1_input_neutral_min": settings.analog1.inputNeutralMin = parseU16(key, value); break; case "analog1_input_neutral_max": settings.analog1.inputNeutralMax = parseU16(key, value); break; case "analog1_input_max": settings.analog1.inputMax = parseU16(key, value); break; case "analog1_error_max": settings.analog1.errorMax = parseU16(key, value); break; case "analog2_alternate_use": settings.analog2.alternateUse = parseAlternateUse(key, value); break; case "analog2_pin_mode": settings.analog2.pinMode = parsePinMode(key, value); break; case "analog2_invert": settings.analog2.invert = parseBool(key, value); break; case "analog2_scaling_degree": settings.analog2.scalingDegree = parseByte(key, value); break; case "analog2_error_min": settings.analog2.errorMin = parseU16(key, value); break; case "analog2_input_min": settings.analog2.inputMin = parseU16(key, value); break; case "analog2_input_neutral_min": settings.analog2.inputNeutralMin = parseU16(key, value); break; case "analog2_input_neutral_max": settings.analog2.inputNeutralMax = parseU16(key, value); break; case "analog2_input_max": settings.analog2.inputMax = parseU16(key, value); break; case "analog2_error_max": settings.analog2.errorMax = parseU16(key, value); break; case "pwm_period_factor": settings.pwmPeriodFactor = parseByte(key, value); break; case "motor_invert": settings.motorInvert = parseBool(key, value); break; case "coast_when_off": settings.coastWhenOff = parseBool(key, value); break; case "speed_update_period": settings.speedUpdatePeriod = parseU16(key, value); break; case "forward_max_speed": settings.forwardLimits.maxSpeed = parseU16(key, value); break; case "forward_max_acceleration": settings.forwardLimits.maxAcceleration = parseU16(key, value); break; case "forward_max_deceleration": settings.forwardLimits.maxDeceleration = parseU16(key, value); break; case "forward_brake_duration": settings.forwardLimits.brakeDuration = parseU16(key, value); break; case "forward_starting_speed": settings.forwardLimits.startingSpeed = parseU16(key, value); break; case "reverse_max_speed": settings.reverseLimits.maxSpeed = parseU16(key, value); break; case "reverse_max_acceleration": settings.reverseLimits.maxAcceleration = parseU16(key, value); break; case "reverse_max_deceleration": settings.reverseLimits.maxDeceleration = parseU16(key, value); break; case "reverse_brake_duration": settings.reverseLimits.brakeDuration = parseU16(key, value); break; case "reverse_starting_speed": settings.reverseLimits.startingSpeed = parseU16(key, value); break; case "current_limit": settings.currentLimit = parseU16(key, value); break; case "current_offset_calibration": settings.currentOffsetCalibration = parseU16(key, value); break; case "current_scale_calibration": settings.currentScaleCalibration = parseU16(key, value); break; case "min_pulse_period": settings.minPulsePeriod = parseU16(key, value); break; case "max_pulse_period": settings.maxPulsePeriod = parseU16(key, value); break; case "rc_timeout": settings.rcTimeout = parseU16(key, value); break; case "consec_good_pulses": settings.consecGoodPulses = parseByte(key, value); break; case "vin_scale_calibration": settings.vinScaleCalibration = parseU16(key, value); break; case "temp_limit_gradual": settings.tempLimitGradual = parseBool(key, value); break; case "over_temp_complete_shutoff_threshold": settings.overTempCompleteShutoffThreshold = parseU16(key, value); break; case "over_temp_normal_operation_threshold": settings.overTempNormalOperationThreshold = parseU16(key, value); break; case "low_vin_shutoff_timeout": settings.lowVinShutoffTimeout = parseU16(key, value); break; case "low_vin_shutoff_mv": settings.lowVinShutoffMv = parseU16(key, value); break; case "low_vin_startup_mv": settings.lowVinStartupMv = parseU16(key, value); break; case "high_vin_shutoff_mv": settings.highVinShutoffMv = parseU16(key, value); break; case "disable_safe_start": settings.disableSafeStart = parseBool(key, value); break; case "ignore_pot_disconnect": settings.ignorePotDisconnect = parseBool(key, value); break; case "ignore_err_line_high": settings.ignoreErrLineHigh = parseBool(key, value); break; case "never_sleep": settings.neverSleep = parseBool(key, value); break; case "command_timeout": settings.commandTimeout = parseU16(key, value); break; default: throw new Exception("Unrecognized key: \"" + key + "\"."); } } // [Add-new-settings-here] // TODO: parse settings return(settings); }