分享

06 洋桃开发板笔记(六 ) STM32自带的Flash闪存使用,主要配合其他外设

 用久智能 2020-07-05

Flash闪存与其他外设的使用

杜洋工作室 www.DoYoung.net

洋桃电子 www.DoYoung.net/YT

  • 在此声明一下所有代码均为 杜洋工作室 的不允许复制,转发等,本人只是在此程序上进行理解和注释。


上一次的笔记是在洋桃开发板上进行Flash的使用,主要为洋桃电子的代码。对Flash的基本了解有兴趣可以去看看:
https://blog.csdn.net/qq_40546576/article/details/99305530

本次实验结果结合视频模式:
https://www.bilibili.com/video/av64134556

本次主要讲的是Flash闪存的使用。由于本次特殊,需要截取部分图片进行讲解,还有代码有点长,可能造成了观看不适,请大家谅解!谢谢。


探索

每一个地址存放16位无符号的数据有多大,并且我们用单片机的oled屏幕来显示自己存取的数据。
要求:
1、用OLED显示器显示数据地址及对应数据
2、地址采用0x的16进制,数据采用16进制
3、利用按键修改内存数据并且探索最大数值(数据)
4、存放数据位数种类较多,探索其每一位地址标准容量(最小容量)多大?


Flash的固件库

建议小伙伴多看看《STM32F103固件函数库用户手册(中文)》的105页,里面有详细的库函数。我们本次截取需要的部分,进行理解Flash。

flash存放数据的长度_以及_写Flash的函数

在固件库里解释了flash存放数据的长度分有3种:字(32 bit)、半字(16 bit)、字节(8 bit)。可能和我们学其他的编程语言不一样,这个无所谓。每家都有自己的风格。下面是固件库的解释:
在这里插入图片描述
这个为stm32f10x_flash.h声明中:

在这里插入图片描述
上面解决了,flash的最小容量数据为一个字节(8 bit),下面程序中我们采用半字(16 bit)进行编程。
写字的函数
在这里插入图片描述
写半字的函数
在这里插入图片描述
写字节的函数
在这里插入图片描述

写入Flash数据用到的其他函数

flash存储器在写入数据前必须清除该页的所有数据,因为stm32flash采用的是NAND Flash,所以每次清除数据最小只能以页为单位。
1、以下为擦除函数:
在这里插入图片描述
2、解锁Flash以及上锁:
由于Flash擦写次数有限,所以就有了对Flash的保护
在这里插入图片描述
我们每次写入数据需要解锁,每次写完数据需要上锁。以防误操作。
3、清除Flash标志位
为什么要清除标志位,我是真的不知道为社么,如果有小伙伴知道,可以下面留言啊!万分感激!!
在这里插入图片描述

以上的flash相关库函数了解过后,我们就来解释Flash.c文件,如何编写_Flash_W函数,Flash_R函数_就应该有所了解。

读数据因为不需要写入,自己读,所以洋桃电子直接用指针方法。


下面编写本次测试的主要代码:

一、flash.c和flash.h的文件

这两个文件和上次一样基本不变

flash.c文件
#include "flash.h"

//FLASH写入数据
void FLASH_W(u32 add,u16 dat){ //参数1:32位FLASH地址。参数2:16位数据
//	 RCC_HSICmd(ENABLE); //打开HSI时钟
	 FLASH_Unlock();  //解锁FLASH编程擦除控制器
     FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
     FLASH_ErasePage(add);     //擦除指定地址页
     FLASH_ProgramHalfWord(add,dat); //从指定页的addr地址开始写
     FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
     FLASH_Lock();    //锁定FLASH编程擦除控制器
}

//FLASH读出数据
u16 FLASH_R(u32 add){ //参数1:32位读出FLASH地址。返回值:16位数据
	u16 a;
    a = *(u16*)(add);//从指定页的addr地址开始读
return a;
}
flash.h文件
#ifndef __FLASH_H
#define __FLASH_H 			   
#include "sys.h"
 

void FLASH_W(u32 add,u16 dat);
u16 FLASH_R(u32 add);

#endif

二、编写自己的字库CH_16x16.h

该文件删除了洋桃家原有文件,用取模软件,获取自己需要的字库,“状态”,“读”,“写”,“地址”,“数据”

#ifndef __CHS_16x16_H
#define __CHS_16x16_H	 

uc8 GB_16[] = {         // 数据表 
	  0x00,0x08,0x30,0x00,0xFF,0x20,0x20,0x20,//"状", 
      0x20,0xFF,0x20,0x22,0x24,0x30,0x20,0x00,
      0x08,0x0C,0x02,0x01,0xFF,0x40,0x20,0x1C,
      0x03,0x00,0x03,0x0C,0x30,0x60,0x20,0x00,

	  0x04,0x04,0x84,0x84,0x44,0x24,0x54,0x8F,//"态", 
      0x14,0x24,0x44,0x44,0x84,0x86,0x84,0x00,
      0x01,0x21,0x1C,0x00,0x3C,0x40,0x42,0x4C,
      0x40,0x40,0x70,0x04,0x08,0x31,0x00,0x00,

   	  0x40,0x40,0x42,0xCC,0x00,0x20,0x24,0x24,//"读",
      0x64,0xA4,0x3F,0xE4,0x26,0xA4,0x60,0x00,
      0x00,0x00,0x00,0x7F,0x20,0x14,0x84,0x85,
      0x46,0x24,0x1C,0x27,0x44,0xC6,0x04,0x00,

      0x08,0x06,0x02,0x02,0xFA,0x22,0x22,0x22,//"写", 
      0x22,0x22,0x32,0x22,0x82,0x0A,0x06,0x00,
      0x00,0x08,0x08,0x08,0x09,0x09,0x09,0x09,
      0x09,0x4D,0x89,0x41,0x3F,0x01,0x00,0x00,

	  0x40,0x40,0xFE,0x40,0x40,0x80,0xFC,0x40,//"地", 
      0x40,0xFF,0x20,0x20,0xF0,0x20,0x00,0x00,
      0x20,0x60,0x3F,0x10,0x10,0x00,0x3F,0x40,
      0x40,0x5F,0x44,0x48,0x47,0x40,0x70,0x00,

 	  0x10,0x10,0x10,0xFF,0x10,0x18,0x10,0xF8,//"址",
      0x00,0x00,0xFF,0x20,0x20,0x30,0x20,0x00,
      0x20,0x60,0x20,0x3F,0x10,0x50,0x48,0x7F,
      0x40,0x40,0x7F,0x40,0x40,0x60,0x40,0x00,

      0x10,0x92,0x54,0x30,0xFF,0x50,0x94,0x32,//"数", 
      0xD8,0x17,0x10,0x10,0xF0,0x18,0x10,0x00,
      0x02,0x82,0x4E,0x33,0x22,0x52,0x8E,0x40,
      0x23,0x14,0x08,0x16,0x61,0xC0,0x40,0x00,

      0x10,0x10,0x10,0xFF,0x90,0x50,0xFE,0x92,//"据",
      0x92,0x92,0xF2,0x92,0x92,0xDF,0x82,0x00,
      0x02,0x42,0x81,0x7F,0x40,0x38,0x07,0xFC,
      0x44,0x44,0x47,0x44,0x44,0xFE,0x04,0x00
};

#endif

三、旋转编码器encoder.c和encoder.h

这两个一样也不需要修改,直接采用洋桃家的就可以了。

encoder.c文件
#include "encoder.h"


u8 KUP;//旋钮锁死标志(1为锁死)
u16 cou;

void ENCODER_Init(void){ //接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
    GPIO_InitStructure.GPIO_Pin = ENCODER_L | ENCODER_D; //选择端口号                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(ENCODER_PORT_A,&GPIO_InitStructure);	

    GPIO_InitStructure.GPIO_Pin = ENCODER_R; //选择端口号                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(ENCODER_PORT_B,&GPIO_InitStructure);				
}

u8 ENCODER_READ(void){ //接口初始化
	u8 a;//存放按键的值
	u8 kt;
	a=0;
	if(GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L))KUP=0;	//判断旋钮是否解除锁死
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&KUP==0){ //判断是否旋转旋钮,同时判断是否有旋钮锁死
		delay_us(100);
		kt=GPIO_ReadInputDataBit(ENCODER_PORT_B,ENCODER_R);	//把旋钮另一端电平状态记录
		delay_ms(3); //延时
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)){ //去抖
			if(kt==0){ //用另一端判断左或右旋转
				a=1;//右转
			}else{
				a=2;//左转
			}
			cou=0; //初始锁死判断计数器
			while(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&cou<60000){ //等待放开旋钮,同时累加判断锁死
				cou++;KUP=1;delay_us(20); //
			}
		}
	}
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)&&KUP==0){ //判断旋钮是否按下  
		delay_ms(20);
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)){ //去抖动
			a=3;//在按键按下时加上按键的状态值
			//while(ENCODER_D==0);	等等旋钮放开
		}
	}
	return a;
} 

encoder.h文件

#ifndef __ENCODER_H
#define __ENCODER_H	 
#include "sys.h"
#include "delay.h"

#define ENCODER_PORT_A	GPIOA		//定义IO接口组
#define ENCODER_L	GPIO_Pin_6	//定义IO接口
#define ENCODER_D	GPIO_Pin_7	//定义IO接口

#define ENCODER_PORT_B	GPIOB		//定义IO接口组
#define ENCODER_R	GPIO_Pin_2	//定义IO接口


void ENCODER_Init(void);//初始化
u8 ENCODER_READ(void);


		 				    
#endif

四、I2C文件需要修改

由于洋桃家的I2C默认把,总线通信速度调为200000,我本人实验是发现我的数据不正常,于是把速度调低,解决乱码问题。在i2c.h文件中修改BusSpeed即可,其他不变。以后有时间我们来解释I2C的通信原理。

i2c.h文件
#ifndef __I2C_H
#define __I2C_H	 
#include "sys.h"

#define I2CPORT		GPIOB	//定义IO接口
#define I2C_SCL		GPIO_Pin_6	//定义IO接口
#define I2C_SDA		GPIO_Pin_7	//定义IO接口

#define HostAddress	0xc0	//总线主机的器件地址
#define BusSpeed	100000	//总线速度(不高于400000)


void I2C_Configuration(void);
void I2C_SAND_BUFFER(u8 SlaveAddr, u8 WriteAddr, u8* pBuffer, u16 NumByteToWrite);
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer);
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead);
u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr);
		 				    
#endif
i2c.c文件
#include "i2c.h"


void I2C_GPIO_Init(void){ //I2C接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); //启动I2C功能 
    GPIO_InitStructure.GPIO_Pin = I2C_SCL | I2C_SDA; //选择端口号                      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(I2CPORT, &GPIO_InitStructure);
}

void I2C_Configuration(void){ //I2C初始化
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_GPIO_Init(); //先设置GPIO接口的状态
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//设置为I2C模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = HostAddress; //主机地址(从机不得用此地址)
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//允许应答
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //7位地址模式
	I2C_InitStructure.I2C_ClockSpeed = BusSpeed; //总线速度设置 	
	I2C_Init(I2C1,&I2C_InitStructure);
	I2C_Cmd(I2C1,ENABLE);//开启I2C					
}

void I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite){ //I2C发送数据串(器件地址,寄存器,内部地址,数量)
	I2C_GenerateSTART(I2C1,ENABLE);//产生起始位
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);//发送器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6
	I2C_SendData(I2C1,WriteAddr); //内部功能地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//移位寄存器非空,数据寄存器已空,产生EV8,发送数据到DR既清除该事件
	while(NumByteToWrite--){ //循环发送数据	
		I2C_SendData(I2C1,*pBuffer); //发送数据
		pBuffer++; //数据指针移位
		while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除EV8
	}
	I2C_GenerateSTOP(I2C1,ENABLE);//产生停止信号
}
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer){ //I2C发送一个字节(从地址,内部地址,内容)
	I2C_GenerateSTART(I2C1,ENABLE); //发送开始信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //等待完成	
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送从器件地址及状态(写入)
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待完成	
	I2C_SendData(I2C1,writeAddr); //发送从器件内部寄存器地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_SendData(I2C1,pBuffer); //发送要写入的内容
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_GenerateSTOP(I2C1,ENABLE); //发送结束信号
}
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead){ //I2C读取数据串(器件地址,寄存器,内部地址,数量)
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);//开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));	//清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //写入器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除 EV6
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr); //发送读的地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //清除 EV8
	I2C_GenerateSTART(I2C1,ENABLE); //开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver); //将器件地址传出,主机为读
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //清除EV6
	while(NumByteToRead){
		if(NumByteToRead == 1){ //只剩下最后一个数据时进入 if 语句
			I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
			I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
		}
		if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)){ //读取数据
			*pBuffer = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pBuffer
			pBuffer++; //指针移位
			NumByteToRead--; //字节数减 1 
		}
	}
	I2C_AcknowledgeConfig(I2C1,ENABLE);
}
u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr){ //I2C读取一个字节
	u8 a;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); 
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
	I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
	a = I2C_ReceiveData(I2C1);
	return a;
}

五、触摸按键touch_key.c和touch_key.h

不需要改变,直接采用洋桃家的就可以了
在此就不展示了

六、oled.c和oled.h文件

这两个文件添加了自己测试需要的函数。
void OLED_DISPLAY_8x16_DATA32(u8 x,u8 y,u32 w);//数据显示
void OLED_DISPLAY_8x16_DATA16(u8 x,u8 y,u16 w);//数据显示
void OLED_DISPLAY_8x16_DATA8(u8 x,u8 y,u8 w);//数据显示

oled.h文件
#ifndef __OLED_H
#define __OLED_H	 
#include "sys.h"
#include "i2c.h"

#define OLED0561_ADD	0x78  // OLED的I2C地址(禁止修改)
#define COM				0x00  // OLED 指令(禁止修改)
#define DAT 			0x40  // OLED 数据(禁止修改)

void OLED0561_Init(void);//初始化
void OLED_DISPLAY_ON (void);//OLED屏开显示
void OLED_DISPLAY_OFF (void);//OLED屏关显示
void OLED_DISPLAY_LIT (u8 x);//OLED屏亮度设置(0~255)
void OLED_DISPLAY_CLEAR(void);//清屏操作
void OLED_DISPLAY_8x16(u8 x,u8 y,u16 w);//显示8x16的单个字符 
void OLED_DISPLAY_8x16_BUFFER(u8 row,u8 *str);//显示8x16的字符串

void OLED_DISPLAY_16x16(u8 x,u8 y,u16 w); //汉字显示
void OLED_DISPLAY_PIC1(void);//图片显示

void OLED_DISPLAY_8x16_DATA32(u8 x,u8 y,u32 w);//数据显示
void OLED_DISPLAY_8x16_DATA16(u8 x,u8 y,u16 w);//数据显示
void OLED_DISPLAY_8x16_DATA8(u8 x,u8 y,u8 w);//数据显示
		 				    
#endif
oled.c文件,此代码有所 省略 ——省略主要为洋桃家代码
#include "usart.h"
#include "delay.h"
#include "oled0561.h"
#include "ASCII_8x16.h" //引入字体 ASCII

#include "CHS_16x16.h" //引入汉字字体 
#include "PIC1.h" //引入图片

void OLED0561_Init (void){//OLED屏开显示初始化
	OLED_DISPLAY_OFF(); //OLED关显示
	OLED_DISPLAY_CLEAR(); //清空屏幕内容
	OLED_DISPLAY_ON(); //OLED屏初始值设置并开显示
}
void OLED_DISPLAY_ON (void){//OLED屏初始值设置并开显示
	u8 buf[28]={
	0xae,//0xae:关显示,0xaf:开显示
    0x00,0x10,//开始地址(双字节)       
	0xd5,0x80,//显示时钟频率?
	0xa8,0x3f,//复用率?
	0xd3,0x00,//显示偏移?
	0XB0,//写入页位置(0xB0~7)
	0x40,//显示开始线
	0x8d,0x14,//VCC电源
	0xa1,//设置段重新映射?
	0xc8,//COM输出方式?
	0xda,0x12,//COM输出方式?
	0x81,0xff,//对比度,指令:0x81,数据:0~255(255最高)
	0xd9,0xf1,//充电周期?
	0xdb,0x30,//VCC电压输出
	0x20,0x00,//水平寻址设置
	0xa4,//0xa4:正常显示,0xa5:整体点亮
	0xa6,//0xa6:正常显示,0xa7:反色显示
	0xaf//0xae:关显示,0xaf:开显示
	}; //
	I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,28);
}
....
此处省略直接采用洋桃家的代码即可
....

/*********************************************************************************************
 * 杜洋工作室 www.DoYoung.net
 * 洋桃电子 www.DoYoung.net/YT 
*********************************************************************************************/
//Mannix添加代码
void OLED_DISPLAY_8x16_DATA32(u8 x,u8 y,u32 w){ 
	u8 i,index=0;//i为取数据的为位数
	u16 data[8];//声明一个相应位数的数组,存放数值
	
	i=8;//数值一共有8位
	index=7;		   //数组的数据从最后一个往前放,
	while(i--)		   //数值的低位先放入数组的最后一位
	{
		data[index]=w%0x10;//数值的低位先放入数组的后面
		w/=0x10; //去掉取过的位数的数值
		index--; //数组索引向前跑一位
	}

	index=0;  //数组索引归零,数组一次放入OLED中显示
	while(index!=8)
	{
		if(data[index]==0xa)//需要判断是否为字母,自动识别字母显示到屏幕内
		{
			OLED_DISPLAY_8x16(x,y*8,'a');
		}else if(data[index]==0xb){
			OLED_DISPLAY_8x16(x,y*8,'b');
		}else if(data[index]==0xc){
			OLED_DISPLAY_8x16(x,y*8,'c');
		}else if(data[index]==0xd){
			OLED_DISPLAY_8x16(x,y*8,'d');
		}else if(data[index]==0xe){
			OLED_DISPLAY_8x16(x,y*8,'e');
		}else if(data[index]==0xf){
			OLED_DISPLAY_8x16(x,y*8,'f');
		}else{
			OLED_DISPLAY_8x16(x,y*8,data[index]+0X30);	//数字显示到屏幕内
		}
		y++; //下一个数的完整列数,自动换到下一个数显示
		index++; //下一个数值
    }
}
void OLED_DISPLAY_8x16_DATA16(u8 x,u8 y,u16 w){	
	u8 i,index=0;//i为取数据的为位数
	u16 data[4];//声明一个相应位数的数组,存放数值
	
	i=4;//数值一共有4位
	index=3;		   //数组的数据从最后一个往前放,
	while(i--)		   //数值的低位先放入数组的最后一位
	{
		data[index]=w%0x10;//数值的低位先放入数组的后面
		w/=0x10; //去掉取过的位数的数值
		index--; //数组索引向前跑一位
	}

	index=0;  //数组索引归零,数组一次放入OLED中显示
	while(index!=4)
	{
		if(data[index]==0xa)//需要判断是否为字母,自动识别字母显示到屏幕内
		{
			OLED_DISPLAY_8x16(x,y*8,'a');
		}else if(data[index]==0xb){
			OLED_DISPLAY_8x16(x,y*8,'b');
		}else if(data[index]==0xc){
			OLED_DISPLAY_8x16(x,y*8,'c');
		}else if(data[index]==0xd){
			OLED_DISPLAY_8x16(x,y*8,'d');
		}else if(data[index]==0xe){
			OLED_DISPLAY_8x16(x,y*8,'e');
		}else if(data[index]==0xf){
			OLED_DISPLAY_8x16(x,y*8,'f');
		}else{
			OLED_DISPLAY_8x16(x,y*8,data[index]+0X30);	//数字显示到屏幕内
		}
		y++; //下一个数的完整列数,自动换到下一个数显示
		index++; //下一个数值
    }
}

void OLED_DISPLAY_8x16_DATA8(u8 x,u8 y,u8 w){ 	
	u8 i,index=0;//i为取数据的为位数
	u8 data[2];//声明一个相应位数的数组,存放数值
	
	i=2;//数值一共有4位
	index=1;		   //数组的数据从最后一个往前放,
	while(i--)		   //数值的低位先放入数组的最后一位
	{
		data[index]=w%0x10;//数值的低位先放入数组的后面
		w/=0x10; //去掉取过的位数的数值
		index--; //数组索引向前跑一位
	}

	index=0;  //数组索引归零,数组一次放入OLED中显示
	while(index!=2)
	{
		if(data[index]==0xa)//需要判断是否为字母,自动识别字母显示到屏幕内
		{
			OLED_DISPLAY_8x16(x,y*8,'a');
		}else if(data[index]==0xb){
			OLED_DISPLAY_8x16(x,y*8,'b');
		}else if(data[index]==0xc){
			OLED_DISPLAY_8x16(x,y*8,'c');
		}else if(data[index]==0xd){
			OLED_DISPLAY_8x16(x,y*8,'d');
		}else if(data[index]==0xe){
			OLED_DISPLAY_8x16(x,y*8,'e');
		}else if(data[index]==0xf){
			OLED_DISPLAY_8x16(x,y*8,'f');
		}else{
			OLED_DISPLAY_8x16(x,y*8,data[index]+0X30);	//数字显示到屏幕内
		}
		y++; //下一个数的完整列数,自动换到下一个数显示
		index++; //下一个数值
    }
}

七、主函数main.c,重头戏需要大家品味

由于我个人编写代码时,喜欢采用大学C语言的标准写法,语句略微长些,但是语句清楚。代码均为本人编写,注释也具备,如果不想看的可以直接复制。但是需要上面的所以文件配置好,主函数才可以运行!

#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "rtc.h"
#include "oled0561.h"
#include "flash.h"
#include "key.h"
#include "touch_key.h"
#include "encoder.h"

 
int main (void){//主程序
	u8 i,k1,k2,k4,index;//i为数组变量  K1触摸按键A变量   K2 为触摸按键B  K4为触摸按键D  index为数组索引
	u8 b=0;	 //旋转编码器的状态变量
	u32 addr[8];//声明一个相应位数的数组,存放数值
	u16 datas[4];//声明放数据数组
	u16 data;  //声明数据变量
	vu32 FLASH_START_ADDR=0x0801f000;	  //写入的起始地址 可修改其内容

	delay_ms(100); //上电时等待其他器件就绪

	ENCODER_Init();	 //旋转编码器初始化
	TOUCH_KEY_Init();  //触摸按键初始化

	RCC_Configuration(); //系统时钟初始化
	RTC_Config(); //RTC实时时钟初始化

	I2C_Configuration();//I2C初始化

	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_LIT(100);//亮度设置

	

	OLED_DISPLAY_8x16_BUFFER(0,"   FlashTest"); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"Addr:"); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(4,"Data:"); //显示字符串
	OLED_DISPLAY_16x16(6,0*16,0); //显示'状’
	OLED_DISPLAY_16x16(6,1*16,1);  //显示'态’
	OLED_DISPLAY_8x16(6,2*16,':'); //显示 ':’
	
	
	/**地址变成数组*****/
	i=8;//数值一共有8位
	index=7;		   //数组的数据从最后一个往前放,
	while(i--)		   //数值的低位先放入数组的最后一位
	{
		addr[index]=FLASH_START_ADDR%0x10;//数值的低位先放入数组的后面
		FLASH_START_ADDR/=0x10; //去掉取过的位数的数值
		index--; //数组索引向前跑一位
	}

	/**数组变成地址*****/
	index=0;		   //数组索引为零
	FLASH_START_ADDR=0;	//清空地址
	while(index!=8)		   //数值依次放入地址变量
	{
		FLASH_START_ADDR=(FLASH_START_ADDR*0x10)+addr[index]; //把数值推向前一位,并加上数组一位
		index++; //数组索引向后跑一位,需要变为地址变量的低位
	}

	/*读地址数据******/
	data=FLASH_R(FLASH_START_ADDR);	//读数据
	OLED_DISPLAY_8x16_DATA32(2,5,FLASH_START_ADDR); //显示地址
	OLED_DISPLAY_8x16_DATA16(4,5,data);		 //显示数据

	/**内存变成数组*****/
	i=4;//数值一共有4位
	index=3;		   	//于地址方法一模一样,不在赘述
	while(i--)		   
	{
		datas[index]=data%0x10;
		data/=0x10;
		index--; 
	}

	/**数组变成数据*****/
	index=0;		   //于地址方法一模一样,不在赘述,
	data=0;
	while(index!=4)		   
	{
		data=(data*0x10)+datas[index]; 
		index++; 
	}

	while(1)
	{
		/****写地址**触摸按键**A***/
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){ //读A触摸按键的电平
			k1=1;
			k2=0;
			index=7;
			OLED_DISPLAY_16x16(6,3*16,3);
			OLED_DISPLAY_16x16(6,4*16,4);
			OLED_DISPLAY_16x16(6,5*16,5);
//			OLED_DISPLAY_8x16_DATA8(4,5,data); //显示字符串
			while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
		}

		/****写数据**触摸按键**B***/
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){ //读B触摸按键的电平
			k2=1;
			k1=0;
			index=3;
			OLED_DISPLAY_16x16(6,3*16,3);
			OLED_DISPLAY_16x16(6,4*16,6);
			OLED_DISPLAY_16x16(6,5*16,7);
//			OLED_DISPLAY_8x16_DATA32(2,5,FLASH_START_ADDR); //显示字符串
			while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B));
		}

		/****选择修改位数**触摸按键**C***/
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){ //读C触摸按键的电平
			if(k1==1) //是否为a按下
			{
				if(index==3)
				{
					index=7;
				}else
				{
					index--;
				}
				
			}
			if(k2==1) //是否为b按下
			{
				if(index==0)
				{
					index=3;
				}else
				{
					index--;
				}	
			}
			while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C));
		}

		/****确认修改位数**触摸按键**D***/
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){ //读D触摸按键的电平
			k4=4;
		}

		/*判断是否需要到旋转编码器**修改地址*触摸按键**D*/
		if(k4==4&&k1==1)//判断是否修改地址
		{	
			while(k1==1){
				b=ENCODER_READ();	//读出旋转编码器值

				if(b==2)
				{
					if(addr[index]==0x0)
					{
						addr[index]=0xf;
					}else{
						addr[index]--;
					}
				}
					
				if(b==1)
				{
					if(addr[index]==0xf)
					{
						addr[index]=0;
					}else{
						addr[index]++;
					}
				} //分析按键值,并加减计数器值。
				

				if(addr[index]==0xa)//需要判断是否为字母,自动识别字母显示到屏幕内
				{
					OLED_DISPLAY_8x16(2,(index+5)*8,'a');
				}else if(addr[index]==0xb){
					OLED_DISPLAY_8x16(2,(index+5)*8,'b');
				}else if(addr[index]==0xc){
					OLED_DISPLAY_8x16(2,(index+5)*8,'c');
				}else if(addr[index]==0xd){
					OLED_DISPLAY_8x16(2,(index+5)*8,'d');
				}else if(addr[index]==0xe){
					OLED_DISPLAY_8x16(2,(index+5)*8,'e');
				}else if(addr[index]==0xf){
					OLED_DISPLAY_8x16(2,(index+5)*8,'f');
				}else{
					OLED_DISPLAY_8x16(2,(index+5)*8,addr[index]+0X30);	//数字显示到屏幕内
				}
				if(b==3)
				{
					k1=0;
					index=0;		   //数组的数据从最后一个往前放,
					FLASH_START_ADDR=0x0;
					while(index!=8)		   //数值的低位先放入数组的最后一位
					{
						FLASH_START_ADDR=(FLASH_START_ADDR*0x10)+addr[index]; //去掉取过的位数的数值
						index++; //数组索引向前跑一位
					}
					data=FLASH_R(FLASH_START_ADDR);
					OLED_DISPLAY_8x16_DATA16(4,5,data);
					OLED_DISPLAY_16x16(6,3*16,2);
				}
				
			}
			k4=0;
			OLED_DISPLAY_8x16_DATA32(2,5,FLASH_START_ADDR);	
		}

		/*判断是否需要到旋转编码器**修改数据**/
		if(k4==4&&k2==1)
		{	
			while(k2==1){
				b=ENCODER_READ();	//读出旋转编码器值	
				if(b==1)
				{
					if(datas[index]==0xf)
					{
						datas[index]=0;
					}else{
						datas[index]++;
					}
				} //分析按键值,并加减计数器值。
				if(b==2)
				{
					if(datas[index]==0x0)
					{
						datas[index]=0xf;
					}else{
						datas[index]--;
					}
				}

				if(datas[index]==0xa)//需要判断是否为字母,自动识别字母显示到屏幕内
				{
					OLED_DISPLAY_8x16(4,(index+5)*8,'a');
				}else if(datas[index]==0xb){
					OLED_DISPLAY_8x16(4,(index+5)*8,'b');
				}else if(datas[index]==0xc){
					OLED_DISPLAY_8x16(4,(index+5)*8,'c');
				}else if(datas[index]==0xd){
					OLED_DISPLAY_8x16(4,(index+5)*8,'d');
				}else if(datas[index]==0xe){
					OLED_DISPLAY_8x16(4,(index+5)*8,'e');
				}else if(datas[index]==0xf){
					OLED_DISPLAY_8x16(4,(index+5)*8,'f');
				}else{
					OLED_DISPLAY_8x16(4,(index+5)*8,datas[index]+0X30);	//数字显示到屏幕内
				}
				if(b==3)
				{
					k2=0;
					index=0;		   //数组的数据从最后一个往前放,
					data=0;
					while(index!=4)		   //数值的低位先放入数组的最后一位
					{
						data=(data*0x10)+datas[index]; //去掉取过的位数的数值
						index++; //数组索引向前跑一位
					}

					/*把数据写入地址中*/
					FLASH_W(FLASH_START_ADDR,data);
//					data=FLAS H_R(FLASH_START_ADDR);
					OLED_DISPLAY_8x16_DATA16(4,5,data);
					OLED_DISPLAY_16x16(6,3*16,2);
					OLED_DISPLAY_16x16(6,4*16,6);
					OLED_DISPLAY_16x16(6,5*16,7);
				}
			}
			k4=0;
		}

		/***闪烁程序**每秒闪烁一次**选择位数时运行**/
		RTC_Get();//获取实时时钟
		
		if(rsec%2&&k1==1)//地址闪烁
		{
			OLED_DISPLAY_8x16(2,(index+5)*8,' ');
		}else if(k1==1){
		   	OLED_DISPLAY_8x16_DATA32(2,5,FLASH_START_ADDR); //显示字符串
		}

		if(rsec%2&&k2==1)//数据闪烁
		{
			OLED_DISPLAY_8x16(4,(index+5)*8,' ');
		}else if(k2==1){
		   	OLED_DISPLAY_8x16_DATA16(4,5,data); //显示字符串
		}
	}
}

本次实验结果视频(B站):

https://www.bilibili.com/video/av64134556

可以关注账户以后只要有相关的视频,均用此账户发视频。

注意事项!

在这里插入图片描述


参考来源:

  • Google搜寻引擎等等

  • 杜洋工作室 www.DoYoung.net

  • 洋桃电子 www.DoYoung.net/YT

  • STM32库开发实战指南 基于STM32F103(第二版)

  • 《stm32f1xx 参考手册》


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多