I wrote some code. You should not use it. Well, maybe you can, but I sort of hacked this together quickly to see if you can do an interrupt-based level trigger on an analog pin. The answer is, yes, I think you can, using the STM32F2xx Analog Watchdog feature.
But you can really only have one because there is only one extra ADC. And it only works on pins A1, A2, and A7 because those are the only ones connected to ADC3. But it seems to work. You can have it trigger on RISING, FALLING, or CHANGE, on any value along with a hysteresis amount.
It’s kind of cool; I was surprised I could get it to work. I tested it on unmodified system firmware 0.5.1 and it seems to work for me, but there’s no guarantee it will work on future system versions and certainly not future hardware!
#include "Particle.h"
// Additional includes for hardware access
#include "adc_hal.h"
#include "gpio_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"
// STM32F2xx Analog Watchdog Example
//
// Basically, this works sort of like a regular attachInterrupt digital pin interrupt, except it
// works with analog signals!
//
// You can only use one of these because there is only one extra ADC to run this on, ADC3.
//
// Also, it can only use pins A1, A2, or A7 on the Photon because those are the only pins that can be used with ADC3.
//
// It works for RISING, FALLING, or CHANGE, depending on the mode value that you set.
//
// Note that there are also two values: value and hysteresis. The latter is necessary because the analog signal rarely
// goes cleanly from one state to another, so the hysteresis value prevents the signal from triggering multiple times
// when transitioning states. The default value is 75.
class AnalogInterrupt {
public:
AnalogInterrupt(int pin, InterruptMode mode, raw_interrupt_handler_t handler, int value, int hysteresis = 75);
virtual ~AnalogInterrupt();
void start();
void setValue(int value);
private:
void updateLimits();
void interruptHandler();
static void interruptHandlerStatic();
int pin;
InterruptMode mode;
raw_interrupt_handler_t handler;
int value;
uint8_t channel = 0;
bool wasBelow;
int hysteresis;
// We store this so the system interrupt handler can find the object. This means there can only be
// one of these objects, but that's a reasonable assumption because there is only one ADC3 in hardware,
// so you can't have more than one of these, anyway.
static AnalogInterrupt *instance;
};
// Allocation of the static class member, essentially a global variable
AnalogInterrupt *AnalogInterrupt::instance;
AnalogInterrupt::AnalogInterrupt(int pin, InterruptMode mode, raw_interrupt_handler_t handler, int value, int hysteresis) :
pin(pin), mode(mode), handler(handler), value(value), hysteresis(hysteresis) {
// These are the pin to channel assigments for the Photon:
// A0 = ADC_Channel_15 = PC5 = ADC12_IN15
// A1 = ADC_Channel_13 = PC3 = ADC123_IN13
// A2 = ADC_Channel_12 = PC2 = ADC123_IN12
// A3 = ADC_Channel_5 = PA5 = ADC12_IN5
// A4 = ADC_Channel_6 = PA6 = ADC12_IN6
// A5 = ADC_Channel_7 = PA7 = ADC12_IN7
// A6 = ADC_Channel_4 = PA4 = ADC12_IN4, DAC_OUT1
// A7 = ADC_Channel_0 = PA0 = ADC123_IN0, WKUP
// Since we use ADC3 so as to not conflict with normal ADC operations on ADC1 and ADC2, this
// restricts the pins we can monitor to A1, A2 and A7 (WKP).
switch(pin) {
case A1:
channel = ADC_Channel_13;
break;
case A2:
channel = ADC_Channel_12;
break;
case A7:
channel = ADC_Channel_0;
break;
}
instance = this;
}
AnalogInterrupt::~AnalogInterrupt() {
}
void AnalogInterrupt::start() {
ADC_InitTypeDef ADC_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// Add the system interrupt handler
attachSystemInterrupt(SysInterrupt_ADC_IRQ, interruptHandlerStatic);
// Set the pin to analog input
pinMode(pin, AN_INPUT);
wasBelow = (analogRead(pin) < value);
// Useful example code
// https://github.com/bjornfor/stm32-test/tree/master/STM32L1xx_StdPeriph_Lib_V1.1.1/Project/STM32L1xx_StdPeriph_Examples/ADC/ADC1_AnalogWatchdog
// Enable the HSI
RCC_HSICmd(ENABLE);
// Wait until HSI oscillator is ready
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET) {
}
//This needs to change if using an ADC other than ADC3
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
// ADC Configuration
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC3, &ADC_InitStructure);
// Typically ADC3 = ADC3 and ADC3_CHANNEL = ADC_Channel_13 (pin A1)
ADC_RegularChannelConfig(ADC3, channel, 1, ADC_SampleTime_15Cycles);
// Configure high and low analog watchdog thresholds
updateLimits();
// Configure channel31 as the single analog watchdog guarded channel
ADC_AnalogWatchdogSingleChannelConfig(ADC3, channel);
// Enable analog watchdog on one regular channel
ADC_AnalogWatchdogCmd(ADC3, ADC_AnalogWatchdog_SingleRegEnable);
// There's one interrupt for ADC1-3, so it's not necessary to separate that out based on ADC number
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Enable AWD interrupt
ADC_ITConfig(ADC3, ADC_IT_AWD, ENABLE);
// Enable ADC
ADC_Cmd(ADC3, ENABLE);
// Sample code for the STM32F1xx checks the ADC_FLAG_ADONS here for ready, but I don't
// this that's necessary on the STM32F2xx
// Start ADC Software Conversion
ADC_SoftwareStartConv(ADC3);
}
void AnalogInterrupt::setValue(int value) {
this->value = value;
wasBelow = (analogRead(pin) < value);
updateLimits();
}
void AnalogInterrupt::updateLimits() {
if (wasBelow) {
ADC_AnalogWatchdogThresholdsConfig(ADC3, value + hysteresis, 0);
}
else {
ADC_AnalogWatchdogThresholdsConfig(ADC3, 4095, value - hysteresis);
}
}
// [static]
void AnalogInterrupt::interruptHandlerStatic() {
instance->interruptHandler();
}
void AnalogInterrupt::interruptHandler() {
if (ADC_GetITStatus(ADC3, ADC_IT_AWD) != RESET) {
// AWD (Analog WatchDog) Interrupt
if (wasBelow) {
// Not below anymore
if (mode == RISING || mode == CHANGE) {
handler();
}
wasBelow = false;
}
else {
// Not above anymore
if (mode == FALLING || mode == CHANGE) {
handler();
}
wasBelow = true;
}
updateLimits();
// Clear ADC AWD pending interrupt bit
ADC_ClearITPendingBit(ADC3, ADC_IT_AWD);
}
}
void testInterruptHandler(); // forward declaration
// Note: You can only use A1, A2, or A7!
AnalogInterrupt analogInterrupt(A1, RISING, testInterruptHandler, 1000);
volatile unsigned long interruptTime = 0;
void setup() {
Serial.begin(9600);
analogInterrupt.start();
}
void loop() {
if (interruptTime != 0) {
Serial.printlnf("interrupt at %lu", interruptTime);
interruptTime = 0;
}
// This doesn't do anything useful; it just here for testing purposes
// to make sure the analog watchdog doesn't break normal analogRead
// and vice-versa.
analogRead(A0);
}
void testInterruptHandler() {
// This function is called at interrupt time. Make sure you don't put any long-running
// operations, allocate memory including using malloc or new, use String, any of the
// Particle cloud features like publish, or the Serial port here!
interruptTime = millis();
}