Cheapest microcontroller to convert 1000-2000us to 0-100% PWM

Hello Everyone,

Can someone guide me, I just want to convert 1000-2000 us pulse to 0 to 100% duty cycle PWM output.

So which is the best/cheapest/smallest microcontroller to use to for this purpose?

I think Attiny85 is a good option but apart from that does anyone know any other simple microcontroller to use.

Thank you.

In the absence of more information we are left guessing exactly what you want to achieve.

Sorry, I will be careful next time.

I want to control a DC motor in one direction using Standard RC Radio (Flysky i6 in my case). The receiver of this radio outputs pulse of 1000us (lowest position of throttle stick) to 2000us (highest position of throttle stick)

.. and between the pulses? do they repeat? what is the value of the pulse voltage high & low?

Do you just require a logic level pwm output based on the pulse timing, or do you also need other features eg WiFi, or battery operation?

For timing a 1-2ms pulse most arduino's would do the job

standard servo control.

before Corona cheapest MCU was Attiny13a

ya, Attiny13A seems to be a good choice

Hi @anon14830607

It depends on how accurately you'd like to: a) measure the input signal and b) generate the output PWM. Both of these factors will depend on the resolution of your microcontroller's timers.

Input Considerations

To measure the input you can use interrupts in conjuction with the Arduino micros() function, to time the input's rising and falling edges. However, the micros() function on AVR microcontrollers is accurate to 4us.

If you require a higher resolution, say down to 1us, then you'll need to either repurpose timer 0, in which case you'll lose the Arduino timing function millis(), micros() and delay(), or use a spare 8-bit timer, such as timer 2 on the Atmega328P. In either case, the timer would need to be configured to run at a faster 1us tick rate or higher.

Output Considerations

Output generation will be dependent on the frequency of your PWM output signal and the resolution of the timer used to create it. Only a 16-bit timer will give you 1us accuracy and control over the frequency of the PWM output.

Thanks

Hi @anon14830607

The Attiny13A has a single 8-bit timer. How do you propose to both read the input pulses and generate a PWM output, (unless your PWM output frequency is at 1kHz)?

Hi @anon14830607

Edit: After further consideration, not possible.

On second thoughts, I guess you could do it, but it would mean piggybacking on one of the timer 0's COMPA or COMPB interrupts and bit banging the output manually at your desired PWM frequency.

the only way to find out is to try it out. I will give Attiny13A a try

@anon14830607 I think you'll find that the ATtiny13A won't meet your requirements. Working around the chip's hardware limitations is just too problematic.

Personally, for this task I wouldn't consider any microcontroller without a 16-bit timer. The ATmega328P (Arduino Pro Mini/Nano or standalone chip) could do the job easily with a high level of accuracy, plus you've got the advantage of a huge library of software support available.

DigiKey.com has:
ATtiny10-TSHR for $0.47 each in quantity 1
ATtiny40-SU for $0.51 each in quantity 1
ATtiny402-SSN for $0.55 each in quantity 1
ATtiny212-SSN for $0.59 each in quantity 1
Maybe one of them is capable enough.

40 some years ago I built such a radio controlled electronic speed controller around an LM324 quad comparator. I expect this could be done with virtually any microcontroller that can run a polling/bit-banged PWM loop in a few tens of microseconds and has some reasonable sense of time.

If this is a one-off personal project then smallest and familiarity with the development tools is probably the priority. If this is for large scale production, then unit production cost becomes the priority and most anything "Arduino" probably isn't the answer.

Attiny13 can do it.
I used it for a 29Hz PWM solenoid driver for a fuel metering system.

/*************************************************************************************************
  Electronic fuel metering solenoid driver
  Based on a servo input signal this driver wil output a 29 Hz pulse with modulated width.
  The pulse has a linear modulation from 0% to 100% dutycycle for driving a Stihl solenoid valve.
  Application: Converting methanol RC engines to gas by providing a dynamic fuel metering.

  Software by:
  Jan 2020, V1.1 by Hans Meijdam
  April 2021, V1.2 revised atomic operation of 16-bit fields
  May 2021, V1.3.1 increased resolution by changing timer prescaler from 64 to 8
  June 2021, V1.3.2 increased stability by first capturing TCNT0 and then resetting overflow counters

  HW Platform Microchip / Atmel ATTiny13
  SW platform Arduino IDE with added https://github.com/MCUdude/MicroCore
  Select 9,6 MHz clock speed and micros disabled

  ************************************************************************************************
*/
#define F_CPU 9600000
#define DEBUG
/* input and out put pins. Can be changed to any PB pin, but read Note: below */
#define LED1       PB4  // DEBUG LED (via resistor) to this pin will indicate when throttle ends are detected
#define solenoid1  PB0  // pin to drive solenoid valve via FET
#define solenoid2  PB2  // pin to drive solenoid valve via FET
#define INP        PB3  // incoming servo signal from RC receiver

/* Possible values to compute a shifting average in order to smooth the recieved pulse witdh */
#define AVG_WITH_1_VALUES       0
#define AVG_WITH_2_VALUES       1
#define AVG_WITH_4_VALUES       2
#define AVG_WITH_8_VALUES       3
#define AVG_WITH_16_VALUES      4

#define AVERAGE_LEVEL          AVG_WITH_4_VALUES  /* Choose here the average level among the above listed values */
/* Higher is the average level, more the system is stable (jitter suppression), but lesser is the reaction */

/* Macro for average */
#define AVERAGE(ValueToAverage,LastReceivedValue,AverageLevelInPowerOf2)  ValueToAverage=(((ValueToAverage)*((1<<(AverageLevelInPowerOf2))-1)+(LastReceivedValue))/(1<<(AverageLevelInPowerOf2)))
// AVERAGE(  xxxxx ,  yyyyy  , AVERAGE_LEVEL) ;

// global variables:
volatile boolean watchdog_detected = 0 ;
volatile boolean pulse_change_detected = 0 ;
volatile uint8_t servopulse_overflow_counter; // number of TCNT0 overflows
volatile uint8_t modulatedpulse_start; // value of TCNT0 when pulse starts
volatile uint8_t modulatedpulse_overflow_counter; // number of TCNT0 overflows

#define MAX_PULSE ((unsigned int) ((F_CPU * .0019) / 8)) // Adjust from .0020 for transmitters with more or less servo travel
#define MIN_PULSE ((unsigned int) ((F_CPU * .0010) / 8)) // Adjust from .0010 for transmitters with more or less servo travel 

int main(void)
{
  WDT_off(); //call to disable watchdog timer
  DDRB |= _BV(LED1) | _BV(solenoid1) | _BV(solenoid2);//Led1 & solenoid as outputs
  PORTB |= _BV(INP); //activate internal pullup resistor

  // define pin change interrupts
  GIMSK |= _BV(PCIE); //Enable pin change interrupt
  PCMSK |= _BV(PCINT3); //Attach PB3 to pin change interrupt. Note: use _BV(PCINT2) if INP on PB2 etc.

  // define timer overflow interrupts
  TCCR0B |= _BV(CS01); //start timer, devide timer clock by 8
  TIMSK0 |= _BV(TOIE0); //enable Timer Overflow interrupt

  WDT_Prescaler_Change(); //call to set 29Hz watchdog trigger routine

  while (1) {
    uint8_t servopulse_start; // value of TCNT0 when pulse starts
    uint16_t servopulse_width; // PWM servo signal value
    uint16_t raw_servopulse_width;
    uint16_t modulatedpulse_end; // TCNT0 cycles between start<>end of solenoid valve pulse duration

    if (watchdog_detected) {
      if (servopulse_overflow_counter > 200) servopulse_width = MIN_PULSE;  // servo pulse extinction, set solenoid fully open
      if (servopulse_width > MIN_PULSE) PORTB |= _BV(solenoid1) | _BV(solenoid2); //solenoids on
      modulatedpulse_end = ((servopulse_width - MIN_PULSE) * 38); // map servopulse range (1000us) to solenoid pulse frequency
      watchdog_detected = 0;
    }

    if (pulse_change_detected) {
      if (PINB & _BV(INP)) { //pulse rising edge detected, if INP value is high
        servopulse_start = TCNT0; // remember timer value at servopulse start
        servopulse_overflow_counter = 0; //will count how many times TCNT0 overflows within one servopulse
      }
      else { // pulse falling edge detected
        cli();
        raw_servopulse_width = TCNT0 + (256 * servopulse_overflow_counter) - servopulse_start;
        sei();
        if ((raw_servopulse_width > 2700) || (raw_servopulse_width < 1000)) raw_servopulse_width = servopulse_width;
        AVERAGE(servopulse_width, raw_servopulse_width, AVERAGE_LEVEL); //read pulse width and smoothen a bit by averaging
        servopulse_width = constrain(servopulse_width, MIN_PULSE, MAX_PULSE);
      }
      pulse_change_detected = 0;
    }

    if ((PORTB & _BV(solenoid1)) && servopulse_width < MAX_PULSE) { // modulated pulse is on, so check if it is time to turn off
      if (TCNT0 + (256 * modulatedpulse_overflow_counter) - modulatedpulse_start >= modulatedpulse_end) {
        PORTB &= ~_BV(solenoid1); //solenoid1 off
        PORTB &= ~_BV(solenoid2); //solenoid2 off
      }
    }
#ifdef DEBUG
    if (servopulse_width == MIN_PULSE || servopulse_width == MAX_PULSE) PORTB |= _BV(LED1); //Led on when servo at endpoints
    else PORTB &= ~_BV(LED1); //Led off
#endif
  }
}

ISR(TIM0_OVF_vect) { //interrupt service routine when timer 0 overflows
  servopulse_overflow_counter++;
  modulatedpulse_overflow_counter++;
}

ISR(PCINT0_vect) {  //interrupt service routine after pin change is detected
  pulse_change_detected = 1;
}

ISR(WDT_vect) { //interrupt service routine when watchdog timer overflow triggers
  modulatedpulse_start = TCNT0;
  modulatedpulse_overflow_counter = 0;
  watchdog_detected = 1;
}

void WDT_off(void) { // turn off watchdog
  cli();
  /* Clear WDRF in MCUSR */
  MCUSR &= ~_BV(WDRF);
  /* Write logical one to WDCE and WDE */
  /* Keep old prescaler setting to prevent unintentional time-out */
  WDTCR |= _BV(WDCE) | _BV(WDE);
  /* Turn off WDT */
  WDTCR = 0x00;
  sei();
}

void WDT_Prescaler_Change(void) { //set 29Hz watchdog trigger routine
  cli();
  /* Start timed sequence */
  WDTCR |= _BV(WDCE) | _BV(WDE);
  /* Set new prescaler(time-out) */
  WDTCR = _BV(WDTIE) | _BV(WDP0); //value = 4K cycles (~0.32 ms)
  sei();
}

But to be honest the Attiny10 does it also and even better due to it's 16 bit timer. with input capture.

The cheapest and simplest way to get what you want is to use an old model servo PCB, as that is just a DC PWM regulator (with H-bridge)

I like the Arduino Nano Every. Buy them in bulk, a 6-pack is about 80 bucks, build a hexacopter!

Isn't this just an ESC? Why not just buy one?

1 Like

because I want to make one.

thank you for the code

Thank you everyone for the replies. I tried Attiny85 and it is working without any issue.