As I'm looking for a way to decode my specific manchester protocol, I searched for a suitable way to detect rising and falling edges which then create an interrupt to determine the exact pulse width.
To do so, I used the code at the end of my post.
My problem now is that the arduino shuts down and stops communication with my serial screen after edges other than perfect "HIGH" and "LOW" appear (code works perfectly fine with a simple switch). Those edges are created by an LM339 comparator which can be adjusted via poti and a fixed resistor of 11k. As it seems that my sensor is not sending out data right now (this problem I will solve later), I started changing the values of the poti to get above or under the V_ref --> at this point my arduino always stops working after detecting some edge changes.
To me it looks like the Board detects too many fast changes between HIGH and LOW and thus it shuts down. (Due to the noise in reststor value during movement of the poti)
My only suggestion why this happens:
Could this be possible if the ISR execution needs too much time so the next ISR which is already triggered collides with the actual running ISR? As far as I understood, ISRs are executed completeley and then the next pending ISR is computed and so on?
Here ist my code so far:
// Idea from: https://forum.arduino.cc/t/analog-rising-and-falling-edge-detect-and-interrupt-on-arduino-due/640089/9
volatile boolean Look_for_Rising = true; // true is looking for rising/false is looking for falling
void setup() {
Serial.begin(115200);
adc_setup();
pinMode(LED_BUILTIN, OUTPUT); // to check wether the Arduino is still running
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
void adc_setup ()
{
PMC->PMC_PCER1 |= PMC_PCER1_PID37; // ADC power ON
ADC->ADC_WPMR = 0x41444300; // Disable write protect
ADC->ADC_CR = ADC_CR_SWRST; // Reset ADC
ADC->ADC_MR |= ADC_MR_FREERUN_ON // ADC in free running mode
| ADC_MR_LOWRES_BITS_12 // 12 bits resolution
| ADC_MR_PRESCAL(1)
| ADC_MR_SETTLING_AST3
| ADC_MR_TRACKTIM(10)
| ADC_MR_TRANSFER(2);
ADC->ADC_EMR = ADC_EMR_CMPMODE_OUT // Generates an event when the converted data is out of the comparison window.
| ADC_EMR_CMPSEL(0) // Compare channel 0 = A7
| ADC_EMR_CMPFILTER(0); // Number of consecutive compare events necessary
// to raise the flag = CMPFILTER+1
ADC->ADC_CWR = ADC_CWR_LOWTHRES(0) | ADC_CWR_HIGHTHRES(2047); // at beginning, looking for rising edge
ADC->ADC_CHER = ADC_CHER_CH0; // Enable Channel 0 = A7
ADC->ADC_IER = ADC_IER_COMPE; // Interrupt on Compare match enable
NVIC_EnableIRQ(ADC_IRQn); // Enable ADC interrupt
}
void ADC_Handler() {
// print what kind of interrupt is this
// 1=found Rising edge; 0=found Falling edge
//Serial.print("Looking for rising (1) or falling(0): ");
if (Look_for_Rising == false) {
Serial.print("now looking for falling (0)! ");
}
if (Look_for_Rising == true) {
Serial.print("now looking for rising (1)! ");
}
Serial.println(Look_for_Rising);
Look_for_Rising = !Look_for_Rising; // invert what to looking for
if (!Look_for_Rising) {
ADC->ADC_CWR = ADC_CWR_LOWTHRES(2048) | ADC_CWR_HIGHTHRES(4095); // looking for falling
}
if (Look_for_Rising) {
ADC->ADC_CWR = ADC_CWR_LOWTHRES(0) | ADC_CWR_HIGHTHRES(2047); // looking for raising
}
ADC->ADC_ISR; // Read and clear status register
}
Informations about the protocol:
pulse width: 50us
period: periodically sent every 150ms; alternativeley the protocol can be triggered by an event, then it will also be sent before the next 150ms
one protocol contains
one initial pulse with a width of 50us (14 or 28mA, depending on the triggering event)
25us (t_p/2) LOW
9 data bits
LOW: 7mA --> 0.35V on measurement resistor
HIGH: 14mA --> 0.7V on measurement resistor
Special event (only first pulse with 50us width): 28mA --> 1.4V on measurement resistor --> if the protocol is triggered additionally to the 150ms cycle, the first pulse is 28mA high
To get an idea of my layout, attached the schematic (thanks @Railroader for the hint):
Manchester is usually a way to code a serial set of signals, not a protocol.
Do You have an oscilloscope picture of the signals?
Posting schematics is highly requested. The Shakespeare type, named word sallad, is never a good way to give information.
If You think You need to help helpers, maybe You're the best guy to help Yourself?
So far I don't identify the declarations of the ISR's, how execution is directed to them. Don't ever use Serial.print inside an ISR. It crashes things.
This was only to give an idea on where I'm focusing at the moment. I wouldn't ask here if I could solve the problem by myself I think the better english word I shoul've used is 'guess' instead of suggestion. My bad.
As I understood, the line NVIC_EnableIRQ(ADC_IRQn); calls the ADC_handler function. Is that correct?
Very good hint, I'll thy to move the serial print to the main loop and see what happens then.
My idea is to use the interrupt to find the exact time when the rising or falling edge appears. This is needed to identify if the next edge is containing information because one full period passed or if the next edge is only for the level change so the next edge can be a falling or rising edge. The first pulse defines the pulse width of all the following 8 bits. I guess the picture will clarify my intention better:
Only needed if the data is really fast - significantly more than 1000 events per second. I queried it as you gave no description at all of what sort of data you were examining.
In any case, you generally cannot print out a continuous stream of descriptions in real time with the input - unless it is really slow. You usually need to collect some data for a short time, then give up collecting data and print the analysis. In which case, the data collection can generally be performed by polling.
That makes sense. As I use an Arduino Due, the limit is 3.3V. Nevertheless, my setup as I use a measurement resistor can only reach max. 1.4V so this shouldn't be a problem by now.
If I understand you correctly, you assume that I don't necessarily need an ISR - it would be enough to check the values of my measurement resistor within the main loop periodically (as fast as possible in the main loop, not using delays I guess) and then after finding a HIGH value, I measure for at least the duration it takes my protocol to be completed. Later on I interprete the values to get the contained information? Of course all values saved in a 2 dimensional array which also saves the timestamp?! Did I understood your intention correctly?
I will add the needed information about the protocol in my initial post to give you a better overview about it.
As you mentioned a solution without using an ISR in general, I did a quick try on this. Is this the kind of solution you are talking about? Not finished completeley (I need to find a way to detect if the next edge contains information or not) but to understand your suggestion:
bool booV_BusyFlag_g = false; // indicates, if a measurement is taken at the moment (then true)
byte bV_ADValues_g[100]; // stores the 100 sample points made during one protocol cycle measurement
unsigned long ulV_Time_g[100]; // stores the corresponding time to the ADC values
unsigned long ulV_DeltaTime_g = 0; // stores the delta time between the high and low thresholds
byte bV_actualDataProtocol_g[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // array for actual received data protocol;
byte bV_DataProtocolPosition_g = 0; // counter to display the bit-number of actual data protocol (0-9)
byte bV_InformationEdgeFlag_g = false; // indicates if seen edge contains data or if edge is only the needed change for upcoming data edge
// #############################################################################################################
void setup() {
// put your setup code here, to run once:
analogReadResolution(8); // ADC resolution set to 8 bit for optimized data consupmtion --> one value can be stored in a byte variable
Serial.begin(115200);
}
// #############################################################################################################
void loop() {
if(booV_BusyFlag_g == true){
MeasureValues(); // measure the next 500us with higher resolution
DecodeProtocol(); // decode the currently measured protocol
}else{
// update serial monitor
// decode measured values
// save values on SD-Card
// clear the protocol buffer
for(uint8_t i = 0; i < sizeof(bV_ADValues_g); ++i)
bV_ADValues_g[i] = 0;
// check if a new protocol started
if(analogRead(A0) > 39){ // checks if a pulse is detected; 0.5V*(256/3.3V)=39
booV_BusyFlag_g = true;
}
// print last decoded protocol:
Serial.println(bV_actualDataProtocol_g);
}
}
// #############################################################################################################
// measure ADC values with high sample rate:
void MeasureValues(){
// take 100 sample points, one each 5us
for(int i=0; i<100; i++){
bV_ADValues_g[i] = analogRead(A0); // read ADC value
ulV_Time_g[i] = micros(); // note the time of actual ADC value
delayMicroseconds(5); // wait 5us until next measurement
}
booV_BusyFlag_g = false; // reset the flag to not busy due to completed measurement
}
// #############################################################################################################
// decoding / interpretation of measured values
void DecodeProtocol(){
for(int i=1; i<100; i++){ // check all 100 new ADC values exept of the first one
// check thresholds:
// >70 --> speed pulse: 0.9V*(256/3.3V)=70
// >39 --> HIGH: 0.5V*(256/3.3V)=39
// <39 --> LOW: 0.5V*(256/3.3V)=39
// checks how long the same level is present
if(bV_ADValues_g[i]>(0.9*bV_ADValues_g[i-1]) && bV_ADValues_g[i]<(01.1*bV_ADValues_g[i-1])){ // checks if there were bigger differences in levels
ulV_DeltaTime_g = ulV_DeltaTime_g +(ulV_Time_g[i]-ulV_Time_g[i-1]); // add approx. 5 us to the delta time
}else{
ulV_DeltaTime_g = 0; // resets the delta time to zero due to higher level differences
}
// special case of first pulse
if(bV_DataProtocolPosition_g == 0){
if(bV_ADValues_g[i]>70){// speed protocol
bV_actualDataProtocol_g[0] = 1;
}else{ // speed replacement protocol
bV_actualDataProtocol_g[0] = 0;
}
if(ulV_DeltaTime_g > 35){
bV_DataProtocolPosition_g++;
}
}
// special case of t_p/2 between speed(replacement)pulse and first data bit
if()...
// checks if the next edge contains information
if(Distance to last information edge about 50us +-?){
bV_InformationEdgeFlag_g = true;
}
//if((ulV_DeltaTime_g > 35) && (ulV_DeltaTime_g < 65)){ // pulse width of 50us +-15%
//}
if(bV_InformationEdgeFlag_g == true){
if(bV_ADValues_g[i-2]>39){
bV_actualDataProtocol_g[bV_DataProtocolPosition_g] = 1;
}else if(bV_ADValues_g[i-2]<39){
bV_actualDataProtocol_g[bV_DataProtocolPosition_g] = 0;
}//else if(bV_ADValues_g[i-2] > 70){
//bV_actualDataProtocol_g[0] = 1;
//}
bV_InformationEdgeFlag_g = false; // resets the information flag as the information is already interpreted in this cycle
}
}
bV_DataProtocolPosition_g = 0;// reset position for next decodation cycle
ulV_DeltaTime_g = 0; // reset delta time for next decodation cycle
bV_InformationEdgeFlag_g = false; // resets the information flag for next decodation cycle
}
I don't know the exact details, but using analogRead is substantially slower than digitalRead.
I just took a look at your original code - can't figure it out at all. I was not aware that the analog subsystem of the ATmega could generate an interrupt simply on a change in an analog reading.
In any case, using digitalRead, the most practical approach would be to continue the loop reading the input and record the value of micros() whenever it changes. Blindly recording input values and times uses a ridiculous amount of memory to relatively little benefit as you must then go back and search the list for the genuine transitions.
If there is a good reason for using the slower analogRead - I haven't seen it - then you probably need to include a criterion for detecting significant changes in order to record (only) those event times. the extra code to actually do this is probably minor compared to the analog read time itself.
As I understand your approach, my problem then is that I only receive voltages of 0.35, 0.7 and sometimes also 1.4V - but never 0 or 3.3V . This way I can't just use the digital read as far as I know - or is there a trick to do so?
Damn - I'm already working on several different ways for a solution that I start mixing them up.
For this setting: I do use a 2k pullup for the LM339 output. 10k would do its job as well I guess, slowing the pullup process down marginally. (I updated the schematic in post#1)
In the end you are totally right: for this LM339 setup a digital read would be suitable. In that case I also do not need to configure the ADC --> way shorter code
My general intention is to trigger the rising or falling edge (seems legit to just use the LM339 and a digital pin interrupt) and then additionally save the exact voltage on my measurement resistor. This analog value (0.35, 0.7 or 1.4V) could then be read when the digital ISR is executed.
As @Railroader already said in his first post, the problem was my serial print within the ISR - after I moved it inside the main loop the ISR in general worked pretty well. Unfortunately I still have problems after integrating my MWE to my main project code. It seems like the ISR I coded collides with the I2C library - I always get the error that I leave the 127 possible address area of I2C.
I guess I have to do some further research where this problem comes from.
Nevertheless: thanks a lot for your input and comments. That helped a lot!
Addressing above address 127 sounds alarming. The detected address is corrupted some way. Overwriting somehow.
Using the phone now I'll have to come back later, when the Pc is at hand.
To proceed with my project and also to avoid ISRs in general (as suggested in this thread), I now switched to an other structure in my code: I now use the LM339 to detect the first rising edge which shows the beginning of the 550uS long protocol. Then I take measurements with the ADC every 5 us and save them in an array, also saving the micros value in another array. After the protocol passed by, I have plenty of time to interprete/decode the measured values.
This way I avoid ISRs and additionally I receive a better overview if the protocol itself is sent correctly due to the oversampling.
Lets see, where I will find the next hurdle to overcome.
So far so good. Until now I was able to realize the complete decodation of my protocol. Now I'm intergating the MWE in my overall project, running in a FSM. I hope there will be no timing issues due to the LCD communication via I2C but my code should (as my level of knowledge) be able to compensate some smaller delays if they should appear.
I guess I found my next problem to solve:
If I integrate my working decodation code (displayed via serial screen) in my bigger project code, I don't get any useful informations out of it. After testing several possible problem-makers, it seems like my I2C LCD display makes some problems. I guess the I2C communication is interrupting my code so it only generates trash after resuming its work.
So far I couldn't find informations on how the I2C in general works but I guess with interrupts? This would explain the behaviour of my code after integrating the I2C LCD communication.
My next try will be a solution without the use of I2C, driving the display with an 8bit communication - there should be enough free pins available.