CMS32L051 SPI分享
–前言
前几天做了个项目用到了CMS32L051这款芯片,需要用SPI去读取传感器的数据并通过SPI转发给上位机。
读取传感器的SPI采用了用I/O(引脚)模拟的方式去实现的SPI通信,而与上位机的通信调用的是芯片本身硬件的SPI。
所以本文前半部分分享的是I/O模拟SPI的流程及代码,后半部分分享如何通过初始化相关寄存器来实现SPI通信。
一、SPI是什么?
SPI的详细解释我就不过多赘述了,网上的相关介绍还是非常详尽的。
我就针对四根线的具体含义解释一下(为模拟SPI铺垫一下):
- SSN(片选):从设备使能信号,由主设备控制(本质上就是主机发送下拉信号)。
- SCK(时钟):时钟信号,由主设备产生(本质上就是连续的下拉上拉信号)。
- MISO:M主机S从机 I输入O输出(数据传输的本质也是高低电平 )。
- MOSI:M主机S从机 I输入O输出(例如0x83 1000 0011 高电平为1低电平为0)。
二、I/O模拟SPI
1.SPI的本质
从我上文的铺垫可以看出,不管SSN信号、SCK时钟还是数据本质上都是高低电平的组合。这就给我们通过I/O去模拟SPI提供了事实基础。让我们来看一下SPI的时序图。
模拟SPI读取MT6815(磁性旋转编码器芯片)的流程为:
发送读取命令0x83,接收03寄存器的数据,发送读取命令0x84,接收04寄存器的数据。
让我们结合代码来看一看I/O模拟是怎么实现的
#define SPI_OUTPUT_ENABLE() GPIO_Output_Enable(&PORT->P7, 0x1C) // 0001 1100 P72 P73 P74 输出使能
#define SPI_INPUT_ENABLE() PORT_Init( PORT7, PIN4, PULLDOWN_INPUT); //P74 默认上拉输入 需要设置为下拉输入
#define SET_SPI_CSN() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) | 0x04 ) //P72 CSN 置1
#define RESET_SPI_CSN() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) & ~0x04 ) //P72 CSN 置0
#define SET_SPI_SCK() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) | 0x08 ) //P73 SCK 置1
#define RESET_SPI_SCK() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) & ~0x08 ) //P73 SCK 置0
#define SET_SPI_SDAT() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) | 0x10 ) //P74 SDAT 置1
#define RESET_SPI_SDAT() GPIO_Set_Value(&PORT->P7, GPIO_Get_Value(&PORT->P7) & ~0x10) //P74 SDAT 置0
static unsigned short s_usSPI_Data[3];
unsigned short Read_Magnetic_Encode(void) //产生clk信号,发送数据和接收数据
{
unsigned char i;
unsigned short j;
s_usSPI_Data[0] = 0; //
j =0x84; //待发送的数据
SPI_OUTPUT_ENABLE(); //P72 P73 P74 输出使能 SDAT为输出 准备发送读的命令0x83
RESET_SPI_CSN(); //P72 CSN 拉低 发送开始前线拉低片选信号
for(i = 0;i< 8;i ++) //为了不占太多行就if一句话的不加花括号了
{
RESET_SPI_SCK(); //P73 SCK 拉低
if(j&0x80) //如果待发送的是1 就把P74数据线置高
SET_SPI_SDAT(); //P74 SDAT 拉高
else //否则 就把P74数据线置低
RESET_SPI_SDAT(); //P74 SDAT 拉低
j <<= 1; //发完一位数据发下一位数据
SET_SPI_SCK(); //P73 SCK 拉高
} //发送完读的命令(0x83) 切换SDAT为输入 准备接收数据
SPI_INPUT_ENABLE(); //P74 输入使能
for(i = 0;i< 8;i ++)
{
RESET_SPI_SCK(); //P73 SCK 拉低
s_usSPI_Data[0] <<= 1;
if( (GPIO_Get_Value(&PORT->P7) & 1 << 4) != 0 ) //P74 SDAT 输入不为零
s_usSPI_Data[0] += 1;
SET_SPI_SCK(); //P73 SCK 拉高
}
SET_SPI_CSN(); //P72 CSN 拉高
SPI_OUTPUT_ENABLE(); //P72 P73 P74 输出使能 SDAT为输出 准备发送读的命令0x84
/************** 第二遍发送 接收 *************/
j = 0x84;
RESET_SPI_CSN(); //P72 CSN 拉低 发送开始前线拉低片选信号
for(i = 0;i< 8;i ++) //为了不占太多行就if一句话的不加花括号了
{
RESET_SPI_SCK(); //P73 SCK 拉低
if(j&0x80) //如果待发送的是1 就把P74数据线置高
SET_SPI_SDAT(); //P74 SDAT 拉高
else //否则 就把P74数据线置低
RESET_SPI_SDAT(); //P74 SDAT 拉低
j <<= 1; //发完一位数据发下一位数据
SET_SPI_SCK(); //P73 SCK 拉高
} //发送完读的命令(0x83) 切换SDAT为输入 准备接收数据
SPI_INPUT_ENABLE(); //P74 输入使能
for(i = 0;i< 8;i ++)
{
RESET_SPI_SCK(); //P73 SCK 拉低
s_usSPI_Data[0] <<= 1;
if( (GPIO_Get_Value(&PORT->P7) & 1 << 4) != 0 ) //P74 SDAT 输入不为零
s_usSPI_Data[0] += 1;
SET_SPI_SCK(); //P73 SCK 拉高
}
SET_SPI_CSN(); //P72 CSN 拉高
}
最后展示一下I/O模拟SPI的波形图
二、硬件SPI
1.SPI从机模式初始化
让我们翻看一下CMS32L051用户手册第13章 串行接口SPI。
13.4.3从属的发送和接收
废话不多说上代码
void SPI_SlaveInit(spi_mode_t mode)
{
//Clock Generate Control (CGC)
//PER1 0x0000041A)外设使能寄存器1 PER10 外围设备启用寄存器0
CGC->PER1 |= 0x80; // 提供SPI的输入时钟的控制 ***步骤 1***
//串行接口SPI (SPI) SPIC SPI控制寄存器 (0x0007U) /* 从SCK输入的外部时钟*/
SPI->SPIC = _0007_SPI_SLAVE_MODE | (~mode & 0x03) << 3; //***步骤 2** **步骤 3***
//SPI->SPIC = _0007_SPI_SLAVE_MODE | ( 0x03 ) << 3;
//SPIM 0x00000000) SPI模式控制寄存器
// (0x0040U) transmission/reception buffer empty interrupt (0x0004U) 16-bit data length
SPI->SPIM = _0040_SPI_RECEPTION_TRANSMISSION | _0000_SPI_MSB | _0008_SPI_BUFFER_EMPTY | _0004_SPI_LENGTH_16| _0080_SPI_START_TRG_ON; // | _0020_SPI_NSS_ENABLE | _0080_SPI_START_TRG_ON;
//***步骤 4***
PORT->SPIPCFG = 0x01; //将SPI通信兼用功能映射到 P50 P51 P16 P17
NSS_PORT_SETTING(); // P50 NSS 输入
SCK_PORT_SETTING(); // P51 SCK 输入
MOSI_PORT_SETTING(); //P16 M0SI 从机输入
MISO_PORT_SETTING(); //P17 MISO 从机输出
// 结束初始设定如果给SDRO寄存器设定发送数据, 等待主控设备的时钟
SPI->SDRO = 0x0000;
}
int main()
{
uint32_t msCnt; // count value of 1ms
SystemCoreClockUpdata();
msCnt = SystemCoreClock /1000 ;
SysTick_Config(msCnt);
}
int main()
{
uint32_t msCnt; // count value of 1ms
//Systick setting 相关性不强的系统配置按下不表
SPI_SlaveInit(SPI_MODE_2); //SPI初始化 选模式二 即CPOL=1(极性),CPHA=0(相位)
//关于极性和相位的具体含义大家可以自行搜索
//我参考的是这篇文章 https://www.sohu.com/a/157067231_505803
GPIO_Output_Enable(&PORT->P1,GPIO_Get_Value(&PORT->P1) | 0x04 ); //P12 输出使能
GPIO_Set_Value(&PORT->P1,GPIO_Get_Value(&PORT->P1) | 0x04 ); //P12置高 点灯
while(1)
{
if((GPIO_Get_Value(&PORT->P5) == 0x02) //等待CS P50 下拉信号
{
GPIO_Set_Value( &PORT->P1, GPIO_Get_Value(&PORT->P1) & ~0x80 ); //P17
//拉低(MISO) 这也是我被坑的最惨地方 在CS下拉信号之后竟然还需要给MISO一个下触发信号
//这还是我乱试试出来的 完全不合逻辑的操作折磨了我一个星期
while( (GPIO_Get_Value(&PORT->P5) & 0x01 )== 0x00)//CS 下拉信号持续时间
{ //主机在拿取 SPI->SDRO(发送缓冲寄存器) 中的数据
}
if((GPIO_Get_Value(&PORT->P5) == 0x03) //CS 下拉信号结束 从机数据发送完毕
{
SPI->SDRO = Read_Magnetic_Encode();
//将模拟SPI接收的数据赋给SDRO 等待下一次CS的下拉信号发送给主机
}
}//if
}//while
}//main
总结
- 相对于硬件SPI , I/O模拟SPI的优势在于不需要了解芯片SPI的相关配置寄存器 。只要求掌握基础的GPIO输入与输出 和 SPI时序有一定的了解,在有现成的 时序逻辑代码 后移植起来非常的轻松。
- 相比于I/O模拟SPI 硬件SPI的优势在于:
1 输出的波形比较标准,抗干扰能力相较于I/O模拟SPI更强;
2只需要配置好相关的寄存器,数据的收发将会无比轻松,只需要 读写 相关的 发送(SDRO)接收(SDRI)寄存器 。难点在于SPI的初始化配置。
项目源码链接
阿里云盘:「6816磁性编码器项目」https://www.aliyundrive.com/s/4ogh6XGsAUt 提取码: 4m8c
百度网盘:链接:https://pan.baidu.com/s/1gjs7E7tujoMb6NhcZdmlww 提取码:accg