Bar Code India · Connectivity
Automatic Toll Collection Reader
A roadside RFID reader that identifies tags on moving vehicles. The system split
cleanly in two: the RF and tag-decoding side, and the connectivity side. I owned
the connectivity side.
My scope
A teammate owned the STM32 reader firmware — driving the RF frontend
and parsing raw tag reads into structured JSON. My work began where that JSON arrived:
an ESP32 that ingested the stream, parsed it, buffered it, and delivered it
over the network. Everything below is the IoT half of the product.
Getting the data out
The device had to work at sites with very different infrastructure, so no single
transport could be assumed. It supported raw sockets,
MQTT and AWS for the uplink, carried over
WiFi, GSM or Ethernet, plus
Bluetooth for local access.
Configuration, two ways
-
Over the network. The device accepted commands at runtime, so a deployed
unit could be reconfigured remotely without anyone visiting it.
-
Over a WiFi access point. The ESP32 could bring up its own AP and serve a
configuration webpage, so a technician on site could set the device up from a phone or
laptop with nothing else installed.
The hard part: keeping up, and staying up
Two problems dominated the work, and both came down to the fact that tags arrive whether or
not the network is ready for them.
-
Ingest vs. storage. Tag reads came in continuously and had to be held
without loss while the uplink was busy, reconnecting or slow. I handled this with
buffering between the ingest path and the network path, decoupling the
two so a stalled network couldn’t stall reception.
-
Concurrent connectivity. Network handling ran as
multiple RTOS tasks in parallel, and which tasks were running was
itself driven by the active configuration — the task set changed with the selected
transport and protocol rather than being fixed at build time.
-
GSM. Cellular was the least predictable path, so I built it as an explicit
state machine that consumed incoming commands and transitioned on network
state changes, rather than trying to handle modem behaviour inline.
Results
In test scenarios the pipeline sustained roughly 200 tag reads per second
at about 99% read success.
- ESP32
- C
- MQTT
- AWS
- Sockets
- WiFi
- GSM
- Ethernet
- Bluetooth
- RTOS Tasks
- State Machine
- Ring Buffering
- JSON
- Web Config
Bar Code India · BLE Platform
Low-Power BLE Beacon & Gateway
A BLE beacon and gateway system for zonal tracking, mobile app integration and wireless
asset telemetry. It was the first thing I took from a blank page all the way to a finished
product — and the first project that was entirely mine.
My scope
All of it. I wrote the beacon firmware and the gateway firmware,
with ESP32-based IoT support behind the gateway. No one else was on it, which
meant every decision — and every problem — was mine to work through.
Why Zephyr
Honestly, it followed from the silicon rather than from an RTOS bake-off. The
nRF52 was the platform, Nordic’s nRF Connect SDK is built
on Zephyr, so committing to the SoC meant committing to Zephyr. That turned
into the most valuable part of the project: I had to learn a real RTOS properly rather than
staying in a bare-metal loop.
The constraint that shaped everything
The beacon ran from a coin cell. That single fact drove the design —
how often the device could wake, how long it could stay on air, and how much work it could
afford to do while awake. Power stopped being something to optimise at the end and became
the thing the design had to be built around from the start.
What was new to me
- First end-to-end product. Not a module or a feature inside someone else’s system — the whole path from nothing to something that worked and shipped.
- First time on Nordic silicon and the nRF Connect SDK.
- First real RTOS work — Zephyr’s tasks, timing and driver model.
- First time owning delivery, which is a different skill from writing firmware: deciding what was good enough, what to fix, and what to leave.
Why it still matters to me
Everything I did afterwards — the asset-tracking gateway, the wearable, the ESP32 sensor
hubs at Frinso — leaned on what this project forced me to learn. It’s the point where
I stopped being someone who could write firmware and became someone who could deliver a device.
- nRF52
- Zephyr RTOS
- nRF Connect SDK
- BLE
- Beacon
- Gateway
- ESP32
- Coin Cell
- Low Power Modes
- C
Bar Code India · Localization
BLE Asset Tracking Gateway
Knowing roughly where something is inside a building, without wiring anything to it.
Static BLE beacons around an office, and a gateway that turned what it heard from them into a
zonal position for each tracked asset. Like the beacon and gateway work before it, this one
was entirely mine.
My scope
The whole project, on my own again. It followed naturally from the beacon and gateway firmware
— I already knew that hardware and that RTOS, so this was the first time I got to build
something on top of work I had already done rather than starting cold.
How it worked
Beacons were fixed at known points around the office. Rather than trying to
track anything to a precise coordinate, the goal was zonal — keeping a
live picture of which area an asset was in. The gateway used the signal strength
it observed from the beacons to place assets, holding roughly 5-metre accuracy
for static assets.
The position itself came out of trilateration: convert each beacon’s
RSSI into a distance, treat that distance as a radius, draw a circle around
each beacon, and take the point where the circles intersect as the asset’s position. The
~5 m figure wasn’t a specification handed to me — it was what that method
actually delivered once I tested it, and the RSSI-to-distance conversion is where most of the
error lives.
That distinction mattered: zone-level answers were what the use case actually needed, and RSSI
is honest about being an approximation. Chasing precision it can’t deliver would have
been the wrong engineering call.
Angle of Arrival / Angle of Departure — a proposal, not a build
RSSI works acceptably for assets that sit still; assets that move are a harder problem.
I researched AoA and AoD as the direction that could address
that, and took the idea forward for approval.
It stayed in the concept phase. I want to be clear about that — I
investigated it and proposed it, but I never built it. What it does show is that I was looking
past what the system already did and at what it would need next.
What I took from it
Less a new technology than a way of working. Running a project by myself — deciding the
approach, testing it, finding out what accuracy it actually gave, and judging whether that was
good enough for the job — is a different skill from writing firmware to someone else’s
spec. This is where that became something I could rely on rather than something I was
attempting for the first time.
- BLE
- RSSI
- Trilateration
- Static Beacons
- Zonal Positioning
- Gateway
- Asset Tracking
- C
Bar Code India · Where I started
Wearable Pedometer
My first project at the company, and the one that got me into embedded sensor work. The BLE
side and the mobile app had already been designed, which made it a good place to learn:
I could concentrate entirely on the part I didn’t know yet — getting a real,
trustworthy answer out of a noisy sensor.
My scope
Sensor integration. Everything between the accelerometer and a step count
worth reporting. I deliberately treated this as a learning project rather than pretending it
was more than it was — the surrounding system already existed, and I used that as a
chance to go deep on one thing.
Starting from the data, not from a library
I began by walking, myself, and plotting what the accelerometer produced.
Looking at those plots is where the actual understanding came from: you can see the shape a
walking gait makes in the data, and once the pattern is visible you can decide what counts as
a step. From there I set thresholds to pick real steps out of the signal.
Then I checked myself against things that already worked — phone step-counting
apps and wearable watches — so that I had an external reference rather than only
my own opinion of whether the count looked right.
The mistake I avoided: tuning it to one person
A threshold that works beautifully on the engineer who wrote it is a threshold that works on
exactly one person. Two things came out of realising that:
-
Wear position changes everything. I tested the device in several positions
— hanging at the neck, worn on the wrist — because the same walk produces a
different signal depending on where the sensor sits.
-
People walk differently. So I asked colleagues around the office to each
walk 5,000 steps wearing it. That gave me a spread of real gaits to compare,
and let me choose a threshold that held up across all of them instead of one tuned to me.
That data set is what got the design to roughly 96% step-detection accuracy,
with activity streamed live to the app to support fatigue and activity monitoring.
Where it ended up
It was developed and tested, but never sold. I’m including it anyway,
because the part that mattered to me was the method rather than the product — and the
method is what carried into everything sensor-facing I did afterwards.
What I took from it
Sensor integration as a discipline: look at the raw data before writing the algorithm, validate
against an independent reference, and assume your own body, desk and habits are not a
representative test set. Everything sensor-facing I did afterwards started from those habits.
- Accelerometer
- BLE
- Signal Analysis
- Thresholding
- Wearable
- Field Testing
- C
CSU Chico · Advanced Embedded Systems
Industrial Hazard Sensor & IoT Dashboard
A handheld hazard monitor on a Tiva TM4C123: six sensor and peripheral drivers,
a menu-driven OLED interface, and readings published onward to an MQTT dashboard. The interesting
part isn’t the sensor list — it’s what it takes to make readings from cheap
sensors trustworthy.
The driver layer
Six modules, each with its own header and a deliberately small API:
OLED (I2C framebuffer), gas (I2C),
MPU6050 accelerometer (I2C), DHT11 temperature and humidity
(single-wire), sound (ADC) and buttons (GPIO). They sit on
TivaWare’s peripheral layer, so the work was in the device protocols and the module
boundaries rather than in re-writing I2C from scratch.
The single-wire sensor was the hard one
The DHT11 has no bus — it talks over one pin with a timing-coded protocol, so the driver
has to drive the line low, release it, and then measure pulse widths in
microseconds. I calibrate the delay loop from the actual system clock at init
rather than hard-coding loop counts, and every wait for a line transition carries a
timeout, so a disconnected or dead sensor degrades the reading instead of
hanging the main loop.
Making cheap sensors trustworthy
-
Warm-up and baseline. The gas sensor is meaningless until it has heated and a
clean-air baseline has been established, so the driver runs a warm-up timer and reports a
readiness percentage — the UI shows calibration progress instead of confidently
displaying nonsense.
-
Filtering. Sound and vibration both run through a first-order IIR low-pass
filter, and the sound channel is averaged over sixteen ADC samples before that. The UI shows
the smoothed value; the raw one stays available underneath.
-
Presence detection. Each I2C device reports whether it actually acknowledged
on the bus, so a missing sensor shows as absent rather than as a plausible-looking zero.
-
Timer rollover. Elapsed time comes from SysTick, which counts down and wraps.
The loop handles the wrap explicitly, so timing stays correct across the rollover instead of
producing one absurd delta each cycle.
Fixed point, everywhere
No floating point in the signal path. Gas concentrations are carried as
ppm scaled by 100, and the low-pass filter is a shift-based accumulator, so
smoothing costs an add and a shift. It keeps the maths honest about precision and keeps the
cost predictable on a part with no FPU.
The interface
A four-state UI — main menu, detail view, gas submenu, gas detail — navigated with
three buttons and wrap-around selection, drawn into an offscreen framebuffer and pushed to the
OLED each frame. Six gases are individually selectable: CO, NO₂, NH₃, CH₄,
H₂ and ethanol.
- Tiva TM4C123
- TivaWare
- Embedded C
- I2C
- ADC
- GPIO
- SysTick
- Bit-banged Protocol
- IIR Filtering
- Fixed Point
- State Machine
- MQTT
- Raspberry Pi
- FastAPI
CSU Chico · VLSI Design & Verification
Hardware Matrix Multiplier in SystemVerilog
Matrix multiplication is the inner loop of DSP, image processing and neural networks. This is
that operation built as hardware: a parameterized M×N by N×P multiplier
over 32-bit unsigned values, designed for throughput rather than for code size.
Two modules
A pipelined multiplier, and a top-level matrix multiplier
that instantiates a whole array of them under FSM control. The matrix dimensions are
parameters, so the same source scales to a different size without being rewritten.
Pipelining: one result per clock
The multiplier uses shift-and-add, and each of the 32 bits becomes its own
pipeline stage — registers between stages carry the partial product,
the shifted multiplicand and a validity flag forward. The cost is latency to fill the
pipeline; the payoff is that once it is full, a finished multiplication emerges
every clock cycle instead of every 32.
Parallelism: every product at once
Nested generate blocks instantiate one independent multiplier per
element-wise product — M×N×P of them. For a 3×3 multiply that is
27 multipliers computing simultaneously, so total time collapses from
27 sequential multiplications to roughly one multiplication plus the accumulation.
The FSM that keeps it honest
Five states — IDLE, LOAD, MULTIPLY, ACCUMULATE, DONE. LOAD latches the
operands into internal registers and fires every multiplier; MULTIPLY waits until all of them
report done; ACCUMULATE sums the partial products along k into each output element; DONE
raises the completion flag.
What pipelining actually bought
The design started with a plain shift-and-add multiplier, where every multiplication ran its
32 cycles end to end. For a 3×3 multiply that came to roughly 260 cycles
of latency, and the timing constraint had to be relaxed to a 5 ns clock — about
1.3 µs per matrix.
Restructuring the multiplier into staged, overlapping execution dropped that to
38 cycles, and let the design close at a far more aggressive
2.4 ns clock — roughly 91 ns per matrix.
Faster clock and fewer cycles compound into about a 6.7× throughput
improvement.
Verification and results
-
Testbench. Reads both operand matrices from text files, drives the design at
a 100 MHz simulation clock in Vivado, waits on
done, and writes the result matrix back
out to a file — so results are checkable rather than eyeballed in a waveform.
-
Timing closed at 416 MHz. A 2.400 ns target period with
+0.011 ns worst negative slack and +0.144 ns hold slack. The
critical path lands in the FSM sequential logic at 2.007 ns.
-
Power. 0.074 W total on-chip, dominated by clocking, with a
59.6 °C thermal margin.
Eleven picoseconds of margin is a thin but genuine pass — the design meets its target
rather than being reported at whatever frequency it happened to reach.
- SystemVerilog
- Pipelining
- Parallel Datapath
- FSM
- Shift-and-Add
- Generate Blocks
- ModelSim
- Vivado
- Xilinx FPGA
- Static Timing Analysis
- RTL Verification
CSU Chico · Secure Computing
Secure Temperature Telemetry over ECIES and TLS
A sensor node reports a temperature reading to a collector. That sounds like the most harmless
packet on the network — which is exactly why it is a good test case. In an industrial
setting, a stream of temperature readings can leak occupancy, production schedules, operating
status and equipment health.
Two layers, on purpose
The payload is protected twice over. TLS, via mbedTLS and X.509 certificates,
secures the transport and authenticates the server. Then an ECIES envelope
independently encrypts the JSON payload itself.
The reason for the second layer is the interesting part: TLS often terminates at a proxy or
load balancer rather than at the application. If that happens — or if TLS is simply
misconfigured — the plaintext is exposed at the boundary. An application-layer envelope
stays sealed all the way to the process that is actually supposed to read it.
What goes on the wire
A reading such as {"type":"sensor","temp":23.4,"unit":"C"} is encrypted into a
structure carrying the ephemeral public key (65 bytes), a
12-byte nonce, the ciphertext, and a 16-byte GCM tag. Those
are serialized in order, Base64-encoded, and sent as a single prefixed line over the TLS
connection. The server reverses it: decode, parse, re-derive the key, verify the tag, recover
the JSON.
The parameters
NIST P-256 for the ECDH key agreement, HKDF-SHA-256
(RFC 5869) to derive the symmetric key from the shared secret, and
AES-256-GCM for authenticated encryption — 32-byte key, 12-byte nonce,
16-byte tag. Both ends use identical KDF parameters, which is what makes the two independently
derived keys agree.
Forward secrecy
A new ephemeral key pair is generated for every message, not per session. If the
server’s long-term private key were later compromised, previously captured telemetry
would still not be recoverable.
Testing the failure paths
The happy path is the easy half. I also corrupted and truncated the Base64, and flipped bytes
inside the serialized blob — and confirmed the server rejected each one with a decode,
parse or decrypt failure and printed no plaintext. That is the test that
actually demonstrates the GCM tag is being enforced rather than merely computed.
Structure, and what I would do next
TLS plumbing and application-layer crypto live in separate modules, so neither has to know how
the other works. Known limits, stated plainly: keys are loaded from PEM files rather than a
secure element, there is no client authentication yet, and performance was only characterised
qualitatively on a laptop — measuring it on constrained embedded hardware is the obvious
next step.
- C
- mbedTLS
- TLS 1.2/1.3
- X.509
- ECDH
- NIST P-256
- HKDF-SHA-256
- AES-256-GCM
- Base64
- TCP Sockets
- OpenSSL
- Linux