加多一个硬件SPI的程序
//时钟
/*SPI1*/
RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_SPI2, ENABLE);
//GPIO初始化
//SPI2_SCK->PB13
//SPI2_MOSI->PB15
GPIO_InitStructure.GPIO_Pins = GPIO_Pins_13 | GPIO_Pins_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//SPI2_MISO->PB14
GPIO_InitStructure.GPIO_Pins = GPIO_Pins_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//SPI2_NSS->PB12
GPIO_InitStructure.GPIO_Pins = GPIO_Pins_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;
GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
void SPI2_Configuration()
{
SPI_InitType SPI1_InitStructure;
SPI1_InitStructure.SPI_TransMode = SPI_TRANSMODE_FULLDUPLEX;
SPI1_InitStructure.SPI_CPHA = SPI_CPHA_1EDGE;
SPI1_InitStructure.SPI_CPOL = SPI_CPOL_LOW;
SPI1_InitStructure.SPI_CPOLY = 7;
SPI1_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;
SPI1_InitStructure.SPI_FrameSize = SPI_FRAMESIZE_8BIT;
SPI1_InitStructure.SPI_MCLKP = SPI_MCLKP_8;//75/32 = 2.34375 MBits/s
SPI1_InitStructure.SPI_NSSSEL = SPI_NSSSEL_SOFT;
SPI1_InitStructure.SPI_Mode = SPI_MODE_MASTER;
SPI_Init(SPI2, &SPI1_InitStructure);
SPI_Enable(SPI2, ENABLE);
}
//发送一个字节
void SPI1_sendByte(uint8_t b)
{
SPI_I2S_TxData(SPI1, b);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TE) == RESET);
SPI_I2S_ClearFlag(SPI1, SPI_I2S_FLAG_TE);
}
//发送多个字节
void SPI1_sendNbyte(uint8_t* index, uint8_t len)
{
while (len--)
{
SPI1_sendByte(*index++);
}
}
//读一个字节
uint8_t SPI1_readByte()
{
uint16_t tryCnt = 0;
SPI_I2S_TxData(SPI1, 0x00);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RNE) == RESET)
{
if (tryCnt++ > 1000) break;
}
if (tryCnt < 1000)
return SPI1->DT;
else
return 0;
}
//发送并接收一个字节
uint8_t spi_transmit(uint8_t b)
{
uint16_t tryCnt = 0;
/* Wait for Tx buffer empty */
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TE) == RESET)
{
if (tryCnt++ > 1000) break;
}
/* Send data */
SPI2->DT = b;
/* Wait for data reception */
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RNE) == RESET)
{
if (tryCnt++ > 1000) break;
}
/* Read received data */
return SPI2->DT;
}
————————————————————分割线——————————————
最近在学习51单片机的内容,为了防止自己学过就忘,在这里写一些平时的学习笔记,如果有错误希望大家可以给我指正一下。这里是最近学习的SPI串行总线通讯协议的内容,由于所用单片机没有硬件SPI,所以需要用IO口来模拟SPI。
什么是SPI
SPI的定义可以去网上查,能查到很多,这里主要记一下比较有用的部分。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。这四根线分别是MISO、MOSI、SCLK、CS,具体的描述见下表:
MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
SCLK – Serial Clock,时钟信号,由主设备产生;
CS – Chip Select,从设备使能信号,由主设备控制。
可能各个地方的名字不同,比如MISO线有的也叫做MDI(master data input),MOSI也叫作MDO(master data output),SCLK也叫SCK(时钟)等等。
需要注意SPI是串行通讯协议,所以他的数据是一位一位进行传输的。
SPI的通讯模式
在介绍SPI通讯模式之前,有两个值需要了解,CPOL(时钟极性)和CPHA(时钟相位)。CPOL是用来配置SCK时钟信号什么时候是空闲状态,什么时候是有效状态;CPHA用来配置数据采样发生在第几个边沿。
CPOL=0表示当SCK=0时为空闲状态,SCK=1时为有效状态。
CPOL=1表示当SCK=1时为空闲状态,SCK=0时为有效状态。
CPHA=0表示数据采样是在第1个边沿,数据发送在第2个边沿。
CPHA=1表示数据采样是在第2个边沿,数据发送在第1个边沿。这里需要结合SPI的时序图来看
SPI共有四种通讯模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
Mode00:CPOL=0,CPHA=0
Mode01:CPOL=0,CPHA=1
Mode10:CPOL=1,CPHA=0
Mode11:CPOL=1,CPHA=1
通讯协议
从SPI的通讯模式可以知道,四种不同的模式对应的通讯协议不同,所以我把四种模式下的通讯协议都写出来了,需要用到那种模式就用哪个协议就好了,可能会有些问题,希望大家可以指正出来。
详细程序
SPI通讯默认是高位字节(MSB)优先传送,除非某些特定的设备明确说明是低位字节(LSB)优先传送。
一般常用的是Mode00和Mode01两种模式。
首先第一种,MODE00
/* CPHA=0 CPOL=0 写入数据 MSB优先*/
void soft_SPI_Write_MODE00(u8 write_data)
{
u8 i=0;
CS=0;
SCK=0; //SCK空闲时为低电平
for (i = 0; i < 8; i++) //写入数据,从高位开始
{
if (write_data & 0x80)
{
MOSI=1;
}
else
{
MOSI=0;
}
SCK=1; //时钟拉高 上升沿传输一位数据
write_data<<=1;
SCK=0; //时钟拉低等待下一位数据的传输
}
CS=1; //片选拉高结束通讯
}
/*读取数据*/
u8 soft_SPI_Read_Mode00(u8 write_data)
{
u8 read_data = 0;
u8 i=0;
CS=0;
SCK=0;
for (i = 0; i < 8; i++) //读取数据,从高位开始
{
read_data<<=1;
if (MISO)
read_data |= 0x01;
SCK=1; //时钟拉高 上升沿传输一位数据
SCK=0; //时钟拉低等待下一位数据的传输
}
CS=1;
return read_data;
}
第二种Mode01
/* CPHA=0 CPOL=1 写入数据 MSB优先*/
void soft_SPI_RW_MODE01(u8 write_data)
{
CS=0; //片选拉低有效
SCK=1; //SCK空闲时为高电平
for (i = 0; i < 8; i++) //写入数据,从高位开始
{
if (write_data & (0x80 >> i))
MOSI=1;
else
MOSI=0;
SCK=0; //时钟拉低 下降沿传输一位数据
SCK=1; //时钟拉高 等待下一位数据的传输
}
}
/*读取数据*/
u8 soft_SPI_Read_Mode01(u8 write_data)
{
u8 read_data = 0;
u8 i;
for (i = 0; i < 8; i++) //读取数据,从高位开始
{
read_data <<= 1;
if (MISO)
read_data |= 0x01;
SCK=0; //时钟拉低 下降沿传输一位数据
SCK=1; //时钟拉高 等待下一位数据的传输
}
CS=1; //一个字节传输结束 片选拉高 最后一位数据传输结束后时钟线已被拉高为空闲状态 不需要再次拉高
return read_data;
}
另外两个模式一般不太用到,这里就不贴代码了。
简写程序
上面几段程序是根据SPI时序图来写的比较详细的通讯协议,程序会显得比较长,冗杂。但其实SPI通讯是全双工的,主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。也就是说,当上升沿主机发送数据的时候,从机也发送了数据,再根据资料中写到的SPI是串行通信的方式,每次只发送一位数据,我们就可以在主机发送一位数据的同时也接收一位数据。以Mode00为例贴一段简写后的程序
u8 soft_SPI_RW_MODE00(u8 write_data)
{
uint8_t i;
uint8_t read_data=0;
CS=0;
SCK=0; //空闲状态为低电平
for(i=0;i<8;i++)
{
if(write_data&0X80) //读一位
MOSI=1;
else
MOSI=0;
read_data<<=1;
if(MISO)
read_data|=0x01; //写一位
SCK=1;
write_data<<=1;
SCK=0;
}
CS=1;
return read_data;
}
结语
以上就是本次软件SPI通讯协议的全部内容了,有什么错误希望大家可以多多指正,共同进步。