auto-sync

This commit is contained in:
rusEfi 2014-09-01 23:02:45 -05:00
parent 7e2f3c16f2
commit a66d0211ea
7 changed files with 311 additions and 286 deletions

View File

@ -184,9 +184,10 @@ typedef struct {
brain_pin_e o2heaterPin; brain_pin_e o2heaterPin;
pin_output_mode_e o2heaterPinModeTodO; pin_output_mode_e o2heaterPinModeTodO;
unsigned int is_enabled_spi_1 : 1; unsigned int is_enabled_spi_1 : 1; // bit 0
unsigned int is_enabled_spi_2 : 1; unsigned int is_enabled_spi_2 : 1; // bit 1
unsigned int is_enabled_spi_3 : 1; unsigned int is_enabled_spi_3 : 1; // bit 2
unsigned int isSdCardEnabled : 1; // bit 3
int unused2[6]; int unused2[6];

View File

@ -1,275 +1,273 @@
/** /**
* @file mmc_card.c * @file mmc_card.c
* *
* @date Dec 28, 2013 * @date Dec 28, 2013
* @author Kot_dnz * @author Kot_dnz
* @author Andrey Belomutskiy, (c) 2012-2014 * @author Andrey Belomutskiy, (c) 2012-2014
* *
* default pinouts in case of SPI2 connected to MMC: PB13 - SCK, PB14 - MISO, PB15 - MOSI, PD4 - CS, 3.3v * default pinouts in case of SPI2 connected to MMC: PB13 - SCK, PB14 - MISO, PB15 - MOSI, PD4 - CS, 3.3v
* default pinouts in case of SPI3 connected to MMC: PB3 - SCK, PB4 - MISO, PB5 - MOSI, PD4 - CS, 3.3v * default pinouts in case of SPI3 connected to MMC: PB3 - SCK, PB4 - MISO, PB5 - MOSI, PD4 - CS, 3.3v
* *
*/ */
#include "main.h" #include "main.h"
#if EFI_FILE_LOGGING || defined(__DOXYGEN__) #if EFI_FILE_LOGGING || defined(__DOXYGEN__)
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "mmc_card.h" #include "mmc_card.h"
#include "pin_repository.h" #include "pin_repository.h"
#include "ff.h" #include "ff.h"
#include "hardware.h" #include "hardware.h"
#include "engine_configuration.h"
#define PUSHPULLDELAY 500
extern board_configuration_s *boardConfiguration;
static THD_WORKING_AREA(tp_MMC_Monitor,UTILITY_THREAD_STACK_SIZE); // MMC monitor thread
#define PUSHPULLDELAY 500
/**
* MMC driver instance. static THD_WORKING_AREA(tp_MMC_Monitor,UTILITY_THREAD_STACK_SIZE); // MMC monitor thread
*/
MMCDriver MMCD1; /**
* MMC driver instance.
// Peripherial Clock 42MHz SPI2 SPI3 */
// Peripherial Clock 84MHz SPI1 SPI1 SPI2/3 MMCDriver MMCD1;
#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ
#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz // Peripherial Clock 42MHz SPI2 SPI3
#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz // Peripherial Clock 84MHz SPI1 SPI1 SPI2/3
#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz #define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ #define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz #define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz #define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz #define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz #define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz #define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz #define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz static SPIConfig hs_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN,
#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz SPI_BaudRatePrescaler_8 };
#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz static SPIConfig ls_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN,
#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz SPI_BaudRatePrescaler_256 };
#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz
static SPIConfig hs_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN, /* MMC/SD over SPI driver configuration.*/
SPI_BaudRatePrescaler_8 }; // don't forget check if STM32_SPI_USE_SPI2 defined and spi has init with correct GPIO in hardware.c
static SPIConfig ls_spicfg = { NULL, SPI_SD_MODULE_PORT, SPI_SD_MODULE_PIN, static MMCConfig mmccfg = { &MMC_CARD_SPI, &ls_spicfg, &hs_spicfg };
SPI_BaudRatePrescaler_256 };
static bool fs_ready = false;
/* MMC/SD over SPI driver configuration.*/
// don't forget check if STM32_SPI_USE_SPI2 defined and spi has init with correct GPIO in hardware.c #define PUSHPULLDELAY 500
static MMCConfig mmccfg = { &MMC_CARD_SPI, &ls_spicfg, &hs_spicfg };
/**
static bool fs_ready = false; * fatfs MMC/SPI
*/
#define PUSHPULLDELAY 500 static FATFS MMC_FS;
/** static Logging logger;
* fatfs MMC/SPI
*/ // print FAT error function
static FATFS MMC_FS; static void printError(char *str, FRESULT f_error) {
scheduleMsg(&logger, "FATfs Error \"%s\" %d", str, f_error);
static Logging logger; }
// print FAT error function static FIL FDLogFile;
static void printError(char *str, FRESULT f_error) {
scheduleMsg(&logger, "FATfs Error \"%s\" %d", str, f_error); static int totalLoggedBytes = 0;
}
static void printMmcPinout(void) {
static FIL FDLogFile; scheduleMsg(&logger, "MMC CS %s:%d", portname(SPI_SD_MODULE_PORT), SPI_SD_MODULE_PIN);
// todo: we need to figure out the right SPI pinout, not just SPI2
static int totalLoggedBytes = 0; // scheduleMsg(&logger, "MMC SCK %s:%d", portname(EFI_SPI2_SCK_PORT), EFI_SPI2_SCK_PIN);
// scheduleMsg(&logger, "MMC MISO %s:%d", portname(EFI_SPI2_MISO_PORT), EFI_SPI2_MISO_PIN);
static void printMmcPinout(void) { // scheduleMsg(&logger, "MMC MOSI %s:%d", portname(EFI_SPI2_MOSI_PORT), EFI_SPI2_MOSI_PIN);
scheduleMsg(&logger, "MMC CS %s:%d", portname(SPI_SD_MODULE_PORT), SPI_SD_MODULE_PIN); }
// todo: we need to figure out the right SPI pinout, not just SPI2
// scheduleMsg(&logger, "MMC SCK %s:%d", portname(EFI_SPI2_SCK_PORT), EFI_SPI2_SCK_PIN); static void sdStatistics(void) {
// scheduleMsg(&logger, "MMC MISO %s:%d", portname(EFI_SPI2_MISO_PORT), EFI_SPI2_MISO_PIN); printMmcPinout();
// scheduleMsg(&logger, "MMC MOSI %s:%d", portname(EFI_SPI2_MOSI_PORT), EFI_SPI2_MOSI_PIN); scheduleMsg(&logger, "fs_ready=%d totalLoggedBytes=%d", fs_ready, totalLoggedBytes);
} }
static void sdStatistics(void) { /**
printMmcPinout(); * @brief Create a new file with the specified name
scheduleMsg(&logger, "fs_ready=%d totalLoggedBytes=%d", fs_ready, totalLoggedBytes); *
} * This function saves the name of the file in a global variable
* so that we can later append to that file
/** */
* @brief Create a new file with the specified name static void createLogFile(void) {
* lockSpi(SPI_NONE);
* This function saves the name of the file in a global variable memset(&FDLogFile, 0, sizeof(FIL)); // clear the memory
* so that we can later append to that file FRESULT err = f_open(&FDLogFile, "rusefi.log", FA_OPEN_ALWAYS | FA_WRITE); // Create new file
*/ if (err != FR_OK && err != FR_EXIST) {
static void createLogFile(void) { unlockSpi();
lockSpi(SPI_NONE); printError("Card mounted...\r\nCan't create Log file, check your SD.\r\nFS mount failed", err); // else - show error
memset(&FDLogFile, 0, sizeof(FIL)); // clear the memory return;
FRESULT err = f_open(&FDLogFile, "rusefi.log", FA_OPEN_ALWAYS | FA_WRITE); // Create new file }
if (err != FR_OK && err != FR_EXIST) {
unlockSpi(); err = f_lseek(&FDLogFile, f_size(&FDLogFile)); // Move to end of the file to append data
printError("Card mounted...\r\nCan't create Log file, check your SD.\r\nFS mount failed", err); // else - show error if (err) {
return; unlockSpi();
} printError("Seek error", err);
return;
err = f_lseek(&FDLogFile, f_size(&FDLogFile)); // Move to end of the file to append data }
if (err) { f_sync(&FDLogFile);
unlockSpi(); fs_ready = true; // everything Ok
printError("Seek error", err); unlockSpi();
return; }
}
f_sync(&FDLogFile); static void ff_cmd_dir(char *path) {
fs_ready = true; // everything Ok DIR dir;
unlockSpi(); FILINFO fno;
} char *fn;
static void ff_cmd_dir(char *path) { if (!fs_ready) {
DIR dir; scheduleMsg(&logger, "Error: No File system is mounted");
FILINFO fno; return;
char *fn; }
if (!fs_ready) { FRESULT res = f_opendir(&dir, path);
scheduleMsg(&logger, "Error: No File system is mounted");
return; if (res != FR_OK) {
} scheduleMsg(&logger, "Error opening directory %s", path);
return;
FRESULT res = f_opendir(&dir, path); }
if (res != FR_OK) { int i = strlen(path);
scheduleMsg(&logger, "Error opening directory %s", path); for (;;) {
return; res = f_readdir(&dir, &fno);
} if (res != FR_OK || fno.fname[0] == 0)
break;
int i = strlen(path); if (fno.lfname[0] == '.')
for (;;) { continue;
res = f_readdir(&dir, &fno); fn = fno.lfname;
if (res != FR_OK || fno.fname[0] == 0) if (fno.fattrib & AM_DIR) {
break; // TODO: WHAT? WE ARE APPENDING FILE NAME TO PARAMETER??? WEIRD!!!
if (fno.lfname[0] == '.') path[i++] = '/';
continue; strcpy(&path[i], fn);
fn = fno.lfname; // res = ff_cmd_ls(path);
if (fno.fattrib & AM_DIR) { if (res != FR_OK)
// TODO: WHAT? WE ARE APPENDING FILE NAME TO PARAMETER??? WEIRD!!! break;
path[i++] = '/'; path[i] = 0;
strcpy(&path[i], fn); } else {
// res = ff_cmd_ls(path); scheduleMsg(&logger, "%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %-12s", (fno.fattrib & AM_DIR) ? 'D' : '-',
if (res != FR_OK) (fno.fattrib & AM_RDO) ? 'R' : '-', (fno.fattrib & AM_HID) ? 'H' : '-',
break; (fno.fattrib & AM_SYS) ? 'S' : '-', (fno.fattrib & AM_ARC) ? 'A' : '-', (fno.fdate >> 9) + 1980,
path[i] = 0; (fno.fdate >> 5) & 15, fno.fdate & 31, (fno.ftime >> 11), (fno.ftime >> 5) & 63, fno.fsize,
} else { fno.fname);
scheduleMsg(&logger, "%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %-12s", (fno.fattrib & AM_DIR) ? 'D' : '-', }
(fno.fattrib & AM_RDO) ? 'R' : '-', (fno.fattrib & AM_HID) ? 'H' : '-', }
(fno.fattrib & AM_SYS) ? 'S' : '-', (fno.fattrib & AM_ARC) ? 'A' : '-', (fno.fdate >> 9) + 1980, }
(fno.fdate >> 5) & 15, fno.fdate & 31, (fno.ftime >> 11), (fno.ftime >> 5) & 63, fno.fsize,
fno.fname); static int errorReported = FALSE; // this is used to report the error only once
}
} /**
} * @brief Appends specified line to the current log file
*/
static int errorReported = FALSE; // this is used to report the error only once void appendToLog(char *line) {
UINT bytesWrited;
/**
* @brief Appends specified line to the current log file if (!fs_ready) {
*/ if (!errorReported)
void appendToLog(char *line) { scheduleMsg(&logger, "appendToLog Error: No File system is mounted");
UINT bytesWrited; errorReported = TRUE;
return;
if (!fs_ready) { }
if (!errorReported) UINT lineLength = strlen(line);
scheduleMsg(&logger, "appendToLog Error: No File system is mounted"); totalLoggedBytes += lineLength;
errorReported = TRUE; lockSpi(SPI_NONE);
return; FRESULT err = f_write(&FDLogFile, line, lineLength, &bytesWrited);
} if (bytesWrited < lineLength) {
UINT lineLength = strlen(line); printError("write error or disk full", err); // error or disk full
totalLoggedBytes += lineLength; }
lockSpi(SPI_NONE); f_sync(&FDLogFile);
FRESULT err = f_write(&FDLogFile, line, lineLength, &bytesWrited); unlockSpi();
if (bytesWrited < lineLength) { }
printError("write error or disk full", err); // error or disk full
} /*
f_sync(&FDLogFile); * MMC card umount.
unlockSpi(); */
} static void MMCumount(void) {
if (!fs_ready) {
/* scheduleMsg(&logger, "Error: No File system is mounted. \"mountsd\" first");
* MMC card umount. return;
*/ }
static void MMCumount(void) { f_close(&FDLogFile); // close file
if (!fs_ready) { f_sync(&FDLogFile); // sync ALL
scheduleMsg(&logger, "Error: No File system is mounted. \"mountsd\" first"); mmcDisconnect(&MMCD1); // Brings the driver in a state safe for card removal.
return; mmcStop(&MMCD1); // Disables the MMC peripheral.
} f_mount(0, NULL); // FATFS: Unregister work area prior to discard it
f_close(&FDLogFile); // close file memset(&FDLogFile, 0, sizeof(FIL)); // clear FDLogFile
f_sync(&FDLogFile); // sync ALL fs_ready = false; // status = false
mmcDisconnect(&MMCD1); // Brings the driver in a state safe for card removal. scheduleMsg(&logger, "MMC/SD card removed");
mmcStop(&MMCD1); // Disables the MMC peripheral. }
f_mount(0, NULL); // FATFS: Unregister work area prior to discard it
memset(&FDLogFile, 0, sizeof(FIL)); // clear FDLogFile /*
fs_ready = false; // status = false * MMC card mount.
scheduleMsg(&logger, "MMC/SD card removed"); */
} static void MMCmount(void) {
// printMmcPinout();
/*
* MMC card mount. if (fs_ready) {
*/ scheduleMsg(&logger, "Error: Already mounted. \"umountsd\" first");
static void MMCmount(void) { return;
// printMmcPinout(); }
// start to initialize MMC/SD
if (fs_ready) { mmcObjectInit(&MMCD1); // Initializes an instance.
scheduleMsg(&logger, "Error: Already mounted. \"umountsd\" first"); mmcStart(&MMCD1, &mmccfg); // Configures and activates the MMC peripheral.
return;
} // Performs the initialization procedure on the inserted card.
// start to initialize MMC/SD lockSpi(SPI_NONE);
mmcObjectInit(&MMCD1); // Initializes an instance. if (mmcConnect(&MMCD1) != CH_SUCCESS) {
mmcStart(&MMCD1, &mmccfg); // Configures and activates the MMC peripheral. warning(OBD_PCM_Processor_Fault, "Can't connect or mount MMC/SD");
unlockSpi();
// Performs the initialization procedure on the inserted card. return;
lockSpi(SPI_NONE);
if (mmcConnect(&MMCD1) != CH_SUCCESS) { }
warning(OBD_PCM_Processor_Fault, "Can't connect or mount MMC/SD"); unlockSpi();
unlockSpi(); // if Ok - mount FS now
return; memset(&MMC_FS, 0, sizeof(FATFS)); // reserve the memory
if (f_mount(0, &MMC_FS) == FR_OK) {
} createLogFile();
unlockSpi(); scheduleMsg(&logger, "MMC/SD mounted!\r\nDon't forget umountsd before remove to prevent lost your data");
// if Ok - mount FS now }
memset(&MMC_FS, 0, sizeof(FATFS)); // reserve the memory }
if (f_mount(0, &MMC_FS) == FR_OK) {
createLogFile(); #if defined __GNUC__
scheduleMsg(&logger, "MMC/SD mounted!\r\nDon't forget umountsd before remove to prevent lost your data"); __attribute__((noreturn)) static msg_t MMCmonThread(void)
} #else
} static msg_t MMCmonThread(void)
#endif
#if defined __GNUC__ {
__attribute__((noreturn)) static msg_t MMCmonThread(void) chRegSetThreadName("MMC_Monitor");
#else
static msg_t MMCmonThread(void) while (true) {
#endif // this returns TRUE if SD module is there, even without an SD card?
{ if (blkIsInserted(&MMCD1)) {
chRegSetThreadName("MMC_Monitor");
if (!fs_ready) {
while (true) { MMCmount();
// this returns TRUE if SD module is there, even without an SD card? }
if (blkIsInserted(&MMCD1)) { }
if (!fs_ready) { // this thread is activated 10 times per second
MMCmount(); chThdSleepMilliseconds(PUSHPULLDELAY);
} }
} }
// this thread is activated 10 times per second bool isSdCardAlive(void) {
chThdSleepMilliseconds(PUSHPULLDELAY); return fs_ready;
} }
}
void initMmcCard(void) {
bool isSdCardAlive(void) { initLogging(&logger, "mmcCard");
return fs_ready; if (!boardConfiguration->isSdCardEnabled) {
} return;
}
void initMmcCard(void) {
initLogging(&logger, "mmcCard"); /**
* FYI: SPI does not work with CCM memory, be sure to have main() stack in RAM, not in CCMRAM
/** */
* FYI: SPI does not work with CCM memory, be sure to have main() stack in RAM, not in CCMRAM
*/ // start to initialize MMC/SD
mmcObjectInit(&MMCD1);
// start to initialize MMC/SD mmcStart(&MMCD1, &mmccfg);
mmcObjectInit(&MMCD1);
mmcStart(&MMCD1, &mmccfg); chThdCreateStatic(tp_MMC_Monitor, sizeof(tp_MMC_Monitor), LOWPRIO, (tfunc_t) MMCmonThread, NULL);
chThdCreateStatic(tp_MMC_Monitor, sizeof(tp_MMC_Monitor), LOWPRIO, (tfunc_t) MMCmonThread, NULL); addConsoleAction("sdstat", sdStatistics);
addConsoleAction("mountsd", MMCmount);
addConsoleAction("sdstat", sdStatistics); addConsoleActionS("appendToLog", appendToLog);
addConsoleAction("mountsd", MMCmount); addConsoleAction("umountsd", MMCumount);
addConsoleActionS("appendToLog", appendToLog); addConsoleActionS("ls", ff_cmd_dir);
addConsoleAction("umountsd", MMCumount); }
addConsoleActionS("ls", ff_cmd_dir);
} #endif /* EFI_FILE_LOGGING */
#endif /* EFI_FILE_LOGGING */

View File

@ -63,11 +63,13 @@ public class EcuStimulator {
private final JPanel content = new JPanel(new BorderLayout()); private final JPanel content = new JPanel(new BorderLayout());
private JPanel panel = ChartHelper.create3DControl(data, model, TITLE);
private static EcuStimulator instance = new EcuStimulator(); private static EcuStimulator instance = new EcuStimulator();
private final JLabel statusLabel = new JLabel();
private EcuStimulator() { private EcuStimulator() {
JPanel panel = ChartHelper.create3DControl(data, model, TITLE);
content.add(statusLabel, BorderLayout.NORTH);
content.add(panel, BorderLayout.CENTER); content.add(panel, BorderLayout.CENTER);
content.add(inputs.getContent(), BorderLayout.WEST); content.add(inputs.getContent(), BorderLayout.WEST);
} }
@ -143,6 +145,8 @@ public class EcuStimulator {
*/ */
sleepRuntime(SLEEP_TIME); sleepRuntime(SLEEP_TIME);
statusLabel.setText("RPM " + rpm + ", el " + engineLoad);
/** /**
* We are making a number of measurements and then we take the middle one * We are making a number of measurements and then we take the middle one
*/ */

View File

@ -52,7 +52,7 @@ public class StimulationInputs {
} }
public double getEngineLoadR2Resistance() { public double getEngineLoadR2Resistance() {
return (double) elResistance2.getValue(); return (Integer) elResistance2.getValue();
} }
} }

View File

@ -0,0 +1,21 @@
package com.rusefi.test;
import com.rusefi.StimulationInputs;
import com.rusefi.ui.widgets.PotCommand;
import junit.framework.TestCase;
import org.junit.Test;
public class VoltageDividerTest extends TestCase {
@Test
public void testR1() {
assertEquals(2000.0, PotCommand.getR1InVoltageDivider3(1, 5, 10000));
assertEquals(2040.816326530612, PotCommand.getR1InVoltageDivider3(1, 4.9, 10000));
}
}

View File

@ -26,6 +26,7 @@ public class ChartHelper {
public static JPanel create3DControl(final XYData data, SurfaceModel surfaceModel, String title) { public static JPanel create3DControl(final XYData data, SurfaceModel surfaceModel, String title) {
JPanel result = new JPanel(new BorderLayout()); JPanel result = new JPanel(new BorderLayout());
result.setBorder(BorderFactory.createLineBorder(Color.red));
final JSurfacePanel jsp = new JSurfacePanel(surfaceModel); final JSurfacePanel jsp = new JSurfacePanel(surfaceModel);
jsp.setTitleText(title); jsp.setTitleText(title);

View File

@ -83,11 +83,11 @@ public class PotCommand {
public static void requestPotChange(int channel, int resistance) { public static void requestPotChange(int channel, int resistance) {
if (resistance < 0 || resistance > 10000) if (resistance < 0 || resistance > 10000)
throw new IllegalArgumentException("resistance: " + resistance); throw new IllegalArgumentException("resistance: " + resistance);
CommandQueue.getInstance().write("pot" + channel + " " + resistance); CommandQueue.getInstance().write("pot " + channel + " " + resistance);
} }
public static int getPotResistance(Double vout, double vRef) { public static int getPotResistance(double vout, double vRef) {
double r = getR1InVoltageDividor3(vout, vRef, EcuStimulator.getInstance().getInputs().getEngineLoadR2Resistance()); double r = getR1InVoltageDivider3(vout, vRef, EcuStimulator.getInstance().getInputs().getEngineLoadR2Resistance());
MessagesCentral.getInstance().postMessage(PotCommand.class, "VRef=" + vRef + ", needed resistance: " + r); MessagesCentral.getInstance().postMessage(PotCommand.class, "VRef=" + vRef + ", needed resistance: " + r);
// pot command accept resistance and does the conversion itself // pot command accept resistance and does the conversion itself
return (int) r; return (int) r;
@ -101,8 +101,8 @@ public class PotCommand {
// return (int) (256 - (Rwa - 52) * 256 / 10000); // return (int) (256 - (Rwa - 52) * 256 / 10000);
// } // }
private static double getR1InVoltageDividor3(double Vout, double Vin, double r2) { public static double getR1InVoltageDivider3(double Vout, double Vin, double r2) {
return r2 * Vin / Vout - r2; return r2 * Vout / Vin;
} }
} }