欢迎光临散文网 会员登陆 & 注册

05-对part3-helloworld的搬运和翻译

2023-08-05 12:59 作者:Xdz-2333  | 我要投稿

非黑色字体为我自己添加,原文放在文章末尾

让某些事情发生

        目前位置我们的操作系统只有一个黑色的屏幕.我们如何确保我们的代码确实运行了呢?让我们做一些更有趣的事情来展示出我们对硬件的控制.

        通常,软件开发人员所学的第一件事是在屏幕上打印出"hello world".然而在裸机开发中,在屏幕上打印可能是一个巨大的挑战,所以我么从简单的开始.

对串口(UART)的介绍

        可能从我们的操作系统"发送信息"的最简单的方法就是通过串口或者串行通信电路.UART 是 Universal Asynchronous Receiver/Transmitter (通用异步发射器/接收器)的缩写,它非常古老而且简答,只用两根线就能使两个设备进行通信.在USB到来之前,像鼠标,打印机,调制调节器等设备均使用这种方式进行连接.

        我们将要直接把你的开发设备连接到你的树莓派上,并且利用树莓派发送"hello world"到你的开发设备上,你的开发设备将把它打印到屏幕上.

你将需要:

  1. 一个USB转串口的线

  2. 下载并安装驱动(https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers)

  3. 在开发设备上下载并安装PuTTY (https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html),实际上可以利用Pyserial这个库编写代码来进行串口控制

  4. 如果你使用Mac,我推荐安装串口工具(https://apps.apple.com/gb/app/serialtools/id611021963?mt=12)作为PuTTY作为替代

        如果你想要在我们开始前阅读串口通信,我推荐SparkFun Website   (https://learn.sparkfun.com/tutorials/serial-communication/all)

        关于串口的简单补充:

        串口是一种十分简单的通信协议,在物理层面上只有两根线,一根收,一根发,而数据的组织上,串口由起始位,8bit/9bit数据,校验位(看设置可选),停止位组成,串口是异步的,也就是说连个设备用串口通信时不会传输时钟以进行信号同步(IIC或者SPI等就有时钟,在这些通信设备里面谁掌握时钟谁是主设备(master),剩下的是从设备(slaves)),因为没有时钟,所以双方需要约定一个波特率,比如常见的9600(这个得自己先设置好),串口也没有像TCP一样的纠错机制,数据丢了就丢了,但是它就是简单好使,在低速,干扰不大的数据传输时很常见

把线连上

        如果你安装好了驱动,继续,把方形的USB接口连上你的电脑.几乎没有什么会发生,但是如果你现在打开控制面板,点击设备管理器,并且打开"接口"这一节,你会看见"Prolific"这个选项,这就是说我们的线正常工作了.

        我的设备上是这样的:


作者设备上的接口图像

        记下条目里面的 COMx 的数字,在我的机器上时COM5

        同样的线也能运行在Mac上而无需额外驱动.

        现在我们需要查看树莓派来确认线的另一端如何进行连接.你需要寻找 GPIO 管脚(pin),它们一共有40个,都在树莓派版权声明的上方.

        下面这张图显示了你需要连接的地方.我推荐有突破引线的电线并且有如下的颜色编码:

  • 黑色=地

  • 红=+5V 电源

  • 绿色=发送端(从USB发送到树莓派)

  • 白色=接收端(从USB接收树莓派的发送)

        接地的头(我的例子里面时黑色线)接在树莓派的地上(引脚6),发送的头(白色)在TXD上(GPIO 14/引脚8)和接收的头(绿色),在RXD上(GPIO 15/引脚 10).注意交叉连接RX和TX是必要的,比如把RX连到TX上,反之亦然(串口的线有RX和TX之分,树莓派上的串口引脚也有RX和TX之分,而插串口的线的时候需要把RX的线连接到TX的引脚上,不然传不了数据).当我们使用专用电启动树莓派时,注意不要连接红色的头(多电源供电可能会烧掉芯片)

树莓派引脚说明图

这是我正确连接了线的树莓派:

作者的树莓派的接线

设置PuTTY

  1. 在你的开发设备上运行PuTTY

  2. 在左手边的面板上点击 "Session" 目录

  3. 设置"Connection type"为 Serial

  4. 设置"Serial line to connect to"到我们之前找到的 COMx 的数字,我的时COM5

  5. 设置"Speed (baud)" 为 115200(baud为波特率,它和比特率略有区别,请自行百度相关概念)

  6. 确认数据位为8bit,停止位为1,"Parity"为None,"Flow Contorl"为None

  7. 回到"Session"目录你会发现设置已经更改了

  8. 在"Saved Session"下面的文本框输入名字,比如 "树莓派4" 来保存设置,然后点击保存(小心中文名称导致找不到,建议使用英文名)

  9. 你可以通过双击"树莓派4"来进行连接,如果你这么做了,你因该可以看见一个空的黑窗口

        如果你使用不同的终端模拟器,你需要遵循应用程序供应商关于如何使用软件的执导来设置成上面相同的设定.比如,Mac上的Serial Tools 在这里解释.(https://www.w7ay.net/site/Applications/Serial%20Tools/)

快速改变 config.txt

        你记得吗,回到第一篇教程,我不得不编辑了SD上的 config.txt 文件,使我的树莓派能成功在电视上显示.现在我们需要加一条来确保UART是可靠的.

        UART通信与时序关系较大,两端在数据的收发速度上达成一致是很重要的.当我们设置PuTTY的时候,我们告诉PuTTY以115200的波特率通信,我们需要树莓派以相同的速率通信.事实上,我们不能确定它是否会-它可能以更快或者更慢的速率通信,取决于CPU多繁忙.

        在config.txt里面加上这一行来解决这个问题


core_freq_min=500

让UART在代码里运行

        首先,让我们更新 kernel.c的代码,来加一些新的调用.

#include "io.h"


void main()

{

    uart_init();

    uart_writeText("Hello world!\n");

    while (1);

}

        我们从包括一个新的头文件, io.h 来开始,这将允许我们在 kernel.c文件外写一些代码,当我们需要的时候可以调用它.

        你要注意我们的main()函数也有一些新行,首先我们调用一个函数来实例化UART,然后我们调用另一个函数来写入"Hello world".字符串末尾的奇怪字符 - \n - 是我们在文本末尾加入一个新行的方法,就像在文字处理程序里面输入回车一样!

        现在我们用以下内容创建 io.h

void uart_init();

void uart_writeText(char *buffer);

        这是一个拥有两个函数定义的,非常小的文件.(然而我觉得是函数声明而不是函数定义).函数uart_init() 是一个返回值为 void 的函数,并且没有参数,就像main() 一样.这意味着它不需要来自调用者任何的数据来完成功能.而且它完成时不需要返回任何数据给调用者.你会注意到 uart_writeText 同样是一个返回值为 void 的函数,但是它需要一个参数,因为我们要告诉它写什么.

        我把实现这些的代码放到另一个新文件中, io.c

// GPIO


enum {

    PERIPHERAL_BASE = 0xFE000000,

    GPFSEL0         = PERIPHERAL_BASE + 0x200000,

    GPSET0          = PERIPHERAL_BASE + 0x20001C,

    GPCLR0          = PERIPHERAL_BASE + 0x200028,

    GPPUPPDN0       = PERIPHERAL_BASE + 0x2000E4

};


enum {

    GPIO_MAX_PIN       = 53,

    GPIO_FUNCTION_ALT5 = 2,

};


enum {

    Pull_None = 0,

};


void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; }

unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; }


unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) {

    unsigned int field_mask = (1 << field_size) - 1;

  

    if (pin_number > field_max) return 0;

    if (value > field_mask) return 0; 


    unsigned int num_fields = 32 / field_size;

    unsigned int reg = base + ((pin_number / num_fields) * 4);

    unsigned int shift = (pin_number % num_fields) * field_size;


    unsigned int curval = mmio_read(reg);

    curval &= ~(field_mask << shift);

    curval |= value << shift;

    mmio_write(reg, curval);


    return 1;

}


unsigned int gpio_set     (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); }

unsigned int gpio_clear   (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); }

unsigned int gpio_pull    (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); }

unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); }


void gpio_useAsAlt5(unsigned int pin_number) {

    gpio_pull(pin_number, Pull_None);

    gpio_function(pin_number, GPIO_FUNCTION_ALT5);

}


// UART


enum {

    AUX_BASE        = PERIPHERAL_BASE + 0x215000,

    AUX_ENABLES     = AUX_BASE + 4,

    AUX_MU_IO_REG   = AUX_BASE + 64,

    AUX_MU_IER_REG  = AUX_BASE + 68,

    AUX_MU_IIR_REG  = AUX_BASE + 72,

    AUX_MU_LCR_REG  = AUX_BASE + 76,

    AUX_MU_MCR_REG  = AUX_BASE + 80,

    AUX_MU_LSR_REG  = AUX_BASE + 84,

    AUX_MU_CNTL_REG = AUX_BASE + 96,

    AUX_MU_BAUD_REG = AUX_BASE + 104,

    AUX_UART_CLOCK  = 500000000,

    UART_MAX_QUEUE  = 16 * 1024

};


#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1)


void uart_init() {

    mmio_write(AUX_ENABLES, 1); //enable UART1

    mmio_write(AUX_MU_IER_REG, 0);

    mmio_write(AUX_MU_CNTL_REG, 0);

    mmio_write(AUX_MU_LCR_REG, 3); //8 bits

    mmio_write(AUX_MU_MCR_REG, 0);

    mmio_write(AUX_MU_IER_REG, 0);

    mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts

    mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200));

    gpio_useAsAlt5(14);

    gpio_useAsAlt5(15);

    mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX

}


unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; }


void uart_writeByteBlockingActual(unsigned char ch) {

    while (!uart_isWriteByteReady()); 

    mmio_write(AUX_MU_IO_REG, (unsigned int)ch);

}


void uart_writeText(char *buffer) {

    while (*buffer) {

       if (*buffer == '\n') uart_writeByteBlockingActual('\r');

       uart_writeByteBlockingActual(*buffer++);

    }

}

        你将会看到,在我们 io.h 文件中定义的两个函数(个人依旧认为 io.h 中是函数声明而不是定义) 现在有了一些具体的代码,并且有一些其他的支持的函数.我将要在下一篇教程里解释代码是如何运行的.但是让我们现在掠过,直接行动起来吧.

        有了你的 io.c 和 io.h 文件,并且改变了 kernel.c的代码,运行 make 来构建你的新操作系统.

然后:

  1. 复制你的新的 kernel8.img 到SD卡里,然后把SD卡放回树莓派

  2. 确认你的USB转TTL线正确连接(记住电源线别连,不然别人成功你爆炸)

  3. 运行你的终端模拟器,(比如PuTTY),并且连接到之前设置好的树莓派上 - 你应该看见一个空的黑窗口,并且没有错误.

  4. 启动你的树莓派

        如果您遵循了所有这些说明,几秒钟后,您将在开发机器上的终端仿真器窗口中看到“Hello world!”

        这是来自树莓派的消息,表示操作系统正在工作。终于有证据了!


原文如下


Writing a "bare metal" operating system for Raspberry Pi 4 (Part 3)

Making something happen


So far our OS produces only a black screen. How can we be sure that our code is actually running? Let's do something a bit more interesting to really demonstrate that we have control of the hardware.


Usually, the first thing a software developer learns is to print "Hello world!" to the screen. In bare metal development however, printing to the screen can be quite a big challenge, so we're going to do something simpler to start with.


Introducing the UART


Perhaps the easiest way we can "send a message" from our OS is via the **UART** or serial communications circuit. UART stands for Universal Asynchronous Receiver/Transmitter and it's a very old and fairly simple interface that uses just two wires to communicate between two devices. Before USB came along, devices like mice, printers and modems were connected in this way. 


We're going to connect your dev machine directly to your RPi4 and have the RPi4 send the "Hello world!" message to your dev machine! Your dev machine will print it to the screen.


You will need:


 * A [USB to serial TTL cable](https://www.amazon.co.uk/gp/product/B01N4X3BJB/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1)

 * To [download and install drivers for the cable](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers)

 * To [download and install PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) on your dev machine

 * If you're using a Mac, I'd recommend [installing Serial Tools](https://apps.apple.com/gb/app/serialtools/id611021963?mt=12) as an alternative to PuTTY


If you'd like to read up on serial communcation before we start, I recommend looking at the [SparkFun website](https://learn.sparkfun.com/tutorials/serial-communication/all).


Connecting the cable


If you have the drivers installed, go ahead and connect the cable to a spare USB port on your dev machine. Very little will happen, but if you now open Control Panel, click on Device Manager and open the Ports section, you should see a "Prolific" entry. That tells us that your cable is working correctly.


Here's what my machine looks like:


![Windows Control Panel with cable installed](images/3-helloworld-ctlpanel.png)


Make a note of the COMx number in brackets after the Prolific entry - in my case, that's **COM5**.


The same cable will work on a Mac without the need to install any drivers.


Now we need to look at the RPi4 to identify how to connect the other end of the cable. You'll be looking for the **GPIO pins**, all 40 of them, which are just above the Raspberry Pi copyright notice. 


The diagram below shows where you need to make connections. The cable I recommended has breakout leads that are colour-coded as follows:


 * BLACK = Ground

 * RED = +5v Power

 * GREEN = TX (transmits from USB port to RPi4)

 * WHITE = RX (receives to USB port from RPi4)


The Ground lead (BLACK in my case) hooks over the RPi4's Ground pin (Pin 6), the RX lead (WHITE) over TXD (GPIO 14/Pin 8) and the TX lead (GREEN) over RXD (GPIO 15/Pin 10). Note how it's necessary to cross RX and TX, i.e. connect RX to TX and vice versa. As we are powering the RPi4 using a dedicated power supply, make sure you **don't connect the RED connector**.


![GPIO location](images/3-helloworld-pinloc.png)


Here's my RPi4 with the cable connected correctly:


![GPIO photo with cable connected](images/3-helloworld-cable.jpg)


Setting up PuTTY


 * Run PuTTY on your dev machine

 * Click on the "Session" category in the left-hand pane

 * Set "Connection type" to Serial

 * Click on "Serial" under the "Connection" category in the left-hand pane

 * Set the "Serial line to connect to" to the COMx number we found above, mine was COM5

 * Set the "Speed (baud)" to 115200

 * Ensure "Data bits" is 8, "Stop bits" is 1, "Parity" is None and "Flow control" is None

 * Click back to the "Session" category in the left-hand pane and you should see the changed settings

 * Save these settings by typing a name e.g. "Raspberry Pi 4" in the textbox under "Saved Sessions" and clicking Save

 * You can now start the connection by double-clicking on "Raspberry Pi 4" - if you do, all you will see for now is an empty black window


If you're using a different terminal emulator, you'll need to use the same settings as above following the application vendor's instructions on how to use the software. For example, Serial Tools on Mac is explained [here](https://www.w7ay.net/site/Applications/Serial%20Tools/).


A quick config.txt change


Do you remember that, back in the first tutorial, I had to edit the _config.txt_ file on the SD card to get Raspbian up on my TV screen? Now we need to add a line to ensure that our UART connection will be reliable.


UART communication is a lot to do with timing, and it's important that both ends agree on the exact speed of data being sent/received. When we set up PuTTY, we told it to communicate at 115200 baud, and we'll need the RPi4 to communicate at the same rate. As it is, we can't be sure that it will - it might communicate faster or slower depending on how busy the CPU is.


Add this line to your _config.txt_ to resolve this:


```c

core_freq_min=500

```


Getting the UART going in code


First off, let's update _kernel.c_ to make a few new calls:


```c

#include "io.h"


void main()

{

    uart_init();

    uart_writeText("Hello world!\n");

    while (1);

}

```


We start by including a new **header file**, _io.h_. This allows us to write some new code outside of the _kernel.c_ file, and call it in when we need it.


You'll note that our `main()` routine has also some new lines. First we call a function to initialise the UART, and then we call another function to write "Hello world!" to it. The weird character at the end of the string - `\n` - is how we add a newline to the end of our text, just like pressing Enter in a word processor!


Let's now create _io.h_ with the following contents:


```c

void uart_init();

void uart_writeText(char *buffer);

```


This is a very short file with two **function definitions**. `uart_init()` is a **void function** with no **parameters**, just like `main()` is. This means that it doesn't need any data from the caller to do its job, and it doesn't send any data back to the caller when it's done. You'll note that `uart_writeText` is also a void function, but it does take a parameter since we need to tell it what text to write!


We'll put the actual code for these functions in another new file, _io.c_:


```c

// GPIO


enum {

    PERIPHERAL_BASE = 0xFE000000,

    GPFSEL0         = PERIPHERAL_BASE + 0x200000,

    GPSET0          = PERIPHERAL_BASE + 0x20001C,

    GPCLR0          = PERIPHERAL_BASE + 0x200028,

    GPPUPPDN0       = PERIPHERAL_BASE + 0x2000E4

};


enum {

    GPIO_MAX_PIN       = 53,

    GPIO_FUNCTION_ALT5 = 2,

};


enum {

    Pull_None = 0,

};


void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; }

unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; }


unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) {

    unsigned int field_mask = (1 << field_size) - 1;

  

    if (pin_number > field_max) return 0;

    if (value > field_mask) return 0; 


    unsigned int num_fields = 32 / field_size;

    unsigned int reg = base + ((pin_number / num_fields) * 4);

    unsigned int shift = (pin_number % num_fields) * field_size;


    unsigned int curval = mmio_read(reg);

    curval &= ~(field_mask << shift);

    curval |= value << shift;

    mmio_write(reg, curval);


    return 1;

}


unsigned int gpio_set     (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); }

unsigned int gpio_clear   (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); }

unsigned int gpio_pull    (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); }

unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); }


void gpio_useAsAlt5(unsigned int pin_number) {

    gpio_pull(pin_number, Pull_None);

    gpio_function(pin_number, GPIO_FUNCTION_ALT5);

}


// UART


enum {

    AUX_BASE        = PERIPHERAL_BASE + 0x215000,

    AUX_ENABLES     = AUX_BASE + 4,

    AUX_MU_IO_REG   = AUX_BASE + 64,

    AUX_MU_IER_REG  = AUX_BASE + 68,

    AUX_MU_IIR_REG  = AUX_BASE + 72,

    AUX_MU_LCR_REG  = AUX_BASE + 76,

    AUX_MU_MCR_REG  = AUX_BASE + 80,

    AUX_MU_LSR_REG  = AUX_BASE + 84,

    AUX_MU_CNTL_REG = AUX_BASE + 96,

    AUX_MU_BAUD_REG = AUX_BASE + 104,

    AUX_UART_CLOCK  = 500000000,

    UART_MAX_QUEUE  = 16 * 1024

};


#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1)


void uart_init() {

    mmio_write(AUX_ENABLES, 1); //enable UART1

    mmio_write(AUX_MU_IER_REG, 0);

    mmio_write(AUX_MU_CNTL_REG, 0);

    mmio_write(AUX_MU_LCR_REG, 3); //8 bits

    mmio_write(AUX_MU_MCR_REG, 0);

    mmio_write(AUX_MU_IER_REG, 0);

    mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts

    mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200));

    gpio_useAsAlt5(14);

    gpio_useAsAlt5(15);

    mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX

}


unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; }


void uart_writeByteBlockingActual(unsigned char ch) {

    while (!uart_isWriteByteReady()); 

    mmio_write(AUX_MU_IO_REG, (unsigned int)ch);

}


void uart_writeText(char *buffer) {

    while (*buffer) {

       if (*buffer == '\n') uart_writeByteBlockingActual('\r');

       uart_writeByteBlockingActual(*buffer++);

    }

}

```


You'll see that the two functions we defined in our _io.h_ header file now have some actual code, along with some other supporting functions. I'll explain what's going on in this code in the next tutorial, but let's skip straight to the action now!


With your new _io.c_ and _io.h_ files in place, as well as the changes to _kernel.c_ made, run `make` to build your new OS. 


Then: 


 * Copy the newly built _kernel8.img_ to the SD card, and then put the SD card into your RPi4

 * Make sure your USB to serial TTL cable is connected correctly

 * Run your terminal emulator (e.g. PuTTY) and connect to the "Raspberry Pi 4" session you set up earlier - you should see an empty black screen and no errors

 * Power on your RPi4


If you've followed all these instructions, after a few seconds you'll see "Hello world!" appear in the terminal emulator window on your dev machine.

It's a message from your RPi4 to say that your OS is working. Proof at last!




05-对part3-helloworld的搬运和翻译的评论 (共 条)

分享到微博请遵守国家法律