Go Down

Topic: core13: An Arduino core for the Attiny13 *testers wanted* (Read 49987 times) previous topic - next topic


kosine

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

Code: [Select]

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,  256*5=1280 clock cycles and 1280*0.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.

smeezekitty


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

Code: [Select]

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

Quote

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,  256*5=1280 clock cycles and 1280*0.833=1066us.

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

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
Avoid throwing electronics out as you or someone else might need them for parts or use.
Solid state rectifiers are the only REAL rectifiers.
Resistors for LEDS!

kosine

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.

Code: [Select]

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
}

kosine

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:
Code: [Select]

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:

Code: [Select]

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.

common_ground

#290
Oct 29, 2014, 03:57 am Last Edit: Oct 29, 2014, 04:21 am by common_ground
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 - http://razniepodelki.blogspot.com/2014/05/attiny13-74ch595-lcd-on-hd44780-2.html

and with this small serial library - http://nerdralph.blogspot.dk/2014/01/avr-half-duplex-software-uart.html
( 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.



Quote
#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++;
  }
}



smeezekitty

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
Avoid throwing electronics out as you or someone else might need them for parts or use.
Solid state rectifiers are the only REAL rectifiers.
Resistors for LEDS!

common_ground

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.

mcnobby

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
while (z--) { snoreEvenLouder(); }
www.smartshow.lighting - www.dmx512.lighting

smeezekitty

Thanks for sharing.
I am long overdue for an update
Avoid throwing electronics out as you or someone else might need them for parts or use.
Solid state rectifiers are the only REAL rectifiers.
Resistors for LEDS!

Go Up