简介:
此程序是根据标准SPI协议规范使用模式0编写的一份模拟SPI全双工数据收发例程,经过测试,一个字节收发时长可压缩至最低115us左右,约9091字节每秒=73Kbps的通讯速率,注释中尽可能解释了每一步的含义,后续有想法应该会对其进行优化。
注:笔者开发经验较少,在编程上或许复杂了一些。
一、SPI主机部分:
#define SPI_CS GPIO_Pin_0 #define SPI_SCK GPIO_Pin_1 #define SPI_MOSI GPIO_Pin_2 #define SPI_MISO GPIO_Pin_3 /*SPI初始化*/ void SPI_Init() { GPIOB_ModeCfg(SPI_CS | SPI_SCK | SPI_MOSI, GPIO_ModeOut_PP_5mA); GPIOB_SetBits(SPI_CS | SPI_MOSI); GPIOB_ResetBits(SPI_SCK); GPIOB_ModeCfg(SPI_MISO, GPIO_ModeIN_PU); }
/*SPI数据发送并返回读取的数据*/ uint8_t SPI_SendByte(uint8_t Data) { uint8_t Byte=0x00; GPIOB_ResetBits(SPI_CS);//SPI片选使能 DelayUs(5);//通过从机端识别到片选线边沿中断后从中断出来的时间约4.14us,因此这里必须延时至少4.14us,否则从机中断还没有出来,会让从机丢失下一次进入中断的判断时机 for (uint8_t i = 0; i < 8; i ++)//循环8次,依次交换每一位数据 { if(Data & (0x80 >> i)) { GPIOB_SetBits(SPI_MOSI); } else { GPIOB_ResetBits(SPI_MOSI); } GPIOB_SetBits(SPI_SCK);//SCK上升沿发送MOSI数据,主机提前把数据放在MOSI线上,拉高时钟线让从机来读取 /*——————————————————————————————————————————————————————————————————————*/ if(GPIOB_ReadPortPin(SPI_MISO))//SCK时钟线产生上升沿中断后,从机会将对应bit数据放在MISO线上,此时主机应立即采集数据,从逻辑分析仪中查看,在拉高时钟线后,从机会在1us后收到中断并把下一位bit数据放在MISO线上,主机应在1us之内将数据读走,否则读到的数据就是从机的下一位数据了。 { Byte |= (0x80>>i) ; } DelayUs(4);//这里延时同理,从机收到SCK信号中断后处理需要时间,发送数据中断占用3.86us,读取数据中断占用2.92us,因此取最大值,至少延时3.86us,取4us GPIOB_ResetBits(SPI_SCK);//SCK下降沿接收MISO数据,拉低时钟线,让从机把数据放到MISO上,主机准备读取 DelayUs(5);//延时同理,防止从机中断程序没出来就发起下一次中断 } GPIOB_SetBits(SPI_CS);//SPI片选失能 return Byte;//数据读取完毕后将数据输出 }
int main() { SetSysClock(CLK_SOURCE_PLL_60MHz); /* 配置串口调试 */ DebugInit(); PRINT("Start @ChipID=%02X\n", R8_CHIP_ID); SPI_Init(); while(1) { recdata=SPI_SendByte(0x85); PRINT("%x\n",recdata); DelayMs(1);//连续两个字节读写间隔不得低于5us,否则会造成数据错乱 } }
二、SPI从机部分:
#define SPI_CS GPIO_Pin_0 #define SPI_SCK GPIO_Pin_1 #define SPI_MOSI GPIO_Pin_2 #define SPI_MISO GPIO_Pin_3 //SPI模式0: //空闲时片选线为高电平,拉低片选线选中从机 //空闲时时钟线为低电平,第一个跳变沿(上升沿)写数据:从机提前将数据放在MISO上,第二个跳变沿(下降沿)读数据 void SPI_SLAVE_Init() { GPIOB_ModeCfg(SPI_CS | SPI_MOSI, GPIO_ModeIN_PU); GPIOB_ITModeCfg(SPI_CS, GPIO_ITMode_FallEdge); PFIC_EnableIRQ(GPIO_B_IRQn);//CS片选线使用GPIOB中断 GPIOA_ModeCfg(SPI_SCK, GPIO_ModeIN_PD); GPIOA_ITModeCfg(SPI_SCK, GPIO_ITMode_RiseEdge); PFIC_EnableIRQ(GPIO_A_IRQn);//SCK时钟线使用GPIOA中断 GPIOB_ModeCfg(SPI_MISO, GPIO_ModeOut_PP_5mA); GPIOB_SetBits(SPI_MISO); }
uint8_t sendcount=0;//1个字节每位发送计数,计数到8清0 uint8_t recvcount=0;//接收1个字节计数,计数到8清0 uint8_t Byte_Read=0x00;//从机接收数据的变量 uint8_t Data=0x69;//从机待发送数据
BOOL SPI_Select=0; __HIGH_CODE __INTERRUPT void GPIOB_IRQHandler(void) { GPIOA_InverseBits(GPIO_Pin_0);//翻转IO测试使用,逻辑分析仪测出中断运行时长 if(GPIOB_ReadITFlagBit(SPI_CS))//检测到片选线下降沿中断代表从机被选中 { if(!SPI_Select)//SPI_Select初值为0,第一次则进入这里,从机放入最高位数据,等待时钟线第一个跳变沿到来被主机采集 { if(Data&(0x80 >> sendcount))//放置字节最高位 { GPIOB_SetBits(SPI_MISO); } else { GPIOB_ResetBits(SPI_MISO); } sendcount++;//sendcount++,下一次在时钟跳变沿中断中准备放最高位的下一位 SPI_Select=1;//片选标志取反,下一次if判断会进入上升沿中断 GPIOB_ITModeCfg(SPI_CS, GPIO_ITMode_RiseEdge);//将下降沿中断改为上升沿中断,等待片选线拉高结束此次数据传输 } else { sendcount=0;//一个字节发送完成,sendcount置0 SPI_Select=0;//片选标志取反,下一次if判断会进入下降沿中断 GPIOB_ITModeCfg(SPI_CS, GPIO_ITMode_FallEdge);//将上升沿中断改为下降沿中断,等待片选线拉低进行下一次数据传输 } GPIOA_InverseBits(GPIO_Pin_0);//翻转IO测试使用,逻辑分析仪测出中断运行时长 GPIOB_ClearITFlagBit(SPI_CS);//清除中断标志位 } }
BOOL SCK_EN=0; __HIGH_CODE __INTERRUPT void GPIOA_IRQHandler(void) { if(GPIOA_ReadITFlagBit(SPI_SCK))//按照SPI的规则来,片选线拉低后,紧跟着就会来一个时钟线的跳变沿中断,此处为模式0,因此第一个跳变沿为上升沿 { GPIOA_InverseBits(GPIO_Pin_2);//同理,测算中断运行时长 if(!SCK_EN)//检测到时钟信号上升沿中断,说明从机数据已经被读走,提前将下一位数据准备好放入MISO线上 { if(Data&(0x80 >> sendcount))//最高位bit在片选拉低时已经放入,此时已经被读走,因此这里从最高位第二位bit开始,依次放入数据 { GPIOB_SetBits(SPI_MISO); } else { GPIOB_ResetBits(SPI_MISO); } sendcount++;//sendcount++,下一次在时钟上升沿中断放入下一位bit SCK_EN=1;//将时钟跳变沿标志取反,下一次会进入下降沿中断 GPIOA_ITModeCfg(SPI_SCK, GPIO_ITMode_FallEdge);//配置上升沿中断为下降沿中断,等待时钟线拉低 } else { if(GPIOB_ReadPortPin(SPI_MOSI))//从最高位依次读取MOSI的数据 { Byte_Read |=(0x80>>recvcount); } recvcount++;//recvcount++,下一次在时钟下升沿中断读取MOSI下一位bit if(recvcount>=8) { recvcount=0;//如果连续读取了8位,则将recvcount置0,等待下一次被片选后读取新数据 // PRINT("%x\n",Byte_Read);//将读取的数据打印出来,这里将打印屏蔽节省时间,实际使用可直接赋值到缓存中读取 Byte_Read=0;//数据取走后,将Byte_Read恢复为默认值0 } SCK_EN=0;//将时钟跳变沿标志取反,下一次会进入上降沿中断 GPIOA_ITModeCfg(SPI_SCK, GPIO_ITMode_RiseEdge);//配置下降沿中断为上升沿中断,等待时钟线拉高 } GPIOA_InverseBits(GPIO_Pin_2);//测算中断时长 GPIOA_ClearITFlagBit(SPI_SCK);//清除中断标志位 } }
测试结果:
标签:__,MISO,SCK,中断,GPIOB,SPI,GPIO From: https://www.cnblogs.com/azou/p/18345379