第88讲 LCD显示实验_哔哩哔哩_bilibili
ARM芯片本身基本介绍,裸机开发基本知识
第2遍看视频,增加截图、代码
我的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
芯片手册【3619页】、开发板原理图获取方式
启动模式设置:三大模式--熔丝、外部 USB 串口、内部 SD卡 eMMc NAND;内部介质;
接口编号;介质属性;
芯片手册:具体引脚,启动模式设置中涉及的8个引脚
手册 8.2.1 手册 8.5 手册 5.1 野火开发板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 “镜像”指包含空偏移自动添加上一节讲的3部分信息:IVT、boot data、DCD
获取NXP官方SDK:官网、Linux平台
NXP SDK linux官网链接【需注册】
Linux中安装【下载,直接执行run文件】
查看readme、详细解读脚本文件
查看readmemkimage.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为空文件,仅仅演示用
运行模式:9种模式 用户--资源访问受限;系统--无限制;一般中断--硬件中断;快速中断--高速信号;管理--上电默认、初始化、软中断;数据访问终止--非法访问;未定义指令--跑飞了;用户安全扩展、虚拟化扩展
寄存器组:通用寄存器组r0~r15【sp、lr、pc】;程序状态寄存器cpsr spsr;系统寄存器 cp15
汇编格式、常用段名、常见伪操作、寄存器间数据传输【非通用寄存器操作对应专用指令】、内存与寄存器数据传输、压栈 出栈、跳转、算术运算、逻辑运算
官网下载安装,终端code打开软件
特点--无需新建项目,直接打开文件夹;不同目录文件夹可保存至工作区;默认预览模式【打开后文件名斜体,再打开新文件,旧文件自动关闭】
推荐插件:c/c++【按ctrl后跳转】;Chinese;material theme UI;CodeSpell Checker;Bracket Pair Colorizer;rainbow-highlighter
高效工作:快捷键
VsCode 跳转到函数之后怎么跳转回之前的位置:Ctrl + Alt + '-'【注意不是小键盘的-】
调整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一侧vscode工作区用法:先打开文件夹A,“文件”-->“将工作区另存为”,命名保存,“文件”-->“将文件夹添加到工作区”
使用官方sdk:包含寄存器宏定义,避免反复查datasheet;
使用C:bin文件段;c环境--bss段清0、栈指针设置;裸机控制外设--外设对应寄存器;
链接脚本lds文件;Makefile修改
按下一次灯状态翻转
按键key硬件原理图
程序实现
文件整理:source文件夹按模块文件夹存放文件;source/common存放共用函数;source/project存放main、启动文件;include文件夹存放sdk移植头文件;Makefile修改路径
中断头文件移
ARM有4个版本规范,A7使用V2 GIC-400
GIC-400是根据GIC规范设计的具体硬件产品
GIC结构:信号源--软件中断、私有中断、共享中断 使用最多 多核共享;分发器--中断信号到cpu接口单元;cpu接口单元--分发器到cpu、保存中断ID
获取GIC基地址:芯片手册;cp15协处理器--16个协处理器【1个协处理器=一大类寄存器】、功能需要配置、配置为CBAR SCTLR VBAR
针对ARMv7-A
一级查表:自动跳转指定位置运行;写死在start.s文件中;
start.s文件中的一级查表
二级查表:在sdk的头文件中;
汇编+C语言
中断上下文:内核寄存器进入中断前的值
具体流程:cps设置cpsr进入IRQ模式、初始化栈指针【不同模式栈不同】、push、获取中断编号、执行中断代码、还原现场、返回原程序
三级流水线:取指令、译指令、执行指令
上电先进汇编代码复位中断,随后跳转到C代码main,
C语言内嵌汇编:c语言读cp15协处理器,详细解释几行代码
IO中断相关
程序执行和变量访问方式:pc指针+偏移地址;绝对地址
位置无关码--pc指针+偏移地址、普通代码段、局部变量、可在任意内存运行;
位置相关码--绝对地址、必须在指定运行地址运行。
重定位:利用位置无关码将位置相关码加载到指定位置
data_start=0x85000000bin加载地址:
这条命令使用hexdump工具来查看sdk20-app.img文件的十六进制和ASCII码。-n 50表示只显示文件的前50个字节,-C表示以规范的十六进制+ASCII码格式输出,-s 1024表示从文件的第1024个字节【SD卡镜像有1K的空偏移】开始显示。
输出的前4个字节为IVT header,随后4个字节为bin文件链接地址0x8000 2000。
时钟的4个层次:晶振、PL PFD、PLL选择、根时钟/外设时钟
系统时钟:24MHz、RTC时钟
PLL PFD倍频:7路PLL
手册10.3.1PLL选择时钟:分频、选择
手册 18.5.1.5根时钟/外设时钟:时钟树
手册 18.3对照上一讲介绍代码【学习阶段不要一个一个查寄存器,把握总体框架即可。除非做项目有需求】
//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分频
硬件原理图:
相关寄存器:串口时钟、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除法库
代码效果【上电发送提示字符,随后收什么发什么】:
RGB接口三原色传输
硬件原理图:
基本原理:视频即多帧图片、图片逐行显示
时序:帧同步、行同步、帧开始/结束缓冲时间
参数:看数据手册、RGB888 565、分辨率、像素时钟
相关寄存器:
代码讲解:引脚配置、时钟配置、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
}