SPI(Serial Peripheral Interface--串行外设接口) 总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。 SPI是Freescale(原 Motorola)公司首先在其处理器上定义的。
SPI是一种高速、主从式、全双工、同步传输的通信总线,SPI总线在物理层体现为四根传输线:
- MOSI (Master Output Slaver Input) – 主器件数据输出,从器件数据输入
- MISO (Master Input Slaver Output) – 主器件数据输入,从器件数据输出
- SCLK – 时钟信号,由主器件产生
- NSS – 从器件使能信号,由主器件控制,有的IC会标注为CS(Chip select)
CS线用于控制片选信号,当一个SPI从设备的CS线识别到了预先规定的片选电平,则该设备被选中。显然可以通过CS线,完成“一主多从”的SPI网络架设,在进行“一主一从”的SPI通信时,SPI并不是必须的。
SPI总线传输数据时,由主机的SCLK线提供时钟脉冲,从机被动的接收时钟脉冲。主机在数据发送前,将数据写入数据寄存器,等待时钟脉冲移位输出,每个脉冲周期传输1位数据。 从机在主机的数据发送中,依靠主机的时钟,将从机的数据寄存器内容移位发送。所以要实现主从数据交换,在时钟信号前,主机 从机都必须先将数据写入数据寄存器,并且从机必须在主机前写入,然后由主机的SCLK时钟驱动发送。 不注意这个问题很容易造成SPI接收的数据错位。
这样的全双工、同步传输完全依赖于 主机控制的时钟线SCLK,而且SCLK上只有数据传输的时候才有时钟信号。主机向从机发送数据不会有问题,但是如果从机主动向主机发送数据呢?
从机要发送数据,必须要有SCLK的时钟,所以只能主机发送一个DUMMY(哑巴)字节,产生时钟,来实现和从机的数据交换。 从设备只能被动发送数据,无法主动发送数据。
本例实现 通过将STM32上的2个SPI接口对接,进行一个简单的数据交换。使用SPI1作为主设备,SPI2作为从设备,通过串口查看数据通信的情况。
实现结果如下:
直接操作寄存器
首先配置SPI主机的频率
SPI1设备属于高速设备,隶属APB2总线,最大时钟72Mhz;
SPI2属于低速设备,隶属APB1总线,最大36Mhz。
在控制寄存器中设置时钟分频值,设置时钟极性和相位等。程序中有注释,详见代码:
001 | #include <stm32f10x_lib.h> |
016 | u8 SPI1_Buffer_Tx[BufferSize] = |
018 | 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, |
019 | 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10, |
020 | 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, |
021 | 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 |
025 | u8 SPI2_Buffer_Tx[BufferSize] = |
027 | 0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58, |
028 | 0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60, |
029 | 0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68, |
030 | 0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70 |
033 | u8 SPI1_Buffer_Rx[BufferSize] = {0xFF}; |
034 | u8 SPI2_Buffer_Rx[BufferSize] = {0xFF}; |
043 | Rcc_Init(9); //系统时钟设置 |
045 | Usart1_Init(72,9600); |
047 | Nvic_Init(1,0,SPI1_IRQChannel,4); //设置抢占优先级为1,响应优先级为0,中断分组为4 |
048 | Nvic_Init(1,1,SPI2_IRQChannel,4); //设置抢占优先级为1,响应优先级为1,中断分组为4 |
056 | while (Tx_Counter < BufferSize) |
059 | Spi_Write(SPI2,SPI2_Buffer_Tx[Tx_Counter]); //必须先将从设备数据写入数据寄存器,等待时钟同步 |
061 | Spi_Write(SPI1,SPI1_Buffer_Tx[Tx_Counter]); //主设备将数据写入数据寄存器,触发同步时钟,让主从数据寄存器由此时钟发送 |
063 | SPI2_Buffer_Rx[Rx_Counter] = Spi_Read(SPI2); |
065 | SPI1_Buffer_Rx[Rx_Counter] = Spi_Read(SPI1); |
072 | printf ( "\r\n The SPI1 has sended data below : \r\n" ); |
076 | printf ( " %0.2d \r " ,SPI1_Buffer_Tx[k]); |
080 | printf ( "\r\n The SPI2 has received data below : \r\n" ); |
086 | printf ( " %0.2d \r " ,SPI2_Buffer_Rx[k]); |
092 | printf ( "\r\n The SPI2 has sended data below : \r\n" ); |
096 | printf ( " %0.2d \r " ,SPI2_Buffer_Tx[k]); |
100 | printf ( "\r\n The SPI1 has received data below : \r\n" ); |
106 | printf ( " %0.2d \r " ,SPI1_Buffer_Rx[k]); |
116 | RCC->APB2ENR |= 1<<2; //使能PORTA时钟 |
117 | RCC->APB2ENR |= 1<<3; //使能PORTB时钟; |
122 | GPIOA->CRL &= 0x000FFFFF; //PA 5,6,7 复用 |
123 | GPIOA->CRL |= 0xBBB00000; |
127 | GPIOB->CRH &= 0x000FFFFF; //PB 13,14,15 复用 |
128 | GPIOB->CRH |= 0xBBB00000; |
133 | GPIOA -> CRH &= 0xFFFFF00F; //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入 |
134 | GPIOA -> CRH |= 0x000008B0; |
User/stm32f10x_it.c
01 | #include "stm32f10x_it.h" |
13 | if (SPI1->SR & 1<<7) //SPI正忙于通信,或者发送缓冲非空 |
15 | printf ( "SPI1 is Busy" ); |
18 | if (SPI1->SR & 1<<6) // 出现溢出错误 |
20 | printf ( "SPI1 is Overrun" ); |
23 | if (SPI1->SR & 1<<5) //出现模式错误 |
25 | printf ( "SPI1 is Mode fault" ); |
28 | if (SPI1->SR & 1<<4) //收到的CRC值和SPI_RXCRCR寄存器中的值不匹配 |
30 | printf ( "SPI1 is CRC Error" ); |
Library/src/spi.c
04 | //SPI1主机模式,SPI2从机模式,8bit数据格式,时钟空闲保持为低,数据采样从第二个时钟边沿开始,波特率 fPCLK/32 |
05 | //先发送LSB(最低有效位),禁止硬件CRC校验 |
06 | void Spi_Init(SPI_TypeDef * SPIx) |
10 | RCC -> APB2ENR |= 1<<12; //SPI1时钟使能 |
11 | RCC -> APB2RSTR |= 1<<12; //复位SPI1寄存器 |
12 | RCC -> APB2RSTR &= ~(1<<12); //复位结束SPI1寄存器 |
14 | SPIx -> CR1 |= 1<<2; //主设备选择 0:配置为从设备 1:配置为主设备 |
15 | SPIx -> CR1 |= 1<<8; //SSI位,要保持主机模式,必须NSS 接到高电平信号 |
17 | } else if (SPIx == SPI2){ |
18 | RCC -> APB1ENR |= 1<<14; //SPI2时钟使能 |
19 | RCC -> APB1RSTR |= 1<<14; //复位SPI2寄存器 |
20 | RCC -> APB1RSTR &= ~(1<<14); //复位结束SPI2寄存器 |
21 | SPIx -> CR1 |= 0<<2; //主设备选择 0:配置为从设备 1:配置为主设备 |
22 | //SPIx -> CR1 |= 0<<8; //SSI位,要保持主机模式,必须NSS 接到高电平信号 |
26 | SPIx -> CR1 |= 0<<10; //设置全双工模式 0:全双工(发送和接收) 1:禁止输出(只接收模式) |
27 | SPIx -> CR1 |= 0<<11; //数据帧格式 0:使用8位数据帧格式进行发送/接收 1:使用16位数据帧格式进行发送/接收 |
28 | SPIx -> CR1 |= 1<<7; //帧格式 0:先发送MSB 1:先发送LSB |
30 | //配置NSS为GPIO输出口控制从设备片选 |
31 | SPIx -> CR1 |= 1<<9; //软件从设备管理 当此位(SSM)被置位时,NSS引脚上的电平由SSI位的值决定。 |
34 | SPIx -> CR1 |= 0<<1; //配置时钟极性 0: 空闲状态时,SCK保持低电平 1: 空闲状态时,SCK保持高电平 |
35 | SPIx -> CR1 |= 1<<0; //时钟相位 0: 数据采样从第一个时钟边沿开始 1: 数据采样从第二个时钟边沿开始 |
37 | SPIx -> CR1 |= 4<<3; //波特率控制[5:3] 000: fPCLK/2 001: fPCLK/4 010: fPCLK/8 011: fPCLK/16 100: fPCLK/32 |
38 | // 101: fPCLK/64 110: fPCLK/128 111: fPCLK/256 |
40 | //SPIx -> CR2 |= 1<<7; //发送缓冲区空中断使能 |
41 | //SPIx -> CR2 |= 1<<6; //接收缓冲区非空中断使能 |
42 | SPIx -> CR2 |= 1<<5; //错误中断使能 |
44 | SPIx -> CR1 |= 1<<6; //SPI设备使能 |
48 | void Spi_Write(SPI_TypeDef * SPIx,u8 data) |
50 | //while((SPIx->SR&1<<1) == 0); //等待发送缓冲为空置位 |
54 | Spi_Delay(3); //必须稍作延时 |
58 | u8 Spi_Read(SPI_TypeDef * SPIx) |
61 | //while((SPIx->SR&1<<0) == 1); //等待接收缓冲非空置位 |
Library/inc/spi.h
1 | #include <stm32f10x_lib.h> |
3 | void Spi_Init(SPI_TypeDef * SPIx); |
4 | void Spi_Write(SPI_TypeDef * SPIx,u8 data); |
5 | u8 Spi_Read(SPI_TypeDef * SPIx); |
操作库函数
main.c
001 | #include "stm32f10x.h" |
007 | void RCC_Configuration( void ); |
008 | void GPIO_Configuration( void ); |
009 | void USART_Configuration( void ); |
010 | void SPI_Configuration( void ); |
014 | #define delay() for(i=0;i<200;i++) |
016 | SPI_InitTypeDef SPI_InitStructure; |
018 | u8 SPI1_Buffer_Tx[BufferSize] = |
020 | 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, |
021 | 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10, |
022 | 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, |
023 | 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 |
026 | u8 SPI2_Buffer_Tx[BufferSize] = |
028 | 0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58, |
029 | 0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60, |
030 | 0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68, |
031 | 0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70 |
034 | u8 SPI1_Buffer_Rx[BufferSize+1] = {0}; |
035 | u8 SPI2_Buffer_Rx[BufferSize] = {0}; |
044 | GPIO_Configuration(); |
045 | USART_Configuration(); |
048 | while (Tx_Counter < BufferSize) |
050 | SPI_I2S_SendData(SPI2,SPI2_Buffer_Tx[Tx_Counter]); //必须从机先发送数据 |
051 | //while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET); //如果spi2 还有发送缓存则等待发送完成 |
053 | SPI_I2S_SendData(SPI1,SPI1_Buffer_Tx[Tx_Counter]); |
055 | while (SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //没有接收缓存则等待 |
056 | SPI2_Buffer_Rx[Rx_Counter] = SPI_I2S_ReceiveData(SPI2); |
059 | while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) ==RESET); |
061 | SPI1_Buffer_Rx[Rx_Counter] = SPI_I2S_ReceiveData(SPI1); |
067 | printf ( "\r\n The SPI1 has sended data below : \r\n" ); |
071 | printf ( " %0.2d \r " ,SPI1_Buffer_Tx[k]); |
075 | printf ( "\r\n The SPI2 has received data below : \r\n" ); |
081 | printf ( " %0.2d \r " ,SPI2_Buffer_Rx[k]); |
087 | printf ( "\r\n The SPI2 has sended data below : \r\n" ); |
091 | printf ( " %0.2d \r " ,SPI2_Buffer_Tx[k]); |
095 | printf ( "\r\n The SPI1 has received data below : \r\n" ); |
101 | printf ( " %0.2d \r " ,SPI1_Buffer_Rx[k]); |
108 | void SPI_Configuration( void ) |
111 | SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; |
112 | SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; |
113 | SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; |
114 | SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; |
115 | SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; |
116 | SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; |
117 | SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB; |
118 | SPI_InitStructure.SPI_CRCPolynomial = 7; |
120 | SPI_InitStructure.SPI_Mode = SPI_Mode_Master; |
121 | SPI_Init(SPI1,&SPI_InitStructure); |
123 | SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; |
124 | SPI_Init(SPI2,&SPI_InitStructure); |
126 | SPI_Cmd(SPI1,ENABLE); |
127 | SPI_Cmd(SPI2,ENABLE); |
131 | void GPIO_Configuration( void ) |
133 | GPIO_InitTypeDef GPIO_InitStructure; |
135 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
137 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7; |
138 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
139 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
141 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; |
142 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
143 | GPIO_Init(GPIOB , &GPIO_InitStructure); |
146 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; |
147 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
148 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
150 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; |
151 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; |
152 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
155 | void RCC_Configuration( void ) |
157 | /* 定义枚举类型变量 HSEStartUpStatus */ |
158 | ErrorStatus HSEStartUpStatus; |
163 | RCC_HSEConfig(RCC_HSE_ON); |
165 | HSEStartUpStatus = RCC_WaitForHSEStartUp(); |
166 | /* 判断HSE起是否振成功,是则进入if()内部 */ |
167 | if (HSEStartUpStatus == SUCCESS) |
169 | /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ |
170 | RCC_HCLKConfig(RCC_SYSCLK_Div1); |
171 | /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ |
172 | RCC_PCLK2Config(RCC_HCLK_Div1); |
173 | /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ |
174 | RCC_PCLK1Config(RCC_HCLK_Div2); |
176 | FLASH_SetLatency(FLASH_Latency_2); |
178 | FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); |
179 | /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ |
180 | RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); |
184 | while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); |
185 | /* 选择SYSCLK时钟源为PLL */ |
186 | RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); |
187 | /* 等待PLL成为SYSCLK时钟源 */ |
188 | while (RCC_GetSYSCLKSource() != 0x08); |
190 | /* 打开APB2总线上的GPIOA时钟*/ |
191 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_USART1|RCC_APB2Periph_SPI1, ENABLE); |
193 | //RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); |
195 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE); |
196 | //RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP|RCC_APB1Periph_WWDG, ENABLE); |
201 | void USART_Configuration( void ) |
203 | USART_InitTypeDef USART_InitStructure; |
204 | USART_ClockInitTypeDef USART_ClockInitStructure; |
206 | USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; |
207 | USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; |
208 | USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; |
209 | USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; |
210 | USART_ClockInit(USART1 , &USART_ClockInitStructure); |
212 | USART_InitStructure.USART_BaudRate = 9600; |
213 | USART_InitStructure.USART_WordLength = USART_WordLength_8b; |
214 | USART_InitStructure.USART_StopBits = USART_StopBits_1; |
215 | USART_InitStructure.USART_Parity = USART_Parity_No; |
216 | USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; |
217 | USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; |
218 | USART_Init(USART1,&USART_InitStructure); |
220 | USART_Cmd(USART1,ENABLE); |
225 | int fputc ( int ch, FILE *f) |
227 | USART_SendData(USART1,(u8) ch); |
228 | while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); |
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。