stm32week8
stm32学习
五.闪存
1.读取闪存和芯片ID的代码
MyFlash的封装:
#include "stm32f10x.h" // Device header/*** 函 数:FLASH读取一个32位的字* 参 数:Address 要读取数据的字地址* 返 回 值:指定地址下的数据*/
uint32_t MyFLASH_ReadWord(uint32_t Address)
{return *((__IO uint32_t *)(Address)); //使用指针访问指定地址下的数据并返回
}/*** 函 数:FLASH读取一个16位的半字* 参 数:Address 要读取数据的半字地址* 返 回 值:指定地址下的数据*/
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{return *((__IO uint16_t *)(Address)); //使用指针访问指定地址下的数据并返回
}/*** 函 数:FLASH读取一个8位的字节* 参 数:Address 要读取数据的字节地址* 返 回 值:指定地址下的数据*/
uint8_t MyFLASH_ReadByte(uint32_t Address)
{return *((__IO uint8_t *)(Address)); //使用指针访问指定地址下的数据并返回
}/*** 函 数:FLASH全擦除* 参 数:无* 返 回 值:无* 说 明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在*/
void MyFLASH_EraseAllPages(void)
{FLASH_Unlock(); //解锁FLASH_EraseAllPages(); //全擦除FLASH_Lock(); //加锁
}/*** 函 数:FLASH页擦除* 参 数:PageAddress 要擦除页的页地址* 返 回 值:无*/
void MyFLASH_ErasePage(uint32_t PageAddress)
{FLASH_Unlock(); //解锁FLASH_ErasePage(PageAddress); //页擦除FLASH_Lock(); //加锁
}/*** 函 数:FLASH编程字* 参 数:Address 要写入数据的字地址* 参 数:Data 要写入的32位数据* 返 回 值:无*/
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{FLASH_Unlock(); //解锁FLASH_ProgramWord(Address, Data); //编程字FLASH_Lock(); //加锁
}/*** 函 数:FLASH编程半字* 参 数:Address 要写入数据的半字地址* 参 数:Data 要写入的16位数据* 返 回 值:无*/
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{FLASH_Unlock(); //解锁FLASH_ProgramHalfWord(Address, Data); //编程半字FLASH_Lock(); //加锁
}
基于MyFlash的封装:
#include "stm32f10x.h" // Device header
#include "MyFLASH.h"#define STORE_START_ADDRESS 0x0800FC00 //存储的起始地址
#define STORE_COUNT 512 //存储数据的个数uint16_t Store_Data[STORE_COUNT]; //定义SRAM数组/*** 函 数:参数存储模块初始化* 参 数:无* 返 回 值:无*/
void Store_Init(void)
{/*判断是不是第一次使用*/if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) //读取第一个半字的标志位,if成立,则执行第一次使用的初始化{MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定页MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5); //在第一个半字写入自己规定的标志位,用于判断是不是第一次使用for (uint16_t i = 1; i < STORE_COUNT; i ++) //循环STORE_COUNT次,除了第一个标志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000); //除了标志位的有效数据全部清0}}/*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/for (uint16_t i = 0; i < STORE_COUNT; i ++) //循环STORE_COUNT次,包括第一个标志位{Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2); //将闪存的数据加载回SRAM数组}
}/*** 函 数:参数存储模块保存数据到闪存* 参 数:无* 返 回 值:无*/
void Store_Save(void)
{MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定页for (uint16_t i = 0; i < STORE_COUNT; i ++) //循环STORE_COUNT次,包括第一个标志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]); //将SRAM数组的数据备份保存到闪存}
}/*** 函 数:参数存储模块将所有有效数据清0* 参 数:无* 返 回 值:无*/
void Store_Clear(void)
{for (uint16_t i = 1; i < STORE_COUNT; i ++) //循环STORE_COUNT次,除了第一个标志位{Store_Data[i] = 0x0000; //SRAM数组有效数据清0}Store_Save(); //保存数据到闪存
}
main.c的测试程序:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"uint8_t KeyNum; //定义用于接收按键键码的变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化Key_Init(); //按键初始化Store_Init(); //参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失/*显示静态字符串*/OLED_ShowString(1, 1, "Flag:");OLED_ShowString(2, 1, "Data:");while (1){KeyNum = Key_GetNum(); //获取按键键码if (KeyNum == 1) //按键1按下{Store_Data[1] ++; //变换测试数据Store_Data[2] += 2;Store_Data[3] += 3;Store_Data[4] += 4;Store_Save(); //将Store_Data的数据备份保存到闪存,实现掉电不丢失}if (KeyNum == 2) //按键2按下{Store_Clear(); //将Store_Data的数据全部清0}OLED_ShowHexNum(1, 6, Store_Data[0], 4); //显示Store_Data的第一位标志位OLED_ShowHexNum(3, 1, Store_Data[1], 4); //显示Store_Data的有效存储数据OLED_ShowHexNum(3, 6, Store_Data[2], 4);OLED_ShowHexNum(4, 1, Store_Data[3], 4);OLED_ShowHexNum(4, 6, Store_Data[4], 4);}
}
读取ID的代码:
OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4); //使用指针读取指定地址下的产品唯一身份标识寄存器
OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
六.OLED
1.入门
工程里添加文件夹和其中的文件:
- 确定已经文件夹添加到工程文件夹中
- 点击三个箱子的按钮(Manage Project Items),新建一个group,在新的group中add files,文件类型选All files,添加要添加的文件
- 点击魔术棒按钮,点C/C++这一栏,在Include Paths中点击三个点的按钮,点击新建,点击三个点的按钮,选择要添加的文件夹
解决中文乱码(UTF-8):在魔术棒中,C/C++这一栏,在Misc Controls中添加–no-multibyte-chars
GB2312:点击右边的扳手,Encoding选择Chinese GB2312(Simplified),如果要添加新文件,记得将文件的编码格式转换成GB2312
2.取模软件的用法
基于江协科技写的函数库
取模软件:PCtoLCD
使用步骤:
- 模式->字符模式
- 点击齿轮,选阴码、列行式、逆向、十六进制、自定义格式、选择C51模式、将行前后缀的{}删掉
- 模式->图形模式,点击白纸新建一个空白图像,这样就可以手绘图像
- 可以直接导入.bmp文件
3.OLED硬件
SSD1306是一款OLED(有机发光二极管)/PLED(高分子发光二极管)点阵显示屏的控制器,可以嵌入在屏幕中,用于执行接收数据、显示数据、扫描刷新等任务
驱动接口:128个SEG引脚和64个COM引脚,对应12864像素点显示屏
内置显示存储器(GDDRAM):12864bit(128*Byte)SRAM
供电:VDD=1.653.3V(IC逻辑),VCC=715V(面板驱动),最小系统板内置电压放大电路和降压电路
通信接口:8位6800/8080并行接口(一般用于数据量大的地方),3/4线SPI接口,I2C接口

最左边是MCU通信接口,传到右边的GDDRAM,然后传到右边的显示控制器,然后传到右边的驱动器,中间的是段驱动器,上下两部分是公共端驱动器
通信接口选择及通信线定义:

4针脚I2C接口模块原理图:

7针脚SPI接口模块原理图:

4.OLED软件
字节传输-6800并口的时序图和指令表(用的少):

字节传输-8080并口的时序图和指令表(用的少):

字节传输-4线SPI:

图中是SPI模式0或者模式3,高位先行
3线SPI:

没有D/C#引脚,固定在每字节数据前发送一个D/C#
I2C:

I2C也没有D/C#,固定在每个数据字节前发送一个控制字节
Co表示是否为连续模式,连续模式下每个数据字节前必有一个控制字节,非连续模式下控制模式后会跟数个数据字节,一般不用连续模式
每个字节定义了一列8个像素的亮灭,每页都是8行,写命令控制写在哪页哪列,是低位先行

命令表:
通过写命令时序传输的字节,作为发送给SSD1306的一个命令
SSD1306查询命令表的定义,执行相应的操作
命令可以由一个字节或者连续的多个字节组成
命令可分为基础命令、滚屏命令、寻址命令、硬件配置命令、时间及驱动命令5大类

上两个命令共同组成一个列地址,第三个是设置页地址
初始化过程(内部提供VCC):

5.I2C连接OLED代码
初始化函数:
void OLED_Init(void)
{OLED_GPIO_Init(); //先调用底层的端口初始化/*写入一系列的命令,对OLED进行初始化配置*/OLED_WriteCommand(0xAE); //设置显示开启/关闭,0xAE关闭,0xAF开启OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80); //0x00~0xFFOLED_WriteCommand(0xA8); //设置多路复用率OLED_WriteCommand(0x3F); //0x0E~0x3FOLED_WriteCommand(0xD3); //设置显示偏移OLED_WriteCommand(0x00); //0x00~0x7FOLED_WriteCommand(0x40); //设置显示开始行,0x40~0x7FOLED_WriteCommand(0xA1); //设置左右方向,0xA1正常,0xA0左右反置OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常,0xC0上下反置OLED_WriteCommand(0xDA); //设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81); //设置对比度OLED_WriteCommand(0xCF); //0x00~0xFFOLED_WriteCommand(0xD9); //设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4); //设置整个显示打开/关闭OLED_WriteCommand(0xA6); //设置正常/反色显示,0xA6正常,0xA7反色OLED_WriteCommand(0x8D); //设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF); //开启显示OLED_Clear(); //清空显存数组OLED_Update(); //更新显示,清屏,防止初始化后未显示内容时花屏
}
设置写入地址:
void OLED_SetCursor(uint8_t Page, uint8_t X)
{/*如果使用此程序驱动1.3寸的OLED显示屏,则需要解除此注释*//*因为1.3寸的OLED驱动芯片(SH1106)有132列*//*屏幕的起始列接在了第2列,而不是第0列*//*所以需要将X加2,才能正常显示*/
// X += 2;/*通过指令设置页地址和列地址*/OLED_WriteCommand(0xB0 | Page); //设置页位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
/*** 函 数:将OLED显存数组更新到OLED屏幕* 参 数:无* 返 回 值:无* 说 明:所有的显示函数,都只是对OLED显存数组进行读写* 随后调用OLED_Update函数或OLED_UpdateArea函数* 才会将显存数组的数据发送到OLED硬件,进行显示* 故调用显示函数后,要想真正地呈现在屏幕上,还需调用更新函数*/
void OLED_Update(void)
{uint8_t j;/*遍历每一页*/for (j = 0; j < 8; j ++){/*设置光标位置为每一页的第一列*/OLED_SetCursor(j, 0);/*连续写入128个数据,将显存数组的数据写入到OLED硬件*/OLED_WriteData(OLED_DisplayBuf[j], 128);}
}
void OLED_Clear(void)
{uint8_t i, j;for (j = 0; j < 8; j ++) //遍历8页{for (i = 0; i < 128; i ++) //遍历128列{OLED_DisplayBuf[j][i] = 0x00; //将显存数组数据全部清零}}
}
void OLED_ShowNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{uint8_t i;for (i = 0; i < Length; i++) //遍历数字的每一位 {/*调用OLED_ShowChar函数,依次显示每个数字*//*Number / OLED_Pow(10, Length - i - 1) % 10 可以十进制提取数字的每一位*//*+ '0' 可将数字转换为字符格式*/OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);}
}
void OLED_ShowImage(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{uint8_t i = 0, j = 0;int16_t Page, Shift;/*将图像所在区域清空*/OLED_ClearArea(X, Y, Width, Height);/*遍历指定图像涉及的相关页*//*(Height - 1) / 8 + 1的目的是Height / 8并向上取整*/for (j = 0; j < (Height - 1) / 8 + 1; j ++){/*遍历指定图像涉及的相关列*/for (i = 0; i < Width; i ++){if (X + i >= 0 && X + i <= 127) //超出屏幕的内容不显示{/*负数坐标在计算页地址和移位时需要加一个偏移*/Page = Y / 8;Shift = Y % 8;if (Y < 0){Page -= 1;Shift += 8;}if (Page + j >= 0 && Page + j <= 7) //超出屏幕的内容不显示{/*显示图像在当前页的内容*/OLED_DisplayBuf[Page + j][X + i] |= Image[j * Width + i] << (Shift);}if (Page + j + 1 >= 0 && Page + j + 1 <= 7) //超出屏幕的内容不显示{ /*显示图像在下一页的内容*/OLED_DisplayBuf[Page + j + 1][X + i] |= Image[j * Width + i] >> (8 - Shift);}}}}
}