Software
The controller is clocked by the internal RC oscillator. Precision of a crystal is not required since there are no asynchronous interfaces (like RS232) and no real time clock.
Therefore, a crystal is also not provided on the PCB.
Programming
Always remember, while programming the chip you have to keep the button pressed since the pins change to hi-Z and so the power would be switched off. The pressed button holds the power on during programming.
Timer Interrupt
Timer 0 is used to do repetitive tasks (like multiplexing the display). The ISR is called 1000 times per second. Counting six digits this means a repeat rate of 166 Hz. This is not only flicker free, it also allows dimming in three steps. For this, the display is only active in one up to all steps (or even in none of them, display blanked). Since we are working with batteries here, this can be used to save energy.
Since the dimming effectively reduces the refresh rate, it is not useful to have more than three steps. So we still have 55 Hz and thus a flicker free display.
Timer 0 also operates a second counter which is used to change to power down or stand by mode or to power off after some time of inactivity.
Note that this is not part of a real time clock! It will be reset to 0 at certain opportunities, mainly to create time out events.
Bin6toBCD8
Since the AD converter produces a 24 bit value we need a routine to convert this (after calculating the kg) into a (packed) BCD number. Packed means there are two digits in each byte. The BCD number thus fits into one long value. It can easily be written into the display RAM.
I wrote this routine in assembler since this is much more
effective. In C
there is no carry flag and so the shift
operations would be more complex.
The method I chose takes 24 shift operations (each over all seven bytes). Before the shift we have to check for each digit (i.e. for each nibble) of the value is greater than 5. If it is, we have to add 3. Here it was modified in a way that we always add 3 and subtract 3 again if we did wrong. This gives a slightly shorter code since the MSB of each nibble is only set if it was greater than 5. If it is not set, we subtract 3 again.
I keep all values in registers. This reduces code
execution time which can be considerable long. I only use
registers that are outlaws
in AVR GCC i.e. there is
no need to save their original values on the stack.
If the calling routine would use them, it would have to
save them itself.
If you use a different compiler this may be different and you may have to save them before use!
My version needs no RAM and no stack but it cannot be easily extended to bigger data types since there are not enough registers available.
Of course, this method sabotages every thought of portability. Not only for other architectures, even for other AVR models you would have to think about how to implement it. But do we need this? No, not really! It must work, it must work fast and it must consume least energy.
Power Save Mode
If the weight does not significantly change over some time, the scale enters a power save mode. The display is dimmed more and more to save energy. Measuring continues, though.
If the weight changes, it switches back to normal mode and the display shows full brightness again.
Change
means more than 5 g
difference to the weight one second ago.
Power down
Inactivity of more than 4 minutes causes the scale to switch off. Only then the actual weight is forgotten and when switching on again, it shows 0 as initial weight, no matter what actually lays on the weighing table.
If you really need more than 4 minutes you can return to normal mode any time by softly pressing on the weighing table or the key and you have again 4 minutes to power down.
If there is no significant load (±5 g) power down occurs after 15 s. This is to save battery and can be used intentionally by setting the weigh to 0 when done.
Calibration
If you press the key for more than 10 s the scale enters calibration mode. The display shows CAL.
If you release the key, you can step through predefined reference weights by pressing the key. If you don't press the key for five seconds, the weight actually displayed is selected. It will blink fast for three seconds to indicate this.
Keep the reference weight ready now, we'll need it soon. If you made a mistake here, just cut off the power or wait a minute until the scale powers itself down and nothing will change.
The scale awaits an empty table for now and shows
don't touch
(-touch).
It takes 128 measurement and uses the mean value, taking about
13 s.
A (hexadecimal) countdown counter showing alternately gives a
visual progress bar
.
The display shows rEF then.
Wait until then. Do not touch the weighing table and do not
make unneccesary wind. You would degrade calibration accuracy
if you did.
Put on the reference weight now which can be one of
1 kg, 2 kg, 5 kg or 10 kg, just as you selected before.
Ideally it would be 10 kg
since this would give maximum accuracy.
The scale again takes a mean value of 128 measurements, taking
13 s again.
Do not touch the weighing table or make unneccesary wind.
Do not turn on the heating and do not open a window!
The scale finally shows the measured weight (what should be
very close to the reference weight) and you can accept it
by pressing the key, storing the calibration data permanetly
in the EEPROM.
If there was any mistake until here, just cut off the power or
wait a minute until the scale powers off by itself and
everything stays as it was before. You may then repeat
the procedure.
If you put on a wrong reference weight, you can degrade
the scale's performance up to unusability.
Especially if you select a wrong reference weight (e.g.
you select 5 kg but put only
2 kg) a completely insane
calibration value will be stored.
If you accept the weight (and only then) it will immediately be stored in the EEPROM and is valid even after restart.
If you dont accept it (i.e. if you don't press the key) The scale will restart after a minute and everything is like it was before.
Converting the Raw Values
To calculate the weight from a 24 bit raw value with the simple formula using integer arithmetic we have to use at least 48 bit wide numbers. The AVR library luckily knows a type int64_t so we can easily do this without using floating point.
So during calibration, it is easily possible to calculate the coefficients a and b.
I found it interesting that this calculation in integer
produces fewer code but takes with 285 µs about three times as long as the calculation
in double precision!
The reason for this is, that double in the AVR-
Filtering the Values
Due to the noise of the AD converter its value is not suitable for direct display.
For testing, I implemented three methods of filtering:
- Sliding average
- IIR filter
- FIR SINC filter
Sliding Average
Called GetAVG1 in my sources. My first approach was to calculate an average over the past 8 measurements.
The advantage of this method is a linear approximation to the current value. The disadvantage is a high demand of memory since we have to store the latest 8 measurements.
Indeed, I wrote an extended version taking the mean of the last 64 values. You can switch between by double clicking (pressing the key twice within a second).
After a double click, the display shows H1 or L0 for a few seconds to indicate the fast or precision mode.
The disadvantage of this method is the usage of memory. Sliding average takes about 100 bytes more flash and 256 bytes more RAM than the following:
IIR Filter
The second method, GetAVG2, computes the actual value just from the last measurement and the previous value which is weighted more by a constant factor, according to the formula:
The clear advantage is a very low memory requirement, even for large values of c. The disadvantage is that the display value approximates the real value following an e function and since we are using integer numbers, in fact, it never will. It will reach the measurement value up to (c-1) but since this value is noisy, this limit can be overcome if the noise is more than c.
Although, just this e function represents the behaviour of a mechanic scale. You can see almost immediately where the target goes but if you want to see the exact value, you'll have to wait a few seconds.
avgFactor
Is the determining factor for GetAvg2(). This is the factor the last result is stronger than the last measurement value. avgFactor will be much smaller than histSize in the previous method since the value will stabilize only after 12×avgFactor measurements.
Again, you may choose between fast (L0) or precision (H1).
For baking cakes, LO is absolutely sufficient since we have not to rely on 10 or 100 mg. If you want more precise measurement, switch via double click.
FIR Filter
The third approach was a FIR SINC filter, called GetAvg3(). The finite impulse response gets a stable value after a finite time.
At first I used the tool from Tom Roelands to calculate the coefficients (cutoff frequency 2 Hz, transition bandwith 3 Hz, Hamming-window).
I found it suspicious that the first and last coefficient was 0.
Well, another calculator at TFilter gave a result where they were not!
Entering coefficients is not that complicated and so you have both options in my sources and you can experiment with it.
The crucial question: are 32 bits enough for the calculation? Well, not always! If all coefficients were 127 and all 24 bit values were 8388607 definitively not. But the coefficients are not all 127. They are constants so we know one of the factors.
The sum of the absolute values is 335 or 361 so we get no overflow up to nearly 6 millions.
The AD converter produces only ±2 million counts. The filter still works with 6 m without overflow so we have enough buffer to risk the calculation with 32 bits.
This is my first estimation which also worked experimentally. Since the FIR filter showed no significant improvement, I did not test other configurations. I only tried one with 0.1 Hz (where TFilter failed but Roelands gave a result) but still seems not to be better than the simple IIR filter.
Selecting the Filter
You'll have to specify the method at compile time. Change the #define USE_GetAvgx (in scale.h) to 1,2 or 3. In my sources, I decided for IIR but it is only a matter of #defines.
Composing the Display Value
The HX gives 10 values per second. Even the filtered value has significant noise and thus is not suited for direct display. The last two digits would appear only flickering and would not give much information to the user.
So we do not display every value but we do again take a mean of four succeeding values which are displayed 2.5 times per second.
This rate can be easily cought and the value will be much more smoother.