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 参考手册》