spi领悟:加一个功能,应该是先在不加判判错的时候,先把东西跑通,然后在加判错等一些功能,前期的调试越简单越好,逐步加难,我当时调的时候就是拿了宝哥的程序,直接加判错,以至于改了一些东西还加了功能,非常的乱
具体实现如下:
添加了两根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都是会影响后面的标志位的判断操作
错2:主机对b线的判断应该判断下降沿,因为从机在接收到主机a线的信号后会线拉高b线,然后去准备数据和缓冲区,准备好后马上拉低b线通知主机,这时从机会进入对主机的时钟的等待才能进行发送接收操作,即进入等待才是准备好要收发数据,主机也应该在此时给时钟信号,所以是下降沿。
如下是和错二相关的从机的程序
还要注意的一点是,每次在切换识别上下升沿的时候都要关闭中断后重新设置,不然可能会不生效。可以看出,把数据放入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);