【STM32】串口通信入门

一、串口通信

(一)串口通信基本概念

1、串行/并行
通过数据传送的方式,可将通信的方式分为串行通信和并行通信两种。在这里插入图片描述

串行通信并行通信
定义利用单一传输线,按数据位形式一位一位地传输数据利用多条传输线,将数据的各位同时传送
传输方式传输一个字节(8个位)的数据时,串口将这8个位进行排队,然后逐个地在一条连接线上进行传输传输一个字节时(8个位)的数据时,并口将这8个位一字排开,分别在8条连接线上同时传输
比喻串行通信就像单车道公路,同一时刻只能传输一位数据并行通信就像多车道公路,同一时刻可传输多位数据
特点硬件资源耗费少、成本低、抗干扰能力强和传输距离远的优点,但存在传输速度慢的缺点有着传输速度快的优点,但存在硬件资源耗费高、成本高、抗干扰能力弱和传输距离近的缺点

2、单工/半双工/全双工
在这里插入图片描述

通信方式说明
单工在任何时刻都只能进行一个方向的通信,即一个固定为发送设备,另一个固定为接收设备
半双工两个设备之间可以收发数据,但不能在同一时刻进行
全双工在同一时刻,两个设备之间可以同时收发数据

3、同步/异步通信
根据通信中的数据同步方式,又分为同步和异步两种,可以根据通信过程中是否使用时钟信号进行简单的划分。

  • 同步通信:
    在这里插入图片描述
    在这里插入图片描述
    在同步通信中,收发设备双方会使用一根信号表示时钟信号,在时钟信号的驱动下,双方进行协调,同步数据。通信中通常双方会统一规定在时钟信号的上升沿或者下降沿对数据线进行采样。上图所示的就是在上升沿对数据线进行采样。
  • 异步通信:
    在这里插入图片描述
    在异步通信中,不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些信号位用于同步信号,或者把主题数据进行打包,以数据帧的格式传输数据。同时,收发双方需要约定数据的传输速率,以便正确地解码数据。

(二)串口协议

1、UART协议(异步串行通信协议)

在这里插入图片描述
串口通信的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位【可选】和停止位,通信双方的数据包格式统一是正常收发数据的必要条件之一。

  • 波特率
    UART是串口异步通信,因此没有时钟信号,所以两个通信设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。常用的波特率为4800、9600以及115200等。这里要注意区分:比特率和波特率。
  • 通信的起始信号和终止信号
    串口通信的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,前提是双方要约定一致。
  • 有效数据
    在数据包的起始位之后紧接着就是要传输的主体数据内容,亦称为有效数据。其长度常被约定为5、6、7或8位长。
  • 数据校验
    在有效数据之后,有一个可选的校验位。为避免通信过程中,受外界干扰而导致数据传输出现偏差的问题,可以通过在传输过程中加上校验位来解决该问题。校验方法有:奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)和无校验(noparity),共计5种方法。

2、RS-232协议

RS232协议是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家、计算机终端生产厂家共同制定的用于串行通讯的标准,该标准规定采用一个标准的连接器,标准中对连接器的每个引脚的作用加以规定,还对信号的电平加以规定。
在这里插入图片描述

  • 接口
    该标准规定采用一个25引脚的DB-25连接器,标准中对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定;后来IBM的PC机将RS232简化成了DB-9连接器,后来成为事实标准;现在工业控制的RS-232接口一般只使用RXD、TXD、GND三条线;
  • 信号
    该标准规定逻辑"1"的电平为-5V到-15V,逻辑"0"的电平为+5V到+15V,选用该电气标准的目的在于提高抗干扰能力,增大通信距离,其传输距离可达15m;

3、电平标准

根据使用的电平标准不同,串口通信可分为RS-232标准及TTL标准,具体标准如下:
在这里插入图片描述

4、USB/TTL转232

CH340是一个USB总线的转接芯片,实现USB转串口、USB转IrDA红外或者USB转打印口。为了增加串口通讯的远距离传输及抗干扰能力,RS-232标准使用-15V表示逻辑1,+15V 表示逻辑0。常常会使用MH340芯片对USB/TTL与RS- 232电平的信号进行转换。
CH340工作原理图:
CH340

二、标准外设库

为了解决不同芯片厂商生产的基于Cortex内核的微处理器在软件上的兼容问题,ARM公司与众多芯片和软件厂商共同制定了CMSIS标准(Cortex Microcontroller Software Interface Standard,Cortex微控制器软件接口标准),意在将所有Cortex芯片厂商产品的软件接口标准化。
在这里插入图片描述

(一)STM32标准外设库文件结构

在这里插入图片描述
在这里插入图片描述

(二)标准外设库遵从的命名规则

在这里插入图片描述

三、标准库点亮LED灯

要求:在上一次寄存器方式点亮LED灯的基础上,改用标准库方式,完成LED的点灯或流水灯实验。

与51类似,要想在32上点亮LED灯,就需要给IO口写电平。在32中的IO我们叫做GPIO通用输入输出口。
IO口的八种工作模式
在这里插入图片描述
八种模式对应的代码

输入模式输出模式
-输入浮空(GPIO_ Mode_ IN_ FLOATING)开漏输出(GPIO_ Mode_ Out _OD)
-输入上拉(GPIO_ Mode. IPU)-开漏复用功能(GPIO_ Mode. AF_ OD)
-输入下拉(GPIO_ Mode_ IPD)-推挽式输出(GPIO_ Mode_ Out PP)
-模拟输入(GPIO_ Mode_ AIN)-推挽式复用功能(GPIO_ Mode_ AF_ PP)

实验代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
 
 
int main(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启A口的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;//用的GPIO A的0号引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_SetBits(GPIOA,GPIO_Pin_0);
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);//配置低电平,若只需要点亮LED灯就只执行此条命令。
	while(1)
	{
		GPIO_Write(GPIOA,~0x0001);//0000 0000 0000 0001 PA0  0000 0000 0000 0010 PA2
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0002);//0000 0000 0000 0010
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0004);//0000 0000 0000 0100
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0008);//0000 0000 0000 1000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0010);//0000 0000 0001 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0020);//0000 0000 0010 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0040);//0000 0000 0100 0000
		Delay_ms(500);
		GPIO_Write(GPIOA,~0x0080);//0000 0000 1000 0000
		Delay_ms(500);
		GPIO_Write(GPIOB,~0x0001);//0000 0000 0000 0001 PA0  0000 0000 0000 0010 PA2
		Delay_ms(500);	
	}
}

从代码中我们可以看出,我们并没有直接去操作GPIOx_CRL和GPIOx_CRH等寄存器。而是通过
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0直接选择引脚了,这样显而易见效率提升了不少。
keil波形图
在这里插入图片描述

实验结果
在这里插入图片描述

四、标准库的查询方式

要求:采用标准库的查询方式(暂不使用中断方式)设置波特率为9600,1位停止位,无校验位,并给上位机连续发送"hello windows!"。上位机(win10)采用串口助手工具接收。

(一)设置波特率

1、串口初始化

void Usart2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能串口2时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //选中串口默认输出管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //定义输出最大速率
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//定义管脚9的模式
GPIO_Init(GPIOA, &GPIO_InitStructure); //调用函数,把结构体参数输入进行初
始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口
USART_InitStructure.USART_BaudRate = 115200; //速率
115200bps
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位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_Init
进行初始化
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
USART_Cmd(USART2, ENABLE);//开启USART2,注意与上面RCC_APB2PeriphClockCmd()设置的区}

2、修改波特率

void USART_Config(uint32_t baud)
{
USART_InitTypeDef USART_InitStructure;
USART_Cmd(USART2, DISABLE);
USART_InitStructure.USART_BaudRate =baud;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);
}

(二)通过串口发送“hello windows!”

#include "sys.h"
#include "usart.h"
#include "delay.h"
int main(void)
{
u16 t; u16 len; u16 times=0;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,115200); //串口初始化为115200
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
printf("\r\n Hello Windows! \r\n\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%200==0)printf("Hello Windows!\r\n");
delay_ms(10);
}
}
}

实验结果
在这里插入图片描述
keil波形图
在这里插入图片描述

五、STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。

代码如下
串口函数Serial.c

#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIOA_9初始化*/
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); //将PA9引脚初始化为复用推挽输出
//GPIOA_10
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);
//选择A6为亮灯口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
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_Parity = USART_Parity_No; //奇偶校验,不需要
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使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
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]);
}
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *string)
{
uint8_t i;
for(i=0;string[i]!='\0';i++)
{
Serial_SendByte(string[i]);
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于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');//由十进制的高位到低位依次发送
//加上'0'就转换为ascll码的类型,从ascll码中字符0的位置开始算,也可以改为0x30.
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/ //将printf的底层重定向到自己的发送字节函数
//什么是重定向?重定向是指将fputc里面的输出指向目标设备。因printf函数调用了fputc,
//而fputc输出有默认指向的目标,且不同库中的fputc输出指向不同,所以需要重写fputc
int fputc(int ch,FILE *f) //printf重定向,为串口
{
Serial_SendByte(ch);
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
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);//发送string
}

主函数main.c

#include "stm32f10x.h" // Device header
#include "Serial.h"
//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口
uint8_t KeyNum;
uint8_t RxData;
int main()
{
	OLED_Init();
	Serial_Init();
	GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);//将A6口初始化为电平
//GPIO_SetBits(GPIOA,GPIO_Pin_6);另一种初始化函数
	while(1)
	{
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)//若接收寄存器数据转到RDR中,则RXNE标志位置一,标志位自动清零
		{
		RxData=USART_ReceiveData(USART1);
			if(RxData=='Y')
			{GPIO_ResetBits(GPIOA,GPIO_Pin_6);}
			if(RxData=='N')
			{GPIO_SetBits(GPIOA,GPIO_Pin_6);}
		}
	}
}
  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值