SPI配置方法
1.SPI简介
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
2.SPI工作原理总结
①硬件上为4根线。
②主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
③串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
④外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
3.配置方法
1)时钟使能。GPIO时钟使能RCC->APB2ENR,SPI时钟使能RCC->APB2ENR设置。(为什么还要连接GPIO时钟,参见STM32参考手册8.1.4节。手册上这么说的:对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏)。)
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();
2)配置GPIO工作模式。配置GPIO片选,由软件管理(即自定义引脚),推挽输出,上拉;配置SPI引脚SCK、MOSI、MISO所用到的引脚为复用功能;GPIOX->CR1 GPIOX->ODR;
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
3)SPI设置工作模式。通过配置SPIx->CR1来设置SPI 的工作模式。配置工作模式为全双工,主机模式,SCK闲时电平为高,第二个时钟沿(上升沿)采样数据,内部从机选择软件管理模式。设置SPI的时钟频率(最大18MHZ),设置数据格式(MSB在前还是LSB在后),内部从机选择设置为主机(置1)。
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
4)使能SPI,启动传输。
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
5)SPI传输数据。
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
6)查看SPI传输状态。
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
总结
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1时钟
//PB3,4,5初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5复用功能输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4复用为SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5复用为SPI1
//这里只针对SPI口初始化
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号由SSI控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI1_ReadWriteByte(0xff);//启动传输
}
//SPI1速度设置函数
//SPI速度=FAPB2/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2时钟一般为84Mhz
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
SPI1->CR1&=0XFFC7;//位3-5清零,用来设置波特率
SPI1->CR1|=SPI_BaudRatePrescaler; //设置SPI1的速度
SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个byte数据
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一个byte
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
IIC配置方法
1.IIC功能
I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I2C总线。允许连接到标准(高达100kHz)或快速(高达400kHz)的I2C总线。
- I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
#include "iic.h"
//*****************************************************
// IIC2初始化函数:初始化STM32硬件自带的IIC2
// IIC2_SCL对应GPIO.B10
// IIC2_SDA对应GPIO.B11
//*****************************************************
void I2C2_Init(void)
{
/*GPIO与IIC初始化结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
/*GPIO与IIC时钟使能*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //GPIOB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C2, ENABLE ); //IIC2时钟使能
/*初始化GPIO*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //初始化GPIO.B10(IIC2_SCL),GPIO.B11(IIC2_SDA)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //最高输出速度50Hz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //输入输出模式为复用功能开漏输出
GPIO_Init( GPIOB, &GPIO_InitStructure ); //根据GPIO初始化结构体初始化GPIOB
/*初始化IIC2*/
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //设置为IIC模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //设置IIC的占空比,低电平除以高电平值为2
I2C_InitStructure.I2C_OwnAddress1 = AT24C02_ADDRESS; //指定第一个设备的地址为7位地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //使能ACK信号
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //指定7位地址
I2C_InitStructure.I2C_ClockSpeed = 400000; //时钟频率,必须小于等于400KHz
I2C_Cmd( I2C2, ENABLE ); //使能IIC2
I2C_Init( I2C2, &I2C_InitStructure ); //根据IIC初始化结构体初始化IIC2
/*允许一字节一应答模式*/
I2C_AcknowledgeConfig( I2C2, ENABLE ); //使能IIC2应答状态
}
//*****************************************************
// IIC2写函数 :往IIC设备写入一个BYTE的data
// id :IIC设备的id
// write_address:数据要写入IIC设备的地址
// data :需要写入的数据
//*****************************************************
void I2C2_WriteByte( u8 id, u8 write_address, u8 data )
{
while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY)); //当IIC2状态为BUSY时,一直停在这里死循环
/*发送START之后要等待,意味着START条件被正确释放,此时IIC总线上没有其它外设*/
I2C_GenerateSTART( I2C2, ENABLE ); //一旦不为BUSY,跳出循环,产生START条件
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); //如果主机被选中(死循环等待ACK信号)
/*主机发送地址联系从机后,主机要等待从机的ACK,如果主机设置为发射,*/
/*则收到ACK信号时 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 被置1 */
I2C_Send7bitAddress( I2C2, id, I2C_Direction_Transmitter ); //发送从机地址以选择从机,主机为发送模式
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //如果主机发射模式被选中(死循环等待ACK信号)
/*通讯建立(生成START过程与从机地址被承认)以后,主机发送数据,*/
/*如果数据被转移并输出IIC总线,则 I2C_EVENT_MASTER_BYTE_TRANSMITTED 被置1*/
I2C_SendData( I2C2, write_address ); //将write_address,即要写的地址通过IIC2发送出去
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //如果地址已经从IIC2成功发射出去(死循环等待ACK信号)
/*往寄存器发送数据data*/
I2C_SendData( I2C2, data );
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP( I2C2, ENABLE ); //IIC2产生STOP条件
/*循环确保IIC设备与主机的通讯建立*/
do
{
I2C_GenerateSTART( I2C2, ENABLE ); //IIC2产生START条件
I2C_Send7bitAddress( I2C2, AT24C02_ADDRESS, I2C_Direction_Transmitter );//发送EEPROM地址0XA0
}
while (!(I2C_ReadRegister(I2C2,I2C_Register_SR1)&0x0002)); //读取IIC2->SR1的值,当IIC2->SR1[1] = 1时跳出循环
//此时地址发送结束
I2C_ClearFlag( I2C2, I2C_FLAG_AF ); //IIC2清除应答错误标志位
I2C_GenerateSTOP( I2C2, ENABLE ); //IIC2产生STOP条件
}
//*****************************************************
// IIC2读函数 :从IIC设备的指定地址中读出一个BYTE的数据
// id :IIC设备的id
// read_address :需要读取IIC数据的设备的地址
// 返回值 :读出的数据
//*****************************************************
u8 I2C2_ReadByte( u8 id, u8 read_address )
{
u8 data;
/***主机发送地址***/
while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY)); //当IIC2状态为BUSY时,一直停在这里死循环
/*发送START之后要等待,意味着START条件被正确释放,此时IIC总线上没有其它外设*/
I2C_GenerateSTART( I2C2, ENABLE ); //一旦不为BUSY,跳出循环,产生START条件
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); //如果主机被选中(死循环等待ACK信号)
/*主机发送地址联系从机后,主机要等待从机的ACK,如果主机设置为发射,*/
/*则收到ACK信号时 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 被置1 */
I2C_Send7bitAddress( I2C2, id, I2C_Direction_Transmitter ); //发送从机地址以选择从机,主机为发送模式
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //如果主机发射模式被选中(死循环等待ACK信号)
/*通讯建立(生成START过程与从机地址被承认)以后,主机发送数据,*/
/*如果数据被转移并输出IIC总线,则 I2C_EVENT_MASTER_BYTE_TRANSMITTED 被置1*/
I2C_Cmd( I2C2, ENABLE ); //使能IIC2
I2C_SendData( I2C2, read_address ); //将write_address,即要读的地址通过IIC2发送出去
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //如果地址已经从IIC2成功发射出去(死循环等待ACK信号?
I2C_GenerateSTART( I2C2, ENABLE ); //产生START条件
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); //如果主机被选中(死循环等待ACK信号)
/***主机接收数据***/
I2C_Send7bitAddress( I2C2, id, I2C_Direction_Receiver ); //主机设置为接收模式
while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //如果主机接收模式被选中(死循环等待ACK信号)
I2C_AcknowledgeConfig( I2C2, DISABLE ); //失能IIC2的应答状态
I2C_GenerateSTOP( I2C2, ENABLE ); //产生STOP条件
while (!(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED))); //如果主机接收数据成功(死循环等待ACK信号)
data = I2C_ReceiveData(I2C2); //返回IIC2接收的数据,赋给temp
I2C_AcknowledgeConfig( I2C2, ENABLE ); //再一次使能IIC2的应答状态
return data; //返回接收到的数据
}
SPI驱动OLED屏幕
#include "OLED.h"
#include "oledfont.h"
void delay(int ms) //延时毫秒
{
delay_init(72);
delay_ms(ms);
}
//SPI时序为在时钟的上升沿把数据写入
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{
unsigned char i;
if(cmd) //判断是写命令还是数据,从而拉低对应的IO引脚
OLED_DC_Set();
else
OLED_DC_Clr();
OLED_CS_Clr(); //拉低片选线
for(i=0;i<8;i++)
{
OLED_SCLK_Clr(); //拉低时钟线,数据会在时钟的上升沿把数据写入
if(dat&0x80) //从高位开始传送数据
{
OLED_SDIN_Set();
}
else
OLED_SDIN_Clr();
OLED_SCLK_Set();
dat<<=1;
}
OLED_CS_Set();
OLED_DC_Set();
}
//开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
//关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
unsigned char i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)
OLED_WR_Byte(0,OLED_DATA); //每一页有128列,8行,每写入一个字节会一次性写入8行,每写入一列后再写入的时候会自动写入下一位
} //更新显示
}
//设置要在那一列的哪一列画点,所谓的画点就是让屏幕上某一个位置的点亮或者不亮,因为每次至少写入一个字节,所以一次点亮或关闭的至少是竖着的8个点
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD); //设置页地址
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD); //设置列地址高4位
OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD); //设置列地址低4位
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~7
//mode:0,反白显示;1,正常显示
//size:选择字体 16/12
//char为要显示的字符,这个字符要是ASCII码中的字符
//下面这个函数是在指定位置显示ASCII码的函数
//每一个ASCII码占16*8个点
//x, y指的是要写入的字符左上角是在哪一列,哪一页,x对应列,y对应页
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr)
{
unsigned char c=0,i=0;
c=chr-' '; //得到偏移后的值
if(x>Max_Column-1)
{
x=0;
y=y+2;
}
if(SIZE ==16)
{
OLED_Set_Pos(x,y); //在第y页,第x列开始写字符
for(i=0;i<8;i++) //从第y页,第x列开始写第一个字符,连续写8列
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1); //因为字体垂直方向为16,水平方向为8,所以每写一个字符要设计到两页
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
else
{
OLED_Set_Pos(x,y+1);
for(i=0;i<6;i++)
OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
IIC驱动mpu6050
#include "mpu6050.h"
uint8_t buffer[14];
void MPU6050_setClockSource(uint8_t source){
MPU_IICwriteBits(0x68, 0x6B, 2, 3, source);
}
void MPU6050_setFullScaleGyroRange(uint8_t range) {
MPU_IICwriteBits(0x68, 0x1B, 4, 2, range);
}
void MPU6050_setFullScaleAccelRange(uint8_t range) {
MPU_IICwriteBits(0x68, 0x1C, 4, 2, range);
}
void MPU6050_resetEnabled(uint8_t enabled) {
MPU_IICwriteBit(0x68, 0x6B, 7, enabled);
Delay_ms(100);
}
void MPU6050_set_Gyro_Accel_outRange(uint8_t range) {
MPU_IICwriteBits(0x68, 0x6C, 5, 6, range);
}
void MPU6050_set_SMPRT_DIV(uint8_t range) {
MPU_IICwriteByte(0x68,0x19,range);
}
void MPU6050_set_Gyro_DLPF_outRange(uint8_t range) {
MPU_IICwriteBits(0x68, 0x1A, 2, 3, range);
}
void MPU6050_INT_Disabled(uint8_t enabled) {
MPU_IICwriteBit(0x68, 0x38, 0, enabled);
}
void MPU6050_setSleepEnabled(uint8_t enabled) {
MPU_IICwriteBit(0x68, 0x6B, 6, enabled);
}
void MPU6050_testConnection(void) {
while(buffer[0] != 0x68){
MPU_IICreadBytes(0x68, 0x75, 1, buffer);
};
}
void MPU6050_setI2CMasterModeEnabled(uint8_t enabled) {
MPU_IICwriteBit(0x68, 0x6A, 5, enabled);
}
void MPU6050_setI2CBypassEnabled(uint8_t enabled) {
MPU_IICwriteBit(0x68, 0x37, 1, enabled);
}
void MPU6050_initialize(void) {
MPU_IIC_Init(); //IIC初始化
MPU6050_testConnection(); //检测MPU6050 是否已经连接
MPU6050_resetEnabled(1); //复位mpu6050 需延时100ms
MPU6050_setClockSource(2); //设置时钟
MPU6050_set_Gyro_Accel_outRange(0); // 设置输出三轴陀螺仪和三轴加速度数据
MPU6050_INT_Disabled(0); //禁止中断
MPU6050_set_SMPRT_DIV(0); //采样分频
MPU6050_setFullScaleGyroRange(3);//陀螺仪最大量程 +-1000度每秒
MPU6050_setFullScaleAccelRange(0); //加速度度最大量程 +-2G
MPU6050_set_Gyro_DLPF_outRange(2); //设置陀螺仪和加速度计的低通滤波器
MPU6050_setSleepEnabled(0); //进入工作状态
MPU6050_setI2CMasterModeEnabled(0); //不让MPU6050 控制AUXI2C
MPU6050_setI2CBypassEnabled(1); //主控制器的I2C与 MPU6050的AUXI2C 直通。控制器可以直接访问HMC5883L
}