基于两颗CH582芯片实现GPIO模拟SPI全双工通讯__从机通过GPIO中断读写数据
简介:
此程序是根据标准SPI协议规范使用模式0编写的一份模拟SPI全双工数据收发例程,当前的程序经过测试,60MHz主频下一个字节收发时长可压缩至最低115us左右,约9091字节每秒=73Kbps的通讯速率,只适用于数据量小的传输,与硬件SPI存在较大的差距,注释中尽可能解释了每一步的含义,后续可能会对其进行优化,以达到更快的传输速度。
流程图:
一、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.76us,因此这里必须延时至少4.76us,否则从机中断还没有出来,会让从机丢失下一次进入中断的判断时机
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.84us,读取数据中断占用1.64us,因此取最大值,至少延时3.84us,取4us
GPIOB_ResetBits(SPI_SCK);//SCK下降沿接收MISO数据,拉低时钟线,让从机把数据放到MISO上,主机准备读取
DelayUs(4);//延时同理,防止从机SCK中断程序没出来就发起下一次中断
}
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);//清除中断标志位
}
}
int main()
{
SetSysClock(CLK_SOURCE_PLL_60MHz);
/* 配置串口调试 */
DebugInit();
PRINT("Start @ChipID=%02X\n", R8_CHIP_ID);
SPI_SLAVE_Init();
GPIOA_ModeCfg(GPIO_Pin_0|GPIO_Pin_2, GPIO_ModeOut_PP_5mA);
GPIOA_SetBits(GPIO_Pin_0|GPIO_Pin_2);
while(1);
}
测试结果: