文章修修补补中,有问题可以私信或者群里告诉我(欢迎来实验室线下真实我)


培训时间(暂定):
每周六下午3点-5点
每周日下午3点-5点
上面两个时间选一个来就可以,每次人数原则上不多于8人,有特殊情况私信我。

培训具体内容:
先讲注意事项或者培训内容,剩下的时间跟着讲义往下做,过程中有疑问可以问我。

Q&A
Q:我想提前完成培训内容,没有鸭蛋怎么办?
A:平时可以来实验室使用,因为板子数量不够,没有办法每个人都借走一块。
Q:鸭蛋在培训时怎么分配?
A:一共4块鸭蛋,根据人数,1-2人共用一块。
Q:我能请假(摸鱼)吗?
A:当然可以,私信我。
Q:你个老东西,讲义都写错了。
A:私信或者群里告诉我。
Q:讲义写的不够完善,怎么补充完善讲义?
A:把你想补充的内容用Word或者Markdown写完发给我。

前言

本讲义内容广泛,但各部分较为简明,旨在帮助你快速掌握嵌入式开发、计算机和数字电路等领域的基础知识。通过下图,你可以直观地了解讲义所涉及的主要内容。

2024-10-19T15:25:51.png
2024-10-19T15:25:51.png

在学习过程中,我们将使用鸭蛋FPGA开发平台,运行基于RISC-V架构的Yadan CPU,模拟单片机环境。你将使用C语言或Arduino编写程序,控制开发板上的LED灯,并最终设计数字电路,实现对LED灯的控制。

在每次集中培训前,你需要提前完成相应的预学习任务。同时,我们鼓励你根据讲义提前完成培训的所有内容。


预学习一

内容

  • 你缺失的那门计算机课——Windows的基本使用
  • 复习 C 语言

预计时间

You tell me.

具体内容

验收标准


鸭蛋开发板开发环境配置

预计时间

1h

具体内容

注意事项

  • 务必使用 TD5.6.2 版本
  • 将 license 文件放到 C:\Anlogic\TD5.6.2\license
  • Arduino IDE 使用 2.3.3 版本
  • 在 Arduino 开发板管理器中下载 Yadan 的开发环境时,会遇到报错,自行 STFW(Search The Fucking Web)
  • RISC-V GCC 工具链使用 v10.1.0-1.1 版本

验收标准


程序是如何从C语言编译成计算机可以理解的指令?

如果你想进一步了解这方面的内容,可以阅读《深入理解计算机系统》

C语言是一种编译型语言,它需要通过编译器将源代码转换为可执行的机器代码。编译过程通常分为以下几个阶段:

  1. 预处理(Preprocessing)

    • 处理所有预处理指令,如#include#define、以及条件编译指令等。
    • 输出是一个经过预处理的C源文件。
  2. 编译(Compilation)

    • 将预处理后的C代码转换为汇编代码。这一步主要是进行语法分析、语义分析和优化。
    • 输出是对应平台的汇编代码文件。
  3. 汇编(Assembly)

    • 将汇编代码转换为目标机器代码(机器指令)。
    • 输出是目标文件(object file),通常是二进制格式的。
  4. 链接(Linking)

    • 将多个目标文件和库文件结合在一起,形成一个可执行文件。
    • 处理外部符号引用,并将函数和变量地址进行必要的调整。

编译器(如GCC、Clang等)通过这几个阶段将C源代码转换为最终可以在目标平台运行的可执行程序。这个过程涉及语法和语义检查、代码优化等多种复杂技术。

在使用gcc时,你可以分步骤进行编译。以下是每个步骤的命令:

  1. 预处理

    gcc -E source_file.c -o preprocessed_file.i
  2. 编译(生成汇编代码)

    gcc -S preprocessed_file.i -o assembly_file.s
  3. 汇编(生成目标文件)

    gcc -c assembly_file.s -o object_file.o
  4. 链接(生成可执行文件)

    gcc object_file.o -o executable_name

通过这些步骤,可以手动控制编译过程中的每个阶段,并检查各阶段的中间输出。

链接

C语言编程的核心能力之一就是链接OS所提供的库。链接是一种为你的程序添加额外特性的方法,这些特性由其它人在系统中创建并打包。
C中的库有两种基本类型:

静态

你可以使用ar和ranlib来构建它,就像上个练习中的libYOUR_LIBRARY.a那样(Windows下后缀为.lib)。这种库可以当做一系列.o对象文件和函数的容器,以及当你构建程序时,可以当做是一个大型的.o文件。

动态

它们通常以.so(Linux)或.dll(Windows)结尾。这些文件都被构建好并且放置到指定的地方。当你运行程序时,OS会动态加载这些文件并且“凭空”链接到你的程序中。

静态库

构建静态库

  1. 编写源文件:创建一个或多个C源文件和相应的头文件。例如,你可以有 foo.cfoo.h

    /* foo.h */
    #ifndef FOO_H
    #define FOO_H
    
    void hello();
    
    #endif
    /* foo.c */
    #include <stdio.h>
    #include "foo.h"
    
    void hello() {
        printf("Hello, World!\n");
    }
  2. 编译源文件为目标文件:使用 gcc 或其他C编译器将源文件编译成目标文件(.o 文件)。

    gcc -c foo.c -o foo.o

    这条命令会生成一个名为 foo.o 的目标文件。

  3. 创建静态库文件:使用 ar 命令创建一个静态库文件(.a 文件)。

    ar rcs libfoo.a foo.o

    这条命令会把 foo.o 打包成一个名为 libfoo.a 的静态库。

链接静态库

  1. 编写使用库的程序:创建一个C程序使用你生成的库。

    /* main.c */
    #include "foo.h"
    
    int main() {
        hello();
        return 0;
    }
  2. 编译并链接程序:编译使用静态库的程序,同时链接静态库。

    gcc main.c -L. -lfoo -o main

    这里的 -L. 指定库路径为当前目录,-lfoo 表示链接名为 libfoo.a 的库(lib.a 是默认前缀和后缀,所以只需指定 foo)。

  3. 运行程序:编译成功后,直接运行生成的可执行文件 main

    ./main

    输出应该是:

    Hello, World!

    创建一个动态库(又称共享库)在 C 语言中涉及以下步骤。我们以 Linux 系统为例,使用 gcc 编译器来创建和使用动态库。

动态库

创建动态库

  1. 编写源文件:与创建静态库类似,你需要编写源文件和头文件。例如,使用 foo.cfoo.h

    /* foo.h */
    #ifndef FOO_H
    #define FOO_H
    
    void hello();
    
    #endif
    /* foo.c */
    #include <stdio.h>
    #include "foo.h"
    
    void hello() {
        printf("Hello, Dynamic World!\n");
    }
  2. 编译源文件为目标文件:编译时使用 -fPIC 选项生成位置无关代码,这对于动态库很重要。

    gcc -c foo.c -fPIC -o foo.o
  3. 创建共享库文件:使用 -shared 选项生成动态库(.so 文件)。

    gcc -shared -o libfoo.so foo.o

    这将创建一个名为 libfoo.so 的共享库。

链接和使用动态库

  1. 编写使用库的程序:创建一个程序来使用共享库。

    /* main.c */
    #include "foo.h"
    
    int main() {
        hello();
        return 0;
    }
  2. 编译并链接程序:编译使用动态库的程序,指定库路径和库名称。

    gcc main.c -L. -lfoo -o main

    命令说明:

    • -L. 指定库路径为当前目录。
    • -lfoo 指定要链接的库为 libfoo.solib.so 是约定前缀和后缀)。
  3. 运行程序:为了让程序找到并加载共享库,你可以:

    • 设置 LD_LIBRARY_PATH 环境变量,或者,
    • 将共享库复制到系统库路径下,例如 /usr/lib/usr/local/lib

    使用 LD_LIBRARY_PATH,可以运行:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
    ./main

    运行后,输出应该是:

    Hello, Dynamic World!

使用鸭蛋的基本姿势

预计时间

1.5h

具体内容

注意事项

  • 使用 Yadan SOC,你需要下载Yadan SOC的工程文件,自行使用TD生成比特流文件并且烧录。
  • 自行 STFW,安装 Python 环境
  • 在 4.3.2 编写代码中,你的代码需要在样例空工程中进行编写
  • 鸭蛋文档中的4.3.5, 4.3.6不需要阅读

验收标准

  • 分别使用 Arduino IDE 和 C 语言从底层点亮 LED 灯

UART串口,PWM,GPIO

UART

通信方式在日常的应用中一般分为串行通信(serial communication)和并行通信(parallel communication)。UART使用的是串行通信。首先我们了解下什么是并行通信,并行通信是指多比特数据同时通过并行线进行传送,一般以字或字节为单位并行进行传输。这种传输方式用的通信线多、成本高。
我们再来了解下串行通信的特点。串行通信是指数据在一条数据线上,一比特接一比特地按顺序传送的方式,这一点与并行通信是不同的。这里我们以传输一个字节(8位)数据为例,在并行通信中,一个字节的数据是在 8 条并行传输线上同时由源地传送到目的地;而在串行通信中,因为数据是在一条传输线上一位接一位地顺序传送的,所以一个字节的数据要分8次进行传送。如果我们以T为一个时间单位的话,那么并行通信发送一个字节的数据只需要1T的时间,而串行通信需要8T的时间,由此可以总结出串行通信
的的特点:一是节省传输线,大大降低了使用成本,二是数据传送速度慢,这一点在大位宽的数据传输上尤为明显。综上可知,串行通信主要应用于长距离、低速率的通信场合。

接口定义

UART的接线很简单,只需要连接两根线就可以发送数据

2024-10-26T10:21:45.png
2024-10-26T10:21:45.png

数据格式

起始位:当不传输数据时,UART数据传输线通常保持高电压电平。若要开始数据传输,发送UART会将传输线从高电平拉到低电平并保持1个波特率周期。当接收UART检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。
数据帧:数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是5位到8位。如果不使用奇偶校验位,数据帧长度可以是9位。在大多数情况下,数据以最低有效位优先方式发送。
奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。接收 UART读取数据帧后,将计数值为1的位,检查总数是偶数还是奇数。如果奇偶校验位为0(偶数奇偶校验),则数据帧中的1或逻辑高位总计应为偶数。如果奇偶校验位为1(奇数奇偶校验),则数据帧中的1或逻辑高位总计应为奇数。当奇偶校验位与数据匹配时,UART认为传输未出错。但是,如果奇偶校验位为0,而总和为奇数,或者奇
偶校验位为1,而总和为偶数,则UART认为数据帧中的位已改变。
停止位:为了表示数据包结束,发送UART将数据传输线从低电压驱动到高电压并保持1到2位时间。

2024-10-26T10:24:40.png
2024-10-26T10:24:40.png

奇偶检验

你可以阅读我的文章,详细了解如何通过奇偶检验来纠正数据传输中的错误。

波特率

即每秒传输的位数(bit)。一般选波特率都会有9600,19200,115200等选项。其实就是每秒传输这么多个比特位数(bit)。两个设备需要约定好相同的波特率才能进行通讯。

GPIO

GPIO(General Purpose Input/Output,通用输入/输出)是微控制器和其他集成电路上的一种通用引脚,用于实现数字信号的输入和输出。

功能

输入模式:用于读取外部设备的数字信号。例如,按钮状态。
输出模式:用于向外部设备发送数字信号。例如,控制LED亮灭。

例子

在Arduino中,使用pinMode()函数配置引脚为输入或输出;使用digitalWrite()和digitalRead()分别操作输出和读取输入。

PWM

用于模拟信号控制的数字信号技术。通过调节脉冲信号的占空比,可以模拟输出连续的模拟电压。
下图是一个50%占空比的PWM波,假设他的高电压的时候是5V,那么50%占空比就可以等效成5V*50%=2.5V。

2024-10-26T11:56:08.png
2024-10-26T11:56:08.png

通过PWM,我们可以简单的调整LED灯的亮度,电机的转速等。


使用 Arduino 花式点亮 LED 灯

你将会用到 UART、PWM、GPIO 的知识。

预计时间

4h

具体内容

注意事项

  • 你可能需要使用到GPIO引脚,查看鸭蛋文档1.1获取相关信息

验收标准

  • YADAN Board 能通过 UART 串口接收 PC 发送来的参数以调整呼吸灯的效果,如调整亮度。
  • 开发板可反馈当前呼吸灯参数给 PC。

最简单的CPU是怎样工作的?

阅读南京大学计算机系统基础实验1.2,2.1


使用 C 语言点亮 LED 灯

你需要学习配置定时器寄存器并点亮 LED 灯。

预计时间

1h

具体内容

验收标准

  • 自己配置寄存器并使 LED 灯闪烁

函数库设计

自己为定时器编写库函数,功能自定。比如写一个:

void Timer_cmp_set(); //设置指定定时器的compare值
void Timer_PRE_set(); //设置指定定时器的预分频值
void Timer_en_set(); // 启用指定定时器
void Timer_value_set(); // 设置指定定时器的值
unsigned int Timer_value_read(); //读取指定定时器的值

预计时间

1h

验收标准

  • 看你代码以及运行效果

提醒:你已经学习了嵌入式的部分内容,现在可以尝试使用 51 单片机或鸭蛋制作一个小作品。


进阶一(可选)

内容

自由设计

预计时间

You tell me.

具体内容

自由设计你的作品,比如智能(障)小车、温湿度计、指纹门锁。以下为可选参考:

  • 温湿度显示系统

    使用 DHT11 温湿度传感器采集温湿度信息,通过 UART 串口发送到 PC 机显示。
    DHT11 是一款单总线通信的温湿度传感器,仅需一个上拉的 GPIO 端口就可以实现数据传输。完成此设计需要了解 DHT11 的通信方式,并编写相应库函数,实现对 DHT11 的驱动。
  • 宿舍智能门锁

    使用外接蓝牙模块或者指纹模块,控制舵机转动从而拉动宿舍门锁实现开门。(真的能开门!)
    蓝牙模块或指纹模块均使用UART串口与开发板通讯,舵机通过开发板的PWM信号控制转动的角度。

验收标准


预学习二

内容

学习数电与 Verilog

预计时间

You tell me.

具体内容

验收标准

  • USTC OJ 完成题目至 ID60(根据自己情况适当跳过前面题目)

FPGA 点亮 LED 灯

预计时间

4h

具体内容

验收标准

  • 使用 FPGA 成功点亮 LED 灯

FPGA 使用 PWM 点亮 LED 灯

预计时间

4h

具体内容

验收标准

  • FPGA 使用 PWM 成功点亮 LED 灯

FPGA 使用旋钮调节灯的亮度

预计时间

4h

具体内容

验收标准

  • FPGA 使用 PWM 成功点亮 LED 灯

FPGA 使用 UART 串口实现与电脑通讯

预计时间

4h

具体内容

提醒:你已经学会了如何使用 FPGA 开发,现在可以尝试参加集创赛的数字设计赛道以及 FPGA 应用赛道。

验收标准

  • 从电脑接收或发送数据,并点亮 LED 灯

进阶二(可选)

内容

一生一芯

预计时间

具体内容


进阶二的小帮助

如何使用 sing-box 加速 GitHub 的访问

没法说没法说