STM32应用开发实践教程:基于 RS-485 总线的多机通信应用开发

2023-11-04

5.1.1 任务分析
本任务要求设计一个基于 RS-485 总线的多机通信系统,系统中有两台设备(理论上最多可
接入 32 台设备)。其中一台设备作为主机,连接 OLED 显示屏;另一台设备作为从机,连接温
湿度传感器 DHT11 与 LED 灯。
系统通电后,默认情况下两台设备之间没有数据交互。系统的控制要求如下。
(1)用户按下主机的 Key1,向从机发出“上报温湿度数据”的命令。从机收到此命令后,
以 2s 为周期持续上报相应的数据。主机收到温湿度数据后,在 OLED 屏幕上显示。
(2)用户按下主机的 Key2,向从机发出“停止上报温湿度数据”的命令。从机收到此命令
后,停止上报相应的数据。
(3)用户按下主机的 Key3,向从机发出“翻转 LED 状态”的命令。从机收到此命令后,将
LED 显示状态翻转,同时上报当前 LED 灯的亮灭情况。主机收到从机上报的 LED 灯亮灭情况后,
在 OLED 屏幕上显示。主机显示界面的样式可参考图 5-1-1。

上述任务要求规定了系统的架构与工作流程,因此本任务涉及的知识点主要有:
 RS-485 标准的基础知识;
 RS-485 收发器芯片的工作原理;
 RS-485 总线通信应用层协议的制订方法和技巧。
5.1.2 知识链接
1.RS-485/RS-422/RS-232 标准
在任务 2.3 中,我们学习了 RS-232 串行通信标准。本节着重介绍 RS-422 和 RS-485 标准,
并对 3 种标准进行比较。
RS-232、RS-422 和 RS-485 标准最初都是由美国电子工业协会(EIA)制定并发布的。
RS-232 标准在 1962 年发布,它的缺点是通信距离短、速率低,而且只能点对点通信,无法
组建多机通信系统。另外,在工业控制环境中,基于 RS-232 标准的通信系统经常会由于外
界的电气干扰而导致信号传输出现错误。以上缺点决定了 RS-232 标准无法适用于工业控制
现场总线。

RS-422 标准在 RS-232 的基础上发展而来,它弥补了 RS-232 标准的一些不足。如 RS-422
标准定义了一种平衡通信接口,改变了 RS-232 标准的单端通信的方式,总线上使用差分电压进
行信号的传输。这种连接方式将传输速率提高到 10 Mbit/s,传输距离(在速率低于 100 kbit/s
时)延长到 4000 英尺(1 英尺=0.3048m),而且允许在一条平衡总线上最多连接 10 个接收器。
为了扩展应用范围,EIA 又于 1983 年发布了 RS-485 标准。RS-485 标准与 RS-422 标准
相比,增加了多点、双向的通信功能,在一条平衡总线上最多可连接 32 个接收器。
下面对 RS-232、RS-422 和 RS-485 标准的主要特性进行比较,比较结果如表 5-1-1 所示。

 

2.RS-485 收发器芯片与典型应用电路
RS-485 收发器(Transceiver)芯片是一种常用的通信接口器件,世界上大多数半导体公司
都有符合 RS-485 标准的收发器产品线,如 Sipex 公司的 SP307x 系列芯片、Maxim 公司的
MAX485 系列、TI 公司的 SN65HVD485 系列、Intersil 公司的 ISL83485 系列等。
接下来以 Sipex 公司的 SP3072EEN 芯片为例,讲解 RS-485 标准的收发器芯片的工作原理
与典型应用电路。图 5-1-2 展示了 RS-485 收发器芯片的典型应用电路。 

在图 5-1-2 中,电阻 R3 为终端匹配电阻,其阻值为 120Ω。电阻 R2 和 R4 为偏置电阻,
它们用于确保在静默状态时,RS-485 总线维持逻辑 1 高电平状态。SP3072EEN 芯片的封装是
SOP-8,RO 与 DI 分别为数据接收与发送引脚,它们用于连接 MCU 的 USART 外设。 RE 和 DE
分别为接收使能和发送使能引脚,它们与 MCU 的 GPIO 引脚相连。A、B 两端用于连接 RS-485
总线上的其他设备,所有设备以并联的形式接在总线上。
目前市面上各个半导体公司生产的 RS-485 收发器芯片的管脚分布情况几乎相同,具体的管
脚功能描述如表 5-1-2 所示。 

3.RS-485 的应用层通信协议
RS-485 标准只对接口的电气特性做出相关规定,并未对接插件、电缆和通信协议等做出相
关规定,所以用户需要在 RS-485 总线网络的基础上制定应用层通信协议。一般来说,各应用领
域的 RS-485 通信协议都是指应用层通信协议。
在工业控制领域应用十分广泛的 ModBus 协议(ASCII/RTU 模式)就是一种应用层通信协
议,它可以选择 RS-232 或 RS-485 总线作为基础传输介质。另外,在智能电表领域也有同样
的案例,如多功能电能表通信规约(DL/T645-1997)也是一种基于 RS-485 总线的应用层通信
协议。
接下来根据本任务的要求,讲解如何制定 RS-485 总线中主机与从机之间的通信协议。
RS-485 总线网络支持一主多从的通信模式,网络中各设备拥有唯一的地址。主机以广播的
形式下发指令,从机接收到相关指令后,将指令中的地址码与自己的地址码进行比较,如果是下
发给自己的指令则执行相关指令,执行完毕后发送相应的状态代码给主机。否则丢弃该指令,静
默等待主机的下一条指令。
另外,接收方收到的数据可能会由于传输过程受到干扰而出错。为了避免接收方对错误数据
进行处理,通信协议中一般都会加入某种校验机制,常见的有:和校验、奇校验、偶校验和 CRC
校验等。
根据上述分析,本任务的 RS-485 通信的数据帧应包含如下组成部分。
 帧起始符:预示一帧数据的开始。
 地址域:RS-485 总线中每个设备拥有唯一的地址,可以是多个字节。一般取最大值作
为广播地址,如当地址域占 1 个字节时,0xFF 为广播地址。
 命令码域:作为执行操作的依据。
 数据长度域:指示数据域的长度。
 数据域:包含要发送的数据内容。
 校验码域:自“地址域”至“数据域”所有数据位的和,一般保留低 8 位,溢出位丢弃。
 结束符:预示一帧数据的结束。

完整的数据帧格式如表 5-1-3 所示。

 

地址域”“命令码域”“数据长度域”“数据域”的具体内容如表 5-1-4 所示。

 

根据数据帧格式以及数据帧各域的具体内容定义,以温湿度数据采集为例,完整的主机下发
命令的数据帧与从机回传的数据帧示例如表 5-1-5 所示。 

5.1.3 任务实施
1.硬件连接
图 5-1-3 展示了 RS-485 网络中通信主机与通信从机的连接方式。从图中可以看到,通信
主机与通信从机的连接方式较为简单,只须将两者的 RS-485 接线端 A 与 B 分别相连即可。
STM32F4 系列微控制器与硬件的接线方式如表 5-1-6 所示。

 

由表 5-1-4 可知,RS-485 网络的两个基本组成部分是 MCU 的 USART 外设与 RS-485 收
发器芯片。在本任务中,我们使用 STM32F407ZGT6 的 USART2 外设与 RS-485 收发器芯片
SP3072EEN 相连。
2.绘制主机和从机的程序流程
根据 5.1.1 节的任务分析,绘制主机和从机的程序流程,如图 5-1-4 和图 5-1-5 所示。 

3.编写 USART2 的初始化函数与数据发送函数
复制一份任务 4.4 的工程,重命名为“task5.1_RS485”,在“HARDWARE”文件夹下新建
“USART2”子文件夹,新建“usart2.c”和“usart2.h”两个文件,将它们加入工程中,并配置
头文件的包含路径。在“usart2.h”文件中输入以下代码:

#ifndef __USART2_H
#define __USART2_H
#include "sys.h"
#define USART2_RX_MAX 255 // 定义最大接收字节数 255
#define RS485_TX_MODE GPIO_SetBits(GPIOG, GPIO_Pin_8) //RS-485 发送模式
#define RS485_RX_MODE GPIO_ResetBits(GPIOG, GPIO_Pin_8) //RS-485 接收模式
extern uint8_t USART2_RX_Buffer[USART2_RX_MAX]; // 定义 1.USART2 接收缓存
extern uint8_t USART2_RX_Index; // 定义 2.USART2 接收数组下标
extern uint8_t USART2_RX_OverFlag; // 定义 3.USART2 接收完成标志位
void USART2_Init(uint32_t baud);
void USART2_SendByte(uint8_t ch);
void USART2_SendString(uint8_t *str, uint8_t strlen);
#endif

 上述代码片段中的第 7 行和第 8 行是 RS-485 收发器芯片的“接收使能”与“发送使能”
功能的宏定义。在硬件设计上,我们通常将 RS-485 收发器芯片的 RE 端和 DE 端并联后与 MCU
的某个 GPIO 引脚相连。MCU 输出低电平时 RS-485 收发器芯片进入接收模式;MCU 输出高电
平时 RS-485 收发器芯片进入发送模式。
在“usart2.c”文件中编写 USART2 初始化函数,输入以下代码:

#include "usart2.h"
uint8_t USART2_RX_Buffer[USART2_RX_MAX] = { 0 }; // 定义 1.USART2 接收缓存
uint8_t USART2_RX_Index = 0;  // 定义 2.USART2 接收数组下标
uint8_t USART2_RX_OverFlag = 0; // 定义 3.USART2 接收完成标志位
/**
* @brief USART2 初始化
* @param baud:  波特率设置
* @retval None
*/
void USART2_Init(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能 USART2 时钟
/* USART2 引脚复用映射 */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); //PA2 复用为 USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); //PA3 复用为 USART2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //PA2 与 PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;  // 复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;  // 速度 50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  // 上拉
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置生效
//PG8 推挽输出 , 用于 RS-485 模式控制
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PG8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 输出
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;  // 速度 100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  // 上拉
GPIO_Init(GPIOG, &GPIO_InitStructure); // 配置生效
/* USART2  初始化设置 */
USART_InitStructure.USART_BaudRate = baud; // 波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长 8bit
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收发模式
USART_Init(USART2, &USART_InitStructure); // 配置生效
USART_Cmd(USART2, ENABLE); // 使能 USART2
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 开启接收中断
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); // 开启空闲中断
/* USART2 NVIC  配置 */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RS485_RX_MODE; // 默认为接收模式
}
/**
* @brief USART2 发送一个字节
* @param ch:  要发送的字节数据
* @retval None
*/
void USART2_SendByte(uint8_t ch)
{
/*  发送一个字节数据到 USART2 */
USART_SendData(USART2, ch);
/*  等待发送完毕 */
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
/**
* @brief USART2 发送一个字符串
* @param *str:  要发送的字符串
* @param strlen:  字符串长度
* @retval None
*/
void USART2_SendString(uint8_t *str, uint8_t strlen)
{
unsigned int k = 0;
RS485_TX_MODE; // 进入发送模式
do
{
USART2_SendByte(*(str + k));
} while (k++ < strlen);
RS485_RX_MODE; // 进入接收模式
}

 4.编写 USART2 的中断服务函数
继续在“usart2.c”文件中输入以下代码:

/**
* @brief USART2 中断服务函数
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
uint8_t Res, forclear;
if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
Res = USART_ReceiveData(USART2);
USART2_RX_Buffer[USART2_RX_Index++] = Res;
/*  防止接收缓存下标溢出 */
if (USART2_RX_Index >= USART2_RX_MAX)
USART2_RX_Index = 0;
}
if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
USART2_RX_OverFlag = 1;
forclear = USART_ReceiveData(USART2);
}
}

5.根据任务要求定义必要的数据类型
为了更加方便地进行数据的解析与校验,我们应根据任务要求定义必要的数据类型。本任务
的 RS-485 数据帧包含多个帧域,因此我们可根据数据帧的格式定义相应的结构体类型。另外,
本任务的命令类型有 3 种,为了增加程序的可读性,我们可定义命令的枚举类型。新建“main.h”
文件,在其中输入以下代码:

#ifndef __MAIN_H
#define __MAIN_H
#include "sys.h"
//#define MASTER_DEV  1  // 主机宏定义
#define SLAVE_DEV  1  // 从机宏定义
#define masterAddr 0x01  // 主机地址
#define slaveAddr  0x02  // 从机地址
/*  各种命令的枚举类型定义 */
typedef enum {
CMD_None = 0x00, // 无效命令
CMD_UPLOAD_TH, // 上报温湿度命令
CMD_STOP_UPLOAD_TH, // 停止上报温湿度命令
CMD_TOGGLE_LED, // 翻转 LED 命令
} Command_EnumDef;
/*  自定义 RS-485 数据帧结构体类型 */
typedef struct {
uint8_t sof; // 帧起始符
uint8_t dstAddr; // 目的地址
uint8_t cmd; // 命令码
uint8_t dataLen; // 数据长度
uint8_t data[16];  // 数据域( 16 B )
uint8_t checkSum;  // 校验和
uint8_t eof; // 帧结束符
} DataFrame_TypeDef;
#endif

本任务中的主从机功能虽然不同,但由于两者基于同一硬件平台,底层驱动程序完全一
致,因此它们的程序可编写在同一个工程中。本示例程序采用预编译的方式区别主从机的代
码。上述代码片段第 5 行和第 6 行分别是主机和从机的宏定义,具体用法见本任务工程的
main()函数。
6.编写数据的解析与校验函数
主机和从机在接收完一帧数据后,应先对数据的完整性与正确性进行校验,确保数据无误后
才能实施数据的解析。在“main.c”文件中编写相应的函数如下:

/**
* @brief 获取收到的命令类型
* @param *dataFrame :数据帧结构体首地址
* @retval Command_EnumDef :命令的枚举类型
*/
Command_EnumDef getCommandType(DataFrame_TypeDef *dataFrame)
{
switch (dataFrame->cmd) {
case 1:// 上报温湿度数据
return CMD_UPLOAD_TH;
case 2:// 停止上报温湿度数据
return CMD_STOP_UPLOAD_TH;
case 3:// 翻转 LED
return CMD_TOGGLE_LED;
default:
break;
}
return CMD_None;
}
/**
* @brief 检测数据帧校验和是否正确
* @param *rcvbuf : USART2 接收的数据缓存
* @retval bool : true 校验和正确 | false 校验和错误
*/
bool isCheckSumOK(uint8_t *rcvbuf)
{
uint8_t checkSum = 0, index = 0, tempLength = 0;
tempLength = rcvbuf[3];// 取出帧数据长度
/*  判断帧起始,帧结束,校验和是否正确 */
*/
void buildFeedbackFrame(uint8_t *dataFb, uint8_t length, \
uint8_t cmd, uint8_t *full_dataFb)
{
uint8_t index = 0, tempLength = 0;
full_dataFb[0] = 0xAA;
full_dataFb[1] = masterAddr;
full_dataFb[2] = cmd;
full_dataFb[3] = length;
tempLength = length;
full_dataFb[4 + length] = full_dataFb[1] + full_dataFb[2] + \
full_dataFb[3];
while (tempLength--)
{
full_dataFb[4 + index] = *(dataFb + index);
full_dataFb[4 + length] += *(dataFb + index);  // 计算 checkSum 值
index++;
}
full_dataFb[5 + length] = 0x0C;
}
/**
* @brief OLED 显示环境参数 ( 温度 / 湿度 )
* @param None
* @retval None
*/
void Show_TempHumiLight(void)
{
OLED_Display_String(20, 0, "RS-485", 16);
OLED_Display_String(20, 16, tempString, 16);
OLED_Display_String(20, 32, humiString, 16);
}

7.编写 main()函数
在“main.c”文件中编写 main()函数,输入以下代码:

#include "main.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "dht11.h"
#include "bsp_spi.h"
#include "oled.h"
#include "usart2.h"
#include <stdbool.h>
#include <string.h>
/*  函数声明 */
void Show_TempHumiLight(void);
Command_EnumDef getCommandType(DataFrame_TypeDef *dataFrame);
bool isCheckSumOK(uint8_t *rcvbuf);
void analysisDataFrame(uint8_t *rcvbuf);
void buildFeedbackFrame(uint8_t *dataFb, uint8_t length, \
uint8_t cmd, uint8_t *full_dataFb);
/*  变量定义 */
uint8_t temperature = 0x16; // 采集的温度值,单位℃
uint8_t humidity = 0x40;  // 采集的湿度值
char tempString[50], humiString[50], ledString[50];
uint8_t keyValue = 0, count = 0;
/*  定义命令数据帧:上报 | 停止上报 | 翻转 LED */
uint8_t cmd_upload_th[] = {0x55, 0x02, 0x01, 0x01, 0x00, 0x04, 0x0B};
uint8_t cmd_stop_upload_th[] = {0x55, 0x02, 0x02, 0x01, 0x00, \
0x05, 0x0B};
uint8_t cmd_toggle_led[] = {0x55, 0x02, 0x03, 0x01, 0x00, 0x06, 0x0B};
uint8_t arrayLength = 0;
uint8_t data_upload_flag = 0; // 温湿度数据上报标志位
uint8_t data_feedback[8]; // 通信从机反馈数据存放缓存
uint8_t full_data_feedback[16]; // 通信从机完整反馈数据帧存放缓存
Command_EnumDef cmd_rs485 = CMD_None; //RS-485 命令
DataFrame_TypeDef dataFrame_rs485; //RS-485 数据帧
int main(void)
{
delay_init(168); // 延时函数初始化
LED_Init(); //LED 端口初始化
Key_Init(); // 按键端口初始化
EXTIx_Init();  // 外部中断初始化
USART1_Init(115200);  //USART1 初始化
USART2_Init(115200);  //USART2 初始化
DHT11_Init();  //DHT11 初始化
SPI2_Init(); //SPI2 外设初始化
OLED_Init(); //OLED 显示模块初始化
#ifdef SLAVE_DEV
while (DHT11_Init())  // 等待 DHT11 初始化完成
{
printf("DHT11 Init Error!\r\n");
delay_ms(500);
}
printf("DHT11 Init Success!\r\n");
#endif
while (1)
{
count++;
#ifdef SLAVE_DEV
if (data_upload_flag == 1) // 上报标志位为 1
{
if (count >= 200)
{
count = 0;
/*  读取 DHT11 的温湿度值 */
DHT11_Read_Data(&temperature, &humidity);
/*  组合需要显示的信息 */
data_feedback[0] = temperature;
data_feedback[1] = humidity;
buildFeedbackFrame(data_feedback, 2, 0x01, full_data_feedback);
USART2_SendString(full_data_feedback,strlen((const char *)full_
data_feedback));
}
}
#endif
#ifdef MASTER_DEV
if (keyValue == KEY_D_PRESS) // 下键按下
{
keyValue = 0;
arrayLength = \
sizeof(cmd_upload_th)/sizeof(cmd_upload_th[0]);
USART2_SendString(cmd_upload_th, arrayLength);
}
else if (keyValue == KEY_U_PRESS) // 上键按下
{
keyValue = 0;
arrayLength = \
sizeof(cmd_stop_upload_th) / sizeof(cmd_stop_upload_th[0]);
USART2_SendString(cmd_stop_upload_th, arrayLength);
}
else if (keyValue == KEY_L_PRESS)// 左键按下
{
keyValue = 0;
arrayLength = \
sizeof(cmd_toggle_led) / sizeof(cmd_toggle_led[0]);
USART2_SendString(cmd_toggle_led, arrayLength);
}
#endif
/*  一帧数据接收完毕 */
if (USART2_RX_OverFlag == 1)
{
USART2_RX_OverFlag = 0;
USART2_RX_Index = 0;
cmd_rs485 = CMD_None;
if (isCheckSumOK(USART2_RX_Buffer) == true)
{
/*  先清空数据帧结构体数据域 */
memset(dataFrame_rs485.data, 0, 8);
/*  解析源数据 */
analysisDataFrame(USART2_RX_Buffer);
/*  判断命令类型 */
cmd_rs485 = getCommandType(&dataFrame_rs485);
}
#ifdef MASTER_DEV
/*  上报温湿度数据命令 */
if (cmd_rs485 == CMD_UPLOAD_TH)
{
/* OLED 显示温湿度 */
temperature = dataFrame_rs485.data[0];
humidity = dataFrame_rs485.data[1];
sprintf(tempString, "Temp:%d", temperature);
sprintf(humiString, "Humi:%d", humidity);
Show_TempHumiLight(); //OLED 显示温湿度数据
}
/*  翻转 LED 命令 */
else if (cmd_rs485 == CMD_TOGGLE_LED)
{
/* OLED 显示 LED 状态 |  亮: 2 ,灭: 3 */
printf("led state\r\n");
if (dataFrame_rs485.data[0] == 3)
{
sprintf(ledString, "LED:OFF");
}
else if (dataFrame_rs485.data[0] == 2)
{
sprintf(ledString, "LED: ON");
}
OLED_Display_String(20, 48, ledString, 16);
}
#endif
#ifdef SLAVE_DEV
/*  上报温湿度数据命令 */
if (cmd_rs485 == CMD_UPLOAD_TH)
{
data_upload_flag = 1;
}
/*  停止上报温湿度数据命令 */
else if (cmd_rs485 == CMD_STOP_UPLOAD_TH)
{
data_upload_flag = 0;
}
/*  翻转 LED 命令 */
else if (cmd_rs485 == CMD_TOGGLE_LED)
{
LED0 = ~LED0;
/* LED 状态 |  亮: 2 ,灭: 3 */
data_feedback[0] = GPIO_ReadInputDataBit(GPIOF, \
GPIO_Pin_9) + 2;
/*  反馈 LED 状态至通信主机 */
buildFeedbackFrame(data_feedback, 1, 0x03, \
full_data_feedback);
USART2_SendString(full_data_feedback, \
strlen((char *)full_data_feedback));
}
#endif
/*  用完清空 USART2 接收缓存 */
memset(USART2_RX_Buffer, 0, 255);
}
if (count % 50 == 0)
LED1 = ~LED1;
delay_ms(10);
}
}

8.观察试验现象
按照以下步骤完成本任务的软硬件联调并观察试验现象。
① 用户按下主机的 Key1,向从机发送“上报温湿度”命令。主机的 OLED 屏上将显示温
湿度值,具体的显示样式参考图 5-1-1。
② 用户按下主机的 Key2,向从机发送“停止上报温湿度”命令。从机将停止上报温湿度值,
此时主机的 OLED 屏停止更新。
③ 用户按下主机的 Key3,向从机发送“翻转 LED”命令。从机的 LED 状态将翻转并回传
相应的状态信息给主机,主机的 OLED 屏刷新 LED 状态,具体的显示样式参考图 5-1-1。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32应用开发实践教程:基于 RS-485 总线的多机通信应用开发 的相关文章

  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d
  • DSP 库 - RFFT - 奇怪的结果

    最近我一直在尝试在我的STM32F4 Discovery评估板上进行FFT计算 然后将其发送到PC 我已经调查了我的问题 我认为我对制造商提供的 FFT 函数做错了 我正在使用 CMSIS DSP 库 现在我一直在用代码生成样本 如果工作正
  • GCC ARM 汇编预处理器宏

    我正在尝试使用汇编 ARM 宏进行定点乘法 define MULT a b asm volatile SMULL r2 r3 0 1 n t ADD r2 r2 0x8000 n t ADC r3 r3 0 n t MOV 0 r2 ASR
  • 当端点和 PMA 地址均更改时,CubeMX 生成的 USB HID 设备发送错误数据

    我正在调试我正在创建的复合设备的问题 并在新生成的仅 CubeMX 代码中重新创建了该问题 以使其更容易解决 我添加了少量代码main 让我发送 USB HID 鼠标点击 并在按下蓝色按钮时使 LED 闪烁 uint8 t click re
  • 为 ARM 交叉编译 zlib

    我尝试为arm poky linux gnueabi交叉编译zlib 但启动 make 时出现错误 zlib 1 2 11 AR HOST ar CC HOST gcc RANLIB HOST ranlib configure prefix
  • 如何在 Android 设备上运行 VS Code [重复]

    这个问题在这里已经有答案了 我有 Galaxy Tab S6 它具有替代笔记本电脑的很酷的功能 例如连接鼠标和键盘 但不幸的是它运行 Android 操作系统 并且没有很多开发应用程序可用于 Android 所以我想是否有一个选项可以在至少
  • ARM 调用约定是否允许函数不将 LR 存储到堆栈中?

    正如标题所示 我在理解 ARM 架构的调用约定时遇到问题 特别是 我仍然很难知道当你调用子程序时 LR 寄存器会发生什么 我认为 当您进入子程序时 处理 LR 寄存器的最明显 最安全的方法是将其存储到堆栈中 但该行为没有出现在文档中 因此我
  • 为什么 i2c_smbus 函数不可用? (I2C——嵌入式Linux)

    有很多参考使用i2c smbus 开发嵌入式 Linux 软件时在 I2C 总线上进行通信的函数 什么时候i2c smbus函数如i2c smbus read word data在软件项目中引用了 ARM8 处理器错误 例如 i2c smb
  • 产生并处理软件中断

    有人可以告诉我如何在Linux下生成软件中断然后用request irq处理它吗 或者也许这是不可能的 您可以使用软中断来代替 您可以通过编辑 include linux interrupt h 来定义您的 sofirq 然后使用函数 ra
  • 是否可以将 SpaCy 安装到 Raspberry Pi 4 Raspbian Buster

    我一整天都在安装 SpaCy sudo pip install U spacy Looking in indexes https pypi org simple https www piwheels org simple Collectin
  • 为什么当大小大于 50 时,该程序花费的时间会呈指数级增长?

    所以我正在为类编写一个 ARM 汇编快速排序方法 我对大部分内容都有了解 除了复杂性没有意义 我们将其与我们制作的另一种冒泡排序方法进行比较 它对于具有 1 个参数和 10 个参数的示例表现更好 然而 我什至无法比较 100 个参数测试 因
  • gdb 不会从外部架构读取核心文件

    我正在尝试在 Linux 桌面上读取 ARM 核心文件 但似乎无法找出我的核心文件 有什么方法可以指示 gdb 我的核心文件是什么类型吗 file daemon daemon ELF 32 bit LSB executable ARM ve
  • saber sd 如何在没有 SPL 的情况下直接从 uboot 启动

    sabre sd 基于 imx 6 最大内部 RAM 约为 150Kb 然而 uboot 足够大 可以容纳在这个空间中 在这个场景中事情是如何进行的 https community freescale com docs DOC 95015
  • 什么是遗留中断?

    我正在开发一个项目 试图弄清楚 ARM 架构的全局中断控制器中如何处理中断 我正在使用 pl390 中断控制器 我看到有一条线被称为传统中断 它绕过了分配器逻辑 假设有 2 个中断可以被编程为传统中断 任何人都可以帮助解释一下什么是遗留中断
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • 需要一些建议来开始在 ARM(使用 Linux)平台上编程

    我 也许 很快就会在托管 Linux 发行版的 ARM 平台上工作 我不知道哪个发行版 我知道该项目涉及视频流 但我无法告诉你更多信息 其实我只收到通知 还没见到任何人 我从来没有在这样的平台上工作过 所以我的想法是在项目开始之前进行测试
  • 错误:-march= 开关的值错误

    我写了一个Makefile 但无法让它工作 我有一个选项应该选择编译到哪个处理器 然而 当我跑步时make从命令行它说 tandex tandex P 6860FX emulators nintendo sdks 3DS SDK HomeB
  • 当我尝试在 Armv8 程序集中分配数组时,执行冻结

    所以我正在用汇编语言进行编程 这只是一个简单的代码 这样我就可以学习如何分配数组 以便稍后在 NEON 编程中使用它们 ASM FUNC FPE data balign 8 array skip 80 array1 word 10 20 3
  • 在LPC2148 ARM处理器上创建中断向量的汇编代码

    我最近刚刚开始使用 LPC2148 ARM 处理器 我试图理解一些有关创建中断向量的汇编代码 这是代码 Runtime Interrupt Vectors Vectors b start reset start ldr pc undf un
  • 支持 ARM 上的 Windows 10 桌面应用程序 - MFC 和 COM 以及 OPOS 可以工作吗?

    我试图了解将在 x86 Windows 10 上运行的 C MFC 应用程序移植到具有 Qualcomm Snapdragon 处理器的 ARM Windows 10 设备的障碍 32位应用程序具有以下特点 MFC 与 C 用于用户界面 C

随机推荐

  • kibina 启动_kibana启动失败

    log 09 44 53 848 info listening server http Server running at http localhost 5601 log 09 44 53 864 error status plugin x
  • 给定区间[-2^31, 2^31]内的3个整数A、B和C,请判断A+B是否大于C。

    该题使用long double作为A B C的存储类型 都无法通过系统的测试 但是本地测试通过了 系统测试使用 BigInteger 通过测试了 为甚么会出现这个结果 这里面到底是什么原因 本人也没搞清楚 如有大神明白其中原理 希望多多指教
  • python装13的一些写法

    一些当你离职后 让老板觉拍大腿的代码 1 any in for in 判断某个集合元素 是否包含某个 某些元素 代码 if name main 判断 list1 中是否包含某个 某些元素 list1 1 2 3 4 a any x in 5
  • matlab 画折线图

    针对这篇博客里有些不够详细的地方 后期又写了一个稍微更详细的MATLAB画折线图 https blog csdn net Rhiney 97 article details 105000137 代码 效果图 x 1 1 5就是x轴上的数据
  • 模拟实现strstr函数

    目录 strstr函数介绍 使用strstr 出现 未出现 我的strstr模拟实现 代码 代码逻辑 现在来讲一下比较迷惑的点 结语 strstr函数介绍 在C语言的库函数里面有一个函数叫做strstr 这个函数的作用是在一个字符串中判断是
  • 【第59篇】RegNet:设计网络设计空间

    文章目录 摘要 1 介绍 2 相关工作 3 设计空间设计 3 1 设计空间设计的工具 3 2 AnyNet设计空间 3 3 RegNet设计空间 3 4 设计空间概化 4 分析RegNetX设计空间 5 与现有网络的比较 5 1 最先进的比
  • java基于SpringBoot+Vue+nodejs的高校自动排课系统 Element-UI

    自动排课系统也都将通过计算机进行整体智能化操作 对于自动排课系统所牵扯的管理及数据保存都是非常多的 例如 1 管理员 首页 个人中心 学生管理 教师管理 班级信息管理 专业信息管理 教室信息管理 课程信息管理 排课信息管理 系统管理 2 学
  • 微信外环境静态h5跳转小程序,如何传参?

    公司最近提了一个不常见的需求 就是在微信外环境静态h5跳转小程序并且还要传参 在查阅了大量资料后成功解决 官网地址 静态网站 H5 跳小程序 一 环境准备 1 开通微信云开发和静态网站 点击微信开发者工具中的云开发 现在是可以免费体验1个月
  • mysql中文乱码解决方案_Mysql中文乱码解决方案

    Mysql中文乱码解决方案 时间 2017 07 11 来源 华清远见JAVA学院 中文乱码问题一直是我们编程过程中比较常见又让人头疼的问题 但是只要按照标准进行配置 就能很好的避免出现中文乱码问题 今天小编就和大家分享下Mysql中文乱码
  • 统计学基础-数据的图表展示

    理论基础 什么是统计学 统计学分为描述统计和推理统计 统计学研究什么 统计学没有固定的研究对象 统计学研究的是来自各个领域的数据 靠解决其他领域内的工作而生存 我们并不能因此就看轻统计学 就好像计算机现在渗透在各行各业 我们生活中的方方面面
  • iOS支付宝支付接入的几个坑—以及解决办法

    因为近期项目中需要接入支付宝支付功能 自己也爬了很多的坑 所以做了一下这边文章供大家学习参考 远离爬坑 文章主要讲到以下五部分 一 支付宝开放平台创建应用 二 签约移动支付功能 三 接入支付前的准备工作附准备工作中遇到难题的解决方法 四 配
  • window.open打开新窗口报错ie 位指明错误,原因是window没有加引号!

    function JsMod htmlurl tmpWidth tmpHeight htmlurl getRandomUrl htmlurl var newwin window open htmlurl window height tmpH
  • VUE前端框架

    目录 vue 概述 MVVM框架 入门案例 创建HTML文件 并引入vue js 练习 Vue的基础语法 1 运算符 函数 2 解析类型丰富的data 3 data的三种写法 二 Vue的指令 1 概述 2 v model v cloak
  • Download-centos7-repo

    Setup Local Yum Repository On CentOS 7 使用ftp和createrepo来构建iso中的rpm包源 install and cache rpm package cat etc yum conf more
  • 【网络结构设计】6、CSPNet

    文章目录 一 背景 二 方法 2 1 DenseNet 网络结构 2 2 Cross Stage Partial DenseNet 2 3 将 CSPNet 和其他结构结合 三 效果 论文 CSPNet A new backbone tha
  • 数据库相关中间件收录集

    数据库中间件 这里主要介绍互联网行业内有关数据库的相关中间件 数据库相关平台主要解决以下三个方面的问题 为海量前台数据提供高性能 大容量 高可用性的访问 为数据变更的消费提供准实时的保障 高效的异地数据同步 应用层通过分表分库中间件访问数据
  • ajaxForm和ajaxSubmit

    ajaxForm和ajaxSubmit 1 AjaxForm ajaxForm不能提交表单 在document的ready函数中 使用ajaxForm来为AJAX提交表单进行准备 提交动作必须由submit开始 document ready
  • java kafka关闭连接_kafka的连接问题,我如何通过代码知道服务端的kafka服务是否开启?...

    我在电脑的虚拟机搭建了kafka服务 在本地使用Java客户端进行访问 现在假如虚拟机上的服务被我关闭 我在本地的代码就会无法去向kafka推送消息 并且会在1分钟后报一个timeout的错 我可以控制这个timeout的时间吗 我应该怎么
  • 域名与IP地址的联系与区别

    转载自一个好朋友的博客链接 略有修改 共同学习 共同进步 我们也知道每一台机都有一个唯一ip地址 特别难记 所以出现了今天的DNS 域名 当我们的计算机想要和一个远程机器连接时 我们可以申请连接该机器ip地址下的DNS 例如 www bai
  • STM32应用开发实践教程:基于 RS-485 总线的多机通信应用开发

    5 1 1 任务分析 本任务要求设计一个基于 RS 485 总线的多机通信系统 系统中有两台设备 理论上最多可 接入 32 台设备 其中一台设备作为主机 连接 OLED 显示屏 另一台设备作为从机 连接温 湿度传感器 DHT11 与 LED