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)
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.
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)?
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.
@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)