Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 16-bit timers #30

Merged
merged 3 commits into from Apr 16, 2020
Merged

Implement 16-bit timers #30

merged 3 commits into from Apr 16, 2020

Conversation

urish
Copy link
Contributor

@urish urish commented Apr 4, 2020

See #29 for details

@urish urish added the enhancement New feature or request label Apr 4, 2020
@urish urish self-assigned this Apr 4, 2020
@urish urish linked an issue Apr 4, 2020 that may be closed by this pull request
@urish
Copy link
Contributor Author

urish commented Apr 4, 2020

@gfeun can you please check you code with this new branch and report?
All the WGM modes should now be defined correctly both for 8 and 16 bit timers, though not all the behavior is implemented: we still don't buffer the value of OCRx (it always updates Immediately), and TOV flag is always set on BOTTOM. This is not a regression, since we haven't implemented the OCRx / TOV behaviors before, even not for the 8-bit timers.

@gfeun
Copy link
Contributor

gfeun commented Apr 9, 2020

Hey @urish,

So the timer1 works now ... but like a 8 bit one 😃

When i set OCR1A > 255, it doesn't work anymore.
For example if OCR1A = 15624 (1 interrupt per second with a 1024 pre scaler and a 16Mhz µC ) it is treated as a u8: OCR1A = 8 and interrupts are called way more often 😄

So I think it comes from the fact that internally all timer handling functions still assumes 8 bit:
Beginning with OCR1A:


It should be 16bits on 0x89 0x88, see p144 of the doc

Same for TCNT register:


TCNT: 0x84,

It should be 16 bits across 0x85 and 0x84, see p143 of doc

I don't understand how u8 and u16 types are handled since they are both defined as number. They don't seem to be treated differently. But it could also be necessary to update function signatures to take a u16 for 16 bit timers:

cpu.writeHooks[config.TCNT] = (value: u8) => {

set TCNT(value: u8) {

private timerUpdated(value: u8) {

@urish
Copy link
Contributor Author

urish commented Apr 9, 2020

Hi Glenn, thanks for the detailed analysis!

Actually, u16 and u8 are both aliases for number, as you mentioned. They are leftovers from using AssemblyScript, which is supposed to compile TypeScript to Web Assembly. But we don't use that currently, so changing u8 to u16 will have no effect.

It probably has to be with the fact that we add CPU watches for a single address:

cpu.writeHooks[config.OCRA] = (value: u8) => {

I will update the code there to support 16 bit timers as well

@gfeun
Copy link
Contributor

gfeun commented Apr 9, 2020

Thanks for the explanation !

Interesting to hear about assembly script, I'm learning about Web Assembly for work at the moment.

I'm going to test modifying CPU watches on my side too

@urish
Copy link
Contributor Author

urish commented Apr 9, 2020

@gfeun I have updated the branch with some fixes, so hopefully now OCR1A should function as expected. Can you please have a look on the updated code?

@gfeun
Copy link
Contributor

gfeun commented Apr 9, 2020

I think it works with the following changes:

diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index ea66671..b3bc770 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -237,7 +237,7 @@ export class AVRTimer {
   set TCNT(value: u16) {
     this.cpu.data[this.config.TCNT] = value & 0xff;
     if (this.config.bits === 16) {
-      this.cpu.data[this.config.TCNT + 1] = (value >> 16) & 0xff;
+      this.cpu.data[this.config.TCNT + 1] = (value >> 8) & 0xff;
     }
   }
 
@@ -281,7 +281,7 @@ export class AVRTimer {
   private registerHook(address: number, hook: (value: u16) => void) {
     if (this.config.bits === 16) {
       this.cpu.writeHooks[address] = (value: u8) => {
-        hook(this.cpu.data[address + 1] | value);
+        hook((this.cpu.data[address + 1] << 8) | value);
       };
       this.cpu.writeHooks[address + 1] = (value: u8) => {
         hook((value << 8) | this.cpu.data[address]);

set TCNT(value: u16) {
this.cpu.data[this.config.TCNT] = value & 0xff;
if (this.config.bits === 16) {
this.cpu.data[this.config.TCNT + 1] = (value >> 16) & 0xff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.cpu.data[this.config.TCNT + 1] = (value >> 16) & 0xff;
this.cpu.data[this.config.TCNT + 1] = (value >> 8) & 0xff;

private registerHook(address: number, hook: (value: u16) => void) {
if (this.config.bits === 16) {
this.cpu.writeHooks[address] = (value: u8) => {
hook(this.cpu.data[address + 1] | value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hook(this.cpu.data[address + 1] | value);
hook((this.cpu.data[address + 1] << 8) | value);

@gfeun
Copy link
Contributor

gfeun commented Apr 9, 2020

Tested successfully with the following code runnable in the demo, and including my 2 changes, good job !
(I only tested the CTC mode)

void setup()
{
    // Builtin led as output
    DDRB |= 0x20;

    // initialize Timer1
    cli(); // disable global interrupts
    TCCR1A = 0; // set entire TCCR1A register to 0
    TCCR1B = 0; // same for TCCR1B

    // set compare match register to desired timer count:
    OCR1A = 15624;
    // turn on CTC mode:
    TCCR1B |= (1 << WGM12);
    // Set CS10 and CS12 bits for 1024 prescaler:
    TCCR1B |= (1 << CS10);
    TCCR1B |= (1 << CS12);
    // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);
    // enable global interrupts:
    sei();
}

void loop()
{
}

ISR(TIMER1_COMPA_vect)
{
    PORTB ^= 0x20; // Toggle builtin led
}

@urish
Copy link
Contributor Author

urish commented Apr 9, 2020

Yay!

Thanks :)

Can you try to come up with a failing test case(s) for the changes that you suggested?

@urish
Copy link
Contributor Author

urish commented Apr 9, 2020

(I mean tests that fail with the current code, and pass after applying your changes)

@gfeun
Copy link
Contributor

gfeun commented Apr 9, 2020

Update: I was wrong, this test passes for both with and without my modification.

    it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => {
      const timer = new AVRTimer(cpu, timer1Config);
      cpu.writeData(0x84, 0xff); // TCNT1 <- 0x00ff
      cpu.writeData(0x85, 0x00); // ...
      cpu.writeData(0x88, 0x00); // OCR1A <- 0x0100
      cpu.writeData(0x89, 0x01); // ...
      cpu.writeData(0x80, 0x0); // WGM1 <- 0 (Normal)
      cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1
      cpu.cycles = 1;
      timer.tick();
      expect(cpu.data[0x36]).toEqual(2); // TIFR0 should have OCF0A bit on
      expect(cpu.pc).toEqual(0);
      expect(cpu.cycles).toEqual(1);
    });

also fix some issues found by @gfeun and the tests
@urish
Copy link
Contributor Author

urish commented Apr 12, 2020

Alright, I applied the fixes, added tests, fixed a few more issues I found - I think this is ready to go!

@gfeun
Copy link
Contributor

gfeun commented Apr 16, 2020

Great, this also works on my side.

Thanks for finishing this, i couldn't write a test failing for the invalid code.
I think I was missing the cpu.cycles = 2.

@urish
Copy link
Contributor Author

urish commented Apr 16, 2020

Lovely! I will merge and release soon.

@urish urish merged commit 9f06d4e into master Apr 16, 2020
@urish
Copy link
Contributor Author

urish commented Apr 16, 2020

Released as part of avr8js 0.8.0

@urish urish deleted the 16bit-timer-fix branch April 16, 2020 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

16 bit TIMER1 doesn't work
2 participants