Rain_Personal
发表于 2024-5-18 20:26:50
第二十五课 Flash模拟EEPROM
EEPROM的特点是可以随机访问和修改任何一个字节。
Flash属于广义的EEPROM,因为它也是电擦除的ROM。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它Flash。FLASH如果数据不为0XFF,需要擦除之后才能写入。
通常,单片机里的Flash都用于存放运行代码,在运行过程中不能改;EEPROM是用来保存用户数据,运行过程中可以改变,比如我们在程序运行中需要保存密码等参数,就需要存在EEPROM里。
IAP(全称 In Application Programming,即应用编程),和 ISP (全称 In System Programming,即系统编程)可将内部的Flash用于EEPROM,但每次擦除是512kb一个扇区。
不操作EEPROM时要禁止访问EEPROM保证数据安全
void DisableEEPROM(void)
{
IAP_CONTR = 0; //关闭 IAP 功能
IAP_CMD = 0; //清除命令寄存器
IAP_TRIG = 0; //清除触发寄存器
IAP_ADDRE = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRH = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRL = 0xff;
}
读数据32位地址 再通过5a和a5触发
char EEPROM_read_n(u32 EE_address)
{
char dat;
EA = 0; //禁止中断
IAP_CONTR = 0x80; //使能 IAP
IAP_TPS = Tip_Delay; //设置擦除等待参数 24MHz
IAP_CMD = 1;//设置 IAP 读命令
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址需要改变时才需重新送地址)
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //由于STC32G是多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
dat = IAP_DATA; //读 IAP 数据
DisableEEPROM();
EA = 1; //重新允许中断
return dat;
}
写入数据
void EEPROM_write_n(u32 EE_address,u8 dat)
{
EA = 0; //禁止中断
IAP_CONTR = 0x80; //使能 IAP
IAP_TPS = Tip_Delay; //设置擦除等待参数 24MHz
IAP_CMD = 2;//设置 IAP 写命令
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址需要改变时才需重新送地址)
IAP_DATA = dat; //写 IAP 数据
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //由于STC32G是多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
DisableEEPROM();
EA = 1; //重新允许中断
}
按照扇区擦除地址位扇区起始地址
void EEPROM_SectorErase(u32 EE_address)
{
EA = 0; //禁止中断
IAP_CONTR = 0x80; //使能 IAP
IAP_TPS = Tip_Delay; //设置擦除等待参数 24MHz
IAP_CMD = 3; //设置 IAP 擦除命令
IAP_ADDRE = (u8)(EE_address >> 16); //送扇区地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送扇区地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送扇区地址低字节(地址需要改变时才需重新送地址)
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //由于STC32G是多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
DisableEEPROM();
EA = 1; //重新允许中断
}
Rain_Personal
发表于 2024-5-18 21:51:38
本帖最后由 Rain_Personal 于 2024-5-20 19:34 编辑
第二十六课 DS18b20温度读取
单总线连接模式,需要上拉,对于DS18b20,需要接若上拉电阻 4.7kΩ
DS18B20的核心功能是它的直接数字温度传感器。温度传感器的精度为用户可编程的9,10,11或12位。温度分辨率分别为0.5℃、0.25℃、0.125℃和0.0625℃。芯片在上电状态下默认的精度为12位。
使用时操作步骤
void DS18b20_Reset(void) //复位
{
bit flag = 1;
while( flag )
{
DQ = 0; //输出低电平
Delay480us();
DQ = 1; //输出高电平
Delay60us();
flag = DQ; //读取当前电平
Delay480us();
}
}
void DS18b20_Write_0(void) //写逻辑0码
{
DQ = 0; //输出低电平
Delay60us();
DQ = 1; //输出高电平
Delay1us();
Delay1us();
}
void DS18b20_Write_1(void) //写逻辑1码
{
DQ = 0; //输出低电平
Delay1us();
Delay1us();
DQ = 1; //输出高电平
Delay60us();
}
bit DS18b20_Read(void) //读取电平
{
bit state ;
DQ = 0; //输出低电平
Delay1us();
Delay1us();
DQ = 1; //输出低电平
Delay1us();
Delay1us();
state = DQ; //读取当前电平
Delay60us();
return state;
}
// 0XCC= 1100 1100b & 0x01
void DS18b20_WriteByte(u8 dat) //写一个字节
{
u8 i;
for( i=0;i<8;i++ ) //循环八次
{
if( dat & 0x01 )
DS18b20_Write_1();
else
DS18b20_Write_0();
dat >>=1;
}
}
// 1000 0000
// x100 0000
// xx10 0000
u8 DS18b20_ReadByte(void) //读取一个字节
{
u8 i;
u8 dat=0;
for( i=0;i<8;i++ ) //循环八次
{
dat >>=1;
if( DS18b20_Read() ) //如果读取到的是1,
dat |= 0x80;
}
return dat;
}
u16 DS18b20_ReadTemp(void) //读取并且换算温度,并返回
{
u8 TempH;
u8 TempL;
u16 temperture;
DS18b20_Reset(); //1.复位
DS18b20_WriteByte(0XCC);//2.跳过ROM指令
DS18b20_WriteByte(0X44);//3.开始转化
while(!DQ); //4.等待DQ变成高电平
DS18b20_Reset(); //5.复位
DS18b20_WriteByte(0XCC);//6.跳过ROM指令
DS18b20_WriteByte(0XBE);//7.读取
TempL = DS18b20_ReadByte();
TempH = DS18b20_ReadByte();
if( TempH & 0XF8 ) //有1出现就是负数
{
MinusFlag = 1; //标志位负数
temperture = ((TempH<<8) | TempL); //将温度换算成16位
temperture = (~temperture) +1; //按位取反+1
temperture = temperture*10*0.0625; //最终温度保留一位小数
}
else
{
MinusFlag = 0; //标志位正数
temperture = ((TempH<<8) | TempL); //将温度换算成16位
temperture = temperture*10*0.0625; //最终温度保留一位小数
}
Temp = temperture;
return temperture; //保留一位小数的温度
}
Rain_Personal
发表于 2024-5-21 21:26:21
第二十七课 软件模拟SPI
SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
SPI总线包括4条逻辑线,定义如下:
●MISO:Master input slave output 主机输入,从机输出(数据来自从机);
●MOSI:Master output slave input 主机输出,从机输入(数据来自主机);
●SCLK :Serial Clock 串行时钟信号,由主机产生发送给从机;
●SS:Slave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。
传送数据依靠clock引脚改变引脚电压,提前将0或者1 高低电平写入MOSI或者MISO,通过改变SCLK电平,产生上升沿或者下降沿就可以传输数据给从机或者主机
W25X40CL
CS:低电平有效
DI/DO:数据输入输出端
WP:低电平写保护
HOLD:低电平时暂停
CLK:时钟信号
#define CS P22
#define MISO P24
#define WP P53
#define MOSI P23
#define SCLK P25
void SPI_Init(void) //初始化
{
CS = 1; //不选中该器件
MISO= 1; //
WP = 1;
MOSI= 1;
SCLK= 0; //spi模式0,sclk初始化为低电平
}
void SPI_WriteByte(u8 dat) //写入一个字节
{
u8 i;
for( i=0;i<8;i++ )
{
if( dat & 0x80 )
MOSI = 1;
else
MOSI = 0;
SCLK = 1; //下降沿
SCLK = 0;
dat <<=1;
}
}
u8 SPI_ReadByte(void) //读取一个字节
{
u8 i;
u8 dat=0;
for( i=0;i<8;i++ )
{
dat<<=1;
SCLK = 1;
if( MISO ) //从机往主机发送数据的引脚
dat |= 1;
// else
// dat |= 0;
SCLK = 0;
}
return dat;
}
写使能 26h
写失能 04h
读取状态寄存器05h
写入状态寄存器01h
读取数据 03h
快速读取 0Bh
双输出快速读取3Bh
页编程 02h
扇区擦除 20h
32KB块擦除 52h
Rain_Personal
发表于 2024-5-22 21:17:22
第二十八课 硬件SPI读写Flash芯片
Flash芯片外接至P2.3和P2.5两个引脚
这两个引脚可以复用位P2.3 MOSI_2 和P2.5 SCLK_2。
void SPI_Init(void) //初始化
{
P_SW1 &= 0XF3; //1111 0011 引脚选择
P_SW1 |= 0X04; //0000 0100
SPCTL = 0X90; //模式配置 SSIG = 1 MSTR = 1 设置主机模式
SPCTL |= 0X40; //CPOL = 1
SCLK = 0; //y引脚初始化
SPIF = 1; //清空标志位
WCOL = 1;
}
void SPI_WriteByte(u8 dat) //写入一个字节
{
SPDAT = dat; // 发送数据
while(SPIF == 0); // 正在发送数据
SPIF = 1; // 清空标志位
WCOL = 1;
}
u8 SPI_ReadByte(void) //读取一个字节
{
SPDAT = 0XFF;
while(SPIF == 0);
SPIF = 1;
WCOL = 1;
return SPDAT;
}
Rain_Personal
发表于 2024-5-25 17:39:33
第二十九课 SPI读写Flash芯片
读取芯片的ID 为 12
u8 FLASH_CheckID(void) //读取ID
{
u8 id;
CS = 0;
SPI_WriteByte(0XAB); //写入读取id命令
SPI_WriteByte(0X00); //写入空命令
SPI_WriteByte(0X00); //写入空命令
SPI_WriteByte(0X00); //写入空命令
id = SPI_ReadByte(); //读取命令
CS = 1;
return id;
}
当发送90时
SPI_WriteByte(0X90); //写入读取id命令
SPI_WriteByte(0X00); //写入空命令
SPI_WriteByte(0X00); //写入空命令
SPI_WriteByte(0X00); //写入空命令
MID = SPI_ReadByte(); //读取命令
ID = SPI_ReadByte(); //读取命令
会首先返回制造商id即MIDEF
随后会发送ID 设备ID 12
u8 FLASH_CheckBusy(void) //读取状态
{
u8 state;
CS = 0;
SPI_WriteByte(0X05); //写入读取状态命令
state = SPI_ReadByte(); //读取命令
CS = 1;
return state;
}
void FLASH_WriteEnable(void) //写能使
{
while(FLASH_CheckBusy()>0);
CS = 0;
SPI_WriteByte(0X06); //写入写使能命令
CS = 1;
}
void FLASH_Erase(u32 addr) //扇区擦除
{
while(FLASH_CheckBusy()>0);
FLASH_WriteEnable();
CS = 0;
SPI_WriteByte(0X20); //写入扇区擦除
SPI_WriteByte((u8)(addr>>16)); //写入地址最高位
SPI_WriteByte((u8)(addr>>8)); //写入地址
SPI_WriteByte((u8)(addr>>0)); //写入地址最第位
CS = 1;
}
void FLASH_Read(u32 addr,u8 *buffr,u16 length) //d扇区读取
{
u16 i=0;
while(FLASH_CheckBusy()>0);
CS = 0;
SPI_WriteByte(0X03); //写入
SPI_WriteByte((u8)(addr>>16)); //写入地址最高位
SPI_WriteByte((u8)(addr>>8)); //写入地址
SPI_WriteByte((u8)(addr>>0)); //写入地址最第位
do
{
buffr = SPI_ReadByte();
i++;
}while(length--);
CS = 1;
}
void FLASH_Write(u32 addr,u8 *buffr,u16 length) //d扇区写入
{
u16 i=0;
if( length==0 )
return ;
while(FLASH_CheckBusy()>0);
FLASH_WriteEnable();
CS = 0;
SPI_WriteByte(0X02); //写入
SPI_WriteByte((u8)(addr>>16)); //写入地址最高位
SPI_WriteByte((u8)(addr>>8)); //写入地址
SPI_WriteByte((u8)(addr>>0)); //写入地址最第位
do
{
SPI_WriteByte(buffr);
i++;
if( addr&0xff==0 )
break;
}while(length--);
CS = 1;
}
Rain_Personal
发表于 2024-5-26 15:38:50
第三十课 软件模拟IIC
IIC总线简单来说就是一根线来控制时钟(SCL)和一个线来发送数据(SDA)。两个线按照一定的传输协议来进行传输数据。由于只有一根线来读写数据,也就意味着同一时间只能有一个发送设备。可以通过地址操作控制与那个iic设备进行通讯。
iic的两条通讯线都需要通过上拉电阻,使空闲电位处于高电位。
主机发送数据的信号控制模式
首先主设备给总线发送起始信号,和从机地址,随后从机应答,主机发送数据,从机应答。。。最后发送停止信号
主设备发送起始信号和从机地址,随后从机应答,并传送数据,主机应答,从机再次发送数据,。。。。最后是停止信号
iic的协议需要
1.启动信号
void IIC_START(void) //IIC开始
{
SCL = 1;
SDA = 1;
IIC_DELAY();
SDA = 0;
IIC_DELAY();
SCL = 0;
IIC_DELAY();
}
2.等待从机应答信号
void IIC_WAITACK(void) //等到从机ACK
{
SDA = 1;
IIC_DELAY();
SCL = 1;
IIC_DELAY();
ack = SDA;
IIC_DELAY();
SCL = 0;
IIC_DELAY();
}
等待iic从机设备将SDA数据引脚电位拉低
3.终止信号
void IIC_STOP(void) //IIC结束
{
SCL = 0;
SDA = 0;
IIC_DELAY();
SCL = 1;
IIC_DELAY();
SDA = 1;
IIC_DELAY();
}
4.主机发送ACK信号
主机将SDA引脚拉低
void IIC_SENDACK(void) //发送ACK
{
SDA = 0;
IIC_DELAY();
SCL = 1;
IIC_DELAY();
SCL = 0;
IIC_DELAY();
}
5.主机发送NO_ACK信号
void IIC_SENDNACK(void) //发送NACK
{
SDA = 1;
IIC_DELAY();
SCL = 1;
IIC_DELAY();
SCL = 0;
IIC_DELAY();
}
6.发送一个字节
void IIC_SENDBYTE(u8 dat) //发送一个字节
{
u8 i=8;
do
{
if( dat& 0x80 )
SDA = 1;
else
SDA = 0;
IIC_DELAY();
dat<<=1;
SCL = 1;
IIC_DELAY();
SCL = 0;
IIC_DELAY();
}
while(--i);
}
7.读取一个字节
u8 IIC_READBYTE(void) //读取一个字节
{
u8 i=8,dat=0;
SDA = 1;
do
{
SCL = 1;
IIC_DELAY();
dat<<=1;
if( SDA )
dat |= 1;
SCL = 0;
IIC_DELAY();
}
while(--i);
return dat;
}
lksbbs
发表于 2025-6-3 16:46:16
Rain_Personal 发表于 2024-5-26 15:38
第三十课 软件模拟IIC
您好,IIC 的速度可以多快有没有测试过呢?
Rain_Personal
发表于 2025-6-3 20:09:06
lksbbs 发表于 2025-6-3 16:46
您好,IIC 的速度可以多快有没有测试过呢?
IIC总线:半双工,只有2根线。数据线和时钟线。
工作速度分为3种版本:
S(标准模式):100Kbps,即 100/8 = 12.5KB/s
F(快速模式):400Kbps,即400/8 = 50KB/s
HS(高速模式):3.4Mbps,即3.4M/8 = 435KB/s
超高速模式:5Mbit/s,即5M/8 = 525KB/s
如果想用快速,比如彩屏刷新,建议采用spi协议,那个速率快,且跟单片机频率有一定相关性
lksbbs
发表于 2025-6-4 08:20:49
Rain_Personal 发表于 2025-6-3 20:09
IIC总线:半双工,只有2根线。数据线和时钟线。
工作速度分为3种版本:
这个是标准IIC的速度啊,软件模拟,用通用io肯定要慢很多,之前驱动屏幕老是花屏(也有可能是我没做电磁屏蔽),
Rain_Personal
发表于 2025-6-5 19:22:26
lksbbs 发表于 2025-6-4 08:20
这个是标准IIC的速度啊,软件模拟,用通用io肯定要慢很多,之前驱动屏幕老是花屏(也有可能是我没做电磁 ...
iic通讯主要看时序,速度受时钟频率影响,如果是花屏,可以采用硬件iic试试,有可能是频率与数据处理不到位,如果强磁环境,或者线很长,做一下电磁屏蔽,或者了解一下can总线,我也是小白,仅供参考哈