如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78979855
一、SPI简介
参看:SPI详解
SPI(serial peripheral interface,串行外围设备接口)总线技术是 Motorola 公司推出的一种同步串行接口。它用于CPU与各种外围器件进行全双工、同步串行通讯。它只需四条线就可以完成 MCU 与各种外围器件的通讯,这四条线是:串行时钟线(CSK)、主机输入->从机输出数据线(MISO)、主机输出<-从机输入数据线(MOSI)、低电平有效从机选择线CS。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出(高位在前),同时从输入引脚(MISO)接收的数据逐位移到移位寄存器(高位在前)。发送一个字节后,从另一个外围器件接收的字节数据进入移位寄存器中。即完成一个字节数据传输的实质是两个器件寄存器内容的交换。主SPI的时钟信号(SCK)使传输同步。
二、SPI 主、从硬件连接图
参看:SPI -- 维基百科
SPI是用来跟各种外设,如:
传感器:温度,压力,ADC,触摸屏,视频游戏控制器
控制设备:音频编解码器,数字电位器,DAC
相机镜头:佳能EF镜头卡口
通讯:以太网,USB,USART,CAN,IEEE 802.15.4,IEEE 802.11,手持视频游戏
内存:闪存和EEPROM
实时时钟
LCD,有时甚至用于管理的图像数据
任何MMC或SD卡(包括SDIO变体[6] )
对于高性能系统中,FPGA中有时使用SPI接口作为从一台主机,作为主传感器,或用于用于引导如果它们是基于SRAM的闪存。
查看芯片手册和原理图 DM368 有 5 组 SPI 总线,每组 3 个片选;Hi3516A 有 2 组 SPI 总线,每组 3 个片选。
SPI总线一个主机接多个从机连接方法:
典型的SPI总线:主站和三个独立的从站
在独立从配置,存在用于每个从一个独立的片选线。电源和芯片选择线之间的上拉电阻强烈建议为每个独立的装置,以减少设备之间的串扰。[3]这是通常使用SPI的方式。因为从站的MISO引脚连接在一起时,它们需要是三态销(高,低或高阻抗)。
菊花链式SPI总线:主站和协同从站
实现SPI一些产品可在被连接的菊花链配置中,第一从输出被连接到第二从输入等每个从属的SPI端口被设计第二组时钟的过程中发出脉冲的精确副本数据它的第一组时钟脉冲的期间接收到的。整个链充当通信移位寄存器 ; 菊花链通常与移位寄存器完成通过SPI提供的输入或输出的银行。这种特征仅需要从主,而不是为每个从一个单独的SS线的单个SS线。可以与SPI需要菊花链配置潜在互操作的其它应用包括SGPIO,JTAG,[5]和双线接口。
三、SPI 四种工作模式
(1)工作模式
SPI 有四种工作模式,各个工作模式的不同在于 SCLK 不同, 具体工作由 CPOL,CPHA 决定。
CPOL: (Clock Polarity),时钟极性:
当CPOL为0时,时钟空闲时电平为低;
当CPOL为1时,时钟空闲时电平为高;
CPHA:(Clock Phase),时钟相位:
当CPHA为0时,时钟周期的上升沿采集数据,时钟周期的下降沿输出数据;
当CPHA为1时,时钟周期的下降沿采集数据,时钟周期的上升沿输出数据;
CPOL和CPHA,分别都可以是0或时1,对应了四种组合。
四种工作方式时序分别为:
(2)辨识技巧
上面讲到了有四种工作模式,该如何确认自己的设备使用的是哪种工作模式呢?
以GV7601SDI 解码芯片为例:
1、先确认从机需求的 SCLK 极性,不工作时是在低电位还是高电位,由此确认 CPOL 为 0 或 1
时钟空闲时电平为低,得出:CPOL 为 0
2、再由slave芯片 datasheet 中的时序图确认 slave 芯片是在 SCLK 的下降沿采集数据,还是在SCLK的上升沿
翻译一下:
在读取序列(命令字R / W位设置为高电平)期间,将传输串行数据或先接收MSB,与串行时钟SCLK的上升沿同步。芯片选择(CS)信号必须在第一个之前设置为低至少1.5ns(图4-62中的t0)时钟边缘以确保正确的操作。串行输出(SDOUT)的第一位(MSB)在读命令的最后一个下降SCLK沿之后是可用的(图4-63中的t5)字,剩余的位在SCLK的负边沿输出。
注意:当多个设备连接到GSPI链时,只能有一个CS在读取序列期间被断言。
在写序列(命令字R / W位设置为低电平)期间,等待状态为37.1ns(t4 in命令字和以下数据字之间需要图4-62)。这个在连续的命令字/数据字之间也必须保持等待状态写序列。当选择自动增量模式(AutoInc = 1)时,等待状态必须在初始命令之后的连续数据字之间保持字/数据字序列。在写序列期间,所有命令和随后的数据字输入到SDIN引脚输出在SDOUT引脚不变。当几个设备连接到GSPI链,数据可以同时写入所有CS设备低。
得出:CPHA为 0
四、优缺点
优点[ 编辑]
- 在这个协议的默认版本全双工通信。
- 推挽式驱动器(而不是开漏)提供良好的信号完整性和高速
- 更高的吞吐量比I²C或SMBus的。不限于任何最大时钟速度,从而使潜在的高速
- 完整的协议的灵活性传输的比特
- 不限于8位字
- 邮件大小,内容和目的任意选择
- 极其简单的硬件接口
- 仅使用4上的IC封装引脚,和电线中的电路板布局或连接器,比并行接口少得多的
- 在每个装置最多一个唯一的总线信号(芯片选择); 所有其他人共享
- 信号单向允许简单的电隔离
- 简单的软件实现
缺点[ 编辑]
- 需要在IC封装大于销I²C,即使是在三线变种
- 没有带寻址; 外的带芯片选择信号需要在共享总线
- 没有硬件流量控制由从属(但主机可以延迟下一个时钟边沿以减慢传输速率)
- 没有硬件从确认(主可以传输到任何地方和不知道它)
- 通常只支持一个主设备(取决于设备的硬件实现)
- 没有错误检查协议被定义
- 如果没有一个正式的标准,验证一致性是不可能的
- 相比仅处理短距离RS-232,RS-485,或CAN总线。(其距离可以使用收发器等来扩展RS-422)
- 许多现有的变化,因此很难找到发展的工具,如支持这些变化的主机适配器
- SPI不支持热插拔(动态添加节点)。
- 中断必须要么与出的带外信号来实现,或通过使用定期轮询类似于USB 1.1和2.0可以伪造
- 像一些变体多I / O SPI和三线串行总线定义见下文是半双工。
五、单片机上 SPI 通信
参看:SOFTWARE SPI EXAMPLES FOR THE C8051F30X FAMILY
//-----------------------------------------------------------------------------
// SPI_defs.h
//-----------------------------------------------------------------------------
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 7 DEC 01
//
// This file defines the pins used for the SPI device.
// The SPI device is mapped to pins P0.0 - P0.3, but can be modified to map to
// any of the available GPIO pins on the device.
//
#ifndef SPI_DEFS
#define SPI_DEFS
sbit MOSI = P0^0; // Master Out / Slave In (output)
sbit MISO = P0^1; // Master In / Slave Out (input)
sbit SCK = P0^2; // Serial Clock (output)
sbit NSS = P0^3; // Slave Select (output to chip select)
#endif
工作模式为:(0,0)
//-----------------------------------------------------------------------------
// SPI_MODE0.c
//-----------------------------------------------------------------------------
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This file contains a ‘C’ Implementation of a Mode 0 Master SPI device.
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//
#include <c8051f300.h> // SFR declarations
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// SPI_Transfer
//-----------------------------------------------------------------------------
//
// Simultaneously transmits and receives one byte <SPI_byte> using
// the SPI protocol. SCK is idle-low, and bits are latched on SCK rising.
//
// Timing for this routine is as follows:
//
// Parameter Clock Cycles
// MOSI valid to SCK rising edge 6
// SCK rising to MISO latched 2
// SCK falling to MOSI valid 7
// SCK high time 8
// SCK low time 13
char SPI_Transfer (char SPI_byte)
{
unsigned char SPI_count; // counter for SPI transaction
for (SPI_count = 8; SPI_count > 0; SPI_count--) // single byte SPI loop
{
MOSI = SPI_byte & 0x80; // put current outgoing bit on MOSI
SPI_byte = SPI_byte << 1; // shift next bit into MSB
SCK = 0x01; // set SCK high
SPI_byte |= MISO; // capture current bit on MISO
SCK = 0x00; // set SCK low
}
return (SPI_byte);
} // END SPI_Transfer
工作模式为(0,1)
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This file contains a ‘C’ Implementation of a Mode 1 Master SPI device.
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//
#include <c8051f300.h> // SFR declarations
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// SPI_Transfer
//-----------------------------------------------------------------------------
//
// Simultaneously transmits and receives one byte <SPI_byte> using
// the SPI protocol. SCK is idle-low, and bits are latched on SCK falling.
//
// Timing for this routine is as follows:
//
// Parameter Clock Cycles
// SCK rising edge to MOSI valid 4
// MOSI valid to SCK falling edge 6
// SCK falling to MISO latch 2
// SCK high time 10
// SCK low time 11
char SPI_Transfer (char SPI_byte)
{
unsigned char SPI_count; // counter for SPI transaction
for (SPI_count = 8; SPI_count > 0; SPI_count--) // single byte SPI loop
{
SCK = 0x01; // set SCK high
MOSI = SPI_byte & 0x80; // put current outgoing bit on MOSI
SPI_byte = SPI_byte << 1; // shift next bit into MSB
SCK = 0x00; // set SCK low
SPI_byte |= MISO; // capture current bit on MISO
}
return (SPI_byte);
} // END SPI_Transfer
工作模式为(1,0)
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This file contains a ‘C’ Implementation of a Mode 2 Master SPI device.
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//
#include <c8051f300.h> // SFR declarations
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// SPI_Transfer
//-----------------------------------------------------------------------------
//
// Simultaneously transmits and receives one byte <SPI_byte> using
// the SPI protocol. SCK is idle-high, and bits are latched on SCK falling.
//
// Timing for this routine is as follows:
//
// Parameter Clock Cycles
// MOSI valid to SCK falling edge 6
// SCK falling to MISO latched 2
// SCK rising to MOSI valid 7
// SCK low time 8
// SCK high time 13
char SPI_Transfer (char SPI_byte)
{
unsigned char SPI_count; // counter for SPI transaction
for (SPI_count = 8; SPI_count > 0; SPI_count--) // single byte SPI loop
{
MOSI = SPI_byte & 0x80; // put current outgoing bit on MOSI
SPI_byte = SPI_byte << 1; // shift next bit into MSB
SCK = 0x00; // set SCK low
SPI_byte |= MISO; // capture current bit on MISO
SCK = 0x01; // set SCK high
}
return (SPI_byte);
} // END SPI_Transfer
工作模式为(1,1)
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This file contains a ‘C’ Implementation of a Mode 3 Master SPI device.
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//
#include <c8051f300.h> // SFR declarations
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// SPI_Transfer
//-----------------------------------------------------------------------------
//
// Simultaneously transmits and receives one byte <SPI_byte> using
// the SPI protocol. SCK is idle-high, and bits are latched on SCK rising.
//
// Timing for this routine is as follows:
//
// Parameter Clock Cycles
// SCK falling edge to MOSI valid 4
// MOSI valid to SCK rising edge 6
// SCK rising to MISO latch 2
// SCK low time 10
// SCK high time 11
char SPI_Transfer (char SPI_byte)
{
unsigned char SPI_count; // counter for SPI transaction
for (SPI_count = 8; SPI_count > 0; SPI_count--) // single byte SPI loop
{
SCK = 0x00; // set SCK low
MOSI = SPI_byte & 0x80; // put current outgoing bit on MOSI
SPI_byte = SPI_byte << 1; // shift next bit into MSB
SCK = 0x01; // set SCK high
SPI_byte |= MISO; // capture current bit on MISO
}
return (SPI_byte);
} // END SPI_Transfer
测试程序:
//-----------------------------------------------------------------------------
// SPI_F300_Test.c
//-----------------------------------------------------------------------------
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This program demonstrates how a collection of SPI master
// routines for the 8051F30x processors can be used in a C program.
//
// This program sets up the GPIO pins on the C8051F30x device for the correct
// functionality, then uses the SPI_Transfer function to send and receive
// information through the SPI pins. As information is sent, the progress of
// the program is sent out through the UART to be monitored on a connected
// terminal program.
//
// For this code to be functional, *one* of the following files should also be
// compiled or assembled, and the resulting object file must be linked to the
// object file produced from this file:
//
// SPI_MODE0.c Mode 0 SPI Master Implementation in C
// SPI_MODE0.asm Mode 0 SPI Master Implementation in Assembly
// SPI_MODE1.c Mode 1 SPI Master Implementation in C
// SPI_MODE1.asm Mode 1 SPI Master Implementation in Assembly
// SPI_MODE2.c Mode 2 SPI Master Implementation in C
// SPI_MODE2.asm Mode 2 SPI Master Implementation in Assembly
// SPI_MODE3.c Mode 3 SPI Master Implementation in C
// SPI_MODE3.asm Mode 3 SPI Master Implementation in Assembly
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <c8051f300.h> // SFR declarations
#include <stdio.h> // Standard I/O
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// 16-bit SFR Definitions for ‘F30x
//-----------------------------------------------------------------------------
sfr16 DP = 0x82; // data pointer
sfr16 TMR2RL = 0xca; // Timer2 reload value
sfr16 TMR2 = 0xcc; // Timer2 counter
sfr16 PCA0CP1 = 0xe9; // PCA0 Module 1 Capture/Compare
sfr16 PCA0CP2 = 0xeb; // PCA0 Module 2 Capture/Compare
sfr16 PCA0 = 0xf9; // PCA0 counter
sfr16 PCA0CP0 = 0xfb; // PCA0 Module 0 Capture/Compare
//-----------------------------------------------------------------------------
// Global CONSTANTS
//-----------------------------------------------------------------------------
#define SYSCLK 24500000 // SYSCLK frequency in Hz
#define BAUDRATE 115200 // Baud rate of UART in bps
//-----------------------------------------------------------------------------
// Function PROTOTYPES
//-----------------------------------------------------------------------------
void PORT_Init (void); // Port I/O configuration
void SYSCLK_Init (void); // SYSCLK Initialization
void UART0_Init (void); // UART0 Initialization
extern char SPI_Transfer (char); // SPI Transfer routine
//-----------------------------------------------------------------------------
// Global VARIABLES
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// MAIN Routine
//-----------------------------------------------------------------------------
void main (void) {
unsigned char test_counter, SPI_return; // used to test SPI routine
// Disable Watchdog timer
PCA0MD &= ~0x40; // WDTE = 0 (clear watchdog timer
// enable)
SYSCLK_Init (); // initialize oscillator
PORT_Init (); // initialize ports and GPIO
UART0_Init (); // initialize UART0
EA = 1; // enable global interrupts
while (1)
{
for (test_counter = 0; test_counter <= 0xFF; test_counter++)
{
NSS = 0x00; // select SPI Slave device
SPI_return = SPI_Transfer(test_counter); // send/receive SPI byte
NSS = 0x01; // de-select SPI Slave device
printf(“\nSPI Out = 0x%02X, SPI In = 0x%02X”, (unsigned)test_counter,
(unsigned)SPI_return);
// send SPI data out to UART
// for verification purposes
}
}
}
//-----------------------------------------------------------------------------
// Initialization Subroutines
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// PORT_Init
//-----------------------------------------------------------------------------
//
// Configure the Crossbar and GPIO ports.
// P0.0 - MOSI (push-pull)
// P0.1 - MISO
// P0.2 - SCK (push-pull)
// P0.3 - NSS (push-pull)
// P0.4 - UART TX (push-pull)
// P0.5 - UART RX
// P0.6 -
// P0.7 -
//
void PORT_Init (void)
{
XBR0 = 0x0F; // skip SPI pins in XBAR
XBR1 = 0x03; // UART0 TX and RX pins enabled
XBR2 = 0x40; // Enable crossbar and weak pull-ups
P0MDOUT |= 0x1D; // enable TX0, MOSI, SCK, and NSS as
// push-pull outputs
}
//-----------------------------------------------------------------------------
// SYSCLK_Init
//-----------------------------------------------------------------------------
//
// This routine initializes the system clock to use the internal 24.5 MHz clock
// as its clock source.
//
void SYSCLK_Init (void)
{
OSCICN = 0x07; // select internal oscillator as SYSCLK
// source
}
//-----------------------------------------------------------------------------
// UART0_Init
//-----------------------------------------------------------------------------
//
// Configure the UART0 using Timer1, for <BAUDRATE> and 8-N-1.
//
void UART0_Init (void)
{
SCON0 = 0x10; // SCON0: 8-bit variable bit rate
// level of STOP bit is ignored
// RX enabled
// ninth bits are zeros
// clear RI0 and TI0 bits
if (SYSCLK/BAUDRATE/2/256 < 1)
{
TH1 = -(SYSCLK/BAUDRATE/2);
CKCON &= ~0x13;
CKCON |= 0x10; // T1M = 1; SCA1:0 = xx
}
else if (SYSCLK/BAUDRATE/2/256 < 4)
{
TH1 = -(SYSCLK/BAUDRATE/2/4);
CKCON &= ~0x13;
CKCON |= 0x01; // T1M = 0; SCA1:0 = 01
}
else if (SYSCLK/BAUDRATE/2/256 < 12)
{
TH1 = -(SYSCLK/BAUDRATE/2/12);
CKCON &= ~0x13; // T1M = 0; SCA1:0 = 00
}
else
{
TH1 = -(SYSCLK/BAUDRATE/2/48);
CKCON &= ~0x13;
CKCON |= 0x02; // T1M = 0; SCA1:0 = 10
}
TL1 = 0xff; // set Timer1 to overflow immediately
TMOD |= 0x20; // TMOD: timer 1 in 8-bit autoreload
TMOD &= ~0xD0; // mode
TR1 = 1; // START Timer1
TI0 = 1; // Indicate TX0 ready
}
SPI程序:
//-----------------------------------------------------------------------------
// SPI_EE_F30x.c
//-----------------------------------------------------------------------------
// Copyright 2001 Cygnal Integrated Products, Inc.
//
// AUTH: BD
// DATE: 14 DEC 01
//
// This program demonstrates how a collection of SPI master routines for the
// 8051F30x devices can be used in a C program.
//
// In this example, a Microchip 25LC320 4k X 8 Serial EEPROM is interfaced to a
// SPI master device implemented in the C8051F30x. The EEPROM is written with
// two test patterns: 1) all locations are 0xFF and 2) each location is written
// with the LSB of the corresponding address.
// The EEPROM contents are then verified with the test patterns. If the test
// patterns are verified with no errors, the LED blinks on operation completion.
// Otherwise, the LED stays off. Progress can also be monitored by a terminal
// connected to UART0 operating at 115.2kbps.
//
// For this code to be functional, *one* of the following files should also be
// compiled or assembled, and the resulting object file must be linked to the
// object file produced from this code:
//
// SPI_MODE0.c Mode 0 SPI Master Implementation in C
// SPI_MODE0.asm Mode 0 SPI Master Implementation in Assembly
// SPI_MODE3.c Mode 3 SPI Master Implementation in C
// SPI_MODE3.asm Mode 3 SPI Master Implementation in Assembly
//
// This EEPROM’s serial port will only operate with a Mode 0 or Mode 3
// SPI configuration.
//
// Target: C8051F30x
// Tool chain: KEIL C51 6.03 / KEIL EVAL C51
//
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <c8051f300.h> // SFR declarations
#include <stdio.h> // Standard I/O
#include “SPI_defs.h” // SPI port definitions
//-----------------------------------------------------------------------------
// 16-bit SFR Definitions for ‘F30x
//-----------------------------------------------------------------------------
sfr16 DP = 0x82; // data pointer
sfr16 TMR2RL = 0xca; // Timer2 reload value
sfr16 TMR2 = 0xcc; // Timer2 counter
sfr16 PCA0CP1 = 0xe9; // PCA0 Module 1 Capture/Compare
sfr16 PCA0CP2 = 0xeb; // PCA0 Module 2 Capture/Compare
sfr16 PCA0 = 0xf9; // PCA0 counter
sfr16 PCA0CP0 = 0xfb; // PCA0 Module 0 Capture/Compare
//-----------------------------------------------------------------------------
// Global CONSTANTS
//-----------------------------------------------------------------------------
#define SYSCLK 24500000 // SYSCLK frequency in Hz
#define BAUDRATE 115200 // Baud rate of UART in bps
#define EE_SIZE 4096 // EEPROM size in bytes
#define EE_READ 0x03 // EEPROM Read command
#define EE_WRITE 0x02 // EEPROM Write command
#define EE_WRDI 0x04 // EEPROM Write disable command
#define EE_WREN 0x06 // EEPROM Write enable command
#define EE_RDSR 0x05 // EEPROM Read status register
#define EE_WRSR 0x01 // EEPROM Write status register
sbit LED = P0^6; // LED Indicator
//-----------------------------------------------------------------------------
// Function PROTOTYPES
//-----------------------------------------------------------------------------
void PORT_Init (void); // Port I/O configuration
void SYSCLK_Init (void); // SYSCLK Initialization
void UART0_Init (void); // UART0 Initialization
extern char SPI_Transfer (char); // SPI Transfer routine
void Timer0_ms (unsigned ms);
void Timer0_us (unsigned us);
unsigned char EE_Read (unsigned Addr);
void EE_Write (unsigned Addr, unsigned char value);
//-----------------------------------------------------------------------------
// Global VARIABLES
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// MAIN Routine
//-----------------------------------------------------------------------------
void main (void) {
unsigned EE_Addr; // address of EEPROM byte
unsigned char test_byte;
// Disable Watchdog timer
PCA0MD &= ~0x40; // WDTE = 0 (clear watchdog timer
// enable)
SYSCLK_Init (); // initialize oscillator
PORT_Init (); // initialize ports and GPIO
UART0_Init (); // initialize UART0
EA = 1; // enable global interrupts
SCK = 0;
// fill EEPROM with 0xFF’s
LED = 1;
for (EE_Addr = 0; EE_Addr < EE_SIZE; EE_Addr++)
{
test_byte = 0xff;
EE_Write (EE_Addr, test_byte);
// print status to UART0
if ((EE_Addr % 16) == 0)
{
printf (“\nwriting 0x%04x: %02x “, EE_Addr, (unsigned) test_byte);
}
else
{
printf (“%02x “, (unsigned) test_byte);
}
}
// verify EEPROM with 0xFF’s
LED = 0;
for (EE_Addr = 0; EE_Addr < EE_SIZE; EE_Addr++)
{
test_byte = EE_Read (EE_Addr);
// print status to UART0
if ((EE_Addr % 16) == 0)
{
printf (“\nverifying 0x%04x: %02x “, EE_Addr, (unsigned) test_byte);
}
else
{
printf (“%02x “, (unsigned) test_byte);
}
if (test_byte != 0xFF)
{
printf (“Error at %u\n”, EE_Addr);
while (1); // stop here on error
}
}
// fill EEPROM memory with LSB of EEPROM address.
LED = 1;
for (EE_Addr = 0; EE_Addr < EE_SIZE; EE_Addr++)
{
test_byte = EE_Addr & 0xff;
EE_Write (EE_Addr, test_byte);
// print status to UART0
if ((EE_Addr % 16) == 0)
{
printf (“\nwriting 0x%04x: %02x “, EE_Addr, (unsigned) test_byte);
}
else
{
printf (“%02x “, (unsigned) test_byte);
}
}
// verify EEPROM memory with LSB of EEPROM address
LED = 0;
for (EE_Addr = 0; EE_Addr < EE_SIZE; EE_Addr++)
{
test_byte = EE_Read (EE_Addr);
// print status to UART0
if ((EE_Addr % 16) == 0)
{
printf (“\nverifying 0x%04x: %02x “, EE_Addr, (unsigned) test_byte);
}
else
{
printf (“%02x “, (unsigned) test_byte);
}
if (test_byte != (EE_Addr & 0xFF))
{
printf (“Error at %u\n”, EE_Addr);
while (1); // stop here on error
}
}
while (1)
{ // Flash LED when done
Timer0_ms (100);
LED = ~LED;
}
}
//-----------------------------------------------------------------------------
// Subroutines
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Initialization Subroutines
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// PORT_Init
//-----------------------------------------------------------------------------
//
// Configure the Crossbar and GPIO ports.
// P0.0 - MOSI (push-pull)
// P0.1 - MISO
// P0.2 - SCK (push-pull)
// P0.3 - NSS (push-pull)
// P0.4 - UART TX (push-pull)
// P0.5 - UART RX
// P0.6 - LED
// P0.7 -
//
void PORT_Init (void)
{
XBR0 = 0x0F; // skip SPI pins in XBAR
XBR1 = 0x03; // UART0 TX and RX pins enabled
XBR2 = 0x40; // Enable crossbar and weak pull-ups
P0MDOUT |= 0x5D; // enable TX0, MOSI, SCK, LED and NSS as
// push-pull outputs
}
//-----------------------------------------------------------------------------
// SYSCLK_Init
//-----------------------------------------------------------------------------
//
// This routine initializes the system clock to use the internal 24.5 MHz clock
// as its clock source.
//
void SYSCLK_Init (void)
{
OSCICN = 0x07; // select internal oscillator as SYSCLK
// source
}
//-----------------------------------------------------------------------------
// UART0_Init
//-----------------------------------------------------------------------------
//
// Configure the UART0 using Timer1, for <BAUDRATE> and 8-N-1.
//
void UART0_Init (void)
{
SCON0 = 0x10; // SCON0: 8-bit variable bit rate
// level of STOP bit is ignored
// RX enabled
// ninth bits are zeros
// clear RI0 and TI0 bits
if (SYSCLK/BAUDRATE/2/256 < 1)
{
TH1 = -(SYSCLK/BAUDRATE/2);
CKCON &= ~0x13;
CKCON |= 0x10; // T1M = 1; SCA1:0 = xx
}
else if (SYSCLK/BAUDRATE/2/256 < 4)
{
TH1 = -(SYSCLK/BAUDRATE/2/4);
CKCON &= ~0x13;
CKCON |= 0x01; // T1M = 0; SCA1:0 = 01
}
else if (SYSCLK/BAUDRATE/2/256 < 12)
{
TH1 = -(SYSCLK/BAUDRATE/2/12);
CKCON &= ~0x13; // T1M = 0; SCA1:0 = 00
}
else
{
TH1 = -(SYSCLK/BAUDRATE/2/48);
CKCON &= ~0x13;
CKCON |= 0x02; // T1M = 0; SCA1:0 = 10
}
TL1 = 0xff; // set Timer1 to overflow immediately
TMOD |= 0x20; // TMOD: timer 1 in 8-bit autoreload
TMOD &= ~0xD0; // mode
TR1 = 1; // START Timer1
TI0 = 1; // Indicate TX0 ready
}
//-----------------------------------------------------------------------------
// Timer0_ms
//-----------------------------------------------------------------------------
//
// Configure Timer0 to delay <ms> milliseconds before returning.
//
void Timer0_ms (unsigned ms)
{
unsigned i; // millisecond counter
TCON &= ~0x30; // STOP Timer0 and clear overflow flag
TMOD &= ~0x0f; // configure Timer0 to 16-bit mode
TMOD |= 0x01;
CKCON |= 0x08; // Timer0 counts SYSCLKs
for (i = 0; i < ms; i++) // count milliseconds
{
TR0 = 0; // STOP Timer0
TH0 = (-SYSCLK/1000) >> 8; // set Timer0 to overflow in 1ms
TL0 = -SYSCLK/1000;
TR0 = 1; // START Timer0
while (TF0 == 0); // wait for overflow
TF0 = 0; // clear overflow indicator
}
}
//-----------------------------------------------------------------------------
// Timer0_us
//-----------------------------------------------------------------------------
//
// Configure Timer0 to delay <us> microseconds before returning.
//
void Timer0_us (unsigned us)
{
unsigned i; // millisecond counter
TCON &= ~0x30; // STOP Timer0 and clear overflow flag
TMOD &= ~0x0f; // configure Timer0 to 16-bit mode
TMOD |= 0x01;
CKCON |= 0x08; // Timer0 counts SYSCLKs
for (i = 0; i < us; i++) { // count microseconds
TR0 = 0; // STOP Timer0
TH0 = (-SYSCLK/1000000) >> 8; // set Timer0 to overflow in 1us
TL0 = -SYSCLK/1000000;
TR0 = 1; // START Timer0
while (TF0 == 0); // wait for overflow
TF0 = 0; // clear overflow indicator
}
}
//-----------------------------------------------------------------------------
// EE_Read
//-----------------------------------------------------------------------------
//
// This routine reads and returns a single EEPROM byte whose address is
// given in <Addr>.
//
unsigned char EE_Read (unsigned Addr)
{
unsigned char retval; // value to return
NSS = 0; // select EEPROM
Timer0_us (1); // wait at least 250ns (CS setup time)
// transmit READ opcode
retval = SPI_Transfer(EE_READ);
// transmit Address MSB-first
retval = SPI_Transfer((Addr & 0xFF00) >> 8); // transmit MSB of address
retval = SPI_Transfer((Addr & 0x00FF)); // transmit LSB of address
// initiate dummy transmit to read data
retval = SPI_Transfer(0x00);
Timer0_us (1); // wait at least 250ns (CS hold time)
NSS = 1; // de-select EEPROM
Timer0_us (1); // wait at least 500ns (CS disable time)
return retval;
}
//-----------------------------------------------------------------------------
// EE_Write
//-----------------------------------------------------------------------------
//
// This routine writes a single EEPROM byte <value> to address <Addr>.
//
void EE_Write (unsigned Addr, unsigned char value)
{
unsigned char retval; // return value from SPI
NSS = 0; // select EEPROM
Timer0_us (1); // wait at least 250ns (CS setup time)
// transmit WREN (Write Enable) opcode
retval = SPI_Transfer(EE_WREN);
Timer0_us (1); // wait at least 250ns (CS hold time)
NSS = 1; // de-select EEPROM to set WREN latch
Timer0_us (1); // wait at least 500ns (CS disable
// time)
NSS = 0; // select EEPROM
Timer0_us (1); // wait at least 250ns (CS setup time)
// transmit WRITE opcode
retval = SPI_Transfer(EE_WRITE);
// transmit Address MSB-first
retval = SPI_Transfer((Addr & 0xFF00) >> 8); // transmit MSB of address
retval = SPI_Transfer((Addr & 0x00FF)); // transmit LSB of address
// transmit data
retval = SPI_Transfer(value);
Timer0_us (1); // wait at least 250ns (CS hold time)
NSS = 1; // deselect EEPROM (initiate EEPROM
// write cycle)
// now poll Read Status Register (RDSR) for Write operation complete
do {
Timer0_us (1); // wait at least 500ns (CS disable
// time)
NSS = 0; // select EEPROM to begin polling
Timer0_us (1); // wait at least 250ns (CS setup time)
retval = SPI_Transfer(EE_RDSR);
retval = SPI_Transfer(0x00);
Timer0_us (1); // wait at least 250ns (CS hold
// time)
NSS = 1; // de-select EEPROM
} while (retval & 0x01); // poll until WIP (Write In
// Progress) bit goes to ‘0’
Timer0_us (1); // wait at least 500ns (CS disable
// time)
}
再有参看:
参看: 基于51单片机的SPI总线
//实例:基于DS1302的日历时钟
#include<reg51.h> //包含单片机寄存器的头文件
#include<intrins.h> //包含_nop_()函数定义的头文件
/*********************************
以下是DS1302芯片的操作程序
**********************************/
unsigned char code digit[10]={"0123456789"};
//定义字符数组显示数字
sbit DATA="P1"^1; //位定义1302的数据输出端定义在P1.1引脚
sbit RST="P1"^2; //位定义1302的复位端口定义在P1.2引脚
sbit SCLK="P1"^0; //位定义1302的时钟输出端口定义在P1.0引脚
/*****************************
函数功能:延时若干微秒
入口参数:n
******************************/
void delaynus(unsigned char n)
{
unsigned char i;
for(i=0;i<n;i++)
;
}
/**********************************
函数功能:向1302写一个字节数据
入口参数:dat
***********************************/
void Write1302(unsigned char dat)
{
unsigned char i;
SCLK=0; //拉低SCLK,为脉冲上升沿写入数据做好准备
delaynus(2); //稍微等待,使硬件做好准备
for(i=0;i<8;i++) //连续写8个二进制位数据
{
DATA=dat&0x01; //取出dat的第0位数据写入1302
delaynus(2); //稍微等待,使硬件做好准备
SCLK=1; //上升沿写入数据
delaynus(2); //稍微等待,使硬件做好准备
SCLK=0; //重新拉低SCLK,形成脉冲
dat>>=1; //将dat的各数据位右移1位,准备写入下一个数据位
}
}
/***********************************************
函数功能:根据命令字,向1302写一个字节数据
入口参数:Cmd,储存命令字;dat,储存待写的数据
************************************************/
void WriteSet1302(unsigned char Cmd,unsigned char dat)
{
RST=0; //禁止数据传递
SCLK=0; //确保写数居前SCLK被拉低
RST=1; //启动数据传输
delaynus(2); //稍微等待,使硬件做好准备
Write1302(Cmd); //写入命令字
Write1302(dat); //写数据
SCLK=1; //将时钟电平置于已知状态
RST=0; //禁止数据传递
}
/********************************
函数功能:从1302读一个字节数据
出口参数:dat
*********************************/
unsigned char Read1302(void)
{
unsigned char i,dat;
delaynus(2); //稍微等待,使硬件做好准备
for(i=0;i<8;i++) //连续读8个二进制位数据
{
dat>>=1; //将dat的各数据位右移1位
if(DATA==1) //如果读出的数据是1
dat|=0x80; //将1取出,写在dat的最高位
SCLK=1; //将SCLK置于高电平,为下降沿读出
delaynus(2); //稍微等待
SCLK=0; //拉低SCLK,形成脉冲下降沿
delaynus(2); //稍微等待
}
return dat; //将读出的数据返回
}
/**********************************************
函数功能:根据命令字,从1302读取一个字节数据
入口参数:Cmd 出口参数:dat
**********************************************/
unsigned char ReadSet1302(unsigned char Cmd)
{
unsigned char dat;
RST=0; //拉低RST
SCLK=0; //确保写数居前SCLK被拉低
RST=1; //启动数据传输
Write1302(Cmd); //写入命令字
dat=Read1302(); //读出数据
SCLK=1; //将时钟电平置于已知状态
RST=0; //禁止数据传递
return dat; //将读出的数据返回
}
/********************************
函数功能: 1302进行初始化设置
*********************************/
void Init_DS1302(void)
{
WriteSet1302(0x8E,0x00); //写入不保护指令
WriteSet1302(0x80,((0/10)<<4|(0%10))); //写入秒的初始值
WriteSet1302(0x82,((0/10)<<4|(0%10))); //写入分的初始值
WriteSet1302(0x84,((12/10)<<4|(12%10))); //写入小时的初始值
WriteSet1302(0x86,((24/10)<<4|(24%10))); //写入日的初始值
WriteSet1302(0x88,((4/10)<<4|(4%10))); //写入月的初始值
WriteSet1302(0x8c,((10/10)<<4|(10%10))); //写入年的初始值
}
/*******************************
以下是对液晶模块的操作程序
********************************/
sbit RS="P2"^0; //寄存器选择位,将RS位定义为P2.0引脚
sbit RW="P2"^1; //读写选择位,将RW位定义为P2.1引脚
sbit E="P2"^2; //使能信号位,将E位定义为P2.2引脚
sbit BF="P0"^7; //忙碌标志位,,将BF位定义为P0.7引脚
/*******************************************************************
函数功能:延时1ms
(3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒
********************************************************************/
void delay1ms()
{
unsigned char i,j;
for(i=0;i<10;i++)
for(j=0;j<33;j++)
;
}
/*****************************
函数功能:延时若干毫秒
入口参数:n
*******************************/
void delaynms(unsigned char n)
{
unsigned char i;
for(i=0;i<n;i++)
delay1ms();
}
/***********************************************
函数功能:判断液晶模块的忙碌状态
返回值:result。result=1,忙碌;result=0,不忙
***********************************************************/
bit BusyTest(void)
{
bit result;
RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态
RW=1;
E=1; //E=1,才允许读写
_nop_(); //空操作
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
result=BF; //将忙碌标志电平赋给result
E=0; //将E恢复低电平
_nop_();
_nop_();
_nop_();
_nop_();
return result;
}
/**************************************************************
函数功能:将模式设置指令或显示地址写入液晶模块
入口参数:dictate
***************************************************************/
void WriteInstruction (unsigned char dictate)
{
while(BusyTest()==1); //如果忙就等待
RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令
RW=0;
E=0; //E置低电平,为了让E从0到1发生正跳变,所以应先置"0"
_nop_();
_nop_(); //空操作两个机器周期,给硬件反应时间
P0=dictate; //将数据送入P0口,即写入指令或地址
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=1; //E置高电平
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令
_nop_();
_nop_();
_nop_();
_nop_();
}
/*********************************************
函数功能:指定字符显示的实际地址
入口参数:x
************************************************/
void WriteAddress(unsigned char x)
{
WriteInstruction(x|0x80); //显示位置的确定方法为"80H+地址码x"
}
/***************************************************************
函数功能:将数据(字符的标准ASCII码)写入液晶模块
入口参数:y(为字符常量)
******************************************************************/
void WriteData(unsigned char y)
{
while(BusyTest()==1);
RS=1; //RS为高电平,RW为低电平时,可以写入数据
RW=0;
E=0; //E置低电平,为了让E从0到1发生正跳变,所以应先置"0"
P0=y; //将数据送入P0口,即将数据写入液晶模块
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=1; //E置高电平
_nop_();
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令
_nop_();
_nop_();
_nop_();
_nop_();
}
/*******************************************************
函数功能:对LCD的显示模式进行初始化设置
*************************************************************/
void LcdInitiate(void)
{
delaynms(15); //首次写指令时应给LCD一段较长的反应时间
WriteInstruction(0x38);
//显示模式设置:16×2显示,5×7点阵,8位数据
delaynms(5); //给硬件一点反应时间
WriteInstruction(0x38);
delaynms(5); //给硬件一点反应时间
WriteInstruction(0x38); //连续三次,确保初始化成功
delaynms(5); //给硬件一点反应时间
WriteInstruction(0x0c);
//显示模式设置:显示开,无光标,光标不闪烁
delaynms(5); //给硬件一点反应时间
WriteInstruction(0x06); //显示模式设置:光标右移,字符不移
delaynms(5); //给硬件一点反应时间
WriteInstruction(0x01); //清屏幕指令,将以前的显示内容清除
delaynms(5); //给硬件一点反应时间
}
/**********************************
以下是1302数据的显示程序
***********************************/
/************************
函数功能:显示秒
入口参数:x
*************************/
void DisplaySecond(unsigned char x)
{
unsigned char i,j; //i,j分别储存秒的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x49); //写显示地址,将在第2行第7列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //延时1ms给硬件一点反应时间
}
/************************
函数功能:显示分钟
入口参数:x
**************************/
void DisplayMinute(unsigned char x)
{
unsigned char i,j; //i,j分别储存分钟的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x46); //写显示地址,将在第2行第7列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //延时1ms给硬件一点反应时间
}
/*************************
函数功能:显示小时
入口参数:x
***************************/
void DisplayHour(unsigned char x)
{
unsigned char i,j; //i,j分别储存小时的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x43); //写显示地址,将在第2行第7列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //延时1ms给硬件一点反应时间
}
/*********************
函数功能:显示日
入口参数:x
**********************/
void DisplayDay(unsigned char x)
{
unsigned char i,j; //i,j分别储存日的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x0d); //写显示地址,将在第1行第14列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //给硬件一点反应时间
}
/*********************
函数功能:显示月
入口参数:x
************************/
void DisplayMonth(unsigned char x)
{
unsigned char i,j; //i,j分别储存月的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x0a); //写显示地址,将在第1行第11列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //给硬件一点反应时间
}
/**********************
函数功能:显示年
入口参数:x
***********************/
void DisplayYear(unsigned char x)
{
unsigned char i,j; //i,j分别储存年的十位和个位
i=x/10; //取十位
j=x%10; //取个位
WriteAddress(0x07); //写显示地址,将在第1行第8列开始显示
WriteData(digit[i]); //将十位数字的字符常量写入LCD
WriteData(digit[j]); //将个位数字的字符常量写入LCD
delaynms(50); //给硬件一点反应时间
}
/***********************
函数功能:主函数
************************/
void main(void)
{
unsigned char second,minute,hour,day,month,year;
//分别储存秒、分、小时,日,月,年
unsigned char ReadValue; //储存从1302读取的数据
LcdInitiate(); //将液晶初始化
WriteAddress(0); //写Date的显示地址,将在第1行第1列开始显示
WriteData('D'); //将字符常量写入LCD
WriteData('a'); //将字符常量写入LCD
WriteData('t'); //将字符常量写入LCD
WriteData('e'); //将字符常量写入LCD
WriteData(':'); //将字符常量写入LCD
WriteData('2'); //将字符常量写入LCD
WriteData('0'); //将字符常量写入LCD
WriteAddress(0x09); //写年月分隔符的显示地址
WriteData('-'); //将字符常量写入LCD
WriteAddress(0x0c); //写月日分隔符的显示地址
WriteData('-'); //将字符常量写入LCD
WriteAddress(0x45); //写小时与分钟分隔符的显示地址
WriteData(':'); //将字符常量写入LCD
WriteAddress(0x48); //写分钟与秒分隔符的显示地址
WriteData(':'); //将字符常量写入LCD
Init_DS1302(); //将1302初始化
while(1)
{
ReadValue = ReadSet1302(0x81); //从秒寄存器读数据
second=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplaySecond(second); //显示秒
ReadValue = ReadSet1302(0x83); //从分寄存器读数据
minute=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplayMinute(minute); //显示分
ReadValue = ReadSet1302(0x85); //从时寄存器读数据
hour=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplayHour(hour); //显示小时
ReadValue = ReadSet1302(0x87); //从日寄存器读数据
day=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplayDay(day); //显示日
ReadValue = ReadSet1302(0x89); //从月寄存器读数据
month=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplayMonth(month); //显示月
ReadValue = ReadSet1302(0x8d); //从年寄存器读数据
year=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
//将读出数据转化
DisplayYear(year); //显示年
}
}
六、SPI 设备驱动
参看:linux spi 设备驱动简析 一(基于s5pv210)
参看:linux spi 设备驱动简析 二(基于s5pv210)
参看:Linux SPI总线设备驱动模型详解
参看:A10+Linux+SPI设备驱动开发-2012.1.31
驱动程序可以参看Linux下 drivers/spi 目录下的程序:
以GV7601为例:
参看:DM8168平台中完成gv7601的spi总线驱动笔记
下载:GV7601 资料下载
如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78979855