首页 > 其他分享 >基于两颗CH582芯片实现GPIO模拟SPI全双工通讯__从机通过GPIO中断读写数据

基于两颗CH582芯片实现GPIO模拟SPI全双工通讯__从机通过GPIO中断读写数据

时间:2024-08-06 16:18:24浏览次数:7  
标签:__ MISO SCK 中断 GPIOB SPI GPIO

简介:

  此程序是根据标准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

相关文章

  • Mac开发基础12-NSTextField(二)
    NSTextField是一个功能强大的控件,不仅可以作为简单的文本输入框,还可以实现更多高级功能。例如,支持富文本、实现自定义绘制、处理复杂的输入校验等。进阶使用和技巧1.富文本显示与编辑NSTextField支持富文本,也就是说你可以为文本设置不同的颜色、字体、大小等。设置富文本O......
  • odoo17 环境配置
    1、PostgreSql数据库安装教程:Windows上安装PostgreSQL|菜鸟教程(runoob.com) (建议版本15以上)注意:由于Odoo是不允许用pg自带的管理员角色--postgres,所以得创一个odoo使用数据库的角色:createuserodoowithpassword'odoo';alterroleodoowithsuperuser;也可......
  • 迟钝的舞会 题解
    题目id:1329题目描述牛是公认的笨拙的舞者。然后,约翰发现富有音乐细胞的母牛能产更多的奶。因此,他把他的整圈的牛都拉进了舞蹈培训班,包括所有的公牛(因为跳舞的时候得一男一女-_-)。这些牛正好有\(n\)头是公的,有\(n\)头是母的。在第一堂课开始之前,舞蹈老师想将他们分成一对一对的(......
  • 在webapi中发起HttpPost请求
    1.第一步,在启动文件添加builder.Services.AddHttpClient();实体类: publicclassSearchReq{publicstringName{get;set;}publicstringDescription{get;set;}publicintPageIndex{get;set;}publicintPageSize{get;set;}}u......
  • Omnissa Horizon 8 2406 (8.13) 发布下载 - 虚拟桌面基础架构 (VDI) 和应用软件
    OmnissaHorizon82406(8.13)发布-虚拟桌面基础架构(VDI)和应用软件之前称为VMwareHorizon,通过高效、安全的虚拟桌面交付增强您的工作空间请访问原文链接:https://sysin.org/blog/omnissa-horizon-8/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgHorizon......
  • OS第二章——进程管理
    2.1进程与线程进程的概念、组成、特征进程的概念程序是静态的,就是一个存放在磁盘里的可执行文件,就是一系列的指令集合。进程:是动态的,是程序的依次执行过程。(同一个程序的多次执行会对应多个进程)进程的组成PCB当进程被创建时,OS会为该进程分配一个唯一的、不重复的“身份......
  • Reviewer Instructions
    ReviewerInstructionsAsanIP&Mreviewer,yourobjectiveistoevaluatethemeritsandpresentationoftheresearchpresentedinthissubmission.Submissionsshouldemployappropriatemethodsanddatathatsubstantiatestheconclusionsandinterpreta......
  • 织梦dedecms调用文章列表时候判断文章自定义属性
    有时候我们需要通过判断文章的属性来给相应的属性以相应的样式,例如为推荐的文章添加推荐的标志等等。例如以下代码就可以判断出文章是否是推荐和图片这两个属性,并作不同的样式输出:[field:arrayrunphp=&#39;yes&#39;]if(@me[&#39;flag&#39;]==&#39;c,p&#39;)@me=&#39;<em>......
  • WordPress 简单吗?
     是的,WordPress对于初学者来说非常简单易用。WordPress是一个开源的网站构建平台,专为简单性和用户友好性而设计,使其非常适合初学者和技术水平较低的用户。以下是几个使WordPress易于使用的原因:1.直观的用户界面:WordPress拥有一个干净且直观的界面,菜单和选项清晰易懂。......
  • wordpress教程栏目给大家介绍自定义wordpress文件上传路径的方法
    自WordPress3.5版本开始,隐藏了后台媒体设置页面的“默认上传路径和文件的完整URL地址”选项,可以通过下面的代码将该选项调出来。将下面的代码添加到当前主题functions.php文件中,就可以调出该选项:if(get_option(&#39;upload_path&#39;)==&#39;wp-content/uploads&#39;||get_op......