使用STM32在OLED上制作贪吃蛇(1)

简介:最近在熟悉和探究OLED屏幕,本文是第一部分,实现了贪吃蛇的移动、增加删除和边缘检测(此边缘检测非计算机视觉的边缘检测,只是检测贪吃蛇有没有走出所规定的范围)

目录

第一部分代码效果展示

实现思路

1. OLED代码编写

自己代码编写部分

2. 贪吃蛇移动部分


第一部分代码效果展示

第一部分视频效果:

贪吃蛇第一部分

实现思路

        在实现贪吃蛇时,可以先列出一个清单,然后一步一步实现。

例如我的实现步骤是:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值