目标:
实现spi的Master端。
参考资料:
http://bbs.elecfans.com/jishu_441914_1_1.html
这篇文章讲的非常直观易懂,看完就明白了。
1、首先配置口线,串口用来输入和打印,四个io口用来模拟spi,原本准备在一个单片机上同时模拟主从,但貌似不太可行,这种模拟的方式都是阻塞的。
2、proteus连接口线,放spi调试器和示波器,本来准备放个逻辑分析仪,但既然有spi调试器,用起来更方便,就没弄逻辑分析仪。
3、打开cubemx生成的工程,首先修改 main.h,加入口线的控制函数:
/* Private defines -----------------------------------------------------------*/
#define MASTER_CLK_Pin GPIO_PIN_1
#define MASTER_CLK_GPIO_Port GPIOC
#define MOSI_Pin GPIO_PIN_2
#define MOSI_GPIO_Port GPIOC
#define MISO_Pin GPIO_PIN_3
#define MISO_GPIO_Port GPIOC
#define CS_Pin GPIO_PIN_4
#define CS_GPIO_Port GPIOC
/* USER CODE BEGIN Private defines */
#define SCLK_SET_H HAL_GPIO_WritePin(MASTER_CLK_GPIO_Port, MASTER_CLK_Pin, GPIO_PIN_SET)
#define SCLK_SET_L HAL_GPIO_WritePin(MASTER_CLK_GPIO_Port, MASTER_CLK_Pin, GPIO_PIN_RESET)
#define MOSI_SET_H HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET)
#define MOSI_SET_L HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET)
#define MISO_READ HAL_GPIO_ReadPin(MISO_GPIO_Port, MISO_Pin)
#define CS_H HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)
#define CS_L HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET)
uint8_t SPI_RW_BYTE_MODE0(uint8_t data);
uint8_t SPI_RW_BYTE_MODE1(uint8_t data);
uint8_t SPI_RW_BYTE_MODE2(uint8_t data);
uint8_t SPI_RW_BYTE_MODE3(uint8_t data);
void SPI_WRITE_BUF_MODE0(uint8_t *pData,int len);
void SPI_WRITE_BUF_MODE1(uint8_t *pData,int len);
void SPI_WRITE_BUF_MODE2(uint8_t *pData,int len);
void SPI_WRITE_BUF_MODE3(uint8_t *pData,int len);
4、在主函数中,实现四种模式的单字节收发函数:
在实现函数之前,请了解spi协议的时序,以及四种模式,本文侧重仿真测试,不做具体原理讲解:
CPOL = 0 时,时钟在逻辑 0 处空闲:
MODE0: CPOL = 0,CPHA = 0,数据会在 SCK的 上升沿采样,下降沿变化。
MODE1: CPOL = 0,CPHA = 1,数据会在 SCK的 下降沿采样,上升沿变化。
CPOL = 1时,时钟在逻辑高电平处空闲:
MODE2: CPOL = 1,CPHA = 0,数据会在 SCK的 下降沿采样,上升沿变化。
MODE3: CPOL = 1,CPHA = 1,数据会在 SCK的 上升沿采样,下降沿变化。
/*
MODE0: CPOL = 0,CPHA = 0,数据会在 SCK的 上升沿采样,下降沿变化。
sck:
-------- --------
| | | |
--- -------
mosi:
----------
| |
---------------
miso:
----------
| |
---------------
*/
uint8_t SPI_RW_BYTE_MODE0(uint8_t data)
{
uint8_t i;
uint8_t output=0;
SCLK_SET_H;
SPIDelay;
CS_L;
SPIDelay;
for(i=0;i<8;i++)
{
SCLK_SET_L;
SPI_SEND_BIT(data); //下降沿发送
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
SCLK_SET_H;
SPI_READ_BIT(output);//上升沿采样,注意,必须先准备好数据,再才升上升沿,否则会出现第一个包不正确的情况。
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
}
SCLK_SET_L;
SPIDelay;
CS_H;
SPIDelay;
return output;
}
/*
MODE1: CPOL = 0,CPHA = 1,数据会在 SCK的 下降沿采样,上升沿变化。
sck:
-------- -------
| | |
--- -------
mosi:
--- -------
|_____________|
miso:
--- -------
|_____________|
*/
uint8_t SPI_RW_BYTE_MODE1(uint8_t data)
{
uint8_t i;
uint8_t output=0;
SCLK_SET_L;
SPIDelay;
CS_L;
SPIDelay;
for(i=0;i<8;i++)
{
SCLK_SET_H;
SPI_SEND_BIT(data); //上升沿发送
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
SCLK_SET_L;
SPI_READ_BIT(output);//下降沿采样
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
}
SCLK_SET_L;
SPIDelay;
CS_H;
SPIDelay;
return output;
}
/*
MODE2: CPOL = 1,CPHA = 0,数据会在 SCK的 下降沿采样,上升沿变化。
sck:
--- --------
| | |
-------- ---
mosi:
---------
|
----------
miso:
---------
|
----------
*/
uint8_t SPI_RW_BYTE_MODE2(uint8_t data)
{
uint8_t i;
uint8_t output=0;
SCLK_SET_L;
SPIDelay;
CS_L;
SPIDelay;
for(i=0;i<8;i++)
{
SCLK_SET_H;
SPI_SEND_BIT(data); //上升沿发送
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
SCLK_SET_L;
SPI_READ_BIT(output);//下降沿采样
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
}
SCLK_SET_H;
SPIDelay;
CS_H;
SPIDelay;
return output;
}
/*
MODE3: CPOL = 1,CPHA = 1,数据会在 SCK的 上升沿采样,下降沿变化。
sck:
--- --------
| | |
-------- ---
mosi:
--- ---
| |
---------------
miso:
--- ---
| |
---------------
*/
uint8_t SPI_RW_BYTE_MODE3(uint8_t data)
{
uint8_t i;
uint8_t output=0;
SCLK_SET_H;
SPIDelay;
CS_L;
SPIDelay;
for(i=0;i<8;i++)
{
SCLK_SET_L;
SPI_SEND_BIT(data); //下降沿发送
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
SCLK_SET_H;
SPI_READ_BIT(output); //上升沿采样
SPIDelay; //每次时钟变化之前,进行delay,确保数据稳定,对方有足够的采样时间
}
SCLK_SET_H;
SPIDelay;
CS_H;
SPIDelay;
return output;
}
5、修改串口测试程序,用于发送spi命令:
switch(aRxBuffer){
case 'a':
ret=SPI_RW_BYTE_MODE0(0x0a);
snprintf(( char *)output,sizeof(output),"mode0,ret=0x%02x\r\n",ret);
len = strlen(output);
break;
case 'b':
ret=SPI_RW_BYTE_MODE1(0x0b);
snprintf(( char *)output,sizeof(output),"mode1,ret=0x%02x\r\n",ret);
len = strlen(output);
break;
case 'c':
ret=SPI_RW_BYTE_MODE2(0x0c);
snprintf(( char *)output,sizeof(output),"mode2,ret=0x%02x\r\n",ret);
len = strlen(output);
break;
case 'd':
ret=SPI_RW_BYTE_MODE3(0x0d);
snprintf(( char *)output,sizeof(output),"mode3,ret=0x%02x\r\n",ret);
len = strlen(output);
break;
case 'e':
SPI_WRITE_BUF_MODE0(buf,sizeof(buf));
goto END;
case 'f':
SPI_WRITE_BUF_MODE1(buf,sizeof(buf));
goto END;
case 'g':
SPI_WRITE_BUF_MODE2(buf,sizeof(buf));
goto END;
case 'h':
SPI_WRITE_BUF_MODE3(buf,sizeof(buf));
goto END;
default:
snprintf(( char *)output,sizeof(output),"no supported command\r\n");
len = strlen(output);
break;
}
6、仿真测试结果:
7、可以双击spi访问器件,修改极性和相位,分别测试四种模式:
8、在上述工程基础上,加上buffer写入的方式:
void SPI_WRITE_BUF_MODE0(uint8_t *pData,int len)
{
int i,j;
uint8_t data = 0;
SCLK_SET_H;
SPIDelay;
CS_L;
SPIDelay;
for(i=0;i
9、压力测试结果:
256个字节确认收到无误!
以上是整个实验过程,相关代码和仿真设计,请到如下地址下载(没积分也是苦恼,为了能下点东西,象征性收取1个积分,请见谅!):
https://download.csdn.net/download/qq_39657229/10918641