Introduction: DIY E-Kayak Part 2: Oar Controller

I am building the e-bike of kayaks.

So how do you control an e-kayak? With a phone app? With a remote control? All while trying to hold a kayak paddle? I wanted the user interface to feel as natural as possible without removing you from the actual experience of kayaking. This Instructable shows my solution. A subtle, water proof, on oar solution that keeps the user fully engaged. 

If you missed part one, get outta here! Go. Check it out, then come back. It's all about creating the minimum viable product, proving I could create an electric kayak for under $1,000. If you've already seen part one then you're in the right place. This is phase two of my DIY, AI powered, electric kayak (that's a mouthful).

Phase Two

As much as I love getting weird looks on the lake, it was time to get the computer out of my lap and build up the form factor oar hardware! I'll break down the oar cluster electronics and firmware that goes inside this 3D printed housing. And in the next post will go into further detail about designing and testing the 3D printed housing itself.

Check out the full article on my personal blog, and my YouTube channel!

If you want to see more, consider buying me a coffee! Every small donation is like a little boost of motivation!


  1. Feather M0 Basic - Feather M0
  2. NRF24 Transceiver - NRF24
  3. NeoPixel Ring - 1.5" NeoPixel Ring
  4. BNO0855 IMU - BNO085
  5. PMIC - STM6601
  6. Raspberry Pi 4 - RPI 4
  7. Inflatable Kayak - Intex K2 Explorer
  8. LiFePo4 Battery - Aegis 48V battery 
  9. BLDC Motor - Flipsky 160KV Motor
  10. Motor Controller - Flipsky 70A VESC
  11. Waterproof Case - Knock off Pelican Case
  12. Custom Anti-spart Switch
  13. Custom 3D Printed Fin

Step 1: Quick Note on the 3D Printed Oar Housing

Let’s talk real quick about this 3D printed oar cluster housing. My good friend and talented designer, Jordan Godoy created this for me. It took a lot of iterations and we’re still testing and tweaking things. We went with this tubular design for two reasons, one to fit the LED ring and two, water proofing with an O-ring sounded 100 times easier than with a custom shaped gasket.

You can expect another post / video coming soon showing that whole process so make sure to subscribe so you don’t miss out! 

Step 2: System Overview

Overall System

The final system will have two halves, the kayak portion and the oar hardware cluster. The oar cluster acts as the user input / feedback, gathers AI training data and generally look super awesome. The kayak half contains the battery, speed controller and anti-spark switch. The kayak half won't change much from part one except for the addition of a Raspberry Pi. The Pi talks to the oar hardware through an RF transceiver, receiving commands on how to set the motor speed as well as collecting oar positional data.

In the next phase of the project, the Pi will do a lot more but for now, all it does is log the oar positional IMU data.

Oar System

Before talking about the oar electronics and firmware design in detail, let's understand what I need the oar cluster to do.


I want the kayak to have 2 modes, automatic and manual. Automatic mode is what I am calling the e-bike functionality. It will be able to detect when someone is paddling and automatically assist them in speed and direction control. In manual mode, the user can just turn on the motor to a set speed and it will stay on wether they are paddling or not. Both modes will have a low, medium and high speed setting all controlled through a single push button input. 

When in automatic mode, I need the e-kayak to be able to detect when the user is paddling and even what direction they are trying to steer. For this, I am using a positional sensor called an IMU (Inertial measurement unit) which uses a combination of a gyroscope, accelerometer and magnetometer to calculate its roll, pitch and yaw or overall position in space. This is the same sensor used on airplanes or in your iPhone. Eventually I’ll try to create a machine learning model with this positional data in order to detect paddling and steering. For now, I just need the oar firmware to record IMU data at 20Hz.


The last thing I need the oar cluster to do is communicate with the Raspberry Pi that will be sitting on the kayak, controlling the VESC motor controller. To do this, I use a cheap NRF24 radio transceiver chip. This sends the mode and speed data to the Pi and receives battery status / fault info from the Pi. 

LED Ring Indication:

Okay I lied, the last thing I need the oar cluster to do is display the mode, speed, battery info and any faults to the user. For this, I chose to use an RGB neopixel LED ring display. It gets defused by the white 3D printed housing and looks really cool.

Alright, let's break down the oar cluster hardware / firmware.

Step 3: Power / Input Button

The oar cluster has one button to rule them all. This button can turn on the hardware, act as user input and even power everything down. I found this STM6601 PMIC (Power Management IC) on / off controller that handles all this functionality while only drawing micro amps of current! The first button press turns on the electronics. Any button pushes after that register as inputs to the system and a button press longer than 3 seconds powers everything down.

At first I was going to use the IMU to detect tap inputs on the enclosure to act as user input. This would have been interesting but there were some major limitations with this approach. One, I think it would be super annoying to have it accidentally trigger any time the oar gets bumped and two, I realized I didn't have a power button for the oar electronics. So, after some research, I found this PMIC chip that allowed me to use only one button for all this functionality.

This is one of my favorite features of the build so far and I am going to create a breakout board of this chip for all my future projects! Keep an eye out on my Tindie store if you want this on your own projects!

Once on, the button can cycle the kayak through it's two “modes” with a double click and cycle speeds with a single click.

Step 4: RF Transceiver

For the communication between the kayak Raspberry Pi and the oar hardware cluster, I used a NRF25 module. This was surprisingly difficult to get up and running on the Raspberry Pi. First off, there are a handful of R-Pi libraries for this device and not all of them work well.

I had the best luck with the pyRF24 library. After spending a frustrating afternoon getting acquainted with this library and little RF transceiver, I learned a few tips and tricks.

For the life of me, I could not get the NRF24s to acknowledging each other during the Tx. Most NRF24 example code uses 10Mz baud for the SPI bus. But Lowering this to 4Mhz got them (mostly) communicating. 

I read that putting bypass caps on the NRF24 power input can also help with communication dropouts. After plugging in my IMU and LED ring onto the same bus, I did start to see more coms drops. Adding a 100uF bypass cap on the power rail seemed to get me the rest of the way. 

One other piece of advice is to install the NRF24 library in a virtual python environment. I did not do this at first and for some reason I could only run the Python code using sudo. I tried a bunch of things but could only get it to run without sudo after re-installing in a virtual environment.

Step 5: IMU Position Data

The IMU was extremely easy to get running with Adafruit's libraries. It probably took me ten minutes. Right now I chose to log roll, pitch, yaw, raw acceleration and raw gyroscope data. The IMU can report lots of other information like step counts, shake detection and tap detection. I did test the tap detect when I was going to use tapping as user input and it worked surprisingly well. It is suppose to detect which axis the tap occurred on and this was not very accurate.

I think reporting the IMU data at 20Hz will be fast enough to give my future ML models the best chance at working. The oar cluster RTOS first priority is ensuring this data get's sent out at 20Hz.

Step 6: LED Ring

I realized I need some sort of feedback for the user to know what speed / mode the kayak is running in. So I got my hands on a neopixel ring from Adafruit. This allowed me to get creative with how I display information. I created different animations for various scenarios like connecting, faults and status. During normal operation, one half of the LED ring acts as a speed indicator and the other half reports the battery level. 


While establishing a connection to the kayak Raspberry Pi, three green LEDs will circle around the ring. If it does not establish connection within 8 seconds, a coms fault will trigger.


Once connected, the ring will flash green before moving into the startup animation.


After establishing connection, the speed and battery indication halves will ramp up to 100% then back down to 0% before displaying the current true speed and battery level.

Speed / Battery:

This is the main display when the kayak is under normal operation. The speed and battery level are displayed on each half of the ring at one time. The LED ring has 12 total LEDs so 6 are used for speed and 6 are used for battery level. There are three speed settings, slow, medium and high. The battery level is displayed based on battery percentage from red (0%), yellow (50%) to green (100%).


The kayak will always default into automatic mode on boot. To switch modes the user double clicks on the button input. When a mode change is triggered, the LED ring will quickly flash blue twice.


Any fault, wether it be motor related or coms related will appear on the LED ring as the whole ring slowly flashing red.

Step 7: Firmware Deep Dive

I absolutely love to make things harder for myself so I decided to use an RTOS for the first time on this project. I want this project to teach me new things and I’ve been wanting to experiment with an RTOS for a long time. While this would have worked without it, I do think it made the firmware less complex and better organized. It also helped guarantee I am getting oar positional data from the IMU at 20Hz which I think will be vital for training my ML model down the road.

I wen't through a lot of debugging to get the RTOS humming along smoothly. Once I figured out I had my priorities backward... it still didn't work. I had to monitor task stack sizes, optimize RAM usage and ultimately re-design my whole LED ring driver task (More on that later).

RTOS Breakdown:

The RTOS firmware is broken down into three main stages, input tasks, processing tasks and output tasks. My key constraints for the RTOS were to guarantee I receive IMU at 20Hz, read user inputs without lag and update the LED ring display without glitching or lag.

Input tasks: 

The final version of the RTOS has three input tasks. An RF receiver, an IMU reader and a button reader task.

RF Receiver Task:

This task reads RF data streaming from the kayak Raspberry Pi at 10Hz. The Pi sends motor battery updates and any motor fault information.

IMU Reader Task:

The IMU task reads roll, pitch and yaw data from the IMU. It also reads raw accelerometer and gyroscope data. All this reads at 20Hz intervals.

Button Reader Task:

The button input task debounces the input button and monitors for single or double button clicks. These get forwarded on to command motor speeds and kayak modes.

All this data gets pipped through the RTOS using queues. 

processing Tasks:

Two processing tasks handle the main logic for the oar cluster firmware. They determine the desired mode / speed of the kayak and provide indication information to the LED driver task like faults, battery levels and connection status.

State manager Task:

This task takes in button information from the button reader task. The button reader task pushes button inputs into a queue with a value of 1 for single click and 2 for double click. The state manager reads out of this queue and determines the current desired mode / speed setting.

The state manager task also reads in RF messages from the RF Receiver task. It will trigger a coms drop out fault if no new RF messages are received within 8 seconds. If RF messages are being received on time, the state manager forwards them on to the output processor.

Output Processor Task:

The output processor task takes in RF messages from the Raspberry Pi, speed / mode information and reads the oar battery voltage level. It determines what animation needs to be sent to the LED driver task wether that be a fault, speed, mode or battery update animation. The output processor packages these animations to the LED driver task by sending a pixel map struct.

Motor battery from the Raspberry pi is compared to the oar battery voltage and the lowest of the two is reported by the LED ring. Right now, I can only rely on battery voltage to estimate the state of charge. Because of this, I map each batteries voltage to it's cell chemistry discharge curve in order to estimate a percent state of charge as accurately as possible. For example, my motor battery is a 24V LiFePo4. These batteries do not have a linear discharge curve. The battery will hold near 26V for most of it's lifecycle before quickly dropping off to a minimum 20V.

Output Tasks

The output tasks include an RF transmitter task and an LED driver task.

RF Transmitter Task:

This task reads the IMU data out of a queue forwarded on from the IMU reader task. It also reads the current speed / mode setting out of a queue from the state manager task. The RF transmitter task packages all this data together and sends it to the Raspberry Pi waiting on the kayak.

LED Driver Task:

The LED driver task acts as the primary feedback to the user. It displays connection status, fault conditions, speed and battery level. During normal operation, one half of the LED ring displays the current speed and the other half displays the battery level.

Step 8: Issues and Debugging

RTOS Issues: 

At first, the RTOS could not run every task together. To debug, I focused on general RAM usage and priority issues. I was allocating way to much RAM to each task, had my priorities flipped and needed to combine some tasks / queues to remove redundancies eating away my 32K RAM. After I got these issues dialed in, I still had huge delays in triggering different animations on time. 

LED Driver

The biggest issue I had with the RTOS implementation was how I was driving the LED ring. I was looping through all 12 pixels while setting delays to create the animations. I was trying to call the LED driver task at 25ms periods and my delays on the LED ring loops were on the order of 50ms. So during each delay of my animation loops, the task would interrupt itself and the overflows would build and build causing massive delays. 

Re-thinking how to properly drive the ring with an RTOS, I turned to LCD display concepts where they use pixel RAMs fed from FIFOs. The LCD drivers reads from the pixel RAM at a fixed rate to determine the next frame of pixels to set. It then can work on setting all the right pixels in the background until the next pixel RAM reading. For my 12 LED ring “pixel ram” I created a struct to hold a 2X2 array of 12 sets of 12 colors. The 12 colors in one "set" serve as one “frame” for the ring. Each time the LED driver task is called, the 12 LEDs are updated to the next frame. This continues a total of 12 times before repeating. The struct also contains a delay value. Each time the LED driver task is executed, it checks if the delay time has been met before turning on the next frame of LEDs. 

Next, I needed to have some animations block and others not. For example, I only want the connecting animation to run until we establish a connection and then stop to run the connected animation. But I needed to make sure the connected animation completes before allowing the battery / speed update animations to run. For this, I updated the led pixel ram struct to contain a block counter. The LED driver task will not allow any new animations to run and continue to repeat the 12 frame cycle until the blocking counter hits 0. 


Another major issue I had was not properly accessing the NRF24 radio which is a shared resource between the receive and transmit tasks. After noticing delays / hanging when trying to run these two tasks at the same time, I realized this was a shared resource and implemented a simple mutex for accessing the radio. This solved my hanging issues.

The final problem I discovered is how long it takes the NRF24 to timeout when trying to transmit to a radio that is not powered on. If no radio is on to receive its transmission, I found that the NRF24 takes about 30ms to finally give up on my message. When the other radio is on, all my tasks execute on time but if not, there was a small visual delay in the animations. This was partly due to me using the task execution time to calculate my animation delays and not the absolute program tick time. I updated my LED driver task to use absolute time and the noticeable delay went away. I could look into reducing the NRF24 transmission timeout or disabling the auto acknowledge between transceivers but honestly I don’t care that much. If one radio is off, the system should not be running anyway and all the oar firmware needs to do is flash red to report a coms fault.

Step 9: Going Forward

At this point, I was hoping to have the Raspberry Pi also controlling the VESC motor controller based on the oar cluster commands but progress is progress.

The next goals of this project are to fully test the oar 3D printed housing, shrink the oar electronics to fit inside and get the Raspberry Pi commanding my motor!

Thanks for reading! Subscribe to my Instructables and Youtube channel to stay up to date on my progress!

Happy Hacking