My softwarePWM

Go To Last Post
25 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello. I'm waiting for some servos I ordered to come and I thought I would better use this time I have untill
they come to do something for educational sake.
So I wrote a software PWM implementation using Timer0 of Atmega8 because that timer lacks many features.
I defined some constants:

const float OVF_IN_1_SEC = F_CPU/256;
const float SERVO_MIN = 1.0; // ms
const float SERVO_MIN_IN_OVF = SERVO_MIN * OVF_IN_1_SEC / 1000;
const float SERVO_NEUTRAL = 1.5; // ms
const float SERVO_MAX = 2.0; // ms
const float SERVO_MAX_IN_OVF = SERVO_MAX * OVF_IN_1_SEC / 1000;

const float PRECISION = (float)1000 / ( (float)F_CPU / 256 ); // 1 s = 1000 ms; F_CPU / 256 = how many times it overflows per second; PRECISION tells you how much time passed since last overflow :)
const float SERVO_FREQUENCY = 20; // ms
const float SERVO_FREQUENCY_IN_OVF = SERVO_FREQUENCY * OVF_IN_1_SEC / 1000;

    OVF_IN_1_SEC represents how many times Timer0 overflows in 1 second without a prescaler (1) SERVO_MIN_IN_OVF represents how many times Timer0 overflows in SERVO_MIN ms
    SERVO_MAX_IN_OVF represents how many times Timer0 overflows in SERVO_MAX ms
    PRECISION represents the time between overflows
    SERVO_FREQUENCY_IN_OVF represents how many times Timer0 overflows in SERVO_FREQUENCY ms
Because I didn't wanted those const in my code, I made a simple program that will convert them to defines

void printInfo()
{
	cout << "F_CPU = " << F_CPU << endl;
	cout << "OVF_IN_1_SEC = " << OVF_IN_1_SEC << endl;
	cout << "SERVO_MIN = " << SERVO_MIN << endl;
	cout << "SERVO_MIN_IN_OVF = " << SERVO_MIN_IN_OVF << endl;
	cout << "SERVO_NEUTRAL = " << SERVO_NEUTRAL << endl;
	cout << "SERVO_MAX = " << SERVO_MAX << endl;
	cout << "SERVO_MAX_IN_OVF = " << SERVO_MAX_IN_OVF << endl;
	cout << "PRECISION = " << PRECISION << endl;
	cout << "SERVO_FREQUENCY = " << SERVO_FREQUENCY << endl;
	cout << "SERVO_FREQUENCY_IN_OVF = " << SERVO_FREQUENCY_IN_OVF << endl;
}

After that I created a class "Object" that represents a servo.

class Object
{
public:
	inline Object() : m_duty(SERVO_NEUTRAL), m_port(0), m_pin(0), m_expired(1) { }
	inline ~Object() { }
	inline void setDuty( float duty ) { if(duty >= SERVO_MIN && duty <= SERVO_MAX) m_duty = duty; else { m_duty = 0; setLow(); } }
	inline float getDuty() { return m_duty; }
	inline void setExpired( volatile uint8_t expired ) { m_expired = expired; }
	inline uint8_t isExpired() { return m_expired; }
	inline void setOutput( volatile uint8_t *port, uint8_t pin )
	{
		m_port = port;
		m_pin = pin;
	}
	inline void setHigh() { bit_set(*m_port, BIT(m_pin)); }
	inline void setLow() { bit_clear(*m_port, BIT(m_pin)); }

private:
	float volatile m_duty; // in ms
	volatile uint8_t *m_port;
	volatile uint8_t m_pin;
	volatile uint8_t m_expired;
};

then an Objects manager called "ObjectsVector"

class ObjectsVector
{
public:
	inline ObjectsVector() : m_list(0), m_length(0), m_maxLength(0) { }
	inline ~ObjectsVector()
	{
		if(m_list)
			free(m_list);
	}
	inline void init( uint8_t maxLength )
	{
		m_maxLength = maxLength;
		if( (m_list = (Object**)malloc( m_maxLength * sizeof(Object) )) == NULL )
			m_maxLength = 0;
	}
	
	
	inline uint8_t add( Object *object )
	{
		if( m_length < m_maxLength )
		{
			m_list[m_length++] = object;
			sort();
			return FALSE;
		}
		else
			return TRUE;
	}

	/*inline Object *getHead() { return m_list[0]; }
	inline Object *getTail() { return m_list[m_length]; }*/
	inline Object **getList() { return m_list; }
	inline uint8_t getLength() { return m_length; }
	inline void sort()
	{
		if(m_length == 1)
			return;
		for(i = 0; i < m_length - 1; ++i)
			for(uint8_t j = i + 1; j < m_length; ++j)
			{
				if ( this->getList()[i]->getDuty() < this->getList()[j]->getDuty() )
				{
					Object * temp = this->getList()[i];
					this->getList()[i] = this->getList()[j];
					this->getList()[j] = temp;
				}
			}
	}

private:
	Object **m_list;
	uint8_t m_length; // m_index-1 is the last one added ;)
	uint8_t m_maxLength; // max length
	
};
void softwarePWM();
extern ObjectsVector myList;

and the implementation of SoftwarePWM()

// the list must be sorted from the highest duty to the lowest

ObjectsVector myList;
//todo: put the ++Timer0_OVF_Counter at the very beginning of the softwarePWM then compare everything to value+1
void softwarePWM()
{
	static float time_passed = 0;
	static uint16_t Timer0_OVF_Counter = 0;

	if(Timer0_OVF_Counter == 0) // we are starting again :)
	{
		for(i = 0; i < myList.getLength(); ++i)
			if ( myList.getList()[i]->getDuty() )
			{
				myList.getList()[i]->setHigh();
				myList.getList()[i]->setExpired(0);				
			}
		//return;
	}

	//if(Timer0_OVF_Counter >= 62 && Timer0_OVF_Counter < 125)
	if(Timer0_OVF_Counter >= SERVO_MIN_IN_OVF && Timer0_OVF_Counter <= SERVO_MAX_IN_OVF) // ONLY if we are between SERVO_MIN - SERVO_MAX, there is no point to check SERVO_MAX - SERVO_FREQUENCY ms, everyone should be "sleeping" already :)
	{
		//time_passed = Timer0_OVF_Counter * 0.016;
		time_passed = Timer0_OVF_Counter * PRECISION;
		for(i = 0; i < myList.getLength(); ++i)
		{
			if( !myList.getList()[i]->getDuty() )
				continue;
			else if( myList.getList()[i]->isExpired() )
				break; // because our list is ordered from the highest to the lowest, if we found one that is expired, everyone after him should be expired
			else if( time_passed >= myList.getList()[i]->getDuty() )
			{
				myList.getList()[i]->setLow();
				myList.getList()[i]->setExpired(TRUE);
			}
		}
		//return;
	}

//	if(Timer0_OVF_Counter == 1250) // 20 ms passed! reset :)
	if(Timer0_OVF_Counter == SERVO_FREQUENCY_IN_OVF) // SERVO_FREQUENCY ms passed! reset :)
		Timer0_OVF_Counter = 0;
	else
		++Timer0_OVF_Counter;
}

ObjectsManager sorts the list of servos like this: the one with the highest duty is the first, and the one with the lowest duty is the last.
(I know, I have a bug, if you modify the duty, the list can become unsorted. )
When a period of SERVO_FREQUENCY starts, Timer0_OVF_Counter is 0 and SoftwarePWM() puts every Object to high (setHigh()) and expired to 0. After that if we are between SERVO_MIN_IN_OVF and SERVO_MAX_IN_OVF it checks to see if an object has used its time, if it did, it sets it as expired (setExpired(1)) .
SoftwarePWM() is called here:

SIGNAL( TIMER0_OVF_vect )
{
	if( Timer0::_timer0OvfHandler )
		Timer0::_timer0OvfHandler();
}

and here is my test program:

int main()
{
    DDRB = 0b11101110; //PB4 = MISO 
    DDRC = 0b11111110; //
    DDRD = 0b11110000; //PORTD (RX on PD0)

	//wdt_enable( WDTO_1S );

	//adcInit();
	//adcSetPrescaler(ADC_PRESCALE_DIV128);

	UART::Init( 0, 9600 );

	unsigned float duty = 1.5f;

	Object led;
	led.setDuty( duty );
	led.setOutput( &PORTC, 1 );
	myList.init( 3 );
	myList.add( &led );

	Timer0::init( TIMER0_CLK_DIV1 );
	Timer0::attachHandler( softwarePWM );

	uint8_t c = 0;

	//wdt_reset();

	for( ;; ) {
		//wdt_reset();
		/*if( adcConvert10bit(0) < 500 )
			bit_set(PORTC, BIT(1));
		else
			bit_clear(PORTC, BIT(1));*/
		bit_set( PORTB, BIT(1) );
		if( (c = UART::getByte()) )
		{
			(void)printf( "code = %c\n", c);
			/*if(c == '1')
			{
				(void)printf( "ADCH = %i\n", adcConvert10bit(0) );
			}*/
			if( c == '2' )
			{
				duty += 0.1f;
				led.setDuty( duty );
			}
			else if( c == '3' )
			{
				duty -= 0.1f;
				led.setDuty( duty );
			}
			else if( c == '4' )
			{
				led.setDuty( 0.0f );
			}

			UART::sync();
		}
	}
	
	return 0;
}

As you can see, I don't have a servo yet, so I tested it on a LED. It works okay, but my space is very limited on Atmega8 and I need some help in making the code lighter(I'm not a professional programmer, as I bet you saw that already) because it takes 99.8% flash space ( Program: 8172 bytes (99.8% Full) )

Thanks in advance,
reSpawn.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good start. Now figure out how to do all that with no floating point and no c++. You can cheat and look at the servo examples in the projects section here. What compiler did you use?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm using avr-gcc with some additional flags to remove unused functions and -Os.
Hm, I should get a lightweight printf that knows to print only uint8_t, uint16_t and uint32_t
later edit: Well, I used C++ because it looks more nice :P but it should generate the same code.. I belive if I'll add private copy operator and the copy constructor. Anyway they *SHOULD* be deleted by the linker because nobody uses them anywhere in the code.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What precision do you require in positioning the servo? If it travels 180 degrees, and you have 255 positions, thats better than one degree resolution. You couldn't control a telescope or an eyeball laser with that res, but its good enough for model airplanes. Surely all the calcs could be done in 16 bit integers. Dont need fp till you start doing transcendentals.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I can avoid floats using µs instead of ms. What do you say? Should I switch?

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No fp will free up 4k of the 8k rom

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you. I got rid of floats :)
Device: atmega8
Program: 6822 bytes (83.3% Full)

I guess my code is a little faster too.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Now figure out how to do all that with [...] no c++

OK, Bob. Is this another imsinuation of "C++ will always bloat" without any solid proof? What where you trying to say here?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have no quibble about using any object abstraction to decompose a problem into nouns and verbs or methods or procedures or whatever... especially if it is an application that benefits from that model... something with a hierarchy... but this is a model airplane servo... the most common microcontroller app there is... there must be a dozen of them right here with source code for 3 different c compilers and a assembler. If you asked me 'Hey... can I write a servo controller with only a couple of classes and some queues and sorting and a little floating point, and burn it into a mega8'? I'd really think you were pulling my leg. Like the guys from the dorm at Yale that used to send Ann Landers outrageous letters and she'd try to answer them. That's how you catch a snook. You get a shiny lure and a battery and you go trolling....

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, sometimes you need to make your own to understand things and this way you actually create something on your own. And I got rid of floating point operations. And btw, I took a look at "Procyon AVRlib" and it uses Timer1 and some hacks I don't quite understand that's why I decided to take a different approach.
I'll post later the finished version with some other optimizations too.

Cheers,
reSpawn.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But Bob, if he has already written it in c++ using classes for encapsulation, why would he need to re-write it?

The only thing concerning me is the kind of automatic fire that comes on to any "C++ target moving on the range". It needs a fair treatment. There has been another lengthy thread recently discussing C++ for AVRs, and as long as we (as in "me" and maybe others) get down to testing the actual code generated etc I see it as a case of the jury still being out.

Of-course OP could have used code that someone else has written, but maybe he wanted to have fun. (My own AVR adventures started many years ago now, with me wanting to built a motorized drive for my telescope. Still not finished, and if I ever get it done I reckon I will have paid much more for my AVR adventures than a redy to run Meade 8" would have cost me...)

There are some interesting things in the C++ code presented that I'm hoping to try out, time permitting, eg. the extensive use of inline methods - saving the cost of call, but still having to handle the dereferencing of the "this" pointer, how much is saved?

Abd why the "this->" explicit de-referencing?

Now I gotta go Wiki "Ann Landers"...

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I suggest an lcd support package in c++ that will handle blits and buttons and drop shadows.. sort of a monochrome window manager. Surely that would be an excellent demonstration of the power of c++. I would study it very carefully. I never have been able to figure out how windows could redraw the half hidden window behind the top window. The brute force way would be to draw them all one at a time from back to front, but you would see that happening.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With or without "this" pointer the compiler should generate the same code, I used it so that the list of candidate functions could show up( I use Visual Studio ).
And Bob, that's a Graphics API what you say there and I saw some lightweight graphics engines for megas. I will search and post what I find. And the mechanism for redrawing only parts of objects is called Frustum Culling( I'm not sure ). And even if you would redraw everything from back to front you wouldn't see the flicker because you can do it double buffered, and you only switch buffers.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bob! Let's not hi-jack - I have written down what little I know about window updates here: https://www.avrfreaks.net/index.p...

Oh, well OP kind of accepted the hijack as he managed to respont while I was creating the other thread. Well, it's there if you want to read it (double buffering and all...)

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I used it so that the list of candidate functions could show up( I use Visual Studio ).

OK, I kind of see the point. I still think that it bloats the source, but whatever gets you going...

What version of Visual Studio?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I removed those already. I will post tomorrow the modified version so others can take a look at it.
Ah, and the engine I saw wasn't for atmegas, it is for PICs : http://www.pyrofersprojects.com/... , but I can't see where to download the src, maybe I'm blind.
Anyway, I'm off to bed.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just one question, why use so much code for something that can be done with sooooo much less?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Isnt there a way of controlling the servo with one assignment statement to the OCR reg once the timer is initialized?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When I wrote some controll code for a servo, I just had a variable "servo_angle" that ranged from -45 to 45, and whatever that var was, the servo set accordently. wasn't many lines of code...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Eh...if it's space (and speed) you need, then avoid doing it the bizzare way, and do it the simple way. No objects, no floats.

How many servos do you want to use? I mean, isn't making a task manager a bit extreme for making PWM for a few servos?

edit: C vs. C++ round 65468

There are pointy haired bald people.
Time flies when you have a bad prescaler selected.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

well, blader and Bob you are my guests to make it in less lines of code. Atmega8 has only 3 PWM channels, what I've done here is not hardware PWM but softwarePWM. Timer0 has no CTC. I wanted to make SoftwarePWM using Timer0 because that timer is not so used, all implementations of Softwarre PWM I saw were using Timer1.
You should be able to output PWM on every IO pin.

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

agreed :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's a program I whipped up to do 8 channels of 8 bit sw pwm on portA. Timer0 with prescaler of 2 overflow int is used for timing. The pwm period is about 30ms, and the pwm varied 0-100% using the az and sx keys. The interrupt handler takes 11usec, 7 lines, 35 words.

Attachment(s): 

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How can you time the code execution?

Nicu reSpawn.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Its in there. I ran the subroutine thats called in the int handler a million times... took 11 sec

Imagecraft compiler user