delayMicroseconds() is way off with attiny85 @8MHz #685
Replies: 9 comments 4 replies
-
Yes, I had the same issue when playing with IR transmitters for the first time. The "delayMicroseconds()" function and the "digitalWrite()" function take up quite some clock-cycles themselves to complete. Discarding digitalWrite and using direct port manipulation may improve that a bit, but delayMicroseconds is not accurate enough on the T85 itself I observed. I had to use the hardware timer (TCNT0 or TCNT1) to get a stable PWM |
Beta Was this translation helpful? Give feedback.
-
Also note that the internal oscillator is only 10% accurate if not user calibrated as specified in the datasheet. |
Beta Was this translation helpful? Give feedback.
-
Why would you expect something like that? digitalWrite() is slow as shit. It takes around 6us to execute (at 8 MHz)... . The Arduino API functions were written largely without consideration for efficiency and with an ATmega x8 running at a power-of-2 MHz clock as the target, and that happened to become a huge smash hit. Making no assumptions about the pin mapping or target processor, the conceptual basis of digitalWrite() is sound, and it's implementation follows from that that rather straightforward starting point.
Of course, there are a lot of simplifying assumptions that can be made particuarly if you consider what parts are being used. (ex, on the 85, you know that any valid pin will be on PB and hence use PORTB/DDRB/PINB registers, that any pin number < 6 is valid and all others are not, and that it's bitmask is 1<<pin number). In 2.0.0-dev (used for above as well), if you replace the calls to digitalWrite() with digitalWriteFast(), you get fully optimal code - each write to a constant numbered pin compiles to an SBI or CBI instruction. digitalWriteFast is not available in the released versions. Currently I have considered making this happen for all calls to digitalWrite with constant pin numbers but decided against it because there is a large volume of code in the wild that makes implicit assumptions about the speed of digitalWrite (this is not theoretical; I've seen sketches and libraries that people were copying code from and posted about on the forums (for unrelated reasons) and noticed that the code would break badly if automatic optimization of digitalWrite() with constant pins was performed, which I'd been considering (until I saw that sketch, which finished off that idea. But yeah, your problem is the fact that digitalWrite() is a lot slower than you were expecting. Why do you feel a need to reinvent the wheel w/regards to USI-based I2C? The included Wire.h library will transparently choose the correct implementation of I2C (for example, using the USI on t85, and a fully software implementation for I2C master on the 841 and 828 because they have neither hardware I2C master nor a USI - the only hardware help they have is the TWI slave interface, which of course we use when the part is configured as slave). |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for responding to this thread. @hmeijdam, your response gave me confidence that I am not on wrong track. @SpenceKonde , Thanks a lot for your detailed response. I indeed tried Wire, Adafruit TinyWireM or directly USI_TWI_Master libraries. They don't use digitalWrite(). They use direct register and port manipulations. None of them are working with default T_LOW=4.7us and T_HIGH=4us to create a 100KHz clock. In fact, these libraries are not even creating a valid I2C pattern (screen shot attached). This is my test code with Wire library. // Setup routine, here we will configure the microcontroller and compass. void loop() If I don't use external 4.7K pullups, logic analyzer captures flatlines on SCL/SDA. When I use 4.7K external pullups, only START condition is created (see attachment)...that's it. using my homegrown code for USI I2C, I am able to create perfect I2C pattern and able to read a magnetometer over I2c. But I am using very slow clock now (50, 25), just to solidify my code. Is there a way to make Wire library create a slower I2C clock? (just to see if is are not working because of clock speed, or some bug in the library)? Without it I am stuck with these libraries and need to reinvent my own USI I2C code. Thanks a lot for your time. [UPDATED] I apologize for wasting your time. |
Beta Was this translation helpful? Give feedback.
-
The sceenshot I sent earlier (where 100kHz I2C is working), is when my sketch includes USI_TWI_Master.h and uses its functions directly. But when I include <Wire.h>, and use Wire library member functions, the I2C clock becomes 36KHz. I noticed that the USI_TWI_Master.h I am using and the one included in your ATTinyCore is slightly different. I was using USI_TWI_Master.h that comes included with Adafruit TinyWireM library. This has the following and uses _delay_us function. It was giving me 100KHz SCL.
Whereas, USI_TWI_Master.h that you are using in ATTinycore is little different. It uses the following definitions
I am suspecting the _delay_loop_1 function inside delay_basic.h is causing it. I have already proved my project in Nano using Wire library. I am just porting it to AtTiny85 and I would love to stick with Wire library, instead of using USI_TWI_Master library natively. Is there any way (without modifying the lines above in SRC directory of ATTinyCore,) I can override DELAY_T2TWI to (_us_delay(5)) from my sketch? Thanks for your help. |
Beta Was this translation helpful? Give feedback.
-
Hello everyone,
I don't know where the port in this library comes from, but it has two big problems:
in my research I then came across this page almost by chance: For me the best thing would be to go back to the modified Atmel code:
For the USI_TWI_Master.c you cannot use the Atmel code directly because the USI_TWI_Start_Transceiver_With_Data has been modified for the needs of Wire (stop ...), but you can easily make the following changes to the current one: change all occurrence of: and paying attention to this:
to be changed to:
these also greatly simplify the reading of the code ... I then also modified Wire.cpp:
as I have completely eliminated the USI_TWI_Master_Speed function. Using these changes in USI_TWI_Master.h and USI_TWI_Master.c a transfer of 7 bytes + address at 100KHz takes exactly the same 940us average time of the mega328P, at 400KHz it is slightly faster, ~ 312 us against the 316-320 of the mega328P. at 16MHz the delay cycle calculation is very correct, for 'strange' clocks there will probably be some changes to be made... One last thing ... I noticed that with more I2C peripherals the tiny85 needs more 'robust' pullups. I hope I have written something useful for you :) |
Beta Was this translation helpful? Give feedback.
-
I think I've gotten the USI I2C problem solved for 2.0.0, essentially in the same way, only without removing the master speed function and hence the ability to enable fastmode. |
Beta Was this translation helpful? Give feedback.
-
That would be because I haven't checked in those changes.... I think the built-in pullup interacts with the USI differently than it does with real hardware I2C. I2C without an external pullup is never remotely close to meeting spec HOWEVER where there's real hardware I2C, classic AVRs when they aren't actively driving the bus low, will set the pins high and input - hence turning on the internal pullup which is about 35k (ie, nearly an order of magnitude weaker than it should be). This is, amazingly, sometimes good enough for very simple configurations to work under ideal conditions. In DxCore and megaTinyCore, I specifically don't turn on the pullups unless the user asks for them. If the pullups being turned on fixes the problem, you forgot to install external pullups, or the Vcc rail you connected them to isn't actually tied to vcc or something like that. 4.7k is appropriate for pullups on at normal speed, I think you need stronger pullups for fast mode. |
Beta Was this translation helpful? Give feedback.
-
This may be, I have no experience with using the USI device such as I2C, so far I have only used it in SPI mode. |
Beta Was this translation helpful? Give feedback.
-
![ATtiny85_delayMicroseconds](https://user-images.githubusercontent.com/46716000/160541035-7f5dac3d-cd71-4b07-9704-6bb0777c07ab.png)
I am using a naked attiny85 on a breadboard and using an Uno as ISP. I had installed this core v1.5.2 today. Selected the following,Board: "ATtiny25/45/85 (No Bootloader)"
Chip: "ATtiny85"
Clock Source (Only set on bootload): "8 MHz (internal)"
Timer 1 Clock: "CPU (CPU frequency)"
LTO (1.6.11+ only): "Enabled"
Save EEPROM (only set on bootload): "EEPROM retained"
B.O.D. Level (Only set on bootload): "B.O.D Disabled (saves power)"
The burned the bootloader. Readback the fuses and they are correct (E: FF, H: DF, L: E2].
I intend to implement a USI I2C master @100khz, that will master 2 slaves. In USI, the clock is generated by SW by writing to USICR register. The clock frequency achievable thus depends on resolution and accuracy of delay functions.
I did a simple test with register write to a output pin and capturing the pin using a logic analyzer. the sketch is,
`//ATTIny85 Pin Map
#define LED1_PIN 3 //PB2 (pin 2)
#define LED2_PIN 4 //PB4 (pin 3)
void setup()
{
pinMode(LED1_PIN, OUTPUT);
digitalWrite(LED1_PIN,LOW);
pinMode(LED2_PIN, OUTPUT);
digitalWrite(LED2_PIN,LOW);
delay(1000);
digitalWrite(LED1_PIN,HIGH);
delayMicroseconds(4);
digitalWrite(LED1_PIN,LOW);
delayMicroseconds(5);
digitalWrite(LED1_PIN,HIGH);
delayMicroseconds(4);
digitalWrite(LED1_PIN,LOW);
delayMicroseconds(5);
digitalWrite(LED1_PIN,HIGH);
delayMicroseconds(4);
digitalWrite(LED1_PIN,LOW);
delayMicroseconds(5);
digitalWrite(LED1_PIN,HIGH);
delayMicroseconds(4);
digitalWrite(LED1_PIN,LOW);
delayMicroseconds(5);
}
// Our main program loop.
void loop()
{
} `
I was expecting to see for pulses with 4us high period and 5us low period, with around 1us deviation (for execution cycles). But the logic analyzer trace shows that the periods are way off. T(High) is 9.33us and T(Low) is 10.5us (see attached screenshot)
When I use delayMicroseconds(1), the periods become worst. So, I can't even create a clock with period (High _ Low) 10us, which is required for 100KHz I2C. 400KHz is out of question. Max I seem to be able to reach is 50KHz.
Is it expected with Attiny @8MHz? I understand that 8MHz is 125ns cycle period. but 4us means 32 cycles. It should be easily achievable in a tight delay loop, isn't it? I believe some people reported to use Attiny85 to easily achieve 100KHz I2C. But I fail to see how?
Is it issue with this core? Some setting is messing with the delay loop implementation of delayMicroseconds?
Anybody faced this kind of issue? Appreciate if you can help me with your insight.. I really want to reach at least 100KHz I2C.
Beta Was this translation helpful? Give feedback.
All reactions