> 技术文档 > 利用CubeMx实现485与以太网消息互传-HAL库_freertos rs485

利用CubeMx实现485与以太网消息互传-HAL库_freertos rs485

本文将基于STM32F4+FreeRTOS+LWip实现485和网络双向通信,485接收的数据通过TCP发出来,板卡是TCP Server,网络收到的消息发送到485上。

在实现的过程中会涉及到一些其他知识,会贴出本人在学习时找到的一些资料,感兴趣的可以自己去学习。

目录

一、理论分析

二、环境配置

三、STM32CubeMx配置

2.1 选择芯片型号

2.2 移植FreeRTOS

2.3 移植LWip

2.3.1 以太网配置

2.3.2 LWip配置

2.4 485配置

2.5 时钟配置

2.6 线程创建

2.7 队列创建

2.8 信号量创建

2.9 配置工程属性

 2.6 MDK配置

四、代码实现

3.1实现TCP通信

3.1.1实现服务器初始化

3.1.2实现TCP数据接收

3.1.3实现TCP数据发送

3.2 实现485通信

3.3 结果展示

五、相关文章


一、理论分析

分为两条线路:

1.串口收到的消息通过TCP发出来,利用串口助手发送数据,接收的数据在网络调试助手显示

2.TCP收到的消息通过串口发出来,利用网络调试助手发送数据,接收的数据在串口助手显示

这个地方需要注意的是,串口与TCP的传输协议不同,不能简单的将串口收到的数据放在一个内存,然后用TCP发出去。具体参考:串口转以太网技术解析-CSDN博客

如图所示,我们可以规定一个数据包长度,将数据打包发给TCP。因此我们需要设计一个队列来存放串口收到的数据。

线路2相比与线路1简单的多,我们设计两个线程,TCP收到数据时将数据放入队列,没有数据发来时,线程阻塞等待数据;另外一个线程读取队列数据,队列没有数据时,线程堵塞等待数据。

对于线路1而言想到两种实现方式:

1.串口打开接收中断,每收到一个字节就进入中断,将收到的数据放入队列并且计数值+1,当达到我们规定的数据包长度时,释放信号量;TCP发送函数接收到信号量,在队列中将规定长度的数据读出来并发送。

2.利用DMA与空闲中断,一次性将数据发送过去DMA将数据存入缓存区,发送完毕后进入空闲中断,将DMA缓存区的数据写入队列并释放信号量;TCP发送函数接收到信号量,在队列中将规定长度的数据读出来并发送。

我采用的是方法2,感兴趣的可以自己实现一下方法1。

综上所述,需要五个线程,串口的收发线程、TCP的收发线程以及服务器初始化线程;两个队列,一个队列存放串口收到的数据,一个存放TCP收到的数据;以及一个信号量,串口需要等待信号量将数据打包放入队列;最后,需要开启串口的DMA收发功能和中断功能。

二、环境配置

本次使用到的软件有:STM32CubeMx、Keil 5、串口调试助手、网络socket调试助手,使用的芯片为STM32F407,具体的环境配置过程不再赘述。

三、STM32CubeMx配置

2.1 选择芯片型号

点击File/New Project创建一个新项目如图所示。

本次使用的芯片为STM32F407ZG,如图所示双击红框位置进入配置页面 

2.2 移植FreeRTOS

按图顺序依次点击Middleware->FREERTOS->CMSIS_V2,CMSIS_V2是CubeMx对FreeRTOS封装的版本,相比于CMSIS_V1更便捷但是效率也有所下降,在查找资料的过程中,CMSIS_V2版本的教程更多,因此本文基于CMSIS_V2版本来实现,但是如果项目的要求不高,选择CMSIS_V1版本或许效率更高。

初始化部分默认设置即可

2.3 移植LWip

2.3.1 以太网配置

要想使用LWip,首先需要开启芯片的以太网(ETH)功能,如图2-4所示。这个地方需要注意,ETH配置需要三个部分:芯片->网卡->网口,需要检查芯片是否支持ETH通信(STMF407支持),同时需要检查开发板原理图是否具备网卡(后续会对网卡进行配置),最后检查开发板是否具备网络接口。

对于芯片而言,接口协议分为MII和RMII,两者的区别参考如下链接:

MII与RMII接口的区别-CSDN博客

在查找资料时,比较常见的网卡为LAN8720A,可以参考如下链接:

以太网PHY层芯片LAN8720A简介_lan8720a中文资料-CSDN博客

本文采用的STM32F407芯片的RMII接口,参考芯片数据手册。因此上述开启ETH时,配置的是RMII接口。

引脚部分根据参考手册与原理图配置即可

PHY Address要根据芯片的PHYAD引脚的实际连接来配置,如果PHYAD引脚是悬空的,引脚内部带一个弱下拉,这儿就选择0。我的板子是上拉电阻,因此设置为1。

如下图所示对网卡进行配置,CubeMx集成了两种网卡的配置,如果是LAN8742A与DP83848可以直接使用默认设置即可。对于LAN8720A大部分的配置与LAN8742A相同,也可直接使用默认配置。如果是上述网卡可以直接进入下一步,不用看后续的自定义配置。

本文开发板采用的网卡为RTL8201FI,因此选择user PHY进行自定义配置(其实该网卡也适配默认配置,只是介绍一下自定义配置过程)。

下载RTL8201FI的芯片资料,找到Contronl Register与Status Registrer,如图所示

对照上述资料去配置下述参数,例如:PHY Reset的地址为0:15,用16进制表示即0x8000,实际上一路对照下来RTL8201FI也是默认配置即可。

2.3.2 LWip配置

按照下图步骤使能LWip

为了方便关闭DHCP,采用静态IP进行配置,此处配置的是板卡的IP地址,后面要将自己PC的地址也配置为静态,才能与板卡进行通信。由于此处采用的是TCP通信,不使用UDP功能,于是关闭。

打开状态回调函数,LWip配置完毕

最后配置一下自己电脑的静态IP,在设置中点击以太网属性

按下图步骤进行设置,子网此处不能设置为255.255.255.0,而应该输入长度24,如果是在高级网络适配器中配置则输入255.255.255.0。

2.4 485配置

打开串口1,选择异步通信

RS485是半双工通信,其通过软件控制硬件来实现消息的收发控制,因此多了一个使能IO口,在发送数据前需要拉高该IO口电平,发送完毕需要拉低电平否则会收不到消息。

具体参考:学习STM32 RS485 原理与应用_stm32与485-CSDN博客

查看原理图,串口1的RE引脚为PA12,因此将PA12设置为OUTput,在GPIO设置名字为RE

打开串口的中断功能和DMA收发功能

2.5 时钟配置

 为了防止出现,烧录以后仿真器无法连接的情况,在 System Core目录下将 SYS 里面的 Debug 设置成Serial Wire, 这样问题得到解决。

裸机默认的systick在移植了操作系统之后,会与操作系统的嘀嗒冲突,因此需要重新配置一个中断嘀嗒,此处选择TIM4。

时钟源选择外部高速时钟

按照下述步骤配置时钟树,选择HSE外部高速时钟源,输入最大时钟频率168,按回车系统会自动配置时钟树。这个地方需要注意的是对于网卡而言,在配置时不能低于50MHz时钟频率。

2.6 线程创建

总共需要五个线程,分别是485收发线程、TCP收发线程与TCP初始化线程。如图所示分别添加五个线程,优先级默认即可,全部选的动态创建,线程大小需要注意的是TCPServerInit初始化线程要稍大一些,因为计划在该线程中创建TCP收线程,也就是先初始化TCP,并且监听客户端,如果监听成功,就创建TCP收线程。

2.7 队列创建

如图添加两个队列,队列大小表示该队列能够存放多少条消息,数据类型代表每次存储的是什么类型的数据。

需要注意的是,这个数据类型很有讲究,我们将缓存区内的数据放入队列的时候是有两种方式的。方式1为存放数组内部实际存放的数据,那么队列的数据类型就应该根据数组内部存放的数据而定。方式2为传递这个数组的地址,读队列时读这个数组的地址,然后将这个数组copy到另外一个缓存区,这样的方式是更好的。

对于方式2而言我们要注意,如果我们用下面函数使能DMA  

HAL_UART_Receive_DMA(&huart1, USART1_Buff.Uart1_Rxbuff, USART1_BUFF_SIZE);   

那么串口收到的数据会存放在USART1_Buff.Uart1_Rxbuff这个数组中。当我们区存放这个数组的地址到队列时,不能够直接对这个数组取地址,即 osMessageQueuePut(Uart1SendQueueHandle,&USART1_Buff.Uart1_Rxbuff,0,0); 
因为&USART1_Buff.Uart1_Rxbuff最后得到的是整个数组的地址,即数据类型为uint8_t (*)[],我们想要得到的是数组首元素的地址,因此我们应该创建一个指针区存放USART1_Buff.Uart1_Rxbuff首元素的地址,再把这个指针的地址放到队列中。

如果听不懂后续看代码的时候,请留意一下

2.8 信号量创建

按图创建信号量,这里分为二值信号量和计数信号量,二值信号量适合线程间的同步,因此我们创建二值信号量。

如果使用接收中断的方式来实现,比较适合计数信号量,每次接收中断放一个字节到队列,计数值信号量+1,到达指定数量释放。

创建的信号量默认为1,需要在代码中将其修改为初始值为0。

2.9 配置工程属性

 选择 Project Manager 选项,配置工程的名称,路径,使用的 IDE 工具,堆栈大小,如图所示。注意不要使用中文路径和工程名称。

 

        在Code Generator目录下勾选下图所示的选项,第一个选项是只创建需要的库函数,勾选后可以加快函数执行效率;第二个选项是分别创建.c/.h文件,使得函数可读性更高。
 

点击右上角 GENERATE CODE, 在设定的路径成功生成代码,选择Open Project打开工程。

 2.6 MDK配置

下述为基础配置,给新手看的,老手可跳过。

点击魔术棒按钮进入Device设置界面,选择对应的芯片包,如图所示。

进入Target界面,勾选使用微库,点击OK确定选项并编译程序,观察是否能够成功运行,如图。

 
进入Debug设置界面,选择对应的下载调试工具,我使用的JLink,点击Settings进入设置界面。

 

我的设备,设置成SW模式,才能够识别出来

点击进入Flash Download设置界面,勾选Reset and Run,不勾选的话程序只能在第一次运行成功。确保芯片Flash识别出来了,如果识别失败,点击Add添加芯片对应的内存,点击确定保存设置,如图步骤3,4所示。

 

四、代码实现

为了代码的可读性,我单独创建了TCP和RS485文件夹存放代码,需要对自动生成的freertos.c进行一点点变动,把线程的实现函数换个位置。freertos.c中只实现队列与信号量的创建。

#include \"FreeRTOS.h\"#include \"task.h\"#include \"main.h\"#include \"cmsis_os.h\"#include \"tcpserver.h\"#include \"rs485.h\"osThreadId_t TcpServerInitHandle;const osThreadAttr_t TcpServerInit_attributes = { .name = \"TcpServerInit\", .priority = (osPriority_t) osPriorityNormal, .stack_size = 1024 * 4};/* Definitions for Uart1Send */osThreadId_t Uart1SendHandle;const osThreadAttr_t Uart1Send_attributes = { .name = \"Uart1Send\", .priority = (osPriority_t) osPriorityLow, .stack_size = 256 * 4};/* Definitions for Uart1Recv */osThreadId_t Uart1RecvHandle;const osThreadAttr_t Uart1Recv_attributes = { .name = \"Uart1Recv\", .priority = (osPriority_t) osPriorityLow, .stack_size = 256 * 4};/* Definitions for Uart1Cenlit_Send */osThreadId_t Uart1Tcp_SendHandle;const osThreadAttr_t Uart1Tcp_Sendattributes = { .name = \"Uart1Tcp_Send\", .priority = (osPriority_t) osPriorityLow, .stack_size = 256 * 4};/* Definitions for Uart1SendQueue */osMessageQueueId_t Uart1SendQueueHandle;const osMessageQueueAttr_t Uart1SendQueue_attributes = { .name = \"Uart1SendQueue\"};/* Definitions for Uart1CenlitQueue */osMessageQueueId_t Uart1CenlitQueueHandle;const osMessageQueueAttr_t Uart1CenlitQueue_attributes = { .name = \"Uart1CenlitQueue\"};/* Definitions for Uart1RecvSem */osSemaphoreId_t Uart1RecvSemHandle;const osSemaphoreAttr_t Uart1RecvSem_attributes = { .name = \"Uart1RecvSem\"};extern void TCPServerInitTask(void *argument);extern void Uart1SendTask(void *argument);extern void Uart1RecvTask(void *argument);extern void Uart1Tcp_SendTask(void *argument);extern void MX_LWIP_Init(void);void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */void MX_FREERTOS_Init(void) { Uart1RecvSemHandle = osSemaphoreNew(1, 0, &Uart1RecvSem_attributes); Uart1SendQueueHandle = osMessageQueueNew (32, sizeof(uint8_t *), &Uart1SendQueue_attributes); Uart1CenlitQueueHandle = osMessageQueueNew (32, sizeof(uint8_t *), &Uart1CenlitQueue_attributes); TcpServerInitHandle = osThreadNew(TcpServerInitTask, NULL, &TcpServerInit_attributes); Uart1SendHandle = osThreadNew(Uart1SendTask, NULL, &Uart1Send_attributes); Uart1RecvHandle = osThreadNew(Uart1RecvTask, NULL, &Uart1Recv_attributes); Uart1Tcp_SendHandle = osThreadNew(Uart1Tcp_SendTask, NULL, &Uart1Tcp_Sendattributes);}

3.1实现TCP通信

3.1.1实现服务器初始化

首先在TcpServerInitTask中实现客户端的监听,步骤为创建套接字,随后绑定、监听客户端,当有客户端监听成功时创建Uart1Tcp_RecvTask线程。

此处为什么将Uart1Tcp_RecvTask线程放在初始化成功之后呢?

从顺序上来讲只有服务器初始化成功之后才能开始读取数据

int sock = -1,connected;void TcpServerInitTask(void *argument){ MX_LWIP_Init();socklen_t sin_size; struct sockaddr_in server_addr,client_addr; sock = socket(AF_INET, SOCK_STREAM, 0); //创建一个socket套接字sock server_addr.sin_family = AF_INET; //选择IPV4协议族 server_addr.sin_addr.s_addr = INADDR_ANY;//允许任何地址链接 server_addr.sin_port = htons(TCP_ECHO_PORT); //设置端口号 memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)//使用bind()绑定之前配置的信息 { printf(\"Unable to bind\\n\"); goto __exit; } if (listen(sock, 5) == -1)//sock最多监听5个链接 { printf(\"Listen error\\n\"); goto __exit; }sin_size = sizeof(struct sockaddr_in);while(1){connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);//阻塞等待客户端链接,若没有链接任务会在此进入一个阻塞态。printf(\"new client connected from (%s, %d)\\n\",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));//打印客户端的地址端口信息{int flag = 1;setsockopt(connected,IPPROTO_TCP, /* set option at TCP level */TCP_NODELAY, /* name of option */(void *) &flag, /* the cast is historical cruft */sizeof(int)); /* length of option value */}Uart1Tcp_RecvHandle = osThreadNew(Uart1Tcp_RecvTask, NULL, &Uart1Tcp_Recvattributes);}__exit: if (sock >= 0) closesocket(sock);//服务器socket创建成功,则关闭服务器socket}

3.1.2实现TCP数据接收

在Uart1Tcp_RecvTask线程中监听客户端发来的消息,当有TCP数据传递过来时,放入USART1_Buff.Uart1Cenlit_Rxbuff队列中。

这个地方需要注意,在Uart1Tcp_RecvTask中我创建了一个uint8_t *uart1cenlit_rx指针来存放USART1_Buff.Uart1Cenlit_Rxbuff缓存区的地址
在放入队列时

osMessageQueuePut(Uart1CenlitQueueHandle, &uart1cenlit_rx, 0, portMAX_DELAY)即放进队列的是一个指针,这个指针内部存放的是数组的地址。

在读队列时,仍然用一个指针uint8_t *uart1_tx去读放队列里面存放的指针

osMessageQueueGet(Uart1CenlitQueueHandle,&uart1_tx,0,portMAX_DELAY);

portMAX_DELAY代表超时时间,一直等待直到从队列中获取到数据,如果没有数据就阻塞。

这个地方为什么这么操作?

在存放队列时有两种方式,第一直接存放这个数组里面的数据,第二存放这个数组的地址,显然存放地址更为灵活且效率更高。

但是如果我们在存放地址的时候直接对数组取地址,即:osMessageQueuePut(Uart1CenlitQueueHandle, &USART1_Buff.Uart1Cenlit_Rxbuff, 0, portMAX_DELAY)

此时&USART1_Buff.Uart1Cenlit_Rxbuff代表的是整个数组的地址,他的数据类型为(uint8_t *[ ])而不是uint8_t * 

那如果把&USART1_Buff.Uart1Cenlit_Rxbuff换成USART1_Buff.Uart1Cenlit_Rxbuff或者&USART1_Buff.Uart1Cenlit_Rxbuff[0]是否可行呢?笔者没有测试,感兴趣的可以试试。

void Uart1Tcp_RecvTask(void *argument){uint8_t *uart1cenlit_rx;while(1){USART1_Buff.Uart1Cenlit_Rxlen = recv(connected, USART1_Buff.Uart1Cenlit_Rxbuff, USART1_BUFF_SIZE, 0);//客户端连接上后会在此阻塞,等待接收数据if (USART1_Buff.Uart1Cenlit_Rxlen = 0)closesocket(connected);//若之前有客户的连接,这里会关闭客户端连接connected = -1;}

3.1.3实现TCP数据发送

串口收到数据时会将数据放入队列,因此TCP发送数据时直接去队列中读取即可。上面已经强调了,队列中存放的是数组的地址,因此需要创建一个指针去接收队列中的地址。读完数据后清空缓存区并重新开启DMA接收(为什么要开启,会在串口部分讲解)。

void Uart1Tcp_SendTask(void *argument){uint8_t *uart1tcp_tx;while(1){osStatus_t os_err = osMessageQueueGet(Uart1SendQueueHandle,&uart1tcp_tx,0,portMAX_DELAY);if(os_err == osOK ){write(connected,uart1tcp_tx,USART1_Buff.Uart1_Rxlen);memset(USART1_Buff.Uart1_Rxbuff,0x00,USART1_BUFF_SIZE);//清空缓存,重新接收HAL_UART_Receive_DMA(&huart1,USART1_Buff.Uart1_Rxbuff,USART1_BUFF_SIZE); //重新开始一次DMA传输。printf(\"Tcp Send success\\n\");}}}

完整的tcpserver.c的代码如下:

#include \"tcpserver.h\"#include \"FreeRTOS.h\"#include \"task.h\"#include #include #include #include \"cmsis_os.h\"#include \"queue.h\"#include \"rs485.h\"#include \"semphr.h\"/* Definitions for Uart1Tcp_Recv */osThreadId_t Uart1Tcp_RecvHandle;const osThreadAttr_t Uart1Tcp_Recvattributes = { .name = \"Uart1Tcp_Recv\", .priority = (osPriority_t) osPriorityLow, .stack_size = 256 * 4};extern void MX_LWIP_Init(void);extern osMessageQueueId_t Uart1CenlitQueueHandle;extern osMessageQueueId_t Uart1SendQueueHandle;extern _USART_Buff USART1_Buff;int sock = -1,connected;void TcpServerInitTask(void *argument){ MX_LWIP_Init();socklen_t sin_size; struct sockaddr_in server_addr,client_addr; sock = socket(AF_INET, SOCK_STREAM, 0); //创建一个socket套接字sock server_addr.sin_family = AF_INET; //选择IPV4协议族 server_addr.sin_addr.s_addr = INADDR_ANY;//允许任何地址链接 server_addr.sin_port = htons(TCP_ECHO_PORT); //设置端口号 memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)//使用bind()绑定之前配置的信息 { printf(\"Unable to bind\\n\"); goto __exit; } if (listen(sock, 5) == -1)//sock最多监听5个链接 { printf(\"Listen error\\n\"); goto __exit; }sin_size = sizeof(struct sockaddr_in);while(1){connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);//阻塞等待客户端链接,若没有链接任务会在此进入一个阻塞态。printf(\"new client connected from (%s, %d)\\n\",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));//打印客户端的地址端口信息{int flag = 1;setsockopt(connected,IPPROTO_TCP, /* set option at TCP level */TCP_NODELAY, /* name of option */(void *) &flag, /* the cast is historical cruft */sizeof(int)); /* length of option value */}Uart1Tcp_RecvHandle = osThreadNew(Uart1Tcp_RecvTask, NULL, &Uart1Tcp_Recvattributes);}__exit: if (sock >= 0) closesocket(sock);//服务器socket创建成功,则关闭服务器socket}void Uart1Tcp_RecvTask(void *argument){uint8_t *uart1cenlit_rx;while(1){USART1_Buff.Uart1Cenlit_Rxlen = recv(connected, USART1_Buff.Uart1Cenlit_Rxbuff, USART1_BUFF_SIZE, 0);//客户端连接上后会在此阻塞,等待接收数据if (USART1_Buff.Uart1Cenlit_Rxlen = 0)closesocket(connected);//若之前有客户的连接,这里会关闭客户端连接connected = -1;}void Uart1Tcp_SendTask(void *argument){uint8_t *uart1tcp_tx;while(1){osStatus_t os_err = osMessageQueueGet(Uart1SendQueueHandle,&uart1tcp_tx,0,portMAX_DELAY);if(os_err == osOK ){write(connected,uart1tcp_tx,USART1_Buff.Uart1_Rxlen);memset(USART1_Buff.Uart1_Rxbuff,0x00,USART1_BUFF_SIZE);//清空缓存,重新接收HAL_UART_Receive_DMA(&huart1,USART1_Buff.Uart1_Rxbuff,USART1_BUFF_SIZE); //重新开始一次DMA传输。printf(\"Tcp Send success\\n\");}}}

tcpserver.h的代码如下

#ifndef __TCP_SERVER_H#define __TCP_SERVER_H#define TCP_ECHO_PORT 2048void TcpServerInitTask(void *argument);void Uart1Tcp_RecvTask(void *argument);void Uart1Tcp_SendTask(void *argument);#endif

3.2 实现485通信

rs485.c如下:

对于串口发送线程,在调用DMA发送数据时,需要注意DMA并不是实时发送数据,随着数据的增多,发送的时间会变长。

#include \"rs485.h\"#include \"tcpserver.h\"#include #include \"string.h\"#include \"FreeRTOS.h\"#include \"queue.h\"#include \"cmsis_os.h\"#include #include \"semphr.h\"_USART_Buff USART1_Buff;extern osMessageQueueId_t Uart1CenlitQueueHandle;extern osMessageQueueId_t Uart1SendQueueHandle;extern osSemaphoreId_t Uart1RecvSemHandle;extern DMA_HandleTypeDef hdma_usart1_rx;void Uart1SendTask(void *argument){uint8_t *uart1_tx;while(1) {osStatus_t os_err = osMessageQueueGet(Uart1CenlitQueueHandle,&uart1_tx,0,portMAX_DELAY);if(os_err == osOK && (&huart1)->Instance == USART1 ){UART1_TX_ENABLE();HAL_UART_Transmit_DMA(&huart1,uart1_tx,USART1_Buff.Uart1Cenlit_Rxlen);}}}void Uart1RecvTask(void *argument){uint8_t *uart1_rx; for(;;) {xQueueSemaphoreTake(Uart1RecvSemHandle,portMAX_DELAY);uart1_rx = USART1_Buff.Uart1_Rxbuff;osStatus_t os_err = osMessageQueuePut(Uart1SendQueueHandle,&uart1_rx,0,portMAX_DELAY); if(os_err == osOK ) {printf(\"Put Uart1Cenlit_Queue\\n\");} }}void UsartReceive_IDLE(UART_HandleTypeDef *huart) //串口接收空闲中断{if( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)!= RESET){if(huart->Instance == USART1) {__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位HAL_UART_AbortReceive(&huart1); //停止DMA接收,防止数据出错printf(\"dma close\\n\");USART1_Buff.Uart1_Rxlen = USART1_BUFF_SIZE-hdma_usart1_rx.Instance->NDTR;// 获取DMA中传输的数据个数printf(\"num = %d\\n\",USART1_Buff.Uart1_Rxlen);BaseType_t pxHigherPriorityTaskWoken = pdTRUE;xQueueGiveFromISR(Uart1RecvSemHandle,&pxHigherPriorityTaskWoken);}}}void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)//HAL的发送完成回调函数{if (huart->Instance == USART1) { // 确认是UART1的回调USART1_Buff.Uart1Cenlit_Rxlen = 0;memset(USART1_Buff.Uart1Cenlit_Rxbuff,0x00,USART1_BUFF_SIZE);//清空缓存,重新接收printf(\"Uart1 Send Success\\n\");UART1_RX_ENABLE(); }}

rs485.h如下:

#ifndef _RS485_H_#define _RS485_H_#include \"usart.h\"/* User Config */#define UART1_GPIO_PORT GPIOA#define UART1_RE_GPIO_PIN GPIO_PIN_12/* RS485 TX/RX Control */#define UART1_TX_ENABLE() HAL_GPIO_WritePin(UART1_GPIO_PORT, UART1_RE_GPIO_PIN, GPIO_PIN_SET);\\HAL_Delay(10);#define UART1_RX_ENABLE() HAL_GPIO_WritePin(UART1_GPIO_PORT, UART1_RE_GPIO_PIN, GPIO_PIN_RESET);\\HAL_Delay(10);#define USART1_BUFF_SIZE128typedef struct {uint8_t Uart1_Rxbuff[USART1_BUFF_SIZE];uint8_t Uart1Cenlit_Rxbuff[USART1_BUFF_SIZE];uint16_t Uart1_Rxlen;uint16_t Uart1Cenlit_Rxlen;} _USART_Buff;void Uart1SendTask(void *argument);void Uart1RecvTask(void *argument);#endif 

在uart.c的MX_USART1_UART_Init()函数中使能中断与DMA。

#include \"rs485.h\"extern _USART_Buff USART1_Buff;void MX_USART1_UART_Init(void){ huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); }__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //开启IDLE中断,以帧方式接收不定长数据HAL_UART_Receive_DMA(&huart1, USART1_Buff.Uart1_Rxbuff, USART1_BUFF_SIZE);//开始DMA接收}

修改stm32f4xx_it.c中的

#include \"usart.h\"void USART1_IRQHandler(void){ /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE BEGIN USART1_IRQn 0 */ UsartReceive_IDLE(&huart1); /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */}

3.3 结果展示

如图,PC利用485发送数据被板卡TCP接收;PC利用TCP发送数据被板卡485接收。

五、相关文章

利用CubeMx实现CAN与以太网消息互传-HAL库_stm32f407 stm32cubemx can-CSDN博客

利用CubeMx实现串口数据回显-HAL库_gd32使用hal-CSDN博客