基于正点原子 ALPHA开发板,长文预警,建议收藏用到之后再查看

文章目录

  • 主频与时钟
    • I.MX6U系统时钟分析
      • 7路PLL
      • 时钟树
      • 外设如何选择时钟
      • 需要初始化的PLL和PFD
    • I.MX6U系统配置
      • 系统主频的配置
      • 各个PLL时钟的配置
      • 其他外设时钟源配置
    • C代码
  • 中断
    • Cortex-A7中断系统
      • Cortex-A中断向量表
      • 中断向量偏移
      • GIC中断控制器
    • IMX6U中断号
      • 中断服务函数的编写
      • 编写按键中断例程。
      • 修改start.S
      • CP15协处理器
      • 退出中断
    • 中断汇编代码
      • 复位中断函数
      • IRQ中断函数
    • C语言中断编写
      • 带有中断号的中断处理函数
      • irqTable结构体
      • 中断函数服务表赋值函数
      • 中断初始化函数,在main函数最开始进行调用中
    • 中断初始化以及中断流程
      • 中断初始化流程
      • 中断过程
  • 定时器与延时
    • EPIT定时器
      • 相关寄存器
      • C程序编写
        • 初始化
        • 中断服务函数
        • 外部调用
    • GPT定时器
      • 相关寄存器
      • C程序编写
        • GPT初始化
        • 延时api
  • 串口
    • 6ULL的串口寄存器
    • C程序编写
      • 使能/失能代码
      • 初始化代码
      • 发送代码
      • 接收代码
    • 移植C标准库函数printf
  • DDR3
    • 内存简介
    • DDR3时间参数
    • I.MX6U MMDC控制器
    • DDR3L初始化与测试
      • ddr_stress_tester配置文件
      • 开始测试
  • RGBLCD
    • 显示原理
      • 像素点
      • 分辨率
      • 像素格式
      • LCD屏幕接口
      • LCD时间参数和LCD时序
        • 行时序
        • 帧时序
        • 像素时钟
      • 显存
    • 6ULL的RGB接口
      • LCDIF控制器接口原理
      • LCD像素时钟的设置
    • C程序编写
      • LCD驱动程序编写
        • LCD初始化
          • IO复用初始化
          • 时钟初始化
          • LCD控制器的初始化
          • 底层API
      • LCD操作API函数编写
  • RTC
    • RTC原理详解
    • 时间乱码的问题
      • 问题
      • 问题解决方法
    • C程序编写
      • 相关API以及结构体
      • RTC初始化
  • I2C
    • I2C协议简单介绍
    • 开发板上的I2C
      • AP3216C简介
    • 6ULL I2C接口详解
    • C程序编写
      • I2C模块
        • I2C初始化
        • I2C主机时序产生
        • I2C读写
        • I2C读写封装
      • AP3216C模块
        • 读写
        • 初始化
        • 读取数据
  • SPI
    • SPI协议简单介绍
    • 开发板上的SPI接口
    • 6ULL SPI接口详解
    • C程序编写
      • SPI模块
        • SPI初始化
        • 读写数据
      • 错误修改
  • 多点电容触摸屏
    • 多点电容触摸屏简介
    • FT54x6/FT52x6电容触摸芯片
    • C程序编写
      • 芯片的读写
      • 初始化芯片
      • 读取坐标
      • 中断服务
  • PWM背光实验
    • 6ULL的PWM
    • C程序编写
      • 初始化
  • 写在最后

主频与时钟

恩智浦在内部ROM中配置为默认的396MHz。但是官方文档显示最高可达800M左右,有些浪费CPU资源。

##硬件原理图分析

​ 1、32.768khz的晶振,共给RTC使用。

​ 2、在6U的T16和T17这两个IO上接了一个24MHz的晶振。

I.MX6U系统时钟分析

7路PLL

​ 为了方便生成时钟,6从24MHz晶振生出来7路PLL。这7路PLL中有的又生出来PFD。

PLL1:ARM PLL供给ARM内核。

PLL2:sysytem PLL,528MHz,528_PLL,此路PLL分出了4路PFD,分别为PLL2_PFD0~PFD3

PLL3: USB1 PLL,480MHz 480_PLL,此路PLL分出了4路PFD,分别为PLL3_PFD0~PFD3。

PLL4: Audio PLL,主供音频使用。

PLL5: Video PLL,主供视频外设,比如RGB LCD接口,和图像处理有关的外设。

PLL6:ENET PLL,主供网络外设。

PLL7: USB2_PLL ,480MHz,无PFD。

时钟树

太难了,原图分为了两截,我重新用ps拼接了一下

外设如何选择时钟

​ 比如ESAI时钟源选择:

​ PLL4、PLL3_PFD2、PLL5、PLL3。

需要初始化的PLL和PFD

​ PLL1,

​ PLL2,以及PLL2_PFD0~PFD3.

​ PLL3以及PLL3_PFD0~PFD3.

​ 一般按照时钟树里面的值进行设置。

I.MX6U系统配置

系统主频的配置

  1. 设置ARM内核主频为528MHz,设置CACRR寄存器的ARM_PODF位为2分频,然后设置PLL1=1056MHz即可。CACRR的bit3~0为ARM_PODF位,可设置0~7,分别对应1~8分频。应该设置CACRR寄存器的ARM_PODF=1。
  2. 设置PLL1=1056MHz。PLL1=pll1_sw_clk。pll1_sw_clk有两路可以选择,分别为pll1_main_clk,和step_clk,通过CCSR寄存器的pll1_sw_clk_sel位(bit2)来选择。为0的时候选择pll1_main_clk,为1的时候选step_clk。
  3. 设置系统时钟的时候需要给6ULL一个临时的时钟,也就是step_clk。在修改PLL1的时候需要将pll1_sw_clk切换到step_clk上。
  4. 设置step_clk。Step_clk也有两路来源,由CCSR的step_sel位(bit8)来设置,为0的时候设置step_clk为osc=24MHz。
  5. 时钟切换成功以后就可以修改PLL1的值。
  6. 通过CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位(bit6~0)来设置PLL1的频率,公式为:Output = fref*DIV_SEL/2
    因此 1056=24*DIV_SEL/2 -> DIEV_SEL=88。
    设置CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位=88即可。PLL1=1056MHz
  7. 设置CCM_ANALOG_PLL_ARM寄存器的ENABLE位(bit13)为1,也就是使能输出。
  8. 在切换回PLL1之前,设置置CACRR寄存器的ARM_PODF=1!!切记。

各个PLL时钟的配置

  1. PLL2和PLL3。PLL2固定为528MHz,PLL3固定为480MHz。
  2. 初始化PLL2_PFD0~PFD3。寄存器CCM_ANALOG_PFD_528用于设置4路PFD的时钟。比如PFD0= 528*18/PFD0_FRAC。设置PFD0_FRAC位即可。比如PLL2_PFD0=352M=528*18/PFD0_FRAC,因此FPD0_FRAC=27。
  3. 初始化PLL3_PFD0~PFD3

其他外设时钟源配置

​ AHB_CLK_ROOT、PERCLK_CLK_ROOT以及IPG_CLK_ROOT。

  1. 因为PERCLK_CLK_ROOT和IPG_CLK_ROOT要用到AHB_CLK_ROOT,所以我们要初始化AHB_CLK_ROOT。
  2. AHB_CLK_ROOT的初始化。
  3. AHB_CLK_ROOT=132MHz。 设置CBCMR寄存器的PRE_PERIPH_CLK_SEL位,设置CBCDR寄存器的PERIPH_CLK_SEL位0。设置CBCDR寄存器的AHB_PODF位为2,也就是3分频,因此396/3=132MHz。
  4. IPG_CLK_ROOT初始化
  5. 设置CBCDR寄存器IPG_PODF=1,也就是2分频。
  6. PERCLK_CLK_ROOT初始化
  7. 设置CSCMR1寄存器的PERCLK_CLK_SEL位为0,表示PERCLK的时钟源为IPG。

C代码

/*** @description   : 初始化系统时钟,设置系统时钟为528Mhz,并且设置PLL2和PLL3各个PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值.* @param       : 无* @return       : 无*/
void imx6u_clkinit(void)
{unsigned int reg = 0;/* 1、设置ARM内核时钟为528MHz *//* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而*      pll1_sw_clk有两个来源:pll1_main_clk和tep_clk(参考手册648页)。*      如果我们要让ARM内核跑到528M的话那必须选择pll1_main_clk作为pll1的时钟源。*      如果我们要修改pll1_main_clk时钟的话就必须先将pll1_sw_clk从pll1_main_clk切换到step_clk,*      当修改完pll1_main_clk以后在将pll1_sw_clk切换回pll1_main_clk。而step_clk的时钟源可以选择*         板子上的24MHz晶振。*/if((((CCM->CCSR) >> 2) & 0x1 ) == 0)   /* 当前pll1_sw_clk使用的pll1_main_clk*/{ CCM->CCSR &= ~(1 << 8);               /* 配置step_clk时钟源为24MH OSC */    CCM->CCSR |= (1 << 2);                /* 配置pll1_sw_clk时钟源为step_clk */}/* 1.2、设置pll1_main_clk为1056MHz,也就是528*2=1056MHZ,*      因为pll1_sw_clk进ARM内核的时候会被二分频!*      配置CCM_ANLOG->PLL_ARM寄存器*      bit13: 1 使能时钟输出*      bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,1056=24*div_select/2.0,*                   得出:div_select=    88  */CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F);   /* 配置pll1_main_clk=1056MHz */CCM->CCSR &= ~(1 << 2);                                 /* 将pll_sw_clk时钟重新切换回pll1_main_clk */CCM->CACRR = 1;                                            /* ARM内核时钟为pll1_sw_clk/2=1056/2=528Mhz *//* 2、设置PLL2(SYS PLL)各个PFD */reg = CCM_ANALOG->PFD_528;reg &= ~(0X3F3F3F3F);     /* 清除原来的设置                      */reg |= 32<<24;             /* PLL2_PFD3=528*18/32=297Mhz     */reg |= 24<<16;             /* PLL2_PFD2=528*18/24=396Mhz(DDR使用的时钟,最大400Mhz) */reg |= 16<<8;                /* PLL2_PFD1=528*18/16=594Mhz     */reg |= 27<<0;              /* PLL2_PFD0=528*18/27=352Mhz     */CCM_ANALOG->PFD_528=reg;  /* 设置PLL2_PFD0~3                *//* 3、设置PLL3(USB1)各个PFD */reg = 0;                    /* 清零   */reg = CCM_ANALOG->PFD_480;reg &= ~(0X3F3F3F3F);      /* 清除原来的设置                          */reg |= 19<<24;             /* PLL3_PFD3=480*18/19=454.74Mhz  */reg |= 17<<16;             /* PLL3_PFD2=480*18/17=508.24Mhz  */reg |= 16<<8;              /* PLL3_PFD1=480*18/16=540Mhz     */reg |= 12<<0;              /* PLL3_PFD0=480*18/12=720Mhz     */CCM_ANALOG->PFD_480=reg;  /* 设置PLL3_PFD0~3                    */  /* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/CCM->CBCMR &= ~(3 << 18);    /* 清除设置*/ CCM->CBCMR |= (1 << 18);    /* pre_periph_clk=PLL2_PFD2=396MHz */CCM->CBCDR &= ~(1 << 25);  /* periph_clk=pre_periph_clk=396MHz */while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 *//* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是* 我没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。* 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!!* 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF,* AHB_ROOT_CLK也依旧等于396/3=132Mhz。* 淦,就这?就这?就这?*/
#if 0/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */CCM->CBCDR &= ~(7 << 10);   /* CBCDR的AHB_PODF清零 */CCM->CBCDR |= 2 << 10;      /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
#endif/* 5、设置IPG_CLK_ROOT最小3Mhz,最大66Mhz (boot rom自动设置好了可以不用设置)*/CCM->CBCDR &= ~(3 << 8);   /* CBCDR的IPG_PODF清零 */CCM->CBCDR |= 1 << 8;       /* IPG_PODF 2分频,IPG_CLK_ROOT=66MHz *//* 6、设置PERCLK_CLK_ROOT时钟 */CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT时钟源为IPG */CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF位清零,即1分频 */
}

中断

Cortex-A7中断系统

Cortex-A中断向量表

​ Cortex-A中断向量表有8个中断,其中重点关注IRQ。

外部中断均进入IRQ中断,根据中断号进行区分。

Cortex-A的中断向量表需要用户自己定义。

中断向量偏移

​ 裸机例程都是从0X87800000开始的,因此要设置中断向量偏移。

GIC中断控制器

​ 同NVIC一样,GIC用于管理Cortex-A的中断。GIC提供了开关中断,设置中断优先级。

  1. GIC 将众多的中断源分为分为三类:
  2. SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
  3. PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
  4. SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信

IMX6U中断号

​ 为了区分不同的中断,引入了中断号。

​ ID0~ID15是给SGI。

​ ID16~ID31是给PPI。

​ ID32~ID1019给SPI,也就是按键中断、串口中断。。。。

​ 6ULL支持128个中断。

中断服务函数的编写

​ IRQ中断服务函数的编写。

​ 在IRQ中断服务函数里面去查找并运行的具体的外设中断服务函数。

编写按键中断例程。

​ KEY0使用UART1_CTS这个IO。编写UART1_CTS的中断代码。

修改start.S

​ 添加中断向量表,编写复位中断服务函数和IRQ中断服务函数。

/*** 描述:  _start函数,首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)*           ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)* _start 最开始加入以下代码*/
_start:ldr pc, =Reset_Handler      /* 复位中断                     */  ldr pc, =Undefined_Handler /* 未定义中断                    */ldr pc, =SVC_Handler     /* SVC(Supervisor)中断        */ldr pc, =PrefAbort_Handler   /* 预取终止中断                   */ldr pc, =DataAbort_Handler   /* 数据终止中断                   */ldr   pc, =NotUsed_Handler   /* 未使用中断                    */ldr pc, =IRQ_Handler     /* IRQ中断                    */ldr pc, =FIQ_Handler     /* FIQ(快速中断)未定义中断           */
  1. 编写复位中断服务函数,内容如下:
  2. 关闭I,D Cache和MMU。(内部ROM已关)
  3. 设置处理器9中工作模式下对应的SP指针(每种工作模式都有自己独有的SP指针)。要使用中断那么必须设置IRQ模式下的SP指针。索性直接设置所有模式下的SP指针。
  4. 清除bss段。
  5. 跳到main函数

MMU负责地址映射,将CPU中虚拟地址VA映射到物理地址PA

i-cache(instruction cache):指令高速缓冲存储器

dcache(data cache):数据高速缓冲存储器

CP15协处理器

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有

16 个 32 位寄存器。CP15 协处理器的访问通过如下另个指令完成

MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。

MCR :将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中:

@MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>@各个参数的意义
@cond:指令执行的条件码,如果忽略的话就表示无条件执行。
@opc1:协处理器要执行的操作码。
@Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
@CRn:CP15 协处理器的目标寄存器。
@CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
@CRm 设置为 C0,否则结果不可预测。
@opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。

SCTLR寄存器。也就是系统控制寄存器,此寄存器bit0用于打开和关闭MMU,bit1控制对齐,bit2控制D Cache的打开和关闭。Bit11用于控制分支预测。Bit12用于控制I Cache。

下图

所以SCTLR的读取方式如下

mrc     p15, 0, r0, c1, c0, 0     /* 读取SCTLR寄存器到R0中*/
mcr     p15, 0, r0, c1, c0, 0     /* 将r0中的值写入到SCTLR寄存器中 */

中断向量偏移设置

​ 将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器。

同理,可得VBAR寄存器的访问方法如下:

@mcr{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
mrc p15,0,r0,c12,c0,0
mcr p15,0,r0,c12,c0,0

IRQ中断服务函数

mrc p15, 4, r1, c15, c0, 0

读取CBAR寄存器。CBAR寄存器保存了GIC控制器的寄存器组首地址。GIC寄存器组偏移0x10000x1fff为GIC的分发器。0x20000x3fff为CPU接口端。

​ 代码中,R1寄存器保存着GIC控制器的CPU接口端基地址。读取CPU接口段的GICC_IAR寄存器的值保存到R0寄存器里面。

​ GICC_IAR的bit9~0存放着中断ID号,然后跳转到对应的中断处理函数。

​ system_irqhandler就是具体的中断处理函数,此函数有一个参数,为GICC_IAR寄存器的值。

​ system_irqhandler处理完具体的中断以后,需要将对应的中断ID值写入到GICC_EOIR寄存器里面。

退出中断

subs pc, lr, #4              /* 将lr-4赋给pc */

中断处理完成以后就要重新返回到曾经被中断打断的地方运行,这里为什么要将lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?ARM 的指令是三级流水线:取指、译指、执行,pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8。

比如下面代码示例:

0X2000 MOV R1, R0 ;执行

0X2004 MOV R2, R3 ;译指

0X2008 MOV R4, R5 ;取值 PC

上面示例代码中,左侧一列是地址,中间是指令,最右边是流水线。当前正在执行 0X2000地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。当中断处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到 lr 里面保存的地址处(0X2008)开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这是一个很严重的错误!所以就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,R3”开始执行。

中断汇编代码

复位中断函数

复位中断函数会在上电时执行

/* 复位中断 */
Reset_Handler:cpsid i                       /* 关闭全局中断 *//* 关闭I,DCache和MMU * 采取读-改-写的方式。*/mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中                          */bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache                */bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache                  */bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐                     */bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测                    */bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU                        */mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中                  */#if 0/* 汇编版本设置中断向量表偏移 该部分在c语言的中断编写种也有写到,因此注释*/ldr r0, =0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsb  @数据同步指令,有该指定确保之前的数据全部写入或者读取成功isb  @指令同步指令,有该指定确保之前的指令全部写入或者读取成功
#endif/* 设置各个模式下的栈指针,* 注意:IMX6UL的堆栈是向下增长的!* 堆栈指针地址一定要是4字节地址对齐的!!!* DDR范围:0X80000000~0X9FFFFFFF*//* 进入IRQ模式 */mrs r0, cpsrbic r0, r0, #0x1f    /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4   */orr r0, r0, #0x12     /* r0或上0x13,表示使用IRQ模式                   */msr cpsr, r0      /* 将r0 的数据写入到cpsr_c中                    */ldr sp, =0x80600000  /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB *//* 进入SYS模式 */mrs r0, cpsrbic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4   */orr r0, r0, #0x1f     /* r0或上0x13,表示使用SYS模式                   */msr cpsr, r0      /* 将r0 的数据写入到cpsr_c中                    */ldr sp, =0x80400000  /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB *//* 进入SVC模式 */mrs r0, cpsrbic r0, r0, #0x1f     /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4   */orr r0, r0, #0x13     /* r0或上0x13,表示使用SVC模式                   */msr cpsr, r0      /* 将r0 的数据写入到cpsr_c中                    */ldr sp, =0X80200000  /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */cpsie i                /* 打开全局中断 */
#if 0/* 使能IRQ中断 */mrs r0, cpsr      /* 读取cpsr寄存器值到r0中           */bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */msr cpsr, r0       /* 将r0重新写入到cpsr中            */
#endifb main                /* 跳转到main函数                */

IRQ中断函数

IRQ_Handler:push {lr}                    /* 保存lr地址 ,lr为当前pc的值*/push {r0-r3, r12}         /* 保存r0-r3,r12寄存器 */mrs r0, spsr             /* 读取spsr寄存器 */push {r0}                    /* 保存spsr寄存器 */mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49* Cortex-A7 Technical ReferenceManua.pdf P68 P138*/                          add r1, r1, #0X2000         /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */ldr r0, [r1, #0XC]           @ GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,@ GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据@ 这个中断号来绝对调用哪个中断服务函数@push {r0, r1}              /* 保存r0,r1 */cps #0x13                  /* 进入SVC模式,允许其他中断再次进去 */push {lr}                    /* 保存SVC模式的lr寄存器 */ldr r2, =system_irqhandler  /* 加载C语言中断处理函数到r2寄存器中*/blx r2                       /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */pop {lr}                    /* 执行完C语言中断服务函数,lr出栈 */cps #0x12                 /* 进入IRQ模式 */pop {r0, r1}               str r0, [r1, #0X10]         /* 中断执行完成,写EOIR */pop {r0}                       msr spsr_cxsf, r0           /* 恢复spsr */pop {r0-r3, r12}            /* r0-r3,r12出栈 */pop {lr}                   /* lr出栈 */subs pc, lr, #4               /* 将lr-4赋给pc */

C语言中断编写

带有中断号的中断处理函数

/*** @brief             : C语言中断服务函数,irq汇编中断服务函数会调用此函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行。* @param - giccIar       : 中断号* @return                 : 无*/
void system_irqhandler(unsigned int giccIar)
{uint32_t intNum = giccIar & 0x3FFUL;/* 检查中断号是否符合要求 */if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)){return;}irqNesting++; /* 中断嵌套计数器加一 *//* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);irqNesting--;    /* 中断执行完成,中断嵌套寄存器减一 */}

irqTable结构体

/* 中断服务函数形式 */
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);/* 中断服务函数结构体*/
typedef struct _sys_irq_handle
{system_irq_handler_t irqHandler; /* 中断服务函数 */void *userParam;                 /* 中断服务函数参数 */
} sys_irq_handle_t;/* 中断嵌套计数器 */
static unsigned int irqNesting;/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

中断函数服务表赋值函数

void system_irqtable_init(void)
{unsigned int i = 0;irqNesting = 0;/* 先将所有的中断服务函数设置为默认值 */for(i = 0; i < NUMBER_OF_INT_VECTORS; i++){system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);}
}/*** @brief               : 给指定的中断号注册中断服务函数 * @param - irq           : 要注册的中断号* @param - handler        : 要注册的中断处理函数* @param - usrParam    : 中断服务处理函数参数* @return              : 无*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{irqTable[irq].irqHandler = handler;irqTable[irq].userParam = userParam;
}/*** @brief               : 默认中断服务函数* @param - giccIar       : 中断号* @param - usrParam   : 中断服务处理函数参数* @return              : 无*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{while(1) {}
}/*** IRQn_Type  为core_ca7中的关于终端号的定义* 这两个函数实现了IRQ中断服务函数的赋值* 此处所有的中断服务函数均使用了默认的中断服务函数以作示例,在具体项目中,需要对其具体定义*/

中断初始化函数,在main函数最开始进行调用中

/*** @brief     : 中断初始化函数* @param      : 无* @return       : 无*/
void int_init(void)
{GIC_Init();                        /* 初始化GIC   此函数声明在core_ca7.h                        */system_irqtable_init();               /* 初始化中断表                           */__set_VBAR((uint32_t)0x87800000);     /* 中断向量表偏移,偏移到起始地址                   */
}

中断初始化以及中断流程

中断初始化流程

上电执行汇编语言中的 _start函数, 在函数中定义了a7内核支持的八种中断服务函数(其实是7个,有一个未使用)。

执行复位函数,因为初始化了中断服务函数,因此在之后执行复位中断函数,先关闭全局中断以及保存在通用寄存器的中断服务函数指针、I cache、D cache以及MMU也需要关闭。之后定义9种运行状态下的sp指针(栈指针),跳转到c语言中的main函数

main函数执行int_init函数,初始化所有的中断服务函数。

中断过程

当中断发生时,进入到IRQ_Handler函数,保存现场

IRQ_Handler判断中断ID号,进入中断ID对应的中断服务函数

执行用户指定的中断服务内容

恢复现场

定时器与延时

EPIT定时器

实现周期性的中断以及定时功能。

  • EPIT是32位的一个向下计数器。
  • EPIT的时钟源可以选择,例程选择ipg_clk=66MHz。
  • 可以对时钟源进行分频,12位的分频器,0~4095分别代表1~4096分频。
  • 开启定时器以后,计数寄存器会每个时钟减1,如果和比较寄存器里面的值相等的话就会触发中断。
  • EPIT有两种工作模式:Set-add-forget,一个是free-runing
  • 5、6ULL有两个EPIT定时器。

​ EPIT_CR寄存器用于配置EPIT。

相关寄存器

​ EPIT_CR bit0为1,设置EPIT使能,bit1为1,设置计数器的初始值为记载寄存器的值。Bit2为1使能比较中断,bit3为1设置定时器工作在set-and-forget模式下。Bit15~bit4设置分频值。Bit25:24设置时钟源的选择,我们设置为1,那么EPIT的时钟源就为ipg_clock=66MHz

​ EPIT_SR寄存器,只有bit0有效,表示中断状态,写1清零。当OCIF位为1的时候表示中断发生,为0的时候表示中断未发生。我们处理完定时器中断以后一定要清除中断标志位。

​ EPIT_LR寄存器设置计数器的加载值。计数器每次计时到0以后就会读取LR寄存器的值重新开始计时。

​ CMPR比较计数器,当计数器的值和CMPR相等以后就会产生比较中断。

​ 使用EPIT实现500ms周期的定时器。500ms进入一次中断。

C程序编写

初始化

/*** @brief         : 初始化EPIT定时器.*                    EPIT定时器是32位向下计数器,时钟源使用ipg=66Mhz       * @param - frac   : 分频值,范围为0~4095,分别对应1~4096分频。* @param - value    : 倒计数值。* @return           : 无*/
void epit1_init(unsigned int frac, unsigned int value)
{if(frac > 0XFFF)frac = 0XFFF;EPIT1->CR = 0;    /* 先清零CR寄存器 *//** CR寄存器:* bit25:24 01 时钟源选择Peripheral clock=66MHz* bit15:4  frac 分频值* bit3:    1  当计数器到0的话从LR重新加载数值* bit2: 1  比较中断使能* bit1:    1  初始计数值来源于LR寄存器值* bit0:    0  先关闭EPIT1*/EPIT1->CR = (1<<24 | frac << 4 | 1<<3 | 1<<2 | 1<<1);EPIT1->LR = value;  /* 倒计数值 */EPIT1->CMPR    = 0;   /* 比较寄存器,当计数器值和此寄存器值相等的话就会产生中断 *//* 使能GIC中对应的中断          */GIC_EnableIRQ(EPIT1_IRQn);/* 注册中断服务函数             */system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)epit1_irqhandler, NULL); EPIT1->CR |= 1<<0;    /* 使能EPIT1 */
}

中断服务函数

/*** @brief         : EPIT中断处理函数*/
void epit1_irqhandler(void)
{ if(EPIT1->SR & (1<<0))           /* 判断比较事件发生 */{/* User code begin*//* User code end  */}EPIT1->SR |= 1<<0;                /* 清除中断标志位 */
}

外部调用

epit1_init(0, 66000000/2);   /* 初始化EPIT1定时器,1分频计数值为:66000000/2,也就是定时周期为500ms。*/

GPT定时器

正点原子使用该定时器实现高精度精准阻塞延时

  • GPT定时器是32位向上计数器。
  • GPT定时器有捕获的功能。
  • GPT定时器支持比较输出或中断功能。
  • GPT定时器有一个12位的分频器。
  • GPT时钟源可以选择,这里我们使用ipg_clk=66M作为GPT的时钟源。
  • GPT定时器有两种工作模式:restart和free-run。

Restart模式下:定时器计数值和比较寄存器OCR的值相等的话定时器就会重新从0开始计时。注意!只有比较通道1才有此功能。

Free-run模式:所有三个输出比较通道都适用。从0开始一直加到0xffffffff,然后重新从0开始,周而复始。

相关寄存器

GPT_CR寄存器,bit0为GPT使能位,为0的时候关闭GPT,为1的时候使能GPT。Bit1确定GPT定时器计数器的初始值,为0的时候表示GPT定时器计数值默认为上次关闭的时候遗留的值,为1的话计数值为0。Bit8~6为时钟源的选择,设置为1,表示GPT时钟源为ipg_clk=66MHz。bit9设置GPT定时器工作模式,为0的时候工作在restart模式,为1的时候工作在free-run模式。Bit15软件复位。

GPT_PR寄存器的bit110为分频值,可设置0-4095,表示14096分频。

GPT_SR寄存器,bit5表示溢出发生,bit4和bit3分别为输入通道2和1的捕获中断标志位。Bit20,也就是OF3OF1为比较中断。

GPT_IR寄存器,也就是中断使能寄存器

C程序编写

GPT初始化

/*** @brief     : 延时有关硬件初始化,主要是GPT定时器GPT定时器时钟源选择ipg_clk=66Mhz* @param     : 无* @return       : 无*/
void delay_init(void)
{GPT1->CR = 0;                  /* 清零,bit0也为0,即停止GPT              */GPT1->CR = 1 << 15;             /* bit15置1进入软复位                 */while((GPT1->CR >> 15) & 0x01);  /*等待复位完成                        *//*** GPT的CR寄存器,GPT通用设置* bit22:20  000 输出比较1的输出功能关闭,也就是对应的引脚没反应* bit9:    0   Restart模式,当CNT等于OCR1的时候就产生中断* bit8:6   001 GPT时钟源选择ipg_clk=66Mhz* bit*/GPT1->CR = (1<<6);/*** GPT的PR寄存器,GPT的分频设置* bit11:0  设置分频值,设置为0表示1分频,*          以此类推,最大可以设置为0XFFF,也就是最大4096分频*/GPT1->PR = 65; /* 设置为65,即66分频,因此GPT1时钟为66M/(65+1)=1MHz *//*** GPT的OCR1寄存器,GPT的输出比较1比较计数值,*   GPT的时钟为1Mz,那么计数器每计一个值就是就是1us。* 为了实现较大的计数,我们将比较值设置为最大的0XFFFFFFFF,* 这样一次计满就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min* 也就是说一次计满最多71.5分钟,存在溢出*/GPT1->OCR[0] = 0XFFFFFFFF;GPT1->CR |= 1<<0;           //使能GPT1
}

延时api

/*** @brief         : 微秒(us)级延时* @param - value    : 需要延时的us数,最大延时0XFFFFFFFFus* @return           : 无*/
void delayus(unsigned    int usdelay)
{unsigned long oldcnt,newcnt;unsigned long tcntvalue = 0;  /* 走过的总时间  */oldcnt = GPT1->CNT;while(1){newcnt = GPT1->CNT;if(newcnt != oldcnt){if(newcnt > oldcnt)        /* GPT是向上计数器,并且没有溢出 */tcntvalue += newcnt - oldcnt;else                   /* 发生溢出    */tcntvalue += 0XFFFFFFFF-oldcnt + newcnt;//0xFFFFFFFF为初始化的比较值oldcnt = newcnt;if(tcntvalue >= usdelay)/* 延时时间到了 */break;                 }}
}/*** @brief           : 毫秒(ms)级延时* @param - msdelay  : 需要延时的ms数* @return            : 无*/
void delayms(unsigned    int msdelay)
{int i = 0;for(i=0; i<msdelay; i++){delayus(1000);}
}

串口

串口协议这种东西,不用在这里写了吧。

6ULL的串口寄存器

​ 6ULL的UART_URXD寄存器保存这串口接收到的数据。

​ UART_UTXD寄存器为发送数据寄存器,如果需要通过串口发送数据,只需要将数据写入到UART_UTXD寄存器里面。

​ UART_UCR1~UCR4都是串口的控制寄存器。UART_UCR1的bit0是UART的使能位,为1的时候使能UART。Bit14为自动检测波特率使能位,为1的时候使能波特率自动检测。

​ UART_UCR2的bit0为软件复位位。为0的时候复位UART。Bit1使能UART的接收,我们要配置为1。Bit2为发送使能,要设置为1。Bit5设置数据位,0的话表示7位数据位,1的话表示8位数据位。Bit6设置停止位,0的话表示1位停止位,1的话表示2位。Bit7奇偶校验位,为0的时候是偶校验,为1的时候是计校验。Bit8校验使能位,为0的时候关闭校验。

​ UART_UCR3的bit2必须为1!!!

​ UART_UFCR寄存器的bit9~7设置分频值,UART的时钟源=PLL3/6=480/6=80MHz。CSCDR1寄存器的UART_CLK_SEL位设置UART的时钟源,为0的时候UART时钟源为80MHz,为1的时候UART时钟源为24M晶振。CSCDR1寄存器的UART_CLK_PODF位控制分频,一般设置为1分频,因此UART_CLK_ROOT=80MHZ

​ UART_UFCR、UART_UBIR和UART_UBMR这三个寄存器决定了串口波特率。

公式

​ UART_USR2寄存器的bit0为1的时候表示有数据可以读取。Bit3为1的时候表示数据发送完成。

C程序编写

使能/失能代码

/*** @brief       : 关闭指定的UART* @param - base: 要关闭的UART* @return       : 无*/
void uart_disable(UART_Type *base)
{base->UCR1 &= ~(1<<0);
}/*** @brief       : 打开指定的UART* @param - base: 要打开的UART* @return     : 无*/
void uart_enable(UART_Type *base)
{base->UCR1 |= (1<<0);
}/*** @brief       : 复位指定的UART* @param - base: 要复位的UART* @return     : 无*/
void uart_softreset(UART_Type *base)
{base->UCR2 &= ~(1<<0);           /* UCR2的bit0为0,复位UART        */while((base->UCR2 & 0x1) == 0); /* 等待复位完成                    */
}

初始化代码

/*** @brief       : 初始化串口1所使用的IO引脚* @param     : 无* @return       : 无*/
void uart_io_init(void)
{/* 1、初始化IO复用 * UART1_RXD -> UART1_TX_DATA* UART1_TXD -> UART1_RX_DATA*/IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX,0);    /* 复用为UART1_TX */IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX,0); /* 复用为UART1_RX *//* 2、配置UART1_TX_DATA、UART1_RX_DATA的IO属性 *bit 16:0 HYS关闭*bit [15:14]: 00 默认100K下拉*bit [13]: 0 keeper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 驱动能力R0/6*bit [0]: 0 低转换率*/IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX,0x10B0);IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX,0x10B0);
}/*** @brief       : 初始化串口1,波特率为115200* @param        : 无* @return       : 无*/
void uart_init(void)
{    /* 设置UART时钟源频率为80M */CCM->CSCDR1 &= ~(1 << 6);   /* UART时钟源为pll3_80m */CCM->CSCDR1 &= ~0X3F;     /* UART时钟1分频            *//* 1、初始化串口IO          */uart_io_init();/* 2、初始化UART1              */uart_disable(UART1);  /* 先关闭UART1         */uart_softreset(UART1);    /* 软件复位UART1        */UART1->UCR1 = 0;      /* 先清除UCR1寄存器 *//** 设置UART的UCR1寄存器,关闭自动波特率* bit14: 0 关闭自动波特率检测,我们自己设置波特率*/UART1->UCR1 &= ~(1<<14);/** 设置UART的UCR2寄存器,设置内容包括字长,停止位,校验模式,关闭RTS硬件流控* bit14: 1 忽略RTS引脚* bit8: 0 关闭奇偶校验* bit6: 0 1位停止位* bit5: 1 8位数据位* bit2: 1 打开发送* bit1: 1 打开接收*/UART1->UCR2 |= (1<<14) | (1<<5) | (1<<2) | (1<<1);/** UART1的UCR3寄存器* bit2: 1 必须设置为1!参考IMX6ULL参考手册3624页*/UART1->UCR3 |= 1<<2; /** 设置波特率* 波特率计算公式:Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1)) * 如果要设置波特率为115200,那么可以使用如下参数:* Ref Freq = 80M 也就是寄存器UFCR的bit9:7=101, 表示1分频* UBMR = 3124* UBIR =  71* 因此波特率= 80000000/(16 * (3124+1)/(71+1))=80000000/(16 * 3125/72) = (80000000*72) / (16*3125) = 115200*/UART1->UFCR = 5<<7; //ref freq等于ipg_clk/1=80MhzUART1->UBIR = 71;UART1->UBMR = 3124;/* 使能串口 */uart_enable(UART1);
}

发送代码

/*** @brief       : 发送一个字符* @param - c : 要发送的字符* @return      : 无*/
void putc(unsigned char c)
{while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */UART1->UTXD = c & 0XFF;               /* 发送数据 */
}/*** @brief       : 发送一个字符串* @param - str    : 要发送的字符串* @return     : 无*/
void puts(char *str)
{char *p = str;while(*p)putc(*p++);
}

makefile 修改:

putc和puts编译的时候会提示吧报错,要在Makefile中添加 -fno-builtin

$(SOBJS) : obj/%.o : %.S$(CC) -Wall -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

接收代码

未使用中断死等的代码

/*** @brief       : 接收一个字符* @param         : 无* @return       : 接收到的字符*/
unsigned char getc(void)
{while((UART1->USR2 & 0x1) == 0);/* 等待接收完成 */return UART1->URXD;                /* 返回接收到的数据 */
}

移植C标准库函数printf

和STM32类似,printf打印数据最后会调用putc函数。

移植stdio时,直接找到格式化函数源码,移植到工程中即可

移植的printf不支持浮点计算和输出!!!!!

DDR3

内存简介

RAM

随机存储器,可以随时进行读写操作,速度很快,掉电以后数据会丢失。比如内存条、SRAM、SDRAM、DDR 等都是 RAM。RAM 一般用来保存程序数据、中间结果

SRAM:在上电之后就会一直保存数据,成本较高,但是足够快速,可用作Cache。

SDRAM:功耗低,成本低,适合做大容量存储。SDRAM需要时钟线,常见的频率就是100MHz,133MHz,166Mhz,200MHz

DDR3:是 Double Data Rate 3 SDRAM,容量更大,成本更低

DDR3时间参数

  • 时间参数
  • 传输速率
  • 速率不同,时序不同
  • tRCD
  • 行地址、列地址之间的延时
  • CL
  • 列地址选通潜伏期
  • tRC
  • 两个 ACTIVE 命令,或者 ACTIVE 命令到 REFRESH 命令之间的周期
  • tRAS
  • ACTIVE 命令到 PRECHARGE 命令之间的最小时间

I.MX6U MMDC控制器

I.MX6U通过MMDC控制器来与DDR3进行连接通讯

1、多模支持DDR3/DDR3L LPDDR2 x16位

2、MMDC最高支持DDR3频率是400MHz,800MT/S

3、MMDC提供的DDR3连接信号。6ULL给DDR提供了专用的IO,

DDR时钟配置

DDR使用的时钟源为MMDC_CLK_ROOT=PLL2_PFD2=396MHz。

CBCMR寄存器的PRE_PERIPH2_CLK_SE位来选择,也就是bit22:21,设置pre_periph2时钟源,设置为01,也就是PLL2_PFD2作为pre_periph2时钟源。

CBCDR寄存器的PERIPH2_CLK_SEL位,也就是bit26,设置为0,PLL2作为MMDC时钟源,396MHz。

CBCDR寄存器的FABRIC_MMDC_PODF位,bit5:3,设置0,也就是1分频。最终MMDC_CLK_ROOT=396MHz。

DDR3L初始化与测试

ddr_stress_tester配置文件

NXP 提供了一个非常好用的 DDR 初始化工具,叫做 ddr_stress_tester

①、此工具通过 USB OTG 接口与开发板相连接,也就是通过 USB OTG 口进行 DDR 的初始化与测试。

②、此工具有一个默认的配置文件,为 excel 表,通过此表可以设置板子的 DDR 信息,最后生成一个.inc 结尾的 DDR 初始化脚本文件。这个.inc 文件就包含了 DDR 的初始化信息,一般都是寄存器地址和对应的寄存器值。

③、此工具会加载.inc 表里面的 DDR 初始化信息,然后通过 USB OTG 接口向板子下载DDR 相关的测试代码,包括初始化代码。

④、对此工具进行简单的设置,即可开始 DDR 测试,一般要先做校准,因为不同的 PCB其结构肯定不同,必须要做一次校准,校准完成以后会得到两个寄存器对应的校准值,我们需要用这个新的校准值来重新初始化 DDR。

⑤、此工具可以测试板子的 DDR 超频性能,一般认为 DDR 能够以超过标准工作频率10%~20%稳定工作的话就认定此硬件 DDR 走线正常。

⑥、此工具也可以对 DDR 进行 12 小时的压力测试。

进行测试需要弹出SD卡,修改启动方式!!!连接USB

开始测试

首先根据选择的DDR型号来对excel进行配置。

正点原子使用的DDR3型号只需要修改以下红框中的数据,生成inc结尾的文件。

ddr_stress_tester界面如下:

  1. 选择生成的inc文件
  2. 选择芯片以及DDR的大小
  3. ARM的速度选择
  4. 这里千万不要选,否则会报错
  5. 下载等待即可
  6. 校准频率以及开始校准,校准时间较长可能花费10+分钟
  7. 校准结果,将此结果对应的寄存器以及寄存器值修改到inc中,不存在的寄存器可忽略
  8. 超频测试,修改校准结果后重启,进行超频测试,可以最大达到多少频率,此处测试时间也较长。当超频到不能使用的频率之后会报错,此时可以找到最大的超频频率,我测试我的为561MHz

RGBLCD

显示原理

LCD 全称是 Liquid Crystal Display,也就是液晶显示器

像素点

​ 不管是液晶屏,还是手机,平板,RGBLCD屏幕他都是有由一个个的彩色小灯构成的。彩色点阵屏每个像素点有三个小灯,红色、绿色和蓝色,也叫做RGB。RGB就是光的三原色。通过调整RGB三种颜色的比例,就可以变成彩色。

分辨率

​ 要想显示文字,图片,视频等等就需要很多个像素点,分辨率说的就是像素点的个数,1080P、720P、2K、4K,8K。1080P=19201080,表示一行有1920个像素点,一列有1080个。显示器有尺寸!24寸,27寸、55寸。尺寸不变的情况下,分辨率越高,显示效果越精细。4K=38402160相当于4个1080P

​ 正点原子的RGB屏幕有:4.3寸480272,800480,7寸的800480和1024600,10.1寸的1280*800。

​ Iphone4屏幕尺寸是3.5寸,960*640分辨率,PPI=327。PPI 为分辨率除以尺寸

像素格式

​ 将RGB三种颜色进行量化,每种颜色用8bit表示,RGB就需要8-8-8共24bit。可以描述出2^24种颜色16777216=1677万种颜色。现在流行10bit,HDR10,支持HDR效果的10bit面板,RGB10 10 10。

​ 在RGB888的基础上在加上8bit的ALPHA通道,也就是透明通道,ARGB8888=32位。

LCD屏幕接口

​ RGB格式的屏幕,一般叫做RGB接口屏。

屏幕接口有:MIPI、LVDS、MCU、RGB接口。

正点原子屏幕ID:使用ID可以识别出不同的屏幕,在RGBLCD屏幕上对R7,G7,B7焊接上拉或下拉电阻实现不同的ID。,

正点原子的ALPHA地板RGB屏幕接口用了3个3157模拟开关。原因是防止LCD屏幕上的ID电阻影响到6ULL的启动。

LCD时间参数和LCD时序

行时序

​ 水平:

​ HSYNC:水平同步信号,行同步信号,当出现HSYNC信号的时候表示新的一行开始显示

​ 1、产生HSYNC信号,表示新的一行开始显示,HSYNC信号得维持一段时间,这个时间叫做HSPW。

​ 2、HSYNC信号完成以后,需要一段时间延时,这段时间叫做HBP。

​ 3、显示1024个像素点的数据,1024个clk。

​ 3、一行像素显示完成以后,到HSYNC下一行信号的产生之间有个延时,叫做HFP。

​ 因此真正显示一行所需的时间计时HSPW+HBP+WIDTH(屏幕水平像素点个数,比如1024)+HFP=20+140+1024+160=1344CLK.

帧时序

​ 垂直:

​ VSYNC:垂直同步信号,帧同步信号,当出现VSYNC信号的时候表示新的一帧开始显示。

​ 1、VSYNC信号,持续一段时间,位VSPW。

​ 2、VSYNC信号完成以后,需要一段时间,叫做VBP

​ 3、VBP信号结束以后,就是要显示的行数,比如600行,

​ 4、所有的行显示完成以后,一段VFP延时,
DE持续时间为有效数据

我使用的是正点原子的7寸屏幕(1024*600):

像素时钟

像素时钟就是 RGB LCD 的时钟信号,以 1024*600这款屏幕为例,显示一帧图像所需要的时钟数就是:

= (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)

= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160)

= 635 * 1344

= 853440。

显示一帧图像需要853440个时钟数,那么显示60帧就是:853440 * 60 = 51206400≈51.2M,

所以像素时钟就是 51.2MHz。

显存

​ 显存:显示存储空间,采用ARGB8888=32bit=4B。这4个字节的数据表示一个像素点的信息,必须得存起来。1024*600=2.5MB。因此需要流出2.5MB的内存给LCD用。

6ULL的RGB接口

LCDIF控制器

LCDIF控制器接口原理

  1. 使用DOTCLK接口,也就是VSYNC、HSYNC、ENABLE(DE)和DOTCLK(PCLK)
  2. LCDIF_CTRL寄存器,bit0必须置1,bit1设置数据格式24位全部有效,设置为0。Bit5设置LCDIF接口工作在主机模式下,要置1。Bit9:8设置输入像素格式位24bit,写3。Bit11:10,设置数据传输宽度位24bit,写3。Bit13:12设置数据交换,我们不需要交换设置位0。Bit15:14设置输入数据交换,不交换设置位0。Bit17置1,LCDIF工作在DOTCLK模式下。Bit19必须置1,因为工作在DOTCL模式。Bit31是复位功能必须置0.
  3. LCDIF_CTRL1寄存器的bit19:16设置位0X7。24位的格式
  4. LCDIF_TRANSFER_COUNT寄存器的bit15:0是LCD一行的像素数,1024。Bit31:16是LCD一共有多少行,600行
  5. LCDIF_VDCTRL0寄存器,bit17:0为vspw参数。Bit20设置vsync信号的宽度单位 ,设置为1。Bit21设置为1。Bit24设置ENABLE信号极性,为0的时候是低电平有效,为1是高电平,我们设置为1。Bit25设置CLK信号极性,设置为0.。bit26设置HSYNC信号极性,设置0,低电平有效,bit27设置VSYNC信号极性,设置为0,低电平有效。Bit28设置1,开始ENABLE信号。Bit29设置为0,VSYNC输出
  6. LCDIF_VDCTRL1寄存器为两个VSYNC信号之间的长度,那就是VSPW+ VBPD+HEIGHT+VFP
  7. LCDIF_VDCTRL2寄存器bit17:0是两个HSYNC信号之间的长度,那就是hspw+hbp+width+hfp。Bit31:18为hspw
  8. LCDIF_VDCTRL3寄存器,bit15:0是vbp+vspw。Bit27:16是hbp+hspw。
  9. LCDIF_VDCTRL4寄存器,bit17:0是一行有多少个像素点,1024
  10. LCDIF_CUR_BUF,LCD当前缓存,显存首地址。
  11. LCDIF_NEXT_BUF,LCD下一帧数据首地址。
  12. LCD IO初始化。

LCD像素时钟的设置

​ LCD需要一个CLK信号,这个时钟信号是6ULL的CLK引脚发送给RGB LCD的。比如7寸1024*600的屏幕需要51.2MHz的CLK。

​ LCDIF_CLK_ROOT就是6ULL的像素时钟。PLL5(video PLL)为LCD的时钟源,请查看第一章主频与时钟

PLL5CLK=Fref*(DIV_SELECT + NUM/DENOM)

​ CCM_ANALOG_PLL_VIDEO的bit6:0,也就是DIV_SELEC位,可选范围27-54。设置PLL_VIDEO寄存器的bit20:19为2,表示1分频。设置CCM_ANALOG_MISC2寄存器的bit31:30为0,也就是VIDOE_DIV为0,为1分频。不使用小数分频器,因此CCM_ANALOG_PLL_VIDEO_NUM=0,再设置CCM_ALALOG_PLL_VIDEO_DENOM=0。

​ CCM_CSCDR2寄存器的bit17:15,设置LCDIF_PRE_CLK_SEL,选择LCDIF_CLK_ROOT的时钟源,设置为0x2。表示LCDIF时钟源为PLL5。Bit14:12为LCDIF_PRED位,设置前级分频,可以设置07,分别对应18分频。

​ CCM_CBCMR寄存器,bit25:23为LCDIF_PODF,设置第二级分频,可以设置为07,分别对应18分频。

​ CCM_CSCDR2寄存器的bit11:9为LCDIF_CLK_SEL,选择LCD CLK的最终时钟源,设置为0,LCDIF的最终时钟源来源于pre-muxed LCDIF clock。

C程序编写

LCD驱动程序编写

LCD初始化

如果使用的正点原子的开发板和RGB屏幕,那么在驱动LCD之前,要先读取屏幕ID。

MMP,这要是自己写,真的累死了,

我觉得了解下流程即可,我也不知道我吧代码贴出来是为了什么

IO复用初始化
/*** IO引脚:   LCD_DATA00 -> LCD_B0*            LCD_DATA01 -> LCD_B1*            LCD_DATA02 -> LCD_B2*            LCD_DATA03 -> LCD_B3*            LCD_DATA04 -> LCD_B4*            LCD_DATA05 -> LCD_B5*            LCD_DATA06 -> LCD_B6*            LCD_DATA07 -> LCD_B7**           LCD_DATA08 -> LCD_G0*            LCD_DATA09 -> LCD_G1*            LCD_DATA010 -> LCD_G2*           LCD_DATA011 -> LCD_G3*           LCD_DATA012 -> LCD_G4*           LCD_DATA012 -> LCD_G4*           LCD_DATA013 -> LCD_G5*           LCD_DATA014 -> LCD_G6*           LCD_DATA015 -> LCD_G7**          LCD_DATA016 -> LCD_R0*           LCD_DATA017 -> LCD_R1*           LCD_DATA018 -> LCD_R2 *          LCD_DATA019 -> LCD_R3*           LCD_DATA020 -> LCD_R4*           LCD_DATA021 -> LCD_R5*           LCD_DATA022 -> LCD_R6*           LCD_DATA023 -> LCD_R7**          LCD_CLK -> LCD_CLK*          LCD_VSYNC -> LCD_VSYNC*          LCD_HSYNC -> LCD_HSYNC*          LCD_DE -> LCD_DE*            LCD_BL -> GPIO1_IO08 *//*** @brief      : LCD GPIO初始化* @param      : 无* @return       : 无*/
void lcdgpio_init(void)
{gpio_pin_config_t gpio_config;/* 1、IO初始化复用功能 */IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23,0);IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK,0);   IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0); IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0);IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0);IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08,0);         /* 背光BL引脚      *//* 2、配置LCD IO属性    *bit 16:0 HYS关闭*bit [15:14]: 0 默认22K上拉*bit [13]: 0 pull功能*bit [12]: 0 pull/keeper使能 *bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 111 驱动能力为R0/7*bit [0]: 1 高转换率*/IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0xB9);IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08,0xB9);  /* 背光BL引脚       *//* GPIO初始化 */gpio_config.direction = kGPIO_DigitalOutput;            /* 输出           */gpio_config.outputLogic = 1;                             /* 默认关闭背光 */gpio_init(GPIO1, 8, &gpio_config);                      /* 背光默认打开 */gpio_pinwrite(GPIO1, 8, 1);                             /* 打开背光     */
}
时钟初始化
/*** @brief         : LCD时钟初始化, LCD时钟计算公式如下:*                      LCD CLK = 24 * loopDiv / prediv / div* @param - loopDiv : loopDivider值* @param -   loopDiv : lcdifprediv值* @param -   div     : lcdifdiv值* @return           : 无*/
void lcdclk_init(unsigned char loopDiv, unsigned char prediv, unsigned char div)
{/* 先初始化video pll * VIDEO PLL = OSC24M * (loopDivider + (denominator / numerator)) / postDivider*不使用小数分频器,因此denominator和numerator设置为0*/CCM_ANALOG->PLL_VIDEO_NUM = 0;      /* 不使用小数分频器 */CCM_ANALOG->PLL_VIDEO_DENOM = 0;  /** PLL_VIDEO寄存器设置* bit[13]:    1   使能VIDEO PLL时钟* bit[20:19]  2  设置postDivider为1分频* bit[6:0] : 32  设置loopDivider寄存器*/CCM_ANALOG->PLL_VIDEO =  (2 << 19) | (1 << 13) | (loopDiv << 0); /** MISC2寄存器设置* bit[31:30]: 0  VIDEO的post-div设置,时钟源来源于postDivider,1分频*/CCM_ANALOG->MISC2 &= ~(3 << 30);CCM_ANALOG->MISC2 = 0 << 30;/* LCD时钟源来源与PLL5,也就是VIDEO           PLL  */CCM->CSCDR2 &= ~(7 << 15);    CCM->CSCDR2 |= (2 << 15);         /* 设置LCDIF_PRE_CLK使用PLL5 *//* 设置LCDIF_PRE分频 */CCM->CSCDR2 &= ~(7 << 12);      CCM->CSCDR2 |= (prediv - 1) << 12;    /* 设置分频  *//* 设置LCDIF分频 */CCM->CBCMR &= ~(7 << 23);                   CCM->CBCMR |= (div - 1) << 23;                /* 设置LCD时钟源为LCDIF_PRE时钟 */CCM->CSCDR2 &= ~(7 << 9);                   /* 清除原来的设置          */CCM->CSCDR2 |= (0 << 9);                    /* LCDIF_PRE时钟源选择LCDIF_PRE时钟 */
}
LCD控制器的初始化
/*** @brief     : 复位ELCDIF接口* @param       : 无* @return       : 无*/
void lcd_reset(void)
{LCDIF->CTRL  = 1<<31; /* 强制复位 */
}/*** @brief       : 结束复位ELCDIF接口* @param         : 无* @return       : 无*/
void lcd_noreset(void)
{LCDIF->CTRL  = 0<<31; /* 取消强制复位 */
}/*** @brief       : 使能ELCDIF接口* @param       : 无* @return       : 无*/
void lcd_enable(void)
{LCDIF->CTRL |= 1<<0; /* 使能ELCDIF */
}/*** @brief           : 清屏* @param - color   : 颜色值* @return             : 读取到的指定点的颜色值*/
void lcd_clear(unsigned int color)
{unsigned int num;unsigned int i = 0; unsigned int *startaddr=(unsigned int*)tftlcd_dev.framebuffer;  //指向帧缓存首地址num=(unsigned int)tftlcd_dev.width * tftlcd_dev.height;          //缓冲区总长度for(i = 0; i < num; i++){startaddr[i] = color;}
}/* LCD控制参数结构体 */
typedef struct tftlcd_typedef{unsigned short height;        /* LCD屏幕高度 */unsigned short width;      /* LCD屏幕宽度 */unsigned char pixsize;     /* LCD每个像素所占字节大小 */unsigned short vspw;unsigned short vbpd;unsigned short vfpd;unsigned short hspw;unsigned short hbpd;unsigned short hfpd;unsigned int framebuffer;    /* LCD显存首地址       */unsigned int forecolor;     /* 前景色 */unsigned int backcolor;        /* 背景色 */
}tftlcd_typedef_t;
tftlcd_typedef_t tftlcd_dev={0};#define LCD_FRAMEBUF_ADDR  (0x89000000)#define LCD_BLACK         0x00000000
#define LCD_WHITE         0x00FFFFFF
/*** @brief    : 始化RGBLCD* @param         : 无* @return       : 无*/
void lcd_init(void)
{lcdgpio_init();            /* 初始化IO            */lcdclk_init(32, 3, 5);    /* 初始化LCD时钟         */lcd_reset();          /* 复位LCD            */delayms(10);          /* 延时10ms           */lcd_noreset();            /* 结束复位             *//* TFTLCD参数结构体初始化 */tftlcd_dev.height = 600; tftlcd_dev.width = 1024;tftlcd_dev.pixsize = 4;               /* ARGB8888模式,每个像素4字节 */tftlcd_dev.vspw = 3;tftlcd_dev.vbpd = 20;tftlcd_dev.vfpd = 12;tftlcd_dev.hspw = 20;tftlcd_dev.hbpd = 140;tftlcd_dev.hfpd = 160;tftlcd_dev.framebuffer = LCD_FRAMEBUF_ADDR;    tftlcd_dev.backcolor = LCD_WHITE;  /* 背景色为白色 */tftlcd_dev.forecolor = LCD_BLACK;  /* 前景色为黑色 *//* 初始化ELCDIF的CTRL寄存器* bit [31] 0 : 停止复位* bit [19] 1 : 旁路计数器模式* bit [17] 1 : LCD工作在dotclk模式* bit [15:14] 00 : 输入数据不交换* bit [13:12] 00 : CSC不交换* bit [11:10] 11 : 24位总线宽度* bit [9:8]   11 : 24位数据宽度,也就是RGB888* bit [5]     1  : elcdif工作在主模式* bit [1]     0  : 所有的24位均有效*/LCDIF->CTRL |= (1 << 19) | (1 << 17) | (0 << 14) | (0 << 12) |(3 << 10) | (3 << 8) | (1 << 5) | (0 << 1);/** 初始化ELCDIF的寄存器CTRL1* bit [19:16]  : 0X7 ARGB模式下,传输24位数据,A通道不用传输*/ LCDIF->CTRL1 = 0X7 << 16; /** 初始化ELCDIF的寄存器TRANSFER_COUNT寄存器* bit [31:16]  : 高度* bit [15:0]   : 宽度*/LCDIF->TRANSFER_COUNT  = (tftlcd_dev.height << 16) | (tftlcd_dev.width << 0);/** 初始化ELCDIF的VDCTRL0寄存器* bit [29] 0 : VSYNC输出* bit [28] 1 : 使能ENABLE输出* bit [27] 0 : VSYNC低电平有效* bit [26] 0 : HSYNC低电平有效* bit [25] 0 : DOTCLK上升沿有效* bit [24] 1 : ENABLE信号高电平有效* bit [21] 1 : DOTCLK模式下设置为1* bit [20] 1 : DOTCLK模式下设置为1* bit [17:0] : vsw参数*/LCDIF->VDCTRL0 = 0; //先清零LCDIF->VDCTRL0 = (0 << 29) | (1 << 28) | (0 << 27) |(0 << 26) | (0 << 25) | (1 << 24) |(1 << 21) | (1 << 20) | (tftlcd_dev.vspw << 0);/** 初始化ELCDIF的VDCTRL1寄存器* 设置VSYNC总周期*/  LCDIF->VDCTRL1 = tftlcd_dev.height + tftlcd_dev.vspw + tftlcd_dev.vfpd + tftlcd_dev.vbpd;  //VSYNC周期/** 初始化ELCDIF的VDCTRL2寄存器* 设置HSYNC周期* bit[31:18] :hsw* bit[17:0]  : HSYNC总周期*/ LCDIF->VDCTRL2 = (tftlcd_dev.hspw << 18) | (tftlcd_dev.width + tftlcd_dev.hspw + tftlcd_dev.hfpd + tftlcd_dev.hbpd);/** 初始化ELCDIF的VDCTRL3寄存器* 设置HSYNC周期* bit[27:16] :水平等待时钟数* bit[15:0]  : 垂直等待时钟数*/ LCDIF->VDCTRL3 = ((tftlcd_dev.hbpd + tftlcd_dev.hspw) << 16) | (tftlcd_dev.vbpd + tftlcd_dev.vspw);/** 初始化ELCDIF的VDCTRL4寄存器* 设置HSYNC周期* bit[18] 1 : 当使用VSHYNC、HSYNC、DOTCLK的话此为置1* bit[17:0]  : 宽度*/ LCDIF->VDCTRL4 = (1<<18) | (tftlcd_dev.width);/** 初始化ELCDIF的CUR_BUF和NEXT_BUF寄存器* 设置当前显存地址和下一帧的显存地址*/LCDIF->CUR_BUF = (unsigned int)tftlcd_dev.framebuffer;LCDIF->NEXT_BUF = (unsigned int)tftlcd_dev.framebuffer;lcd_enable();          /* 使能LCD    */delayms(10);lcd_clear(LCD_WHITE); /* 清屏       */
}
底层API

只不过是数组内中对应偏移赋值

/*** @brief         : 画点函数 * @param - x        : x轴坐标* @param - y     : y轴坐标* @param - color : 颜色值* @return             : 无*/
inline void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color)
{ *(unsigned int*)((unsigned int)tftlcd_dev.framebuffer + tftlcd_dev.pixsize * (tftlcd_dev.width * y+x))=color;
}/*** @brief           : 读取指定点的颜色值* @param - x        : x轴坐标* @param - y     : y轴坐标* @return            : 读取到的指定点的颜色值*/
inline unsigned int lcd_readpoint(unsigned short x,unsigned short y)
{ return *(unsigned int*)((unsigned int)tftlcd_dev.framebuffer + tftlcd_dev.pixsize * (tftlcd_dev.width * y + x));
}/*** @brief           : 清屏* @param - color   : 颜色值* @return             : 读取到的指定点的颜色值*/
void lcd_clear(unsigned int color)
{unsigned int num;unsigned int i = 0; unsigned int *startaddr=(unsigned int*)tftlcd_dev.framebuffer;  //指向帧缓存首地址num=(unsigned int)tftlcd_dev.width * tftlcd_dev.height;          //缓冲区总长度for(i = 0; i < num; i++){startaddr[i] = color;}
}/*** @brief           : 以指定的颜色填充一块矩形* @param - x0        : 矩形起始点坐标X轴* @param - y0       : 矩形起始点坐标Y轴* @param - x1       : 矩形终止点坐标X轴* @param - y1       : 矩形终止点坐标Y轴* @param - color    : 要填充的颜色* @return          : 读取到的指定点的颜色值*/
void lcd_fill(unsigned    short x0, unsigned short y0, unsigned short x1, unsigned short y1, unsigned int color)
{ unsigned short x, y;if(x0 < 0) x0 = 0;if(y0 < 0) y0 = 0;if(x1 >= tftlcd_dev.width) x1 = tftlcd_dev.width - 1;if(y1 >= tftlcd_dev.height) y1 = tftlcd_dev.height - 1;for(y = y0; y <= y1; y++){for(x = x0; x <= x1; x++)lcd_drawpoint(x, y, color);}
}

LCD操作API函数编写

API函数在此 只做声明因为实在太多了,内部只不过for循环加条件判断然后调用上个小节底层API的画点函数lcd_drawpoint

void lcd_drawline(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2);
void lcd_draw_rectangle(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2);
void lcd_draw_Circle(unsigned short x0,unsigned short y0,unsigned char r);//以下API需要有点阵字库
void lcd_showchar(unsigned     short x,unsigned short y,unsigned char num,unsigned char size, unsigned char mode);
void lcd_shownum(unsigned short x, unsigned short y, unsigned int num, unsigned char len,unsigned char size);
void lcd_showxnum(unsigned short x, unsigned short y, unsigned int num, unsigned char len, unsigned char size, unsigned char mode);
void lcd_show_string(unsigned short x,unsigned short y,unsigned short width, unsigned short height, unsigned char size,char *p);

RTC

RTC原理详解

​ 6U内部自带到了一个RTC外设,确切的说是SRTC。6U和6ULL的RTC内容在SNVS章节。6U的RTC分为LP和HP。LP叫做SRTC,HP是RTC,但是HP的RTC掉电以后数据就丢失了,即使用了纽扣电池也没用。所以必须要使用LP,也就是SRTC。

​ SNVS章节有些是跟加密有关的,需要与NXP签订NDA协议才可以拿到。

​ RTC分为SNVS_LP和SNVS_HP,

​ 如果做产品,建议使用外置RTC芯片,PCF8563。

​ RTC很类似定时器,外接32.768KHz的晶振,然后就开始计时,RTC使用两个寄存器来保存计数值。

​ RTC使用很简单,打开RTC,然后RTC就开始工作,我们要做的就是不断地读取RTC计数寄存器,获取时间值,或者向RTC计数器写入时间值,也就是调整时间。

​ SNVS_HPCOMR的bit31置1,表示所有的软件都可以访问SNVS所有寄存器。Bit8也是和安全有关的,我们置1,也可以不置1.

​ SNVS_LPCR寄存器,bit0置1,开始SRTC功能。

​ SNVS_LPSRTCMR的bit14:0为RTC计数寄存器的高15位

​ SNVS_LPSRTCLR是低32为RTC计数器,与LPSRTCMR共同组成了SRTC计数器,,每1秒数据加1。

​ 6U的RTC模式从1970年1月1日0时0点0分0秒开始计时。(32位秒计时逃不过的2038年)

时间乱码的问题

问题

​ 当我们按照6U的参考手册编写代码,读取SRTC的LPSRTCMR和LPSRTCLR获取时间值的时候,发现按照手册的说法,时间是错误的。我淦

​ 手册上写的:LPSRTCMR是SRTC的高15bit。LPSRTCLR寄存器是SRTC的低32位。RTC计数器是47bit。

问题解决方法

​ LPSRTCMR作为SRTC计数器的高15位,但是LPSRTCLR寄存器bit31:15作为SRTC计数器的低17位。相当于SRTC的计数器是个32位的。不是47位!

C程序编写

相关API以及结构体

/* 时间日期结构体 */
typedef struct rtc_datetime
{unsigned short year;  /* 范围为:1970 ~ 2099       */unsigned char month;  /* 范围为:1 ~ 12               */unsigned char day;    /* 范围为:1 ~ 31 (不同的月,天数不同).*/unsigned char hour;   /* 范围为:0 ~ 23          */unsigned char minute; /* 范围为:0 ~ 59               */unsigned char second; /* 范围为:0 ~ 59               */
}rtc_datetime_t;/* 相关宏定义 */
#define SECONDS_IN_A_DAY        (86400) /* 一天86400秒         */
#define SECONDS_IN_A_HOUR       (3600)  /* 一个小时3600秒        */
#define SECONDS_IN_A_MINUTE     (60)    /* 一分钟60秒           */
#define DAYS_IN_A_YEAR          (365)   /* 一年365天           */
#define YEAR_RANGE_START        (1970)  /* 开始年份1970年        */
#define YEAR_RANGE_END          (2099)  /* 结束年份2099年        *//*** @brief      : 开启RTC*/
void rtc_enable(void)
{/** LPCR寄存器bit0置1,使能RTC*/SNVS->LPCR |= 1 << 0;    while(!(SNVS->LPCR & 0X01));//等待使能完成}/*** @brief    : 关闭RTC*/
void rtc_disable(void)
{/** LPCR寄存器bit0置0,关闭RTC*/SNVS->LPCR &= ~(1 << 0); while(SNVS->LPCR & 0X01);//等待关闭完成
}/*** @brief       : 判断指定年份是否为闰年,闰年条件如下:* @param - year: 要判断的年份* @return      : 1 是闰年,0 不是闰年*/
unsigned char rtc_isleapyear(unsigned short year)
{   unsigned char value=0;if(year % 400 == 0)value = 1;else {if((year % 4 == 0) && (year % 100 != 0))value = 1;else value = 0;}return value;
}/*** @brief           : 将时间转换为秒数* @param - datetime: 要转换日期和时间。* @return             : 转换后的秒数*/
unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime)
{   unsigned short i = 0;unsigned int seconds = 0;unsigned int days = 0;unsigned short monthdays[] = {0U, 0U, 31U, 59U, 90U, 120U, 151U, 181U, 212U, 243U, 273U, 304U, 334U};for(i = 1970; i < datetime->year; i++){days += DAYS_IN_A_YEAR;      /* 平年,每年365天 */if(rtc_isleapyear(i)) days += 1;/* 闰年多加一天       */}days += monthdays[datetime->month];if(rtc_isleapyear(i) && (datetime->month >= 3)) days += 1;/* 闰年,并且当前月份大于等于3月的话加一天 */days += datetime->day - 1;seconds = days * SECONDS_IN_A_DAY + datetime->hour * SECONDS_IN_A_HOUR +datetime->minute * SECONDS_IN_A_MINUTE +datetime->second;return seconds;
}/*** @brief           : 设置时间和日期* @param - datetime: 要设置的日期和时间* @return          : 无*/
void rtc_setdatetime(struct rtc_datetime *datetime)
{unsigned int seconds = 0;unsigned int tmp = SNVS->LPCR; rtc_disable();    /* 设置寄存器HPRTCMR和HPRTCLR的时候一定要先关闭RTC *//* 先将时间转换为秒         */seconds = rtc_coverdate_to_seconds(datetime);SNVS->LPSRTCMR = (unsigned int)(seconds >> 17); /* 设置高17位 */SNVS->LPSRTCLR = (unsigned int)(seconds << 15); /* 设置低15位 *//* 如果此前RTC是打开的在设置完RTC时间以后需要重新打开RTC */if (tmp & 0x1)rtc_enable();
}

RTC初始化

/*** @brief   : 初始化RTC*/
void rtc_init(void)
{/** 设置HPCOMR寄存器* bit[31] 1 : 允许访问SNVS寄存器,一定要置1* bit[8]  1 : 此位置1,需要签署NDA协议才能看到此位的详细说明,*             这里不置1也没问题*/SNVS->HPCOMR |= (1 << 31) | (1 << 8);#if 0rtc_datetime rtcdate;rtcdate.year = 2021U;rtcdate.month = 2U;rtcdate.day = 25U;rtcdate.hour = 23U;rtcdate.minute = 49;rtcdate.second = 0;rtc_setDatetime(&rtcdate); //初始化时间和日期,我也要写成我现在的时间
#endifrtc_enable(); //使能RTC}

黑人问号脸?就两句?就这?就这?就这?

I2C

I2C协议简单介绍

I2C是一个比较简单且运用非常广泛的协议,如不了解可百度了解,这里不做介绍

1、I2C分为SCL和SDA,两个必须要接上拉电阻到VCC,比如3.3V,一般是4.7K上拉电阻。
2、I2C总线支持多从机,通过从机地址来区分从机。
3、I2C频率标准模式100kbit/S,快速模式400Kbit/S

开发板上的I2C

​ 开发板上有个AP3216C,这是一个IIC接口的器件,这是一个环境光传感器。AP3216C连接到了I2C1上:
​ I2C1_SCL: 使用的是UART4_TXD这个IO,复用位ALT2
​ I2C1_SDA: 使用的是UART4_RXD这个IO。复用为ALT2

AP3216C简介

​ 1、AP3216C是一个三合一的环境光传感器,ALS+PS+IRLED,ALS是环境光,PS是接近传感器,IR是红外LED灯。I2C接口,最高400Kbit/S的频率。环境光,ALS是16位输出。接近传感器PS是10bit输出。IR传感器也是10bit
​ 2、AP3216C的从机地址位0X1E。
​ 3、0X0A 是IR Ddata low。Bit7为0的时候表示IR和PS数据有效,为1的时候IR和PS数据无效。Bit1:0是IR的低2位。
​ 4、0X0B 是IR Data high,big7:0是高字节。与0X0A一起组成需要使用的数据。
​ 5、0X0C、0X0D 分别位ALS的低8位和高8位。
​ 6、0X0E 的bit3:0是低4位数据,0X0F的bit5:0是高6位数据。加起来就是10位
​ 7、0X00 是系统配置寄存器,bit2:0设置AP3216C开始那些传感器,我们需要设置位011,也就是0x3,表示开始ALS+PS+IR。

6ULL I2C接口详解

​ 1、时钟源选择perclk_clk_root=ipg_clk_root=66MHz

​ 2、IFDR寄存器设置I2C频率,bit5:0设置频分值,假如我们现在需要100kbit的速率,那么66000000/100000=660。经过查找IC位设置位0X38或0X15的时候,为640分频,66000000/640=103.125Kbit.

​ 3、I2CR寄存器,bit7为I2C使能位,置1使能I2C。bit5为主从模式选择位,为0表示从机,为1表示主机。Bit4为发送/接收设置位,为0的时候是接收,为1的时候是发送

​ 4、I2SR寄存器,bit7为传输完成位,为0表示正在发送,为1表示发送完成。Bit5是I2C忙闲位,为0的时候I2C总线空闲,为1的时候I2C总线忙。Bit0是读确认位,也就是ACK信号

​ 5、I2DR寄存器,数据寄存器。

C程序编写

I2C模块

I2C初始化

/*** @brief         : 初始化I2C,波特率100KHZ* @param - base   : 要初始化的IIC设置* @return          : 无*/
void i2c_init(I2C_Type *base)
{/* 1、配置I2C */base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C *//* 设置波特率为100K* I2C的时钟源来源于IPG_CLK_ROOT=66Mhz* IC2 时钟 = PERCLK_ROOT/dividison(IFDR寄存器)* 设置寄存器IFDR,IFDR寄存器参考IMX6UL参考手册P1260页,表29-3,* 根据表29-3里面的值,挑选出一个还是的分频数,比如本例程我们* 设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.* 在表29-3里面查找,没有660这个值,但是有640,因此就用640,* 即寄存器IFDR的IC位设置为0X15*/base->IFDR = 0X15 << 0;/** 设置寄存器I2CR,开启I2C* bit[7] : 1 使能I2C,I2CR寄存器其他位其作用之前,此位必须最先置1*/base->I2CR |= (1<<7);
}

I2C主机时序产生

/*** @brief             : 发送开始信号* @param - base        : 要使用的IIC* @param - addrss     : 设备地址* @param - direction : 方向* @return              : 0 正常 其他值 出错*/
unsigned char i2c_master_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
{if(base->I2SR & (1 << 5))         /* I2C忙 */return 1;/** 设置寄存器I2CR* bit[5]: 1 主模式* bit[4]: 1 发送*/base->I2CR |=  (1 << 5) | (1 << 4);/** 设置寄存器I2DR* bit[7:0] : 要发送的数据,这里写入从设备地址*            参考资料:IMX6UL参考手册P1249*/ base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);return 0;
}/*** @brief               : 发送重新开始信号* @param - base      : 要使用的IIC* @param - addrss     : 设备地址* @param - direction : 方向* @return              : 0 正常 其他值 出错*/
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
{/* I2C忙并且工作在从模式,跳出 */if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))     return 1;/** 设置寄存器I2CR* bit[4]: 1 发送* bit[2]: 1 产生重新开始信号*/base->I2CR |=  (1 << 4) | (1 << 2);/** 设置寄存器I2DR* bit[7:0] : 要发送的数据,这里写入从设备地址*            参考资料:IMX6UL参考手册P1249*/ base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);return 0;
}/*** @brief       : 停止信号* @param - base  : 要使用的IIC* @param          : 无* @return           : 状态结果*/
unsigned char i2c_master_stop(I2C_Type *base)
{unsigned short timeout = 0xffff;/** 清除I2CR的bit[5:3]这三位*/base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));/* 等待忙结束 */while((base->I2SR & (1 << 5))){timeout--;if(timeout == 0) /* 超时跳出 */return I2C_STATUS_TIMEOUT;}return I2C_STATUS_OK;
}/*** @brief           : 检查并清除错误* @param - base   : 要使用的IIC* @param - status : 状态* @return          : 状态结果*/
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{/* 检查是否发生仲裁丢失错误 */if(status & (1<<4)){base->I2SR &= ~(1<<4);       /* 清除仲裁丢失错误位            */base->I2CR &= ~(1 << 7);    /* 先关闭I2C               */base->I2CR |= (1 << 7);     /* 重新打开I2C              */return I2C_STATUS_ARBITRATIONLOST;} else if(status & (1 << 0))      /* 没有接收到从机的应答信号 */{return I2C_STATUS_NAK;       /* 返回NAK(No acknowledge) */}return I2C_STATUS_OK;
}

I2C读写

/*** @brief         : 发送数据* @param - base  : 要使用的IIC* @param - buf        : 要发送的数据* @param - size    : 要发送的数据大小* @param - flags : 标志* @return          : 无*/
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
{/* 等待传输完成 */while(!(base->I2SR & (1 << 7))); base->I2SR &= ~(1 << 1);   /* 清除标志位 */base->I2CR |= 1 << 4;      /* 发送数据 */while(size--){base->I2DR = *buf++;  /* 将buf中的数据写入到I2DR寄存器 */while(!(base->I2SR & (1 << 1)));   /* 等待传输完成 */    base->I2SR &= ~(1 << 1);          /* 清除标志位 *//* 检查ACK */if(i2c_check_and_clear_error(base, base->I2SR))break;}base->I2SR &= ~(1 << 1);i2c_master_stop(base);     /* 发送停止信号 */
}/*** @brief           : 读取数据* @param - base  : 要使用的IIC* @param - buf        : 读取到数据* @param - size : 要读取的数据大小* @return            : 无*/
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size)
{volatile uint8_t dummy = 0;dummy++;     /* 防止编译报错 *//* 等待传输完成 */while(!(base->I2SR & (1 << 7))); base->I2SR &= ~(1 << 1);                /* 清除中断挂起位 */base->I2CR &= ~((1 << 4) | (1 << 3));  /* 接收数据 *//* 如果只接收一个字节数据的话发送NACK信号 */if(size == 1)base->I2CR |= (1 << 3);dummy = base->I2DR; /* 假读 */while(size--){while(!(base->I2SR & (1 << 1)));    /* 等待传输完成 */    base->I2SR &= ~(1 << 1);          /* 清除标志位 */if(size == 0){i2c_master_stop(base);           /* 发送停止信号 */}if(size == 1){base->I2CR |= (1 << 3);}*buf++ = base->I2DR;}
}

I2C读写封装

正点原子在这个模块通过结构体对I2C读写函数的封装,代码如下

/*** @brief    :I2C方向枚举类型,确定I2C的方向*/
enum i2c_direction
{kI2C_Write = 0x0,         /* 主机向从机写数据 */kI2C_Read = 0x1,         /* 主机从从机读数据 */
} ;/*** @brief    :主机传输结构体、确定I2C的数据传输规则*/
struct i2c_transfer
{unsigned char slaveAddress;        /* 7位从机地址           */enum i2c_direction direction;         /* 传输方向             */unsigned int subaddress;              /* 寄存器地址            */unsigned char subaddressSize;     /* 寄存器地址长度          */unsigned char *volatile data;     /* 数据缓冲区            */volatile unsigned int dataSize;   /* 数据缓冲区长度          */
};/*对外部读写只有一个接口,使用起来较为简单*/
/*** @brief    : I2C数据传输,包括读和写* @param - base: 要使用的IIC* @param - xfer: 传输结构体* @return        : 传输结果,0 成功,其他值 失败;*/
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{unsigned char ret = 0;enum i2c_direction direction = xfer->direction; base->I2SR &= ~((1 << 1) | (1 << 4));           /* 清除标志位 *//* 等待传输完成 */while(!((base->I2SR >> 7) & 0X1)){}; /* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read)){direction = kI2C_Write;}ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */if(ret){ return ret;}while(!(base->I2SR & (1 << 1))){};         /* 等待传输完成 */ret = i2c_check_and_clear_error(base, base->I2SR);  /* 检查是否出现传输错误 */if(ret){i2c_master_stop(base);                      /* 发送出错,发送停止信号 */return ret;}/* 发送寄存器地址 */if(xfer->subaddressSize){do{base->I2SR &= ~(1 << 1);          /* 清除标志位 */xfer->subaddressSize--;               /* 地址长度减一 */base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器写入子地址while(!(base->I2SR & (1 << 1)));      /* 等待传输完成 *//* 检查是否有错误发生 */ret = i2c_check_and_clear_error(base, base->I2SR);if(ret){i2c_master_stop(base);                 /* 发送停止信号 */return ret;}  } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));if(xfer->direction == kI2C_Read)      /* 读取数据 */{base->I2SR &= ~(1 << 1);           /* 清除中断挂起位 */i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 发送重复开始信号和从机地址 */while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 *//* 检查是否有错误发生 */ret = i2c_check_and_clear_error(base, base->I2SR);if(ret){ret = I2C_STATUS_ADDRNAK;i2c_master_stop(base);         /* 发送停止信号 */return ret;  }}}    /* 发送数据 */if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0)){i2c_master_write(base, xfer->data, xfer->dataSize);}/* 读取数据 */if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0)){i2c_master_read(base, xfer->data, xfer->dataSize);}return 0;
}

6ULL这个I2C对于新手是不太友好的,需要自己操作起始信号和停止信号,但是从机地址却不需要自己去主动发送,总觉得是有些别扭的。调试起来也会比较麻烦。需要自己阅读datasheet进行调试。

AP3216C模块

读写

/*** @brief     : 向AP3216C写入数据* @param - addr: 设备地址* @param - reg : 要写入的寄存器* @param - data: 要写入的数据* @return         : 操作结果*/
unsigned char ap3216c_writeonebyte(unsigned char addr,unsigned char reg, unsigned char data)
{unsigned char status=0;unsigned char writedata=data;struct i2c_transfer masterXfer;/* 配置I2C xfer结构体 */masterXfer.slaveAddress = addr;           /* 设备地址                 */masterXfer.direction = kI2C_Write;           /* 写入数据                 */masterXfer.subaddress = reg;             /* 要写入的寄存器地址            */masterXfer.subaddressSize = 1;               /* 地址长度一个字节             */masterXfer.data = &writedata;                /* 要写入的数据               */masterXfer.dataSize = 1;                     /* 写入数据长度1个字节           */if(i2c_master_transfer(I2C1, &masterXfer))status=1;return status;
}/*** @brief       : 从AP3216C读取一个字节的数据* @param - addr: 设备地址* @param - reg : 要读取的寄存器* @return        : 读取到的数据。*/
unsigned char ap3216c_readonebyte(unsigned char addr,unsigned char reg)
{unsigned char val=0;struct i2c_transfer masterXfer;   masterXfer.slaveAddress = addr;                /* 设备地址                 */masterXfer.direction = kI2C_Read;            /* 读取数据                 */masterXfer.subaddress = reg;             /* 要读取的寄存器地址            */masterXfer.subaddressSize = 1;               /* 地址长度一个字节             */masterXfer.data = &val;                      /* 接收数据缓冲区              */masterXfer.dataSize = 1;                 /* 读取数据长度1个字节           */i2c_master_transfer(I2C1, &masterXfer);return val;
}

初始化

/*** @brief     : 初始化AP3216C* @param       : 无* @return       : 0 成功,其他值 错误代码*/
unsigned char ap3216c_init(void)
{unsigned char data = 0;/* 1、IO初始化,配置I2C IO属性   * I2C1_SCL -> UART4_TXD* I2C1_SDA -> UART4_RXD*/IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);/* *bit 16:0 HYS关闭*bit [15:14]: 1 默认47K上拉*bit [13]: 1 pull功能*bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 驱动能力为R0/6*bit [0]: 1 高转换率*/IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x70B0);IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0X70B0);i2c_init(I2C1);       /* 初始化I2C1 *//* 2、初始化AP3216C */ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X04);    /* 复位AP3216C            */delayms(50);                                                  /* AP33216C复位至少10ms */ap3216c_writeonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR             */data = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_SYSTEMCONG);    /* 读取刚刚写进去的0X03 */if(data == 0X03)return 0;   /* AP3216C正常    */else return 1;    /* AP3216C失败    */
}

读取数据

/*** @brief     : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!*                : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms* @param - ir : ir数据* @param - ps    : ps数据* @param - ps    : als数据 * @return      : 无。*/
void ap3216c_readdata(unsigned short *ir, unsigned short *ps, unsigned short *als)
{unsigned char buf[6];unsigned char i;/* 循环读取所有传感器数据 */for(i = 0; i < 6; i++) {buf[i] = ap3216c_readonebyte(AP3216C_ADDR, AP3216C_IRDATALOW + i);   }if(buf[0] & 0X80)  /* IR_OF位为1,则数据无效 */*ir = 0;                   else                /* 读取IR传感器的数据           */*ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);             *als = ((unsigned short)buf[3] << 8) | buf[2];   /* 读取ALS传感器的数据           */  if(buf[4] & 0x40)  /* IR_OF位为1,则数据无效           */*ps = 0;                                                     else                /* 读取PS传感器的数据    */*ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}

SPI

SPI协议简单介绍

SPI是一个比较简单且运用非常广泛的协议,如不了解可百度了解,这里不做介绍

​ 1、SPI相比I2C最大的优势有两点:一个是速度快,最高可以大几十M,甚至上百MHz,第二个就是SPI是个全双工。

​ 2、SPI接口和I2C一样,一个SPI接口可以连接多个SPI外设,SPI通过CS引脚/数据线,片选引脚来选择和哪个SPI外设通信。SPI通信前先将指定的SPI外设对应的CS引脚拉低来选中此设备。

​ 3、根据CPOL和CPHA可以设置四种工作模式,一般使用CPOL=0、CPHA=0。

开发板上的SPI接口

通过ECSPI3接口连接了一个6轴传感器,引脚如下:

​ ECSPI3_SCLK : UART2_RX

​ ECSPI3_MOSI:UART2_CTS

​ ECSPI3_SS0:UART2_TXD

​ ECSPI3_MISO: UART2_RTS

​ 6ULL一个SPI主接口有4个硬件片选,分别为SS0~SS3。

6ULL SPI接口详解

​ 1、6ULL的SPI接口叫做ECSPI,支持全双工、主丛可配置。

​ 2、4个硬件片选信号,可以使用软件片选,这样一个SPI接口所能连接的外设就无限制了。

​ 1、RXDATA寄存器为接收到的数据。

​ 2、TXDATA寄存器为发送数据寄存器。

​ 3、CONREG寄存器为配置寄存器,bit0置1,使能SPI。Bit3置1,表示当向TXFIFO写入数据以后马上开启SPI突发访问,也就是发送数据。Bit7:4设置SPI通道主从模式,bit7为通道3,bit4为通道0,我们使用到了SS0,也就是通道0,因此需要设置bit4为1。Bit19:18设置为00,我们使用到SS0,也就是通道0。Bit31:30设置突发访问长度,我们设置为7,也就是8bit突发长度,一个字节。

​ 4、CONFIGREG寄存器的bit0为PHA,设置为0,表示 串行时钟的第一个跳变沿开始采集数据。设置bit4为PO,设置为0,表示SCLK空闲的时候为低电平。Bit8设置0。Bit12设置 为0。Bit16设置为0,表示空闲的时候数据线为高。Bit20设置为0,表示SCLK空闲的时候为低。

​ 5、STATREG寄存器,bit0表示TXFIFO为空,我们在发送数据之前要等待TXFIFO为空,也就是等待bit0为1。Bit3表示RXFIFO是否有数据,为1的时候示RXFIFO至少有1个字的数据,我们在接收数据的时候要等到bit3为1。

​ 6、PERIODREG寄存器,bit14:0设置wait states时间,我们设置为0X2000。Bit15设置wait states的时钟源为SPI CLK,将此位设置0。Bit21:16表示片选信号的延时,可设置0-63,这里设置为0.

​ 7、SPI时钟设置!

​ SPI时钟源最终来源于pll3_sw_clk=480MHz/8=60MHz,设置CSCDR2寄存器的bit18为0,也就是ECSPI时钟源为60MHz。bit24:19设置为0,表示1分频,因此最终进入到SPI外设的时钟源为60MHz

​ ECSPI模块还需要对时钟进行两级分频,由ECSPI_CONREG寄存器设置。Bit15:12设置前级分频,可以设置00xf,表示116分频。Bit11:8设置2级分频,设置2^n分频,n=0~15.

C程序编写

SPI模块

SPI初始化

/*** @brief     : 初始化SPI* @param - base    : 要初始化的SPI* @return            : 无*/
void spi_init(ECSPI_Type *base)
{/* 配置CONREG寄存器* bit0 :         1   使能ECSPI* bit3 :         1   当向TXFIFO写入数据以后立即开启SPI突发。* bit[7:4] :    0001 SPI通道0主模式,根据实际情况选择,*                 开发板上的ICM-20608接在SS0上,所以设置通道0为主模式* bit[19:18]:    00  选中通道0(其实不需要,因为片选信号我们我们自己控制)* bit[31:20]: 0x7 突发长度为8个bit。 */base->CONREG = 0; /* 先清除控制寄存器 */base->CONREG |= (1 << 0) | (1 << 3) | (1 << 4) | (7 << 20); /* 配置CONREG寄存器 *//** ECSPI通道0设置,即设置CONFIGREG寄存器* bit0:    0 通道0 PHA为0* bit4:  0 通道0 SCLK高电平有效* bit8:  0 通道0片选信号 当SMC为1的时候此位无效* bit12:  0 通道0 POL为0* bit16:  0 通道0 数据线空闲时高电平* bit20: 0 通道0 时钟线空闲时低电平*/base->CONFIGREG = 0;       /* 设置通道寄存器 *//*  * ECSPI通道0设置,设置采样周期* bit[14:0] :    0X2000  采样等待周期,比如当SPI时钟为10MHz的时候*            0X2000就等于1/10000 * 0X2000 = 0.8192ms,也就是连续*             读取数据的时候每次之间间隔0.8ms* bit15    :  0  采样时钟源为SPI CLK* bit[21:16]:  0  片选延时,可设置为0~63*/base->PERIODREG = 0X2000;       /* 设置采样周期寄存器 *//** ECSPI的SPI时钟配置,SPI的时钟源来源于pll3_sw_clk/8=480/8=60MHz* 通过设置CONREG寄存器的PER_DIVIDER(bit[11:8])和POST_DIVEDER(bit[15:12])来* 对SPI时钟源分频,获取到我们想要的SPI时钟:* SPI CLK = (SourceCLK / PER_DIVIDER) / (2^POST_DIVEDER)* 比如我们现在要设置SPI时钟为6MHz,那么PER_DIVEIDER和POST_DEIVIDER设置如下:* PER_DIVIDER = 0X9。* POST_DIVIDER = 0X0。* SPI CLK = 60000000/(0X9 + 1) = 60000000=6MHz*/base->CONREG &= ~((0XF << 12) | (0XF << 8));  /* 清除PER_DIVDER和POST_DIVEDER以前的设置 */base->CONREG |= (0X9 << 12);                  /* 设置SPI CLK = 6MHz */
}

读写数据

因为是全双工,在发送的时候也在读取数据,所有代码较为简单

/*** @brief         : SPI通道0发送/接收一个字节的数据* @param - base    : 要使用的SPI* @param - txdata : 要发送的数据* @return          : 无*/
unsigned char spich0_readwrite_byte(ECSPI_Type *base, unsigned char txdata)
{ uint32_t  spirxdata = 0;uint32_t  spitxdata = txdata;/* 选择通道0 */base->CONREG &= ~(3 << 18);base->CONREG |= (0 << 18);while((base->STATREG & (1 << 0)) == 0){} /* 等待发送FIFO为空 */base->TXDATA = spitxdata;while((base->STATREG & (1 << 3)) == 0){} /* 等待接收FIFO有数据 */spirxdata = base->RXDATA;return spirxdata;
}

错误修改

​ 使用浮点计算的时候程序卡死了,因为没有开始6UL的硬件浮点运算。在编译的时候没有使用浮点。解决此问题需要两点:

​ ①、开启6UL的硬件浮点单元

/*** @brief     : 使能I.MX6U的硬件NEON和FPU(浮点运算单元)* @param      : 无* @return       : 无*/void imx6ul_hardfpu_enable(void)
{uint32_t cpacr;uint32_t fpexc;/* 使能NEON和FPU */cpacr = __get_CPACR();cpacr = (cpacr & ~(CPACR_ASEDIS_Msk | CPACR_D32DIS_Msk))|  (3UL << CPACR_cp10_Pos) | (3UL << CPACR_cp11_Pos);__set_CPACR(cpacr);fpexc = __get_FPEXC();fpexc |= 0x40000000UL;   __set_FPEXC(fpexc);
}

​ ②、编译指定硬件浮点。

#-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard
$(COBJS) : obj/%.o : %.c$(CC) -Wall -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

多点电容触摸屏

多点电容触摸屏简介

1、多点触摸,不需要按下。
2、电容触摸屏需要一个IC驱动控制的,一般是I2C接口,多点触摸屏驱动最终就是一个I2C外设驱动。
触摸屏IP:
CT_INT,触摸中断线,连接到了GPIO1_IO09
I2C2_SCL:连接到了UART5_TXD
I2C2_SDA:连接到了UART5_RXD
RESET:连接到了SNVS_TAMPER9

电容触摸芯片输出的触摸点坐标信息为对应的屏幕像素点信息,因此不需要校准。电阻屏需要校准。

FT54x6/FT52x6电容触摸芯片

1、IIC接口,最大400KHz,因此此模块需要用到I2C中的I2C模块
2、正点原子7寸屏幕FT5426的IIC地址为0X38.
3、需要用到的寄存器
DEVICE_MODE 0X00,需要设置为0X0,表示正常运行模式。
ID_G_LIB_VERSION_H以及ID_G_LIB_VERSION_L 0XA1和0XA2。表示固件版本号。
ID_G_MODE 0XA4,设置为1,表示采用中断方式上报触摸信息。
TD_STATUS 0X02,当前触摸点的个数,1~5。
TOUCH1_XH 0X03开始记录着触摸屏的触摸点坐标信息,一个触摸点6个寄存器,一共需要5*6=30个寄存器。一直读取到0X20

一个触摸点坐标信息用12bit表示,其中H的bit3:0这4个bit为高4位,L寄存器的bit7:0为低8位。
对于XH寄存器,bit7:6表示事件,很重要。
YH寄存器的bit7:4表示触摸ID,

当有触摸时,产生中断信号到6ULL

C程序编写

芯片的读写

/*** @brief     : 向FT5429写入数据* @param - addr: 设备地址* @param - reg : 要写入的寄存器* @param - data: 要写入的数据* @return      : 操作结果*/
unsigned char ft5426_write_byte(unsigned char addr,unsigned char reg, unsigned char data)
{unsigned char status=0;unsigned char writedata=data;struct i2c_transfer masterXfer;/* 配置I2C xfer结构体 */masterXfer.slaveAddress = addr;           /* 设备地址                 */masterXfer.direction = kI2C_Write;           /* 写入数据                 */masterXfer.subaddress = reg;             /* 要写入的寄存器地址            */masterXfer.subaddressSize = 1;               /* 地址长度一个字节             */masterXfer.data = &writedata;                /* 要写入的数据               */masterXfer.dataSize = 1;                     /* 写入数据长度1个字节           */if(i2c_master_transfer(I2C2, &masterXfer))status=1;return status;
}/*** @brief       : 从FT5426读取一个字节的数据* @param - addr: 设备地址* @param - reg : 要读取的寄存器* @return         : 读取到的数据。*/
unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg)
{unsigned char val=0;struct i2c_transfer masterXfer;   masterXfer.slaveAddress = addr;                /* 设备地址                 */masterXfer.direction = kI2C_Read;            /* 读取数据                 */masterXfer.subaddress = reg;             /* 要读取的寄存器地址            */masterXfer.subaddressSize = 1;               /* 地址长度一个字节             */masterXfer.data = &val;                      /* 接收数据缓冲区              */masterXfer.dataSize = 1;                 /* 读取数据长度1个字节           */i2c_master_transfer(I2C2, &masterXfer);return val;
}/*** @brief       : 从FT5429读取多个字节的数据* @param - addr: 设备地址* @param - reg : 要读取的开始寄存器地址* @param - len : 要读取的数据长度.* @param - buf : 读取到的数据缓冲区* @return       : 无*/
void ft5426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf)
{   struct i2c_transfer masterXfer; masterXfer.slaveAddress = addr;                /* 设备地址                 */masterXfer.direction = kI2C_Read;            /* 读取数据                 */masterXfer.subaddress = reg;             /* 要读取的寄存器地址            */masterXfer.subaddressSize = 1;               /* 地址长度一个字节             */masterXfer.data = buf;                       /* 接收数据缓冲区              */masterXfer.dataSize = len;                   /* 读取数据长度1个字节           */i2c_master_transfer(I2C2, &masterXfer);
} /*** @brief      : 读取当前触摸点个数* @param        : 无* @return       : 无*/
void ft5426_read_tpnum(void)
{ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
}

初始化芯片

/* 触摸屏结构体 */
struct ft5426_dev_struc
{   unsigned char initfalg;     /* 触摸屏初始化状态 */unsigned char intflag;        /* 标记中断有没有发生 */unsigned char point_num; /* 触摸点      */unsigned short x[5];      /* X轴坐标     */unsigned short y[5];      /* Y轴坐标     */};struct ft5426_dev_struc ft5426_dev;/*** @brief     : 初始化触摸屏,其实就是初始化FT5426* @param      : 无* @return       : 无*/
void ft5426_init(void)
{unsigned char reg_value[2];ft5426_dev.initfalg = FT5426_INIT_NOTFINISHED;/* 1、初始化IIC2 IO* I2C2_SCL -> UART5_TXD* I2C2_SDA -> UART5_RXD*/IOMUXC_SetPinMux(IOMUXC_UART5_TX_DATA_I2C2_SCL,1);IOMUXC_SetPinMux(IOMUXC_UART5_RX_DATA_I2C2_SDA,1);/* 配置I2C2 IO属性  *bit 16:0 HYS关闭*bit [15:14]: 1 默认47K上拉*bit [13]: 1 pull功能*bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 驱动能力为R0/6*bit [0]: 1 高转换率*/IOMUXC_SetPinConfig(IOMUXC_UART5_TX_DATA_I2C2_SCL,0x70B0);IOMUXC_SetPinConfig(IOMUXC_UART5_RX_DATA_I2C2_SDA,0X70B0);/* 2、初始化触摸屏中断IO和复位IO */gpio_pin_config_t ctintpin_config;IOMUXC_SetPinMux(IOMUXC_GPIO1_IO09_GPIO1_IO09,0);     /* 复用为GPIO1_IO9 */IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0);/* 复用为GPIO5_IO9 */IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09,0xF080);IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0X10B0);/* 中断IO初始化 */ctintpin_config.direction = kGPIO_DigitalInput;ctintpin_config.interruptMode = kGPIO_IntRisingOrFallingEdge;gpio_init(GPIO1, 9, &ctintpin_config);GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);              /* 使能GIC中对应的中断 */system_register_irqhandler(GPIO1_Combined_0_15_IRQn, (system_irq_handler_t)gpio1_io9_irqhandler, NULL);    /* 注册中断服务函数 */gpio_enableint(GPIO1, 9);                             /* 使能GPIO1_IO18的中断功能 *//* 复位IO初始化 */ctintpin_config.direction=kGPIO_DigitalOutput; ctintpin_config.interruptMode=kGPIO_NoIntmode; ctintpin_config.outputLogic=1;                    gpio_init(GPIO5, 9, &ctintpin_config); /* 3、初始化I2C 与I2C模块相同*/i2c_init(I2C2); /* 4、初始化FT5426 */gpio_pinwrite(GPIO5, 9, 0);    /* 复位FT5426 */delayms(20);gpio_pinwrite(GPIO5, 9, 1); /* 停止复位FT5426 */delayms(20);ft5426_write_byte(FT5426_ADDR, FT5426_DEVICE_MODE, 0);    /* 进入正常模式               */ft5426_write_byte(FT5426_ADDR, FT5426_IDG_MODE, 1);   /* FT5426中断模式           */ft5426_read_len(FT5426_ADDR, FT5426_IDGLIB_VERSION, 2, reg_value);printf("Touch Frimware Version:%#X\r\n", ((unsigned short)reg_value[0] << 8) + reg_value[1]);ft5426_dev.initfalg = FT5426_INIT_FINISHED;  /* 标记FT5426初始化完成 */ft5426_dev.intflag = 0;
}

读取坐标

/*** @brief     : 读取当前所有触摸点的坐标* @param         : 无* @return       : 无*/
void ft5426_read_tpcoord(void)
{unsigned char i = 0;unsigned char type = 0;//unsigned char id = 0;unsigned char pointbuf[FT5426_XYCOORDREG_NUM];ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);/** 从寄存器FT5426_TOUCH1_XH开始,连续读取30个寄存器的值,这30个寄存器* 保存着5个点的触摸值,每个点占用6个寄存器。*/ft5426_read_len(FT5426_ADDR, FT5426_TOUCH1_XH, FT5426_XYCOORDREG_NUM, pointbuf);for(i = 0; i < ft5426_dev.point_num ; i++){unsigned char *buf = &pointbuf[i * 6];/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:* bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件* bit5:4  保留* bit3:0  X轴触摸点的11~8位。*/ft5426_dev.x[i] = ((buf[2] << 8) | buf[3]) & 0x0fff;ft5426_dev.y[i] = ((buf[0] << 8) | buf[1]) & 0x0fff;type = buf[0] >> 6;  /* 获取触摸类型 *//* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:* bit7:4  Touch ID  触摸ID,表示是哪个触摸点* bit3:0  Y轴触摸点的11~8位。*///id = (buf[2] >> 4) & 0x0f;if(type == FT5426_TOUCH_EVENT_DOWN || type == FT5426_TOUCH_EVENT_ON )/* 按下    */{} else  {    /* 释放 */    }}
}

中断服务

/*** @brief             : GPIO1_IO9最终的中断处理函数* @param               : 无* @return               : 无*/
void gpio1_io9_irqhandler(void)
{ if(ft5426_dev.initfalg == FT5426_INIT_FINISHED){//ft5426_dev.intflag = 1;ft5426_read_tpcoord();//读取坐标并记录}gpio_clearintflags(GPIO1, 9); /* 清除中断标志位 */
}

PWM背光实验

通过PWM来实现屏幕亮度的调节,占空比越高,屏幕越亮

6ULL的PWM

​ 1、6ULL的PWM是16位计数器,

​ 2、有4个16位的FIFO。

​ 3、一个12位的分频器

​ 4、正点原子LCD屏幕的背光IO连接到了GPIO1_IO08上。GPIO1_IO08可以复用位PWM1_OUT信号。

​ PWM计数器从0X0000开始计数,当计数器的值等于PWMPR+1的时候定时器就会重新开始下一个周期的运行,因此PWMPR寄存器控制着PWM频率。

​ FIFO保存着采样值,当我们向PWMSAR寄存器写采样值的时候会写到FIFO里面,每当读取一次PWMSAR寄存器,FIFO里面的数据都会减一,或者每产生一个PWM信号,FIFO的数据也会减一。直到FIFO为空,那么就无法再产生PWM信号。FIFO为空的时候会产生中断,我们可以在中断中向FIFO写入采样数据,也就是向PWMSAR写数据。

​ PWMCR寄存器,bit0是PWM使能信号,bit2:1设置为0,每个周期使用FIFO里面的一个数据。Bit15:4,PWM分频设置,可以设置04095,对应14096分频。Bit17:16设置PWM时钟源,设置为1,表示使用ipg_clk=66MHz。bit19:18,设置为0。Bit27:26,设置为01,表示当FIFO里面空余位置大于2的时候FIFO为空。

​ PWMIR寄存器,bit0设置为1,开启FIFO空中断。

C程序编写

初始化

/* 背光PWM结构体 */
struct backlight_dev_struc
{   unsigned char pwm_duty;     /* 占空比  */
};
/* 背光设备 */
struct backlight_dev_struc backlight_dev;/*** @brief           : 设置PWM周期,就是设置寄存器PWMPR,PWM周期公式如下*                   PWM_FRE = PWM_CLK / (PERIOD + 2), 比如当前PWM_CLK=1MHz*                   要产生1KHz的PWM,那么PERIOD = 1000000/1K - 2 =  998* @param -  value   : 周期值,范围0~0XFFFF* @return           : 无*/
void pwm1_setperiod_value(unsigned int value)
{unsigned int regvalue = 0;if(value < 2)regvalue = 2;else regvalue = value - 2;PWM1->PWMPR = (regvalue & 0XFFFF);
}/*** @brief           : 设置Sample寄存器,Sample数据会写入到FIFO中,*                       所谓的Sample寄存器,就相当于比较寄存器,假如PWMCR中的POUTC*                    设置为00的时候。当PWM计数器中的计数值小于Sample的时候*                   就会输出高电平,当PWM计数器值大于Sample的时候输出底电平,*                   因此可以通过设置Sample寄存器来设置占空比* @param -  value : 寄存器值,范围0~0XFFFF* @return          : 无*/
void pwm1_setsample_value(unsigned int value)
{PWM1->PWMSAR = (value & 0XFFFF);
}/*** @brief       : 使能PWM* @param        : 无* @return       : 无*/
void pwm1_enable(void)
{PWM1->PWMCR |= 1 << 0;
}/*** @brief           : 设置PWM占空比* @param -  value    : 占空比0~100,对应0%~100%* @return           : 无*/
void pwm1_setduty(unsigned char duty)
{unsigned short preiod;unsigned short sample;backlight_dev.pwm_duty = duty;preiod = PWM1->PWMPR + 2;sample = preiod * backlight_dev.pwm_duty / 100;pwm1_setsample_value(sample);
}/*** @brief       : 初始化背光PWM* @param     : 无* @return       : 无*/
void backlight_init(void)
{unsigned char i = 0;/* 1、背光PWM IO初始化 */IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0); /* 复用为PWM1_OUT *//* 配置PWM IO属性    *bit 16:0 HYS关闭*bit [15:14]: 10 100K上拉*bit [13]: 1 pull功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 010 驱动能力为R0/2*bit [0]: 0 低转换率*/IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0XB090);/* 2、初始化PWM1        *//** 初始化寄存器PWMCR* bit[27:26]   : 01  当FIFO中空余位置大于等于2的时候FIFO空标志值位* bit[25]      :0  停止模式下PWM不工作* bit[24]        : 0   休眠模式下PWM不工作* bit[23]      : 0   等待模式下PWM不工作* bit[22]      : 0   调试模式下PWM不工作* bit[21]      : 0   关闭字节交换* bit[20]       : 0   关闭半字数据交换* bit[19:18]  : 00  PWM输出引脚在计数器重新计数的时候输出高电平*                    在计数器计数值达到比较值以后输出低电平* bit[17:16]   : 01  PWM时钟源选择IPG CLK = 66MHz* bit[15:4]   : 65  分频系数为65+1=66,PWM时钟源 = 66MHZ/66=1MHz* bit[3]        : 0   PWM不复位* bit[2:1]      : 00  FIFO中的sample数据每个只能使用一次。* bit[0]       : 0   先关闭PWM,后面再使能*/PWM1->PWMCR = 0; /* 寄存器先清零 */PWM1->PWMCR |= (1 << 26) | (1 << 16) | (65 << 4);/* 设置PWM周期为1000,那么PWM频率就是1M/1000 = 1KHz。 */pwm1_setperiod_value(1000);/* 设置占空比,默认50%占空比   ,写四次是因为有4个FIFO */backlight_dev.pwm_duty = 50;for(i = 0; i < 4; i++){pwm1_setduty(backlight_dev.pwm_duty);   }/* 使能FIFO空中断,设置寄存器PWMIR寄存器的bit0为1 */PWM1->PWMIR |= 1 << 0;system_register_irqhandler(PWM1_IRQn, (system_irq_handler_t)pwm1_irqhandler, NULL); /* 注册中断服务函数 */GIC_EnableIRQ(PWM1_IRQn); /* 使能GIC中对应的中断 */PWM1->PWMSR = 0;           /* PWM中断状态寄存器清零 */pwm1_enable();                /* 使能PWM1 */}

写在最后

到这里,正点原子的裸机视频已经全部结束了,视频很多,但是有很多视频个人觉得不用全部看完,比如一些寄存器的讲解以及编写。因为这些寄存器之类的只适用于当前芯片,换一个芯片就不能用了,然而在实际工作过程中,选择该款芯片作为产品芯片的概率也不会太高。所以我在学习的过程中,把这些视频掠过,先看了原子哥在讲解过程中编写的笔记,然后阅读了一下代码的大概流程,有个系统性的认知。大概会有哪些东西,以便于在使用其他芯片时能够更好的找到问题并解决问题!

ARM嵌入式裸机简单使用相关推荐

  1. ARM嵌入式裸机程序学习(一)

    这里不用ADS集成开发环境,因为ads兼容性存在问题,而且通过自己手动编译\链接对学习有好处,所以我们下面都在linux环境下进行ARM开发. 开发环境:Linux操作系统(Ubuntu) 开发工具: ...

  2. arm裸机与嵌入式linux驱动开发,如何编写基于ARM的裸机程序和基于Linux的驱动程序?...

    在嵌入式开发中,ADC应用比较频繁,本文主要讲解ADC的基本原理以及如何编写基于ARM的裸机程序和基于Linux的驱动程序. ARM架构:Cortex-A9Linux内核:3.14 在讲述ADC之前, ...

  3. ARM嵌入式linux系统学习之裸机(一)

    1.裸机学习什么? 裸机主要是学习对通过arm处理器的寄存器的操作来实现其内部资源和外部设备的控制及通信.学习裸机主要有两个目的,第一是熟悉其硬件的资源配置,学会编写bootloader:第二就是学习 ...

  4. arm嵌入式项目经典15例

    之前在书店看了一眼刘波文的arm嵌入式项目三位一体精讲,里面的案例.都是裸机单片机实现的. 我要学习做这几个案例,用linux下的操作系统来实现.看起来很简单,调试起来可不简单哦.时间为2014年6月 ...

  5. 嵌入式linux的运行过程,ARM嵌入式设备Linux系统启动步骤和方式

    > 1). 简介本文引用地址:http://www.eepw.com.cn/article/201607/294237.htm 本文简单介绍ARM嵌入式设备基于嵌入式Linux操作系统时候的启动 ...

  6. 8、ARM嵌入式系统:UART初始化

    一.UART概述 百度上UART的定义:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART.它将要传输的资料在串行通信与并行 ...

  7. ARM嵌入式主板在激光雕刻机领域的应用

    近年来随着激光加工技术的兴起,基于CNC的激光雕刻机作为一种机.光.电.算相结合的高科技产品在市场上得到了广泛应用.航大物联(www.lrist.com)激光雕刻机ARM嵌入式主板,采用DSP + A ...

  8. ARM嵌入式主板之路

    2009-10-13  来源: 短短几年,中国嵌入式产业发展迅猛,ARM嵌入式的异军突起,让嵌入式应用的领域更加日益广阔,同时ARM嵌入式正在成为带动IT产业新增长点,其主要应用在两个不同的领域,一个 ...

  9. ARM嵌入式的定义和开发工具介绍

    综述:[e800专稿] ARM嵌入式简介 ARM(Advanced RISC Machines),既可认为是一个公司的名字,也可认为是对一类微处理器的统称. ARM是微处理器行业的一家知名企业,设计了 ...

最新文章

  1. 完美解决distinct中使用多个字段的方法
  2. Lambda表达式【转】
  3. php请编写一个函数来将一个_为什么开发人员讨厌PHP
  4. java 异常处理机制(java 编程思想)
  5. [vue] 你了解vue的diff算法吗?
  6. hadoop--HDFS搭建客户端API环境
  7. 2018.09.22 上海大学技术分享 - An Introduction To Go Programming Language
  8. c语言 char转int_第三章、C语言中的数据类型
  9. MyBatis-Plus 分页查询以及自定义sql分页
  10. excel锁定单元格不能修改_锁定单元格不被任意修改和删除
  11. 《神奇的数学》读后感_奇妙的数学王国读后感10篇
  12. 地铁视频监控系统中无线监控技术的应用
  13. 《谷歌大数据经典论文读后感》
  14. mysql redo 物理复制 彭立勋_MySQL基础技能与原理——基本原理.ppt
  15. HTML图片热区map area的用法整理
  16. 【医学成像】超声成像中的分辨率
  17. 爱死Intellij Idea 01
  18. 一元三次函数的最值计算
  19. 编程竞赛_编程竞赛和挑战清单
  20. 农民工看完都学会了!龙湖集团java研发

热门文章

  1. 几种知名p2p网贷风险评估及投资回报率【更新2014-07-13】
  2. nexus 5 刷机
  3. 分类预测 | MATLAB实现SVM(支持向量机)分类预测
  4. Active Silicon FireBird CXP图像采集卡AS-FBD-4XCXP12-3PE4的特点
  5. js中,在数组后添加数组
  6. [C#] Newtonsoft.Json 版本冲突
  7. 国际网络领域会议:ACM SIGCOMM
  8. 【东北/西工/中山/厦大/东南/中农】公布复试分数线!【34所自划线】
  9. 让Linux支持usb虚拟网卡。
  10. JS高级知识点最新最全梳理上篇(codewhy视频)