【嵌入式学习-STM32F103-USART串口通信】

目录

1、串口通信协议(简介+软硬件规则)

2、STM32内部的USART外设

3、USART基本结构(江科大简化)

4、串口发送代码

4-1 基本流程

4-2 整体代码

4-2-1 main.c
4-2-2 Serial.c
4-2-3 Serial.h

5、串口接收代码

5-1 查询

5-2 中断

5-3 整体代码

5-3-1 main.c
5-3-2 Serial.c
5-3-3 Serial.h

6、USART串口数据包

6-1 使用状态机接收数据包的思路

6-2 串口收发HEX数据包

6-2-1 main.c
6-2-2 Serial.c
6-2-3 Serial.h

6-3串口收发文本数据包

6-3-1 main.c
6-3-2 Serial.c
6-3-3 Serial.h

补充

1、串口通信协议(简介+软硬件规则)

在这里插入图片描述

全双工:打电话。半双工:对讲机。单工:广播

时钟:I2C和SPI有单独的时钟线,所以它们是同步的,接收方可以在时钟信号的指引下进行采样。串口、CAN和USB没有时钟线,需要双方约定一个采样频率,它们是异步的,并且需要加一些帧头帧尾等进行采样位置的对齐。

电平:1、单端->引脚的高低电平都是对GND的电压差,所以单端信号通信的双方必须要共地,就是把GND接在一起。2、

差分->它是靠两个差分引脚的电压差来传输信号的,在通讯的时候,可以不需要共地,可极大提高抗干扰特性,所以差分信号一般传输速度和距离都会非常高。

设备:点对点->老师单独找一位学生谈话;多设备->老师在教室里面对所有同学谈话,需要有一个寻址的过程,以确定通信的对象。寻址:给不同的设备编号,对应不同的学生的名字

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

波特率:如果双方规定波特率为1000bps,表示1s要发送1000位,每一位的时间就是1ms。 发送方每隔1ms发送1位,接收方每隔1ms接收1位。波特率,它决定了每隔多久发送一位。

起始位:首先,串口的空闲状态是高电平,也就是没有数据传输的时候,引脚必须要置高电平,作为空闲状态,然后需要传输的时候,必须要先发送一个起始位,这个起始位必须是低电平,来打破空闲状态的高电平,产生一个下降沿,该下降沿就告诉设备这一帧数据要开始了。如果没有起始位,数据线就一直都是高电平,没有任何波动,这样接收方怎么知道要接收数据呢。

停止位:为下一个起始位做准备。如果没有停止位,那当我数据最后一位是0的时候,下次再发送新的一帧,就没有办法产生下降沿了。
例子,连续发送两个0x55,1个停止位和2个停止位

在这里插入图片描述

校验位:串口使用的是一种叫奇偶校验的数据验证方法。奇偶校验可以判断数据传输是否出错。如果数据出错了,可以选择丢弃或者要求重传。

在这里插入图片描述

串口通信总结:TX引脚输出定时翻转的高低电平,RX引脚定时读取引脚的高低电平。每个字节的数据加上起始位,停止位,可选的校验位(无,奇,偶),打包成数据帧,一次输出在TX引脚,另一端RX引脚依次接收,这样就完成了字节数据的传递。

在这里插入图片描述

2、STM32内部的USART外设

USART外设就是串口通信的硬件支持电路。
常用配置:波特率9600或者115200,数据位8位,停止位1位,无校验
USART1:APB2总线的设备
USART2、USART3:APB1总线的设备

在这里插入图片描述

USART功能框图

在这里插入图片描述

TX和RX走线
在这里插入图片描述

发送数据寄存器和发送移位寄存器怎么工作的呢?
比如在某一时刻给TDR写入0x55数据
在这里插入图片描述

发送端

此时,硬件检测到你写入数据,它就会检查当前一位寄存器是不是有数据正在移位,如果没有,这个01010101就会立刻全部移动到发送移位寄存器,准备发送。当数据从TDR移动到移位寄存器时,会置一个标志位,叫TXE(TX Empty),发送寄存器空。如果该标志位置1,可在TDR写入下一个数据。注意,当TXE标志位置1时,数据还没有发送出去,只要数据从TDR转移到移位寄存器,TXE就会置1,此时可写入新的数据。然后发送移位寄存器就会在发生器控制的驱动下,向右移位,然后一位一位地把数据输出到TX引脚,正好与串口协议规定的低位先行一致。当数据移位完成时,新的数据就会再次自动地从TDR转移到发送移位寄存器里来。如果当前移位寄存器移位还没有完成,TDR的数据就会进行等待,一旦移位完成,就会立刻转移过来。有了TDR和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲。简单来说,数据一旦从TDR转移到移位寄存器,管你有没有移位完成,就会把下一个数据放在TDR等待。一旦移位寄存器移动完成,新的数据就会立刻跟上。

在这里插入图片描述

在这里插入图片描述

接收端

数据从RX引脚通向接受移位寄存器,在接收器控制的驱动下,一位一位地读取RX电平,先放在最高位,然后向右移动,移位8次后就可以接受一个字节。
在这里插入图片描述

同样,因为串口协议规定是低位先行,所以接受移位寄存器是从高位往低位这个方向移动,当一个字节移位完成后,这一个字节的数据就会整体地转移到接收数据寄存器RDR里,在转移的过程中会置一个标志位,叫RXNE(RX Not Empty),接收数据寄存器非空。当我们检测到RXNE置1之后,就可以把数据读走。同时,这个标志位可以去申请中断,在收到数据时,直接进入中断函数。

在这里插入图片描述

这里也是两个寄存器进行缓存,当数据从移位寄存器转移到RDR时,就可以直接移位接受下一帧数据。

在这里插入图片描述
发送器控制:用来控制发送移位寄存器的工作
接受器控制:用来控制接收移位寄存器的工作
在这里插入图片描述

以下模块用于产生同步时钟信号,它是配合发送移位寄存器输出的,发送寄存器每移位一次,同步时钟电平就跳变一个周期,时钟告诉对方我移出去移位数据了 。该时钟只支持输出,不支持输入,因此两个USART之间不能实现同步的串口通信。
时钟作用
1、兼容别的协议,比如串口加时钟,与SPI协议相似,因此可兼容SPI
2、自适应波特率,如接收设备不确定发送设备给的是什么波特率,可通过测量时钟周期,再计算得到波特率。
在这里插入图片描述

以下是判断发送状态和接收状态的必要标志位

在这里插入图片描述

波特率发生器就是分频器,APB时钟进行分频,得到发送和接收移位的时钟。

在这里插入图片描述

串口的引脚
在这里插入图片描述

3、USART基本结构(江科大简化)

‘>>’右移,表示数据低位先行
开关控制->配置完成时,用cmd开启USART外设
在这里插入图片描述

输入的采样频率和波特率一致,还要保证每次输入采样的位置,要正好处于每一位的正中间,只有在每一位的正中间采样,这样的高低电平读进来,才是最可靠的。如果采样点过于靠前或靠后,那有可能高低电平还正在翻转,电平不稳定或者稍有误差,数据就采样出错了。

在这里插入图片描述

起始位侦测和采样位置对齐的策略
噪声标志位:NE

在这里插入图片描述

在这里插入图片描述

波特率发生器
为什么要除以16,因为它内部还有一个16倍频波特率的采样时钟

在这里插入图片描述

比如我要配置USART1为9600波特率,那如何配置BRR寄存器呢。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4、串口发送代码

4-1 基本流程

1、第一步开启时钟,把需要用到的USART和GPIO的时钟打开
2、第二步,GPIO初始化,把Tx配置成复用输出,Rx配置成输入
3、第三步,配置USART,直接使用一个结构体
4、如果你只需要发送的功能,就直接开启USART,初始化就结束了;如果你需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITConfig和NVIC的代码

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */
	/* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 ,默认使用串口1作为中断源*/
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  /* 抢断优先级*/
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

// 串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断,接收数据寄存器非空,表示接收到数据就产生中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);

5、初始化完成后,如果要发送数据,调用一个发送函数即可;如果要接收数据,就调用接收的函数。如果要获取发送和接收的状态,就调用获取位的函数

4-2 整体代码

4-2-1 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main(void)
{
	OLED_Init();
	//初始化串口
	Serial_Init();
	//发送数据,调用该函数后,TX引脚就会产生一个0x41对应的波形,
	//可将该波形发送给其他支持串口的模块,也可以通过USB转串口的模块发送到电脑端
	Serial_SendByte(0x41);
	
	uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};
	Serial_SendArray(MyArray, 4);//传入数组的首地址,指定传输4个字节  
	
	Serial_SendString("\r\nNum1=");
	
	Serial_SendNumber(111, 3);
	
	printf("\r\nNum2=%d", 222);
	
	char String[100];  //定义字符串
	sprintf(String, "\r\nNum3=%d", 333);  //打印字符串
	Serial_SendString(String);  //发送字符串
	
	Serial_Printf("\r\nNum4=%d", 444);
	Serial_Printf("\r\n");
	
	while (1)
	{
		
	}
}
4-2-2 Serial.c

取某一位就是–>数字 / 10^x % 10
/ 10^x 去掉右边
% 10 去掉左边
在这里插入图片描述

在这里插入图片描述
重定向fputc跟printf有什么关系呢?
这是因为fputc是printf函数的底层实现,printf函数在打印的时候就是不断调用fputc函数一个个打印的,我们把fputc函数重定向到串口,那printf自然久输出到串口。

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

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

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  //USART1的外设时钟时APB2
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIO时钟
	//初始G化PIO
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* 将PA9配置为复用推挽输出,供USART的Tx使用 */
	//TX引脚是USART外设控制的输出脚,所以要选复用推挽输出,RX引脚是USART外设数据输入脚,
	//所以要选择输入模式,一根线只能有一个输出,但可以有多个输入
	//因为串口波形空闲状态是高电平,所以不使用下拉输入
	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);
	
	//初始化USART
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;  //Init函数内部会自动算好9600对应的分频系数,然后写到BRR寄存器
	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
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{ 
	//将Byte变量写入到TDR
	USART_SendData(USART1, Byte);
	//等待TXE置1,表明TDR数据已经转移到移位数据寄存器,要不然如果数据
	//还在TDR进行等待,我们再写入数据就会产生数据覆盖,所以在数据发送之后,还需要等待以下标志位
	//如果TXE标志位==RESET,就一直循环,直到SET,结束等待,标志位置1后,不需要手动清零,
	//当下一次SendData时,该标志位自动清零
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); 
}

//uint8_t *Array这是一个uint8_t 的指针类型,指向待发送数组的首地址,传递数组需要使用指针
//length由于数组无法判断是否结束,所以需要再传递一个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 ++)  //数据0,对应空字符,是字符串结束标志位,如果不等于0,就是还没有结束,进行循环,如果等于0,就是结束,停止循环
	{
		Serial_SendByte(String[i]);
	}
}
//返回值为 x的y次方
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;  //X的Y次方
	}
	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');  //目前循环,参数会以10进制从高位到低位依次发送,再加上偏移量
		//假设length为2,当i=0时,发送的是10位,当i=1时,发送的是个位
		//从高位到低位依次发送
		
	}
}
//
//单个串口重定向,只能有一个串口用来打印
//printf重定向,fputc是printf的底层,将fputc函数重定向到了串口,那printf自然就输出到串口
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

//多个串口重定向,可多个串口用来打印
//封装sprintf
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);
}
4-2-3 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

若需要输出中文,则
在这里插入图片描述

程序现象及注意事项

串口助手的发送和接收都需要选择HEX模式,若不同,则OLED和电脑界面会显示不同。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、串口接收代码

5-1 查询

在主函数里不断判断RXNE标志位,如果置1,表明收到数据,再调用ReceiveData,读取DR寄存器即可

//查询
	while(1)
	{
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
		{
			RxData = USART_ReceiveData(USART1);  //读完DR寄存器,该标志位自动清除
			OLED_ShowHexNum(1,1,RxData,2);
		}
	}

5-2 中断

/*===================================================*/
	/* 串口接收 可使用 查询和中断两种方法 以下是中断*/
	//开启中断,选择RXNE,表示这一个字节的数据就会整体地转移到接收数据寄存器RDR里
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	//中断优先级分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//配置NVIC
	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);
	/*===================================================*/

/*==============中断接收和变量的封装====================*/
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}
//中断函数的名字在启动文件里面(startup_stm32f10x_md.s)
void USART1_IRQHandler(void)
{
	//先判断标志位
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		//将接收寄存器里的数据放到自定义变量里
		Serial_RxData = USART_ReceiveData(USART1);
		//读完数据后置标志位1
		Serial_RxFlag = 1;
		//如果读取DR,则自动清除标志位,否则,手动清除标志位
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);  
	}
}

5-3 整体代码

5-3-1 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_ShowString(1, 1, "RxData:");
	
	Serial_Init();
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData = Serial_GetRxData();
			//把接收到的数据回传(到电脑)功能
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
	
//	//查询
//	while(1)
//	{
//		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
//		{
//			RxData = USART_ReceiveData(USART1);  //读完DR寄存器,该标志位自动清除
//			OLED_ShowHexNum(1,1,RxData,2);
//		}
//	}
	
}



5-3-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);
	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);
	
	/*===================================================*/
	/* 串口接收 可使用 查询和中断两种方法 以下是中断*/
	//开启中断,选择RXNE,表示这一个字节的数据就会整体地转移到接收数据寄存器RDR里
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	//中断优先级分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//配置NVIC
	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 *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);
}

/*==============中断接收和变量的封装====================*/
//对Serial_RxFlag变量封装get函数,实现读后自动清除的作用
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}
//对Serial_RxData变量封装get函数
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}
//中断函数的名字在启动文件里面(startup_stm32f10x_md.s)
void USART1_IRQHandler(void)
{
	//先判断标志位
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		//将接收寄存器里的数据放到自定义变量里
		Serial_RxData = USART_ReceiveData(USART1);
		//读完数据后置标志位1
		Serial_RxFlag = 1;
		//如果读取DR,则自动清除标志位,否则,手动清除标志位,此处手动清除没影响
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);  
	}
}
/*==========================================================*/
5-3-3 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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void)
{
	OLED_Init();
	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);
		}
	}
	
//	//查询
//	while(1)
//	{
//		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
//		{
//			RxData = USART_ReceiveData(USART1);  //读完DR寄存器,该标志位自动清除
//			OLED_ShowHexNum(1,1,RxData,2);
//		}
//	}
	
}

程序现象

串口收发HEX数据

数据包有规定的格式,以FF为包头,FE为包尾,中间固定4个字节为数据。每按一次按键,发送一个数据包,中间的数据会呈现递增显示。
在这里插入图片描述
在这里插入图片描述
包头FF和包尾FE用于控制接收,接收时不显示。
在这里插入图片描述
在这里插入图片描述

串口收发文本数据

发送模式和接收模式都选择文本模式
以@为包头
以换行符为包尾
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、USART串口数据包

数据包的任务,通过额外添加包头包尾实现数据分割打包的思路,方便接收方识别。

问题:

1、包头包尾和数据载荷重复的问题,如果数据和包头包尾重复,可能会引起误判

解决方法:

1、限制载荷数据的范围,如果可以的话,我们在发送的时候,对数据进行限幅

2、如果无法避免载荷数据和包头包尾重复,我们就尽量使用固定长度的数据包,由于载荷数据是固定的,只要我们通过包头包尾对齐了数据,就可以严格知道哪个数据是包头包尾,哪个数据应该是载荷数据。在接收载荷数据的时候,我们并不会判断他是否是包头包尾,而在接收包头包尾的时候,我们会判断它是不是确实是包头包尾,用于数据对齐

3、增加包头包尾的数量,并且让它尽量呈现出载荷数据出现不了的状态。

HEX数据包

在HEX数据包里面,数据都是以原始的字节数据本身呈现的

文本数据包

在文本数据包里面,每个字节经过一层编码和译码,最终以文本格式表现出来。

可变包长,各种字母、符号、数字都可以随意使用。
当我们接收到载荷数据后,得到的就是一个字符串,在软件中再对字符串进行操作和判断,就可以实现各种指令控制的功能,而且字符串数据包表达意义明显,可以把字符串数据包直接打印到串口助手上

HEX优点:传输直接,解析数据非常简单,比较适合一些模块发送原始的数据,比如一些使用串口通信的陀螺仪、温湿度传感器;缺点是灵活性不足,载荷容易和包头包尾重复。

文本优点:数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,蓝牙模块的AT指令,CNC和3D打印机常用的G代码,缺点解析效率低

6-1 使用状态机接收数据包的思路

标志位只有0和1,而状态机是多标志位状态的一种方式。最开始,S=0,收到一个数据,进入中断,根据S=0,进入第一个状态的程序,判断数据是不是包头FF,如果是FF,则代表收到包头,之后置S=1,退出中断,结束,下次再进中断,根据S=1,就可以进行接受数据的程序。如果在第一个状态,如果收到的不是FF,证明数据没有对齐,我们应该等待读数据包包头的出现。这时S=0.下次进中断,就还是判断包头的逻辑,知道出现FF,才能转到下一个状态,来到接收数据状态,此时收到数据,我们就直接把他存在数组中,另外再用一个变量记录接受了多少个数据,如果没有收购4个数据,就一直是接收状态,如果收够了,就置S=2.下次进入中断时,就可以进入下一个状态了。最后的状态就是等待包尾,判断数据是否是FE,这样就可以置S=0,回到最初的状态,开始下一个轮回。如果包尾位置不是·FE,此时可以进入重复等待包尾的状态,直到接收到真正的包尾。这样加入包尾的判断,更加能够预防因数据和包头重复造成的错误。

在这里插入图片描述
状态机:比如设计菜单,按什么键,切换什么菜单,执行什么程序

6-2 串口收发HEX数据包

6-2-1 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
	OLED_Init();
	Key_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");
	
	//填充缓冲区数组(发送缓存赋初始值)
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	
	while (1)
	{
		KeyNum = Key_GetNum();
		//按键按下,则执行发送数据
		if (KeyNum == 1)
		{
			//变换数据
			Serial_TxPacket[0] ++;
			Serial_TxPacket[1] ++;
			Serial_TxPacket[2] ++;
			Serial_TxPacket[3] ++;
			//发送数据包
			Serial_SendPacket();
			//OLED显示数据
			OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
			OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
			OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
			OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
		}
		
		if (Serial_GetRxFlag() == 1)
		{
			OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
		}
	}
}

6-2-2 Serial.c
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

//为了收发数据包,定义两个缓冲区的数组
uint8_t Serial_TxPacket[4];				//FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4];
//如果收到一个数据包,就置RxFlag(标志位)
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	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_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	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 *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);
}

//发送数据包,调用该函数,TxPacket数组的4个数据就会自动加上包头包尾发送出去
void Serial_SendPacket(void)
{
	//发送包头
	Serial_SendByte(0xFF);
	//发送四个载荷数据
	Serial_SendArray(Serial_TxPacket, 4);
	//发送包尾
	Serial_SendByte(0xFE);
}

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

//在接收中断函数里,我们需要用状态机来执行接收逻辑
void USART1_IRQHandler(void)
{
	//定义一个标志当前状态的变量S,静态变量(类似于全局变量,函数进入只会初始化依次0,在函数退出后,数据仍然有效,
	//与全局变量不同的是,静态变量只能在本函数使用)
	static uint8_t RxState = 0;
	//指示接收数据到哪一个,最开始默认为0
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		//状态0,进入等待包头的程序
		if (RxState == 0)
		{
			if (RxData == 0xFF)
			{
				RxState = 1;
				pRxPacket = 0;  //状态0转移到状态1,提前清零
			}
		}
		//状态1,进入接收数据的程序
		else if (RxState == 1)
		{
			//依次接收4个数据,存在RxPack数组里
			Serial_RxPacket[pRxPacket] = RxData;  //将RxData存到接收数组里
			pRxPacket ++;  //移动到下一个位置
			if (pRxPacket >= 4)
			{
				RxState = 2;
			}
		}
		//状态2,等待包尾
		else if (RxState == 2)
		{
			//判断是否是包尾,
			if (RxData == 0xFE)
			{
				RxState = 0;  //如果是的话,回到最初的状态
				Serial_RxFlag = 1;  //接收标志位置1,代表数据包接收到了
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);  //手动清除中断标志位
	}
}
hex发送数据包
//发送数据包,调用该函数,TxPacket数组的4个数据就会自动加上包头包尾发送出去
void Serial_SendPacket(void)
{
	//发送包头
	Serial_SendByte(0xFF);
	//发送四个载荷数据
	Serial_SendArray(Serial_TxPacket, 4);
	//发送包尾
	Serial_SendByte(0xFE);
}
hex接收数据包(串口中断)
//USART串口数据包
//串口的中断服务函数
void USART1_IRQHandler(void)
{
	//静态变量类似于全局变量,函数进入只初始化一次,在函数退出后,数据仍然有效,与全局变量不同的是,静态变量只能在本函数中使用。
	static uint8_t RxState = 0;// 接收数据状态变量
	static uint8_t pRxPacket = 0;// 数据包缓冲区指针
	// 检查USART1接收中断(USART_IT_RXNE)是否触发
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  //数据寄存器非空,该寄存器置1,我们就可以将数据读走
	{
		// 从USART数据寄存器读取接收到的数据
		uint8_t RxData = USART_ReceiveData(USART1);
		// 状态机处理接收到的数据包
		//等待包头
		if (RxState == 0)
		{	
			 // 检查数据包头部 (0xFF)
			if (RxData == 0xFF)//收到包头
			{
				RxState = 1;// 切换到下一个状态
				pRxPacket = 0;// 重置数据包缓冲区指针
			}
		}
		//接收数据
		else if (RxState == 1)
		{	
		//依次接收四个数据存在rxpacket数组里
	     // 将接收到的数据存储到数据包缓冲区
	     //以下为没进入一次接收状态,数据久转存一次缓存数组,同时存的位置++
			Serial_RxPacket[pRxPacket] = RxData;
			pRxPacket ++;// 移动到数据包缓冲区的下一个位置
			// 检查是否已接收到至少3个字节(包括头部)
			if (pRxPacket > 2)
			{
				RxState = 2;
			}
		}
		//等待包尾
		else if (RxState == 2)
		{
			// 检查数据包尾部 (0xFE)
			if (RxData == 0xFE)//包尾
			{			
				RxState = 0;// 重置状态以开始接收新的数据包
				Serial_RxFlag = 1;// 设置标志表示已接收到完整的数据包
			}
		}
		// 清除USART1接收中断标志以确认中断
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除中断标志位
	}
}

6-2-3 Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

//extern 外部可调用
extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

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, ...);

void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);//用于判断是不是接收到了数据包

#endif

6-3串口收发文本数据包

在这里插入图片描述

以@符号为包头,换行的两个符号为包尾,中间的载荷字符数量不固定。
注意S=1时,判断该诗剧是否是\r,如果不是,则正常接收,如果是,则不接受收,同时跳到下一个状态,等待包尾\n

6-3-1 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include "string.h"

int main(void)
{
	OLED_Init();
	LED_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");
	
	while (1)
	{
		//判断是否接收到数据包
		if (Serial_RxFlag == 1)
		{
			OLED_ShowString(4, 1, "                ");  //相当于擦除
			OLED_ShowString(4, 1, Serial_RxPacket);
			
			//判断两个字符串是否相等
			if (strcmp(Serial_RxPacket, "LED_ON") == 0)
			{
				//点亮LED
				LED1_ON();
				//向串口助手回传字符串,电脑显示
				Serial_SendString("LED_ON_OK\r\n");
				//OLED显示
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");
			}
			//接收到的数据于LED_OFF相同
			else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
			{
				LED1_OFF();
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");
			}
			//错误指令
			else
			{
				Serial_SendString("ERROR_COMMAND-com\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND-oled");
			}
			
			Serial_RxFlag = 0;
		}
	}
}

6-3-2 Serial.c
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

//接收的数据类型定义为char,用于接收字符,100个字符(单条置零最长不能超过100个字符),防止溢出
char Serial_RxPacket[100];				//"@MSG\r\n"
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	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_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	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 *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);
}

//文本数据包的中断接收状态机
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		//状态0,等待包头  
		if (RxState == 0)
		{
			//等待包头的时候,满足以下条件才执行接收,否则你发太快,我还没处理完,就跳过这个数据包,导致错位
			//只有RxFlag为0,才会继续接收下一个数据包
			if (RxData == '@' && Serial_RxFlag == 0)
			{
				//转移到状态1
				RxState = 1;
				//计数器清零
				pRxPacket = 0;
			}
		}
		//状态1,因为载荷字符数量并不确定,所以每次接收之前,我们必须先判断是不是包尾
		else if (RxState == 1)
		{
			if (RxData == '\r')
			{
				//转到状态2
				RxState = 2;
			}
			//如果不是,仍然需要接收数据
			else
			{
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
			}
		}
		//状态2,需要检测RxData是不是'\n',等待第二个包尾
		else if (RxState == 2)
		{
			//如果是的话,状态置0,接收标志位置1
			if (RxData == '\n')
			{
				RxState = 0;
				//需要给这个字符数组的最后加一个字符串结束标志位'\0',方便后续对字符串进行处理
				Serial_RxPacket[pRxPacket] = '\0';
				Serial_RxFlag = 1;
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

6-3-3 Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

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

补充

在这里插入图片描述

在这里插入图片描述

  • 11
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: STM32F103是一款基于ARM Cortex-M3内核的单片机,它具有丰富的外设和性能优势。FreeRTOS是一款流行的实时操作系统,可用于在STM32F103上实现多任务和调度。USARTSTM32F103系列的串行通信接口,用于与外部设备进行通信。DMA(直接内存访问)是一种数据传输方式,可实现高效的数据传输,提高系统性能。CubeMX是一款图形化开发工具,可用于配置和生成STM32F103的初始化代码。 在STM32F103上使用FreeRTOS框架,可以实现多任务和调度功能。通过配置CubeMX,可以方便地设置USART和DMA外设。首先,使用CubeMX配置USART外设的工作模式、波特率等参数,并使用DMA传输数据。然后,通过编程将USART设置为DMA模式,使数据在接收和发送时通过DMA传输,提高效率和性能。在FreeRTOS任务中,可以编写代码实现与外设的通信,通过USART发送和接收数据。使用FreeRTOS的任务和调度功能,可以同时处理多个任务,提高系统的并发性和响应能力。 总之,通过结合STM32F103、FreeRTOS、USART和DMA,以及使用CubeMX配置工具,可以方便地实现多任务调度和串口通信。这样的架构可以提高系统性能,实现更复杂的应用。 ### 回答2: STM32F103是一款由意法半导体(STMicroelectronics)推出的32位单片机,它使用了Cortex-M3内核。在STM32F103中,我们可以使用FreeRTOS(Real-Time Operating System)来实现多任务处理和实时性。 USART是通用异步收发传输器,它可以用于串行通信。在STM32F103中,我们可以使用USART来进行与外部设备的通信。 DMA(Direct Memory Access)是一种数据传输方法,它可以在系统的CPU不直接参与的情况下进行数据传输。在STM32F103中,通过配置USART的DMA,可以实现高效的数据传输。DMA还可以在多个外设之间进行数据传输,提高系统的效率。 CubeMX是一个图形化的配置工具,它可以帮助我们快速地配置和初始化STM32的硬件资源,生成相应的代码框架。 通过结合使用FreeRTOS、USART、DMA和CubeMX,我们可以实现在STM32F103上进行多任务处理、串行通信和高效数据传输的需求。首先,使用CubeMX快速配置USART和DMA相关的硬件资源,并生成代码框架。然后,根据需要,使用FreeRTOS进行任务的创建、调度和管理,实现多任务处理。在任务中,通过调用USART的相应接口来进行串行通信,并通过配置DMA来实现高效的数据传输。 总的来说,使用STM32F103、FreeRTOS、USART、DMA和CubeMX的组合,可以帮助我们充分发挥STM32的功能,实现复杂的应用需求,并提升系统的性能和效率。 ### 回答3: stm32f103是STMicroelectronics(ST)公司推出的一款32位单片机,它的系列名称是STM32。该系列的芯片具有高性能和低功耗的特点,广泛应用于嵌入式系统中。 FreeRTOS是一款实时操作系统(RTOS),广泛应用于嵌入式系统开发中。它提供了丰富的功能,包括任务管理、时间管理、内存管理和通信等。使用FreeRTOS能够更好地组织和管理任务,提高系统的实时性和稳定性。 USART是通用同步/异步收发器(Universal Synchronous/Asynchronous Receiver Transmitter)的缩写,是一种常用的串口通信接口。它可以实现串行数据的发送和接收,常见的应用场景包括与外部设备进行数据通信和调试信息的输出。 DMA是直接内存访问(Direct Memory Access)的缩写,它是一种不需要CPU干预的数据传输方式。在数据量较大或者需要高速传输的场景下,使用DMA能够提高数据传输的效率,减轻CPU负担。 CubeMX是一个集成开发环境(IDE),用于简化STM32芯片的配置和代码生成。通过CubeMX可以轻松地配置芯片的外设、时钟和中断等,方便开发者快速搭建项目框架。它还可以生成基于CMSIS和HAL库的初始化代码,简化开发流程。 结合以上信息,stm32f103 freertos usart dma cubemx可以理解为在使用stm32f103芯片开发嵌入式系统时,使用FreeRTOS实现任务管理和时间管理,通过USART进行与外部设备的通信,利用DMA实现高效的数据传输,同时使用CubeMX进行芯片配置和代码生成的开发流程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值