SPI是高速、全双工、同步的通信总线。
SPI应用于存储芯片、AD转换器及LCD中。
同步、异步区别:是否有时钟线,例如SPI、I2C是同步通信,需要用到时钟线,串口是异步通信,没有时钟线。
SPI通信需要四根线:MISO MOSI SCLK(时钟) CS(片选)
MISO和MOSI有没有容易搞混的小伙伴,直接看英文记忆。
例如:MISO 英文全称为 Master input slave output,缩写MISO,主机输入从机输出。
以之前开源项目里的触摸屏工程为例,那个工程触摸屏驱动是厂家写好的,把程序上传单片机,连接屏幕,看一下波形。
在LA2016波形仪器上选择四个通道,依次为MISO MOSI SCLK(时钟) CS(片选)。
连接四个通道至单片机的对应引脚,点击软件LA2016等待触发,点击屏幕触发,波形如下(没有设置):
调试了好久,都没有得到MISO MOSI 两个数据线上的波形,原因:
没有设置LA2016软件关于CPOL和CPHA!!!!!
这里CPOL和CPHA需要找到程序,看程序里是如何编写的,在LA2016波形显示软件上进行对应设置。
找到程序关于SPI初始化部分的函数,如下:
void SPI2_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; //配置SPI2管脚 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //SPI2配置选项 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2 ,ENABLE); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI2, &SPI_InitStructure); //使能SPI2 SPI_Cmd(SPI2, ENABLE); }
****时钟极性******
CPOL = 0:时钟空闲IDLE为低电平 0;
CPOL= 1:时钟空闲IDLE为高电平1;
****时钟相位******
CPHA= 0:在时钟信号SCK的第一个跳变沿采样;
CPHA= 1:在时钟信号SCK的第二个跳变沿采样;
看程序得知,CPOL =0;CPHA=1,软件上对应配置如下:
连接调试仪器与单片机对应引脚,上电,LA2016软件点击等待触发,触摸屏幕产生SPI通信,正确波形如下:
注意:片选信号下降沿触发有效!
选取一小块波形,我们来看下通讯流程:
流程:
1、主设备发起信号,将CS片选信号拉低。
2、主设备发送时钟信号,告诉从设备进行读数据写数据下降沿有效(图中的波形,有的是上升沿)。
3、主设备将要发送的数据发送到数据缓存区,缓存区通过移位寄存器、串行移位寄存器通过MOSI信号线一位一位发送出去,同时MISO将接收到的数据通过移位寄存器一位一位的移到接收缓存区。
4、从机通过自己的通过MISO线将内容发送出去,MOSI接收。
找到正点原子HAL库SPI例子的代码,直接在上面改main函数。
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/usart/usart.h" #include "./SYSTEM/delay/delay.h" #include "./USMART/usmart.h" #include "./BSP/LED/led.h" #include "./BSP/LCD/lcd.h" #include "./BSP/KEY/key.h" #include "./BSP/NORFLASH/norflash.h" #include "./BSP/SPI/spi.h" int main(void) { uint8_t key; uint16_t i = 0; uint8_t rec_data = 0; HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ usmart_dev.init(72); /* 初始化USMART */ led_init(); /* 初始化LED */ lcd_init(); /* 初始化LCD */ key_init(); /* 初始化按键 */ norflash_init(); while (1) { key = key_scan(0); if (key == KEY1_PRES) /* KEY1按下,写入 */ { NORFLASH_CS(0); spi2_read_write_byte(0x06); NORFLASH_CS(1); } } }
注意看SPI初始化!!!!
看程序配置CPOL和CPHA,连接LA2016时配置软件。
测试下我们在main函数中写的代码,把程序上传到单片机,并连接LA2016和单片机引脚,点击触发没看波形如下:
最喜欢正点原子写的这个函数,可以同时读写:
uint8_t spi2_read_write_byte(uint8_t data) { uint8_t rec_data = 0; HAL_SPI_TransmitReceive(&g_spi2_handler, &data, &rec_data, 1, 1000); return rec_data; }
测试一下读数据,用一根杜邦线把MISO和MOSI连接起来。
看波形有数据收到!