一、I2C总线通信
I2C简介
I2C总线,两线式的串行总线。
特征:
两条总线线路:SDA(数据线),SCL(时钟线),属于同步通信
I2C总线上每一个设备都可以是主设备也可以是从设备(一主多从),每一个设备都有一个唯一地址
传输速率标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
I2C总线上的主设备与从设备之间以字节(8位)为单位进行单双工的数据传输
物理拓扑图:
一般芯片作为主设备,其他设备作为从设备
接上拉电阻原因:当I2C不工作时,默认处于高电平状态
总线协议:
空闲状态:SDA、SCL保持高电平
起始信号:SCL高电平,SDA由高电平转为低电平
结束信号:SCL高电平,SDA由低电平转为高电平
数据传输:
数据传输以字节为单位(8位),数据只有在SCL为高电平采样,在SCL低电平时可以修改电平。
主设备传输数据:首先指定设备地址+1位操作位(读1/写0),SCL默认低电平回应,之后传输发送数据,SCL1位应答
读写操作示意图:
主设备向从设备写:
主设备向从设备读:
主设备读从设备的某个寄存器
软件模拟I2C和硬件控制产生I2C(基本上所有芯片都会自带这个功能)。
EEPROM(AT24C01为例)
EEPROM,带电可擦可编程只读存储器,掉电不丢失存储芯片,常用来存储一些配置信息
ATX芯片引脚图
设备地址:
前四位固定为1010,低三位取决A0-A2的值
一个字节写时序:
读时序:
EEPROM读写实例
#define Waddr 0XA0
#define Raddr 0XA1
uint8_t Wbuf[]="EEPROM is OK";
uint8_t Rbuf[20]={0};
/******************************************************************************************************/
/*单字节写*/
void EEPROM_WRITE(uint8_t Memaddr,uint8_t* Wbuf,uint8_t len){
for(int i=0;i<len;i++){
while(HAL_I2C_Mem_Write(&hi2c1, Waddr, Memaddr, I2C_MEMADD_SIZE_8BIT, Wbuf, 1, 100)!=HAL_OK);
Memaddr++;
Wbuf++;
}
return;
}
/*随机读*/
void EEPROM_READ(uint8_t Memaddr,uint8_t* Rbuf,uint8_t len){
while(HAL_I2C_Mem_Read(&hi2c1,Raddr,Memaddr,I2C_MEMADD_SIZE_8BIT,Rbuf,len,100)!=HAL_OK);
return;
}
/******************************************************************************************************/
EEPROM_WRITE(0,Wbuf,sizeof(Wbuf));
HAL_Delay(500);
EEPROM_READ(0,Rbuf,sizeof(Wbuf));
printf("READ:%s\n",Rbuf);
二、SPI总线协议
SPI简介
定义:全双工三线同步串行总线,采用主从模式(一主多从)。
拓扑结构:
SCLK:时钟线,用于同步
MISO(主机数据输入,从机数据输出):主机接受数据,从机发送数据
MOSI(主机数据输入,从机数据输入):主机发送数据,从机接受数据
/SS(片选引脚):确定连接设备(默认低电平使能)
SPI总线协议:
起始信号:NSS信号由高变低,设备使能,为起始信号
结束信号:NSS信号由低变高,设备失能,为结束信号
数据传输:SCL用于时钟同步,MISO和MOSI用于主机和从机之间数据传输,注意:输入输出是同时进行的,如果仅仅是只读或者只写,都需要进行完成的读写过程。
主机将数据通过SPI数据寄存器发送到从机的SPI数据寄存器,与此同时,从机的也将数据通过SPI数据寄存器发送给主机的SPI数据寄存器,两个过程是同时进行的。
如果主机只想要读,那么他需要任意发送一个字符(一般是0)给从机,从机忽略主机发送的数据即可,主机可以一直读。
如果主机只想要写,那么他需要将写的数据发送给从机,从机接受数据,与此同时将数据发送给主机,主句忽略接受的数据即可。
即不管是只读或者只写,都会经历一个完整的收发过程
SPI四种通信模式(根据从机模式来设置)
时钟极性CPOL : 设置时钟空闲时的电平
当CPOL = 0 ,SCK引脚在空闲状态保持低电平;
当CPOL = 1 ,SCK引脚在空闲状态保持高电平。
时钟相位CPHA :设置数据采样时的时钟沿
当 CPHA=0 时,MOSI或 MISO 数据线上的信号将会在 SCK时钟线的奇数边沿被采样
当 CPHA=1时, MOSI或 MISO 数据线上的信号将会在 SCK时钟线的偶数边沿被采样
总结表:
notes:SPI控制器可以设置数据帧的长度(8/16位),也可以配置MSB(高位在前,默认使用)或者 LSB(低位在前)模式
串行FLASH_W25QXX介绍
flash:fflash又称作闪存,掉电不丢失,和EEPPROM一样是存储元件,但是EEPROM存储容量小,flash存储容量大,且进行一大片的擦写。EEPROM容量小,可以进行单字节擦写。
SPI_W25QXX介绍
W25Q256JV阵列被组织成131,072个可编程页,每个页256字节。每次最多可编程256字节。页可以以16为一组(4KB扇区擦除)、128为一组(32KB块擦除)、256为一组(64KB块擦除)或整个芯片(芯片擦除)进行擦除。
硬件连线:
CE:片选引脚,可以接普通的gpio引脚,而可以不使用SPI提供的引脚
SI_IO0(MOSI):主机输出,从机输入
SO_IO1(MISO):主机输入,从机输出
CLK:时钟引脚
WP:写保护引脚
HOLD:可用于暂停通讯,可不使用
控制指令:
读取设备ID指令(90)
通常在调试的情况下用到,以此来确定设备是否可用
写使能命令(06H)
在向flash写数据时,首先要使能写操作
扇区擦除(20H)
flash存储器特性,决定他只能把原来的1改为0,而不能执行将0写为1的操作,因此在写入之前需要对目标存储矩阵进行擦除
读状态寄存器(05H)
使用前需要打开写使能,flash向存储矩阵写入或者擦除操作时,需要耗费一定的时间,因此需要判断这些操作是否已经完成,因此只需要判断状态寄存器最后一位的值。(1表示忙碌,0表示空闲)
读数据(03H)
,读数据可以从存储器中依次读出1个或者多个字节,每传输一个字节地址都会自动递增,只要时钟继续传输,可以不断读取存储器中的数据。
写数据(02H)(page program页编程)
页编程可以在擦除的存储单元写入256个字节。如果超过页地址,那么寻址将会自动的切换到页开头,进行覆盖。
也变成可能存在的一些问题:
页(256个字节)->扇区->块(段)
当写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入
SPI读写flash实例
w25q.h
#ifndef _WX25Q_H_
#define _WX25Q_H_
#include "stm32f4xx_hal.h"
#define sFLASH_ReadDeviceID 0X90
#define sFLASH_WriteEnable 0X06
#define sFLASH_Earse 0X20
#define sFLASH_ReadRegrestStatus 0X50
#define sFLASH_ReadDate 0X03
#define sFLASH_PageWrite 0X02
#define sFLASH_DUMMY_BYTE 0X00
#define sFLASH_BUSY_FLAG 0X01
#define sFLASH_PAGE_SIZE 0X100
#define sFLASH_CS_LOW() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_6,GPIO_PIN_RESET)
#define sFLASH_CS_HIGH() HAL_GPIO_WritePin(GPIOF,GPIO_PIN_6,GPIO_PIN_SET)
uint8_t sFLASH_SendByte(uint8_t byte);
uint16_t sFLASH_ReadID(void);
void sFLASH_Sector_Earse(uint32_t Sector_Addr);
void sFLASH_WaitForEnd(void);
void sFLASH_Read_Data(uint8_t* Rxbuff,uint32_t ReadAddr,uint32_t len);
void sFLASH_Write_Enable(void);
void sFLASH_Write_Page(uint8_t *Txbuf,uint32_t WriteAddr,uint32_t len);
void sFLASH_Write_Buff(uint8_t * txbuf,uint32_t txaddr,uint32_t len);
#endif
w25q.c
#include "wx25q.h"
extern SPI_HandleTypeDef hspi5;
/*读写一个字节函数*/
uint8_t sFLASH_SendByte(uint8_t byte){
uint8_t tx_data=byte;
uint8_t rx_data=0;
HAL_SPI_TransmitReceive(&hspi5, &tx_data, &rx_data, 1,1000);
return rx_data;
}
uint16_t sFLASH_ReadID(void){
uint8_t temp1=0;
uint8_t temp2=0;
uint16_t device_id=0;
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_ReadDeviceID);
sFLASH_SendByte(sFLASH_DUMMY_BYTE);
sFLASH_SendByte(sFLASH_DUMMY_BYTE);
sFLASH_SendByte(sFLASH_DUMMY_BYTE);
/*接收ID*/
temp1=sFLASH_SendByte(sFLASH_DUMMY_BYTE);
temp2=sFLASH_SendByte(sFLASH_DUMMY_BYTE);
device_id=temp1<<8 | temp2;
sFLASH_CS_HIGH();
return device_id;
}
void sFLASH_WaitForEnd(void){
uint8_t Busy_Flag=0;
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_ReadRegrestStatus);
do{
Busy_Flag=sFLASH_SendByte(sFLASH_DUMMY_BYTE);
}while(Busy_Flag & sFLASH_BUSY_FLAG);
sFLASH_CS_HIGH();
}
void sFLASH_Write_Enable(void){
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_WriteEnable);
sFLASH_CS_HIGH();
}
void sFLASH_Sector_Earse(uint32_t Sector_Addr){
sFLASH_Write_Enable();
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_Earse);
sFLASH_SendByte((Sector_Addr>>16) & 0xff);
sFLASH_SendByte((Sector_Addr>>8) & 0xff);
sFLASH_SendByte((Sector_Addr>>0) & 0xff);
sFLASH_CS_HIGH();
sFLASH_WaitForEnd();
}
void sFLASH_Read_Data(uint8_t* Rxbuff,uint32_t ReadAddr,uint32_t len){
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_ReadDate);
sFLASH_SendByte((ReadAddr>>16) & 0xff);
sFLASH_SendByte((ReadAddr>>8) & 0xff);
sFLASH_SendByte((ReadAddr>>0) & 0xff);
while(len--){
*Rxbuff=sFLASH_SendByte(sFLASH_DUMMY_BYTE);
Rxbuff++;
}
sFLASH_CS_HIGH();
}
void sFLASH_Write_Page(uint8_t *Txbuf,uint32_t WriteAddr,uint32_t len){
if(len>sFLASH_PAGE_SIZE){
len=sFLASH_PAGE_SIZE;
printf("the number of data is over a page\n");
}
sFLASH_Write_Enable();
sFLASH_CS_LOW();
sFLASH_SendByte(sFLASH_PageWrite);
sFLASH_SendByte((WriteAddr>>16) & 0xff);
sFLASH_SendByte((WriteAddr>>8) & 0xff);
sFLASH_SendByte((WriteAddr>>0) & 0xff);
while(len--){
sFLASH_SendByte(*Txbuf);
Txbuf++;
}
sFLASH_CS_HIGH();
sFLASH_WaitForEnd();
}
/*解决跨页写的问题*/
void sFLASH_Write_Buff(uint8_t * txbuf,uint32_t txaddr,uint32_t len){
uint8_t Numberofpage=0,Numberofbyte=0,offset=0,count=0;
offset=txaddr % sFLASH_PAGE_SIZE;
count=sFLASH_PAGE_SIZE - offset;
if(offset && (len>count)){
sFLASH_Write_Page(txbuf,txaddr,count);
txbuf+=count;
txaddr+=count;
Numberofpage=(len-count)/sFLASH_PAGE_SIZE;
Numberofbyte=(len-count)%sFLASH_PAGE_SIZE;
}
while(Numberofpage--){
sFLASH_Write_Page(txbuf,txaddr,sFLASH_PAGE_SIZE);
txaddr+=sFLASH_PAGE_SIZE;
txbuf+=sFLASH_PAGE_SIZE;
}
if(Numberofbyte){
sFLASH_Write_Page(txbuf,txaddr,Numberofbyte);
}
sFLASH_WaitForEnd();
}
main.c
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t device_id=0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI5_Init();
MX_USART6_UART_Init();
/* USER CODE BEGIN 2 */
printf("this is spi test\n");
printf("读取设备id测试\n");
device_id=sFLASH_ReadID();
printf("devce_id=%x\n",device_id);
printf("扇擦除测试\n");
sFLASH_Sector_Earse(0);
sFLASH_Read_Data(rxbuf,0,4096);
for(int i=0;i<strlen((const char *)rxbuf);i++){
printf("%x ",rxbuf[i]);
}
printf("\n");
printf("写数据测试\n");
for(int i=0;i<1000;i++){
txbuf[i]=0x55;
}
sFLASH_Write_Page(txbuf,0,200);
HAL_Delay(500);//延迟才能接收到数据,否则出现读不到数据情况(暂时不知道原因)
sFLASH_Read_Data(rxbuf,0,200);
for(int i=0;i<200;i++){
printf("%x ",rxbuf[i]);
}
printf("\n");
printf("跨页写数据测试\n");
sFLASH_Write_Buff(txbuf,200,1000);
HAL_Delay(500);//延迟才能接收到数据,否则出现读不到数据情况(暂时不知道原因)
sFLASH_Read_Data(rxbuf,200,1000);
for(int i=0;i<1000;i++){
printf("%x ",rxbuf[i]);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
三、单总线传感器(略)
单总线传感器(红外遥控,温度传感器),只需要一根I/O线即可完成数据的传输,主要是依靠一根性传输高低电平的时间长短来规定协议,这些元器件基本上购买产品时已经有配套的驱动代码,只需要略微改动一下即可使用,因此本章节不在具体叙述。后期有使用在来完善这部分。
四、FSMC扩展SRAM(略)
常用存储器介绍
DRAM(动态随机存储器)
有电荷表示1,无电荷表示0,需要定期刷新
同步和异步DRAM(有无CLK),同步速率更快,因此采用更多,称为SDRAM;
DDR SDRAM:为了提高速率而设计,由原来的单边沿读取数据变成双边沿读取数据
SRAM(静态)随机存储器
内部结构为锁存器的结构,能保持当前状态,因此不需要定时刷新
非易失性存储器
ROM:
flash:
SDRAM(W9825G6KH-6芯片)
W9825G6KH是一种高速同步动态随机存取存储器(SDRAM),由4M字x 4组x 16位组成。
配置实例:
数据手册PH7对应为SDCKE1,所以是选择SDRAM2.
使用SDRAM扩展内存
1.使能GPIO,设置引脚模式
2.SDRAM,初始化SDRAM外设
/* USER CODE BEGIN FMC_Init 2 */
/*1.提供时钟信号*/
FMC_SDRAM_CommandTypeDef Command;
Command.CommandMode=FMC_SDRAM_CMD_CLK_ENABLE;
Command.AutoRefreshNumber=1;
Command.CommandTarget=FMC_SDRAM_CMD_TARGET_BANK2;
Command.ModeRegisterDefinition=0;
HAL_SDRAM_SendCommand(&hsdram2, &Command, 0XFFFF);
/*2.至少延迟200us*/
HAL_Delay(1);
/*3.引脚预充电*/
Command.CommandMode=FMC_SDRAM_CMD_PALL;
Command.AutoRefreshNumber=1;
Command.CommandTarget=FMC_SDRAM_CMD_TARGET_BANK2;
Command.ModeRegisterDefinition=0;
HAL_SDRAM_SendCommand(&hsdram2, &Command, 0XFFFF);
/*4.插入8个自动刷新周期*/
Command.CommandMode=FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command.AutoRefreshNumber=8;
Command.CommandTarget=FMC_SDRAM_CMD_TARGET_BANK2;
Command.ModeRegisterDefinition=0;
HAL_SDRAM_SendCommand(&hsdram2, &Command, 0XFFFF);
/*5.编程SDRAM加载模式寄存器*/
Command.CommandMode=FMC_SDRAM_CMD_LOAD_MODE;
Command.AutoRefreshNumber=1;
Command.CommandTarget=FMC_SDRAM_CMD_TARGET_BANK2;
Command.ModeRegisterDefinition=0X230;
HAL_SDRAM_SendCommand(&hsdram2, &Command, 0XFFFF);
/*6.配置STM32FMC自动刷新周期*/
FMC_SDRAM_ProgramRefreshRate((FMC_SDRAM_TypeDef *)&hsdram2, 703);
/* USER CODE END FMC_Init 2 */
5、编程加载寄存器 Command.ModeRegisterDefinition参数解释
6。配置STM32FMC自动刷新周期
90*7.81-20
五、FSMC-LCD显示屏(略)
六、电源管理
电源管理结构框图
1.备份域
拥有纽扣电池给电路供电,锁存器结构,主电源供电时,纽扣电池不要供电,主电源断开,纽扣电池供电,保持电路运行
2.调压器供电电路
为调压器以及待机电路以外的电路供电
3.ADC电源电路
ADC精度要求,独立供电
工作模式
运行模式
全功率供电
停止模式
关闭所有时钟,所有外设停止工作,但是保留内核电源供电,因此内核寄存器信息未丢失
睡眠模式
仅关闭内核时钟,内核停止运行,但是外设正常工作
待机模式
关闭所有电源,唤醒相当于复位
相关配置函数
睡眠模式
HAL_PWR_EnterSLEEPMode(uint32_t Regulator, uint8_t SLEEPEntry);//进入睡眠模式,中断唤醒
但是systick始终中断未关闭,因此会一直唤醒,因此还需要关闭systick中断
void HAL_SuspendTick(void)//关闭systick中断
void HAL_ResumeTick(void)//开启中断
停止模式
HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)//进入停止模式
关闭了时钟,因此不需要关闭systick,但是唤醒后默认是HSI(内部低速时钟,因此需要重新指定系统工作时钟(HSE,PLL))
待机模式
1.使能wakeup按键
void HAL_PWR_DisableWakeUpPin(uint32_t WakeUpPinx);
2.清除标志位(唤醒)
3.进入待机模式
void HAL_PWR_EnterSTANDBYMode(void)