diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 8b4c31082..1de1bc4c6 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -94,6 +94,11 @@ jobs: additional-sketch-paths: | - libraries/WiFiS3 - libraries/OTAUpdate + - libraries/OPAMP + - board: + fqbn: "arduino-git:renesas:minima" + additional-sketch-paths: | + - libraries/OPAMP steps: - name: Checkout repository diff --git a/libraries/OPAMP/README.md b/libraries/OPAMP/README.md new file mode 100644 index 000000000..1c98055ca --- /dev/null +++ b/libraries/OPAMP/README.md @@ -0,0 +1,64 @@ +# OPAMP Libray for Uno R4 Minima & WiFi + +## Description + +The Arduino UNO R4 Minima and WiFi boards, both a featuring a Renesas R7FA4M1AB3CFM#AA0 microcontroller, do have a built-in OPAMP peripheral. + +Operational amplifiers (or "op amp") are very versatile. They can: +* mirror an input voltage to its output ("voltage follower") +* amplify a small analog voltage to its output pin, output voltage range from 0 to ~4.7 V ("non-inverting amplifier") +* compare two input voltages and give a binary "higher" or "lower" output ("comparator") +* integrate and differentiate signals ("integrator", "differentiator") +* many more + +Electrical characteristics: +* Input from 0.2 V (low speed) / 0. 3V (highspeed) to AVCC0 - 0.5 V (lowspeed) to AVCC0 - 0.6 V (high-speed) +* Output from 0.1 V to AVCC0 - 0.1 V +* Open gain: 120 dB typical +* Input offset voltage: -10 to 10 mV +* Gain-bandwidth product: 0.04 MHz (low-speed) / 1.7 MHz (high-speed) +* Load current: -100 to 100 µA max. + +## Usage + +To startup the opamp, simply include the library and call `OPAMP.begin(speed)`. As the optional `speed` argument to this function, can chose either `OPAMP_SPEED_LOWSPEED` as the low-speed (=lower power) mode or `OPAMP_SPEED_HIGHSPEED` as the high-speed, high-power mode. + +```cpp +#include + +void setup () { + OPAMP.begin(OPAMP_SPEED_HIGHSPEED); +} + +void loop() {} +``` + +## Pinout + +Both the Uno R4 Minima and WiFi feature their OPAMP channel 0 output on the same pins: +* Analog A1: Plus +* Analog A2: Minus +* Analog A3: Output + +

+ +

+ +(Vs+ is fixed to about 5V, Vs- is fixed to GND.) +## Testing + +To test the OPAMP in the simplest possible "voltage follower" configuration, connect A2 to A3. +Then, any voltage applied at A1 should be mirrored onto A3. For example, if you connect A1 to GND, the OPAMP output should be GND. +Connect A1 to 3.3V, the output should be 3.3V. + +For an amplifier circuit, see https://www.electronics-tutorials.ws/opamp/opamp_3.html. A simple 2x amplifier can be built by using e.g. two 10K resistors: Connect one resistor between "Minus" and GND. Then use the second resistor to connect the output and "Minus" together. Any signal input at the "Plus" will now appear with double the amplitude at the output pin. Of course, the input signal and the Arduino Uno R4 should share the same GND and the amplified output signal should not go above ~4.7V, otherwise clipping will appear. + +Below is a capture of an oscilloscope in which a circa 2V square-wave (green, channel 2) is amplified to 4V square-wave (yellow, channel 1) with the aforementioned circuit. The input signal was generated by a function generator (and shared GND was connected). + +

+ +

+ +

+ +

diff --git a/libraries/OPAMP/amp_circuit.png b/libraries/OPAMP/amp_circuit.png new file mode 100644 index 000000000..c2c08c71c Binary files /dev/null and b/libraries/OPAMP/amp_circuit.png differ diff --git a/libraries/OPAMP/amp_screenshot.png b/libraries/OPAMP/amp_screenshot.png new file mode 100644 index 000000000..e056e7408 Binary files /dev/null and b/libraries/OPAMP/amp_screenshot.png differ diff --git a/libraries/OPAMP/amp_symbol.png b/libraries/OPAMP/amp_symbol.png new file mode 100644 index 000000000..9bea7373a Binary files /dev/null and b/libraries/OPAMP/amp_symbol.png differ diff --git a/libraries/OPAMP/examples/start_opamp/start_opamp.ino b/libraries/OPAMP/examples/start_opamp/start_opamp.ino new file mode 100644 index 000000000..57d5bd8e0 --- /dev/null +++ b/libraries/OPAMP/examples/start_opamp/start_opamp.ino @@ -0,0 +1,23 @@ +#include + +void setup () { + Serial.begin(9600); + delay(2000); // serial monitor delay + // activate OPAMP, default channel 0 + // Plus: Analog A1 + // Minus: Analog A2 + // Output: Analog A3 + if (!OPAMP.begin(OPAMP_SPEED_HIGHSPEED)) { + Serial.println("Failed to start OPAMP!"); + } + bool const isRunning = OPAMP.isRunning(0); + if (isRunning) { + Serial.println("OPAMP running on channel 0!"); + } else { + Serial.println("OPAMP channel 0 is not running!"); + } +} + +void loop() { + delay(1000); // do nothing +} diff --git a/libraries/OPAMP/library.properties b/libraries/OPAMP/library.properties new file mode 100644 index 000000000..3a67163ab --- /dev/null +++ b/libraries/OPAMP/library.properties @@ -0,0 +1,10 @@ +name=OPAMP +version=1.0.0 +author=Maximilian Gerhardt +maintainer=Maximilian Gerhardt +sentence=Library for using the OPAMP on Arduino UNO R4 boards. +paragraph=Supports OPAMP on Arduino UNO R4 (Minima, WiFi). +category=Signal Input/Output +url=https://github.com/arduino/ArduinoCore-renesas/tree/main/libraries/OPAMP +architectures=renesas,renesas_uno +includes=OPAMP.h diff --git a/libraries/OPAMP/src/OPAMP.cpp b/libraries/OPAMP/src/OPAMP.cpp new file mode 100644 index 000000000..b55ba9248 --- /dev/null +++ b/libraries/OPAMP/src/OPAMP.cpp @@ -0,0 +1,113 @@ +#include "OPAMP.h" +#include + +/* Make sure this library fails to compile for unsupported boards. */ +#if !defined(ARDUINO_UNOWIFIR4) && !defined(ARDUINO_MINIMA) +#error "Unsupported board for OPAMP library." +#endif + +/* pin mode needed to activate OPAMP functionality */ +#define OPAMP_IN_PINCFG (IOPORT_CFG_PORT_DIRECTION_INPUT | IOPORT_CFG_PERIPHERAL_PIN | IOPORT_CFG_ANALOG_ENABLE) +#define OPAMP_OUT_PINCFG (IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PERIPHERAL_PIN | IOPORT_CFG_ANALOG_ENABLE) +#define FSP_CHECK(err) do { if( (err) != FSP_SUCCESS) return false; } while(0) + +// Compact structure for OPAMP channel pins +struct opamp_channel_pins_t { + bsp_io_port_pin_t plus; + bsp_io_port_pin_t minus; + bsp_io_port_pin_t output; +}; + +// See Renesas RA4M1 Group Datasheet +// Note: Channel 0 is the only accessible one one the Arduino Minima boards. +static const opamp_channel_pins_t opamp_channels[] = { + {BSP_IO_PORT_00_PIN_00, BSP_IO_PORT_00_PIN_01, BSP_IO_PORT_00_PIN_02}, /* CH0 */ + {BSP_IO_PORT_00_PIN_13, BSP_IO_PORT_00_PIN_12, BSP_IO_PORT_00_PIN_03}, /* CH1 */ + {BSP_IO_PORT_00_PIN_11, BSP_IO_PORT_00_PIN_10, BSP_IO_PORT_00_PIN_04}, /* CH2 */ + {BSP_IO_PORT_00_PIN_05, BSP_IO_PORT_00_PIN_06, BSP_IO_PORT_00_PIN_07}, /* CH3 */ +}; + +bool OpampClass::initPins(uint8_t const channel_mask) { + fsp_err_t err; + ioport_instance_ctrl_t ioport_ctrl {}; + // Make sure to return false if nothing was given to initialize + // or a too high channel bit is in there + if (channel_mask == 0 || channel_mask > 0b1111) { + return false; + } + // Check the 4 possible channels + for (uint8_t i = 0; i < 4; i++) { + // was this channel selected? + if (!(channel_mask & (1u << i))) { + continue; + } + opamp_channel_pins_t pins = opamp_channels[i]; + err = R_IOPORT_PinCfg(&ioport_ctrl, pins.plus, OPAMP_IN_PINCFG); + FSP_CHECK(err); + err = R_IOPORT_PinCfg(&ioport_ctrl, pins.minus, OPAMP_IN_PINCFG); + FSP_CHECK(err); + err = R_IOPORT_PinCfg(&ioport_ctrl, pins.output, OPAMP_OUT_PINCFG); + FSP_CHECK(err); + } + // if we got here, none of the checks triggered an early return. + return true; +} + +void OpampClass::initOpamp(OpampSpeedMode speed, uint8_t const channel_mask) { + uint8_t ampmc_val = 0U; + /* setup amplifier speed mode within amplifier mode control */ + /* for all boards, this is at bit position 7 with either 0 (lowspeed) or 1 (highspeed) */ + ampmc_val = (uint8_t) ((uint8_t) speed << R_OPAMP_AMPMC_AMPSP_Pos) & R_OPAMP_AMPMC_AMPSP_Msk; + /* reset opamp */ + R_OPAMP->AMPC = 0U; + /* write prepared mode control value */ + R_OPAMP->AMPMC = ampmc_val; + /* setup activation trigger select register */ + /* we only support "Software start & stop" for now, value 0. */ + R_OPAMP->AMPTRS = 0; + R_OPAMP->AMPTRM = 0; + /* set the bits for the activated channels */ + R_OPAMP->AMPC |= channel_mask; + /* note: we don't have to activate the charge pump (AMPCPC) because here AVCC0 > 2.7V */ + /* delay for the wanted init time in microseconds */ + if (speed == OPAMP_SPEED_LOWSPEED) { + delayMicroseconds(BSP_FEATURE_OPAMP_MIN_WAIT_TIME_LP_US); + } else if (speed == OPAMP_SPEED_HIGHSPEED) { + delayMicroseconds(BSP_FEATURE_OPAMP_MIN_WAIT_TIME_HS_US); + } +} + +bool OpampClass::begin() { + return this->begin(OPAMP_SPEED_HIGHSPEED); +} + +bool OpampClass::begin(OpampSpeedMode const speed) { + + return this->begin(1u << ARDUINO_UNO_R4_DEFAULT_OPAMP_CHANNEL, speed); +} + +bool OpampClass::begin(uint8_t const channel_mask, OpampSpeedMode const speed) { + + if (!initPins(channel_mask)) { + return false; + } + initOpamp(speed, channel_mask); + return true; +} + +bool OpampClass::isRunning(uint8_t const channel) { + return (R_OPAMP->AMPMON & (1u << channel)) != 0; +} + +void OpampClass::end() { + // deactivate all channels. + R_OPAMP->AMPC = 0; +} + +void OpampClass::end(uint8_t const channel_mask) { + // deactivate given channels + R_OPAMP->AMPC &= ~channel_mask; +} + +/* global instance */ +OpampClass OPAMP; diff --git a/libraries/OPAMP/src/OPAMP.h b/libraries/OPAMP/src/OPAMP.h new file mode 100644 index 000000000..c6e63539d --- /dev/null +++ b/libraries/OPAMP/src/OPAMP.h @@ -0,0 +1,57 @@ +#ifndef _OPAMP_H_ +#define _OPAMP_H_ + +#include + +/* Available speed modes */ +/* Note: No middle speed mode on the Uno R4 boards */ +enum OpampSpeedMode { + OPAMP_SPEED_LOWSPEED = 0, + OPAMP_SPEED_HIGHSPEED = 1, +}; + +/* The supported boards have 4 OPAMP channels, however, only channel 0 is accessible. */ +/* All other channels are connected to the LED matrix or not exposed. */ +#define ARDUINO_UNO_R4_DEFAULT_OPAMP_CHANNEL 0 + +/** + * Pin Mapping for OPAMP + * Arduino UNO R4 (Minima, WiFi): + * ~Channel 0~ + * Plus: Analog A1 (Renesas P0.00) + * Minus: Analog A2 (Renesas P0.01) + * Output: Analog A3 (Renesas P0.02) + * ~Channel 1~ (Inaccessible) + * +: P0.13, -: Renesas P0.12, Out: Renesas P0.03 + * ~Channel 2~ (Inaccessible) + * +: P0.11, -: Renesas P0.10, Out: Renesas P0.04 + * ~Channel 3~ (Inaccessible) + * +: P0.05, -: Renesas P0.06, Out: Renesas P0.07 +*/ +class OpampClass { +public: + /* startup the OPAMP on channel 0 in high-speed mode */ + bool begin(); + /* startup the OPAMP on channel 0 with specific mode */ + bool begin(OpampSpeedMode const speed); + + /* startup the OPAMP with arbitrary channel mask */ + bool begin(uint8_t const channel_mask, OpampSpeedMode const speed); + + /* stop all OPAMP channels */ + void end(); + /* stop specific OPAMP channel(s) */ + void end(uint8_t const channel_mask); + /* returns true if the specified OPAMP channel number is running */ + bool isRunning(uint8_t const channel); +private: + /* initializes OPAMP pins for given channel(s) */ + bool initPins(uint8_t const channel_mask); + /* activates OPAMP for given speed and channel(s) */ + void initOpamp(OpampSpeedMode const speed, uint8_t const channel_mask); + +}; + +extern OpampClass OPAMP; + +#endif /* _OPAMP_H_ */