Serial Peripheral interface 通用串行外围设备接口
是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间。
SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作。
而这里的SPI中的时钟和相位,指的就是SCLk时钟的特性,即保证主从设备两者的时钟的特性一致了,以保证两者可以正常实现SPI通讯。
Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.
上图只是对 SPI 设备间通信的一个简单的描述, 下面详细解释一下图中所示的几个组件(Module):
Synchronous Serial Port Buffer, 泛指 SPI 设备里面的内部缓冲区, 一般在物理上是以 FIFO 的形式, 保存传输过程中的临时数据;
我们知道, 在每个时钟周期内, Master 与 Slave 之间交换的数据其实都是 SPI 内部移位寄存器从 SSPBUF 里面拷贝的. 我们可以通过往 SSPBUF 对应的寄存器 (Tx-Data / Rx-Data register) 里读写数据, 间接地操控 SPI 设备内部的 SSPBUF。
例如, 在发送数据之前, 我们应该先往 Master 的 Tx-Data 寄存器写入将要发送出去的数据, 这些数据会被 Master-SSPSR 移位寄存器根据 Bus-Width 自动移入 Master-SSPBUF 里, 然后这些数据又会被 Master-SSPSR 根据 Channel-Width 从 Master-SSPBUF 中移出, 通过 Master-SDO 管脚传给 Slave-SDI 管脚, Slave-SSPSR 则把从 Slave-SDI 接收到的数据移入 Slave-SSPBUF 里. 与此同时, Slave-SSPBUF 里面的数据根据每次接收数据的大小(Channel-Width), 通过 Slave-SDO 发往 Master-SDI, Master-SSPSR 再把从 Master-SDI 接收的数据移入 Master-SSPBUF.在单次数据传输完成之后, 用户程序可以通过从 Master 设备的 Rx-Data 寄存器读取 Master 设备数据交换得到的数据。
Synchronous Serial Port Register, 泛指 SPI 设备里面的移位寄存器(Shift Regitser), 它的作用是根据设置好的数据位宽(bit-width) 把数据移入或者移出 SSPBUF;
SSPSR 是 SPI 设备内部的移位寄存器(Shift Register). 它的主要作用是根据 SPI 时钟信号状态, 往 SSPBUF 里移入或者移出数据, 每次移动的数据大小由 Bus-Width 以及 Channel-Width 所决定。
Bus-Width 的作用是指定地址总线到 Master 设备之间数据传输的单位.
例如, 我们想要往 Master 设备里面的 SSPBUF 写入 16 Byte 大小的数据: 首先, 给 Master 设备的配置寄存器设置 Bus-Width 为 Byte; 然后往 Master 设备的 Tx-Data 移位寄存器在地址总线的入口写入数据, 每次写入 1 Byte 大小的数据(使用 writeb 函数); 写完 1 Byte 数据之后, Master 设备里面的 Tx-Data 移位寄存器会自动把从地址总线传来的1 Byte 数据移入 SSPBUF 里; 上述动作一共需要重复执行 16 次。
Channel-Width 的作用是指定 Master 设备与 Slave 设备之间数据传输的单位. 与 Bus-Width 相似, Master 设备内部的移位寄存器会依据 Channel-Width 自动地把数据从 Master-SSPBUF 里通过 Master-SDO 管脚搬运到 Slave 设备里的 Slave-SDI 引脚, Slave-SSPSR 再把每次接收的数据移入 Slave-SSPBUF里.通常情况下, Bus-Width 总是会大于或等于 Channel-Width, 这样能保证不会出现因 Master 与 Slave 之间数据交换的频率比地址总线与 Master 之间的数据交换频率要快, 导致 SSPBUF 里面存放的数据为无效数据这样的情况
泛指 SPI 设备里面的控制寄存器, 可以通过配置它们来设置 SPI 总线的传输模式。
通常情况下, 我们只需要对上图所描述的四个管脚(pin) 进行编程即可控制整个 SPI 设备之间的数据通信。
Master 设备里面的 Controller 主要通过时钟信号(Clock Signal)以及片选信号(Slave Select Signal)来控制 Slave 设备. Slave 设备会一直等待, 直到接收到 Master 设备发过来的片选信号, 然后根据时钟信号来工作。
Master 设备的片选操作必须由程序所实现. 例如: 由程序把 SS/CS 管脚的时钟信号拉低电平, 完成 SPI 设备数据通信的前期工作; 当程序想让 SPI 设备结束数据通信时, 再把 SS/CS 管脚上的时钟信号拉高电平.
Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;
Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问。
Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据。
Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据。
SPI 设备在进行通信的过程中, Master 设备和 Slave 设备之间会产生一个数据链路回环(Data Loop), 就像上图所画的那样, 通过 SDO 和 SDI 管脚, SSPSR 控制数据移入移出 SSPBUF, Controller 确定 SPI 总线的通信模式, SCK 传输时钟信号。
要想搞清楚SPI的数据传输,首先要搞清楚相位和极性的概念,即SPI的极性Polarity和相位Phase。
最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
对于一个时钟周期内,有两个edge,分别称为:
(1)Leading edge=前一个边沿=第一个边沿,对于开始电压是1,
那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;
(2)Trailing edge=后一个边沿=第二个边沿,对于开始电压是1,
那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),
对于开始电压是0,那么就是1变成0的时候;
本博文采用如下用法:
CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:
模式 | 极性 相位 |
---|---|
Mode 1 | CPOL=0,CPHA=0 |
Mode 2 | CPOL=0,CPHA=1 |
Mode 3 | CPOL=1,CPHA=0 |
Mode 4 | CPOL=1,CPHA=1 |
脉冲传输前和完成后都保持在低电平状态,所以 CPOL=0,即低电平是空闲时的电平。在第一个边沿(上升沿)采样数据,第二个边沿(下降沿)输出数据,对应着CPHA=0。
脉冲传输前和完成后都保持在低电平状态,所以 CPOL=0,即低电平是空闲时的电平。在第二个边沿(下降沿)采样数据,第一个边沿(上升沿)输出数据,对应着CPHA=1。
脉冲传输前和完成后都保持在高电平状态,所以 CPOL=1,即高电平是空闲时的电平。在第一个边沿(下降沿)采样数据,第二个边沿(上升沿)输出数据,对应着CPHA=0。
脉冲传输前和完成后都保持在高电平状态,所以 CPOL=1,即高电平是空闲时的电平。在第二个边沿(上升沿)采样数据,第一个边沿(下降沿)输出数据,对应着CPHA=1。
SPI分主设备和从设备,两者通过SPI协议通讯。
而设置SPI的模式,是从设备的模式,决定了主设备的模式。
所以要先去搞懂从设备的SPI是何种模式,然后再将主设备的SPI的模式,设置和从设备相同的模式,即可正常通讯。
对于从设备的SPI是什么模式,有两种:
(1)固定的,有SPI从设备硬件决定的
SPI从设备,具体是什么模式,相关的datasheet中会有描述,需要自己去datasheet中找到相关的描述,即:
关于SPI从设备,在空闲的时候,是高电平还是低电平,即决定了CPOL是0还是1;
然后再找到关于设备是在上升沿还是下降沿去采样数据,这样就是,在定了CPOL的值的前提下,对应着可以推算出CPHA是0还是1了。
(2)可配置的,由软件自己设定
从设备也是一个SPI控制器,4种模式都支持,此时只要自己设置为某种模式即可。
然后知道了从设备的模式后,再去将SPI主设备的模式,设置为和从设备模式一样即可。
对于如何配置SPI的CPOL和CPHA的话,不多细说,多数都是直接去写对应的SPI控制器中对应寄存器中的CPOL和CPHA那两位,写0或写1即可。
SPI只有主模式和从模式之分,没有读和写的说法,因为实质上每次SPI是主从设备在交换数据。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。
SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”。 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了。
一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access)。 所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。 在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效。
因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的。
下面举一个例子帮助大家理解。
SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
假设下面的8位寄存器装的是待发送的数据10101010,上升沿发送、下降沿接收、高位先发送。
那么第一个上升沿来的时候 数据将会是sdo=1;寄存器=0101010x。下降沿到来的时候,sdi上的电平将所存到寄存器中去,那么这时寄存器=0101010sdi,这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成里一个spi时序。
举例:
假设主机和从机初始化就绪:并且主机的sbuff=0xaa,从机的sbuff=0x55,下面将分步对spi的8个时钟周期的数据情况演示一遍:假设上升沿发送数据
这样就完成了两个寄存器8位的交换,上面的上表示上升沿、下表示下降沿,sdi、sdo相对于主机而言的。
下一步就是把 上面的过程转为动画
了解了SPI协议说明之后,下面我们基于Cortex-A9架构的exynos-4412,讲解SPI控制器的使用。
本例是基于FS4412开发板,SPI控制器外接了MCP2515,MCP2515与exynos-4412的硬件连接图如下图所示。
由上图可知SPI个引脚与SOC的pin连接关系:
CS <---------> BUF_BK_LED <---------> GPC1_2
SO <---------> BUF_I2C_SDA6 <---------> GPC1_3
SI <---------> BUF_I2C_SCL6 <---------> GPC1_4
SCK <---------> BUF_GPC1_1 <---------> GPC1_1
INT <-------------------------------------------> BUF_GPX0_0
MCP2515芯片连接在4412芯片的SPI2上。
中断连接在GPX0_0上;
CS、SO、SI、SCK复用了GPIO引脚GPC1的引脚。
MCP2515输出连接SN65HVD230 CAN总线收发器,SN65HVD230是德州仪器公司生产的3.3V CAN收发器。为了节省功耗,缩小电路体积,MCP2515 CAN总线控制器的逻辑电平采用LVTTL,SN65HVD230就是与其配套的收发器。
exynos4412 scp中的串行外设接口(SPI)通过各种外设来传输串行数据。SPI包括两个8、16和32位移位寄存器,用于传输和接收数据。在SPI传输过程中,它同时传输(串行移出)和接收(串行移位)数据。
SPI在Exynos 4412 SCP和外部设备之间传输1位串行数据。
Exynos 4412 SCP中的SPI支持CPU或DMA分别同时发送或接收FIFO和双向传输数据。
SPI有两个信道,即Tx信道和Rx信道。Tx信道有来自Tx的路径
FIFO到外部设备。Rx通道有从外部设备到Rx FIFO的路径。
CPU(或DMA)必须将数据写入寄存器SPI_TX_DATA,才能将数据写入FIFO。寄存器上的数据会自动移动到Tx FIFO。要从Rx FIFO读取数据,CPU(或DMA)必须访问寄存器SPI_RX_DATA,数据会自动发送到SPI_RX_DATA寄存器。
CMU寄存器可以控制SPI的工作频率。
SPI有两种模式,即主模式和从模式。
在主模式下,生成SPICLK并将其传输到外部设备。XspiCS#是选择从机的信号,它指示在设置XspiCS时数据有效低水平。在发送或接收数据包之前,必须将XspiCS设置为低。
SPI支持对fifo的CPU访问和DMA访问。
对fifo的CPU访问和DMA访问的数据大小从8位、16位或32位数据中选择。当它选择8位数据大小时,有效位是0到7比特。用户可以定义触发阈值来引发CPU中断。端口0中每个FIFO的触发电平由从0到252字节的步进为4个字节,端口1中每个FIFO的字节从0到63字节按1个字节的步长设置。
SPI_MODE_CFG寄存器的TxDMAOn或RxDMAOn位必须设置为使用DMA访问。DMA访问支持只有单次传输和4突发传输。在Tx FIFO中,DMA请求信号是高的,直到Tx FIFO满为止。在Rx FIFO中,如果FIFO不为空,DMA请求信号高。
芯片选择XspiCS是激活的低信号。换句话说,当XspiCS输入为0时,选择芯片。
您可以自动或手动控制XspiCS。不需要改变。
手动模式
当您使用手动控制模式时,您应清除AUTO_N_MANUAL(默认值为0)。NSSOUT位控制XspiCS级别。
自动模式
使用自动控制模式时,必须将AUTO_N_MANUAL设置为1。XspiCS在数据包和自动打包。NCS_TIME_COUNT 控制XspiCS的非激活期。NSSOUT在此时不可用。
【bit:5】 软件复位的时候必须先将此位设置为1,给一个延时后再设置为0;
【bit:4】 设置SPI端口是主机还是从机,0:主机,1:从机;
【bit:2-3】设置相位和极性;
【bit:1】接收数据的通道使能位;
【bit:0】发送数据的通道使能位。
发送操作开始,如果移位寄存器空了,该值置1,通过该值判断数据是否发送出去。
因为CS、SO、SI、SCK复用了GPIO引脚GPC1的引脚。首先需要设置GPIO引脚为SPI模式。
如上所示,该寄存器设置方式如下:
GPC1.CON = (GPC1.CON & ~0xffff0) | 0x55550;//设置IO引脚为SPI模式
Step 1 设置CPOL CPHA 为 00 模式
SPI2.CH_CFG&=~((0x1<< 4)|(0x1<<3)|(0x1<< 2)|0x3);
//master mode, CPOL = 0, CPHA = 0 (Format A)
时钟的设置需要依赖锁相环(PLL)时钟产生器。
从30.2.1节可知,时钟配置需要参考CMU这一章。
由上图可知SPI 的clock源是SCLK_SPI。
从第七章搜所有的SPI
继续搜索
由此可知SPI0~2的之中受CLKMPLL_USER_T 控制,继续搜索。
而此时钟位于寄存器CLK_SRC_PERIL1的bit[27:24]。
继续搜索SPI2。
搜索到CLK_SRC_MASK_PERIL1、CLK_DIV_PERIL2。
可知寄存器CLK_SRC_MASK_PERIL1默认是打开的,所以不用设置。
CLK_DIV_PERIL2用来设置SPI2分频的,于是寄存器设置如下:
CLK_SRC_PERIL1 = (CLK_SRC_PERIL1 & ~(0xF<<24)) | 6<<24;
// 0x6: 0110 = SCLKMPLL_USER_T 800Mhz
CLK_DIV_PERIL2 = 19 <<8 | 3;//SPI_CLK = 800/(19+1)/(3+1)
void soft_reset(void)
{
SPI2.CH_CFG |= 0x1 << 5;
delay(1); //延时
SPI2.CH_CFG &= ~(0x1 << 5);
}
SPI2.CH_CFG &= ~( (0x1 << 4) | (0x1 << 3) | (0x1 << 2) | 0x3);
SPI2.MODE_CFG &= ~((0x3 << 17) | (0x3 << 29));
//BUS_WIDTH=8bit,CH_WIDTH=8bit
SPI2.CS_REG &= ~(0x1 << 1); //选择手动选择芯片
初始化搞定后,下面我们来看如何利用SPI收发数据。
收发数据流程如下:
【注意】一下代码为设置每次只读写1个byte数据,每次读写1个byte数据都要按照上述步骤操作。
void soft_reset(void)
{
SPI2.CH_CFG |= 0x1 << 5;
delay(1); //延时
SPI2.CH_CFG &= ~(0x1 << 5);
}
void slave_enable(void)
{
SPI2.CS_REG &= ~0x1; //enable salve
delay(3);
}
void send_byte(unsigned char data)
{
SPI2.CH_CFG |= 0x1; // enable Tx Channel
delay(1);
SPI2.SPI_TX_DATA = data;
while( !(SPI2.SPI_STATUS & (0x1 << 25)) );
SPI2.CH_CFG &= ~0x1; // disable Tx Channel
}
unsigned char recv_byte()
{
unsigned char data;
SPI2.CH_CFG |= 0x1 << 1; // enable Rx Channel
delay(1);
data = SPI2.SPI_RX_DATA;
delay(1);
SPI2.CH_CFG &= ~(0x1 << 1); //disable Rx Channel
return data;
}
void slave_disable(void)
{
SPI2.CS_REG |= 0x1; //disable salve
delay(1);
}
OK,到底为止,如何通过SPI收发数据,我们已经可以实现了,但是SPI往往外部回接各种各样的从设备。下面我们来看SPI如何操作从设备。
上面我们说了,fs4412开发板的SPI2外设外接了MCP2515,现在我们来分析一下MCP2515。
MCP2515详细资料,大家自行搜索 MCP2515 datasheet 《带有 SPI 接口的独立 CAN 控制器》。
MCP2515是一种独立的CAN总线通信控制器,是Microchip公司首批独立CAN解决方案的升级器件,其传输能力较Microchip公司原有CAN控制器(MCP2510)高两倍,高通信速率可达到1Mbps。MCP2515能够接收和发送标准数据帧和扩展数据帧以及远程帧,通过两个接收屏蔽寄存器和六个接收过滤寄存器滤除无关报文,从而减轻CPU负担。
MCP2515主要功能参数及电气特性如下:
MCP2515 是一款独立 CAN 控制器, 可简化需要与 CAN总线连接的应用。如上图 简要显示了 MCP2515 的结构框图。该器件主要由三个部分组成:
CAN 模块的功能是处理所有 CAN 总线上的报文接收和发送。报文发送时,首先将报文装载到正确的报文缓冲器和控制寄存器中。通过 SPI 接口设置控制寄存器中的相应位或使用发送使能引脚均可启动发送操作。通过读取相应的寄存器可以检查通讯状态和错误。 会对在 CAN总线上检测到的任何报文进行错误检查,然后与用户定义的滤波器进行匹配,以确定是否将报文移到两个接收缓冲器中的一个。
MCU通过SPI接口与该器件连接。使用标准的SPI读/写指令以及专门的 SPI 命令来读 / 写所有的寄存器。
MCP2515 设计可与许多单片机的串行外设接口( SPI)直接相连,支持 0,0 和 1,1 运行模式。外部数据和命令通过 SI 引脚传送到器件中,且数据在 SCK 时钟信号的上升沿传送进去。 MCP2515 在 SCK 的下降沿通过 SO引脚传送出去。在进行任何操作时, CS 引脚都必须保持为低电平。
我们要想操作MCP2515,只能用过SPI总线向MCP2515发送一些数据,根据规定,发送不同的数据就代表不同的操作,于是就形成了对应的指令集。
如上图所示,比如我们要执行复位操作,那么我只需要通过SPI总线写入11000000,即0xC0即可。
下面我们来详细分析如何向MCP2515发送复位命令,如何读写数据。
void reset_2515()
{
soft_reset(); //复位spi控制器
slave_enable() ; //片选从机
send_byte(0xc0); //发送复位命令
slave_disable() ; //取消片选
}
unsigned char read_byte_2515(unsigned char Addr)
{
unsigned char ret;
slave_enable();
send_byte(0x03);
send_byte(Addr);
ret = recv_byte();
slave_disable();
return(ret);
}
void write_byte_2515(unsigned char addr,unsigned char data)
{
slave_enable();
send_byte(0x02);
send_byte(addr);
send_byte(data);
slave_disable();
}
知道该如何和CAN通信,下面我们来看如何初始化CAN。
CAN的初始化步骤如下:
此外为方便测试,我们再加一步:
7. 回环模式,用于测试
void reset_2515()
write_byte_2515(0x0f, 0x80); //CANCTRL寄存器--进入配置模式 中文DATASHEET 58页
CAN总线上的所有节点都必须具有相同的标称比特率。CAN 协议采用不归零( Non Return to Zero, NRZ)编码方式,在数据流中不对时钟信号进行编码。因此,接收时钟信号必须由接收节点恢复并与发送器的时钟同步。
由于不同节点的振荡器频率和传输时间不同,接收器应具有某种能与数据传输边沿同步的锁相环( Phase Lock Loop, PLL)来同步时钟并保持这种同步。
鉴于数据采用 NRZ 编码,有必要进行位填充以确保至少每 6 位时间发生一次边沿,使数字锁相环 ( Digital Phase LockLoop, DPLL)同步。MCP2515 通过 DPLL 实现位定时。 DPLL 被配置成同输入数据同步,并为发送数据提供标称定时。 DPLL 将每一 个 位 时 间 分 割 为 由 最 小 单 位 为 时 间 份 额 ( Time Quanta, TQ)所组成的多个时间段。
在位时间帧中执行的总线定时功能,例如与本地振荡器同步、网络传输延迟补偿和采样点定位等,都是由DPLL 的可编程位定时逻辑来规定的。
CNF1
BRP<5:0> 控制波特率预分频比的设置。这些位根据OSC1 输入频率设置 TQ 的时间长度。当 BRP<5:0> =‘b000000’, TQ 最小值取 2 TOSC。通过 SJW<1:0> 选择以 TQ 计的同步跳转宽度。
CNF2
PRSEG<2:0> 位设定以 TQ 计的传播段时间长度。PHSEG1<2:0>位设定以TQ计的相位缓冲段PS1的时间长度。
CNF3
如果 CNF2.BTLMODE 位为 1,则相位缓冲段 PS2 的时间长度将由 PHSEG2<2:0> 位设定,以 TQ 计。如果BTLMODE 位为 0,则 PHSEG2<2:0> 位不起作用。
MCP2515 波特率配置以16M晶振为例, SJW 段数(1+PRSEG+PRSEG1+PRSEG2)
#define CNF1_20K 0xd3 //4 20(1+4+8+7)
#define CNF2_20K 0xfb
#define CNF3_20K 0x46
//可以设置的波特率 5K 10K 15K 20K 25K 40K 50K 80K 100K 125K 200K 400K 500K 667K 800K 1M
write_byte_2515(0x2A, CNF1_20K); //CNF1位定时配置寄器
write_byte_2515(0x29, CNF2_20K); //CNF2位定时配置寄器
write_byte_2515(0x28, CNF3_20K); //CNF3位定时配置寄器
write_byte_2515(0x2B, 0x1f); //CANINTE中断使能寄存器
write_byte_2515(0x60, 0x60); //RXB0CTRL接收缓冲器0 控制寄存器
void bit_modify_2515(unsigned char addr, unsigned char mask, unsigned char data)
{
// CS_SPI = 0 ;
slave_enable() ;
send_byte(0x05) ;
send_byte(addr) ;
send_byte(mask) ;
send_byte(data) ;
slave_disable() ;
// CS_SPI = 1 ;
}
bit_modify_2515(0x0C, 0x0f, 0x0f); //BFPCTRL_RXnBF 引脚控制寄存器和状态寄存器 中文DATASHEET 29 页
write_byte_2515(0x0f, 0x40); //CAN控制寄存器--回环模式,用于测试
用 5 个字节来装载标准和扩展标识符以及其他报文仲裁信息(见寄存器 3-3 到寄存器 3-7) 。最后 8 个字节用于装载等待发送报文的 8 个可能的数据字节 。
至少须将 TXBnSIDH、 TXBnSIDL 和 TXBnDLC 寄存器装载数据。如果报文包含数据字节,还需要对 TXBnDm寄存器进行装载。若报文采用扩展标识符,应对 TXBnEIDm 寄存器进行装载,并将 TXBnSIDL.EXIDE 位置1。
下面我们看如何向CAN的缓冲区0发送和接收数据。
can缓冲区0数据发送流程如下:
write_byte_2515(0x30, 0x03); //设置为发送最高优先级
write_byte_2515(0x31, 0xff); //发送缓冲器0标准标识符高位
write_byte_2515(0x32, 0x00); //发送缓冲器0标准标识符低位
write_byte_2515(0x35, 0x08); //发送缓冲器0数据长度码8字节
write_byte_2515(0x36+i ,tx_buff[i]); //向txb缓冲器中写入8个字节
void send_req_2515()
{
// CS_SPI = 0; //复位
soft_reset(); //复位spi控制器
slave_enable() ; //片选从机
send_byte(0x81); //发送请求命令
slave_disable() ; //取消片选
// CS_SPI=1;
}
从CAN缓冲区读取数据流程如下:
当报文传送至某一接收缓冲器时,与该接收缓冲器对应的 CANINTF.RXnIF 位将置 1。一旦缓冲器中的报文处理完毕, MCU 就必须将该位清零以接收下一条报文。该控制位提供的锁定功能确保 MCU 尚未处理完上一条报文前, MCP2515 不会将新的报文载入接收缓冲器。
如果 CANINTE.RXnIE 位被置 1,器件会在 INT 引脚产生一个中断,显示接收到报文有效。另外,如果被配置为接收缓冲器满中断引脚,与之相应的 RXnBF 引脚会被拉低。
MCP2515 有八个中断源。 CANINTE 寄存器包含了使能各中断源的中断使能位。 CANINTF 寄存器包含了各中断源的中断标志位。当发生中断时, INT 引脚将被MCP2515 拉为低电平, 并保持低电平状态直至 MCU 清除中断。中断只有在引起相应中断的条件消失后,才会被清除。
建议在对 CANINTF 寄存器中的标志位进行复位操作时,采用位修改命令而不要使用一般的写操作。这是为了避免在写命令执行过程中无意间修改了标志位,进而导致中断丢失。
应该注意的是, CANINTF 中的中断标志位是可读写位,因此在相关 CANINTE 中断使能位置 1 的前提下,对上述任一位置 1 均可使 MCU 产生中断请求。
rx_buff[i]= read_byte_2515(0x66+i);
soft_reset();
bit_modify_2515(0x2c,0x01,0x00);//修改bit 0
write_byte_2515(0x2c, 0x00);
节省篇幅,重复函数不贴了。
#define CNF1_20K 0xd3 //4 20(1+4+8+7)
#define CNF2_20K 0xfb
#define CNF3_20K 0x46
void Init_can(void)
{
reset_2515(); //复位
write_byte_2515(0x0f, 0x80); //CANCTRL寄存器--进入配置模式 中文DATASHEET 58页
//可以设置的波特率 5K 10K 15K 20K 25K 40K 50K 80K 100K 125K 200K 400K 500K 667K 800K 1M
write_byte_2515(0x2A, CNF1_20K); //CNF1位定时配置寄器 中文DATASHEET 41-42页
write_byte_2515(0x29, CNF2_20K); //CNF2位定时配置寄器 中文DATASHEET 41-42页
write_byte_2515(0x28, CNF3_20K); //CNF3位定时配置寄器 中文DATASHEET 41-43页
write_byte_2515(0x2B, 0x1f); //CANINTE中断使能寄存器 中文DATASHEET 50 页
write_byte_2515(0x60, 0x60); //RXB0CTRL接收缓冲器0 控制寄存器 中文DATASHEET 27 页
//write_byte_2515(0x70, 0x20); //接收缓冲器1控制寄存器
bit_modify_2515(0x0C, 0x0f, 0x0f); //BFPCTRL_RXnBF 引脚控制寄存器和状态寄存器 中文DATASHEET 29 页
write_byte_2515(0x0f, 0x40); //CAN控制寄存器--回环模式,用于测试
}
void send_byte(unsigned char data)
{
SPI2.CH_CFG |= 0x1; // enable Tx Channel
delay(1);
SPI2.SPI_TX_DATA = data;
while( !(SPI2.SPI_STATUS & (0x1 << 25)) );
SPI2.CH_CFG &= ~0x1; // disable Tx Channel
}
unsigned char recv_byte()
{
unsigned char data;
SPI2.CH_CFG |= 0x1 << 1; // enable Rx Channel
delay(1);
data = SPI2.SPI_RX_DATA;
delay(1);
SPI2.CH_CFG &= ~(0x1 << 1); //disable Rx Channel
return data;
}
void bit_modify_2515(unsigned char addr, unsigned char mask, unsigned char data)
{
// CS_SPI = 0 ;
slave_enable() ;
send_byte(0x05) ;
send_byte(addr) ;
send_byte(mask) ;
send_byte(data) ;
slave_disable() ;
// CS_SPI = 1 ;
}
void Can_send(unsigned char *tx_buff)
{
unsigned char i;
write_byte_2515(0x30, 0x03); //设置为发送最高优先级
write_byte_2515(0x31, 0xff); //发送缓冲器0标准标识符高位
write_byte_2515(0x32, 0x00); //发送缓冲器0标准标识符低位
write_byte_2515(0x35, 0x08); //发送缓冲器0数据长度码8字节
for(i = 0; i < 8; i++)
{
write_byte_2515(0x36+i ,tx_buff[i]); //向txb缓冲器中写入8个字节
// printf("%x ",tx_buff[i]);
}
send_req_2515();
}
unsigned char Can_receive(unsigned char *rx_buff)
{
unsigned char i,flag;
flag = read_byte_2515(0x2c); //CANINTF——中断标志寄存器
printf("flag=%x\n",flag);
// printf(" SPI2.SPI_STATUS =%x\n", SPI2.SPI_STATUS );
// soft_reset();
if (flag&0x1) //接收缓冲器0满中断标志位
{
for(i = 0; i < 16; i++)
{
rx_buff[i]= read_byte_2515(0x66+i);
// printf("%x ",rx_buff[i]);
// printf(" SPI2.SPI_STATUS =%x\n", SPI2.SPI_STATUS );
soft_reset();
}
bit_modify_2515(0x2c,0x01,0x00);
write_byte_2515(0x2c, 0x00);
if (!(rx_buff[1]&0x08)) return(1); //接收标准数据帧
}
return(0);
}
int main(void)
{
GPX2.CON = 0x1 << 28;
uart_init();
unsigned char ID[4],buff[8]; //状态字
unsigned char key;
unsigned char ret;//,j,k,ret0,ret1,ret2,ret3,ret4,ret5,ret6,ret7,ret8,ret9;
unsigned int rx_counter;
volatile int i=0;
GPC1.CON = (GPC1.CON & ~0xffff0) | 0x55550;//设置IO引脚为SPI模式
/*spi clock config*/
CLK_SRC_PERIL1 = (CLK_SRC_PERIL1 & ~(0xF<<24)) | 6<<24;// 0x6: 0110 = SCLKMPLL_USER_T 800Mhz
CLK_DIV_PERIL2 = 19 <<8 | 3;//SPI_CLK = 800/(19+1)/(3+1)
soft_reset(); // 软复位SPI控制器
SPI2.CH_CFG &= ~( (0x1 << 4) | (0x1 << 3) | (0x1 << 2) | 0x3);//master mode, CPOL = 0, CPHA = 0 (Format A)
SPI2.MODE_CFG &= ~((0x3 << 17) | (0x3 << 29)); //BUS_WIDTH=8bit,CH_WIDTH=8bit
SPI2.CS_REG &= ~(0x1 << 1); //选择手动选择芯片
mydelay_ms(10); //延时
Init_can(); //初始化MCP2515
printf("\n************ SPI CAN test!! ************\n");
while(1)
{
//Turn on led
GPX2.DAT = GPX2.DAT | 0x1 << 7;
mydelay_ms(50);
printf("\nplease input 8 bytes\n");
for(i=0;i<8;i++)
{
src[i] = getchar();
putc(src[i]);
}
printf("\n");
Can_send(src); //发送标准帧
mydelay_ms(100);
ret = Can_receive(dst); //接收CAN总线数据
printf("ret=%x\n",ret);
printf("src=");
for(i=0;i<8;i++) printf(" %x", src[i]);//将CAN总线上收到的数据发到串行口
printf("\n");
printf("dst=");
for(i=0;i<8;i++) printf(" %x",dst[6+i]); //将CAN总线上收到的数据发到串行口
printf("\n");
//Turn off
GPX2.DAT = GPX2.DAT & ~(0x1 << 7);
mydelay_ms(100);
} //while(1)
return 0;
} //main
问题
1 什么是feddback 时钟?
2 Can的缓冲寄存器组 原理