diff --git a/babel.config.js b/babel.config.js index e00595d..bfd75db 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], + presets: [require.resolve("@docusaurus/core/lib/babel/preset")], }; diff --git a/docs/electronics/arduino/chapter1.md b/docs/electronics/arduino/chapter1.md index 4d46648..bc3e4b5 100644 --- a/docs/electronics/arduino/chapter1.md +++ b/docs/electronics/arduino/chapter1.md @@ -10,27 +10,27 @@ Arduino 至今拥有非常多的开发板,其中最为流行的就是 8 位的 AVR 开发板: -| Arduino Uno | Arduino Nano | Arduino Mega | -| :----------------------------: | :-----------------------------: | :------------------------------ | -| ![Arduino Uno](images/1-1.png) | ![Arduino Nano](images/1-2.png) | ![Arduino Mega](images/1-3.png) | +| Arduino Uno | Arduino Nano | Arduino Mega | +| :------------------------------: | :-------------------------------: | :-------------------------------- | +| ![Arduino Uno](./images/1-1.png) | ![Arduino Nano](./images/1-2.png) | ![Arduino Mega](./images/1-3.png) | Arduino Nano 33 系列开发板: -| Arduino Nano 33 IOT | Arduino Nano 33 BLE | Arduino Nano 33 BLE Sense | -| :------------------------------------: | :------------------------------------: | :------------------------------------------- | -| ![Arduino Nano 33 IOT](images/1-4.png) | ![Arduino Nano 33 BLE](images/1-5.png) | ![Arduino Nano 33 BLE Sense](images/1-6.png) | +| Arduino Nano 33 IOT | Arduino Nano 33 BLE | Arduino Nano 33 BLE Sense | +| :--------------------------------------: | :--------------------------------------: | :--------------------------------------------- | +| ![Arduino Nano 33 IOT](./images/1-4.png) | ![Arduino Nano 33 BLE](./images/1-5.png) | ![Arduino Nano 33 BLE Sense](./images/1-6.png) | Arduino 还有一些工业级的开发板: -| Arduino MKR Vidor 4000 | Arduino MKR NB 1500 | Arduino Portenta H7 | -| :----------------------------------------: | :-------------------------------------: | :-------------------------------------: | -| ![Arduino MKR Vidor 4000](images/1-11.png) | ![Arduino MKR NB 1500](images/1-12.png) | ![Arduino Portenta H7](images/1-13.png) | +| Arduino MKR Vidor 4000 | Arduino MKR NB 1500 | Arduino Portenta H7 | +| :------------------------------------------: | :---------------------------------------: | :---------------------------------------: | +| ![Arduino MKR Vidor 4000](./images/1-11.png) | ![Arduino MKR NB 1500](./images/1-12.png) | ![Arduino Portenta H7](./images/1-13.png) | 以及一批不再生产的退休开发板: -| Arduino Lilypad | Arduino ESPLORA | Arduino YUN Shiled | -| :--------------------------------: | :--------------------------------: | :------------------------------------: | -| ![Arduino Lilypad](images/1-7.png) | ![Arduino ESPLORA](images/1-8.png) | ![Arduino YUN Shield](images/1-14.png) | +| Arduino Lilypad | Arduino ESPLORA | Arduino YUN Shiled | +| :----------------------------------: | :----------------------------------: | :--------------------------------------: | +| ![Arduino Lilypad](./images/1-7.png) | ![Arduino ESPLORA](./images/1-8.png) | ![Arduino YUN Shield](./images/1-14.png) | ## 2. Arduino Uno @@ -38,7 +38,7 @@ Arduino Uno 可以说是 Arduino 开发板家族中最为流行的一款,可 下面是 Arduino Uno 的引脚图: -![Arduino引脚图](images/1-9.png) +![Arduino引脚图](./images/1-9.png) 可以发现 Arduino Uno 有以下几个引脚分布: @@ -74,7 +74,7 @@ Arduino IDE 是一个开源的软件,它使编写代码和上传到开发板 下面是 Arduino IDE 的基本信息,关于该 IDE 的更多使用可以访问官方教程:[Arduino IDE Guide](https://www.arduino.cc/en/Guide) -![Arduino IDE](images/1-10.png) +![Arduino IDE](./images/1-10.png) ### Arduino 2.x diff --git a/docs/electronics/arduino/chapter2.md b/docs/electronics/arduino/chapter2.md index 2aedc6c..bf24148 100644 --- a/docs/electronics/arduino/chapter2.md +++ b/docs/electronics/arduino/chapter2.md @@ -86,4 +86,4 @@ ms 参数:参数就是延时时间,单位是毫秒,数据类型为`unsigne 整个程序的流程示意图如下: -![Blink](images/2-1.png) +![Blink](./images/2-1.png) diff --git a/docs/electronics/arduino/chapter3.md b/docs/electronics/arduino/chapter3.md index 08d054d..d69c1a2 100644 --- a/docs/electronics/arduino/chapter3.md +++ b/docs/electronics/arduino/chapter3.md @@ -10,7 +10,7 @@ title: 使用按钮 下面是一个按钮接线图和使用按钮的一个程序,大家可以先看一下。 -![按钮接线图](images/3-1.png) +![按钮接线图](./images/3-1.png) ```cpp int button = 2; @@ -52,7 +52,7 @@ digitalRead(pin); 所以在 loop 循环中,我们通过`bool buttonStatus = digitalRead(button)`来读取按钮的状态。当按钮按下时,5V 的电压会通过按钮输入到 2 号引脚,单片机读取引脚状态,存储到`buttonStatus`中。如果是高电平,就点亮板载 LED,低电平就熄灭 LED。 -![按钮和LED](images/3-2.png) +![按钮和LED](./images/3-2.png) ## 3. 按钮进阶 @@ -138,23 +138,23 @@ Arduino 同时还有另一个记录时间的函数`micros()`,它的单位是 u 下面是按钮抖动的示意图: -![按钮抖动](images/3-3.png) +![按钮抖动](./images/3-3.png) 那我们如何解决这个问题呢,比较常用的方法就是加入**上拉电阻**或者**下拉电阻**帮助消抖。也就是让按钮在没有被完全按下的时候**保持高电平**或者**低电平**。 下面是我们的按钮加入下拉电阻的接线图: -![下拉电阻](images/3-4.png) +![下拉电阻](./images/3-4.png) 原理图如下: -![下拉电阻原理图](images/3-5.png) +![下拉电阻原理图](./images/3-5.png) 由于下拉电阻的存在,当按钮还在波动的期间,IO 口都会被下拉电阻拉低,直到完全按下时,下拉电阻被短路,IO 口输入高电平。 同理有上拉电阻: -![上拉电阻](images/3-6.png) +![上拉电阻](./images/3-6.png) 在大部分应用中,上拉电阻的使用更多一些。同时,现在的单片机大多都有内部上拉电阻,有的也有内部下拉电阻,这就是`pinMode()`mode 参数的第三个可选值`INPUT_PULLUP`。 diff --git a/docs/electronics/arduino/chapter4.md b/docs/electronics/arduino/chapter4.md index 882e8b2..52b3f78 100644 --- a/docs/electronics/arduino/chapter4.md +++ b/docs/electronics/arduino/chapter4.md @@ -10,7 +10,7 @@ title: 中断Interrupt 下面是一个中断示意图: -![中断示意图](images/4-6.png) +![中断示意图](./images/4-6.png) 在单片机中有**硬件中断**和**软件中断**: diff --git a/docs/electronics/arduino/chapter6.md b/docs/electronics/arduino/chapter6.md index f9ac745..bceea1d 100644 --- a/docs/electronics/arduino/chapter6.md +++ b/docs/electronics/arduino/chapter6.md @@ -21,7 +21,7 @@ Arduino 数字引脚只能输出 5V(当变为高电平时)或 0V(当变为 下面是 PWM 原理的示意图: -![PWM](images/6-1.png) +![PWM](./images/6-1.png) ## 2. 使用 PWM diff --git a/docs/electronics/arduino/chapter8.md b/docs/electronics/arduino/chapter8.md index 34f24c7..20c77cd 100644 --- a/docs/electronics/arduino/chapter8.md +++ b/docs/electronics/arduino/chapter8.md @@ -137,4 +137,4 @@ void loop() { 有关 Arduino 串口通信的其他函数可以参考以下内容:[Arduino Serial Function Lists](https://www.arduino.cc/reference/en/language/functions/communication/serial/) -这里只对 Arduino 的串口通信进行简单介绍,有关 UART 的通信机制及更多内容,请参考后面的章节([通讯专题/UART](./chapter1.md)) +这里只对 Arduino 的串口通信进行简单介绍,有关 UART 的通信机制及更多内容,请参考后面的章节([通讯专题/UART](../communication/serial/uart/intro.md)) diff --git a/docs/electronics/arduino/chapter9.md b/docs/electronics/arduino/chapter9.md index 5209b99..45e34b2 100644 --- a/docs/electronics/arduino/chapter9.md +++ b/docs/electronics/arduino/chapter9.md @@ -8,7 +8,7 @@ title: 单片机的存储器和EEPROM 一般单片机的存储空间可以用下图表示: -![MCU Memory](images/9-2.png) +![MCU Memory](./images/9-2.png) 单片机中**SRAM(Static Random-Access Memory)**,主要是用于存储程序运行过程中的变量,因为 RAM 拥有读写速度快的优点,但是 RAM 的数据在掉电后会丢失。 @@ -22,7 +22,7 @@ EEPROM 的主要特点如下: 单片机中的 EEPROM 的由来和发展非常有意思,可以用下面的图表示: -![EEPROM由来](images/9-1.png) +![EEPROM由来](./images/9-1.png) Flash 和 EEPROM 在某些方面比较相似。因为 Flash 就是由 EEPROM 发展而来,支持大擦除块,一般是**512 字节甚至更高**,EEPROM 不仅擦除块小,且每次擦除都需要大概 3.3ms 左右,因此,Flash 更适合用来做为程序储存空间。Flash 和 EEPROM 一样,掉电后数据不会丢失,属于**Non-Volatile Memory**,因此在一些没有 EEPROM 的单片机上,可以使用 Flash 代替 EEPROM,比如 ESP32。但是 Flash 的擦写周期要比 EEPROM 短,通常在**10,000**次。 diff --git a/docs/electronics/arduino/intro.md b/docs/electronics/arduino/intro.md index 260ceec..2eb4233 100644 --- a/docs/electronics/arduino/intro.md +++ b/docs/electronics/arduino/intro.md @@ -21,4 +21,4 @@ Arduino 这个名字来自意大利伊夫雷亚的一家**酒吧**,该项目 在这里我们也只对 Arduino 这个硬件和软件平台作简单的介绍,嵌入式学习肯定不是学习 Arduino,只是 arduino 很适合入门,不需要配置环境,使用非常之简便,很适合教学,也很适合做项目。 -同时本专题内容进队 Arduino 的基础硬件做简单介绍,有关 Arduino 硬件的通信内容可以学习后面的章节([通信专题](./chapter1.md)) +同时本专题内容进队 Arduino 的基础硬件做简单介绍,有关 Arduino 硬件的通信内容可以学习后面的章节([通信专题](../communication/intro.md)) diff --git a/docs/electronics/c-lang/chapter1.md b/docs/electronics/c-lang/chapter1.md new file mode 100644 index 0000000..7ec9fff --- /dev/null +++ b/docs/electronics/c-lang/chapter1.md @@ -0,0 +1,100 @@ +--- +title: C语言基本语法 +--- + +# C 语言基本语法 + +## 1. 语句 + +C 语言的基本单位是语句,每条语句必须以`;`结束。它表明一个逻辑实体的结束。 + +多条语句在某些情况下,可以用`,`隔开合并成一条语句。 + +```c +#include + +int main() { + int input, ouput; + printf("Input your age:"); + scanf("%d", &input); + ouput = input; + printf("Your age is:%d", ouput); + return 0; +} +``` + +## 2. 注释 + +注释是用来给代码添加解释,让自己或者其他人方便阅读或者方便自己今后修改代码,注释会被编译器忽略,对代码的运行没有影响。 + +C 语言注释有两种: + +```c +//单行注释 +//单行注释 + +/* +多行注释 +多行注释 +多行注释 +*/ +``` + +## 3. 关键字 + +关键字是 C 语言预定义的,对编译器有特殊意义的保留字,这些关键字是不能被用来当作变量名或者函数名等。 + +| 关键字 | 作用 | +| :----: | :----------------------------: | +| int | 声明整形变量或函数 | +| float | 声明浮点型变量或函数返回值类型 | +| long | 声明长整型变量或函数返回值类型 | +| void | 声明函数无返回值或无参数 | +| if | 条件判断语句 | +| else | 条件分支否定语句 | +| for | 一种循环语句 | +| while | 一种循环语句 | +| return | 子程序返回语句 | +| define | 定义一个标识符来表示一个常量 | + +## 4. 函数 + +函数需要返回值,需要函数名,需要参数,C 语言的 main 函数就是一个标准的函数。 + +```c +#include + +int main(void) { + printf("Hello world!"); + return 0; +} +``` + +多条语句可以用函数进行封装,封装可以简化代码,方便调用。 + +```c +#include + +void printHelloWorld() { + printf("Hello world!"); +} + +int main() { + printHelloWorld(); + return 0; +} +``` + +## 5. 预处理(#) + +预处理的特点: + +- **为了区分一般的语句,预处理命令行都必须以#开始,结尾不加分号** +- **预处理命令可以放在程序中的任意位置** +- **在程序中凡是以#开始的语句都是预处理命令行** + +预处理的作用: + +- **宏定义:#define** +- **文件包含:#include ** +- **条件编译:#ifdef #define #endif** diff --git a/docs/electronics/c-lang/chapter10.md b/docs/electronics/c-lang/chapter10.md new file mode 100644 index 0000000..03d075f --- /dev/null +++ b/docs/electronics/c-lang/chapter10.md @@ -0,0 +1,160 @@ +--- +title: 数组 +--- + +# 数组 + +## 1. 什么是数组 + +C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。 + +所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。 + +![数组内存](./images/10-1.png) + +数组中的特定元素可以通过索引访问,第一个索引值为 0。 + +![数组索引值](./images/10-2.png) + +## 2. 声明数组 + +在 C 中要声明一个数组,需要指定元素的**类型**和元素的**数量**,如下所示: + +```c +type arrayName [ arraySize ]; +``` + +这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型。例如,要声明一个类型为 double 的包含 10 个元素的数组 balance,声明语句如下: + +```c +double balance[10]; +``` + +## 3. 初始化数组 + +在 C 中,您可以逐个初始化数组,也可以使用一个初始化语句,如下所示: + +```c +double balance[5] = {10.0, 20.6, 3.4, 7.2}; +``` + +大括号 { } 之间的值的数目**不能大于**在数组声明时在方括号 [ ] 中指定的元素数目,未赋值的元素自动赋值为 0。 + +![数组声明](./images/10-3.png) + +如果省略了数组的大小,数组的大小则为初始化时元素的个数。 + +```c +double balance[] = {10.0, 20.6, 3.4, 7.2}; +``` + +![数组声明](./images/10-4.png) + +## 4. 访问数组元素 + +数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。 + +例如: + +```c +double balance[5] = {10.0, 20.6, 3.4, 7.2}; +blance[4] = 50.6; +``` + +下面是一个将输入的 5 个值存储到数组中,显示输出并求和的一个程序: + +```c +#include + +int main() { + int num[5], sum = 0; + for (int i = 0; i < 5; i++) { + printf("Input a number:"); + // 将输入的元素存入数组当中 + scanf("%d", &num[i]); + } + printf("These numbers are ."); + for (int i = 0; i < 5; i++) { + // 求出所有元素之和 + sum += num[i]; + printf("%d ", num[i]); + } + printf("\nThe sum of them is %d\n.", sum); + return 0; +} +``` + +## 5. 冒泡排序 + +冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法,是学习编程语言必不可少的以部分,它是循环和数组的结合,很适合初学者学习。 + +冒泡排序重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从 Z 到 A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。 + +这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。 + +下面是一个可供参考的冒泡排序代码: + +```c +#include + +#define LENGTH 10 + +int main() { + int temp; + int num[LENGTH] = {25, 20, 12, 36, 89, 65, 35, 45, 67, 34}; + // 外层循环为排序的趟数,次数为数组长度-1 + for (int i = 0; i < LENGTH - 1; i++) { + //内层循环为比较相邻元素的次数,次数为需要比较元素个数-1 + for (int j = 0; j < LENGTH - i - 1; j++) { + // 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) + if (num[j] > num[j + 1]) { + temp = num[j]; + num[j] = num[j + 1]; + num[j + 1] = temp; + } + } + } + // 输出结果 + for (int i = 0; i < LENGTH; i++) { + printf("%d ", num[i]); + } + return 0; +} +``` + +## 6. 选择排序 + +选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。 + +下面是一个可供参考的选择排序代码: + +```c +#include + +#define LENGTH 10 + +int main() { + int temp; + int num[LENGTH] = {25, 20, 12, 36, 89, 65, 35, 45, 67, 34}; + // 外层循环为排序的趟数,次数为数组长度-1 + for (int i = 0; i < LENGTH - 1; i++) { + //内层循环为找出元素中最小的元素,次数为需要比较元素个数-1 + int minPos = i; // 先假设每次循环时,最小数的索引为i + for (int j = i + 1; j < LENGTH; j++) { + // 每一个元素都和剩下的未排序的元素比较 + if (num[minPos] > num[j]) { + minPos = j; + } + } + //经过一轮循环,就可以找出第一个最小值的索引,然后把最小值放到i的位置 + temp = num[minPos]; + num[minPos] = num[i]; + num[i] = temp; + } + // 输出结果 + for (int i = 0; i < LENGTH; i++) { + printf("%d ", num[i]); + } + return 0; +} +``` diff --git a/docs/electronics/c-lang/chapter11.md b/docs/electronics/c-lang/chapter11.md new file mode 100644 index 0000000..31a5147 --- /dev/null +++ b/docs/electronics/c-lang/chapter11.md @@ -0,0 +1,95 @@ +--- +title: 多维数组 +--- + +# 多维数组 + +## 1. 多维数组 + +C 语言支持多维数组。多维数组声明的一般形式如下: + +```c +type name[size1][size2]...[sizeN]; +``` + +例如,下面的声明创建了一个三维 5 . 10 . 4 整型数组: + +```c +int threedim[5][10][4]; +``` + +## 2. 二维数组 + +多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组,形式如下: + +```c +type arrayName [ x ][ y ]; +``` + +其中,type 可以是任意有效的 C 数据类型,arrayName 是一个有效的 C 标识符。一个二维数组可以被认为是一个带有 x 行和 y 列的表格。下面是一个二维数组,包含 3 行和 4 列: + +```c +int x[3][4]; +``` + +![](./images/11-1.png) + +## 3. 初始化二维数组 + +多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组: + +```c +int a[3][4] = { + {0, 1, 2, 3}, /* 初始化索引号为 0 的行 */ + {4, 5, 6, 7}, /* 初始化索引号为 1 的行 */ + {8, 9, 10, 11} /* 初始化索引号为 2 的行 */ +}; +``` + +内部嵌套的括号是可选的,下面的初始化与上面是等同的: + +```c +int a[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; +``` + +## 4. 访问二维数组 + +二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如: + +```c +int val = a[2][3]; +``` + +上面的语句将获取数组中第 3 行第 4 个元素。您可以通过上面的示意图来进行验证。 + +让我们来看看下面的程序,我们将使用嵌套循环来处理二维数组: + +```c +#include + +int main() { + /* 一个带有 5 行 2 列的数组 */ + int a[5][2] = {{0, 0}, {1, 2}, {2, 4}, {3, 6}, {4, 8}}; + + /* 输出数组中每个元素的值 */ + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 2; j++) { + printf("a[%d][%d] = %d ", i, j, a[i][j]); + } + putchar('\n'); + } + return 0; +} +``` + +输出的结果如下: + +```plaintext +a[0][0] = 0 a[0][1] = 0 +a[1][0] = 1 a[1][1] = 2 +a[2][0] = 2 a[2][1] = 4 +a[3][0] = 3 a[3][1] = 6 +a[4][0] = 4 a[4][1] = 8 +``` + +在 C 语言中我们可以创建任意长的数组,但是在一般情况下,我们创建的数组都是一维数组和二维数组。 diff --git a/docs/electronics/c-lang/chapter12.md b/docs/electronics/c-lang/chapter12.md new file mode 100644 index 0000000..41d1669 --- /dev/null +++ b/docs/electronics/c-lang/chapter12.md @@ -0,0 +1,46 @@ +--- +title: 字符串 +--- + +# 字符串 + +## 1. 字符串 + +在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。 + +空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符,\0 是转义字符,意思是告诉编译器,这不是字符 0,而是空字符。 + +下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符 \0,所以字符数组的大小比单词 RUNOOB 的字符数多一个: + +```c +char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'}; +``` + +上面的初始化也可以写成如下格式: + +```c +char site[] = "RUNOOB"; +``` + +以下是 C/C++ 中定义的字符串的内存表示: + +![](./images/12-1.png) + +下面是使用字符串输出从终端输入的内容的程序: + +```c +#include + +int main() { + char string[50]; + puts("Input a string without space:"); + scanf("%s", string); + puts(string); + return 0; +} +``` + +## 2. 小测试 + +- **输出一段字符串,将其中字母全部大写。** +- **输入一段任意长度的字符串,通过排序将其按照 A-Z,a-z 重新排序。** diff --git a/docs/electronics/c-lang/chapter13.md b/docs/electronics/c-lang/chapter13.md new file mode 100644 index 0000000..4a5de09 --- /dev/null +++ b/docs/electronics/c-lang/chapter13.md @@ -0,0 +1,148 @@ +--- +title: 指针 +--- + +# 指针 + +## 1. 指针 + +学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。 + +每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。 + +下面是一个输出定义的变量的地址的程序: + +```c +#include + +int main() { + int virable = 0; + int* pointer = &virable; // 定义指针变量 + printf("The address of virable is %p.\n", pointer); + return 0; +} +``` + +运行的结果如下: + +```plaintext +The address of virable is 000000000061FE14. +``` + +## 2. 什么是指针 + +指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为: + +```c +type *var-name; +``` + +在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的`*`与乘法中使用的`*`是相同的。但是,在这个语句中,`*`是用来指定一个变量是指针。 + +以下是有效的指针声明: + +```c +int* ip; /* 一个整型的指针 */ +double* dp; /* 一个 double 型的指针 */ +float* fp; /* 一个浮点型的指针 */ +char* cp; /* 一个字符型的指针 */ +``` + +所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。 + +不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。 + +## 3. 如何使用指针 + +使用指针时会频繁进行以下几个操作: + +定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 \* 来返回位于操作数所指定地址的变量的值。 + +下面是一个实例: + +```c +#include + +int main() { + int var = 20; /* 实际变量的声明 */ + int* ip; /* 指针变量的声明 */ + + ip = &var; /* 在指针变量中存储 var 的地址 */ + + /* 使用指针访问值 */ + printf("The value of var is %d.\n", var); + + /* 使用指针访问值 */ + printf("The value of *ip is %d.\n", *ip); + + printf("The address of var is %p.\n", &var); + + /* 在指针变量中存储的地址 */ + printf("The address of ip is %p.\n", ip); + + return 0; +} +``` + +输出的结果如下: + +```plaintext +The value of var is 20. +The value of *ip is 20. +The address of var is 000000000061FE14. +The address of ip is 000000000061FE14. +``` + +下面是使用指针来交换两个数的程序,这也是指针很常用的一个例子: + +```c +#include + +void swap(int* num1, int* num2) { + int temp; + temp = *num1; + *num1 = *num2; + *num2 = temp; +} + +int main() { + int a = 4, b = 6; + printf("a=%d, b=%d.\n", a, b); + swap(&a, &b); + printf("a=%d, b=%d.\n", a, b); + return 0; +} +``` + +输出的结果如下: + +```plaintext +a=4, b=6. +a=6, b=4. +``` + +在这个例子中,如果我们不使用指针,是达不到交换两个数的目的的。当然,使用指针还有更多方便的好处,在后面的章节中会陆陆续续介绍。 + +## 4. C 中的 NULL 指针 + +在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。 + +NULL 指针是一个定义在标准库中的值为零的常量。 + +请看下面的程序: + +```c +#include + +int main() { + int* ptr = NULL; + printf("The address of ptr is %p\n", ptr); + return 0; +} +``` + +输出的结果如下: + +```plaintext +The address of ptr is 0000000000000000 +``` diff --git a/docs/electronics/c-lang/chapter14.md b/docs/electronics/c-lang/chapter14.md new file mode 100644 index 0000000..bb945c3 --- /dev/null +++ b/docs/electronics/c-lang/chapter14.md @@ -0,0 +1,112 @@ +--- +title: 指针的算数运算 +--- + +# 指针的算数运算 + +## 1. 指针的算数运算 + +C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。 + +假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算: + +```c +long num = 10; +long* ptr = # +ptr++; +``` + +在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。 + +我们概括一下: + +指针的每一次递增,它其实会指向下一个元素的存储单元。 +指针的每一次递减,它都会指向前一个元素的存储单元。 +指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 long 就是 4 个字节。 + +## 2. 递增(递减)一个指针 + +我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。 + +下面的程序通过递增指针变量,以便按顺序访问数组中的每一个元素: + +```c +#include + +#define LENGTH 3 + +int main() { + int num[] = {10, 100, 200}; + int* ptr; + /* 指针中的数组地址 */ + ptr = num; + for (int i = 0; i < LENGTH; i++) { + printf("num[%d] = %d ", i, *ptr); + printf("num[%d] = %p\n", i, ptr); + /* 指向下一个位置 */ + ptr++; + } + return 0; +} +``` + +输出的结果如下: + +```plaintext +num[0] = 10 num[0] = 000000000061FE08 +num[1] = 100 num[1] = 000000000061FE0C +num[2] = 200 num[2] = 000000000061FE10 +``` + +同理我们可以递减一个指针,即把值减去其数据类型的字节数,如下所示: + +```c +#include + +#define LENGTH 3 + +int main() { + int num[] = {10, 100, 200}; + int* ptr; + /* 指针中的数组地址 */ + ptr = &num[2]; + for (int i = LENGTH - 1; i >= 0; i--) { + printf("num[%d] = %d ", i, *ptr); + printf("num[%d] = %p\n", i, ptr); + /* 指向上一个位置 */ + ptr--; + } + return 0; +} +``` + +输出的结果如下: + +```plaintext +num[2] = 200 num[2] = 000000000061FE10 +num[1] = 100 num[1] = 000000000061FE0C +num[0] = 10 num[0] = 000000000061FE08 +``` + +下面是一个通过指针自增求数组所有元素之和的程序: + +```c +#include + +#define LENGTH 10 + +int main() { + int num[LENGTH] = {25, 20, 12, 36, 89, 65, 35, 45, 67, 34}; + int *ptr, sum; + /* 指针中的数组地址 */ + ptr = num; + printf("The arrey is ["); + for (int i = 0; i < LENGTH; i++) { + printf("%d ", *ptr); + sum += *ptr; + ptr++; + } + printf("]\nThe sum of arrey is %d.\n", sum); + return 0; +} +``` diff --git a/docs/electronics/c-lang/chapter15.md b/docs/electronics/c-lang/chapter15.md new file mode 100644 index 0000000..007d1d0 --- /dev/null +++ b/docs/electronics/c-lang/chapter15.md @@ -0,0 +1,159 @@ +--- +title: 指针和数组、函数 +--- + +# 指针和数组、函数 + +## 1. 数组中的指针 + +在前面通过指针自增求数组和例子中,我们通过`ptr = num`来为指针 ptr 初始化,而在自减的例子中,我们是用`ptr = &num[2]`。 + +这是因为,在数组中,数组名相当于一个指针,指向整个数组第一个元素的指针,是一个常量指针。因此我们在自增的例子中也可以通过`ptr = &num[0]`来为指针 ptr 初始化,只是前一种方式更加简约。 + +![](./images/15-1.png) + +观察下面的例子: + +```c +#include + +#define LENGTH 10 + +int main() { + int num[LENGTH] = {25, 20, 12, 36, 89, 65, 35, 45, 67, 34}; + int* ptr = num; + // 输出结果 + for (int i = 0; i < LENGTH; i++) { + printf("%d ", num[i]); + } + putchar('\n'); + for (int i = 0; i < LENGTH; i++) { + printf("%d ", ptr[i]); + } + return 0; +} +``` + +其输出的结果是一样的: + +```plaintext +25 20 12 36 89 65 35 45 67 34 +25 20 12 36 89 65 35 45 67 34 +``` + +可见,我们通过 **ptr[i]** 和通过 **num[i]** 来对数组进行访问是一样的,也就是说,我们对数组的操作其实就是对指针的操作,**指针同样支持下标进行访问**,这点在下面将数组作为参数传递给函数的例子中体现得更为明显。 + +# 2. 将数组传递给函数 + +如果你想要在函数中传递一个一维数组作为参数,你可以通过以下三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,你也可以传递一个多维数组作为形式参数。 + +第一种,形式参数是一个已定义大小的数组: + +```c +void myFunction(int param[10]) +{ + // Your code. +} +``` + +第二种,形式参数是一个未定义大小的数组: + +```c +void myFunction(int param[]) +{ + // Your code. +} +``` + +第三种,形式参数是一个指针: + +```c +void myFunction(int *param) +{ + // Your code. +} +``` + +其实不论哪一种,我们将实参传递的时候,都是将数组的首地址传递过去,所以从理论上来讲,传递数组就是传递一个指向数组首地址的指针变量,通过地址直接访问数组罢了。 + +下面我们将前面的冒泡排序,通过传递数组的,包装成函数的方式重新编写,参考代码如下: + +```c +#include + +#define LENGTH 10 + +void bubbleSort(int num[], int length) { + int temp; + // 外层循环为排序的趟数,次数为数组长度-1 + for (int i = 0; i < length - 1; i++) { + //内层循环为比较相邻元素的次数,次数为需要比较元素个数-1 + for (int j = 0; j < length - i - 1; j++) { + // 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) + if (num[j] > num[j + 1]) { + temp = num[j]; + num[j] = num[j + 1]; + num[j + 1] = temp; + } + } + } +} + +int main() { + int num[LENGTH] = {25, 20, 12, 36, 89, 65, 35, 45, 67, 34}; + bubbleSort(num, LENGTH); + // 输出结果 + for (int i = 0; i < LENGTH; i++) { + printf("%d ", num[i]); + } + return 0; +} +``` + +在这个例子中,`bubbleSort(int num[], int length)`也可以改成`bubbleSort(int* num, int length)`。 + +可以发现,如果不通过指针的方式,我们就无法通过包装函数的方式来对数组进行操作。 + +同理,我们可以将某一个数组当作返回值进行返回,返回的依然是一个指向数组第一个元素的指针变量,同时,由于 C 语言不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量,更多细节这里不做介绍,大家日常也用的少,有兴趣的可以自行学习。 + +# 3. 指向函数的指针 + +通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。 + +函数指针可以像一般函数一样,用于调用函数、传递参数。 + +函数指针变量的声明: + +```c +int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型 +``` + +下面是一个具有函数指针的实例: + +```c +#include + +int MAX(int x, int y) { + return x > y ? x : y; +} + +int main(void) { + /* p 是函数指针 */ + int (*p)(int, int) = &MAX; // &可以省略 + int a, b, c, max; + + printf("Input three numbers:"); + scanf("%d %d %d", &a, &b, &c); + + /* 与直接调用函数等价,max = MAX(MAX(a, b), c) */ + max = p(p(a, b), c); + + printf("The biggest one is: %d\n", max); + + return 0; +} +``` + +同理我们可以将函数当作参数进行传递,这称之为`回调函数`,通过回调函数,我们让用户传入自定义的函数,这在中断中使用比较多,日常中使用的比较少,这里也不做介绍了,感兴趣的同学可以自行查阅相关资料进行学习。 + +可见,指针在 C 语言中的功能真的非常多,有了指针可以实现很多更高级的功能,这里只对指针做简单介绍,更多内容可自行学习。 diff --git a/docs/electronics/c-lang/chapter16.md b/docs/electronics/c-lang/chapter16.md new file mode 100644 index 0000000..823490b --- /dev/null +++ b/docs/electronics/c-lang/chapter16.md @@ -0,0 +1,87 @@ +--- +title: 枚举(enum) +--- + +# 枚举(enum) + +## 1. 枚举 + +枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。 + +枚举语法定义格式为: + +```plaintext +enum 枚举名 {枚举元素1,枚举元素2,……}; +``` + +接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名: + +```c +#define MON 1 +#define TUE 2 +#define WED 3 +#define THU 4 +#define FRI 5 +#define SAT 6 +#define SUN 7 +``` + +这个看起来代码量就比较多,接下来我们看看使用枚举的方式: + +```c +enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN }; +``` + +注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。 + +可以在定义枚举类型时改变枚举元素的值: + +```c +enum season {spring, summer=3, autumn, winter}; +``` + +没有指定值的枚举元素,其值为前一元素加 1。 + +也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5。 + +## 2. 枚举变量的定义 + +前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。 + +我们可以通过以下三种方式来定义枚举变量: + +1、先定义枚举类型,再定义枚举变量: + +```c +enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN }; +enum DAY day; +``` + +2、定义枚举类型的同时定义枚举变量: + +```c +enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN } day; +``` + +3、省略枚举名称,直接定义枚举变量: + +```c +enum { MON = 1, TUE, WED, THU, FRI, SAT, SUN } day; +``` + +下面是一个使用枚举的实例: + +```c +#include + +enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN } day; + +int main() { + // 遍历枚举元素 + for (day = MON; day <= SUN; day++) { + printf("The element is %d.\n", day); + } +} +``` + +这里仅对枚举作简单介绍,更多内容可自行学习。 diff --git a/docs/electronics/c-lang/chapter17.md b/docs/electronics/c-lang/chapter17.md new file mode 100644 index 0000000..b4c88bb --- /dev/null +++ b/docs/electronics/c-lang/chapter17.md @@ -0,0 +1,149 @@ +--- +title: 结构体(struct) +--- + +# 结构体(struct) + +## 1. 结构体 + +**结构**是 C 编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。 + +假如我们要定义一个有关日期格式的数据类型,可能会用到以下几个数据: + +- **year** +- **month** +- **date** +- **hour** +- **minute** +- **second** + +那么我们该如何定义这样一个自定义的数据类型呢。 + +## 2. 定义结构体 + +为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下: + +```c +struct tag +{ + member-list + member-list + member-list + ... +} variable-list; +``` + +tag 是结构体标签。 + +member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。 + +variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。 + +下面是声明 Time 结构的方式: + +```c +struct Time +{ + int year; + int month; + int date; + int hour; + int minute; + int second; +} now; +``` + +在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。 + +下面是三个定义结构的实例: + +```c +//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c +//同时又声明了结构体变量s1 +//这个结构体并没有标明其标签 +struct +{ + int a; + char b; + double c; +} s1; +``` + +```c +//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c +//结构体的标签被命名为SIMPLE,没有声明变量 +struct SIMPLE +{ + int a; + char b; + double c; +}; +//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3 +struct SIMPLE t1, t2[20], *t3; +``` + +```c +//也可以用typedef创建新类型 +typedef struct +{ + int a; + char b; + double c; +} Simple2; +//现在可以用Simple2作为类型声明新的结构体变量 +Simple2 u1, u2[20], *u3; +``` + +## 3. 结构体变量的初始化 + +和其它类型变量一样,对结构体变量可以在定义时指定初始值。 + +下面是一个初始化结构体的实例: + +```c +#include + +struct Time { + int year; + int month; + int date; + int hour; + int minute; + int second; +} now = {2022, 2, 5, 12, 30, 0}; + +int main() { + printf("%d-%d-%d %d:%d:%d\n", now.year, now.month, now.date, now.hour, + now.minute, now.second); + return 0; +} +``` + +## 4. 结构体作为函数参数 + +下面是一个结构体作为函数参数的实例: + +```c +#include + +struct Time { + int year; + int month; + int date; + int hour; + int minute; + int second; +} now = {2022, 2, 5, 12, 30, 0}; + +void getTime(struct Time time) { + printf("%d-%d-%d %d:%d:%d\n", time.year, time.month, time.date, time.hour, + time.minute, time.second); +} + +int main() { + getTime(now); + return 0; +} +``` + +总之,结构体作为一种用户自定义的数据类型同其他数据类型一样,具有同等的功能,可以作为参数,可以作为返回值,可以使用指针,可以创建数组等等,这里仅作简单介绍,更多内容可自行学习。 diff --git a/docs/electronics/c-lang/chapter18.md b/docs/electronics/c-lang/chapter18.md new file mode 100644 index 0000000..d3ba0ab --- /dev/null +++ b/docs/electronics/c-lang/chapter18.md @@ -0,0 +1,64 @@ +--- +title: 共用体(union) +--- + +# 共用体(union) + +共用体是一种特殊的数据类型,允许**在相同的内存位置存储不同的数据类型**。我们可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。 + +我们可以使用 union 定义共用体,定义方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。 + +union 定义方式如下: + +```c +union [union tag] +{ + member definition; + member definition; + ... + member definition; +} [one or more union variables]; +``` + +union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。 + +下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str: + +```c +union Data +{ + int i; + float f; + char str[20]; +} data; +``` + +现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。 + +共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,**字符串所占用的空间是最大的**。 + +下面的是一个显示共用体占用的总内存大小的实例: + +```c +#include + +union Data { + int i; + float f; + char str[20]; +}; + +int main() { + union Data data; + printf("Memory size occupied by data is %d.\n", sizeof(data)); + return 0; +} +``` + +输出的结果为: + +```plaintext +Memory size occupied by data is 20. +``` + +这里对共用体仅作简单介绍,更多内容可自行学习。 diff --git a/docs/electronics/c-lang/chapter2.md b/docs/electronics/c-lang/chapter2.md new file mode 100644 index 0000000..96aaa18 --- /dev/null +++ b/docs/electronics/c-lang/chapter2.md @@ -0,0 +1,55 @@ +--- +title: 数据类型 +--- + +# 数据类型 + +## 1. 什么是数据类型 + +C 语言的数据类型指的是某个数据元素在编译器中所占的存储空间,我们要用不同的数据类型来表示不同的数据,数据类型就是定义数据的大小。 + +数据类型主要分为**整型**,**浮点型**,**其他**。 + +常用的数据类型有: +int,unsigned int,short,unsigned short,long,unsigned long,char,unsigned char,flaot,double,long double,void,其他。 + +## 2. 整数类型 + +| 数据类型 | 字节数 | 大小 | +| :------------: | :----: | :----------------------: | +| char | 1 | -127-128 或 0-255 | +| unsigned char | 1 | 0-255 | +| int | 2 | -32768-32767 | +| unsigned int | 2 | 0-65535 | +| short | 2 | -32768-32767 | +| unsigned short | 2 | 0-65535 | +| long | 4 | -2147483648 ~ 2147483647 | +| unsigned long | 4 | 0-4294967295 | + +## 3. 浮点类型 + +| 数据类型 | 字节数 | 精度 | +| :---------: | :----: | :-------: | +| float | 4 | 6 位有效 | +| double | 8 | 15 位有效 | +| long double | 16 | 19 位有效 | + +## 4. 其他类型 + +- **void 类型** +- **指针类型(\*)** +- **数组类型([])** +- **结构类型(struct)** +- **共用体类(union)** +- **函数类型** + +## 5. typedef + +使用 typedef 重新定义一个数据类型的名称,如: + +```c +typedef int BOOL; +#define TRUE 1 +#define FALSE 0 +BOOL isDisplay = TRUE; +``` diff --git a/docs/electronics/c-lang/chapter3.md b/docs/electronics/c-lang/chapter3.md new file mode 100644 index 0000000..605ddd2 --- /dev/null +++ b/docs/electronics/c-lang/chapter3.md @@ -0,0 +1,88 @@ +--- +title: 变量和常量 +--- + +# 变量和常量 + +## 1. 什么是变量和常量 + +常量是固定值,在程序执行期间不会改变。 + +变量是指指向某一个存储空间的名称,而存储空间我们用数据类型来定义,因此定义一个变量就是定义了一个可操作存储空间。 + +## 2. 整形常量 + +0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。 + +整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。 + +```c +212 /* 合法的 */ +215u /* 合法的 */ +0xFeeL /* 合法的 */ +078 /* 非法的:8 不是八进制的数字 */ +032UU /* 非法的:不能重复后缀 */ +``` + +```c +85 /* 十进制 */ +0213 /* 八进制 */ +0x4b /* 十六进制 */ +30 /* 整数 */ +30u /* 无符号整数 */ +30l /* 长整数 */ +30ul /* 无符号长整数 */ +``` + +## 3. 浮点型常量 + +当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者,默认的浮点类型是 double 类型。 + +带符号的指数是用 e 或 E 引入的整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。 + +```c +3.14159 /* 合法的 */ +314159E-5L /* 合法的 */ +510E /* 非法的:不完整的指数 */ +210f /* 非法的:没有小数或指数 */ +.e55 /* 非法的:缺少整数或分数 */ +``` + +## 4. 字符常量 + +字符常量是括在单引号中,如:‘x‘,’X’。 + +转义字符指无法输出打印,或者会引起歧义的字符,如: + +| 转义字符 | 字符含义 | +| :------: | :--------: | +| \\n | 换行 | +| \\t | 水平制表符 | +| \\' | 单引号 | +| \\" | 双引号 | +| \\\ | 斜杠 | + +## 5. 定义变量的命名规范 + +- **变量可以由字母、数字和下划线字符组成,必须以字母或下划线开头** +- **C 语言区分大小写,Print 和 print 是两个不同的变量** +- **定义变量是可以使用=给变量赋初值** +- **定义相同类型的变量时,可以连续定义而不需要定义数据类型** + +## 6. 定义一个常量(象征性) + +使用 define,如: + +```c +#define LED_PIN 6 +#define NUM_LEDS 30 +#define LED_TYPE WS2812 +#define COLOR_ORDER GRB +``` + +使用 const,如: + +```c +const uint8_t LED_PIN = 6; +const uint8_t NUM_LEDS = 30; +``` diff --git a/docs/electronics/c-lang/chapter4.md b/docs/electronics/c-lang/chapter4.md new file mode 100644 index 0000000..1b55769 --- /dev/null +++ b/docs/electronics/c-lang/chapter4.md @@ -0,0 +1,77 @@ +--- +title: 运算符 +--- + +# 运算符 + +## 1. 什么是运算符 + +运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。 + +通常运算符分以下几类: + +- **算术运算符** +- **关系运算符** +- **逻辑运算符** +- **赋值运算符** +- **位运算符** +- **其他运算符** + +## 2. 算术运算符 + +| 运算符 | 描述 | 示例 | +| :----: | :--------------: | :---------------------------: | +| + | 将两个操作数相加 | int a = 5+6; | +| - | 将两个操作数相减 | int a = 5-6; | +| \* | 将两个操作数相乘 | int a = 5\*6; | +| / | 将两个操作数相除 | int a = 5/2; float b = 5/2.0; | +| % | 取模运算 | int a = 5%2; | +| ++ | 将自身体的值加 1 | int a = 12;a++;++a; | +| -- | 将自身体的值减 1 | int a =12;a--;--a; | + +## 3. 关系运算符 + +| 运算符 | 描述 | 示例 | +| :----: | :------------------------------: | :-------------------: | +| == | 判断两个操作数是否相等 | bool result = (4==5); | +| != | 判断两个操作数是否不相等 | bool result = (4==5); | +| > | 判断一个操作数是否大于另一个 | bool result = (4>5); | +| < | 判断一个操作数是否小于另一个 | bool result = (4<5); | +| >= | 判断一个操作数是否大于等于另一个 | bool result = (4>=5); | +| <= | 判断一个操作数是否小于等于另一个 | bool result = (4<=5); | + +## 4. 逻辑运算符 + +| 运算符 | 描述 | 示例 | +| :----: | :----: | :------------------------------: | +| && | 且运算 | bool result = (4==5)&&(4<3); | +| \|\| | 或运算 | bool result = (4==5) \|\| (4<3); | +| ! | 非运算 | bool result = !(4>5); | + +## 5. 赋值运算 + +| 运算符 | 描述 | 示例 | +| :----: | :----------------------------------: | :----------------: | +| = | 右操作数赋值给左操作数 | int a = 12; a=6; | +| += | 右操作数加上左操作数赋值给左操作数 | int a = 12; a+=6; | +| -= | 右操作数减去上左操作数赋值给左操作数 | int a = 12; a-=6; | +| \*= | 右操作数乘上左操作数赋值给左操作数 | int a = 12; a\*=6; | +| /= | 右操作数除以左操作数赋值给左操作数 | int a = 12; a/=6; | +| %= | 右操作数取模左操作数赋值给左操作数 | int a = 12; a%=6; | + +## 6. 其他运算符 + +| 运算符 | 描述 | 示例 | +| :----: | :--------------------------------: | :----------------------: | +| sizeof | 获取某一个变量所占存储空间的字节数 | int a;int b = sizeof(a); | +| ? | 条件表达式 | int a = (4>5)?4:5; | +| & | 返回变量的地址 | int a =12;int b =&a; | +| \* | 指针变量指向一个变量 | int a = 12; int \*b=\*a; | + +## 7. 运算符优先级和结合性 + +在表达式中,优先级较高的先于优先级较低的进行运算。 + +而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理,即左结合和右结合。 + +![运算符优先级和结合性](./images/4-1.png) diff --git a/docs/electronics/c-lang/chapter5.md b/docs/electronics/c-lang/chapter5.md new file mode 100644 index 0000000..3f05d91 --- /dev/null +++ b/docs/electronics/c-lang/chapter5.md @@ -0,0 +1,193 @@ +--- +title: 条件判断if和switch +--- + +# 条件判断 if 和 switch + +## 1. 判断条件的真假 + +条件判断,即为判断一个或者多个事件的真假性,然后执行对应的语句。 + +在 C 语言中,把任何**非零**和**非空**的值假定为**true**,把**零**或 **null**假定为**false**。 + +下面是一个真假值表: + +| 真 | 假 | +| :--: | :---: | +| 1 | 0 | +| true | false | +| 2 | !2 | +| 3<4 | 3>4 | + +C 语言提供了以下类型的判断语句。 + +| 语句 | 描述 | +| :-----: | :---------------------------------------------------: | +| if | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 | +| if/else | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 | +| switch | 一个 switch 语句允许测试一个变量等于多个值时的情况。 | + +## 2. if 语句 + +只要 if()中条件判断为真,则执行{}中的代码块,没有{}则仅执行 if 后的一条语句。 + +语法: + +```c +if(/*布尔表达式*/) +{ + // 布尔表达式为真时执行的语句 +} +``` + +## 3. if/else 语句 + +同 if 语句一样,条件成立执行 if 后的代码块,为假执行 else 后的代码块。 + +语法: + +```c +if(/*布尔表达式*/) +{ + // 如果布尔表达式为真将执行的语句 +} +else +{ + // 如果布尔表达式为假将执行的语句 +} +``` + +下面是一个判断输入的年龄是否是未成年的实例 + +```c +#include + +int main() { + int input; + printf("Input your age:"); + scanf("%d", &input); + if (input < 18) { + printf("You're an underage!"); + } else { + printf("Congratulations! You're already an adult!"); + } + return 0; +} +``` + +## 4. 嵌套 if/else if/else + +我们可以在 if/else 后添加另外一个 if,添加更多的判断语句。 + +语法: + +```c +//条件判断一 +if(/*布尔表达式*/) +{ + // 如果布尔表达式为真将执行的语句 +} +//条件判断二 +else if(/*布尔表达式*/) +{ + // 如果布尔表达式为真将执行的语句 +} +//条件判断三 +else +{ + // 如果布尔表达式为假将执行的语句 +} +``` + +试通过 C 语言编程完成以下个人所得税的问题: + +```plaintext +假设国家对个人收入调节税是按这样的标准进行的: +起征点是1000元,1000-2000元为5%,2000-3000元为10%, +3000-5000元为15%,5000元以上为20%. +通过编程输入工资,计算实际工资。 +``` + +参考代码如下: + +```c +#include + +int main() { + float salary, tax; + printf("Input your salary:"); + scanf("%f", &salary); + if (salary < 1000) tax = 0; + else if (salary < 2000) tax = 0.05; + else if (salary < 3000) tax = 0.1; + else if (salary < 5000) tax = 0.15; + else tax = 0.2; + + salary = salary * (1 - tax); + printf("Your final salary is %.2f", salary); + return 0; +} +``` + +## 5. switch 语句 + +switch 语句的功能和 if/else if/else 相像。 + +语法: + +```c + +switch (expression) +{ + case constant - expression: + statement(s); + break; /* 可选的 */ + case constant - expression: + statement(s); + break; /* 可选的 */ + + /* 您可以有任意数量的 case 语句 */ + default: /* 可选的 */ + statement(s); +} +``` + +- **在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。** +- **case 的 constant-expression 必须与 switch 中的变量具有相同的数据类型,且必须是一个常量。** +- **当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。** +- **当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。** +- **不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。** +- **一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。** +- **相同的标签不能出现两次。** + +大家可以试着将上面的薪资问题改成 switch,以增进对 switch 的认识。 + +参考代码如下: + +```c +#include + +int main() { + float salary, tax; + printf("Input your salary:"); + scanf("%f", &salary); + + switch ((int)(salary / 1000)) { + case 0: tax = 0; break; + case 1: tax = 0.05; break; + case 2: tax = 0.1; break; + case 3: + case 4: tax = 0.15; break; + default: tax = 0.3; break; + } + + salary = salary * (1 - tax); + printf("Your final salary is %.2f", salary); + return 0; +} +``` + +## 6. 小测试 + +- **输入一个整数,判断该数是偶数还是奇数。** +- **输入三个整数,求出三个数中的最大值。** diff --git a/docs/electronics/c-lang/chapter6.md b/docs/electronics/c-lang/chapter6.md new file mode 100644 index 0000000..8a906d4 --- /dev/null +++ b/docs/electronics/c-lang/chapter6.md @@ -0,0 +1,122 @@ +--- +title: 循环while和do/while +--- + +# 循环 while 和 do/while + +## 1. while + +只要给定的条件为真,while 循环语句会重复执行其后代码块中的内容。 + +语法: + +```c +while(/*布尔表达式*/) +{ + // 如果布尔表达式为真将执行的语句 +} +``` + +在这里,while 循环的关键点是循环可能一次都不会执行。当条件为 false 时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句。 + +下面是一个输出 0-10 中偶数的实例: + +```c +#include + +int main() { + int count = 0; + while (count < 10) { + // 如果是偶数则输出该数 + if (count % 2 == 0) + printf("%d\n", count); + count++; + } + return 0; +} +``` + +## 2. 循环控制语句 + +在 while 循环中我们可以通过其他语句来控制循环的过程。 + +C 语言中有以下三个循环控制语句: + +| 控制语句 | 描述 | +| :------: | :----------------------------------------------------: | +| break | 终止循环或 switch 语句 | +| continue | 告诉一个循环体立刻停止本次循环,重新开始下次循环 | +| goto | 将控制转移到被标记的语句,不建议在程序中使用 goto 语句 | + +如果在上面的代码中加入 break,则可以改成如下代码: + +```c +#include + +int main() { + int count = 0; + while (1) { + if (count % 2 == 0) + printf("%d\n", count); + count++; + if (count >= 10) + break; + } + return 0; +} +``` + +如果在上面的代码中加入 continue,则可以改成如下代码: + +```c +#include + +int main() { + int count = 0; + while (count < 10) { + if (count % 2 == 1) { + count++; + continue; + } else + printf("%d\n", count); + count++; + } + return 0; +} +``` + +## 3. do/while 语句 + +do/while 循环是在循环的尾部检查它的条件。 + +do/while 循环与 while 循环类似,但是 do/while 循环会确保至少执行一次循环。 + +语法: + +```c +do +{ + statement(s); +}while( condition ); +``` + +将上面的例子改成 do/while 语句后,代码如下: + +```c +#include + +int main() { + int count = 0; + do { + if (count % 2 == 0) + printf("%d\n", count); + count++; + } while (count < 10); + return 0; +} +``` + +## 4. 小测试 + +- **输入一个整数,计算出该整数的位数。** +- **输入一个整数,逆向输出该数,如输入 123456,反向输出 654321.** diff --git a/docs/electronics/c-lang/chapter7.md b/docs/electronics/c-lang/chapter7.md new file mode 100644 index 0000000..73906fb --- /dev/null +++ b/docs/electronics/c-lang/chapter7.md @@ -0,0 +1,150 @@ +--- +title: 循环for +--- + +# 循环 for + +## 1. for 语句 + +for 循环可以方便地执行指定次数的循环。 + +语法: + +```c +for ( init; condition; increment ) +{ + statement(s); +} +``` + +- **init 会首先被执行,且只会执行一次。这一步允许声明并初始化任何循环控制变量。也可以不在这里写任何语句,只要有一个分号出现即可。** +- **接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。** +- **接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。** +- **条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。** + +下面我们用 for 循环重新编写前面求 0-10 中偶数的程序,代码如下: + +```c +#include + +int main() { + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) + printf("%d\n", i); + } + return 0; +} +``` + +可以发现,使用 for 循环编写循环非常之简介明了。 + +请大家尝试编写求`0+1+2+...+10`之和的程序代码。 + +参考代码如下: + +```c +#include + +int main() { + int sum = 0; + for (int i = 0; i < 11; i++) { + sum = sum + i; + // sum += i; + } + printf("The sum is %d", sum); + return 0; +} +``` + +## 2. for 循环嵌套 + +我们可以在 for 循环内使用一个或多个 for 循环。 + +下面请大家通过两层 for 循环打印如下 4\*4 的直角三角形。 + +```plaintext +* +** +*** +**** +``` + +参考代码如下: + +```c +#include + +int main() { + for (int i = 1; i <= 4; i++) { + for (int j = 1; j <= i; j++) { + putchar('*'); + } + putchar('\n'); + } + return 0; +} +``` + +下面我们编写一个求 0-100 类所有质数的程序。 + +质数是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。 + +参考代码如下: + +```c +#include + +int main() { + for (int i = 2, j; i <= 100; i++) { + for (j = 2; j <= i; j++) { + //遇到第一个因数则跳出循环 + if (i % j == 0) + break; + } + //如果该因数等于这个数本身则表明为质数,输出 + if (j == i) + printf("%d\n", i); + } + return 0; +} +``` + +如果改成每行最多 5 个质数就换行,则代码如下: + +```c +#include + +int main() { + for (int i = 2, j, count = 0; i <= 100; i++) { + for (j = 2; j <= i; j++) { + //遇到第一个因数则跳出循环 + if (i % j == 0) + break; + } + //如果该因数等于这个数本身则表明为质数,输出 + if (j == i) { + printf("%-3d", i); + count++; + //判断质数的个数,五个质数就换行 + if (count % 5 == 0) + putchar('\n'); + } + } + return 0; +} +``` + +输出的结果如下: + +```plaintext +2 3 5 7 11 +13 17 19 23 29 +31 37 41 43 47 +53 59 61 67 71 +73 79 83 89 97 +``` + +## 3. 小测试 + +- **输入一个整数,求其阶乘。** +- **输入两个整数,求其最大公约数和最小公倍数。** diff --git a/docs/electronics/c-lang/chapter8.md b/docs/electronics/c-lang/chapter8.md new file mode 100644 index 0000000..ce6a760 --- /dev/null +++ b/docs/electronics/c-lang/chapter8.md @@ -0,0 +1,132 @@ +--- +title: 函数 +--- + +# 函数 + +## 1. 什么是函数 + +函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。 + +定义一个函数: + +```c +return_type function_name( parameter list ) +{ + body of the function +} +``` + +- **返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。** +- **函数名称:这是函数的实际名称。函数名和变量名具有相同的命名规范,由字母、下划线和数字组成,只能由字母和下划线开头。** +- **参数:参数就像是占位符。当函数被调用时,向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。** +- **函数主体:函数主体包含一组定义函数执行任务的语句。** + +下面我们将求上面求质数的例子进行函数的封装: + +```c +#include + +int isPrime(int num) { + int i = 2; + for (; i <= num; i++) { + //遇到第一个因数则跳出循环 + if (num % i == 0) + break; + } + //如果该因数等于这个数本身则表明为质数,返回真 + if (i == num) + return 1; + else + return 0; +} + +int main() { + for (int i = 2, count = 0; i <= 100; i++) { + if (isPrime(i)) { + printf("%-3d", i); + count++; + //判断质数的个数,五个质数就换行 + if (count % 5 == 0) + printf("\n"); + } + } + return 0; +} +``` + +请大家尝试完成求两个数中最大公约数的函数封装。 + +参考代码如下: + +```c +#include + +int getGCD(int num1, int num2) { + int min = num1; + if (num1 > num2) + min = num2; + for (int i = min; i > 0; i--) { + if (num1 % i == 0 && num2 % i == 0) + return i; + } +} + +int main() { + int num1, num2; + printf("Input two intergers:"); + scanf("%d %d", &num1, &num2); + printf("The greatest common divisor between %d and %d is %d.\n", + num1, num2, getGCD(num1, num2)); + return 0; +} +``` + +## 2. 函数声明 + +函数声明会告诉编译器函数名称及如何调用函数。一般我们都是把创建的函数放在 main()之前,通过函数声明,我们就可以把函数主体放在 main()之后。 + +函数声明的格式: + +```c +return_type function_name( parameter list ); +``` + +在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明: + +```c +int getGCD(int, int); +``` + +针对上面求最大公约数的例子,通过函数声明可以改成如下: + +```c +#include + +int getGCD(int num1, int num2); +// int getGCD(int, int); + +int main() { + int num1, num2; + printf("Input two intergers:"); + scanf("%d %d", &num1, &num2); + printf("The greatest common divisor between %d and %d is %d.\n", + num1, num2, getGCD(num1, num2)); + return 0; +} + +int getGCD(int num1, int num2) { + int min = num1; + if (num1 > num2) + min = num2; + for (int i = min; i > 0; i--) { + if (num1 % i == 0 && num2 % i == 0) + return i; + } +} +``` + +## 3. 小测试 + +- **尝试编写通过函数的方式求圆面积的程序** +- **输入一个整数,求其\\(x^2\\)的程序** diff --git a/docs/electronics/c-lang/chapter9.md b/docs/electronics/c-lang/chapter9.md new file mode 100644 index 0000000..5c05179 --- /dev/null +++ b/docs/electronics/c-lang/chapter9.md @@ -0,0 +1,93 @@ +--- +title: 函数的作用域规则 +--- + +# 函数的作用域规则 + +## 1. 函数的作用域规则 + +任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有两个地方可以声明变量: + +- **在函数内部申明的局部变量** +- **在所有函数外部声明的全局变量** + +## 2. 局部变量 + +在某个**函数**或**块**的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。建议在函数内部使用的变量声明在函数内部。 + +我们以求 0-100 中质数的程序为例,虽然函数`isPrime()`和`main()`中都有参数 i,但是由于是在不同的两个函数中,**作用域不同,这两个参数无法相互访问**。 + +另外再`isPrime()`这个函数中,我们把`int i = 2;`**放在循环体这个块之外声明**,就是因为我们需要在后面去判断 i 和 num 的大小关系,如果再循环体声明这个变量我们就无法访问了。 + +```c +#include + +int isPrime(int num) { + int i = 2; + for (; i <= num; i++) { + //遇到第一个因数则跳出循环 + if (num % i == 0) + break; + } + //如果该因数等于这个数本身则表明为质数,返回真 + if (i == num) + return 1; + else + return 0; +} + +int main() { + for (int i = 2, count = 0; i <= 100; i++) { + if (isPrime(i)) { + printf("%-3d", i); + count++; + //判断质数的个数,五个质数就换行 + if (count % 5 == 0) + printf("\n"); + } + } + return 0; +} +``` + +## 3. 全局变量 + +全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。 + +下面是一个使用全局变量的例子: + +```c +#include + +/* 全局变量声明 */ +int global; + +int main() { + /* 局部变量声明 */ + int a = 10, b = 20; + /* 访问全局变量 */ + global = a + b; + printf("Value of a = %d, b = %d and global = %d.\n", a, b, global); + return 0; +} +``` + +在程序中,局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,则会优先使用局部变量。 + +```c +#include + +/* 全局变量声明 */ +int global = 10; + +int main() { + /* 局部变量声明 */ + int global = 20; + printf("Value of global = %d.\n", global); + return 0; +} +``` + +## 4. 初始化局部变量和全局变量 + +当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化,因为全局变量属于静态变量(static),而局部变量是动态变量(auto),所有静态变量未初始化都默认为**0 或者 NULL**,而动态变量未初始化为**任意有效值**。 diff --git a/docs/electronics/c-lang/images/10-1.png b/docs/electronics/c-lang/images/10-1.png new file mode 100644 index 0000000..1cae95c Binary files /dev/null and b/docs/electronics/c-lang/images/10-1.png differ diff --git a/docs/electronics/c-lang/images/10-2.png b/docs/electronics/c-lang/images/10-2.png new file mode 100644 index 0000000..5a05886 Binary files /dev/null and b/docs/electronics/c-lang/images/10-2.png differ diff --git a/docs/electronics/c-lang/images/10-3.png b/docs/electronics/c-lang/images/10-3.png new file mode 100644 index 0000000..ad061b4 Binary files /dev/null and b/docs/electronics/c-lang/images/10-3.png differ diff --git a/docs/electronics/c-lang/images/10-4.png b/docs/electronics/c-lang/images/10-4.png new file mode 100644 index 0000000..c4edbf8 Binary files /dev/null and b/docs/electronics/c-lang/images/10-4.png differ diff --git a/docs/electronics/c-lang/images/11-1.png b/docs/electronics/c-lang/images/11-1.png new file mode 100644 index 0000000..a37d102 Binary files /dev/null and b/docs/electronics/c-lang/images/11-1.png differ diff --git a/docs/electronics/c-lang/images/12-1.png b/docs/electronics/c-lang/images/12-1.png new file mode 100644 index 0000000..f8624b9 Binary files /dev/null and b/docs/electronics/c-lang/images/12-1.png differ diff --git a/docs/electronics/c-lang/images/15-1.png b/docs/electronics/c-lang/images/15-1.png new file mode 100644 index 0000000..7bf0f83 Binary files /dev/null and b/docs/electronics/c-lang/images/15-1.png differ diff --git a/docs/electronics/c-lang/images/4-1.png b/docs/electronics/c-lang/images/4-1.png new file mode 100644 index 0000000..8ac9a80 Binary files /dev/null and b/docs/electronics/c-lang/images/4-1.png differ diff --git a/docs/electronics/c-lang/intro.md b/docs/electronics/c-lang/intro.md new file mode 100644 index 0000000..a3b9970 --- /dev/null +++ b/docs/electronics/c-lang/intro.md @@ -0,0 +1,13 @@ +--- +title: C 语言简介 +--- + +# C 语言简介 + +C 是一种通用的编程语言,广泛用于系统软件与应用软件的开发。于 1969 年至 1973 年间,为了移植与开发 UNIX 操作系统,由丹尼斯·里奇与肯·汤普逊,以 B 语言为基础,在贝尔实验室设计、开发出来。 + +C 语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点,在程序设计中备受青睐,成为最近 25 年使用最为广泛的编程语言。目前,C 语言编译器普遍存在于各种不同的操作系统中,例如 Microsoft Windows、macOS、Linux、Unix 等。C 语言的设计影响了众多后来的编程语言,例如 C++、Objective-C、Java、C#等。 + +当前最新的 C 语言标准为 C18 ,在它之前的 C 语言标准有 C17、C11...C99 等。 + +在这里我们也是只做简单的讲解和介绍,更多内容大家可以参考其他书籍和网站。 diff --git a/docs/electronics/communication/intro.md b/docs/electronics/communication/intro.md new file mode 100644 index 0000000..534a69e --- /dev/null +++ b/docs/electronics/communication/intro.md @@ -0,0 +1,28 @@ +--- +title: 通信专题 +--- + +# 通信专题 + +通信,在单片机中尤其重要,没有了通信,那么单片机就只能是一个孤岛,就不能延伸它的能力。 + +在这里,我们只对单片机中很常用的通信方式做简单介绍,至于蓝牙,WIFI 等无线及其他通信方式,这里不会涉及。同时,这里所有代码都是基于 Arudino 框架进行编写的,主要是环境搭建方便,但是内容是基于通信原理,所以完全可以移植到其他任何一种单片机,重要的学习每一种通信其中的原理。 + +在下面的章节中,我们将学习以下五种,单片机常用的通信方式: + +- [One-Wire](serial/one-wire/intro.md) +- [UART](serial/uart/intro.md) +- [I2C](serial/i2c/intro.md) +- [SPI](serial/spi/intro.md) +- [并行通信](parallel/intro.md) + +同时本章需要阅读一些数据手册,你可以通过下方链接阅读下载: + + diff --git a/docs/electronics/communication/parallel/chapter1.md b/docs/electronics/communication/parallel/chapter1.md new file mode 100644 index 0000000..94225af --- /dev/null +++ b/docs/electronics/communication/parallel/chapter1.md @@ -0,0 +1,29 @@ +--- +title: 并行通信原理 +--- + +# 并行通信原理 + +## 1. 并行通信机制 + +一般情况下,并行通信的机制和串行通信非常相似,尤其是和 SPI 通信相似。有些 SPI 通信的设备也支持并行通信,主要是为了提高数据传输的速度。比如,TFT 显示屏,支持 SPI 也支持并行通信,OLED 支持 I2C,SPI 和并行通信。 + +下面是 ST7735 的**SPI 通信**时域图: + +![ST7735的SPI通信时域图](./images/1-1.png) + +下面是 ST7735 的**并行通信**时域图: + +![ST7735的并行通信时域图](./images/1-2.png) + +由于并行通信没有统一的标准,关键还是需要阅读数据手册。我们后面具体问题具体分析。 + +## 2. 适用于哪些设备 + +常见的使用并行通信的设备有以下几个: + +| LCD1602 | TFT Display | 电脑显示屏 | +| :--------------------------: | :------------------------------: | :-----------------------------: | +| ![LCD1602](./images/1-3.png) | ![TFT Display](./images/1-4.png) | ![电脑显示屏](./images/1-5.png) | + +后面我们将具体学习如何通过并行通信驱动 LCD1602 显示屏。 diff --git a/docs/electronics/communication/parallel/chapter2.md b/docs/electronics/communication/parallel/chapter2.md new file mode 100644 index 0000000..21c30b0 --- /dev/null +++ b/docs/electronics/communication/parallel/chapter2.md @@ -0,0 +1,274 @@ +--- +title: LCD1602数据手册速览 +--- + +# LCD1602 数据手册速览 + +## 1. LCD1602 基本参数 + +| 参数 | 参数值 | +| :--------: | :-----------------------------------------: | +| 供电电压 | 4.5V-5.5V | +| 通信方式 | 并行通信,支持 4 总线和 8 总线 | +| 字符分辨率 | 支持 5x8,或者 5x10 字符点阵 | +| 字符数 | 可显示字符 16x2,可存储字符 40x2 | +| 自定义字符 | 可自定义 8 个 5x8 字符,或者 4 个 5x10 字符 | +| 光标 | 支持光标以及光标闪烁 | +| 字符滚动 | 支持字符水平滚动 | + +## 2. LCD 引脚介绍 + +下面是 LCD1602 的 16 个引脚分布: + +| 引脚序号 | 引脚编号 | 介绍 | +| :------: | :-----------: | :-------------------------------------------: | +| 1 | \\(V\_{SS}\\) | 供电负极 | +| 2 | \\(V\_{DD}\\) | 供电正极 | +| 3 | V0 | 对比度调节电压 | +| 4 | RS | 数据/命令选择引脚,高电平:数据,低电平:命令 | +| 5 | RW | 读/写选择引脚,高电平:读,低电平:写 | +| 6 | EN | 芯片使能引脚 | +| 7 | DB0 | 数据引脚 0 | +| 8 | DB1 | 数据引脚 1 | +| 9 | DB2 | 数据引脚 2 | +| 10 | DB3 | 数据引脚 3 | +| 11 | DB4 | 数据引脚 4 | +| 12 | DB5 | 数据引脚 5 | +| 13 | DB6 | 数据引脚 6 | +| 14 | DB7 | 数据引脚 7 | +| 15 | A | 背光灯正极 | +| 16 | K | 背光灯负极 | + +下面是 LCD1602 和 Arduino Uno 的接线图: + +![LCD1602 and arduino](./images/2-1.png) + +## 3. LCD1602 的存储器 + +LCD1602 主要有三个存储器,分别是 DDRAM,CGROM,CGRAM。 + +下面我们介绍一下这三个存储器。 + +### 3.1 DDRAM + +**DDRAM**(Display Data RAM)是用来存储显示在屏幕上的字符的一个存储器。 + +该存储器一共可以存储 40x2 个字符,存储器内部结构示意图如下: + +![SSRAM](./images/2-2.png) + +### 3.2 CGROM + +**CGROM**(Character Generator ROM)预先存储了一些字符,可以方便我们使用。 + +下面是 LCD1602 内置的字符表: + +![CGROM](./images/2-3.png) + +### 3.3 CGRAM + +**CGRAM**(Character Generator RAM)用于存储我们自定义的字符。 + +观察上面 CGROM 的第一列,我们可以发现,有八个 CGRAM 的地址。也就是说,我们通过 CGRAM 定义的字符会**指向 CGROM 的这 8 个地址**。我们可以通过 CGROM 的地址访问 CGRAM 的内容,最终会缓存到 DDRAM 中。 + +## 4. LCD1602 的读写操作 + +LCD1602 可以通过 RS,RW 选择 4 种不同的读写操作: + +| RS | RW | 介绍 | +| :-: | :-: | :--------------------------: | +| 0 | 0 | 写命令 | +| 0 | 1 | 读取 BUSY Flag 及 AC 地址 | +| 1 | 0 | 向 CGRAM 或者 DDRAM 写入数据 | +| 1 | 1 | 向 CGRAM 或者 DDRAM 读取数据 | + +通常情况下,我们都不会读取 RAM 中的数据,因此,我们使用的也就只有前三种。 + +有的时候为了节省 IO 口的使用而选择放弃读取 BUSY FLAG,让 RW 直接接地,RW=0,这样就省去了 5 个 IO 口。 + +不过在这边我们会**使用所有的引脚**,但是不去读 RAM 中的数据。 + +RS 和 RW 是命令/数据的读/写选择端口,但是如何让 LCD1602 知道何时接收这些数据呢,那就是 EN 引脚的作用了。 + +结合 EN 引脚,我们可以将上面三种读写操作总结如下: + +- 读 Flag:RS=0,RW=1,EN 高电平 +- 写命令:RS=0,RW=0,EN 高脉冲 +- 写数据:RS=1,RW=0,EN 高脉冲 + +可以发现,读取 Falg 和写数据/命令的 EN 条件有点不同,EN 高电平比较好理解,**EN 高脉冲**指的是 EN 的引脚要有一个从低到高的脉冲变化,数据手册介绍这个脉冲至少持续**230 纳秒**。 + +其实我们如果从数据手册上的时序图来看,LCD1602 的读写时序图是一致的,都有一个 EN 的高峰,只是我们在写代码的时候这样可以达到最好的数据传输效果。 + +LCD1602 写操作: + +![LCD1602 write and read operation](./images/2-4.png) + +LCD1602 读操作: + +![LCD1602 write and read operation](./images/2-4.png) + +## 5. LCD1602 指令表 + +在 LCD1602 数据手册中,将命令(Command)称为指令(introduction),其实是一样的。 + +同时**AC**(Address Counter)在 LCD1602 中指的是指向 DDRAM 或者 CGRAM 的地址计数器,这个计数器很重要。 + +![LCD1602 introduction Table](./images/2-5.png) + +下面介绍一下每一条指令: + +### 5.1 Clear Display + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | + +解析时间:1.53ms + +介绍:该指令会把 DDRAM 的数据全部写为 0x00,同时让 AC 指向 DDRAM 的第一个地址 0x00。 + +### 5.2 Return Home + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | \* | + +解析时间:1.53ms + +介绍:该指令会让 AC 指向 DDRAM 第一个 0x00 的地址,同时让光标回到 0x00。 + +### 5.3 Entry Mode Set + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | I/D | SH | + +解析时间:39us + +介绍:I/D 用于控制结束读写操作后 AC 是自动递增还是递减。**如果 I/D = 1,自动递增;如果 I/D = 0,自动递减**。 + +### 5.4 Display ON/OFF Control + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 0 | 0 | 1 | D | C | B | + +解析时间:39us + +介绍:D 用于控制是否显示屏幕。**如果 D=1,屏幕开;如果 D=0,屏幕关**。C 用于控制是否显示光标。**如果 C=1,光标开;如果 C=0,光标关**。B 用于控制是否闪烁光标,在闪烁开的情况下,将会以**409.6ms**的频率进行闪烁。**如果 B=1,闪烁开;如果 B=0,闪烁关**。 + +### 5.5 Cursor or Display Shift + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 0 | 1 | S/C | R/L | \* | \* | + +解析时间:39us + +介绍:S/C 用于控制光标的移动。**如果 S/C=1,屏幕移动;如果 S/C=0,光标移动**。R/L 用于控制屏幕或者光标的移动反向。**如果 R/L=1,屏幕或光标向右移动一列;如果 R/L=0,屏幕或光标向左移动一列**。 + +将其列成表格如下: + +| S/C | R/L | 结果 | +| :-: | :-: | :----------: | +| 0 | 0 | 光标向左移动 | +| 0 | 1 | 光标向右移动 | +| 1 | 0 | 屏幕向左移动 | +| 1 | 1 | 屏幕向右移动 | + +### 5.6 Function Set + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 0 | 1 | DL | N | F | \* | \* | + +解析时间:39us + +介绍:DL 用于控制通讯总线模式。**如果 DL=1,8 总线模式;如果 DL=0,4 总线模式**。N 用于控制控制显示行数。**如果 N=1,显示两行;如果 N=0,显示 1 行**。F 用于控制显示字体。**如果 F=1,5x10 字体;如果 F=0,5x8 字体**。 + +### 5.7 Set CGRAM Address + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 0 | 1 | AC5 | AC4 | AC3 | AC2 | AC1 | AC0 | + +解析时间:39us + +介绍:该指令用于设置 AC 指向 CGRAM 的地址。当我们需要自定义字体的时候需要用到。 + +### 5.8 Set DDRAM Addresss + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 0 | 1 | AC6 | AC5 | AC4 | AC3 | AC2 | AC1 | AC0 | + +解析时间:39us + +介绍:该指令用于设置 AC 指向 DDRAM 的地址。当我们需要设置光标位置的时候需要用到。 + +### 5.9 Read Busy Flag and Address + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 0 | 1 | BF | AC6 | AC5 | AC4 | AC3 | AC2 | AC1 | AC0 | + +解析时间:0us + +介绍:该指令用于读取 LCD1602 的忙碌状态和 AC 地址。 + +### 5.10 Write Data to RAM + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | 0 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + +解析时间:43us + +介绍:该指令用于向 DDRAM 或者 CGRAM 写入数据,具体写到哪一个 RAM**取决于 AC 的指向**。 + +### 5.11 Read Data from RAM + +指令码: + +| RS | RW | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + +解析时间:43us + +介绍:该指令用于向 DDRAM 或者 CGRAM 读取数据,具体读取哪一个 RAM**取决于 AC 的指向**。 + +### 6. LCD1602 的初始化 + +LCD 有 8 总线和 4 总线模式。 + +根据数据手册的介绍,8 总线的初始化如下: + +![LCD1602 Init](./images/2-6.png) + +相似地,4 总线的初始化如下: + +![LCD1602 Init](./images/2-7.png) + +下一章我们将学习如何借助 8 总线方式驱动 LCD1602。 diff --git a/docs/electronics/communication/parallel/chapter3.md b/docs/electronics/communication/parallel/chapter3.md new file mode 100644 index 0000000..a2e0984 --- /dev/null +++ b/docs/electronics/communication/parallel/chapter3.md @@ -0,0 +1,217 @@ +--- +title: 代码逻辑分析 +--- + +# 代码逻辑分析 + +## 1. 8 位引脚操作 + +在这里,我们将以 8 总线的方式驱动 LCD,同时使用所有引脚。 + +由于我们使用 Arduino 的框架,Arduino 不像 C51,对 8 个 IO 口进行操作不是很方便,所以我们要解决如何从 8 个 IO 口读取和发送 8 位数据。 + +下面是示例代码: + +```cpp +const uint8_t data_pins[] = {9, 8, 7, 6, 5, 4, 3, 2}; +// 发送数据到引脚 +void send_pins(uint8_t dat) { + for (int i = 0; i < 8; i++) { + digitalWrite(data_pins[i], (dat >> i) & 0x01); + } +} + +// 读取引脚数据 +uint8_t read_pins() { + uint8_t sta = 0; + for (uint8_t i = 0; i < 8; i++) { + sta |= (uint8_t)digitalRead(data_pins[i]) << i; + } + return sta; +} +``` + +## 2. 读取忙碌状态 + +这里我们还要解决一个问题,那就是读取 LCD1602 的忙碌状态,因为通过读取忙碌状态,我们可以规避麻烦的 LCD 初始化,不需要关注解析时长短的问题。 + +下面是示例代码: + +```cpp +// 等待LCD1602准备好 +void wait_ready() { + uint8_t sta; + // 读取LCD状态RS=0,RW=1 + digitalWrite(RS, LOW); + digitalWrite(RW, HIGH); + // 将引脚设置为输入模式 + for (uint8_t i = 0; i < 8; i++) { + pinMode(data_pins[i], INPUT); + } + // 读取引脚状态 + do { + digitalWrite(EN, HIGH); + sta = read_pins(); + digitalWrite(EN, LOW); + } while (sta & 0x80); // 读取Bit7的Busy Flag + // 将引脚重新设置为输出 + for (uint8_t i = 0; i < 8; i++) { + pinMode(data_pins[i], OUTPUT); + } +} +``` + +这里我们只需要读取 LCD 的忙碌状态就可以了,不需要关注 AC 的地址。 + +## 3. 写命令/数据 + +解决了发送和读取引脚数据的问题,下面我们就要解决前面讲到的很核心也很重要的问题,数据的读写。这里我们只是数据的写,没有数据的读,唯一的读操作就是读取 Flag。 + +下面是示例代码: + +```cpp +// 写命令 +void write_cmd(uint8_t cmd) { + wait_ready(); + // 写命令RS=0,RW=0 + digitalWrite(RS, LOW); + digitalWrite(RW, LOW); + send_pins(cmd); + // 发送命令需要EN高脉冲 + digitalWrite(EN, HIGH); + digitalWrite(EN, LOW); +} + +// 写数据到DDRAM +void write_data(uint8_t dat) { + wait_ready(); + // 写数据RS=1,RW=0 + digitalWrite(RS, HIGH); + digitalWrite(RW, LOW); + send_pins(dat); + // 发送数据需要EN高脉冲 + digitalWrite(EN, HIGH); + digitalWrite(EN, LOW); +} +``` + +在这里,我们需要保证 EN 的高脉冲是在发送引脚状态之后。 + +## 4. LCD1602 初始化 + +构建了这些底层函数,我们就可以写数据和命令。 + +首先我们要对 LCD 进行初始化。由于 LCD1602 默认使用 8 总线模式,因此数据手册中前面选择模式的部分我们不需要操作,我们只需要初始化最后四条指令。 + +![LCD1602 Init](./images/2-8.png) + +```cpp +// LCD初始化 +void lcd_init() { + write_cmd(0x38); // 8总线,2行显示区域,5x8字体 + write_cmd(0x0C); // 显示开,光标关,闪烁关 + write_cmd(0x01); // 清屏 + write_cmd(0x06); // 地址自动+1,文字不动 +} +``` + +## 5. 其他 API 函数函数 + +在完成了这些底层操作及初始后之后,我们需要完成一些 API 函数,方便我们使用 LCD。 + +下面是一些重要的 API 函数: + +```cpp +// 清空屏幕 +void lcd_clear() { + write_cmd(LCD_CLEAR); +} + +// 光标以及光标指针回到初始状态 +void lcd_home() { + write_cmd(LCD_HOME); +} + +// 关闭LCD +void display_off() { + LCD_DISPLAY_CONTROL &= ~0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开LCD +void display_on() { + LCD_DISPLAY_CONTROL |= 0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标 +void cursor_off() { + LCD_DISPLAY_CONTROL &= ~0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标 +void cursor_on() { + LCD_DISPLAY_CONTROL |= 0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标闪烁 +void blink_off() { + LCD_DISPLAY_CONTROL &= ~0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标闪烁 +void blink_on() { + LCD_DISPLAY_CONTROL |= 0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 向左滚动一列 +void scroll_left() { + write_cmd(LCD_SURSOR_SHIFT); +} + +// 向右滚动一列 +void scroll_right() { + write_cmd(LCD_SURSOR_SHIFT | 0x04); +} + +// 设置光标位置 +void set_cursor(uint8_t x, uint8_t y) { + uint8_t addr; + if (y == 0) + addr = 0x00 + x; // 第一行字符地址从 0x00开始 + else + addr = 0x40 + x; // 第二行字符地址从 0x40开始 + write_cmd(LCD_SET_DDRAM_ADDR | addr); // 设置DDRAM地址 +} + +// 打印字符 +void print(const char cha) { + write_data(cha); +} + +// 打印字符串 +void print(const char* str) { + while (*str) { + write_data(*str++); // 先取指针的值,再将指针地址自增 + } +} + +// 自定义一个新的CGRAM字符 +void creat_char(uint8_t num, uint8_t* bit_map) { + // 设置CGRAM地址 + write_cmd(LCD_SET_CGRAM_ADDR + 8 * num); + for (uint8_t i = 0; i < 8; i++) { + write_data(bit_map[i]); + } +} +``` + +关于自定义字符,我想强调的是,字符必须是**横向取模**,一个字符的大小是**8 个 uint8_t**。当你完成写入 CGRAM 之后,你可以通过`print()`显示该字符,参数必须是 0-7 这 8 个数字。 + +另外,由于 LCD1602 使用 AC 指向 CGRAM 或者 DDRAM,因此完成自定义字符后,可能不能正常显示,因为**AC 指向 CGRAM,而不是 DDRAM**,你需要通过`set_cursor()`重新设置 AC 的指向。 + +下一章有完整代码。 diff --git a/docs/electronics/communication/parallel/chapter4.md b/docs/electronics/communication/parallel/chapter4.md new file mode 100644 index 0000000..8ff7883 --- /dev/null +++ b/docs/electronics/communication/parallel/chapter4.md @@ -0,0 +1,225 @@ +--- +title: 整合打包代码 +--- + +# 整合打包代码 + +下面是完整的 LCD1602 8 总线的代码: + +```cpp +#define LCD_CLEAR 0x01 +#define LCD_HOME 0x02 +#define LCD_ENTRY_MODE 0x06 +#define LCD_SURSOR_SHIFT 0x18 +#define LCD_FUNCTION_SET 0x38 +#define LCD_SET_CGRAM_ADDR 0x40 +#define LCD_SET_DDRAM_ADDR 0x80 + +uint8_t LCD_DISPLAY_CONTROL = 0x0C; + +//定义LCD1602引脚 +const uint8_t EN = 10, RW = 11, RS = 12; +const uint8_t data_pins[] = {9, 8, 7, 6, 5, 4, 3, 2}; + +const char* message = "Hello world!"; +uint8_t duck[] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4}; + +void send_pins(uint8_t); +uint8_t read_pins(); +void wait_ready(); +void write_cmd(); +void write_data(); +void lcd_init(); +void lcd_clear(); +void lcd_home(); +void display_off(); +void display_on(); +void cursor_off(); +void cursor_on(); +void blink_off(); +void blink_on(); +void scroll_left(); +void scroll_right(); +void set_cursor(); +void print(const char); +void print(const char*); +void creat_char(uint8_t, uint8_t*); + +void setup() { + lcd_init(); + creat_char(0, duck); + set_cursor(0, 0); + print(message); + print('*'); + set_cursor(7, 1); + print("Have a nice day! By Mr.Addict!"); + set_cursor(37, 1); + write_data(0); +} + +void loop() { + scroll_left(); + delay(1000); +} + +// 发送数据到引脚 +void send_pins(uint8_t dat) { + for (int i = 0; i < 8; i++) { + digitalWrite(data_pins[i], (dat >> i) & 0x01); + } +} + +// 读取引脚数据 +uint8_t read_pins() { + uint8_t sta = 0; + for (uint8_t i = 0; i < 8; i++) { + sta |= (uint8_t)digitalRead(data_pins[i]) << i; + } + return sta; +} + +// 等待LCD1602准备好 +void wait_ready() { + uint8_t sta; + // 读取LCD状态RS=0,RW=1 + digitalWrite(RS, LOW); + digitalWrite(RW, HIGH); + // 将引脚设置为输入模式 + for (uint8_t i = 0; i < 8; i++) { + pinMode(data_pins[i], INPUT); + } + // 读取引脚状态 + do { + digitalWrite(EN, HIGH); + sta = read_pins(); + digitalWrite(EN, LOW); + } while (sta & 0x80); // 读取Bit7的Busy Flag + // 将引脚重新设置为输出 + for (uint8_t i = 0; i < 8; i++) { + pinMode(data_pins[i], OUTPUT); + } +} + +// 写命令 +void write_cmd(uint8_t cmd) { + wait_ready(); + // 写命令RS=0,RW=0 + digitalWrite(RS, LOW); + digitalWrite(RW, LOW); + send_pins(cmd); + // 发送命令需要EN高脉冲 + digitalWrite(EN, HIGH); + digitalWrite(EN, LOW); +} + +// 写数据到DDRAM +void write_data(uint8_t dat) { + wait_ready(); + // 写数据RS=1,RW=0 + digitalWrite(RS, HIGH); + digitalWrite(RW, LOW); + send_pins(dat); + // 发送数据需要EN高脉冲 + digitalWrite(EN, HIGH); + digitalWrite(EN, LOW); +} + +// LCD初始化 +void lcd_init() { + pinMode(EN, OUTPUT); + pinMode(RS, OUTPUT); + pinMode(RW, OUTPUT); + + write_cmd(LCD_FUNCTION_SET); // 8总线,2行显示区域,5x8字体 + write_cmd(LCD_DISPLAY_CONTROL); // 显示开,光标关,闪烁关 + write_cmd(LCD_CLEAR); // 清屏 + write_cmd(LCD_ENTRY_MODE); // 地址自动+1,文字不动 +} + +// 清空屏幕 +void lcd_clear() { + write_cmd(LCD_CLEAR); +} + +// 光标以及光标指针回到初始状态 +void lcd_home() { + write_cmd(LCD_HOME); +} + +// 关闭LCD +void display_off() { + LCD_DISPLAY_CONTROL &= ~0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开LCD +void display_on() { + LCD_DISPLAY_CONTROL |= 0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标 +void cursor_off() { + LCD_DISPLAY_CONTROL &= ~0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标 +void cursor_on() { + LCD_DISPLAY_CONTROL |= 0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标闪烁 +void blink_off() { + LCD_DISPLAY_CONTROL &= ~0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标闪烁 +void blink_on() { + LCD_DISPLAY_CONTROL |= 0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 向左滚动一列 +void scroll_left() { + write_cmd(LCD_SURSOR_SHIFT); +} + +// 向右滚动一列 +void scroll_right() { + write_cmd(LCD_SURSOR_SHIFT | 0x04); +} + +// 设置光标位置 +void set_cursor(uint8_t x, uint8_t y) { + uint8_t addr; + if (y == 0) + addr = 0x00 + x; // 第一行字符地址从 0x00开始 + else + addr = 0x40 + x; // 第二行字符地址从 0x40开始 + write_cmd(LCD_SET_DDRAM_ADDR | addr); // 设置DDRAM地址 +} + +// 打印字符 +void print(const char cha) { + write_data(cha); +} + +// 打印字符串 +void print(const char* str) { + while (*str) { + write_data(*str++); // 先取指针的值,再将指针地址自增 + } +} + +// 自定义一个新的CGRAM字符 +void creat_char(uint8_t num, uint8_t* bit_map) { + // 设置CGRAM地址 + write_cmd(LCD_SET_CGRAM_ADDR + 8 * num); + for (uint8_t i = 0; i < 8; i++) { + write_data(bit_map[i]); + } +} +``` diff --git a/docs/electronics/communication/parallel/chapter5.md b/docs/electronics/communication/parallel/chapter5.md new file mode 100644 index 0000000..6a54dc9 --- /dev/null +++ b/docs/electronics/communication/parallel/chapter5.md @@ -0,0 +1,215 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +## 1. 总结 + +总得来说,LCD1602 的通信方式其实和前面所讲的其他方式没有很大的差别,同样需要完成数据命令的读写,需要对寄存器进行配置等等。只是将串行通信变成了并行罢了。 + +回头来看,可以发现,我们在整个操作中我们唯一的读操作就是读取 Flag。其实,一般显示的设备都是不需要读操作的,像 OLED,TFT 等等。那我们选择增加一个引脚的意义又在哪里呢。其实这么做不仅可以简化代码,而且能够**大大提高效率**。事实上,Arduino 的官方 LCD1602 库`LiquidCrystal.h`就是使用解析时间代替读取 Flag,那是为了兼容 4 总线的接口模式。 + +## 2. 拓展 + +前面我们是介绍 LCD 的 8 总线模式,那么 LCD 的 4 总线模式如何使用呢。 + +其实使用 4 总线模式的时候,如果通过解析时间代替 Flag,不仅可以节省 4 个 DB,还可以省下 RW。也就是说只对 LCD1602 进行写操作,不进行读操作。 + +只是呢,进入 4 总线模式就需要对 LCD1602 进行配置,同时发送数据也要分两次发送,相对来说就要复杂一些。 + +下面是我的一个示例代码,大家可以参考一下: + +```cpp +#define LCD_CLEAR 0x01 +#define LCD_HOME 0x02 +#define LCD_ENTRY_MODE 0x06 +#define LCD_SURSOR_SHIFT 0x18 +#define LCD_FUNCTION_SET 0x28 +#define LCD_SET_CGRAM_ADDR 0x40 +#define LCD_SET_DDRAM_ADDR 0x80 + +uint8_t LCD_DISPLAY_CONTROL = 0x0C; + +const uint8_t EN = 8, RS = 9; +uint8_t data_pins[] = {7, 6, 5, 4}; + +const char* message = "Hello world!"; +uint8_t bell[8] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4}; + +void send_pins(uint8_t); +void write_cmd(uint8_t); +void write_data(uint8_t); +void lcd_init(); +void lcd_clear(); +void lcd_home(); +void display_off(); +void display_on(); +void cursor_off(); +void cursor_on(); +void blink_off(); +void blink_on(); +void scroll_left(); +void scroll_right(); +void set_cursor(); +void print(const char); +void print(const char*); +void creat_char(uint8_t, uint8_t*); + +void setup() { + lcd_init(); + creat_char(0, bell); + set_cursor(0, 0); + print(message); + print('*'); + set_cursor(7, 1); + print("Have a nice day! By Mr.Addict!"); + set_cursor(37, 1); + write_data(0); +} + +void loop() { + scroll_left(); + delay(1000); +} + +// 发送数据到引脚 +void send_pins(uint8_t dat) { + for (int i = 0; i < 4; i++) { + digitalWrite(data_pins[i], (dat >> i) & 0x01); + } + digitalWrite(EN, HIGH); + digitalWrite(EN, LOW); + delayMicroseconds(50); +} + +// 写命令 +void write_cmd(uint8_t cmd) { + // 命令RS=0,RW=0 + digitalWrite(RS, LOW); + // 高四位 + send_pins(cmd >> 4); + // 低四位 + send_pins(cmd); +} + +// 写数据到DDRAM +void write_data(uint8_t dat) { + // 数据RS=1,RW=0 + digitalWrite(RS, HIGH); + // 高四位 + send_pins(dat >> 4); + // 低四位 + send_pins(dat); +} + +// LCD初始化 +void lcd_init() { + for (uint8_t i = 0; i < 4; i++) { + pinMode(data_pins[i], OUTPUT); + } + pinMode(EN, OUTPUT); + pinMode(RS, OUTPUT); + + delay(15); + for (uint8_t i = 0; i < 3; i++) { + send_pins(0x03); + delayMicroseconds(4200); + } + send_pins(0x02); // 设置4总线模式 + + write_cmd(LCD_FUNCTION_SET); // 4总线,2行显示区域,5x8字体 + write_cmd(LCD_DISPLAY_CONTROL); // 显示开,光标关,闪烁关 + lcd_clear(); // 清屏 + write_cmd(LCD_ENTRY_MODE); // 地址自动+1,文字不动 +} + +// 清空屏幕 +void lcd_clear() { + write_cmd(LCD_CLEAR); + delayMicroseconds(1600); // 该指令需要花较长时间 +} + +// 光标以及光标指针回到初始状态 +void lcd_home() { + write_cmd(LCD_HOME); + delayMicroseconds(1600); // 该指令需要花较长时间 +} + +// 关闭屏幕 +void display_off() { + LCD_DISPLAY_CONTROL &= 0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开屏幕 +void display_on() { + LCD_DISPLAY_CONTROL |= 0x04; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标 +void cursor_off() { + LCD_DISPLAY_CONTROL &= ~0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标 +void cursor_on() { + LCD_DISPLAY_CONTROL |= 0x02; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 关闭光标闪烁 +void blink_off() { + LCD_DISPLAY_CONTROL &= ~0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 打开光标闪烁 +void blink_on() { + LCD_DISPLAY_CONTROL |= 0x01; + write_cmd(LCD_DISPLAY_CONTROL); +} + +// 向左滚动一列 +void scroll_left() { + write_cmd(LCD_SURSOR_SHIFT); +} + +// 向右滚动一列 +void scroll_right() { + write_cmd(LCD_SURSOR_SHIFT | 0x04); +} + +// 设置光标位置 +void set_cursor(uint8_t x, uint8_t y) { + uint8_t addr; + if (y == 0) + addr = 0x00 + x; // 第一行字符地址从 0x00开始 + else + addr = 0x40 + x; // 第二行字符地址从 0x40开始 + write_cmd(addr | LCD_SET_DDRAM_ADDR); // 设置DDRAM地址 +} + +// 打印字符 +void print(const char cha) { + write_data(cha); +} + +// 打印字符串 +void print(const char* str) { + while (*str) { + write_data(*str++); // 先取指针的值,再将指针地址自增 + } +} + +// 自定义一个新的CGRAM字符 +void creat_char(uint8_t num, uint8_t* bit_map) { + // 设置CGRAM地址 + write_cmd(LCD_SET_CGRAM_ADDR + 8 * num); + for (uint8_t i = 0; i < 8; i++) { + write_data(bit_map[i]); + } +} +``` diff --git a/docs/electronics/communication/parallel/images/0-1.png b/docs/electronics/communication/parallel/images/0-1.png new file mode 100644 index 0000000..90e3c53 Binary files /dev/null and b/docs/electronics/communication/parallel/images/0-1.png differ diff --git a/docs/electronics/communication/parallel/images/1-1.png b/docs/electronics/communication/parallel/images/1-1.png new file mode 100644 index 0000000..e260987 Binary files /dev/null and b/docs/electronics/communication/parallel/images/1-1.png differ diff --git a/docs/electronics/communication/parallel/images/1-2.png b/docs/electronics/communication/parallel/images/1-2.png new file mode 100644 index 0000000..2b12928 Binary files /dev/null and b/docs/electronics/communication/parallel/images/1-2.png differ diff --git a/docs/electronics/communication/parallel/images/1-3.png b/docs/electronics/communication/parallel/images/1-3.png new file mode 100644 index 0000000..e68b165 Binary files /dev/null and b/docs/electronics/communication/parallel/images/1-3.png differ diff --git a/docs/electronics/communication/parallel/images/1-4.png b/docs/electronics/communication/parallel/images/1-4.png new file mode 100644 index 0000000..b540442 Binary files /dev/null and b/docs/electronics/communication/parallel/images/1-4.png differ diff --git a/docs/electronics/communication/parallel/images/1-5.png b/docs/electronics/communication/parallel/images/1-5.png new file mode 100644 index 0000000..3866150 Binary files /dev/null and b/docs/electronics/communication/parallel/images/1-5.png differ diff --git a/docs/electronics/communication/parallel/images/2-1.png b/docs/electronics/communication/parallel/images/2-1.png new file mode 100644 index 0000000..05e3eb1 Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-1.png differ diff --git a/docs/electronics/communication/parallel/images/2-2.png b/docs/electronics/communication/parallel/images/2-2.png new file mode 100644 index 0000000..702f4bd Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-2.png differ diff --git a/docs/electronics/communication/parallel/images/2-3.png b/docs/electronics/communication/parallel/images/2-3.png new file mode 100644 index 0000000..2117c46 Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-3.png differ diff --git a/docs/electronics/communication/parallel/images/2-4.png b/docs/electronics/communication/parallel/images/2-4.png new file mode 100644 index 0000000..1b65855 Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-4.png differ diff --git a/docs/electronics/communication/parallel/images/2-5.png b/docs/electronics/communication/parallel/images/2-5.png new file mode 100644 index 0000000..22418c4 Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-5.png differ diff --git a/docs/electronics/communication/parallel/images/2-6.png b/docs/electronics/communication/parallel/images/2-6.png new file mode 100644 index 0000000..85532b7 Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-6.png differ diff --git a/docs/electronics/communication/parallel/images/2-7.png b/docs/electronics/communication/parallel/images/2-7.png new file mode 100644 index 0000000..f5c631c Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-7.png differ diff --git a/docs/electronics/communication/parallel/images/2-8.png b/docs/electronics/communication/parallel/images/2-8.png new file mode 100644 index 0000000..0f36a2b Binary files /dev/null and b/docs/electronics/communication/parallel/images/2-8.png differ diff --git a/docs/electronics/communication/parallel/intro.md b/docs/electronics/communication/parallel/intro.md new file mode 100644 index 0000000..6f67532 --- /dev/null +++ b/docs/electronics/communication/parallel/intro.md @@ -0,0 +1,17 @@ +--- +title: 并行通信简介 +--- + +# 并行通信简介 + +## 1. 什么是并行通信 + +并行通信(Parallel Communication)主要区别于串行通信。在串行通信中,通常是将一个数据帧一位一位地进行传输,而并行通信则是一次性传输一个数据帧。并行通信在早期的电子设备中使用较多,现代使用的更多的还是串行通信。但是无论如何,串口数据最后还是需要转换成并口数据。 + +通常来说,并行通信有 8 线,16 线,32 线等,也有 4 线,9 线,12 线等特殊的总线。所以说,并行通信的效率更高,传输速度更快。举例来说,在时钟频率相同的情况下,8 总线的并行通信的速度就要是串行通信的 8 倍。并行通信的缺点就是接线更加复杂。 + +![并行通信](./images/0-1.png) + +## 2. 并行通信的外设 + +相对来说,并行通信和 SPI 一样,不需要什么外设。 diff --git a/docs/electronics/communication/serial/i2c/chapter1.md b/docs/electronics/communication/serial/i2c/chapter1.md new file mode 100644 index 0000000..558b193 --- /dev/null +++ b/docs/electronics/communication/serial/i2c/chapter1.md @@ -0,0 +1,164 @@ +--- +title: I2C的通信原理 +--- + +# I2C 的通信原理 + +## 1. I2C 的 Clock + +前面讲到,I2C 是同步通信,我们还记得 UART 是异步通信。这是因为,在 UART 中,数据的发送方只负责发送数据,接收方根据波特率进行接收数据。而 I2C 通过 Clock 同步双方的通信过程,只要 SCL 出现了上升沿,那么从机就会接收数据,这样可以有效避免双方数据采样出现误差的问题。 + +I2C 通常有四种时钟频率,也就是 SCL 的脉冲频率: + +- **标准模式**:小于 100KHz +- **快速模式**:小于 400KHz +- **高速模式**:小于 3.4MHz +- **超快速模式**:小于 5MHz + +我们可以拿 I2C 的快速模式 400KHz 和 UART 的 115200,也就是 115.2KHz 相比,可见 I2C 的通信速度要高于 UART。但是 I2C 的速度远远低于 SPI 的速度,这个到我们介绍 SPI 再做介绍。 + +值得注意的是,一般的设备都是**支持标准模式和快速模式**的,支持高速模式和超快速模式的设备比较少,也不建议 I2C 的通信速度高于 400KHz。 + +## 2. I2C 的通信过程 + +I2C 有严格的时序要求,当 SCL 处于低电平期间,SDA 可以改变其状态,而当 SCL 处于高电平期间,SDA 的电平必须稳定,因为这时候接收方要对数据进行采样。 + +标准的 I2C 通信由**START+ADDR+R/W+ACK(+DATA+ACK+DATA+ACK+...)+STOP**组成。 + +下面是标准的 I2C 通信时序图: + +![I2C通信时序图](./images/1-1.png) + +下面我们对这几个部分做一一介绍。 + +### 2.1 START + +I2C 的开始信号是指 SCL 保持高电平的时候,SDA 由高电平变为低电平。 + +### 2.2 STOP + +I2C 的结束信号正好和开始信号相反,在 SDA 低电平的时候,SCL 由低电平变为高电平。 + +### 2.3 ADDR + +I2C 的地址位一般都是 7 位模式,也有 10 位模式,但是常用的还是 7 位模式。也就是说 I2C 可以由 128 个地址可以使用,但是**0-7 是保留地址**,因此有**8-127**这 120 个地址可以使用。日常项目中几乎不会用这么多,所以完全不必担心地址不够用的情况。 + +### 2.4 R/W + +一般情况下,继地址位之后就是读/写位,这一位决定了主机是需要向从机写数据还是读数据,0 是写数据,1 是读数据。 + +### 2.5 ACK + +无论是主机还是从机,在接收完数据后都需要发送一个应答位,应答位可以告诉对方成功接收到数据。 + +### 2.6 DATA + +在发送完地址,收到从机的应答后,主机就可以接收或者发送数据了。每次只能发送一个 8 位的数据,和 UART 不同,数据由**高位向低位**进行发送。 + +## 3. Arduino 的 Wire 库 + +上面是 I2C 的通信原理,但是通常情况下,任何一款单片机都是有封装好的硬件驱动,Arduino 的 I2C 驱动是 Wire 库。同时,Arduino Uno 只有一个 I2C 接口,即**SCL—A5,SDA—A4**。 + +下面我们对 Arduino 的 Wire 库中几个重要的函数做简单介绍。 + +### 3.1 begin() + +语法: + +```cpp +Wire.begin(); +Wire.begin(address); +``` + +用于初始化 Arduino 的 I2C,如果你想让 Arduino 作为主机,参数为空,如果想让 Arduino 作为从机,则可以填入想要的从机地址。正常情况都是作为主机,参数可以为空。 + +### 3.2 beginTransmission() + +语法: + +```cpp +Wire.beginTransmission(address); +``` + +向从机发出开始信号,参数为从机地址。 + +### 3.3 write() + +语法: + +```cpp +Wire.write(value); +Wire.write(string); +Wire.write(data, length); +``` + +向从机发送数据,可以是一个字节的数据,也可以是一个字符串,或者是一个数组。 + +### 3.4 requestFrom() + +语法: + +```cpp +Wire.requestFrom(address, quantity); +Wire.requestFrom(address, quantity, stop); +``` + +向从机发送读数据的请求,读取的数据会存储在缓冲中,可以通过`read()`函数读取。参数 address 是从机地址,quantity 是请求的字节数。可选参数 stop 是一个布尔值,如果是 0,接受完数据后会发送一个重新开始的信号;如果是 1,接受完数据后会发送结束通信的信号,结束通信。 + +Arduino 为了方便初学者使用,使用了`requestFrom()`这个函数,方便向从机读数据,虽然这样就完全掩盖了 I2C 的通信原理。 + +### 3.5 read() + +语法: + +```cpp +Wire.read(); +``` + +向从机读取一个字节的数据。 + +### 3.6 endTransmission() + +语法: + +```cpp +Wire.endTransmission(); +Wire.endTransmission(stop); +``` + +向从机发送一个结束通信的信号。在`Wire.endTransmission()`中,你还可以填入一个布尔值,如果是 0,Arduino 会发送一个重新开始的信号;如果是 1,Arduino 会直接发送停止信号,结束通信。 + +可以发现,在 Wire 库中没有提到 ACK,这是因为 Wire 库自动为我们验证和发送了 ACK。同时,由于使用了`requestFrom()`函数,导致 I2C 的通信原理也被封装了,后面的例子中我将尽力解释清楚其中的原理。 + +下面是一个 Arduino 官网提供的 I2C 读取数据的例子: + +```cpp +#include + +void setup() { + Wire.begin(); // join i2c bus (address optional for master) + Serial.begin(9600); // start serial for output +} + +void loop() { + Wire.requestFrom(2, 6); // request 6 bytes from slave device #2 + + while (Wire.available()) // slave may send less than requested + { + char c = Wire.read(); // receive a byte as character + Serial.print(c); // print the character + } + + delay(500); +} +``` + +## 4. 适用的设备 + +常见的使用 I2C 的设备有以下几个: + +| DS3231 | MPU6050 | OLED | +| :-------------------------: | :--------------------------: | :-----------------------: | +| ![DS3231](./images/1-2.png) | ![MPU6050](./images/1-3.png) | ![OLED](./images/1-4.png) | + +下个的章节我们将学习如何通过 I2C 向 DS3231 读写数据。 diff --git a/docs/electronics/communication/serial/i2c/chapter2.md b/docs/electronics/communication/serial/i2c/chapter2.md new file mode 100644 index 0000000..7b6810b --- /dev/null +++ b/docs/electronics/communication/serial/i2c/chapter2.md @@ -0,0 +1,101 @@ +--- +title: DS3231数据手册速览 +--- + +# DS3231 数据手册速览 + +## 1. DS3231 基本参数 + +| 参数 | 参数值 | +| :------------: | :------------------------------: | +| 供电电压 | 2.3V-5.5V | +| 通信方式 | I2C | +| 日期格式 | 秒,分,时,日,月,年(2000-2100) | +| 时间精度 | ±2 分/年 | +| 温度精度 | ±3°,一分钟更新一次 | +| 闹钟 | 两个闹钟 | +| 支持的通信速度 | 100KHz 标准模式,400KHz 快速模式 | + +## 2. DS3231 接线图 + +DS3231 和 Arduino Uno 接线图如下: + +![DS3231接线图](./images/2-1.png) + +## 3. DS3231 的寄存器列表 + +DS3231 的 I2C 的 7 位地址为 0x68,下面是 DS3231 的寄存器列表,这里我们仅对其做简单讲解,更多内容请参考数据手册: + +![DS3231寄存器列表](./images/2-2.png) + +### 3.1 0x00-0x06 时间寄存器 + +这七个寄存器储存了 DS3231 的时间信息,我们可以通过这七个寄存器读取和写入时间的信息,在晶振使能位开启的情况下,时间寄存器中的信息会自动更新。 + +唯一需要注意的是,0x03 这个寄存器用于存储星期,值从 1-7 共 7 个值,这个寄存器 24H 后自动更新+1,至于哪个数字对应哪个星期,我们自己约定就好了。 + +可以发现,DS3231 存储时间数据的时候,采用**BCD 编码**方式而不是 DEC 编码,因此在后面我们读取和写入数据的时候,要对数据进行相应的转换。 + +![DS3231 时间寄存器](./images/2-3.png) + +### 3.2 0x07-0x0D 闹钟寄存器 + +DS3231 的闹钟寄存器和时间寄存几乎相同,只是闹钟 1 有 4 个寄存器,存储秒,分,时,天(星期),闹钟 2 只有三个寄存器,存储分,时,天(星期)。其中 0x0A 和 0x0D 寄存器的**DY/DT**位用于设置闹钟是以天位单位闹铃,还是以星期位单位进行闹铃。如果是 0,则对应天;如果是 1,对应星期。默认值为 0。 + +当当前时间与设置的闹铃时间一致后,对应的闹铃状态位会被写为 1,我们可以通过这个闹铃状态位获取闹铃信息。 + +![DS3231 闹钟寄存器](./images/2-4.png) + +闹钟寄存器的最高位是闹钟响应模式掩码,你可以通过掩码设置闹铃模式,掩码信息如下: + +![DS3231 掩码信息](./images/2-5.png) + +### 3.3 0x0E-0x0F 控制/状态寄存器 + +下面是 DS3231 控制/状态寄存器列表,这里我们只对其中部分位做介绍,其余位在使用过程中,保持默认即可: + +![DS3231控制/状态寄存器](./images/2-6.png) + +#### 3.3.1 EOSC#晶振使能位 + +该位用于控制 DS3231 的晶振是否使能,该位只在 DS3231 处于电池供电情况下有效,VCC 供电该位无效。当该位为 0 时,晶振使能;为 1 时,晶振不使能,同时寄存器数据将处于静止状态,即数据不会自动更新。 + +#### 3.3.2 INTCN 中断选择控制位 + +这一位用于控制 INT/SQW 引脚的功能,如果该位是 0,则该引脚用作输出方波(SQW);如果该位是 1,则该引脚用于闹钟闹铃中断。默认值为 1。 + +#### 3.3.3 A2IE/A1IE 闹铃 2 中断使能位 + +该位用于决定闹钟是否在闹铃时触发 INT/SQW 引脚。当该位是 0 时,不会触发中断;为 1 时,触发中断。默认值为 0。 + +#### 3.3.4 OSF 晶振状态位 + +该位用于显示晶振是否使能,1 表示未使能,0 表示使能。 + +#### 3.3.5 A2F/A1F 闹钟状态为 + +该位用于显示 DS3231 两个闹钟是否触发,该位为 0 时,表示未触发;为 1 时表示触发。该位只能写入 0。 + +### 3.4 0x11-0x12 温度寄存器 + +该寄存器用于存储温度数据,它是一个 10 位的数据,前 8 位为有符号整数位,后一个字节的前两位是小数位,精度为 0.25。温度一分钟更新一次。 + +![DS3231温度寄存器](./images/2-7.png) + +## 4. DS3231 的 I2C 通信 + +由数据手册可知,DS3231 支持标准的 I2C 通信,标准速度为 100KHz,快速模式为 400KHz,可以进行连续的数据读写。 + +DS3231 数据写: + +![DS3231数据写](./images/2-8.png) + +DS3231 数据读: + +![DS3231数据写](./images/2-9.png) + +DS3231 数据读/写: + +![DS3231的I2C通信](./images/2-10.png) + +因此,这里我不再对 DS3231 的 I2C 通信过程进行介绍了,下一章我们将直接学习如何使用 DS3231。 diff --git a/docs/electronics/communication/serial/i2c/chapter3.md b/docs/electronics/communication/serial/i2c/chapter3.md new file mode 100644 index 0000000..de996b7 --- /dev/null +++ b/docs/electronics/communication/serial/i2c/chapter3.md @@ -0,0 +1,212 @@ +--- +title: 代码逻辑分析 +--- + +# 代码逻辑分析 + +## 1. BCD 和 DEC 转换 + +由于 DS3231 的时间寄存器和闹钟寄存器使用了 BCD 编码格式,因此,在正式编写程序之前,我们要学会**BCD 和 DEC 的相互转换**。比如,用户输入的肯定是十进制数,我们要将其转换成 BCD 格式才能写入寄存器;我们从寄存器读取的是 BCD 格式,需要将其转换成 DEC 才能进行输出。 + +下面是 BCD 和 DEC 相互转换的代码,这里大家看一下,不做详细讲解: + +```cpp +uint8_t bcd2dec(uint8_t bcd) { + uint8_t ones = bcd % 0x10; + uint8_t tens = bcd / 0x10; + uint8_t dec = ones + tens * 10; + return dec; +} + +uint8_t dec2bcd(uint8_t dec) { + uint8_t ones = dec % 10; + uint8_t tens = dec / 10; + uint8_t bcd = ones + tens * 0x10; + return bcd; +} +``` + +## 2. I2C 基本的读写操作 + +根据经验,在任何通信过程中,我们需要完成以下三个函数: + +```cpp +void write_cmd(uint8_t cmd); +void write_data(uint8_t data); +uint8_t read_data(uint8_t data); +``` + +由于在 DS3231 中,写入的时候没有数据和命令的区分,因此我们统一认为写入的是数据,因此我们只需要完成两个函数: + +```cpp +void write_data(uint8_t data); +uint8_t read_data(uint8_t data); +``` + +同时,因为 DS3231 支持一次性写入读取多个字节的数据,因此,我们可以把上述两个函数扩展成以下四个: + +```cpp +void write_data(uint8_t data); +void write_data(uint8_t* data, uint8_t length); +uint8_t read_data(uint8_t data); +void read_data(uint8_t* data, uint8_t length); +``` + +然后根据 Arduino 的 Wire 库的使用方法,我们可以将上述函数补全: + +```cpp + +void write_data(uint8_t data) { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(data); + Wire.endTransmission(); +} + +void write_data(uint8_t *data, uint8_t length) { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(data, length); + Wire.endTransmission(); +} + +uint8_t read_data() { + Wire.requestFrom(DS3231_ADDRESS, 1); + return Wire.read(); +} + +void read_data(uint8_t* data, uint8_t length) { + Wire.requestFrom(DS3231_ADDRESS, length); + for (uint8_t i = 0; i < length; i++) { + data[i] = Wire.read(); + } +} +``` + +## 3. 读/写一个数据 + +在完成整个代码前,我们先完成一个小目标,读取 DS3231 的温度。读取数据的时候,我们**只读取温度的整数位,忽略其小数位**,也就是只从一个寄存器读取一个字节的数据。这样的话,读取温度就比较简单直接了。 + +### 3.1 读取温度 + +![DS3231的I2C通信](./images/2-10.png) + +参照 DS3231 读/写数据的时序图,可以发现,完成读操作我们需要完成以下几步: + +首先,向 DS3231 发送一个写数据的请求,写入的数据为目标地址,即温度寄存器(0x11),代码如下: + +```cpp +write_data(TEMPERATURE_ADDRESS); +``` + +然后我们就可以从 DS3231 读取返回的温度数据了,代码如下: + +```cpp +int8_t temperature = read_data(); +``` + +DS3231 在只读取整数位,忽略小数位的情况下,温度的数据类型是`int8_t`。 + +这样我们就完成了通过寄存器,读取 DS3231 的温度数据。整合一下,读取温度的代码如下: + +```cpp +int8_t readTemp() { + write_data(TEMPERATURE_ADDRESS); + int8_t temperature = read_data(); + return temperature; +} +``` + +### 3.2 读/写取秒数据 + +下面请大家尝试读取 DS3231 的**秒**数据。 + +参考代码如下: + +```cpp +uint8_t readSecond() { + write_data(SECOND_ADDRESS); + uint8_t second = bcd2dec(read_data()); + return second; +} +``` + +是不是很简单,这里我们只需要将写入的数据改为秒寄存器地址(0x00),将数据类型改为`uint8_t`,同时将读取的 BCD 数据转换成 DEC 格式就好了。 + +下面请大家尝试写入秒的数据。 + +参考代码如下: + +```cpp +void setSecond(uint8_t second) { + uint8_t data[] = {SECOND_ADDRESS, dec2bcd(second)}; + write_data(data, 2); +} +``` + +这里我们写数据的时候,同样可以连续写入。第一个是秒的地址,第二个是秒的数据,但是要注意把秒数据转换成 BCD 格式再写入。 + +## 4. 读/写指定长度的数据 + +上面我们完成了读/写秒的数据,也就是完成了读/写一个字节的数据,那我们是不是可以依葫芦画瓢完成读/写秒,分,时,日,月,年数据了呢。 + +这样是可以的,但是这么做显然不够高效。因为在这个过程中,我们每次读写数据都重复做了`发送开始信号,发送结束信号`这两件事,对于读写 7 个字节可能是比较容易,但是如果发送很多数据,这么做就会很浪费时间。 + +借助 DS3231 提供了连续读写功能,我们可以简化这个过程。 + +不过在完成这个连续读写的任务前,我们还需要做一件事,就是定义一个结构体。使用结构体可以大大简化操作。 + +需要定义的结构体如下: + +```cpp +struct Time { + uint16_t year; //年 + uint8_t mon; //月 + uint8_t date; //日 + uint8_t hour; //时 + uint8_t min; //分 + uint8_t sec; //秒 + uint8_t day; //星期 +}; +``` + +然后我们就可以完成连续写的任务了: + +```cpp +void setTime(Time now) { + // 记得写入数据前先将DEC数据转换成BCD格式 + uint8_t data[] = { + SECOND_ADDRESS, + dec2bcd(now.sec), + dec2bcd(now.min), + dec2bcd(now.hour), + dec2bcd(now.day), + dec2bcd(now.date), + dec2bcd(now.mon), + dec2bcd(now.year % 100), + }; + write_data(data, 8); +} +``` + +然后是连续读的任务: + +```cpp +Time readTime() { + Time now; + uint8_t data[7]; + write_data(SECOND_ADDRESS); + read_data(data, 7); + // 读取后的数据需要转换格式 + now.sec = bcd2dec(data[0]); + now.min = bcd2dec(data[1]); + now.hour = bcd2dec(data[2]); + now.day = bcd2dec(data[3]); + now.date = bcd2dec(data[4]); + now.mon = bcd2dec(data[5]); + now.year = bcd2dec(data[6]) + 2000; + return now; +} +``` + +完成了读写时间和温度,在 DS3231 上,我们其实还有一个功能没有使用,那就是闹钟。除了闹钟,还有输出方波,使用中断等等,我认为不需要全部都会使用。能够读取时间就可以了。 + +因此,这里我将不再详介绍如何设置闹钟,读取闹钟状态,以及关闭闹钟等,完整代码在下一章。 diff --git a/docs/electronics/communication/serial/i2c/chapter4.md b/docs/electronics/communication/serial/i2c/chapter4.md new file mode 100644 index 0000000..54c1745 --- /dev/null +++ b/docs/electronics/communication/serial/i2c/chapter4.md @@ -0,0 +1,204 @@ +--- +title: 整合打包代码 +--- + +# 整合打包代码 + +完整的 DS3231 I2C 通信代码如下: + +```cpp +#include + +#define DS3231_ADDRESS 0x68 +#define SECOND_ADDRESS 0x00 +#define ALARM1_SECOND_ADDRESS 0x07 +#define ALARM2_MINUTE_ADDRESS 0x0B +#define STATUS_ADDRESS 0x0F +#define TEMPERATURE_ADDRESS 0x11 + +// 时间的结构体 +struct Time { + uint16_t year; //年 + uint8_t mon; //月 + uint8_t date; //日 + uint8_t hour; //时 + uint8_t min; //分 + uint8_t sec; //秒 + uint8_t day; //星期 +}; + +// 星期字符串 +char weekofday[7][10] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + +uint8_t bcd2dec(uint8_t); +uint8_t dec2bcd(uint8_t); +void write_data(uint8_t); +void write_data(uint8_t*, uint8_t); +uint8_t read_data(); +void write_data(uint8_t*, uint8_t); +Time readTime(); +void setTime(Time); +void setAlarm(uint8_t, uint8_t, bool choice = false); +bool readAlarmFlag(bool choice = false); +void turnAlarmOFF(bool choice = false); +int8_t readTemp(); + +void setup() { + Wire.begin();// 初始化I2C + Wire.setClock(100 * 1000);// 设置I2C频率为100KHz + Serial.begin(115200); + Time now = {2022, 2, 10, 12, 30, 0, 4}; // 2022-2-10 12:30:0 Thursday + setTime(now); + setAlarm(12, 31); + turnAlarmOFF(); +} + +void loop() { + char str[50]; + Time now = readTime();// 更新时间 + sprintf(// 格式化一段字符串数据 + str, + "%d-%d-%d %d:%d:%d %s %d^C ", + now.year, now.mon, now.date, now.hour, now.min, now.sec, weekofday[now.day], readTemp() + ); + Serial.print(str); + if (readAlarmFlag()) { + Serial.println("Alarm ON!"); + turnAlarmOFF(); + } + else Serial.println("Alarm OFF!"); + delay(1000); +} + +// 将BCD数据转换成DEC格式 +uint8_t bcd2dec(uint8_t bcd) { + uint8_t ones = bcd % 0x10; + uint8_t tens = bcd / 0x10; + uint8_t dec = ones + tens * 10; + return dec; +} + +// 将DEC数据转换成BCD格式 +uint8_t dec2bcd(uint8_t dec) { + uint8_t ones = dec % 10; + uint8_t tens = dec / 10; + uint8_t bcd = ones + tens * 0x10; + return bcd; +} + +// 写入一个字节 +void write_data(uint8_t data) { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(data); + Wire.endTransmission(); +} + +// 写入一个指定长度的数组 +void write_data(uint8_t *data, uint8_t length) { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(data, length); + Wire.endTransmission(); +} + +// 读取一个字节 +uint8_t read_data() { + Wire.requestFrom(DS3231_ADDRESS, 1); + return Wire.read(); +} + +// 读取一个指定长度的数组 +void read_data(uint8_t* data, uint8_t length) { + Wire.requestFrom(DS3231_ADDRESS, length); + for (uint8_t i = 0; i < length; i++) { + data[i] = Wire.read(); + } +} + +// 连续读取整个时间 +Time readTime() { + Time now; + uint8_t data[7]; + write_data(SECOND_ADDRESS); + read_data(data, 7); + // 读取后的数据需要转换格式 + now.sec = bcd2dec(data[0]); + now.min = bcd2dec(data[1]); + now.hour = bcd2dec(data[2]); + now.day = bcd2dec(data[3]); + now.date = bcd2dec(data[4]); + now.mon = bcd2dec(data[5]); + now.year = bcd2dec(data[6]) + 2000; + return now; +} + +// 连续设置整个时间 +void setTime(Time now) { + // 记得写入数据前先将DEC数据转换成BCD格式 + uint8_t data[] = { + SECOND_ADDRESS, + dec2bcd(now.sec), + dec2bcd(now.min), + dec2bcd(now.hour), + dec2bcd(now.day), + dec2bcd(now.date), + dec2bcd(now.mon), + dec2bcd(now.year % 100), + }; + write_data(data, 8); +} + +// 设置闹钟,hour:minute格式,每日闹铃,记得写入数据前先将DEC数据转换成BCD格式 +void setAlarm(uint8_t hour, uint8_t minute, bool choice = false) { + // 闹钟1 + if (!choice) { + uint8_t data[] = { + ALARM1_SECOND_ADDRESS, + 0, //秒,A1M1=0 + dec2bcd(minute), //分,A1M2=0 + dec2bcd(hour), //时,A1M3=0 + 0x80, //天/星期,A1M4=1 + }; + write_data(data, 5); + } + // 闹钟2 + else { + uint8_t data[] = { + ALARM2_MINUTE_ADDRESS, + dec2bcd(minute), //分,A2M2=0 + dec2bcd(hour), //时,A2M3=0 + 0x80, //天/星期,A2M4=1 + }; + write_data(data, 4); + } +} + +bool readAlarmFlag(bool choice = false) { + write_data(STATUS_ADDRESS); + // 闹钟1Flag + if (!choice) { + bool flag = read_data() & 0x01; + return flag; + } + // 闹钟2flag + else { + bool flag = (read_data() & 0x02) >> 1; + return flag; + } +} + +// 关闭闹钟Flag +void turnAlarmOFF(bool choice = false) { + write_data(STATUS_ADDRESS); + uint8_t mask = 0xFE; + if (choice)mask = 0xFD; + uint8_t data[] = {STATUS_ADDRESS, read_data() & mask}; + write_data(data, 2); +} + +// 读取温度,忽略温度的小数位,只读取整数位,从而温度在DS3231中的数据类型int8_t +int8_t readTemp() { + write_data(TEMPERATURE_ADDRESS); + int8_t temperature = read_data(); + return temperature; +} +``` diff --git a/docs/electronics/communication/serial/i2c/chapter5.md b/docs/electronics/communication/serial/i2c/chapter5.md new file mode 100644 index 0000000..22cb8c6 --- /dev/null +++ b/docs/electronics/communication/serial/i2c/chapter5.md @@ -0,0 +1,48 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +## 1. 总结 + +就像前面提到的,UART 通信最为直接,通信过程就像正常的命令行读写一样,而下面的三种通信方式(I2C,SPI,并口通信)都涉及相对来说复杂的数据读写。 + +因此,一般在通信前,我们都要完成以下三个函数: + +```cpp +void write_cmd(uint8_t cmd); +void write_data(uint8_t data); +uint8_t read_data(uint8_t data); +``` + +有时候我们为了提高效率,一次性连续读写多个数据,就要将这三个函数扩展到下面六个: + +```cpp +void write_cmd(uint8_t cmd); +void write_cmd(uint8_t* cmd, uint8_t length); +void write_data(uint8_t data); +void write_data(uint8_t* data, uint8_t length); +uint8_t read_data(uint8_t data); +void read_data(uint8_t* data, uint8_t length); +``` + +我们要根据情况进行取舍,DS3231 由于没有命令和数据的区分,所以只需要 4 个函数。在后面的学习中,我们会逐渐发现这几个函数的重要性。 + +## 2. 拓展 1 + +我们之所以在 I2C 专题学习 DS3231 的使用,是因为**DS3231 的使用包含了数据的读和写**,而**MPU6050 只包含数据读**,**OLED 只包含数据写**。 + +MPU6050 是在运动机构中很常用的一个元器件,相比较而言,MPU6050 的使用要比 DS3231 的使用简单得多,因为它只有数据的读操作。有能力、由余力的同学可自行阅读 MPU6050 的数据手册,尝试通过 I2C 读取数据。 + +至于 OLED,虽然它只有数据写操作,但是其数据手册比较长,还有复杂的寄存器操作,点亮屏幕后还需要手动编写图形函数,字符库函数,所以说后面应该是不会做详细介绍了,有兴趣有能力的同学可以自行学习。 + +实在不行还可以调库。 + +## 3. 拓展 2 + +在前面的 UART 章节讲到,除了使用硬件支持的 Serial 库,还可以使用软串口库,也就是`SoftwareSerial.h`库。同理,在我们学习完 I2C 的原理后,也是可以自己手动编写一个 I2C 的软驱动库。 + +在 Arduino 中,也有一个官方的软 I2C 库,也就是`SoftI2CMaster.h`库,这个库同样可以让你使用任何引脚作为 I2C 引脚,但是这个需要自己手动下载安装。 + +官方资料在这里 [SoftI2CMaster](https://github.com/felias-fogg/SoftI2CMaster) diff --git a/docs/electronics/communication/serial/i2c/images/0-1.png b/docs/electronics/communication/serial/i2c/images/0-1.png new file mode 100644 index 0000000..6f2053c Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/0-1.png differ diff --git a/docs/electronics/communication/serial/i2c/images/1-1.png b/docs/electronics/communication/serial/i2c/images/1-1.png new file mode 100644 index 0000000..28821c6 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/1-1.png differ diff --git a/docs/electronics/communication/serial/i2c/images/1-2.png b/docs/electronics/communication/serial/i2c/images/1-2.png new file mode 100644 index 0000000..d42e00b Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/1-2.png differ diff --git a/docs/electronics/communication/serial/i2c/images/1-3.png b/docs/electronics/communication/serial/i2c/images/1-3.png new file mode 100644 index 0000000..51e0765 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/1-3.png differ diff --git a/docs/electronics/communication/serial/i2c/images/1-4.png b/docs/electronics/communication/serial/i2c/images/1-4.png new file mode 100644 index 0000000..8051b48 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/1-4.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-1.png b/docs/electronics/communication/serial/i2c/images/2-1.png new file mode 100644 index 0000000..8d63e52 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-1.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-10.png b/docs/electronics/communication/serial/i2c/images/2-10.png new file mode 100644 index 0000000..d6f8eb2 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-10.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-2.png b/docs/electronics/communication/serial/i2c/images/2-2.png new file mode 100644 index 0000000..d5b7c02 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-2.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-3.png b/docs/electronics/communication/serial/i2c/images/2-3.png new file mode 100644 index 0000000..c47fcb8 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-3.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-4.png b/docs/electronics/communication/serial/i2c/images/2-4.png new file mode 100644 index 0000000..78f9229 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-4.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-5.png b/docs/electronics/communication/serial/i2c/images/2-5.png new file mode 100644 index 0000000..24681f0 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-5.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-6.png b/docs/electronics/communication/serial/i2c/images/2-6.png new file mode 100644 index 0000000..5592d8d Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-6.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-7.png b/docs/electronics/communication/serial/i2c/images/2-7.png new file mode 100644 index 0000000..32123c4 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-7.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-8.png b/docs/electronics/communication/serial/i2c/images/2-8.png new file mode 100644 index 0000000..1309368 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-8.png differ diff --git a/docs/electronics/communication/serial/i2c/images/2-9.png b/docs/electronics/communication/serial/i2c/images/2-9.png new file mode 100644 index 0000000..c00bf16 Binary files /dev/null and b/docs/electronics/communication/serial/i2c/images/2-9.png differ diff --git a/docs/electronics/communication/serial/i2c/intro.md b/docs/electronics/communication/serial/i2c/intro.md new file mode 100644 index 0000000..3a8fa06 --- /dev/null +++ b/docs/electronics/communication/serial/i2c/intro.md @@ -0,0 +1,17 @@ +--- +title: I2C简介 +--- + +# I2C 简介 + +## 1. 什么是 I2C + +I2C(Inter-Integrated Circuit),也称为 IIC,\\(I^2C\\)是一种**同步**,**多主从设备**的一种**串行**通信方式,由 Philips 半导体公司发明于 1982 年。是继 UART 第二种更常用的单片机通信方式,UART 主要用于**板间通信**,如单片机和电脑,而 I2C 多用于**板内通信**,如单片机和其他传感器等。 + +I2C 最大的一个优势就是可以同时连接多个设备,每个设备都有自己独特的地址,主机通过这个地址和设备进行通信。而且其中任何一台设备都可以成为主机,也都可以相互通信,只要知道对方的地址即可。但任意时刻只能有一个主机。 + +## 2. I2C 的外设 + +一般 I2C 设备都需要在两条总线上添加上拉电阻组成一个**开漏电路**。由于所有的 SCL 都接在一起,所有的 SDA 都接在一起,因此,各个设备之间是‘与’关系。也就是说,只有所有的设备是高电平总线才是高电平,任何一个设备是低电平都会拉低总线,任何一个设备也就都能成为主机。 + +![I2C Bus](./images/0-1.png) diff --git a/docs/electronics/communication/serial/one-wire/chapter1.md b/docs/electronics/communication/serial/one-wire/chapter1.md new file mode 100644 index 0000000..680f88a --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/chapter1.md @@ -0,0 +1,21 @@ +--- +title: One-Wire通信原理 +--- + +# One-Wire 通信原理 + +## 1. 通信机制 + +不同的设备单总线通信机制大都不同,总的来说,单总线的基本的通信方式就是通过调整高低电平的时长来发送‘0’和‘1’,通过起始位和终止位来开始和结束通信。 + +因此在这里我们就不做理论上的介绍了,我们后面再具体问题具体分析。 + +## 2. 适用于哪些设备 + +常见的使用单总线的设备有以下几个: + +| DHT11 | WS2812 | IR Receiver | +| :------------------------: | :-------------------------: | :------------------------------: | +| ![DHT11](./images/1-1.png) | ![WS2812](./images/1-2.png) | ![IR Receiver](./images/1-3.png) | + +后面我们将具体学习 DHT11 的单总线通信方式。 diff --git a/docs/electronics/communication/serial/one-wire/chapter2.md b/docs/electronics/communication/serial/one-wire/chapter2.md new file mode 100644 index 0000000..c3c2501 --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/chapter2.md @@ -0,0 +1,53 @@ +--- +title: DHT11数据手册速览 +--- + +# DHT11 数据手册速览 + +## 1. DHT11 的基本参数 + +| 参数 | 参数值 | +| :------: | :---------: | +| 供电电压 | 3.3V-5.5V | +| 通信方式 | 单总线 | +| 湿度范围 | 20-90%±5%RH | +| 温度范围 | 0-50±2°C | + +## 2. DHT11 引脚接线图 + +DHT11 有四个引脚,其中一号引脚是电源接口(3.3-5.5V);二号引脚是数据输入输出接口,数据手册中建议加一个上拉电阻;三号引脚悬空不接;四号引脚接地。 +![接线图](./images/2-1.png) + +## 3. DHT11 通信中的‘0’和‘1’ + +根据数据手册的介绍,我们可以总结出 DHT11 发送‘0’和‘1’的规律如下: + +**50us 低电平 + 26-28us 的高电平 = ‘0’** + +**50us 的低电平 + 70us 的高电平 = ‘1’** + +![数据‘0’和’1‘](./images/2-2.png) + +## 4. DHT11 返回的数据包 + +DHT11 返回的数据包共有 5 个字节,即 40 位的数据,其中包括 16 位的湿度信息,16 位的温度信息以及 8 位的校验和信息。其中湿度和温度的第一个字节是整数位,第二个字节是小数位,每个字节都是高位在前,低位在后。最后一个字节是校验和,**校验和=湿度整数位+湿度小数位+温度整数位+温度小数位**。 + +下面是 DHT11 返回的数据包示意图: + +![DHT11数据格式](./images/2-3.png) + +虽然数据手册上表示湿度和温度的小数位都是 0,但是我在实际操作过程中发现湿度的小数位确实是 0,而温度的小数位不是总是 0,温度有小数位,精度为 0.1。正因如此,导致我在验证校验和的时候通过**湿度整数位+温度整数位!=校验和**总是返回**DHT11_ERROR**。 + +所以说权威也有错误的时候,我们需要有一颗会怀疑的心,想办法证明自己。 + +## 5. DHT11 和 MCU 的通信过程 + +第一步,信号线正常情况下处于被拉高状态,MCU 需要拉低信号线至少 18ms,再拉高至少 20-40us,发送开始信号,然后等待 DHT11 应答。 + +第二步,DHT11 接收到开始信号后会先拉低信号线 80us,再拉高 80us,表明 DHT11 准备好了,MCU 可以开始准备接受数据了。 + +第三步,接着 DHT11 将发送 40 位的数据包,发送完之后就再次把信号线拉高,通信结束。 + +值得注意的是,数据手册上说明了 DHT11 采集更新数据需要 2 秒钟,因此我建议每次和 DHT11 结束通信后至少**延时 3 秒钟**,保证数据的准确性。 + +![DHT11和MCU的通信过程](./images/2-4.png) diff --git a/docs/electronics/communication/serial/one-wire/chapter3.md b/docs/electronics/communication/serial/one-wire/chapter3.md new file mode 100644 index 0000000..209807a --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/chapter3.md @@ -0,0 +1,114 @@ +--- +title: 代码逻辑分析 +--- + +# 代码逻辑分析 + +## 1.第一步,定义获取高低电平时间的函数 + +在分析数据手册的时候我们会发现,获取高低电平的时间非常重要,因此我们可以先定义这两个函数**getLowTime()**,和**getHighTime()**: + +```cpp +uint32_t getLowTime() { + uint32_t lastTime = micros(); + while (!digitalRead(sensorPin)) { + if (micros() - lastTime > TIMEOUT) { + break; + } + } + return (micros() - lastTime); +} +``` + +```cpp +uint32_t getHighTime() { + uint32_t lastTime = micros(); + while (digitalRead(sensorPin)) { + if (micros() - lastTime > TIMEOUT) { + break; + } + } + return (micros() - lastTime); +} +``` + +**micros()** 的返回值是 unsigned long 也就是 uint32_t,单位是 us。 + +同时我们同时我们要保证程序不会因为硬件或者其他问题,使程序被长时间 block,因此我们加入**TIMEOUT**的判断,同时在后面我们还需要定义另外两个宏定义,这在后面会用到,即**DHT11_OKAY**和**DHT11_ERROR**。 + +```cpp +#define TIMEOUT 1e5 +#define DHT11_OKAY 0x00 +#define DHT11_ERROR 0x01 +``` + +## 2.第二步,MCU 和 DHT11 握手通信 + +首先由 MCU 发出开始信号,发送 18ms 的低电平,40us 的高电平。 + +```cpp +/*MCU发送开始信号, 并等待应答*/ +pinMode(sensorPin, OUTPUT); +//拉低18ms +digitalWrite(sensorPin, LOW); +delay(18); +//拉高40us +digitalWrite(sensorPin, HIGH); +delayMicroseconds(40); +``` + +然后 MCU 等待 DHT11 的应答,应答信号是 80us 的低电平和 80us 的高电平,这边我们不需要精确的时间,只需要等待高低电平过去。 + +```cpp +/*接受DHT11做出的应答*/ +pinMode(sensorPin, INPUT_PULLUP); +getLowTime(); +getHighTime(); +``` + +## 3. 第三步,接受来自 DHT11 的数据 + +接收 40 位的数据包,我们将其分 5 为个字节接收。 + +这里我们不需要知道低电平的时间,等待低电平过去就可以。但是需要知道高电平的时间,通过**50us 的阈值**来判断这一位是‘0’还是‘1’,因为‘0’的高电平是 26-28us,‘1’的高电平是 70us,因此我们大概取个中间值 50us。 + +因为 5 个字节都被初始化为‘0’,所以我们只需要移位和按位求和把‘1’加进去就好了。 + +```cpp +/*开始接受40bit数据*/ +uint8_t buffer[5] = {0}; +for (uint8_t i = 0; i < 5; i++) { + for (uint8_t j = 0; j < 8; j++) { + getLowTime(); + if (getHighTime() > 50) + buffer[i] |= (0x80 >> j); + } +} +``` + +结束通信。 + +```cpp +/*结束通信*/ +getLowTime(); +``` + +## 4. 第四步,检查校验和并更新数据 + +在更新数据前我们要保证数据的准确性,检查一下校验和。 + +```cpp +/*检查校验和*/ +if (buffer[0] + buffer[1] + buffer[2] + buffer[3] != buffer[4]) + return DHT11_ERROR; + +``` + +当数据准确无误后再更新数据,温度的小数位的精度是 0.1。 + +```cpp +/*更新数据*/ +humidity = buffer[0]; +temperature = buffer[2] + buffer[3] * 0.1; +return DHT11_OKAY; +``` diff --git a/docs/electronics/communication/serial/one-wire/chapter4.md b/docs/electronics/communication/serial/one-wire/chapter4.md new file mode 100644 index 0000000..afcbee8 --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/chapter4.md @@ -0,0 +1,98 @@ +--- +title: 整合打包代码 +--- + +# 整合打包代码 + +通过前面的分析,我们可以整合出如下 DHT11 完整的 One—Wire 通信代码: + +```cpp +#define TIMEOUT 1e5 +#define DHT11_OKAY 0x00 +#define DHT11_ERROR 0x01 + +uint8_t humidity; +float temperature; +uint8_t sensorPin = 2; + +void getLowTime(); +void getHighTime(); +void updateData(); + +void setup() { + Serial.begin(115200); +} + +void loop() { + switch (updataData()) { + case DHT11_ERROR: + Serial.println("DHT11 ERROR!"); + break; + case DHT11_OKAY: + Serial.print("DHT11 Okay,humidity is "); + Serial.print(humidity); + Serial.print(", temperature is "); + Serial.println(temperature); + break; + } + delay(3000); +} + +uint32_t getLowTime() { + uint32_t lastTime = micros(); + while (!digitalRead(sensorPin)) { + if (micros() - lastTime > TIMEOUT) { + break; + } + } + return (micros() - lastTime); +} + +uint32_t getHighTime() { + uint32_t lastTime = micros(); + while (digitalRead(sensorPin)) { + if (micros() - lastTime > TIMEOUT) { + break; + } + } + return (micros() - lastTime); +} + +bool updataData() { + /*MCU发送开始信号, 并等待应答*/ + pinMode(sensorPin, OUTPUT); + //拉低18ms + digitalWrite(sensorPin, LOW); + delay(18); + //拉高40us + digitalWrite(sensorPin, HIGH); + delayMicroseconds(40); + + /*接受DHT11做出的应答*/ + pinMode(sensorPin, INPUT_PULLUP); + getLowTime(); + getHighTime(); + + /*开始接受40bit数据*/ + uint8_t buffer[5] = {0}; + for (uint8_t i = 0; i < 5; i++) { + for (uint8_t j = 0; j < 8; j++) { + getLowTime(); + if (getHighTime() > 50) + buffer[i] |= (0x80 >> j); + } + } + + /*结束通信*/ + getLowTime(); + + /*检查校验和*/ + if (buffer[0] + buffer[1] + buffer[2] + buffer[3] != buffer[4]) + return DHT11_ERROR; + + /*更新数据*/ + humidity = buffer[0]; + temperature = buffer[2] + buffer[3] * 0.1; + return DHT11_OKAY; +} +``` diff --git a/docs/electronics/communication/serial/one-wire/chapter5.md b/docs/electronics/communication/serial/one-wire/chapter5.md new file mode 100644 index 0000000..464f0f2 --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/chapter5.md @@ -0,0 +1,36 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +## 1. 拓展一 + +通过实践可以发现,One—Wire 非常重要的就是获取高低电平的时间,其实红外接收管的的信号也是由**开始信号+用户码+用户码(用户反码)+按键码+按键反码+结束信号**组成,通信过程几乎一致,感兴趣的同学可以自行查阅相关资料对红外信号进行解码。 + +下面是 NEC 协议的红外遥控的方波图: + +![红外遥控方波图](./images/5-1.png) + +## 2. 拓展二 + +由于通信对时序的要求很高,我们在代码中使用的是**digitalWrite()** 和 **pinMode()** 两个 Arduino 已经为我们封装好的函数,其实更好的方法是通过寄存器进行操作,提高效率。但这样同时也会增加编程的难度。 + +感兴趣的同学可以参考下面的文章进行学习 [AVR I/O Register Configuration](https://exploreembedded.com/wiki/AVR_I/O_Register_Configuration)。 + +![AVR I/O Register Configuration](./images/5-2.png) + +## 3. 拓展三 + +前面我们学习的是接收 One-Wire 发送的数据包,而 One-Wire 的数据发送对时序的要求更高,我们一般不直接通过软件实现对数据的发送,而是结合相应的硬件发送数据,如 PWM,Timer,DMA,PIO 等等。 + +下面以 WS2812 和 DHT11 的时序作对比: + +| bit | WS2812 | DHT11 | +| :-: | :-----------------: | :-------------: | +| ‘0’ | 0.40us H + 0.85us L | 50us L + 27us H | +| ‘1’ | 0.80us H + 0.45us L | 50us L + 70us H | + +下面是 WS2812 的时序表,想挑战一下的同学可以尝试如何自己驱动点亮 WS2812: + +![](./images/5-3.png) diff --git a/docs/electronics/communication/serial/one-wire/images/0-1.png b/docs/electronics/communication/serial/one-wire/images/0-1.png new file mode 100644 index 0000000..a14fcef Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/0-1.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/1-1.png b/docs/electronics/communication/serial/one-wire/images/1-1.png new file mode 100644 index 0000000..38573d9 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/1-1.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/1-2.png b/docs/electronics/communication/serial/one-wire/images/1-2.png new file mode 100644 index 0000000..e3ea8c9 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/1-2.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/1-3.png b/docs/electronics/communication/serial/one-wire/images/1-3.png new file mode 100644 index 0000000..7328f42 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/1-3.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/2-1.png b/docs/electronics/communication/serial/one-wire/images/2-1.png new file mode 100644 index 0000000..303d881 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/2-1.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/2-2.png b/docs/electronics/communication/serial/one-wire/images/2-2.png new file mode 100644 index 0000000..9a24db2 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/2-2.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/2-3.png b/docs/electronics/communication/serial/one-wire/images/2-3.png new file mode 100644 index 0000000..c7fb42e Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/2-3.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/2-4.png b/docs/electronics/communication/serial/one-wire/images/2-4.png new file mode 100644 index 0000000..9cccbe7 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/2-4.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/5-1.png b/docs/electronics/communication/serial/one-wire/images/5-1.png new file mode 100644 index 0000000..87dd038 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/5-1.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/5-2.png b/docs/electronics/communication/serial/one-wire/images/5-2.png new file mode 100644 index 0000000..ec135bb Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/5-2.png differ diff --git a/docs/electronics/communication/serial/one-wire/images/5-3.png b/docs/electronics/communication/serial/one-wire/images/5-3.png new file mode 100644 index 0000000..b8cef01 Binary files /dev/null and b/docs/electronics/communication/serial/one-wire/images/5-3.png differ diff --git a/docs/electronics/communication/serial/one-wire/intro.md b/docs/electronics/communication/serial/one-wire/intro.md new file mode 100644 index 0000000..7b1b539 --- /dev/null +++ b/docs/electronics/communication/serial/one-wire/intro.md @@ -0,0 +1,17 @@ +--- +title: One-Wire简介 +--- + +# One-Wire 简介 + +## 1. 什么是单总线通信 + +单总线通信是单片机通信中最基础,接线最简单的一种通信方式,字面意思就是只通过一根线进行的通信方式,单片机只需要一个 IO 口就可以和设备进行通信。 + +也正因为如此,单总线通信也是其他通信当中通信速度最慢,最容易丢失数据,它的速度上限是**16.3 kbit/s**。并且没有相对统一标准的一种通信方式,因此大部分设备的硬件通信都需要手动编写。 + +## 2. 单总线通信的外设 + +正常情况下我们需要将通信接口拉高以保证数据的稳定。同时可能还需要在电源之间接一个电容进行滤波。 + +![拉高稳定信号](./images/0-1.png) diff --git a/docs/electronics/communication/serial/spi/chapter1.md b/docs/electronics/communication/serial/spi/chapter1.md new file mode 100644 index 0000000..f4d2d1e --- /dev/null +++ b/docs/electronics/communication/serial/spi/chapter1.md @@ -0,0 +1,257 @@ +--- +title: SPI通信原理 +--- + +# SPI 通信原理 + +## 1. SPI 引脚介绍 + +下面是典型的单主从机模式的 SPI 示意图: + +![SPI Circuit](./images/1-1.png) + +- **MOSI(Master In Slave Out)**——用于主机向从机发送数据。 +- **MISO(Master In Slave Out)**——用于从机向主机发送数据。 +- **SCK(Serial Clock)**——用于同步主从机的时钟频率。 +- **CS(Chip Select)**——也称 SS(Slave Select),用于主机选择需要通信的从机。 + +## 2. CPOL 和 CPHA + +前面讲到,SPI 的通信协议有着灵活的变种,这个变化主要是在时钟线上。即**CPOL(Clock Polarity)**和**CPHA(Clock Phase)**,也就是时钟极性和时钟相位。 + +CPOL 简单来说,就是时钟是在高电平时空闲还是在低电平时空闲。如果 CPOL = 0,则先出现上升沿,再出现下降沿;如果 CPOL = 1,先出现下降沿,再出现上升沿。大部分设备 CPOL = 0。 + +CPHA 简单来说,就是数据是否在时钟信号的上升沿或下降沿移入或移出。如果 CPHA=0,MOSI 和 MISO 在时钟线的上升沿读取数据,下降沿改变数据;如果 CPHA=1,MOSI 和 MISO 在时钟线的下降沿读取数据,上升沿改变数据。大部分设备 CPHA = 0。 + +根据 CPOL 和 CPHA 的不同,SPI 通信可以分为以下四类: + +![SPI模式示意图](./images/1-2.png) + +通常情况下,SPI 的通信模式为**模式 0**。 + +## 3. SPI 的通信过程 + +SPI 虽然不像 UART 那样,没有开始位和结束位;但是和 I2C 相似,有开始信号和结束信号。 + +开始信号,主机拉低 CS,表示通信开始。 + +结束信号,主机拉高 CS,表示通信结束。 + +一般在通信过程中,MOSI 和 MISO 可以不间断地传输数据,在这之间没有校验信息,通常都是**高位在前,低位在后**。 + +下面是模式 0 的 SPI 通信时域图: + +![SPI通信时域图](./images/1-3.png) + +## 4. SPI 的优缺点 + +SPI 通信的优点: + +- **没有开始位和结束位,可以连续发送数据** +- **没有复杂的设备地址,取而代之的是 CS** +- **MOSI 和 MISO 可以让设备同时收发送数据** +- **传输速度快,正常都可以达到 10MHz 甚至更高** + +SPI 通信的缺点: + +- **四根线,有的设备还有 D/C 线,RST 线等,接线相对复杂** +- **无 ACK 验证信息是否传输成功** +- **无校验信息** +- **只能有一个主机** + +## 5. Arduino 的 SPI 库 + +Arduino 的官方 SPI 库是`SPI.h`,Arduino Uno 只有一个 SPI 接口,即**MOSI—D11,MISO—D12,SCK—D13,CS—D10**,你可以使用任何 GPIO 作为 CS,不一定是 D10。 + +下面我们对 SPI 库部分重要的函数做简单讲解。 + +### 5.1 begin() + +语法: + +```cpp +SPI.begin(); +``` + +用于初始化 SPI。默认使用 SPI 模式 0,4MHz 时钟频率,拉高 CS,拉低 SCK 和 MOSI。 + +### 5.2 setsetClockDivider() + +语法: + +```cpp +SPI.setClockDivider(divider); +``` + +用来配置 SPI 的时钟频率,可选参数有: + +- **SPI_CLOCK_DIV2** +- **SPI_CLOCK_DIV4**(Default speed) +- **SPI_CLOCK_DIV6** +- **SPI_CLOCK_DIV8** +- **SPI_CLOCK_DIV16** +- **SPI_CLOCK_DIV32** +- **SPI_CLOCK_DIV64** +- **SPI_CLOCK_DIV128** + +### 5.3 setBitOrder() + +语法: + +```cpp +SPI.setBitOrder(order); +``` + +用来配置 SPI 的数据发送顺序,可选参数有: + +- **LSBFIRST** +- **MSBFIRST** + +### 5.4 setDataMode() + +语法: + +```cpp +SPI.setDataMode(mode); +``` + +用来配置 SPI 的模式,可选参数有: + +- **SPI_MODE0** +- **SPI_MODE1** +- **SPI_MODE2** +- **SPI_MODE3** + +### 5.5 beginTransaction() + +语法: + +```cpp +SPI.beginTransaction(mySettings); +``` + +用于配置更加详细的 SPI 参数,参数为**SPISettings**,建议不要同时使用`SPI.beginTransaction(mySettings)`和`SPI.setClockDivider`等,使用一个配置即可。 + +### 5.6 SPISettings + +语法: + +```cpp +SPISettings mySettting(speedMaximum, dataOrder, dataMode); +``` + +参数 speedMaximum:用于设置 SPI 时钟频率,默认为 4MHz,一般这个参数要小于单片机的时钟频率,Uno 板可以达到 14MHz。 + +参数 dataOrder:用于设置传输数据时,是低位在前还是高位在前,默认是高位在前。可选值有`MSBFIRST `和`LSBFIRST`。 + +参数 dataMode:用于设置 SPI 的通信模式,模式为前面介绍的 4 种模式,默认为模式 0。可选值有`SPI_MODE0`,`SPI_MODE1`,`SPI_MODE2`和`SPI_MODE3`。 + +一般设置为: + +```cpp +SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0)); +``` + +### 5.7 endTransaction() + +语法: + +```cpp +SPI.endTransaction(); +``` + +放在`SPI.beginTransaction(mySettings)`,用于结束 SPI 通信。 + +### 5.8 transfer(), transfer16() + +语法: + +```cpp +uint8_t receivedVal = SPI.transfer(val); +uint16_t receivedVal16 = SPI.transfer16(val16); +SPI.transfer(buffer, size); +``` + +用于发送和接收数据。参数和返回值的数据类型可以是`uint8_t `或者`uint16_t`,也可以发送一个数据类型为`uint8_t `的数组。 + +下面是一个使用`SPI.setClockDivider`的示例代码: + +```cpp +#include +#define CS 10 + +void setup() { + pinMode(CS, OUTPUT); + SPI.begin(); + SPI.setClockDivider(SPI_CLOCK_DIV2); + + // transfer data 128 + digitalWrite(CS, LOW); + SPI.transfer(128); + digitalWrite(CS, HIGH); +} + +void loop() { +} +``` + +下面是一个使用`SPISettings`的示例代码: + +```cpp + +#include + +// using two incompatible SPI devices, A and B. Incompatible means that they +// need different SPI_MODE +const int slaveAPin = 20; +const int slaveBPin = 21; +uint8_t stat, val1, val2, result; + +// set up the speed, data order and data mode +SPISettings settingsA(2000000, MSBFIRST, SPI_MODE1); +SPISettings settingsB(16000000, LSBFIRST, SPI_MODE3); + +void setup() { + // set the Slave Select Pins as outputs: + pinMode(slaveAPin, OUTPUT); + pinMode(slaveBPin, OUTPUT); + // initialize SPI: + SPI.begin(); +} + +void loop() { + // read three bytes from device A + SPI.beginTransaction(settingsA); + digitalWrite(slaveAPin, LOW); + // reading only, so data sent does not matter + stat = SPI.transfer(0); + val1 = SPI.transfer(0); + val2 = SPI.transfer(0); + digitalWrite(slaveAPin, HIGH); + SPI.endTransaction(); + // if stat is 1 or 2, send val1 or val2 else zero + if (stat == 1) { + result = val1; + } else if (stat == 2) { + result = val2; + } else { + result = 0; + } + // send result to device B + SPI.beginTransaction(settingsB); + digitalWrite(slaveBPin, LOW); + SPI.transfer(result); + digitalWrite(slaveBPin, HIGH); + SPI.endTransaction(); +} +``` + +## 6. 适用于哪些设备 + +常见的使用 SPI 的设备有以下几个: + +| SD Card Module | RFID Module | ST7735 TFT Display | +| :--------------------------: | :-----------------------: | :-------------------------: | +| ![SD Card](./images/1-4.png) | ![RFID](./images/1-5.png) | ![ST7735](./images/1-6.png) | + +下一个章节我们将详细学习如何通过 SPI 向 MPU9250 读写数据。 diff --git a/docs/electronics/communication/serial/spi/chapter2.md b/docs/electronics/communication/serial/spi/chapter2.md new file mode 100644 index 0000000..df54c59 --- /dev/null +++ b/docs/electronics/communication/serial/spi/chapter2.md @@ -0,0 +1,154 @@ +--- +title: MPU9250数据手册速览 +--- + +# MPU9250 数据手册速览 + +## 1. MPU9250 基本参数 + +| 参数 | 参数值 | +| :----------: | :--------------------------------------------: | +| 供电电压 | 2.4V-3.6V | +| 通信方式 | I2C 或者 SPI | +| 通信速度 | I2C 不大于 400KHz,SPI 不大于 1MHz | +| 9 轴传感器 | 3 轴陀螺仪,3 轴加速度计,3 轴磁力计 | +| 温度传感器 | -40°C 到 +125°C | +| FIFO | 512 字节的 FIFO | +| 中断 | 支持多种模式中断 | +| 外部震荡源 | 支持外部晶振 | +| I2C 主机模式 | MPU9250 支持作为 I2C 主机,可同时连接 4 台设备 | +| DMP | MPU9250 拥有一个 DMP,可以帮助处理内部数据 | + +## 2. MPU9250 引脚分布 + +| 引脚 | 介绍 | +| :-----------: | :---------------------------------------------------------: | +| AUX_CL,AUX_DA | MPU9250 作为 I2C 主机时的 I2C 总线 | +| VDD | 供电正极 | +| AD0/SDO | I2C 模式下作为 MPU9250 的 LSB 地址选项,SPI 模式下作为 MISO | +| FSYNC | 外部震荡源输入接口 | +| INT | 中断口 | +| nCS | SPI 模式下作为 CS | +| SCL/SCLK | I2C 或者 SPI 模式下的时钟线 | +| SDA/SDI | I2C 模式下数据口,SPI 模式下 MOSI | + +![MPU9250 Pinout](./images/2-1.png) + +由于我们这一章是介绍 SPI 的通信方式,因此我们要把 MPU9250 和 Arduino 通过 SPI 的方式连接。 + +接线图如下: + +![MPU9250 and Arduino](./images/2-2.png) + +## 3. MPU9250 的寄存器列表 + +虽然 MPU9250 有着很长的寄存器列表,但是如果我们不使用中断,不适使用外部震荡源,不使用 I2C 主机模式,不需要自我测试的情况下,MPU9250 使用还是非常简单的,只需要几个简单的配置就可以了。 + +值得注意的是,MPU9250 的寄存器默认值都为 0,除了以下两个: + +- 0x6B(默认值 0x01) +- 0x75(默认值 0x71) + +在下面的列表中,`*`表示这里不做介绍,具体内容可参考数据手册。下面我们介绍几个重要的寄存器。 + +### 3.1 寄存器 0x6B——PWR_MGMT_1 + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :-----: | :---: | :---: | :--: | :--: | :--: | :--: | :--: | +| 0x6B | PWR_MGMT_1 | H_RESET | SLEEP | CYCLE | \* | \* | \* | \* | \* | + +该寄存器用于配置 MPU9250 的模式,Bit7 用于重置设备,Bit6 用于唤醒 MPU9250。 + +由于 MPU650 在上电时会进入睡眠模式,因此为了兼容 MPU9250,建议上电时对此寄存器写入**0x00**。 + +### 3.2 寄存器 0x6A——USER_CTRL + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :-----: | :--------: | :-------: | :--: | :--: | :--: | :--: | +| 0x6A | USER_CTRL | \* | FIFO_EN | I2C_MST_EN | 2C_IF_DIS | \* | \* | \* | \* | + +该寄存器用于配置 MPU9250 的功能,Bit6 用于使能 FIFO,Bit5 用于使能 I2C 主机模式,**Bit4 用于使能 SPI 模式**。 + +由于我们适用 SPI 模式,因此需要写入 0x10,其他功能不需要使能。 + +### 3.3 寄存器 0x75——WHO_AM_I + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 0x75 | WHO_AM_I | \* | \* | \* | \* | \* | \* | \* | \* | + +该寄存器用于读取 MPU9250 的 ID 值,默认值为 0x71,可以用来验证通信是否成功。 + +### 3.4 寄存器 0x1A——CONFIG + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :--: | :----------: | :----------: | :----------: | :--: | :--: | :--: | +| 0x1A | CONFIG | \* | \* | FSYNC_SET[2] | FSYNC_SET[1] | FSYNC_SET[0] | \* | \* | \* | + +该寄存器用于配置 FSYNC 模式。 + +由于我们不使用 FSYNC,写入 0x00 即可。 + +### 3.4 寄存器 0x38——INT_ENABLE + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 0x38 | INT_ENABLE | \* | \* | \* | \* | \* | \* | \* | \* | + +用于配置中断,写入 0x00,不使用中断。 + +### 3.5 寄存器 0x1B——GYRO_CONFIG + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :--: | :--: | :--------: | :--------: | :--: | :--: | :--: | +| 0x1B | GYRO_CONFIG | \* | \* | \* | GYRO_FS[1] | GYRO_FS[0] | \* | \* | \* | + +该寄存器可以设置陀螺仪的测量范围,下面是范围设置表: + +| GYRO_FS[1] | GYRO_FS[0] | 范围 | +| :--------: | :--------: | :------- | +| 0 | 0 | ±250°/s | +| 0 | 1 | ±500°/s | +| 1 | 0 | ±1000°/s | +| 1 | 1 | ±2000°/s | + +### 3.6 寄存器 0x1C——ACCEL_CONFIG + +| Addr | Register Name | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | +| :--: | :-----------: | :--: | :--: | :--: | :---------: | :---------: | :--: | :--: | :--: | +| 0x1C | ACCEL_CONFIG | \* | \* | \* | ACCEL_FS[1] | ACCEL_FS[0] | \* | \* | \* | + +该寄存器可以设置加速度的测量范围,下面是范围设置表: + +| ACCEl_FS[1] | ACCEL_FS[0] | 范围 | +| :---------: | :---------: | :--- | +| 0 | 0 | ±2g | +| 0 | 1 | ±4g | +| 1 | 0 | ±8g | +| 1 | 1 | ±16g | + +### 3.7 寄存器 0x3B + +从 0x3B 到 0x40 这六个寄存器用于存储加速度 X 轴,Y 轴,Z 轴的信息,每轴都是**16 位的有符号 ADC 数据**,数值实际范围由 ACCEL_CONFIG 控制。 + +### 3.8 寄存器 0x43 + +从 0x43 到 0x48 这 6 个寄存器用于存储陀螺仪 X 轴,Y 轴,Z 轴的信息,每轴都是**16 位的有符号 ADC 数据**,数值实际范围由 GYRO_CONFIG 控制。 + +### 3.9 寄存器 0x41 + +从 0x41 到 0x42 这两个寄存器用于存储温度的信息,是一个**16 位的有符号数据**。 + +温度转换公式为:`TEMP_degC = ((TEMP_OUT –RoomTemp_Offset)/Temp_Sensitivity)+ 21degC` + +简化公式为:`TEMP_degC = (TEMP_OUT-0)/321.0 +21` + +### 4. MPU9250 的 SPI 通信 + +由 MPU9250 数据手册可知,其 SPI 通信的**CPOL=0,CPHA=0**,属于 SPI 的**模式 0**,最大通信速度时**1MHz**。 + +需要注意的是,MPU9250 的 SPI 通信需要在地址位的 MSB 加入读写位,1 表示读数据,0 表示写数据,示意图如下: + +![MPU SPI Protocol](./images/2-3.png) + +下一章,我们将学习如何通过 SPI 对 MPU9250 进行读写操作。 diff --git a/docs/electronics/communication/serial/spi/chapter3.md b/docs/electronics/communication/serial/spi/chapter3.md new file mode 100644 index 0000000..3a64b0b --- /dev/null +++ b/docs/electronics/communication/serial/spi/chapter3.md @@ -0,0 +1,166 @@ +--- +title: 代码逻辑分析 +--- + +# 代码逻辑分析 + +## 1. 使用到的寄存器 + +根据前面的介绍,我们需要用到以下几个寄存器: + +```cpp +#define MPU_FSYNC_ADDR 0x1A +#define MPU_GFS_ADDR 0x1B +#define MPU_AFS_ADDR 0x1C +#define MPU_EN_INT 0x38 +#define MPU_ACCEL_ADDR 0x3B +#define MPU_GYRO_ADDR 0x43 +#define MPU_TEMP_ADDR 0x41 +#define MPU_FIFO_EN 0x6A +#define MPU_RESET 0x6B +#define MPU_ID_ADDR 0x75 +``` + +同时由于 MPU9250 需要在地址中加入读写位,因此,还定义以下两个宏 + +```cpp +#define MPU_SPI_WRITE 0x00 +#define MPU_SPI_READ 0x80 +``` + +## 2. MPU9250 的 SPI 读写 + +根据前面的介绍,我们需要定义以下几个用于读写操作的函数: + +```cpp +void write_cmd(uint8_t cmd); +void write_cmd(uint8_t* cmd, uint8_t length); +void write_data(uint8_t data); +void write_data(uint8_t* data, uint8_t length); +uint8_t read_data(uint8_t data); +void read_data(uint8_t* data, uint8_t length); +``` + +由于 MPU9250 没有命令和数据的区分,因此我们统一认为是数据,同时在 MPU9250 中不需要连续写的操作,因此我们只需要定义以下三个读写函数: + +```cpp +void write_data(uint8_t data); +uint8_t read_data(); +void read_data(uint8_t* data, uint8_t length); +``` + +在这里,我们需要把对应的寄存器地址加上去,最终的函数声明如下: + +```cpp +void write_data(uint8_t addr, uint8_t data); +uint8_t read_data(uint8_t addr); +void read_data(uint8_t addr, uint8_t* data, uint8_t length); +``` + +根据 Arduino 的 SPI 的使用方法,我们可以将上面三个函数补全: + +```cpp +void write_data(uint8_t addr, uint8_t data) { + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_WRITE); + SPI.transfer(data); + digitalWrite(CS, HIGH); + SPI.endTransaction(); +} + +uint8_t read_data(uint8_t addr) { + uint8_t data; + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_READ); + data = SPI.transfer(0xFF); + digitalWrite(CS, HIGH); + SPI.endTransaction(); + return data; +} + +void read_data(uint8_t addr, uint8_t* data, uint8_t length) { + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_READ); + for (uint8_t i = 0; i < length; i++) { + data[i] = SPI.transfer(0xFF); + } + digitalWrite(CS, HIGH); + SPI.endTransaction(); +} +``` + +## 3. MPU9250 初始化 + +在读取 MPU9250 之前,我们需要对 MPU9250 进行简单的配置,比如唤醒 MPU,不使能某些功能,设置陀螺仪和加速度计的范围等等。 + +```cpp +// Wake up MPU +write_data(MPU_RESET, 0x00); +// Disable FIFO,Disable I2C +write_data(MPU_FIFO_EN, 0x10); +// Disable FSYNC +write_data(MPU_FSYNC_ADDR, 0x00); +// Disable interupt +write_data(MPU_EN_INT, 0x00); +// Set Gyroscope full scable range to ±250°/s +write_data(MPU_GFS_ADDR, 0x00); +// Set Accelerometer full scable range to ±2g +write_data(MPU_AFS_ADDR, 0x00); +``` + +可以发现其中很多的寄存器都是不需要配置,写入的数据都是 0x00,属于默认配置,但是这里列出来,方便大家以后的使用和更改。 + +前面我们介绍寄存器的时候有一个 ID 寄存器,这个寄存器可以帮助我们辨别 MPU9250 的初始化是否成功。 + +```cpp +// Check ID +if (read_data(MPU_ID_ADDR) == 0x71) + return true; +else + return false; +``` + +## 4. 读取 MPU9250 数据 + +完成了底层的函数,我们就可以从 MPU9250 读取数据了。 + +同样,我们先来读取温度。因为温度是一个有符号的 16 位数据,因此我们需要读两次,将两个`uint8_t`转换成一个`int16_t`,同时,温度的计算公式是:`TEMP_degC = (TEMP_OUT-0)/321.0 +21`。 + +```cpp + +float readTemp() { + int16_t temp; + uint8_t buffer[2]; + read_data(MPU_TEMP_ADDR, buffer, 2); + temp = buffer[0] << 8 | buffer[1]; // 第一个字节是高位,第二字节是低位 + return (temp / 321.0 + 21); // 温度计算公式:TEMP_degC = TEMP_OUT/321.0 +21 +} +``` + +这样我们就完成了读取温度的操作。读取陀螺仪和加速度的操作基本相同,只是他们都是 6 个字节,每个都要读取六次,这里我们只读取原始数据,不对数据进行加工。 + +```cpp +void readRawAccel(int16_t* accel) { + uint8_t buffer[6]; + read_data(MPU_ACCEL_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + accel[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} + +void readRawGyro(int16_t* gyro) { + uint8_t buffer[6]; + read_data(MPU_GYRO_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + gyro[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} +``` + +这样,我们就完成了 MPU9250 的所有数据读写操作,完整代码在下一章。 diff --git a/docs/electronics/communication/serial/spi/chapter4.md b/docs/electronics/communication/serial/spi/chapter4.md new file mode 100644 index 0000000..6d6cbd0 --- /dev/null +++ b/docs/electronics/communication/serial/spi/chapter4.md @@ -0,0 +1,151 @@ +--- +title: 整合打包代码 +--- + +# 整合打包代码 + +下面是完整的 MPU9250 SPI 通信代码: + +```cpp +#include + +#define MPU_FSYNC_ADDR 0x1A +#define MPU_GFS_ADDR 0x1B +#define MPU_AFS_ADDR 0x1C +#define MPU_EN_INT 0x38 +#define MPU_ACCEL_ADDR 0x3B +#define MPU_GYRO_ADDR 0x43 +#define MPU_TEMP_ADDR 0x41 +#define MPU_FIFO_EN 0x6A +#define MPU_RESET 0x6B +#define MPU_ID_ADDR 0x75 + +#define MPU_SPI_WRITE 0x00 +#define MPU_SPI_READ 0x80 + +#define CS 10 + +void write_data(uint8_t addr, uint8_t data); +uint8_t read_data(uint8_t addr); +void read_data(uint8_t addr, uint8_t* data, uint8_t length); +bool MPU_init(); +float readTemp(); +void readRawAccel(int16_t* accel); +void readRawGyro(int16_t* gyro); + +void setup() { + Serial.begin(115200); + if (MPU_init()) { + Serial.println("MPU init success!"); + } else { + Serial.println("MPU init failed!"); + } + Serial.println(); +} + +void loop() { + int16_t accel[3], gyro[3]; + readRawAccel(accel); + readRawGyro(gyro); + + Serial.print("Accel X="); + Serial.print(accel[0]); + Serial.print(",Y="); + Serial.print(accel[1]); + Serial.print(",Z="); + Serial.println(accel[2]); + + Serial.print("Gyro X="); + Serial.print(gyro[0]); + Serial.print(",Y="); + Serial.print(gyro[1]); + Serial.print(",Z="); + Serial.println(gyro[2]); + + Serial.print("Temp="); + Serial.println(readTemp(), 2); + Serial.println(); + delay(1000); +} + +void write_data(uint8_t addr, uint8_t data) { + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_WRITE); + SPI.transfer(data); + digitalWrite(CS, HIGH); + SPI.endTransaction(); +} + +uint8_t read_data(uint8_t addr) { + uint8_t data; + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_READ); + data = SPI.transfer(0xFF); + digitalWrite(CS, HIGH); + SPI.endTransaction(); + return data; +} + +void read_data(uint8_t addr, uint8_t* data, uint8_t length) { + // 设置MPU9250最大的时钟频率1MHz,高位在前,模式0 + SPI.beginTransaction(SPISettings(1000 * 1000, MSBFIRST, SPI_MODE0)); + digitalWrite(CS, LOW); + SPI.transfer(addr | MPU_SPI_READ); + for (uint8_t i = 0; i < length; i++) { + data[i] = SPI.transfer(0xFF); + } + digitalWrite(CS, HIGH); + SPI.endTransaction(); +} + +bool MPU_init() { + pinMode(CS, OUTPUT); + // 初始化SPI + SPI.begin(); + // Wake up MPU + write_data(MPU_RESET, 0x00); + // Disable FIFO,Disable I2C + write_data(MPU_FIFO_EN, 0x10); + // Disable FSYNC + write_data(MPU_FSYNC_ADDR, 0x00); + // Disable interupt + write_data(MPU_EN_INT, 0x00); + // Set Gyroscope full scable range to ±250°/s + write_data(MPU_GFS_ADDR, 0x00); + // Set Accelerometer full scable range to ±2g + write_data(MPU_AFS_ADDR, 0x00); + // Check ID, dafaule value is 0x71 + if (read_data(MPU_ID_ADDR) == 0x71) + return true; + else + return false; +} + +void readRawAccel(int16_t* accel) { + uint8_t buffer[6]; + read_data(MPU_ACCEL_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + accel[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} + +void readRawGyro(int16_t* gyro) { + uint8_t buffer[6]; + read_data(MPU_GYRO_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + gyro[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} + +float readTemp() { + int16_t temp; + uint8_t buffer[2]; + read_data(MPU_TEMP_ADDR, buffer, 2); + temp = buffer[0] << 8 | buffer[1]; // 第一个字节是高位,第二字节是低位 + return (temp / 321.0 + 21); // 温度计算公式:TEMP_degC = TEMP_OUT/321.0 +21 +} +``` diff --git a/docs/electronics/communication/serial/spi/chapter5.md b/docs/electronics/communication/serial/spi/chapter5.md new file mode 100644 index 0000000..052ced1 --- /dev/null +++ b/docs/electronics/communication/serial/spi/chapter5.md @@ -0,0 +1,155 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +## 1. 拓展 1 + +我们在这一章只是读取 MPU9250 的原始数据,没有读取到的进行加工。原因很简单,我对如何使用 MPU9250 的数据还不是很了解,但是,MPU 获取的数据,一般就是用来做**姿态解算**,通过陀螺仪的数据获取当前的 MPU 姿态,计算出`roll`,`yaw`和`pitch`。 + +其实在手机内部,游戏手柄,无人机等等都是通过 MPU 获取数据,然后进行姿态解算的。这属于算法范畴,涉及复杂的数学推理运算,在这里我就不做介绍,有能力的同学可以自行查找相关资料进行学习。 + +如果你想尝试 MPU 的姿态解算,可以使用[Jrowberg 的 GitHub 项目](https://github.com/jrowberg/i2cdevlib.git),里面包含了各个单片机平台的 MPU 库。 + +![MPU6050](./images/5-1.png) + +## 2. 拓展 2 + +MPU9250 和 MPU6050 在寄存器上几乎一致,只是存在些微差别,比如 ID 的默认值不同,计算温度的公式不同等等。MPU9250 的功能也要比 MPU6050 强大一些。MPU9250 是 9 轴的,支持 SPI;而 MPU6050 是 6 轴的,不支持 SPI。这也是我在这一章使用 MPU9250 的原因。 + +但是呢,通常情况下,我们都只使用 I2C 的通信模式,而不使用 SPI。 + +下面是 MPU9250 的 I2C 示例代码,大家可以参考一下: + +```cpp +#include + +#define MPU_ADDR 0x68 + +#define MPU_FSYNC_ADDR 0x1A +#define MPU_GFS_ADDR 0x1B +#define MPU_AFS_ADDR 0x1C +#define MPU_EN_INT 0x38 +#define MPU_ACCEL_ADDR 0x3B +#define MPU_GYRO_ADDR 0x43 +#define MPU_TEMP_ADDR 0x41 +#define MPU_FIFO_EN 0x6A +#define MPU_RESET 0x6B +#define MPU_ID_ADDR 0x75 + +void write_data(uint8_t addr, uint8_t data); +uint8_t read_data(uint8_t addr); +void read_data(uint8_t addr, uint8_t* data, uint8_t length); +bool MPU_init(); +float readTemp(); +void readRawAccel(int16_t* accel); +void readRawGyro(int16_t* gyro); + +void setup() { + Serial.begin(115200); + if (MPU_init()) { + Serial.println("MPU init success!"); + } else { + Serial.println("MPU init failed!"); + } +} + +void loop() { + int16_t accel[3], gyro[3]; + readRawAccel(accel); + readRawGyro(gyro); + + Serial.print("Accel X="); + Serial.print(accel[0]); + Serial.print(",Y="); + Serial.print(accel[1]); + Serial.print(",Z="); + Serial.println(accel[2]); + + Serial.print("Gyro X="); + Serial.print(gyro[0]); + Serial.print(",Y="); + Serial.print(gyro[1]); + Serial.print(",Z="); + Serial.println(gyro[2]); + + Serial.print("Temp="); + Serial.println(readTemp(), 2); + Serial.println(); + delay(1000); +} + +void write_data(uint8_t addr, uint8_t data) { + Wire.beginTransmission(MPU_ADDR); + Wire.write(addr); + Wire.write(data); + Wire.endTransmission(); +} + +uint8_t read_data(uint8_t addr) { + Wire.beginTransmission(MPU_ADDR); + Wire.write(addr); + Wire.endTransmission(); + Wire.requestFrom(MPU_ADDR, 1); + return Wire.read(); +} + +void read_data(uint8_t addr, uint8_t* dest, uint8_t length) { + Wire.beginTransmission(MPU_ADDR); + Wire.write(addr); + Wire.endTransmission(); + Wire.requestFrom(MPU_ADDR, length); + for (uint8_t i = 0; i < length; i++) { + dest[i] = Wire.read(); + } +} + +bool MPU_init() { + // 初始化I2C + Wire.begin(); + // 设置I2C频率为100KHz + Wire.setClock(100 * 1000); + // Wake up MPU from sleep mode + write_data(MPU_RESET, 0x00); + // Disable FIFO + write_data(MPU_FIFO_EN, 0x00); + // Disable FSYNC + write_data(MPU_FSYNC_ADDR, 0x00); + // Disable interupt + write_data(MPU_EN_INT, 0x00); + // Set Gyroscope full scable range to ±250°/s + write_data(MPU_GFS_ADDR, 0x00); + // Set Accelerometer full scable range to ±2g + write_data(MPU_AFS_ADDR, 0x00); + // Check ID + if (read_data(MPU_ID_ADDR) == 0x71) + return true; + else + return false; +} + +void readRawAccel(int16_t* accel) { + uint8_t buffer[6]; + read_data(MPU_ACCEL_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + accel[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} + +void readRawGyro(int16_t* gyro) { + uint8_t buffer[6]; + read_data(MPU_GYRO_ADDR, buffer, 6); + for (uint8_t i = 0; i < 3; i++) { + gyro[i] = buffer[i * 2] << 8 | buffer[i * 2 + 1]; // 第一个字节是高位,第二字节是低位 + } +} + +float readTemp() { + int16_t temp; + uint8_t buffer[2]; + read_data(MPU_TEMP_ADDR, buffer, 2); + temp = buffer[0] << 8 | buffer[1]; // 第一个字节是高位,第二字节是低位 + return (temp / 321.0 + 21); // 温度计算公式:TEMP_degC = TEMP_OUT/321.0 +21 +} +``` diff --git a/docs/electronics/communication/serial/spi/images/0-1.png b/docs/electronics/communication/serial/spi/images/0-1.png new file mode 100644 index 0000000..78f86b0 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/0-1.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-1.png b/docs/electronics/communication/serial/spi/images/1-1.png new file mode 100644 index 0000000..5f808a2 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-1.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-2.png b/docs/electronics/communication/serial/spi/images/1-2.png new file mode 100644 index 0000000..7640697 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-2.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-3.png b/docs/electronics/communication/serial/spi/images/1-3.png new file mode 100644 index 0000000..3e2b677 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-3.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-4.png b/docs/electronics/communication/serial/spi/images/1-4.png new file mode 100644 index 0000000..5e50b84 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-4.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-5.png b/docs/electronics/communication/serial/spi/images/1-5.png new file mode 100644 index 0000000..f5df921 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-5.png differ diff --git a/docs/electronics/communication/serial/spi/images/1-6.png b/docs/electronics/communication/serial/spi/images/1-6.png new file mode 100644 index 0000000..e03d3ed Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/1-6.png differ diff --git a/docs/electronics/communication/serial/spi/images/2-1.png b/docs/electronics/communication/serial/spi/images/2-1.png new file mode 100644 index 0000000..573ed63 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/2-1.png differ diff --git a/docs/electronics/communication/serial/spi/images/2-2.png b/docs/electronics/communication/serial/spi/images/2-2.png new file mode 100644 index 0000000..faa9fd5 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/2-2.png differ diff --git a/docs/electronics/communication/serial/spi/images/2-3.png b/docs/electronics/communication/serial/spi/images/2-3.png new file mode 100644 index 0000000..e08de8b Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/2-3.png differ diff --git a/docs/electronics/communication/serial/spi/images/5-1.png b/docs/electronics/communication/serial/spi/images/5-1.png new file mode 100644 index 0000000..f69d259 Binary files /dev/null and b/docs/electronics/communication/serial/spi/images/5-1.png differ diff --git a/docs/electronics/communication/serial/spi/intro.md b/docs/electronics/communication/serial/spi/intro.md new file mode 100644 index 0000000..470d362 --- /dev/null +++ b/docs/electronics/communication/serial/spi/intro.md @@ -0,0 +1,19 @@ +--- +title: SPI简介 +--- + +# SPI 简介 + +## 1. 什么是 SPI 通信 + +SPI(Serial Peripheral Interface)是一种同步串行通信,由**Motorola**公司发明于 1980s,同样是单片机通信当中非常常用的通信方式之一。SPI 和 I2C 的最大的不同就在于 SPI 的**Peripheral**,为什么一个串行通信会有并行这个概念,那是因为 SPI 在通信过程中,主从机可以同时交换数据,也就是**Peripheral**的由来。 + +SPI 在通信协议上有着比较灵活的变种,SPI 没有字节数的限制,除了 8 位,还有 12 位,16 位等等。有的 SPI 设备是**只读**,有的是**只写**,也就是说这些设备只能被写入数据,有的只能被读取数据,这时候就由标准的 4-wire 变成了 3-wire。在时钟线上也有很多变化,后面我再详细介绍。 + +## 2. SPI 的外设 + +SPI 几乎不需要什么外设,这也降低了 SPI 通信的功耗,只是 SPI 的接线要比 I2C 复杂一些。同时 SPI 也支持同时连接多个设备,只是同时也只能有一个主机,一般是 MCU。 + +下面是标准的 SPI 通信示意图: + +![SPI Bus](./images/0-1.png) diff --git a/docs/electronics/communication/serial/uart/chapter1.md b/docs/electronics/communication/serial/uart/chapter1.md new file mode 100644 index 0000000..1308f30 --- /dev/null +++ b/docs/electronics/communication/serial/uart/chapter1.md @@ -0,0 +1,61 @@ +--- +title: UART通信原理 +--- + +# UART 通信原理 + +## 1. 数据包 + +在串口通信中,数据包的格式是**起始位+数据帧(+校验位)+停止位**组成的。 + +下面是串口通信数据包的示意图: + +![串口通信数据包的示意图](./images/1-1.png) + +下面我们对这四个组成部分做一一解释。 + +### 1.1 起始位 + +在串口通信中,正常情况下,TX 和 RX 都是保持在**高电平**状态,如果需要发送数据,那么就把 TX 总线拉低,RX 发现 TX 被拉低之后就知道 TX 这边准备发送数据了,下面就是准备接收这个数据。 + +### 1.2 数据帧 + +数据帧就是需要发送的数据,一般情况下都是**8 位**,也就是一个字节,但是你也可以发送 5 位,6 位或者 7 位的数据,只要双方是相同的配置就可以。在没有校验位的情况下,也可以发送 9 位的数据。在数据发送过程中是 **从低位(LSB)向高位(MSB)** 进行发送。 + +### 1.3 校验位 + +校验位也称**奇偶校验位**,那是因为 UART 中,为了验证数据传输无误,通过计算‘1’的个数来校验数据,在使用`偶校验`的情况下,如果这一数据帧中**有偶数个‘1’,则这一位为 0,有奇数个‘1’,则这一位为 1**;反之同理。所以称之为奇偶校验位。大多数情况下都不使用校验位。 + +### 1.4 停止位 + +当数据传输结束后,TX 总线会拉高 1-2 位的时间,正常情况都是**1 位**时间,停止位结束后才会进行下一轮的数据发送或者一直保持在高电平,结束通信。 + +## 2. 波特率 + +前面说到,UART 是异步通信,所谓异步,也就是说在 UART 中没有 Clock 线,不能在通信双方之间保持同步的数据收发,而是通过间隔一定时间对数据进行采样。这个`一定的时间`也就是**波特率**,也称之为**比特率**,即发送一位所需要的时间。 + +常用的波特率有 300,600,1200,2400,4800,9600,14400,19200,28800,38400,57600,115200 等。在没有校验位,数据帧为 8 位,停止位为 1 位的情况下,我们发送一个数据包就是 10 位,如果使用的是 9600 波特率,所需要的时间就是\\(\frac{1}{9600}\*10\approx1.0416ms \\)。 + +下面是串口通信的时域图: + +![串口通信的时域图](./images/1-2.png) + +由于是异步通信,数据采样就难免会有误差,为了减小这个误差,实际采样检测到开始位之后,等待半个位时间(0.5\*1/Baudrate)后,以此为基准进行采样,这样可以保证采样点可以在每一位的中间。 + +波特率设置得越高,采样出错的可能性越大。 + +## 3. Arduino 的 Serial 库 + +再 Arduino 中,`Serial.begin()`的第一个参数用来设置波特率,它还有第二个可选参数,可以用来设置发送的数据格式,可以选择几位数据帧,是否需要校验位(以及是奇数校验还是偶数校验),几位停止位等等。 + +有关 Arduino 的 Serial 库的更多详细内容,可以参考前面的章节([Arduino 基础/第八章——串口通信 UART](../../../arduino/chapter8.md)) + +## 4. 适用于那些设备 + +常见的使用串口通信的设备有以下几个: + +| HC-05 | ESP-01 | 电脑 | +| :------------------------: | :-------------------------: | :------------------------ | +| ![HC-06](./images/1-3.png) | ![ESP-01](./images/1-4.png) | ![电脑](./images/1-5.png) | + +我们会在下面的章节详细介绍 HC-05 蓝牙模块的使用。 diff --git a/docs/electronics/communication/serial/uart/chapter2.md b/docs/electronics/communication/serial/uart/chapter2.md new file mode 100644 index 0000000..eea5083 --- /dev/null +++ b/docs/electronics/communication/serial/uart/chapter2.md @@ -0,0 +1,56 @@ +--- +title: HC-05数据手册速览 +--- + +# HC-05 数据手册速览 + +## 1. HC-05 基本参数 + +| 参数 | 参数值 | +| :----------: | :------------: | +| 供电电压 | 1.8V-3.6V | +| 通信方式 | UART | +| 默认波特率 | 38400 | +| 默认配对密码 | 1234 | +| 默认设备名 | H-C-2010-06-01 | +| 默认模式 | 从机模式 | + +## 2. HC-05 接线图 + +在使用蓝牙前,我们需要通过 USB 转串口模块对 HC-05 做一些配置。注意`RX-TX`的接线方式。同时我们使用的是模块,模块上有降压芯片,因此可以直接 5V 供电。 + +接线图如下: + +![HC-05接线图](./images/2-1.png) + +## 3. HC-05 的 AT 设置 + +HC-05 使用**AT 命令集**,也称**贺氏命令集**,是一种用于**本地命令控制**的命令集。 + +HC-05 如果要进入 AT 模式,需要在断电的情况下,按住模块上的按钮,通电后松开就可以了。 + +HC-05 使用的默认波特率为 38400,8N1 数据包。在向 HC-05 发送命令时,需要在每个命令结尾加上`\r\n`。如果你使用的是 Arduino 的串口助手,则可以在右下方选择`Both NL & CR`。 + +![Both NL & CR](./images/2-2.png) + +下面是几个 HC-05 常用的的 AT 命令表,更多命令可以参考数据手册: + +| AT 命令 | 解释 | +| :---------------------------------: | :------------------------------: | +| AT | 用于测试,HC05 回复 OK | +| AT+RESET | 软重启,重启后会自动推出 AT 模式 | +| AT+VERSION? | 检查 HC-05 固件版本 | +| AT+ORGL | 重置设备,参数均重置为默认值 | +| AT+ADDR? | 查看 HC-05MAC 地址 | +| AT+NAME=**name** | 用于设置设备名称 | +| AT+NAME? | 查看设备名称 | +| AT+ROLE=**role** | 设置模块主从模式,0=从机,1=主机 | +| AT+ROLE? | 查看模块主从模式 | +| AT+PSWD=**password** | 设置设备连接密码,密码为四位数字 | +| AT+PSWD? | 查看设备连接密码 | +| AT+UART=**baudrate,stopbit,parity** | 设置模块 UART 配置参数 | +| AT+UART? | 查看模块 UART 参数 | + +总的来说,HC-05 的使用非常简单,UART 通讯也非常简单,是五种通讯当中最为简单的一个。 + +下一章节我们学习以下如何通过 HC-05 蓝牙模块控制板载 LED。 diff --git a/docs/electronics/communication/serial/uart/chapter3.md b/docs/electronics/communication/serial/uart/chapter3.md new file mode 100644 index 0000000..dc55385 --- /dev/null +++ b/docs/electronics/communication/serial/uart/chapter3.md @@ -0,0 +1,82 @@ +--- +title: HC-05蓝牙的使用 +--- + +# HC-05 蓝牙的使用 + +## 1. 配置 HC-05 + +在使用前,我们先对 HC-05 做简单的配置。 + +首先更改设备名,我将其改为**LED Controller**,设备名大家随意就好,不一定需要一样。 + +其次是 UART 参数,我将其改为**38400,1,0**,这样主要是方便进入 AT 模式。 + +其他参数也可以根据自己需要更改,这边我只更改这两个。 + +## 2. HC-05 接线图 + +下面是 HC-05 和 Arduino Uno 的接线图,接线方式与前面相似,注意 RX-TX。 + +![HC-05和Arduino Uno的接线图](./images/3-1.png) + +## 3. 编写代码 + +对于这个项目来说,我们最终的目标是,手机连接蓝牙后,我们发送`字符1`或者`字符0`的命令给 HC-05,HC-05 接收到后就相应地打开或者关闭板载 LED。 + +### 3.1 代码 + +由于这个项目的代码比较简单,因此在这里,我不做特别讲解,下面是示例代码: + +```cpp +const uint8_t LED = 13; + +void setup() { + pinMode(LED, OUTPUT); + Serial.begin(38400); +} + +void loop() { + // 串口有缓存的数据 + if (Serial.available()) { + // 读取数据,记住是字符类型,不是数字类型 + char message = Serial.read(); + // 字符'1',打开LED;字符'0',关闭LED + if (message == '1') { + digitalWrite(LED, HIGH); + } + else if (message == '0') { + digitalWrite(LED, LOW); + } + } +} +``` + +上传代码前,记得要先拔掉 HC-05,不然串口被占用,无法上传代码。 + +### 3.2 软件 + +一般呢,我们不直接自己制作相应的软件,下面是一个我认为比较简单实用的软件,大家可以点击连接下载安装。 + + + +下面我教大家如何使用这个软件。 + +第一步,连接蓝牙后,打开软件,选择`Switch Mode`: + +![Switch Mode](./images/3-2.png) + +第二步,点击右上角`小齿轮`,进入设置: + +![小齿轮](./images/3-3.png) + +第三步,`设置相应的参数`: + +![设置参数](./images/3-4.png) + +最后点击左上角的开关返回就可以使用了,大家尝试点击一下,观察 LED 是不是可以被点亮和关闭。 + +该软件还有其他非常实用的功能,大家可以自行探索使用。 diff --git a/docs/electronics/communication/serial/uart/chapter4.md b/docs/electronics/communication/serial/uart/chapter4.md new file mode 100644 index 0000000..dea3694 --- /dev/null +++ b/docs/electronics/communication/serial/uart/chapter4.md @@ -0,0 +1,126 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +## 1. 总结 + +整体来说,不论是蓝牙的使用还是 UART 的使用,在单片机中都非常地简单,没有什么复杂的寄存器操作和配置等。在 Arduino 框架下,你只要会用`Serial`,一般就可以用好蓝牙,其他框架也类似,使用起来不会很难。 + +其实 Serial 还有其他几个重要的函数,如果用得好,可以实现很多实用的功能。 + +下面是一个前面我讲到的 WS2812 灯板,有关蓝牙部分的代码,大家可以参考一下: + +```cpp +void getSerial() { + if (Serial.available()) { + uint8_t message = Serial.read();// 获取第一个命令 + uint8_t parameter = Serial.parseInt();// 获取第一个命令的参数 + + /* 设置模式的命令 */ + if (message == 'M' || message == 'm') { + switch (parameter) { + case 0: Mode = 0; break; + case 1: Mode = 1; break; + case 2: Mode = 2; break; + case 3: Mode = 3; break; + } + } + + /* 打开/关闭灯板的命令 */ + else if (message == 'S' || message == 's') { + switch (parameter) { + case 0: Dispaly_Status = false; break; + default: Dispaly_Status = true; break; + } + } + + /* 设置灯板亮度的命令 */ + else if (message == 'B' || message == 'b') { + BRIGHTNESS = parameter; + if (Mode == 0 || Mode == 2) { + FastLED.setBrightness(BRIGHTNESS); + } + } + + /* 设置显示时间颜色的命令 */ + else if (message == 'R' || message == 'r') { + R = parameter; + G = Serial.parseInt(); + B = Serial.parseInt(); + } + + /* 设置时间的命令 */ + else if (message == 'H' || message == 'h') { + if (Mode == 2) { + Hour = parameter; + Minute = Serial.parseInt(); + SetTime(); + } + } + } +} +``` + +## 2. 拓展 + +其实,在学完有关 UART 的通信原理之后,我们是完全可以自己写一个 UART 通信的底层驱动的,我们一般称之为`软串口`,因为相对于硬件支持的串口通信,通过软件编写的串口通信一般速度会低一些,同时在时序上也没有硬件支持的串口通信精准、稳定。不过我们常用的波特率使用起来是没有问题的。 + +在 Arduino 框架中,有一个支持的官方库`SoftwareSerial.h`,它的使用和`Serial`几乎一致,而且它支持使用任何引脚作为 TX 和 RX 引脚。支持常用的波特率,最高可以设置 115200。你可以创建多个软串口,但同时只能使用一个。 + +下面是一个使用了软串口的实例: + +```cpp +#include + +SoftwareSerial serial(10, 11); // RX:10,TX:11 +void setup(){ + serial.begin(38400); +} + +void loop(){ + serial.println("Hello world!"); + delay(1000); +} +``` + +其实呢,通过软串口,我们可以把 HC-05 接到其他引脚上,这样我们就可以不占用硬件串口,不需要每次更新代码都把 HC-05 拔下来。 + +同时呢,我们还可以**通过软串口实现 AT 配置**,也就是通过读取硬件串口的数据,然后借助软件串口将命令发送给 HC-05。这样我们就**不需要 USB 转串口**,直接使用 Arduino Uno 实现 AT 命令配置。这样就使得整个过程变得简单很多。 + +下面是一个我使用的借助软串口发送 AT 命令给 HC-05 的代码: + +```cpp +#include + +SoftwareSerial serial(2, 3); // RX:2,TX:3 + +void setup() { + Serial.begin(38400); + serial.begin(38400); + while (!Serial); +} + +void loop() { + /* 读取硬件串口的AT命令 */ + if (Serial.available()) { + String message = ""; + while (Serial.available()) { + message += (char)Serial.read(); + } + // 发送命令给软串口,也就是发送给HC-05 + serial.print(message); + } + + /* 读取软串口,也就是HC-05的应答数据 */ + if (serial.available()) { + // 读取HC-05数据,同时发送到硬件串口 + String message = ""; + while (serial.available()) { + message += (char)serial.read(); + } + Serial.print(message); + } +} +``` diff --git a/docs/electronics/communication/serial/uart/images/0-1.png b/docs/electronics/communication/serial/uart/images/0-1.png new file mode 100644 index 0000000..6588a5b Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/0-1.png differ diff --git a/docs/electronics/communication/serial/uart/images/0-2.png b/docs/electronics/communication/serial/uart/images/0-2.png new file mode 100644 index 0000000..e8702d9 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/0-2.png differ diff --git a/docs/electronics/communication/serial/uart/images/1-1.png b/docs/electronics/communication/serial/uart/images/1-1.png new file mode 100644 index 0000000..9cee09c Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/1-1.png differ diff --git a/docs/electronics/communication/serial/uart/images/1-2.png b/docs/electronics/communication/serial/uart/images/1-2.png new file mode 100644 index 0000000..9bdf82b Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/1-2.png differ diff --git a/docs/electronics/communication/serial/uart/images/1-3.png b/docs/electronics/communication/serial/uart/images/1-3.png new file mode 100644 index 0000000..c2f0bde Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/1-3.png differ diff --git a/docs/electronics/communication/serial/uart/images/1-4.png b/docs/electronics/communication/serial/uart/images/1-4.png new file mode 100644 index 0000000..1a20343 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/1-4.png differ diff --git a/docs/electronics/communication/serial/uart/images/1-5.png b/docs/electronics/communication/serial/uart/images/1-5.png new file mode 100644 index 0000000..6dae56d Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/1-5.png differ diff --git a/docs/electronics/communication/serial/uart/images/2-1.png b/docs/electronics/communication/serial/uart/images/2-1.png new file mode 100644 index 0000000..191f6e3 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/2-1.png differ diff --git a/docs/electronics/communication/serial/uart/images/2-2.png b/docs/electronics/communication/serial/uart/images/2-2.png new file mode 100644 index 0000000..12ff737 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/2-2.png differ diff --git a/docs/electronics/communication/serial/uart/images/3-1.png b/docs/electronics/communication/serial/uart/images/3-1.png new file mode 100644 index 0000000..3b985e1 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/3-1.png differ diff --git a/docs/electronics/communication/serial/uart/images/3-2.png b/docs/electronics/communication/serial/uart/images/3-2.png new file mode 100644 index 0000000..f2cd9d0 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/3-2.png differ diff --git a/docs/electronics/communication/serial/uart/images/3-3.png b/docs/electronics/communication/serial/uart/images/3-3.png new file mode 100644 index 0000000..b8d069a Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/3-3.png differ diff --git a/docs/electronics/communication/serial/uart/images/3-4.png b/docs/electronics/communication/serial/uart/images/3-4.png new file mode 100644 index 0000000..8ef4883 Binary files /dev/null and b/docs/electronics/communication/serial/uart/images/3-4.png differ diff --git a/docs/electronics/communication/serial/uart/intro.md b/docs/electronics/communication/serial/uart/intro.md new file mode 100644 index 0000000..23ddb51 --- /dev/null +++ b/docs/electronics/communication/serial/uart/intro.md @@ -0,0 +1,25 @@ +--- +title: UART简介 +--- + +# UART 简介 + +## 1. 什么是 UART 通信 + +UART(Universal Asynchronous Receiver/Transmitter)指的是**通用异步收发器**。 + +串口通信是单片机最为常用的一种通信方式,通常用于单片机和单片机,单片机和电脑之间的通信。在串口通信中,数据是使用单线逐位传输的。在双向通信中,只需要两条线就可以传输数据。根据应用和系统要求,串口通信需要的电路和接线更少,从而成为成本低廉,应用广泛的一种通信方式。 + +## 2. 串口通信的外设 + +在单片机和单片机之间,我们可以通过连接 RT-TX,TX-RX 的方式直接进行通信。 + +下面是典型的接线示意图: + +![UART接线示意图](./images/0-1.png) + +而电脑和单片机之间的通信就需要另外的外设。因为现在大部分的电脑以不再使用笨重的 9 针串行接口,更多的是使用 USB 虚拟串口,因此单片机和电脑通信都需要一个**USB 转串口芯片**,通常在单片机开发板上都是做好这样的外设的。 + +下面是一个典型的 CH340C 原理图: + +![CH340C](./images/0-2.png) diff --git a/docs/electronics/projects/cnc-engraver/chapter1.md b/docs/electronics/projects/cnc-engraver/chapter1.md new file mode 100644 index 0000000..c0199a5 --- /dev/null +++ b/docs/electronics/projects/cnc-engraver/chapter1.md @@ -0,0 +1,150 @@ +--- +title: 什么是CNC +--- + +# 什么是 CNC + +雕刻机其实也是一种数控技术,所谓数控就是`CNC(computer numerical control)`,主要是用于自动化、机械化控制应用。CNC 一般是高精度的代名词,可以做出非常多酷的机器,包括写字机,雕刻机,3D 打印机,机械臂,铣床等等。这些机器的背后都是精确的数字化控制。 + +CNC 机器主要由三个部分组成,包括`硬件(hardware)`,`固件(firmware)`和`软件(software)`。 + +硬件就是机器本体,固件是连接硬件和软件的桥梁,软件可以通过先固件发送指令,然后固件指导硬件运动。 + +下面我简单介绍一下这三个部分。 + +## 1. 硬件 + +CNC 机器有很多种结构类型,我介绍几种常见的类型。 + +### 1.1 XYZ 型 + +大部分的 CNC 机器都是`XYZ型`的,也就是可以在 X,Y 及 Z 轴独立运动的机器,常见的 3D 打印机都是这种类型,同时我设计的雕刻机也是 XY 类型,这种机器控制起来比较方便。 + +![XY CNC machine](./images/1-1.png) + +### 1.2 SCARA robot + +还有一种结构的 CNC 机器在制作机械臂方面应用比较广泛,一般称之为`SCARA robot`,这种结构的机器运动方式比较简单,搭建也很容易。 + +![Scara Robot](./images/1-3.png) + +### 1.3 CoreXY 类型 + +另外还有一种移动快速的`CoreXY类型`,这种类型的机器在运动的时候 X 轴和 Y 轴会相互牵连运动,从一点到另一点的也运动更加快速。这种结构在 DIY CNC 机器中使用比较多,商业化的很少。 + +![CoreXY CNC machine](./images/1-2.png) + +### 1.4 Delta Robot + +最后还有一种运动更加高效的 CNC 机器,叫做`Delta Robot`,这种结构的机器在 3D 打印机和工厂的流水线上都有使用,主要优点就是效率高,结构简单。 + +![Delata Robot](./images/1-4.png) + +## 2. 固件 + +固件在 CNC 机器当中也可以称之为系统,比如日本的`FANUC系统`,德国的`西门子系统`,但是这些都属于商业工业用途的系统,DIY 爱好者还是倾向于一些开源的固件。 + +其中比较流行的两个固件是[Grbl 固件](https://github.com/grbl/grbl.git)和[Marlin 固件](https://github.com/MarlinFirmware/Marlin.git),这两个固件都相当地有名,有些 3D 打印机就是使用的 Marlin 固件,激光雕刻机使用 GRBL 固件比较多。大家可以前往 GitHub 详细了解这两个开源项目。 + +下面我介绍一下 GRBL 这个开源项目。 + +### 2.1 GRBL + +GRBL 这个项目最初是由`Sungeon (Sonny) Jeon`编写的,并且原项目是专门为 Arduino 的 AVR 芯片设计的,因此几乎每一个学习 Arduino 的人最终都会了解到这个项目。目前这个项目已经更新到 1.1 版本。 + +![GRBL](./images/1-5.png) + +GRBL 目前有以下几个官方合作的项目: + +![OFFICAL GRBL PROJECT](./images/1-6.png) + +因为 GRBL 固件的流行,后来在 GRBL 的基础上逐渐衍生出其他功能更强大的开源项目,知名的项目有以下三个: + +- [GRBL_ESP32](https://github.com/bdring/Grbl_Esp32.git) +- [grbl32](https://github.com/pvico/grbl32.git) +- [grblHALL](https://github.com/terjeio/grblHAL.git) + +其中 GRBL_ESP32 主要是为 ESP32 设计的 GRB 了固件,支持脱机运行,WIFI 连接,蓝牙连接等强大的功能。grbl32 是为国内设计的廉价的 STM32“蓝板”设计的,grblHALL 同样是为了 STM32 芯片设计的 CNC 固件,支持的板子大部分的 STM32 开发板,功能也更加大,可以媲美大部分商业固件。有兴趣的可以前往这些项目的主页看一下。 + +不论是什么固件,它们都支持一种广泛用于 CNC 机器的编程语言,叫做`G-code`,也叫 G 代码。下面我简单介绍一下 G 代码。 + +### 2.2 G-code + +之所以称之为 G-code,是因为这种代码主要都是以 G 开头。大部分情况下我们不需要自己去写 G 代码,G 代码一般都是软件直接生成的,你可以根据自己的需求对其中部分代码做改动,但前提是要懂得 G 代码的基本语法。 + +其实将 G 代码称之为 G 命令更妥当,下面是几个常用的命令: + +| G 命令 | 解释 | +| :----: | :--------------: | +| G0 | 快速线性运动 | +| G1 | 指定速度线性运动 | +| G20 | 英寸为单位 | +| G21 | 毫米为单位 | +| G0 | 快速线性运动 | +| M3 | 钻头顺时针运动 | +| M4 | 钻头逆时针运动 | +| M5 | 停止钻头 | +| M8,M9 | 冷却液控制 | + +一般在雕刻机当中,M3,M4 是用来控制激光头的功率的,在 DIY 的机器当中,你可以用 M8,M9 来控制风扇。 + +有关 G 代码的更多详细内容可参考[维基百科](https://en.wikipedia.org/wiki/G-code)。 + +解决了 G 代码的问题,我们一般还要解决 GRBL 的参数配置问题,这也是非常重要的一个环节。 + +### 2.3 GRBL 的参数配置 + +GRBL 目前一共有 34 个配置参数,大家不理解的话可以直接参考每个命令的建议值。GRBL 的所有参数列表如下: + +| 命令 | 解释 | +| :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| $0=10 | (steppulse, usec) STEP 信号的高电平时间,建议`10us`。 | +| $1=25 | (step idle delay, msec) 步进电机除能延迟时间。当电机完成一个动作并停止时,经过此时间后,电机驱动模块的 ENABLE 引脚会拉高,除能电机锁定来节能。把该值设为最大值 255 来禁用电机延迟除能功能。 | +| $2=0 | (stepport invert mask:00000000) 步进脉冲信号反相掩码。默认情况下,步进信号从正常低电平开始,并在发生步进脉冲事件时变为高电平。反转后,步进脉冲的行为会从正常高电平切换为脉冲期间的低电平,然后再回到高电平。 | +| $3=0 | (dirport invert mask:00000110) 步进电机方向信号反相掩码。此设置反转每个轴的方向信号。默认情况下,Grbl 假定当方向引脚信号为低电平时,轴沿正方向移动;当方向引脚信号为高时,轴沿负方向移动。 | +| $4=0 | (stepenable invert, bool) 步进电机使能信号反相设置。默认情况下,步进使能引脚为高电平是禁用电机驱动,而低电平是使能电机驱动。如果需要相反的操作,只需输入$4=1 即可反转步进使能引脚的电平。 | +| $5=0 | (limit pins invert, bool) 限位 IO 口信号反相设置。 默认情况下,限位引脚通过 Arduino 的`内部上拉电阻`保持在高电平状态。当限位引脚为低电平时,Grbl 将其解释为触发。对于相反的行为,只需输入$5=1 即可反转触发电平。 | +| $6=0 | (probe pin invert, bool) 探针 IO 口信号反相设置。 默认情况下,探针由 Arduino 的内部上拉电阻保持在常高状态。当探针引脚为低电平时,Grbl 将其解释为触发。对于相反的行为,只需键入$6=1 即可反转探针。 | +| $10=1 | (status report mask:00000011) 状态报告掩码。当发送“?”给 GRBL 时,它会返回当前的实时数据。该数据包括当前运行状态,实时位置,实时进给速度,引脚状态,当前倍率值,缓冲区状态和当前正在执行的 g 代码行号(如果通过编译时选项启用)。本设置用于指定位置类型是显示机器位置(MPos:)还是工作位置(WPos:),但不能同时显示两者。在某些情况下,当通过串行终端直接与 Grbl 进行交互时,启用工作位置很有用,但是默认情况下应使用机器位置报告。还是可以启用查看 Grbl 的计划器和串行 RX 缓冲区的使用数据。这会显示各个缓冲区中可用的块或字节数。通常用于帮助确定在测试流接口时 Grbl 的性能。默认情况下应禁用此功能。 | +| $11=0.010 | (junction deviation, mm) 节点偏差。加速度管理器使用结点偏差来确定它可以在 G 代码程序路径的线段结点处移动的速度。例如,如果 G 代码路径急转 10 度,并且机器以全速移动,则此设置有助于确定机器需要减速多少才能安全通过弯道而不会丢失步数。 | +| $12=0.002 | (arc tolerance, mm) 圆弧公差。Grbl 通过将 G2 / G3 圆,弧和螺旋细分为细小的细线来渲染 G2 / G3 的圆,弧和螺旋,以使弧跟踪精度永远不会低于此值。您可能永远不需要调整此设置,因为 0.002mm 它远低于大多数 CNC 机床的精度。但是,如果您发现自己的圆太粗或弧形追踪的速度很慢,请调整此设置。较低的值可提供较高的精度,但可能会因过多的细线使 Grbl 过载而导致性能问题。或者,较高的值会导致较低的精度,但由于 Grbl 需要处理的线数较少,因此可以提高圆弧性能。 | +| $13=0 | (report inches, bool) 位置坐标的单位设置。默认情况下,它设置为以 mm 为单位报告,但是通过发送$13=1 命令,将此布尔标志发送为 true,这些报告功能现在将以英寸为单位报告。$13=0 重新设置为毫米。 | +| $20=0 | (soft limits, bool) 软限位开关。软限制是一项安全功能,可帮助您防止机器行驶太远且超出行驶限制,撞毁或破坏昂贵的物品。它通过了解每个轴的最大行程限制以及 Grbl 在机器坐标中的位置来工作。每当有新的 G 代码运动发送到 Grbl 时,它都会检查您是否意外地超出了机器空间。如果这样做,Grbl 会在任何位置发出即时进给保持,关闭主轴和冷却液,然后设置系统警报以指示问题所在。机器位置将在之后保留,因为它不是由于硬限制而立即被迫停止。 | +| $21=0 | (hard limits, bool) 硬限位开关。硬限制的工作原理与软限制基本相同,但改用物理开关。基本上,您在每个轴的行进结束时或在任何您觉得如果程序移到不应移动的地方可能会遇到麻烦的地方,连接一些开关(机械的,磁性的或光学的)。开关触发后,它将立即停止所有运动,关闭冷却液和主轴(如果已连接),并进入警报模式,这将迫使您检查机器并重置所有内容。 | +| $22=0 | (homing cycle, bool) 归零使能位。 GRBL 将控制 3 个轴移动以碰到限位开关,以此位置来作为零点。(归零时的移动方向,由后面的参数设置) | +| $23=0 | (homing dir invert mask:00000001) 归位方向反相掩码。默认情况下,Grbl 假设您的归位限位开关在正方向上,归位过程中会首先将 z 轴向正向移动,然后将 xy 轴正向移动,然后再通过在开关周围缓慢地来回移动来精确定位机器零点。 | +| $24=400.000 | (homing feed, mm/min) 归位进给速率,毫米每分钟。(homing feed, mm/min) 归位进给速率,毫米每分钟。 | +| $25=3000.000 | (homing seek, mm/min) 归位快速速率。归零时首先以较高的速率搜索限位开关,找到限位开关后,再以较低的进给速率移动到机床零位的精确位置。归位快速速率是最开始快的那个速度。 | +| $26=250 | (homing debounce, msec) 归零时限位开关消抖时间。每当开关触发时,开关上会出现一些电气/机械噪声。要解决此问题,您需要使用某种硬件来对信号进行反跳信号调节或通过软件延迟很短的时间使信号弹起。 | +| $27=2.000 | (homing pull-off, mm) 归零后移出限位开关距离。为了更好地利用硬限位功能,归零完成后,GRBL 会把电机移出限位开关。它有助于防止在归零后意外触发硬限制。要确保该值足够大以清除限位开关。否则,Grbl 将因未能清除错误而引发警报错误。 | +| $30=100 | (Max spindle speed, RPM) 钻头最大旋转速度。这个参数在雕刻机模式下就是激光的最大功率。 | +| $31=0 | (Min spindle speed, RPM) 钻头最小速度。这个参数在雕刻机模式下是激光的最小功率。 | +| $32=1 | (Laser mode enable, bool) 激光模式,布尔。该参数用于是否启用激光模式,如果启用,GRBL 会优化机器的运动,使其更加符合激光的特性。 | +| $100=80 | (x, step/mm) x 轴移动 1 毫米需要多少个脉冲。以我们常用的 42 步进电机为例,42 步进电机是旋转一周 200 步,如果我们使用了 A4988 步进电机,三个跳帽都使用就是 16 步细分,那么旋转一圈就是 200\*16=3200 个脉冲。如果我们使用的是 GT2 同步轮,齿间距是 2mm,一共有 20 齿,那么点击旋转一圈就是 20\*2=40mm,即移动一毫米需要 3200/40=80 个脉冲。 | +| $101=80 | (y, step/mm) y 轴移动 1 毫米需要多少个脉冲。 | +| $102=80 | (z, step/mm) z 轴移动 1 毫米需要多少个脉冲. | +| $110=3000.000 | (x max rate, mm/min) x 轴最大速率 毫米/分钟。确定这些值的最简单方法是通过逐渐增加最大速率设置并移动它来一次测试每个轴。 | +| $111=3000.000 | (y max rate, mm/min) y 轴最大速率 毫米/分钟。 | +| $112=3000.000 | (z max rate, mm/min) z 轴最大速率 毫米/分钟. | +| $120=1000.000 | (x accel, mm/sec2) x 轴加速度 毫米/(s2)。确定此设置的值的最简单方法是用缓慢增加的值分别测试每个轴。 | +| $121=1000.000 | (y accel, mm/sec2) y 轴加速度 毫米/(s2)。 | +| $122=1000.000 | (z accel, mm/sec2) z 轴加速度 毫米/(s2)。 | +| $130=300.000 | (x max travel, mm) x 轴最大行程。这将以毫米为单位设置每个轴从一端到另一端的最大行程。仅当启用了软限制(和原点复归)时,此功能才有用,因为 Grbl 的软限制功能仅使用此功能来检查是否已通过运动命令超出了机器限制。 | +| $131=300.000 | (y max travel, mm) y 轴最大行程。 | +| $132=300.000 | (z max travel, mm) z 轴最大行程。 | + +更多有关 GRBL 参数配置的内容可参考这位 Youtuber 有关[GRBL Shield](https://www.youtube.com/watch?v=5KAFm3XMNlQ&list=PLJJWXVM5tQNqSfzYOUUsjYgfPH_-OvGpu)的内容,我认为他讲的很好很详细,值得大家学习。 + +## 3. 软件 + +使用雕刻机肯定就需要一个趁手的软件,好的软件可以大大提高效率。 + +目前为止,我所知道的、比较好用的开源软件是[LaserGrbl](https://lasergrbl.com/),作者花了很多的心思和精力在这个软件上,虽然这个软件不是最好的,但是作为一个开源软件已经非常优秀了,使用起来效果也很好,大家可以支持一下。 + +![LaserGrbl](./images/1-7.png) + +这个软件虽然没有强大编辑能力,但是它支持.svg 格式的图片,所以我们只需要把我们需要雕刻的图片或者文字借助.svg 进行编辑,然后导入到 LaserGrbl 中就可以了。 + +[Inksacpe](https://inkscape.org/)就是一个比较好用、功能强大开源的软件。 + +![Inkscape](./images/1-8.png) diff --git a/docs/electronics/projects/cnc-engraver/chapter2.md b/docs/electronics/projects/cnc-engraver/chapter2.md new file mode 100644 index 0000000..8c37c67 --- /dev/null +++ b/docs/electronics/projects/cnc-engraver/chapter2.md @@ -0,0 +1,63 @@ +--- +title: 雕刻机硬件搭建 +--- + +# 雕刻机硬件搭建 + +本项目主要是 DIY 为主,因此大部分零件都尽量做到高性价比,稳定实用的同时保证不会花费太多。 + +## 1. 材料清单 + +该项目需要购买的材料有: + +| 零件 | 规格 | 数目 | +| :--------------: | :-----------------: | :----: | +| NEMA17 步进电机 | 42 步进电机 | 3 | +| 2020 铝型材 V 轮 | 内孔 5mm | 12 | +| GT2 同步带轮 | 20 齿高 16 内孔 5mm | 3 | +| 钢套轴套 | 内孔 5mm 高 8mm | 16 | +| GT2 同步带 | 宽 6mm | 2 米 | +| M5 螺丝 | 长 45mm | 12 | +| M5 螺母 | 304 材质 | 12 | +| 2020 铝型材 | 长度按需求而定 | 同规格 | + +同时你还可能需要购买一些电子元器件: + +| 零件 | 数目 | +| :-----------------------: | :--: | +| A4988 | 3 | +| XH2.54 电机线 | 3 | +| 限位开关 | 2 | +| 限位开关导线 | 2 | +| 1.6W 或者更高功率的激光头 | 1 | +| 12V3A 电源适配器 | 1 | +| Aruino 的 CNC 扩展板 | 1 | + +除了以上需要购买的零件之外,你还需要 3D 打印或者机床铣出一些定制的部件,即下图中的绿色零件: + +| 零件 | 数目 | +| :------------: | :--: | +| X 轴电机支座 | 1 | +| X 轴激光头支座 | 1 | +| Y 轴电机支座 | 2 | +| 限位开关支座 | 2 | +| 雕刻机支架支座 | 4 | +| CNC 扩展板支座 | 1 | + +![3D viewer](./images/0-3.png) + +大家可以前往我的[Github 仓库](https://github.com/MR-Addict/CNC-Laser-Engraver.git)下载相关文件,同时我设计了一块适用于 Arduino Nano 的 CNC 扩展板,大家也可以前往下载使用,如果你不想使用购买的 CNC 扩展版的话。 + +扩展原理图及 PCB 图如下: + +| 原理图 | PCB | +| :----------------------------: | :----------------------: | +| ![SCHEMATIC](./images/2-1.png) | ![PCB](./images/2-2.png) | + +## 2. 搭建硬件 + +因为我没有视频,我制作的时候也没有拍照片,大家准备好所有的元器件之后,根据感觉安装吧,没啥难度。你们可以参考下面的实物图进行安装。 + +![Assemble process](./images/0-2.jpg) + +另外扩展版的使用和焊接可以参考上面的原理图,或者多找一些 DIY 的 CNC 机器看一看,加深对 CNC 机制的了解,这里我也不做介绍了。 diff --git a/docs/electronics/projects/cnc-engraver/chapter3.md b/docs/electronics/projects/cnc-engraver/chapter3.md new file mode 100644 index 0000000..dfe86a5 --- /dev/null +++ b/docs/electronics/projects/cnc-engraver/chapter3.md @@ -0,0 +1,103 @@ +--- +title: 雕刻机控制软件 +--- + +# 雕刻机控制软件 + +## 1. LaserGrbl 的使用 + +这里我已开源的 LaserGrbl 为例,介绍一下如何配置和控制雕刻机。 + +## 1.1 第一步,连接电源和电脑 + +使用 12V3A 的电源适配器给雕刻机供电,同时通过 USB 将电脑和雕刻机连接在一起。 + +## 1.2 第二步,连接设备 + +选择相应的端口以及波特率,一般是`115200`,但我设置了更小一点的波特率。 + +![LaseGrbl](./images/3-1.png) + +## 1.3 第三步,配置 Grbl + +在使用之前你需要对你的机器做一些配置,打开 Grbl Configuration,然后按照前面介绍的进行配置对应的数值。 + +![Grbl Configuration](./images/3-2.png) + +你们可以参照下面的默认值: + +| 推荐值 | 推荐值 | +| :-----------: | :-----------: | +| $0=10 | $1=25 | +| $2=0 | $3=0 | +| $4=0 | $5=0 | +| $6=0 | $10=1 | +| $11=0.010 | $12=0.002 | +| $13=0 | $20=0 | +| $21=0 | $22=0 | +| $23=0 | $24=400.000 | +| $25=3000.000 | $26=250 | +| $27=2.000 | $30=100 | +| $31=0 | $32=1 | +| $100=80 | $101=80 | +| $102=80 | $110=3000.000 | +| $111=3000.000 | $112=3000.000 | +| $120=1000.000 | $121=1000.000 | +| $122=1000.000 | $130=300.000 | +| $131=300.000 | $132=300.000 | + +## 1.4 第四步,LaserGrbl 功能分区 + +![LaserGrbl Fucntion](./images/3-3.png) + +在启动连接设备之后,你可以通过左下方的移动箭头移动雕刻机,两侧的数字可以用来调节移动的速度和移动的距离。 + +软件下方是方便使用的一些快捷按钮。 + +- 第一个闪电的按钮用于紧急制动。 +- 第二个房屋的按钮用于机器调零,只有在机器归零启动的情况下该图标才会出现,如果你的机器有限位开关的话可以考虑打开这个功能。 +- 第三个锁的按钮用于当机器出现警报错误时,取消错误警报,重新启用机器。 +- 第四个地球仪按钮用于设置工作原点。 +- 第五个案例用于将机器移动至雕刻图案中心。 +- 第六个图标用于将机器移动至雕刻图案左下角。 +- 第七个图标用于让机器绕雕刻图案的最小矩形边框运动,同时发出微弱的激光用于校准位置。 + +## 1.5 第五步,处理图案 + +点击 Filename 旁的图标可以打开需要雕刻的文件,LaserGrbl 支持`G-Code`,`.svg`,`图片`等格式的文件。这里我们以图片为例。 + +![Open File](./images/3-4.png) + +打开图片后会跳出模式选择窗口: + +![Mode select](./images/3-5.png) + +我们选择平滑模式,下面的滑动条可以对图片做调整,比如对比度,亮度等等。 + +在雕刻图片,尤其是人像的时候,使用点阵雕刻可以实现图片的明暗层次的变化,如果你的激光头的功率不高的话这是很好的一个选择。接着就是模糊算法的选择,我们保持默认就好了。下面还有一个分辨率的选择,单位是每毫米有多少条线条,数值越大,雕刻的分辨率越高,但是时间也越长,建议默认值 10。 + +然后点击下一步: + +![Next](./images/3-6.png) + +在这里你可以选择雕刻时的参数,比如雕刻速度,建议默认值 1000,最下面可以选择雕刻图案的大小以及偏移量,一般不使用偏移量,但是可以大小很有用,这里我设置 80\*100mm。 + +最后点击创建就可以了。 + +## 1.6 开始雕刻 + +一切都准备好之后,点击开始雕刻就可以了。 + +![Begin Engraving](./images/3-7.png) + +## 2. 成品展示 + +我自己雕刻了一些图片,让大家感受一下欣赏雕刻机的乐趣。 + +| 卡通 | 山脉 | +| :-------------------------: | :-------------------------: | +| ![image1](./images/3-8.png) | ![image2](./images/3-9.png) | + +| 长恨歌 | 龙 | +| :--------------------------: | :--------------------------: | +| ![image3](./images/3-10.png) | ![image4](./images/3-11.png) | diff --git a/docs/electronics/projects/cnc-engraver/chapter4.md b/docs/electronics/projects/cnc-engraver/chapter4.md new file mode 100644 index 0000000..83fcc1b --- /dev/null +++ b/docs/electronics/projects/cnc-engraver/chapter4.md @@ -0,0 +1,11 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +雕刻机连同其他 CNC 机器一样,都有很高的难度,同时也有探索不禁的玩法和功能,比如脱机运行,WIFI 功能,自动换刀,增加一些外设,比如一个屏幕等等。下面是一台`CNC String Art Machine`。 + +![String art cnc machine](./images/4-1.png) + +CNC 既神奇,又充满魅力,很值得大家深入探索哦。 diff --git a/docs/electronics/projects/cnc-engraver/images/0-1.jpeg b/docs/electronics/projects/cnc-engraver/images/0-1.jpeg new file mode 100644 index 0000000..bc9ba2f Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/0-1.jpeg differ diff --git a/docs/electronics/projects/cnc-engraver/images/0-2.jpg b/docs/electronics/projects/cnc-engraver/images/0-2.jpg new file mode 100644 index 0000000..3d68fe1 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/0-2.jpg differ diff --git a/docs/electronics/projects/cnc-engraver/images/0-3.png b/docs/electronics/projects/cnc-engraver/images/0-3.png new file mode 100644 index 0000000..13d5b28 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/0-3.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-1.png b/docs/electronics/projects/cnc-engraver/images/1-1.png new file mode 100644 index 0000000..0094fae Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-1.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-2.png b/docs/electronics/projects/cnc-engraver/images/1-2.png new file mode 100644 index 0000000..c544dd2 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-2.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-3.png b/docs/electronics/projects/cnc-engraver/images/1-3.png new file mode 100644 index 0000000..f7a07b2 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-3.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-4.png b/docs/electronics/projects/cnc-engraver/images/1-4.png new file mode 100644 index 0000000..75b4808 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-4.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-5.png b/docs/electronics/projects/cnc-engraver/images/1-5.png new file mode 100644 index 0000000..214ec8a Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-5.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-6.png b/docs/electronics/projects/cnc-engraver/images/1-6.png new file mode 100644 index 0000000..5fa89ad Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-6.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-7.png b/docs/electronics/projects/cnc-engraver/images/1-7.png new file mode 100644 index 0000000..3402350 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-7.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/1-8.png b/docs/electronics/projects/cnc-engraver/images/1-8.png new file mode 100644 index 0000000..98adb4e Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/1-8.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/2-1.png b/docs/electronics/projects/cnc-engraver/images/2-1.png new file mode 100644 index 0000000..3ea97af Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/2-1.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/2-2.png b/docs/electronics/projects/cnc-engraver/images/2-2.png new file mode 100644 index 0000000..b1e1af0 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/2-2.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-1.png b/docs/electronics/projects/cnc-engraver/images/3-1.png new file mode 100644 index 0000000..933b007 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-1.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-10.png b/docs/electronics/projects/cnc-engraver/images/3-10.png new file mode 100644 index 0000000..b744714 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-10.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-11.png b/docs/electronics/projects/cnc-engraver/images/3-11.png new file mode 100644 index 0000000..ac6a8d2 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-11.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-2.png b/docs/electronics/projects/cnc-engraver/images/3-2.png new file mode 100644 index 0000000..1b0717d Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-2.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-3.png b/docs/electronics/projects/cnc-engraver/images/3-3.png new file mode 100644 index 0000000..15dd04e Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-3.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-4.png b/docs/electronics/projects/cnc-engraver/images/3-4.png new file mode 100644 index 0000000..e24acd4 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-4.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-5.png b/docs/electronics/projects/cnc-engraver/images/3-5.png new file mode 100644 index 0000000..bb983db Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-5.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-6.png b/docs/electronics/projects/cnc-engraver/images/3-6.png new file mode 100644 index 0000000..9860238 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-6.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-7.png b/docs/electronics/projects/cnc-engraver/images/3-7.png new file mode 100644 index 0000000..8281742 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-7.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-8.png b/docs/electronics/projects/cnc-engraver/images/3-8.png new file mode 100644 index 0000000..c0adab0 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-8.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/3-9.png b/docs/electronics/projects/cnc-engraver/images/3-9.png new file mode 100644 index 0000000..78bbf18 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/3-9.png differ diff --git a/docs/electronics/projects/cnc-engraver/images/4-1.png b/docs/electronics/projects/cnc-engraver/images/4-1.png new file mode 100644 index 0000000..21057c8 Binary files /dev/null and b/docs/electronics/projects/cnc-engraver/images/4-1.png differ diff --git a/docs/electronics/projects/cnc-engraver/intro.md b/docs/electronics/projects/cnc-engraver/intro.md new file mode 100644 index 0000000..eb9bb89 --- /dev/null +++ b/docs/electronics/projects/cnc-engraver/intro.md @@ -0,0 +1,23 @@ +--- +title: 激光雕刻机简介 +--- + +# 激光雕刻机 + +激光雕刻机应该说是很多 DIY 爱好者都喜欢做的一个项目,因为它确实很酷。我们技术部在之前也有这样一台大佬们买的 1.6W 的雕刻机。可见,虽然我们处在不同的时空,但是最终我们不约而同地还是走到了同一个地方。 + +其实我在大一学习了 Arduino 之后就知道了 Arduino 可以制作雕刻机,那个时候一直想做一个,但是毕竟那个时候能力不足,有心无力。后来经过一段时间的学习,我又回到了这个项目上。 + +我是先着手做了一台写字机,但显然没有成功。 + +![Plotter](./images/0-1.jpeg) + +后来我在我们办公室发现了这一台老前辈们玩过的雕刻机,我从那台雕刻机上受到了启发,完成了我们现在使用的雕刻机。 + +![Engraver](./images/0-2.jpg) + +这个版本我画了 3D 模型,你们可以使用 CNC 机床铣出来,或者用 3D 打印机打印,绿色部分是需要定制的,其余的材料直接购买就可以了。 + +![3D viewer](./images/0-3.png) + +激光雕刻机在硬件上要求比较高,同时还需要掌握一些额外的知识,软件方面大部分直接使用预先写好的固件就可以了,也就是说你有一台机器的话就相当于完成了整个项目。 diff --git a/docs/electronics/projects/intro.md b/docs/electronics/projects/intro.md new file mode 100644 index 0000000..28734e0 --- /dev/null +++ b/docs/electronics/projects/intro.md @@ -0,0 +1,13 @@ +--- +title: 项目制作简介 +--- + +# 项目制作 + +这个专题主要是我们电子组,或者说是技术部的长久以来有趣的项目集合,希望大家能够齐心协力,有比较成熟的项目都可以写到这里,多多丰富这个专题的内容。 + +下面是已有项目: + +- [办公室 NFC 门禁卡](nfc/intro.md) +- [办公室 LED Board](led-board/intro.md) +- [DIY 激光雕刻机](cnc-engraver/intro.md) diff --git a/docs/electronics/projects/led-board/chapter1.md b/docs/electronics/projects/led-board/chapter1.md new file mode 100644 index 0000000..011926b --- /dev/null +++ b/docs/electronics/projects/led-board/chapter1.md @@ -0,0 +1,62 @@ +--- +title: 制作步骤 +--- + +# 制作步骤 + +这个项目说难也不难,只是在制作过程中很考验耐心。我当时是有学姐作为帮手,另外还有郭鑫惺帮我焊接,所以总的来说还好,如果你们是自己做可能就要麻烦点了。 + +## 1. 制作框架 + +对于电子元器件,我们后面介绍。制作框架需要用到一块木板,木板的尺寸看你的分辨率。我前后做了两个版本,一个是 21x7 的,一个是 19x7 的。这里我以 21x7 为例,你大概需要 84x24.78cm 的木板以及 135 个乒乓球。 + +零件清单大概如下: + +| 零件 | 数目 | +| :------------: | :----: | +| 轻木板 | 1 个 | +| 乒乓球 | 135 个 | +| 其他装饰性物品 | N 个 | + +准备好板子后,把板子按照如下尺寸进行切割: + +![Ping pong board layout](./images/1-1.jpg) + +## 2. 焊接灯条及乒乓球 + +切割好木板后,你可以按照上面的尺寸图对木板进行规划,把每一个灯条的位置标记好,接着就把剪下来的灯珠按照位置贴好,你可以通过导线把相邻的灯珠焊接在一起,或者用排针的针头,那样更好焊接一点。 + +然后把 135 个乒乓球底部切掉大概 1/4。 + +![Cut Ping pong balls](./images/1-2.png) + +然后按照上面的位置把切好的乒乓球用热熔胶粘贴在每个灯珠上,留出三根导线就好了,你也可以对外观进行一些装饰。 + +最终的成品大概如下: + +![Finished Project](./images/1-3.png) + +## 3. 原理图及 PCB + +这里使用到的电子元器件主要有: + +| 零件 | 数目 | +| :-------------: | :--: | +| DC005 接头 | 1 个 | +| 5V3A 电源适配器 | 1 个 | +| ESP32 | 1 个 | +| 按钮 | 4 个 | +| DS3231 模块 | 1 个 | +| 100uF 电容 | 1 个 | +| 10K 电阻 | 1 个 | +| 3P 接线端子 | 1 个 | + +你可以按照如下原理把各个零件焊接洞洞板上: + +![Schematic](./images/1-4.png) + +也可以到我的[GitHub 仓库](https://github.com/MR-Addict/WS2812-LED-Ping-Pong-Board.git)下载 Gerber 文件进行 PCB 打样。 + +下面是 PCB 的三维示意图: + +![PCB 3D viewer](./images/1-5.png) diff --git a/docs/electronics/projects/led-board/chapter2.md b/docs/electronics/projects/led-board/chapter2.md new file mode 100644 index 0000000..be073f7 --- /dev/null +++ b/docs/electronics/projects/led-board/chapter2.md @@ -0,0 +1,109 @@ +--- +title: 代码及使用 +--- + +# 代码及使用 + +有关代码部分我也比较建议大家前往我的[GitHub 仓库](https://github.com/MR-Addict/WS2812-LED-Ping-Pong-Board.git)下载使用,其中 21x7 版本的使用了 WIFI,如果你不想使用 WIFI,仅仅使用 APP 的话,可以使用 19x7 的版本。 + +## 1. 项目文件及代码 + +该项目的代码文件结构如下: + +- HeaderConfig.h +- LEDBoard21x7.ino +- LEDFunction.h +- WIFI.h +- data + - favicon.png + - index.html + - index.js + - login.html + - sytle.css + +其中`HeaderConfig.h`主要包含了全局变量的声明,`LEDBoard21x7.ino`就是主程序了,`LEDFunction.h`主要是灯板的控制以及光效函数,`WIFI.h`包含了有关 WIFI 服务器和 websocket 的配置,`data`文件夹包含了有关网页的文件。 + +本项目同样有很大的难度,包括网页设计,WIFI 服务器搭建,以及 SPIFFS 的使用,相关内容可参考上一个项目[NFC 门禁卡](../nfc/chapter2.md)。这里我不做详细介绍了,三言两语也讲不清楚,大家可以自行学习。 + +## 2. 灯板的使用 + +灯板内置了三种显示模式,包括: + +- 显示时间 +- 显示日期 +- 显示灯效 + +灯板支持四种控制模式,包括: + +- 按钮 +- 手机 APP +- WIFI +- 定时任务 + +后两种模式可以设置灯板的时间和日期,而按钮只能切换模式和背景,定时任务是用来定时开关灯板的。 + +下面我介绍每一种控制模式的使用方法。 + +### 2.1 按钮控制 + +| 按钮 | 功能 | +| :----: | :------------: | +| 按钮一 | 打开/关闭灯板 | +| 按钮二 | 设置背景模式 | +| 按钮三 | 上一个显示模式 | +| 按钮四 | 下一个显示模式 | + +值得注意的是,因为我把板子内置了 5 种背景模式,有`彩虹模式`,`白云模式`,`海洋模式`,`岩浆模式`,`森林模式`。根据我的观察,显示时间和显示日期需要比较浅的背景,所以在这两种模式下一直都是白云模式,不能更换背景的。 + +## 2.2 手机 APP + +我使用了一个叫做[MIT APP Inventor](http://ai2.appinventor.mit.edu/?locale=en#5202799305818112)开源项目制作了这个手机 APP,该软件只支持安卓系统。 + +你可以通过这个链接[下载](/assets/software/LED-Board.apk)安装使用。 + +![Android APP](./images/1-6.png) + +在使用 APP 前,你需要先连接 ESP32 的蓝牙,我设置了蓝牙名称为`LED Board 507`,你可以通过更改`LEDBoard21x7.ino`文件中`setup`函数的下面一行更改设备蓝牙名称: + +```cpp +// 设置蓝牙名称 +SerialBT.begin("LED Board 507"); +``` + +连接蓝牙后你就可以通过这个 APP 控制灯板,相信大家看了就会使用这个 APP,挺简单的,这里我不做介绍了。 + +### 2.3 WIFI + +这里 WIFI 的使用和 NFC 的 WIFI 开门是相似的,也需要先登录验证。在连接相同路由器的情况下,你可以通过 ESP32 的主机名进行访问,我设置的主机名是`ledboard-507`,即,你可以通过[http://ledboard-507](http://ledboard-507)访问。 + +你可以通过更改`WIFI.h`文件下的`WIFI_INIT`的以下几行更改 ESP32 的主机名: + +```cpp +// change hostname to ledboard-507 +String hostname = "ledboard-507"; +WiFi.mode(WIFI_STA); +WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); +WiFi.setHostname(hostname.c_str()); +``` + +登录后的需要身份验证,默认用户名是`admin`,默认密码是`dinghao666`: + +![Login](./images/1-7.png) + +登录后就可以控制灯板了: + +![WEB UI](./images/1-8.png) + +相信大家看了这个 UI 应该也知道怎么用了,这里我也不做详细介绍了。 + +### 2.4 定时任务 + +DS3231 拥有两个内置的闹钟,这非常适合用来作为定时器,但是为了提高效率,我加入了使用了 DS3231 的中断。因为 DS3231 在闹铃时会触发`三号中断引脚`,但是这个引脚在我买的 DS3231 模块上没有焊接出来,因此我需要自己焊接,同时添加一个 10K 的上拉电阻。 + +我同时使用了两个闹钟,一个闹钟用来在`晚上12:00关闭灯板`,另一个用来在`中午12:00开启灯板`,大家可以通过更改`LEDBoard21x7.ino`文件中`setup`函数的以下几行重新设置这个时间: + +```cpp +attachInterrupt(digitalPinToInterrupt(INT_PIN), alarm_irq, FALLING); +rtc.setAlarm1(DateTime(2021, 12, 28, 0, 0, 0), DS3231_A1_Hour); // 每日凌晨12点关闭LED +rtc.setAlarm2(DateTime(2021, 12, 28, 8, 0, 0), DS3231_A2_Hour); // 每日早晨8点打开LED +``` diff --git a/docs/electronics/projects/led-board/chapter3.md b/docs/electronics/projects/led-board/chapter3.md new file mode 100644 index 0000000..dd7ee48 --- /dev/null +++ b/docs/electronics/projects/led-board/chapter3.md @@ -0,0 +1,31 @@ +--- +title: 总结和拓展 +--- + +# 总结和拓展 + +其实围绕着 WS2812,我制作了很多的项目,也在每个项目上做了很多的改进,有简单的,也有复杂的,其中一些对做我其他项目的制作也有很大帮助。 + +## 1. 桌面灯条 + +比如我带电子组制作的 LED 灯条,虽然我没有把这个项目放到 GitHub 上,但是我也带着大家亲手做了,从焊接板子,到学习 FastLED 库,再到整合代码,相信大家还有印象。有点懒,不想整理文件了,大家自己学习。目前我的桌子下面就用的是这个灯条,我还给它添加了电源以及充电的功能,总的来说挺好用的,也很好看。 + +![LED Strip](./images/2-1.jpg) + +## 2. 初代 8x8 灯阵 + +我在大一结束的那个暑假的时候,又制作了一个 8x8 的灯阵。这个灯阵有着丰富的变化,可以显示数字和 IP 地址以及字母,能够用 WIFI 控制灯阵,还有画画的功能。其实这也是我首次在 ESP32 上使用 WIFI 的功能。 + +大家可以前往我的[GitHub 仓库](https://github.com/MR-Addict/8X8-LED-Matrix-using-ESP32-Web-Server.git)参考相关内容。 + +![LED Matrix](./images/2-2.png) + +## 3. 进阶灯阵 + +大概是在我大二上学期金工实习的那段时间,我完成了 WS2812 更加进阶的功能。其大小不在只局限于 8x8,而是任意的分辨率,而且支持多种灯条连接模式,支持自定义字体显示,不局限于数字和字母,还有其他字,同样支持画直线,画圆,画矩形等图形,最后还能显示图片。和普通的屏幕已经无异了,这为我后来编写其他屏幕驱动打下了基础。 + +大家可以前往我的这个[GitHub 仓库](https://github.com/MR-Addict/WS2812LED-Matrix-Library.git)下载使用这几个库。 + +后来我借助这个库,制作了另一个项目[SmartLED](https://github.com/MR-Addict/SmartLED.git)大部分也就是上面提到的功能,我记得我还在这个灯阵上写了一个贪吃蛇的游戏,有兴趣的可以去看一下。 + +![Mario](./images/2-3.jpg) diff --git a/docs/electronics/projects/led-board/images/0-1.jpg b/docs/electronics/projects/led-board/images/0-1.jpg new file mode 100644 index 0000000..d37c440 Binary files /dev/null and b/docs/electronics/projects/led-board/images/0-1.jpg differ diff --git a/docs/electronics/projects/led-board/images/1-1.jpg b/docs/electronics/projects/led-board/images/1-1.jpg new file mode 100644 index 0000000..21925ed Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-1.jpg differ diff --git a/docs/electronics/projects/led-board/images/1-2.png b/docs/electronics/projects/led-board/images/1-2.png new file mode 100644 index 0000000..63908b4 Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-2.png differ diff --git a/docs/electronics/projects/led-board/images/1-3.png b/docs/electronics/projects/led-board/images/1-3.png new file mode 100644 index 0000000..b83ac1f Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-3.png differ diff --git a/docs/electronics/projects/led-board/images/1-4.png b/docs/electronics/projects/led-board/images/1-4.png new file mode 100644 index 0000000..010b295 Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-4.png differ diff --git a/docs/electronics/projects/led-board/images/1-5.png b/docs/electronics/projects/led-board/images/1-5.png new file mode 100644 index 0000000..dcf6431 Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-5.png differ diff --git a/docs/electronics/projects/led-board/images/1-6.png b/docs/electronics/projects/led-board/images/1-6.png new file mode 100644 index 0000000..6fd9de6 Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-6.png differ diff --git a/docs/electronics/projects/led-board/images/1-7.png b/docs/electronics/projects/led-board/images/1-7.png new file mode 100644 index 0000000..744a814 Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-7.png differ diff --git a/docs/electronics/projects/led-board/images/1-8.png b/docs/electronics/projects/led-board/images/1-8.png new file mode 100644 index 0000000..0d75e9a Binary files /dev/null and b/docs/electronics/projects/led-board/images/1-8.png differ diff --git a/docs/electronics/projects/led-board/images/2-1.jpg b/docs/electronics/projects/led-board/images/2-1.jpg new file mode 100644 index 0000000..3cfc0b4 Binary files /dev/null and b/docs/electronics/projects/led-board/images/2-1.jpg differ diff --git a/docs/electronics/projects/led-board/images/2-2.png b/docs/electronics/projects/led-board/images/2-2.png new file mode 100644 index 0000000..5c2c7c4 Binary files /dev/null and b/docs/electronics/projects/led-board/images/2-2.png differ diff --git a/docs/electronics/projects/led-board/images/2-3.jpg b/docs/electronics/projects/led-board/images/2-3.jpg new file mode 100644 index 0000000..306b8aa Binary files /dev/null and b/docs/electronics/projects/led-board/images/2-3.jpg differ diff --git a/docs/electronics/projects/led-board/intro.md b/docs/electronics/projects/led-board/intro.md new file mode 100644 index 0000000..9569f29 --- /dev/null +++ b/docs/electronics/projects/led-board/intro.md @@ -0,0 +1,17 @@ +--- +title: LED Board简介 +--- + +# LED Board + +LED Board 这个项目始于大一下学期末,当时是徐照钦推荐我去给建筑院几个学姐做一个期末项目。一开始她们是要制作一个有互动性的、体现一点艺术感与科技感的作品,我们当时参考了很多项目,最终选择了这个乒乓球灯板。我记得当时的初代版本我只花了一个星期就完成了,包括硬件设计,编写代码以及制作 APP。有难度但是也很有趣。 + +记得那个时候,因为乒乓球和灯带间隙不吻合,我们就把每个灯珠剪下来,然后一个个焊到板子上,我和郭鑫惺从下午 4 点开始,一直焊接到晚上 10 点钟,脖子都酸了,郭鑫惺至少被电烙铁烫到 5,6 次。真心挺累的。 + +我记得代码写得也很累,因为板子不是规则的矩形,而是一个菱形,这对于访问灯板上的灯珠就比较麻烦,那个时候没想到什么好法子,绕了挺大的圈子,我个人感觉代码写得挺糟糕的。不过在后来这个问题总算是解决了。 + +![LED Board](./images/0-1.jpg) + +目前这个灯板主要是放在办公室当作时钟使,整体效果挺不错的。一开始我是使用 DS1302,但是这个时钟芯片的精度很差,几个星期就要误差一两分钟,后来我换成了 DS3231,这个时钟芯片精度是真高,一年的误差也就在一分钟上下,而且还有两个闹钟可以使,非常不错。 + +希望大家喜欢这个项目。 diff --git a/docs/electronics/projects/nfc/chapter1.md b/docs/electronics/projects/nfc/chapter1.md new file mode 100644 index 0000000..bf08ddd --- /dev/null +++ b/docs/electronics/projects/nfc/chapter1.md @@ -0,0 +1,306 @@ +--- +title: 初代门禁版本 +--- + +# 初代门禁版本 + +虽然说我们目前使用的我制作的最新版本,前代也有很多问题,但是我还是认为有必要让大家了解一下,这样也能开阔大家的思维。 + +## 1. 一代版本——继电器版 + +继电器这个版本是大佬们的做法,可能也是比较专业的做法,但是可能是我学识浅薄,有些问题解决不了,所以就放弃了。大家自己也可以尝试一下。 + +继电器版的接线图如下: + +![Relay Version](./images/1-1.png) + +我使用的示例代码如下: + +```cpp +#include +#include + +const int SS_PIN = 10; +const int RST_PIN = 9; +const int Buzzer = 8; +const int Relay = 7; +const int UIDNUM = 50; + +const String UID[UIDNUM] = { + "F1 41 01 5F", "01 5E 95 2B", "1A FD A5 21", "E1 D8 FC 5E", "E1 E0 00 5F", + "D1 7E FF 5E", "21 DE 01 5F", "D1 3A F6 5E", "31 AB FD 5E", "21 35 F7 5E", + "0A 57 80 1C", "FA 5D 70 7F", "D2 AA 1C 3E", "15 4F B9 E5", "75 89 B6 E5", + "0A 8C 36 B6", "00 69 0F 93", "75 89 B6 E5", "FA 50 35 B6", "EA B1 1C 3E", + "60 DB 0D 93", "5A 32 34 B6", "72 B1 85 44", "A3 A4 1B 3E", "0C 3C 1A 3F", + "72 B1 85 44", "50 DF D3 03", "A5 72 1B 3E", "A1 C2 53 74", "6C AE 73 17", + "0A 46 95 1C", "47 25 38 E1", "DC 53 14 3E", "80 54 0C 93", "2A 76 64 21", + "10 FD FC 2E", "19 F0 95 B4", "D7 FC 80 67", "60 B9 2A BE", "75 36 B5 E5", + "87 90 91 5E", "69 E9 1F C5", "C9 DE 3B B4", "08 71 21 DC", "9C B3 BC 3E", + "17 A4 91 5E", "F7 88 94 5E", "89 28 3A B4", "BC C3 87 43", "DC FB 0E 3F", +}; + +MFRC522 mfrc522(SS_PIN, RST_PIN); + +void BuzzerRing(boolean Dur = false) { + for (uint8_t i = 0; i < 2; i++) { + tone(Buzzer, 300); + if (Dur)delay(200); + else delay(100); + noTone(Buzzer); + delay(100); + } +} + +boolean GetKey(String CONTECTED_UID) { + for (uint8_t i = 0; i < UIDNUM; i++) + if (CONTECTED_UID == UID[i])return true; + return false; +} + +void setup() +{ + Serial.begin(115200); + SPI.begin(); + mfrc522.PCD_Init(); + pinMode(Relay, OUTPUT); + pinMode(Buzzer, OUTPUT); + BuzzerRing(); + Serial.print("System Started!\nBuilt in UID Num:"); + Serial.println(UIDNUM); + delay(3000); +} + +void loop() +{ + if ( ! mfrc522.PICC_IsNewCardPresent())return; + if ( ! mfrc522.PICC_ReadCardSerial())return; + + String content = ""; + Serial.print("UID("); + for (byte i = 0; i < mfrc522.uid.size; i++) + { + Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); + Serial.print(mfrc522.uid.uidByte[i], HEX); + content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ")); + content.concat(String(mfrc522.uid.uidByte[i], HEX)); + } + content.toUpperCase(); + String CONTECTED_UID = content.substring(1); + + if (GetKey(CONTECTED_UID)) { + Serial.println(" ):Access Accpeted!"); + BuzzerRing(); + digitalWrite(Relay, HIGH); + delay(1000); + digitalWrite(Relay, LOW); + } + else { + Serial.println(" ):Access Denied!"); + BuzzerRing(true); + } +} +``` + +## 2. 二代版本——舵机版 + +舵机版和继电器版几乎一致,只是将继电器换成舵机罢了。 + +舵机版接线图如下: + +![Servo Version](./images/1-2.png) + +我使用的示例代码如下: + +```cpp +#include +#include +#include + +const int SS_PIN = 10; +const int RST_PIN = 9; +const int Buzzer = 7; +const int UIDNUM = 50; + +const String UID[UIDNUM] = { + "F1 41 01 5F", "01 5E 95 2B", "1A FD A5 21", "E1 D8 FC 5E", "E1 E0 00 5F", + "D1 7E FF 5E", "21 DE 01 5F", "D1 3A F6 5E", "31 AB FD 5E", "21 35 F7 5E", + "0A 57 80 1C", "FA 5D 70 7F", "D2 AA 1C 3E", "15 4F B9 E5", "75 89 B6 E5", + "0A 8C 36 B6", "00 69 0F 93", "75 89 B6 E5", "FA 50 35 B6", "EA B1 1C 3E", + "60 DB 0D 93", "5A 32 34 B6", "72 B1 85 44", "A3 A4 1B 3E", "0C 3C 1A 3F", + "72 B1 85 44", "50 DF D3 03", "A5 72 1B 3E", "A1 C2 53 74", "6C AE 73 17", + "0A 46 95 1C", "47 25 38 E1", "DC 53 14 3E", "80 54 0C 93", "2A 76 64 21", + "10 FD FC 2E", "19 F0 95 B4", "D7 FC 80 67", "60 B9 2A BE", "75 36 B5 E5", + "87 90 91 5E", "69 E9 1F C5", "C9 DE 3B B4", "08 71 21 DC", "9C B3 BC 3E", + "17 A4 91 5E", "F7 88 94 5E", "89 28 3A B4", "BC C3 87 43", "DC FB 0E 3F", +}; + +Servo myservo; +MFRC522 mfrc522(SS_PIN, RST_PIN); + +void BuzzerRing(boolean Dur = false) { + for (uint8_t i = 0; i < 2; i++) { + tone(Buzzer, 300); + if (Dur)delay(200); + else delay(100); + noTone(Buzzer); + delay(100); + } +} + +boolean GetKey(String CONTECTED_UID) { + for (uint8_t i = 0; i < UIDNUM; i++) + if (CONTECTED_UID == UID[i])return true; + return false; +} + +void setup() +{ + Serial.begin(115200); + SPI.begin(); + mfrc522.PCD_Init(); + myservo.attach(8); + myservo.write(40); + pinMode(Buzzer, OUTPUT); + BuzzerRing(); + Serial.print("System Started!\nBuilt in UID Num:"); + Serial.println(UIDNUM); + delay(3000); +} + +void loop() +{ + if ( ! mfrc522.PICC_IsNewCardPresent())return; + if ( ! mfrc522.PICC_ReadCardSerial())return; + + String content = ""; + Serial.print("UID("); + for (byte i = 0; i < mfrc522.uid.size; i++) + { + Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); + Serial.print(mfrc522.uid.uidByte[i], HEX); + content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ")); + content.concat(String(mfrc522.uid.uidByte[i], HEX)); + } + content.toUpperCase(); + String CONTECTED_UID = content.substring(1); + + if (GetKey(CONTECTED_UID)) { + Serial.println(" ):Access Accpeted!"); + BuzzerRing(); + for (int pos = 40; pos <= 60; pos += 1) { + myservo.write(pos); + delay(5); + } + delay(1000); + for (int pos = 60; pos >= 40; pos -= 1) { + myservo.write(pos); + delay(5); + } + } + else { + Serial.println(" ):Access Denied!"); + BuzzerRing(true); + } +} +``` + +## 3. 三代版本——步进电机版 + +步进电机版和前面也是类似,只是将舵机换成步进电机,这里是使用 28BYJ-48 步进电机。 + +步进电机版接线图如下: + +![Relay Version](./images/1-3.png) + +我使用的示例代码如下: + +```cpp +#include +#include +#include + +const uint8_t PinA = 2; +const uint8_t PinB = 3; +const uint8_t PinC = 4; +const uint8_t PinD = 5; +const uint8_t Buzzer = 8; +const uint8_t RST_PIN = 9; +const uint8_t SS_PIN = 10; +const uint16_t stepsPerRevolution = 400; +const uint8_t UIDNUM = 37; + +const String UID[UIDNUM] = { + "F1 41 01 5F", "01 5E 95 2B", "1A FD A5 21", "E1 D8 FC 5E", "E1 E0 00 5F", + "D1 7E FF 5E", "21 DE 01 5F", "D1 3A F6 5E", "31 AB FD 5E", "21 35 F7 5E", + "0A 57 80 1C", "FA 5D 70 7F", "D2 AA 1C 3E", "15 4F B9 E5", "75 89 B6 E5", + "0A 8C 36 B6", "00 69 0F 93", "75 89 B6 E5", "FA 50 35 B6", "EA B1 1C 3E", + "60 DB 0D 93", "5A 32 34 B6", "72 B1 85 44", "A3 A4 1B 3E", "0C 3C 1A 3F", + "72 B1 85 44", "50 DF D3 03", "A5 72 1B 3E", "A1 C2 53 74", "6C AE 73 17", + "0A 46 95 1C", "47 25 38 E1", "DC 53 14 3E", "80 54 0C 93", "2A 76 64 21", + "DC FB 0E 3F", "5A 20 4C 2D", +}; + +MFRC522 mfrc522(SS_PIN, RST_PIN); +AccelStepper stepper(8, PinA, PinC, PinB, PinD); + +void BuzzerRing(boolean Dur = false) { + for (uint8_t i = 0; i < 2; i++) { + digitalWrite(Buzzer, HIGH); + if (Dur)delay(200); + else delay(100); + digitalWrite(Buzzer, LOW); + delay(100); + } +} + +boolean GetKey(String CONTECTED_UID) { + for (uint8_t i = 0; i < UIDNUM; i++) + if (CONTECTED_UID == UID[i])return true; + return false; +} + +void setup() +{ + SPI.begin(); + Serial.begin(115200); + mfrc522.PCD_Init(); + stepper.setMaxSpeed(500.0); + stepper.setAcceleration(1000.0); + stepper.runToNewPosition(0); + pinMode(Buzzer, OUTPUT); + BuzzerRing(); + Serial.print("System Started!\nBuilt in UID Num:"); + Serial.println(UIDNUM); + delay(3000); +} + +void loop() +{ + if ( ! mfrc522.PICC_IsNewCardPresent())return; + if ( ! mfrc522.PICC_ReadCardSerial())return; + + String content = ""; + Serial.print("UID("); + for (byte i = 0; i < mfrc522.uid.size; i++) { + Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); + Serial.print(mfrc522.uid.uidByte[i], HEX); + content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ")); + content.concat(String(mfrc522.uid.uidByte[i], HEX)); + } + content.toUpperCase(); + String CONTECTED_UID = content.substring(1); + + if (GetKey(CONTECTED_UID)) { + Serial.println(" ):Access Accpeted!"); + BuzzerRing(); + stepper.runToNewPosition(stepsPerRevolution); + delay(1000); + stepper.runToNewPosition(0); + } + else { + Serial.println(" ):Access Denied!"); + BuzzerRing(true); + } + delay(3000); +} +``` diff --git a/docs/electronics/projects/nfc/chapter2.md b/docs/electronics/projects/nfc/chapter2.md new file mode 100644 index 0000000..e499be6 --- /dev/null +++ b/docs/electronics/projects/nfc/chapter2.md @@ -0,0 +1,105 @@ +--- +title: 目前门禁版本 +--- + +# 目前门禁版本 + +## 1. 原理图及 PCB + +这个版本我画了 PCB,大家可以到我的这个[Github 仓库](https://github.com/MR-Addict/Door-lock-system.git)下载 Gerber 文件进行 PCB 打样。 + +原理图如下: + +![Schematic](./images/2-3.png) + +PCB 三维示意图如下: + +![PCB Preview](./images/2-4.png) + +## 2. 项目文件及代码 + +下面我简单介绍一下项目文件。 + +该项目的文件结构如下: + +- RFID_ESP32.ino +- WIFI_WS.h +- data + - favicon.png + - index.html + - index.js + - login.html + - style.css + +其中`RFID_ESP32.ino`包含了主要的控制代码,`WIFI_WS.h`主要是有关包含了有关 WIFI 服务器和 websocket 的配置,而`data文件夹`就是网页服务器相关的文件了。 + +关于示例代码,我也建议大家前往我的[Github 仓库](https://github.com/MR-Addict/Door-lock-system.git)下载参考,因为这个项目相对来说比较大,代码量相对来说比较多,同时还有网页设计,服务器搭建等等,大家量力而行。 + +同时,这个项目因为需要把网页内容上传到到 ESP32 中,需要使用到`SPIFFS(SPI Flash File Storage)`技术,关于 ESP32 的 SPIFFS 大家可以参考以下内容: + +[Install ESP32 Filesystem Uploader in Arduino IDE](https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/) + +如果你不使用 WIFI,那么可以把有关 WIFI 的内容删掉。 + +在这边我强调几点重要的地方。 + +### 2.1 第一点 + +首先为了安全,我在 Arduino 的库文件夹下创建了`arduino_secrets的文件夹`,里面有一个`arduino_secrets.h`的文件,我在里面定义了几个重要的变量,包括办公室网络名,网络密码,默认用户名,默认用户密码: + +```cpp +const char* ssid = "STAS-507"; +const char* password = "dinghao666"; +const char* login_user = "admin"; +const char* login_pwd = "dinghao666"; +``` + +当然你也可以将这些变量直接定义在项目文件里。 + +### 2.2 第二点 + +你可以通过更改 WIFI_WS 中 WIFI_Init 函数里面以下几行来设置你的`ESP主机名`,这也是[http://unlockdoor-507](http://unlockdoor-507)的由来: + +```cpp +// change hostname to unlockdoor-507 +String hostname = "unlockdoor-507"; +WiFi.mode(WIFI_STA); +WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); +WiFi.setHostname(hostname.c_str()); +``` + +### 2.3 第三点 + +由于这个项目跨度比较长,而 Arduino 的`MFRC522.h`库也在不断地改进和完善,目前的版本使用起来更加简单了,但是还是兼容旧版本的。新版本能够直接读取门禁卡数据,解码成 4 个 8 位的数据,因此我用数组存储每张卡的信息。 + +如下: + +```cpp +const uint8_t UID[][4] = { + {0xF1, 0x41, 0x01, 0x5F}, {0x01, 0x5E, 0x95, 0x2B}, {0x1A, 0xFD, 0xA5, 0x21}, {0xE1, 0xD8, 0xFC, 0x5E}, {0xE1, 0xE0, 0x00, 0x5F}, + {0xD1, 0x7E, 0xFF, 0x5E}, {0x21, 0xDE, 0x01, 0x5F}, {0xD1, 0x3A, 0xF6, 0x5E}, {0x31, 0xAB, 0xFD, 0x5E}, {0x21, 0x35, 0xF7, 0x5E}, + {0x0A, 0x57, 0x80, 0x1C}, {0xFA, 0x5D, 0x70, 0x7F}, {0xD2, 0xAA, 0x1C, 0x3E}, {0x15, 0x4F, 0xB9, 0xE5}, {0x75, 0x89, 0xB6, 0xE5}, + {0x0A, 0x8C, 0x36, 0xB6}, {0x00, 0x69, 0x0F, 0x93}, {0x75, 0x89, 0xB6, 0xE5}, {0xFA, 0x50, 0x35, 0xB6}, {0xEA, 0xB1, 0x1C, 0x3E}, + {0x60, 0xDB, 0x0D, 0x93}, {0x5A, 0x32, 0x34, 0xB6}, {0x72, 0xB1, 0x85, 0x44}, {0xA3, 0xA4, 0x1B, 0x3E}, {0x0C, 0x3C, 0x1A, 0x3F}, + {0x72, 0xB1, 0x85, 0x44}, {0x50, 0xDF, 0xD3, 0x03}, {0xA5, 0x72, 0x1B, 0x3E}, {0xA1, 0xC2, 0x53, 0x74}, {0x6C, 0xAE, 0x73, 0x17}, + {0x0A, 0x46, 0x95, 0x1C}, {0x47, 0x25, 0x38, 0xE1}, {0xDC, 0x53, 0x14, 0x3E}, {0x80, 0x54, 0x0C, 0x93}, {0x2A, 0x76, 0x64, 0x21}, + {0xDC, 0xFB, 0x0E, 0x3F}, {0x5A, 0x20, 0x4C, 0x2D}, {0x3A, 0x79, 0x4E, 0x2D}, {0x08, 0x82, 0xDB, 0x28}, {0x37, 0xF1, 0x20, 0xB3}, + {0x7A, 0xD7, 0x4F, 0x2D}, +}; +``` + +你可以通过仓库的另外一个`RFID_READ`程序读取新卡的信息,然后复制到到上面的数组中就可以了。 + +## 3. 使用方法 + +目前的门禁是由 ESP32 作为核心,仍然使用 MFRC522 扫描门禁卡,使用步进电机来开门,用蜂鸣器作为提示音,同时使用避障传感器触发中断,兼有 WIFI 功能。 + +当有人靠近 NFC 时会触发中断,然后 MFRC522 检测是否有门禁卡,如果该门禁卡是内部存储的卡则开门,并伴随两声短促的蜂鸣器声音;没有则不开门,同时蜂鸣器发出两声较长的声音。 + +你也可以通过 WIFI,访问 ESP32 的内部服务器。在连接办公室网络的情况下,你可以通过[http://unlockdoor-507](http://unlockdoor-507)进行访问,`unlockdoor-507`是 ESP32 的主机名。进入网页的时候需要进行身份验证,默认的用户名是`admin`,默认密码`dinghao666`。 + +![Login](./images/2-1.png) + +登录成功后点击`open`就可以开门了。 + +![WEB UI](./images/2-2.png) diff --git a/docs/electronics/projects/nfc/chapter3.md b/docs/electronics/projects/nfc/chapter3.md new file mode 100644 index 0000000..70d3b21 --- /dev/null +++ b/docs/electronics/projects/nfc/chapter3.md @@ -0,0 +1,16 @@ +--- +title: 未来的改进 +--- + +# 未来的改进 + +我总结了一下,目前我们的门禁主要有以下两个问题: + +- 开门方式 +- NFC 模块的稳定性 + +相对来说,虽然我们的开门方式不是很好,很不专业,但依然有效,大家可以找找办法,解决瞬间电流过大的问题。 + +NFC 模块的稳定性是更大问题,据我观测,目前虽然使用了中断,但是 NFC 还是会宕机,但仍然可以通过 WIFI 开门,所以我推测应该是 NFC 模块的问题。我想到的一个办法是通过计时器时不时地重启 NFC,不需要重启 ESP32。另一个办法就是寻找更加稳定的 NFC 模块。 + +希望大家日后可以有更多的想法,不断解决这些问题,完善我们的门禁系统。 diff --git a/docs/electronics/projects/nfc/images/0-1.png b/docs/electronics/projects/nfc/images/0-1.png new file mode 100644 index 0000000..9837fb1 Binary files /dev/null and b/docs/electronics/projects/nfc/images/0-1.png differ diff --git a/docs/electronics/projects/nfc/images/0-2.png b/docs/electronics/projects/nfc/images/0-2.png new file mode 100644 index 0000000..86b6ca0 Binary files /dev/null and b/docs/electronics/projects/nfc/images/0-2.png differ diff --git a/docs/electronics/projects/nfc/images/1-1.png b/docs/electronics/projects/nfc/images/1-1.png new file mode 100644 index 0000000..fc29db0 Binary files /dev/null and b/docs/electronics/projects/nfc/images/1-1.png differ diff --git a/docs/electronics/projects/nfc/images/1-2.png b/docs/electronics/projects/nfc/images/1-2.png new file mode 100644 index 0000000..4d7cd84 Binary files /dev/null and b/docs/electronics/projects/nfc/images/1-2.png differ diff --git a/docs/electronics/projects/nfc/images/1-3.png b/docs/electronics/projects/nfc/images/1-3.png new file mode 100644 index 0000000..28ca9ef Binary files /dev/null and b/docs/electronics/projects/nfc/images/1-3.png differ diff --git a/docs/electronics/projects/nfc/images/2-1.png b/docs/electronics/projects/nfc/images/2-1.png new file mode 100644 index 0000000..b02816a Binary files /dev/null and b/docs/electronics/projects/nfc/images/2-1.png differ diff --git a/docs/electronics/projects/nfc/images/2-2.png b/docs/electronics/projects/nfc/images/2-2.png new file mode 100644 index 0000000..825d3e2 Binary files /dev/null and b/docs/electronics/projects/nfc/images/2-2.png differ diff --git a/docs/electronics/projects/nfc/images/2-3.png b/docs/electronics/projects/nfc/images/2-3.png new file mode 100644 index 0000000..16e376a Binary files /dev/null and b/docs/electronics/projects/nfc/images/2-3.png differ diff --git a/docs/electronics/projects/nfc/images/2-4.png b/docs/electronics/projects/nfc/images/2-4.png new file mode 100644 index 0000000..67beeae Binary files /dev/null and b/docs/electronics/projects/nfc/images/2-4.png differ diff --git a/docs/electronics/projects/nfc/intro.md b/docs/electronics/projects/nfc/intro.md new file mode 100644 index 0000000..1dd90dd --- /dev/null +++ b/docs/electronics/projects/nfc/intro.md @@ -0,0 +1,19 @@ +--- +title: 办公室门禁历史 +--- + +# 办公室门禁历史 + +似乎对于每一届的技术部人来说,研究自动开门已经是一个老传统了。从办公室门伤痕累累的洞眼和前届大佬们画的 PCB 板可以看出来大家都做了很多研究。我们在这里做一下文档整理也可以减少重复造轮子的过程,因为老前辈们的资料显然是找不到了。 + +![image](./images/0-1.png) + +我在门禁卡这个项目上也做了接近一年了,初版是仿照前辈们的做法,使用 Arduino 和继电器,不过在后来的使用过程中似乎是开门的瞬间电流太大导致整个系统不稳定宕机。接着我找到一个很投机的办法,就是使用舵机牵动门锁里面的拉杆开门,这个方法要有效的得多。至今我们办公室对面的 508 就是这个方法。 + +可能是因为我们办公室的人进出比较频繁,所以我们的门禁卡使用一段时间就挂机了。我初步猜测是因为舵机需要计时器不断控制,非常占用资源,所以我把舵机换成了步进电机,这个方法确实更加有效,但是还是会时不时宕机。由于那个时候已经是大二上学期末了,所以我就将其暂时搁置了,宕机了就重启。 + +![image](./images/0-2.png) + +后来我猜测可能是由于 Arduino 的 AVR 芯片太差,需要换一个更好的单片机。于是大二下学期开学我就把 AVR 单片机换成了**ESP32**,同时为了提高效率,减少单片机内存占用,我**添加了中断**,只有在有人的时候才会去检测 NFC 卡。同时我在这次的项目中加入了**WIFI**的功能,这样我们就可以在连接办公室网络的情况下开门了,即使没带门禁卡也没关系。其实呢,将 ESP32**映射到公网**也是可以做到的,这个功能可以让大家在日后添加,我认为暂时没有必要,技术上也没什么难度。 + +这个版本也是目前的最新版本,但是我个人认为这个系统还有地方需要改进,等到日后有时间再说吧。 diff --git a/docs/electronics/tutorial/eda/chapter1.md b/docs/electronics/tutorial/eda/chapter1.md new file mode 100644 index 0000000..0f63e54 --- /dev/null +++ b/docs/electronics/tutorial/eda/chapter1.md @@ -0,0 +1,64 @@ +--- +title: 555计时器 +--- + +# 555 计时器 + +一般在设计 PCB 之前我们已经对所要设计的核心原件有了足够的认识了,一无所知就去设计 PCB 非常不明智。你最好是已经通过实物验证过了你的设计,然后再设计。本入门的例子使用的是 555 计时器,首先我们来了解一下 555 计时器。 + +## 1. 555 计时器 + +55 定时器是美国 Signetics 公司 1972 年研制的用于取代机械式定时器的中规模集成电路,因输入端设计有三个 5kΩ 的电阻而得名。此电路后来竟风靡世界。目前,流行的产品有 4 个: + +- BJT + - 555 + - 556(含有两个 555) +- CMOS + - 7555 + - 7556(含有两个 7555) + +## 2. 555 计时器工作原理 + +| 原理图 | 波形 | +| :-------------------------------------: | :------------------------: | +| ![555Timer-Schematic](./images/1-1.png) | ![Scope](./images/1-2.png) | + +### 3. 555 计时器计算公式 + +不同的 R1,R2,和 C 能够获得不同的频率和占空比,计算公式如下: + +\\(T\_{high} = In(2)*(R_1+R_2)*C\\) + +\\(T\_{low} = In(2)*{R_2}*C\\) + +\\(F = \frac{1}{T*{high}+T*{low}}=\frac{1}{In(2)*(R1+2R2)*C}\\) + +\\(W*{duty} = 100\*\frac{T*{high}}{T*{high}+T*{low}}=100\*\frac{R_1+R_2}{R1+2R2}\\) + +因此加入我们使用 R1=1K,R2=470K,C=1uF,我们可以得到: + +- \\(T\_{high} = 0.326\\) +- \\(T\_{low} = 0.325\\) +- \\(F = 1.530\\) +- \\(W\_{duty} = 50.053\\) + +你可以通过一些写好的程序计算这个值,比如: + +[![555Timer-Online-Calculator](./images/1-3.png)](https://www.kechuang.org/tools/elc/tool/ne555.html) + +### 4. 555 计时器应用 + +555 计时器的应用主要有: + +- 震荡器 +- PWM 产生器 + +### 5. 555Timer BOM + +| BOM | 数量 | 备注 | +| :------: | :--: | :--------: | +| 555Timer | 1 | 定时器 | +| 电源 | 1 | 4.5-18V | +| LED | 1 | 红色 | +| 电容 | 1 | 1uF | +| 电阻 | 3 | 1K,1K,470K | diff --git a/docs/electronics/tutorial/eda/chapter2.md b/docs/electronics/tutorial/eda/chapter2.md new file mode 100644 index 0000000..ea2acd9 --- /dev/null +++ b/docs/electronics/tutorial/eda/chapter2.md @@ -0,0 +1,24 @@ +--- +title: 绘制符号和封装 +--- + +# 绘制符号和封装 + +## 1. 绘制符号 + +- 唯一有物理作用的是**引脚** +- 引脚的顺序可以根据实际情况调整 +- 做好标记和提示 + +## 2. 绘制封装 + +- 与符号有联系的是**引脚和焊盘** +- 标准元器件请按照相应的标准绘制 +- 仔细检查封装的尺寸规格(非常重要) +- 做好标记和提示 +- 给封装添加 3D 模型 +- [NE555 数据手册](pdf/NE555.pdf) +- [数据手册下载网址](https://datasheetspdf.com/) +- [3D 模型下载网址](https://www.3dcontentcentral.com/default.aspx) + +[![IC-Packages](./images/2-1.png)](pdf/IC-Packages.pdf) diff --git a/docs/electronics/tutorial/eda/chapter3.md b/docs/electronics/tutorial/eda/chapter3.md new file mode 100644 index 0000000..299de71 --- /dev/null +++ b/docs/electronics/tutorial/eda/chapter3.md @@ -0,0 +1,9 @@ +--- +title: 设计原理图 +--- + +# 设计原理图 + +- 实物验证 +- 合理使用标签 +- 规划好板块,适当使用标记 diff --git a/docs/electronics/tutorial/eda/chapter4.md b/docs/electronics/tutorial/eda/chapter4.md new file mode 100644 index 0000000..a13349e --- /dev/null +++ b/docs/electronics/tutorial/eda/chapter4.md @@ -0,0 +1,9 @@ +--- +title: 设计PCB +--- + +# 设计 PCB + +- 先规划后布线 +- 滤波/去耦电容尽量靠近相关元器件 +- 双层板上横下竖 diff --git a/docs/electronics/tutorial/eda/images/0-1.png b/docs/electronics/tutorial/eda/images/0-1.png new file mode 100644 index 0000000..767b524 Binary files /dev/null and b/docs/electronics/tutorial/eda/images/0-1.png differ diff --git a/docs/electronics/tutorial/eda/images/1-1.png b/docs/electronics/tutorial/eda/images/1-1.png new file mode 100644 index 0000000..61fa913 Binary files /dev/null and b/docs/electronics/tutorial/eda/images/1-1.png differ diff --git a/docs/electronics/tutorial/eda/images/1-2.png b/docs/electronics/tutorial/eda/images/1-2.png new file mode 100644 index 0000000..34971bf Binary files /dev/null and b/docs/electronics/tutorial/eda/images/1-2.png differ diff --git a/docs/electronics/tutorial/eda/images/1-3.png b/docs/electronics/tutorial/eda/images/1-3.png new file mode 100644 index 0000000..65f4262 Binary files /dev/null and b/docs/electronics/tutorial/eda/images/1-3.png differ diff --git a/docs/electronics/tutorial/eda/images/2-1.png b/docs/electronics/tutorial/eda/images/2-1.png new file mode 100644 index 0000000..b76982e Binary files /dev/null and b/docs/electronics/tutorial/eda/images/2-1.png differ diff --git a/docs/electronics/tutorial/eda/intro.md b/docs/electronics/tutorial/eda/intro.md new file mode 100644 index 0000000..8f74943 --- /dev/null +++ b/docs/electronics/tutorial/eda/intro.md @@ -0,0 +1,28 @@ +--- +title: EDA入门简介 +--- + +# EDA 入门简介 + +## 1. 什么是 EDA + +EDA 即 **Electronic design automation(电器设计自动化)** 的简称。是通过 **CAD(计算机辅助设计)** 软件来完成超大规模集成电路芯片的功能设计、综合、验证、物理设计(包括布局、布线、版图、设计规则检查等)等流程的设计方式。 + +常见的 EDA 设计软件有: + +- Protel +- Altium Designer +- Matlab +- KiCad +- LCEDA + +## 2. EDA 设计流程 + +![EDA-Design](./images/0-1.png) + +## 3. 章节内容 + +- [555 计时器](./chapter1.md) +- [绘制符号和封装](./chapter2.md) +- [设计原理图](./chapter3.md) +- [设计 PCB](./chapter4.md) diff --git a/docs/electronics/tutorial/eda/pdf/IC-Packages.pdf b/docs/electronics/tutorial/eda/pdf/IC-Packages.pdf new file mode 100644 index 0000000..8c5ee2f Binary files /dev/null and b/docs/electronics/tutorial/eda/pdf/IC-Packages.pdf differ diff --git a/docs/electronics/tutorial/eda/pdf/NE555.pdf b/docs/electronics/tutorial/eda/pdf/NE555.pdf new file mode 100644 index 0000000..b93f498 Binary files /dev/null and b/docs/electronics/tutorial/eda/pdf/NE555.pdf differ diff --git a/docs/electronics/tutorial/git/Images/0-1.png b/docs/electronics/tutorial/git/Images/0-1.png new file mode 100644 index 0000000..6bbeab6 Binary files /dev/null and b/docs/electronics/tutorial/git/Images/0-1.png differ diff --git a/docs/electronics/tutorial/git/Images/1-1.png b/docs/electronics/tutorial/git/Images/1-1.png new file mode 100644 index 0000000..0c22ce2 Binary files /dev/null and b/docs/electronics/tutorial/git/Images/1-1.png differ diff --git a/docs/electronics/tutorial/git/Images/1-2.png b/docs/electronics/tutorial/git/Images/1-2.png new file mode 100644 index 0000000..954fcb8 Binary files /dev/null and b/docs/electronics/tutorial/git/Images/1-2.png differ diff --git a/docs/electronics/tutorial/git/chapter1.md b/docs/electronics/tutorial/git/chapter1.md new file mode 100644 index 0000000..844c2a8 --- /dev/null +++ b/docs/electronics/tutorial/git/chapter1.md @@ -0,0 +1,29 @@ +--- +title: 集中式VS分布式 +--- + +# 集中式 VS 分布式 + +## 1.版本控制系统的联系与区别 + +在软件开发过程中,**协同开发以及版本迭代更新**是再正常不过的事情了。但是能够完整、便捷的将版本进行管理确是不太容易的一件事情。 + +就好比,一个项目,我们开发了第一版、第二版、第三版、打死不改版、绝对不改版、终极版、终极不修改版以至于再改就砸电脑版,最终敲定的方案是第一版。这种情况下,改到最后已经看不出当初的模样了。所以,我们需要将每一次的修改记录并保存下来,以便于为后面的版本溯源。 + +凡事都是有两面性的,作为版本保存和记录的软件系统也是一样的。所以就诞生了两种不同的管理模式,一种是叫**集中式版本控制系统**(例如 CVS 及 SVN),另一种则是**分布式版本控制系统**(例如 Git)。 + +## 2.集中式版本控制系统 + +集中式的版本控制系统,所有的版本库是放在中央服务器中的,也就是说我们每一次的修改上传都是保存在中央服务器中的。中央服务器就是个大仓库,大家把产品都堆里面,每一次需要改进和完善的时候,需要去仓库里面把文件给提出来,然后再操作。 + +这种版本管理模式,**存在的问题是协同工作时可能造成的提交文件不完整,版本库损坏等问题**,A 提交,B 也提交,C 下载的时候可能得到的仅是 A,B 提交版本中的一部分。其次,**集中式版本控制系统必须要联网才能工作**,不论是局域网还是互联网,必须上网才能将本地版本推送至服务器进行保存。 + +![集中式](./images/1-1.png) + +## 3.分布式版本控制系统 + +分布式版本控制系统,**重点在于分布**。分布的含义不是说每台计算机上只留有版本库的一部分。恰恰相反,分布的含义是**每台计算机上都还有一个完整的版本库**。这个时候,你的修改仅仅需要提交给本地的版本库进行保存就可以了。 + +不同于集中式版本控制系统的“中央服务器”,分布式版本控制系统可以通过推送版本库,实现不同的计算机之间的版本共享。就是说对于同一个文件 A,如果两个人同时对 A 文件进行了修改,最新的版本应该都保存在各自的计算机中,想要实现协同开发,只需要将各自的最新版本库推送给对方,就可以得到最新的版本库了。 + +![分布式](./images/1-2.png) diff --git a/docs/electronics/tutorial/git/chapter2.md b/docs/electronics/tutorial/git/chapter2.md new file mode 100644 index 0000000..45b01e1 --- /dev/null +++ b/docs/electronics/tutorial/git/chapter2.md @@ -0,0 +1,26 @@ +--- +title: Git的安装与配置 +--- + +# Git 的安装与配置 + +## 1. Git 的安装 + +打开 git 官网下载 git 对应操作系统的版本。如果官网下载太慢,我们可以使用淘宝镜像下载:[链接](http://npm.taobao.org/mirrors/git-for-windows/)找到最新版本的 Git 点击下载。 + +## 2. Git 的配置 + +**设置用户名与邮箱**(用户标识,必要) + +当你安装 Git 后首先要做的事情是设置你的用户名称和 e-mail 地址。这是非常重要的,因为每次 Git 提交都会使用该信息。它被永远的嵌入到了你的提交中: + +```shell +git config --global user.name "*****" #名称 +git config --global user.email ******@***.com #邮箱 +``` + +可以使用命令查看是否设置完成 + +```shell +git config --global --list +``` diff --git a/docs/electronics/tutorial/git/chapter3.md b/docs/electronics/tutorial/git/chapter3.md new file mode 100644 index 0000000..eb153ad --- /dev/null +++ b/docs/electronics/tutorial/git/chapter3.md @@ -0,0 +1,96 @@ +--- +title: Git的常用命令 +--- + +# Git 的常用命令 + +## 1. git init + +创建一个新的仓库。执行后可以看到,仅仅在项目目录**多出了一个.git 目录**,关于版本等的所有信息都在这个目录里面。 + +## 2. git clone + +克隆一个远程仓库。 + +```bash +git clone [url] # https://github.com/ +``` + +## 3. git add . + +git add .命令可将所有文件添加到暂存区。 + +## 4. git commit + +将暂存区的内容添加到**本地仓库中**。 + +```bash +git commit -m [message] +``` + +## 5. git push + +git push 命用于从将本地的分支版本上传到远程并合并。 + +命令格式如下: + +```bash +git push <远程主机名> <本地分支名>:<远程分支名> +``` + +#如果本地分支名与远程分支名相同,则可以省略冒号: + +```bash +git push <远程主机名> <本地分支名> #例如 +git push origin main +``` + +## 6. git branch + +创建一个分支,分支上的操作不会影响到主支: + +```bash +git branch (branch name) +``` + +删除分支命令: + +```bash +git branch -d (branch name) +``` + +## 7. git checkout + +切换分支命令。 + +```bash +git checkout (branch name) +``` + +## 8. git merge + +合并分支命令。 + +```bash +git merge (branch name) +``` + +## 9. git log + +在使用 Git 提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,我们可以使用 git log 命令查看。 + +## 10. git status + +git status 命令用于查看在你上次提交之后是否有对文件进行再次修改。 + +## 11. git reset --hard + +git reset 命令用于回退版本,可以指定退回某一次提交的版本。 + +git reset 命令语法格式如下: + +```bash +git reset [--soft | --mixed | --hard] [HEAD] +#--mixed 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交 +#(commit)保持一致,工作区文件内容保持不变。 +``` diff --git a/docs/electronics/tutorial/git/intro.md b/docs/electronics/tutorial/git/intro.md new file mode 100644 index 0000000..6188ad5 --- /dev/null +++ b/docs/electronics/tutorial/git/intro.md @@ -0,0 +1,19 @@ +--- +title: Git简介 +--- + +# Git 简介 + +Git 是什么? + +Git 是目前世界上最先进的**分布式版本控制系统**(没有之一)。 + +那什么是版本控制系统? + +如果你用 Microsoft Word 写过长篇大论,那你一定有这样的经历:想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的 Word 文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的 Word 文档变成了这样: + +![Word文档](./images/0-1.png) + +看着这一堆乱七八糟的文件,心里难免会烦躁不安。于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便? + +使用 Git 便可以达成以上的要求。 diff --git a/docs/electronics/tutorial/intro.md b/docs/electronics/tutorial/intro.md new file mode 100644 index 0000000..b7185d1 --- /dev/null +++ b/docs/electronics/tutorial/intro.md @@ -0,0 +1,5 @@ +# 半小时入门介绍 + +本专题的内容主要是对某个专题或者内容作简短的介绍,选择从整体的全貌来看某些问题,起到开拓视野的作用,不会涉及非常详细的内容。 + +本专题也会涉及一些技术难度不高,但是非常有用的技术,比如 Git,markdown 等等。 diff --git a/docs/electronics/tutorial/markdown/chapter1.md b/docs/electronics/tutorial/markdown/chapter1.md new file mode 100644 index 0000000..4afbbde --- /dev/null +++ b/docs/electronics/tutorial/markdown/chapter1.md @@ -0,0 +1,101 @@ +--- +title: Markdown语法(1) +--- + +# Markdown 语法 + +## 1. 标题 + +在 markdown 中你可以使用`#`标注标题: + +| Markdown | 解释 | +| :------: | :------: | +| # | 一级标题 | +| ## | 二级标题 | +| ### | 三级标题 | +| #### | 四级标题 | +| ##### | 五级标题 | +| ###### | 六级标题 | + +## 2. 段落 + +在 markdown 中,你可以对段落进行一些修饰,主要有: + +### 2.1 换行 + +- 使用`两个以上空格`加上`回车` +- 使用`空行重新开始一个段落` + +### 2.2 字体 + +| Markdown | 解释 | 输出结果 | +| :----------------: | :--------------: | :--------------: | +| \*斜体文本\* | 文字斜体 | _斜体文本_ | +| \*\*粗文本\*\* | 加粗文本 | **粗体文本** | +| \*\*\*粗斜体\*\*\* | 加粗并且倾斜文字 | **_粗斜体文本_** | + +### 2.3 分割线 + +连用三个以上的`*`,`-`或者`_`来建立一个分割线,行内不能有其他内容,例如`***`将会输出: + +--- + +### 2.4 删除线 + +在文字两端都加上两个波浪线\~\~,例如`~~Markdowm~~`将会输出: + +~~Markdowm~~ + +### 2.5 下划线 + +下划线的功能只能使用 HTML 的标签来实现,例如`带下划线文本`将会输出: + +带下划线文本 + +## 3. 列表 + +### 3.1 无序列表 + +Markdown 支持有序列表和无序列表。 + +无序列表使用星号`*`,加号`+`或是减号`-`作为列表标记,这些标记后面要添加一个空格,然后再填写内容。 + +例如: + +```plaintext +- 列表一 +- 列表二 +- 列表三 +- 列表四 +``` + +将会输出: + +- 列表一 +- 列表二 +- 列表三 +- 列表四 + +### 3.2 有序列表 + +有序列表使用数字并加上`.`号来表示,有序内标将`忽略.前的数字`,但是一定要有数字。 + +例如: + +```plaintext +1. 列表1 +4. 列表2 +8. 列表3 +3. 列表4 + 1. 列表4-1 + 3. 列表4-2 +``` + +将会输出: + +1. 列表 1 +2. 列表 2 +3. 列表 3 +4. 列表 4 + 1. 列表 4-1 + 2. 列表 4-2 diff --git a/docs/electronics/tutorial/markdown/chapter2.md b/docs/electronics/tutorial/markdown/chapter2.md new file mode 100644 index 0000000..6e7b8d1 --- /dev/null +++ b/docs/electronics/tutorial/markdown/chapter2.md @@ -0,0 +1,136 @@ +--- +title: Markdown语法(2) +--- + +# Markdown 语法 + +## 1. 代码 + +在 Markdown 中标注代码主要有三种方法。 + +### 1.1 第一种,使用` + +段落上的一个函数或片段的代码可以用反引号\`把它包起来,例如 **\`printf()\`** 将会输出: + +`printf()` + +### 1.2 第二种,使用制表符 + +代码区块使用 4 个空格或者一个制表符(Tab 键)。 + +例如: + +```plaintext + #include + + int main(){ + printf("Hello world!"); + return 0; + } +``` + +将会输出: + + #include + + int main(){ + printf("Hello world!"); + return 0; + } + +### 1.3 第三种,使用``` + +用```包裹一段代码,并指定一种语言(也可以不指定)。 + +例如: + +```c +#include + +int main(){ + printf("Hello world!"); + return 0; +} +``` + +## 2. 链接 + +使用`[]`,`()`或者`<>`将链接地址包括。 + +例如: + +```plaintext +[google](https://www.google.com) + +[百度](https://www.baidu.com) +``` + +将会输出: + +[google](https://www.google.com) + +[百度](https://www.baidu.com) + +## 3. 图片 + +格式: + +- 开头一个感叹号 ! +- 接着一个方括号,里面放上图片的替代文字 +- 接着一个普通括号,里面放上图片的网址,最后还可以用引号包住并加上选择性的 'title' 属性的文字。 + +例如: + +```plaintext +![老友记](https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic4.zhimg.com%2Fv2-2e5240f843638b567930ab2b765e1cd0_1200x500.jpg&refer=http%3A%2F%2Fpic4.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650372506&t=1f1d3e9d41d792cf4f7b03630101f367) + +![老友记](./images/1-1.jpeg) +``` + +将会输出: + +![老友记](https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic4.zhimg.com%2Fv2-2e5240f843638b567930ab2b765e1cd0_1200x500.jpg&refer=http%3A%2F%2Fpic4.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650372506&t=1f1d3e9d41d792cf4f7b03630101f367) + +![老友记](./images/1-1.jpeg) + +## 4. 表格 + +使用`|`来分隔不同的单元格,使用`-`来分隔表头和其他行。 + +例如: + +```plaintext +| 表头 | 表头 | +| ------ | ------ | +| 单元格 | 单元格 | +| 单元格 | 单元格 | +``` + +将会输出: + +| 表头 | 表头 | +| ------ | ------ | +| 单元格 | 单元格 | +| 单元格 | 单元格 | + +对齐方式: + +- -: 设置内容和标题栏居右对齐。 +- :- 设置内容和标题栏居左对齐。 +- :-: 设置内容和标题栏居中对齐。 + +例如: + +```plaintext +| 左对齐 | 居中 | 右对齐 | +| :--------- | :--------: | ---------: | +| 单元格 | 单元格 | 单元格 | +| 单元格元素 | 单元格元素 | 单元格元素 | +``` + +将会输出: + +| 左对齐 | 居中 | 右对齐 | +| :--------- | :--------: | ---------: | +| 单元格 | 单元格 | 单元格 | +| 单元格元素 | 单元格元素 | 单元格元素 | diff --git a/docs/electronics/tutorial/markdown/images/1-1.jpeg b/docs/electronics/tutorial/markdown/images/1-1.jpeg new file mode 100644 index 0000000..72bde69 Binary files /dev/null and b/docs/electronics/tutorial/markdown/images/1-1.jpeg differ diff --git a/docs/electronics/tutorial/markdown/intro.md b/docs/electronics/tutorial/markdown/intro.md new file mode 100644 index 0000000..8d237e4 --- /dev/null +++ b/docs/electronics/tutorial/markdown/intro.md @@ -0,0 +1,13 @@ +--- +title: Markdown简介 +--- + +# Markdown 简介 + +Markdown 是一种**轻量级标记语言**,它允许人们使用**易读易写**的纯文本格式编写文档。 + +当前许多网站都广泛使用 Markdown 来撰写帮助文档或是用于论坛上发表消息。Markdown 编写的文档可以导出 HTML、Word、图像、PDF、Epub 等多种格式的文档。Markdown 编写的文档后缀为 **.md** 或者 **.markdown**。 + +Markdown 语言在 2004 由约翰·格鲁伯(英语:John Gruber)创建。 + +Markdown 尤其是受 Github 青睐的一种标记语言,大部分的 Github 的`README`都是 Markdown,还有 GitHub 的 issue 及各种文档也是支持 Markdowm 的。 diff --git a/docs/electronics/tutorial/web/chapter1.md b/docs/electronics/tutorial/web/chapter1.md new file mode 100644 index 0000000..89a1500 --- /dev/null +++ b/docs/electronics/tutorial/web/chapter1.md @@ -0,0 +1,29 @@ +--- +title: 前端三语言 +--- + +# 前端三语言 + +## 一、什么是 HTML + +**超文本标记语言**(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的**标准标记语言**。HTML 是一种基础技术,常与 CSS、JavaScript 一起被众多网站用于设计网页、网页应用程序以及移动应用程序的用户界面。网页浏览器可以读取 HTML 文件,并将其渲染成可视化网页。HTML 描述了一个网站的结构语义随着线索的呈现,使之成为一种标记语言而非编程语言。 + +HTML 元素是构建网站的基石。HTML 允许嵌入图像与对象,并且可以用于创建交互式表单,它被用来结构化信息——例如标题、段落和列表等等,也可用来在一定程度上描述文档的外观和语义。HTML 的语言形式为尖括号包围的 HTML 元素(如),浏览器使用 HTML 标签和脚本来诠释网页内容,但不会将它们显示在页面上。 + +HTML 可以嵌入如**JavaScript**的脚本语言,它们会影响 HTML 网页的行为。网页浏览器也可以引用层叠样式表(CSS)来定义文本和其它元素的外观与布局。维护 HTML 和 CSS 标准的组织万维网联盟(W3C)鼓励人们使用 CSS 替代一些用于表现的 HTML 元素。 + +## 二、什么是 CSS + +**层叠样式表**(英语:Cascading Style Sheets,缩写:CSS;又称串样式列表、级联样式表、串接样式表、阶层式样式表)是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,由 W3C 定义和维护。CSS3 现在已被大部分现代浏览器支持,而下一版的 CSS4 仍在开发中。 + +CSS 不仅可以静态地修饰网页,还可以配合各种脚本语言动态地对网页各元素进行格式化。CSS 能够对网页中元素位置的排版进行像素级精确控制,支持几乎所有的字体字号样式,拥有对网页对象和模型样式编辑的能力。 + +CSS 不能单独使用,必须与**HTML**或**XML**一起协同工作,为 HTML 或 XML 起装饰作用。本文主要介绍用于装饰 HTML 网页的 CSS 技术。其中 HTML 负责确定网页中有哪些内容,CSS 确定以何种外观(大小、粗细、颜色、对齐和位置)展现这些元素。CSS 可以用于设定页面布局、设定页面元素样式、设定适用于所有网页的全局样式。CSS 可以零散地直接添加在要应用样式的网页元素上,也可以集中化内置于网页、链接式引入网页以及导入式引入网页。 + +## 三、什么是 Javascript + +**JavaScript**(通常缩写为 JS)是一门基于原型和头等函数的多范式高级解释型编程语言,它支持面向对象程序设计、指令式编程和函数式编程。它提供方法来操控文本、数组、日期以及正则表达式等。不支持 I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。它由 ECMA(欧洲电脑制造商协会)通过 ECMAScript 实现语言的标准化。目前,它被世界上的绝大多数网站所使用,也被世界主流浏览器(Chrome、IE、Firefox、Safari 和 Opera)所支持。 + +JavaScript 与 Java 在名字和语法上都很相似,但这两门编程语言从设计之初就有很大不同。JavaScript 在语言设计上主要受到了 Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响,在语法结构上它和 C 语言很相似(如 if 条件语句、switch 语句、while 循环和 do-while 循环等)。 + +对于客户端来说,JavaScript 通常被实现为一门解释语言,但如今它已经可以被即时编译(JIT)。随着 HTML5 和 CSS3 语言标准的推行,它还可以用于**游戏**、**桌面**和**移动应用程序**的开发,以及在服务器端网络环境运行(如 Node.js) diff --git a/docs/electronics/tutorial/web/chapter2.md b/docs/electronics/tutorial/web/chapter2.md new file mode 100644 index 0000000..8462edc --- /dev/null +++ b/docs/electronics/tutorial/web/chapter2.md @@ -0,0 +1,91 @@ +--- +title: HTML基础 +--- + +# HTML 基础 + +## 一、Hello world + +```html + + + + This is a title. + + +

Hello world!

+ + +``` + +简单说明: + +> - ``用于声明这是一个 HTML 文档 +> - ``表示头部元素,一般不可见 +> - ``标签页标题 +> - ``网页主体,显示可见内容 +> - `

`段落标签,用于显示文章段落文本 + +## 二、HTML 的标签元素 + +HTML 文档由嵌套的 HTML 元素构成。它们用 HTML 标签表示,包含于**尖括号**中,如`

`。 + +在一般情况下,一个元素由一对标签表示:**开始标签**`

`与**结束标签**`

`。元素如果含有文本内容,就被放置在这些标签之间。 + +在开始与结束标签之间也可以封装另外的标签,包括标签与文本的混合。这些**嵌套元素**是父元素的子元素。 + +开始标签也可包含**标签属性**。这些属性有诸如标识文档区块、将样式信息绑定到文档演示和为一些如``等的标签嵌入图像、引用图像来源等作用。 + +一些元素如换行符`
`,不允许嵌入任何内容,无论是文字或其他标签。这些元素只需一个**单一的空标签**(类似于一个开始标签),无需结束标签。 + +许多标签是可选的,尤其是那些很常用的段落元素`

`的闭合端标签。HTML 浏览器或其他介质可以从上下文识别出元素的闭合端以及由 HTML 标准所定义的结构规则。这些规则非常复杂,不是大多数 HTML 编码人员可以完全理解的。 + +因此,一个 HTML 元素的一般形式为:`content`。一些 HTML 元素被定义为空元素,其形式为``。空元素不能封装任何内容。例如`
`标签或内联标签``。一个 HTML 元素的名称即为标签使用的名称。 + +> 注意,结束标签的名称前面有一个斜杠“/”,空元素不需要也不允许结束标签。如果元素属性未标明,则使用其默认值。 + +## 三、标签示例 + +标题:HTML 标题由`

`到`

`六个标签构成,字体由大到小递减: + +```html +

Heading level 1

+

Heading level 2

+

Heading level 3

+

Heading level 4

+
Heading level 5
+
Heading level 6
+``` + +段落: + +```html +

第一段

+

第二段

+``` + +换行:`
`: + +```html +

這是
一個
使用換行
段落

+``` + +链接: + +```html +技术开发部的值班笔记! +``` + +注释: + +```html + +``` + +文档引用: + +```html + + + +``` diff --git a/docs/electronics/tutorial/web/chapter3.md b/docs/electronics/tutorial/web/chapter3.md new file mode 100644 index 0000000..61e8788 --- /dev/null +++ b/docs/electronics/tutorial/web/chapter3.md @@ -0,0 +1,72 @@ +--- +title: CSS基础 +--- + +# CSS 基础 + +## 一、Hello world + +```html + + + + This is a title. + + + +

这是一个标题

+

Hello world!

+ + +``` + +CSS 由多组规则组成。每个规则由**选择器**(selector)、**属性**(property)和**值**(value)组成: + +- 选择器(Selector):多个选择器可以半角逗号(,)隔开。 +- 属性(property):CSS1、CSS2、CSS3 规定了许多的属性,目的在控制选择器的样式。 +- 值(value):指属性接受的设置值,多个关键字时大都以空格隔开。 +- 属性和值之间用半角冒号(:)隔开,属性和值合称为特性。多个特性间用;隔开,最后用`{}`括起来。 + +## 二、选择器 + +选择器类型: + +- 标签选择器 +- 类选择器 +- ID 选择器 +- 属性选择器(如`input[type='submit']`) + +## 三、样式表的引用 + +```html + +``` + +例如: + +```html + + + + This is a title. + + + +

这是一个标题

+

Hello world!

+ + +``` diff --git a/docs/electronics/tutorial/web/chapter4.md b/docs/electronics/tutorial/web/chapter4.md new file mode 100644 index 0000000..ad75a3c --- /dev/null +++ b/docs/electronics/tutorial/web/chapter4.md @@ -0,0 +1,28 @@ +--- +title: 自我介绍 +--- + +# 自我介绍 + +## 项目要求 + +- 使用 HTML 写一个自我介绍 +- 要有图片和段落标签 +- 自我介绍不少于 100 字 +- 欢迎根据个人能力进行自我发挥 +- 上传到分享网址 + +## 一、VSCode 插件 + +安装 VSCode 插件`HTML CSS Support`,输入`!`后按下回车会自动生成基础模板。 + +![Extenstion](./images/4-1.png) + +## 二、相关链接 + +- 示例源码:[profile](profile.zip) +- 分享网址:[https://hello.mraddict.one](https://hello.mraddict.one/) + +## 三、示例图片 + +![Preview](./images/4-2.png) diff --git a/docs/electronics/tutorial/web/images/4-1.png b/docs/electronics/tutorial/web/images/4-1.png new file mode 100644 index 0000000..6985937 Binary files /dev/null and b/docs/electronics/tutorial/web/images/4-1.png differ diff --git a/docs/electronics/tutorial/web/images/4-2.png b/docs/electronics/tutorial/web/images/4-2.png new file mode 100644 index 0000000..ad4cf30 Binary files /dev/null and b/docs/electronics/tutorial/web/images/4-2.png differ diff --git a/docs/electronics/tutorial/web/intro.md b/docs/electronics/tutorial/web/intro.md new file mode 100644 index 0000000..dc3a63f --- /dev/null +++ b/docs/electronics/tutorial/web/intro.md @@ -0,0 +1,5 @@ +--- +title: 前端基础 +--- + +# 前端基础 diff --git a/docs/electronics/tutorial/web/profile.zip b/docs/electronics/tutorial/web/profile.zip new file mode 100644 index 0000000..1728ee7 Binary files /dev/null and b/docs/electronics/tutorial/web/profile.zip differ diff --git a/docusaurus.config.js b/docusaurus.config.js index a3d112e..a0ad5bf 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,44 +1,44 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/dracula'); +const lightCodeTheme = require("prism-react-renderer/themes/github"); +const darkCodeTheme = require("prism-react-renderer/themes/dracula"); /** @type {import('@docusaurus/types').Config} */ const config = { - title: '技术部文档', - tagline: '热情,投入,技术,极致', - favicon: 'favicon.ico', + title: "技术部文档", + tagline: "热情,投入,技术,极致", + favicon: "favicon.ico", - url: 'https://njtustas.github.io', - baseUrl: '/', + url: "https://njtustas.github.io", + baseUrl: "/", - organizationName: 'NJTUSTAS', - projectName: 'njtustas.github.io', + organizationName: "NJTUSTAS", + projectName: "njtustas.github.io", - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", i18n: { - defaultLocale: 'zh-Hans', - locales: ['zh-Hans'], + defaultLocale: "zh-Hans", + locales: ["zh-Hans"], }, presets: [ [ - 'classic', + "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - sidebarPath: require.resolve('./sidebars.js'), + sidebarPath: require.resolve("./sidebars.js"), }, blog: { showReadingTime: true, - blogSidebarTitle: '所有文章', - blogSidebarCount: 'ALL', + blogSidebarTitle: "所有文章", + blogSidebarCount: "ALL", }, theme: { - customCss: require.resolve('./src/css/custom.css'), + customCss: require.resolve("./src/css/custom.css"), }, }), ], @@ -47,75 +47,75 @@ const config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ - image: 'img/docusaurus-social-card.jpg', + image: "img/docusaurus-social-card.jpg", navbar: { - title: '技术开发部', + title: "技术开发部", logo: { - alt: 'logo', - src: 'favicon.ico', + alt: "logo", + src: "favicon.ico", }, items: [ { - type: 'docSidebar', - sidebarId: 'docs', - position: 'left', - label: '部门文档', + type: "docSidebar", + sidebarId: "docs", + position: "left", + label: "部门文档", }, { - to: '/blog', - label: '博客文章', - position: 'left' + to: "/blog", + label: "博客文章", + position: "left", }, { - href: 'https://github.com/NJTUSTAS/njtustas.github.io', - label: 'GitHub', - position: 'right', + href: "https://github.com/NJTUSTAS/njtustas.github.io", + label: "GitHub", + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: '文档', + title: "文档", items: [ { - label: '电子组文档', - to: '/docs/electronics/intro', + label: "电子组文档", + to: "/docs/electronics/intro", }, { - label: '编程组文档', - to: '/docs/electronics/intro', + label: "编程组文档", + to: "/docs/electronics/intro", }, ], }, { - title: '资源', + title: "资源", items: [ { - label: '本站仓库', - to: 'https://github.com/NJTUSTAS/njtustas.github.io', + label: "本站仓库", + to: "https://github.com/NJTUSTAS/njtustas.github.io", }, { - label: '值班笔记', - href: 'https://punch.mraddict.top', + label: "值班笔记", + href: "https://punch.mraddict.top", }, ], }, { - title: '友链', + title: "友链", items: [ { - label: '南工OJ', - href: 'https://acm.online.njtech.edu.cn', + label: "南工OJ", + href: "https://acm.online.njtech.edu.cn", }, { - label: '远景实验室', - href: 'https://www.vistalab.top', + label: "远景实验室", + href: "https://www.vistalab.top", }, { - label: 'Mars工作室', - href: 'https://marsstudio.vercel.app', + label: "Mars工作室", + href: "https://marsstudio.vercel.app", }, ], }, diff --git a/sidebars.js b/sidebars.js index f9a7753..51efcf3 100644 --- a/sidebars.js +++ b/sidebars.js @@ -9,7 +9,7 @@ const sidebars = { { type: "doc", id: "electronics/intro", - label: "说明" + label: "说明", }, { type: "category", @@ -18,59 +18,561 @@ const sidebars = { { type: "doc", id: "electronics/arduino/intro", - label: "Arduino简介" + label: "Arduino简介", }, { type: "doc", id: "electronics/arduino/chapter1", - label: "Arduino和Arduino IDE" + label: "Arduino和Arduino IDE", }, { type: "doc", id: "electronics/arduino/chapter2", - label: "第一个程序Blink" + label: "第一个程序Blink", }, { type: "doc", id: "electronics/arduino/chapter3", - label: "使用按钮" + label: "使用按钮", }, { type: "doc", id: "electronics/arduino/chapter4", - label: "中断Interrupt" + label: "中断Interrupt", }, { type: "doc", id: "electronics/arduino/chapter5", - label: "定时器Timer" + label: "定时器Timer", }, { type: "doc", id: "electronics/arduino/chapter6", - label: "脉冲宽度调制PWM" + label: "脉冲宽度调制PWM", }, { type: "doc", id: "electronics/arduino/chapter7", - label: "模数转换ADC" + label: "模数转换ADC", }, { type: "doc", id: "electronics/arduino/chapter8", - label: "串口通信UART" + label: "串口通信UART", }, { type: "doc", id: "electronics/arduino/chapter9", - label: "单片机的存储器和EEPROM" - } - ] - } - ] - } - ], + label: "单片机的存储器和EEPROM", + }, + ], + }, + { + type: "category", + label: "C语言", + items: [ + { + type: "doc", + id: "electronics/c-lang/intro", + label: "C语言简介", + }, + { + type: "doc", + id: "electronics/c-lang/chapter1", + label: "C语言基本语法", + }, + { + type: "doc", + id: "electronics/c-lang/chapter2", + label: "数据类型", + }, + { + type: "doc", + id: "electronics/c-lang/chapter3", + label: "变量和常量", + }, + { + type: "doc", + id: "electronics/c-lang/chapter4", + label: "运算符", + }, + { + type: "doc", + id: "electronics/c-lang/chapter5", + label: "条件判断if和switch", + }, + { + type: "doc", + id: "electronics/c-lang/chapter6", + label: "循环while和do/while", + }, + { + type: "doc", + id: "electronics/c-lang/chapter7", + label: "循环for", + }, + { + type: "doc", + id: "electronics/c-lang/chapter8", + label: "函数", + }, + { + type: "doc", + id: "electronics/c-lang/chapter9", + label: "函数的作用域规则", + }, + { + type: "doc", + id: "electronics/c-lang/chapter10", + label: "数组", + }, + { + type: "doc", + id: "electronics/c-lang/chapter11", + label: "多维数组", + }, + { + type: "doc", + id: "electronics/c-lang/chapter12", + label: "字符串", + }, + { + type: "doc", + id: "electronics/c-lang/chapter13", + label: "指针", + }, + { + type: "doc", + id: "electronics/c-lang/chapter14", + label: "指针的算数运算", + }, + { + type: "doc", + id: "electronics/c-lang/chapter15", + label: "指针和数组、函数", + }, + { + type: "doc", + id: "electronics/c-lang/chapter16", + label: "枚举(enum)", + }, + { + type: "doc", + id: "electronics/c-lang/chapter17", + label: "结构体(struct)", + }, + { + type: "doc", + id: "electronics/c-lang/chapter18", + label: "共用体(union)", + }, + ], + }, + { + type: "category", + label: "通信专题", + items: [ + { + type: "doc", + id: "electronics/communication/intro", + label: "通信专题", + }, + { + type: "category", + label: "One-Wire", + items: [ + { + type: "doc", + id: "electronics/communication/serial/one-wire/intro", + label: "One-Wire简介", + }, + { + type: "doc", + id: "electronics/communication/serial/one-wire/chapter1", + label: "One-Wire通信原理", + }, + { + type: "doc", + id: "electronics/communication/serial/one-wire/chapter2", + label: "DHT11数据手册速览", + }, + { + type: "doc", + id: "electronics/communication/serial/one-wire/chapter3", + label: "代码逻辑分析", + }, + { + type: "doc", + id: "electronics/communication/serial/one-wire/chapter4", + label: "整合打包代码", + }, + { + type: "doc", + id: "electronics/communication/serial/one-wire/chapter5", + label: "总结和拓展", + }, + ], + }, + { + type: "category", + label: "UART", + items: [ + { + type: "doc", + id: "electronics/communication/serial/uart/intro", + label: "UART简介", + }, + { + type: "doc", + id: "electronics/communication/serial/uart/chapter1", + label: "UART通信原理", + }, + { + type: "doc", + id: "electronics/communication/serial/uart/chapter2", + label: "HC-05数据手册速览", + }, + { + type: "doc", + id: "electronics/communication/serial/uart/chapter3", + label: "HC-05蓝牙的使用", + }, + { + type: "doc", + id: "electronics/communication/serial/uart/chapter4", + label: "总结和拓展", + }, + ], + }, + { + type: "category", + label: "I2C", + items: [ + { + type: "doc", + id: "electronics/communication/serial/i2c/intro", + label: "I2C简介", + }, + { + type: "doc", + id: "electronics/communication/serial/i2c/chapter1", + label: "I2C通信原理", + }, + { + type: "doc", + id: "electronics/communication/serial/i2c/chapter2", + label: "DS3231数据手册速览", + }, + { + type: "doc", + id: "electronics/communication/serial/i2c/chapter3", + label: "代码逻辑分析", + }, + { + type: "doc", + id: "electronics/communication/serial/i2c/chapter4", + label: "整合打包代码", + }, + { + type: "doc", + id: "electronics/communication/serial/i2c/chapter5", + label: "总结和拓展", + }, + ], + }, + { + type: "category", + label: "SPI", + items: [ + { + type: "doc", + id: "electronics/communication/serial/spi/intro", + label: "SPI简介", + }, + { + type: "doc", + id: "electronics/communication/serial/spi/chapter1", + label: "SPI通信原理", + }, + { + type: "doc", + id: "electronics/communication/serial/spi/chapter2", + label: "MPU9250数据手册速览", + }, + { + type: "doc", + id: "electronics/communication/serial/spi/chapter3", + label: "代码逻辑分析", + }, + { + type: "doc", + id: "electronics/communication/serial/spi/chapter4", + label: "整合打包代码", + }, + { + type: "doc", + id: "electronics/communication/serial/spi/chapter5", + label: "总结和拓展", + }, + ], + }, + { + type: "category", + label: "并行通信", + items: [ + { + type: "doc", + id: "electronics/communication/parallel/intro", + label: "并行通信简介", + }, + { + type: "doc", + id: "electronics/communication/parallel/chapter1", + label: "并行通信原理", + }, + { + type: "doc", + id: "electronics/communication/parallel/chapter2", + label: "LCD1602数据手册速览", + }, + { + type: "doc", + id: "electronics/communication/parallel/chapter3", + label: "代码逻辑分析", + }, + { + type: "doc", + id: "electronics/communication/parallel/chapter4", + label: "整合打包代码", + }, + { + type: "doc", + id: "electronics/communication/parallel/chapter5", + label: "总结和拓展", + }, + ], + }, + ], + }, + { + type: "category", + label: "项目制作", + items: [ + { + type: "doc", + id: "electronics/projects/intro", + label: "项目制作", + }, + { + type: "category", + label: "NFC", + items: [ + { + type: "doc", + id: "electronics/projects/nfc/intro", + label: "办公室门禁历史", + }, + { + type: "doc", + id: "electronics/projects/nfc/chapter1", + label: "初代门禁版本", + }, + { + type: "doc", + id: "electronics/projects/nfc/chapter2", + label: "目前门禁版本", + }, + { + type: "doc", + id: "electronics/projects/nfc/chapter3", + label: "未来的改进", + }, + ], + }, + { + type: "category", + label: "LED-Board", + items: [ + { + type: "doc", + id: "electronics/projects/led-board/intro", + label: "LED Board简介", + }, + { + type: "doc", + id: "electronics/projects/led-board/chapter1", + label: "制作步骤", + }, + { + type: "doc", + id: "electronics/projects/led-board/chapter2", + label: "代码及使用", + }, + { + type: "doc", + id: "electronics/projects/led-board/chapter3", + label: "总结和拓展", + }, + ], + }, + { + type: "category", + label: "激光雕刻机", + items: [ + { + type: "doc", + id: "electronics/projects/cnc-engraver/intro", + label: "激光雕刻机简介", + }, + { + type: "doc", + id: "electronics/projects/cnc-engraver/chapter1", + label: "什么是CNC", + }, + { + type: "doc", + id: "electronics/projects/cnc-engraver/chapter2", + label: "雕刻机硬件搭建", + }, + { + type: "doc", + id: "electronics/projects/cnc-engraver/chapter3", + label: "雕刻机控制软件", + }, + { + type: "doc", + id: "electronics/projects/cnc-engraver/chapter4", + label: "总结和拓展", + }, + ], + }, + ], + }, + + { + type: "category", + label: "半小时入门", + items: [ + { + type: "doc", + id: "electronics/tutorial/intro", + label: "半小时入门", + }, + { + type: "category", + label: "Git", + items: [ + { + type: "doc", + id: "electronics/tutorial/git/intro", + label: "Git简介", + }, + { + type: "doc", + id: "electronics/tutorial/git/chapter1", + label: "集中式VS分布式", + }, + { + type: "doc", + id: "electronics/tutorial/git/chapter2", + label: "Git的安装与配置", + }, + { + type: "doc", + id: "electronics/tutorial/git/chapter3", + label: "Git的常用命令", + }, + ], + }, + { + type: "category", + label: "EDA", + items: [ + { + type: "doc", + id: "electronics/tutorial/eda/intro", + label: "EDA入门简介", + }, + { + type: "doc", + id: "electronics/tutorial/eda/chapter1", + label: "555计时器", + }, + { + type: "doc", + id: "electronics/tutorial/eda/chapter2", + label: "绘制符号和封装", + }, + { + type: "doc", + id: "electronics/tutorial/eda/chapter3", + label: "设计原理图", + }, + { + type: "doc", + id: "electronics/tutorial/eda/chapter4", + label: "设计PCB", + }, + ], + }, + { + type: "category", + label: "Markdown", + items: [ + { + type: "doc", + id: "electronics/tutorial/markdown/intro", + label: "Markdown简介", + }, + { + type: "doc", + id: "electronics/tutorial/markdown/chapter1", + label: "Markdown语法(1)", + }, + { + type: "doc", + id: "electronics/tutorial/markdown/chapter2", + label: "Markdown语法(2)", + }, + ], + }, + { + type: "category", + label: "前端基础", + items: [ + { + type: "doc", + id: "electronics/tutorial/web/intro", + label: "前端基础", + }, + { + type: "doc", + id: "electronics/tutorial/web/chapter1", + label: "前端三语言", + }, + { + type: "doc", + id: "electronics/tutorial/web/chapter2", + label: "HTML基础", + }, + { + type: "doc", + id: "electronics/tutorial/web/chapter3", + label: "CSS基础", + }, + { + type: "doc", + id: "electronics/tutorial/web/chapter4", + label: "自我介绍", + }, + ], + }, + ], + }, + ], + }, + ], }; module.exports = sidebars; diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx index 91ef460..ee86e91 100644 --- a/src/components/HomepageFeatures/index.tsx +++ b/src/components/HomepageFeatures/index.tsx @@ -1,17 +1,17 @@ -import React from 'react'; -import clsx from 'clsx'; -import styles from './styles.module.css'; +import React from "react"; +import clsx from "clsx"; +import styles from "./styles.module.css"; type FeatureItem = { title: string; - Svg: React.ComponentType>; + Svg: React.ComponentType>; description: JSX.Element; }; const FeatureList: FeatureItem[] = [ { - title: 'Easy to Use', - Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, + title: "Easy to Use", + Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default, description: ( <> Docusaurus was designed from the ground up to be easily installed and @@ -20,8 +20,8 @@ const FeatureList: FeatureItem[] = [ ), }, { - title: 'Focus on What Matters', - Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, + title: "Focus on What Matters", + Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go @@ -30,8 +30,8 @@ const FeatureList: FeatureItem[] = [ ), }, { - title: 'Powered by React', - Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, + title: "Powered by React", + Svg: require("@site/static/img/undraw_docusaurus_react.svg").default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can @@ -41,9 +41,9 @@ const FeatureList: FeatureItem[] = [ }, ]; -function Feature({title, Svg, description}: FeatureItem) { +function Feature({ title, Svg, description }: FeatureItem) { return ( -
+
diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3652d8f..7b03089 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,7 +15,10 @@ function HomepageHeader() {

{siteConfig.title}

{siteConfig.tagline}

- + Docusaurus Tutorial - 5min ⏱️
diff --git a/static/assets/datasheet/DHT11.pdf b/static/assets/datasheet/DHT11.pdf new file mode 100644 index 0000000..1db6f2d Binary files /dev/null and b/static/assets/datasheet/DHT11.pdf differ diff --git a/static/assets/datasheet/DS3231.pdf b/static/assets/datasheet/DS3231.pdf new file mode 100644 index 0000000..4b51d64 Binary files /dev/null and b/static/assets/datasheet/DS3231.pdf differ diff --git a/static/assets/datasheet/HC-05.pdf b/static/assets/datasheet/HC-05.pdf new file mode 100644 index 0000000..5416c9e Binary files /dev/null and b/static/assets/datasheet/HC-05.pdf differ diff --git a/static/assets/datasheet/LCD1602.pdf b/static/assets/datasheet/LCD1602.pdf new file mode 100644 index 0000000..410f365 Binary files /dev/null and b/static/assets/datasheet/LCD1602.pdf differ diff --git a/static/assets/datasheet/MPU9250-Register-Map.pdf b/static/assets/datasheet/MPU9250-Register-Map.pdf new file mode 100644 index 0000000..7dff038 Binary files /dev/null and b/static/assets/datasheet/MPU9250-Register-Map.pdf differ diff --git a/static/assets/datasheet/MPU9250.pdf b/static/assets/datasheet/MPU9250.pdf new file mode 100644 index 0000000..ea405c7 Binary files /dev/null and b/static/assets/datasheet/MPU9250.pdf differ diff --git a/static/assets/software/Bluetooth-Android.apk b/static/assets/software/Bluetooth-Android.apk new file mode 100644 index 0000000..ed59550 Binary files /dev/null and b/static/assets/software/Bluetooth-Android.apk differ diff --git a/static/assets/software/LED-Board.apk b/static/assets/software/LED-Board.apk new file mode 100644 index 0000000..09b7bf7 Binary files /dev/null and b/static/assets/software/LED-Board.apk differ