简介:最近在熟悉和探究OLED屏幕,本文是第一部分,实现了贪吃蛇的移动、增加删除和边缘检测(此边缘检测非计算机视觉的边缘检测,只是检测贪吃蛇有没有走出所规定的范围)
目录
第一部分代码效果展示
第一部分视频效果:
贪吃蛇第一部分
实现思路
在实现贪吃蛇时,可以先列出一个清单,然后一步一步实现。
例如我的实现步骤是:
1. 了解OLED → 在OLED规定位置画一个点 → 在OLED上画线
2. 实现线段长度不断增加 → 记录增加的点位,删除尾部的点位 → 控制方向 → 检测是否到达边缘
3. 贪吃蛇本体的碰撞检测
最后加上一些启动界面,就可以完成一个贪吃蛇了
1. OLED代码编写
在此之前,我的OLED代码只接触过江协科技的代码,我的代码中包括OLED初始化、写命令、设置光标、填值都是直接套用的江协科技的代码。观察江协科技代码可以发现,OLED的实现方式是先设置光标,再填值
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
一次性能填2个16进制的数,换成2进制例如1111_1111也就是8个像素点
自己代码编写部分
我采用了逻辑运算和位运算的方式来处理数据,下面两个函数实现了在OLED数组中赋一个点的值和清除一个点的值,具体实现是不断调试得到的
uint8_t OLED[8][128]={0};
void point(int x,int y)
{
int row=y%8;
OLED[y/8][x]=(0x01<<row)|OLED[y/8][x];
}
void point_clear(int x,int y)
{
int row=y%8;
OLED[y/8][x] &= ~(0x01 << row);
}
在实现给数组赋值后,我的第一个想法是把数组中的所有值同时填入OLED中,然后不断重复这个步骤,实际写好后会发现OLED屏幕刷新太快闪烁严重,所以采用了局部变化屏幕,及哪个位置改变了就把光标移到哪,下面三个函数依次实现了将所有值填入屏幕、让屏幕的一个像素点亮起、清除一个像素点
//刷新屏幕内容为数组内容
void my_try(){
for(int j=0;j<8;j++){
OLED_SetCursor(j, 0);//这个函数有延时
for(int i=0; i<128;i++){
OLED_WriteData(OLED[j][i]);
}
}
}
//局部变化屏幕,该变的地方变更加高效
void point1(int x,int y)
{
int row=y%8;
int reg1=y/8;
OLED_SetCursor(reg1,x);
OLED[reg1][x]=(0x01<<row)|OLED[reg1][x];
OLED_WriteData(OLED[reg1][x]);
}
void point1_clear(int x,int y)
{
int row=y%8;
int reg1=y/8;
OLED_SetCursor(reg1,x);
OLED[y/8][x] &= ~(0x01 << row);
OLED_WriteData(OLED[reg1][x]);
}
下面是清屏函数和画线函数,画线函数目前只能画水平和竖直方向的线,但是完成贪吃蛇也够用了
void clear_oled(void)
{
for(int j=0;j<8;j++){
OLED_SetCursor(j, 0);//这个函数有延时
for(int i=0; i<128;i++){
OLED[j][i]=0;
OLED_WriteData(OLED[j][i]);
}
}
}
void line(int x1,int y1,int x2,int y2)
{
if(x1==x2&&y2>y1){
for(int i=y1;i<y2;i++){
point(x1,i);
}
}
else if(x1==x2&&y2<y1){
for(int i=y2;i<y1;i++){
point(x1,i);
}
}
else if(y1==y2&&x2<x1){
for(int i=x2;i<x1;i++){
point(i,y1);
}
}
else if(y1==y2&&x1<x2){
for(int i=x1;i<x2;i++){
point(i,y1);
}
}
my_try();//OLED数组填入
}
OLED代码编写部分已完成
2. 贪吃蛇移动部分
贪吃蛇的移动我首先想到的是链表,每次移动链表首部增加、尾部删除,并且还能记录贪吃蛇身体所含坐标,下面的结构体声明了一个点和一条链表
//结构体声明
struct My_point{
int x;
int y;
};
struct Line_point{
struct My_point point;
struct Line_point *next;
struct Line_point *front;
};
struct Line{
struct Line_point *begin;
struct Line_point *end;
};
贪吃蛇移动实现,链表增首、删尾,L是Line的结构体,代表贪吃蛇,此外还加入判断是否吃到东西来取消一次贪吃蛇的尾部删除
//该函数会增加一个新的节点,删除一个旧的节点,同时会检测贪吃蛇是否吃到东西
void line_add_del(struct Line *L,int x,int y,int* eat){
point1(x,y);
//add
struct Line_point *w=(struct Line_point*)malloc(sizeof(struct Line_point));//创建一个新的节点并初始化
w->point.x=x;
w->point.y=y;
w->next=L->begin;
w->front=NULL;
if (L->begin != NULL) {
L->begin->front = w; // 原头节点的 front 指向新节点
} else {
L->end = w; // 如果链表为空,尾指针也指向新节点
}
L->begin = w;
//delete
if(*eat==0){
if (L->end != NULL) { // 确保链表非空
struct Line_point *to_delete = L->end; // 保存待删除节点
// 更新尾指针
L->end = L->end->front;
if (L->end != NULL) {
L->end->next = NULL; // 新尾节点的 next 置空
} else {
L->begin = NULL; // 如果删除后链表为空,头指针也置空
}
point1_clear(to_delete->point.x, to_delete->point.y);
free(to_delete); // 释放被删除节点的内存
}
}
*eat=0;
}
另外要注意STM32使用malloc函数特别容易堆栈溢出,具体表现为贪吃蛇长度增加到一定长度程序就卡死了,所以我们要在startup文件中修改堆栈大小,把它们调大即可
最后加入一些初始化代码即可开始测试功能,由于我目前没有按钮来控制贪吃蛇,所以我选择了运用现成的ASR PRO语言控制,使用其它控制把mes获取方式改为其它方式,例如按钮中断修改mes的值,然后修改go的值来判断贪吃蛇走的方向,下面是main函数部分:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "bsp_i2c.h"
#include <stdlib.h>
#include "Serial.h"
struct My_point{
int x;
int y;
};
struct Line_point{
struct My_point point;
struct Line_point *next;
struct Line_point *front;
};
struct Line{
struct Line_point *begin;
struct Line_point *end;
};
void line_add_del(struct Line *L,int x,int y,int *eat);
int main(void)
{
/*模块初始化*/
Usart1_Init();
OLED_Init(); //OLED初始化
int x=32,y=32;
int mes=0;
struct Line L = {NULL,NULL}; // 初始化链表为空
// 分配并初始化第一个节点(头节点)
struct Line_point *node1 = (struct Line_point*)malloc(sizeof(struct Line_point));
node1->point.x = 33;
node1->point.y = 34;
node1->front = NULL; // 头节点的 front 应为 NULL
node1->next = NULL; // 初始时 next 也为 NULL(仅一个节点)
point1(node1->point.x,node1->point.y);
// 分配并初始化第二个节点(尾节点)
struct Line_point *node2 = (struct Line_point*)malloc(sizeof(struct Line_point));
node2->point.x = 33;
node2->point.y = 33;
node2->front = node1; // 尾节点的 front 指向头节点
node2->next = NULL; // 尾节点的 next 应为 NULL
point1(node2->point.x,node2->point.y);
// 连接头节点和尾节点
node1->next = node2;
// 更新链表头尾指针
L.begin = node1;
L.end = node2;
//go表示前进方向,x轴和y轴都有两个方向,用1234表示四个方向
uint8_t go=0x32;//1上2下 3左4右
int pause=0;
int eat=0;
//在OLED上画一个长方形边界
line(0,0,0,63);
line(0,0,127,0);
line(127,63,0,63);
line(127,63,127,0);
my_try();
while (1)
{
//my_try();
if(pause==0){
if (Usart1_GetRxFlag() == 1) //检查串口接收数据的标志位,一旦检查到为1后标志位会自动置为0
{
mes = Usart1_GetRxData(); //获取串口接收的数据
go=mes;
mes=0;
}
if(go==0x31&&x<128&&y<64){
y--;
line_add_del(&L,x,y,&eat);
}
if(go==0x32&&x<128&&y<64){
y++;
line_add_del(&L,x,y,&eat);
}
if(go==0x33&&x<128&&y<64){
x--;
line_add_del(&L,x,y,&eat);
}
if(go==0x34&&x<128&&y<64){
x++;
line_add_del(&L,x,y,&eat);
}
if(x>=128||y>=64)
{
clear_oled();
line(0,0,0,63);
line(0,0,127,0);
line(127,63,0,63);
line(127,63,127,0);
my_try();
OLED_ShowString(2, 5 , "fail");
x=33;
y=33;
pause=1;
}
}
Delay_ms(200);
}
}
//该函数会增加一个新的节点,删除一个旧的节点,同时会检测贪吃蛇是否吃到东西
void line_add_del(struct Line *L,int x,int y,int* eat){
point1(x,y);
//add
struct Line_point *w=(struct Line_point*)malloc(sizeof(struct Line_point));//创建一个新的节点并初始化
w->point.x=x;
w->point.y=y;
w->next=L->begin;
w->front=NULL;
if (L->begin != NULL) {
L->begin->front = w; // 原头节点的 front 指向新节点
} else {
L->end = w; // 如果链表为空,尾指针也指向新节点
}
L->begin = w;
//delete
if(*eat==0){
if (L->end != NULL) { // 确保链表非空
struct Line_point *to_delete = L->end; // 保存待删除节点
// 更新尾指针
L->end = L->end->front;
if (L->end != NULL) {
L->end->next = NULL; // 新尾节点的 next 置空
} else {
L->begin = NULL; // 如果删除后链表为空,头指针也置空
}
point1_clear(to_delete->point.x, to_delete->point.y);
free(to_delete); // 释放被删除节点的内存
}
}
*eat=0;
}
OLED.c
#include "stm32f10x.h"
#include "OLED_Font.h"
#include "Delay.h"
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
uint8_t OLED[8][128]={0};
void point(int x,int y)
{
int row=y%8;
OLED[y/8][x]=(0x01<<row)|OLED[y/8][x];
}
void point_clear(int x,int y)
{
int row=y%8;
OLED[y/8][x] &= ~(0x01 << row);
}
//刷新屏幕内容为数组内容
void my_try(){
for(int j=0;j<8;j++){
OLED_SetCursor(j, 0);//这个函数有延时
for(int i=0; i<128;i++){
OLED_WriteData(OLED[j][i]);
}
}
}
//局部变化屏幕,该变的地方变更加高效
void point1(int x,int y)
{
int row=y%8;
int reg1=y/8;
OLED_SetCursor(reg1,x);
OLED[reg1][x]=(0x01<<row)|OLED[reg1][x];
OLED_WriteData(OLED[reg1][x]);
}
void point1_clear(int x,int y)
{
int row=y%8;
int reg1=y/8;
OLED_SetCursor(reg1,x);
OLED[y/8][x] &= ~(0x01 << row);
OLED_WriteData(OLED[reg1][x]);
}
void clear_oled(void)
{
for(int j=0;j<8;j++){
OLED_SetCursor(j, 0);//这个函数有延时
for(int i=0; i<128;i++){
OLED[j][i]=0;
OLED_WriteData(OLED[j][i]);
}
}
}
void line(int x1,int y1,int x2,int y2)
{
if(x1==x2&&y2>y1){
for(int i=y1;i<y2;i++){
point(x1,i);
}
}
else if(x1==x2&&y2<y1){
for(int i=y2;i<y1;i++){
point(x1,i);
}
}
else if(y1==y2&&x2<x1){
for(int i=x2;i<x1;i++){
point(i,y1);
}
}
else if(y1==y2&&x1<x2){
for(int i=x1;i<x2;i++){
point(i,y1);
}
}
my_try();//OLED数组填入
}
OLED.h
#ifndef __OLED_H
#define __OLED_H
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_SetCursor(uint8_t Y, uint8_t X);
void OLED_WriteData(uint8_t Data);
void my_try(void);
void point(int x,int y);
void point_clear(int x,int y);
void point1(int x,int y);
void line(int x1,int y1,int x2,int y2);
void clear_oled(void);
void point1_clear(int x,int y);
#endif
Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
Serial.c串口控制部分,只使用了串口1,很多代码都没用,其中串口2是esp8266的代码,要学习可以借鉴一下
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Usart1_RxData; //定义串口1接收的数据变量
uint8_t Usart1_RxFlag; //定义串口1接收的标志位变量
uint8_t Usart2_RxData; //定义串口1接收的数据变量
uint8_t Usart2_RxFlag; //定义串口1接收的标志位变量
//串口1初始化
void Usart1_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; //波特率,9600,115200
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线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
//串口2初始化
void Usart2_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //开启USART2的时钟
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_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA3引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 115200; //波特率
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(USART2, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART2
/*中断输出配置*/
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //选择配置NVIC的USART2线
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(USART2, ENABLE); //使能USART2,串口开始运行
}
//串口2初始化
void Usart3_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //开启USART2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA3引脚初始化为上拉输入
/*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(USART3, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART2
/*USART使能*/
USART_Cmd(USART3, ENABLE); //使能USART2,串口开始运行
}
void Usart1_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
void Usart2_SendByte(uint8_t Byte)
{
USART_SendData(USART2, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
void Usart3_SendByte(uint8_t Byte)
{
USART_SendData(USART3, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
void Usart1_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Usart1_SendByte(String[i]); //依次调用Usart2_SendByte发送每个字节数据
}
}
void Usart2_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Usart2_SendByte(String[i]); //依次调用Usart2_SendByte发送每个字节数据
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于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 Usart1_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Usart1_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Usart1_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Usart1_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Usart1_SendString(String); //串口发送字符数组(字符串)
}
uint8_t Usart1_GetRxFlag(void)
{
if (Usart1_RxFlag == 1) //如果标志位为1
{
Usart1_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
uint8_t Usart1_GetRxData(void)
{
return Usart1_RxData; //返回接收的数据变量
}
uint8_t Usart2_GetRxFlag(void)
{
if (Usart2_RxFlag == 1) //如果标志位为1
{
Usart2_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
uint8_t Usart2_GetRxData(void)
{
return Usart2_RxData; //返回接收的数据变量
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Usart1_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Usart1_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
typedef enum {
ESP_IDLE, // 空闲状态,等待起始符
ESP_FOUND_PLUS, // 检测到 '+'
ESP_FOUND_IPD, // 检测到 'I','P','D'
ESP_GET_ID, // 获取连接ID(可选)
ESP_GET_LENGTH, // 获取数据长度
ESP_RECEIVE_DATA, // 接收数据内容
ESP_WAIT_END // 等待数据结束(可选)
} ESP_ParseState;
ESP_ParseState espState = ESP_IDLE;
uint8_t rxBuffer[10];
uint8_t dataBuffer[10];
uint16_t dataLength = 0;
uint16_t receivedBytes = 0;
uint8_t connectionId = 0;
uint8_t data[10];
/**
* 函 数:USART2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
//void USART2_IRQHandler(void)
//{
// if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //判断是否是USART2的接收事件触发的中断
// {
// Usart2_RxData = USART_ReceiveData(USART2); //读取数据寄存器,存放在接收的数据变量
// Usart2_RxFlag = 1; //置接收标志位变量为1
// USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除USART2的RXNE标志位
// //读取数据寄存器会自动清除此标志位
// //如果已经读取了数据寄存器,也可以不执行此代码
// }
//}
void USART2_IRQHandler(void) {
if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) {
uint8_t byte = USART_ReceiveData(USART2);
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除USART2的RXNE标志位
switch (espState) {
case ESP_IDLE:
if (byte == '+') {
espState = ESP_FOUND_PLUS;
rxBuffer[0] = byte;
}
break;
case ESP_FOUND_PLUS:
if (byte == 'I') {
}
else if (byte == 'P') {
espState = ESP_FOUND_IPD;
rxBuffer[1] = byte;
} else {
espState = ESP_IDLE; // 非预期字符,重置
}
break;
case ESP_FOUND_IPD:
if (byte == 'D') {
espState = ESP_GET_ID; // 进入获取ID/长度状态
rxBuffer[2] = byte;
} else {
espState = ESP_IDLE; // 错误,重置
}
break;
case ESP_GET_ID:
if(byte == ','){
}
else{
// 解析连接ID(可选,若存在)
connectionId = byte - '0'; // 假设ID为单数字
espState = ESP_GET_LENGTH;
rxBuffer[3] = byte;
}
break;
case ESP_GET_LENGTH:
if(byte == ','){
}
else{
// 提取数据长度(ASCII转10进制)
dataLength = (byte - '0'); // 简单示例,支持多位需循环处理
espState = ESP_RECEIVE_DATA;
rxBuffer[4] = byte;
receivedBytes = 0;
}
break;
case ESP_RECEIVE_DATA:
// 接收数据内容
dataBuffer[receivedBytes++] = byte;
// 检查是否接收完指定长度数据
if (receivedBytes >= dataLength) {
espState = ESP_IDLE; // 完成,准备处理数据
for(int i=0;i<receivedBytes;i++){
data[i]=dataBuffer[i];
dataBuffer[i]=NULL;
}
receivedBytes=0;
}
break;
default:
espState = ESP_IDLE;
break;
}
}
}
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
void Usart1_Printf(char *format, ...);
void Usart1_Init(void);
void Usart2_Init(void);
void Usart1_SendByte(uint8_t Byte);
void Usart2_SendByte(uint8_t Byte);
void Usart1_SendString(char *String);
void Usart2_SendString(char *String);
void Usart1_SendNumber(uint32_t Number, uint8_t Length);
void Usart3_Init(void);
void Usart3_SendByte(uint8_t Byte);
uint8_t Usart1_GetRxFlag(void);
uint8_t Usart1_GetRxData(void);
uint8_t Usart2_GetRxFlag(void);
uint8_t Usart2_GetRxData(void);
#endif