Перейти к содержанию
    

STM32F207+FreeRTOS+LwIP замирает задача ethernetif_input

Продолжаю разбираться с FreeRTOS+LwIP. Камень STM32F207, физика KSZ8041.

Делаю простой UDP сервер, netconn, на основе примера ST.

 

Симптом следующий.

Посылаю с ПК запросы, устройство отвечает. Примерно через полчаса такой работы ответы приходить перестают. Просмотр состояния задач, очередей показал, что задача ethernetif_input висит в состоянии READY, но sheduler ее не запускает. Cемафор s_xSemaphore заполняется полностью. При этом другие задачи с меньшим приоритетом (мигание лампочки) выполняться продолжают.

 

Прочитал топик, внес изменения в ethernetif.c, это ни к чему не привело.

static void ethernet_watchdog(void) {
   /* When Rx Buffer unavailable flag is set: clear it and resume reception */
   if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
   {
       /* Clear RBUS ETHERNET DMA flag */
       ETH->DMASR = ETH_DMASR_RBUS;   

       /* Resume DMA reception. The register doesn't care what you write to it. */
       ETH->DMARPDR = 0;
   }
}

unsigned int thread_cnt = 0;
void ethernetif_input( void * pvParameters )
{
 struct pbuf *p;

 for( ;; )
 {
   if (xSemaphoreTake( s_xSemaphore, emacBLOCK_TIME_WAITING_FOR_INPUT)==pdTRUE)
   {
     while ((p = low_level_input( s_pxNetIf )) != 0) 
//      p = low_level_input( s_pxNetIf );
     {
       thread_cnt++;
       if (p != 0) {
           if (ERR_OK != s_pxNetIf->input( p, s_pxNetIf))
           {
             pbuf_free(p);
             p=NULL;
           }
       }
     }
   }
   ethernet_watchdog();
 }
}

 

Настройки FreeRTOS:

#define configUSE_PREEMPTION		1
#define configUSE_IDLE_HOOK	        0
#define configUSE_TICK_HOOK	        0
#define configCPU_CLOCK_HZ              ( ( unsigned long ) 120000000 )	
#define configTICK_RATE_HZ	        ( ( portTickType ) 1000 )
#define configMAX_PRIORITIES		( ( unsigned portBASE_TYPE ) 7 )
#define configMINIMAL_STACK_SIZE	( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 15 * 1024 ) )
#define configMAX_TASK_NAME_LEN		( 16 )
#define configUSE_TRACE_FACILITY	0
#define configUSE_16_BIT_TICKS		0
#define configIDLE_SHOULD_YIELD		1
#define configUSE_MUTEXES               1
#define configUSE_COUNTING_SEMAPHORES   1
#define configUSE_MALLOC_FAILED_HOOK    0

//For queue trace
#define configQUEUE_REGISTRY_SIZE       5

#define configCHECK_FOR_STACK_OVERFLOW  0

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 		0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */

#define INCLUDE_vTaskPrioritySet		0
#define INCLUDE_uxTaskPriorityGet		0
#define INCLUDE_vTaskDelete			0
#define INCLUDE_vTaskCleanUpResources	        0
#define INCLUDE_vTaskSuspend			0
#define INCLUDE_vTaskDelayUntil			0
#define INCLUDE_vTaskDelay		        1

/* This is the raw value as per the Cortex-M3 NVIC.  Values can be 255
(lowest) to 0 (1?) (highest). */
#define configKERNEL_INTERRUPT_PRIORITY         255
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191 /* equivalent to 0xb0, or priority 11. */


/* This is the value being used as per the ST library which permits 16
priority values, 0 to 15.  This must correspond to the
configKERNEL_INTERRUPT_PRIORITY setting.  Here 15 corresponds to the lowest
NVIC value of 255. */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY	15

 

Настройки LwIP:

#define SYS_LIGHTWEIGHT_PROT    1

#define ETHARP_TRUST_IP_MAC     0
#define IP_REASSEMBLY           0
#define IP_FRAG                 0
#define ARP_QUEUEING            0

#define LWIP_TCPIP_CORE_LOCKING 0

#define NO_SYS                  0

#define MEM_ALIGNMENT           4

#define MEM_SIZE                (20*1024)

#define MEMP_NUM_PBUF           200
#define MEMP_NUM_UDP_PCB        6
#define MEMP_NUM_TCP_PCB        10
#define MEMP_NUM_TCP_PCB_LISTEN 5
#define MEMP_NUM_TCP_SEG        20
#define MEMP_NUM_SYS_TIMEOUT    10

#define PBUF_POOL_SIZE          20

#define PBUF_POOL_BUFSIZE       500

#define LWIP_TCP                0
#define TCP_TTL                 255

#define LWIP_ICMP                       1

#define LWIP_DHCP               0

#define LWIP_UDP                1
#define UDP_TTL                 255

/* ---------- Statistics options ---------- */
#define LWIP_STATS 1
#define ETHARP_STATS 1
#define UDP_STATS 1
#define LWIP_PROVIDE_ERRNO 1
#define MEM_STATS 1


#define CHECKSUM_BY_HARDWARE 

#define LWIP_NETCONN                    1

#define LWIP_SOCKET                     0

#define LWIP_DEBUG                      0

#define TCPIP_THREAD_STACKSIZE          1000
#define TCPIP_MBOX_SIZE                 50
#define DEFAULT_UDP_RECVMBOX_SIZE       2
#define DEFAULT_TCP_RECVMBOX_SIZE       20
#define DEFAULT_ACCEPTMBOX_SIZE         20
#define DEFAULT_THREAD_STACKSIZE        500
#define TCPIP_THREAD_PRIO               (configMAX_PRIORITIES - 2)

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

На работе сталкивались с подобной проблемой, но только на STMF417, могу посмотреть как там решили проблему(честно говоря не помню решили ли её вообще).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

V_M_Luck, а в ethernetif.c в low_level_input() тоже внесли изменения?

static struct pbuf * low_level_input(struct netif *netif)
{
 struct pbuf *p, *q;
 u16_t len;
 uint32_t l=0,i =0;
 FrameTypeDef frame;
 u8 *buffer;
 __IO ETH_DMADESCTypeDef *DMARxNextDesc;

 p = NULL;

 /* Get received frame */
 frame = ETH_Get_Received_Frame_interrupt();

 if (frame.descriptor && frame.buffer) {
  /* check that frame has no error */
  if ((frame.descriptor->Status & ETH_DMARxDesc_ES) == (uint32_t)RESET)
  {

	/* Obtain the size of the packet and put it into the "len" variable. */
	len = frame.length;
	buffer = (u8 *)frame.buffer;

	/* We allocate a pbuf chain of pbufs from the pool. */
	p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

	/* Copy received frame from ethernet driver buffer to stack buffer */
	if (p != NULL)
	{
	  for (q = p; q != NULL; q = q->next)
	  {
		memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);
		l = l + q->len;
	  }
	}
  }

  /* Release descriptors to DMA */
  /* Check if received frame with multiple DMA buffer segments */
  if (DMA_RX_FRAME_infos->Seg_Count > 1)
  {
	DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
  }
  else
  {
	DMARxNextDesc = frame.descriptor;
  }

  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
  { 
	DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
	DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
  }

  /* Clear Segment_Count */
  DMA_RX_FRAME_infos->Seg_Count =0;
 }
 return p;
}

Изменено пользователем IXUS666

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

IXUS666, да эти изменения я внес. Извините, я это сделал сразу в начале разборок и подзабыл где и что именно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если отвлечься от LwIP, как во FreeRTOS можно завесить задачу с наивысшим приоритетом в состоянии READY? Что ее может блокировать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

IXUS666, да эти изменения я внес. Извините, я это сделал сразу в начале разборок и подзабыл где и что именно.

Попробуйте запустить сервер на не блокирующих сокетах..

Похожая ситуация выплыла с HTTP сервером, когда размер HTTP запроса был больше чем PBUF_POOL_BUFSIZE-(~40-60байт) и netconn_thread блокировалась на совсем. Пока толком не разбирался с этим, но если запустить входное соединения на не блокирующих сокетах то работает (хотя я полагаю что это неправильно, т.к. должно работать и так).

Для этого нужно в определить в lwipopts.h #define LWIP_SO_RCVTIMEO 1 и после newconn = netconn_accept(conn), определить newconn->recv_timeout = XX, XX в ms. Хотя у вас механизм UDP, как с ним работать я не знаю.

 

 

Если отвлечься от LwIP, как во FreeRTOS можно завесить задачу с наивысшим приоритетом в состоянии READY? Что ее может блокировать?

Так если используете плагин AVIX-RT для RTOS, то в окошке Tasks можно посмотреть Event Object. Если задача заблокирована то в Event Object отображается адресс обьекта (очередь/мьютекс) котораый заблокировал задачу.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Попробуйте запустить сервер на не блокирующих сокетах..

Для UDP вродебы нет смысла, там нет установления соединений, ожидания подтверждений и т.п. Да и пакеты я шлю небольшие. От компа 160 байт, в обратную сторону - 60 байт.

Так если используете плагин AVIX-RT для RTOS, то в окошке Tasks можно посмотреть Event Object. Если задача заблокирована то в Event Object отображается адресс обьекта (очередь/мьютекс) котораый заблокировал задачу.

Я и смотрю. Состояние задачи READY! Ничего не блокирует. А шедулер на эту задачу не переключает.

 

Вот что еще накопал.

Во время сбоя стал бряком в vTaskSwitchContext( ). Вижу, что в pxReadyTasksLists на списке уровня приоритета 6 (задача ethernetif_input) задача есть. А в переменной uxTopReadyPriority то 0, то 1. Т.е. шедулер так и не узнал о существовании готовой высокоприоритетной задачи. Или что-то ему помешало переключиться на эту задачу при ее размещении в очереди готовых. Если руками здесь подправить uxTopReadyPriority на 6, все начинает крутиться дальше, правда до следующего сбоя.

Справедливости ради, необходимо отметить, что иногда, гораздо реже, чем возникновение проблемы, описанной в топике, я сваливаюсь в HardFault. Видимо разваливается стек. Что из этих проблем первично - не совсем понятно, но проблема с зависанием задачи возникает чаще, пока разбираюсь с ней.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Кажется разобрался. Дело было в приоритетах прерываний.

Я назначил для прерывания Ethernet приоритет выше, чем configMAX_SYSCALL_INTERRUPT_PRIORITY, что совсем неправильно.

1,5 часа - полет нормальный.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

На самом деле проблема в том, что вот в этом куске кода:

            // Освобождаем дескрипторы DMA

            // Проверяем, фрейм растянут на несколько дескрипторов или нет
            if (DMA_RX_FRAME_infos->Seg_Count > 1)
            {
                DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
            }
            else
            {
                DMARxNextDesc = frame.descriptor;
            }


            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
            }
  
            // Очищаем счётчик сегментов
            DMA_RX_FRAME_infos->Seg_Count = 0;
  
  
            // Если стоял флаг о том, что Rx буфер недоступен, то снимаем его и опять включаем приём
            if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
            {
                // Снимаем флаг Receive buffer unavailable
                ETH->DMASR = ETH_DMASR_RBUS;
      
                // Включаем приём
                ETH->DMARPDR = 0;
            }

Может возникнуть такая ситуация, что мы работаем с дескриптором, но внутренняя периферия Ethernet принимает ещё один фрейм в это время. И при переключении на следующий дескриптор там уже нет флага OWN и работать с ним DMA не может. Соответственно и в прерывание мы не попадём. Лечится очень просто. В этом куске кода:

            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
            }

Добавляем, чтобы стало так:

            // Взводим бит OWN в Rx дескрипторах - отдаём буферы назад DMA
            for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
            {  
                DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
                DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);

                // Если после смены дескриптора на следующий он не свободен, то нужно обработать его тоже
                // фактически ещё раз попасть в это прерывание
                if ((DMARxNextDesc->Status & ETH_DMARxDesc_OWN) == 0) {
                    again = true;
                }
            }

А там, где снимаем флаги прерываний (далее по коду) поменять на:

        // Снимаем флаги прерываний
        if (!again)
        {
            ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
            ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
        }
        else
        {
            // Сюда можно добавить счётчик и убедиться, что иногда такое бывает
        }

Ну и где-нибудь объявить переменную bool again = false;

Мне такой танец с бубном помог. Оставил блок на всю ночь, вот утром проверил - полёт нормальный. А до этого через 2 часа работы зависал стабильно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Добрый день уважаемый V_M_Luck. Работаю с той же физикой KSZ8041.

Вопрос следующий: как вы изменили файл stm32_eth.h, а конкретно мне интересно

та часть кода где указывается адрес PHY_SR, регистра который у каждой физики разный

 

/** 
  * @brief  For LAN8700  
  */ 
//#define PHY_SR                           31         /*!< Tranceiver Status Register */
/** 
  * @brief  For DP83848  
  */ 
#define PHY_SR                           16     /*!< Tranceiver Status Register */

/* The Speed and Duplex mask values change from a PHY to another so the user have to update
   this value depending on the used external PHY */
/** 
  * @brief  For LAN8700  
  */ 
//#define PHY_Speed_Status            ((u16)0x0004)       /*!< Configured information of Speed: 10Mbps */
//#define PHY_Duplex_Status           ((u16)0x0010)       /*!< Configured information of Duplex: Full-duplex */

/** 
  * @brief  For DP83848  
  */ 
#define PHY_Speed_Status            ((u16)0x0002)    /*!< Configured information of Speed: 10Mbps */
#define PHY_Duplex_Status           ((u16)0x0004)    /*!< Configured information of Duplex: Full-duplex */
#define IS_ETH_PHY_ADDRESS(ADDRESS) ((ADDRESS) <= 0x20)
#define IS_ETH_PHY_REG(REG) (((REG) == PHY_BCR) || \
                             ((REG) == PHY_BSR) || \
                             ((REG) == PHY_SR))

 

за ответ зараннее благодарен

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Добрый день уважаемый V_M_Luck. Работаю с той же физикой KSZ8041.

Вопрос следующий: как вы изменили файл stm32_eth.h, а конкретно мне интересно

та часть кода где указывается адрес PHY_SR, регистра который у каждой физики разный

Извините, что сразу не ответил. Если еще актуально...

Все адреса регистров и биты описаны в даташите на KSZ8041.

Фактически, если работаете с библиотеками STM, вам нужно оперделить адреса регистров и биты и слегка подправить ETH_Init в файле stm32fxxx_eth.c.

У меня в этой функции от настройки физики оставлено только это:

  /*-------------------- PHY initialization and configuration ----------------*/
  /* Put the PHY in reset mode */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_BCR, PHY_Reset)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }
  
  while(ETH_ReadPHYRegister(PHYAddress, PHY_BCR) & PHY_Reset);

  /* Enable Auto-Negotiation */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_ANAR, PHY_ANAR_100BTX_FD | PHY_ANAR_100BTX_HD | PHY_ANAR_10BT_FD | PHY_ANAR_10BT_HD | PHY_ANAR_SELECT)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }
  /* Start Auto-Negotiation */
  if(!(ETH_WritePHYRegister(PHYAddress, PHY_BCR, PHY_AutoNegotiation | PHY_Restart_AutoNegotiation)))
  {
    /* Return ERROR in case of write timeout */
    return ETH_ERROR;
  }

И, далее, в какой-нибудь низкоуровневой задаче (или просто в main) проверять состояние подключения (идею и куски кода позаимствовал у кого-то на форуме - большое ему спасибо):

  if (ETH_ReadPHYRegister(PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)
  {
    //Соединение установлено
    switch(prev_link_state)
    {
    case 0:
      RegValue = ETH_ReadPHYRegister(PHY_ADDRESS, PHY_CNTR2) & PHY_CNTR2_OP_MODE;
      
      switch(RegValue)
      {
      case PHY_CNTR2_OP_MODE_10HD:  // 10 BASE T Half-duplex
        prev_link_state=1;
        RegValue = PHY_HALFDUPLEX_10M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_100HD:  // 100 BASE TX Half-duplex
        prev_link_state=1;
        RegValue = PHY_HALFDUPLEX_100M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        RegValue |= ETH_MACCR_FES;
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_10FD: // 10 BASE T Full-duplex
        prev_link_state=2;
        RegValue = PHY_FULLDUPLEX_10M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue &= ~(ETH_MACCR_FES | ETH_MACCR_DM);
        RegValue |= ETH_MACCR_DM;
        ETH->MACCR = RegValue;
        break;
      case PHY_CNTR2_OP_MODE_100FD: // 100 BASE TX Full-duplex
        prev_link_state=2;
        RegValue = PHY_FULLDUPLEX_100M;
        ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, RegValue);
        RegValue = ETH->MACCR;
        RegValue |= (ETH_MACCR_FES | ETH_MACCR_DM);
        ETH->MACCR = RegValue;
        break;
      default:
        break;
      }
    case 1:
      
      break;
    }
  }
  else
  {
    //Нет соединения
    if (prev_link_state) 
    {
      //Соединение было потеряно
      prev_link_state=0;
      // Enable and start Auto-Negotiation
      ETH_WritePHYRegister(PHY_ADDRESS, PHY_BCR, PHY_AutoNegotiation | PHY_Restart_AutoNegotiation);
    }
  }

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...