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

 
 
2 страниц V   1 2 >  
Reply to this topicStart new topic
> Код получения прерывания от RXSETUP на SAM7S
Shaienn
сообщение Nov 20 2009, 08:02
Сообщение #1


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Добрый день. Посмотрите пожалуйста код. Ожидается, что при получении Control пакета возникнет бит RXSETUP и будет вызвано прерывание isrUDP, но в прерывание не заходит.

CODE
#include <AT91SAM7S256.H> /* AT91SAMT7S64 definitions */
#include <lib_AT91SAM7S256.h>
/*
* Main Program
*/
int i;
__irq void isrUDP(void)
{
unsigned int status = AT91C_BASE_UDP->UDP_ISR;
AT91PS_UDP pUDP = AT91C_BASE_UDP;
AT91_REG isr = pUDP->UDP_ISR;
AT91_REG csr = pUDP->UDP_CSR[0]; //rejestr csr endpointu 0
if (isr&AT91C_UDP_EPINT0)
{
if (i)
{
AT91C_BASE_PIOA->PIO_SODR = 1;
i = 0;
}
else
{
AT91C_BASE_PIOA->PIO_CODR = 1;
i = 1;
}
}
AT91C_BASE_UDP->UDP_CSR[0] = (0<<AT91C_UDP_RXSETUP);
AT91C_BASE_AIC->AIC_EOICR = status;
}

int main (void) {
AT91C_BASE_PIOA->PIO_PER = (1 << AT91C_PIO_PA0);
AT91C_BASE_PIOA->PIO_OER = 0x00000001;
AT91C_BASE_PMC->PMC_PCER = (1UL << AT91C_ID_PIOA);

// Set the PLL USB Divider
AT91C_BASE_CKGR->CKGR_PLLR |= AT91C_CKGR_USBDIV_1;
AT91C_BASE_PMC->PMC_SCER = AT91C_PMC_UDP; //Разрешили частоту на UDP
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_UDP);

AT91C_BASE_UDP->UDP_RSTEP = (1 << 0);
AT91C_BASE_UDP->UDP_RSTEP = 0;
AT91C_BASE_UDP->UDP_CSR[0] = AT91C_UDP_EPTYPE_CTRL;
AT91C_BASE_UDP->UDP_CSR[0] = AT91C_UDP_EPEDS;

// Enable UDP PullUp (USB_DP_PUP) : enable & Clear of the corresponding PIO
// Set in PIO mode and Configure in Output
AT91F_PIO_CfgOutput(AT91C_BASE_PIOA,AT91C_PIO_PA16);
// Clear for set the Pul up resistor
AT91F_PIO_ClearOutput(AT91C_BASE_PIOA,AT91C_PIO_PA16);

AT91C_BASE_UDP->UDP_IMR = AT91C_UDP_ENDBUSRES|AT91C_UDP_EPINT0;
AT91C_BASE_AIC->AIC_SVR[AT91C_ID_UDP] = (unsigned int)isrUDP;
AT91C_BASE_AIC->AIC_SMR[AT91C_ID_UDP] = 6;
AT91C_BASE_AIC->AIC_IECR = (1UL << AT91C_ID_UDP);

// Loop forever
for (;;)
{
}
}


При чтении состояния регистра UDP_ISR в главном цикле без прерывания при данных настройках возвращает 0x00000100 (хотя должен 0x00000001). смотрю через дебаггер ulink. При замене AT91C_UDP_EPINT0 на AT91C_UDP_EPINT2 все равно не работает.

Вроде бы все указал... Затык.


Спасибо.
Причина редактирования: Уменьшение видимого объема цитаты исходника.
Go to the top of the page
 
+Quote Post
vmp
сообщение Nov 20 2009, 08:46
Сообщение #2


Местный
***

Группа: Свой
Сообщений: 426
Регистрация: 20-01-05
Из: Зеленоград
Пользователь №: 2 070



Почему бы не взять за основу какой-нибудь атмеловский пример по работе с USB?
Запустить его, посмотреть как он работает, а затем уже дорабатывать под свои нужды.
Там пока дойдет дело до RXSETUP, надо еще кучу событий обработать.
Go to the top of the page
 
+Quote Post
sergeeff
сообщение Nov 20 2009, 10:50
Сообщение #3


Профессионал
*****

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



Глобальная переменная i первоначально не инициализирована. Это так, к сведению.
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 20 2009, 11:00
Сообщение #4


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Цитата
Почему бы не взять за основу какой-нибудь атмеловский пример по работе с USB?
Запустить его, посмотреть как он работает, а затем уже дорабатывать под свои нужды.
Там пока дойдет дело до RXSETUP, надо еще кучу событий обработать.


Я бы рад, но как атмеловские проекты под IAR запустить в Keil`e? А через IAR не могу, ибо имею Ulink, который Keil only. Приходится разгребать самому.

Я так понимаю, что после подключения устройства к USB порту, хост отсылает Control пакет на который USB-устройство должно ответить. До ответа я еще не дошел, но хочу зафиксировать принятие этого пакета.
Намекните, какие еще события должны произойти до этого?

Цитата(sergeeff @ Nov 20 2009, 13:50) *
Глобальная переменная i первоначально не инициализирована. Это так, к сведению.

Согласно Источнику
Цитата
2. Глобальные переменные всегда инициализируются, и если это не сделано явно, то они инициализируются нулевым значением.


Или это не правда?

Сообщение отредактировал Shaienn - Nov 20 2009, 11:03
Go to the top of the page
 
+Quote Post
vmp
сообщение Nov 20 2009, 11:04
Сообщение #5


Местный
***

Группа: Свой
Сообщений: 426
Регистрация: 20-01-05
Из: Зеленоград
Пользователь №: 2 070



Цитата(Shaienn @ Nov 20 2009, 14:00) *
Я бы рад, но как атмеловские проекты под IAR запустить в Keil`e?

А зачем запускать в Кейле Иаровские примеры? Не проще ли сразу взять пример под Кейл:
Цитата
AT91SAM7S-EK Software Package for IAR 5.2, Keil and GNU (37 MB, updated 12/08)
This package provides software drivers and libraries to build any application for AT91SAM7S devices.

http://www.atmel.com/dyn/resources/prod_do...t91sam7s-ek.zip
Go to the top of the page
 
+Quote Post
aaarrr
сообщение Nov 23 2009, 07:04
Сообщение #6


Гуру
******

Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448



Цитата(Shaienn @ Nov 20 2009, 11:02) *
Ожидается, что при получении Control пакета возникнет бит RXSETUP и будет вызвано прерывание isrUDP, но в прерывание не заходит.

Вообще-то задолго до RXSETUP случится ENDBUSRES, который этот самый RXSETUP успешно запретит.
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 25 2009, 04:16
Сообщение #7


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



2vmp
Спасибо за ссылку. Куча откровений smile.gif

Цитата(aaarrr @ Nov 23 2009, 10:04) *
Вообще-то задолго до RXSETUP случится ENDBUSRES, который этот самый RXSETUP успешно запретит.

Это уже стало понятно smile.gif


В процессе разбирания USB, понял следующее.

1) Подключаем подтяжку к D+, с этого момента хост считает, что устройство присоединено.
2) Хост производит процедуру сброса, после которой возникает флаг ENDBUSRES.

В связи с этим следующие вопросы:
1) Я так понимаю, что прерывание ENDBUSRES нигде в регистрах разрешения прерываний не объявляется и прерывание разрешено изначально?
2) Почему-то в моем Keil нет возможности при отладке заглянуть в регистры UDP и в AIC нет регистра прерывания UDP. Это лечится?
Go to the top of the page
 
+Quote Post
aaarrr
сообщение Nov 25 2009, 10:40
Сообщение #8


Гуру
******

Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448



Цитата(Shaienn @ Nov 25 2009, 07:16) *
1) Я так понимаю, что прерывание ENDBUSRES нигде в регистрах разрешения прерываний не объявляется и прерывание разрешено изначально?

Да.

Цитата(Shaienn @ Nov 25 2009, 07:16) *
2) Почему-то в моем Keil нет возможности при отладке заглянуть в регистры UDP и в AIC нет регистра прерывания UDP. Это лечится?

Регистр прерывания UDP никакого отношения к AIC'у не имеет.
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 25 2009, 12:33
Сообщение #9


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Цитата(aaarrr @ Nov 25 2009, 14:40) *
Регистр прерывания UDP никакого отношения к AIC'у не имеет.


Хм, а вот в этой штуке
Цитата
AT91SAM7S-EK Software Package for IAR 5.2, Keil and GNU (37 MB, updated 12/08)
This package provides software drivers and libraries to build any application for AT91SAM7S devices.

прерывание UDP делается через AIC:
Код
    AIC_ConfigureIT(AT91C_ID_UDP, 0, USBD_InterruptHandler);
    AIC_EnableIT(AT91C_ID_UDP);


А сами функции:
Код
void AIC_ConfigureIT(
    unsigned int source,
    unsigned int mode,
    void (*handler)(void))
{
    // Disable the interrupt first
    AT91C_BASE_AIC->AIC_IDCR = 1 << source;

    // Configure mode and handler
    AT91C_BASE_AIC->AIC_SMR[source] = mode;
    AT91C_BASE_AIC->AIC_SVR[source] = (unsigned int) handler;

    // Clear interrupt
    AT91C_BASE_AIC->AIC_ICCR = 1 << source;
}


Может я чего-то не понимаю?
Go to the top of the page
 
+Quote Post
aaarrr
сообщение Nov 25 2009, 14:12
Сообщение #10


Гуру
******

Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448



Цитата(Shaienn @ Nov 25 2009, 15:33) *
Может я чего-то не понимаю?

В AIC заводится только сигнал прерывания UDP, точно так же, как и сигналы другой периферии. Какой "регистр прерывания UDP" вы хотите там увидеть?
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 25 2009, 14:18
Сообщение #11


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Цитата(aaarrr @ Nov 25 2009, 18:12) *
В AIC заводится только сигнал прерывания UDP, точно так же, как и сигналы другой периферии. Какой "регистр прерывания UDP" вы хотите там увидеть?


Я неправильно выразился. Когда отлаживал прерывания разной периферии, Keil позволял посмотреть, все ли настройки установились и позволял увидеть вектор прерывания для данной периферии, но вот UDP в списке периферии Keil-а нет.
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 29 2009, 15:51
Сообщение #12


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Здравствуйте.

Поправьте меня в процессе отправки дескриптора.

1) Возникает прерывание RXSETUP
2) Принимаем управляющую посылку 8 байт, читаем 2-й байт bRequest и 3-й байт слова wValue.
3) Первым возникает запрос GET_DESCRIPTOR типа "стандартный дескриптор устройства"
4) Начинаем отсылать первые 8 байт дескриптора
5) Ставим флаг TXPKTRDY
6) Ждем флага TXCOMP
7) Отсылаем вторые 8 байт дескриптора
8) Ставим флаг TXPKTRDY
9) Ждем флага TXCOMP
10) Отсылаем последние 2 байта
11) Ставим флаг TXPKTRDY
12) Ждем флага TXCOMP
13) Освобождаем конечную точку
14) Должно возникнуть следующее прерывание RXSETUP с запросом дескриптора конфигурации

Два вопроса:
1) Академический. При считывании управляющей посылки ее поле wLength не 18, а 64 байта. В Атмеловском примере они специально проверяют на это несовпадение и фиксируют его:
Код
if (length > USBGenericDescriptor_GetLength((USBGenericDescriptor *) pDevice)) {

                length = USBGenericDescriptor_GetLength((USBGenericDescriptor *) pDevice);
            }


Почему такая штука?

2) Практический. Вроде бы отправляю весь дескриптор устройства, но в следующем RXSETUP хост опять требует его же. Так происходит 3 раза, затем хост говорит, что USB устройство работает неправильно. Если хост требует дескриптор устройства снова и снова это значит, что я ему отправляю что-то неправильное? Вроде, все как завещано.
Go to the top of the page
 
+Quote Post
aaarrr
сообщение Nov 29 2009, 17:51
Сообщение #13


Гуру
******

Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448



Цитата(Shaienn @ Nov 29 2009, 18:51) *
Почему такая штука?

Потому что по спецификации хост может запросить много. А отправить вы должны не более чем запрошено.

Цитата(Shaienn @ Nov 29 2009, 18:51) *
Если хост требует дескриптор устройства снова и снова это значит, что я ему отправляю что-то неправильное? Вроде, все как завещано.

Значит неправильно отправляете - не вовремя снимаете RXSETUP, не вовремя или неправильно ставите DIR и т.п.
Go to the top of the page
 
+Quote Post
Shaienn
сообщение Nov 30 2009, 11:12
Сообщение #14


Частый гость
**

Группа: Участник
Сообщений: 103
Регистрация: 21-06-09
Пользователь №: 50 494



Тогда посмотрите пожалуйста код:

CODE
/// Bitmap for all status bits in CSR.
#define REG_NO_EFFECT_1_ALL AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1 \
|AT91C_UDP_STALLSENT | AT91C_UDP_RXSETUP \
|AT91C_UDP_TXCOMP

/// Clears the specified bit(s) in the UDP_CSR register.
/// \param endpoint The endpoint number of the CSR to process.
/// \param flags The bitmap to set to 1.
#define SET_CSR(endpoint, flags) \
{ \
volatile unsigned int reg; \
reg = AT91C_BASE_UDP->UDP_CSR[endpoint]; \
reg |= REG_NO_EFFECT_1_ALL; \
reg |= (flags); \
AT91C_BASE_UDP->UDP_CSR[endpoint] = reg; \
while ( (AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) != (flags)); \
}

/// Sets the specified bit(s) in the UDP_CSR register.
/// \param endpoint The endpoint number of the CSR to process.
/// \param flags The bitmap to clear to 0.
#define CLEAR_CSR(endpoint, flags) \
{ \
volatile unsigned int reg; \
reg = AT91C_BASE_UDP->UDP_CSR[endpoint]; \
reg |= REG_NO_EFFECT_1_ALL; \
reg &= ~(flags); \
AT91C_BASE_UDP->UDP_CSR[endpoint] = reg; \
while ( (AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) == (flags)); \
}

//------------------------------------------------------------------------------
/// Структура конечных точек
//------------------------------------------------------------------------------
typedef struct {
unsigned short size;
char *pData;
unsigned int remaining;
unsigned char state;
}sEndpoint;
sEndpoint Endpoint[4];


//------------------------------------------------------------------------------
/// Заполнение FIFO буфера конечной точки
//------------------------------------------------------------------------------
static void USB_WriteFIFO(unsigned char EndpointNum)
{
signed int size = Endpoint[EndpointNum].size;
if (Endpoint[EndpointNum].remaining < size)
{
size = Endpoint[EndpointNum].remaining;
}
Endpoint[EndpointNum].remaining -= size;
while (size > 0) {
AT91C_BASE_UDP->UDP_FDR[EndpointNum] = *(Endpoint[EndpointNum].pData);
Endpoint[EndpointNum].pData++;
size--;
}
}

//------------------------------------------------------------------------------
/// Подготовка данных
//------------------------------------------------------------------------------
static char USB_PrepareTransfer(unsigned char EndpointNum, const void *pData,unsigned int Length)
{ if (Endpoint[EndpointNum].state != UDP_ENDPOINT_IDLE) {
return 0;
}
Endpoint[EndpointNum].remaining = Length;
Endpoint[EndpointNum].pData = (void *)pData;
while((AT91C_BASE_UDP->UDP_CSR[EndpointNum]&AT91C_UDP_TXPKTRDY)==AT91C_UDP_TXPKTRDY);
Endpoint[EndpointNum].state = UDP_ENDPOINT_SENDING;
USB_WriteFIFO(EndpointNum);
SET_CSR(EndpointNum, AT91C_UDP_TXPKTRDY); // Enable interrupt on endpoint
AT91C_BASE_UDP->UDP_IER = 1 << EndpointNum;
return 1;
}
//------------------------------------------------------------------------------
/// Если прерывание isrUDP возникло от конечной точки
//------------------------------------------------------------------------------
static void UDP_EndpointHandler(unsigned char EndpointNum)
{ unsigned int status = AT91C_BASE_UDP->UDP_CSR[EndpointNum];
if ((status & AT91C_UDP_TXCOMP) != 0)
{ cnt++; //Так я понял, что TXCOMP возникает только один раз
if (cnt == 2)
{
LED_Toggle(2);
}
if (Endpoint[EndpointNum].state == UDP_ENDPOINT_SENDING)
{
if (Endpoint[EndpointNum].remaining != 0) //Не все данные переданы
{
USB_WriteFIFO(EndpointNum);
SET_CSR(EndpointNum, AT91C_UDP_TXPKTRDY); //После этой строки больше TXCOMP не приходит
CLEAR_CSR(EndpointNum, AT91C_UDP_TXCOMP);
}
else
{
Endpoint[EndpointNum].state = UDP_ENDPOINT_IDLE;
CLEAR_CSR(EndpointNum, AT91C_UDP_TXCOMP);
}
}
else
{
CLEAR_CSR(EndpointNum, AT91C_UDP_TXCOMP);
}
}
if ((status & AT91C_UDP_RXSETUP) != 0)
{
if (count!=0){return;}
count++;
UDP_ReadRequest(&request);
if ((request.bmRequestType & 0x80) != 0)
{
SET_CSR(EndpointNum, AT91C_UDP_DIR); //меняем направление передачи данных
}
CLEAR_CSR(EndpointNum, AT91C_UDP_RXSETUP);
UDP_RequestReceived(&request);
}
}

//------------------------------------------------------------------------------
/// Обработчик прерывания AT91C_BASE_UDP
//------------------------------------------------------------------------------
__irq void isrUDP(void)
{
unsigned int status;
status = AT91C_BASE_UDP->UDP_ISR;
status &= AT91C_BASE_UDP->UDP_IMR;
//Если прерывание по концу сброса шины:
if ((status & AT91C_UDP_ENDBUSRES) != 0)
{
// Enables the UDP transceiver.
AT91C_BASE_UDP->UDP_TXVC = (1 << AT91C_UDP_TXVDIS);
// Configure Endpoint 0 for CONTROL Transaction
ConfigureEndpoint(0);
//Reset ENDBUSRES interrupt
AT91C_BASE_UDP->UDP_ICR |= (unsigned int) 0x1 << 12;
}
//Если прерывание по восстановлению активности на шине:
else if ((status & (AT91C_UDP_WAKEUP | AT91C_UDP_RXRSM)) != 0)
{
AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_WAKEUP | AT91C_UDP_RXRSM;
AT91C_BASE_UDP->UDP_IDR = AT91C_UDP_WAKEUP | AT91C_UDP_RXRSM;
}
//Если прерывание от конечной точки:
else if ((status & AT91C_UDP_EPINT0) != 0)
{
UDP_EndpointHandler(0);
}
//Прерывание выполнено
AT91C_BASE_AIC->AIC_EOICR = status;
}


Как я понял, происходит следующее. Прерывание RXSETUP обрабатывается, считываем пакет, расшифровываем его. Подготавливаем данные функцией USB_PrepareTransfer, из нее вызывается функция USB_WriteFIFO, где непосредственно записываем 8 байт в FIFO буфер. Затем возникает прерывание TXCOMP, которое обрабатывается в UDP_EndpointHandler, вызывается еще один раз функция USB_WriteFIFO без вызова USB_PrepareTransfer, передается еще 8 байт информации (Остается передать еще два) и после этого прерывание по TXCOMP больше не срабатывает.

То есть после USB_PrepareTransfer появляется TXCOMP, а после обработки этого TXCOMPа следующий TXCOMP уже не появляется. Что-то я уже голову сломал...

Функции SET_CSR и CLEAR_CSR взял из атмеловского примера:

CODE
/// Bitmap for all status bits in CSR.
#define REG_NO_EFFECT_1_ALL AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1 \
|AT91C_UDP_STALLSENT | AT91C_UDP_RXSETUP \
|AT91C_UDP_TXCOMP

/// Clears the specified bit(s) in the UDP_CSR register.
/// \param endpoint The endpoint number of the CSR to process.
/// \param flags The bitmap to set to 1.
#define SET_CSR(endpoint, flags) \
{ \
volatile unsigned int reg; \
reg = AT91C_BASE_UDP->UDP_CSR[endpoint]; \
reg |= REG_NO_EFFECT_1_ALL; \
reg |= (flags); \
AT91C_BASE_UDP->UDP_CSR[endpoint] = reg; \
while ( (AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) != (flags)); \
}

/// Sets the specified bit(s) in the UDP_CSR register.
/// \param endpoint The endpoint number of the CSR to process.
/// \param flags The bitmap to clear to 0.
#define CLEAR_CSR(endpoint, flags) \
{ \
volatile unsigned int reg; \
reg = AT91C_BASE_UDP->UDP_CSR[endpoint]; \
reg |= REG_NO_EFFECT_1_ALL; \
reg &= ~(flags); \
AT91C_BASE_UDP->UDP_CSR[endpoint] = reg; \
while ( (AT91C_BASE_UDP->UDP_CSR[endpoint] & (flags)) == (flags)); \
}

//------------------------------------------------------------------------------
/// Структура конечных точек
//------------------------------------------------------------------------------
typedef struct {
unsigned short size;
char *pData;
unsigned int remaining;
unsigned char state;
}sEndpoint;
sEndpoint Endpoint[4];


Модератор (rezident). Shaienn, предупреждаю Вас один раз. Либо Вы прикрепляете к сообщению исходники в виде файлов, как этого требуют Правила форума. Либо пользуйтесь тэгами [ codebox ] для оформления объемных цитат исходников, самостоятельно редактируя их так, чтобы страницу не "распирало". Я Вам не нянька, чтобы редактировать за Вас оформление Ваших же исходников! twak.gif
Причина редактирования: Нарушение п.3.4 Правил форума.
Go to the top of the page
 
+Quote Post
aaarrr
сообщение Nov 30 2009, 11:56
Сообщение #15


Гуру
******

Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448



Код, я бы сказал, малость раздутый. Поле size действительно равно восьми? Есть явная ошибка, которая проявится, если размер передаваемых данных будет кратен размеру endpoint'а - в этом случае в конце не будет передан пакет нулевой длины.
И зачем вот это:
Код
                        if (count!=0){return;}        
                        count++;


И аккуратнее с атмеловскими примерами, макросы SET_CSR и CLEAR_CSR - это просто безобразие.
Go to the top of the page
 
+Quote Post

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

 


RSS Текстовая версия Сейчас: 18th July 2025 - 13:10
Рейтинг@Mail.ru


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