江科大笔记—串口发送&串口发送+接收

串口发送

在这里插入图片描述
下面这个是我们的USB转串口的模块,这里有个跳线帽,上节也说过,要插在VCC和3V3这两个脚上,选择通信的TTL电平为3.3V,然后通信引脚,TXD和RXD,要接在STM32的PA9和PA10口。为什么是这两个口呢,我们看一下引脚定义表就知道USART1的TX是PA9, RX是PA10,我们计划用USART1进行通信,所以就选这两个脚。TX和RX交叉连接,这边一定要注意,别接错了。然后,两个设备之间要把负极接在一起,进行共地,一般多个系统之间互连,都要进行共地。最后,这个串口模块和STLINK都要插在电脑上,这样,STM32和串口模块都有独立供电,所以这里通信的电源正极就不需要接了。
在这里插入图片描述
USART1的TX是PA9, RX是PA10

当然我们第一个代码,只有STM32发送的部分,所以,通信线只有这个发送的有用,另一根线,第一个代码没有用到,暂时可以不接,在我们下一个串口发送+接收的代码,两根通信线就都需要接了。所以我们把这两根通信线一起都接上吧,这样两个代码的接线图是一模一样的。

在这里插入图片描述

库函数

在这里插入图片描述

//这个两个用来配置同步时钟输出
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
//可以开启USART到DMA的触发通道
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);//发送数据,SendData就是写DR寄存器
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);//接收数据,ReceiveData就是读DR寄存器

第一步,开启时钟,把需要用的USART和GPIO的时钟打开
第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入
第三步,配置USART,直接使用一个结构体,就可以把这里所有的参数都配置好了
第四步,如果你只需要发送的功能,就直接开启USART,初始化就结束了。如果你需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。
那初始化完成之后,如果要发送数据,调用一个发送函数就行了;如果要接收数据,就调用接收的函数;如果要获取发送和接收的状态,就调用获取标志位的函数,这就是USART外设的使用思路。

串口助手

这里必须要插上串口,且串口是空闲的状态,才能扫描到对应的串口号。这里的参数需要和代码初始化参数一致。波特率:9600,数据位:8位,停止位,1位,校验位:无。然后打开串口。
这里是NEX模式,以原始数据的形式显示。
在这里插入图片描述

显示字符串,用文本模式

在这里插入图片描述

数据模式

在这里插入图片描述
ASCII字符集
做数据和字符串相互转换时,需要查一下这个表。
在这里插入图片描述
介绍printf函数移植方法
在使用printf之前,打开工程选项,在TArget选项中,把Use MicroLIB勾上

在这里插入图片描述

汉字乱码接解决方案

显示汉字的操作方法,printf打印会乱码,解决方案:这个的汉字编码格式,选择的是UTF8,最终发送到串口,汉字会以UTF8的方式编码,最终串口也得选择UTF8,才能解码正确。

UTF8不乱码的方案,比如这里写,“你好,世界”字符串,这样直接写汉字,编译器有时会报错,解决方法,打开工程选项,C/C++ 这里杂项控制栏。 写好了之后,在串口助手这,选择编码方式,是UTF8。这是UTF8解决方案

在这里插入图片描述

但是UTF8可能有些软件兼容性可能不好,所以第2种方式是,切换为GB2312编码。在这里选择配置。完成之后,汉字还是UTF8,需要把“你好世界“删掉,再把当前文件关掉,重新打开,等字体变为宋体了,编码格式才是改过来的,在重新输入汉字,这样编码才是GB2312。
打开串口助手,在串口助手选择GBK编码,一般window软件默认是GBK编码。
总结:要么keil和串口助手都选择UTF8,且keil加上 --no-multibyte-chars参数。要么都使用GB开头的中文编码格式,参数不用加。这就是汉字乱码接解决方案。

在这里插入图片描述

程序发送现象

在这里插入图片描述
mian.c

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

int main(void)
{
	/*模块初始化*/
	OLED_Init();   //OLED初始化
	
	Serial_Init();  //串口初始化
	
	/*串口基本函数*/
	Serial_SendByte(0x41);  //串口发送一个字节数据0x41
	
	uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};  //定义数组
	Serial_SendArray(MyArray, 4);        //串口发送一个数组
	
	Serial_SendString("\r\nNum1=");      //串口发送字符串。转义字符\r\n是换行
	
	Serial_SendNumber(111, 3);           //串口发送数字
	
	/*下述3种方法可实现printf的效果*/
	
	/*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/    //你重定向到串口1了,到了串口2再用就没了。
	
	printf("\r\nNum2=%d", 222);     //串口发送printf打印的格式化字符串
	                                //需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
	
	/*方法2:多个串口想用printf。sprintf可以把格式化字符输出到一个字符串里。   使用sprintf打印到字符串,再用串口发送字符串,此方法打印到字符串,之后想怎么处理都可以,可在多处使用*/
	
	char String[100];                     //定义字符串
	sprintf(String, "\r\nNum3=%d", 333); //第一个参数是打印输出的位置,指定打印String 。    使用sprintf,把格式化字符串打印到字符数组
	Serial_SendString(String);          //把字符串String通过串口发送出去
	
	
	/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/
	
	Serial_Printf("\r\nNum4=%d", 444);    //串口打印字符串,使用自己封装的函数实现printf的效果
	Serial_Printf("\r\n");
	
	while (1)
	{
		
	}
}

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的时钟
	
	//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引脚初始化为复用推挽输出,供USART1的TX使用 
	
	//USART初始化
	USART_InitTypeDef USART_InitStructure;  //定义结构体变量
	USART_InitStructure.USART_BaudRate = 9600;  //波特率为9600。  写完之后,这个Init函数内部会自动算好9600对应的分频系数,然后写入到BRR寄存器 
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //硬件流控制,无流控。这个参数取值一般以参数名称为前缀
	USART_InitStructure.USART_Mode = USART_Mode_Tx;   //模式,选择为发送模式。    如果既需要发送模式,又需要接收模式,那就用或|,符号把TX和RX或起来
	USART_InitStructure.USART_Parity = USART_Parity_No;  //奇偶校验,无校验。     No是无校验,Odd奇校验,Even偶校验
	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)  //调用这个函数,可以从TX引脚发送一个字节数据
{
	USART_SendData(USART1, Byte);  //将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  //等待发送完成。  使用USART_FLAG_TXE,发送数据空标志位。 要等待TXE置1,套一个while循环,如TXE标志位==RESET,就一直循环,直到SET,结束等待。
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)  //这是一个uint8_t *指针类型,指向待发送数组的首地址,由于数组无法判断是否结束,所以需要再传递一个Length进来。
{
	uint16_t i;       //定义变量i
	for (i = 0; i < Length; i ++)  //遍历数组。 for循环就会执行Length次,可以对Array数据进行遍历。 
	{
		Serial_SendByte(Array[i]);  //依次调用Serial_SendByte发送每个字节数据。   就是依次取出数组Array[i]的每一项,然后通过SendByte发送。
	}
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;                     //定义变量
	for (i = 0; String[i] != '\0'; i ++) //遍历字符数组(字符串),遇到字符串结束标志位后停止。  这里循环条件,用结束标志位来判断,这里0,是空字符,是字符串结束标志位,如不等于0,还没结束,继续循环,如果等于0,循环停止。这里0可以写成字符的形式'\0',这是空字符的转义字符的表现形式。
	{
		Serial_SendByte(String[i]);  //依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:次方函数(内部使用)   //总结是,取某一位,对数字/10的x次方%10
  * 返 回 值:返回值等于X的Y次方
  */

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1; //设置结果初值为1
	while (Y --)         //执行Y次
	{
		Result *= X;     //Result累乘Y到次X。就是X的Y次方
	}
	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发送每位数字。  需要把Number的个位,十位,百位,以十进制拆开,然后转换成字符数字对应的数据,依次发送。     '0'是以字符形式
	}
	                                                                  //总结是,取某一位,对数字/10的x次方%10
}


/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)   //这是fputc函数原型
{
	Serial_SendByte(ch);     //将printf的底层重定向到自己的发送字节函数
	return ch;
}
                              //重定向fputc跟printf的关系,是这个fputc是printf的底层,printf函数在不断打印的时候,就是不断调用fputc函数一个个打印,我们把fputc函数重定向到串口,那printf自然就输出到串口了。

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 用来接收后面的可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)  //函数用来接收格式化字符串
{
	char String[100];   //定义字符串
	va_list arg;        //定义一个参数列表变量,va_list类型名,arg变量名。 定义可变参数列表数据类型的变量arg
	va_start(arg, format);    //从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);  //String是打印位置,format格式化字符串,arg是参数列表。在这里使用vsprintf,因为sprintf只能接收直接写的参数,对于封装格式用vsprintf。
	va_end(arg);                    //结束变量arg
	Serial_SendString(String);      //串口发送String
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

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

#endif

串口发送+接收

在这里插入图片描述
对于串口接收来说,可以使用查询和中断两种方法。如果使用查询初始化就结束了,如使用中断,则需开启中断,配置NVIC。
查询的流程是,在主函数里不判断RXNE标志位,如置1,就收到数据 ,那再调用ReceiveData,读取DR寄存器,这样就行了。

在这里插入图片描述

在串口助手,我们需要在发送取区写入数据,发送模式可以选择HEX模式或文本模式。HEX就是原始数据,文本模式首先过一遍字符编码,我们选择HEX模式,在HEX模式下,只能写16进制数,就是0~9,A ~F,不用写0x的。两个数为一组,非法字符都将被忽略。

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初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "RxData:");
	
	/*串口初始化*/
	Serial_Init();
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)  //检查串口接收数据的标志位
		{
			RxData = Serial_GetRxData();  //获取串口接收的数据
			Serial_SendByte(RxData);      //串口将收到的数据回传回去,用于测试
			OLED_ShowHexNum(1, 8, RxData, 2);  //显示串口接收的数据
		}
	}
}

Serial.c

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

uint8_t Serial_RxData;  //定义串口接收的数据变量。  封装了
uint8_t Serial_RxFlag;  //定义串口接收的标志位变量。    封装了

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
 
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引脚初始化为复用推挽输出
	
	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);   //将PA10引脚初始化为上拉输入
	
	
	/*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_ITConfig(USART1, USART_IT_RXNE, ENABLE);   //开启串口接收数据的中断。 开启RXNE标志位到NVIC的输出
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //配置NVIC为分组2
	
	
	/*NVIC配置*/ //初始化NVIC的USART1的通道
	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,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数: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]);
	}
}

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 Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

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

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


/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)   //如果标志位为1
	{
		Serial_RxFlag = 0;   //则返回1,并自动清零标志位
		return 1;
	}
	return 0;               //如果标志位为0,则返回0
}

/**
  * 函    数:获取串口接收的数据
  * 参    数:无
  * 返 回 值:接收的数据,范围:0~255
  */
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;   //返回接收的数据变量
}


/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  //判断是否是USART1的接收事件触发的中断
	{
		Serial_RxData = USART_ReceiveData(USART1);  //读取数据寄存器,存放在接收的数据变量
		Serial_RxFlag = 1;                          //置接收标志位变量为1
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);  //清除USART1的RXNE标志位
	}
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

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

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

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值