初学单片坤之STM32串口入门

一、标准库

因为STM32的寄存器太多,操作起来比51复杂不少,所以就引出了标准库。简单理解标准库就是官方给32的寄存器以及其操作重新封装命令,以方面我们使用。
如图
请添加图片描述
图中的各种.c,.h 文件就是官方给的标准库文件(除开自己新建的文件)建议观看江协视频进行学习添加库文件操作。视频教学更为直接

二、标准库点亮LDE灯

与51类似,要想在32上点亮led灯,就需要给io口写电平。在32中的io我们叫做GPIO(General Purpose Input Output)通用输入输出口。

1、GPIO

IO内部结构图
请添加图片描述
IO口的八种工作模式
请添加图片描述
八种模式对应的代码
在这里插入图片描述
本次采用推挽输出点亮led灯

2、亮灯代码

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


//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口
int main()
{
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启A口时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;//GPIO结构体
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出模式
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//选择A口的第0个引脚
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//输出速度选择50mhz;
	
	GPIO_Init(GPIOA,&GPIO_InitStructure);//读取GPIO结构体参数,最后写入GPIO配置寄存器
	
	//GPIO_ResetBits(GPIOA,GPIO_Pin_0);//把指定端口设定为低电平
	
	//GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);//设置端口值
	
	while(1)
	{
		GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);//另一种设置端口低电平函数
		Delay_ms(500);
		GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
		Delay_ms(500);
	}
	
}

从代码种我们可以看出,我们并没有直接去操作GPIOx_CRL和GPIOx_CRH等寄存器。而是通过GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0直接选择引脚了,这样显而易见效率提升了不少。

三、串口发送“Hello,STM32”

1、USART结构框图

请添加图片描述
当给发送数据寄存器写入数据时,就会检查发送寄存器里面有没有正在移位的数据,如果没有,TDR里面的数据就会传到发送移位寄存器里。

当TDR里面的数据全部移到发送移位寄存器里,会置一个标志位TXE;如果TXE置1,又可以向TDR里面写入数据。同理当接收移位寄存器里的数全部移位到RDR种会,会有RXNE标志位被置位,标志着可以接收数据。清零操作皆为自动清零。

2、发送数据帧

请添加图片描述
从图中可以看出,每个数据帧都是以低电平起始位开始,高电平停止位结束。

3、代码

串口.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的时钟
	
	/*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);					//将PA9引脚初始化为复用推挽输出
	
	/*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_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


}

本次使用的是PA_9口去发送数据
因为需要使用串口,所以就需要串口初始化+IO口初始化
主函数.c

#include "stm32f10x.h"                  // Device header

#include "Serial.h"


//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口

int main()
{
	//OLED_Init();
	
	Serial_Init();
	
	//Serial_SendByte(0x41);//发送一个字节数据
	
//	uint8_t MyArray[]={0x42,0x43,0x44,0x45};
//	Serial_SendArray(MyArray,4);
	
	//Serial_SendString("hello world");//如果要进行换行就要Serial_SendString("hello world\r\n");
	
	//Serial_SendNumber(12345,5);
	
	//
	
//	char String[100];   //若多个串口都想要打印
//	sprintf(String,"Num=%d\r\n",666);
//	Serial_SendString(String);
//	
	
	//Serial_Printf("Num=%d\r\n",666);//可变参数发送函数
	
	
//	Serial_Printf("弄好");
//		Serial_SendByte(0x41);
	while(1)
	{
		printf("Hello,STM32");
	}
	
}

效果如下
请添加图片描述
注意自己所选的编码模式,如果与KEIL中选择的编码模式不同可能就会导致接收端数据有问题。
重点解释
printf函数重定向:在平时其他编译器上使用的printf函数都是显示在对应编译器的显示器上,但keil中没有,所以我们这里将它的打印对象重新定义到串口上。就能在串口调试助手上显示
编码选择操作
请添加图片描述

4、波形仿真图

请添加图片描述
我们用后的时间减去前的时间
请添加图片描述
得出1.041ms,转换us就是1041us。我们本次设置的波特率为9600,就相当于1/9600=104us,每一位二进制传输时间为104us,但这里为啥是1041?因为这是串口发送的一个数据帧,包含了一个起始位,一个一位停止位,再加上数据总共十位,所以就相当于10位传输速时间为1041us。

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

1、代码

#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


}

主函数

#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);
			}
		}
	}
	
}

PA_10为接收数据(RX),所以也需要初始化,同时还初始化了A6用作点灯

五、串口协议

串口协议是指在串行通信中双方设备之间约定的数据传输规则和通信方式。串口协议通常定义了数据帧格式、起始位、停止位、数据位、奇偶校验等参数,以及数据传输的时序和流程。常见的串口协议包括 RS-232、RS-485、UART 等。

1、RS-232标准的浅显理解

RS232协议是双极性标准。 逻辑0表示对应于+3至+ 12V模拟电压范围的SPACE条件; 逻辑1对应于-3至-12V的负电压范围,表示MARK条件。
而 TTL 通常采用正逻辑,即逻辑“0”对应于低电平,逻辑“1”对应于高电平(2.0V 至 5.0V)。

生活中RS232协议就是我们电脑主机后面插的那个九针接口即DB9
请添加图片描述
DB9又有公母之分,上面一个为母,下面一个为公
请添加图片描述
参考链接https://blog.csdn.net/qq_21805743/article/details/114544671?ops_request_misc=&request_id=&biz_id=102&utm_term=RS-232%E6%A0%87%E5%87%86&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-4-114544671.142v100pc_search_result_base8&spm=1018.2226.3001.4187

2、USB-TTL转232

通过USB转TTL(用到了CH340),再通过TTL转232电平(用到了MAX232)
参考链接
https://blog.csdn.net/chenhuanqiangnihao/article/details/113615276?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171551752616800186564837%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171551752616800186564837&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-113615276-null-null.142v100pc_search_result_base8&utm_term=USB%2FTTL%E8%BD%AC232&spm=1018.2226.3001.4187

https://blog.csdn.net/li128530/article/details/131734748?ops_request_misc=&request_id=&biz_id=102&utm_term=USB/TTL%E8%BD%AC232&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-2-131734748.142v100pc_search_result_base8&spm=1018.2226.3001.4187

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值