WebServer на STM32+ENC28J60+uIP+RTOS

Нужно было мне сделать девайс с веб-сервером. Сам сервер должен просто отдавать файлы и принимать/отправлять UDP, поэтому, использовать сложные стеки мне не хотелось(несмотря на большую производительность STM32). Остановился я однозначно на uIP.
Но в примерах есть только веб-сервер с отдачей файлов, сохраненных в памяти МК, а мне надо было отдавать с карты. Помучался 5 вечеров и вроде что то сделал. В стеке мне очень не понравилось то, что он оптимизирован под ОС, пришлось ставить FreeRTOS.
Теперь о том, что я переделал.
httpd.c:
static unsigned short generate_part_of_file(void *state)
{
	struct httpd_state *s = (struct httpd_state *)state;

/*	if(s->file.len > uip_mss()) {
		s->len = uip_mss();
	} else {
		s->len = s->file.len;
	}*/
	s->len = s->file.len;
	memcpy(uip_appdata, s->file.data_part, s->len);

	return s->len;
}
/*---------------------------------------------------------------------------*/
static PT_THREAD(send_file(struct httpd_state *s)) 
{
	PSOCK_BEGIN(&s->sout);

/*	do {
		PSOCK_GENERATOR_SEND(&s->sout, generate_part_of_file, s);
		s->file.len -= s->len;
		s->file.data += s->len;
	} while (s->file.len > 0);*/

	do {
		f_read(&s->file.fil_obj, s->file.data_part, uip_mss(), &s->file.len);	// читаем часть файла
		PSOCK_GENERATOR_SEND(&s->sout, generate_part_of_file, s);	// отправляем
	} while (s->file.len==uip_mss());	// повторяем процедуру, пока заданый размер равен считаному, если считаный меньше, то была считана последняя часть файла

	f_close(&s->file.fil_obj);	//закрываем файл
	printf("\rfile closed");

	PSOCK_END(&s->sout);
}


/*---------------------------------------------------------------------------*/
static
PT_THREAD(handle_output(struct httpd_state *s))
{
	PT_BEGIN(&s->outputpt);
/*
	if(!httpd_fs_open(s->filename, &s->file)) {
	//	httpd_fs_open(http_404_html, &s->file);//////////////////////////
		s->file.data = HTTP404Header;
		s->file.len = sizeof(HTTP404Header);
		///////////////////////////////////////////
		strcpy(s->filename, http_404_html);
		PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404));
		PT_WAIT_THREAD(&s->outputpt, send_file(s));
	} else {
		PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200));
		ptr = strchr(s->filename, ISO_period);
		if(ptr != NULL && strncmp(ptr, http_shtml, 6) == 0) {
			PT_INIT(&s->scriptpt);
			PT_WAIT_THREAD(&s->outputpt, handle_script(s));
		} else {
			PT_WAIT_THREAD(&s->outputpt, send_file(s));
		}
		///////////////////////////////
	}*/
	

	FRESULT	fr = f_open(&s->file.fil_obj, s->filename, FA_READ);	// открываем файл
	if (fr != FR_OK) fr = f_open(&s->file.fil_obj, "404.html", FA_READ);	// файл не открылся, отправляем страницу ошибку
	printf("\rf_open %s = %d", s->filename, s->file.len);

	s->file.len = s->file.fil_obj.fsize;

	PT_WAIT_THREAD(&s->outputpt, send_file(s));	
	
	PSOCK_CLOSE(&s->sout);
	PT_END(&s->outputpt);
}
/*---------------------------------------------------------------------------*/
static PT_THREAD(handle_input(struct httpd_state *s))
{
	PSOCK_BEGIN(&s->sin);
	PSOCK_READTO(&s->sin, ISO_space);

	if(strncmp(s->inputbuf, http_get, 4) != 0) {
		PSOCK_CLOSE_EXIT(&s->sin);
	}
	PSOCK_READTO(&s->sin, ISO_space);

	if(s->inputbuf[0] != ISO_slash) {
		PSOCK_CLOSE_EXIT(&s->sin);
	}

	if(s->inputbuf[1] == ISO_space) {
		strncpy(s->filename, http_index_html, sizeof(s->filename));
	} else {
		s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
		strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename));
	}

	printf("\r------------------input:\r");
	printf(s->inputbuf);

	s->state = STATE_OUTPUT;

	while(1) {
		PSOCK_READTO(&s->sin, ISO_nl);

		if(strncmp(s->inputbuf, http_referer, 8) == 0) {
			s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
			//httpd_log(&s->inputbuf[9]);
		}
	}

	PSOCK_END(&s->sin);
}
/*---------------------------------------------------------------------------*/
static void handle_connection(struct httpd_state *s)
{
	handle_input(s);
	if (s->state == STATE_OUTPUT) {
		handle_output(s);
	}
}
/*---------------------------------------------------------------------------*/
void httpd_appcall(void)
{
	struct httpd_state *s = (struct httpd_state *) &(uip_conn->appstate);

	//printf("\rappcal");

	if (uip_closed() || uip_aborted() || uip_timedout()) {
		if (s->file.fil_obj.fs)	{ // если открыт файл
			printf("\rfile conn closed");
			f_close(&s->file.fil_obj);	//закрываем файл
		}

	} else if (uip_connected()) {
		PSOCK_INIT(&s->sin, s->inputbuf, sizeof(s->inputbuf) - 1);
		PSOCK_INIT(&s->sout, s->inputbuf, sizeof(s->inputbuf) - 1);
		PT_INIT(&s->outputpt);
		s->state = STATE_WAITING;
		/*    timer_set(&s->timer, CLOCK_SECOND * 100);*/
		s->timer = 0;
		handle_connection(s);
	} else if (s != NULL) {
		if (uip_poll()) {
			++s->timer;
			if (s->timer >= 20) {
				uip_abort();
			}
		} else s->timer = 0;
		handle_connection(s);
	} else {
		uip_abort();
	}
}
/*---------------------------------------------------------------------------*/

void
httpd_init(void)
{
	uip_listen(HTONS(80));
}

Закомментированую часть я специально не убирал, если что то не так, исправьте плиз:).
main.c(лишнее убрал):
#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
void Task_uIP(void *pvParameters)
{
	uint32_t	i;
	uint32_t	delay_arp = 0;

	while(1)
	{
		// принимаем пакет (если есть)
		uip_len = enc28j60_recv_packet((uint8_t *) uip_buf, UIP_BUFSIZE);

		if (uip_len > 0) {
			if (BUF->type == htons(UIP_ETHTYPE_IP)) {
				// если пакет IP, то засылаем в стек
				uip_arp_ipin();
				uip_input();
				if (uip_len > 0) {
					// если есть что-то на выход, засылаем в сеть
					uip_arp_out();
					enc28j60_send_packet((uint8_t *) uip_buf, uip_len);
				}
			} else if (BUF->type == htons(UIP_ETHTYPE_ARP)) {
				// если это касается ARP, то передаем в блок ARP
				uip_arp_arpin();
				if (uip_len > 0) {
					enc28j60_send_packet((uint8_t *) uip_buf, uip_len);
				}
			}
		}

//////////////////// periodic
		vTaskDelay(5);	// если меньше 5, то через инет не работает(только в локалке), если убрать, то вообще не работает

		for (i = 0; i < UIP_CONNS; i++) {
			uip_periodic(i);
			if (uip_len > 0) {
				uip_arp_out();
				enc28j60_send_packet((uint8_t *) uip_buf, uip_len);
			}
		}

	#if UIP_UDP
		for(i = 0; i < UIP_UDP_CONNS; i++) {
			uip_udp_periodic(i);
			if(uip_len > 0) {
				uip_arp_out();
				network_send();
			}
		}
	#endif // UIP_UDP

		if ((delay_arp++) > 2000) { // около 10 сек.
			delay_arp = 0;
			uip_arp_timer();

		}
	}
}

int main(void)
{
	struct uip_eth_addr mac = { { 0x00, 0x01, 0x02, 0x03, 0x04, 0x00 } };

	enc28j60_init(mac.addr);

	uip_init();
	uip_arp_init();

	httpd_init();

	uip_setethaddr(mac);

	uip_ipaddr_t ipaddr;
	uip_ipaddr(ipaddr, 192, 168, 1, 170);
	uip_sethostaddr(ipaddr);
	uip_ipaddr(ipaddr, 192, 168, 1, 1);
	uip_setdraddr(ipaddr);
	uip_ipaddr(ipaddr, 255, 255, 255, 0);
	uip_setnetmask(ipaddr);


	xTaskCreate( &vGreenBlinkTask, (signed char*)"GreenBlink", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
	xTaskCreate( &vRedBlinkTask, (signed char*)"RedBlink", configMINIMAL_STACK_SIZE, NULL, 1, NULL );

	xTaskCreate(&Task_uIP, (signed char*)"uIP", configMINIMAL_STACK_SIZE*5, NULL, 1, ( xTaskHandle * ) NULL);

	vTaskStartScheduler();
}

Итак, здесь есть такая строка: vTaskDelay(5);. Если сделать эту задержку не 5 мс, а 1, то файлы достаточно быстро отдаются в локальной сети (80 кбайт/с стабильно). Но если сделать на роутере проброс порта и запросить страничку через инет, то постоянно вылазит ошибка 324 (net::ERR_EMPTY_RESPONSE): Сервер разорвал соединение, не отправив данные, пришлось делать 5мс, в этом случае скорость в локальной сети падает до 25-30 кб/с, но он нормально доступен через инет.
Сам инет у меня такой: роутер WR1043 с OpenWRT+CDMA модем интертелеком. Скорость инета в момент проверки была больше 50 кб/с.
Отлаживаю на такой платке www.ebay.com/itm/120640315306, ниже прилепил файлы проекта.
Есть несколько вопросов:
1. Проблемы при передачи через инет связаны с девайсом или роутером?
2. Можно ли выжать больше 80кб/с (правда, мне и этого достаточно).
Только плиз, не предлагайте использовать lwip с внешним PHY, мне нужно сделать на этой связке.
PS: Раньше FreeRTOS и uIP не использовал, поэтому сильно не пинайте
  • 0
  • 28 декабря 2012, 01:45
  • MrMisha

Комментарии (36)

RSS свернуть / развернуть
нужен кат!
0
эм, что за кат?
0
Находясь на странице редактирования поста, переведите свой взор в правый верхний угол (возле формы) и соизвольте почитать что там написано большими буквами…
0
нужно поставить тэг
после слов «Теперь о том, что я переделал.»
0
тэг <сut>
0
сорри, исправил))))
0
Автор поста наверняка еще спит=)… Эх, если бы он еще все исходники пихнул статью, то листать до предыдущих постов былобы еще интереснее;)
+1
я в 11 просынаюсь обычно
0
Тоже хорошо, здоровый сон — залог здоровья! (С)
0
я ложусь в 4 утра
0
Ну тут сложнее=)
0
+1
В стеке мне очень не понравилось то, что он оптимизирован под ОС, пришлось ставить FreeRTOS.
Ну это довольно типично для стеков. Их довольно сложно делать без ОС. Точнее, тогда придется выкатывать много требований к использующему стек коду.
0
  • avatar
  • Vga
  • 28 декабря 2012, 12:13
struct uip_eth_addr mac
должен быть const, нет?
0
f_read(&s->file.fil_obj, s->file.data_part, uip_mss(), &s->file.len);   // читаем часть файла

при нормальном раскладе имеем MSS=1460
+14 байт заголовок Ethernet
+20 байт заголовок IP
+20 байт заголовок TCP
в результате получаем пакет 1514 байт
Поразвлекался я с всякими параметрами MSS и ENC28J60_MAXFRAME и пришел к эксперментальным данным, что даже ENC28J60_MAXFRAME=1514 оказывается маловато для отправки таких пакетов. В результате поставил 1536, более точную цифру достаточную для работы вычислю потом.
Далее… У меня SD карта подключена по SDIO, поверх TCPIP сервисов еще портмаппер разруливает какому сервису какой коннект отдать. Сейчас вкучу завязаны DHCPС, NTPС и HTTP с файловой системой на SD карточке. Осталось скрипты прикрутить, чтобы динамические странички делать. На днях попробую заняться, если вдруг в этом проекте не случится доработка, чтобы позаимствовать.
Так вот… основная печаль на данный момент — скорость. У меня файл отдается 6.5килобайт в секунду. Жутко медленно. Отсюда думаю, стоит ли продолжать, или сразу потратиться на демоплатку с STM32Fx07 процом и нормальной сеткой на борту…
0
У меня при пакете 1024 скорость получилась 4,5 кбайт. Конечно же, этого слишком мало, но учитывая то, что у меня средний размер странички с джавой с стилями получается около 100 кбайт, я решил оставить такую скорость. Тем более, страничка будет только одна, а по нажатии на ссылки, с помощью джава скриптов будет происходить cgi запрос, ответ которого легко вмещается в 1 пакет, таким образом у меня будет главная страница грузиться примерно 20-30 сек, а остальные ссылки очень быстро (с точки зрения пользователя). Еще можно некоторые библиотеки (джава и стили)сохранять не на карте, а на отдельном веб сервере, тогда сама страница будет грузиться секунд 5, но мне важно, чтобы девайс мог работать в локальной сети, где нет инета. Также, заметил, что если периодическую функцию вызывать с задержкой не в пол секунды, а с задержкой 1-2мс, то при размере пакета 512 байт скорость вырастает до 70-80 кбайт/с, в локале работает отлично, но если делать запрос с инета через внешний ИП, то страница грузится далеко не всегда, примерно 1 раз с 5-10 попыток, как выяснилось, это очень сильно зависит от скорости инета (я использую cdma и у меня бывает скорость падает до 20 кбайт/с). Если посмотреть при запросе через локальную сеть посмотреть через wireShark, то видно, что девайс после передачи пакета какого то хрена отправляет ретрансмиссию(именно сразу же, не дожидаясь ответа от компа), а если в периодике убрать uip_udp_periodic(i);, то передача вроде бы работает нормально, но скорость 4 кб/с. Я еще заменил таймеры на нормальные (там таймеры зависели от частоты вызова функции), но скорость не выросла. Ну и плюс допилял httpd, чтобы файлы передавало вполне нормально+добавил CGI.
0
сделал тест, запросил 3 файла одновременно. Каждый скачался со скоростью 6.5кб в сек. Т.е. суммарная скорость в 3 раза выше. Мораль — надо копать в сторону ОС и каких-то задержек. Ограничение получается искусственное…
0
Ограничение, скорее всего, связано с тем, что в TCP на каждый пакет нужно получать подтверждение получения. Это медленно (отправил 576 байт данных, дождался пока они дойдут и будут приняты, дождался пока получишь подтверждение, только после этого послал следущие 576 байт, а пинги мееееедленные). Лечится только изменением алгоритма — отправляется сразу много пакетов и затем ожидается их групповое подтверждение. Lifelover например реализовывал подобный функционал, но в урезанном виде из-за ограниченности ресурсов.
0
ыы, точняк! Он там как раз ждет ACK после каждого пакета. Как освобожусь, поковыряюсь в исходниках.
Vga, пасиб за наводку!!!
0
Почитай доки на TCP, в частности на TCP_WINDOW. И попробуй это реализовать.
0
при увеличении частоты вызова куска кода:
if( ( xCurrentTime — xStartTime ) >= (RT_CLOCK_SECOND/50) )
происходит увеличение скорости отдачи файлов, но в тоже время сбивается алгоритм ожидания подтвержения и программа начинает делать перепосылки. Получается 2 исходящих пакета следом подтверждение.
0
там в каждом пакете с данными uip отправляет ACK+PSH, чем говорит компу, что надо немедленно отправить подтверждение (АСК). Вот нужно сделать так, чтобы PSH отправлялся не в каждом пакете, а только в конце окна (ну или просто каждые 3-5 пакетов) Вторая проблема в том, что юип после каждого отправленного пакета ожидает подтверждения от компа, вот это мне решить пока что не удалось.
0
Чтобы это сделать — надо сначала реализовать функционал перепосылки всего окна. А это требует много памяти, насколько я обмозговал эту ситуацию.
Т.е. каждый поток должен выделить буфер под окно. Это очень дофига…
0
эм, наверно вы что то спутали, там память жрать не должно. Нужно просто сделать, чтобы PHS слался каждые 3-5 пакетов, и чтобы юип ожидал ACK от компа только если передан PSH
0
Память оно таки жрет, т.к. не получив подтверждение на 5 пакетов — нужно все эти 5 пакетов перепослать. Но проблемы это вызывает только с динамически генерируемыми данными, статику можно просто заново прочитать.
Стек Lifelover'а проблему решает тем, что перезапрашивает все окно у использующей его программы. Тащемта это означает то, что использующая стек программа вынуждена это окно или хранить, или перегенерировать.
0
Помойму, отмотать файл назад с помощью ф-ции f_seek — это не проблема, проблема сделать так, чтобы uip не ждал ACK после каждого пакета, ато код юипа уж очень сильно запутаный
0
с SD картой вопрос решаемый. что делать с цги? сгенерировать тотже самый контент может быть невозможно, при использовании показаний часов или датчиков. цифры будут другими…
0
может и так, но реально размер ответного сообщения CGI у вас будет несколько байт, врятли он потеряется
0
Ну и плюс допилял httpd, чтобы файлы передавало вполне нормально+добавил CGI.
Последний вариант можно увидеть?
0
0
а почему не stm32Fxx7 с аппаратным MAC?
0
потому что до него очередь еще не дошла))))
0
и цена вопроса демо платы получается равной готовому планшетнику, с вифи или возможностью подключения усб карточки с rg45. в общем это уже уровень компьютера, с линуксом или андроидом.
Скорее всего кое что плавно перейдет именно на этот уровень :-(
0
О гуру Ethernet подключений помогите!!!
Писал в статью we.easyelectronics.ru/electro-and-pc/stm32-uip-enc28j60.html
Там мои последние коменты… так никто и не отвечает((
Подскажите пожалуйста что мне делать… надо через сеть попинать релюшку!!!
-1
Вопрос такой появился если кто разбирался глубже: функция netconn_write(...) использует буферированный вывод до fs_close или отправляет сразу же то, что получит из аргументов? т.е. если писать собирать данные для отправки вызвая эту ф-цию для маленьких порций (4-10 байт), количество IP пакетов также будет больше, чем в случае если отправлять макс. большим буффером?
0
  • avatar
  • valio
  • 05 октября 2013, 18:18
А ссылочку на файлы проекта можно?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.