SPI协议:全双工 3/4根信号线
SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
MISO 主设备数据输入,从设备数据输出。(MISO:master input slave output)
MOSI 主设备数据输出,从设备数据输入。(MOSI:master output slave input)
SCLK时钟信号,由主设备产生。(SCLK:serial clock)
CS从设备片选信号,由主设备控制。(CS:chip select)这个一主多从通信的时候才会用到
SPI工作原理
(1)硬件上为4根线。
(2)主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
(3)串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
(4)外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI初始化配置
SPI控制器的初始化步骤有以下几步:
(1)使能SPI复用功能所映射的GPIO口时钟。
(2)SPI控制器时钟使能。
(3)配置GPIO为复用功能。(MOSI/MISO/CLK)
(4)GPIO复用功能为第几复用功能(AFx)。
(5)配置GPIO输出速率为50MHz。
(6)配置SPIx->CR1寄存器。
//SPI 口初始化
//这里针是对 SPI1 的初始化
void SPI1_Init(void)
{
u16 tempreg=0;
RCC->AHB1ENR|=1<<0; //使能 PORTA 时钟
RCC->APB2ENR|=1<<12; //SPI1 时钟使能
GPIO_Set(GPIOB,7<<3,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_100M,
GPIO_PUPD_PU); //PB3~5 复用功能输出
GPIO_AF_Set(GPIOB,3,5); //PB3,AF5
GPIO_AF_Set(GPIOB,4,5); //PB4,AF5
GPIO_AF_Set(GPIOB,5,5); //PB5,AF5
//这里只针对 SPI 口初始化
RCC->APB2RSTR|=1<<12; //复位 SPI1
RCC->APB2RSTR&=~(1<<12);//停止复位 SPI1
tempreg|=0<<10; //全双工模式
tempreg|=1<<9; //软件 nss 管理
tempreg|=1<<8;
tempreg|=1<<2; //SPI 主机
tempreg|=0<<11; //8 位数据格式
tempreg|=1<<1; //空闲模式下 SCK 为 1 CPOL=1
tempreg|=1<<0; //数据采样从第 2 个时间边沿开始,CPHA=1
ALIENTEK 探索者 STM32F407 开发板教程
343
STM32F4 开发指南(寄存器版)
//对 SPI1 属于 APB2 的外设.时钟频率最大为 84Mhz 频率.
tempreg|=7<<3; //Fsck=Fpclk1/256
tempreg|=0<<7; //MSB First
tempreg|=1<<6; //SPI 启动
SPI1->CR1=tempreg; //设置 CR1
SPI1->I2SCFGR&=~(1<<11);//选择 SPI 模式
SPI1_ReadWriteByte(0xff);//启动传输
}
//SPI1 速度设置函数
//SpeedSet:0~7
//SPI 速度=fAPB2/2^(SpeedSet+1)
//fAPB2 时钟一般为 84Mhz
void SPI1_SetSpeed(u8 SpeedSet)
{
SpeedSet&=0X07; //限制范围
SPI1->CR1&=0XFFC7;
SPI1->CR1|=SpeedSet<<3; //设置 SPI1 速度
SPI1->CR1|=1<<6; //SPI 设备使能
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
while((SPI1->SR&1<<1)==0); //等待发送区空
SPI1->DR=TxData; //发送一个 byte
while((SPI1->SR&1<<0)==0); //等待接收完一个 byte
return SPI1->DR; //返回收到的数据
}
IIC:是一个半双工同步串行通信,用于IIC设备之间通讯的协议。
半双工同步串行通信的意义为:
半双工:同一时间只能发送或者接收(数据脚收发公用)。
同步:有时钟线控制。
串行:数据位是一个一个位发出去。
两条线:一条SDA数据线和一条SCK时钟线
通信速度:
标准IIC(100KHz),快速IIC(400KHz),高速IIC(3.4MHz)。
工作原理:
1)主发送器向从接收器发送数据。主机发送的第一个字节是从机地址,接下来是数据字节。从机每接收一个字节就返回一个应答位。
2)从发送器向主接收器发送数据。主机发送的第一个字节是从机地址,然后从机返回一个应答位。接下来从机向主机发送数据字节。主机每接收一个字节都会返回一个应答位,最后一个字节除外。接收完最后一个字节后,主机返回一个"非应答位"。主机产生所有串行时钟脉冲、起始条件以及停止条件。每一帧都以一个停止条件或一个重复起始条件来结束。由于重复的起始条件也是下一帧的开始,所以将不会释放IIC 总线。
I2C 总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号: 接收数据的IIC器件在接收到 8bit 数据后, 向发送数据的主控发出特定的低电平脉冲, 表示已收到数据。主控向被控制的IIC器件发出8bit 数据后,等待被控制的IIC器件发出一个应答信号,主控 接收到应答信号后, 根据实际情况作出是否继续传递数据的判断。若未收到应答信号, 则判断被控制的IIC器件出现故障。 这些信号中,起始信号时必须的, 结束信号和应答信号, 都可以不要。 IIC 总线时序如图所示:
时序图
关于IIC应答信号说明:
不给应答:只有主机可以不给应答,什么情况可以不给应答?主机接到最后一个数据,它不需要从机再给数据主机了,不给应答(把数据线拉高电平)。
从机收到数据,从机给出应答(谁去接收应答?主机),从机只会给应答0。
主机在时钟线SCL为高电平的时候,读取应答信号
主机收到数据,主机给出应答(谁去接收应答?从机)。给应答信号:代表主机要继续要数据。不给应答:代表主机不再需要数据。
/******************************************************************************
* INCLUDES
*/
#include "stm32f4xx.h"
#include "iic.h"
#include "gpio.h"
#include "delay.h"
//IO方向设置
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;} //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式
#define SCL_H() (GPIOB->BSRRL = 1<<8)
#define SCL_L() (GPIOB->BSRRH = 1<<8)
#define SDA_H() (GPIOB->BSRRL = 1<<9)
#define SDA_L() (GPIOB->BSRRH = 1<<9)
#define RD_SDA() ((GPIOB->IDR & 1<<9)&&1)
void IIC_init(void)
{
RCC->AHB1ENR|=1<<1; //使能PORTB时钟
GPIO_set(GPIOB,PIN8|PIN9,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB8/PB9设置
SCL_H();
SDA_H();
}
/******************************************************************************
* 函数名: IIC_start
* 功能描述: 模拟iic启动信号
******************************************************************************/
void IIC_start(void)
{
SDA_OUT(); //sda线输出
SDA_H();
SCL_H();
SDA_L();
//快速IIC:延时0.6us 标准IIC: 延时4us
delay_us(6);
}
/******************************************************************************
* 函数名: IIC_stop
* 功能描述: 模拟iic停止信号
******************************************************************************/
void IIC_stop(void)
{
SDA_OUT();//sda线输出
SCL_L();
SDA_L();//STOP:when CLK is high DATA change form low to high
delay_us(4);
SCL_H();
delay_us(4);
SDA_H();//发送I2C总线结束信号
}
/******************************************************************************
* 函数名: IIC_waitAck
* 功能描述: IIC master 等待应答信号
* 参数说明: 1,接收应答失败
* 0,接收应答成功
******************************************************************************/
u8 IIC_waitAck(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
SDA_H();delay_us(1);
SCL_H();delay_us(1);
while(RD_SDA())
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_stop();
return 1;
}
}
SCL_L();//时钟输出0
return 0;
}
/******************************************************************************
* 函数名: IIC_ack
* 功能描述: 给应答信号
******************************************************************************/
void IIC_ack(void)
{
SCL_L();
SDA_OUT();
SDA_H();
delay_us(2);
SDA_L();
delay_us(3);
SCL_H();
delay_us(4);
SCL_L();
delay_us(2);
SDA_H();
}
/******************************************************************************
* 函数名: IIC_nAck
* 功能描述: 不给应答信号
******************************************************************************/
void IIC_nAck(void)
{
SCL_L();
SDA_OUT();
SDA_H();
delay_us(2);
SDA_H();
delay_us(3);
SCL_H();
delay_us(4);
SCL_L();
delay_us(2);
SDA_H();
}
/******************************************************************************
* 函数名: IIC_sendByte
* 功能描述: 发送一个字节
******************************************************************************/
void IIC_sendByte(u8 txd)
{
u8 i;
SDA_OUT();
for(i=0;i<8;i++)//连续写8个位
{
SCL_L();
if(txd&(1<<(7-i)))
{
SDA_H();
}
else
{
SDA_L();
}
//快速IIC:延时1.3us 标准IIC: 延时4.7us
//delay_us(2);
delay_us(3);
SCL_H();
//快速IIC:延时0.6us 标准IIC: 延时4us
//delay_us(1);
delay_us(3);
SCL_L();
}
SDA_H();//释放管脚控制,等待应答
}
/******************************************************************************
* 函数名: IIC_readByte
* 功能描述: 读取一个字节
* 参数说明: ack=0时,发送ACK,ack=1,发送nACK
* 返回值说明: 返回读取到的数据
******************************************************************************/
u8 IIC_readByte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++)//连续的读8个位
{
SCL_L();
//快速IIC:延时1.3us 标准IIC: 延时4.7us
delay_us(5);
SCL_H();
//快速IIC:延时0.6us 标准IIC: 延时4us
delay_us(4);
if(RD_SDA()==1)
{
receive |= (1<<(7-i));
}
else
{
receive &= ~(1<<(7-i));
}
SCL_L();
}
if (!ack)
IIC_ack(); //发送ACK
else
IIC_nAck();//发送nACK
return receive;
}