SPI的组成
SPI系统中四根线为串行时钟引脚SCK、从机选择引脚SS(Slave Select)、主出从入引脚MOSI、主入从出引脚MISO,如果从机不需要向主机发送消息的情况下,可以不需要主入从出引脚MISO。
1.串行时钟引脚SCK
SCK用于控制主机和从机之间的数据传输。时钟信号由主机提供,从主机的SCK引脚输出给从机的SCK引脚,由主机控制传输的速度。一次传输中,从主机SCK引脚中输出自动产生的8个时钟周期信号,SCK信号的一个沿跳变进行一位数据移位传输。
2.从机选择引脚SS
在主机方式下工作,引脚SS为高电平;在从机方式下工作,引脚SS设置为低,则表示主机选中了该从机,引脚SS设置为高,表示主机未选中该从机。
在一个主MCU带多个从属MCU的系统中,主MCU的引脚SS直接接高电平,从机MCU的引脚SS接主机的IO口,由主机输出低/高电平表明主机是/否选中该从机。
3.主出从入引脚MOSI
主机发出启动信号开始,主机将要传送的数据装入8位移位寄存器,同时产生8个时钟信号依次从SCK引脚送出,在SCK信号的控制下,主机中的8位移位寄存器中的数据依次从MOSI引脚送出到从机的MOSI引脚再送到从机的8位移位寄存器。
4.主入从出引脚MISO
主机发出启动信号开始,从机收到启动信号后,从机将要传送的数据装入8位寄存器,根据主机发过来的时钟信号,在SCK信号的控制下,从机中的8位移位寄存器中的数据依次从MISO引脚送出到主机的MISO引脚再送到主机的8位寄存器。
SPI的时序
由于SPI的数据传输是在时钟信号SCK(同步信号)的控制下完成的,就会产生不取数的时候空闲电平的选择以及什么时候去取数的问题。空闲电平为低,那么SPI的时钟极性CPOL就为0,空闲电平为高,那么SPI的时钟极性CPOL就为1。当数据采样是在第一个边沿,时钟相位则为0,数据采样在第二个边沿时,时钟相位为1。根据这两个性质的选择,就会有4种可能情况。
1.CPOL = 0,CPHA = 0
平时处于低电平,在数据上线半个周期后,处于稳定状态时,接收方此时开始采集数据,再半个周期后,时钟信号又达低电平,此时可以更换数据,在半个小时后的上升沿时又采集一次数据,以此类推。这种模式决定接收方会在时钟的上升沿时取数。
2.CPOL = 1,CPHA = 0
与第一种的区别是,该情况下平时处于高电平,同样接收方会在数据上线半个周期后开始取数。这种模式决定接收方会在时钟的下降沿时取数。
3.CPOL = 0,CPHA = 1
与第一种的区别时,该情况下接收方会在第二个跳变沿开始取数,那么此时数据不需要提前上线,由于平时是低电平,第一次跳变沿为上升沿,第二次跳变沿为下降沿。所以这种模式决定接收方会在时钟的下降沿时取数。
4.CPOL = 1, CPHA = 1
与第一种模式相反,平时处于高电平,接收方会在第二个跳变沿开始取数,数据不提前上线,第二个跳变沿为上升沿。这种模式就会决定接收方会在时钟的上升沿时取数。
SPI构件设计
SPI构件主要有初始化SPI_init,发送一字节数据SPI_send1,发送多个字节数据SPI_sendN,接收一字节数据SPI_receive1,接收多个字节数据SPI_receiveN,打开SPI接收中断SPI_enable_re_int,关闭SPI接收中断SPI_disable_re_int。
在从机不回发的情况下,主机主要使用到的函数有SPI_init,SPI_sendN(N*SPI_send1)。
从机主要使用到的函数有SPI_init,SPI_receiveN(N*SPI_receive1),SPI_enable_re_int,SPI_disable_re_int。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int main() { SPI_init(SPI_0,1,6000,0,0); SPI_init(SPI_1,0,6000,0,0); SPI_enable_re_int(SPI_1); for(;;) { SPI_send1(SPI_0,TransferTemp); TransferTemp++; } }
void SPI_Handler() { uint_8 redata; redata = SPI_receive1(SPI_1); }
|
各函数主要功能设计如下:
SPI_init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| void SPI_init(uint8_t No, uint8_t MSTR, uint16_t BaudRate, uint8_t CPOL, uint8_t CPHA) { uint8_t BaudRate_High; uint8_t BaudRate_Low; uint8_t BaudRate_Mode; if(No < 0 || No > 1) No = 0; if(No == 0) { BSET(SIM_SCGC4_SPI0_SHIFT,SIM_SCGC4); #if(SPI_0_GROUP == 1) PORTD_PCR0 = (0|PORT_PCR_MUX(0x02)); PORTD_PCR1 = (0|PORT_PCR_MUX(0x02)); PORTD_PCR2 = (0|PORT_PCR_MUX(0x02)); PORTD_PCR3 = (0|PORT_PCR_MUX(0x02)); #endif #if(SPI_0_GROUP == 2) PORTA_PCR14 = (0|PORT_PCR_MUX(0x02)); PORTA_PCR15 = (0|PORT_PCR_MUX(0x02)); PORTA_PCR16 = (0|PORT_PCR_MUX(0x02)); PORTA_PCR17 = (0|PORT_PCR_MUX(0x02)); #endif #if(SPI_0_GROUP == 3) PORTC_PCR4 = (0|PORT_PCR_MUX(0x02)); PORTC_PCR5 = (0|PORT_PCR_MUX(0x02)); PORTC_PCR6 = (0|PORT_PCR_MUX(0x02)); PORTC_PCR7 = (0|PORT_PCR_MUX(0x02)); #endif SPI0_C1 = 0x00; BSET(SPI_C1_SPE_SHIFT, SPI0_C1); (MSTR == 1)?BSET(SPI_C1_MSTR_SHIFT, SPI0_C1):BSET(SPI_C1_SPIE_SHIFT, SPI0_C1); (CPOL == 0)?BCLR(SPI_C1_CPOL_SHIFT, SPI0_C1):BSET(SPI_C1_CPOL_SHIFT, SPI0_C1); (CPHA == 0)?BCLR(SPI_C1_CPHA_SHIFT, SPI0_C1):BSET(SPI_C1_CPHA_SHIFT, SPI0_C1); BSET(SPI_C1_SSOE_SHIFT, SPI0_C1); SPI0_C2 = 0x00; if(MSTR == 1) BSET(SPI_C2_MODFEN_SHIFT, SPI0_C2); SPI_BR = 0x00U; BaudRate_High = 0; BaudRate_Low = 0; BaudRate_Mode = 12000/BaudRate; while(BaudRate_Mode%2 == 0) { BaudRate_Mode = BaudRate_Mode/2; BaudRate_Low++; } BaudRate_High = --BaudRate_Mode; SPI0_BR = BaudRate_High<<4; SPI0_BR |= BaudRate_Low; } else { } }
|
SPI_send1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint_8 SPI_send1(uint8_t No, uint8_t data) { uint32_t i; SPI_MemMapPtr baseadd = SPI_baseadd(No); while(!(SPI_S_REG(baseadd)&SPI_S_SPTEF_MASK)); SPI_D_REG(baseadd) = data; for(i = 0; i < 0xFFF0; i++) { if((SPI_S_REG(baseadd)&SPI_S_SPTEF_MASK)) { return(1); } } return(0); }
|
SPI_sendN
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void SPI_sendN(uint8_t No, uint8_t n, uint8_t data[]) { uint32_t k; SPI_MemMapPtr baseadd = SPI_baseadd(No); for(k = 0; k < n; k++) { while(!(SPI_S_REG(baseadd)&SPI_S_SPTEF_MASK)); SPI_D_REG(baseadd) = data[k]; SPI_S_REG(baseadd) != SPI_S_SPTEF_MASK; } }
|
SPI_receive1
1 2 3 4 5 6 7 8 9
| uint8_t SPI_receive1(uint8_t No) { SPI_MemMapPtr baseadd = SPI_baseadd(No); while(!(SPI_S_REG(baseadd) & SPI_S_SPRF_MASK)); return SPI_D_REG(baseadd); }
|
SPI_receiveN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| uint8_t SPI_receive1(uint8_t No, uint8_t n, uint8_t data[]) { SPI_MemMapPtr baseadd = SPI_baseadd(No); uint32_t m = 0; while(m < n) { if(SPI_S_REG(baseadd) & SPI_S_SPRF_MASK) { data[m] = SPI_D_REG(baseadd); m++; } } return (1); }
|
SPI寄存器
SPI需要用到6个8位寄存器,其中含有两个控制寄存器,一个波特率寄存器,一个状态寄存器,一个数据寄存器,一个匹配寄存器。
1.两个控制寄存器SPIx_C1,SPIx_C2中,SPIx_C1主要负责SPI使能控制、中断使能和配置选项(主从模式选择、时钟极性位选择、时钟相位位选择、移位器方向选择)。SPIx_C2。。。。。。
2.波特率寄存器SPIx_BR。该寄存器用于位一个SPI主机设定标器和位速率分频因子。该寄存器的8位分别为D7(保留位),D6-D4(SPPR-SPI波特率预分频系数),通过这三位位段可以得到SPI波特率预分频系数。SPPR[2:0]所表示的十进制数为x(0-7),则预分频系数SPPR=x+1(1-8)。输入为总线速率时钟(BUSCLK),输出驱动SPI波特率系数的输入。以及D3-D0(SPR-SPI波特率系数),通过这四位位段可以得到波特率系数。SPR[3:0]所表示的十进制数为y(0-7),则波特率系数SPR=2^(y+1)(2-256)。其输入来自SPI波特率预分频器,输出是主模式的SPI波特率时钟。
SPI主模式波特率 = f_BUSCLK /(SPPR×SPR),其中f_BUSCLK为总线时钟,SPPR和SPR可分别由SPI波特率预分频系数和SPI波特率系数得出。
3.状态寄存器SPIx_S。该寄存器主要用于SPI发送和接收缓存区满空的判断和设置,其中4个只读状态位(D7-D4)。D7位SPRF(SPI Receive Flag)为SPI接收缓冲器满标志。在一次SPI传输完成时,SPRF被置位,表明接收到的数据可以从SPI数据寄存器(SPI_D)读取。当SPRF被置位时,通过读SPRF,然后读取SPI数据寄存器,可将其清除。D6位SPMF(SPI Match Flag)为SPI匹配标志。表明接收数据缓冲区的值是否与匹配寄存器中的值相同,硬件值与软件值进行比对。D5位SPTEF(SPI Transmit Empty Flag)位SPI发送缓冲器空标志。表明发送缓冲区是否为空。通过读取SPIx_S,可将其清除。并且在向SPIx_D写数据之前,需要清SPTEF位,否则写入无效。在DMA方式下,产生DMA请求后,SPTEF自动清除0。D4位MODF为主模式故障标志。。。。。。
4.数据寄存器SPIx_D。读取该寄存器将返回从接收数据缓冲器中读取的数据。写该寄存器将会把数据写入发送数据缓冲器。当SPI被设置为主模式时,写入数据到传输数据缓冲器发起一次SPI传输。除非SPI发送缓冲器空标志(SPTEF)被置位,数据不应被写入到发送数据缓冲器,表明发送缓冲器内有空间来排列一个新的发送字节。在SPRF置位后且另以传输完成之前的任意时刻,数据都可以从SPID中被读取。在一个新的传输完成前,从接收数据缓冲器读出数据失败,将会引起一个接收丢包状态,并且新传输的数据也会丢失。
5.匹配寄存器SPIx_M。此寄存器存储硬件比较值,用来与SPI接收数据缓冲区中的值进行比较。当SPI接收数据缓冲区收到的值等于此硬件比较值时,SPI匹配标志(SPMF)置位。
GPIO模拟SPI
利用GPIO模拟SPI主要通过对使用四个GPIO引脚分别对应SS引脚、SCLK引脚、MOSI引脚、MISO引脚,利用延时函数模拟时钟周期,进行对SPI通信的模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #define SS GPIO0 #define SCLK GPIO1 #define MOSI GPIO2 #define MISO GPIO3 #define OUTPUT 1 #define INPUT 0
void SPI_init() { Set_GPIO_Direction(SS, OUTPUT); Set_GPIO_Direction(SCLK, OUTPUT); Set_GPIO_Direction(MOSI, OUTPUT); Set_GPIO_Direction(MISO, INPUT); Set_GPIO_Value(SCLK, 0); Set_GPIO_Value(MOSI, 0); }
void SS_Enable(int enable) { if(enable) Set_GPIO_Value(SS, 0); else Set_GPIO_Value(SS, 1); }
void SPI_Write1(uint8_t ch) { for(int i = 7; i >= 0; i--) { Set_GPIO_Value(SCLK, 0); Set_GPIO_Value(MOSI, ch&(1<<i)); Delay(); Set_GPIO_Value(SCLK, 1); Delay(); } }
uint8_t SPI_Read1() { uint8_t ch; for(int i = 0; i < 8; i++) { Set_GPIO_Value(SCLK, 0); Delay(); Set_GPIO_Value(SCLK, 1); ch = (ch<<1) | Get_GPIO_Value(MISO); Delay(); } return ch; }
void SPI_Write(uint8_t* buf, int len) { SS_Enable(1); Delay(); for(int i = 0; i < len; i++) SPI_Write1(buf[i]); Delay(); SS_Enable(0); }
void SPI_Read(uint8_t* buf, int len) { SS_Enable(1); Delay(); for(int i = 0; i < len; i++) buf[i] = SPI_Read1(); Delay(); SS_Enable(0); }
|