串口通信 TTL 232 485

备注:主要记录自己的学习内容,大部分内容来自于网络。单片机推荐B站江科大。

串口是一种应用十分广泛的通讯接口,串口成本低,容易使用、通信线路简单,可实现两个设备的互相通信,可以用来调试。

全双工:通信双方可以在同一时刻互相传输数据

半双工:通信双方可以互相传输数据,但必须分时复用一根数据线

单工:通信只能有一方发送到另一方,不能反向传输

异步:通信双方各自约定通信速率

同步:通信双方靠一根时钟线来约定通信速率

总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)

串口是全双工异步通信


一、硬件电路

简单双向串口通信有两根通信线(发送端TX和接收端RX。两根通信线的高低电平都是根据GND的,所以严格来讲GND也算是通信线。所以VCC并不需要接在一起)

TX与RX要交叉连接

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

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


二、串口参数及时序

每一个字节都装载在一个数据帧里面。每个数据帧由起始位数据位停止位组成。

波特率:串口通信速率(波特率的计算公式后续补充) 想要通过串口使两个设备之间通信,必须使用相同的波特率,比如约定每秒发送接受1位,如果某一方处理速度快了,会重复接受。波特率的意思是每秒传输码元的个数,单位:码元/s。在二进制调制的情况下,一个码元就是一个bit。1.5个停止位就是这么来的,刚开始学习的时候见到0.5或者1.5个停止位肯定不明白,其实就是停止位持续半个周期的时间

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

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

校检位:用于数据验证,根据数据计算得来。奇校验:包括校检位在内的9个数据位1的个数为奇数。例如8位数据位里1的个数为偶数,那么校检位为1,让1的个数为奇数。奇偶校验只能保证一定的检出率,如果想要保证更高的检出率,可以了解下CRC

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

电平的高低反转是USART外设自动完成的,不需要我们去做,当然也可以用软件模拟,但是串口通信基本上都是通过硬件实现的。


三、stm32单片机上USART外设

硬件部分

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器(S就是同步的意思,一般很少用,所以UART跟USART没太多区别)

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

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

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

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

支持同步模式、硬件流控制(多出一根线,准备好了才发数据,防止因为处理太慢数据丢失)、DMA、智能卡、IrDA、LIN

STM32F103C8T6 USART资源: USART1、 USART2、 USART3

主要用TX RX两根线。TDR,RDR只表示成了一个寄存器,物理地址是一样的,读的时候用RDR,写的时候TDR。两个移位寄存器没有数据,数据从DR里进入移位寄存器,置标志位TXE,RXE为1,此时数据没有传输出去,但已经可以写入新数据了。

硬件数据流控就是进行硬件流控制的,有两个引脚。主机做接收端时,RTS接从机的CTS。当主机能接收时,RTS置低电平,请求对方发送。RTS置高电平,对方停止发送。

SCLK模块用于产生同步的时钟信号,发送寄存器每移位一次同步寄存器就跳变一个周期,时钟只支持输出。可以做同步通信,测波特率。

唤醒单元用来支持串口连接多设备,在USART地址寄存器里写从机地址,

中断控制,设置中断能否通过NVIC。TXE发送寄存器空,RXNE接受寄存器非空,这两个是比较重要的标志位。

USART_BRR是波特率设置部分,fpclkx(挂载在APB2就写2,一般是72Mhz。APB1就写1,一般是36),然后进行DIV分频。分频系数支持小数点后四位。分频完再除16,得到发送器时钟,接收器时钟。RE,TE为1,表示该部分使能,波特率有效。

接收数据从移位寄存器到DR里时,会置RXNE标志位,可以用来申请中断。两个移位寄存器,两个数据寄存器,但是在软件上之表现为一个DR寄存器。

数据帧部分

选择数据帧最好是9位字长有校验或者8位字长无校验。

停止位0.5 1 1.5 2个。

起始位侦测

发送只需定时反转高低电平,接受要比发送复杂。输入部分的电路可以以16倍的波特率采样(因为有时候有噪声),详细看数据手册吧。

波特率

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定 计算公式:波特率 = fPCLK2/1 / (16 * DIV)

代码

//库函数实现串口初始化、发送和接受。
//寄存器版本,HAL版本后续补充

/* 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);

//使用DMA
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

//发送数据接收数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

//标志位相关函数
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);

*/

/**
  *@prief    串口初始化
  *@param    无
  *@retval    无
  */
void Serial_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟

    GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        /复用推挽输出 TX
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA9引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;            // 上拉输入 RX空闲为高电平
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
    

    /*USART初始化*/
	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_StopBits = USART_StopBits_1;	//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);				//将结构体变量交给USART_Init,配置USART1
    
    //如果需要配置中断
/*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设

    /*USART使能*/
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行
    
}
/**
  *@brief:串口发送一个字节
  *param:Byte 要发送的一个字节
  *retval:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}


/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
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');	//依次调用Serial_SendByte发送每位数字
	}
}

(这一部分主要是江科大的源码,需要的自己去B站找)


四、RS232 RS485


串口缺点

串口的电气接口不统一。只是对时序做了定义,并没有规定电气特性

串口通信时直接使用的是处理器的电平,即TTL电平,但是不同的处理器使用的电平存在差异,所以不同的处理器使用UART通信时一般不能直接连接。比如stm32的串口和51单片机的串口尽量不要直接通信。因为32的高电平是3.3V,串口认为高电平是3.3V。同理51使用的是5V,所以最好不要直接连。

抗干扰能力差,因此通信距离也短。3-5米就会出问题。

232协议

是基于串口的协议。常用DB9插头。该协议规定-5v到-15v为逻辑1,5v到15V为逻辑0,这样传输距离可以到达15m,抗干扰能力增强。

通信双方都连接一个232芯片,把TTL转成232,再把232转成TTL。

232存在的问题

接口的信号电平值比较高,易损坏接口电路的芯片,又因为与TTL不兼容,所以需要电平转换芯片才能与TTL电路连接

通信速度比较低

容易产生共模干扰

传输距离还是不够

485协议

同样是基于串口的,规定使用差分信号进行传输,两线压差2v到6v表示逻辑1,-2v到-6v表示逻辑0。使用差分信号能延长通信距离,抗干扰能力加强(因为同一个干扰会被做差去掉),可以连接多个从机而且不容易损害电气设备。(因为是两根信号线的差分信号,所以485是半双工的)

无论是232还是485,在软件层面上还是串口的程序。

本篇文章只是自己学习内容的记录,偶尔复习一下很方便,所以有很多内容是借鉴的别人的。如果大家想学习单片机,推荐B站上江科大的视频。

  • 42
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值