【STM32】USART串口协议


STM32通信接口

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

STM32通信接口:

名称引脚双工时钟电平设备
USARTTX、RX全双工异步单端点对点
I2CSCL、SDA半双工同步单端多设备
SPISCLK、MOSI、MISO、CS全双工同步单端多设备
CANCAN_H、CAN_L半双工异步差分多设备
USBDP(D+)、DM(D-)半双工异步差分点对点

串口通信

串口的基本概念

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口 (Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信(因为一般串口都采用点对点的通信协议,所以是实现两个设备之间的通信)
  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

串口设备示例:

在这里插入图片描述

串口硬件电路

  • 简单双向串口通信有两根通信线(发送端TX和接收端RX)

  • TX与RX要交叉连接

  • 当只需单向的数据传输时,可以只接一根通信线

  • 当电平标准不一致时,需要加电平转换芯片

    电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

    • TTL电平(最常用):+3.3V 或 +5V 表示 1,0V 表示 0

    • RS232电平:-3 ~ -15V表示1,+3 ~+15V表示0(一般在大型机器上使用)

    • RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号,抗干扰能力强,通讯距离长)

    在硬件电路上,协议规定,一个设备使用 TX 发送高低电平,另一个设备使用RX 接收高低电平

在这里插入图片描述

串口参数及时序

串口数据帧的基本结构

串口中每一个字节都装载在一个数据帧里面,每个数据帧都由起始位、数据位(奇偶校验位)和停止位组成

在这里插入图片描述

串口参数

  • 波特率:串口通信的速率(因为串口是异步通信,所以需要通信双方约定一个通信频率)

  • 起始位:标志一个数据帧的开始,固定为低电平

  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行(数据从低位开始发送)

  • 校验位:用于数据验证,根据数据位计算得来(串口使用奇偶校验的方法)

    • 无校验:如上图 串口数据帧的基本结构 图中左边结构所示,没有校验位
    • 奇校验:发送方在发送数据后会补一个校验位,保证 1 的个数为奇数,接收方在接收数据后会验证数据位和校验位
    • 偶校验:保证 1 的个数为偶数

    奇偶校验的检出率不高,如果两位同时出错就不能检验出来

  • 停止位:用于数据帧间隔,固定为高电平(停止位也是为下一个起始位做准备)

通过以上介绍,应该就清楚数据帧是如何使用 1 和 0 来组成我们想要发送的一个字节数据的

USART介绍

USART基本概念

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器

  • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里

    可以理解为:USART外设就是串口通信的硬件支持电路,USART大体可分为发送和接收两部分,发送部分就是将数据寄存器的一个字节数据自动转换成协议规定的波形,从 TX 引脚发出去,接收部分就是自动接收RX引脚的波形,按照协议规定解码为一个字节的数据存放在数据寄存器里

  • 自带波特率发生器,最高达4.5Mbits/s

    波特率发生器就是用于配置波特率的,可以视为一个分频器

  • 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)

  • 可选校验位(无校验/奇校验/偶校验)

  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

STM32F103C8T6 USART资源: USART1(APB2)、 USART2(APB1)、 USART3(APB1)

USART框图

在这里插入图片描述

USART简化结构图

在这里插入图片描述

USART常用库函数

/*初始化相关函数*/
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

/*用于配置同步时钟输出的函数*/
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
//用于开启USART到DMA的触发通道void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
//用于发送数据(写DR寄存器)uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
//用于接收数据(读DR寄存器)

/*标志位相关函数*/
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

串口发送

接线图

在这里插入图片描述

执行程序之前需要先确保串口驱动成功

在这里插入图片描述

Strial模块

Strial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>

void Serial_Init(void){
	
    /*第一步:开启时钟,把需要用的USART和GPIO的时钟打开*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
    /*第二步:GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
    /*配置USART,直接使用一个结构体*/
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;	//波特率的数值可以直接写,USART_Init函数内部会自动计算好对应的分频系数
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
    /*开启USART*/
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

Strial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);

#endif

【补充】

串口常用的模块函数

  • 发送数组函数

    void Serial_SendArray(uint8_t* Array, uint16_t Length){
        uint16_t i;
        for(i = 0; i < Length; i++){
            Serial_SendByte(Array[i]);
        }
    }
    
  • 发送字符串函数

    void Serial_SendString(char* String){//由于字符串自带一个结束标志位,所以不需要传递长度参数
        uint8_t i;
        for(i = 0; String[i] != '\0'; i++){
            Serial_SendByte(String[i]);
        }
    }
    
  • 发送字符串形式的数字

    uint32_t Serial_Pow(int32_t X, uint32_t Y){	//函数返回值为X的Y次方
        int32_t Result = 1;
        while(Y--){
            Result *= X;
        }
        return Result;
    }
    
    void Serial_SendNumber(uint32_t Number, uint8_t Length){
        uint8_t i;
        for(i = 0; i < Length; i++){
            Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
        }
    }
    

printf( ) 函数的移植

使用 printf 之前打开工程选项,将 Use MicroLIB 勾选上

在这里插入图片描述

MicroLIB 是 Keil 为嵌入式平台优化的一个精简库

方法一

对 printf 进行重定向

因为 printf 函数默认输出到屏幕,但是单片机没有屏幕,所以要进行重定向,将 printf 输出的东西输出到串口

  • Step1:在串口模块(Strial.c 和 Strial.h)中加上 #include <stdio.h> 头文件

  • Step2:在串口模块(Strial.c)中重写 fputc 函数

    int fputc(int ch, FILE *f){
        Serial_SendByte(ch);
    	return ch;
    }
    

    fputc 函数是 printf 函数的底层, printf 函数在打印的时候就是不断调用 fputc 函数一个个打印的

这种 printf 移植的缺点是 printf 只能有一个,如果 printf 被重定向到串口1了,那么串口2就不能使用了

方法二

想要多个串口都可以使用 printf 函数,可以使用 sprintf

sprintf 可以把格式化字符输出到一个字符串里

  • Step1:直接在 main.c 函数中定义一个字符串(长度给够)

  • Step2:调用 sprintf 和 Serial_SendString 函数实现 printf 函数移植

    	char String[100];
    	sprintf(String, "Num = %d\r\n", 666);
    	Serial_SendString(String);
    

    sprintf 函数第一个参数是指定打印的位置,利用 sprintf 将要打印的内容传送到指定位置后再利用 Serial_SendString(char* String) 函数将要打印的内容通过串口发送出去

    因为 sprintf 可以指定打印位置,不涉及重定向的东西,所以每个串口都可以使用 sprintf 进行格式化打印

main.c 源程序

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main(void){	
	
	OLED_Init();
	Serial_Init();
	
	Serial_SendByte(0x41);
    
	//uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};
	//Serial_SendArray(MyArray, 4);
	
	//Serial_SendString("HelloWorld!");
	
	//Serial_SendNumber(12345, 5);
	
	//printf("Num = %d\r\n",666);	
	
	//char String[100];
	//sprintf(String, "Num = %d\r\n", 666);
	//Serial_SendString(String);
	
	while(1){
		
	}
}

串口发送+接收

接线图

和串口发送接线图一样

Strial模块

strial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init(void){
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}


uint8_t Serial_GetRxData(void){
		return Serial_RxData;
}

uint8_t Serial_GetRxFlag(void){
	if(Serial_RxFlag == 1){
		Serial_RxFlag = 0;
		return 1;
	}else{
		return 0;
	}
}

void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}


serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

main.c 源程序

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void){	
	
	OLED_Init();	
	OLED_ShowString(1, 1, "RaData:");
	
	Serial_Init();
	
	while(1){
		if(Serial_GetRxFlag() == 1){
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
}


STM32 专栏文章均参考 《STM32入门教程-2023版 细致讲解 中文字幕》教程视频

<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内容。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`容错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值