GD32F405RGT6-SPI主从模式代码
唔,其实好难受,对于我们这种有着轻微强迫症的人来说只能利用下班以及周末的空闲时间对GD的外设撸代码。。。。一个周末加今天的下班时间,撸到了GD的SPI外设。在我的印象中对于SPI通信最直观的想法就是“一问一答”。不论是主从双方,要想询问对方都得发东西过去,然后同时接受自己想要的。
咳咳,那也顺便聊一聊SPI通信吧。有兴趣的个人觉的还是自己手写一遍SPI的代码弄懂时序,结合逻辑分析仪应该上手是很快的。(个人观点)。
1.介绍
SPI总线是Motorola公司推出的三线同步接口,同步串行3线方式进行通信:一条时钟线SCK,一条数据输入线MOSI,一条数据输出线MISO。用于 CPU与各种外围器件进行全双工、同步串行通讯。
2.SPI主要特点
SPI主要特点有:可以同时发出和接收串行数据,可以当作主机或从机工作,提供频率可编程时钟,发送结束中断标志,写冲突保护,总线竞争保护等。
3.SPI的工作方式
SPI总线有四种工作方式(SP0、SP1、SP2、SP3),其中使用的最为广泛的是 SPI0 和 SPI3 方式。SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
如果CPOL = 0,串行同步时钟的空闲状态为低电平。如果CPOL = 1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA = 0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样。如果CPHA = 1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
SPI主模块和与之通信的外设音时钟相位和极性应该一致。
原文链接:https://blog.csdn.net/zhengqijun_/article/details/52550855
二、SPI时序详解*
1.SPI时序图
SPI接口在模式0下输出第一位数据的时刻SPI接口有四种不同的数据传输时序,取决于CPOL 和 CPHL这两位的组合。图1表现了这四种时序,时序与CPOL、CPHL的关系也可以从图1中看出。
CPOL 是用来决定 SCK 时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA 是用来决定采样时刻的,CPHA = 0,在每个周期的第一个时钟沿采样,CPHA = 1,在每个周期的第二个时钟沿采样。由于器件工作在模式0这种时序(CPOL = 0,CPHA = 0),所以将图1简化为图2,只关注模式0的时序。
SCK的第一个时钟周期,在时钟的前沿采样数据(上升沿,第一个时钟沿),在时钟的后沿输出数据(下降沿,第二个时钟沿)。
首先来看主器件,主器件的输出口(MOSI)输出的数据bit1,在时钟的前沿被从器件采样,那主器件是在何时刻输出 bit1 的呢?
bit1 的输出时刻实际上在 SCK 信号有效以前,比 SCK 的上升沿还要早半个时钟周期。bit1 的输出时刻与 SSEL 信号没有关系。
再来看从器件,主器件的输入口(MISO)同样是在时钟的前沿采样从器件输出的 bit1 的,那从器件又是在何时刻输出 bit1 的呢?
从器件是在SSEL信号有效后,立即输出 bit1,尽管此时SCK信号还没有起效。
从这张图就可以很清楚的看出主从器件的bit1 是怎样输出的。
原文链接https://blog.csdn.net/zhengqijun_/article/details/52550855
上代码(主机模式):
/*
************************************************************
*
* notes:
* (1):PC1--MOSI
* (2):PC10--SCK
* (3):PC11--MISO
* (4):PA4--NSS
************************************************************
*/
void SPI2_Init(void)
{
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_SPI2);
/* SPI2_CLK(PC10), SPI2_MISO(PC11), SPI2_MOSI(PC1)*/
gpio_af_set(GPIOC, GPIO_AF_5, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.device_mode = SPI_MASTER;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_32;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(SPI2, &spi_init_struct);
spi_enable(SPI2);
}
//发送半字
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2,half_word);
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));
return spi_i2s_data_receive(SPI2);
}
//发送一个字节
uint8_t spi_flash_send_byte(uint8_t byte)
{
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2,byte);
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));
return(spi_i2s_data_receive(SPI2));
}
上代码(从机模式)
/*
************************************************************
* (1):PC1--MOSI
* (2):PC10--SCK
* (3):PC11--MISO
* (4):PA4--NSS
************************************************************
*/
void SPI2_Init(void)
{
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_SPI2);
/* SPI2_CLK(PC10), SPI2_MISO(PC11), SPI2_MOSI(PC1)*/
gpio_af_set(GPIOC, GPIO_AF_5, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN_1|GPIO_PIN_10| GPIO_PIN_11);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.device_mode = SPI_SLAVE;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_32;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(SPI2, &spi_init_struct);
spi_enable(SPI2);
spi_i2s_interrupt_enable(SPI2,SPI_I2S_INT_RBNE);
nvic_irq_enable(SPI2_IRQn, 0, 2);
}
//发送半字
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2,half_word);
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));
return spi_i2s_data_receive(SPI2);
}
//发送一个字节
uint8_t spi_flash_send_byte(uint8_t byte)
{
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2,byte);
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));
return(spi_i2s_data_receive(SPI2));
}
//中断服务函数
void SPI2_IRQHandler(void)
{
u8 spi_resive = 0;
if(spi_i2s_interrupt_flag_get(SPI2, SPI_I2S_INT_FLAG_RBNE) != RESET)
{
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE));
spi_resive = spi_i2s_data_receive(SPI2);
while(RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2,spi_resive);
SPI_DATA(SPI2);
}
}
注意在SPI的中断函数这一块,它的库并没有提供清除中断标志的操作,我们就可以通过查看手册知道:
可通过读SPI 的数据寄存器清除该标志:SPI_DATA(SPI2);
附上完整的工程代码:
(1)主机模式:https://download.csdn.net/download/tiange1996/84847574
(2)从机模式:
https://download.csdn.net/download/tiange1996/84847782