2 第一个嵌入式程序
1、下位机要完成的工作:
1)准备一个TF卡
2)将已有的uboot二进制文件烧写到TF卡上烧写在上位机完成
3)将TF卡插入下位机
4)下位机TF卡的uboot运行
5)利用uboot的tftp命令从上位机下载uboot到内存,然后再内存烧写到EMMC上
注意:tftp命令不能直接烧写到EMMC上
6)将TF卡拔出,重启上电,EMMC上的uboot运行
7)利用EMMC的uboot的tftp命令下载裸板程序到内存,然后从内存中启动裸板程序到此裸板程序在下位机就可以正常运行起来
8)整个过程类比U盘安装windows系统(一模一样)
案例:向X6818下位机下载和运行一个裸板程序,实现开关X6818下位机上的LED灯
实施步骤:
上位机实施步骤:
1)安装tftp网络服务
sudo apt-get install tftpd-hpa
2)配置tftp服务,指定下载目录
sudo vim /etc/default/tftpd-hpa
将:TFTP_DIRECTORY="/var/lib/tftpboot"
修改为:
TFTP_DIRECTORY="/tftpboot"
保存退出
说明:也就是将下载目录指定为/tftpboot目录
3)重启tftp网络服务
sudo service tftpd-hpa restart
4)创建下载目录和修改下载目录的用户和组权限(由root用户修改为当前用户)
sudo mkdir /tftpboot
sudo chown baby/tftpboot -R //修改/tftpboot目录的用户为baby
sudo chgrp baby /tftpboot –R //修改/tftpboot目录的组为baby
5)拷贝led.bin裸板程序到下载目录
获取led.bin:resource.rar/day01/led.bin
将windows下led.bin二进制文件拷贝到linux虚拟机中:
5.1)在linux虚拟机中创建共享文件目录
点击虚拟机软件菜单栏中的"虚拟机"->再点击"设置"
->再点击"选项"->再点击"共享文件夹"->在右侧点击"总是启动"
->在下方点击"添加"->再点击"下一步"->再点击“浏览”,用来
指定windows下的共享目录(例如E:s5p6818)此时下方也会出现
跟共享目录同名的名称s5p6818,此名称用于linux虚拟机中使用
->再点击"下一步"和完成即可,此时windows和linux共享目录创建完毕
5.2)将windows下的文件拷贝到linux虚拟机的tftpd-hpa下载目录中
先将led.bin拷贝到windows下的共享目录(例如:E:s5p6818)
在linux虚拟机中执行:
cp /mnt/hgfs/ s5p6818/led.bin /tftpboot
6)配置上位机的IP地址为192.168.1.8
配置步骤:
右键点击ubuntu右上角的网络连接图标(上下箭头或者扇形)
->再点击"编辑连接"->将别人的配置信息全部删除->
点击"添加"->连接名称指定为"TPAD"->在点击“IPV4设置"->
"方法"选择为”手动“,->再点击"添加"
->IP地址为:192.168.1.8,子网掩码:255.255.255.0
网关:192.168.1.1->最后点击保存退出即可
注意:可以手动点击网络连接图标,选中自己的配置TPAD
如果是虚拟机,还需要配置网络的连接信息:
点击虚拟机软件菜单栏的“虚拟机”->在点击设置->
选中网络适配器->选择为"桥接方式"->最后点击确定即可
如果是虚拟机,还要注意虚拟机linux的虚拟网卡桥接到实际的哪个网卡上,一定要确保桥接到和下位机连接的那个网卡上。
具体配置如下:
点击虚拟机软件菜单栏的"编辑"->在点击虚拟网络编辑器
->桥接到具体的那个网卡(跟下位机连接)可以在网络邻居中查看
->确定以后选中桥接的网卡即可,点击应用确定即可
2、下位机实施步骤:
1)明确:X6818下位机系统启动顺序
上电->CPU自动检查SD0卡槽是否有TF卡插入,如果有,CPU就运行TF卡上的uboot软件,如果没有,CPU再去运行EMMC(类似硬盘)的uboot。如果EMMC也没有uboot,系统就会启动失败。
2)首先向TF卡上烧写一个uboot
1.准备一个TF卡
2.将TF卡插入到TF卡转USB的转接器上
3.将转接器插入到上位机的USB口
4.然后打开目录resource.rar oolsIROM_Fusing_ToolV2.0下的软件IROM_Fusing_Tool.exe
注意:如果是WIN7/WIN10系统,必须以管理员的权限打开。
5.点击"Browse",选择文件ubootpak.bin(传说中的uboot二进制文件)
此文件路径:resource.rar/day01/ubootpak.bin,选中此文件以后,再点击"Add"
6.最后在点击“START”,如果出现"Fusing image done"表示成功的将ubootpak.bin这个二进制文件烧写到TF卡上。然后将TF卡拔出,插入到下位机的“SD0”卡槽上!
问:下位机上电以后,如何知道TF卡上的uboot是否正常运行呢?
如果正常运行,如何在上位机给下位机的uboot下发命令呢?
答:只需在上位机运行一个UART串口工具即可。
将来在上位机上通过UART串口来获取下位机的软件运行的打印信息,还可以通过UART串口给下位机下发命令
Windows串口工具:SecureCRT
linux串口工具:kermit或者minicom
注意:kermit/minicom在配置时指定UART串口的端口为ttyUSB0(仅限USB转串口)
SecureCRT软件在ftp://ARM/SecureCRT_CN.zip
解压缩(建议存放的目录中不要有中文路径),然后运行SecureCRT.exe
紧接着配置SecureCRT软件:
点击左上角的“快速链接”:
协议:Serial
端口:COM1,注意:如果是USB转串口,到设备管理器中查看串口的端口号,如果USB转串口的端口号是COM4,那就选择COM4
波特率:115200
数据位:8
奇偶校验:None
停止位:1
将“RTS/CTS”前面的勾去掉
最后点击"连接"
然后重启下位机(插拔电源线或者按复位键)即可在SecureCRT上获取到下位机运行的uboot软件信息(前提是TF卡已经插到SD0卡槽上)
如果上电以后,uboot运行,会出现"Hitany key to stop autoboot: 3"倒计时快速按任意键中断倒计时,进入uboot的命令行模式,出现uboot命令行的提示符:S5P6818#至此TF卡上的uboot正常运行,至此通过uboot的命令行可以在上位机给下位机发送控制命令。
3)利用TF卡上的uboot和uboot相关的烧写命令,向下位机的EMMC烧写一个同样的uboot。
上位机的操作步骤:
1.获取要向下位机烧写的uboot二进制文件
resource.rar/day01/ubootpak.bin
2.拷贝uboot二进制文件到tftp服务的下载目录
注意:先从windows拷贝
cp /mnt/hgfs/baby/ubootpak.bin /tftpboot
3、下位机操作步骤:
1)确保TF卡已经插入SD0卡槽
2)下位机上电,并且进入uboot命令行模式
3)在uboot的命令行模式下,执行以下命令:
print //此uboot命令用于打印uboot的环境变量,重点关注以下环境变量:
serverip=192.168.1.8 //告诉uboot,上位机的IP为192.168.1.8
ipaddr=192.168.1.165 //给下位机配置的IP为192.168.1.165
setenv ipaddr 192.168.1.110 //设置环境变量ipaddr的值为192.168.1.110
//将下位机的ip重新配置为192.168.1.110
同理也可以配置上位机IP
saveenv //将设置的新环境变量进行保存
ping 192.168.1.8 //下位机来ping上位机,测试硬件网络是否连同
如果出现:
ping failed; host 192.168.1.8 is not alive //硬件连接失败
host 192.168.1.8 is alive //硬件连接成功
tftp 0x48000000 ubootpak.bin
命令说明:tftp命令用于从上位机下载文件到下位机的内存中,此命令不能直接将下载文件烧写到EMMC上。
从上位机利用tftp服务下载ubootpak.bin二进制文件到下位机的内存的0x48000000存储空间中。
注意:X6818内存的地址空间范围:0x40000000~0x40000000+1GB-1
再次强调:tftp命令仅仅是将软件下载到下位机的内存中
注意:如果下载时,出现"Loading:T T T T..."说明下载超时。
解决办法:重启上位机的tftp网络服务:
sudo service tftpd-hpa restart
如果出现:"Bytestransferred = A (B hex)"
表明下载文件成功,A=文件大小(按10进制表示),B=文件大小(按16进制表示)
update_mmc 2 2ndboot 0x48000000 0x200 0x78000
命令说明:update_mmc命令用于更新EMMC上的内容,也就是将内存中的数据烧写到EMMC上。
明确:EMMC和SD卡和TF卡硬件特性一模一样
明确:EMMC的起始地从0x0开始
"2":代表EMMC,类推:如果是"0":代表SD0卡槽上的TF卡
如果是"1":代表SD1卡槽上的TF卡
"2ndboot":uboot所在分区的名称
0x48000000:要烧写的文件在内存的存储起始地址
0x200:将文件烧写到EMMC的起始地址
问:为什么不将uboot烧写到EMMC的0x0起始地址呢?
答:三星官方要求,三星要求必须烧写到EMMC的0x200。也就是CPU上电,自动到EMMC的0x200找uboot运行0x78000:向EMMC烧写文件的大小。
命令结果:将内存的0x48000000存储的文件(ubootpak.bin)烧写到EMMC的0x200起始地址,烧0x78000这么大。
至此:向下位机的EMMC烧写uboot完毕,拔出TF卡,重新复位下位机通过SecureCRT观察下位机的EMMC的uboot是否正常运行。
4、向下位机下载裸板程序
led.bin并且运行led.bin,观察下位机LED灯闪烁情况
上位机执行:
1)获取裸板程序led.bin
resource.rar/day01/led.bin
2)拷贝文件到下载目录
cp led.bin /tftpboot
下位机执行:
1)复位下位机,进入uboot的命令行模式,然后执行一下命令:
tftp 0x48000000 led.bin
go 0x48000000
说明:让CPU跑到0x48000000内存地址去运行led.bin
5、明确相关基本概念
1)计算机系统包括硬件和软件,硬件层面上仅仅包括两类:CPU核和"外设"。
2)任何ARM处理器(例如三星的S5P6818)内部包括两部分:CPU核和各种硬件外设控制器。
例如:具体参见S5P6818芯片手册SEC_S5P6818X完整版.pdf的P34
结论:S5P6818内部包含了8个CPU核和各种硬件外设的控制器(LCD显示屏控制器,Nandflash控制器,DDR内存控制器,SD卡/TF卡/EMMC控制器,UART串口控制器等)
将来CPU核要想访问某个外设,CPU必须通过对应的控制器来访问具体的外设。
外设的控制器类似桥梁,控制器帮助CPU核间接访问外设。
参见bt.bmp,根据此图得到结论:
1.明确CPU核不允许直接访问BT
2.CPU首先访问UART控制器
3.UART控制器在访问BT
3)CPU核功能:仅仅做数据运算,数据来自外设(例如BT或者内存条)数据经过CPU核的运算最终还是要回归到外设。
"外设":提供数据,除了CPU核以外的任何硬件,包括肉眼看得到的硬件(例如EMMC,内存条)还包括肉眼看不到的硬件(各种处理器内部集成的控制器)
结论:只要以上电,CPU核就和外设玩命时时刻刻的进行数据的交互 。
4)ARM处理器内部集成的各种外设的控制器内部同样集成了一大堆的寄存器
“寄存器”:能够暂存数据的硬件,掉电数据丢失,和内存一模一样。
并且这些一大堆的寄存器都有自己的物理地址。
注意:寄存器同样也是外设
5)切记:CPU核访问外设都是以地址指针的形式访问
CPU核要想访问某个外设,必须先获取到这个外设的起始地址
6)CPU核访问外设的具体流程
以CPU核给BT蓝牙发送数据为例:
1.CPU核软件上以地址指针的形式访问UART控制器内部的一大堆寄存器,本质就是CPU核通过控制器内部的寄存器间接给控制器发送控制命令
例如:某个UART控制器中寄存器的基地址为0xC002C000,寄存器的大小为4字节目标是向这个寄存器写入命令数据0x55555555
*(unsigned long *)0xC002C000 = 0x55555555;
2.UART控制器一旦接收到CPU发送来的控制命令,UART控制器硬件上自动的操作处理器和BT之间的硬件连接线。
3.硬件连接线将来在自动影响外设BT
总结:CPU核访问外设的基本流程:
1.CPU核软件上以地址指针的形式访问外设对应的控制器内部的寄存器,其实就是CPU核给控制器发送控制命令。
2.控制器一旦接收到了CPU核发送来的控制命令,控制器会自动操作之间的硬件连接信号线。
3.硬件连接信号线将来在自动影响操作外设。
切记:计算机中,CPU核和外设的硬件通信方式常见的有以下几种:
GPIO通信方式/UART串口通信/I2C总线通信方式/SPI总线通信方式/1-Wire总线通信方式等。
7)案例:X6818下位机上有四个LED指示灯,实现CPU核交替着开关四个灯
例如:LED1开->LED2开->LED3开->LED4开
->LED4关->LED3关->LED2关->LED1关
实施步骤:
1.认真分析用户需求
目前的用户需求就是交替开关灯,实现流水灯功能
高电平=3.3V=二进制1
低电平=0V=二进制0
2.掌控LED灯的硬件信息
粗看:
看LED灯在下位机的连接位置(确保外设LED灯焊接上)
细看:先看原理图后看芯片手册
先看原理图
1.打开底板原理图x6818bv2.pdf
通过LED部分的原理图得到(以LED1灯为例):
LED1这个灯一端的连接线名称为GPIOC12
要想让LED1灯亮,只需让GPIOC12这根连接线给低电平
要想让LED1灯灭,只需让GPIOC12这根连接线给高电平
问:GPIOC12这根线最终连接到哪里呢?
答:这根线势必最终连接到S5P6818上。
2.再打开核心板原理图x4418cv3_release20150713.pdf
然后在核心板原理图搜索关键字"GPIOC12",得到:
LED1最终连接到S5P6818引脚上,此引脚为:SA12/GPIOC12/SPITXD2/SDnRST2
并且发现此引脚具有四种功能:
SA12:作为地址线功能,一般用于连接内存条
GPIOC12:作为GPIO功能,就是输入或者输出功能
SPITXD2:作为SPI总线的发送数据功能
SDnRST2:作为SD卡的复位功能
注意:一个引脚具有多种功能,简称引脚的复用功能,同一时刻只能用其中一种。
结论:LED1连接的S5P6818引脚应该选择GPIOC12功能
此时此刻,将复杂的硬件原理图进行简化,参见led.bmp
问:连接线何为输入或者输出功能?
答:明确:处理器和外设之间的硬件连接线永远要不是高电平要不是低电平,关键看谁操作!
明确:引脚的输入和输出功能同样同一时刻选择一个,要不是输入要不是输出
引脚为输入功能:引脚对应的连接线的电平由外设影响,此引脚就是输入功能
引脚为输出功能:引脚对应的连接线的电平由CPU影响,此引脚即使输出功能
结论:LED1对应的引脚CPIOC12应该选择为输出功能。也就是:
CPU通过引脚CPIOC12输出一个高电平,LED1灭
CPU通过引脚GPIOC12输出一个低电平,LED1亮
结论:CPU要想开关LED1,看图得到具体操作步骤如下:
1.CPU核软件上以地址指针的形式访问GPIO控制器内部一大堆寄存器,也就是CPU核通过寄存器给GPIO控制器发送控制命令。
2.GPIO控制器接收到命令以后,GPIO控制器硬件上自动影响操作。
GPIOC12这个引脚(无非就是给高电平或者低电平)
3.GPIOC12引脚的状态(无非就是给高电平或者低电平)最终影响LED1的状态。
问:GPIO控制器以及内部的一大堆寄存器是如何访问操作呢?
答:由于GPIO控制器集成在S5P6818内部,所以只需看S5P6818的芯片手册中关于GPIO控制器的章节即可。
最后看芯片手册:
1.打开S5P6818芯片手册P739
2.得到GPIO控制器相关的硬件信息如下:
S5P6818共支持160个引脚,并且将160个引脚分成五组:A/B/C/D/E
每一组32个引脚(联想:4字节=32bit位)
结论:LED1对应的引脚GPIOC12就是C组的第12个引脚
P742具体阐述GPIOC12如何操作的步骤:
1.先设置复用功能选择寄存器对应的BIT位的值选择为GPIO功能
2.然后设置输出使能寄存器对应的BIT位的值选择为输出功能
3.最后设置输出寄存器对应的BIT为的值输出高电平或者低电平
结论:涉及的三个关键寄存器
复用功能选择寄存器:选择哪种功能
输出使能寄存器:选择输入还是输出
输出寄存器:输出高或者低电平
3.P745具体开始阐述各个寄存器的信息
以LED1对应的GPIOC12为例:
GPIOCOUT:输出寄存器
基地址:0xC001C000
并且一个bit位对应一个GPIO引脚的配置
BIT[12] = 0; //GPIOC12输出低电平,灯就亮
BIT[12] = 1; //GPIOC12输出高电平,灯就灭
C语言代码:注意至修改BIT[12]的值,其余BIT位不允许修改
开灯代码:
*(unsigned long *)0xC001C000 &= ~(1 << 12);
关灯代码:
*(unsigned long *)0xC001C000 |= (1 << 12);
GPIOCOUTENB:输出使能寄存器
基地址:0xC001C004
并且一个bit位对应一个GPIO引脚的配置
BIT[12] = 0; //GPIOC12配置为输入功能
BIT[12] = 1; //GPIOC12配置为输出功能
C语言代码:注意至修改BIT[12]的值,其余BIT位不允许修改
配置为输入:
*(unsigned long *)0xC001C000 &= ~(1 << 12);
配置为输出:
*(unsigned long *)0xC001C000 |= (1 << 12);
GPIOCALTFN0:复用功能选择寄存器
基地址:0xC001C020
两个bit位对应一个GPIO引脚
GPIOC12对应的bit位为BIT[25:24]
注意:具体选择哪个功能,参见P56,查表确定哪个功能
BIT[25:24]=00 //SA12
BIT[25:24]=01 //GPIOC12
BIT[25:24]=10 //SPITXD2
BIT[25:24]=11 //SDnRST2
C语言代码:
*(unsigned long*)0xC001C020 &= ~(3 << 24); //BIT[25:24]=00
*(unsigned long*)0xC001C020 |= (1 << 24); //BIT[25:24]=01
注意:操作的BIT位数大于等于2位,先清0,后置1
至此,硬件信息掌控完毕,接下来开始编写LED的裸板程序。