1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
|
# Byseekel (scrapped version)
> Note: This is the scrapped version of [Byseekel](byseekel.md). I began
> writing this article in early 2022, until I abandoned it 2022-05-02. The
> next year I revived and finished the project, but killed many features.
> That was when I decided to rewrite this blogpost from scratch, with less
> groundless optimism you'd find in this scrapped version.
> Nevertheless, for archival reasons, I am publishing the scrapped version
> on 2024-02-14. This article is not finished and will not be updated.
> Please disregard any sentence that suggests otherwise.
> I deleted the images a long time ago, so I put in the alt text instead.
> Article begins here.
Freddie Mercury famously sang:
> I want to ride my bicycle I want to ride my bike
> I want to ride my bicycle I want to ride it where I like
I, too, own a bicycle, but in terms of performance and condition it is
like crap. I certainly wouldn't take riding my bicycle as much of
a pleasure as Mercury did. This makes me ask myself a question: What do
I need to make a bike trip more enjoyable, or just less painful?
A [cyclocomputer](https://en.wikipedia.org/wiki/Cyclocomputer).
Image: Cateye cyclocomputer mounted to a bicycle handlebar.
Image credit: Wikipedia user
[Mnisawlpsa](https://commons.wikimedia.org/wiki/User:Mnisawlpsa).
Despite its stupid name (cyclo? computer? smh) it's kinda useful. It keeps
track of the speed and mileage of your bike, and displays them on
a screen. I do know one application where it definitely fits, and that is
cheating the 80-kilometers-of-jogging-per-semester mandate from my school
office.
Every (able) undergrad in my uni has to accumulate a certain jogging
mileage by the end of each semester. For boys, the number is 80
kilometers. To track your movement you turn on your phone's GPS and start
jogging. But the loophole is, it can't actually tell if you're jogging, as
long as you keep your speed within 3-9 minutes/km. Experiments show I am
able to maintain a steady 3-5 min/km on a bike and trick the system into
believing I'm legitimately jogging. But even so, I have to pull out my
phone mid-journey — a quite dangerous act, even when no one was around
— to make sure I'm not going too fast. It would be helpful if I had
a device to check that for me and warn me if it exceeds that range.
Guess what, it'd be extra helpful if it could tell me how far I've cycled.
It'd also be cool if I could integrate this with my previous
semi-successful project, [Bikeblinkers](../bikeblinkers). What if besides
blinkers, it also had a braking light. Hey, since looking for my bike
among 200 others in front of the gate is such a pain, how about something
that would tell me where it is?
I should totally make a bucket list of features to implement:
- speedometer
- odometer
- blinkers
- hazard light
- braking indicator
- remote-activated buzzer
And no, this is so much more than a cyclocomputer. As a tribute to Freddie
Mercury, I'm going to name my project after how he enunciates the word
"bicycle".
Note: this blogpost is continuously being updated.
## Hardware
### MCU (spoiler: it's AVR)
The project at hand really seems to dwarf [Bikeblinkers](../bikeblinkers)
in terms of complexity, and apparently a single 555 IC simply won't do,
and an MCU is necessary.
Which MCU? That is one of the most consequential decisions to make. Up to
this point I had a really limited tech stack, because before this one all
I knew was Arduino (in this blogpost I treat the Arduino as a distinct
"MCU", despite its real MCU being ATmega328P), and I hadn't really done
any project that (a) has an MCU, and (b) runs more than one meter away
from a computer and a power source that draws power from the mains.
"How about Arduino," I thought. However, upon close inspection, the
Arduino isn't all cake.
- Many Arduino libraries are bloat and inefficient (I'm not judging
though)
- Arduino IDE v1 is a huge pain to use (Yes I am judging here)
- The Arduino has a 3.3V regulator which isn't useful for this project but
consumes quite a substantial chunk of power
That said, I am really hoping there was some alternative. What I want is
a bare-bones version of Arduino, where I get the freedom to do whatever
I want, but still somehow match the Arduino's capabilities.
I have a few ATmega328P's lying around, so let's try them.
🛒 BOM += ATmega328P
Admittedly, you _can_ embed AVR C/C++ snippets in your `.ino` or
completely write your sketch with it, and if done right it's possible to
compile and download an Arduino sketch onto your average ATmega328P, but
what's the fun? I'm going to ditch Arduino completely here.
I'm going with avr-gcc and avr-libc, so that I won't touch assembly and as
a bonus practice C which I had been learning for 2/3 of a semester.
I fetched a copy of datasheet for the ATmega328P and began reading. The
most important information as of now is
- Pinout (GPIO and SPI)
- IO registers (`PORTB`, `DDRB`, etc.)
I bought a [USBASP](https://www.fischl.de/usbasp/) to download my code
onto the chip. Well, in fact it's not the code I'm downloading, it's the
hex file obtained by compiling the code, linking the libraries, generating
an elf, then summoning Satan.
There are three limitations on the specs one ought to care about when
designing software for an AVR chip:
- Flash size
- SRAM size
- Clock frequency
The 328P has 32KB flash, 2KB SRAM and a maximum frequency of 20MHz, but of
course we're not using all that much. Of the three what worries me most is
RAM, because after 2/3 of a semester of C I know how hard keeping track of
memory can be, and for some reason my programs easily ate up megabytes on
the OJ. Although 2KB sounds extremely limited, fortunately we don't need
fancy algorithms here.
So far, all I needed was the I/O functionality, and the most basic
conditionals and loops C has to offer. By lumping up stuff like
```c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB |= _BV(PB5); // set pin direction of PB5 to output
while (1) {
PORTB ^= _BV(PB5); // toggle PB5
_delay_ms(500);
}
}
```
I was able to blink an LED.
Image: An illuminating LED driven by a DIP-28 ATmega328P on a breadboard
This was all very basic, equivalent to what you would do with `pinMode`
and `digitalWrite` on an Arduino. Although I was eager to write some
_real_ code, I should figure out how my MCU would sense and react to the
physical world.
### Input Peripherals
#### Learn to reed
To calculate the speed the bicycle is traveling at, we need to count the
number of revolutions. I initially had two ideas:
- Optical: reflective material on the spokes, IR LED and phototransistor
on the fork;
- Magnetic: reed switch on the fork and a permanent magnet on the spokes.
The optical solution in theory works like this:
Image: Diagram showing the location and schematics of the optical sensor
However, the weaknesses are pretty obvious.
- Requires power source
- Requires directional alignment
- Requires a minimum of three wires (VCC, GND, output)
- Aluminum foil on a bicycle wheel looks silly
- Electrical noise
Moreover, the industrial standard for cyclocomputers is reed switches.
Image: Wired reed switch sensor with spoke mounted magnet.
Image credit: Wikipedia user
[AndrewDressel](https://en.wikipedia.org/wiki/User:AndrewDressel).
They should be installed on the rear wheel, because rotations of the rear
wheel are more representative of the real mileage.
🛒 BOM += reed switch, permanent magnet
#### Turning big
In Bikeblinkers my control for the blinker circuit was a tiny SP3T toggle
switch. It's a bit too small, and it was the source of two design errors
in my rev. 1 PCB (look I gotta find someone else to blame okay?)
Image: A SP3T toggle switch mounted on a PCB
🛒 BOM += bigger SP3T switch
I spent a fortune buying one, but it was worth it. Cue the industrial
switch!
Image: Industrial SP3T switch in metal and plastic casing. It is thrown to the right.
(ruler in centimeters)
This is all good and such, but one problem is it's pretty likely you'll go
all the way to the opposite direction when your intention is to throw it
back to neutral. No countermeasure so far.
In February 2022, I came up with an alternative. This is one of those
blinker switches they use on e-bikes. It has three terminals: left, right,
and common. Flick it to the left, and left connects to common. Flick it to
the right, and right does so. Push the stalk down to disconnect all
terminals. Two models are sold: one that allows three terminals to be
simultaneously connected, and one that doesn't. I picked the former, as
hazard lights are just left and right blinkers on at the same time.
🛒 BOM += e-bike blinker switches
Image: A pair of e-bike blinker switches. One of them has its three copper
terminals exposed.
#### Brake it apart
The brake is a little trickier and as of the time of writing I have one
idea but have absolutely no idea how it would work out in the physical
world.
Image: Diagram of the location and schematics of the brake switches
This design taps into movements of the brake handle but not anywhere
further in the braking mechanism, e.g. brake pads. This is because brakes
are a key part to bike safety, and it's stupid to tamper it just for
a braking indicator.
It comprises of four metal contacts: two on the left, two on the right. Of
each two, one is on the hinge and remains stationary relative to the
bicycle, but the other is somewhere on the moving part, the handle. They
are normally closed, forming an electrical connection. When you pull back
the handle to engage the brake though, they are forced apart, breaking the
continuity. The two pairs are wired in series and act like a single
switch. When it is open, it means at least one brake is engaged, which is
good enough since we don't care which.
Sure, ugly hack, but having read Arduino forum threads like
[this](https://forum.arduino.cc/t/interfacing-with-a-bicycle-sensing-braking-solved/72661)
it actually isn't _the_ ugliest one, while in my reach in terms of
afforability.
Problems:
- I can't think of a way to install them other than superglue
- Rainwater can interfere with the connection, for example shorting a pair
of contacts that are supposed to be open
- The location of contacts have to be super precise
- Exposed wires are a potential cosmetic imperfection (shut up your bike
isn't beautiful anyway)
🛒 BOM += tiny metal contact plates? is that a thing?
📋 TODO: install this to prove I'm not an idiot
#### WhereTheFuck™
WhereTheFuck™ (WTF) is a bleeding-edge, innovative and disruptive
technology which augments and accelerates your vehicle retrieving
experience with audiovisual cues.
OK, it's just a regular bike finder. The components are an RF receiver and
a transmitter at 433MHz, and a LED-beeper combo, which I will show later.
🛒 BOM += RF TX/RX kit
I am not a radio expert, so what I bought is this kit. It's a cute
[OOK](https://en.wikipedia.org/wiki/On%E2%80%93off_keying) RF receiver
module capable of detecting four programmable signals, which happens to be
the number of keys the transmitter has. The transmitter is just your
average garage door opener.
Image: Left to right: four-key RF transmitter with antenna extended, RF
receiver as a small yellow PCB with 7 bent pin headers.
Looks like I can attach an antenna to the RX module too, so here's a bunch
of them I ordered a few weeks later.
Image: 10 RF antennae in plastic packaging. They look like coils with tails and
are made of copper.
📋 TODO: install, find out how effective it is
### Output Peripherals
#### Crystal clear
Sure, we're able to calculate our speed and mileage now, but of course we
will need to display it somehow. For this purpose we will use an LCD.
I have one lying around but it's one of those expensive 12864 models,
which is best for complex graphics and a complete overkill for showing
a bunch of numbers. So I opted for a downgrade, i.e. 1602, which can only
draw characters only (of course, there are hacks which let you draw custom
pixmaps. we won't touch them here)
🛒 BOM += 1602 LCD
Image: An LCD with soldered pins
#### Blinkers, again (also stop lamp)
On one hand, I could reuse the blinkers for my previous project,
Bikeblinkers. They are working perfectly fine; they're just four sets of
yellow LEDs. I even spent hours designing a 3D printed case, which in the
end was never gestated into the physical world, but I feel it would be
a pity if no one saw it, so here's a picture.
Image: FreeCAD model of an enclosure for blinkers and a stop lamp
However I want something big. No, not physically, but it's going to amaze
everyone.
🛒 BOM += LED strips
Look at these bright boys!
Image: 1 meter long red LED strip resting on my lap. It's drawing 654mA at
12.0V from my DC power supply and hella bright
I bought a red strip for the stop lamp and a yellow one for blinkers, one
meter each which is a plenty amount. Given I cut them in appropriate
lengths, they don't draw much more power than the discrete LEDs do.
#### WhereTheFuck™
My idea for WhereTheFuck™ is, I press a button on my transmitter, and
something on my bicycle flashes and/or makes a loud noise. That is
basically my patent claim. How novel.
I bought this thingie, which is basically two LEDs and a (really loud)
buzzer, and it fits in a 22mm hole designed for industrial pushbuttons.
When fed 12V it happily beeps on and off thanks to its internal RC
circuitry (it's visible from the holes).
Image: Buzzer. It's a black plastic cylinder with a transluscent red cap.
Image: The red lid is removed to reveal two LEDs and a buzzer.
## Software
### Oh boy, avr-gcc
That's right y'all, it's official: I wrote C for AVR. Embedded
programming. One more line on my resume I guess?
Oh, you're asking me if it's enjoyable? You better ask yourself: is it
really possible to enjoy C? Is it in all plausibility an attainable
outcome that I, a mere mortal of flesh and soul, would have the `__asm__
__volatile__` in my command, or possess enough power to conquer the army
of CMOS logic gates? No. To write C one must suffer. To control the
silicon, one must first wrestle it, then tame it.
#### State and global variables
Global variables are gross, but atop 2,048 bytes of memory there are no
rules. I am representing global state in a pile of global variables. One
of them is called `state`. At the time of writing it holds 5 boolean
states in bits [6..2] (imagine the memory saved compared to 5 ints) and
the two least significant bits represent a finite state of four.
```c
uint8_t state = 0;
```
As you might have expected, reading and writing on `state` is a pain. But
AVR C is full of bitwise stuff anyways, so it doesn't really matter if you
define macros wisely.
In my `main.h` I defined the following:
```c
#define BRAKE_BIT _BV(6)
#define BLINKER_L_BIT _BV(5)
#define BLINKER_R_BIT _BV(4)
// ...
```
(`_BV(x)` is macro for `1 << x` which is used to create a bitmask, with BV
standing for Bit Value)
To test a bit, say `BRAKE_BIT`, I need to do:
```c
if (state & BRAKE_BIT) { /*...*/ }
```
And to alter it:
```c
state |= BRAKE_BIT; // set to one
state &= ~BRAKE_BIT; // set to zero
```
#### LCD
#### Stop lamp
Despite the hardware implementation being unproven, this is one of the
easiest features in software. There are only two possibilities:
- braking, and
- not braking
If it is the former, we turn on the red stop lamp. Otherwise, turn it off.
Here's the code:
```c
void braking(void) {
if (PIND & BRAKES && !(state & BRAKE_BIT)) {
state |= BRAKE_BIT;
PORTC |= STOPLAMP;
lcd_print("[BR]", 0x19);
} else if (!(PIND & BRAKES) && state & BRAKE_BIT) {
state &= ~BRAKE_BIT;
PORTC &= ~STOPLAMP;
lcd_clear(0x19, 4);
}
}
```
...where `STOPLAMP` is a macro for `_BV(PC1)`, the assigned pin.
#### Blinker
#### Sleep mode
#### WhereTheFuck™
|