USART(串口通信协议)

USART(串口通信协议)

【通信的目的】将一个设备的数据传输到另外一个设备,拓展硬件系统

 【 通信协议】制定通信的规则,通信双方按照协议进行数据的收发

串口通信中各个参数的含义

【TX】 数据接收脚
【RX】 数据发送脚
【SCL】     时钟
【SDA】   数据
【SCLK】   时钟
【MOSI】    主机输出数据脚
【MISO】 主机输入数据脚
【CS】   片选,指定通信的对象
 【CAN_H】,【CAN_L】差分数据脚,使用两个引脚表示一个差分数据
【DP】  D+  ,【DM】D-   差分数据脚

  数据传输不同的方式    

【全双工】由两根数据线,一根用来接收,一根用来发送
【半双工】一根数据线,既可以用来发送,也可以用来接收
【单工】无法做到同时发送和接受数据
【同步通信】有时间线,不需要约定采样频率
【异步通信】需要约定合适的采样频率,用于在约定的频率内采集数据

串口通信

def:串口是一种应用广泛的通讯接口,串口的成本低,容易使用,通信线路简单,可以实现两个设备之间的相互通信【串口通信方式既可以同步通信也可以异步通信】

def:单片机的串口可以使单片机与单片机,单片机与电脑,单片机与各式各样的模块进行通信,极大的拓展了单片机的应用范围,增强了单片机系统的硬件实力

串口和芯片简介

  

 

【串口的硬件电路】

注:

  • 【简单的双向串口通信有两根通信线(发送端TX和接收端RX)】
  • 【TX与RX要交叉连接】
  • 【当只需单项的数据传输时可以只接一根通信线】
  • 【当电平标准不一致时,需要加电平转换芯片】

发送接收原理图

部分电平标准(简要介绍)

注:

电平标准是数据0和数据1的表达方式,是传输线缆中认为规定的电压和数据的对应关系,串口常用的电平标准有以下的几种

  • 【TTL电平】 :+3.3v或 + 5v表示1,0V表示0
  • 【RS232电平】 :-3~ 15v 表示1, +3~+15v表示0
  • 【RS485电平】 :两线压差+2~+6v表示1,-2~-6v表示0(差分信号)

【串口参数及时序】

注:

  • 【波特率】 串口通信的速率
  • 【起始位】 标志一个数据帧的开始,固定为低电平
  • 【数据位】 数据帧的有效载荷,1为高电平,0为低电平,低位先行
  • 【校验位】 用于数据验证,根据数据位计算得来
  • 【停止位】 用于数据帧间隔,固定为高电平

图中的意思是可以有八位,第一个位是起始位,最后一位是截止位,中间的数据位数是数据位

在没有进入起始位之间电平的极性【注:呈现出高电平特性】,在进入起始位后电平转换为低点平表示开始发送数据,数据呈现出8个字节一位的特性,进入最后一位后电平转化为高电平,方便下一次数据的发送,大概原理入下图所示。

注:校验的方式有【奇校验】,【偶校验】,【循环冗余校验】

串口时序参考


USART 同步异步收发器

定义

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

  • 【自带波特率发生器,最高达4.5Mbits/s】
  • 【可自动配置数据长度(8/9),停止位长度(0.5/1/1.5/2】
  • 【可以选择奇偶校验或者无校验】
  • 【支持同步模式,硬件流控制,DMA...】
  • 【STM32F103C8T6 -------------> USART资源:USART1,USART2,USART3】

USART1 是APB2总线上的设备,USART2/3是APB1总线上的设备

STM32F103C8T6引脚定义表

【引脚定义表取用自江科大的STM32配套资料,学习自江科大的STM32视频资料】

【波特率发生器:用于产生约定的通信速率】

【发送控制器】【接收控制器】:分别用于发送移位和接收移位

【发送数据寄存器和>>发送移位寄存器>> 】经过CPIO的复用输出到TX引脚

【接收数据寄存器和>>发送移位寄存器>> 】经过CPIO的复用输出到RX引脚

【开关控制】

我对以上表示含义的理解【不一定准确,可以去参考一下手册】

起始位侦测和接收数据对齐

SERIAL代码编写】9-1:串口数据发送----------> 代码部分

  • 代码编写步骤:

  • 【开启时钟,RCC开启SERIAL和GPIO时钟】

  • 【GPIO初始化TX配置为复用,RX配置为输出】

  • 【配置USART,直接使用结构体】

  • 【要同时接收的话还要额外配置ITConfig和USART的代码】

  • 【开启USART】

串口数据模式

获取余数相关知识(插入补充)

注: 【对万位数进行取余就是对10000取余然后再%10,取千位就是对1000取余%10】,对某一位进行取余就是/ 10 ^n次方然后对10取余数 】。


串口接收数据包

  •   数据包的格式【以FF为数据包的包头,FE位数据包的包尾】
  •   发送数据包格式为【FF 02 03 04 05 FE】
  •   接收数据包格式为【FF 66 88 99 98 FE】
  •   串口收发HEX数据包的程序现象,串口收发文本数据包的方式

程序的编写者,以约定的模式对发送的数据进行打包,设置文件的包头和文件的包尾,让数据按特定的格式进行收发。

几种不同的数据包简介

HEX数据包【在该数据包中数据是以字节本身为数据呈现的】

【解决发送和接收数据重复出错的方法】

  • 1:定义有效载荷,在限定的范围内发送对应的数据
  • 2:若无法避免载荷数据和包头包尾重复,尽量使用固定长度的数据包
  • 3:增加包头包尾的数量
  • 4:可以只要包头不要包尾

【文本数据包:需要经过编码和译码】

【HEX数据包接收】

状态机设计思维【重要】

【状态机--------------> 这是一种程序设计的思维,后面需要进一步的学习】

【以上的接收数据的方式采用状态机的方式进行开发记录的是三种不同的状态】

【第一个状态是等待包头,每一个状态需要使用一个变量来进行标志,第一个状态为S = 0】

【第二个状态是接收数据,第二个状态是S = 1】

【第三个状态是等待包尾,第三个状态是S = 2】

【第一个状态是收到一个数据进入中断,根据S = 0 进入第一个状态程序,判断数据是不是FF,如果是FF表示收到包头,之后设置状态S = 1 ,退出中断 结束,之后第二次就转入接收数据的状态将收到的数据存入数组中,用一个变量记录收了多少个数据... 最后一个状态就进入第三个状态】

【文本数据包的接收流程】 使用状态机的方式

程序实现(案例)

 串口函数指引

复位串口 void USART_DeInit(USART_TypeDef* USARTx);
初始化串口void USART_Init(USART_TypeDef* USARTx, 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);
 发送数据,写DR寄存器void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
接收数据,读DR寄存器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);

eg1:串口发送数据

注:程序编写步骤

  •  1:RCC开启USART和GPIO时钟
  •  2:GPIO初始化,将TX配置为复用输出,RX配置为输入
  • 【这里为什么要使用复用推挽输出,主要是开始引脚的电平是高电平,使用上拉的方式】
  •  3: 配置USART使用结构体将这里所有的参数配置完成
  •  4: 开启USART结束初始化
  • 【这里没有设置中断因为仅涉及到数据的发送,没有涉及到数据的接收】

9-1:程序接线图

模块化编程

Serial.c文件

 RCC开始时钟

       // 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // 开启USART1的时钟// 开启GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 初始化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);

串口初始化

       // 初始化USARTUSART_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;// 选择字长USART_InitStructure.USART_WordLength =USART_WordLength_8b;USART_Init(USART1, &USART_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;// 字符串的结束标志位,如果不等于0表示还没有结束,继续循环,如果等于0表示结束退出循环for(i = 0; String[i] != 0;i++ ){Serial_SendByte(String[i]); }}

...

Serial.c完整代码

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>// 1: 第一步开启时钟// 2: GPIO初始化,将TX配置为复用输出,RX配置为输入// 3: 配置USART使用结构体将这里所有的参数配置完成// 4: 开启USART结束初始化
void Serial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // 开启USART1的时钟// 开启GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 初始化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);// 初始化USARTUSART_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;// 选择字长USART_InitStructure.USART_WordLength =USART_WordLength_8b;USART_Init(USART1, &USART_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;// 字符串的结束标志位,如果不等于0表示还没有结束,继续循环,如果等于0表示结束退出循环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;
}// 调用SendNum发送一个数字
void Serial_SendNum(uint32_t Num,uint8_t Length){uint8_t i;for(i = 0; i < Length; i++){Serial_SendByte(Num / Serial_Pow(10,Length-i - 1) %10 + 0x30);}
}//移植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);
}

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_SendNum(uint32_t Num,uint8_t Length);
void Serial_Printf(char * format,...);
#endif

main.c主函数文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"uint8_t MyArray[] = {0x42,0x43,0x44,0x45};
int main(void)
{// 初始化oledOLED_Init();// 初始化串口Serial_Init();// 调用串口发送一个0x41Serial_SendByte(0x41);// 调用Serial_SendArraySerial_SendArray(MyArray,4);Serial_SendString("HelloWord!");Serial_SendNum(12345,5);printf("Num = %d\r\n",666);char String[100];sprintf(String,"Num = %d\r\n",666);Serial_SendString(String);Serial_Printf("Num = %d\r\n",888);while (1){}
}
eg2:串口发送接收  9-2

接线图

Serial.c代码

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>// 1: 第一步开启时钟// 2: GPIO初始化,将TX配置为复用输出,RX配置为输入// 3: 配置USART使用结构体将这里所有的参数配置完成// 4: 开启USART结束初始化uint8_t Serial_RxData;
uint8_t Serial_RxFlag;void Serial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // 开启USART1的时钟// 开启GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 初始化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);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);// 初始化USARTUSART_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的标志位到NVIC的中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);// 配置nvic,配置nvic的优先级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;// 字符串的结束标志位,如果不等于0表示还没有结束,继续循环,如果等于0表示结束退出循环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;
}
// 调用SendNum发送一个数字
void Serial_SendNum(uint32_t Num,uint8_t Length){uint8_t i;for(i = 0; i < Length; i++){Serial_SendByte(Num / Serial_Pow(10,Length-i - 1) %10 + 0x30);}
}
//移植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);
}
// 配置自己的函数
uint8_t Serial_GetRxFlag(void){if(Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}else{return 0;}
}
uint8_t Serial_GetRxData(void){return Serial_RxData;
}void SPI1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){Serial_RxData = USART_ReceiveData(USART1);Serial_RxFlag = 1;USART_ClearITPendingBit(USART1,USART_IT_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_SendNum(uint32_t Num,uint8_t Length);
void Serial_Printf(char * format,...);
uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);#endif

main函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"uint8_t RxData;
int main(void)
{// 初始化oledOLED_Init();// 初始化串口Serial_Init();OLED_ShowString(1, 1, "RxData:");while (1){if(Serial_GetRxFlag() == 1){RxData = Serial_GetRxData();Serial_SendByte(RxData);OLED_ShowHexNum(1, 8, RxData, 2);}}
}
eg3:串口发送HEX数据包 9-3

Serial.c代码

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>// 存储发送或者接收的载荷数据
uint8_t Serial_TxPacket[4];		
uint8_t Serial_RxPacket[4];		
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);			//开启串口接收数据的中断/*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线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1NVIC_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]);		//依次调用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;	//设置结果初值为1while (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发送每位数字}
}/*** 函    数:使用printf需要重定向的底层函数* 参    数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数return ch;
}/*** 函    数:自己封装的prinf函数* 参    数:format 格式化字符串* 参    数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100];				//定义字符数组va_list arg;					//定义可变参数列表数据类型的变量argva_start(arg, format);			//从format开始,接收参数列表到arg变量vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);					//结束变量argSerial_SendString(String);		//串口发送字符数组(字符串)
}void Serial_SendPacket(void){Serial_SendByte(0xFF); // 发送包头Serial_SendArray(Serial_TxPacket,4);Serial_SendByte(0xFE); // 发送包尾}/*** 函    数:获取串口接收标志位* 参    数:无* 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零*/
uint8_t Serial_GetRxFlag(void)
{if (Serial_RxFlag == 1)			//如果标志位为1{Serial_RxFlag = 0;return 1;					//则返回1,并自动清零标志位}return 0;						//如果标志位为0,则返回0
}/*** 函    数:USART1中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void USART1_IRQHandler(void)
{// 定义一个静态变量static uint8_t RxState = 0;static uint8_t pRxPacket = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断{uint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0){if(RxData == 0xFF){RxState = 1;pRxPacket = 0;}}else if(RxState == 1){Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;if(pRxPacket >= 4){RxState = 2;}}else if(RxState == 2){if(RxData == 0xFE){RxState = 0;Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);			//清除USART1的RXNE标志位//读取数据寄存器会自动清除此标志位//如果已经读取了数据寄存器,也可以不执行此代码}
}

Serial.h代码

#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>
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, ...);uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);void Serial_SendPacket(void);#endif

mian.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"uint8_t KeyNum;
uint8_t RxData;			//定义用于接收串口数据的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化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_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);}}
}

状态机的应用代码示例【后续要加深学习】

以上代码定义了三种不同的状态,满足其中一种状态就向另外一种状态转变

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Visual Components软件为您带来的价值 衡祖仿真

Visual Components具备丰富的3D仿真功能、快速建模能力、定制化应用程序逻辑和大量预定义组件库等多种特点&#xff0c;为自动化设备制造商、整合商、制造型公司提供简单、 快速和的设计方式&#xff0c;可以有效提高生产效率&#xff0c;并优化资源分配&#xff0c;避免制造过…

用户行为分析模型实践(四)—— 留存分析模型

作者&#xff1a;vivo 互联网大数据团队- Wu Yonggang、Li Xiong 本文是vivo互联网大数据团队《用户行为分析模型实践》系列文章第4篇 -留存分析模型。 本文详细介绍了留存分析模型的概念及基本原理&#xff0c;并阐述了其在产品中具体实现。针对在实际使用过程问题&#xff0…

揭秘消费增值:如何让每次购物都变得更有价值

亲爱的消费者朋友们&#xff0c;大家好&#xff01;我是微三云周丽&#xff0c;今天我将和你分享一种全新的消费理念——消费增值&#xff0c;让你的每一次消费都变得更有价值&#xff01; 在传统的消费观念中&#xff0c;我们通常只是单纯地用钱购买物品或享受服务&#xff0…

WideDeep

这里写目录标题 1. 背景2. 贡献3 模型结构&#xff08;1&#xff09;任务定义&#xff08;2&#xff09;The Wide Component&#xff08;3&#xff09;The Deep Component&#xff08;4&#xff09;联合训练Wide和Deep Model 4. 参考 1. 背景 (1) 广义线性回归通常被用于推荐模…

Scanpy(2)多种可视化

本篇内容为scanpy的可视化方法&#xff0c;可以分为三部分&#xff1a; embedding的散点图&#xff1b;用已知marker genes的聚类识别&#xff08;Identification of clusters&#xff09;&#xff1b;可视化基因的差异表达&#xff1b; 我们使用10x的PBMC数据集&#xff08;…

基于SSM的平面设计课程在线学习平台系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的平面设计课程在线学习平台系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;…

51单片机学习笔记——LED点亮

一、独立按键控制LED元器件和原理图 根据厂家给的原理图找到独立按键模块&#xff0c;观察下图我们知道按钮的一个头接GND&#xff0c;一头接IO口。由此可知我们如果需要使用第一个按钮则需要用p31。 二、独立按键控制LED程序 程序编写需要使用到IF else语句 当如果P310时P20…

盒子模型之弹性盒模型

弹性盒模型 经常适用于手机端图标布局 display: flex;让这个盒子显示成弹性盒&#xff08;很适合移动端布局&#xff09; 影响&#xff1a;1.让里面的子元素默认横向排列 2.如果子元素是行内元素&#xff0c;则直接变成块元素 3.只有一个元素&#xff0c;margin: auto;自动居中…

uni.uploadFile上传图片后台接收不到数据

今天遇到一个很奇怪的问题&#xff0c;通过使用uni.uploadFile上传文件时后端接收不到文件&#xff0c;查过很多资料&#xff0c;原来是自定义了header的Content-Type问题。取消即可&#xff0c;另把自定义文件上传的代码贴出来。 分析&#xff1a;当我们加上请求头的时候 不…

一步一步写线程之九线程池任务的窃取

一、介绍 在实际的工作安排中&#xff0c;如果有一个比较大的工作&#xff0c;又可以细分的&#xff0c;诸如有一天一万个萝卜要洗这样的工作。假如做为一个工作的分配者&#xff0c;怎么处理这种需求&#xff1f;可能每个人都会想&#xff0c;先看看一个人一天洗多少萝卜&…

大数据真题讲解系列——拼多多数据分析面试题

拼多多数据分析面试题&#xff1a;连续3次为球队得分的球员名单 问题&#xff1a; 两支篮球队进行了激烈的比赛&#xff0c;比分交替上升。比赛结束后&#xff0c;你有一个两队分数的明细表&#xff08;名称为“分数表”&#xff09;。表中记录了球队、球员号码、球员姓名、得…

CSS基础:盒子模型详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃&#xff0c;大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 We…

Liunx挂载硬件设备

一、mount命令&#xff08;用于挂载文件系统&#xff09; &#xff08;一&#xff09;语法格式&#xff1a;mount 参数 源设备路径 目的路径 &#xff08;二&#xff09;参数 1、-t&#xff1a;指定挂载的文件系统 &#xff08;1&#xff09;iso9660&#xff1a;光盘或光盘…

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available 解决办法&#xff1a;下载想安装的node压缩包&#xff0c;放入nvm对应目录。 2024年最新node压缩包地址&#xff1a;https://nodejs.org/dist/ 1、选择对应的node版本&#xff1a;例如&#xff0c;我选的…

乡政府管理系统|基于Springboot的乡政府管理系统设计与实现(源码+数据库+文档)

乡政府管理系统目录 目录 基于Springboot的乡政府管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、活动信息管理 3、新闻类型管理 4、新闻动态管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推…

FPGA中按键程序设计示例

本文中使用Zynq 7000系列中的xc7z035ffg676-2器件的100MHz PL侧的外部差分时钟来检测外部按键是否按下&#xff0c;当按键被按下时&#xff0c;对应的灯会被点亮。当松开按键时&#xff0c;对应的灯会熄灭。 1、编写代码 新建工程&#xff0c;选用xc7z035ffg676-2器件。 点击…

ansible执行mysql脚本

目录 概述实践环境要求ansible yml脚本命令离线包 概述 ansible执行mysql脚本 实践 官网文档 环境要求 环境需要安装以下内容: 1.mysql客户端(安装了mysql即会有)2.安装MySQL-python (Python 2.X) 详细插件安装链接 ansible yml脚本 关键代码如下&#xff1a; # 剧本…

vscode设置conda默认python环境,简单有效

本地conda 可能安装了各种环境&#xff0c;默认的vscode总是base环境&#xff0c;这时你想要在vscode调试python代码&#xff0c;使用默认的环境没有安装对应的包就会遇到报错解决这个问题的方法很简单ctrlshiftp 调出命令面板 再输入 select interpreter , 选择 python 选择解…

设计模式——2_9 模版方法(Template Method)

人们往往把任性也叫做自由&#xff0c;但是任性只是非理性的自由&#xff0c;人性的选择和自决都不是出于意志的理性&#xff0c;而是出于偶然的动机以及这种动机对感性外在世界的依赖 ——黑格尔 文章目录 定义图纸一个例子&#xff1a;从文件中获取信息分几步&#xff1f;Rea…

为什么用CubeMX配置STM32H7主频只能配到200,但实际配到400没报错,超过400报错,其他深色也要把前边的分频器向小调?

原因&#xff1a; STM32CUBEMX配置STM32H750时钟480M时失败_stm32h750 时钟配置_小李干净又卫生的博客-CSDN博客 STM32CUBEMX默认设置的是VOS1&#xff0c;是不能支持480M运行的&#xff0c;只能400 但还不清楚为什么这里没有更多选项Scale &#xff1f;