基于两颗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);
}

 

 


 

测试结果:

 

 

 

posted @ 2024-08-06 16:12  oTvTo  阅读(154)  评论(0编辑  收藏  举报