SPI接口(续三)

在SPI接口中,判断传输的数据位上电平的高低是通过时钟来衡量的,根据时钟的上升沿/下降沿和数据电平的保持/更改,可以组合出4种方式,具体如下图所示。

从上图中可以看出,时钟相位CPHA决定传输的数据电平什么时候被采样、什么时候可以更改,时钟极性CPOL决定时钟是低电平空闲还是高电平空闲。 在上图中,当CPHA=0时,当时钟从空闲状态发生跳变时(CPOL=0为上跳,CPOL=1为下跳),采样数据位的电平,当时钟跳变成空闲状态时(CPOL=0为下跳,CPOL=1为上跳),允许数据位上的电平更改。当CPHA=1时,当时钟从空闲状态发生跳变时(CPOL=0为上跳,CPOL=1为下跳),允许数据位上的电平更改,当时钟跳变成空闲状态时(CPOL=0为下跳,CPOL=1为上跳),采样数据位的电平。具体的4种模式见下表。

至于SPI要采用哪种模式,要看实际的SPI器件,一般来说,采用模式3的较为常见。

接下来看一个使用SPI接口的实际例子。通过LPC824驱动一块SPI接口的液晶屏进行图像显示,这里选用诺基亚的5110液晶屏,要求在屏幕上显示一个五角星的图形。
5110液晶屏具有4032个像数,可以同时显示15个汉字或84个ASCII码字符,具备简易的SPI接口,超低功耗、速度超快,因此很受欢迎。在实际连线时,由于在默认状态下LPC824的SPI0接口引脚并未引出到物理引脚上,所以在实际配置时可依据具体情况来灵活选择物理引脚。另外,5110液晶屏的SPI接口并没有MISO端,在连线时可不接此端口。在本例中,规定的连线方式定义如下:
SCK<——>SCK0(PIO0_20)
DIN<——>MOSI0(PIO0_21)
SCE<——>SSEL0(PIO0_14)
D\C<——>PIO0_6
RST<——>PIO0_22
最后还要给液晶屏接上电源,该液晶屏的工作电压为3.3V。

参考程序代码如下:

#include <LPC82x.h>
#define SPI_CFG_ENABLE (0x1)
#define SPI_CFG_MASTER (0x4)
#define SPI_STAT_RXRDY (0x1)
#define SPI_STAT_TXRDY (0x2)
#define SPI_STAT_SSD (0x20)
#define SPI_STAT_MSTIDLE (0x100)
#define SPI_TXDATCTL_SSEL_N(s) ((s) << 16)
#define SPI_TXDATCTL_EOT (1 << 20)
#define SPI_TXDATCTL_EOF (1 << 21)
#define SPI_TXDATCTL_RXIGNORE (1 << 22)
#define SPI_TXDATCTL_FLEN(l) ((l) << 24)

uint8_t const bmp[]={0xE0,0xF0,0xF0,0xF0,0xF0,0xFC,0xFE,0xFF,0xFF,0xFF,0xFC,0xF0,0xF0,0xF0,0xF0,0xE0,0x00,0x01,0x7B,0xFF,0xFF,0xFF,0x7F,0x7F,0x3F,0x7F,0x7F,0xFF,0xFF,0x7F,0x01,0x00};

void Port_init(void)
{
    LPC_GPIO_PORT->DIR0 |= 0x400040; //设置端口为输出方向
}    
    
void Spi0_init(void)
{
    LPC_SYSCON->SYSAHBCLKCTRL|=1<<7;  //开启SWM时钟
    LPC_SWM->PINASSIGN3 &= ~0xFFFFFFFF;
    LPC_SWM->PINASSIGN3 |= 0x14<<24;
    LPC_SWM->PINASSIGN4 &= ~0xFFFFFFFF;
    LPC_SWM->PINASSIGN4 |= 0x15 | (0x0e<<16);
    LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<7); //关闭SWM时钟(使用完之后记得关闭,节省功耗)
    LPC_SYSCON->SYSAHBCLKCTRL |= 1<<11;      //开启SPI0时钟
    LPC_SYSCON->PRESETCTRL &= ~(1<<0);      //开启复位SPI0
    LPC_SYSCON->PRESETCTRL |= (1<<0);          //关闭复位SPI0
    LPC_SPI0->DIV = 6;
    LPC_SPI0->CFG = SPI_CFG_MASTER | SPI_CFG_ENABLE;
}

void SPI_MasterTransmit(uint8_t Data)
{
    while(~LPC_SPI0->STAT & SPI_STAT_TXRDY);
    LPC_SPI0->TXDATCTL = SPI_TXDATCTL_FLEN(7) | SPI_TXDATCTL_RXIGNORE | SPI_TXDATCTL_EOT | SPI_TXDATCTL_SSEL_N(0xe) | Data;
    while(~LPC_SPI0->STAT & SPI_STAT_MSTIDLE);
}

void LCD_write_byte(uint8_t dat,uint8_t command)
{
    if(command==0)
        LPC_GPIO_PORT->PIN0 &= ~(1<<6);
    else
            LPC_GPIO_PORT->PIN0 |= (1<<6);
    SPI_MasterTransmit(dat);           //发送数据 
}    
    
void LCD_set_XY(uint8_t X,uint8_t Y)
{
    LCD_write_byte(0x40|Y,0);     //写纵坐标地址(参看基本命令,须写第6位为1,所以要或上0x40)
    LCD_write_byte(0x80|X,0);     //写横坐标地址(参看基本命令,须写第7位为1,所以要或上0x80)
}

void LCD_draw_bmp_pixel(uint8_t X,uint8_t Y,uint8_t const *map,uint8_t Pix_x,uint8_t Pix_y)
{
    uint16_t i,n;
    uint8_t row;
    if(Pix_y%8 == 0)
        row = Pix_y/8; //若纵向上的像数坐标小于8,则行的值取1
    else
        row = Pix_y/8+1; //若纵向上的像数坐标大于8,则行的值取8的倍数加1
    LCD_set_XY(X,Y);
    for(n=0;n<row;n++)  //纵向上的地址增加
        {
            for(i=0;i<Pix_x;i++) //在横向上依次增加显示区域
                 {
                        LCD_set_XY(X+i,Y+n); //设定显示区域的坐标
                        LCD_write_byte(map[i+n*Pix_x],1); //从图像数组中依次取值进行显示
                 }
         }
}

void LCD_clear(void)
{
    uint8_t t,k;
    LCD_set_XY(0,0);      //从最左上角开始
    for(t=0;t<=6;t++)
        {
            for(k=0;k<84;k++) //一直到最右下角结束
                LCD_write_byte(0,1);  //写显示数据0,以清屏
         }
}

void delay_us(uint32_t us)
{
    SysTick->LOAD = (((12)*us)-1);         //载入初始值
    SysTick->VAL = 0;                      //写当前值寄存器使其清零
    SysTick->CTRL |= (1<<0);               //启动定时器,选择半系统时钟
    while(!(SysTick->CTRL & 0x10000));     //循环查询,等待定时时间到
    SysTick->CTRL &= ~(1<<0);              //关闭定时器
}

void delay_ms(uint32_t ms)
{
    SysTick->LOAD = (((12000)*ms)-1);         //载入初始值
    SysTick->VAL = 0;                         //写当前值寄存器使其清零
    SysTick->CTRL |= (1<<0);                  //启动定时器,选择半系统时钟
    while(!(SysTick->CTRL & 0x10000));        //循环查询,等待定时时间到
    SysTick->CTRL &= ~(1<<0);                 //关闭定时器
}

void LCD_init(void)
{
    LPC_GPIO_PORT->PIN0 &= ~(1<<22);
    delay_us(5);                        //复位宽度
    LPC_GPIO_PORT->PIN0 |= (1<<22);
    LCD_write_byte(0x21,0);   //上电模式、水平寻址、扩展指令集
    LCD_write_byte(0xbe,0);   //设置偏置电压
    LCD_write_byte(0x06,0);   //设置温度系数
    LCD_write_byte(0x13,0);   //设置1:48混合比例
    LCD_write_byte(0x20,0);   //恢复到基本指令集方式
    LCD_clear();              //清屏
    LCD_write_byte(0x0c,0);   //正常显示模式
}

int main(void)
{
    Port_init();                 //端口初始化
    Spi0_init();                 //SPI初始化
    LCD_init();          //LCD初始化
    delay_ms(15);        //延时
    LCD_draw_bmp_pixel(0,0,bmp,16,16); //在左上角显示一颗五角星
    while(1)
        ;
}

把上述程序编译后下载到LPC824中,5110液晶屏按上面的要求接好连线,给系统上电,可在显示屏上看到如下的图案,说明SPI接口工作正常。

 

posted @ 2021-02-28 16:50  fxzq  阅读(444)  评论(1)    收藏  举报