- 小结关于spi的内容,附TM4C & stm32配置代码
- 这里直接照搬正点原子的介绍词:SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议
- spi是一种串行设备,需要时钟信号控制着数据传输。设备间有主机和从机之分,从机的时钟信号只能来自主机,因此主机必须存在。
- spi有四根连接线线“:
MOSI
是从机到主机的信号传输;MOSI
是主机到从机的信号传输;SCLK
是时钟线,控制数据传递过程;CS
是片选线,主机通过它选定通讯对象,这允许一主多从连接
- spi协议允许信号一位一位传输
- spi的数据输入和输出线独立,所以允许同时完成数据的输入和输出。事实上这是一个数据交换协议,主从双方各有一个移位寄存器,主机向其移位寄存器传送一个数据来启动一次传输。主从机移位寄存器中的一个数据通过
MOSI
和MISO
交换。因此如果想只读或只写一个数据,都需要传输无效数据,无效数据在收到后应被忽略
文章图片
- 【嵌入式|通讯协议(2)—— SPI】spi的缺点:没有指定的流控制,没有应答机制确认是否接收到数据
- spi有四根连接线
SDO
/MOSI
:主机输出,从机输入SDI
/MISO
:主机输入,从机输出SCK
/SCLK
:时钟信号(由主机控制)CS
:从设备使能信号,即片选信号(由主机控制)
- 一主一从连接
文章图片
- 一主多从连接
文章图片
- SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置。可配置的两个位为:
位 | 作用 | 描述 |
---|---|---|
CPOL | 时钟极性选择 | 为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平,对传输协议没有重大的影响 |
CPHA | 时钟相位选择 | 为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样 |
- 就像前面那个动图显示的,SPI是一个环形总线结构,无论哪种工作方式,都可以看作是在sck的控制下,两个双向移位寄存器进行数据交换。
- 根据配置,当sck的某个边沿到来时,主从双方移位寄存器中高位的值被采样到线上,寄存器移动一位,最低位暂时悬空,当下一个紧接着的边沿边沿到来时,线上的数据传送到双方移位寄存器低位,从而完成数据交换
- CPHA=0,CPOL=0;总线空闲为低电平,在SCK第一个跳变沿采样
- 时序图:
文章图片
- CPHA=0,CPOL=1;总线空闲为高电平,在SCK第一个跳变沿采样
- 时序图:
文章图片
- CPHA=1,CPOL=0;总线空闲为低电平,在SCK第二个跳变沿采样
- 时序图:
文章图片
- CPHA=1,CPOL=1;总线空闲为高电平,在SCK第二个跳变沿采样
- 时序图:
文章图片
- 在主设备这边配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,因为主设备这边的时钟极性和相位都是以从设备为基准的。
- 注意主SDO连接从SDI,主SDO的极性和从SDI相反,和从SDO相同。例如:从设备在时钟上升沿接收数据,则主设备应在下降沿输出数据
- 来自正点原子探索者例程
- stm32作为主机,配置流程为:
- 配置相关引脚复用功能,使能spi时钟
- 初始化spi,配置spi工作模式
- 使能spi
SPI_Cmd()
- 利用spi传输数据
SPI_I2S_SendData()
和SPI_I2S_ReceiveData()
- 查看spi状态
SPI_I2S_GetFlagStatus()
- 以下为正点原子的代码
//以下是SPI模块的初始化代码,配置成主机模式
//SPI口初始化
//这里针是对SPI1的初始化
void SPI1_Init(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
SPI_InitTypeDefSPI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
//使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
//使能SPI1时钟
//GPIOFB3,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信号(CS)由硬件(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最近接收的数据 }
(2)TM4C123版本
- 来自匿名拓空者飞控源码
- TM4C的封装度比stm32高一些,不过spi配置使用流程基本一样的
void Drv_Spi0Init(void)
{
ROM_SysCtlPeripheralEnable( SYSCTL_PERIPH_SSI0 );
ROM_SysCtlPeripheralEnable(SPI0_SYSCTL);
/*配置IO口*/
ROM_GPIOPinTypeSSI(SPI0_PROT,SPI0_CLK_PIN|SPI0_RX_PIN|SPI0_TX_PIN);
ROM_GPIOPinConfigure(SPI0_CLK);
ROM_GPIOPinConfigure(SPI0_RX);
ROM_GPIOPinConfigure(SPI0_TX);
/* SSI配置 模式3(Polarity = 1 Phase = 1) 主设备模式 速率1MHz 数据长度8位*/
ROM_SSIConfigSetExpClk(SPI0_BASE, ROM_SysCtlClockGet(), SSI_FRF_MOTO_MODE_3,SSI_MODE_MASTER, 10000000,8);
/*开启SSI0*/
ROM_SSIEnable(SPI0_BASE);
}
/* SPI读写函数 */
uint8_t Drv_Spi0SingleWirteAndRead(uint8_t SendData)
{
uint32_t ui_TempData;
uint8_t uc_ReceiveData;
/* 向SSI FIFO写入数据 */
ROM_SSIDataPut(SPI0_BASE, SendData);
/* 等待SSI不忙 */
while(ROM_SSIBusy(SPI0_BASE));
/* 从FIFO读取数据 */
ROM_SSIDataGet(SPI0_BASE, &ui_TempData);
/* 截取数据的低八位 */
uc_ReceiveData = https://www.it610.com/article/ui_TempData & 0xff;
return uc_ReceiveData;
}
void Drv_Spi0Transmit(uint8_t *ucp_Data, uint16_t us_Size)
{
uint16_t i = 0;
/* 连续写入数据 */
for(i = 0;
i < us_Size;
i++)
{
Drv_Spi0SingleWirteAndRead(ucp_Data[i]);
}
}
void Drv_Spi0Receive(uint8_t *ucp_Data, uint16_t us_Size)
{
uint16_t i = 0;
/* 连续读取数据 */
for(i = 0;
i < us_Size;
i++)
{
ucp_Data[i] = Drv_Spi0SingleWirteAndRead(0xFF);
}
}
推荐阅读
- linux|I.MX6UL核心模块使用连载-USB接口测试 (六)
- 嵌入式周报|《安富莱嵌入式周报》第268期(2022.05.30--2022.06.05)
- 树莓派|树莓派4B的串口使用,并通过串口与其他设备经过通讯协议发送、接收数据。
- 英国新财政大臣(软银收购ARM 海外投资热度不减)
- 人工智能|安卓之父 Andy Rubin(要把互联网现实化)
- 【3】7系列FPGA结构|从底层结构开始学习FPGA----MMCM与PLL
- arduino|单片机编程简单吗(arduino为什么不适合大学生?)
- 好嗨哟!OneOS 图形组件显示自己喜欢的图片
- #|i.MX6ULL终结者Linux 电容触摸屏实验使用Linux内核自带的ft5426驱动