Skip to content

Commit

Permalink
Further pulse train streaming preparation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Onwrikbaar committed Jan 12, 2025
1 parent 0532671 commit 4f31283
Show file tree
Hide file tree
Showing 11 changed files with 1,646 additions and 1,583 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ This project is intended for experienced electronics hobbyists and professionals
NeoDK is _NOT_ a ready-for-play e-stim box. It is a minimal viable design (MVD) of a powerful and highly efficient electrostimulation device that is not limited by 2-pole 'channels' or strictly TENS-like waveforms. Schematic and board CAD files are included, but to get a working device involves some soldering. To get more than the basic functionality requires programming in C.

### How to get a working NeoDK
By far the easiest way to build NeoDK, is to order the SMD-populated PCB from JLCPCB (using the files in directory JLCPCB_prod) and buy the six through hole components separately. Soldering the through hole components onto the board is quite easy.
By far the easiest way to build NeoDK, is to order the SMD-populated PCB from JLCPCB (using the files in directory JLCPCB_prod) and buy the six through hole components separately. Soldering the through hole components onto the board is easy.

### Licensing
NeoDK's electronics and firmware can do things no commercially available e-stim box comes close to. If this appeals to you but you would like to have more features than NeoDK offers, by all means use NeoDK as the foundation for your own design - while observing this project's [Licence](LICENSE.txt). Regarding licensing for commercial / non-open source purposes, please contact the author.
Expand All @@ -55,4 +55,8 @@ NeoDK's electronics and firmware can do things no commercially available e-stim
"A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system." -- John Gall

## NeoDK in the media
Joanne's Reviews' always informative and entertaining YouTube [E-stim Livestreams](https://www.youtube.com/@JoannesReviews/streams) recently featured an [item about NeoDK](https://youtu.be/giEaDksRmh0?t=1460).
Joanne's Reviews' always informative and entertaining YouTube [E-stim Livestreams](https://www.youtube.com/@JoannesReviews/streams) recently featured an [item about NeoDK](https://youtu.be/giEaDksRmh0?t=1460).

## Other OSHW electrostimulation projects
- [zc95](https://github.com/CrashOverride85/zc95). A feature-rich four channel machine.
- Noisy Cricket (soon to be published). An incredibly compact device primarily aimed at BDSM play.
2 changes: 1 addition & 1 deletion firmware/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ LIB_FILES += -lc -lnosys -lm
default: neodk_g071

libmao.a:
$(CC) $(CFLAGS) -I$(MAO_LIB_INC) -I$(SEGGER) -c $(MAO_LIB_SRC)/*.c
$(CC) $(CFLAGS) -I$(MAO_LIB_INC) -I$(PROJ_DIR_INC) -I$(SEGGER) -c $(MAO_LIB_SRC)/*.c
$(AR) maolib/$@ *.o
rm *.o

Expand Down
3,054 changes: 1,528 additions & 1,526 deletions firmware/build/neodk_g071.hex

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion firmware/inc/pattern_iter.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ typedef struct {

void PatternIterator_init(PatternIterator *, PatternDescr const *);
void PatternIterator_setPulseWidth(PatternIterator *, uint8_t width_µs);
bool PatternIterator_scheduleNextBurst(PatternIterator *);
char const *PatternIterator_name(PatternIterator *);
bool PatternIterator_done(PatternIterator *);
bool PatternIterator_getNextBurst(PatternIterator *, Burst *);

#endif
1 change: 1 addition & 0 deletions firmware/inc/pulse_train.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bool PulseTrain_isValid(PulseTrain const *, uint16_t sz);
void PulseTrain_clearDeltas(PulseTrain *);
void PulseTrain_setDeltas(PulseTrain *, int8_t delta_width_¼_µs, int8_t delta_pace_µs);
uint16_t PulseTrain_amplitude(PulseTrain const *);
uint8_t PulseTrain_phase(PulseTrain const *);
uint8_t PulseTrain_pulseWidth(PulseTrain const *);
Burst const *PulseTrain_getBurst(PulseTrain const *, Burst *);
Deltas const *PulseTrain_getDeltas(PulseTrain const *, uint16_t sz, Deltas *);
Expand Down
39 changes: 39 additions & 0 deletions firmware/maolib/inc/ptd_queue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* ptd_queue.h
*
* NOTICE (do not remove):
* This file is part of project NeoDK (https://github.com/Onwrikbaar/NeoDK).
* See https://github.com/Onwrikbaar/NeoDK/blob/main/LICENSE.txt for full license details.
*
* Created on: 12 Jan 2025
* Author: mark
* Copyright 2025 Neostim™
*/

#ifndef INC_PTD_QUEUE_H_
#define INC_PTD_QUEUE_H_

#include <stdbool.h>
#include <stdint.h>

#include "pulse_train.h"

typedef struct _PtdQueue PtdQueue; // Opaque type.

#ifdef __cplusplus
extern "C" {
#endif

// Class method.
PtdQueue *PtdQueue_new(uint16_t nr_of_descriptors);

// Instance methods.
void PtdQueue_bytesFree(PtdQueue *, uint16_t[2]);
bool PtdQueue_addDescriptor(PtdQueue *, PulseTrain const *, uint16_t sz);
void PtdQueue_delete(PtdQueue *);

#ifdef __cplusplus
}
#endif

#endif
Binary file modified firmware/maolib/libmao.a
Binary file not shown.
2 changes: 1 addition & 1 deletion firmware/src/bsp_stm32g071.c
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ void BSP_init()

char const *BSP_firmwareVersion()
{
return "v0.48-beta";
return "v0.49-beta";
}


Expand Down
61 changes: 48 additions & 13 deletions firmware/src/pattern_iter.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,37 @@ static uint8_t const *getNextPattern(PatternIterator *me, uint16_t *nr_of_pulses
return pd->pattern[elcon_nr];
}


static bool getNextBurst(PatternIterator *me, Burst *burst)
{
if (PatternIterator_done(me)) return false;

uint8_t const *elcon = getNextPattern(me, &burst->nr_of_pulses);
burst->elcon[0] = elcon[0];
burst->elcon[1] = elcon[1];
burst->pulse_width_¼_µs = me->pulse_width_micros * 4;
burst->pace_µs = me->pattern_descr->pace_µs;
return true;
}


static uint8_t getPhase(uint8_t const elcon[2])
{
M_ASSERT((elcon[0] & elcon[1]) == 0); // Prevent shorts.
return (elcon[0] & 0x5) ? 0 : 1;
}


static bool startBurst(Burst const *burst, Deltas const *deltas)
{
if (Burst_isValid(burst)) {
return BSP_startBurst(burst, deltas);
}

BSP_logf("Invalid burst\n");
return false;
}

/*
* Below are the functions implementing this module's interface.
*/
Expand All @@ -67,26 +98,30 @@ void PatternIterator_setPulseWidth(PatternIterator *me, uint8_t width_µs)
}


char const *PatternIterator_name(PatternIterator *me)
bool PatternIterator_scheduleNextBurst(PatternIterator *me)
{
return Patterns_name(me->pattern_descr);
Burst burst;
if (getNextBurst(me, &burst)) {
if (burst.pulse_width_¼_µs > MAX_PULSE_WIDTH_¼_µs) {
burst.pulse_width_¼_µs = MAX_PULSE_WIDTH_¼_µs;
}
// BSP_logf("Pulse width is %hu µs\n", burst.pulse_width_¼_µs / 4);
burst.phase = getPhase(burst.elcon);
Deltas deltas = {0};
return startBurst(&burst, &deltas);
}

return false;
}


bool PatternIterator_done(PatternIterator *me)
char const *PatternIterator_name(PatternIterator *me)
{
return me->nr_of_reps == 0;
return Patterns_name(me->pattern_descr);
}


bool PatternIterator_getNextBurst(PatternIterator *me, Burst *burst)
bool PatternIterator_done(PatternIterator *me)
{
if (PatternIterator_done(me)) return false;

uint8_t const *elcon = getNextPattern(me, &burst->nr_of_pulses);
burst->elcon[0] = elcon[0];
burst->elcon[1] = elcon[1];
burst->pulse_width_¼_µs = me->pulse_width_micros * 4;
burst->pace_µs = me->pattern_descr->pace_µs;
return true;
return me->nr_of_reps == 0;
}
2 changes: 1 addition & 1 deletion firmware/src/pulse_train.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ PulseTrain *PulseTrain_init(PulseTrain *me, uint8_t seq_nr, uint32_t timestamp,
bool PulseTrain_isValid(PulseTrain const *me, uint16_t sz)
{
// TODO More checks.
return sz >= 14 && sz <= 16;
return sz >= 14 && sz <= sizeof(PulseTrain);
}


Expand Down
58 changes: 20 additions & 38 deletions firmware/src/sequencer.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@
#include <string.h>

#include "bsp_dbg.h"
#include "bsp_mao.h"
#include "debug_cli.h"
#include "app_event.h"
#include "attributes.h"
#include "pattern_iter.h"
#include "pulse_train.h"
#include "ptd_queue.h"

// This module implements:
#include "sequencer.h"
Expand Down Expand Up @@ -136,21 +135,28 @@ static bool startBurst(Burst const *burst, Deltas const *deltas)
}


static void handleDescriptor(Sequencer *me, AOEvent const *evt)
static void execPulseTrain(PatternIterator *pi, PulseTrain const *pt, uint16_t sz)
{
uint16_t sz = AOEvent_dataSize(evt);
// TODO Check size.
PulseTrain const *pt = (PulseTrain const *)AOEvent_data(evt);
PulseTrain_print(pt, sz);
// Scale amplitude 0..255 to 0..8160 mV (for now).
BSP_setPrimaryVoltage_mV(PulseTrain_amplitude(pt) * 32);
PatternIterator_setPulseWidth(&me->pi, PulseTrain_pulseWidth(pt));
PatternIterator_setPulseWidth(pi, PulseTrain_pulseWidth(pt));
Burst burst;
Deltas deltas;
startBurst(PulseTrain_getBurst(pt, &burst), PulseTrain_getDeltas(pt, sz, &deltas));
}


static void handlePtEvent(Sequencer *me, AOEvent const *evt)
{
PulseTrain const *pt = (PulseTrain const *)AOEvent_data(evt);
uint16_t sz = AOEvent_dataSize(evt);
if (PulseTrain_isValid(pt, sz)) {
PulseTrain_print(pt, sz);
execPulseTrain(&me->pi, pt, sz);
}
}


static bool queueIncomingDescriptor(Sequencer *me, AOEvent const *evt)
{
bool stat = PulseTrain_isValid((PulseTrain const *)AOEvent_data(evt), AOEvent_dataSize(evt))
Expand All @@ -160,9 +166,9 @@ static bool queueIncomingDescriptor(Sequencer *me, AOEvent const *evt)
}


static bool execQueuedDescriptor(Sequencer *me)
static bool handleQueuedDescriptor(Sequencer *me)
{
bool stat = EventQueue_handleNextEvent(&me->ptd_queue, (EvtFunc)&handleDescriptor, me);
bool stat = EventQueue_handleNextEvent(&me->ptd_queue, (EvtFunc)&handlePtEvent, me);
Sequencer_notifyPtQueue(me);
return stat;
}
Expand All @@ -174,7 +180,7 @@ static void *stateStreaming(Sequencer *me, AOEvent const *evt)
{
case ET_AO_ENTRY:
BSP_logf("Sequencer_%s ENTRY\n", __func__);
execQueuedDescriptor(me);
handleQueuedDescriptor(me);
break;
case ET_AO_EXIT:
EventQueue_clear(&me->ptd_queue);
Expand All @@ -199,7 +205,7 @@ static void *stateStreaming(Sequencer *me, AOEvent const *evt)
break;
case ET_BURST_EXPIRED:
// BSP_logf("Burst expired\n");
if (execQueuedDescriptor(me)) break;// Process next burst, if present.
if (handleQueuedDescriptor(me)) break;// Process next burst, if present.
return &stateIdle; // Otherwise transition.
default:
return stateCanopy(me, evt); // Forward the event.
Expand Down Expand Up @@ -279,38 +285,14 @@ static void *statePaused(Sequencer *me, AOEvent const *evt)
}


static uint8_t getPhase(uint8_t const elcon[2])
{
M_ASSERT((elcon[0] & elcon[1]) == 0); // Prevent shorts.
return (elcon[0] & 0x5) ? 0 : 1;
}


static bool scheduleNextBurst(Sequencer *me)
{
Burst burst;
if (PatternIterator_getNextBurst(&me->pi, &burst)) {
if (burst.pulse_width_¼_µs > MAX_PULSE_WIDTH_¼_µs) {
burst.pulse_width_¼_µs = MAX_PULSE_WIDTH_¼_µs;
}
// BSP_logf("Pulse width is %hu µs\n", burst.pulse_width_¼_µs / 4);
burst.phase = getPhase(burst.elcon);
Deltas deltas = {0};
return startBurst(&burst, &deltas);
}

return false;
}


static void *statePulsing(Sequencer *me, AOEvent const *evt)
{
switch (AOEvent_type(evt))
{
case ET_AO_ENTRY:
BSP_logf("Sequencer_%s ENTRY\n", __func__);
setPlayState(me, PS_PLAYING);
scheduleNextBurst(me);
PatternIterator_scheduleNextBurst(&me->pi);
break;
case ET_AO_EXIT:
BSP_logf("Sequencer_%s EXIT\n", __func__);
Expand All @@ -322,7 +304,7 @@ static void *statePulsing(Sequencer *me, AOEvent const *evt)
// BSP_logf("Burst started\n");
break;
case ET_BURST_EXPIRED:
if (! scheduleNextBurst(me)) {
if (! PatternIterator_scheduleNextBurst(&me->pi)) {
CLI_logf("Finished '%s'\n", PatternIterator_name(&me->pi));
return &stateIdle; // Transition.
}
Expand Down

0 comments on commit 4f31283

Please sign in to comment.