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);
|
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);}}
}
状态机的应用代码示例【后续要加深学习】
以上代码定义了三种不同的状态,满足其中一种状态就向另外一种状态转变