本文将基于STM32F4+FreeRTOS+LWip实现485和网络双向通信,485接收的数据通过TCP发出来,板卡是TCP Server,网络收到的消息发送到485上。
在实现的过程中会涉及到一些其他知识,会贴出本人在学习时找到的一些资料,感兴趣的可以自己去学习。
目录
一、理论分析
分为两条线路:
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,两者的区别参考如下链接:
在查找资料时,比较常见的网卡为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) {break;}//若客户端断开连接,客户端发送FIN包,recv()会返回-1,跳出这个while循环回到accept()阻塞
printf("recv %d len data\n",USART1_Buff.Uart1Cenlit_Rxlen);
uart1cenlit_rx = USART1_Buff.Uart1Cenlit_Rxbuff;
osStatus_t os_err = osMessageQueuePut(Uart1CenlitQueueHandle, &uart1cenlit_rx, 0, portMAX_DELAY);
if(os_err == osOK )
{
printf("Put Uart1Cenlit_Queue\n");
}
}
if (connected >= 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 <stdio.h>
#include <string.h>
#include <lwip/sockets.h>
#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) {break;}//若客户端断开连接,客户端发送FIN包,recv()会返回-1,跳出这个while循环回到accept()阻塞
printf("recv %d len data\n",USART1_Buff.Uart1Cenlit_Rxlen);
uart1cenlit_rx = USART1_Buff.Uart1Cenlit_Rxbuff;
osStatus_t os_err = osMessageQueuePut(Uart1CenlitQueueHandle, &uart1cenlit_rx, 0, portMAX_DELAY);
if(os_err == osOK )
{
printf("Put Uart1Cenlit_Queue\n");
}
}
if (connected >= 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 2048
void 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 <stdio.h>
#include "string.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "cmsis_os.h"
#include <lwip/sockets.h>
#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_SIZE 128
typedef 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接收。