core13: An Arduino core for the Attiny13 *testers wanted*

A quick comparison using millis() and micros() shows them to be pretty bad when using a simple while loop:

void loop() {
 PORTB=1;
 period=micros()+1000;
 while(micros()<period){
 }
 PORTB=0;
 period=micros()+1000;
 while(micros()<period){
 }
}

Using micros() the sketch compiles to 398 bytes and times at 1400-1800us (with very inconsistent timing)
Using millis() the sketch compiles to 414 bytes, and times at 1120ms consistently.

In constrast, the new delay() and delayMicroseconds routines give:

delayMicroseconds(1000) compiles to 266 bytes, times at 1064us
delay(1000) compiles to 252 bytes, times at 1070ms

An obvious problem with using millis() or micros() is that the timer overflow counter is only incremented every 256 clock cycles, ie. every 213us at 1.2MHz. With millis() this is then scaled by 5/19/37 for 1.2/4.8/9.6MHz. These don't quite give exact ms values. At 1.2MHz for example, 2565=1280 clock cycles and 12800.833=1066us.

micros() fares even worse, since the time has increased by 213us for each ovrf++ (at 1.2MHz). And because there's no way of controlling exactly when these functions are executed, ovrf may have increased beyond the required timing value, being some multiple of 213us larger than desired. (Unless 213 happens to be an aliquot part of your required duration.) This also means that micros() can't deal with periods less than 213us.

I think that with higher clock speeds and with longer delays millis() and micros() are probably adequate. But micros() is not too good for actually counting microseconds.

kosine:
A quick comparison using millis() and micros() shows them to be pretty bad when using a simple while loop:

void loop() {

PORTB=1;
period=micros()+1000;
while(micros()<period){
}
PORTB=0;
period=micros()+1000;
while(micros()<period){
}
}




Using micros() the sketch compiles to 398 bytes and times at 1400-1800us (with very inconsistent timing)
Using millis() the sketch compiles to 414 bytes, and times at 1120ms consistently.

This is quite expected from the current micros() routine at clock clock rates
I am very surprised they are so large though

In constrast, the new delay() and delayMicroseconds routines give:

delayMicroseconds(1000) compiles to 266 bytes, times at 1064us
delay(1000) compiles to 252 bytes, times at 1070ms

An obvious problem with using millis() or micros() is that the timer overflow counter is only incremented every 256 clock cycles, ie. every 213us at 1.2MHz. With millis() this is then scaled by 5/19/37 for 1.2/4.8/9.6MHz. These don't quite give exact ms values. At 1.2MHz for example, 2565=1280 clock cycles and 12800.833=1066us.

Well I am seriously thinking that I am going to increase the timer interrupt rate in a future version

I think that with higher clock speeds and with longer delays millis() and micros() are probably adequate. But micros() is not too good for actually counting microseconds.

I am fully aware of the limitations on the uS routines. However I will work on improvements.

The problem with your routines is they fail completely on clock speeds below 1 MHz. Not a huge deal with uS since "microsecond"
timing and overhead would be pretty awful at 128 or 600 khz. BUT working millisecond timing is important.

I may set it up so it switches routines based on clock speed

Yeah, I need to have a look at the slower clock speeds. 600kHz might not be too terrible, but I can see 128kHz being awkward. I'll give it some thought. (Perhaps 1MHz << 8 is close enough?)

A reasonable millis() can be done, but a robust micros() eludes me at present. With some modified functions I can get these results:

@ 9.6MHz (chip 1):
micros()+100 : 124 to 154us
micros()+500 : 520us

millis()+5 : 4.1 to 4.7ms
millis()+50 : 49.6ms
millis()+500 : 492ms
millis()+5000 : 4920ms

@ 1.2MHz (chip 2):
micros()+100 : 400 to 600us
micros()+500 : 640 to 860us

millis()+5 : 4.84 to 5.20ms
millis()+50 : 50.8ms
millis()+500 : 508ms
millis()+5000 : 5040ms

The figures are slightly different between chips, so this is probably as good as I can get.

unsigned long micros(){
//	asm("CLI\n");
	unsigned long us_actual = ovrf << 9;		// Put number of overflows into higher bits (equivalent to total clocks*2)
	us_actual = us_actual + TCNT0;			// Put current timer-counter into lower 8 bits


	#if F_CPU == 1000000				// 1.0us per clock = 1 clock per us
	us_actual = us_actual >> 1;			// Equivalent to clocks

	#elif F_CPU == 2000000 				// 0.5us per clock = 2 clocks per us
	us_actual = us_actual >> 2;			// Equivalent to clocks/2

	#elif F_CPU == 4000000 				// 0.25us per clock = 4 clocks per us
	us_actual = us_actual >> 3;			// Equivalent to clocks/4

	#elif F_CPU == 8000000 				// 0.125us per clock = 8 clocks per us
	us_actual = us_actual >> 4;			// Equivalent to clocks/8

	#elif F_CPU == 16000000 			// 0.0625us per clock = 16 clocks per us
	us_actual = us_actual >> 5;			// Equivalent to clocks/16


	#elif F_CPU == 600000				// 1.66us per clock = 0.833 clock per us
	us_actual = (us_actual >> 1) + (us_actual >> 2) + (us_actual >> 3) + (us_actual >> 6);	// Equivalent to clocks*0.8906

	#elif F_CPU == 1200000 				// 0.83333us per clock = 1.2 clocks per us
	us_actual = (us_actual >> 2) + (us_actual >> 3) + (us_actual >> 4) + (us_actual >> 7);	// Equivalent to clocks*0.4453

	#elif F_CPU == 4800000 				// 0.20833us per clock = 4.8 clocks per us
	us_actual = (us_actual >> 4) + (us_actual >> 5) + (us_actual >> 6) + (us_actual >> 9);	// Equivalent to clocks*0.1113

	#elif F_CPU == 9600000 				// 0.10416us per clock = 9.6 clocks per us
	us_actual = (us_actual >> 5) + (us_actual >> 6) + (us_actual >> 7) + (us_actual >> 10);	// Equivalent to clocks*0.0557

	#else
	#error This CPU frequency is not defined
	#endif
//	asm("SEI\n");

	return us_actual;
}


unsigned long millis(){
	return (micros() >> 10); 			// This is 2.4% too low is but compensated for above at 0.6/1.2/4.8/9.6MHz
}

Been a bit busy last few weeks, but tried a couple of alternative ways to improve the timing functions. None were much better sadly.

Did discover a couple of other things while playing about:

  1. Removing the init() function from wiring.c and pasting the code into main() (main.cpp) seems to save 92 bytes if you don't need the timing functions. A "bare minimum" sketch normally compiles to about 194 bytes, but moving the init() code reduces it to 102 bytes. The overhead comes back if you call any timing function, but it doesn't if you're not using those.

  2. The following code appears in analogWrite:

		if(pin == 1){
			TCCR0A |= (1 << COM0B1);
			OCR0B = (val / 4) * 4;
		}
		if(pin == 0){
			TCCR0A |= (1 << COM0A1);
			OCR0A = val;
		}

Is there a reason for "(val / 4) * 4"? Replacing it with "OCR0B = val" should work and avoids the overheads

On the subject of analogWrite, I needed full control over the PWM output so wrote this:

void configPWM(uint8_t pwmmode, uint16_t prescaler)	// set PWM mode and prescaler frequency
{	
	if (pwmmode){
	TCCR0A = _BV(WGM00);}				// set to phase correct PWM mode.

	if (!pwmmode){
	TCCR0A = _BV(WGM00)|_BV(WGM01);}		// set to fast PWM 



	if (prescaler == 8){
	TCCR0B = _BV(CS01);}				// clk/8 prescaler

	else if (prescaler == 64){
	TCCR0B = _BV(CS01) | _BV(CS00);}		// clk/64 prescaler

	else if (prescaler == 256){
	TCCR0B = _BV(CS02);}				// clk/256 prescaler

	else if (prescaler == 1024){
	TCCR0B = _BV(CS02) | _BV(CS00);}		// clk/1024 prescaler

	else{
	TCCR0B = _BV(CS00);}				// no prescaler (Default setting.)
}

I've tested it at 1.2MHz and the measured outputs are pretty close to the theoretical - slightly lower, but possibly just due to chip tolerances. The nominal FAST output (pwmmode=1) with prescaler=1 should be 4687Hz, I measure it at about 4460Hz. The nominal PHASE CORRECT output (pwmmode=0) with prescaler=1024 should be 2.3Hz, I measure it at about 2.1Hz.

The function compiles to 68 bytes, with an additional 8 bytes for every reuse.

I just like this tiny13 MCU , even more than ATtiny85, some litle effort and you can do great things with little resource.
Many thanks to smeezekitty - you did great job with this core.
I find some fine two channel volt meter here - MrПоделкинЦ Blog: Двух канальный вольтметр на Attiny13 и 74CH595

and with this small serial library - Nerd Ralph: AVR half-duplex software UART supporting single pin operation
( only 64 bytes more on flash work also great with ATtiny85) i manage to do some modifications and to create this small ATtiny13 two chanel Console terminal volt meter.
I use LP2950ACZ-5.0 (5V - 0.5%) LDO for power supply and for reference also ( 100mA max. current), 10KOhm input resistors , and result is pretty accurate ( 4.096 V , 0.728 V with my cheap volt meter ). And a little unnecessary graphics purely to try to see what can do with so little memory.

#include <BasicSerial.h> // Samo TX ( SALJE Serial)
//#include <BasicSerial3.h> // TX i RX( SALJE I PRIMA Serial)
#include <util/delay.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////
// #define UART_Tx 3 - U BasicSerial.S tj. Po defaultu Pin 3 ( PB3 ) - 115.2kbps ako je 9.6 MHz-a
void serOut(const char* str){
while (*str) TxByte (*str++);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////

#include <avr/pgmspace.h>
// Ispis na istoj poziciji u externoj konzoli - pre svake komande mora Escape - \033 ( Umesto \033 moze \x1B ):
// \033 - Escape
// [2J - Clear screen
// [H - Go to home position (0,0)
// [0;36m - color tirkiz
// [0;34m - plava , [1;34m - svetlo plava
// [?25l - hide_cursor
// [4m - underscore
// [0;m - Normal color za sve
// [0;5m"; - blink
// [1;5m"; - blink bold
// \033[m - stop za blink
prog_char Message0[] PROGMEM = {
"\033[H\n\033[1;34m#\033[0;31m-------- \033[0;36mQ"};
prog_char Message1[] PROGMEM = {
"\033[H\n\n\033[1;34m#\033[0;31m-------- \033[0;32mQ"};
prog_char Message2[] PROGMEM = {
"\033[1;34m### \033[0;m\033[1;5mATTINY13 VOLT METER\033[m \033[1;34m###\033[?25lQ"};
prog_char Message3[] PROGMEM = {
" \033[0;31m--------\033[1;34m#Q"};
prog_char Message4[] PROGMEM = {
"\033[1;34m###########################Q"};

#define ADC_Pin_1 A1 // PB2
#define ADC_Pin_2 A2 // PB4

/////////////////////////////////////////////////////////////////////////////
int main() {
#if defined (AVR_ATtiny13)
char buf[2];
byte b = 0;
while(1){
buf[0] = pgm_read_byte_near(&(Message2**));**
** buf[1] = '\0';**
** if(buf[0] == 'Q')break;**
** serOut(buf);**
** b++;**
** }**
** b = 0;**
** serOut("\n\n\n");**
** while(1){**
buf[0] = pgm_read_byte_near(&(Message4**));
__
buf[1] = '\0';__
__
if(buf[0] == 'Q')break;__
__
serOut(buf);__
__
b++;__
__
}__
#elif defined(AVR_ATtiny85)
__
serOut("\x1B[0;31mATtiny85\033[?25l");__
__
#else**__
** serOut("");**
#endif
** DDRB &= ~(1 << ADC_Pin_1); // pinMode(ADC_Pin, INPUT);
DDRB &= ~(1 << ADC_Pin_2); // pinMode(ADC_Pin, INPUT);
#if defined (AVR_ATtiny13)
turn_on_adc(); // Samo ako odaberemo drugu verziju**

#endif
** while(1) {**
** unsigned int adc_read_1,adc_read_2;
#if defined (AVR_ATtiny13)
adc_read_1 = (/analogRead/readADC(ADC_Pin_1));
adc_read_2 = (/analogRead/readADC(ADC_Pin_2));
#elif defined(AVR_ATtiny85)
adc_read_1 = (((unsigned int)analogRead(ADC_Pin_1)*49/10));
adc_read_2 = (((unsigned int)analogRead(ADC_Pin_2)*49/10));
__
#endif**__
** print_tree_UINT_dec( adc_read_1, 1 );
print_tree_UINT_dec( adc_read_2, 2 );
delay_ms(500);
__
}**_
** return 0;**
}
/////////////////////////////////////////////////////////////////////////////////////////
void turn_on_adc(){ADCSRA |= (1 << ADEN) |(1 << ADPS2)|(1 << ADPS1);
** ACSR |= (1 << ACD);**
** DIDR0 |= (1 << ADC3D)|(1 << ADC2D);**
}
unsigned int readADC(unsigned char ch) {
** ADMUX = ch;**
** ADCSRA |= (1 << ADSC);**
** while((ADCSRA & (1 << ADSC)));**
** // return(ADC); // 0-1023**
////// LP2950ACZ-5.0 ~~ 5V , 0.5% ///////////////
__ return(ADC*49/10); // 1023 * 49 / 10 = (int)5012.4 = 5012( 49/10 kada je 5.0 V = Vcc = refV)
** // Posto koriscenje float prom. trosi mem. mora se koristiti racional aprox. kao ovde a za ref. voltazu birati Vcc sa sto tacnijom regulacijom.**
}
////////////////////////////////////////////////////////////////////////////////////////////////
// unsigned int max do 65 536
void print_tree_UINT_dec( unsigned int aprox_a , int voltCh ){
** unsigned int decimals = 1000; // 3 decimale**
** unsigned int b = aprox_a / decimals; // ceo broj 3**
** unsigned int c = (aprox_a % decimals) / (decimals/10); // 1. decimala - ostatak 1**
** unsigned int d = (aprox_a % (decimals/10) ) / (decimals/100); // 2. decimala - ostatak 4**
** unsigned int e = aprox_a % (decimals/100); // 3. decimala - ostatak 4**
** char buf[2];**
** byte bb = 0;**
** if(voltCh == 1){**
** while(1){**
** buf[0] = pgm_read_byte_near(&(Message0[bb]));
__
buf[1] = '\0';**

** if(buf[0] == 'Q')break;**
** serOut(buf);**
** bb++;**
** }**
** }**
** if(voltCh == 2){**
** bb = 0;**
** while(1){**
** buf[0] = pgm_read_byte_near(&(Message1[bb]));
__
buf[1] = '\0';__
__
if(buf[0] == 'Q')break;__
__
serOut(buf);__
__
bb++;__
__
}__
__
}__
__
char buffer [2];__
__
itoa (b,buffer,10);__
__
serOut( buffer );__
__
serOut( "." );__
__
itoa (c,buffer,10);__
__
serOut( buffer );__
__
itoa (d,buffer,10);__
__
serOut( buffer );__
__
itoa (e,buffer,10);__
__
serOut( buffer );__
__
serOut( " V" );__
__
// Crvene crtice desno __
__
bb = 0;__
__
while(1){__
buf[0] = pgm_read_byte_near(&(Message3[bb]));
__
buf[1] = '\0';__
__
if(buf[0] == 'Q')break;__
__
serOut(buf);__
__
bb++;__
__
}__
__
}__
__
[/quote]__
ATtiny13_Terminal_VoltMeter.zip (1.76 KB)**

Thanks for sharing. Although it begs the question on why you are overriding the ADC functions.

To kosine:
I have noticed your feedback but I haven't had time to implement anything.
That /4 line was for testing and somehow it slipped through. It will be fixed in the next release

smeezekitty:
Thanks for sharing. Although it begs the question on why you are overriding the ADC functions.

No reason at all, build in analogRead work fine in 0.19 core , i just trying something and forget to roll back before post.

Hi there all,

Ive been out of the t13 for a few weeks now, but thought I would get round to publishing a working project on here

ATTiny13 - DMX512 to WS2812 RGB LED interface

Thanks to all that have helped and contributed

Bob

Thanks for sharing.
I am long overdue for an update

Core13 0.20 released!

Features improved PWM output and rewritten delayMicroseconds() code
which should be more accurate at all clock speeds and delay length.

(first release of 2015!)

smeezekitty:
Core13 0.20 released!

Features improved PWM output and rewritten delayMicroseconds() code
which should be more accurate at all clock speeds and delay length.

(first release of 2015!)

Great, will give it a go to make sure my arduino-based 13a stuff still works. Any impact of these changes on generated code size?

evildave_666:
Great, will give it a go to make sure my arduino-based 13a stuff still works. Any impact of these changes on generated code size?

Certain functions may be a little larger. Sorry about that :frowning:

I have been playing with ATTINY13A again

This time I got to drive a projector in 8 colours DIRECTLY from the ATTINY13 !!

http://forum.arduino.cc/index.php?topic=288766.0

hi all.

I just unzip core13_20.zip on ...\Arduino\hardware\tiny\cores\core13

Where is the attiny def for boards.txt?

Am i in the right way?

It works with Attiny13A?

thanks.

furri:
Where is the attiny def for boards.txt?

There is a lot of boards.txt that are kicking around.
Some are more correct than others. I hacked this together:

########################

attiny13a.name=Attiny 13A standalone 128khz
attiny13a.upload.using=arduino:arduinoisp
attiny13a.upload.maximum_size=1024
attiny13a.upload.speed=19200
attiny13a.bootloader.low_fuses=0x7b
attiny13a.bootloader.high_fuses=0xFF
attiny13a.bootloader.path=empty
attiny13a.bootloader.file=empty
attiny13a.bootloader.unlock_bits=0xFF
attiny13a.bootloader.lock_bits=0xFF
attiny13a.build.mcu=attiny13
attiny13a.build.f_cpu=128000L
attiny13a.build.core=core13
#######################

attiny13f.name=Attiny 13A standalone 600khz
attiny13f.upload.using=arduino:arduinoisp
attiny13f.upload.maximum_size=1024
attiny13f.upload.speed=19200
attiny13f.bootloader.low_fuses=0x69
attiny13f.bootloader.high_fuses=0xFF
attiny13f.bootloader.path=empty
attiny13f.bootloader.file=empty
attiny13f.bootloader.unlock_bits=0xFF
attiny13f.bootloader.lock_bits=0xFF
attiny13f.build.mcu=attiny13
attiny13f.build.f_cpu=600000L
attiny13f.build.core=core13
#######################

attiny13c.name=Attiny 13A standalone 1.2mhz
attiny13c.upload.using=arduino:arduinoisp
attiny13c.upload.maximum_size=1024
attiny13c.upload.speed=19200
attiny13c.bootloader.low_fuses=0x6a
attiny13c.bootloader.high_fuses=0xFF
attiny13c.bootloader.path=empty
attiny13c.bootloader.file=empty
attiny13c.bootloader.unlock_bits=0xFF
attiny13c.bootloader.lock_bits=0xFF
attiny13c.build.mcu=attiny13
attiny13c.build.f_cpu=1200000L
attiny13c.build.core=core13

#######################
attiny13d.name=Attiny 13A standalone 4.8Mhz
attiny13d.upload.using=arduino:arduinoisp
attiny13d.upload.maximum_size=1024
attiny13d.upload.speed=19200
attiny13d.bootloader.low_fuses=0x79
attiny13d.bootloader.high_fuses=0xFF
attiny13d.bootloader.path=empty
attiny13d.bootloader.file=empty
attiny13d.bootloader.unlock_bits=0xFF
attiny13d.bootloader.lock_bits=0xFF
attiny13d.build.mcu=attiny13
attiny13d.build.f_cpu=4800000L
attiny13d.build.core=core13
#######################
attiny13e.name=Attiny 13A standalone 9.6Mhz
attiny13e.upload.using=arduino:arduinoisp
attiny13e.upload.maximum_size=1024
attiny13e.upload.speed=19200
attiny13e.bootloader.low_fuses=0x7A
attiny13e.bootloader.high_fuses=0xFF
attiny13e.bootloader.path=empty
attiny13e.bootloader.file=empty
attiny13e.bootloader.unlock_bits=0xFF
attiny13e.bootloader.lock_bits=0xFF
attiny13e.build.mcu=attiny13
attiny13e.build.f_cpu=9600000L
attiny13e.build.core=core13

It works with Attiny13A?

It works on all Attiny13 series

Yay! My AtTiny13A's just arrived. 10 days from Shenzhen to Scandinavia must be some kind of record :slight_smile:

Just received two attiny13a PU.
Plan 1: use it to drive a 4-pin RGB led. User shud be able to control intensity of each color using a push-button.

Plan 2: use it to drive a single(may be multiple, but don't really need it now) WS2812 based neoPixcel. Here also user should be able to control the individual color using push buttons.

In both of above case, either 3 push switch can be used to change individual color intensity with a step value of say 32 or 64. OR 2 push switch can be used; one for select the color to change and other to change the color intensity by a step value.

Should I burn a bootloader to the tiny? Please suggest how can I keep the code below 1K.

sudhirkhamari:
Plan 2: use it to drive a single(may be multiple, but don't really need it now) WS2812 based neoPixcel. Here also user should be able to control the individual color using push buttons.

I did THIS on attiny13a, most is not relevant here, but I wrote a fast ws2812 driving routine in assembler which works a treat !!

Thanks mcnobby. I hv gone through your post on dmx/attiny. I am planning to leverage from the same. This is the t13 i bought:http://m.ebay.com/itm/201190520005?nav=SEARCH
Could you please suggest me what should i put in boards.txt file.

I've found this WorldSemi library useful: GitHub - cpldcpu/light_ws2812: Light weight library to control WS2811/WS2812 based LEDS and LED Strings for 8-Bit AVR microcontrollers.

I've used it on attiny13 with the raw avr libraries.