stm32week8

server/2025/3/31 11:07:21/

stm32_0">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.入门

工程里添加文件夹和其中的文件:

  1. 确定已经文件夹添加到工程文件夹中
  2. 点击三个箱子的按钮(Manage Project Items),新建一个group,在新的group中add files,文件类型选All files,添加要添加的文件
  3. 点击魔术棒按钮,点C/C++这一栏,在Include Paths中点击三个点的按钮,点击新建,点击三个点的按钮,选择要添加的文件夹

解决中文乱码(UTF-8):在魔术棒中,C/C++这一栏,在Misc Controls中添加–no-multibyte-chars

GB2312:点击右边的扳手,Encoding选择Chinese GB2312(Simplified),如果要添加新文件,记得将文件的编码格式转换成GB2312

2.取模软件的用法

基于江协科技写的函数库
取模软件:PCtoLCD
使用步骤:

  1. 模式->字符模式
  2. 点击齿轮,选阴码、列行式、逆向、十六进制、自定义格式、选择C51模式、将行前后缀的{}删掉
  3. 模式->图形模式,点击白纸新建一个空白图像,这样就可以手绘图像
  4. 可以直接导入.bmp文件

3.OLED硬件

SSD1306是一款OLED(有机发光二极管)/PLED(高分子发光二极管)点阵显示屏的控制器,可以嵌入在屏幕中,用于执行接收数据、显示数据、扫描刷新等任务
驱动接口:128个SEG引脚和64个COM引脚,对应12864像素点显示屏
内置显示存储器(GDDRAM):128
64bit(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);}}}}
}

http://www.ppmy.cn/server/179804.html

相关文章

设计模式-领域模式

前言 “领域“模式&#xff1a;在特定领域中&#xff0c;某些变化虽然频繁&#xff0c;但可以抽象为某种规则。这时候&#xff0c;结合特定领域&#xff0c;将问题抽象为语法规则&#xff0c;从而给出在该领域下的一般性解决方案。 典型模式&#xff1a; Interpreter Inter…

深度解析 BPaaS:架构、原则与研发模式探索

在当今复杂多变的业务环境下&#xff0c;软件开发面临着诸多挑战&#xff0c;如何有效地管理业务复杂性并实现系统的可扩展性成为关键。BPaaS应运而生&#xff0c;它作为一种创新的理念和架构模式&#xff0c;改变着企业研发的方式。本文将深入探讨 BPaaS 是什么&#xff0c;以…

java后端接收数组,数组长度超256个就会报错

1.原因 DataBinder 中默认限制了list最大只能增长到256。 2.解决方案 1.在BaseController添加InitBinder方法&#xff0c;其余继承BaseController InitBinder //类初始化是调用的方法注解public void initBinder(WebDataBinder binder) {//给这个controller配置接收list的长…

AndroidStudio导入jar,aar到项目kts/groovy方式

背景 导入jar和aar到项目中新的配置方式有2中&#xff0c;简单&#xff0c;方便 把jar和aar放到libs目录即可 kts版本 implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))groov…

当人类关系重构:从“相互需要”到“鹅卵石化”——生成式人工智能(GAI)认证的角色与影响

在数字化浪潮的席卷之下,人类社会正经历着前所未有的变革。人与人之间的连接方式、互动模式以及价值认同,都在悄然发生着变化。这一过程中,一个显著的现象是,人与人之间的关系逐渐从传统的“相互需要”模式,转变为一种更为复杂、多元且稳定的“鹅卵石化”结构。在此背景下…

LangChain4j与DashScope深度集成实战:一站式开发指南

本篇文章会通篇详细的讲清楚LangChain4j与DashScope集成的各个方面&#xff0c;从Springboot的集成到Ai对话、会话记忆、RAG、FunctionCalling、互联网搜索、结构化的输出、多模态等都给出相应的说明&#xff0c;希望通过这篇文章对于LLM不了解的同仁一样可以扩展出自己的AI应用…

Spring Boot网站性能优化全解析

在开发Spring Boot网站时&#xff0c;性能优化涵盖Java应用、操作系统、Java虚拟机&#xff08;JVM&#xff09;等多个层面。下面将从Spring Boot应用层、Linux系统、JVM参数等方面&#xff0c;详细介绍优化方案&#xff0c;同时阐述操作系统差异处理、验证与监控以及注意事项。…

爬虫框架Scrapy从入门到实战

一、Scrapy框架概述 1.1 什么是Scrapy Scrapy是一个基于Twisted的异步网络爬虫框架&#xff0c;具有以下特性&#xff1a; 内置数据提取器&#xff08;Selector&#xff09; 完善的中间件扩展体系 自动的请求调度机制 支持多种数据存储方式 1.2 Scrapy vs Requests 特性…