有一次,在我之前有一项任务是在STM32上提供仅具有COM端口的Internet访问。 为了解决这个问题,我需要PPP,或者确切地说是PPPoS(串行点对点协议-实现PPP的方法之一,当通过COM端口连接时使用它)。
在解决摆在我面前的任务的过程中,我遇到了一些困难,我认为其中之一是对互联网上与PPPoS有关的问题的报道不足。 在我的适度知识允许的范围内,我将通过这篇文章尝试缩小指定的差距。
本文介绍如何从头开始为System Workbench for STM32创建项目。 显示使用UART的示例。 有用于实现PPP的代码示例。 当然,还有向附近的计算机发送消息的示例。
引言
PPP(点对点协议)是OSI网络模型的两点数据链接协议。 它通常用于在两个网络节点之间建立直接通信,并且可以提供连接身份验证,加密和数据压缩。 用于多种类型的物理网络:空调制解调器电缆,电话线,蜂窝电话等。
PPP协议的子种类通常很多,例如以太网上的点对点协议(PPPoE),用于通过以太网(有时是通过DSL)进行连接; ATM上的点对点协议(PPPoA),用于通过ATM适配层5(AAL5)进行连接,这是DSL的主要PPPoE替代方案。
PPP是一系列协议:链路控制协议(LCP),网络控制协议(NCP),身份验证协议(PAP,CHAP),多通道PPP(MLPPP)。
来自维基百科 。
准备工作
为了解决这个问题,我们需要:
铁:
- 调试板stm32f4_discovery:
- USB至miniUSB适配器,用于将开发板连接至计算机。
- 两个USBtoUART FT232适配器:
- 两条USB延长线也很有用,不一定,但很方便。
软:
- 虚拟机VirtualBox。 您可以在此处下载。 我们还下载并安装VirtualBox 扩展包 。
- 两个安装盘,分别带有Windows和Linux操作系统。 我们在这里使用Windows, 在这里使用 Linux。
安装操作系统后,您将需要为来宾操作系统安装附加组件。 对于我们拥有足够多32x系统的任务,您不能无视虚拟化。 - 对于Windows,我们需要一个可以接受请求并通过TCP / IP响应的程序,以及一个用于使用COM端口的终端程序。 在此处下载PacketSender(单击“不,谢谢,让我下载。”),终端在此处 。 此外,我们需要STM32CubeMX进行项目的初始设置。 从st.com下载(注册后,链接将通过电子邮件提供)。
- 我们将STM32的System Workbench放在主操作系统上。 从这里下载(需要注册)。
阶段1.创建项目
首先,打开STM32CubeMX并在那里为我们的stm32f4-discovery板创建一个新项目。 打开RCC,以太网(ETH),SYS,USART2,USART3,然后打开FREERTOS和LWIP。


为了进行诊断,我们需要板上有LED。 为此,将PD12-PD15的支路配置为GPIO_Output。

在“时钟配置”选项卡上,设置频率,如下图所示。

接下来,在“配置”选项卡上,配置USART端口。 我们将以DMA模式与他们合作。 我们有两个USART端口,一个用于通过PPP传输和接收数据,另一个用于记录。 为了使它们工作,我们需要在两个端口的RX和TX上配置DMA。 对于所有DMA调整分支,将“中”设置为优先级。 对于USART2支脚RX,将模式设置为“圆形”。 其余设置默认为保留。

您还需要在“ NVIC设置”选项卡上为两个端口启用全局中断。
这样就完成了STM32CubeMX中项目的初始设置。 我们保存项目文件,并为STM32的System Workbench进行代码生成。

实作
现在,让我们验证下载的代码是否可以编译和运行。 为此,在“ StartDefaultTask”函数的main.c文件中,我们用LED的打开和关闭代码替换了for(;;)循环的主体。
应该是这样的:
void StartDefaultTask(void const * argument) { MX_LWIP_Init(); 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); } }
我们编译固件并查看。 板上的所有四个LED均应闪烁。
第2阶段。与USART合作
我们的下一个任务是验证USART的正确操作。
我们需要做的第一件事是将FT232连接到发现设备。 为此,请查看USART接口离婚的那条腿。 我分别为USART2_RX和USART2_TX使用PD6和PD5。

以及USART3_RX和USART3_TX的PD9和PD8。

另外,我们需要一个GND脚。
我们在板上找到了这些引脚并将它们连接到FT232引脚,而板上的GND引脚可以是任意引脚,板上的RX引脚必须连接到FT232的TX引脚,并且板上的TX引脚必须连接到FT232的RX引脚。 其余结论未使用。
仍然可以将FT232连接到计算机的USB端口,以及通过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.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.c文件 #include "logger.h" #include "stm32f4xx_hal.h" #include <stdarg.h> 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语言中不可用。
typedef unsigned char bool; #define true 1 #define false 0
现在该检查结果代码的功能了。 为此,请在
main.c文件中,更改已知任务“ StartDefaultTask”的代码
#include "usart.h" #include "logger.h" #define MAX_MESSAGE_LENGTH 100 void StartDefaultTask(void const * argument) { MX_LWIP_Init(); usart_Open(); 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); } } }
另外,我们需要给任务堆栈更多的内存。 为此,在调用osThreadDef()函数main.c文件时,您需要通过128 * 10来修复128 * 10:
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, <b>128*10</b>)
我们编译并刷新。 LED闪烁的方式与上一个任务相同。
要查看我们的工作结果,您需要在我们的虚拟机中运行Terminal程序。 程序的一个实例用于记录端口,第二个实例用于主程序。 在设备管理器中查看为您的FT232分配了哪些端口号。 如果分配的数字超过10,请重新分配。
当您启动该程序的第二个实例时,可能会发生错误,请关闭该错误窗口并继续使用该程序。
对于这两个端口,我们以115200波特建立连接,数据位-8,奇偶校验-无,停止位-1,握手-无。
如果您正确执行了所有操作,则将在usart2的终端窗口中发送“发送消息”消息。 同一条消息将在记录器的终端窗口中重复,仅带有前缀“ SEND-”
如果在usart2的终端窗口中,在“发送”字段中键入一些文本,然后单击该字段右侧的相应按钮,则在记录器窗口中,您将看到带有前缀“ RECV-”的相同消息。
在下图中:左边是记录器,右边是usart2。

阶段3。PPP入门
作为此任务的一部分,我们将建立PPP连接。 首先,启用PPP使用,将ppp_opts.h文件中的PPP_SUPPORT定义的值更改为1。然后,在lwipopts.h文件中重新定义必要的定义,
#define MEMP_NUM_SYS_TIMEOUT 8 #define CHECKSUM_GEN_IP 1 #define CHECKSUM_GEN_TCP 1
同时,旧的定义需要被注释掉。
现在,我们修改lwip.c文件,将以下代码插入“ / * 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: { 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: logger("ppp_link_status_cb: PPPERR_PARAM\n"); break; case PPPERR_OPEN: logger("ppp_link_status_cb: PPPERR_OPEN\n"); break; case PPPERR_DEVICE: logger("ppp_link_status_cb: PPPERR_DEVICE\n"); break; case PPPERR_ALLOC: logger("ppp_link_status_cb: PPPERR_ALLOC\n"); break; case PPPERR_USER: logger("ppp_link_status_cb: PPPERR_USER\n"); break; case PPPERR_CONNECT: logger("ppp_link_status_cb: PPPERR_CONNECT\n"); break; case PPPERR_AUTHFAIL: logger("ppp_link_status_cb: PPPERR_AUTHFAIL\n"); break; case PPPERR_PROTOCOL: logger("ppp_link_status_cb: PPPERR_PROTOCOL\n"); break; case PPPERR_PEERDEAD: logger("ppp_link_status_cb: PPPERR_PEERDEAD\n"); break; case PPPERR_IDLETIMEOUT: logger("ppp_link_status_cb: PPPERR_IDLETIMEOUT\n"); break; case PPPERR_CONNECTTIME: logger("ppp_link_status_cb: PPPERR_CONNECTTIME\n"); break; case PPPERR_LOOPBACK: logger("ppp_link_status_cb: PPPERR_LOOPBACK\n"); break; default: logger("ppp_link_status_cb: unknown errCode %d\n", err_code); break; } }
然后,在函数MX_LWIP_Init()中的“ / * USER CODE BEGIN 3 * /”块中,将调用添加到pppConnect()函数。
此外,您需要增加堆大小,为此,在FreeRTOSConfig.h文件中,需要注释掉configTOTAL_HEAP_SIZE定义,然后在文件末尾的/ * USER CODE BEGIN定义* /块中用新值声明它。
#define configTOTAL_HEAP_SIZE ((size_t)1024*30)
另外,在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) {
完成后,您可以编译并刷新。
此时,您需要启动PPP服务器。 为此,您必须首先使用Linux部署虚拟机。 我使用Ubuntu 16.04 x32。 安装操作系统后,需要配置使用COM端口。
在这一部分中,我们不需要带有Windows的虚拟机,我们可以安全地将其关闭。 我们在Linux中都连接了FT232。
在Linux中,在开始使用COM端口之前,必须允许用户使用它。 为此,请执行以下命令:
sudo addgroup USERNAME dialout
其中USERNAME是当前用户的名称。
要查看COM系统中的可用端口,您需要运行以下命令:
dmesg | grep tty

我们看到系统中有两个ttyUSB端口。 我们不能立即说出哪个是记录器,哪个是usart2。 您只需要依次检查它们即可。
首先,执行命令以从一个端口读取:
stty -F /dev/ttyUSB0 115200 cat /dev/ttyUSB0
然后从另一个:
stty -F /dev/ttyUSB1 115200 cat /dev/ttyUSB1
我们看到这样的图片的地方就是记录器。

您可以离开此窗口,它不会打扰我们。
接下来,您需要允许从我们的主板发送的数据包离开其子网的限制。 为此,请配置iptables。 我们执行以下操作:
1.打开一个新的控制台窗口
2.您需要找出您的IP和网络接口名称(运行
ifconfig命令)

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文件./pppd.conf命令完成的必须先启动PPPD服务器,然后再开始发现,因此在启动PPPD之后,您需要单击板上的“重置”按钮。
如果一切操作正确,您将看到以下图片:

在左侧运行pppd,在右侧运行logger。
阶段4。我们寄出一个袋子
在这个阶段,我们需要两个虚拟机。 Linux for pppd和Windows接收该软件包。 为了简化任务,两台计算机必须位于同一子网中,理想的解决方案是在VirtualBox网络设置中为两台计算机指定一个网桥连接,并在Windows中禁用防火墙。
我们启动虚拟机,并使用pppd配置发现板的ppp连接。 在Windows上,我们找到了计算机的IP地址(ipconfig命令),我得到了192.168.10.97。
启动Packet Sender并按以下方式进行配置:

现在再次修改
main.c文件中的StartDefaultTask任务。
#include "logger.h" #include "sockets.h" typedef uint32_t SOCKET; void StartDefaultTask(void const * argument) {
作为addr变量的值,我们使用Windows计算机的地址,端口号6565。
发送消息“测试消息TCP / IP。”,响应“消息已接收。”
在这里您可以看到PPP功能没有直接用于发送和接收消息。 所有工作都在更高层次上进行,我们的功能会自动调用。
我们编译并刷新。
在Linux机器上可以看到连接到pppd的结果:

在Windows机器上的Packet Sender程序中可以看到已接收的请求和已发送的响应:

好了,就是这样,我们从发现板发送的数据包到达了COM端口,到达了pppd服务器,被发送至计算机的Windows端口6565,在此成功接收,作为响应,另一个数据包通过了方向相反,并已在董事会成功采用。 您可能还可以将消息发送到Internet上的任何计算机。
→完整的项目代码可在
此处下载