【海思篇】【Hi3516DV300】八、手撸一个海思SPI驱动

目的:使用SPI接口让LCD绘制LOGO;让更多的爱好者了解海思、加入海思。

目录

1 查询SPI相关的寄存器

2 SPI的管脚复用

3 SPI的时钟开启

4 SPI1_CSN0启用和配置

5 实现读写寄存器函数

6 spi开启关闭函数

7 spi帧格式、极性、相性及位宽函数

8 spi串行时钟频率函数

9 spi自动手动模式

10 spi数据传输函数

11 调用例子

总结


说明:

    根据《外围设备驱动操作指南》里的用户态SPI,想要在LCD绘制LOGO,按照它样例来,结果不成功,原因未知;

    看网友说这个SSP操作要自己实现,然后我就参考chip下的《用户指南》和Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\drv\extdrv\ssp-st7789下的驱动文档自己来实现SSP;

    本章主要记录下SPI的实现,关于LCD(ILI9342)的详细调试会在其他章节里介绍;

1 查询SPI相关的寄存器

《用户指南.pdf》和《3516DV300_PINOUT_EN.xlsx》查询如下,(我样例中用的是SPI1和SPI1_CSN0):

/*
SPI0 基地址是 0x120C_0000
SPI1 基地址是 0x120C_1000
SPI2 基地址是 0x120C_2000
*/
#define SSP_BASE            (0x120c1000)

/*
SPI1 pinmux
*/
#define SPI1_SCLK           (0x112F0020)
#define SPI1_SDO            (0x112F0024)
#define SPI1_CSN0	          (0x112f0028)
#define SPI1_SDI            (0x112F002C)

/*
PERI_CRG111 - SPI Clock Control
*/
#define PERI_CRG111         (0x120101BC)

/*
MISC_CTRL0  - SP1_CSN0 Check
*/
#define MISC_CTRL0	         (0x12030000+0x0000)

#define SSP_SIZE             0x1000          // 4KB
#define DEFAULT_MD_LEN       (128)
#define IO_ADDRESS_VERIFY(x) (x)


/* SSP register definition .*/
#define SSP_CR0              IO_ADDRESS_VERIFY(SSP_BASE + 0x00)
#define SSP_CR1              IO_ADDRESS_VERIFY(SSP_BASE + 0x04)
#define SSP_DR               IO_ADDRESS_VERIFY(SSP_BASE + 0x08)
#define SSP_SR               IO_ADDRESS_VERIFY(SSP_BASE + 0x0C)
#define SSP_CPSR             IO_ADDRESS_VERIFY(SSP_BASE + 0x10)
#define SSP_IMSC             IO_ADDRESS_VERIFY(SSP_BASE + 0x14)
#define SSP_RIS              IO_ADDRESS_VERIFY(SSP_BASE + 0x18)
#define SSP_MIS              IO_ADDRESS_VERIFY(SSP_BASE + 0x1C)
#define SSP_ICR              IO_ADDRESS_VERIFY(SSP_BASE + 0x20)
#define SSP_DMACR            IO_ADDRESS_VERIFY(SSP_BASE + 0x24)
#define SSP_SPITXFIFOCR      IO_ADDRESS_VERIFY(SSP_BASE + 0x28)
#define SSP_SPIRXFIFOCR      IO_ADDRESS_VERIFY(SSP_BASE + 0x2C)


#define SPI_SR_BSY           (0x1 << 4)/* spi busy flag */
#define SPI_SR_TFE           (0x1 << 0)/* Whether to send fifo is empty */
#define SPI_DATA_WIDTH       (9)
#define SPI_SPO              (1)/* 极性 */
#define SPI_SPH              (1)/* 相性 */
#define SPI_SCR              (0)//(8)
#define SPI_CPSDVSR          (2)//(8)
#define SPI_FRAMEMODE        (0)

#define MAX_WAIT 10000

2 SPI的管脚复用

static void spi1_pinmux()
{
  ssp_writew(SPI1_SCLK, 0x4f1);
  ssp_writew(SPI1_SDO, 0x4f1);
  ssp_writew(SPI1_SDI, 0x4f1);
  spi1_csn0_pinmux();

  printf("spi1_pinmux\n");
}

3 SPI的时钟开启

独立软复位:由寄存器 PERI_CRG111 的 bit[17:15] 控制。相应位写“ 0 ”, SPI 退出软复位;相应位写“ 1 ”, SPI 进入软复位。上电缺省值为 0 。
时钟门控:由寄存器 PERI_CRG111 的 bit[14:12] 控制。相应位写“ 0 ”,关闭时钟;相应位写“ 1 ”,打开时钟。上电缺省值为 1 。

PERI_CRG111的第13bit为SPI1始终们控配置寄存器

    0: 关闭时钟

    1: 打开时钟

static void spi1_open_clock()
{
  unsigned int peri_cfg111;
  ssp_readw(PERI_CRG111, peri_cfg111);
  ssp_writew(PERI_CRG111, peri_cfg111|0x1<<13);

  printf("spi1_open_clock\n");
}

4 SPI1_CSN0启用和配置

MISC_CTRL0的第2bit为SPI1信号输出片选选择

    0: SPI1_CSN0

    1: SPI1_CSN1

MISC_CTRL0的第3bit为SPI1片选信号0极性控制

    0: SPI1_CSN0低有效

    1: SPI1_CSN0高有效

static void spi1_csn0_pinmux()
{
    	// 复选spi_csn0
	ssp_writew(SPI1_CSN0, 0x0501);

	// 配置csn0低有效 且选择片选0
	unsigned int misc_ctrl0;
	ssp_readw(MISC_CTRL0, misc_ctrl0);
	misc_ctrl0 &= ~(0x1<<3); // 低有效
	misc_ctrl0 &= ~(0x1<<2); // 片选0
	ssp_writew(MISC_CTRL0, misc_ctrl0);
}

5 实现读写寄存器函数

参考himm和himd

#ifdef __HuaweiLite__
#define  ssp_readw(addr,ret)            (ret =(*(volatile unsigned int *)(addr)))
#define  ssp_writew(addr,value)         ((*(volatile unsigned int *)(addr)) = (value))
#else
static const char dev[]="/dev/mem";

static void write_reg(unsigned int Addr, unsigned int Value)
{
    int fd = open (dev, O_RDWR | O_SYNC);
    if (fd < 0)
    {
        printf("open %s error!\n", dev);
        return ;
    }
    
    /* addr align in page_size(4K) */
    unsigned long phy_addr_in_page;
    unsigned long page_diff;
    phy_addr_in_page = Addr & PAGE_SIZE_MASK;
    page_diff = Addr - phy_addr_in_page;

    /* size in page_size */
    unsigned long size_in_page;
    unsigned long size = PAGE_SIZE;
    size_in_page =((size + page_diff - 1) & PAGE_SIZE_MASK) + PAGE_SIZE;
    
    void *addr = mmap((void *)0, size_in_page, PROT_READ|PROT_WRITE, MAP_SHARED, fd, phy_addr_in_page);
    if (addr == MAP_FAILED)
    {
        printf("mmap @ 0x%x error!\n", phy_addr_in_page);
        close(fd);
        return;
    }

    unsigned int *addr_cur = (unsigned int *)(addr+page_diff);

    *addr_cur = Value;
    
    /* munmap */
    if(munmap(addr, size_in_page) != 0 )
    {
        printf("munmap failed!\n");
    }

    close(fd);
    //printf("ssp_write_reg %x %d\n", Addr, Value);
}

static unsigned int read_reg(unsigned int Addr)
{
    unsigned int Value = 0;
    int fd = open (dev, O_RDWR | O_SYNC);
    if (fd < 0)
    {
        printf("open %s error!\n", dev);
        return -1;
    }
    
    /* addr align in page_size(4K) */
    unsigned long phy_addr_in_page;
    unsigned long page_diff;
    phy_addr_in_page = Addr & PAGE_SIZE_MASK;
    page_diff = Addr - phy_addr_in_page;

    /* size in page_size */
    unsigned long size_in_page;
    unsigned long size = PAGE_SIZE;
    size_in_page =((size + page_diff - 1) & PAGE_SIZE_MASK) + PAGE_SIZE;
    
    void *addr = mmap((void *)0, size_in_page, PROT_READ|PROT_WRITE, MAP_SHARED, fd, phy_addr_in_page);
    if (addr == MAP_FAILED)
    {
        printf("mmap @ 0x%x error!\n", phy_addr_in_page);
        close(fd);
        return -1;
    }

    unsigned int *addr_cur = (unsigned int *)(addr+page_diff);

    Value = *addr_cur;
    
    /* munmap */
    if(munmap(addr, size_in_page) != 0 )
    {
        printf("munmap failed!\n");
    }

    close(fd);
    //printf("ssp_read_reg %x %d\n", Addr, Value);
    return Value;
}

#define  ssp_readw(addr,ret)            (ret=read_reg(addr))
#define  ssp_writew(addr,value)         (write_reg(addr, value))
#endif

6 spi开启关闭函数

SSP_CR1的第1bit为设置SPI使能

    0: 不使能

    1: 使能

static void spi_enable(void)
{
    ssp_writew(SSP_CR1, 0x42);
}

static void spi_disable(void)
{
    ssp_writew(SSP_CR1, 0x40);
}

void hi_ssp_enable(void)
{
    int ret = 0;
    ssp_readw(SSP_CR1,ret);
    ret = (ret & 0xFFFD) | 0x2;

    ret = ret | (0x1 << 4); /* big/little end, 1: little, 0: big */

    ret = ret | (0x1 << 15); /* wait en */

    ssp_writew(SSP_CR1,ret);
}


void hi_ssp_disable(void)
{
    int ret = 0;
    ssp_readw(SSP_CR1,ret);
    ret = ret & (~(0x1 << 1));
    ssp_writew(SSP_CR1,ret);
}

7 spi帧格式、极性、相性及位宽函数

SSP_CR0的[4-5]bit为帧格式选择,这里我们选择了Motorola SPI

    00: Motorla SPI帧格式

    01: TI同步串行格式

    10: National Microwire帧格式

    11: 保留

SSP_CR0的第6bit为SPICLKOUT极性(SPO)

SSP_CR0的第7bit为SPICLKOUT相性(SPH)

SSP_CR0的[0-3]bit为设置数据位宽:

    0011: 4bit

    1000: 9bit

    1101: 14bit

    0100: 5bit

    1001: 10bit

int hi_ssp_set_frameform(unsigned char framemode,unsigned char spo,unsigned char sph,unsigned char datawidth)
{
    int ret = 0;
    ssp_readw(SSP_CR0,ret);
    if(framemode > 3)
    {
        printf("[ERROR]set frame parameter err.\n");
        return -1;
    }
    ret = (ret & 0xFFCF) | (framemode << 4);
    if((ret & 0x30) == 0)
    {
        if(spo > 1)
        {
            printf("[ERROR]set spo parameter err.\n");
            return -1;
        }
        if(sph > 1)
        {
            printf("[ERROR]set sph parameter err.\n");
            return -1;
        }
        ret = (ret & 0xFF3F) | (sph << 7) | (spo << 6);
    }
    if((datawidth > 16) || (datawidth < 4))
    {
        printf("[ERROR]set datawidth parameter err.\n");
        return -1;
    }
    ret = (ret & 0xFFF0) | (datawidth -1);
    ssp_writew(SSP_CR0,ret);
    return 0;
}

8 spi串行时钟频率函数

输出 SPI 时钟频率计算方式如下:
Fsspclkout = Fsspclk/ ( CPSDVR x ( 1+SCR ))
Fsspclk : SPI 的工作参考时钟 100M 。
CPSDVR 、 SCR 请查询相应寄存器。

SPICR0的[8-15]bit为串行时钟率(SCR),取值范围0~255;

SPICPSR的[0-7]bit为时钟分频因子(CPSDVR),此值必须是2~254之间的偶数,也就是0bit位必须为0;

int hi_ssp_set_serialclock(unsigned char scr,unsigned char cpsdvsr)
{
    int ret = 0;
    ssp_readw(SSP_CR0,ret);
    ret = (ret & 0xFF) | (scr << 8);
    ssp_writew(SSP_CR0,ret);
    if((cpsdvsr & 0x1))
    {
        printf("[ERROR]set cpsdvsr parameter err.\n");
        return -1;
    }
    ssp_writew(SSP_CPSR,cpsdvsr);
    return 0;
}

9 spi自动手动模式

SSP_CR1的第6bit介绍:

    0: 片选信号由芯片逻辑根据所选时序自动产生

    1: 当采用Motorola SPI帧格式时,片选CS信号由SPI使能信号控制,是能后片选拉低,否则片选拉高。

int hi_ssp_alt_mode_set(int enable)
{
    int ret = 0;

    ssp_readw(SSP_CR1,ret);
    if (enable)
    {
        ret = ret & (~0x40);
    }
    else
    {
        ret = (ret & 0xFF) | 0x40;
    }
    ssp_writew(SSP_CR1,ret);

    return 0;
}

 

10 spi数据传输函数

static int hi_spi_check_timeout(void)
{
    unsigned int value =  0;
    unsigned int tmp = 0;
    while (1)
    {
        ssp_readw(SSP_SR,value);
        if ((value & SPI_SR_TFE) && (!(value & SPI_SR_BSY)))
        {
            break;
        }

        if (tmp++ > MAX_WAIT)
        {
          printf("spi transfer wait timeout!\n");
        	return -1;
        }
        usleep(1);
    }
    return 0;
}

void spi_write_a9byte(unsigned char cmd_dat,unsigned char dat)
{
  unsigned short spi_data = 0;
  int ret = 0;
  if(cmd_dat)
  {
      spi_data = 1 << 8;
  }
  else
  {
      spi_data = 0 << 8;
  }

  spi_data |= dat;
  //printf("spi_data: %x %x\n", spi_data, dat);
  spi_enable();
  ssp_writew(SSP_DR,spi_data);
  ret =  hi_spi_check_timeout();
  if(ret != 0)
  {
      printf("spi_send timeout\n");
  }
  spi_disable();
}

void spi_write_a16byte(unsigned short spi_dat)
{
  int ret = 0;
  spi_enable();
  ssp_writew(SSP_DR, spi_dat);
  //printf("spi_data:0x%x\n",spi_dat);
  ret =  hi_spi_check_timeout();
  if(ret != 0)
  {
      printf("spi_send timeout\n");
  }
  spi_disable();
}

11 调用例子

void hi_ssp_demo(void)
{
  spi1_pinmux();
  spi1_open_clock();

  spi_disable();
  hi_ssp_set_frameform(SPI_FRAMEMODE, SPI_SPO, SPI_SPH, SPI_DATA_WIDTH);
  hi_ssp_set_serialclock(SPI_SCR, SPI_CPSDVSR);
  hi_ssp_alt_mode_set(1);
  hi_ssp_enable();

  
  spi_write_a16byte(0x1111);
  spi_write_a16byte(0x1112);
  spi_write_a16byte(0x1113);
  spi_write_a16byte(0x1114);

  hi_ssp_disable();
}

 

总结

1 SPI时钟、数据线、片选线管脚要先复用;

2 SPI模式、极性、相性、位宽、时钟速率要配置好;

3 SPI时钟要开启;我的样例一直在发送数据后检测SR_TFE中阻塞,原因是SPI_SCLK没有开启。

4 片选使能、高低有效位要配置好;

5 如果用于应用层,由于重复打开/dev/mem及mamp,使通讯速度变慢,可以针对性优化。

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
海思hi3516dv300硬件设计用户指南是一本针对海思公司推出的hi3516dv300芯片的硬件设计的指导册。该册的目的是帮助用户了解hi3516dv300芯片的硬件设计要求和规范,并提供相关的设计指导和建议。 hi3516dv300芯片是一款高性能、低功耗的视频处理芯片,主要应用于视频监控、智能交通和安防等领域。硬件设计是确保芯片正常工作和发挥最佳性能的基础,因此对于使用hi3516dv300芯片的设计人员来说,掌握硬件设计指南是非常重要的。 在这本指南中,首先介绍了hi3516dv300芯片的主要特性和功能,包括处理能力、视频编解码、音频处理等。然后详细讲解了芯片的外部接口和引脚分配,包括视频输入、输出接口、音频接口、以太网接口等。同时,还对时钟、电源、存储器和外设等方面的设计进行了说明和建议。 此外,指南中还提供了关于PCB设计的一些建议,包括地域分布、信号电源分离、阻抗匹配、电磁兼容性等方面,帮助设计人员提高设计质量和可靠性。 总之,海思hi3516dv300硬件设计用户指南是一本重要的参考资料,它提供了关于hi3516dv300芯片硬件设计方面的详尽说明和建议,对于希望使用该芯片进行设计的用户来说是不可或缺的。通过认真阅读和遵循指南中的规范和要求,设计人员可以更好地实现hi3516dv300芯片的应用,提高产品的性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值