sadr0b0t / arduino-timer-api

Cross-platform Arduino timer API
GNU Lesser General Public License v3.0
35 stars 18 forks source link

Добавить варианты 32-битных таймеров на PIC32/ChipKIT #1

Closed sadr0b0t closed 6 years ago

sadr0b0t commented 6 years ago

Добавить варианты 32-битных таймеров на PIC32/ChipKIT

Во-первых, без 32-битного режима на PIC32 не получится добавить реализацию вспомогательных вызовов timer_init_ISR_1Hz и timer_init_ISR_2Hz, которые есть на Ардуине

Варианты вызовов timer_init_ISR_2Hz (2Гц, период 500мс) и timer_init_ISR_1Hz (1Гц, период 1с) пока отключены, т.к. при 16-битных режимах таймеров PIC32MX 80МГц комбинация делитель частоты (prescaler - максимальный вариант 1/256) + поправка периода (adjustment - максимальный вариант 2^16=65536) дают минимальную частоту 5Гц (период - 200мс): 80000000/256/65536 = 4.8Гц

Чтобы сделать еще меньше, нужно переводить таймер в режим 32 бит (при этом на PIC32 не все таймеры работают в 32-битном режиме, а те, которые работают, занимают ресурсы 2го таймера: например, Timer4(16бит)+Timer5(16бит)=Timer4(32бит)).

На Ардуине можно получить частоту 1Гц стандартными делителями, но без аналогичного вариата для PIC32 вызовы не получится сделать кросс-платформенными.

Во-вторых, раз уж такие таймеры есть, то логично иметь в качестве варианта в библиотеке, работающей с таймерами.

В API можно решить вопрос следующим образом:

Доки по включению таймеров в разных режимах смотреть здесь https://github.com/1i7/snippets/tree/master/chipkit-timer/doc https://github.com/1i7/snippets/blob/master/chipkit-timer/doc/61105F-pic32-rm-sec14-Timers.pdf

sadr0b0t commented 6 years ago

Немного теории

В даташите на PIC32MX3-4 https://github.com/sadr0b0t/snippets/blob/master/chipkit-timer/doc/PIC32MX3-4%20Data%20Sheet.pdf

глава 14.0 Timer2/3 and Timer4/5, стр. 105 Two 32-

bit synchronous timers are available by combining Timer2 with Timer3 and Timer4 with Timer5. The 32-bit timers can operate in three modes: • Synchronous Internal 32-bit Timer • Synchronous Internal 32-bit Gated Timer • Synchronous External 32-bit Timer

Note: Throughout this chapter, references to registers TxCON, TMRx and PRx use ‘x’ to represent Timer2 through 5 in 16-bit modes. In 32-bit modes, ‘x’ represents Timer2 or 4; ‘y’ represents Timer3 or 5.

Т.е. 1) Для 32-битных таймеров объединяем 16-битные таймеры Timer2+3 или Timer4+5 2) Когда ссылаемся на имена регистров в контексте беседы о 32-битном таймере, x обозначает таймеры 2 или 4, y - таймеры 3 или 5.

Дальше смотрим референс мануал, раздел 14 - про таймеры, в отдельном файле здесь: PIC32 family reference manual Section 14 Timers https://github.com/1i7/snippets/blob/master/chipkit-timer/doc/61105F-pic32-rm-sec14-Timers.pdf

Там идем в раздел 14.4 Interrupts > Example 14-9: Timer ISR code example

/*
This code example demonstrates a simple interrupt service routine for Timer
interrupts. The user’s code at this ISR handler should perform any application
specific operations and must clear the corresponding Timer interrupt status flag
before exiting.
*/
void __ISR(_Timer_1_Vector,ipl3)Timer1Handler(void)
{
    ... perform application specific operations in response to the interrupt
    IFS0CLR = 0x00000010; // Be sure to clear the Timer1 interrupt status
}

Example 14-10: 32-bit timer interrupt initialization code example

/*
This code example enables Timer5 interrupts, loads the Timer4:Timer5 period
register pair, and starts the 32-bit Timer module.
When a 32-bit period match interrupt occurs, the user must clear the Timer5 interrupt
status flag in software.
*/

    T4CON = 0x0;       // Stop 16-bit Timer4 and clear control register
    T5CON = 0x0;       // Stop 16-bit Timer5 and clear control register
    T4CONSET = 0x0038; //Enable 32-bit mode, prescaler at 1:8,
                       // internal clock source

    TMR4 = 0x0;       // Clear contents of the TMR4 and TMR5
    PR4 = 0xFFFFFFFF; // Load PR4 and PR5 registers with 32-bit value

    IPC5SET = 0x00000004; // Set priority level = 1
    IPC5SET = 0x00000001; //Set sub-priority level = 1
                          // Can be done in a single operation by assigning
                          // IPC5SET = 0x00000005

    IFS0CLR = 0x00100000; // Clear the Timer5 interrupt status flag
    IEC0SET = 0x00100000; // Enable Timer5 interrupts

    T4CONSET = 0x8000; // Start the timer

Здесь можно обратить внимание, что настройки отправляются в регистры для таймера 4, а прерывания - через настройки таймера 5.

Код, внезапно, оказался рабочим, только если добавить еще в начало вызов (вектор именно для таймера5):

setIntVector(_TIMER_5_VECTOR, T45_IntHandler);

плюс мой вариант обработчика (по шаблону из Servo.cpp)

void __attribute__((interrupt(),nomips16)) T45_IntHandler (void) {
    timer_handle_interrupts(TIMER4_32BIT);
    // Timer4(32bit) == Timer4(16bit)+Timer5(16bit),
    // Timer4 is a master timer, but interrupt flags come from Timer5
    IFS0bits.T5IF = 0; // Clear timer interrupt status flag
}

Дальше причесал этот код так, чтобы он больше стал похож на то, что было в Servo.cpp

        // set the vector up
        setIntVector(_TIMER_5_VECTOR, T45_IntHandler);

        // clear contents of the T4CON and T5CON registers
        T4CON = 0x0; // stop 16-bit Timer4 and clear control register
        T5CON = 0x0; // stop 16-bit Timer5 and clear control register

        // set timer clock period on Timer4
        T4CONbits.T32 = 1;           // set 32-bit mode
        T4CONbits.TCKPS = prescaler; // set prescaler
        TMR4 = 0;                    // clear timer4 and timer5 registers
        PR4 = adjustment;            // period register (32-bit value)

        // configure interrupt on Timer5
        IPC5CLR = 0x0000001F;
        IPC5SET = (_T5_IPL_IPC << 2) | _T5_SPL_IPC;

        IFS0bits.T5IF = 0; // clear the Timer5 interrupt status flag
        IEC0bits.T5IE = 1; // enable Timer5 interrupts

        // start Timer4+5
        T4CONbits.ON = 1;  // start Timer4+5

Здесь пояснения к причесыванию

1) Избавились от волшебного числа 38 в T4CONSET

        // Enable 32-bit mode, prescaler at 1:8,
        // internal clock source
        T4CONSET = 0x0038; // 0011 1000

вместо него идентичный участок с побитовым присвоением значений полям:

        T4CON = 0x0; // 0 everywhere for internal clock source
        T5CON = 0x0;
        T4CONbits.T32 = 1;           // set 32-bit mode
        T4CONbits.TCKPS = prescaler; // set prescaler

T4CONSET=0x0038 в двоичном виде: 0011 1000

0011 - это, действительно, prescaler=1/8

const int TIMER_PRESCALER_1_8    = 0b011; // 0011 0000

1000 (4й-бит регистра, индекс 3) - это T4CON<3>=1:

  1. Set the T32 control bit (TxCON<3> = 1) to select 32-bit operations.

по поводу внутреннего источника сигнала (internal clock source):

  1. Clear the TCS control bit (TxCON<1> = 0) to select the internal PBCLK source. т.е. 2й бит T4CON нужно выставить в толь (T4CON<1> = 0) это произошло после глобального обнуления регистра T4CON = 0x0, поэтому тоже ок.

2) Настройки прерываний

Здесь все очевидно

ручная запись значений (xxxCLR - сбросить в 0, xxxSET - записать 1)

    IFS0CLR = 0x00100000; // clear the Timer5 interrupt status flag
    IEC0SET = 0x00100000; // enable Timer5 interrupts

идентична побитовой записи полей:

    IFS0bits.T5IF = 0; // clear the Timer5 interrupt status flag
    IEC0bits.T5IE = 1; // enable Timer5 interrupts

ключевой момент - настройки для Таймера5

3) Настройки приоритетов

Здесь вариант из мануала:

    IPC5SET = 0x00000004;  // Set priority level = 1
    IPC5SET = 0x00000001; //Set sub-priority level = 1

заменил на вариант из Servo.cpp:

    IPC5CLR = 0x0000001F;
    IPC5SET = (_T5_IPL_IPC << 2) | _T5_SPL_IPC;

Проверять константы во 2м случае лень. Видно, что в 1м варианте только SET, а во 2м варианте еще какой-то бит сбрасывается в 0 с CLR. Но работает и так и так, поэтому пока пофик, оставлю вариант из Servo.cpp для единообразия.

Опять важно ,что найстройки выставляются для таймера5, а не 4.

sadr0b0t commented 6 years ago

И еще для полноты картины

Всё тот же референс мануал, раздел 14 - про таймеры, в отдельном файле здесь: PIC32 family reference manual Section 14 Timers https://github.com/1i7/snippets/blob/master/chipkit-timer/doc/61105F-pic32-rm-sec14-Timers.pdf

14.3.4.3. 32-BIT SYNCHRONOUS CLOCK COUNTER INITIALIZATION STEPS, стр 14.

The following s teps must be performed to configure the timer for 32-bit Synchronous Clock Counter mode.

  1. Clear the ON control bit (TxCON<15> = 0) to disable the timer.
  2. Clear the TCS control bit (TxCON<1> = 0) to select the internal PBCLK source.
  3. Set the T32 control bit (TxCON<3> = 1) to select 32-bit operations.
  4. Select the desired timer input clock prescale.
  5. Load/Clear the timer register TMRxy.
  6. Load the period register PRxy with the desired 32-bit match value.
  7. If interrupts are used: a) Clear the TyIF interrupt flag bit in the IFSx register. b) Configure the interrupt priority and subpriority levels in the IPCx register. c) Set the TyIE interrupt enable bit in the IECx register.
  8. Set the ON control bit (TxCON<15> = 1) to enable the timer.

Регистр должен быть не IPCx (т.е. IPC4), а IPCy (т.е. IPC5). С 4м нихера не работает, т.е. таймер, может и работает, но прерывание не вызывается.

Регистра IFSx (в смысле x, как номер таймера) нет, а есть общий на всей таймеры регистр IFS0 с полями T2IF, T3IF, T4IF, T5IF и т.п.

поэтому такой код (точно по инструкции):

    IFS4bits.T5IF = 0;

не будет даже компиляться, а такой скомпиляется и даже заработает

    IFS0bits.T5IF = 0;

По этой инструкции было бы логично написать код

T4CONbits.TCS = 0;

но он не скомпиляется, нет поля TCS. Но, к счастью, как-то обошлось и без него.

В итоге, исправленная инструкция

  1. Clear the ON control bit (TxCON<15> = 0) to disable the timer.
  2. Clear the TCS control bit (TxCON<1> = 0) to select the internal PBCLK source.
  3. Set the T32 control bit (TxCON<3> = 1) to select 32-bit operations.
  4. Select the desired timer input clock prescale.
  5. Load/Clear the timer register TMRxy.
  6. Load the period register PRxy with the desired 32-bit match value.
  7. If interrupts are used: a) Clear the TyIF interrupt flag bit in the IFS0 register. b) Configure the interrupt priority and subpriority levels in the IPCy register. c) Set the TyIE interrupt enable bit in the IEC0 register.
  8. Set the ON control bit (TxCON<15> = 1) to enable the timer.

То же смое с кодом:

    // 1. Clear the ON control bit (TxCON<15> = 0) to disable the timer.
    T4CONbits.ON = 0;

    // 2. Clear the TCS control bit (TxCON<1> = 0) to select the internal PBCLK source.
    //T4CONbits.TCS = 0; // won't compile

    // 3. Set the T32 control bit (TxCON<3> = 1) to select 32-bit operations.
    T4CONbits.T32 = 1;

    // 4. Select the desired timer input clock prescale.
    T4CONbits.TCKPS = prescaler; // set prescaler

    // 5. Load/Clear the timer register TMRxy.
    TMR4 = 0;

    // 6. Load the period register PRxy with the desired 32-bit match value.
    PR4 = adjustment;

    // 7. If interrupts are used:
    //   a) Clear the TyIF interrupt flag bit in the IFS0 register.
    IFS0bits.T5IF = 0;

    //   b) Configure the interrupt priority and subpriority levels in the IPCy register.
    IPC5CLR = 0x0000001F;
    IPC5SET = (_T5_IPL_IPC << 2) | _T5_SPL_IPC;

    //   c) Set the TyIE interrupt enable bit in the IEC0 register.
    IEC0bits.T5IE = 1;

    // 8. Set the ON control bit (TxCON<15> = 1) to enable the timer.
    T4CONbits.ON = 1;

в таком виде все работает - таймер тикает, прерывания прилетают. Это 2й рабочий вариант кода, но оставлю предыдущий, опять же, для единообразия

sadr0b0t commented 6 years ago

Готово дело https://github.com/sadr0b0t/arduino-timer-api/commit/4c984b0833437ee92fcdea8e1efeeaca113708cd

Заодно добавил больше таймеров: _TIMER1, _TIMER2, _TIMER3, _TIMER4, _TIMER5, _TIMER2_32BIT, _TIMER4_32BIT

Все работают на PIC32/ChipKIT, на AVR/Arduino имена не зайдействованы.

На ChipKIT TIMER_DEFAULT=_TIMER4_32BIT (32-битный режим, 1Гц и 2Гц работают из коробки)

И к именам таймеров добавил префикс нижнее подчерчивание "_", т.к. TIMER2 внезапно стал конфликтовать с определением из Arduino.h (при этом другие имена таймеров, как ни странно, заняты не были).