Pi历险记...第4部分(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/pi/adventures-with-the-pi-part-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 16 分钟阅读 - 7634 个词 阅读量 0Pi历险记…第4部分(译文)
原文地址:https://www.codeproject.com/Articles/1173585/Adventures-with-the-Pi-part
原文作者:leon de boer
译文由本站 robot-v1.0 翻译
前言
*Introducing SmartStart something like a BIOS for the Pi *
为Pi引入SmartStart之类的东西,例如BIOS
提供的示例代码(Provided Sample Code)
- 编译的SD卡IMG对Pi1,Pi2和Pi3有效(位于SD卡上)(Compiled SD CARD IMG valid for Pi1, Pi2 and Pi3 (place on SD card))
- 源代码和所有支持文件(Source code and all support files) GITHUB现在可用(GITHUB now available) https://github.com/LdB-ECM/Raspberry-Pi(https://github.com/LdB-ECM/Raspberry-Pi)
介绍(Introduction)
不幸的是,由于所提供代码的范围,本文将相当长.我目前使用Pi的方法是开发自己的64位O/S,但在此过程中,我需要一个加载程序,该加载程序的代码外观和行为与普通计算机上的BIOS非常相似.它的最简单描述是一段代码,可以初始化Pi并计算出Pi模型,内存,外围设备等,然后在启动O/S时将这些信息传递给O/S.最后,由于代码本身替换并执行了自己的功能,因此实际上甚至可能被O/S从内存中删除.(This article unfortunately will be quite long because of the scope of the code presented. My current push with the Pi is developing my own 64 bit O/S but as part of that process, I need a loader, which is a piece of code that looks and behaves a lot like a BIOS on a normal computer. The simplest description of it is a piece of code that can initialize the Pi and work out the Pi model, memory, peripherals, etc. and then pass that information onto the O/S as it boots the O/S. Finally, the code itself may actually even be removed from memory by the O/S as it replaces and enforces its own functions.) 加载程序代码必须涵盖各种功能,从安装驱动器和分区,读取文件系统以及诸如屏幕显示之类的平凡事物.我的加载器在此阶段仍未完成,但是代码处于某个点,可能可以用作其他用途的起点.这是我正在发布的当前版本的屏幕.(The loader code must cover a diverse range of functions from mounting drives and partitions, reading file systems along with the more mundane things such as screen display. My loader is still at this stage incomplete but the code is at a point it might be useful as a start point for other uses. This is the screen of the current version I am releasing.)
背景(Background)
与我以前的文章一样,代码被组织为使用与您正在使用的Pi模型匹配的批处理文件之一进行编译((As with my previous articles, the code is organized to compile with one of the batch files matching the Pi model you are using ()11(pi1),(,)pi2(pi2),(,)pi3.bat(pi3.bat)),并提供一个简单的命令行链接,指向我在其中安装了Windows arm cross编译器的目录() and provides a simple command line link to the directory I installed the windows arm cross compiler in (for me)G:\ Pi \ gcc_pi_6_2(G:\Pi\gcc_pi_6_2)).我使用可从ARM官方网站获得的标准ARM交叉编译器(). I use the standard ARM cross compiler available from the official ARM site) 这里(here) .(.) 因此,我们需要深入了解SmartStart的工作原理.对于那些不熟悉汇编代码的人,您可能需要做一些基础阅读才能完全理解下一部分.为什么汇编程序而不是C有两个部分的答案.首先,这有点像BIOS.它必须是最低的Pi版本代码(ARM6),因为它必须在每个Pi板上运行.您可以设置一个make文件,以在ARM6中创建某些C文件,然后在ARM7/8中创建其他文件,但是该过程非常混乱.第二个因素是精确调整C语言中的代码块的大小.这甚至更加混乱且不可靠,在某种程度上,您永远无法确定代码中包含的所有内容都已包含在内.对于更高的优化,尤其如此,因为甚至可以选择小的C代码块内联到大的C代码块中.这就是为什么我选择汇编程序而不是C的基础.(So we need to get into the details of how SmartStart works. For those not familiar with assembler coding, you may need to do some basic reading to fully understand the next parts. Why assembler and not C has a two part answer from me. First, this is something like a BIOS. It needs to be the lowest Pi version code (ARM6) as it must run on every Pi board. You could setup a make file to make certain C files in ARM6 and then other in ARM7/8 but that process is just plain messy. The second factor is precisely sizing the code blocks in C. This is even more messy and unreliable and to some extent, you can never be certain that everything contained in the code is included. This is especially true with higher optimizations as small C code blocks can even be selected to be inlined into large C blocks. So that is the basis of why I choose assembler over C.) Smartstart首先通过执行一系列自动检测功能来存储该信息,然后将其传递到常规C主块中.然后,来自C的代码可以访问smartstart提供的API函数(始终以RPI_xxxxxxxx开头),并且由于自动检测阶段而完全了解底层Pi模型的结构.他们使用这些持有的信息,然后在任何模型Pi上无缝执行功能,而无需用户干预.(Smartstart starts by doing a series of autodetection functions storing that information and then passing into the normal C main block. Code from C can then access API functions provided by smartstart (which always start with RPI_xxxxxxxx) and which are fully aware of the underlying Pi model structure because of the autodetection phase. They use this held information to then execute the function seamlessly on any model Pi without the user needing to intervene.) Smartstart基本上是汇编代码块,其核心是围绕一个具有7个关键点的相当简单的系统构建的:(Smartstart is basically blocks of assembler code which at its core is built around a fairly simple system that has 7 key points:)
- 将每个汇编器功能源放在一个汇编器文件中.(Put each assembler function source in one assembler file.)
- 将每个函数作为普通部分的自己的部分((Include every function as its own section of the normal sections ().文本(.txt),(,)**.数据(.data)**等)在您的组装过程中(, etc.) in your assemble process)
- 为库函数原型和任何其他文件创建一个C头文件(Create a single C header file for the library function prototypes and any)
typedef
s(s) - 上面的头文件被共享并包含在汇编器中,因此它们永不不同或不会出错(The above header file is shared and included to the assembler so they can never differ or go into error)
- 用标志编译(Compile with the flags)
-ffunction-sections
-Wl
,(,)-gc-sections
(垃圾收集并扔掉未使用的部分)((garbage collect and toss unused sections)) - 将您的资源发布给毫无戒心的客户/公众.(Release your source to the unsuspecting client/public.)
- 他们什么都不需要,仅在使用块时才合并它们.是使用它还是丢失它进行编译.(They need nothing else, blocks are incorporated only if they are used. It is use it or lose it compiling.) 一个典型的节块如下所示:(A typical section block looks like this:)
/* "PROVIDE C FUNCTION: uint64_t RPI_GetArmTickCount (void);" */
.section .text.RPI_GetArmTickCount, "ax", %progbits
.align 2
.globl RPI_GetArmTickCount;
.type RPI_GetArmTickCount, %function
.syntax unified
.arm
;@"================================================================"
;@ RPI_GetArmTickCount -- Composite Pi1, Pi2 & Pi3 code
;@ C Function: uint64_t RPI_GetArmTickCount (void)
;@ Return: R0, R1 will have tickcount value
;@"================================================================"
RPI_GetArmTickCount:
stmfd sp!, {r4, lr}
ldr r3, =RPi_IO_Base_Addr
ldr r3, [r3]
add r3, r3, #0x3000
.HiTimerMoved2:
ldr r2, [r3, #8] ;@ Read timer hi count
ldr r0, [r3, #4] ;@ Read timer lo count
ldr r1, [r3, #8] ;@ Re-Read timer hi count
cmp r2, r1
bne .HiTimerMoved2 ;@ Check both hi count reads were same
ldmfd sp!, {r4, pc}
.align 2
.ltorg ;@ Tell assembler ltorg data for this code can go here
.size RPI_GetArmTickCount, .-RPI_GetArmTickCount
该代码编译为普通的自己的小节(The code compiles to its own subsection of the normal)**.文本(.text)段(小节(segment (subsection) .RPI_GetArmTickCount
),并且我们甚至都附带了节的大小来辅助链接器,如果以后我希望将代码块丢出内存或移动它,我也知道它的大小.() and we even carry the section size both to aide the linker and later if I wish to throw the code block out of memory or move it, I know its size.)
的(The).ltorg(.ltorg)**告诉汇编器在哪里可以放置来自伪操作码指令(例如"(tells the assembler where it can put hard code literals that come from the pseudo opcode instructions like “) ldr r3, =RPi_IO_Base_Addr
“.真正要做的是在(”. What that will really do is put a literal word into the) ltorg
位置”(position “) .word RPi_IO_Base_Addr
这些负载量必须与该块恰好在该位置处链接在一起,它们构成代码的一部分,并因此成为块大小的一部分.(” and the load will be from that literal. Those literal values must be linked with the block at exactly that position and they form part of the code and thus part of the block size.)
封装该函数的C标头如下所示:(The C header to encapsulate that function looks like:)
/*-GetArmTickCount-----------------------------------------------------------
Same as GetTickCount but at full 1Mhz Rapberry Pi system timer resolution.
The timer read is as per the Broadcom specification of two 32bit reads
(http://embedded-xinu.readthedocs.io/en/latest/arm/rpi/BCM2835-System-Timer.html)
24Jan17 LdB
--------------------------------------------------------------------------*/
extern uint64_t RPI_GetArmTickCount(void) __attribute__((pcs("aapcs")));
“(The “) aapcs
“属性是当前用于ARM的标准调用约定,并且在将来还会出现另一个标准时提供保护.该标准仅涵盖在寄存器进入和退出C函数以及我们的汇编程序假设时在寄存器中携带和携带的内容”(” attribute is the current standard calling convention for ARM and just protection should another standard crop up in the future. That standard simply covers what is carried in and on registers as they go into and out of C functions and as our assembler is assuming “) aapcs
“标准,我刚刚向编译器说明过.您可以随时阅读”(” standard, I have just clarified that to the compiler. You can always read up on “) aacps
“(如果您对此感兴趣).(” if you have interest in that.)
现在,如果上面的函数在编译程序的任何地方使用,该代码块将被添加到输出文件中,如果没有,代码将被我们的编译器标志指令抛弃((Now if our function above is used anywhere when we compile our program, the code block will be added to the output file and if not, it will be tossed away by our compiler flag instructions () -ffunction-sections -Wl
,(,) -gc-sections
).().)
因此,让我们采用绝对最少的代码-类似于以下代码:(So let’s take an absolute minimal code - something like this one:)
#include "rpi-smartstart.h" // Needed for smart start API
int main (void) {
while (1){
RPI_Activity_Led(1); // Turn LED on
RPI_WaitMicroSeconds(500000); // 0.5 sec delay
RPI_Activity_Led(0); // Turn Led off
RPI_WaitMicroSeconds(500000); // 0.5 sec delay
}
return(0);
}
是活动指示灯每隔半秒钟亮一次,然后熄灭并重复一次的好旧闪光. SmartStart知道针对每个型号的Pi以及计时器的活动范围在哪里,并且代码可以精巧地压缩为每个型号约9K的最小IMG文件.链接程序将忽略程序中未引用的所有代码块(例如,上面的块).如果您有兴趣尝试一个最小的示例,请参见以下代码.(It is the good old flash the activity LED on a half second interval on, then off and repeats. SmartStart knows where the activity led is for each model of Pi as well as the timer and the code neatly compacts down to a fairly minimal IMG file of about 9K on each model. All the code blocks that are not referenced in the program (the block above for example) will be ignored by the linker. If you are interested in trying a minimal example, here is the code.)
- Smartstart最小示例(Smartstart minimal example)
这些代码块确实可以在使用时发挥作用,或者失去它的设置模式.您不需要对汇编器文件中的代码量惊慌,只需将您使用的部分包括在IMG文件中即可.(The code blocks really do function in a use it or lose it setup mode. You do not need to get alarmed about the amount of code in the assembler file, only sections that you use will be included in your IMG file.)
更高级的用户可能会希望查看链接器文件”(The more advanced users may care to look at the linker file “)**复制文件(rpi.ld)**并查看堆栈,数据和文本段的布局.其中有一些更高级的技巧,例如,我有两个数据段-一个是4字节对齐的,另一个是16字节对齐的,以尽量减少数据间隙许多邮箱数据结构要求16字节对齐,并将它们保持在16字节对齐块中,这有助于数据的精简压缩.(” and look at the layout of the stacks, data and text segments. There are some more advanced tricks in there, for example, I have two data segments - one which is 4 byte aligned and one 16 byte aligned to try to minimize data gaps. A lot of mailbox data structures are required to be 16 byte aligned and by keeping them together in a 16 byte alignment block, it aids neat compacting of the data.)
另一个有趣的事情是(Another interesting thing is “)rpi-smartstart.h(rpi-smartstart.h)“包含在汇编程序和C编译器中,因为两者都需要其中的一些定义才能确保它们匹配.您需要记住(” is included by both the assembler and the C compiler because some of the definitions in it are required by both to ensure they match. You need to remember section between)
#ifdefs
可以用于汇编程序,c编译器或两者.(can be for the assembler, the c compiler or both.) 最终警告是有关ARM7和ARM 8编译的对齐问题.许多固定结构(例如FAT32甚至BMP文件中使用的结构)都未对齐4字节边界.甚至是简单的代码(The final warning is about alignment issues with the ARM7 and ARM 8 compilations. Many fixed structures such as that used in FAT32 and even BMP files are not aligned to 4 byte boundaries. Even simple code like)memcpy
等等将导致处理器抛出未对齐的停止状态.您会在代码参考中的许多地方看到未对齐的访问,并且需要注意不要忽略它. ARM网站上对此主题提供了支持,例如,(, etc. will cause a processor to throw an unaligned halt. You will see at a number of points in the code references to unaligned access and you need to take care not to ignore it. There is support on the subject on the ARM website, for example, on)memcpy
这里(here) .我不喜欢转移注意力的建议(. I do not like the suggestion to divert)memcpy
回到本质上是ARM6版本(back to what is essentially the ARM6 version of)memcpy
并在任何地方都能面对速度损失,因为我可以轻松识别出一些具体情况.但是,如果确实给您带来麻烦,则可以选择.(and face the speed penalty everywhere for what is a few specific cases I can easily identify. However, if it does become troublesome to you, then it may be an option.) 就我个人的工作流程而言,我甚至在Pi2,Pi3上都可以编译和使用ARM6(pi1编译)中的所有内容. Smartstart自动检测功能可以解决两个模型之间的所有差异,并且我的ARM6代码可以完美运行,并且在Pi2,Pi3上没有对齐问题.最后,当我按需运行所有内容时,我转到ARM7或ARM8编译,然后查看哪些中断始终是对齐问题,然后可以轻松解决这些中断.(Personally my workflow goes, I compile and work with everything in ARM6 (pi1 compilation) even on the Pi2, Pi3. The smartstart autodetect takes care of all the differences between the two models and my ARM6 code works flawlessly and without alignment issues on the Pi2, Pi3. Finally, when I have everything running as I desired, I change to ARM7 or ARM8 compilation and then look at what breaks which will always just be alignment issues which can then be easily run down.)
使用代码(Using the Code)
smartstart API内嵌了许多功能,就像所有常规功能一样,如计时器和GPIO以及图形和SD卡访问.我鼓励你打开(The smartstart API has many functions embedded in it like all the normal ones like Timer and GPIO access right through to graphics and SD Card access. I encourage you to open the)**rpi-SmartStart.h(rpi-SmartStart.h)**文件并查看此处记录的各种功能.我试图找时间在smartstart API上创建详细的文档,但就目前而言,最新的描述位于头文件本身中.(file and look at the various functions which are documented there. I am trying to find time to create a detailed documentation on the smartstart API, but for now, the most up to date descriptions are on in the header file itself.) 好的,在介绍了基本背景之后,我将需要讨论提供的示例.该示例提供了不完整的文件系统((Okay, having covered the basic background, I will need to discuss the provided sample. The example provides an incomplete file system ()**文件系统(filesys.c)**和(and)文件系统(filesys.h)),用于测试和调试.此处的关键字不完整-它可以完成某些事情,而不能完成很多其他事情.进一步警告-文件系统基于Windows API,我只是对Linux不够熟悉,无法在其上使用示例.() which I am using to test and debug on. The keyword here is incomplete - it does some things and doesn’t do many others. Further warning - the filesystem is based around the Windows API, I simply am not familiar enough with linux to work an example on it.) 磁盘驱动器上具有一个或多个特定格式的分区,例如FAT16,FAT16,NTFS,EXT3.提供的文件系统文件当前知道FAT16和FAT32,因为Pi上是SD卡的格式,因此在我们的示例中显然是选择.(A disk drive has on it one or more partitions in a particular format such as FAT16, FAT16, NTFS, EXT3. The provided filesystem file currently know FAT16 and FAT32 and as that is what the format of the SD card is on the Pi, it is the obvious choice to use on our sample.) 因此,要使用驱动器,必须先将其安装在您想知道该驱动器可能包含A到Z的所选驱动器号上.挂载代码采用这种形式,很容易将示例挂载SD卡分区0视为”(So to use a drive, one must first mount it on a selected drive letter you want to know that drive by which can be A to Z inclusive. The mount code takes this form and it’s pretty easy to see the sample mounts SD card partition 0 as “)**C(C)**请注意,该代码当前仅安装分区0,我还没有处理多分区驱动器,但是提供了在不久的将来进行扩展的接口.(” drive. Note the code currently only mounts partition 0, I have not yet dealt with multi partition drives but have provided the interface to do that expansion in the near future.)
// Mount the SD card onto a drive letter ... 'C' in this example
if (mountSDCard('C', 0) == SD_OK) {
当我们的裸机文件启动时,我们可以保证SD卡具有FAT16或FAT32分区,因此应该挂载它.安装后,您就可以访问smartstart提供的一系列文件功能.如果您对FAT16/32系统的工作方式感兴趣,那么我不建议您这样做.(As our baremetal file is starting, we are pretty guaranteed that the SD card has a FAT16 or FAT32 partition so it should mount. Once it mounts, you then have access to a range of file functions provided by smartstart. If you are interested in how the FAT16/32 systems work, I cannot go past recommending the) 维基百科页面(wikipedia page) .(.)
根据所有Windows文件操作,它们在称为文件句柄的对象上工作.我们的文件系统遵循该方案,并且我们有两种类型的文件句柄,分别称为搜索句柄和访问句柄.(As per all Windows file operations, they work on a thing called a file handle. Our file system follows that scheme and we have two types of file handles called search handles and access handles.)
该示例的第一部分使用函数中的搜索手柄列出了驱动器上的文件(The first part of the example lists the files on the drive by using a search handle in the functions) FindFirst
,循环播放(, looping on) FindNext
当有效,然后(while valid and then) FindClose
清理手柄.现在,您需要了解许多限制,因为我尚未完成完整的Windows API实现.所有这些限制均在功能代码中明确注释,并在此处进行详细介绍.(to cleanup the handle. Now you need to be aware of a number of restrictions because I have not completed full Windows API implementation. All these restrictions are clearly commented in the function code and refer to there for more detail.)
FindFirst
没有"当前驱动器"的概念,必须提供驱动器号给有效的已安装驱动器(has no concept of “current drive” you must provide a drive letter to a valid mounted drive)FindFirst
没有子目录支持,但只会做根目录(TBD项目)(has no subdirectory support yet it will do only root directory (TBD item))- 搜索句柄占用大约600bytes,它们限制为4 in(Search handles take up around 600bytes they are limited to 4 in)文件系统(filesys.c)(您可以更改代码)((you can change look at code))
- 从3开始,完成搜索后,请使用(From 3, when you have finished with a search, use)
FindClose
释放手柄,以便可以再次使用(to free up that handle so it can be used again) 假设(Assuming)FindFirst
成功返回,然后我们致电(returns successfully, we then call)findnext
返回有效的搜索句柄.它将与下一个文件条目一起返回,或者在不存在更多文件时最终失败.该示例显示了每个有效文件从文件搜索返回的一些详细信息(日期/时间/大小等).然后执行(with the valid search handle returned. It will either return with the next file entry or finally fail when no more files exist. The sample displays some details returned from the file search (date/time/size, etc.) for each valid file. I then execute)FindClose
释放搜索句柄以重复使用.一切都非常简单,我使用标准的C时间函数进行显示.(to release the search handle for re-use. It’s all pretty straight forward and I use the standard C time functions for display.) 您可以在此处显示的代码中看到标记的3个简单步骤:(You can see the 3 simple steps marked in the code shown here:)
// Step 1: Execute a FindFirst
FHANDLE handle = FindFirstFile("C:\\*.*", &search_data);
if (handle & SHE_ERROR_PRESENT) printf("Findfirst failed with Error ID: %8lx\n", handle);
// Step 2: Loop on FindNextFile until it errors
while ((handle & SHE_ERROR_PRESENT) == 0) {
char buffer[26]; // Buffer to format time into
strftime(buffer, sizeof(buffer),
"%d%b%Y %H:%M:%S", &search_data.CreateDT); // Use c function to format date/time
printf("Entry: %s Size: %8lu bytes Created: %s, LFN: %s\n",
search_data.cAlternateFileName, search_data.nFileSizeLow,
&buffer[0], search_data.cFileName); // Display each entry
if (FindNextFile(handle, &search_data) == false) break; // Find next file entry
}
// Step3: Free the search handle
FindClose(handle);
演示的第二部分具有基本的文件读取访问功能.提供的写入功能不完整,因为它没有将FAT条目文件大小设置为TDB练习.在执行加载程序时,我对读取功能(而不是写入功能)更加感兴趣.我只在撰写本文时才记得该缺点,并将在未来几天内尝试对其进行更新.(The second part of the demonstration does basic file read access functions. The write function provided is incomplete as it does not set the FAT entry filesize correctly as a TDB exercise. As I am doing a loader, I have been far more interested in the read functions, not the write functions. I only remembered that shortcoming while writing this article and will attempt to update that in the coming days.) 因此,我们的读取文件测试基本上将两个位图文件显示在屏幕上.首先创建一个有效的文件访问句柄,但同样有一些限制:(So our read file testing basically displays the two bitmap files to screen. It begins by creating a valid file access handle and again we have restrictions:)
CreateFile
没有"当前驱动器"的概念-您必须提供驱动器号给有效的已安装驱动器(has no concept of “current drive” - you must provide a drive letter to a valid mounted drive)CreateFile
不支持子目录,但仅支持根目录(TBD项目)(has no subdirectory support, yet it will do only root directory (TBD item))- 文件访问句柄占用大约640bytes-限制为4 in(File access handles take up around 640bytes - they are limited to 4 in)文件系统(filesys.c)
- 从3开始后,致电(From 3, when you have finished, call)
FileClose
释放手柄,以便可以再次使用(to free up that handle so it can be used again) 位图显示中使用的其他两个功能(The other two functions used in the bitmap display)ReadFile
和(and)SetFilePosition
获取返回的有效文件句柄并对其执行适当的功能.如果要更改位图文件,如果要使用我提供的粗略显示代码,它们必须是24位或32位BMP文件.对于使用低色深BMP文件(例如16色或256色),我没有做任何准备.(take that valid file handle returned and do the appropriate functions with it. If you wish to change the bitmap files, they must be 24 or 32 bit BMP files if you wish to use the rough display code I provided. I have made no provision for the use of low colour depth BMP files with things like 16 or 256 colour.)
兴趣点(Points of Interest)
希望这是许多smartstart版本发行的开始,这些版本将添加越来越多的功能.我目前正在安装USB驱动器,该驱动器可与Pi1,Pi2一起使用,但不能与Pi3一起使用,希望能发布下一个版本.(This is hopefully the beginning of a number of smartstart version releases which will add more and more functionality. I am currently working with mounting USB drives which is working with Pi1, Pi2 but not Pi3 and that will hopefully make the next release.) 如果有人有公开发布的C代码来读取linux ext2/3/4分区,我很乐意在下面的评论链接中或通过我的联系方式通过电子邮件收到您的来信.(If anyone has public release C code to read linux ext2/3/4 partitions, I would love to hear from you in the comments link below or via my email on my contact details.)
历史(History)
- 1.01第一版(1.01 First release)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
ASM assembler C Raspberry 新闻 翻译