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)); //清除EV5I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);//发送器件地址while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6I2C_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));   //清除 EV5I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //写入器件地址while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除 EV6I2C_Cmd(I2C1,ENABLE);I2C_SendData(I2C1,readAddr); //发送读的地址while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //清除 EV8I2C_GenerateSTART(I2C1,ENABLE); //开启信号while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver); //将器件地址传出,主机为读while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //清除EV6while(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);//调用库函数将数据取出到 pBufferpBuffer++; //指针移位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 参考手册》

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

  1. 洋桃开发板笔记(三) 最基本的USART的串口使用

    USART的使用 杜洋工作室 www.DoYoung.net 洋桃电子 www.DoYoung.net/YT 在此声明一下所有代码均为 杜洋工作室 的不允许复制,转发等,本人只是在此程序上进行理解和注 ...

  2. 洋桃开发板笔记(八 )ADC初识——模数转换

    STM32的ADC初识 杜洋工作室 www.DoYoung.net 洋桃电子 www.DoYoung.net/YT 在此声明一下所有代码均为杜洋工作室的不允许复制,转发以及在商业上的行为等,本人只是在 ...

  3. 洋桃开发板笔记(一) 最基本的GPIO接口控制

    点亮LED灯,本质是对GPIO口的控制 _ 杜洋工作室 www.DoYoung.net _ _ 洋桃电子 www.DoYoung.net/YT _ 在此声明一下所有代码均为__杜洋工作室__的不允许复 ...

  4. 《MFC游戏开发》笔记六 图像双缓冲技术:实现一个流畅的动画

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9334121 作者:七十一雾央 新浪微博:http:// ...

  5. Nucleo-64开发板笔记

    最近入手了一块Nucleo-64开发板,顺手记录下学习过程. STM32 Nucleo-64开发板通过从STM32微控制器提供的性能和功耗特性的各种组合中进行选择,为用户提供了一种经济实惠且灵活的方式 ...

  6. Polyworks脚本开发学习笔记(六)-比较运算、数学运算、逻辑运算及流程控制

    Polyworks脚本开发学习笔记(六)-比较运算.数学运算.逻辑运算及流程控制 前言 比较运算.逻辑运算及流程控制是编程的基本语法,Polyworks的语法规则与VB/C#/Python等并没有很大 ...

  7. (39)STM32——FLASH闪存

    目录 学习目标 成果展示 介绍 组成 主存储器 系统存储器 OTP 区域 选项字节 读取 编程 寄存器 步骤 擦除 扇区擦除 批量擦除 寄存器 代码 总结 学习目标 本节我们要来介绍一下关于FLASH ...

  8. 硬件学习、高速dsp开发板制作、STM32学习笔记

    1.硬件工程师成长之路(1)--元件基础_[云轩]的博客-CSDN博客_硬件工程师的成长之路 总目录:https://blog.csdn.net/weixin_44407238/category_10 ...

  9. python 开发板-MicroPython:STM32 上 的 Python 开发

    虽然Python在国外是一门非常火的语言,在黑客界更是赫赫有名,然而中国的大学却极少开设 Python 课程,故而国内 Python 程序员多属自学.而一个没有MCU编程经验的初学者,要想让芯片跑起来 ...

最新文章

  1. 浏览器更改实现webstrom等前端编辑器的同步更新
  2. SparkSQL操作Hive
  3. SaaS风暴:中国软件企业如何应对挑战?
  4. Linux内核:进程上下文切换
  5. 谈谈html5存储之IndexdDB
  6. APICS与AX的Master Planning(一)--Phantom bill of Material 虚项
  7. 前端开源项目周报0109
  8. 如何将SWF的FLASH转成GIF动态图片呢.
  9. 变量之八大基本数据类型#基本数据类型相互转换#基本数据类型与String字符串间转换
  10. 2017年5月—信息安全工程师—上午综合知识(11-15)
  11. 理解java的内存结构——运行时数据区域
  12. EasyExcel web下载excel,多sheet页demo
  13. 面试十五年经验程序员,面试官沦为听众
  14. 自动化测试 —— Pytest测试框架
  15. 关于树莓派编译及运行Snowboy的详细教程。
  16. UHD-SDI GT v2.0(PG380)
  17. 唐诗宋词学习·141~145节
  18. 螺旋面 (几何曲面)
  19. 在iOS中读取本地文件
  20. PCIe Controller(x16)-1901驱动异常导致无法检索到独显的问题

热门文章

  1. c语言数字基带实验报告,数字基带传输实验实验报告.doc
  2. Web后端开发入门(3)
  3. Ueditor处理微信图片
  4. Python日常用法—将列表信息写入到csv文件、列表中的元素直接更改
  5. ie 浏览器直接打印与非ie
  6. 谁再说学不会 MySQL 数据库,就把这个给他扔过去
  7. 告别脏乱差,多应用,快交付的智能公寓管理平台来啦
  8. 向量组等价与极大无关组的理解
  9. pagerank算法c语言,在hadoop的map-reduce框架下实现经典的pagerank算法
  10. Win10设置环境变量的5种方式,在哪打开? 如何打开?