STM32--USART串口

文章目录

通信接口

通信接口是指连接中央处理器(CPU)和标准通信子系统之间的接口,用于实现数据和控制信息在不同设备之间的传输和交换。通信接口可以是硬件或软件实现,其目的是使不同设备之间能够进行有效地通信。

上图是常见的通用通信类型。

双工指的是接口能够实现双向数据传输,即可以同时进行发送和接收数据的功能。

半双工:数据在同一个通信信道上交替地进行双向传输,但不能同时进行发送和接收。发送方和接收方需要在不同时间段进行数据传输。

全双工:数据可以同时进行双向数据传输,发送方和接收方可以在同一时间段进行数据发送和接收。通信双方可以独立地进行发送和接收工作。

时钟是指在传输数据是否能达到同步传输。

同步传输 :发送方和接收方之间使用一个共同的时钟信号来同步数据的传输。发送方根据时钟信号将数据按照固定的速率发送,接收方也按照相同的时钟信号来接收数据。
异步传输:数据的传输不依赖于共同的时钟信号,而是使用起始位、停止位和数据位之间的固定时间间隔进行同步。发送方会在发送数据之前先发送一个起始位作为同步信号,接收方通过检测起始位来确定数据的开始和结束。

电平是指传输信号时的电压信号形式。

单端传输 :信号传输使用单个导线进行,信号的电压相对于某个参考电平变化来表示。这意味着信号只有一个极性,数据通过高低电平表示。在单端传输中,由于信号线未与其他导线耦合,容易受到电磁干扰的影响,可能导致信号质量的下降。
差分传输:信号的传输使用两个导线进行,信号通过两个相互补充的电压信号的差异来表示。差分传输具有很强的抗干扰能力,可以减少电磁干扰对信号的影响。

串口通信

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信;适用于近距离的通信。

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。

硬件电路

简单双向串口通信有两根通信线(发送端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(差分信号)

参数

波特率:串口通信的速率,可以指定每秒传输的位数

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

数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

校验位:用于数据验证,根据数据位计算得来

常用奇偶校验位:通过在数据中添加一个附加位(校验位),以确保接收端可以检测到传输过程中的错误。
奇校验 :如果数据位中1的个数为偶数,则校验位设置为1,使得数据位和校验位中的1的总和保持奇数。如果数据位中1的个数为奇数,那么校验位置0。
偶校验:如果数据位中1的个数为偶数,则校验位置0,使得数据位和校验位的总个数为偶数。如果数据位中1的个数为奇数,校验位置1 。

停止位:用于数据帧间隔,固定为高电平

时序

输入不同的数据,显示不同的时序。

USART

通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。

主要特性

1.全双工的,异步通信

2.分数波特率发生器系统

─ 发送和接收共用的可编程波特率,最高达4.5Mbits/s

3.可编程数据字长度(8位或9位)

4.可配置的停止位-支持1或2个停止位

5.校验控制

─ 发送校验位

─ 对接收数据进行校验

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

框图

先看左上角的接口处 ,TX与RX就是发送和接收的引脚,下面的三个接口是智能卡和IrDA通信的引脚。

发送脚就是通过发送移位寄存器送出去的,接收脚就是将接收数据送到接收移位寄存器的。

灰色部分就是串口的数据寄存器 。有两个数据寄存器,一个用来发送数据,一个用来接收数据;在程序上,只表现为一个寄存器,它们寄存器共用地址进行存储。

当你进行写入操作时,数据就写到TDR,当你进行读取操作时,数据就从RDR进行读出。

下面是移位寄存器,作用是把一个字节的数据一位一位进行位移。

假设 你在某时刻给TDR写入数据0x66,在二进制存储就是01100110,此时硬件会检测到你写入的数据,就会检查移位寄存器是否有数据正在移位,如果没有01100110就会立刻放入移位寄存器中,此时也会置出一个标志位TXE,发送寄存器为空;有了这个标志我们就可以在TDR再写入一个数据了。移位寄存器在发送器控制的情况下,将数据一位一位进行传输到TX引脚进行输出(低位先出),当数据全部移位后,新的数据就会再次从TDR转移到移位寄存器中来,如果移位寄存器还没有完成,那么TDR会等移位寄存器完成移位之后才将数据转移。有了TDR和移位寄存器双重缓存,可以保证数据发送时,数据帧之间没有空闲。

而接收部分也是同样的道理,接收移位寄存器由接收器控制,低位先放到移位寄存器的高位,随着数据的增加而右移,当移位寄存器数据达到一个字节后,传输给接收寄存器RDR,此时也会有一个标志位RXNE置1,意味着接收寄存器有数据了,我们可以对DR寄存器的数据进行读走。

接着看到下面,有一个硬件数据流控 ,如果发送设备发送太快,接收设备来不及处理,可以通过流控来控制传输的速度。

它有两个引脚,一个是nRTS,另一个是nCTS。

nRTS是请求发送,是输出脚,就是告诉别人,我当前能不能接收;

nCTS是清除发送,是输入脚,用于接收别人nRTS的信号;

接着看右边的SCLK ,这是一个产生同步的时钟信号,它是配合发送移位寄存器输出的,发送移位寄存器每发送一次,同步时钟电平就跳变一个周期,时钟会告诉对方我已经移出去一个数据了,你看要不要让我这个时钟信号来指导你接收一下?当然这个时钟只支持输出,不支持输入,所以两个SWART不能实现同步的串口通信。

主要用途是兼容别的协议或者做自适应波特率。

接着看到中间的唤醒单元 ,这部分的作用是实现串口挂载多个设备。

中断控制,支持对标志位标志的地方进行中断。

最下面的波特率发生器部分,其实就是分频器,对APB时钟进行分频,得到发送和接收移位的时钟。

数据帧

字长可以通过编程USART_CR1寄存器中的M位,选择成8或9位(见图249)。在起始位期间, TX脚处于低电平,在停止位期间处于高电平。

发送器

发送器根据M位的状态发送8位或9位的数据字。当发送使能位(TE)被设置时,发送移位寄存器中的数据在TX脚上输出,相应的时钟脉冲在CK脚上输出。

USART支持多种停止位的配置: 0.5、 1、 1.5和2个停止位。

波特率发生器

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定

计算公式:波特率 = fPCLK2/1 / (16 * DIV)

假设我们要输出波特率为9600,那么通过计算DIV=72M/16/9600=468.75

所以置于波特率寄存器中的值为468.75.

SWART串口发送与接收工程

OLED代码链接入口

接线方式:

发送时需要一个串口助手在电脑来显示内容,我们将实现STM32与电脑之间的数据传输。以STM32为主机。

Serial.h

c 复制代码
#ifndef __SERIAL_H__
#define __SERIAL_H__

#include <stdio.h>

void Serial_Init();
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t* Arr,uint8_t Lenth);
void Serial_SendString(char* String);
void Serial_SendNumber(uint32_t Number,uint8_t Lenth);
uint8_t Serial_GetRxFlag();
uint8_t Serial_GetRxData();

#endif 

Serial.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,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开启中断
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//中断源RXNE
    
    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);
}

//发送数组
void Serial_SendArray(uint8_t* Arr,uint8_t Lenth)
{
    uint8_t i;
    for(i=0;i<Lenth;i++)
    {
        Serial_SendByte(Arr[i]);
    }
}

//发送字符串
void Serial_SendString(char* String)
{
    uint8_t i;
    for(i=0;String[i]!='\0';i++)
    {
        Serial_SendByte(String[i]);
    }
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
    uint32_t Result=1;
    while(Y--)
    {
        Result*=X;
    }
    return Result;
}

void Serial_SendNumber(uint32_t Number,uint8_t Lenth)
{
    uint8_t i;
    for(i=0;i<Lenth;i++)
    {
        Serial_SendByte(Number/Serial_Pow(10,Lenth-1-i)%10+'0');
    }
}

//获取接收状态
uint8_t Serial_GetRxFlag()
{
    if(Serial_RxFlag==1)
    {
        Serial_RxFlag=0;
        return 1;
    }
    return 0;
    
}
//获取接收数据
uint8_t Serial_GetRxData()
{
    return Serial_RxData;
}

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

初始化输出需要配置复用功能,因为SWART为片上外设。

发送字节需要用TXE标志位来表示已经从DR寄存器发送到移位寄存器中。所以用到while来进行等待;

接收时,利用RNXE产生的标志位来产生中断,在中断函数中获取DR寄存器的数据。

main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "Serial.h"
#include "OLED.h"
int main()
{
    uint8_t Rxdata;
	OLED_Init();
	Serial_Init();
   
    OLED_ShowString(1,1,"RxData:");
    
    while(1)
    {
        if(Serial_GetRxFlag()==1)
        {
            Rxdata=Serial_GetRxData();
            Serial_SendByte(Rxdata);
            OLED_ShowHexNum(1,8,Rxdata,2);
        }
    }
    
}

串口收发数据包

连接方式:

利用连续的数据序列来对LED灯的亮灭进行控制;

Serial.h

c 复制代码
#ifndef __SERIAL_H__
#define __SERIAL_H__

#include <stdio.h>
#include <string.h>

extern uint8_t Serial_RxFlag;
extern char Serial_RxPacket[];

void Serial_Init();
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t* Arr,uint8_t Lenth);
void Serial_SendString(char* String);
void Serial_SendNumber(uint32_t Number,uint8_t Lenth);
void Serial_Printf(char* format,...);

#endif 

Serial.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>


char Serial_RxPacket[100];
uint8_t Serial_RxFlag;

void Serial_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,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开启中断
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//中断源RXNE
    
    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);
}

//发送数组
void Serial_SendArray(uint8_t* Arr,uint8_t Lenth)
{
    uint8_t i;
    for(i=0;i<Lenth;i++)
    {
        Serial_SendByte(Arr[i]);
    }
}

//发送字符串
void Serial_SendString(char* String)
{
    uint8_t i;
    for(i=0;String[i]!='\0';i++)
    {
        Serial_SendByte(String[i]);
    }
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
    uint32_t Result=1;
    while(Y--)
    {
        Result*=X;
    }
    return Result;
}
//发送数字
void Serial_SendNumber(uint32_t Number,uint8_t Lenth)
{
    uint8_t i;
    for(i=0;i<Lenth;i++)
    {
        Serial_SendByte(Number/Serial_Pow(10,Lenth-1-i)%10+'0');
    }
}

void Serial_Printf(char* format,...)
{
    char String[100];
    va_list arg;
    va_start(arg,format);
    vsprintf(String,format,arg);
    va_end(arg);
    Serial_SendString(String);
}




//串口接收中断(接收一位数据中断一次),接收数据包
void USART1_IRQHandler()
{
    static uint8_t RxState=0; //获取当前状态
    static uint8_t pRxPacket=0; //数据包有效位下标
    if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
    {
        uint8_t RxData=USART_ReceiveData(USART1);
        if(RxState==0)
        {
            if(RxData=='&'&& Serial_RxFlag==0)
            {
                RxState=1;
                pRxPacket=0;
            }
        }
        else if(RxState==1)
        {            
            if(RxData=='\r')
            {
                
                RxState=2;
            }
            else
            {
                Serial_RxPacket[pRxPacket++]=RxData;
                
            }
        }
        else if(RxState==2)
        {
            if(RxData=='\n')
            {
                RxState=0;
                Serial_RxPacket[pRxPacket]='\0';
                Serial_RxFlag=1;
            }
        }
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);
    }
}

对于接收的数据包,利用了一种状态机的方式进行接收

通过包头和包尾对数据有效位进行保护,可以让一连串重复的数据可以找到数据位,避免找不到数据位的头的情况。

main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Serial.h"
#include "OLED.h"
int main()
{
    
	OLED_Init();
	Serial_Init();
    LED_Init();
    
    OLED_ShowString(1, 1, "RxPacket:");
   
	         
    while(1)
    {
        
        if(Serial_RxFlag==1)
        {
            
           
            if(strcmp(Serial_RxPacket,"LED_ON")==0)
            {
                LED1_ON();
                OLED_ShowString(2,1,"LED_ON_OK   ");
                Serial_SendString("LED_ON_OK");
            }
            else if(strcmp(Serial_RxPacket,"LED_OFF")==0)
            {
                LED1_OFF();
                OLED_ShowString(2,1,"LED_OFF_OK   ");
                Serial_SendString("LED_OFF_OK");
            }
            else
            {
                OLED_ShowString(2,1,"ERROR_COMMAND");
                Serial_SendString("ERROR_COMMAND");
            }
            Serial_RxFlag=0;
        }
       

    }
    
}

LED.c

c 复制代码
#include "stm32f10x.h"                  // Device header

void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
}

void LED1_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}

void LED1_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

void LED1_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_1);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
	}
}

void LED2_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}

void LED2_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_2);
}

void LED2_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_2);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_2);
	}
}

LED.h

c 复制代码
#ifndef __LED_H__
#define __LED_H__

void LED_Init();
void LED1_ON();
void LED1_OFF();
void LED1_Turn();
void LED2_ON();
void LED2_OFF();
void LED2_Turn();

#endif
相关推荐
BreezeJuvenile6 分钟前
外设模块学习(5)——DS18B20温度传感器(STM32)
stm32·嵌入式硬件·学习·温度传感器·ds18b20
hollq3 小时前
STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu实现串口重定向
arm开发·stm32·嵌入式硬件
小鱼儿电子4 小时前
17-基于STM32的宠物饲养系统设计与实现
stm32·嵌入式硬件·物联网·宠物·宠物饲养系统
小莞尔6 小时前
【51单片机】【protues仿真】基于51单片机四层电梯系统
单片机·嵌入式硬件
CFZPL6 小时前
使用江科大串口发送函数发送freertos的vTaskList出现跑飞
单片机
F133168929576 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·51单片机·硬件工程·pcb工艺
易享电子8 小时前
基于单片机电器断路器保护器系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·fpga开发·51单片机·proteus
爱倒腾的老唐10 小时前
01、如何学习单片机
单片机·嵌入式硬件·学习
点灯小铭10 小时前
基于单片机的夹具压力控制系统设计
单片机·嵌入式硬件·mongodb·毕业设计·课程设计
雾削木16 小时前
stm32解锁芯片
javascript·stm32·单片机·嵌入式硬件·gitee