В предыдущей части занятия мы продолжили писать функционал нашего клиент-сервера для отправки и приёма данных между собственно сервером и клиентом.
В данной части занятия мы продолжим начатое дело и сначала попробуем наши текстовые данные, посылаемые клиенту, как-то отсортировать.
Стандартная функция вывода процессов не умеет этого делать, поэтому добавим функцию для сортировки выше нашей функции DynWebPageStr
1 2 3 4 5 6 7 8 |
//--------------------------------------------------------------- void StrSort (char* str) { typedef struct strarray{ char data[20][50]; } strarray_ptr; } //--------------------------------------------------------------- |
В данной функции мы создали структуру с одним полем — двумерным символьным массивом. Когда такой массив обёрнут в структуру, к нему будет легче прицепиться.
Использовать для сортировки мы будем метод пузырька. Я думаю, многим этот метод известен. Мы сравниваем две соседние строки или величины, и та, которая меньше (если мы, конечно, сортируем наши данные по возрастанию), либо остаётся вверху, либо уходит вверх. Так мы доходим до самого низа и у нас получается то, что самая большая величина у нас находится уже внизу. Затем мы данный процесс повторяем, но до самой нижней строки уже не доходим, а доходим только до предыдущей. И так до тех пор, пока у нас самая меньшая величина не вытеснится вверх, как воздушный пузырь.
Создадим ещё один указатель на строку, которая у нас пришла в качестве аргумента
1 2 |
} strarray_ptr; char *str_tmp = (void*) str; |
Добавим ещё несколько локальных переменных
1 2 3 4 |
char *str_tmp = (void*) str; char *istr; int cnt = 0, cnt_pass=0, res = 0, i, j; uint32_t itemsize; |
Создадим указатель на данные типа нашей структуры и привяжем его к глобальному символьному буферу
1 2 |
uint32_t itemsize; strarray_ptr *strarray1 = (void*) str_buf; |
Далее добавим цикл, который по символам перевода каретки и новой строки разобьёт на строки нашу входящую строку и добавит их в наш массив
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
strarray_ptr *strarray1 = (void*) str_buf; //copy to array while(1) { istr = strstr(str_tmp,"rn"); itemsize = istr - str_tmp; if((itemsize>10)&&(itemsize<100)) { strncpy(strarray1->data[cnt],str_tmp,itemsize+2); strarray1->data[cnt][itemsize+2] = 0; str_tmp+=itemsize+2; } else { break; } cnt++; } |
Далее добавим цикл сортировки строк по алгоритму, который я объяснил выше
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
cnt++; } //sort cnt_pass=cnt; for(j=0;j<(cnt-1);j++) { for(i=0;i<(cnt_pass-1);i++) { res = strcmp (strarray1->data[i], strarray1->data[i+1]); if (res>0) { //swap strcpy (strarray1->data[cnt],strarray1->data[i+1]); strcpy (strarray1->data[i+1],strarray1->data[i]); strcpy (strarray1->data[i],strarray1->data[cnt]); } } cnt_pass--; } |
И затем, наоборот, скопируем наши строки из массива назад в нашу строку, только теперь они будут уже отсортированы в алфавитном порядке
1 2 3 4 5 6 7 8 9 10 11 |
cnt_pass--; } //copy from array str[0] = 0; for(i=0;i<cnt;i++) { if (i==0) strcpy (str,strarray1->data[i]); else strcat (str,strarray1->data[i]); } str_tmp[0] = 0; } |
По окончании тела функции мы обнулим окончание строки.
Теперь вызовем нашу функцию сортировки строк в функции DynWebPageStr
1 2 3 |
osThreadList((uint8_t *)(PAGE_BODY + len)); StrSort((char *)(PAGE_BODY + len)); netconn_write(conn, PAGE_BODY, strlen((char*)PAGE_BODY), NETCONN_COPY); |
Соберём код, прошьём контроллер, обновим страницу в браузере и нажмём кнопку START
Теперь у нас всё отсортировано, причём процессы, наименование которых с большой буквы, находятся сверху. Даже как-то статично смотрится после того, как прыгали наши строки. Но зато если будет постепенно расти использование стека в какой-то из функции, то мы это увидим без труда.
Пока мы до сих пор передавали с сервера в браузер текстовые данные.
Но есть возможность также передавать данные и других типов.
Зачастую возникает необходимость передать массивы числовых и бинарных данных, которые находятся в памяти не в виде строковых выражений данных величин, а именно в первозданном бинарном виде. Не стоит эти данные сначала преобразовывать в строку, а затем на стороне клиента снова их преобразовывать в числовые. На такие действия уходит очень много ресурсов — как процессорного времени, так и памяти. Если есть возможность обойтись без кодирования-декодирования, то лучше ею пользоваться. Мы так с вами всегда и делаем, когда передаём показания с различных датчиков, а также состояния каких-либо процессов. Поступим так и в нашем случае.
Представим ситуацию, что у нас есть массив каких-то данных, например показаний АЦП за какой-то промежуток времени. И мы этот массив хотим на страничке в браузере клиента показать в виде графика.
Мы, конечно, не будем сегодня подключать и исследовать какие-то сигналы, мы просто создадим массив случайных величин, передадим их клиенту и отобразим там в виде графика.
Для добавления графиков в WEB-страницы существует несколько готовых бесплатных библиотек.
Все они используют JavaScript и найти их несложно.
Вот и мы воспользуемся одной из них — это библиотека Chart.js, которая выпущена под лицензией MIT. Данная лицензия позволяет использовать данную библиотеку бесплатно по любому назначению с одной лишь ремаркой — ссылкой на файл лицензии. Файл лицензии мы также можем разместить в папке со своим проектом, если будем куда-то распространять такой проект. Только делать этого вовсе не нужно, так как мы скачаем с GitHub саму библиотеку в виде файла скрипта JS, а в содержимом данного файла есть ссылка на файл лицензии на Github. Так что, думаю, ничего мы не нарушаем. Поэтому идём на GitHub за библиотекой по ссылке https://github.com/chartjs/Chart.js и спокойно скачиваем скрипт. Он распространён в нескольких версиях, мы возьмём минимизированный, так как наша виртуальная система не любит больших файлов. В минимизированных версиях скриптов убраны все табуляции, пробелы, и даже переводы строк. От этого файл становится порой вдвое меньше, хотя он становится абсолютно нечитабельным, но работоспособность его от этого никак не страдает. Последнюю версию библиотеки мы возьмём по этой ссылке: Chart.js on GitHub.
Перейдём по ссылке и скачаем минимизированную версию
Создадим в папке с нашей главной страницей папку «js» и положим туда данный файл.
Подключим его в нашей главной странице index.php, а заодно добавим заголовок нашей страницы
1 2 3 |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>STM32F746G-DISCO</title> <script src="js/Chart.min.js"></script> |
Теперь мы спокойно можем обращаться ко всем функциям данной библиотеки. Рассказывать я про них не буду, так как урок не об этом, мы просто будем ими пользоваться. На официальном сайте всё об этом рассказано.
В блоке article после нашей информации о процессах, которую мы только что смотрели добавим также блок canvas, так как этого требует библиотека, а также блок с кнопкой START, аналогичной кнопке в предыдущем блоке, которая будет запускать наш график, для чего мы добавим данной кнопке также и функцию
1 2 3 4 5 6 7 8 |
<p></p> <p></p> <canvas id="myChart" width="700" height="400"></canvas> <p></p> <div id="butchart"> <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/> </div> <p></p> |
Прежде чем добавлять данную функцию, мы в блоки скрипта сначала добавим объект класса нашего графика
1 2 |
var xhr; var myChart; |
Затем в функции onload() мы получим ссылку на блок с идентификатором нашего графика и создадим из 512 16-битных чисел
1 2 3 |
xhr = new(XMLHttpRequest); var ctx = document.getElementById("myChart"); var data16Array = new Uint16Array(512); |
Затем мы создадим объект класса графика, в которой проведём первоначальную инициализацию. Так как метки на каждую точку нам не нужны, то мы их забьём пустыми строками, используя соответствующий массив, затем настроим фон, добавим данные в виде нашего массива с нулями, произведём настройки цветов, фонов и некоторых других опций, с которыми вы также можете ознакомиться на официальном сайте разработчика библиотеки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
var data16Array = new Uint16Array(512); myChart = new Chart(ctx, { type: 'line', data: { labels: ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""], datasets: [{ label: '# of Votes', data: data16Array, backgroundColor: 'rgba(150, 150, 0, 0.0)', //полностью прозрачный фон borderColor: 'rgba(150, 150, 0,1)', borderWidth: 2, pointStyle: 'line', pointRadius: 0 }] }, options: { tooltips: { enabled: false }, animation: { duration: 0 }, responsive: true, scales: { xAxes: [{ ticks:{ min: 0, max: 512, stepSize : 64, }, stacked: true, gridLines: { lineWidth: 0, color: "rgba(255,255,255,0.0)" } }], yAxes: [{ stacked: true, ticks: { min: 0, max: 4096, stepSize: 256, } }] } } }); } |
Добавим ещё один таймер, создадим для него сначала идентификатор, добавив ещё одну переменную
1 |
var idTimer1, idTimer2; |
После функции первого таймера Timer1 добавим функцию для второго таймера
1 2 3 4 5 6 |
idTimer1 = setTimeout("Timer1()", 1000); } function Timer2(){ xhr.open("GET", "content.bin?r=" + Math.random(), true); //Math.random() - защита от кеширования xhr.responseType = "arraybuffer"; } |
В данной функции мы также создали запрос, но с другим типом. Такой тип нам и нужен для оперирования сырыми (бинарными) данными.
Затем мы добавим событие на обработку пришедших данных от сервера, в которой мы считаем наши данные как 16-битные и присвоим их полю данных нашего графика, а затем мы обновим его. Потом отправим запрос серверу и запустим наш таймер
1 2 3 4 5 6 7 8 9 |
xhr.responseType = "arraybuffer"; xhr.onload = function (oEvent) { var uint16Array = new Uint16Array(this.response); myChart.data.datasets[0].data = uint16Array; myChart.update(); } xhr.send(null); idTimer2 = setTimeout("Timer2()", 1000); } |
Добавим функцию-обработчик для нашей кнопки в самом низу скрипта
1 2 3 4 5 6 |
function startchart(){ document.getElementById('butstring').innerHTML = ''; document.getElementById('butchart').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #666666;" onclick="stopchart()" value="STOP"/>'; Timer2(); } </script> |
Мы обнулили поле с информацией о процессах, затем кнопку START мы превратили в кнопку STOP, добавив также соответствующую функцию. Ну и, конечно же, не забываем запустить наш таймер, вернее его функцию. Напишем функции для кнопки STOP ниже
1 2 3 4 5 6 |
function stopchart(){ document.getElementById('butstring').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/>'; document.getElementById('butchart').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; clearTimeout(idTimer2); } </script> |
Здесь мы возвращаем кнопку START не только нашему полю, но и полю для запроса информации о процессах. Таким образом, мы во время работы таймера блокируем запуск повторно данной задачи, а также и другой задачи.
Теперь подобные блокировки мы должны добавить и в наши предыдущие функции для кнопок вывода с сервера состояния процессов.
Функция startstring()
1 2 |
function startstring(){ document.getElementById('butchart').innerHTML = ''; |
Функция stopstring()
1 2 |
document.getElementById('butstring').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startstring()" value="START"/>'; document.getElementById('butchart').innerHTML = ' <input class = "butcolor" type="button" style="color: #000000; background-color: #bbbbbb;" onclick="startchart()" value="START"/>'; |
Теперь сохраним нашу страницу, сгенерируем файл fsdata.c, обновим дерево нашего проекта и добавим ещё одну функцию формирования документа на запрос клиента после функции DynWebPageStr
1 2 3 4 5 6 7 8 9 10 |
//--------------------------------------------------------------- void DynWebPage(struct netconn *conn, uint16_t y_pos) { portCHAR PAGE_BODY[1300]; uint16_t len = 0; int i; PAGE_BODY[0] = 0; int val = 0; } //--------------------------------------------------------------- |
В данной функции мы пока только добавили некоторые локальные переменные и массив для будущего документа, который мы будем посылать клиенту на его запрос.
Соберём заголовок для пакета HTTP
1 2 3 |
int val = 0; sprintf(PAGE_BODY,"%s%s%s%s%drn%s",PAGE_HEADER_200_OK,PAGE_HEADER_SERVER,PAGE_HEADER_CONTENT_STREAM, PAGE_HEADER_LEN,1024,PAGE_HEADER_BYTES); |
В данном заголовке мы передали длину в байтах, чтобы клиенту легче было работать с нашим пакетом. У нас всего 512 величин по 16 бит, поэтому байтов будет 1024.
Измерим длину заголовка, и в цикле наберём данные, формируя их случайным образом, используя для наш аппаратный генератор, а затем передадим их вместе с заголовком клиенту
1 2 3 4 5 6 7 8 9 10 11 |
sprintf(PAGE_BODY,"%s%s%s%s%drn%s",PAGE_HEADER_200_OK,PAGE_HEADER_SERVER,PAGE_HEADER_CONTENT_STREAM, PAGE_HEADER_LEN,1024,PAGE_HEADER_BYTES); len = strlen(PAGE_BODY); for(i=0;i<512;i++) { val = HAL_RNG_GetRandomNumber(&hrng)%4096; PAGE_BODY[len + i * 2] = (uint8_t)val; PAGE_BODY[len + i * 2 + 1] = (uint8_t)(val>>8); } netconn_write(conn, PAGE_BODY, strlen((char*)PAGE_BODY) + 1024, NETCONN_COPY); } |
Добавим также в функции задачи обработки запросов отследим запрос клиента, а в теле условия мы вызовем нашу функцию формирования пакета для ответа клиенту
1 2 3 4 5 |
else if (strncmp((char const *)buf,"GET /content.bin",16)==0) { DynWebPage(newconn, arg_sock->y_pos); } else |
Также не забываем добавить запрос клиента на файл библиотеки для графика
1 2 3 4 5 6 7 |
else if (strncmp((char const *)buf,"GET /js/Chart.min.js",20)==0) { fs_open(&file, "/js/Chart.min.js"); netconn_write(newconn, (const unsigned char*)(file.data), (size_t)file.len, NETCONN_NOCOPY); fs_close(&file); } else if (strncmp((char const *)buf,"GET /IMG/img01.jpg",18)==0) |
Соберём код, прошьём контроллер, обновим страницу в браузере и нажмём кнопку START под полем для графика
У нас прекрасно обновляется информация раз в секунду, я пробовал укорачивать интервал таймера до 40 милисекунд, то есть график обновлялся до 25 кадров в секунду, но иногда были подвисания, особенно в загруженной сети. Напрямую я проводом плату соединять с компьютером не пробовал. Но, я считаю, что и с такой скоростью передача 16-битных данных и отображение на графике такого количества данных динамически — это великое дело.
Думаю, что когда мы будем использовать WebSocket, то процесс пойдёт ещё веселее.
Итак, на данном занятии мы научились передавать данные клиенту по запросу посредством протокола HTTP с использованием некоторых интересных возможностей языка JavaScript без перезагрузки страницы. И не только это, а ещё и передавать данные от клиента серверу также без перезагрузки страницы. Также мы научились передавать пакеты по протоколу HTTP не только строковых, но и бинарных типов.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Hello Narod,
First, thank you this lots of STM32 article.
I probaly compile this project, but have some problem.
First:following lines in error.
hdma2d.LayerCfg[0].AlphaInverted = DMA2D_REGULAR_ALPHA;
hdma2d.LayerCfg[0].RedBlueSwap = DMA2D_RB_REGULAR;
Second: I deleted this lines, and complie is ok.
Ping ok, but no load the webpage. Why?
Best regards
Peter
Other info:
The TFT display full black and no visible the text.
Hello Narod,
First, thank you this lots of STM32 article.
I probaly compile this project, but have some problem.
First: following lines in error.
hdma2d.LayerCfg [0] .AlphaInverted = DMA2D_REGULAR_ALPHA;
hdma2d.LayerCfg [0]. RedBlueSwap = DMA2D_RB_REGULAR;
Second: I deleted this lines, and complie is ok.
Ping ok, but no load the webpage. Why?
The TFT display full black and no visible the text.
Best regards
Peter
Спасибо за урок! Очень полезный — чтобы знали. Помогли с ним Вы. Хотелось бы и Вам чем-то посильно помочь. Еще, вот включил DHCP в настройках — в итоге тоже работает все кроме (!) отправки пакета с данными графика. А этот пакет большой (ну и что?), но что-то не хочет отправляться хоть тресни. Памяти не хватает где-то наверно. Но в 'lwip' столько настроек и сколько не менял толку не стало. Покамест не разобрался.