基于ARM板s3c2440---SPI协议
2022-03-03 来源:eefocus
SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,比如AT91RM9200。
简单的说,就是SPI是一种告诉的协议,相当于IIC,只是IIC是两根线(时钟线SCL,数据地址线SDA)而SPI是四根线(SCK时钟信号,DO输出线,DI输入线,CSn片选信号),同样可以挂在多个SPI高速设备。
数据传输
数据传输由CPOL,CPHA两个控制器决定,CPOL决定电平启示状态是低电平还是高电平,CPHA决定在第一个沿采样还是第二个沿采样
通过SPI协议实现对OLED屏幕的显示
OLED显示原理
屏幕分辨率是12864,如同LCD一样,我们需要一个显存来存放数据,这个显存在内存中开辟,一个字节八位,分别表示该列元素的亮(1)或者灭(0)
按168的大小在屏幕上显示,一行为一个page,在光标移动到底8列时,光标归为0,page+1,实现第二个page的写操作
通过GPIO模拟SPI的数据收发
原理图中使用到GPG3567和GPF1,所以需要设置S3C2440相应的管脚的输入输出模式,除开GPG5为输入模式,其余管脚都是输出模式。
static void SPI_GPIO_INIT(void)
{
/*GPF1 OLED_CSn OUTPUT*/ /*主机是输出模式*/
GPFCON &= ~(3<<2);
GPFCON |= (1<<2);
GPFDAT |= (1<<1); /*因为接了OLED 和 FLASH 如果同时为0 会产生冲突*/
/*GPG2 FLASH_CSn OUTPUT*/
/*GPG4 OLED_DC OUTPUT*/
/*GPG5 SPIMISO INPUT*/ /*MI:MASTER INPUT*/
/*GPG6 SPIMOSI OUTPUT*/
/*GPG7 SPICLK OUTPUT*/
GPGCON &= ~((3<<4)|(3<<8)|(3<<12)|(3<<14)|(3<<10));
GPGCON |= ((1<<4)|(1<<8)|(1<<12)|(1<<14));
GPGDAT |=(1<<2); /*因为接了OLED 和 FLASH 如果同时为0 会产生冲突*/
/*CSn低电平有效*/
/*都不片选,使其在使用时裁片选*/
}
由于芯片上接入了FLASH和oled的片选信号,所以在GPIO的初始化时,需要将两个片选信号都拉高,避免默认状态冲突。
OLED初始化
void oledinit(void)
{
/*向OLED发命令初始化*/
/*SPEC UG手册提供的初始化命令*/
OLEDWriteCmd(0xAE); /*display off*/
OLEDWriteCmd(0x00); /*set lower column address*/
OLEDWriteCmd(0x10); /*set higher column address*/
OLEDWriteCmd(0x40); /*set display start line*/
OLEDWriteCmd(0xB0); /*set page address*/
OLEDWriteCmd(0x81); /*contract control*/
OLEDWriteCmd(0x66); /*128*/
OLEDWriteCmd(0xA1); /*set segment remap*/
OLEDWriteCmd(0xA6); /*normal / reverse*/
OLEDWriteCmd(0xA8); /*multiplex ratio*/
OLEDWriteCmd(0x3F); /*duty = 1/64*/
OLEDWriteCmd(0xC8); /*Com scan direction*/
OLEDWriteCmd(0xD3); /*set display offset*/
OLEDWriteCmd(0x00);
OLEDWriteCmd(0xD5); /*set osc division*/
OLEDWriteCmd(0x80);
OLEDWriteCmd(0xD9); /*set pre-charge period*/
OLEDWriteCmd(0x1f);
OLEDWriteCmd(0xDA); /*set COM pins*/
OLEDWriteCmd(0x12);
OLEDWriteCmd(0xdb); /*set vcomh*/
OLEDWriteCmd(0x30);
OLEDWriteCmd(0x8d); /*set charge pump enable*/
OLEDWriteCmd(0x14);
OLEDPAGEADDRMODE();
OLEDCLEAR();
OLEDWriteCmd(0xAF); /*display ON*/
}
在OLED初始中实现对地址采用Page address mode
static void OLEDPAGEADDRMODE(void)
{
OLEDWriteCmd(0x20);
OLEDWriteCmd(0x02);
}
清屏幕
static void OLEDCLEAR(void)
{
int page,col,i;
for(page = 0; page <8;page++)
{
OLEDSETPOS(page,0);
for(i=0;i<128;i++)
{
OLEDWriteDAT(0);
}
}
}
地址/数据模式
OLED_DC:高电平表示数据,低电平表示数据。
/*命令*/
static void OLEDWriteCmd(unsigned int cmd)
{
OLED_SET_DC(0); /*COMMAND*/
OLED_SET_CS(0); /*选中OLED*/
SPISENDBYTE(cmd);
OLED_SET_CS(1); /*取消选中OLED*/
OLED_SET_DC(1); /*COMMAND*/
}
/*地址*/
static void OLEDWriteDAT(unsigned int dat)
{
OLED_SET_DC(1); /*data*/
OLED_SET_CS(0); /*选中OLED*/
SPISENDBYTE(dat);
OLED_SET_CS(1); /*取消选中OLED*/
OLED_SET_DC(1); /*data*/
}
根据传入0/1设置相应的GPIO口输出高低电平
static void OLED_SET_DC(char val)
{
if(val)
{
GPGDAT |= (1<<4);
}
else
{
GPGDAT &= ~(1<<4);
}
}
片选信号
static void OLED_SET_CS(char val)
{
if(val)
{
GPFDAT |= (1<<1);
}
else
{
GPFDAT &= ~(1<<1);
}
}
数据传输
void SPISENDBYTE(unsigned char val)
{
int i;
for(i=0;i<8;i++)
{
SPI_SET_CLK(0);
SPI_SET_DO(val & 0x80);
SPI_SET_CLK(1);
val <<= 1;
}
}
时钟设置
static void SPI_SET_CLK(char val)
{
if(val)
{
GPGDAT |= (1<<7);
}
else
{
GPGDAT &= ~(1<<7);
}
}
数据输出--------按位传递,熊高位到低位
static void SPI_SET_DO(char val)
{
if(val)
{
GPGDAT |= (1<<6);
}
else
{
GPGDAT &= ~(1<<6);
}
}
程序到这里已经实现了CPU的数据传出,接下来需要将数据收到,以及打印到OLED的相应位置
void oledprint(int page,int col,char *str)
{
int i = 0;
while(str[i])
{
OLEDPUTCHAR(page,col,str[i]);
col += 8;
if(col > 127)
{
page += 2;
col = 0;
}
i++;
}
}
一个字节占8列,16行(16*8)
void OLEDPUTCHAR(int page,int col,char c)
{
int i=0;
/*字摸*/
const unsigned char *dots = oled_asc2_8x16[c - ' '];
/*发给OLED*/
OLEDSETPOS(page,col);
/*发出8字节数据*/
for(i=0;i<8;i++)
{
OLEDWriteDAT(dots[i]);
}
OLEDSETPOS(page+1,col);
/*发出8字节数据*/
for(i=0;i<8;i++)
{
OLEDWriteDAT(dots[i+8]); /*原因是字符数组二维数组是16个为一组,前8个是上一排的,后8个是下一排*/
}
}
static void OLEDSETPOS(int page, int col)
{
OLEDWriteCmd(0xb0 + page); /*page address*/
OLEDWriteCmd(col & 0xf); /*Set Lower Column Start Address for Page Addressing Mode*/
OLEDWriteCmd(0x10 +(col>>4)); /*Set Higher Column Start Address for Page Addressing Mode*/
}
通过SPI协议实现对FLASH的操作
读厂家ID、设备ID
首先发送0x90,然后发出3个字节的0(是一个24位的数据,所以需要分割为3个字节来发送),然后GPG5就有数据读入。
void SPIflashreadID(int *pMID,int *pDID)
{
SPI_FLASH_set_cs(0); /*选中SPIflash*/
SPISENDBYTE(0X90);
SPIFlashSendAddr(0); /*当发完这些指令和地址是,GPGDAT5上就有数据传入*/
*pMID = SPIRecvByte();
*pDID = SPIRecvByte();
SPI_FLASH_set_cs(1); /*取消选中SPIflash*/
}
static void SPIFlashSendAddr(unsigned int addr)
{
SPISENDBYTE(addr>>16); /*地址由24位组成,先发高高8位*/
SPISENDBYTE((addr>>8)&0xff); /*地址由24位组成,再发高8位*/
SPISENDBYTE(addr&0xff); /*地址由24位组成,最后发低8位*/
}
static void SPI_FLASH_set_cs(char val)
{
if(val)
{
GPGDAT |= (1<<2); /*不选中*/
}
else
{
GPGDAT &= ~(1<<2); /*选中*/
}
}
/*主机传入*/
static char SPI_SET_DI(void)
{
/*需要判定GPGDAT第5位是什么数据*/
if(GPGDAT &(1<<5))
{
return 1;
}
else
{
return 0;
}
}
unsigned char SPIRecvByte(void)
{
int i;
unsigned char val = 0; /*用于存放本字节的东西*/
for(i=0;i<8;i++)
{
val <<= 1;
SPI_SET_CLK(0);
if(SPI_SET_DI())
{
val |= 1; /*数据为1就或1,否则就是0,前面左移已经产生了,所以不用赋值*/
}
SPI_SET_CLK(1);
}
return val;
}
对FLASH进行读写
对FLASH的写操作,需先进进行写使能,取出状态寄存器的保护,然后擦除,然后写入
写使能:
写保护:
static void SPIFlashWriteEnable(int enable)
{
if (enable)
{
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x06);
SPI_FLASH_set_cs(1);
}
else
{
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x04);
SPI_FLASH_set_cs(1);
}
}
读状态寄存器
发送0x05命令,接下来就可以读数据了
static unsigned char SPIFlashReadStatusReg1(void)
{
unsigned char val;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x05);
val = SPIRecvByte();
SPI_FLASH_set_cs(1);
return val;
}
static unsigned char SPIFlashReadStatusReg2(void)
{
unsigned char val;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x35);
val = SPIRecvByte();
SPI_FLASH_set_cs(1);
return val;
}
写状态寄存器
static void SPIFlashWriteStatusReg(unsigned char reg1, unsigned char reg2)
{
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x01);
SPISENDBYTE(reg1);
SPISENDBYTE(reg2);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
根据状态寄存器1的第0位可以判定是写数据已经完成。1表示正在进行,0表示已经完成
static void SPIFlashWaitWhenBusy(void)
{
while (SPIFlashReadStatusReg1() & 1);
}
去除对状态寄存器的保护
将SRP0,SRP1置0就能去除对状态寄存器的保护
static void SPIFlashClearProtectForStatusReg(void)
{
unsigned char reg1, reg2;
reg1 = SPIFlashReadStatusReg1();
reg2 = SPIFlashReadStatusReg2();
reg1 &= ~(1<<7);
reg2 &= ~(1<<0);
SPIFlashWriteStatusReg(reg1, reg2);
}
去除对数据域的保护
将BP2,BP1,BP0三位清0就可以去除保护
static void SPIFlashClearProtectForData(void)
{
/* cmp=0,bp2,1,0=0b000 */
unsigned char reg1, reg2;
reg1 = SPIFlashReadStatusReg1();
reg2 = SPIFlashReadStatusReg2();
reg1 &= ~(7<<2);
reg2 &= ~(1<<6);
SPIFlashWriteStatusReg(reg1, reg2);
}
擦除
擦除4k大小的区域
void SPIFlashEraseSector(unsigned int addr)
{
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x20);
SPIFlashSendAddr(addr);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
烧写
/* program */
void SPIFlashProgram(unsigned int addr, unsigned char *buf, int len)
{
int i;
SPIFlashWriteEnable(1);
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x02);
SPIFlashSendAddr(addr);
for (i = 0; i < len; i++)
SPISENDBYTE(buf[i]);
SPI_FLASH_set_cs(1);
SPIFlashWaitWhenBusy();
}
读数据
void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)
{
int i;
SPI_FLASH_set_cs(0);
SPISENDBYTE(0x03);
SPIFlashSendAddr(addr);
for (i = 0; i < len; i++)
buf[i] = SPIRecvByte();
SPI_FLASH_set_cs(1);
}
使用SPI控制器取代GPIO模拟
同样是使用相同的管家,只是不再手动操作相应的GPIO管脚,实现0/1的输出,而是操作SPI控制器,让控制器自动进行相应的数据收发。
SPI控制器初始化
将GPGCON中的456位设置为SPI模式,其余不变
static void SPI_GPIO_Init(void)
{
/*GPF1 OLED_CSn OUTPUT*/ /*主机是输出模式*/
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
GPFDAT |= (1<<1); /*因为接了OLED 和 FLASH 如果同时为0 会产生冲突*/
/*GPG2 FLASH_CSn OUTPUT*/
/*GPG4 OLED_DC OUTPUT*/
/*GPG5 SPIMISO INPUT*/ /*MI:MASTER INPUT*/
/*GPG6 SPIMOSI OUTPUT*/ /*这里所有的输入输出都是站在主机CPU的角度谈的*/
/*GPG7 SPICLK OUTPUT*/
GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGDAT |= (1<<2); /*因为接了OLED 和 FLASH 如果同时为0 会产生冲突*/
/*CSn低电平有效*/
/*都不片选,使其在使用时裁片选*/
}
SPI控制器初始化,设置相应的时钟,因为是自动传输,所以时钟(也就是波特率)需要预先设置。
static void SPIControllerInit(void)
{
/* OLED : 100ns, 10MHz
* FLASH : 104MHz
* 取10MHz
* 10 = 50 / 2 / (Prescaler value + 1)
* Prescaler value = 1.5 = 2
* Baud rate = 50/2/3=8.3MHz
*/
SPPRE0 = 2;
SPPRE1 = 2;
/* [6:5] : 00, polling mode
* [4] : 1 = enable
* [3] : 1 = master
* [2] : 0
* [1] : 0 = format A
* [0] : 0 = normal mode
*/
SPCON0 = (1<<4) | (1<<3);
SPCON1 = (1<<4) | (1<<3);
}
收发数据只需要将数据写给SPSTA1寄存器就可以
由于使用的是SPIMISO1,所以使用的是第二控制器
void SPISENDBYTE(unsigned char val)
{
while (!(SPSTA1 & 1));
SPTDAT1 = val;
}
//判定SPSTA1第一位十位准备就绪,然后写数据
unsigned char SPIRecvByte(void)
{
SPTDAT1 = 0xff;
while (!(SPSTA1 & 1));
return SPRDAT1;
}
首先将0xff写入SPTDAT1 ,然后判定SPSTA1第一位十位准备就绪,然后读数据
特别注意:这里的数据寄存器(SPRDATn)是单字符寄存器,所以在地址声明时需要定义为单字节。
- 只用串口和网络裸机开发ARM程序(OK6410开发板)
- 移植openssh至嵌入式ARM开发板
- 【技术支持】ARM9开发板Qt环境的搭建
- ARM-Linux开机自启动设置-mini2440开发板
- 智能驾考系统-基于arm核心板打造智能驾考终端解决方案
- OpenCV答题卡识别模拟-测评米尔ARM+FPGA异构开发板
- ARM+FPGA开发板基于FFMPEG的网络视频播放终端——米尔NXP I.MX 8M
- ARM+FPGA开发板的强劲图形系统体验——米尔基于NXP i.MX 8M Mini+Artix-7开发板
- 我在ARM板上写的第一个驱动程序
- 米尔ARM+FPGA架构开发板PCIE2SCREEN示例分析与测试