Go Down

Topic: Using TC4 to generate a single pulse on SAMD21 (Adafruit Feather M0) (Read 958 times) previous topic - next topic

rhulett

For speed control of an AC motor, I'm trying to create a pulse on a triggering event (a zero crossing). When the zero crossing occurs, I want to calculate a delay period, set CC0 & CC1 per the desired delay & width, and then generate a short pulse using TC4 match counter interrupts.

When the event occurs, I'm trying to setup the CC1 register with my delay count and CC0 with my pulse end count. I then turn on the interrupts, flip the pulse bits when triggered via an interrupt, and then turn off the interrupts until the next event occurs.

I think I'm close but when I run the test code below, the pulse width looks fine, but the delay between my triggering event and the pulse is wildly erratic. I see this when I hook up to pin A5 and trigger my oscilloscope on the leading edge of the pulse-- the pulse on pin 9, while consistent in width, appears at what appears to be completely arbitrary delay intervals.

Code: [Select]

void setup() {
  pinMode(A5, OUTPUT);      // for dev only... rising edge on oscilloscope marks delay start
  pinMode(9, OUTPUT);       // pulse output

 // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV256 |    // Set prescaler to 256, 48MHz/3/256 = 62.5 KHz (16 us)
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency ( MFRQ) mode.
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  enableTc4Interrupts(false);
}

void enableTc4Interrupts(boolean on){
  if(on) {
    // clear the interrupt flags
    REG_TC4_INTFLAG |= TC_INTFLAG_MC0 | TC_INTFLAG_MC1; // TC_INTFLAG_OVF
    // enable TC4 interrupts     
    REG_TC4_INTENSET = TC_INTENSET_MC0 | TC_INTENSET_MC1; // TC_INTENSET_OVF
  }
  else {
    // disable TC4 interrupts
    REG_TC4_INTENCLR = TC_INTENCLR_MC0 | TC_INTENCLR_MC1; // TC_INTENCLR_OVF
  }
}

void loop() {
  int waitValue = 500;

  REG_TC4_COUNT16_CC1 = waitValue*10;         // Set the TC4 CC1 register delay time
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);   
  REG_TC4_COUNT16_CC0 = waitValue*11;         // Set the TC4 CC0 register for the delay time + pulse width
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY); 

  digitalWrite(A5, HIGH);                     // write A5 high as the oscilloscope trigger (delay start)
  enableTc4Interrupts(true);                  // enable the interrupts

  delay(1000);
  digitalWrite(A5, LOW);
  delay(500);
}

// Interrupt Service Routine (ISR) for timer TC4
void TC4_Handler() {
  if (TC4->COUNT16.INTFLAG.bit.MC0 && TC4->COUNT16.INTENSET.bit.MC0) {
    digitalWrite(9, HIGH); 
  }
  if (TC4->COUNT16.INTFLAG.bit.MC1 && TC4->COUNT16.INTENSET.bit.MC1) {
    digitalWrite(9, LOW);
    enableTc4Interrupts(false);
  }
}


I'm wondering if maybe the issue is that I need to zero the TC4 COUNT before I start my delay period? (Or does that happen automatically when the interrupt is enabled via REG_TC4_INTENSET?)

Thanks in advance for your help!
Randy

P.S. Bonus question... Once I get this working, is it ok to set REG_TC4_COUNT16_CC1 inside an ISR? Or is the SYNCBUSY while loop too slow for that to be safe?

MartinL

Hi Randy,

I noticed that in your TC4_Handler() ISR, it's necessary to write a 1 to the MC0 and MC1 interrupt flags, in order to clear them:

Code: [Select]
TC4->COUNT16.INTFLAG.bit.MC0 = 1;   // Clear the MC0 interrupt flag
TC4->COUNT16.INTFLAG.bit.MC1 = 1;   // Clear the MC1 interrupt flag

In the code that you've provided, your loop() function is operating asynchronously with respect to the timer. This means that when you enable TC4 interrupts in the loop(), the delay before the ISR is called is anything between a single timer tick at 16us (1/62.5kHz) and the entire timer period at 88ms (16us * 500 * 11).

It's OK to set the CC1 register and wait for register synchronization within the ISR, as the register is defined as volatile.

I'd through I'd just mention that if you're using an interrupt signal to trigger an output pulse then it's possible to use the SAMD21's event system, (a 12-channel peripheral to peripheral highway) and get it route the trigger from the microcontroller's pin through to a TCC timer operating in Oneshot mode. This operates automatically without CPU intervention. TCC timers are similar, but offer more features than the TC timers such as Oneshot capability. An example of using a external trigger to generate a pulse using the event system and TCC0 is described on message #6 here: https://forum.arduino.cc/index.php?topic=692962.0.

It's also possible use the event system route the trigger to a delay timer then get the delay timer to in turn trigger a second timer that generates a pulse.

Trigger voltages other than standard logic thresholds are available through the SAMD21's Analog Comparator peripheral. The Comparator can also trigger the TCC timers through the event system.


rhulett

Thanks for your response, MartinL.

I'm a little confused by your suggestion about clearing the interrupt flags. I thought I was doing that when I called enableTc4Interrupts() which clears the flags and enables the TC4 interrupts. Within the TC4_Handler() ISR, I didn't think I had to clear the flags since the pulse generator runs as a single shot (i.e. each MCn interrupt is called once and won't be used until we call enableTc4Interrupts() again to setup the next single shot-- so do these flags need to be cleared in TC4_Handler()?).

I think you are right about the asynchronous connection between the loop() and the timer-- that certainly lines up with how it looks on an oscilloscope. What I don't understand is how one would go about making it synchronous. Is the issue that the TC4 count needs to be zeroed when setting up a single shot? If so, I have searched but can't find reference code in the forums that shows how to do that.

I'm excited about your suggestion to leverage the event system. Digging into that now.

Thanks so much!

MartinL

Hi Randy,

Yes, you're right. Sorry, I missed your enableTc4Interrupts() function cleared the interrupt flag bits.

The simplest way might be to retrigger (reset) the TC4 timer before enabling the interrupts.

To retrigger the TC4 timer:

Code: [Select]
TC4->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;   // Retrigger the TC4 timer
while (TC4->COUNT16.STATUS.bit.SYNCBUSY);   // Wait for syncrhonization

The event system is quite a nice feature of the SAMD21, since it allows the peripherals to communicate with each other without the CPU intervention. This means that once the event system and peripherals have been set up, they can run like a clockwork machine without taking up CPU time.

rhulett

Unfortunately, MartinL, the RETRIGGER solution isn't working for me. When I add the retrigger code you shared, my oscilloscope test goes from showing asynchronous pulses to no pulses at all.

This behavior appears to be addressed in this thread.

Further, in the Retrigger Command and Event Action section, the data sheet says:

Quote
Note: When retrigger event action is configured and enabled as an event action, enabling the counter will not start the counter. The counter will start at the next incoming event and restart on any following event.
I can't tell what the "next incoming event" would be that would clear the counter.... but perhaps this is the root of the problem.

Do you think this approach to pulse generation is viable? I feel like I'm really fighting the framework here.

I love what you are proposing with the event system. I spent some time reviewing the code you posted in the thread you referenced. It's an option for me but:
  • I believe I'd need to remap my pins and re-layout my PCB to make it work. From the documentation, it appears the multiplexer won't let me route TCC events with PA4 (my zero crossing input) and PB02 (my triac/pulse output).
  • Beyond the pin mapping, there is work to do to implement the required delay before the pulse in the event system (i.e. in terms of building on your sample code).
  • There is a lot to learn in how you setup the event system in your sample code. I think I can eventually get my head around it, but if the code I shared earlier is close, that is a quicker win.

Thanks again.

MartinL

Hi Randy,

Using the event system it's possible to use (almost) any of the microcontroller's input pins, as external signals are routed to the event system via the External Interrupt Controller (EIC), in other words through any pin capable of a receiving interrupts.

As it's possible to route in any input signal, the input on PA04 isn't an issue. However, the output on PB02 is a bit problematic, because unless you're using larger 64-pin SAMD21J variant on your custom board, this pin isn't connected to a timer output. The workaround in software is to implement what you're already proposing, which is to big bang the output on PB02 using the timer's interrupts. This results in slightly more jitter in both the pulse width and the output pulse delay with respect to the input trigger, this may/may not acceptable depending on your application.

I've slightly modified your existing code to generate a delayed pulse on digital pin D9 after a simulated input trigger on A5. I changed the TC4 timer's mode to Match PWM (MPWM) so that the timer can generate interrupts on CC1 and CC0. I also enabled Oneshot mode, this automatically stops the timer at the end of its period and removes the need for the enableTc4Interrrupts() function.

Here's the code:

Code: [Select]
void setup() {
  pinMode(A5, OUTPUT);      // for dev only... rising edge on oscilloscope marks delay start
  pinMode(9, OUTPUT);       // pulse output

  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |         // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                     GCLK_GENDIV_ID(4);           // Select Generic Clock (GCLK) 4

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
 
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  TC4->COUNT16.INTENSET.reg = TC_INTENSET_MC0 | TC_INTENSET_MC1;    // Enable MC0 and MC1 interrupts
 
  TC4->COUNT16.CTRLBSET.reg = TC_CTRLBSET_ONESHOT;          // Enable Oneshot operation on TC4

  TC4->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV256 |     // Set prescaler to 256, 48MHz/3/256 = 62.5 KHz (16 us)
                           TC_CTRLA_WAVEGEN_MPWM;         // Put the timer TC4 into match PWM (MPWM) mode.
 
  TC4->COUNT16.CTRLA.bit.ENABLE = 1;                       // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization
}

void loop() {
  int waitValue = 500;

  TC4->COUNT16.CC[1].reg = waitValue*10;         // Set the TC4 CC1 register delay time
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);   
  TC4->COUNT16.CC[0].reg = waitValue*11;         // Set the TC4 CC0 register for the delay time + pulse width
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);

  digitalWrite(A5, HIGH);                     // write A5 high as the oscilloscope trigger (delay start)
  TC4->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;   // Retrigger the TC4 timer
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);   // Wait for syncrhonization

  delay(1000);
  digitalWrite(A5, LOW);
  delay(500);
}

// Interrupt Service Routine (ISR) for timer TC4
void TC4_Handler() {
  if (TC4->COUNT16.INTFLAG.bit.MC0 && TC4->COUNT16.INTENSET.bit.MC0) {
    digitalWrite(9, LOW);
  }
  if (TC4->COUNT16.INTFLAG.bit.MC1 && TC4->COUNT16.INTENSET.bit.MC1) {
    digitalWrite(9, HIGH);
  }
  // clear the interrupt flags
  TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0 | TC_INTFLAG_MC1; // TC_INTFLAG_OVF
}

Here's the output, there's the simulated trigger on A5 (yellow) and a delayed 8ms pulse (light blue) with the delay itself set to 80ms:


rhulett

Wow! This is totally perfect, MartinL.

I've got it running and the pulse generation is exactly what I had hoped for. It's very stable for my application. I've tested that I can also pause pulse generation and that pulses resume immediately and with the correct delay as soon as I unpause them.

Someday I'd love to dig into the Event System as it looks incredibly powerful... but this solution is great for my application.

I really appreciate your help on this!

Best,
Randy


rhulett

After a few months, the solution above is working almost perfectly for me...

The one issue I'm having: I occasionally see large, very-brief spikes in motor speed.
This issue occurs erratically but roughly once per minute.

I am using an encoder to determine motor speed and using a PID algorithm to update the (speed-control) pulse delay every 50ms with the following function:

Code: [Select]

// motorSetDrive(): sets delay from next AC zero-crossing for next motor throttle (triac) pulse
void motorSetDrive(unsigned int delay, unsigned int pulseWidth) {
  // set the TC4 CC1 register to the pulse delay time
  TC4->COUNT16.CC[1].reg = delay >> 4;         
  // wait for syncrhonization
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);
  // Set the TC4 CC0 register to the pulse delay + pulse width time
  TC4->COUNT16.CC[0].reg = (delay >> 4) + (pulseWidth >> 4);     
  // wait for syncrhonization
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);
}
// motorPulse(): called on a zero-crossing to restart the delay timer for a one-shot pulse on motor throttle (triac) output
void motorPulse(void) {
  // retrigger the TC4 timer
  TC4->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;   
  // wait for synchronization
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);   
}
// TC4_Handler(): interrupt service routine motor throttle (triac) pulse generation (timer TC4)
void TC4_Handler() {
  // generate trailing edge of throttle pulse
  if (TC4->COUNT16.INTFLAG.bit.MC0 && TC4->COUNT16.INTENSET.bit.MC0) { digitalWrite(A2, LOW); }
  // generate leading edge of throttle pulse
  if (TC4->COUNT16.INTFLAG.bit.MC1 && TC4->COUNT16.INTENSET.bit.MC1) { digitalWrite(A2, HIGH); }
  // clear the interrupt flags
  TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0 | TC_INTFLAG_MC1;
}


I have logged PID parameters and I can see that the encoder and PID outputs are stable, so the issue must reside either in my pulse generation software or in my electronics.

I'm wondering if the issue is the lack of synchronization between the pulse generation interrupt (TC4_Handler) or the zero-crossing interrupt (motorPulse) and the asynchronous, called-in-program-flow motorSetDrive() calls (e.g. what if motorSetDrive() is changing TC4 CC1/CC0 as a pulse is about to start/end with the TC4_Handler interrupt? immediately before a pulse? immediately after a pulse? or motorPulse() retriggers the next pulse while motorSetDrive() is updating?).

Is there a scenario where an asynchronous call to motorSetDrive() occurs at a point in the pulse generation code whereby it somehow disrupts pulse generation (i.e. causes an extra or longer-than-intended pulse)?

Thanks for getting me this far, MartinL. I'm hoping you or others have some ideas on this.

Randy

MartinL

Hi Randy,

Interesting you mention this.

Not sure if it's the same problem, but I was recently answering a post with a similar requirement, generating a delay with TC4 using a oneshot retrigger. It appeared that the TC4 would randomly miss interrupts every 30 seconds to a minute or so.

Switching to TC3 solved the issue.

To switch to TC3 instead, just change the generic clock control register to:

Code: [Select]
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |                 // Enable GCLK4
                    GCLK_CLKCTRL_GEN_GCLK4 |             // Select GCLK at 16MHz
                    GCLK_CLKCTRL_ID_TCC2_TC3;            // Connect to timers TCC2 and TC3

...and change all instances of TC4 to TC3 in your sketch.

rhulett

Thanks for responding so quickly, MartinL!

I implemented the switch to the TC3 timer as you suggested this morning. The motor ran fine but, unfortunately, the intermittent speed spikes did not go away.

Two issues come to mind...

First, I don't understand the interaction between TC3 changes & synchronization-waits when called from inside interrupts and the program flow...

For example, on a zero-crossing (inside an interrupt), I run this code:
Code: [Select]
// retrigger the TC3 timer
TC3->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_RETRIGGER;   
// wait for synchronization
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);


But I also make changes (to the pulse delays) in the non-interrupt program flow in my motorSetDrive() calls:
Code: [Select]
// set the TC3 CC1 register to the pulse delay time
TC3->COUNT16.CC[1].reg = delay >> 4;         
// wait for syncrhonization
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Set the TC3 CC0 register to the pulse delay + pulse width time
TC3->COUNT16.CC[0].reg = (delay >> 4) + (pulseWidth >> 4);     
// wait for syncrhonization
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);


What happens when motorSetDrive() is making changes to the delay and waiting for TC3 synchronization and the zero-crossing interrupt tries to schedule a new pulse by making a change to the same register?

Second, in my logs, I'm noticing that the spikes occur more often when the pulse delay is long (i.e. the pulse starts further from the triggering zero crossing and close to the subsequent one). In this case, the window to set the triac pulse back low is smaller... and I wonder if some intermittent slowness in an interrupt occasionally causes the triac pulse not to return to low before the next zero crossing.

I'm triggering on zero crossings at 60Hz so my pulse window is 8.3ms. In software, I'm limiting my pulse delay to a max of 6.8ms after the zero crossing (1.5ms of headroom) and a pulse width of about 80ms. Is it possible that either latency (e.g. waiting for register synchronization or something) on the zero-crossing interrupt could push back scheduling the pulse delay or, alternatively, latency around the TC3_Handler() could cause the pulse not to clear before the subsequent zero crossing? I know this is pretty far fetched (since the code in my ZC & TC3_Handler interrupt is minimal and a millisecond+ of headroom feels like an eternity)... but I thought I'd throw this out there. (Your missed TC4 interrupt theory was promising since a missed trailing-edge bit bang could also have been the cause.)


Unless one of these issues seems like a possible culprit, I'm seriously considering changing my control board to be compatible with the SAMD21 event system (which you recommended in Sept). At the time, I resisted your suggestion since my already-fabricated boards used A3/PA4 (for the zero crossing interrupt) and A5/PB02 (for my triac pulse). I have since moved the triac pulse to A2/PB09, but I think PB09 is also not connected to a timer output.

It wouldn't take much work to switch the triac pulse output to A4/PA05 which I believe connects to TCC0.

Do you know of a good code reference for using the SAMD21 event system as follows:
  • Zero crossing read on A3/PA4 --> schedule pulse delay on A4/PA05
  • Generate pulse leading edge on A4/PA05 using TCC0 timer
  • Generate pulse trailing edge on A4/PA05 using TCC0 timer


Thanks again for your insights.
Randy

MartinL

Hi Randy,

Quote
What happens when motorSetDrive() is making changes to the delay and waiting for TC3 synchronization and the zero-crossing interrupt tries to schedule a new pulse by making a change to the same register?
Yes, if the motorSetDrive() function is called asynchronously with respect to the zero crossing interrupt then this will affect the delay and pulse generation output, because the timer's CC registers are being changed while the timer is busy.

One way to achieve synchronisation, is to check if the timer is busy by reading its STOP bit in the TC's STATUS register:

Code: [Select]
TC3->COUNT16.STATUS.bit.STOP;
Either inserting the blocking while(); statement before accessing the CCx registers, or wrapping them in the if{} statement in the motorSetDrive() function, will ensure that they are not accessed while the timer is busy:

Code: [Select]
while (!TC3->COUNT16.STATUS.bit.STOP);   // Wait for the timer to become available
or

Code: [Select]
if (TC3->COUNT16.STATUS.bit.STOP) {}   // Check if the timer has become available?
Retriggering the timer when it's still busy might also cause problems, since it essentially resets the timer back to zero, irrespective of whether or not it has finished generating the 8ms pulse on the second interrupt. In this case it's necessary to prevent a retrigger from being called while the timer is busy.

Regarding synchronisation delay, this will be between 229ns and 312ns for each write to a CCx register with the TC timer running at 16MHz on GCLK4:

Synchronisation delay (D): 5 * (1/48MHz) + 2 * (1/16MHz) < D < 6 * (1/48MHz) + 3 * (1/16MHz)

This is probably negligible for your application, that's working in the millisecond realm.

The event system could be used to make the operation of the microcontroller more efficient, but it won't solve any synchronisation issues.

It's possible to receive input events through the EIC (interrupt) system from almost any pin on the SAMD21. If you've currently got your 8ms pulse output connected to PB09, then it's possible to use TC4/WO[1] to output the pulse. This means that the entire system could be implemented using the event system without CPU intervention, provided that a second edge trigger doesn't occur while the timers are busy generating the delay and pulse.

rhulett

Hey Martin,

I made some changes to my motor-delay software and we haven't seen a speed-spike since! :)

I appreciate your suggestion to read the STOP bit in the TC's STATUS register, but I went with something a little different... I decided to queue any asynchronous speed change requests and then implement queued timing changes immediately after the TC3_Hander() interrupt is called to create the trailing edge of the triac pulse. This seems to work well.

As I noted in my previous post, I'm updating my drive pulse timing every 50ms and the pulses occur on an 8.3ms period. There is about 1.5ms between the pulse's trailing edge and the next zero crossing-- plenty of headroom to implement the change.

Thanks again for your quick and always-on-point feedback.
Randy

P.S. For your amusement, I uploaded a video of our carbonation machine in action-- with no speed hiccups!

MartinL

Hi Randy,

Glad to hear that you found a solution for the motor problem.

Thanks for the video of your carbonation machine, interesting project, looks like there's a lot going on.

Kind regards,
MartinL

Go Up