ESP 8266 C problem - Page 1
Forum section: electronics / general

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 01:48:39 pm
content: I am making an electronic ignition for my old BMW bike using an ESP12.I am struggling with these two statements. The commented one does not work but the un-commented one does. All numbers are INTQuoteTimeDwell is fixed at 3500 us, TimeRev is fixed at 20000 us, EEnum is 1/6 of the advance required in microseconds, read from EE. It is 1/6 so it will fit in a byte.I also do not understand why brackets are required round the bit where I am comparing micros().I need to get the commented one to work as the un-commented one will make a misfire on micros() rollover.Note: I readily admit I am the world's worst programmer when it comes to high level languages.

author: Psi
author_rank: Super Contributor
timestamp: June 15, 2019, 02:04:45 pm
content: Expanded for clarityif ((micros() - StartMicros - TimeRev + EEnum * 6 + TimeDwell > 0)&&HadInt == 1)if ((micros() - StartMicros > TimeRev - EEnum * 6 - TimeDwell)&&HadInt == 1)

author: Psi
author_rank: Super Contributor
timestamp: June 15, 2019, 02:06:45 pm
content: I only had a quick look before bed but i'm guessing your issue is the order of operations and casting.You should use brackets around everything to make it 100% clear what order you want things to happen in.Also if you take a variable that's a byte, like EEnum and multiply by 6 the operation is performed on what EEnum is (a byte) and a byte cannot store this. The number is bigger than 255. So you need to give the compiled some extra info so it knows to treat EEnum like a word or other bigger type when doing this calculation.You do this with a cast, like this (int16_t)EEnum * 6(Note: i'm assuming your compiler has the type int16_t, if not you will have to change that part, you could use int)This will cast EEnum to a bigger size for the calculation.etcSo your original code...if ((micros() - StartMicros - TimeRev + EEnum * 6 + TimeDwell > 0)&&HadInt == 1)becomesif (( (micros() - StartMicros - TimeRev + ((int16_t)EEnum * 6) + TimeDwell) > 0)&&(HadInt == 1))Notethere maybe other things that need to be cast as well.Here's some info on castinghttps://www.tutorialspoint.com/cprogramming/c_type_casting.htm

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 02:18:37 pm
content: This doesn't work either:if (((micros() - StartMicros -TimeRev + EEnum*6 + TimeDwell) > 0) && HadInt == 1)Where else can I stick brackets?Is it normal for C compilers to not understand the rules of mathematics?EEnum is defined as an INT and I have previously read a byte into from EE:EEnum = EEPROM.read(RPM/100);

author: Psi
author_rank: Super Contributor
timestamp: June 15, 2019, 02:23:43 pm
content: I edited my original post to add a bit about casting.Have a read of it again.I think one of your issues is not casting EEnum so the multiply by 6 doesnt work right.EDIT: Oh ok, i thought you said EEnum was a byte.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 15, 2019, 02:34:02 pm
content: Looks like you're mixing integers of different size and signedness in the same expression. But if I get it well, most of the terms are 16-bit integers (not sure how your INT type is defined). The resulting sum may exceed 16-bit and will not be automatically promoted to 32-bit, so you may need to cast one of the terms to a 32-bit integer first to make sure the overall expression is computed with 32 bits.

author: Psi
author_rank: Super Contributor
timestamp: June 15, 2019, 02:37:03 pm
content: Another problem maybe that micros() returns an unsigned longWhich you are then doing a subtraction on.Maybe beyond zero?Could tryif (( ((int)(micros()) - StartMicros - TimeRev + EEnum*6 + TimeDwell) > 0 )&&(HadInt == 1))

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 02:50:54 pm
content: Quote from: Psi on June 15, 2019, 02:37:03 pmThat fixed it.Do I now have the problem that in 35 minutes or so that number is going to go negative?

author: cv007
author_rank: Frequent Contributor
timestamp: June 15, 2019, 03:28:26 pm
content: You can separate the int/uint stuff- where the time is unsigned (as micros is unsigned it seems) so you can have unsigned arithmetic for calculating elapsed time (and will always be right, until you have the slowest running engine known to man). That is separate from the other numbers, which can be signed.Now you have simple subtraction on the unsigned time, and simple addition on the signed times.Code:const int32_t TimeRev = 20000; //constant 20000usconst int32_t TimeDwell = -3500; //constant -3500us (negative, as is before TimeRev)//somewhere (triggered every rev I assume)uint32_t StartMicros = micros();//now checkuint32_t ElapsedMicros = micros() - StartMicros; //elapsed time in microseconds since startint32_t TimeAdvance = EEPROM.read(RPM/100)* -6; //negative, as is before TimeRev//if elapsed time is >= the total of the 3 signed vars, start coilif( HadInt == 1 && (ElapsedMicros >= (TimeRev + TimeDwell + TimeAdvance)) ){digitalWrite(OutHall, LOW);HadInt = 2;}

author: TK
author_rank: Super Contributor
timestamp: June 15, 2019, 03:33:08 pm
content: Quote from: Mjolinor on June 15, 2019, 02:18:37 pmComputers are dumb... you need to tell them exactly what you want it to do. It means you need to learn how compilers interpret what you write, order of operations, casting, etc. If you want it to be easier, try micropython (or python).

author: Brutte
author_rank: Frequent Contributor
timestamp: June 15, 2019, 03:54:18 pm
content: Quote from: Mjolinor on June 15, 2019, 01:48:39 pmAll numbers are int and it does not work but if you cast first expression to int it starts to work?PEBCAK

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 06:31:57 pm
content: Quote from: cv007 on June 15, 2019, 03:28:26 pmMuch working out later.If micros() rolls over between StartMicros getting set and ElapsedMicros getting set does this not give me a problem?The reason for me wanting to make my first line work rather than my second line is that the first line is not affected by rollover whereas the second is.I am not sure, I really struggle getting my head round this.If startmicros = FFFF FFFF and it takes a few Us to get to Elapsedmicros then Elapsedmicros is perhaps 2 - FFFFFFFF into an unsigned 32 bit so Elapsedmicros = FFFF FFFD even though it is only 2 Us.I am probably wrong there somewhere.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 06:34:06 pm
content: Quote from: Brutte on June 15, 2019, 03:54:18 pmWell stone me, I wonder why I posted in the first place.Bit of a penchant for stating the obvious there methinks.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 06:35:37 pm
content: Quote from: TK on June 15, 2019, 03:33:08 pmI find python even worse personally. I could do it from scratch in about two hours using assembler but am forcing myself to try it in a higher level language for no good reason really.

author: cv007
author_rank: Frequent Contributor
timestamp: June 15, 2019, 08:23:37 pm
content: QuoteGet out your calculator which can deal with hex numbers (any os has a calculator with hex).2 - 0xFFFFFFFF = 3since we are dealing with a fixed number of bits (32 in this case), ignore any bits beyond that your calculator may show (FFF....)plug in various numbers to see what happensIn this case, your micros() value will always be increasing from the 'start' value, so subtracting the 'start' value from the advancing 'micros()' number will always leave you with the difference- right up to the point where they are equal. At the equal point and beyond, now you have a problem as the 'difference' now has more than one meaning. So if you can be certain the two numbers will never 'overlap', you will always get a valid difference. You can be certain you will not reach the 71 minute mark while the engine is running.Code:(ElapsedMicros >= (TimeRev + TimeDwell + TimeAdvance))I also introduced a 'problem' that is not a problem, until its a problem, which in this case will not be a problem. The addition sum is promoted/demoted to an unsigned as the comparison has an unsigned, which will not be a problem until you invent time travel, I guess. These sum of vals could be changed to unsigned also- (TimeRev-TimeDwell-TimeAdvance), but was just showing a way of thinking about those values in a way that shows the relationship of those values to a fixed point in time (TimeRev).

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 15, 2019, 09:30:28 pm
content: Confused.Simple questionsIs this statement evaluated as an unsigned because micros() is unsigned?(micros() - StartMicros -TimeRev + EEnum + TimeDwell > 0)If so then when TimeRev is subtracted the result of the above is a large +ve number, that is the fail.Adding the cast to micros() makes that large +ve number a -ve number that will pass zero at some point when the coil needs turning on. All works fine in this case.However, when((int)(micros()) - StartMicros -TimeRev + EEnum + TimeDwell > 0)gets the MSB set after 35 minutes or so we are back to a crap situation where the test will fail again.Am I right?

author: cv007
author_rank: Frequent Contributor
timestamp: June 16, 2019, 01:52:40 am
content: You don't say what all the var types are, that I can see. Without knowing that, can't tell.If any of those left hand side vars are unsigned, everything becomes unsigned (bits are not changed, though), which means > 0 will always be true (unless 0).If all the left hand side vars are signed (or cast as signed, and the 0 is signed by default), then you get signed addition/subtraction/comparison. Which of course is not a good thing for the time value when it goes negative (actually positive, but you cast it as signed). On the one hand, unseen/noticed signed to unsigned conversion means you have to watch out for things like comparison to 0, on the other hand when casting to signed to make it all signed values leads to 'corrupting' your cast unsigned data which now has the potential to be nothing like it actually is.Which also leads to the question- why is 'StartMicros' a signed value (I'm deducing, since it seems micros() is the only thing causing things to go unsigned) when it presumably is assigned by a call to micros() which returns an unsigned value? No compiler complaints?I think what I showed previously is correct. Get the time difference as unsigned so it does not matter if start time is higher than the current time (timer rollover). Then deal with the comparison to the other values.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 16, 2019, 06:44:47 am
content: Second sentence of the first post: All the numbers are int.That means all the ones I defined. micros() is unsigned and not under my control.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 16, 2019, 07:48:22 am
content: Right. Got there eventually.StartMicros needs to be unsigned so that I get a correct value for elapsed time. Elapsed time will always be way lower than the MSB rollover (unsigned) so I can safely cast it to a signed number without consequence. I can now use elapsed time with my other signed numbers to detect 0.I think I have it clear in my head now. Bit of an epic but I got there.Thanks for the help.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 16, 2019, 08:32:19 am
content: What I end up with that I understand:Code:#include <EEPROM.h>//#include "stdlib.h"#define InHall 4#define OutHall 13#define power 5uint32_t StartMicros;uint32_t ElapsedTime;int HadInt = 0;int TimeRev= 20000; // 20 millisseconds per rev//int TimeDeg;int TimeDwell = 3500; // 3.5 millisecondsint EEnum;int RPM;int SparkOn;//*************************************************************void setup(void) {Serial.begin(115200);Serial.println("");EEPROM.begin(512);pinMode (OutHall, OUTPUT);digitalWrite(OutHall, HIGH);pinMode (5, OUTPUT);digitalWrite(OutHall, HIGH);delay(1000);StartMicros = micros();Serial.println("Booted OK");}//********************************************************void loop(void){if (HadInt == 0){// tm.reset();digitalWrite(5, LOW);RPM = 60000000 / TimeRev;if (RPM > 7000) RPM = 7000;if (RPM < 500) RPM = 500;EEnum = EEPROM.read(RPM/100)* 6;StartMicros = micros();HadInt = 1;digitalWrite(5, HIGH);}ElapsedTime = micros() - StartMicros;if (((int)(ElapsedTime) -TimeRev + EEnum + TimeDwell > 0) && HadInt == 1)// if ((micros() - StartMicros > TimeRev -EEnum*6-TimeDwell) && HadInt == 1){// Make spark output low, start coil current flowdigitalWrite(OutHall, LOW);// digitalWrite(5, LOW);HadInt = 2;}if (((int)(ElapsedTime) - TimeRev + EEnum > 0) && HadInt == 2)// if ((micros()- StartMicros > TimeRev -EEnum*6) && HadInt == 2){// Make spark output high, stop coil current flowdigitalWrite(OutHall, HIGH);// digitalWrite(5, HIGH);HadInt = 3;}if ((int)(ElapsedTime) > TimeRev && HadInt == 3) HadInt = 0;}This works OK and I am happy that at MSB set it will behave correctly and at micros() rollover it will behave correctly. If it locks the back wheel and throws me in a ditch then I am to blame.Note: I case you are wondering, the above is not the actual ignition routine, I have removed the interrupt routines and inputs. The purpose of the above is to allow me to test and set the dwell angle on the coils without them overheating and without losing too much spark power.When you start current flow in the coil it takes some time to reach maximum value. Once you reach that value any more time and you heat the coils up, if you stop the dwell before maximum current you lose spark power.Once again, thank you for the help and if you think I am still not understanding something please don't hesitate to educate me some more.

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 17, 2019, 08:41:23 am
content: So I spent a few hours in the last day testing the order of doing stuff with this compiler and in every case for both logical and arithmetic operations it obeys the rules of mathematics.I tend to think that if you have a compiler that does not obey those rules then the compiler is flawed somewhere and having to learn the rules for a particular compiler must be a real pain.Anyway, it has been running a day now and I haven't seen it do 'owt daft. I am wondering if I need another programme on some other hardware to catch a misfire, it would probably be sensible I suppose before I stick it on my beloved BMW.

author: TK
author_rank: Super Contributor
timestamp: June 17, 2019, 11:34:11 am
content: Actually google "operator precedence in C" and you will find precise rules about how a standard C compiler should work, no need to do any testing. And if a compiler does not follow the rules, then it is flawed. But this precedence rule is not strict mathYou also need to consider casting and int types used.I prefer to use uint8_t uint16_t and uint32_t when programming microcontrollers to make sure I am using the right number of bits, unless you need signed integer and use lots of parentheses to indicate your own association and precedence rules.You are doing it in assembler anyway by programming the operations in the order you want the computer to execute and the type of registers and operands you want to use

author: Mjolinor
author_rank: Frequent Contributor
timestamp: June 17, 2019, 02:51:36 pm
content: Well dare I admit that up to a few days ago I had no idea what uint32_t was or any other variations of it. I know that "int" is 32 bits when I am using the ESP Arduino compiler and I just used "unsigned int" if I wanted unsigned. Having now read about it all I shall use it in future.

author: cv007
author_rank: Frequent Contributor
timestamp: June 17, 2019, 03:53:44 pm
content: QuoteHow about testing when the compiler promotes signed to unsigned? That was your original problem, not order precedence. Sneak in any unsigned into the mix of comparisons, and they all get treated as unsigned. Any unsigned result of unsigned comparison will be always be greater than 0, unless 0. Your first commented out line, the comparison becomes an unsigned comparison to 0 because you brought in the unsigned result of micros().Here's a start-uint32_t a = 0;int32_t b = -1;printf("%u + %d > 0 ? %s\r\n", a, b, a+b>0 ? "true" : "false");//0 + -1 > 0 ? truea+b should be -1, but its unsigned result of addition is 0xFFFFFFFF, which is greater than 0, so a+b > 0 is true.Now make them all signed (the 0 is a signed number by default), and you get what is expected-int32_t c = 0;int32_t d = -1;printf("%d + %d > 0 ? %s\r\n", c, d, c+d>0 ? "true" : "false");//0 + -1 > 0 ? falseNow change 0 from signed to unsigned-printf("%d + %d > 0u ? %s\r\n", c, d, c+d>0u ? "true" : "false");//0 + -1 > 0u ? trueAnd so on.In addition to getting in a habit of using the uintN_t/intN_t as already mentioned (which also makes you think about what values you are trying to store), you can also break down problems into simpler/smaller pieces that are easier to read and understand. The compiler could care less, and will mostly end up compiling to the same code anyway. The first example of doing that was to compute the elapsed time as a separate expression- you then know that the result is always a valid time, and you can move on the next problem.>the compiler is flawed somewhereIts always a waste of time to think the compiler is messing up somewhere/somehow. Its way more productive to start with the premise that you are not communicating with the compiler properly, or you are not following rules that the compiler is playing by. There are lots of rules, so the key is to simply know there are rules, and when something doesn't work as expected you can probably start by trying to figure out what rules apply.

author: Psi
author_rank: Super Contributor
timestamp: June 18, 2019, 05:52:37 am
content: Quote from: cv007 on June 17, 2019, 03:53:44 pm100% this.Generally speaking, It's easier to write code in many steps and it's easier to read back later when done in steps.You can also comment as you go.This is much easier to write/readCode:// This step divides this value by that value to get output that represents foo.if (Y / X){if ((5+X)>10)// This step checks if we will exceed the 10 limit{if (Y==2){DoSomething();}}}Than this (which is hard to comment all at once. So often people just dont comment it at all)Code:if ( (Y / X) && ((5+X)>10) && (Y==2)){DoSomething();}Sometimes you will have to store an intermediate step in the calculation.And some people find this uncomfortable because either they feel like they are wasting memory or they don't like storing a number that is meaningless. What i mean by that is they want all variables to store something tangible in SI units.But it's totally fine to store results partway through a calculation.You don't have to keep all variables containing official SI units, they can store abstract things in odd units, as long as you comment this.If you declare your viable inside the scope it wont use any more memory than the other way. The compiler will just optimize them both into the same code.