реклама на сайте
подробности

 
 
18 страниц V  < 1 2 3 4 > »   
Reply to this topicStart new topic
> Начало работы with scmRTOS, Несколько вопросиков
IgorKossak
сообщение Mar 4 2008, 20:09
Сообщение #16


Шаман
******

Группа: Модераторы
Сообщений: 3 064
Регистрация: 30-06-04
Из: Киев, Украина
Пользователь №: 221



Цитата(alux @ Mar 4 2008, 17:43) *
В overview XMK, которое можно найти в исходниках указана сегодняшняя дата (March 4, 2008). Выглядит подозрительно wink.gif

Сдаётся мне, что если откроете overview завтра, то там окажется завтрашнее число. cool.gif

Цитата(alux @ Mar 4 2008, 18:09) *
Имеет ли значение уровень оптимизации компилятора для smcRTOS ? Я всегда ставлю MAX.

И я тоже.
Go to the top of the page
 
+Quote Post
dxp
сообщение Mar 5 2008, 04:55
Сообщение #17


Adept
******

Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343



Цитата(Сергей Борщ @ Mar 4 2008, 22:02) *
Где определяется A90_PROC_OPTION я не нашел - ни в исходниках, ни в описании ассемблера. Неопределенный символ считается равным нулю. Можно предположить, что перед этим должна быть (утерянная) строка
Код
#define A90_PROC_OPTION   ((__TID__ >> 4) & 0x0F)

Действительно, косяк. Как он вкрался, не помню хоть убей. Пофиксено.

P.S. Лишний раз "порадовался" "красоте" иаровского подхода - поменял имя МК и началось... Регистра TIMSK там уже нет, вместо него есть TIMSK0 и т.д. Я понимаю, что одного регистра не хватает, но зачем было имя-то менять? Пусть бы для совместимости осталось. То же самое касается TCCR0, которого уже тоже нет, а есть два с буквами на конце. Почему бы было не оставить для совместимости старое имя? Не понимаю. И уж зачем вообще было менять имя вектора ANA_COMP_vect на ANALOG_COMP_vect? А если заменили, то и вставили бы код для совместимости. И такие косяки у них с незапамятных времен. Такое впечатление, что о портировании кода даже внутри семейства они вообще не думают, а заголовки для разных процов лабают разные люди, которые никак свои действия друг с другом не координируют. sad.gif

Про упомянутое

/* SREG */
#define I 7
#define T 6
#define H 5
#define S 4
#define V 3
#define N 2
#define Z 1
#define C 0

я вообще молчу - яркий образчик бездумного подхода и безответственности. Причем что интересно - это не у всех процов есть такое определение, а только у избранного круга:

iom164.h
iom324.h
iom644.h
iom644p.h
iopwm2.h
iopwm3.h

Т.е. больше похоже на чью-то личную инициативу, нежели на общий подход. Бардак, короче. sad.gif


--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
Go to the top of the page
 
+Quote Post
alux
сообщение Mar 5 2008, 07:36
Сообщение #18


Знающий
****

Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447



Цитата(dxp @ Mar 5 2008, 08:55) *
Регистра TIMSK там уже нет, вместо него есть TIMSK0. Бардак, короче. sad.gif
Вот только мне не понятно для чего добавили 0 в имена битов SPI ?
Цитата
/* SPSR */
#define SPIF0 7
#define WCOL0 6
#define SPI2X0 0

/* SPCR */
#define SPIE0 7
#define SPE0 6
#define DORD0 5
#define MSTR0 4
#define CPOL0 3
#define CPHA0 2
#define SPR01 1
#define SPR00 0
Не может же быть несколько SPI-интерфейсов на борту... Или может? Мне кажется, что это они скорее сделали по привычке... Хотя в даташите указаны "правильные" имена. По поводу регистра TIMSK0... Почему не исправлено? Добавить то всего три строчки.
Цитата
#ifndef TIMSK0
#define TIMSK TIMSK0
#endif

Если гора не идет к Магомету, то Магомет идет к горе wink.gif
Цитата(dxp @ Mar 5 2008, 08:55) *
Действительно, косяк. Как он вкрался, не помню хоть убей.
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif
Цитата(IgorKossak @ Mar 5 2008, 00:09) *
Сдаётся мне, что если откроете overview завтра, то там окажется завтрашнее число. cool.gif
Точно smile.gif
Цитата(alux @ Mar 4 2008, 20:09) *
Имеет ли значение уровень оптимизации компилятора для smcRTOS ?

Дело в том, что у jacOS есть примечание для IAR C/EC++ for AVR 4.11A/W32 :
Цитата
Вероятны проблемы при установке опций оптимизации Code motion (?) Cross Call (?)

И последнее. Рискну задать глупый вопрос. Если в проекте используется прерывание, но оно не является источником события для процессов, обязательно ли обработчик прерывания должен иметь тип OS_INTERRUPT и на входе ISR создавать объект OS::TISRW_SS ISRW ? И можно ли разрешать глобальные прерывания до OS::Run() ?
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Mar 5 2008, 10:49
Сообщение #19


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



Цитата(alux @ Mar 5 2008, 09:36) *
Вот только мне не понятно для чего добавили 0 в имена битов SPI ?
Не может же быть несколько SPI-интерфейсов на борту... Или может?
Я думаю что может. И скорее всего добавление 0 вызвано тем, что они начали таки движение в сторону унификации. Для новых кристаллов биты и регистры будут "пронумерованы" а старые потихоньку вымрут. Процесс перехода всегда сложный, но что поделать...
Цитата(alux @ Mar 5 2008, 09:36) *
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif
Вы же понимаете, что мы не можем проверить наши исходники на всех возможных процессорах sad.gif Мы можем только предполагать, что раз программа работает на ATmegaX (LPC2xxx, BFxxx и т.д.), то она будет работать и на ATmegaY (LPC2yyy, BFyyy и т.д.), потому что все AVR (LPC2, BF и т.д.) похожи. К сожалению, они похожи не во всем, отсюда и такие косяки. Чем больше их найдете вы - тем меньше их останется тем, кто будет использовать ее после вас. Кроме того, я, например, уже не работаю с ADuC70xx и STR71x, порты для которых написал. Для STR71x у меня не осталось железа, на котором я мог бы проверить вносимые изменения. Поэтому исправления найденных в других портах багов или улучшения переносятся в этот порт без проверки. Вы бы что предпочли - старую версию, в которой есть известные глюки или новую, в которой они исправлены, но, возможно, внесены новые? wink.gif

Цитата(alux @ Mar 5 2008, 09:36) *
Рискну задать глупый вопрос. Если в проекте используется прерывание, но оно не является источником события для процессов, обязательно ли обработчик прерывания должен иметь тип OS_INTERRUPT и на входе ISR создавать объект OS::TISRW_SS ISRW ?
При отсуствиии в этом прерывании разрешения вложенных - нет, не обязательно. Но помните о размере стека - прерывание будет работать на стеке текущего процесса, значит стеки всех процессов надо будет увеличить на размер, требуемый прерыванию. Если использовать ISRW_SS, то для прерываний организуется отдельный стек, но за это надо платить увеличением времени обработки прерывания.
Цитата(alux @ Mar 5 2008, 09:36) *
И можно ли разрешать глобальные прерывания до OS::Run() ?
Если в разрешаемых прерываниях не используются сервисы ОС, их обработчики не имеют OS::TISRW(__SS), то можно, но зачем?


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
dxp
сообщение Mar 5 2008, 11:14
Сообщение #20


Adept
******

Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343



Цитата(alux @ Mar 5 2008, 13:36) *
Почему не исправлено? Добавить то всего три строчки.

Это должы добавлять те, кто модифицируют код. Чтобы для пользователя было прозрачно и портирование рабочего кода на новый кристалл проходило по максимуму безболезненно.

Цитата(alux @ Mar 5 2008, 13:36) *
Если гора не идет к Магомету, то Магомет идет к горе wink.gif

sad.gif

Цитата(alux @ Mar 5 2008, 13:36) *
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif

Уже начали. smile.gif Кстати, у меня эта ошибка не проявилась. Видимо, потому, что вектор достал до адреса. В любом случае этот не опасный баг - он отлавливается на этапе сборки. Вот те, которые при сборке не проявляются, а на рантайме вылезают - эти опасные.

Цитата(alux @ Mar 5 2008, 13:36) *
И можно ли разрешать глобальные прерывания до OS::Run() ?

Можно. Но зачем? Ведь после OS::Run() управление будет передано сразу процессам, они загрузят свои стеки и свои значения регистра SREG, поэтому разрешение прерываний до запуска оси смысла имеет мало. Если только не планируете долго сидеть до запуска, чтобы успеть по прерываниям попрыгать.


--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
Go to the top of the page
 
+Quote Post
Сергей С.
сообщение Mar 6 2008, 06:53
Сообщение #21





Группа: Новичок
Сообщений: 9
Регистрация: 15-02-08
Пользователь №: 35 076



Цитата(Сергей Борщ @ Mar 5 2008, 14:49) *
Но помните о размере стека - прерывание будет работать на стеке текущего процесса, значит стеки всех процессов надо будет увеличить на размер, требуемый прерыванию.


Как узнать максимальное потребление стека задачами? Можно ли проинициализировать все стеки каким-либо значением вроде 0xA5, как в FreeRTOS, и главное, как сделать это проще всего в scmRTOS? Или например в бутлоадере забить всю память байтами 0xA5, а затем прыгать на начало основной программы?
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Mar 6 2008, 13:26
Сообщение #22


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



Цитата(Сергей С. @ Mar 6 2008, 08:53) *
Как узнать максимальное потребление стека задачами? Можно ли проинициализировать все стеки каким-либо значением вроде 0xA5, как в FreeRTOS, и главное, как сделать это проще всего в scmRTOS?
Память стеков уже по умолчанию проинициализирована значением, только не 0xA5 а 0. В портах для ARM на верхушке контекста лежит содержимое CPSR, который не может быть равен нулю (конструкция у него такая). Поэтому я смотрю визуально (отладчиком или через терминал), где первый "не-ноль" - там было занято. Если есть желание забить каким-то своим значением - допишите memset в конструктор TProcess.
Цитата(Сергей С. @ Mar 6 2008, 08:53) *
Или например в бутлоадере забить всю память байтами 0xA5, а затем прыгать на начало основной программы?
Не пройдет - стеки попадают в область глобальных неинициализированных переменных и cstartup обнуляет эту область.


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
Сергей С.
сообщение Mar 6 2008, 20:14
Сообщение #23





Группа: Новичок
Сообщений: 9
Регистрация: 15-02-08
Пользователь №: 35 076



Цитата(Сергей Борщ @ Mar 6 2008, 17:26) *
Если есть желание забить каким-то своим значением - допишите memset в конструктор TProcess.


Всего одна строчка кода - а сколько пользы! Вся память как на ладони. Спасибо.
Go to the top of the page
 
+Quote Post
alux
сообщение Mar 9 2008, 21:15
Сообщение #24


Знающий
****

Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447



Мне не понятно, почему для генерации программного прерывания удобней использовать прерывание от аналогового компаратора? На мой взгляд лучше и удобней использовать внешнее прерывание. Во-первых, приоритет у него выше. Во-вторых, у большинства мк есть прерывния PCINT (pin change interrupt). Таким образом можно назначить практически любой вывод под это дело. В третьих, для переключения вывода в противоположное состояние достаточно записать PINy |= (1<<PINx); т.е. атомарную операцию SBI PINy, X. И было бы неплохо, чтобы в конфигурационном файле была возможность выбора источника программного прерывания: от компаратора или от внешнего прерывния. И также назначить символические имена выводам, которые используются для прерывания. В общем, максимально "загрузить" препроцессор, чтобы пользователь делал минимум телодвижений. И ошибок...
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Mar 10 2008, 01:42
Сообщение #25


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



Цитата(alux @ Mar 9 2008, 23:15) *
Мне не понятно, почему для генерации программного прерывания удобней использовать прерывание от аналогового компаратора? На мой взгляд лучше и удобней использовать внешнее прерывание. Во-первых, приоритет у него выше.
Исторически сложилось, как самое ненужное. Приоритет ему как раз нужен в идеале самый низкий. ReAl в своем порте реализовал переключение по прерыванию SPM, в этом случае нога не нужна вообще.
Цитата(alux @ Mar 9 2008, 23:15) *
В третьих, для переключения вывода в противоположное состояние достаточно записать PINy |= (1<<PINx);
Посмотрите порт ReAl, в нем (до SPM) было реализовано именно такое поведение - если процессор умеет перекидывать ногу одной командой, компаратор срабатывал от каждого перепада.
Цитата(alux @ Mar 9 2008, 23:15) *
И было бы неплохо, чтобы в конфигурационном файле была возможность выбора источника программного прерывания: от компаратора или от внешнего прерывния.
Это и так реализовано - все функции, завязанные на прерывание переключения контекста находятся в файле scmROS_Target_cfg.h, который находится в папке приложения.


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
alux
сообщение Mar 10 2008, 04:40
Сообщение #26


Знающий
****

Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447



Цитата(Сергей Борщ @ Mar 10 2008, 05:42) *
Приоритет ему как раз нужен в идеале самый низкий.

Почему?
Цитата(Сергей Борщ @ Mar 10 2008, 05:42) *
ReAl в своем порте реализовал переключение по прерыванию SPM, в этом случае нога не нужна вообще.
Так нужно как минимум три маш. цикла на переключение.

Пытаюсь настроить переключение по прерыванию PCINT. Сделал следующее:
Код
#if scmRTOS_CONTEXT_SWITCH_SCHEME == 1

    INLINE inline void RaiseContextSwitch()
    {
      PINC |= (1 << PINC7);
//        PORTB |= (1 << 3); PORTB &= ~(1 << 3); // set flag
    }
    INLINE inline void BlockContextSwitch() { PCMSK2 &= ~(1 << PCINT23); } // disable interrupt
    //{ ACSR &= ~(1 << ACIE); } // disable interrupt

Как изменится в таком случае этот кусок?
Код
    class TNestedISRW              
    {                              
    public:                        
        INLINE TNestedISRW() : State(ACSR) { BlockContextSwitch(); __enable_interrupt(); }
        INLINE ~TNestedISRW()
        {
            __disable_interrupt();
            ACSR = State;
            if(State & (1 << ACI)) RaiseContextSwitch();
        }
                                                                            
    private:                                                                
        byte State;                                                          
    };

Не судите строго, я еще не совсем ориентируюсь в объектно-ориентированном программировании smile.gif
Go to the top of the page
 
+Quote Post
ReAl
сообщение Mar 10 2008, 09:08
Сообщение #27


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Цитата(alux @ Mar 10 2008, 06:40) *
Почему?
Так нужно как минимум три маш. цикла на переключение.

Потому что если вдруг возникнет ещё какое-то прерывание от аппаратуры, оно должно быть обработано, а оно задержится на время переключения контекстов. У задачи, на которую идёт переключение, это прерывание от аппаратуры хоть так, хоть сяк (хоть до переключения, хоть сразу после) время заберёт, т.е. она всё равно реально начнёт работать позже. А само будет задержано переключателем контекстов.
А "три цикла"... По сравнению с сотнями на собственно переключение - что три (in/ori/out для SPMCSR в IO), что два (sbi PIN для компаратора либо PCINT), что четыре (sbi PORT/cbi PORT либо in/ldi/eor/out для компаратора на старых кристаллах), что пять (lds/ori/sts для SPMCSR вне адресов IO) - на практике несущественны.

Хотя можно в примеры вернуть (под условную компиляцию либо в разные примеры разный метод) и прерывание от компаратора (хотя у меня он имеет больше шансов быть занятым, чем PCINT), и даже
PCINT, хоть я не хотел использовать именно потому, что оно слишком приоритетное. Кроме того, это блокирует использование PCINT от других ног этого порта.


Цитата
Пытаюсь настроить переключение по прерыванию PCINT. Сделал следующее:
Код
    INLINE inline void BlockContextSwitch() { PCMSK2 &= ~(1 << PCINT23); } // disable interrupt
    //{ ACSR &= ~(1 << ACIE); } // disable interrupt

Кстати, где как, но если уж считать такты, то у меги168 PCMSK* расположены в области памяти и работа с ними сожрёт весь выигрыш от sbi PIN по сравнению с in SPMCSR/ori/OUTSPMCSR. А в tiny85 у меня как-то рука не поднимается scmRTOS ставить, на 512 байтах ОЗУ только демонстрашку сделать можно.
Да и там работа с SPMCSR и PCINT по сумме баллов мало отличаться будут.

Я бы для PCINT сделал что-то в духе (для той же меги168), всё равно остальные PCINT из этого порта как входы прерываний использовать нельзя:
Код
#define PCINT_PIN C,7,H
#define PCINT_BIT PCIE2

#if scmRTOS_CONTEXT_SWITCH_SCHEME == 1

    INLINE inline void RaiseContextSwitch() { CPL(PCINT_PIN);  }

    INLINE inline void BlockContextSwitch() { PCICR &= ~(1 << PCINT_BIT); } // disable PCINT interrupt

    class TNestedISRW
    {
    public:
        INLINE TNestedISRW() : State(PCICR) { BlockContextSwitch(); sei(); }
        INLINE ~TNestedISRW()
        {
            cli();
            if( State & (1 << PCINT_BIT) )
                PCICR |= (1 << PCINT_BIT);
        }
    private:
        byte State;
    };

Естественно, где-то в начале (до вызова OS::Run()) надо настроить разрешение для нужной ноги.


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
alux
сообщение Mar 10 2008, 09:56
Сообщение #28


Знающий
****

Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447



Спасибо за разъяснение. Маленькое уточнение: sbi PINC, 0x01 занимает один такт. В моем проекте на Меге324P есть один лишний вывод на PC7. Вот я к PCINT и "привязался". Хотя после вышесказанного возможно настрою на SPM. Вывод дороже... Прокомментируйте, пожалуйста, этот код
Цитата(ReAl @ Mar 10 2008, 13:08) *
class TNestedISRW
{
public:
INLINE TNestedISRW() : State(PCICR) { BlockContextSwitch(); sei(); }
INLINE ~TNestedISRW()
{
cli();
if( State & (1 << PCINT_BIT) )
PCICR |= (1 << PCINT_BIT);
}
private:
byte State;
};[/code]
Не понятно в строке конструктора : State(PCICR).

PS. Кстати, только что обратил внимание, у Меги324P SPMCSR находится по адресу 0x37. Т.е можно применять команды IN / OUT. А PCMSK2 - 0x6D. Т.е. уже только ST/STS/STD и LD/LDS/LDD. Наверное в данном случае лучше применить прерывание от SPM .

PS2. Решил попробовать вариант с SPM.
Код
#if defined(SPMCSR)
  #define SPM_CONTROL_REG SPMCSR
#elif defined(SPMCR)
  #define SPM_CONTROL_REG SPMCR
#else
  #error "SPM control register not defined"
#endif
Компилятор выдает ошибку
Код
Fatal Error[Pe035]: #error directive: "SPM control register not defined"
Хотя файл <ioavr.h> подключен. Как правильно определить SPMCSR ? Неужели просто перед #if defined(SPMCSR) написать #define SPMCSR ? 05.gif
Go to the top of the page
 
+Quote Post
ReAl
сообщение Mar 10 2008, 15:36
Сообщение #29


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Цитата(alux @ Mar 10 2008, 11:56) *
Маленькое уточнение: sbi PINC, 0x01 занимает один такт. В моем проекте на Меге324P есть один лишний вывод на PC7. Вот я к PCINT и "привязался". Хотя после вышесказанного возможно настрою на SPM. Вывод дороже...
Я уже думал, что я серьёзно отстал от жизни и mega324P отличается от других AVR-ок.
Глянул в описание - sbi/cbi выполняются за два такта. Под рукой нет для проверки.

Цитата
Прокомментируйте, пожалуйста, этот код
Не понятно в строке конструктора : State(PCICR).

Инициализация поля State значением PCICR (это не совсем одно и то же, что State = PCICR; в теле конструктора).

Цитата
PS2. Решил попробовать вариант с SPM.
...
Компилятор выдает ошибку
Код
Fatal Error[Pe035]: #error directive: "SPM control register not defined"
Хотя файл <ioavr.h> подключен. Как правильно определить SPMCSR ? Неужели просто перед #if defined(SPMCSR) написать #define SPMCSR ? 05.gif
Кто такой ioavr.h ?
Имеется ввиду <avr/io.h> ?

Только что взял снапшот http://scmrtos.ginps.com/scmrtos-avr-gcc-snapshot.rar
распаковал, заменил в 1-EventFlag/makefile тип процессора:
MCU := atmega324p
всё скомпилировалось.
Не знаю, в чём может быть проблема.


--------
Тьху, это, наверно, IAR ?
Ну тогда ничем не могу помочь, у меня его на компе нет.


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
alux
сообщение Mar 10 2008, 16:00
Сообщение #30


Знающий
****

Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447



Цитата(ReAl @ Mar 10 2008, 19:36) *
Глянул в описание - sbi/cbi выполняются за два такта.
Прошу прощения. Заработался ..Не туда глянул 01.gif Действительно 2 такта.
А вопрос с ошибкой компиляции я перенес в другой (IAR) форум. Не могу одолеть. Ну в смысле, если просто везде заменить TIMSK на TIMSK0 , то все OK. А вот для SPMCSR просто тупо вставил
Код
#include <ioavr.h>
#define SPMCSR

#if defined(SPMCSR)
  #define SPM_CONTROL_REG SPMCSR
#elif defined(SPMCR)
  #define SPM_CONTROL_REG SPMCR
#else
  #error "SPM control register not defined"
#endif
Но это же за уши притянуто.. Хочется принципиально для себя решить этот вопрос.
Go to the top of the page
 
+Quote Post

18 страниц V  < 1 2 3 4 > » 
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 16th April 2024 - 21:22
Рейтинг@Mail.ru


Страница сгенерированна за 0.01579 секунд с 7
ELECTRONIX ©2004-2016