主机和从机都是三线的spi(CLK,DI,DO)实现主机从机随时收发驱动详解

spi领悟:加一个功能,应该是先在不加判判错的时候,先把东西跑通,然后在加判错等一些功能,前期的调试越简单越好,逐步加难,我当时调的时候就是拿了宝哥的程序,直接加判错,以至于改了一些东西还加了功能,非常的乱

具体实现如下:

b781b4d2722a4e5389037734257234c0.png

添加了两根gpio线来实现通讯,a线是主机通知从机,b线从机回复主机

1.a线高电平为主机发送电平,开始发送时主机先拉高a线电平通知从机

2.从机收到a线的上升沿产生中断,得知是要发送,进入发送的状态机,拉高b线电平,然后开始准备数据接收数据(设置dma地址或者缓冲区等操作),准备好数据,从机拉低电平,产生下降沿,
告诉主机已经准备好接收数据了,同时从机也会进入等待超时函数,等待主机的时钟开始通讯,主机通过检测b线的下降沿来判断从机是否准备好,然后开始发送数据

3.主机收到从机的b线下降沿回复,知道可以发送数据了,于是产生时钟开始发送

4.若主机长时间没有收到从机的b线回复信号,说明从机没有检测到主机的a线信号,于是需要进行超时操作,即重新拉低拉高a线信号,重新通知从机,注意这个拉低拉高的时间要短一点,后面的就是重复之前的操作

5.接收和发送的操作是相似的,只不过发送是高电平,接收是低电平所以从机也是通过判断a线高低电平来得知自己是发送还是接收状态

说明:

1.上面这种方式和四线的区别是多了一个从机的回复线,用于回复主机从机已经准备好收发数据了,为什么用这个,主要是因为主从芯片不支持spi的同时收发,

如果可以同时收发完全可以用四线来实现,只需在主机准备好数据后拉低a线,并等待一段时间,这个时候从机在检测到主机的a线信号,就会开始准备收发的缓冲区就行,并且b线就不用每次收发数据用于回复主机了,可以直接用于从机要主动发数据的通知主机

2.上面讨论的情况都是主机主动收发的情况,未讨论从机主动通知主机我有数据要发的情况,如果要实现这个,需要在主机那边加判断,可以判断上升沿且高电平大于某个时间值来判断是从机主动发送数据的信号还是从机回复主机的信号,或者这个不好行的话,也可以判断低电平,前提是在发送完信号后电平要做拉高处理,这个没开发过,只提供思路,具体需要测

调试时程序中的巨大bug:

错一:如下是主机spi的发送函数,我在每次调用此函数的时候都对b线输入中断的全局变量进行置0,这个操作是不对的,这种全局变量的标志位应该只用总的初始化的时候初始化为0,在while循环中只有判断完标志位后才能置0,其他地方任何的置0都是会影响后面的标志位的判断操作

faf6136ddb5347b3bdbc88111291cddd.png

错2:主机对b线的判断应该判断下降沿,因为从机在接收到主机a线的信号后会线拉高b线,然后去准备数据和缓冲区,准备好后马上拉低b线通知主机,这时从机会进入对主机的时钟的等待才能进行发送接收操作,即进入等待才是准备好要收发数据,主机也应该在此时给时钟信号,所以是下降沿。

如下是和错二相关的从机的程序

69975383dc85403faedbf345c0f02f4c.png

bf950636d13d40c3b96234d791ef1937.png34ad1691c85a4554ace16f942933e979.png

还要注意的一点是,每次在切换识别上下升沿的时候都要关闭中断后重新设置,不然可能会不生效。可以看出,把数据放入dma寄存器的时候才开始调用fun(),拉低信号线通知主机这样子才是对的,后面的_spi_wait_ok作为从机等待主机的时钟信号,若长时间等不到信号,就会报错超时。

在函数spi_dma_recv_awi和spi_dma_send_awi中,是准备好数据后,就把通知主机的引脚拉低,而不是发送完拉低,所以主机是检测下降沿,这个很关键,必须要从机准备好数据,才通知主机,否则主机来拿数据时拿的数据是错的,原来错就错在了主机拿数据拿早了,因为一直以为是上升沿拿数据

注意,spi都是主机为主的通讯,从机判断能不能发送数据,是判断主机的时钟有没有来,若主机的时钟长时间没来,就会超时报错

注意,spi都是主机为主的通讯,从机判断能不能发送数据,是判断主机的时钟有没有来,若主机的时钟没来,spi数据缓存去FIFO中的数据是不能发出的,spi的完成中断标志位也不能置起,若长时间没来,就会超时报错

要考虑的问题,在进行spi驱动之前要测的几个数据

1.主机和从机gpio中断的反应时间:

有可能从机gpio对中断的反应比较慢,比如杰里的可能开了中断滤波这是有100us内的时间收不到中断,也有可能是本身中断就容易收不到,比如杰里不开滤波可能也要5us来反应中断,所以每次切换信号后,都要保持稳定的电平一段时间

对于需要主机和从机gpio中断的反应时间这个太进行spi驱动之前就需要写一个测试程序测测,并定记录下数据;

2.从机未收到中断,长时间未回复,主机要加超时判断,并重新制造信号电平

以从机为例:从机可能收不到A线的高低电平中断,所以主机要加超时判断,如果长时间收不到从机的回应,说明从机没收到信号,需要从新制造上下升沿的信号:

注意重新制造的上下升信号必定会干扰从机的对主机状态的判断,因为其中一定有上下沿两个信号,所以在主机这边,拉低拉高时间a要在从机能检测到的情况下尽量快一点,并且要在从机端加判断,通过判断大于时间a来确定是否为主机重新发送的通知信号

3.主机从机的延时函数的准确性

注意,delay函数的延时大部分是不准的,由于主从机两边需要对齐时间,所以在开始写驱动之前就要通过写测试程序,比如延时加高低电平的变化,来写出一个准确的延时函数

4.要确定spi能否同时收发,有些芯片的spi手册上虽然写着全双工,也有可能是一次只能进行收发的一种操作,不能同时收发

spi领悟:加一个功能,应该是先在不加判判错的时候,先把东西跑通,然后在加判错等一些功能,前期的调试越简单越好,逐步加难

相关代码

主机

#include "include.h"


#if 1 /******spi*******/
#define SPI_ERR_COUNT_MAX 3
#define SPI_ERR_TIMEOUT 1500
#define TRUE 1
#define FAIL 0

#define AWI_SPI_SEND_STAET 0
#define AWI_SPI_RECEIVE_STAET 1

u8 wbuff_data[8];
u8 rbuff_data[8];


AT(.text.spiflash1_drv)
u8 spi1_dma_sendbytes(void *data, u32 len)
{
     volatile u8 err_count = SPI_ERR_COUNT_MAX;
     volatile u16 spi_timeout;
     // sys_cb.slave_ack = 0;
     SPI_MASTER_NOTICE_SLAVE_PORT_HIGH; // pull up

     while (err_count--)
     {
          spi_timeout = SPI_ERR_TIMEOUT;
          while (spi_timeout) // 等待响应1500us max
          {
               // printf("err_count1 %d, spi_timeout1 %d, sys_cb.slave_ack1 %d\r\n",err_count, spi_timeout, sys_cb.slave_ack);
               user_delay_us(1);
               spi_timeout--;
               if (sys_cb.slave_ack)
               {
                    sys_cb.slave_ack = 0;
                    SPI1CON |= BIT(0);  // enable spi
                    SPI1CON &= ~BIT(4); // 切换为transmit模式

                    SPI1DMAADR = DMA_ADR(data);
                    SPI1DMACNT = len;
                    // wait spi send success
                    while (!(SPI1CON & BIT(16)))
                    {
                    }
                    SPI1CON &= ~BIT(0); // disenable spi
                    printf("send succ\r\n");
                    return TRUE;
               }
          }
          SPI_MASTER_NOTICE_SLAVE_PORT_LOW;
          user_delay_us(20);
          SPI_MASTER_NOTICE_SLAVE_PORT_HIGH;
     }
     return FAIL;
}


AT(.text.spiflash1_drv)
u8 spi1_dma_readbytes(void *buf, u32 len)
{
     volatile u8 err_count = SPI_ERR_COUNT_MAX;
     volatile u16 spi_timeout;
     // sys_cb.slave_ack = 0;
     SPI_MASTER_NOTICE_SLAVE_PORT_LOW; // pull down

     while (err_count--)
     {
          spi_timeout = SPI_ERR_TIMEOUT;
          while (spi_timeout) // 等待响应1500us max
          {
               // printf("err_count2 %d, spi_timeout2 %d, sys_cb.slave_ack2 %d\r\n",err_count, spi_timeout, sys_cb.slave_ack);
               user_delay_us(1);
               spi_timeout--;
               if (sys_cb.slave_ack)
               {
                    sys_cb.slave_ack = 0;
                    SPI1CON |= BIT(0) | BIT(4); // 切换为receive模式//enable spi

                    SPI1DMAADR = DMA_ADR(buf);
                    SPI1DMACNT = len;
                    // wait spi read success
                    while (!(SPI1CON & BIT(16)))
                    {
                    }
                    SPI1CON &= ~BIT(0); // disenable spi
                    printf("read succ\r\n");
                    return TRUE;
               }
          }

          SPI_MASTER_NOTICE_SLAVE_PORT_HIGH;
          user_delay_us(20);
          SPI_MASTER_NOTICE_SLAVE_PORT_LOW;
     }
     return FAIL;
}

AT(.text.spiflash1_drv)
void spi_send_test(void)
{
     // printf("spi send test\r\n");
     unsigned char send_data_test = 0;
     volatile unsigned char spi_send_count = 3;
     u8 res;

     memset(rbuff_data, 0x00, sizeof(rbuff_data));
     WDT_DIS();

     spi_send_count = 3;

     while (spi_send_count)
     {
          send_data_test++;
          memset(wbuff_data, send_data_test, sizeof(wbuff_data));
          res = spi1_dma_sendbytes(wbuff_data, sizeof(wbuff_data));
          if (res == 0)
          {
               // printf("spi send err\r\n");
          }
          spi_send_count--;
          // user_delay_us(1500);
     }
     // user_delay_us(200); //?????加这个是为了让主机通知从机的a线拉高到拉低的时间加长,防止杰里从机收不到电平的中断消息?
     // break;

     res = spi1_dma_readbytes(rbuff_data, sizeof(rbuff_data));
     if (res == 0)
     {
          // printf("spi read err\r\n");
     }
     else
     {
          printf("rec:");
          for (int i = 0; i < sizeof(rbuff_data); i++)
          {
               printf("%x ", rbuff_data[i]);
          }
          printf("\r\n");
     }

     // user_delay_us(200);
}
#endif /******spi*******/

static void task_printf(void)
{
     printf("task_printf1\r\n");

}

// typedef struct _TASK_COMPONENTS
// {
//     unsigned char Run;                 // 程序运行标记:0-不运行,1运行
//     unsigned short Timer;              // 计时器
//     unsigned short ItvTime;              // 任务运行间隔时间【AKA计时器初始值值】
//     void (*TaskHook)(void);    // 要运行的任务函数
// } TASK_COMPONENTS;       // 任务定义

static TASK_COMPONENTS TaskComps[] =
    {
        {0, 1000, 1000, task_printf}, // 测试任务
        {0, 4, 4, spi_send_test},     // SPI任务
     // {0, 500, 100, task_printf2}, // 测试任务
     // {0, 1500, 200, task_printf3}, // 测试任务
     //    {0, 0, 0, func_run}, // 蓝牙任务
     //  {0, 20, 20, TaskKeySan},               // 按键扫描
     //  {0, 30, 30, TaskDispStatus},            // 显示工作状态
     // 这里添加你的任务。。。。
};

typedef enum _TASK_LIST
{
     // TAST_FUN_RUN, // 蓝牙任务
     TASK_PRINTF,
     TASK_TEST_SPI,
     // TASK_PRINTF2,
     // 这里添加你的任务。。。。
     TASKS_MAX // 总的可供分配的定时任务数目
} TASK_LIST;

/**************************************************************************************
 * FunctionName   : TaskRemarks()
 * Description    : 任务标志处理
 * EntryParameter : None
 * ReturnValue    : None
 * attention      : ***在定时器中断中调用此函数即可***
 **************************************************************************************/
AT(.com_text.isr)
void TaskRemarks(void)
{
     volatile unsigned char i;
     for (i = 0; i < TASKS_MAX; i++) // 逐个任务时间处理
     {
          if (TaskComps[i].Timer) // 时间不为0
          {
               TaskComps[i].Timer--; // 减去一个节拍
          }
          else
          {
               TaskComps[i].Timer = TaskComps[i].ItvTime; // 恢复计时器值,从新下一次
               TaskComps[i].Run = 1;                      // 任务可以运行
          }
     }
}

/**************************************************************************************
 * FunctionName   : TaskProcess()
 * Description    : 任务处理|判断什么时候该执行那一个任务
 * EntryParameter : None
 * ReturnValue    : None
 * * attention      : ***放在mian的while(1)即可***
 **************************************************************************************/
void TaskProcess(void)
{
     volatile unsigned char j;
     for (j = 0; j < TASKS_MAX; j++) // 逐个任务时间处理
     {
          if (TaskComps[j].Run) // 时间不为0
          {
               TaskComps[j].TaskHook(); // 运行任务
               TaskComps[j].Run = 0;    // 标志清0
          }
     }
}

从机

void gzm_gpio_isr_state(u8 index)
{
	if(gpio_read(IO_PORTA_04))
		isr_state = gzm_isr_up;
	else
		isr_state = gzm_isr_down;
}

void awi_spi_slave_ack()
{
	gzm_gpio_output_down(IO_PORTA_05);
}

void user_delay_us(u32 usec)
{
    delay(48 * usec);
}


void app_main_run(void)
{
    printf("time1758\r\n");
    clk_set("sys", (120 * 1000000L));
    ui_update_status(STATUS_BT_INIT_OK);
    audio_adc_init(&adc_hdl, &adc_data);
#ifdef GZM_TEST
    u8 count_send = 0x01;
	memset(spi1_send_buf, count_send, sizeof(spi1_send_buf));
	memset(spi1_recv_buf, 0x00, sizeof(spi1_send_buf));

	int err = 0;
	int a4_state, count;
	count = 0;

    wdt_close();
    for(int i = 0; i < 1536; i++){
	   spi1_send_buf[i] = i;
    }
	spi_open(spi1_hdl);
	int i = 0;
	gzm_gpio_input_up(IO_PORTA_04);
	gzm_gpio_output_down(IO_PORTA_05);

    // gzm_gpio_output_up(IO_PORTC_02);//debug
    // gzm_gpio_output_up(IO_PORTC_01);//debug

	int flag = 0;
	set_io_ext_interrupt_cbfun(gzm_gpio_isr_state);

	//先捕捉上升岩
    io_ext_interrupt_init(0, IO_PORTA_04, 0);

#if 1
		while(1) {
			//pull up or down pa5
			//read pa4 state
			switch(isr_state){
			      case gzm_isr_down:{
                    // gzm_gpio_output_down(IO_PORTC_02);//debug
                    user_delay_us(50);
                    // gzm_gpio_output_up(IO_PORTC_02);//debug
                    if(gpio_read(IO_PORTA_04) == 0)
                    {
				  	  io_ext_interrupt_close(0, IO_PORTA_04);
				  	  //设置上升
					  io_ext_interrupt_init(0, IO_PORTA_04, 0);
				      gzm_gpio_output_up(IO_PORTA_05);
					  spi_dma_send_awi(spi1_hdl, spi1_send_buf, 1536,awi_spi_slave_ack);
					  isr_state = gzm_isr_max;
                    }
                    else
                    {
                        isr_state = gzm_isr_up;
                    }

			          break;
				  }
				  case gzm_isr_up:{
                    // gzm_gpio_output_down(IO_PORTC_01);//debug
                    user_delay_us(50);
                    // gzm_gpio_output_up(IO_PORTC_01);//debug
                    if(gpio_read(IO_PORTA_04) == 1)
                    {
				  	  int count_recv = 3;
					  io_ext_interrupt_close(0, IO_PORTA_04);
				  	  //设置成下降
					  io_ext_interrupt_init(0, IO_PORTA_04, 1);
				  	  while(count_recv){
					  	  gzm_gpio_output_up(IO_PORTA_05);
						  spi_dma_recv_awi(spi1_hdl, spi1_recv_buf, 1536,awi_spi_slave_ack);//内部会等待spi接收结束,且在spi开始接收之前就会拉低从机通知主机的中断B线,
                                                                                            //在spi接收时,B线都是稳定的低电平,这样子为下一轮的接收需要拉高电平做准备
                                                                                            
						  count_recv--;
					  }
					  isr_state = gzm_isr_max;

                    }
                    else
                    {
                        isr_state = gzm_isr_down;
                    }
			          break;
				  }
				  default:

				      break;
  			}
		}
#endif
		spi_close(spi1_hdl);

在RT1064作为SPI主机与RT1010作为SPI从机之间进行通讯时,需要先确定好通讯协议,例如数据位数、时钟极性、时钟相位等参数。接下来,我们可以使用SDK提供的SPI驱动函数来实现通讯,下面是一个简单的示例代码: 在RT1064端: ```c #include "fsl_spi.h" void spi_master_init(void) { spi_master_config_t spiConfig = {0}; spiConfig.baudRate_Bps = 500000; // 设置SPI时钟为500kHz spiConfig.enableLoopback = false; spiConfig.phase = kSPI_ClockPhaseFirstEdge; // 时钟第一边沿采样 spiConfig.polarity = kSPI_ClockPolarityActiveHigh; // 时钟空闲状态为高电平 spiConfig.direction = kSPI_MsbFirst; // 高位先传输 SPI_MasterInit(SPI1, &spiConfig, CLOCK_GetFreq(kCLOCK_Flexcomm1Clk)); // 初始化SPI1 } void spi_master_transfer(uint8_t *sendBuf, uint8_t *recvBuf, uint32_t length) { spi_transfer_t xfer = {0}; xfer.txData = sendBuf; xfer.rxData = recvBuf; xfer.dataSize = length; SPI_MasterTransferBlocking(SPI1, &xfer); // 等待传输完成 } ``` 在RT1010端: ```c #include "fsl_spi.h" void spi_slave_init(void) { spi_slave_config_t spiConfig = {0}; spiConfig.phase = kSPI_ClockPhaseFirstEdge; // 时钟第一边沿采样 spiConfig.polarity = kSPI_ClockPolarityActiveHigh; // 时钟空闲状态为高电平 spiConfig.direction = kSPI_MsbFirst; // 高位先传输 spiConfig.dataSize = 8; // 数据位宽为8位 SPI_SlaveInit(SPI1, &spiConfig); // 初始化SPI1 } void spi_slave_transfer(uint8_t *sendBuf, uint8_t *recvBuf, uint32_t length) { spi_transfer_t xfer = {0}; xfer.txData = sendBuf; xfer.rxData = recvBuf; xfer.dataSize = length; SPI_SlaveTransferBlocking(SPI1, &xfer); // 等待传输完成 } ``` 在主机发送数据时,可以调用 `spi_master_transfer()` 函数来进行数据传输;在从机端接收数据时,可以调用 `spi_slave_transfer()` 函数来进行数据接收。具体实现方式可以根据通讯协议的要求进行调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文武先生hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值