This is a status lights holder for AZUB recumbents (mine is AZUB Apus).
It is supposed to be installed on top of the front derailler tube.
The bottom hole is for speedometer illumination LED, and the rear hole
is for error/status LED.
The front derailler tube is -35 degrees from the horizontal plane,
and the line of sight (for the status LED) is + 15 degrees above
the horizontal plane. It is designed for 3mm LEDs.
This is a quick and dirty work, it is not parametrized enough.
FIXME: It would be better to have the object internal hole as two
cones, each for one of LEDs - it would simplify the LED installation.
When changing brightness via pwmled_set_brightness() below,
we want to converge to the target value as fast as possible. Also,
we would like to somehow initialize the mode 3, which is used as
"mode 2 + other PWMLED on". So after the brightness is set,
we also set pwmleds[n].modes_not_yet_stable to MODE_STABILIZATION_TIME.
When modes_not_yet_stable is non-zero, we allow only mode 2 to be set
regardless of what is fed to pwmled_set_mode. We will then converge
to the target value of mode 2 only, and after MODE_STABILIZATION_TIME
ADC measurements, we copy the mode_pwm value to all other modes.
Only then it is allowed to set the other modes.
We try to detect the rapid decrease in the ambient light level,
such as when entering the dark area (tunnel, deep shadow, etc.).
When this happens, we keep the previous (high) ambient zone,
and use the night pattern (front light continuously on) for at least
3 seconds, to give the rider's eyes the opportunity to adapt.
- all error flags are in the bit array err_flags, including braking and
booting (this is unused yet, the idea is to report successful boot and
pwmled init somehow).
- error status is reported as num_pattern(err, 1) by the status LED
- error status (if any) can be cleared by pressing the button 1
(which takes precedence over the panic mode)
We now measure two running averages - ambient_slow and ambient_fast.
The _fast is computed based on the ADC readings and few bits of averaging.
The _slow is computed based on _fast and few bits of averaging.
We don't want to change the zone too often, but on the other hand,
we want fast reaction to the strong ambient light level changes.
So we compute the new ambient zone based on _fast, and when the new
one is current one +1 or -1, we retry using the _slow value.
Only when even the _slow suggests that the ambient zone has changed,
we permit the change to the neighbouring level. Changes to the more distant
zone are permitted immediately.
Also the logging rewritten to accomodate minimum, a few bits of
max-min, and a few bits of level drop.
User-settable parameter 0 is now the ambient light zone:
Value 0 means to use the ambient light sensor,
values 1 .. N_AMBIENT_ZONES represent the manually set light level.
Simulate a sub-LSB resolution inside the ADC IRQ handler. We do multiple
measurements of PWMLED resistor anyway, so it makes sense to modify the
PWM value after each reading. And indeed, experiments show less flicker
of PWM LEDs with this patch.
It is probably an overkill, and can be called only when measuring the
PWMLED output (and not ambient light sensor, for example). But it works
and it is simple enough.
This branch implements handling all three pwmleds in one pattern
in order to avoid having multiple outputs on simultaneously (when possible).
Also, this separates brightness settings from patterns, so we can have
only one blinking pattern for more ambient light levels, and set the
brightness (current) of the pattern separately.
We set the brightness in an universal function pwmled_select_brightness(),
and this function is called on various events, such as
- ambient light zone change
- dim mode toggle
For now, dim mode means different brightness only for night-ish modes,
because for day modes, slow_pattern uses level 1 as main brightness.
braking is handled behind the patterns inside pattern.c
See the comment above pwmled_update_mode():
This is tricky: we use a single pattern for all three pwmleds,
but on some occasions, we want to be able to modify only a single
pwmled status without affecting other outputs. For example, during
braking, we want to modify only the rear pwmled status. We don't
use a separate "braking" pattern for every other pattern used, but instead
we change the pwmled0 status regardless of the original value when
braking. The rule is the following:
- if during braking the pwmled2 (front) is at mode 2, we switch it to
mode 3 (which has the same target current) to avoid flicker.
- if pwmled0 (rear) is off, we set it to mode 2
(if it is with mode 1, we keep it at mode 1)
TODO: something similar should be done for the "entering the dark area"
condition, where we want to switch pwmled2 (front) on, to mode 2.
slowN_pattern, normalN_pattern, and onN_pattern for N=(1..4)
rewritten to be brightness-independent, and to avoid switching
multiple outputs on at once, if possible.
If not (such as when PWMLED 2 is continuously on), use mode 2 and 3
with the same target current to accomodate for different battery voltages
when more than one output is running.
TODO: actually set brightness based on various conditions, such as
ambient lights, user-requested dim mode, etc.
In order to save space for patterns, we set the brightness
independently from pattern. Each brightness has only two levels
for PWMLED 0 and 2, and one for PWMLED 1. Patterns can then use
two-bit values for each PWMLED (one-bit for PWMLED 1), with the
following meaning:
0: off
1: level 1
2: level 2
3: also level 2, with a separate state stored. This can be used for
saving regulation value for a single current level with
and without other outputs running, or with different levels
of other output. This is in order to avoid flicker when one
PWMLED (usually the front one) is steady on, and the others
are blinking.
Include date and git revision in the eeprom variable
... in order to be able to find out which firmware version is this MCU
running. This roughly corresponds to the commit 49420b56b31e113f4c40128c530f2e585a9f8061
of project Tinyboard.
Measure also longer-term running average of ambient light values,
and log instead of minima and maxima of adc readings the diffrence
between this longer-term average and normal avereage (ambient_val).
This is so that we would be able to switch the front light on when
entering a darkish area from the bright sun.
In order to mitigate the problem with watchdog reset, possibly caused
by timer IRQ handling taking too long, we only increment the jiffies
value in the WDT IRQ handler, and then read this value in the main
loop, compare with the previous one, and if those two are different,
we run the timer-induced operations. We can (probably) detect the
timer overrun (the difference in jiffies being greater than 1), and log
it.
The individual timer-induced operations are run in their own atomic
blocks for now, in order to be safe. The finer-grained locking is
in the TODO list :-).
- early HW setup, run once after reset, is moved to the first_boot()
function
- main loop iteration is in a separate function main_loop_iteration()
- both functions (and hw_suspend() as well) are made inline
Apparently, after WDT reset, WDT is still running, and has to be
disabled. Otherwise it will kick in again during the initialization.
We want to indicate the WDT reset contition somehow. We use the GPIO
LED 0 - we set it to on if the reset source was WDT.
Also, we want to read and reset MCUSR as early as possible. We do it
from main(), and we then send the saved value where needed (init_log()
and power_down()).
pwm.c: channels running - visible from the outside
Make the status of T/C1 visible from the outside, in order to
make the on-demand ADC channel selection possible, and also
to allow selecting the sleep modes in the main loop.
A common failure mode is that the magnet slips and does not provide
the signal, so it appears as if constantly braking. Avoid this situation
by allowing continuous braking of at most 16 seconds. After that, report
brake off, and wait for the real "brake off" signall from the Hall sensor.
In order to be able to tune the ambient light sensor better, I made
it to log the minimum and maximum values read for every three minutes
or so. When the log buffer fills, start over only after the next
off/on cycle.
With low-pass filters at the input of PWMLED ADC pins, it is now
relatively stable and noise-free, so we can lower the number of readings
to two. Hopefully this will not cause instability.
The fastest-repeated measurements are needed for PWM LEDs. OTOH,
things like buttons, battery voltage, ambient lights, etc. can be
read less frequently, and should be read in a deterministic time frame.
So we will measure PWMLED current in the free-running mode (as fast
as possible), and the other "slow" ADC inputs with each PATTERN_DIV-th
timer tick.
Run ADC synchronously with timer IRQ. This partially reverts
commit 210916486d18b3dc976c65c7b01b44bca446d856 in order to
prepare for selectively enabling and disabling ADC channels.
When there is a supposed ADC noise problem, it is handful to drop
several first readings, and even discharge the ADC capacitor
by reading the single-ended wire connected to zero. This commit
rewrites the ADC handling to allow exactly this, for each ADC input.
- status LEDs should not blink together
- PWM LEDs should start synchronized, in order to allow for compensation
of battery voltage variations when one output is on
compared to when more outputs are on
I have decided to implement a brake light, which I want to trigger using
Hall-effect sensor mounted in the brake lever. Unfortunately I still
have only three wires for two switches and a Hall-effect sensor, so I
would have to change the digital switch readings (on/off) to ADC
readings (voltages selected using voltage split on resistors).
Here is the schematics for the whole handlebar electronics, and the
symbol file and datasheet for the Honeywell SS341RT Hall-effect sensor.