[问题讨论] SPI接收不到数据,请各位帮忙

hujj   2018-9-26 17:31 楼主
    我的项目要从上位机接收水位数据再进行处理,现在项目进度卡在了数据接收上,上位机是用51芯片,通过串口助手已经可以收到51单片机发送的数据,另外还通过SPI发送数据,用逻辑分析仪观看到正确的时序图(参见下图)。上位机发送的是9个字节的数据,包含两个各三位数的ASCII字符。
上位机SPI发送的9个字节数据时序图.jpg

    我首先调试串口,串口助手可以收到GD32发出的信息,但GD32一直接收不到串口助手发出的信息,由于我刚学32位单片机,从厂家提供的范例中看不出是如何接收数据的。
    随后我又准备通过SPI1来接收,按照范例及参照其他坛友的帖子,反复调试也是无法接收到数据。
    最后我直接设置引脚、写代码来接收SPI数据,调试了几天仍无结果。请各位帮忙看看我的代码是什么问题,若能提供串口接收和SPI1接口的范例也感谢不尽。

GPIO配置代码:
void gpio_config(void)        //GPIO配置SP0(GPIOA),SPI1(GPIOB)和SOFT_SPI(GPIOC)
{
    /* SPI0 GPIO config: SCK/PA5, MISO/PA6, MOSI/PA7 */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_SPI0);
    gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);

    /* SPI1 GPIO config: SCK/PB13, MISO/PB14, MOSI/PB15 */
    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_SPI1);
    gpio_af_set(GPIOB, GPIO_AF_0, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |GPIO_PIN_15);
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |GPIO_PIN_15);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |GPIO_PIN_15);

    //配置soft spi:SL_CS/PC13, SL_MISO/PC12, SL_MOSI/PC11, SL_CLK/PC10
    rcu_periph_clock_enable(RCU_GPIOC);
    gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_10 |GPIO_PIN_11 | GPIO_PIN_13 );
    gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_12);
    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12 );
}



SPI配置程序:
void spi_config(void)
{
    spi_parameter_struct spi_init_struct;
   
    /* SPI0 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX; //全双工
    spi_init_struct.device_mode          = SPI_MASTER;               //主设备
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;       //帧大小为8位
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_8;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI0, &spi_init_struct);

    /* SPI1 parameter config */
    spi_init_struct.trans_mode  = SPI_TRANSMODE_BDRECEIVE;
    spi_init_struct.device_mode = SPI_SLAVE;
    spi_init_struct.nss         = SPI_NSS_SOFT;
    spi_init(SPI1, &spi_init_struct);
}



软件读入SPI:
uint8_t soft_spi_read(void)     //软件编程读SPI1
{
    uint8_t i,j, dat;
    while (gpio_input_bit_get(GPIOC,GPIO_PIN_13) == SET)   //高电平时循环等待片选信号
    {
        if (gpio_input_bit_get(GPIOC,GPIO_PIN_13) == RESET) //低电平时开始接收数据
        {
            for (j=0; j<9; j++)           //接收9个字节数据
            {
                for (i=0; i<8; i++)       //读入8位(1个字节)
                {
                    while(gpio_input_bit_get(GPIOC,GPIO_PIN_10) == RESET);//PC10引脚CLK低电平时循环
                    if (gpio_input_bit_get(GPIOC,GPIO_PIN_11) == SET)         //PC11引脚MOSI
                    {
                        dat = dat + 1;
                    }
                    dat <<= 1;
                    while(gpio_input_bit_get(GPIOB,GPIO_PIN_10) == SET);  //PC10引脚CLK高电平时循环
                }                         //1字节读取完成
                receiver_buffer[j] = dat;
            }                             //块(9字节)读取完成
            return RESET;            //接收完毕,返回正确标志
        }
    }
    return SET;
}


main函数中调用:
    if (soft_spi_read() == RESET) //调取SPI读数据模块
    {
        display_abnormal();       //读到数据
    }
    else
    {
        display_wait();           //未读到数据
    }




回复评论 (19)

呃...
楼主启用片上外设SPI,却软件模拟SPI读取
这个操作满分
https://bbs.eeworld.com.cn/thread-925896-1-1.html
拷过来看看
So TM what......?
点赞  2018-9-26 19:09
串口接收看看这个老大写的贴子
https://bbs.eeworld.com.cn/forum. ... ghlight=gd32%2Buart
虾扯蛋,蛋扯虾,虾扯蛋扯虾
点赞  2018-9-26 19:29
引用: ljj3166 发表于 2018-9-26 19:09
呃...
楼主启用片上外设SPI,却软件模拟SPI读取
这个操作满分
https://bbs.eeworld.com.cn/thread-925896 ...

开始我想用SPI1来接收的,反复调试仍不成功,只好改用软件模拟,部分代码就是参照您的贴子复制过来的,但仍未成功。谢谢关注!
点赞  2018-9-26 19:51
引用: hujj 发表于 2018-9-26 19:51
开始我想用SPI1来接收的,反复调试仍不成功,只好改用软件模拟,部分代码就是参照您的贴子复制过来的,但 ...

SPI1之所以调不通是,因为SPI1的接口有一个是在按键上,一直是高电平,你看看我发的帖子,对照引脚图,看看,我的I2C就是这个原因,一直没调通,你看看是不是这个问题。
点赞  2018-9-26 21:33
引用: 传媒学子 发表于 2018-9-26 21:33
SPI1之所以调不通是,因为SPI1的接口有一个是在按键上,一直是高电平,你看看我发的帖子,对照引脚图,看 ...

貌似与这个没关系。。。
点赞  2018-9-26 21:38
引用: hujj 发表于 2018-9-26 19:51
开始我想用SPI1来接收的,反复调试仍不成功,只好改用软件模拟,部分代码就是参照您的贴子复制过来的,但 ...

嗯,好吧
细细看了一下SPI代码
似乎少了一句使能语句
类似spi_enable(SPI0)这样的
So TM what......?
点赞  2018-9-26 21:47
另:SPI的选通引脚电平是否正常
So TM what......?
点赞  2018-9-26 21:52
用的程序,是移植过来的,该程序也是验证过的可以用的,所以说问题只能出现在他配置该款芯片的外设Io及相关寄存器的配置上,还是仔细看看手册吧。
点赞  2018-9-27 09:38
感谢各位热心帮助,现在程序会进入到软件模拟SPI模块中了,在调试将串行数据恢复。

点赞  2018-9-27 15:00
uint8_t soft_spi_read(void)     //软件模拟读SPI1
{
    uint8_t i,j, dat;
    uint32_t wait;
    while (gpio_input_bit_get(GPIOC,GPIO_PIN_13) == SET)   //高电平时循环等待
    {
        if (gpio_input_bit_get(GPIOC,GPIO_PIN_13) == RESET)//低电平时开始接收数据
        {
            gpio_bit_reset(GPIOC,GPIO_PIN_12);       //准备跟随时钟信号
            for (j=0; j<9; j++)                                 //接收9个字节数据
            {
                for (i=0; i<8; i++)                             //读入8位(1个字节)
                {
                    while(gpio_input_bit_get(GPIOC,GPIO_PIN_10) == RESET);//PC10引脚CLK低电平时循环
                    gpio_bit_set(GPIOC,GPIO_PIN_12); //跟随时钟信号
                    if (gpio_input_bit_get(GPIOC,GPIO_PIN_11) == SET)      //PC11引脚MOSI
                    {
                        dat = dat + 1;
                    }
                    dat <<= 1;
                    while(gpio_input_bit_get(GPIOB,GPIO_PIN_10) == SET);  //PC10引脚CLK高电平时循环
                    gpio_bit_reset(GPIOC,GPIO_PIN_12); //跟随时钟信号
                }                                    //1字节读取完成
                receiver_buffer[j] = dat;
            }                                                 //块(9字节)读取完成
            gpio_bit_set(GPIOC,GPIO_PIN_12);//结束跟随时钟信号

            return RESET;                                      //接收完毕,返回正确标志
        }
        wait++;                                         //准备定时计数用
//        if (wait > 4000000)               //定时结束等待,返回错误标志
//            return SET;
    }
    return SET;
}

   在上面的软件模拟接收SPI数据程序中,我用PC12引脚来跟随SPI的时钟,结果发现程序并未按照预定的循环,而是一个字节只循环了7次就跳出了,并且没有进行9个字节的循环,不知道是那行代码出了问题。
点赞  2018-9-27 16:14
下图是PC12引脚跟随SPI时钟的时序图,在一个字节中本应该循环8次,仅仅循环了7次就跳出了,并且没有进行9个字节的循环。

模拟SPI测试1_1.jpg

放大后发现在半个时钟周期内(时钟高电平)PC12引脚波动了11次,会不会是我在PC12引脚上挂接了发光二极管的缘故。

模拟SPI测试1_2.jpg
点赞  2018-9-27 16:40
    我将挂接在PC12引脚的发光二极管取下(杜邦线连接)再观察时序,PC12引脚仍是波动的图形,在半个周期内波动了11次。
点赞  2018-9-27 17:26
差点时序图被误导了!
    前面曾提到过通过程序将PC12引脚跟随SPI的时钟,结果没有看到就有的波形,以为 程序的循环有问题,但目测检查又查不出问题所在。只好通过变量分别记录接收的字符数和位数(也就是时钟数),最后显示的的确接收了9个字符,共72位,应该是正常循环的。下一步就继续调试接收到的数据为何不正确。

模拟SPI测试1_3.jpg
点赞  2018-9-27 19:26
目前已经排查到下列代码:

                    if (gpio_input_bit_get(GPIOC,GPIO_PIN_11) == SET)      //PC11引脚MOSI
                    {
                        dat = dat + 1;
                    }
                    dat <<= 1;
dat = dat +1;这行代码一直没被执行,为了防止杜邦线问题,我从PC10、PC11、PC13扩展插座下直接焊接排线连接逻辑分析仪,确认SPI的信号已经到达这三个引脚,但不知为什么PC11引脚的高电平不被单片机读入。
点赞  2018-9-27 20:21
    我将PC12引脚作为标志,在开始接收时拉低,接收结束时拉高,从时序图上就发现了问题,整个接收时间仅0.11毫秒,还不到SPI发送一个字节的时间,接收程序与SPI时钟严重不同步。怪不得前面的帖子里提到的观察PC12引脚跟随时钟时的波形会有波动,我数了一下,正好是72个方波,也就是9个字节合计的72位,就是程序共计循环了72次。看来问题是出在时钟同步上了。

模拟SPI测试1_4.jpg
点赞  2018-9-27 21:07
不错,真是太棒了!
点赞  2018-9-28 08:10
  又花费了两天的时间,终于将模拟SPI的程序调试好了,成功地接收到51单片机发来的信息。这期间自己给自己挖了不少坑。开始将接收的代码放在了片选信号等待的循环中,造成了接收时间仅0.11毫秒,一个字节都没有收全。然后是在等待片选信号时放置了计数定时跳出的代码,又造成接收代码没有被执行。期间还怀疑51单片机的5v电压电平信号过高会对GD32单片机接收造成影响,还用二极管加电阻焊了一个信号电平转换电路。见下图:

模拟SPI测试1_6.jpg

    下图是用PC12引脚模拟跟随SPI的MOSI信号,成功地接收到了数据

模拟SPI测试1_5.jpg


以下是最后完成的代码:


uint8_t soft_spi_read(void)     //软件模拟读SPI1
{
    uint8_t i,j, dat;
//    uint32_t wait;
//        wait = 0;
    while (gpio_input_bit_get(GPIOC, GPIO_PIN_13 ) == SET);    //高电平时循环等待

    while (gpio_input_bit_get(GPIOC, GPIO_PIN_13 ) == RESET)  //低电平时开始接收数据
    {

        for (j=0; j<9; j++)           //接收9个字节数据
        {

            dat = 0;
            for (i=0; i<8; i++)       //读入8位(1个字节)
            {
                while(RESET == gpio_input_bit_get(GPIOC, GPIO_PIN_10 )); //PC10引脚CLK低电平时循环
                dat <<= 1;
                if (SET == gpio_input_bit_get(GPIOC, GPIO_PIN_11 ))      //PC11引脚MOSI
                    dat = dat + 1;

                while(SET == gpio_input_bit_get(GPIOC, GPIO_PIN_10 ));   //PC10引脚CLK高电平时循环

            }                         //1字节读取完成
            receiver_buffer[j] = dat;

        }                             //块(9字节)读取完成
               
        return RESET;
    }

    return RESET;                              //接收完毕,返回正确标志

}

    在此向各位热心关注本帖的坛友以及热情提供帮助各位老师表示衷心地感谢!尤其要谢谢nmg版主的热心帮助!
点赞  2018-9-29 20:09
    不好意思,上一贴第一张图发错了,那是最后接收到SPI信息的调试情况,下图才是增加了信号电平转换电路:

增加信号电平转换电路.jpg
点赞  2018-9-29 20:14
    今天参考ljj3166和tinnu老师的帖子,核对和调整了我原来的代码,成功地接收到串口数据,作为一个备用功能留待以后需要时使用。
点赞  2018-10-1 19:19
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复