第十章IIC通信
協議規定, 起始之后主機必須先發送一個字節: 從機地址+讀寫位, 進行尋址
然后接收一下應答位,?
然后再發送一個字節, 寫入從機寄存器地址?
之后就可以進行數據的收發了
注意: 在 主機的接收應答的時候, 立刻釋放SDA 然后這時候從機會立刻做出反應, 即拉低SDA, 也就是置0, 說明給了應答, 如果后面SCL高電平期間, 主機讀應答位的時候, 發現是高電平, 說明從機沒給應答
?
軟件模擬代碼:
#include "Delay.h"
//用宏函數來方便改變電平, 但是這樣有一點缺點, 可以套一個函數
//改變引腳電平后延時10us
//開漏輸出+ 若上拉, 主機輸出1 不是輸出1 , 而不是釋放
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_10, (BitAction)BitValue);Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_11, (BitAction)BitValue);Delay_us(10);
}
//讀取的時候CLK是高電平
uint8_t MyI2C_R_SDA(void)
{uint8_t Bit;Bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);//這里主機讀取, 所以是外部輸入Delay_us(10);return Bit;
}void MyI2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}void MyI2C_Start(void)
{MyI2C_W_SDA(1);MyI2C_W_SCL(1);MyI2C_W_SDA(0);MyI2C_W_SCL(0);
}void MyI2C_Stop(void)
{MyI2C_W_SDA(0);MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for(i = 0;i<8;i++){MyI2C_W_SDA(!!(Byte & (0x80 >> i)));MyI2C_W_SCL(1);MyI2C_W_SCL(0);}
}uint8_t MyI2C_ReceiveByte(void)
{uint8_t Data = 0x00;uint8_t i;MyI2C_W_SDA(1);//先釋放一次就可以了for(i = 0;i<8;i++){MyI2C_W_SCL(1);if(MyI2C_R_SDA()==1){Data |= (0x80 >> i);}MyI2C_W_SCL(0);}return Data;
}void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);//先釋放一次就可以了MyI2C_W_SCL(1);AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);return AckBit;
}
這里的發送和接收都是高位先行, 按照規定SCL低電平的時候進行寫數據, 高電平的時候讀數據
MPU6050簡介
?
?
I2C可以去進行軟件模擬, 因為I2C是同步時序
MPU6050讀取姿態信息代碼展示:
這里用一個MPU6050_Reg.h頭文件來去管理寄存器的宏定義
然后就按照指定地址讀寫進行完成讀寫函數
0xD0是MPU6050的地址, 同時最后一位給1或0 是我們要去讀還是寫的設定 , 給1 是讀, 給0 是寫
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS);MyI2C_ReceiveAck();//接收應答MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();//接收應答MyI2C_SendByte(Data);MyI2C_ReceiveAck();//接收應答MyI2C_Stop();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS);MyI2C_ReceiveAck();//接收應答MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();//接收應答MyI2C_Start();MyI2C_SendByte(MPU6050_ADDRESS | 0x01);MyI2C_ReceiveAck();//接收應答Data =MyI2C_ReceiveByte();MyI2C_SendAck(1);//發送應答 , 主機接收之后要發送MyI2C_Stop();return Data;
}void MPU6050_Init(void)
{MyI2C_Init();MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);MPU6050_WriteReg(MPU6050_CONFIG, 0x06);MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);}uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}void MPU6050_GetDate(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{//接下來就讀取數據, 放到指針里面, 這樣就把數據傳出去了, 數據寄存器是16位的, 那就分別讀取高位和低位的值拼接起來uint8_t DataH, DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ = (DataH<<8) | DataL; }
還有最后通過變量指針的方式將數據帶出函數?
代碼解釋:
硬件I2C
GPIO要配置復用開漏輸出模式
代碼展示:

因為硬件I2C是非阻塞的, 我們要去等待標志位, 如圖, 每一個操作后面都有事件, 我們要去捕捉那些事件, 等待那些事件到來之后再去進行下一步操作
同時這個等待也要進行超時檢測!!!,封裝一下就可以了
硬件I2C就是那些基本的發送, 接收, 起始, 結束, 等待都不需要我們自己去寫
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000;while(I2C_CheckEvent(I2Cx, I2C_EVENT)!=SUCCESS){Timeout--;if(Timeout==0){break;}}
}void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
// MyI2C_Start();
// MyI2C_SendByte(MPU6050_ADDRESS);
// MyI2C_ReceiveAck();//接收應答
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();//接收應答
// MyI2C_SendByte(Data);
// MyI2C_ReceiveAck();//接收應答
// MyI2C_Stop();//軟件I2C都是阻塞形式的, 有延時, 而硬件是非阻塞式的, 那么就要等待標志位I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//自帶應答, 就不用處理了MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, RegAddress);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);I2C_SendData(I2C2, Data);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTOP(I2C2, ENABLE);
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;
// MyI2C_Start();
// MyI2C_SendByte(MPU6050_ADDRESS);
// MyI2C_ReceiveAck();//接收應答
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();//接收應答
//
// MyI2C_Start();
// MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
// MyI2C_ReceiveAck();//接收應答
// Data =MyI2C_ReceiveByte();
// MyI2C_SendAck(1);//發送應答 , 主機接收之后要發送
// MyI2C_Stop();
// return Data;I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//同時給超時退出I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//自帶應答, 就不用處理了MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, RegAddress);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//自帶應答, 就不用處理了MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//這是接收//接收一個字節有特殊設定, 提前配置ACK位, 和給停止I2C_AcknowledgeConfig(I2C2, DISABLE);I2C_GenerateSTOP(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);Data = I2C_ReceiveData(I2C2);I2C_AcknowledgeConfig(I2C2, ENABLE);//恢復給應答的形式, 以便多數據的代碼return Data;
}void MPU6050_Init(void)
{
// MyI2C_Init();RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_InitStructure.I2C_ClockSpeed = 50000;I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//在快速模式下有用, 給不同的時鐘占空比I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;I2C_InitStructure.I2C_OwnAddress1 = 0x00;//這是stm32做從機才有用, 這里隨便填一個I2C_Init(I2C2, &I2C_InitStructure);I2C_Cmd(I2C2, ENABLE);MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);MPU6050_WriteReg(MPU6050_CONFIG, 0x06);MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);}uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}void MPU6050_GetDate(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{//接下來就讀取數據, 放到指針里面, 這樣就把數據傳出去了, 數據寄存器是16位的, 那就分別讀取高位和低位的值拼接起來uint8_t DataH, DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY = (DataH<<8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ = (DataH<<8) | DataL; }
?
這幾個操作時發送一個字節特殊的操作
第十一章SPI通信
ss線, 主機要和哪個從機通信, 就將哪個ss線電平置0
因為SPI是全雙工, 所以可以使用推挽輸出, 那么電平的變化就可以很快, 那么通信速率就可以達到很快
這個模式是對應上面的移位示意圖
即SCK上升沿主機讀數據, 下降沿主機發送數據?
這些都是SCK上升沿或者是下降沿采樣的區別
W25Q60芯片介紹
像這個芯片可以儲存一些數據, 而且掉電不丟失, 有些項目中需要存儲數據時可以選擇
?
忙狀態就是芯片在Flash里面操作數據需要一點時間, 這時候芯片處于忙狀態, 不能去讀寫操作
重要的指令集
Write Enable 06h
Write Disable 04h
?Read Status Register 05h
Pag Program 02h
Sendor Erase 20h
JEDEC ID 9Fh
Read Data 03h?
寫入不能跨頁, 但是讀取可以跨頁, 即寫入到達頁尾的時候, 就會回到頁頭進行寫入
如果要寫多頁, 我們就要計算有多少頁, 然后封裝一個函數, 分批次寫入
軟件SPI
代碼展示:
MySPI.c
#include "stm32f10x.h" // Device headervoid MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4 , (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5 , (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7 , (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_Init(GPIOA, &GPIO_InitStructure);MySPI_W_SS(1);MySPI_W_SCK(0);
}void MySPI_Start(void)
{MySPI_W_SS(0);
}
void MySPI_Stop(void)
{MySPI_W_SS(1);
}
//模式0 --其他的模式都是對這個模式0稍微修改一下就可以了
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t ByteReceive = 0x00;uint8_t i;//因為是軟件模擬, SCK不可能和數據移入移出同時進行for(i = 0;i<8;i++){MySPI_W_MOSI(ByteSend & (0x80>>i));MySPI_W_SCK(1);if(MySPI_R_MISO()==1){ByteReceive |= (0x80>>i);}MySPI_W_SCK(0);}return ByteReceive;
}//改進之后, 更對應移位模型
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t i;
// for(i = 0;i<8;i++)
// {
// MySPI_W_MOSI(ByteSend);
// ByteSend<<=1;
// MySPI_W_SCK(1);
// if(MySPI_R_MISO()==1)
// {
// ByteSend |= 0x01;
// }
// MySPI_W_SCK(0);
// }
//
// return ByteSend;
//}
?W25Q64.c? : 這個上層模塊就是按照SPI通信的要實現寫使能, 等待繁忙, 頁寫入, 頁清除, 讀數據
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{MySPI_Init();
}void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID<<=8;*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);MySPI_Stop();
}void W25Q64_WriteEnable(void)
{MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();
}void W25Q64_WaitBusy(void)
{uint16_t Timeout;MySPI_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);Timeout = 10000;while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==1){Timeout--;if(Timeout==0){break;}}MySPI_Stop();
}void W25Q64_PageProgram(uint32_t Address, int8_t *DataArray, uint16_t Count)
{W25Q64_WriteEnable();MySPI_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);uint16_t i;for(i = 0;i<Count;i++){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();W25Q64_WaitBusy();
}
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();MySPI_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);MySPI_Stop();W25Q64_WaitBusy();
}
//讀數據的時候就不用再去管是否繁忙了
void W25Q64_ReadData(uint32_t Address, int8_t *DataArray, uint32_t Count)
{MySPI_Start();MySPI_SwapByte(W25Q64_READ_DATA);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);uint32_t i;for(i = 0;i<Count;i++){DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);}MySPI_Stop();
}
?MySPI_Ins.h : 這個就是指令 集的封裝
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3#define W25Q64_DUMMY_BYTE 0xFF#endif
硬件SPI
串口是低位先行, I2C和SPI是高位先行
I2S是數字音頻傳輸協議
這里有連續傳輸和非連續傳輸
對于下面這些引腳, 需要解除引腳復用的設置才可以正常當作GPIO , 或者是其他從定義的功能
如何去解除可以看6-4的視頻
對于TXE 和 RXNE 這兩個DR寄存器為空標志位都不需要我們去清除, 硬件自己會清除(這是應為寫入的時候TXE硬件清除, 讀取的時候RXNE硬件清除)
代碼展示:
MySPI.c : 硬件和軟件的上層都一樣, 只是底層硬件會封裝好一些時序寫入讀出函數方便我們使用
#include "stm32f10x.h" // Device headervoid MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4 , (BitAction)BitValue);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_Init(GPIOA, &GPIO_InitStructure);SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//分頻SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//哪個邊沿開始采樣SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//空閑默認時鐘電平 --這兩個就是模式0SPI_InitStructure.SPI_CRCPolynomial = 7;//隨便填一個默認值7SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//通信模式, 一般全雙工SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);MySPI_W_SS(1);
}void MySPI_Start(void)
{MySPI_W_SS(0);
}
void MySPI_Stop(void)
{MySPI_W_SS(1);
}
//模式0 --其他的模式都是對這個模式0稍微修改一下就可以了
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET);//等待TXE為1SPI_I2S_SendData(SPI1, ByteSend);while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET);//等待RXNE為1return SPI_I2S_ReceiveData(SPI1);
}
這里發現一個問題, 用高級定時器輸出PWM的時候要打開這個開關, 要不然不能輸出PWM
第十二章Unix時間戳
BKT備份寄存器跟上一節的Flash閃存有一點類似, 只是Flash閃存是真正的掉電不丟失, 但是BKT備份寄存器是靠著備用電源供電的?
?分頻器其實就是一個計數器, 計幾個數溢出一次就是幾分頻, 重裝值是幾, 分頻值就是重裝值+1
下面是RTC配置的注意事項
讀寫備份寄存器
代碼展示:
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"uint16_t ArrayWrite[] = {0x1234, 0x4444};
uint16_t ArrayRead[2];
uint8_t KeyNum;
int main(void)
{OLED_Init();Key_Init();OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);PWR_BackupAccessCmd(ENABLE);while (1){KeyNum = Key_GetNum();if(KeyNum==1){ArrayWrite[0]++;ArrayWrite[1]++;OLED_ShowHexNum(1, 4, ArrayWrite[0], 4);OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);}ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 4, ArrayRead[0], 4);OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}
?代碼解釋:
實時時鐘
代碼展示:
MyRTC.c
#include "stm32f10x.h" // Device header
#include <time.h>
uint16_t MyRTC_Time[] = {2024, 1, 1, 15, 55, 59};
void MyRTC_SetTime(void);
void MyRTC_Init(void)
{//1使能PWR, PKBRCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);PWR_BackupAccessCmd(ENABLE);//避免重復初始化if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//2開啟LSE, 等待啟動完成RCC_LSEConfig(RCC_LSE_ON);while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);//3選擇時鐘源RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);RCC_RTCCLKCmd(ENABLE);//等待函數RTC_WaitForSynchro();//等待同步RTC_WaitForLastTask();//等待寫入完成//配置預分頻RTC_SetPrescaler(32768-1);//自己帶了進入和退出配置模式RTC_WaitForLastTask();//設置時間MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{RTC_WaitForSynchro();RTC_WaitForLastTask();}
}void MyRTC_SetTime(void)
{time_t time_cnt;struct tm time_date;time_date.tm_year = MyRTC_Time[0]-1900;time_date.tm_mon = MyRTC_Time[1]-1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];time_cnt = mktime(&time_date);RTC_SetCounter(time_cnt);RTC_WaitForLastTask();
}void MyRTC_ReadTime(void)
{time_t time_cnt;struct tm time_date;time_cnt = RTC_GetCounter() + 8*60*60;time_date = *localtime(&time_cnt);MyRTC_Time[0] = time_date.tm_year+1900;MyRTC_Time[1] = time_date.tm_mon+1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}
代碼解釋:
設定時間和讀取時間都是秒計數器和日期時間的轉化