[Из песочницы] Реализация PPPOS на stm32f4-discovery

Однажды передо мной возникла задача обеспечить выход в сеть Интернет на STM32 имея для этого только COM порт. Для решения этой задачи мне понадобился PPP, или, еcли быть точным, PPPoS (англ. Point-to-Point Protocol over Serial — один из способов реализации PPP, используется при подключении через COM-порт).

В процессе решения поставленной передо мной задачи я столкнулся с некоторыми трудностями, одна из которых недостаточное, на мой взгляд, освещение вопросов связанных с PPPoS в сети Интернет. Этим постом я постараюсь закрыть обозначенный пробел, на сколько позволят мои скромные знания.

Статья описывает создание проекта для System Workbench for STM32 с нуля. Показывает пример работы с UART. Есть примеры кода для реализации PPP. Ну и конечно, пример отправки сообщения на соседний компьютер.

Введение


PPP (англ. Point-to-Point Protocol) — двухточечный протокол канального уровня (Data Link) сетевой модели OSI. Обычно используется для установления прямой связи между двумя узлами сети, причём он может обеспечить аутентификацию соединения, шифрование и сжатие данных. Используется на многих типах физических сетей: нуль-модемный кабель, телефонная линия, сотовая связь и т. д.

Часто встречаются подвиды протокола PPP, такие, как Point-to-Point Protocol over Ethernet (PPPoE), используемый для подключения по Ethernet, и иногда через DSL; и Point-to-Point Protocol over ATM (PPPoA), который используется для подключения по ATM Adaptation Layer 5 (AAL5), который является основной альтернативой PPPoE для DSL.

PPP представляет собой целое семейство протоколов: протокол управления линией связи (LCP), протокол управления сетью (NCP), протоколы аутентификации (PAP, CHAP), многоканальный протокол PPP (MLPPP).

Из Wikipedia.

Подготовка


Для решения поставленной задачи нам понадобится:

Железо:


  1. Отладочная плата stm32f4_discovery:
    u10k6-una1bkmaphl4wlazibaca.jpeg
  2. Переходник USB — miniUSB для подключения платы к компьютеру.
  3. Два переходника USBtoUART FT232:
    uhukmolq4_t-pckn8h2rjofspnu.png
  4. Так же пригодятся два USB удлинителя, не обязательно, но просто удобно.


Софт:


  1. Виртуальная машина VirtualBox. Скачать можно тут. Также качаем и устанавливаем Extension Pack для VirtualBox.
  2. Два установочных диска с операционными системами Windows и Linux. Windows берем тут, Linux тут.

    После установки ОС потребуется установка дополнений гостевой ОС. Для поставленной задачи нам достаточно будет 32х систем, можно не морочиться с включением виртуализации.

  3. Для Windows нам понадобится программа способная принимать запросы и отвечать на них по протоколу TCP/IP, ну и терминальная программа для работы с COM портом. PacketSender качаем тут (нажмите на «No thanks, just let me download.»), терминал тут. Кроме того нам понадобится STM32CubeMX для первоначальной настройки проекта. Качаем с st.com (после регистрации ссылка придет на электронную почту).
  4. На основную ОС ставим System Workbench for STM32. Качаем отсюда (потребуется регистрация).


Этап 1. Создание проекта


Первым делом открываем STM32CubeMX и создаем там новый проект под нашу плату stm32f4-discovery. Включаем RCC, Ethernet (ETH), SYS, USART2, USART3, после чего включаем FREERTOS и LWIP.

vrjkveavft5ry-oasa7h2e07rwe.png
9cv2rv-_8yf5w67mr7bw7ynp1vk.png

Для диагностики нам понадобятся светодиоды на плате. По этому настроем ноги PD12-PD15 как GPIO_Output.

-sbblkybmbdz1wrftiow38tq4eg.png

На вкладке Clock Configuration настраиваем частоту, как на картинке ниже.

sdccqyreetba7g_m8yq_gypc-ge.png

Далее, на вкладке Configuration настраиваем порты USART. Мы будем работать с ними в режиме DMA. У нас два порта USART, один мы будем использовать для передачи и получения данных по протоколу PPP, второй для логирования. Чтобы они заработали нам нужно настроить DMA на RX и TX для обоих портов. Для всех ножек настройки DMA приоритет ставим «Medium». Для USART2 ножке RX устанавливаем режим «Circular». Остальные настройки оставляем по-умолчанию.

96s89uloxfrdtxd0scqvcyb-zng.png

Также потребуется включить глобальное прерывание для обоих портов на вкладке «NVIC Settings».

На этом первоначальная настройка проекта в STM32CubeMX завершена. Сохраняем файл проекта и делаем генерацию кода для System Workbench for STM32.

ss_d8lzdxneoj5mjasvmyhhhxiu.png

Реализация


Теперь проверим, что выгруженный код компилируется и работает. Для этого в файле main.c в функции «StartDefaultTask» заменим тело бесконечного цикла for (;;) на код включения и выключения светодиодов.

Должно получиться так:

/* StartDefaultTask function */
void StartDefaultTask(void const * argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();

  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
    osDelay(1000);
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
    osDelay(1000);
  }
  /* USER CODE END 5 */ 
}


Компилируем прошиваем и смотрим. На плате должны моргать все четыре светодиода.

Этап 2. Работа с USART


Следующая наша задача это проверить правильность работы наших USART.

Первое, что нам нужно сделать это подключить наши FT232 к discovery. Для этого смотрим на какие ножки разведены USART интерфейсы. У меня это PD6 и PD5 для USART2_RX и USART2_TX соответственно.

qjyhyoohdofxtqedphuhzq7qcac.png

А также PD9 и PD8 для USART3_RX и USART3_TX соответственно.

37bfomzqqcapediljrf14flpuiu.png

Кроме того нам понадобится ножка GND.

Находим эти выводы на плате и соединяем с выводами FT232 при этом вывод GND на плате может быть любым, вывод RX на плате должен быть соединен с выводом TX на FT232, а вывод TX на плате должен быть соединен с выводом RX на FT232. Остальные выводы не используются.

Осталось подключить наши FT232 к USB портам компьютера, а также подключить к компьютеру саму плату discovery через разъем miniUSB (не путать с microUSB).

После подключения FT232 основная ОС установит для них драйвера, после чего эти устройства нужно будет пробросить в гостевую Windows на виртуальной машине.

Теперь добавляем программный код, который нужен для работы наших USART. Для этого мы добавим четыре файла: usart.h, usart.c, logger.h, logger.c.

Содержимое файлов:

файл usart.h

#ifndef _USART_
#define _USART_

#include "stm32f4xx_hal.h"

void usart_Open(void);
bool usart_Send(char* bArray, int size_bArray);
uint16_t usart_Recv(char* bArray, uint16_t maxLength);

#endif /* _USART_ */


файл usart.c

#include "usart.h"
#include "logger.h"

#include "cmsis_os.h"

#define Q_USART2_SIZE 200

xQueueHandle g_qUsart;
osThreadId g_usart_rxTaskHandle;

extern UART_HandleTypeDef huart2;

void usart_rxTask(void);

uint8_t bGet[Q_USART2_SIZE] = {0};
uint16_t g_tail = 0;

void usart_Open(void)
{
        g_qUsart = xQueueCreate( Q_USART2_SIZE, sizeof( unsigned char ) );

        osThreadDef(usart_rxTask_NAME, usart_rxTask, osPriorityNormal, 0, Q_USART2_SIZE/4+128);
        g_usart_rxTaskHandle = osThreadCreate(osThread(usart_rxTask_NAME), NULL);

        HAL_UART_Receive_DMA(&huart2, bGet, Q_USART2_SIZE);

}

void usart_rxTask(void)
{
        for(;;)
        {
                uint16_t length = Q_USART2_SIZE - huart2.hdmarx->Instance->NDTR;

                while(length - g_tail)
                {
                        uint8_t tmp = bGet[g_tail];
                        xQueueSendToBack( g_qUsart, &tmp, 100 );
                        g_tail++;
                        if (g_tail == Q_USART2_SIZE)
                                g_tail = 0;
                }
        }
}

bool usart_Send(char* bArray, int size_bArray)
{
        HAL_StatusTypeDef status;

        status = HAL_UART_Transmit_DMA(&huart2, bArray, size_bArray);

        while (HAL_UART_GetState(&huart2) != HAL_UART_STATE_READY)
        {
                if (HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_RX)
                        break;

                osDelay(1);
        }

        if (status == HAL_OK)
                return true;

        return false;
}

uint16_t usart_Recv(char* bArray, uint16_t maxLength)
{
        uint8_t tmp = 0;
        uint16_t length = 0;
        while(uxQueueMessagesWaiting(g_qUsart))
        {
                xQueueReceive( g_qUsart, &tmp, 100 );
                bArray[length] = tmp;
                length++;
                if (length >= maxLength)
                        break;
        }

        return length;
}


файл logger.h

#ifndef _LOGGER_
#define _LOGGER_

void logger(const char *format, ...);

#endif /* _LOGGER_ */


файл logger.c

#include "logger.h"

#include "stm32f4xx_hal.h"
#include 

extern UART_HandleTypeDef huart3;

#define MAX_STRING_SIZE 1024

HAL_StatusTypeDef logger_Send(char* bArray, uint32_t size_bArray)
{
        HAL_StatusTypeDef status;

        for(int i=0;i<5;i++)
        {
                status = HAL_UART_Transmit_DMA(&huart3, bArray, size_bArray);
                if (status == HAL_OK)
                        break;
                osDelay(2);
        }


        while (HAL_UART_GetState(&huart3) != HAL_UART_STATE_READY)
        {
                osDelay(1);
        }

        return status;
}

void logger(const char *format, ...)
{
                char buffer[MAX_STRING_SIZE];

                va_list args;
                va_start (args, format);
                vsprintf(buffer, format, args);
                va_end(args);

                buffer[MAX_STRING_SIZE-1]=0;

                logger_Send(buffer, strlen(buffer));
}


Usart нам нужен для передачи и получения данных по usart2. Он будет нашим основным интерфейсом общения с PPP-сервером.

Logger нам нужен для реализации логирования, путем посылки сообщений в терминал. Функция void usart_Open (void) формирует очередь и запускает задачу по обслуживанию этой очереди. Эту функцию нужно выполнить до начала работы с USART. Дальше все просто, функция bool usart_Send (char* bArray, int size_bArray) отправляет данные в порт, а
uint16_t usart_Recv (char* bArray, uint16_t maxLength) получает их из очереди, в которую их любезно сложила функция void usart_rxTask (void).

Для логера все еще проще, там не требуется получения данных следовательно, ни очереди, ни задачи обслуживания очереди не нужно.

В начало файла main.h нужно добавить несколько дефайнов описывающих тип bool, отсутствующий в языке C.

/* USER CODE BEGIN Includes */
typedef unsigned char bool;
#define true 1
#define false 0
/* USER CODE END Includes */


Теперь пришло время проверить работоспособность полученного кода. Для этого в файле main.c, изменим код уже известной нам задачи «StartDefaultTask»

/* USER CODE BEGIN 4 */
#include "usart.h"
#include "logger.h"
#define MAX_MESSAGE_LENGTH 100
/* USER CODE END 4 */

/* StartDefaultTask function */
void StartDefaultTask(void const * argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();

  /* USER CODE BEGIN 5 */
  usart_Open();
  /* Infinite loop */
  uint8_t send[] = "Send message\r\n";
  uint8_t recv[MAX_MESSAGE_LENGTH] = {0};
  uint16_t recvLength = 0;
  for(;;)
  {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
        osDelay(1000);
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
        osDelay(1000);

        if (usart_Send(send, sizeof(send)-1))
          logger("SEND - %s", send);
        recvLength = usart_Recv(recv, MAX_MESSAGE_LENGTH-1);
        if (recvLength)
        {
          recv[recvLength] = 0;
          logger("RECV - %s\r\n", recv);
        }
  }
  /* USER CODE END 5 */ 
}


Кроме того нужно дать побольше памяти стеку нашей задачи. Для этого в вызове функции osThreadDef (), файла main.c, нужно 128 исправить на 128×10 чтобы получилось так:

osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128*10);


Компилируем и прошиваем. Светодиоды моргают так же как и в предыдущей задаче.

Чтобы увидеть результат наших трудов нужно в нашей виртуальной машине запустить программу Terminal. Один экземпляр программы для логирующего порта, второй для основного. Посмотрите в диспетчере устройств какие номера портов были назначены вашим FT232. Если были назначены номера более 10, переназначьте.

При запуске второго экземпляра программы может возникнуть ошибка, закройте окно с ошибкой и продолжите работать с программой.

Для обоих портов устанавливаем соединение на 115200 бод, data bits — 8, parity — none, stop bits — 1, handshaking — none.

Если вы все сделали правильно, то в окне терминала для usart2 будет передаваться сообщение «Send message». В окно терминала для логера будет дублироваться это же сообщение только с префиксом «SEND — »

Если в окне терминала для usart2 вы вобьете какой-то текст в поле «Send» и нажмете соответствующую кнопку, справа от этого поля, то в окне логера вы увидите это же сообщение с префиксом «RECV — »

На картинке ниже: слева — логер, справа — usart2.

k5v6hcxebj6hfdgbitfwyzs1ffw.png

Этап 3. Начинаем работать с PPP


В рамках этой задачи мы поднимем PPP-соединение. Первым делом включаем использование PPP, меняем значение дефайна PPP_SUPPORT в файле ppp_opts.h на 1. Затем переопределяем нужные нам дефайны в файле lwipopts.h,

/* USER CODE BEGIN 1 */
#define MEMP_NUM_SYS_TIMEOUT 8
#define CHECKSUM_GEN_IP 1
#define CHECKSUM_GEN_TCP 1
/* USER CODE END 1 */


При этом старые дефайны нужно закомментировать.

Теперь изменяем файл lwip.c, вставляем в блок »/* USER CODE BEGIN 0 */» следующий код:

/* USER CODE BEGIN 0 */
#include "usart.h"

#include "pppos.h"
#include "sio.h"
#include "dns.h"
#include "ppp.h"

static ppp_pcb *ppp;
struct netif pppos_netif;

void PppGetTask(void const * argument)
{
  uint8_t recv[2048];
  uint16_t length = 0;
  for(;;)
  {
        length=usart_Recv(recv, 2048);
        if (length)
        {
                pppos_input(ppp, recv, length);
                logger("read - PppGetTask() len = %d\n", length);
        }

        osDelay(10);
  }

}

#include "ip4_addr.h"
#include "dns.h"

static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
{
                struct netif *pppif = ppp_netif(pcb);
                LWIP_UNUSED_ARG(ctx);

                switch(err_code)
                {
                        case PPPERR_NONE:               /* No error. */
                        {
                                logger("ppp_link_status_cb: PPPERR_NONE\n\r");
                                logger("   our_ip4addr = %s\n\r", ip4addr_ntoa(netif_ip4_addr(pppif)));
                                logger("   his_ipaddr  = %s\n\r", ip4addr_ntoa(netif_ip4_gw(pppif)));
                                logger("   netmask     = %s\n\r", ip4addr_ntoa(netif_ip4_netmask(pppif)));
                        }
                        break;

                        case PPPERR_PARAM:             /* Invalid parameter. */
                                        logger("ppp_link_status_cb: PPPERR_PARAM\n");
                                        break;

                        case PPPERR_OPEN:              /* Unable to open PPP session. */
                                        logger("ppp_link_status_cb: PPPERR_OPEN\n");
                                        break;

                        case PPPERR_DEVICE:            /* Invalid I/O device for PPP. */
                                        logger("ppp_link_status_cb: PPPERR_DEVICE\n");
                                        break;

                        case PPPERR_ALLOC:             /* Unable to allocate resources. */
                                        logger("ppp_link_status_cb: PPPERR_ALLOC\n");
                                        break;

                        case PPPERR_USER:              /* User interrupt. */
                                        logger("ppp_link_status_cb: PPPERR_USER\n");
                                        break;

                        case PPPERR_CONNECT:           /* Connection lost. */
                                        logger("ppp_link_status_cb: PPPERR_CONNECT\n");
                                        break;

                        case PPPERR_AUTHFAIL:          /* Failed authentication challenge. */
                                        logger("ppp_link_status_cb: PPPERR_AUTHFAIL\n");
                                        break;

                        case PPPERR_PROTOCOL:          /* Failed to meet protocol. */
                                        logger("ppp_link_status_cb: PPPERR_PROTOCOL\n");
                                        break;

                        case PPPERR_PEERDEAD:          /* Connection timeout. */
                                        logger("ppp_link_status_cb: PPPERR_PEERDEAD\n");
                                        break;

                        case PPPERR_IDLETIMEOUT:       /* Idle Timeout. */
                                        logger("ppp_link_status_cb: PPPERR_IDLETIMEOUT\n");
                                        break;

                        case PPPERR_CONNECTTIME:       /* PPPERR_CONNECTTIME. */
                                        logger("ppp_link_status_cb: PPPERR_CONNECTTIME\n");
                                        break;

                        case PPPERR_LOOPBACK:          /* Connection timeout. */
                                        logger("ppp_link_status_cb: PPPERR_LOOPBACK\n");
                                        break;
                        default:
                                        logger("ppp_link_status_cb: unknown errCode %d\n", err_code);
                                        break;
                }
}

// Callback used by ppp connection
static u32_t ppp_output_cb(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx)
{
        LWIP_UNUSED_ARG(pcb);
        LWIP_UNUSED_ARG(ctx);

        if (len > 0)
        {
                if (!usart_Send(data, len))
                                return 0x05;
        }
        logger("write - ppp_output_cb() len = %d\n", len);

        return len;
}

void pppConnect(void)
{
        ppp = pppos_create(&pppos_netif, ppp_output_cb, ppp_link_status_cb, NULL);
        ppp_set_default(ppp);

        osThreadId PppGetTaskHandle;
        osThreadDef(PPP_GET_TASK_NAME, PppGetTask, osPriorityNormal, 0, 128*10);
        PppGetTaskHandle = osThreadCreate(osThread(PPP_GET_TASK_NAME), NULL);

        err_t err = ppp_connect(ppp,0);
        if (err == ERR_ALREADY)
        {
                logger("Connected successfully");
        }

        for(int i=0;i<40;i++)
        {
                osDelay(500);
                if (ppp->phase >= PPP_PHASE_RUNNING)
                        break;
        }

}

/* USER CODE END 0 */


Затем в функцию MX_LWIP_Init (), в блок »/* USER CODE BEGIN 3 */» добавляем вызов функции pppConnect ().

Кроме того нужно увеличить размер кучи, для этого в файле FreeRTOSConfig.h нужно закомментировать дефайн configTOTAL_HEAP_SIZE, а в конце файла, в блоке /* USER CODE BEGIN Defines */ объявить его с новым значением.

/* USER CODE BEGIN Defines */          
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#define configTOTAL_HEAP_SIZE                    ((size_t)1024*30)
/* USER CODE END Defines */ 


А также в файле usart.c изменить значение дефайна Q_USART2_SIZE на 2048.

Настройка соединения начинается с функции MX_LWIP_Init () она создана автоматически мы лишь добавили в нее вызов функции pppConnect (). В этой функции запускаются задачи обслуживающие PPPOS соединение. Функции pppos_create () нужно передать адреса функций, которые будут обслуживать отправку сообщений и вывод информации об изменении статуса соединения. Для нас это функции ppp_output_cb () и ppp_link_status_cb () соответственно. Кроме того в функции pppConnect () будет запущена задача по обслуживанию полученных сообщений. В конце своей работы функция pppConnect () дождется установления соединения с сервером, после чего завершит свою работу.

Работа с сетью будет осуществляться на более высоком уровне, как только LWIP решит, что нужно отправить сообщение в сеть, автоматически будет вызвана функция ppp_output_cb (). Ответ из сети будет получен функцией PppGetTask (), в рамках задачи по обслуживанию входящих сообщений, и передан в недра LWIP. Если изментися статус соединения то автоматически будет вызвана функция ppp_link_status_cb ().

И наконец мы изменим задачу StartDefaultTask. Теперь она должна иметь такой вид:

void StartDefaultTask(void const * argument)
{
  /* init code for LWIP */
//  MX_LWIP_Init();

  /* USER CODE BEGIN 5 */
  usart_Open();
  MX_LWIP_Init();
  /* Infinite loop */
  for(;;)
  {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
        osDelay(1000);
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
        osDelay(1000);
  }
  /* USER CODE END 5 */ 
}


Готово, можно компилировать и прошивать.

На этом этапе нужно запустить сервер PPP. Для этого нужно сначала развернуть виртуалку с ОС Linux. Я использовал Ubuntu 16.04×32. После установки операционной системы нужно настроить использование COM порта.

В этой части нам не нужна виртуальная машина с Windows, можно ее смело выключить. Оба FT232 подключаем в Linux.

В Linux прежде чем начинать работу с COM портом нужно разрешить пользователю его использовать. Для этого выполним следующую команду:

sudo addgroup USERNAME dialout


где USERNAME — имя текущего пользователя.

Чтобы посмотреть доступные в системе COM порты нужно выполнить команду:

dmesg | grep tty


zch4mpfwyybln76dl1jnhpxjt0s.png

Мы видим, что в системе присутствуют два порта ttyUSB. Мы не можем сразу сказать какой из них logger, а какой usart2. Просто нужно их проверить по очереди.

Сначала выполним команды для чтения из одного порта:

stty -F /dev/ttyUSB0 115200
cat /dev/ttyUSB0


затем из другого:

stty -F /dev/ttyUSB1 115200
cat /dev/ttyUSB1


Где увидим такую картину тот и есть logger.

u0cuzcnbhzppwdhpibm2o6zelpk.png

Можно оставить это окно, оно нам не будет мешать.

Далее нужно разрешить пакетам отправленным из нашей платы покидать пределы своей подсети. Для этого нужно настроить iptables. Выполняем следующие действия:

1. Откроем новое окно консоли
2. Нужно узнать свой ip и имя сетевого интерфейса (выполните команду ifconfig)

jyd49satpc3erdlzeddi44gwcy4.png

3. Выполните команды настройки nat

sudo echo 1 | sudo tee -a /proc/sys/net/ipv4/ip_forward > /dev/null
sudo echo 1 | sudo tee -a /proc/sys/net/ipv4/ip_dynaddr > /dev/null
sudo iptables -F FORWARD
sudo iptables -F -t nat
sudo iptables -t nat -A POSTROUTING -o enp0s3 -j SNAT --to-source 192.168.10.196
sudo iptables -t nat -L


где enp0s3 — имя сетевого интерфейса
192.168.10.196 — ваш IP адрес
/proc/sys/net/ipv4/ — путь к соответствующему файлу.

Эти команды можно переписать в пакетный файл и выполнять его каждый раз перед запуском PPP сервера. Можно добавить и в автозапуск, но я этого не делал.

Теперь мы готовы к запуску сервера, осталось только создать файл настроек. Я назвал его »pppd.conf», предлагаю использовать следующие настройки:

nodetach
noauth
passive
local
debug
lock
192.168.250.1:192.168.250.2
/dev/ttyUSB1
115200
lcp-echo-interval 10
lcp-echo-failure 1
cdtrcts


Переписываем настройки в файл после чего можно запускать сервер. Это делается командой sudo pppd file ./pppd.conf

Сервер PPPD должен быть запущен до старта discovery, по этому после старта PPPD нужно нажать на кнопку «Reset» расположенной на плате.

Если вы все сделали правильно, то увидите такую картину:

kv-pljob3duk3bigzj3iazcsasa.png

Слева запущенный pppd, справа logger.

Этап 4. Отправляем пакетик


На этом этапе нам понадобятся обе виртуалки. Linux для pppd и Windows для приема пакета. Для упрощения задачи нужно чтобы обе машины были в одной подсети, идеальным решением будет указать в настроках сети VirtualBox для обоих машин соединение типа «Сетевой мост», а в Windows отключить брандмауэр.

Запускаем виртуалки и настраиваем ppp соединение платы discovery с pppd. На Windows узнаем IP адрес машины (команда ipconfig), у меня он получился 192.168.10.97.

Запускаем Packet Sender и настраиваем его следующим образом:

k8ztuv4eccjueyvlk8gigjbmkj0.png

Теперь снова изменим задачу StartDefaultTask, в файле main.c.

/* USER CODE BEGIN 4 */
#include "logger.h"
#include "sockets.h"
typedef uint32_t SOCKET;
/* USER CODE END 4 */

/* StartDefaultTask function */
void StartDefaultTask(void const * argument)
{
  /* init code for LWIP */
//  MX_LWIP_Init();

  /* USER CODE BEGIN 5 */
  usart_Open();
  MX_LWIP_Init();
  /* Infinite loop */

  uint8_t sendStr[]="Test message TCP/IP.";
  uint8_t resvStr[100]={0};
  int     resvLength = 0;

  struct sockaddr_in sockAddr;
  sockAddr.sin_family = AF_INET;
  sockAddr.sin_port   = htons( 6565 );
  uint32_t addr = inet_addr("192.168.10.97");
  sockAddr.sin_addr.s_addr = addr;

  SOCKET socket = NULL;
  int nError = 0;

  /* Infinite loop */
  for(;;)
  {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
        osDelay(1000);
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
        osDelay(1000);

        socket = socket( AF_INET, SOCK_STREAM, 0 );
        nError = connect( socket, (struct sockaddr*)&sockAddr, sizeof(sockAddr) );
        if ( nError ==  0 )
        {
          nError = send( socket, sendStr, sizeof(sendStr)-1, 0 );
          if ( nError <  0 )
                  logger("SEND ERROR %d\n", nError);
          else
          {
                  logger("SEND - %s\n", sendStr);

                  resvLength = 0;
                  while(resvLength < 1)
                          resvLength = lwip_recv( socket, resvStr, sizeof(resvStr), MSG_WAITALL);

                  resvStr[resvLength]=0;
                  logger("GET - %s\n", resvStr);
          }

          lwip_close(socket);
        }
        else
          logger("CONNECT ERROR %d\n", nError);
  }
  /* USER CODE END 5 */ 
}


В качестве значения переменной addr используем адрес Windows машины, номер порта 6565.
Отправляемое сообщение «Test message TCP/IP.», ответ «The message is received.»

Здесь можно увидеть, что функции PPP непосредственно не используются для отправки и приема сообщений. Вся работа происходит на более высоком уровне, а наши функции вызываются автоматически.

Компилируем и прошиваем.

Результат соединения с pppd видим на Linux машине:

ilogh6wuak7zym8unedcnddcjr4.png

Полученные запросы и отправленные ответы можно увидеть в программе Packet Sender на Windows-машине:

30yh4m_l52nvlkdixrv9zmivehc.png

Ну, вот, собственно и все, отправленный нами пакет из платы discovery отправился в COM порт, попал на pppd сервер, был отправлен на порт 6565 Windows машины, там он был успешно получен, в ответ на него был отправлен другой пакет, который прошел этот путь в обратном направлении и был успешно принят на плате. С таким же успехом вы сможете отправлять сообщения на любую машину в сети Интернет.

→ Полный код проекта можно скачать здесь

© Habrahabr.ru