准备工作
了解485通信基本概念与原理:RS485通信详解_485通讯de接什么口-CSDN博客
安装编译软件:keil uVision 5.6
软件资料:STM32CubeF4 固件包,正点原子RS485通信例程
参考视频:第26讲 基础篇-新建HAL版本MDK工程1_哔哩哔哩_bilibili
第27讲 基础篇-新建HAL版本MDK工程2_哔哩哔哩_bilibili
第156讲 入门篇- RS485_哔哩哔哩_bilibili
使用 STM32F407 串口实现 485 通信(半双工)
1.新建工程文件夹
1.1新建工程文件夹
首先新建一个工程根目录文件夹(最好不要出现中文路径),后续的工程文件都将在这个文件夹里建立,文件夹重命名为:485TestOne。

为了让工程的文件目录结构更加清晰易懂,在工程根目录文件夹下建立以下几个文件夹,每个文件夹名称及其作用如表 1.1 所示:

新建完成以后,最后得到的工程根目录文件夹如图 1.2 所示。

1.2拷贝工程相关文件
接下来,按根目录文件夹顺序介绍每个文件夹及其需要拷贝的文件。
1.2.1 Drivers 文件夹
该文件夹用于存放与硬件相关的驱动层文件,一般包括如表 1.2 所示的三个文件夹:


BSP 文件夹,用于存放正点原子提供的板级支持包驱动代码,如:LED、蜂鸣器、按键、RS485等。
CMSIS 文件夹,用于存放 CMSIS 底层代码(ARM 和 ST 提供),如:启动文件(.s 文件)、stm32f4xx.h 等各种头文件。该文件夹可以直接从 STM32CubeF4 固件包里面拷贝,但由于固件包里面的 CMISIS 兼容了太多芯片,会导致非常大(170 MB),因此可根据实际情况,对其进行精简。
SYSTEM 文件夹,用于存放正点原子提供的系统级核心驱动代码,如:sys.c、delay.c 和usart.c 等,方便大家快速搭建自己的工程。
BSP、CMSIS、SYSTEM文件夹可以之间从正点原子提供的RS485通信例程中拷贝。
STM32F4xx_HAL_Driver 文件夹,用于存放 ST 提供的 F4xx HAL 库驱动代码。该文件夹可以直接从 STM32CubeF4 固件包里面拷贝。直接拷贝“STM32CubeF4 固件包→Drivers”路径下的 “STM32F4xx_HAL_Driver”文件夹到工程的 Drivers 下,只保留 Inc 和 Src 文件夹即可。
执行完以上操作后,Drivers 文件夹最终结构如图 1.3 所示:

关于工程根目录下的 Drivers 文件操作到这里就完成了。
1.2.2 Middlewares 文件夹
该文件夹用于存放正点原子和其他第三方提供的中间层代码(组件/Lib 等),如:USMART、MALLOC、TEXT、FATFS、USB、LWIP、各种 OS、各种 GUI 等等。
1.2.3 Output 文件夹
该文件夹用于存放编译器编译工程输出的中间文件,比如:.hex、.bin、.o 文件等等。这里不需要操作,后面只需要在 MDK 里面设置该文件夹为编译过程中间文件的存放文件夹就行。
1.2.4 Projects 文件夹
该文件夹用于存放编译器(MDK、IAR 等)工程文件,工程主要用 MDK,为了方便区分,在该文件夹下新建:MDK-ARM 文件夹,用于存放 MDK 的工程文件,如图 1.4 所示:

1.2.5 User 文件夹
User 文件夹用于存放 HAL 库用户配置文件、main.c、中断处理文件,以及分散加载文件。
首先从官方固件包里面直接拷贝官方的模板工程下的 HAL 库用户配置文件和中断处理文件到 User 文件夹里。官方的模板工程路径:STM32Cube_FW_F4_V1.26.0\Projects\STM324xG_EVAL\Templates,打开Template_Project 文件夹,如图 1.5 所示。

在 Inc 和 Src 两个文件夹里面找到:stm32f4xx_it.c、 stm32f4xx_it.h、stm32f4xx_hal_conf.h 这三个文件,并且拷贝到 User 文件夹下。
注意:main.c文件也是放在User文件夹里面的,后面需要在MDK 里面新建.c 文件并保存。
2. 新建一个工程框架
2.1 首先,打开 MDK 软件。然后点击 Project→New uVision Project 如图 2.1 所示:

2.2 然后弹出工程命名和保存的操作窗口,将工程文件保存路径设置在上一节新建的工程文件夹内,具体路径为:485TestOne→Projects→MDK-ARM,
工程名字为:atk_f407,最后点击保存即可。具体操作窗口如图 2.2所示:

2.3 之后,弹出器件选择对话框,如图2.3所示。

因为探索者 STM32F407 开发板所使用的STM32 型号为 STM32F407ZGT6,所以选择:STMicroelectronics → STM32F4 Series →STM32F407 → STM32F407ZGTx(如果使用的是其他系列的芯片,选择相应的型号就可以了,特别注意:一定要安装对应的器件 pack 才会显示这些内容哦!!如果没得选择,请关闭 MDK,安装后重试。
2.4 点击 OK,MDK 会弹出 Manage Run-Time Environment 对话框,如图2.4所示:

这是 MDK5 新增的一个功能,在这个界面,可以添加自己需要的组件,从而方便构建开发环境,这里不做介绍。直接点击 Cancel,即可,
2.5 得到如图 2.5 所示界面:

2.6 此时,打开 MDK-ARM 文件夹,会看到 MDK 在该文件夹下自动创建了 3 个文件夹 (DebugConfig、Listings 和 Objects),如图所示:

这三个文件夹的作用:
DebugConfig:用于存放调试设置信息文件(.dbgconf),不可删除!
Listings:用于存放编译过程产生的链接列表等文件
Objects:用于存放编译过程产生的调试信息、.hex、预览、.lib 文件等
编译过程产生的链接列表、调试信息、预览、lib 等文件,统称为中间文件。为了统一管理,方便使用,可以把输出在 Listings 和 Objects 文件夹的内容,统一改为输出到 Output 文件夹(通过魔术棒设置),这里先把 MDK 自动生成的这两个文件夹(Listings 和 Objects)删除。
至此,完成了框架的构建,还有好几个步骤要做,比如添加文件、魔术棒设置、编写 main.c 等
3. 添加文件
本节将分 7 个步骤:1,设置工程名和分组名;2,添加启动文件;3,添加 SYSTEM 源码;4,添加 User 源码;5,添加 STM32F4xx_HAL_Driver 源码;6,添加BSP源码;7,添加UMART源码。
3.1 设置工程名和分组名
3.1.1在 Project→Target 上右键,选择 Manage Project Items…(方法一)或在菜单栏点击品字形红绿白图标(方法二)进入工程管理界面,如图 3.1 所示:

3.1.2 在工程管理界面,可执行设置工程名字(Project Targets)、分组名字(Groups)以及添加每个分组的文件(Files)等操作。设置工程名字为:RS485,并设置以下分组:
Startup(存放启动文件)
User(存放 main.c 等用户代码)
Drivers/SYSTEM(存放系统级驱动代码)
Driver/STM32F4xx_HAL_Driver(存放 HAL 库代码)
Driver/BSP(存放板级支持包驱动代码,如:LED、按键等)
Middleware/USMART(存放中间层代码)
Readme(存放工程说明文件,可选)
如图 3.2 所示:

3.1.3 设置好之后,点击 OK,回到 MDK 主界面,可以看到设置的工程名和分组名如图所示:

注意:为了让工程结构清晰,尽量使 MDK 的工程分组和前面新建的工程文件夹对应起来,由于 MDK 分组不支持多级目录,因此将路径也带入分组命名里面,以便区 分。如:User 分组对应 User 文件夹里面的源码,Drivers/SYSTEM 分组,对应 Drivers/SYSTEM 文件夹里面的源码,Drivers/BSP 分组对应 Drivers/BSP 文件夹里面的源码等。
3.2 添加启动文件
启动文件(.s 文件)包含 STM32 的启动代码,其主要作用包括:1、堆栈(SP)的初始化;2、初始化程序计数器(PC);3、设置向量表异常事件的入口地址;4、调用 main 函数等,是每个工程必不可少的一个文件。
3.2.1 启动文件由 ST 官方提供,存放在 STM32CubeF4 软件包的:Drivers → CMSIS → Device →ST → STM32F4xx → Source → Templates → arm 文件夹下。开发板STM32F407ZGT6对应的启动文件为:startup_stm32f407xx.s。
有两种方法给 MDK 的分组添加文件:1,双击 Project 下的分组名添加。2,进入工程管理界面添加。这里我们使用方法 1 添加(路径:实验 0-3,新建工程实验-HAL 库版本\Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm),如图3.4所示:

上图中,也可以点击 Add 按钮进行文件添加。添加完后,点击 Close,完成启动文件添加,得到工程分组如图所示:

3.3 添加 SYSTEM 源码
3.3.1 这里使用工程管理界面(方法 2)进行 SYSTEM 源码添加。点击: 按钮,进入工程管 理界面,选中 Drivers/SYSTEM 分组,然后点击:Add Files,进入文件添加对话框,在依次添加 delay.c、sys.c 和 usart.c 到该分组下,如图 3.6 所示:

注意:这些源码都是在第 1.2 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。
3.3.2 添加完成后,如图3.7所示:

3.4 添加 User 源码
和3.2步骤一样在工程管理界面(方法 2)进行 User 源码添加。点击: 按钮,进入工程管理界面,选中 User 分组,然后点击:Add Files,进入文件添加对话框,依次添加 stm32f4xx_it.c (480TestOne\User目录下)和system_stm32f4xx.c(480TestOne\Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates目录下) 到该分组下。
注意:这些源码都是在第 1.2 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。添加完成后,如图 3.8 所示:

3.5 添加 STM32F4xx_HAL_Driver 源码
和3.2步骤一样在工程管理界面(方法 2),选中 Drivers/STM32F4xx_HAL_Driver 分组,然后点击:Add Files,进入文件添加对话框,在480TestOne\Drivers\STM32F4xx_HAL_Driver\Src目录下找到文件,依次添加 stm32f4xx_hal.c 、 stm32f4xx_hal_cortex.c 、stm32f4xx_hal_dma.c 、stm32f4xx_hal_dma_ex.c、stm32f4xx_hal_gpio.c、 stm32f4xx_hal_pwr.c、stm32f4xx_hal_pwr_ex.c、stm32f4xx_hal_rcc.c、stm32f4xx_hal_rcc_ex.c、stm32f4xx_hal_sram.c、stm32f4xx_hal_tim.c、stm32f4xx_hal_tim_ex.c、stm32f4xx_hal_uart.c、stm32f4xx_hal_usart.c 、stm32f4xx_ll_fsmc.c到该分组下,如图3.9所示:

注意:如果看到分组中有些.c 文件有个小钥匙的符号,这是因为官方的固件包的文件设置了只读权限,取消480TestOne\Drivers\CMSIS与480TestOne\Drivers\STM32F4xx_HAL_Driver两个文件夹的只读权限就好了。
3.6 添加 BSP 源码
这里和3.2步骤一样在工程管理界面(方法 2)进行 User 源码添加。点击: 按钮,进入工程管理界面,选中 User 分组,然后点击:Add Files,进入文件添加对话框,依次添加 key.c(480TestOne\Drivers\BSP\KEY目录下)、 led.c(480TestOne\Drivers\BSP\LED目录下)、lcd.c(480TestOne\Drivers\BSP\LCD目录下) 、rs485.c (480TestOne\Drivers\BSP\RS485目录下)到该分组下。
注意:这些源码都是在第 1.2 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。添加完成后,如图3.10 所示:

3.7 添加 USMART 源码
这里和3.2步骤一样在工程管理界面(方法 2)进行 User 源码添加。点击: 按钮,进入工程管理界面,选中 User 分组,然后点击:Add Files,进入文件添加对话框,依次添加 usmart.c(480TestOne\Drivers\BSP\KEY目录下)、 led.c(480TestOne\Drivers\BSP\LED目录下)、lcd.c(480TestOne\Drivers\BSP\LCD目录下) 到该分组下。
注意:这些源码都是在第 1.2 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。添加完成后,如图 3.11 所示:

4. 魔术棒设置
为避免编写代码和编译报错,需要通过魔术棒对 MDK 工程进行相关设置。在 MDK主界面,点击: (魔术棒图标,即 Options for Target 按钮),进入工程设置对话框,将进行如下几个选项卡的设置。
4.1 设置 Target 选项卡

在图 4.1 中,设置芯片所使用的外部晶振频率为 8Mhz(uVision 5.36及以上版本是灰色,不用设置),选择 ARM Compiler 版本为:Use default compiler version 5(即 AC5 编译器)。
AC5 和 AC6 编译的差异:

4.2 设置 Output 选项卡
在魔术棒→Output 选项卡里面,进行如图 4.2 所示设置

注意:勾选:Browse Information,可用于输出浏览信息,就能使用 go to definition查看函数/变量的定义,对后续调试代码比较有帮助,如果不需要调试代码,则可以去掉勾选,以提高编译速度。
4.3 设置 Listing 选项卡
在魔术棒→Listing 选项卡里面,进行如图 4.3 所示设置:

经过 Output 和 Listing 这两步设置,原来存储在 Objects 和 Listings 文件夹的内容(中间文件)就都改为输出到 Output 文件夹了。
4.4 设置 C/C++选项卡
在魔术棒→C/C++选项卡里面,进行如图 4.4 所示设置:

在②处设置了全局宏定义:USE_HAL_DRIVER和STM32F407xx,他们之间用英文逗号隔开。STM32F407xx用于定义所用 STM32 型号,在 stm32f4xx.h 里面会用到该宏定义。
在③处设置了优化等级为-O0,可以得到最好的调试效果,当然为了提高优化效果提升性能并降低代码量,可以设置-O1~-O3,数字越大效果越明显,不过也越容易出问题。注意:当使用AC6 编译器的时候,这里推荐默认使用-O1 优化。
在④处勾选 C99 模式,即使用 C99 C 语言标准。
在⑤处,进行头文件包含路径设置,点击此按钮,进行如图 所示设置:

上图中设置了 6 个头文件包含路径,其中 3 个在 Drivers 文件夹下,一个在 User 文件夹下,一个在 Middlewares 文件夹下。为避免频繁设置头文件包含路径,正点原子最新源码的include 全部使用相对路径,即只需要在头文件包含路径里面指定一个文件夹,那么该文件夹下的其他文件夹里面的源码全部使用相对路径,则无需再设置头文件包含路径了,直接在 include 里面就指明了头文件所在。
关于相对路径,这里大家记住 3 点:
1,默认路径就是指 MDK 工程所在的路径,即.uvprojx 文件所在路径(文件夹)
2,“./”表示当前目录(相对当前路径,也可以写做“.\”)
3,“../”表示当前目录的上一层目录(也可以写做“..\”)
举例来说,上图中:..\..\Drivers\CMSIS\Device\ST\STM32F4xx\Include,前面两个“..\”,表示 Drivers 文件夹在当前 MDK 工程所在文件夹(MDK-ARM)的上 2 级目录下。
再举个例子,在完成头文件包含路径设置以后,在代码里面编写:#include "./SYSTEM/sys/sys.h"即表示当前头文件包含路径所指示的 4 个文件夹里面,肯定有某一个文件夹包含了:SYSTEM/sys/sys.h 的路径,实际上就是在 Drivers 文件夹下面,两者结合起来就相当于:#include "../../Drivers/SYSTEM/sys/sys.h"
这就是相对路径。它既可以减少头文件包含路径设置(即减少 MDK 配置步骤,免去频繁设置头文件包含路径的麻烦),同时又可以很方便的知道头文件具体在那个文件夹,因此我们推荐在编写代码的时候使用相对路径。
最后,我们如果使用 AC6 编译器,则在Target选项卡的 Misc Controls 处需要设置:-Wno-invalid source-encoding,避免中文编码报错,如果使用 AC5 编译器,则不需要该设置!!
4.5设置 Debug 选项卡
在魔术棒→Debug 选项卡里面,进行如图 4.6 所示设置:

在图4.6中,选择使用:ST-Link Debugger 仿真器(请根据使用的仿真器进行选择),使用 SW 模式,并设置最大时钟频率为 10Mhz,以得到最高下载速度。将仿真器和开发板连接好,并给开发板供电以后,仿真器就会找到开发板芯片,并在 SW Device 窗口显示芯片的 IDCODE、Device Name 等信息(图中⑤处),当无法找到时,请检查供电和仿真器连接状况。
4.6设置 Utilities 选项卡
在魔术棒→Utilities 选项卡里面,进行如图 4.7 所示设置:

注意:图中⑥处下载算法是 MDK 默认添加的,如果⑥处没有下载算法,则点击 Add 按钮,添加下载算法即可(算法名字和⑥处的算法名字一样)
5. 代码分析与编写
正点原子已经有包装好的RS485通信源码。在1.2小节中已经拷贝在Drivers\BSP文件夹下,并在3.6小节中添加到了工程中。
5.1 驱动源码分析
RS485 驱动相关源码包括两个文件:rs485.c 和 rs485.h。
rs485.h 中使用宏定义 485 相关的控制引脚和串口编号,如果需要使用其它的引脚或者串口,修改宏和串口的定义即可,它们在 rs485.h 中定义,它们列出如下:
/******************************************************************************************/
/* RS485 引脚 和 串口 定义
* 默认是针对RS4852的.
* 注意: 通过修改这10个宏定义, 可以支持UART1~UART7任意一个串口.
*/
#define RS485_RE_GPIO_PORT GPIOG
#define RS485_RE_GPIO_PIN GPIO_PIN_8
#define RS485_RE_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0) /* PD口时钟使能 */
#define RS485_TX_GPIO_PORT GPIOA
#define RS485_TX_GPIO_PIN GPIO_PIN_2
#define RS485_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define RS485_RX_GPIO_PORT GPIOA
#define RS485_RX_GPIO_PIN GPIO_PIN_3
#define RS485_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define RS485_UX USART2
#define RS485_UX_IRQn USART2_IRQn
#define RS485_UX_IRQHandler USART2_IRQHandler
#define RS485_UX_CLK_ENABLE() do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0) /* USART2 时钟使能 */
/******************************************************************************************/
/* 控制RS485_RE脚, 控制RS485发送/接收状态
* RS485_RE = 0, 进入接收模式
* RS485_RE = 1, 进入发送模式
*/
#define RS485_RE(x) do{ x ? \
HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
5.1.1 rs485_init 函数
rs485_init 的配置与串口类似,也需要设置波特率等参数,另外还需要配置收发模式的驱动引脚,程序设计如下:
/**
* @brief RS485初始化函数
* @note 该函数主要是初始化串口
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void rs485_init(uint32_t baudrate)
{
/* IO 及 时钟配置 */
RS485_RE_GPIO_CLK_ENABLE(); /* 使能 RS485_RE 脚时钟 */
RS485_TX_GPIO_CLK_ENABLE(); /* 使能 串口TX脚 时钟 */
RS485_RX_GPIO_CLK_ENABLE(); /* 使能 串口RX脚 时钟 */
RS485_UX_CLK_ENABLE(); /* 使能 串口 时钟 */
GPIO_InitTypeDef gpio_initure;
gpio_initure.Pin = RS485_TX_GPIO_PIN; /* 设置 TX 引脚 */
gpio_initure.Mode = GPIO_MODE_AF_PP; /* 设置引脚模式为复用推挽输出 */
gpio_initure.Pull = GPIO_PULLUP; /* 启用上拉电阻,保证在空闲状态下引脚为高电平 */
gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH; /* 设置引脚速度为高频率,保证信号稳定 */
gpio_initure.Alternate = GPIO_AF7_USART2; /* 将引脚复用为串口 2 (USART2) */
HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_initure); /* 串口TX 脚 模式设置 */
gpio_initure.Pin = RS485_RX_GPIO_PIN; /* 设置 RX 引脚 */
HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_initure); /* 串口RX 脚 必须设置成输入模式 */
gpio_initure.Pin = RS485_RE_GPIO_PIN; /* 设置 RS485_RE 引脚,用于控制发送/接收模式 */
gpio_initure.Mode = GPIO_MODE_OUTPUT_PP; /* 设置为推挽输出模式 */
gpio_initure.Pull = GPIO_PULLUP; /* 启用上拉电阻 */
gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH; /* 设置引脚速度为高频率 */
HAL_GPIO_Init(RS485_RE_GPIO_PORT, &gpio_initure); /* RS485_RE 脚 模式设置 */
/* USART 初始化设置 */
g_rs458_handler.Instance = RS485_UX; /* 选择485对应的串口 */
g_rs458_handler.Init.BaudRate = baudrate; /* 设置串口 波特率 */
g_rs458_handler.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
g_rs458_handler.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
g_rs458_handler.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
g_rs458_handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
g_rs458_handler.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&g_rs458_handler); /* 使能对应的串口, 但会调用MSp */
__HAL_UART_DISABLE_IT(&g_rs458_handler, UART_IT_TC);
#if RS485_EN_RX /* 如果使能了接收 */
/* 使能接收中断 */
__HAL_UART_ENABLE_IT(&g_rs458_handler, UART_IT_RXNE); /* 开启接收中断 */
HAL_NVIC_EnableIRQ(RS485_UX_IRQn); /* 使能USART1中断 */
HAL_NVIC_SetPriority(RS485_UX_IRQn, 3, 3); /* 抢占优先级3,子优先级3 */
#endif
RS485_RE(0); /* 默认为接收模式 */
}
可以看到代码基本跟串口的配置一样,只是多了收发控制引脚的配置。
5.1.2发送函数
发送函数用于输出 485 信号到 485 总线上,默认的 485 方式一般空闲时为接收状态,只有发送数据时485 芯片才进入发送状态,发送完成后马上回到空闲接收状态,这样可以保证操作过程中 485 的数据丢失最小。实现的发送函数如下:
/**
* @brief RS485发送len个字节
* @param buf : 发送区首地址
* @param len : 发送的字节数(为了和本代码的接收匹配,这里建议不要超过 RS485_REC_LEN 个字节)
* @retval 无
*/
void rs485_send_data(uint8_t *buf, uint8_t len)
{
RS485_RE(1); /* 进入发送模式 */
HAL_UART_Transmit(&g_rs458_handler, buf, len, 1000); /* 串口2发送数据 */
g_RS485_rx_cnt = 0;
RS485_RE(0); /* 进入接收模式 */
}
5.1.3 485 接收中断函数
RS485 的接收就与串口中断一样,不过要注意空闲时要切换回接收状态,否则会收不到数据。这里定义了一个全局的缓冲区 g_RS485_rx_buf 进行接收测试,通过串口中断接收数据,编写的接收代码如下:
uint8_t g_RS485_rx_buf[RS485_REC_LEN]; /* 接收缓冲, 最大 RS485_REC_LEN 个字节. */
uint8_t g_RS485_rx_cnt = 0; /* 接收到的数据长度 */
void RS485_UX_IRQHandler(void)
{
uint8_t res;
if ((__HAL_UART_GET_FLAG(&g_rs458_handler, UART_FLAG_RXNE) != RESET)) /* 接收到数据 */
{
HAL_UART_Receive(&g_rs458_handler, &res, 1, 1000);
if (g_RS485_rx_cnt < RS485_REC_LEN) /* 缓冲区未满 */
{
g_RS485_rx_buf[g_RS485_rx_cnt] = res; /* 记录接收到的值 */
g_RS485_rx_cnt++; /* 接收数据增加1 */
}
}
}
5.1.4 485 查询接收数据函数
该函数用于查询 485 总线上接收到的数据,主要实现的逻辑是:一开始进入函数时,先记录下当前接收计数器的值,再来一个延时去判断接收是否结束(即该期间有无接收到数据),假如说接收计数器的值没有改变,就证明接收结束,我们就可以把当前接收缓冲区传递出去。函数实现如下:
/**
* @brief RS485查询接收到的数据
* @param buf : 接收缓冲区首地址
* @param len : 接收到的数据长度
* @arg 0 , 表示没有接收到任何数据
* @arg 其他, 表示接收到的数据长度
* @retval 无
*/
void rs485_receive_data(uint8_t *buf, uint8_t *len)
{
uint8_t rxlen = g_RS485_rx_cnt;
uint8_t i = 0;
*len = 0; /* 默认为0 */
delay_ms(10); /* 等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束 */
if (rxlen == g_RS485_rx_cnt && rxlen) /* 接收到了数据,且接收完成了 */
{
for (i = 0; i < rxlen; i++)
{
buf[i] = g_RS485_rx_buf[i];
}
*len = g_RS485_rx_cnt; /* 记录本次数据长度 */
g_RS485_rx_cnt = 0; /* 清零 */
}
}
5.2 添加 main.c,并编写代码
5.2.1 在 MDK 主界面,点击: ,新建一个txt文件。

5.2.2 点击保存,命名为main.c 并保存在 User 文件夹下,如图 5.2 所示。

5.2.3 参考第3节添加文件步骤,双击 User 分组,弹出添加文件的对话框,将 User 文件夹下的 main.c 文件添加到 User 分组下。得到 如图 5.3 所示的界面:

5.2.4程序流程图

在 main.c 文件里面输入如下代码:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./USMART/usmart.h"
#include "./BSP/KEY/key.h"
#include "./BSP/RS485/rs485.h"
int main(void)
{
uint8_t key;
uint8_t i = 0, t = 0;
uint8_t cnt = 0;
uint8_t rs485buf[5];
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(84); /* 初始化USMART */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
rs485_init(9600); /* 初始化RS485 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "RS485 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY0:Send", RED); /* 显示提示信息 */
lcd_show_string(30, 130, 200, 16, 16, "Count:", RED); /* 显示当前计数值 */
lcd_show_string(30, 150, 200, 16, 16, "Send Data:", RED); /* 提示发送的数据 */
lcd_show_string(30, 190, 200, 16, 16, "Receive Data:", RED);/* 提示接收到的数据 */
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */
{
for (i = 0; i < 5; i++)
{
rs485buf[i] = cnt + i; /* 填充发送缓冲区 */
lcd_show_xnum(30 + i * 32, 170, rs485buf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
rs485_send_data(rs485buf, 5); /* 发送5个字节 */
}
rs485_receive_data(rs485buf, &key);
if (key) /* 接收到有数据 */
{
if (key > 5)key = 5; /* 最大是5个数据. */
for (i = 0; i < key; i++)
{
lcd_show_xnum(30 + i * 32, 210, rs485buf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
}
t++;
delay_ms(10);
if (t == 20)
{
LED0_TOGGLE(); /* LED0闪烁, 提示系统正在运行 */
t = 0;
cnt++;
lcd_show_xnum(30 + 48, 130, cnt, 3, 16, 0X80, BLUE); /* 显示数据 */
}
}
}
5.2.5 编译结果 0 错误 0 警告,即编译成功。
编译结果提示:代码总大小(Porgram Size)为:FLASH 占用 44,864 字节(Code + RO + RW),SRAM 占用 2,128 字节(RW + ZI);并成功创建了 Hex 文件(可执行文件,放在 Output 目录下)。

注意:如果编译提示有错误/警告,请根据提示,从第一个错误/警告开始解决,直到 0 错误0 警告。如果出错,很有可能是之前的操作存在问题,请对照教程找问题。
6. 下载验证
连接开发板,在 MDK 主界面,点击:(下载按钮,也可以按键盘快捷键:F8),就可以将代码下载到开发板,Build Output 提示:Application running…,则表示代码下载成功,且开始运行。

6.1 验证方式一
使用一个STM32开发板和USB 转 485 调试器+串口助手,A接A,B接B。


6.2 验证方式二
通过下载代码到正点原子探索者 STM32F407 开发板上(注意要 2 个开发板都下载这个代码)。
使用 STM32F407 的串口 2 来实现两块开发板之间的 485 通信,并将结果显示在 TFTLCD 模块上。 A连A,B连B。

开发板下载成功后,显示屏显示内容如图所示:

伴随 DS0 的不停闪烁,提示程序在运行。此时,按下 KEY0 就可以在另外一个开发板上面收到这个开发板发送的数据了。


图 6.6 来自开发板 A,发送了 5 个数据,图 6.7 来自开发板 B,接收到了来自开发板A 的 5 个数据。