嵌入式Linux视频笔记----i.MX6ULL裸机开发

7 篇文章 0 订阅

第88讲 LCD显示实验_哔哩哔哩_bilibili

ARM芯片本身基本介绍,裸机开发基本知识

第2遍看视频,增加截图、代码

关于ubuntu网络

我的win10主机通过wifi联网,在 嵌入式Linux视频笔记----Linux基础入门  的 P26 第25讲-安装NFS服务器 这一讲中我自己为了实现nfs,使用网线连接,按照其中设置,导致ubuntu无法联网。

现在将ubuntu联网方式记录如下【参考irtualbox+Ubuntu配置网络(桥接网络)】:

1、桥接网卡改为wifi

2、ubuntu与win10的IP设为同一网段,随后可以互相ping通

3、配置ubuntu DNS【缺点:每次都需要重新配置】

sudo vi /etc/resolv.conf

P1 第68讲 I.MX6ULL启动方式

芯片手册【3619页】、开发板原理图获取方式

启动模式设置三大模式--熔丝、外部 USB 串口、内部 SD卡 eMMc NAND;内部介质;
接口编号;介质属性;

芯片手册:具体引脚,启动模式设置中涉及的8个引脚

手册 8.2.1
手册 8.5​​​
手册 5.1
野火开发板

P2 第69讲 I.MX6ULL镜像文件

boot ROM内置程序:选择内部启动方式,启动boot ROM程序【芯片出厂前固化,用户无法变更】;初始化时钟、DDR3【从DCD获取信息】、从外部存储介质加载代码【从Boot data获取加载地址和大小】

镜像5要素:空偏移;image vector table IVT--1个结构体【包含Boot data 和DCD的位置,进而找到Boot data 和DCD】;Boot data--镜像在内存中的加载地址和大小;Device configuration data--DCD、寄存器初始化列表;bin文件--程序文件。

手册 8.7.1
表格位于手册 8.7.1
表格位于手册 8.7.1
“镜像”指包含空偏移

P3 第70讲 SDK方式烧录镜像

自动添加上一节讲的3部分信息:IVT、boot data、DCD

获取NXP官方SDK:官网、Linux平台

NXP SDK linux官网链接【需注册】

Linux中安装【下载,直接执行run文件

查看readme、详细解读脚本文件

查看readme
mkimage.sh

#!/bin/bash

function usage()
{
  echo "Usage: $0 target"
  echo "       target: ram -- the image will be loaded to RAM and run, the application must be built with ram link file"
  echo "       target: flash -- the image will be run on flash directly, the application must be build with flash link file"
  echo "       target: sd -- the image will be loaded from SD to RAM and run, the application must be build with ram link file"
  echo "Example: $0 ram"
}

if [ "$#" -ne 1 ]; then         # 参数个数不是1个,则给出提示信息
  usage $0
  exit 1
fi

SYSTEM=`uname -s`               # 获取主机类型 Linux
if [ $SYSTEM == "Linux" ]; then
    DCD_BUILDER=dcdgen.bin      # 2个程序赋值
    IMG_BUILDER=imgutil.bin
else
    DCD_BUILDER=dcdgen.exe
    IMG_BUILDER=imgutil.exe
fi

../bin/$DCD_BUILDER dcd.config dcd.bin          # 执行 dcdgen.bin 程序,根据dcd.config生成dcd.bin【dcd.config和本脚本在相同路径,主要是ddr配置信息】
if [ "$1" == "ram" ]; then                      # 传递给脚本的第1个参数,注意 dcd_file为刚生成的dcd.bin,app_file为用户程序
    ../bin/$IMG_BUILDER --combine base_addr=0x80000000 ivt_offset=0x1000 app_offset=0x2000 dcd_file=dcd.bin app_file=sdk20-app.bin ofile=sdk20-app.img image_entry_point=0x80002000
elif [ "$1" == "flash" ]; then                  
    ../bin/$IMG_BUILDER --combine base_addr=0x60000000 ivt_offset=0x1000 app_offset=0x2000 dcd_file=dcd.bin app_file=sdk20-app.bin ofile=sdk20-app.img image_entry_point=0x60002000
elif [ "$1" == "sd" ]; then
    ../bin/$IMG_BUILDER --combine base_addr=0x80000000 ivt_offset=0x400 app_offset=0x2000 dcd_file=dcd.bin app_file=sdk20-app.bin ofile=sdk20-app.img image_entry_point=0x80002000
else
    echo "Unsupported target $1"
    usage $0
fi

野火烧录工具:模仿官方SDK【dcd.config与官方完全一致,DDR与官方相同。重写mkimage.sh】;解压缩 看脚本文件、相比官方增加了烧录功能;演示用法

电脑插入SD卡后ubuntu相应设置
本机实测
视频效果,后来用mkimage.sh时提到sd卡有问题,更换sd卡后与我实测显示一致
mkimage.sh 

#!/bin/bash

function usage()
{
  echo "Usage: $0 file"
  echo "	file : the image which you want to burn "
  echo "Example: $0 helloworld.bin"
}



cur_user=`env | grep USER | cut -d "=" -f 2`	# 从环境变量中提取USER
echo $cur_user
if [ $cur_user == "root" ]; then		# 当前是root用户则报错
	echo -e "\033[31mThe cur_user is $cur_user. Please run the script with a normal user.\033[0m"
	exit 1
fi 

if [ "$#" -ne 1 ]; then
  usage $0
  exit 1
fi




SYSTEM=`uname -s`
if [ $SYSTEM == "Linux" ]; then
    DCD_BUILDER=dcdgen.bin
    IMG_BUILDER=imgutil.bin
else
		exit 1
fi


cat /proc/partitions
while true
do
read -p "Please Input the card ID [a~z]: (Input 'exit' for quit):" dev_index
    case $dev_index in  
    [[:lower:]]) break
    ;;
    exit) exit 0
    ;;

    * ) echo -e "\033[31mInvalid parameter!\033[0m"
				echo -e "\033[31mThe parameter should be between a~z, enter 'exit' to quit.\033[0m"
				echo -e "\033[34mUsage: If the SD card device corresponds to /dev/sdd, enter d\033[0m"
		continue
    ;;

    esac  
done
sd_idnex=sd$dev_index			# 如果SD卡路径为 /dev/sdb ,$dev_index=b,sd_idnex=sdb
echo $sd_index
if [ ! -e /dev/$sd_idnex ]; then 	# 文件不存在【-e 选项表示检查文件是否存在】
	echo "mkimage : /dev/$sd_idnex : No such file or directory"
	exit 1
fi

if [ ! -x $DCD_BUILDER ]; then		# 如果没有执行权限则增加执行权限【-x 选项表示检查文件是否可执行】
	chmod +x $DCD_BUILDER
fi

if [ ! -x $IMG_BUILDER ]; then
	chmod +x $IMG_BUILDER
fi

./$DCD_BUILDER dcd.config dcd.bin    #ivt表基地址0x80000000、bin文件链接地址0x80002000
./$IMG_BUILDER --combine base_addr=0x80000000 ivt_offset=0x400 app_offset=0x2000 dcd_file=dcd.bin app_file=$1 ofile=sdk20-app.img image_entry_point=0x80002000

sudo dd if=sdk20-app.img of=/dev/$sd_idnex bs=512 conv=fsync	# 使用dd命令将sdk20-app.img文件的内容写入SD卡

脚本用法演示,test.bin为空文件,仅仅演示用

P4 第71讲 ARM-V7架构

运行模式9种模式      用户--资源访问受限;系统--无限制;一般中断--硬件中断;快速中断--高速信号;管理--上电默认、初始化、软中断;数据访问终止--非法访问;未定义指令--跑飞了;用户安全扩展、虚拟化扩展

寄存器组通用寄存器组r0~r15【sp、lr、pc】;程序状态寄存器cpsr spsr;系统寄存器 cp15

P5 第72讲 ARM常用汇编指令

汇编格式、常用段名、常见伪操作、寄存器间数据传输【非通用寄存器操作对应专用指令】、内存与寄存器数据传输、压栈 出栈、跳转、算术运算、逻辑运算

P6 第73讲 VSCode编辑器

官网下载安装,终端code打开软件
特点--无需新建项目,直接打开文件夹;不同目录文件夹可保存至工作区;默认预览模式【打开后文件名斜体,再打开新文件,旧文件自动关闭】

关闭预览模式:取消黄色框选中


推荐插件:c/c++【按ctrl后跳转】;Chinese;material theme UI;CodeSpell Checker;Bracket Pair Colorizer;rainbow-highlighter

高效工作:快捷键

VsCode 跳转到函数之后怎么跳转回之前的位置:Ctrl + Alt + '-'【注意不是小键盘的-】

P7 第74讲 GPIO控制原理

数量5组,1组最多32个,总共124个

视频中手册4127页,比我的新

时钟:CCM_CCGR1

引脚复用、引脚属性

 

控制GPIO引脚

小结

P8 第75讲 汇编点亮LED

调整vscode字体 monospace、加入汇编插件ARM

终端字体设置

对比寄存器逐行写代码

.global _start                  #定义全局标号

_start:

@是能GPIO时钟
ldr r0,=0x20c406c         #把立即数【CCM_CCGR1地址】加载到寄存器
ldr r1,=0xffffffff              #本应该是0x3<<26位,对应GPIO1 CLOCK配置【除停止模式外,该外设时钟全程使能】
str r1,[r0]                          #寄存器数据写入到内存,[]可以理解为指针


@设置引脚复用为GPIO
ldr r0,=0x20e006c         #把立即数【SW_MUX_CTL Register 地址】加载到寄存器
ldr r1,=5                            #0101配置为GPIO1_IO04
str r1,[r0]


@设置引脚属性【上下拉/速率/驱动能力,具体配置参考硬件工程师建议或SDK包的配置】
ldr r0,=0x20e02f8         #把立即数【SW_PAD_CTL Register 地址】加载到寄存器
ldr r1,=0x10b0               #具体配置参考SDK包的配置
str r1,[r0] 


@控制GPIO引脚输出高低电平
ldr r0,=0x209c004        #把立即数【GPIOx_GDIR Register 地址】加载到寄存器
ldr r1,=16                         #GPIO的第4位配置为输出
str r1,[r0] 

ldr r0,=0x209c000        #把立即数【GPIOx_DR Register 地址】加载到寄存器
ldr r1,=0                            #GPIO的第4位配置为低电平【简单起见,全为0】
str r1,[r0] 

程序编译:下载裸机gcc编译器 gcc-arm-none-eabi【none指裸机,对应linux】、编译、链接、得到bin文件、添加头信息并烧录、开发板上电验证

第3条目的是为了指定链接地址

sudo apt-get install gcc-arm-none-eabi

arm-none-eabi-gcc -c led.s -o led.o

arm-none-eabi-ld -Ttext 0x80000000 led.o -o led.elf

arm-none-eabi-objcopy -O binary led.elf led.bin

cd ~/workdir/LinuxCode/bare_mental/part_1/download_tool/

./mkimage.sh ~/workdir/embed_linux_tutorial/base_code/bare_metal/part_2_gzc/led.bin

注意wifi的跳线帽要换到远离wifi一侧

P9 第76讲 使用c语言和sdk

vscode工作区用法:先打开文件夹A,“文件”-->“将工作区另存为”,命名保存,“文件”-->“将文件夹添加到工作区

使用官方sdk:包含寄存器宏定义,避免反复查datasheet;

使用C:bin文件段;c环境--bss段清0、栈指针设置;裸机控制外设--外设对应寄存器;
链接脚本lds文件;Makefile修改

P10 第77讲 按键检测输入

按下一次灯状态翻转

按键key硬件原理图

程序实现

P11 第78讲 工程文件整理和中断头文件移植

文件整理:source文件夹按模块文件夹存放文件;source/common存放共用函数;source/project存放main、启动文件;include文件夹存放sdk移植头文件;Makefile修改路径
中断头文件移

工程文件及分类​​​​​

P12 第79讲 通用中断控制器(GIC)

ARM有4个版本规范,A7使用V2 GIC-400

GIC-400是根据GIC规范设计的具体硬件产品


GIC结构:信号源--软件中断、私有中断、共享中断 使用最多 多核共享;分发器--中断信号到cpu接口单元;cpu接口单元--分发器到cpu、保存中断ID

竟然有上千个中断!!!

 


获取GIC基地址:芯片手册;cp15协处理器--16个协处理器【1个协处理器=一大类寄存器】、功能需要配置、配置为CBAR SCTLR VBAR

P13 第80讲 中断向量表

针对ARMv7-A

一级查表:自动跳转指定位置运行;写死在start.s文件中;

start.s文件中的一级查表


二级查表:在sdk的头文件中;

MCIMX6Y2.h文件中的二级查表

P14 第81讲 中断处理流程

汇编+C语言

中断上下文:内核寄存器进入中断前的值

具体流程:cps设置cpsr进入IRQ模式、初始化栈指针【不同模式栈不同】、push、获取中断编号、执行中断代码、还原现场、返回原程序

三级流水线:取指令、译指令、执行指令

P15 第82讲 按键中断实验

上电先进汇编代码复位中断,随后跳转到C代码main

C语言内嵌汇编:c语言读cp15协处理器,详细解释几行代码

IO中断相关

P16 第83讲 位置无关码和重定位

程序执行和变量访问方式:pc指针+偏移地址;绝对地址

位置无关码--pc指针+偏移地址、普通代码段、局部变量、可在任意内存运行;

位置相关码--绝对地址、必须在指定运行地址运行。

重定位:利用位置无关码将位置相关码加载到指定位置

data_start=0x85000000
data_end =0x85000000+*(.data)长度
上电复位时执行这2段汇编代码
加载data段  清零bss段

bin加载地址

这条命令使用hexdump工具来查看sdk20-app.img文件的十六进制和ASCII码。-n 50表示只显示文件的前50个字节,-C表示以规范的十六进制+ASCII码格式输出,-s 1024表示从文件的第1024个字节【SD卡镜像有1K的空偏移】开始显示。 

输出的前4个字节为IVT header,随后4个字节为bin文件链接地址0x8000 2000

P17 第84讲 时钟控制模块

时钟的4个层次:晶振、PL PFD、PLL选择、根时钟/外设时钟

系统时钟:24MHz、RTC时钟

PLL PFD倍频:7路PLL

手册10.3.1

PLL选择时钟:分频、选择

手册 ​​​​​18.5.1.5

根时钟/外设时钟:时钟树

手册 18.3

P18 第85讲 主频修改实验

对照上一讲介绍代码【学习阶段不要一个一个查寄存器,把握总体框架即可。除非做项目有需求】

//4层时钟设置
#include  "clock.h"


void system_clock_init(void)

{
    /******************* 第一层时钟设置--晶振时钟***********************/
    if ((CCM->CCSR & (0x01 << 2)) == 0) //CPU 使用的是 ARM PLL
    {
        /*将CPU时钟切换到XTAL (OSC) 时钟*/                   
        CCM->CCSR &= ~(0x01 << 8);      //控制CCSR: step_sel ,选择 osc_clk 作为时钟源
        CCM->CCSR |= (0x01 << 2);      //设置GLITCHLESS MUX 选择 step_clk 作为时钟源
    }



   /******************* 第二层时钟设置--PLL时钟***********************/
    /*设置PLL1输出时钟为792MHz,它将作为CPU时钟*/
    CCM_ANALOG->PLL_ARM |= (0x42 << 0);


    /*将CPU 时钟重新切换到 ARM PLL【初始化好PLL1就不直接用晶振了】*/
    CCM->CCSR &= ~(0x01 << 2);


    /*设置时钟分频系数为0,即不分频*/
    CCM->CACRR &= ~(0x07 << 0); //清零分频寄存器   不分频

   //CCM->CACRR |= (0x07 << 0);     // 8分频


    /*设置PLL2(System PLL) 输出时钟*/
    /* Configure SYS PLL to 528M */
    CCM_ANALOG->PLL_SYS_SS &= ~(0x8000);     //使能PLL2 PFD输出
    CCM_ANALOG->PLL_SYS_NUM &= ~(0x3FFFFFFF);//设置分频系数为0,即不分频。
    CCM_ANALOG->PLL_SYS |= (0x2000); //使能PLL2 输出
    CCM_ANALOG->PLL_SYS |= (1 << 0); //设置输出频率为528M
    while ((CCM_ANALOG->PLL_SYS & (0x80000000)) == 0) //等待设置生效
    {
    }

    /*设置PLL3(System PLL) 输出时钟*/
    /* Configure USB PLL to 480M */
    CCM_ANALOG->PLL_USB1 |= (0x2000);    //使能 PLL3时钟输出
    CCM_ANALOG->PLL_USB1 |= (0x1000);    //PLL3上电使能
    CCM_ANALOG->PLL_USB1 |= (0x40);      // 使能USBPHYn
    CCM_ANALOG->PLL_USB1 &= ~(0x01 << 0);//设置输出频率为480MHz
    while ((CCM_ANALOG->PLL_SYS & (0x80000000)) == 0)//等待设置生效
    {
    }


    /*关闭暂时不使用的 PLL4 、PLL5  、PLL6 、PLL7*/
    CCM_ANALOG->PLL_AUDIO = (0x1000);    //关闭PLL4
    CCM_ANALOG->PLL_VIDEO = (0x1000);    //关闭PLL5
    CCM_ANALOG->PLL_ENET =  (0x1000);    //关闭PLL6
    CCM_ANALOG->PLL_USB2 =  (0x00);           //关闭PLL7



    /******************第三层时钟设置--PFD*******************/
    /*禁用PLL2 的所有PFD输出*/
    CCM_ANALOG->PFD_528 |=(0x80U) ;      //关闭PLL2 PFD0
    CCM_ANALOG->PFD_528 |=(0x8000U) ;    //关闭PLL2 PFD1
    // CCM_ANALOG->PFD_528 |=(0x800000U) ;  //关闭PLL2 PFD2 ,DDR使用的是该时钟源,关闭后程序不能运行。暂时不关闭
    CCM_ANALOG->PFD_528 |=(0x80000000U); //关闭PLL2 PFD3

   
    /*设置PLL2 的PFD输出频率*/
    CCM_ANALOG->PFD_528 &= ~(0x3FU); //清零PLL2 PFD0 时钟分频
    CCM_ANALOG->PFD_528 &= ~(0x3F00U); //清零PLL2 PFD1 时钟分频
    CCM_ANALOG->PFD_528 &= ~(0x3F00U); //清零PLL2 PFD2 时钟分频
    CCM_ANALOG->PFD_528 &= ~(0x3F00U); //清零PLL2 PFD3 时钟分频


    CCM_ANALOG->PFD_528 |= (0x1B << 0); //设置PLL2 PFD0 输出频率为 352M
    CCM_ANALOG->PFD_528 |= (0x10 << 8); //设置PLL2 PFD0 输出频率为 594M
    CCM_ANALOG->PFD_528 |= (0x18 << 16); //设置PLL2 PFD0 输出频率为 396M
    CCM_ANALOG->PFD_528 |= (0x30 << 24); //设置PLL2 PFD0 输出频率为 198M


    /*启用PLL2 的所有PFD输出*/
    CCM_ANALOG->PFD_528 &= ~(0x80U) ;      //开启PLL2 PFD0
    CCM_ANALOG->PFD_528 &= ~(0x8000U) ;    //开启PLL2 PFD1
    CCM_ANALOG->PFD_528 &= ~(0x800000U) ;  //开启PLL2 PFD2
    CCM_ANALOG->PFD_528 &= ~(0x80000000U); //开启PLL2 PFD3



    /*禁用PLL3 的所有PFD输出*/
    CCM_ANALOG->PFD_480 |=(0x80U) ;      //关闭PLL3 PFD0
    CCM_ANALOG->PFD_480 |=(0x8000U) ;    //关闭PLL3 PFD1
    CCM_ANALOG->PFD_480 |=(0x800000U) ;  //关闭PLL3 PFD2
    CCM_ANALOG->PFD_480 |=(0x80000000U); //关闭PLL3 PFD3


    /*设置PLL3 的PFD输出频率*/
    CCM_ANALOG->PFD_480 &= ~(0x3FU);   //清零PLL3 PFD0 时钟分频
    CCM_ANALOG->PFD_480 &= ~(0x3F00U); //清零PLL3 PFD1 时钟分频
    CCM_ANALOG->PFD_480 &= ~(0x3F00U); //清零PLL3 PFD2 时钟分频
    CCM_ANALOG->PFD_480 &= ~(0x3F00U); //清零PLL3 PFD3 时钟分频


    CCM_ANALOG->PFD_480 |= (0xC << 0); //设置PLL3 PFD0 输出频率为 720M
    CCM_ANALOG->PFD_480 |= (0x10 << 8); //设置PLL3 PFD0 输出频率为 540M
    CCM_ANALOG->PFD_480 |= (0x11 << 16); //设置PLL3 PFD0 输出频率为 508.2M
    CCM_ANALOG->PFD_480 |= (0x13 << 24); //设置PLL3 PFD0 输出频率为 454.7M


    /*启用PLL3 的所有PFD输出*/
    CCM_ANALOG->PFD_480 &= ~(0x80U) ;      //开启PLL3 PFD0
    CCM_ANALOG->PFD_480 &= ~(0x8000U) ;    //开启PLL3 PFD1
    CCM_ANALOG->PFD_480 &= ~(0x800000U) ;  //开启PLL3 PFD2
    CCM_ANALOG->PFD_480 &= ~(0x80000000U); //开启PLL3 PFD3
 


    /******************第四层时钟设置--外设****************/
    CCM->CSCDR1 &= ~(0x01 << 6);        //设置UART选择 PLL3 / 6 = 80MHz
    CCM->CSCDR1 &= ~(0x3F);             //清零
    CCM->CSCDR1 |= ~(0x01 << 0);        //设置串口根时钟分频值为1,UART根时钟频率为:80M / (dev + 1) = 40MHz
}

实验现象:烧2次程序,主频不同,灯闪烁频率不同

    CCM->CACRR &= ~(0x07 << 0); //清零分频寄存器   不分频
//    CCM->CACRR |= (0x07 << 0);     // 8分频

P19 第86讲 串口通信实验

硬件原理图

 

相关寄存器:串口时钟、UART配置、收发数据

中断实现uart通信时,一般都使用fifo

代码【与上述分析一致】

#include "uart.h"

void uart_init(void)
{
    /*时钟初始化,设置 UART 根时钟,并设置为40MHz*/
    CCM->CSCDR1 &= ~(0x01 << 6); //设置UART选择 PLL3 / 6 = 80MHz
    CCM->CSCDR1 &= ~(0x3F);      //清零
    CCM->CSCDR1 |= (0x01 << 0);  //设置串口根时钟分频值为1,UART根时钟频率为:80M / (dev + 1) = 40MHz

    //禁用 UART1
    UART1->UCR1 &= ~UART_UCR1_UARTEN_MASK; 

    /*软件复位【刚开始工作状态不稳定,复位后就稳定了】*/
    UART1->UCR2 &= ~UART_UCR2_SRST_MASK;
    while ((UART1->UCR2 & UART_UCR2_SRST_MASK) == 0)
    {
    }
  
    /*引脚初始化*/
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10b0);

    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10b0);

    /*******uart初始化******/
    /*设置控制寄存器到默认值*/
    UART1->UCR2 |= (1 << 5);  //8位数宽度
    UART1->UCR2 &= ~(1 << 6); //一位停止位
    UART1->UCR2 &= ~(1 << 8); //禁用奇偶校验位

    UART1->UCR2 |= (1 << 2);  //使能发送
    UART1->UCR2 |= (1 << 1);  //使能接收
    UART1->UCR2 |= (1 << 14); //忽略流控

    /* For imx family device, UARTs are used in  mode, so that this bit should always be set.*/
    UART1->UCR3 |= UART_UCR3_RXDMUXSEL_MASK;

    //UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置发送FIFO 阀值
    //UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置接收FIFO 阀值

    UART1->UCR1 &= ~UART_UCR1_ADBR_MASK; //禁用可变波特率


    /*波特率设置方式 1 。 使用官方SDK设置波特率函数*/
   UART_SetBaudRate(UART1, 115200, 40000000);
#if 0
    /*波特率设置方式 2 。 手动计算,填入寄存器*/
    /*设置串口波特率
    * Ref Freq时钟 40MHz
    * UFCR RFDIV   110  0x06 7分频    5.714MHz
    * BaudRate     115200bps
    * UBMR         31-1 = 0x09
    * UBIR         10-1 = 0x1E
    */
    UART1->UFCR &= ~(0x07 << 7); //清零分频值
    UART1->UFCR |= (0x06 << 7);  //设置分频值,40MHz /7 =  5.714MHz

    UART1->UBIR = 0x09;
    UART1->UBMR = 0x1E;
#endif
    /*开启串口*/
    UART1->UCR1 |= UART_UCR1_UARTEN_MASK;

}


/*!
 * 功能:官方SDK 串口字符串读取函数
 * @brief Reads the receiver register.
 *
 * This function is used to read data from receiver register.
 * The upper layer must ensure that the receiver register is full or that
 * the RX FIFO has data before calling this function.
 *
 * @param base UART peripheral base address.
 * @return Data read from data register.
 */
static inline uint8_t UART_ReadByte(UART_Type *base)
{
    return (uint8_t)((base->URXD & UART_URXD_RX_DATA_MASK) >> UART_URXD_RX_DATA_SHIFT);
}


/*函数功能:串口接收函数
 *参数: base,指定串口。data,保存接收到的数据。 length,要接收的数据长度
 *
*/
void UART_ReadBlocking(UART_Type *base, uint8_t *data, uint8_t length)
{
    while (length--)
    {
        /* 等待接收完成 */
        while (!(base->USR2 & UART_USR2_RDR_MASK))
        {
        }
        /*读取接收到的数据 */
        *(data++) = UART_ReadByte(base);
    }
}


/*!
 * 功能:官方SDK 串口发送函数
 * 参数:base,指定串口。data,指定要发送的字节
 * This function is used to write data to transmitter register.
 * The upper layer must ensure that the TX register is empty or that
 * the TX FIFO has room before calling this function.
 */
static inline void UART_WriteByte(UART_Type *base, uint8_t data)
{
    base->UTXD = data & UART_UTXD_TX_DATA_MASK;
}

/*
 *功能:官方SDK 串口字符串发送函数
 *参数说明:
*/
void UART_WriteBlocking(UART_Type *base, const uint8_t *data, uint8_t length)
{

    while (length--)
    {
        /* Wait for TX fifo valid.
         * This API can only ensure that the data is written into the data buffer but can't
         * ensure all data in the data buffer are sent into the transmit shift buffer.
         */
        while (!(base->USR2 & UART_USR2_TXDC_MASK))
        {
        }
        UART_WriteByte(base, *(data++));
    }
}
# include  "common.h"
#include  "led.h"
#include  "button.h"
#include  "interrupt.h"
#include "clock.h"
#include "uart.h"
uint8_t button_status=0;
char g_charA = 'A'; //存储在 .data段
char g_charB = 'A'; //存储在 .data段

/*提示字符串*/
uint8_t txbuff[] = "Uart polling example\r\nBoard will send back received characters\r\n";

int main()
{

    //用于暂存串口收到的字符
    uint8_t ch; 
    /*系统时钟初始化*/
    system_clock_init();
   /*GIC中断和中断向量表初始化*/
    irq_init();
     /*初始化led灯和按键*/
    rgb_led_init();
    /*串口初始化*/
    uart_init();
    /*发送提示字符串*/
    UART_WriteBlocking(UART1, txbuff, sizeof(txbuff) - 1);
    red_led_on;         //红灯亮,提示程序运行中
    while (1)
    {
        UART_ReadBlocking(UART1, &ch, 1);
        UART_WriteBlocking(UART1, &ch, 1);
    }
    return 0;   

Makefile修改:gcc除法库

代码效果【上电发送提示字符,随后收什么发什么】

 

P20 第87讲 LCD显示原理

RGB接口三原色传输

硬件原理图

基本原理:视频即多帧图片、图片逐行显示


时序帧同步行同步、帧开始/结束缓冲时间


参数:看数据手册、RGB888 565、分辨率、像素时钟

P21 第88讲 LCD显示实验

相关寄存器

代码讲解:引脚配置、时钟配置、LCD配置

7种纯色循环显示

# include  "common.h"
#include  "led.h"
#include  "button.h"
#include  "interrupt.h"
#include "clock.h"
#include "uart.h"
#include  "lcd.h"

uint8_t button_status=0;
char g_charA = 'A'; //存储在 .data段
char g_charB = 'A'; //存储在 .data段

/*提示字符串*/
uint8_t txbuff[] = "Uart polling example\r\nBoard will send back received characters\r\n";

int main()
{

    //用于暂存串口收到的字符
    uint8_t ch; 
    /*lcd显存编号*/
    uint32_t frameBufferIndex = 0;
    /*系统时钟初始化*/
    system_clock_init();
   /*GIC中断和中断向量表初始化*/
    irq_init();
     /*初始化led灯和按键*/
    rgb_led_init();
    /*串口初始化*/
    uart_init();
    /*发送提示字符串*/
    UART_WriteBlocking(UART1, txbuff, sizeof(txbuff) - 1);
    /*初始 lcdif 引脚*/
    lcdif_pin_config(); 
     /*初始化时钟*/        
    lcdif_clock_init();        
    /*初始化 lcd属性和中断*/
    lcd_property_Init();  
    
    red_led_on;
    while (1)
    {
        frameBufferIndex ^= 1U;         //异或,相当于取反。1U表示无符号整数1
        APP_FillFrameBuffer(s_frameBuffer[frameBufferIndex]);

        LCDIF->NEXT_BUF = (uint32_t)s_frameBuffer[frameBufferIndex];
        
        /* 等待上一个图片刷新完成   Wait for previous frame complete. */
        while (!s_frameDone);
        s_frameDone = false;

        delay(0xFFFF);
    }

    return 0;    
#include "lcd.h"
#include  "interrupt.h"

/*定义 elcdf 缓冲区[2--两张图片  APP_IMG_HEIGHT--列像素  APP_IMG_WIDTH--行像素]*/
uint32_t s_frameBuffer[2][APP_IMG_HEIGHT][APP_IMG_WIDTH];
uint8_t s_frameDone = false;


/* elcdif 显示接口外部引脚初始化
*
*/
void lcdif_pin_config(void)
{
    IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0U);
    IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0xB9);

    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08, 0U);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08,0xB9);	/* 背光BL引脚 		*/

    /*设置GPIO1_08为输出模式*/
    GPIO1->GDIR |= (1<<8);  

    /*设置GPIO1_08输出电平为低电平【不打开背光灯】*/
    GPIO1->DR |= (0<<8);  
    
}





/*初始化 elcdf 的时钟
*/
void lcdif_clock_init(void)
{
    /*设置 PLL5  的输出时钟*/
    CCM_ANALOG->PLL_VIDEO_NUM &= (0x3 << 30);   //清零PLL 分数分频的分子寄存器
    CCM_ANALOG->PLL_VIDEO_DENOM &= (0x3 << 30); //清零PLL 分数分频的分母寄存器

    /*
     * 设置时钟分频
     *
     * ------------------------------------------------------------------------
     * |        分频数       | PLL_VIDEO[POST_DIV_SELECT]  | MISC2[VIDEO_DIV] |
     * ------------------------------------------------------------------------
     * |         1           |            2                |        0         |
     * ------------------------------------------------------------------------
     * |         2           |            1                |        0         |
     * ------------------------------------------------------------------------
     * |         4           |            2                |        3         |
     * ------------------------------------------------------------------------
     * |         8           |            1                |        3         |
     * ------------------------------------------------------------------------
     * |         16          |            0                |        3         |
     * ------------------------------------------------------------------------
     */
    CCM_ANALOG->PLL_VIDEO = 0;
    CCM_ANALOG->PLL_VIDEO &= ~(0x3 << 19); // 清零PLL_VIDEO[POST_DIV_SELECT]
    CCM_ANALOG->PLL_VIDEO |= (0x01 << 19); //设置分频系数为2

    CCM_ANALOG->MISC2 &= ~(0xC0000000); //清零VIDEO_DIV位
    CCM_ANALOG->MISC2 |= (0x3 << 30);// 配合CCM_ANALOG->PLL_VIDEO寄存器设置时钟分频


    CCM_ANALOG->PLL_VIDEO &= ~(0x7F); // 清零时钟分频
    CCM_ANALOG->PLL_VIDEO |= (0x1F);  //设置时钟分频为 31(十进制)

    CCM_ANALOG->PLL_VIDEO |= 1 << 13; //使能PLL5时钟输出

    /*等待设置生效*/
    while ((CCM_ANALOG->PLL_VIDEO & CCM_ANALOG_PLL_VIDEO_LOCK_MASK) == 0)
    {
    }

    /*设置从PLL5  到 elcdf 根时钟所经过的时钟选择和时钟分频寄存器*/
    CCM->CSCDR2 &= ~(0x07 << 15); //清零
    CCM->CSCDR2 |= (0x02 << 15);  //设置CSCDR2[LCDIF1_PRE_CLK_SEL] 选择 PLL5 输出时钟

    CCM->CSCDR2 &= ~(0x07 << 12); //清零
    CCM->CSCDR2 |= (0x01 << 12);  //设置 CSCDR2[LCDIF1_PRED]时钟分频值

    CCM->CBCMR &= ~(0x07 << 23); //清零CBCMR[LCDIF1_PODF] 时钟分频值
    CCM->CBCMR |= (0x01 << 23);

    CCM->CSCDR2 &= ~(0x07 << 9); //清零
    CCM->CSCDR2 |= (0x00 << 9);  //选择 CSCDR2[LCDIF1_CLK_SEL] 选择 PLL5 输出时钟
}


/* 软复位lcd【使用LCD_RST复位则为硬复位】 */
void ELCDIF_Reset(void)
{
    LCDIF->CTRL  = 1<<31; 

    delay(100);

    LCDIF->CTRL  = 0<<31;

    /*设置GPIO1_08输出电平为高电平,打开背光*/
    GPIO1->DR |= (1<<8);  

}


/*将 lcd 初始化为 rgb 888 模式,并设置lcd中断c
*/
void lcd_property_Init(void)
{

    /* Reset. */
    ELCDIF_Reset();


    LCDIF->CTRL &= ~(0x300); //根据颜色格式设置 CTRL 寄存器 颜色个事为RGB888
    LCDIF->CTRL |= (0x3 << 8);

    LCDIF->CTRL &= ~(0xC00); //设置数据宽度为24位宽
    LCDIF->CTRL |= (0x3 << 10);

    LCDIF->CTRL |= (0x20000); // 选择 RGB 模式
    LCDIF->CTRL |= (0x80000); // 选择 RGB 模式 开启显示
    LCDIF->CTRL |= (0x20);    //设置elcdf接口为主模式



    LCDIF->CTRL1 &= ~(0xF0000);   //清零32位数据有效位
    LCDIF->CTRL1 |= (0x07 << 16); // 设置32位有效位的低24位有效。


    // LCDIF->TRANSFER_COUNT = 0;//清零分辨率设置寄存器
    LCDIF->TRANSFER_COUNT |= APP_IMG_HEIGHT << 16; //设置一列 像素数  480
    LCDIF->TRANSFER_COUNT |= APP_IMG_WIDTH << 0;   //设置一行 像素数  800



    LCDIF->VDCTRL0 |= LCDIF_VDCTRL0_ENABLE_PRESENT_MASK;         //生成使能信号
    LCDIF->VDCTRL0 |= LCDIF_VDCTRL0_VSYNC_PERIOD_UNIT_MASK;      //设置VSYNC周期 的单位为显示时钟的时钟周期
    LCDIF->VDCTRL0 |= LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT_MASK; //设置VSYNC 脉冲宽度的单位为显示时钟的时钟周期


    LCDIF->VDCTRL0 |= (1 << 24);    //设置 数据使能信号的有效电平为高电平
    LCDIF->VDCTRL0 &= ~(0x8000000); //设置 VSYNC 有效电平为低电平
    LCDIF->VDCTRL0 &= ~(0x4000000); //设置HSYNC有效电平为低电平
    LCDIF->VDCTRL0 |= (0x2000000);  // 设置在时钟的下降沿输出数据,在时钟的上升沿捕获数据。

    LCDIF->VDCTRL0 |= APP_VSW;


    //     以显示时钟为单位的周期。

    LCDIF->VDCTRL1 = APP_VSW + APP_IMG_HEIGHT + APP_VFP + APP_VBP; //设置VSYNC 信号周期

    LCDIF->VDCTRL2 |= (APP_HSW << 18);                               //HSYNC 信号有效电平长度
    LCDIF->VDCTRL2 |= (APP_HFP + APP_HBP + APP_IMG_WIDTH + APP_HSW); //HSYNC 信号周期

    LCDIF->VDCTRL3 |= (APP_HBP + APP_HSW) << 16;
    LCDIF->VDCTRL3 |= (APP_VBP + APP_VSW);

    LCDIF->VDCTRL4 |= (0x40000);
    LCDIF->VDCTRL4 |= (APP_IMG_WIDTH << 0);

    LCDIF->CUR_BUF = (uint32_t)s_frameBuffer[0];
    LCDIF->NEXT_BUF = (uint32_t)s_frameBuffer[0];

    /*注册lcd中断函数*/
    system_register_irqhandler(LCDIF_IRQn, (system_irq_handler_t)(uint32_t)APP_LCDIF_IRQHandler, NULL); // 设置中断服务函数

    /*开启中断*/
    GIC_EnableIRQ(LCDIF_IRQn); 

    /*使能 elcdf 一帧传输完成中断*/
    LCDIF->CTRL1_SET |= (0x2000);  

    /*开启 elcdf 开始显示*/
    LCDIF->CTRL_SET |= 0x1;
    LCDIF->CTRL_SET |= (1 << 17); 
}

void APP_FillFrameBuffer(uint32_t frameBuffer[APP_IMG_HEIGHT][APP_IMG_WIDTH])
{
    /* Background color. 【黑色】*/
    static const uint32_t bgColor = 0U;
    /* Foreground color. */
    static uint8_t fgColorIndex = 0U;
    // static uint16_t lowerRightX = (APP_IMG_WIDTH - 1U) / 2U;     //例程代码只在屏幕左上角1/4显示
    // static uint16_t lowerRightY = (APP_IMG_HEIGHT - 1U) / 2U;
    static uint16_t lowerRightX = (APP_IMG_WIDTH - 1U);                     //改为全屏显示
    static uint16_t lowerRightY = (APP_IMG_HEIGHT - 1U);

    //对应7种颜色
    static const uint32_t fgColorTable[] = {0x000000FFU, 0x0000FF00U, 0x0000FFFFU, 0x00FF0000U,
                                            0x00FF00FFU, 0x00FFFF00U, 0x00FFFFFFU};
    uint32_t fgColor = fgColorTable[fgColorIndex];

    uint32_t i, j;

    /* Background color. 【全屏黑色】*/
    for (i = 0; i < APP_IMG_HEIGHT; i++){
        for (j = 0; j < APP_IMG_WIDTH; j++) {
            frameBuffer[i][j] = bgColor;
        }
    }

    /* Foreground color. */
    for (i = 0; i < lowerRightY; i++){
        for (j = 0; j < lowerRightX; j++) {
            frameBuffer[i][j] = fgColor;
        }
    }

    if(fgColorIndex == ARRAY_SIZE(fgColorTable))
        fgColorIndex = 0;
    else
        fgColorIndex++;

}

void APP_LCDIF_IRQHandler(void)
{
    uint32_t intStatus = 0;

    /*获取传输完成中断的状态,*/
    intStatus = ((LCDIF->CTRL1) & (1 <<9));
     /*清除 1 帧传输完成中断标志位*/
    LCDIF->CTRL1_CLR = (1 << 9);

    if (intStatus)
    {
        s_frameDone = true;         //传输完成后,全局变量为true
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值