分类目录归档:兴趣小组

我认为中学语文教学应该有的样子《青春读书课系列》推荐

第一篇

添加图片注释,不超过 140 字(可选)
我理解的散文,就是和诗文或韵文相对应,也就是在文体上是“松散”的结构。也就是说,扣除其它文学形式重叠部分,运用普通语法结构,不讲究音韵,不讲究排比,没有任何束缚及限制的文字梳理方式,都可称为散文。唐代时期,韩愈柳宗元提倡的古文运动,被认为是一次散文的文体文风的改革。其所反对的骈文兴盛于南北朝,因当时儒学衰微,盛行追求艺术之风气,文学作品倾向较重文辞修饰。骈文中虽然也有《滕王阁序》这样的传世名篇,但更多情况是大大提高通过文章表达作者思想的门槛,导致更多的是为凑音律平庸空洞的文章。 注重形式的讲求辞藻的韵文,在印刷术尚未普及和廉价化的时代既有其时代背景,有其固有价值和积极的一面。在清末小说红楼梦要靠传抄流传,民国孔乙己替人抄书都可以糊口的大背景下,越早的时期,载体工具和技术的落后,越是能流传开来和流程后世的文字就需要先哲们不断的含英咀华、反复揣摩言辞用字,以达到利于传诵和抄写和过目不忘的程度。文字流传的带宽不足或者成本高昂就迫使先哲们留下的书面文字务必要微言大义,文章早如《老子》仅五千言,字字珠玑,犹如在网络带宽和通信流量昂贵的时代发电报,压缩网站和邮件中的图片是非常类似的道理。 然而,随着文化教育的普及,即便是从小熟读《声律启蒙》和为必须熟读训练制艺功课的参加科举的古代先哲,亦开始提倡”文以载道“,到了近代随着大众识字率的普及和教育的平民化,这样的趋势更是不可阻挡。 因此,容我简单的把中国文化简单的进行划分,一个是古典的中国,一个是白话的中国。传统古典的中国所对应的文言文体的五千年与上世纪那场以平民语言白话文发声,唤醒和启蒙中国的一百年,都是我们精神家园。 中国何以成为中国?其实是在问有没有一种专属于中国人的心灵、情感思维和生活方式的问题。 古典的中国讲求耕读文化,忠厚传家久,诗书济世长。“一等人忠臣孝子,两件事读书耕田”。简洁明了、质朴无华的语言,蕴涵着浓厚的儒家传统思想,是做官还是生活,始终践行着忠、孝这两个传统文化中至为重要的德行,也寄托着中国传统士大夫阶层他对子女、族人的殷切期望。 20 世纪,中国人换了一种自然的腔调作文-史称“白话文” 。白话文的功绩不只是替代了文言这种纯书面文字,它也改变了传统的思维方式,相伴而来的是心态的开放、个性的解放、与世界交流的强烈欲望以及文化教育的普及。一个民族开始用正常的声音说话了,中国文字由贵族的语言变为平民的语言。 2003 年,百年老店商务印书馆出版了这套教材。《青春读书课》缘起于作者在深圳市育才中学开设的一门选修课。时值1999 年,当时可能是中国内地中学开设的笫一个成系列的语文选修课,原本定位于人文精英课程。在阅读好书中构建自己的精神家园,这是北京大学教授钱理群给这套书写的序言标题,我觉得没有比这句话更适合阐述这套书的选编宗旨了。 白话文学,是中国现代知识分子奠定尊严的文学,是白话文作家确立个性的文学,是中华民族重新审视自己的灵魂、探索自身命运的文学。一边是大刀阔斧的拓荒开路,一边是沉默坚忍的耕耘播种,白话文学逐渐堆积起文言时代所没有的厚度与广度。 世纪之初的启蒙者,大多是平民身份的知识分子,他们以一本杂志(《新青年》)、一所大学(北大)为堡垒,以科学与民主为弹药,向不合时宜的旧文化发动攻势。结果一呼百应,遍地开花。 陈独秀、胡适、鲁迅、陶行知、闻 一多……一代启蒙精英,个个都像霹雳火,直面现实,满纸生烟。写大文章,谈大话题,以文字干政,虽不脱传统士大夫策论习气,但文心已是现代化、个性化、自由化了。 众多的声音沉淀下来,有鲁迅、李敖、王小波一路的思想呐喊,也有周作人、林语堂、张晓凤、董桥一路的日常话语;有闻一多、徐志摩、艾青、舒婷的浪漫激情,也有穆旦、冯至、余光中、北岛、海子、于坚、欧阳江河的冷峻隽思;有茅盾、老舍、张爱玲、白先勇的都市斑娟,也有沈从文、贾平凹、莫言、张烤、余华、阿城、张承志、刘亮程的乡土中国。中国的白话文作家们,实际上一直在为建设现代中国人的自由心灵、开辟多彩多姿的民间生活而坚韧努力着。 在这本书中,你可以读到 鲁迅决定弃医从文 胡适策动文学革命 陈独秀呼唤“ 新青年” 徐志摩在剑桥 郁达夫在日本 孙中山勾画未来中国蓝图 陶行知推行四通八达的教育 叛逆之子巴金走出封建之家 乡下人沈从文走出边城 林语堂在美国神侃“吾土吾民” 朱自清凝望父亲的“背影” 瞿秋白说出“多余的话” 鲁迅笑看青年 林徽因的“你是人间四月天” 余光中满腹乡愁 李敖笑傲江湖 真水无香汪曾祺 北岛用朦胧诗清晰地回答今天 早逝的天才诗人海子

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)
你把文字读活。回顾刚刚过去的20世纪中国历史,不要让现在求学的青年误以为我们只有一 一个精神荒芜的中国,一个风花雪月的中国,一个歌舞升平的中国。所以,就有了这本书一请你仔细看一眼,我们一百年的精神遗产,我们的白话的中国。

添加图片注释,不超过 140 字(可选)
假如三十五年前我能读到这样一套书,我不会是现在这个样子。现在我读了这套书,依然感到内心深处发生了一些微妙的变化。—作家莫言
纵观全部选文,主编者很好地把握了中学生生理、心理成长过程中对文化、思想的特殊需求,这种素质教育方式的成果,在未来的几年里就能显现出来。—北京大学教授曹文轩
学生感悟汉语之美,感受正确而自如地用汉语表达自己的快乐,建立与母语的血缘联系,将母语所蕴含的民族文化、民族精神的根扎在心灵的深处,并在此基础上构造起自己的精神家园,这是中学语文教育的根本,也是严凌君这套读本的归结点:这里充溢着思想之美、文学之美与语言之美,相信孩子们会喜欢它,成年人,我们这些教育工作者,也能从中受到许多启示。—北京大学教授钱理群

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

ChatGPT上手教程

前言

最近两个月,我都在玩ChatGPT,写这篇文章的目的非常单纯,就是希望能帮到大家快速上手这个工具,作为有效的生产力工具融入到自己的工作和生活当中。这是源于我的观察,网上有很多可能会吸引流量很大的视频,各种调弄嬉戏这个AI机器人,或者尝试很多的擦边球问题,这些东西我觉得短时期内可以引发人们的好奇心和底层欲望,只是如果每个月要付费几十美元仅仅只是为了逗乐,怕是坚持不了多久就会放弃,所以希望能把这个工具好好用起来的朋友们能产生和支持这样的实用文章。

GPT-3.5模型的参数量为2000亿,GPT-3的参数量为1750亿,但这一情况在GPT-4被改变了。OpenAI在报告中表示,考虑到竞争格局和大型模型(如GPT-4)的安全影响,本报告没有包含有关架构(包括模型大小)、硬件、训练计算、数据集构造、训练方法或类似内容的进一步细节。

我虽然不是专业的AI开发人员,曾经自学和搭建过一些神经网络模型,早尝试带过学生去做一些基于Yolo视觉的软结合项目,基本了解程序设计和神经网络等计算机科学类的基础知识。即便如此,但在刚接触ChatGPT的时候,我还是走了很多弯路。直到最近真的带着朋友、同学们用起来,在实际工作中,才发现这是一个能引发生产力大爆发的技术,人工智能AI各位前沿探索者们呼吁了多年的“奇点来临”仿佛真的来了的感觉,这种进步比起2000左右刚接触Google搜索引擎的我,更加印象深刻


以下几点是我想强调的:

国内普通人使用ChatGPT有很多困难,很多麻烦,也有很多隐私泄露的风险,但本文不讨论这个问题。其实有很多文章介绍如何通过一系列操作直接使用官方提供的接口,如果您有困难和问题可以私信或者咨询我们,微信ID:zhangchengwust,也可以找熟悉掌握相关技术的朋友帮忙,通常一个普通程序员就够了。我假定接下来的讲述都是基于您能正常访问和使用官方接口下展开的。

本文探讨的是关于ChatGPT作为生产力工具使用具体技巧、方法和要义,对于ChatGPT涉及伦理道德哲学思辨等问题虽然很重要,但不在本文讨论范围内。

根据网上一些冠名“调教”的ChatGPT教程,多数设计让ChatGPT做角色扮演,这个已经很多文字和内容了,所以也不是本文讨论的范围,请各位自行参考相关教程。


使用前重点须知

在这之前,我们先简单了解几个基本要义,包括资料来源,时效性,资料来源的引用,生成内容的随机性,生成文本的长度限制,对话内容的上下文记忆,模糊问题的伦理边界限制。

资料来源

ChatGPT 是基于大量文本数据进行训练的,来源广泛。以下是一些主要的数据来源:

网页文本:模型训练数据中包含了大量网页文本,这些文本来自于各种类型的网站,如新闻网站、博客、论坛、教育网站等。

书籍:训练数据中包括了各种主题的书籍内容,涵盖了文学、历史、科学、艺术、哲学等领域。

论文:学术论文也是 ChatGPT 训练数据的一部分,主要来源于各大学和研究机构。这些论文涵盖了多个学科和研究领域。

百科全书:诸如维基百科等百科全书也是模型训练数据的重要组成部分,这些数据来源为模型提供了大量关于各种主题的知识。

新闻文章:来源于各种新闻媒体的文章也包含在训练数据中,这有助于模型了解时事和事件。

社交媒体:部分训练数据来自社交媒体平台,如推特、Reddit 等,这些数据帮助模型学习网络用语和不同类型的交流方式。

软件代码:训练数据中还包括了一些软件代码,这有助于模型学习编程语言和技术概念。

请注意,由于隐私和版权问题,训练数据中不包含任何个人信息、私人通信、受版权保护的作品等。此外,模型训练数据经过预处理,以去除低质量、重复或不相关的内容。然而,训练数据的质量和准确性仍然受限于原始来源,因此在使用 ChatGPT 时,请注意可能存在的偏差和不准确之处。

下面是一个例子:

时效性有限

Our default models’ training data cuts off in 2021, so they may not have knowledge of current events. 我们默认模型的训练数据在 2021 年中断,因此它们可能不了解时事。

请注意,ChatGPT 的训练数据截止于 2021 年 9 月,所以在那之后的信息可能无法被模型获取

ChatGPT 是基于GPT的神经网络,它把大致是截至到某个时间,网络上的文字信息爬取并输入给这个拥有上亿参数的神经网络,这个网络并不是实时更新的,也没有访问获取互联网信息的能力(可能只是暂时对普通用户的限制),所以你不应该问它一些时效性很强的问题。在训练过程中,它从各种在线资源(包括书籍、文章、网站和论坛等)中学习了大量的知识。然而,由于模型的知识截止日期为2021年9月,有些信息可能已经过时或不准确。下面是一个例子:

看来它只知道2014年发生的克里米亚被占领那次的冲突,关于最近一年的俄乌战争几乎不知情。同样,你也不应该问他明天的天气、昨天的新闻和最近娱乐圈的瓜。当然,如果你可以摘录最新新闻稿的内容,提供给他让它分析总结或者提炼内容。

生成内容随机性

简单理解就是它像是一个被训练过的概率模型,对于现实生活中各种不唯一解答的问题(如想一个标语或者描述介绍),生成的结果往往也呈现出多样性。

所以当你是在使用ChatGPT生成一些文案创作、脚色扮演、头脑风暴等场景时,遇到心仪的生成结果一定要注意及时保存。同时,你也可以让它一次生成多个结果供您参考,如:

聊天对话框内容记忆性

当你使用在同一个对话框或者API中与其对话时,你不需要重复之前的内容,它是可以理解你语句中包含的指代词的。当然,你的指代需要明确且无歧义比较好。

生成文本长度限制

刚开始使用ChatGPT生成一些文字内容较长的文本或者代码时,经常遇到截断,刚开始我还真以为是网络或者服务器负荷的问题,直到我认真仔细看了官方的手册,就明白怎么做了。

在GPT-4上,文本长度被显著提高。

在此之前,调用GPT的API收费方式是按照“token”计费,一个token通常对应大约 4 个字符,而1个汉字大致是2~2.5个token

在GPT-4之前,token的限制大约在4096左右,大约相当于3072个英文单词,一旦对话的长度超过这个限制,就会被截断。

OpenAI在文档中表示,现在GPT-4限制的上下文长度限制为8192个token,允许32768个token的版本名为GPT-4-32K,目前暂时限制了访问权限。在不久的未来,这一功能可能会被开放。

根据上面我们提到的记忆性,直接使用“继续”关键词即可继续生成:

模糊问题和伦理边界

如果你问一些譬如很模糊,不可能靠几百字就能解决的大问题,那么也许ChatGPT可以生成相应的回来,但是对你的帮助几乎没有,当然有时候可以当成一种乐趣。譬如

如果你问一些需要问题专业人士提供法律风险担保,或者有悖伦理的事情,譬如让他充当医生给你诊断病情并开药,或者请求犯罪行为的帮助与指导,那么它大概率会拒绝你。当然,也有时候有些人会办法换种方式提问去绕开这些规则和审查,并以此作乐趣,这不在本文讨论的范围内。

多种语言资料来源的对比参考

ChatGPT主要是基于英语文本进行训练的。尽管它的训练数据中包含了多种语言,但以英语为主。其他语言在训练数据中所占的比例相对较小,具体的比例难以准确估计。根据文献情报的数据,网络上75%以上的内容为英语,而较为可靠和学术性的主题,英文的比例就更高了,据统计高达95%以上。因此,ChatGPT 在处理英语问题时表现得更为流畅和准确,而在处理其他语言时可能会出现一定程度的困难。

为了克服这些问题,ChatGPT才有了一种特有的方法,就是往往会把提问的内容翻译成英文,并将英文生成的结果再返回给我们。下面关于物理主题的回答就非常明显,其所生成的结果基本上都是英文的课本、网站和搜索渠道。

这提醒我们,如果在英语能力允许的情况下,应尽可能使用英语和ChatGPT交互,这在某些词语与英语之间存在多重不同的语义时尤为重要,而这样的词汇并不少见。你可以简单附加一句话,请ChatGPT用英文中文或者某种语言回答,这在一个对话中是可以来回切换的。

资料来源不可呈现

ChatGPT回答内容的资料来源可以提供吗?之前大部分的情况下我发现时不可以的,尤其是设计到模糊的价值判断时,但新版本更新后部分明确的问题又可以提供来一些来源,但是这往往并不十分可靠,所以当网上或者训练资料大量存在冲突或者某些情况中英文直接的差异比较容易引起误会时ChatGPT往往会提供错漏百出的信息,就是我常说你,有些问题你会发现它很一本正经的胡说八道

首先,稍微对广州市中小学情况有些了解的人,都应该知道广州市第六中学和广州市培正中学是完全不同的两所学校,在办学历史上并无重合之处。而执信中学的校训和创办时间也不对。
请注意,ChatGPT不能提供特定的资料来源,因为它在生成回答时并不是从特定的资源中引用信息。它根据从训练数据中学到的知识,尝试生成相关且合理的回答。因此,在使用ChatGPT的回答时,请谨慎对待,并在需要确切信息用于有法律责任和专业职责时,必须进一步核实相关资料,尤其当你想使用ChatGPT生成一些基于本土的资讯,且这些资讯很可能在英文资料库中不完整不充分且中英互译过程中很容易出现错叠的情况

当继续要求其提供详细出处时,需要注意了,它不能给出信息来源以供核实。


行业案例

为了基于真实的应用场,我专门找了几个自己和朋友的真实需求,以下就是这些需求产生交互的结果,我们举例说明:

程序员或者普通人:

中学小学教师:

行政管理人员:

财务人员:

认真读书的医学生:

文学评论及文学爱好者:

想要教育孩子的父母:

超声波雷达-创客开源 Arduino项目

//淘宝『创元素店』https://shop423015102.taobao.com/ //更新日期 2021/03/06 //MiniRadar 超声波雷达 程序 //本程序对应商品 https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-23815833841.8.4f231fe7qvLFZi&id=649834806872

//Github版链接: https://github.com/johnsonwust/MiniRadar

include

include

include "Ucglib.h"

//显示屏的lib 如果没有该lib请按Ctrl+Shift+I 从 库管理器中搜索 ucglib,并安装

define trigPin 6 //超声波模块的Trig口 6

define echoPin 5 //超声波模块的echo口 5

define ServoPin 3 //底座舵机端口 3

int Ymax = 128; //屏幕的竖向像素数 int Xmax = 160; //屏幕的横向像素数 int Xcent = Xmax / 2; //x中位 int base = 118; //基线高度 int scanline = 105; //雷达扫描线长度

Servo baseServo; Ucglib_ST7735_18x128x160_HWSPI ucg(/cd=/ 9, /cs=/ 10, /reset=/ 8);

void setup(void) {

  ucg.begin(UCG_FONT_MODE_SOLID); //初始化屏幕
  ucg.setRotate90();              //设置成横屏  如果屏幕显示方向是反的,可以修改函数 setRotate90 或 setRotate270

  pinMode(trigPin, OUTPUT);       //设置trigPin端口模式
  pinMode(echoPin, INPUT);        //设置echoPin端口模式
  Serial.begin(115200);             //设置串口传输率
  baseServo.attach(ServoPin);     //初始化舵机

  //欢迎屏幕
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 0, 100, 0);
  ucg.setColor(1, 0, 100, 0);
  ucg.setColor(2, 20, 20,20);
  ucg.setColor(3, 20, 20, 20);
  ucg.drawGradientBox(0, 0, 160, 128);
  ucg.setPrintDir(0);
  ucg.setColor(0, 5, 0);
  ucg.setPrintPos(27,42);
  ucg.setFont(ucg_font_logisoso18_tf);  
  ucg.print("Mini Radar");
  ucg.setColor(0, 255, 0);
  ucg.setPrintPos(25,40);
  ucg.print("Mini Radar");
  ucg.setFont(ucg_font_helvB08_tf);
  ucg.setColor(20, 255, 20);
  ucg.setPrintPos(40,100);
  ucg.print("Testing...");
  baseServo.write(90);

  //测试底座的运行情况,注意检测底座位置和转动姿态,是否有卡住(或者导线缠绕)的情况。
  for(int x=0;x<180;x+=5)
      { baseServo.write(x);
        delay(50);
       }
  ucg.print("OK!");
  delay(500);

  //清屏
  //ucg.clearScreen();
  cls();
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setFont(ucg_font_orgv01_hr);

}

void cls() { //清屏 ucg.setColor(0, 0, 0, 0);

for(int s=0;s<128;s+=8) for(int t=0;t<160;t+=16) { ucg.drawBox(t,s,16,8); // delay(1); }

}

int calculateDistance() { long duration; //trigPin断电 并 等待2微妙 digitalWrite(trigPin, LOW); delayMicroseconds(2); //trigPin加电 延时 10微妙 再断电 digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); //读取echoPin返回声波的传播时间(微妙) duration = pulseIn(echoPin, HIGH); //将回声时间转换成距离数值 return duration*0.034/2; }

void fix_font() { ucg.setColor(0, 180, 0); ucg.setPrintPos(70,14); ucg.print("1.00"); ucg.setPrintPos(70,52); ucg.print("0.50"); ucg.setPrintPos(70,90); ucg.print("0.25"); }

void fix() {

  ucg.setColor(0, 40, 0);
  //画基线圆盘
  ucg.drawDisc(Xcent, base+1, 3, UCG_DRAW_ALL); 
  ucg.drawCircle(Xcent, base+1, 115, UCG_DRAW_UPPER_LEFT);
  ucg.drawCircle(Xcent, base+1, 115, UCG_DRAW_UPPER_RIGHT);
  ucg.drawCircle(Xcent, base+1, 78, UCG_DRAW_UPPER_LEFT);
  ucg.drawCircle(Xcent, base+1, 78, UCG_DRAW_UPPER_RIGHT);
  ucg.drawCircle(Xcent, base+1, 40, UCG_DRAW_UPPER_LEFT);
  ucg.drawCircle(Xcent, base+1, 40, UCG_DRAW_UPPER_RIGHT);
  ucg.drawLine(0, base+1, Xmax,base+1);

  ucg.setColor(0, 120, 0);
  //画刻度表
   for(int i= 40;i < 140; i+=2)
   {

    if (i % 10 == 0) 
      ucg.drawLine(105*cos(radians(i))+Xcent,base - 105*sin(radians(i)) , 113*cos(radians(i))+Xcent,base - 113*sin(radians(i)));
    else

     ucg.drawLine(110*cos(radians(i))+Xcent,base - 110*sin(radians(i)) , 113*cos(radians(i))+Xcent,base - 113*sin(radians(i)));
   }

   //画一些装饰性图案 
   ucg.setColor(0,200,0);
   ucg.drawLine(0,0,0,18);
   for(int i= 0;i < 5; i++)
   {
      ucg.setColor(0,random(200)+50,0);
      ucg.drawBox(2,i*4,random(14)+2,3);
   }

   ucg.setColor(0,180,0);
   ucg.drawFrame(146,0,14,14);
   ucg.setColor(0,60,0);
   ucg.drawHLine(148,0,10);
   ucg.drawVLine(146,2,10);
   ucg.drawHLine(148,13,10);
   ucg.drawVLine(159,2,10);

   ucg.setColor(0,220,0);
   ucg.drawBox(148,2,4,4);
   ucg.drawBox(148,8,4,4);
   ucg.drawBox(154,8,4,4);
   ucg.setColor(0,100,0);
   ucg.drawBox(154,2,4,4);

   ucg.setColor(0,90,0);
   ucg.drawTetragon(62,123,58,127,98,127,102,123);
   ucg.setColor(0,160,0);
   ucg.drawTetragon(67,123,63,127,93,127,97,123);
   ucg.setColor(0,210,0);
   ucg.drawTetragon(72,123,68,127,88,127,92,123);

}

void loop(void) {

int distance;

fix(); fix_font(); //重绘屏幕背景元素

for (int x=180; x > 4; x-=2){ //底座舵机从180~0度循环

  baseServo.write(x);             //调整舵机角度

  //绘制雷达扫描线
  int f = x - 4; 
  ucg.setColor(0, 255, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  f+=2;
  ucg.setColor(0, 128, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  f+=2;
  ucg.setColor(0, 0, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  ucg.setColor(0,200, 0);
  //测距
  distance = calculateDistance();

  //根据测得距离在对应位置画点
  if (distance < 100)
  {
    ucg.setColor(255,0,0);
    ucg.drawDisc(distance*cos(radians(x))+Xcent,-distance*sin(radians(x))+base, 1, UCG_DRAW_ALL);
  }
  else
  { //超过1米以上的,用黄色画在边缘区域示意
    ucg.setColor(255,255,0);
    ucg.drawDisc(116*cos(radians(x))+Xcent,-116*sin(radians(x))+base, 1, UCG_DRAW_ALL);
  }

  //调试代码,输出角度和测距值  
  Serial.print(x); 
  Serial.print("    ,   ");
  Serial.println(distance); 

  if (x > 70 and x < 110)  fix_font();  //扫描线和数字重合时,重绘数字

  ucg.setColor(0,155,  0);
  ucg.setPrintPos(0,126);
  ucg.print("DEG: "); 
  ucg.setPrintPos(24,126);
  ucg.print(x);
  ucg.print("  ");
  ucg.setPrintPos(125,126);
  ucg.print("  ");
  ucg.print(distance);
  ucg.print("cm  "); 

} //ucg.clearScreen(); //清屏 如果arduino供电不足,可能会引起白屏(显示信号中断)可以用 cls();函数代替 ucg.clearScreen(); delay(50); cls(); //如有频繁白屏情况,可以使用该函数 。或者增加外部供电

fix(); fix_font(); //重绘屏幕背景元素

for (int x=1; x < 176; x+=2){
baseServo.write(x); //调整舵机角度

  //绘制雷达扫描线
  int f = x + 4;
  ucg.setColor(0, 255, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  f-=2;
  ucg.setColor(0, 128, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  f-=2;
  ucg.setColor(0, 0, 0);
  ucg.drawLine(Xcent, base, scanline*cos(radians(f))+Xcent,base - scanline*sin(radians(f)));
  ucg.setColor(0, 200, 0);
  //测距
  distance = calculateDistance();

  //根据测得距离在对应位置画点
  if (distance < 100)
  {
    ucg.setColor(255,0,0);
    ucg.drawDisc(distance*cos(radians(x))+Xcent,-distance*sin(radians(x))+base, 1, UCG_DRAW_ALL);
  }
  else
  { //超过1米以上的,用黄色画在边缘区域示意
    ucg.setColor(255,255,0);
    ucg.drawDisc(116*cos(radians(x))+Xcent,-116*sin(radians(x))+base, 1, UCG_DRAW_ALL);
  }

  //调试代码,输出角度和测距值  
  Serial.print(x); 
  Serial.print("    ,   ");
  Serial.println(distance); 

  if (x > 70 and x < 110)  fix_font();  //扫描线和数字重合时,重绘数字

  ucg.setColor(0,155,  0);
  ucg.setPrintPos(0,126);
  ucg.print("DEG: "); 
  ucg.setPrintPos(24,126);
  ucg.print(x);
  ucg.print("   ");
  ucg.setPrintPos(125,126);
  ucg.print("   ");
  ucg.print(distance);
  ucg.print("cm   "); 

} //ucg.clearScreen(); // delay(50); cls();

}

树莓派Raspberry Pi Pico入门教程

Raspberry Pi Pico是一款具备灵活脚位,且低成本、高性能的开发板 是一款具备灵活脚位,且低成本、高性能的开发板 是一款具备灵活脚位,且低成本、高性能的开发板 是一款具备灵活脚位,且低成本、高性能的开发板 是一款具备灵活脚位,且低成本、高性能的开发板 ,价格仅 ,价格仅 4元美金, 元美金, 商品特色 商品特色 如下: 如下:

  1. 采用 Raspberry Pi英国设计的 RP2040微控制器 微控制器 ,双核 Arm Cortex M0 +处理器,运行频率 处理器,运行频率 处理器,运行频率 133 MHz
  2. 264KB的 SRAM和 2MB的片上 Flash
  3. 支持低功耗睡眠和休模式
  4. 能通过 USB使用大容量储存进行拖放式 下载程使用大容量储存进行拖放式
    下载程5. 多达 26个多功能 GPIO引脚
  5. 2个 SPI,2个 I2C,2个 UART,3个 12位元的 ADC,16个可 程式控制的 PWM
  6. 精准的时钟和计器 与内建 温度感测器
  7. 8个可透过程式撰写 I / O(PIO)状态机,支持自定义外设备 )状态机,支持自定义外设备
  8. 支援 C / C ++ 和 MicroPython 开发
  9. 可执行 TensorFlow Lite 框架

外观与 脚位 定义如下: (※若元件需使用 ※若元件需使用 5V电压,则使用 电压,则使用 Pin40的 VBUS)

VBUS – 这是来自 这是来自 microUSB 汇流排的电源, 5 V。如果 Pico不是由 microUSB联结器供电,那么这 联结器供电,那么这 里将没有输出。
⚫ VSYS – 这是输入电压,范围为 2 至 5 V。板载电压转换器将为 Pico 将其改为 3.3 V。
⚫ 3V3 – Pico 内部调节 器的 3.3 伏输出。只要将负载保持在 伏输出。只要将负载保持在 伏输出。只要将负载保持在 300mA 以下,它就可用于为其他元件供电。以下,它就可用于为其他元件供电。以下,它就可用于为其他元件供电。以下,它就可用于为其他元件供电。以下,它就可用于为其他元件供电。
⚫ 3V3_EN – 你可以使用 此输入禁你可以使用 此输入禁Pico 的内部电压调节器,从而关闭 的内部电压调节器,从而关闭 的内部电压调节器,从而关闭 的内部电压调节器,从而关闭 Pico 和由其供电的任何元件。和由其供电的任何元件。
⚫ RUN – 可以启用 或禁RP2040 微控制器,也可以将其复位。

Pico 的 BOOTSEL 模式位于 RP2040 晶片内部的唯读存储槽中,不会被意外覆盖。任何情况下按住BOOTSEL 按钮并插入 Pico 时,都会以驱动器的模式出现,可以在其中拖动新的 UF2 韧体文件,但无法藉由软体编写程式。不过在某些情况下可能需要确保净空闪存,您可以藉由大容量存储模式将特殊的UF2 二进制文件拖放到您的 Pico 上格式化闪存。

※硬体基本测试,不可以撰写程式控制,步骤如下:
1.下载 blink.uf2 韧体档案文件。
2.按住 BOOTSEL 按钮,将 Pico 插入电脑的 USB 埠,连接 Pico 后,松开 BOOTSEL 按钮。
3.连接后会出现名为 RPI-RP2 的大容量存储设备。
4.将 blink.uf2 档案文件拖曳进 RPI-RP2 内,Pico 会重新启动,内建 GPIO25 开始闪烁。现在, MicroPython 会开始运作。
※建立 MicroPython 程式控制环境,步骤如下:
1.下载 rp2-pico-20210324-unstable-v1.14-121-g4fc2866f4.uf2 韧体档案文件。
2.按住 BOOTSEL 按钮,将 Pico 插入电脑的 USB 埠,连接 Pico 后,松开 BOOTSEL 按钮。
3.连接后会出现名为 RPI-RP2 的大容量存储设备。
4.将 rp2-pico-20210324-unstable-v1.14-121-g4fc2866f4.uf2 档案文件拖曳进 RPI-RP2 内,Pico 会重新启动,MicroPython 才能开始运作。
5.本机右键→内容→装置管理员,查看 COM?位置,若还看到应是驱动程式有误,请下载Pico_devices_cdc.inf,更新驱动程式后即可看到。
6.到 https://thonny.org/ 下载编辑软体,至少要为 3.3.3 版本以上才有支援。

  1. 进入 Thonny 主程式,『执行→选择直译器』,选择”MicroPython(Raspberry Pi Pico)”直译器与 COM?,最后按确定。

數位輸出測試
A01_內建 LED 閃爍.py 程式碼如下:

註 1:import machine 是用來設定 Pi Pico 所有相關硬體參數,若板子沒有正確連線或者直譯器沒有選對,則執行程式後會出現 ”import machine module named not found” 的錯誤。
註 2:import utime 目的是導入時間相關類別,因為後面 utime.sleep(0.5) 才能正常使用。

A02_RGB_LED.py 程式碼如下:(註:數位輸出僅能隨機顯示 8-1 種顏色,因為黑色代表不亮)

from machine import Pin,PWM,ADC
from time import sleep
adc = ADC(0) #ADC input (knob potentiometer) connected to A0
pwm = PWM(Pin(27))#DAC output (buzzer) connected to A1
pwm.freq(10000)
while True:

    '''Analog port test'''
    val = adc.read_u16()#Read A2 port adc value (65535~0)
    #Drive the buzzer, turn off the buzzer when the adc value is less than 300
    if val > 300:
        pwm.freq(int(val/10))
        pwm.duty_u16(10000)
    else:
        pwm.duty_u16(0)
    print(val)
    sleep(0.05)
from ssd1306 import SSD1306_I2C
from dht11 import *
from machine import Pin, I2C
from time import sleep

i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=200000)#oled connect to I2C1
oled = SSD1306_I2C(128, 64, i2c)
dht2 = DHT(18) #temperature and humidity sensor connect to D18 port

while True:  

    temp,humid = dht2.readTempHumid()#temp:  humid:
    '''I2C port test'''    
    ''' oled display test'''
    oled.fill(0)#Clear screen
    oled.text("Temp:  " + str(temp),0,0)#display tempearture on line 1
    oled.text("Humid: " + str(humid),0,8)
    oled.show()
    sleep(0.5)

from machine import Pin

button = Pin(18, Pin.IN, Pin.PULL_UP)# button connect to D18
button.irq(lambda pin: InterruptsButton(),Pin.IRQ_FALLING)#Set key interrupt
led = Pin(16, Pin.OUT)#led connect to D16
relay = Pin(20, Pin.OUT)
tmp = 0
'''Key interrupt function, change the state of the light when the key is pressed'''
def InterruptsButton(): #button input
    global tmp
    tmp = ~tmp
    led.value(tmp)
    relay.value(tmp)
while True:  
    pass

#from lcd1602 import LCD1602_RGB  #LCD1602 RGB grove
from lcd1602 import LCD1602
from machine import I2C,Pin,ADC
from time import sleep
i2c = I2C(1,scl=Pin(7), sda=Pin(6), freq=400000)
d = LCD1602(i2c, 2, 16)
#d = LCD1602_RGB.display(i2c, 2, 16)
#d.set_rgb(255, 0, 0)
sleep(1)
light = ADC(0)
sound = ADC(1)

while True:

    lightVal = light.read_u16()
    soundVal = sound.read_u16()
    d.home()
    d.print('lightvalue=')
    d.print(str(lightVal))
    #d.set_rgb(0, 255, 0)
    sleep(1)
    d.setCursor(0, 1)
    d.print('soundvalue=')
    d.print(str(soundVal))
    #d.set_rgb(0, 0, 255)
    sleep(1)

from machine import Pin,ADC,PWM
from time import sleep
import utime

miniFun = Pin(16, Pin.OUT)  
miniPir = Pin(18, Pin.IN)  

pwm_Servo=PWM(Pin(27))
pwm_Servo.freq(500)
Servo_Val =0  

while True:

    if  miniPir.value() == 1 :
        miniFun.value(1)

        while  Servo_Val<65535:
            Servo_Val=Servo_Val+50
            utime.sleep_ms(1)
            pwm_Servo.duty_u16(Servo_Val)
        while Servo_Val>0: 
            Servo_Val=Servo_Val-50
            utime.sleep_ms(1)
            pwm_Servo.duty_u16(Servo_Val)

    else :
        miniFun.value(0)

        pwm_Servo.duty_u16(0)    

第3课 第一个项目 点亮LED

    在上一课中,你已经了解了一些关于如何使用Pico和编写你的第一个程序的情况。

    然而,到目前为止,我们在Pico上运行程序时没有使用任何其他外部电子硬件。在本课中,我们将尝试把其他电子硬件连接到Pico上。更具体地说,我们将使用一个LED,并编写一个程序来点亮该LED。但在开始编程之前,首先让我们了解一下程序的结构。

知识库

基本程序结构

    我们用MicroPython编写的程序一般由三种基本程序结构组成。复杂的程序都是由这些基本结构组合而成的,所以先学习它们是很有必要的。分别是:顺序结构、循环结构和选择结构。

顺序结构

    顺序结构是最基本的程序结构。在顺序结构中,程序从上到下,逐行依次运行。例如,当下面的程序运行时,它首先打印 “Hello”,然后是 “World”。
image004
    执行结果。
image005
    如果你仔细观察就会发现,与其他语言需要用特定的终止符来结束每一行的语句不同,在MicroPython中你只需要按ENTER键就可以结束一行。

实践与操作

    现在,让我们回归项目中去,实践一下我们刚刚学到的东西。为了开始本课,我们将使用Grove Shield for Pi Pico来连接Pico和其他Grove电子产品,以实现更有趣的项目。经过观察,你可能会注意到,Pico并没有附带用于连接Pi Pico的Grove Shield的金属针脚。要做到这一点,你需要焊接Pico的针头。

项目1:焊接焊头

    首先,准备好你所需要的焊接头的一切:一个电烙铁,一些焊料,一块清洁海绵,一个支架,两个20针头,一块面包板,当然还有你的Pico。为了方便焊接,我们可以先用面包板来固定两个20针的头线。以针座上的黑色塑料块为界,将针座的长端慢慢插入面包板。插入时,要确保两个针座上下对齐,并与Pico间隔相同的宽度。
image007
    然后,将你的Pico右转,并确保PCB板上的预留针孔与固定在面包板上的排针对准。对准后,慢慢地将两个针脚插入Pico的预留孔中,一直推到针脚上的黑色塑料块夹在你的Pico和面包板之间。这时,你会看到每个针脚都有一小段从PCB板上的预留针孔里伸出来。
image008
    将电烙铁放在支架上,打开开关加热。烙铁的尖端需要3-5分钟才能变热。确保加热时金属尖端不靠任何东西。在加热过程中,先把清洁海绵弄湿,放在一个方便的地方,以便以后清洁烙铁。
加热后,拿起你的烙铁手柄,在你准备的海绵上反复刷洗金属尖端,直到尖端看起来有光泽和干净。

注意!电烙铁的金属部分非常、非常热。在任何情况下,你都不应该触摸电烙铁的金属部分。

    清洁后,用电烙铁的尖端加热针脚和它下面离你最近的金色焊盘。用另一只手拿起一些焊料,从烙铁的相反方向慢慢推入针脚和焊盘的连接处。加热的引脚和焊盘会融化焊料,使其在焊盘周围流动。在完成一个方向的焊接后,继续向其他方向推焊料,直到焊盘完全被焊料覆盖。
image009

注意!焊接时不要使用过多的焊料。如果焊料溢出到相邻的焊盘上,使用Pico时会发生短路。

    好了,祝贺你焊好了第一个引脚!现在,你只需要用同样的方法来焊接其余的39个引脚。焊接时,记得不时地用干净的海绵擦拭你的烙铁,以保持烙铁头的清洁和光泽。
image010
    焊接完毕后,慢慢将Pico从面包板上拉出来。如果引脚和面包板插得太紧,强行将Pico拉出来很容易导致焊盘脱落。如果发生这种情况,你可以左右摇晃Pico,并尝试将Pico一点一点地移出来。
    好了,我们完成了!让我们开始执行我们的第一个任务!

项目2:发光的LED模块

    现在你已经完成了Pico引脚的焊接,我们终于可以使用Grove Shield for Pi Pico连接Grove模块了。让我们先试着点亮一个LED。

硬件连接

在这个项目中,我们将使用以下电子硬件。

  • 树莓派Pico
  • 用于Pi Pico的Grove Shield
  • 格罗夫–LED封装

    在Grove Shield for Pi Pico的帮助下,连接电子硬件的工作变得非常容易。首先,我们只需要将Pico的焊接引脚插入Shield中。
20210804024421
    在插入时,你可以观察Pico背面的针脚屏幕打印和Shield上的屏幕打印,以检查你的插入方向是否正确。然后,用Grove电缆将Grove – LED连接到Shield的D16端口。
image013
写一个程序
image014
    首先,用USB线连接Pico和电脑,然后打开Thonny,点击工具栏上的 “new “按钮,创建一个新程序。点击进入脚本区,用以下一行开始你的程序。

    这一行代码导入了一个名为 “machine “的MicroPython函数库。为了理解这行代码的具体作用,我们需要理解 “库 “的概念。
20210804180041
    在这行代码中,我们导入了一个名为 “machine “的MicroPython库,它包含了与特定硬件相关的各种功能。它可以不受限制地直接访问系统的硬件功能(如CPU、定时器、总线等),这样我们就可以用MicroPython更有效地控制连接到Pico的其他电子硬件。这包括为连接到Pico的各种电子硬件设置引脚。

    在硬件项目的开发中,仅仅将硬件连接到Pico上是不够的。例如,我们将LED连接到Shield的D16上,但除非我们特别告知,否则Pico不会意识到这一点–你必须写一个程序来定义控制电子硬件的引脚。在机器库中,有一类名为 “引脚 “的函数。
20210804180244
    在机器库的帮助下,你可以在程序的下一行轻松定义LED的引脚。
image015
    在这行代码中,我们创建了一个名为 “LED “的对象来帮助我们控制LED,并使用机器库中的Pin函数来定义LED的引脚编号和模式。Pin函数总共有两个参数。第一个参数16代表你定义的引脚编号。因为我们在构建项目硬件时将LED灯连接到了D16,所以我们在这里将其设置为16。第二个参数,machine.Pin.OUT,告诉Pico这个引脚应该被用作输出而不是输入。

    好,现在输入最后一行。
image016
    在这行代码中,我们使用value函数为我们刚刚定义的引脚写入值,以打开灯。当控制Pin类的引脚时,我们通常使用1的值来分配一个高的级别(开)和一个0的值来指定一个低级别(关)。我们的第一个硬件程序已经完成。完整的程序代码如下。
image017
image018
    在我们完成程序后,用USB线将Pico与电脑连接起来,如下图所示。在接下来的课程中,当我们要运行一个程序时,我们总是需要用USB线连接Pico和电脑。
image019
    点击工具栏上的 “运行 “按钮,将程序保存到任何位置,你可以看到插入D16的LED灯被点亮了。

    如果你想关闭LED灯,只需将该值从1改为0。
image021

思维扩展

    尝试用程序关闭LED。
image023
image024

第4课 电子硬件编程中的 “你好世界”。眨眼

    当我们尝试用某种语言进行软件编程时,”Hello World “往往是我们写的第一个程序。眨眼,也就是闪烁LED的任务,是电子硬件世界中的 “你好世界”。在本课中,我们将尝试编写一个Blink程序。要做到这一点,我们将需要使用三个基本编程结构中的另一个–循环结构!

知识库

循环结构

    与顺序结构不同,具有循环结构的程序会重复执行一条或多条指令。根据重复执行的次数,循环结构可以细分为确定的循环结构和不确定的循环结构。当一个具有确定循环结构的程序被执行时,它只重复有限的次数;当它满足某个条件时,循环将自动终止。然而,一个具有不确定循环结构的程序将继续重复循环而不停止。在Python中,我们经常使用for-loop和while-loop。

循环

让我们先来看看一个用while-loop语句编写的程序。
image025
    该程序首先在序列结构中执行。在程序的开始,我们首先声明一个变量 “a”,并用print()打印一行字符 “loop start”。
image026
20210804181158
    接下来,我们使用while语句来创建一个明确的循环结构。这指定了当变量 “a “小于5时,程序应重复执行while语句中的指令。也就是说,它应该打印出 “a “的值,并给 “a “加1,直到 “a “大于或等于5。
image027
    程序的输出结果如下。
image028
    观察样本程序的执行结果,我们可以发现,该程序在执行了while语句中的几个循环后,才开始执行最后一个print()语句。
    但是,计算机如何知道哪些语句需要在while循环语句中重复,哪些语句在while循环语句之外?MicroPython使用缩进和冒号”: “来区分代码块之间的层次,不像其他编程语言(如Java和C)使用大括号”{}”来分隔代码块。这一变化使得用MicroPython编写的代码清晰易懂。
20210804181308

循环

    一个for-loop一般用于遍历一个序列中的所有元素,例如:
image029
程序的输出结果如下。
image030
    其中,range()是MicroPython的一个内置函数,它可以生成一个整数的列表。一般来说,我们在for-loop中使用这个函数。例如,range(10)创建了一个从0到9的九个整数的列表。

    在这个程序中,我们将使用for-loop进行遍历,并使用print()将所有整数输出到Shell。

实践与操作

项目1:用For-loop控制LED的开和关

    在这个项目中,我们将用一个for-loop来控制LED的开和关,以实现闪烁。

硬件连接

在这个项目中,我们将使用以下电子硬件。

  • 树莓派Pico
  • 用于Pi Pico的Grove Shield
  • 格罗夫–LED封装

与之前类似,我们将LED连接到D16。
image013
在上一课中,我们已经学会了如何控制LED的开启和关闭。为了实现最终的Blink程序,我们只需要做一些轻微的修改。

写一个程序
    首先,让我们试着这样修改程序。
image031
    点击 “运行 “按钮,看一下LED灯。你会发现,LED灯只是非常快地闪了一次。这是因为:

  1. 程序的执行速度非常快,以至于闪光效果并不明显。
  2. 我们没有为程序设置循环结构,程序的闪光部分只执行了一次。

    让我们来解决第一个问题。既然问题是程序运行速度太快,我们可以在每个LED的开启和关闭之间设置一个延迟。
在程序的开始,我们引入了一个新的函数库:utime。
20210804181640
image032
    接下来,我们使用utime中的sleep函数,在每个操作LED的程序之后增加一个延迟。
image033
    除非另有说明,睡眠功能的默认单位是秒。通过修改程序,我们将每盏灯的开和关的时间设置为1秒。

    让我们首先尝试使用for-loop来简单地打开和关闭LED。该程序如下。
image034
image035
    当我们执行这个程序时,我们可以发现LED在循环10次后就不再闪烁了。这是因为使用range()函数只产生了10个整数,所以for-loop只循环了10次。当我们把range(10)改为range(20)时,LED循环了20次。

    当然,不管我们把这个值设置得多大,循环总是一个定点循环。如果我们想保持程序的运行,我们需要使用不确定的循环结构来编写程序。

项目2:用While-loop实现眨眼功能

    在这个项目中,我们将使用while-loop来实现连续闪烁的LED的效果。

写一个程序

    使用while循环,我们可以很容易地使程序无限期地重复。我们只需要将程序中的 “for i in range(10) “改为 “while True”。
image036
    ”while True “是while-loop语句的一种用法。与一般的while-loop只在满足某个条件时执行循环不同,这个 “while True “是一个不确定的循环语句,这意味着程序将被反复连续地执行,直到被人为地终止。
完整的代码如下。
image037
image038
    再次点击 “运行 “按钮,连接到Shield的LED开始闪烁。除非人为地停止该进程,否则LED将继续闪烁。
image039
image040

思维扩展

尝试使用两个循环结构和sleep()函数来实现不同的照明效果。

    例如,你可以减少延迟时间以产生更快速的闪烁。
image041
image042

使用 Arduino 进行杆球平衡系统的 PID 控制

使用 Arduino 进行杆球平衡系统的 PID 控制

使用 Arduino 进行杆球平衡系统的 PID 控制

通过研究杆和球系统并使用Arduino作为控制器,很容易理解PID 控制。目标是通过使用闭合控制回路方便地倾斜球,将球放置在杆的中心。

套件淘宝购买链接:

Arduino PID 控制 球 PID算法 控制原理 控制工程 经典item.taobao.com图标

5分钟总结视频(点击即可观看)

杆和球系统

它是控制工程中的经典系统。

  • 使用距离传感器,我们测量球的位置。
  • 使用Controller,通过 PID 控制,我们计算应该倾斜杆以将球定位和稳定在杆中心的角度。
  • 一个执行机构修改栏的倾向。
                                                                   杆和球系统

球位感应

我们通过使用红外光和PSD 检测器的距离传感器来做到这一点 :SHARP GP2Y0A21。

                                                                 夏普传感器

它的测量范围为 6 至 80 厘米。它在 5V 下工作,它的输出是与此特性曲线测量的距离相关的电压:

夏普传感器曲线

如果球距离传感器的距离小于 6cm,则测量结果错误。我们限制球在那个距离内的运动。

                                                                         制动

传感器信号调理

为了过滤(低通)传感器信号并获得更准确和可重复的信号,我们将在传感器输出和地之间连接一个 10μF 电解电容器。

电容滤波效应

由于我们要测量的最大电压为 3.1V,我们将使用指令将 Arduino 的电压参考设置为 3.3V:

analogReference(EXTERNAL);

我们将 AREF 引脚与 Arduino 的 3.3V 输出连接:

将模拟参考连接到 3.3V 输出

这样,Arduino 的 10 位数模转换器提供的 1024 个点将具有 3.3V 的满量程,而不是默认的 5V。因此,我们将分辨率从 5mV/ADC 提高到 3mV/ADC。

传感器校准

为了将传感器提供的张力与以厘米为单位的距离联系起来,我们将沿着杆移动球,注意 ADC 中的读数。在为 Arduino 开发软件中,包含一种操作模式,它通过串行端口连续传输传感器读数:

    if(0){// Para calibrar sensor de Distancia
      Serial.print(dist);
      Serial.print("mm     ADC: ");    
      Serial.println(measure); 
    }

沿着横杆有 9 个点就足够了。我们获得了传感器的校准曲线。

距离传感器校准

在软件中定义为:

int dcal [] = { // Calibracion de ADC a Distancia
  -193, -160, -110, -60, 0, 40, 60, 90, 120};
int ADCcal [] = {
  177, 189, 231, 273, 372, 483, 558, 742, 970};

并且为了将存储在测量变量 中的传感器的 ADC 读数转换为dist变量中以毫米为单位的位置,我们应用以下算法:

   for(int i =0; i<8; i++){ // Aplicamos curva de Calibracion de ADC a mm 
      if (measure >= ADCcal[i] && measure< ADCcal[i+1]){
        dist = map(measure,ADCcal[i],ADCcal[i+1],dcal[i],dcal[i+1]);
      }

变量dist有负值和正值:条形左端为 -193mm,右侧为 120mm,中心为 0。由于我们的目标是将球留在中心点,因此该变量dist相当于PID 控制系统文献中使用的误差

执行器

我们将使用扭矩为 6.9 kg.cm 的 HEXTRONIK HX5010 Servo和固定在杆一端的玻璃纤维连杆倾斜杆。

伺服执行器

正如维基百科所说,我们用可变持续时间的脉冲控制伺服的旋转:

通过脉冲控制伺服位置

在这个特定的伺服系统中,0º 位置是用 0.5ms 脉冲获得的,180º 转动是用 2.3ms 获得的。

标准库(包含在 Arduino IDE 中)舵机,包括写入(角度)指令,角度是0 到 180 之间的整数,它允许我们调整舵机的位置。这是常用的指令,但由于我们希望在转动伺服时获得最大精度,因此我们将使用writeMicroseconds代替。它的语法是:servo.writeMicroseconds (μS),其中μS 是脉冲持续时间的微秒。我们将有 500(向上位置)和 2300(向下位置)之间的值,有 1800 个不同的点,而不是使用最基本的指令:write(角度)只有 180 个。

在调试和调整过程中,我们会借助水平仪计算出静止位置(水平杆)。

气泡水平

控制器

我们将使用带有 ATMEL ATMEGA328-PU 微控制器的 Arduino 克隆,类似于 Arduino UNO 或旧的原始 Duemilanove。

  • 它在其模拟输入 A0 处接收球位置的测量值。
  • 它发出脉冲以控制其数字输出 12 上的伺服。
  • 通过其 USB 连接,它发送不同的数据集用于调试或以下形式的帧:
173,173, -5, -5 $ // dist, dist, vel, vel $

由运行在Processing 中开发应用程序的 PC 接收,这将使我们能够欣赏球的位置和速度随时间变化的图表,如下所示:

加工图

作为奖励,当球距离中心小于 8 毫米时,它会打开连接到输出 13 的 LED。

5V电源

Arduino 通过其 USB 连接接收其运行所需的 5V。它的5V脚的电源不足以给舵机供电,所以我们会用一个辅助电源给它供电。不要忘记将辅助电源的地与Arduino的GND地相连!否则,伺服控制信号将没有公共参考,将无法工作。

完整系统示意图

使用 Arduino 的 Bar and Ball PID 控制方案
连接
Arduino 连接 – 面包板

PID 控制。ARDUINO 软件。

一旦我们实现了物理系统,就该为控制器提供必要的智能,以实现我们的目标:将球留在杆的中心。

测量和反应期

测量和反应序列(程序周期)不会像微控制器那样快,而是每50 毫秒(存储在周期变量中的值)。我们这样做是因为如果测量和反应周期总是相同的持续时间,PID 控制系统会更好地工作。

主频为 16MHz 的微控制器有足够的速度在不到 10ms 的时间内完成程序周期。但是在如此快速的循环中,球速度的测量会失去精度,因为循环之间球的位置差异可以忽略不计,我们将球的速度计算为连续 2 个程序循环中的位置差异。

如果我们将周期延长到 100 毫秒,速度测量会更准确,但伺服以明显的间歇方式工作。

测试后,50ms 的周期提供了可接受的球速测量值和平滑的伺服操作。

球速计算

Arduino 中实现 PID 控制软件中,我们将速度计算为球的当前位置(变量dist)与其在前一个循环中的位置(变量lastDist)之间的差异。我们将通过数字低通滤波器(平均值)来提高此测量的精度,该滤波器包括获得最后 5 个测量速度的平均值。我们将它们存储在矩阵v [] 中,并在每个程序周期中使用以下算法处理它们:

  for (int i = 0; i <level-1; i ++) {// 我们全部向左移动 
      v [i] = v [i + 1];
    }
    v [nvel-1] = (dist - lastDist); // 我们放入一个新数据
    vel = 0;
    for (int i = 0; i <nvel; i ++) {// 我们计算平均值
      vel = vel + v [i];
    }
    vel = vel / nvel;

我们将使用速度值来计算 PID 控制的微分分量。我们还通过串行端口/USB 电缆发送它,以便处理软件接收它并以图形方式表示它。

第一个近似值:比例项

如果是将球带到杆的中心,那么我们应该将杆倾斜得越多,球离中心越远,这似乎是合乎逻辑的。我们用取负值(向上)的变量pos的值来确定舵机的旋转,它决定了杆的倾斜度:

pos &amp;amp;amp;lt;0:伺服向上

和正(向下)从水平条左侧的值 0 开始:

pos&amp;amp;amp;gt; 0:伺服下降

由于我们在变量dist中有球的位置,“球离中心越远,杆越倾斜”在软件中写为:

pos = Kp * dist

其中 Kp 是一个常数。

因此,我们实现了 PID 控制的比例项。

– 别告诉我这不容易!

– 是的,当然……我们为 Kp 分配什么值。

为了给 Kp 赋值(对于后面的 Kdifferential 和 Kintegral),我们首先给它赋值:1、100、235、0.01 或任何你想要的值,我们将观察系统的行为。

我们正在寻找当球接近目标但尚未到达时足以倾斜杆的最小值。大数值让球跑得太快,然后不得不停在中间!

对于构建的系统,合适的值是 2。只有比例项,球永远不会稳定。

以下视频显示了以下效果:

– Kp = 1。太低。在点 0 附近几乎没有影响。
– Kp = 100。太高了。球加速太多。
– Kp = 2。它适用于我们的系统。

术语差异

术语“差异”作用于当前周期和前一个周期之间的位置差异。也就是说,关于球的速度,因为一次测量和下一次测量之间经过的时间总是相同的。这个时间是测量和反应周期,在我们的系统中是 50ms。

微分项在软件中写成:

pos = Kd * vel

在程序的前 3 行中,我们定义了 PID 控制的 3 个常量的值:

float Kp =0; 
float Kd = 100;
float Ki =0;

我们正在做的是使杆倾斜与球的速度相反。我们首先为 Kd 赋予任何值。

– 我可以给它值 10,这是我最喜欢的数字吗?

– 是的。前进。

点击这里即可观看

我们已经设置 Kp = 0 以便比例项不参与,并且 Kd = 10 作为起点。据观察,这是一个不足的值,因为它没有足够的反应来停止球。

我们上升到 Kd = 100:反应过度,系统不稳定。

正确的值在 10 到 100 之间。我们用 50、25 进行测试……观察系统在微分控制下的行为。可接受的值为 Kd = 35。

我们设法非常有效地阻止了球!

我们的目标是将球停在中点。通过将杆倾斜得越远,比例控制使球更靠近中心。球移动得越快,导数控制使杆倾斜得越多,并设法阻止它。现在观察到2个学期的共同作用下,定义POS伺服作为银行足球比赛:

    pos = Kp * dist + Kd * vel

这已经有效了!

比例项和导数项的组合作用是 PD 控制,足以满足许多应用。它的弱点之一是当球停在中心点附近时,它不再做出反应。由于速度为 0,微分项不起作用。由于它靠近中心点,按比例项的杆倾斜很小,可能不足以移动球。这就是这次发生的事情:

这永远不会发生在远离中心的位置,因为比例项已经有足够的实体,因此它的倾斜度会使球移动。

– 我们应该增加比例项吗?

正如我们在之前的视频中看到的 Kp = 100,高 Kp 值会导致系统不稳定。

解决方案是我们需要有一个真正的 PID 控制的字母:积分的 I。

精度:积分项

积分项考虑了球的位置(如比例项)以及它在那里的时间。更严格地说:就像微分项作用于速度(球的位置随时间的导数)一样,积分项作用于位置曲线下随时间变化的区域。

积分项

该区域取决于积分区间。如果我们连续积分曲线下的面积,积分项只会导致系统不稳定。我们只会在球距杆中心小于 4 厘米时对球的位置进行积分。当距离中心小于 8 mm 时,我们认为目标已实现,我们停止积分。如果我们超出一个间隔,我们将重置 I (I = 0)。我们在软件中写成:

int Rint = 8;
int Rext = 40;

    if(abs(dist)>Rint && abs(dist)<Rext){
      I=I+dist*Ki;
    } 
    else {
      I=0;
    }
 pos=Kp*dist+Kd*vel+I;

也就是说,当球距离中心小于 4 厘米且大于 8 毫米时,我们将取其与中心的距离,将其乘以 Ki 并将结果累积在 I 中。我们在那里呆的时间越多,我就会变得越大。
总之,积分项提供了更高的精度,但必须方便地限制其作用,否则会带来太多的不稳定性。

Arduino总程序

#include <Servo.h>
//#include <Wire.h>
//#include "IICLiquidCrystal.h"

// Connect via i2c, default address #0 (A0-A2 not jumpered)
//LiquidCrystal lcd(0);

float Kp = 3;   //2
float Kd = 1;  //35
float Ki = 0.5; //0.1
int Rint = 8;   //
int Rext = 40;  //
int aim = 0;
unsigned long time = 0; //execution time of the last cycle
unsigned long timeSerial = 0;
int period = 50;        //Sampling period in ms
int sensorPin = 0;        //Analog Pin where the Distance Sensor signal is connected
int measure;            //What the sensor measures. They are ADCs.
int dcal [] = {         //Remote ADC calibration
  -193, -160, -110, -60, 0, 40, 60, 90, 120
};
int ADCcal [] = {
  177, 189, 231, 273, 372, 483, 558, 742, 970
};
int lastDist;     //Previous value of Distance to calculate Speed
int dist;         //distance in mm with 0 in the center of the bar
int nvel = 5;       //number of velocity values over which we calculate the average
int v[5];
int vel;          //mean value of the last speed levels
float I;          //Integral Value


Servo myservo;    //create servo object to control a servo
float pos;
float reposo = 1350; //value held by horizontal bar

int ledPin = 13; //Green led pin.

void setup()
{
  analogReference(EXTERNAL);  //AREF connected to 3.3V
  myservo.attach(3);         //attaches the servo on pin X to the servo object
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

  myservo.writeMicroseconds(reposo);
  delay(5000);

  //lcd.begin(16, 2);

}

void loop()
{
  if (millis() > timeSerial + 200)
  {
    timeSerial = millis();
//    Kp = map(analogRead(A1), 0, 1023, 0, 5000) / 100.0;
//    Kd = map(analogRead(A2), 0, 1023, 0, 400) / 100.0;
//    Ki = map(analogRead(A3), 0, 1023, 0, 300) / 100.0;

    //aim = map(analogRead(A5), 0, 1023, -20, 20);

    Serial.println();
    Serial.print("Kp:");
    Serial.println(Kp);

    Serial.print("Kd:");
    Serial.println(Kd);

    Serial.print("Ki:");
    Serial.println(Ki);

    Serial.print("Aim:");
    Serial.println(aim);

    Serial.print("Pos:");
    Serial.println(dist);
  }

  
  if (millis() > time + period) { //
    time = millis();

    //    lcd.setCursor(0, 0);
    //    lcd.print("Kp:");
    //    lcd.print(Kp);
    //
    //    lcd.setCursor(8, 0);
    //    lcd.print("Kd:");
    //    lcd.print(Kd);
    //
    //    lcd.setCursor(0, 1);
    //    lcd.print("Ki:");
    //    lcd.print(Ki);
    //
    //    lcd.setCursor(8, 1);
    //    lcd.print("Pos:");
    //    lcd.print(dist);






    //We measure DISTANCE
    measure = analogRead(sensorPin);
    measure = constrain(measure, ADCcal[0], ADCcal[8]);
    lastDist = dist; //We save the previous value of dist to calculate the speed
    for (int i = 0; i < 8; i++) { //We apply Calibration curve from ADC to mm
      if (measure >= ADCcal[i] && measure < ADCcal[i + 1]) {
        dist = map(measure, ADCcal[i], ADCcal[i + 1], dcal[i], dcal[i + 1]);
      }
    }
    //Average SPEED calculation
    for (int i = 0; i < nvel - 1; i++) { //We all move to the left to free the last one.
      v[i] = v[i + 1];
    }
    v[nvel - 1] = (dist - lastDist); //We put a new data
    vel = 0;
    for (int i = 0; i < nvel; i++) { //We calculate the mean
      vel = vel + v[i];
    }
    vel = vel / nvel;
    // Integral
    if (abs(dist - aim) > Rint && abs(dist - aim) < Rext) { //Only if it is inside (-Rext, Rext) and outside (-Rint, Rint)
      I = I + dist * Ki;
    }
    else {
      I = 0;
    }
    //We calculate servo position
    pos = Kp * (dist - aim) + Kd * vel + I;
    myservo.writeMicroseconds(reposo + pos);

    if (abs(dist) < Rint) { //If we are inside Rint turn on Led
      digitalWrite(ledPin, HIGH);
    }
    else {
      digitalWrite(ledPin, LOW);
    }

    if (1) { //Shipping for PROCESSING
      Serial.print(dist + 200);
      Serial.print(",");
      Serial.print(dist + 200);
      Serial.print(",");
      Serial.print(vel);
      Serial.print(",");
      Serial.print(vel);
      Serial.print("$");
    }
    if (0) { //Debug
      Serial.print(millis());
      Serial.print(" ms|dist: ");
      Serial.print(dist);
      Serial.print("|vel: ");
      Serial.print(vel);
      Serial.print("|Kp*dist: ");
      Serial.print(Kp * dist);
      Serial.print("|Kd*vel: ");
      Serial.print(Kd * vel);
      Serial.print("|Int: ");
      Serial.print(I);
      Serial.print("|pos: ");
      Serial.println(pos);
    }
    if (0) { //To calibrate Distance sensor
      Serial.print(dist);
      Serial.print("mm     ADC: ");
      Serial.println(measure);
    }
    if (0) { //DeBug Speeds
      for (int i = 0; i < (nvel); i++) {
        Serial.print(v[i]);
        Serial.print(",");
      }
      Serial.print("       vel:");
      Serial.println(vel);
    }
  }
}

Processing总程序

// Arduino: _14_HV_logger_2Ch
// VOLTAGE IN PIN Analog0
// CURRENT IN PIN Analog1
import processing.serial.*;
Serial USB;
String message = null;
int jmax = 100000; // Stored readings
int[] VminADC; // From 0 to jmax
int[] VmaxADC; // Raw values 
int[] IminADC;
int[] ImaxADC;
float[] Vmin; // From 0 to jmax
float[] Vmax; // in kV and uA
float[] Imin;
float[] Imax;
int j = 0; // Whole Register
int x = 0; // On Screen
int xpant = 1000; // Dimensions of the screen
int ypant = 800;
int mSup = 60; // Margin Sup and Inf of the trace
int mInf = 50;
int mLat = 50;
int xgraf = xpant - 2 * mLat;
int xDisplay = 30; // Beginning of the text
int yDisplay = 30;
float Cal0 = 1;
float Cal1 = 1;
float CalVEL =  300; // 
float CalPOS = 400; // 
PFont fontVerdana;
String [] com = new String [3];
void setup() {
  size(1000, 800, P3D);
  println(Serial.list());
  String portName = Serial.list()[0]; //    Puerto COM 14 
  USB = new Serial(this, portName, 115200);
  VminADC = new int [jmax];
  VmaxADC = new int [jmax];
  IminADC = new int [jmax];
  ImaxADC = new int [jmax];
  Vmin = new float [jmax];
  Vmax = new float [jmax];
  Imin = new float [jmax];
  Imax = new float [jmax];
  com = new String [3];
  fontVerdana = loadFont("Verdana-20.vlw");
}
void draw() {
  background(190);
  // New Data ***************************************************************************
  while (USB.available () > 0) {
    message = USB.readStringUntil(36); // 644,659,725,733$
    if (message != null) {    
      if (j < jmax - 2) {
        j++;
      } 
      else {
        j = 0;
      }
      message = message.substring(0, message.length()-1); // 644,659,725,733
      String[] com = splitTokens(message, ",");
      VminADC [j] = int(com[0])-200;   // Stored in VminADC[j] as ADC 
      VmaxADC [j] = int(com[1])-200;
      IminADC [j] = int(com[2]);
      ImaxADC [j] = int(com[3]);
      print(VminADC [j]);
      print("\t");
      print(VmaxADC [j]);
      print("\t");
      print(IminADC [j]);
      print("\t");
      println(ImaxADC [j]);
      Vmin [j] = VminADC [j] * Cal0;  // Stored in Vmin[j] as kV
      Vmax [j] = VmaxADC [j] * Cal0;
      Imin [j] = IminADC [j] * Cal1;
      Imax [j] = ImaxADC [j] * Cal1;
    }
  }

  //  Axis ********************************************************************************
  stroke(255);
  strokeWeight(2);
  line(mLat-10, ypant-mInf, xpant-mLat, ypant-mInf);
  line(mLat, ypant-mInf+10, mLat, mSup);
  // Reference Axis *************************************************************************************
  /*  fill(#FF1C20); // Red
   //  text("500uA", 10, (ypant-mInf)-500*(ypant-mSup-mInf)/660+4);
   stroke(#FF1C20, 20); // Red
   line(mLat, (ypant-mInf)-500*(ypant-mSup-mInf)/660, xpant-mLat, (ypant-mInf)-500*(ypant-mSup-mInf)/660);
   */
  fill(#321CFF); // Blue
  text("0", 30, mSup+(ypant-mInf-mSup)/2+4);
  stroke(#321CFF, 20); // Blue
  line(mLat, mSup+(ypant-mInf-mSup)/2, xpant-mLat, mSup+(ypant-mInf-mSup)/2);

  // Draw before 1st scroll ********************************************************************************************
  if (j <= xgraf) {
    for ( int i = 0; i < j; i ++) {
      strokeWeight(1);
      stroke(#FF1C20); // Red VELOCIDAD
      line(i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Imin[i]*(ypant-mInf-mSup)/CalVEL-2, i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Imax[i]*(ypant-mInf-mSup)/CalVEL+2);

      stroke(#321CFF); // Blue POSICIÓN
      line(i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Vmin[i]*(ypant-mInf-mSup)/CalPOS-2, i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Vmax[i]*(ypant-mInf-mSup)/CalPOS+2);
    }
  }
  // Draw with scroll **************************************************************************************************************
  if (j > xgraf) {
    for ( int i = 0; i <= xgraf; i ++) {
      strokeWeight(1);
      stroke(#FF1C20); // Red
      //      line(i+mLat, int((ypant-mInf)-Imin [j-xgraf+i]*(ypant-mSup-mInf)/660), i+mLat, int((ypant-mInf)-Imax [j-xgraf+i]*(ypant-mSup-mInf)/660));      
      line(i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Imin[j-xgraf+i]*(ypant-mInf-mSup)/CalVEL-2, i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Imax[j-xgraf+i]*(ypant-mInf-mSup)/CalVEL+2);
      stroke(#321CFF); // Blue
      // line(i+mLat, int((ypant-mInf)-Vmin [j-xgraf+i]*(ypant-mSup-mInf)/15), i+mLat, int((ypant-mInf)-Vmax [j-xgraf+i]*(ypant-mSup-mInf)/15));
      line(i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Vmin[j-xgraf+i]*(ypant-mInf-mSup)/CalPOS-2, i+mLat, int(mSup+(ypant-mInf-mSup)/2)-Vmax[j-xgraf+i]*(ypant-mInf-mSup)/CalPOS+2);
    }
  }
  // Text Channels Readings ***************************************************************
  stroke(190);
  fill(190); // Blue
  rect(30, 12, 330, 20);
  textFont(fontVerdana, 20);
  fill(#321CFF); // Blue
  text(int(Vmin[j]) +" mm", xDisplay, yDisplay);
  textFont(fontVerdana, 10);
  //  text("+/-" + nf((Vmax[j] - Vmin[j]), 0, 1) +" kV", xDisplay + 80, yDisplay);
  textFont(fontVerdana, 20);
  fill(#FF1C20); // Red
  text(int(Imax[j]) +" mm/s", xDisplay + 200, yDisplay);
  textFont(fontVerdana, 10);
  //  text("+/-" + int((Imax[j] - Imin[j])) +" uA", xDisplay + 200 + 80, yDisplay);
}
/*void keyPressed() {
 if (key == 's' || key =='S') {
 grabar();
 }
 if (key == 'v' || key =='V') {
 Cal0 = 9.0/((VminADC [j]+VmaxADC [j])/2); // 9 kV equals 489 ADC
 println("Calibración de Tensión: "+ Cal0);
 }
 if (key == 'i' || key =='I') {
 Cal1 = 660.0/((IminADC [j]+ImaxADC [j])/2);
 println("Calibración de Intensidad: "+ Cal1);
 }
 }
 */
void grabar() {
  String[] lines = new String[j];
  for (int i = 0; i < j; i++) {
    lines[i] = str(Vmin [i+1]) + "\t" + str(Vmax [i+1]) + "\t" + str(Imin [i+1]) + "\t" + str(Imax [i+1]); // Vmin  Vmax  Imin  Imax
  }
  saveStrings("Registro.txt", lines);
  exit();
}

MIT EECS是如何安排本科生的专业基础课

简介

本文通过搜集资料经历,结合自己自学经验,来谈谈MIT EECS(美国麻省理工学院电气工程与计算机科学系)大一至大三阶段主要的课程框架和教学方法。

在过去的比较长的一段时间里,我自认为比较认真和自习的去了解了MIT EECS科系给本科生准备的各类课程和教学安排,尤其是专业基础课,我也亲自下海,花了数月时间自学了几门,写这篇文章,就是为了和大家共同分享一下我的心得和感受,希望能帮助国内有心的莘莘学子(主要是各强弱电类、信息类、计算机类专业)和在职工程人员们走出专业学习的困境,打好基础,参考世界上最优秀的理工科院校是配合培养自己的本科生的。而且MIT受实用主义影响较深,大部分的课只要花心思去学,基本都不难,且确实令人受益匪浅。希望对在国内读书的同学和想要自我提升的工程师们一些借鉴经验,少走弯路。

MIT是美国乃至世界上最著名的研究型大学之一,其在EECS,MIT EECS(美国麻省理工学院电气工程与计算机科学系)系在工科领域更是闻名遐迩,除在相关研究领域成果瞩目外,也历来重视本科生教学,重视教学改革和课程教材建设。


话外题:

我并非不认可大家可以适度讨论一些问题来满足自己的好奇心,但大体上来说,我还是比较认可胡适先生的说法:“多研究些问题,少谈些主义”。我花时间写这么一篇文章,并计划不断更新,就是希望以身作则和亲身体验来提倡务实的风气,关于MIT EECS(美国麻省理工学院电气工程与计算机科学系),侃侃而谈的文章很多,但拿起资料,从零开始认真学习的人则寥寥无几,希望大家一起努力。


#### [清华姚班与 MIT EECS 本科哪个好?](https://www.zhihu.com/question/27313459 “清华姚班与 MIT EECS 本科哪个好?”)

像是这种问题,知乎上讨论的火热,我并非要一捧一踩,但清华姚班毕竟开办时间并不算太长,而且其课程内容国内公开的中文信息资料相对也较多,大家都可以去查阅看看姚班都给最顶尖的国人学子提供了哪些课程和安排。就我个人感觉而言,其目标和起点都非常高,显然并不适合普通人自学,最主要的还是没有那么多清华的课堂录像和各种资料公开。

但MIT的情况就非常不同了,可能是由于历史沉淀或者其它一些缘故,MIT EECS科系公开的资料之庞大细致,远非一般国内普通高校所能望其项背。而其余四个Four Big(Berkeley, MIT, Stanford, and CMU)也未必向普通公众开放了这么多可供学习的材料。


我对MIT的印象和许多人一样,刚开始懵懵懂懂,互联网利用和资料搜集能力不高,往往可能是从网易开放课之类开始的。但凡你稍微有点英语基础,我都强烈推荐你应该直接去MIT opencourseware直接寻找相关课程的资料,而非转由第三方(有些朋友看B站或者网易开放课也许是为了中英双语字幕,这我并不反对),但第三方的搬运往往遗漏了大量的配套资料。

翻开MIT opencourseware的首页,按照Department选择EECS后,进入这个科系开放课程的页面,如上所示,可以看到上百门浩如烟海的课程。

大概在06年的时候,清华大学相关科系派出了两位著名的教师(看清华相关网课的同学估计都了解这两位教师分别是信号与系统和电路原理的课程负责人)到MIT为期一年左右的访问,用于借鉴和参考帮助清华进行教学改革。以下是他们回来后发表的文章,整理的较为细致,参考价值非常高,我先放出来供大家参考。如果您和我一样,是已经大学毕业多年,相信对国内相关专业的课程体系如数家珍,若读者你还在大学低年级,对下面所说的这些课程都还没有完整的学习经历和感受,也不要担心,后续我还会慢慢更新和讲解。

总的来说,连清华的两位老师都赞叹MIT EECS科系给本科学生提供的教学资源丰富,但这毕竟是十几年前的考察回顾了,双方的课程体系今天都有了比较大的调整。

毕竟我也已毕业将近10年,所以还是以经典体系为例,后续再谈谈现在改革的变化。我们先看看经典的选课图。

有了这张地图,大家就不会对下面这个浩如烟海的课程列表感到不知从何下手了

首先是18开头的两门数学课,微积分1 和 微积分2,以及一门8开头的大学物理课。然后还有线性代数和补充了一门专门的微分方程的课,这几门公共基础课几乎和国内工科大学一开始必学的课程完全类似,虽然也非常经典,但我们讨论的重点是EECS的专业基础课,也不在该科系的页面上,故而不在赘述,有兴趣的读者请自行去查阅。至于离散数学,也是国内计算机相关科系的先修课,往往作为数据结构和算法的预备课程,而非在所有工科类课程中都有开设。

按照传统的MIT的四门课,所有EECS的学生都必须在大二修习并通过的6.001-6.004将会是我们讨论的重点。因为这四门传统的核心基础课程构成了整个系全部本科生必修且学分学时最长的4门课程,可见其基础支柱作用和重要性。

计算机程序的构造与解释

其中6.001在国内尚无完全对应的课程,但其难度对于初学者非常高,其实本来非常不适合作为大学第一门CS相关课程(如果你是天赋和毅力极佳的自学者,可以考虑作为自学的第一门课程)但其重要性非常高。由于作者本人EE工程实践背景远较CS深,所以我是很晚才开始注意到这门课程的重要性的。如果你是有着较为丰富的编程经验的程序员,尤其是涉及系统和算法优化等问题的程序员,强烈建议你马上开始学习和阅读此神书。对绝大多数本科低年级在校生而言,程序设计初学者劝退,建议在充分熟练掌握一门偏底层程序设计语言和具备一些初步的数据结构和算法知识后再开始学习此课程,以免学习积极性受到不必要的挫伤。

Structure and Interpretation of Computer Programs

![](https://pic2.zhimg.com/v2-7120522c5ad2990c419c4d4a3becc539_r.jpg)
只有翻译自原书第二版的简体中文版,由北京大学数学学院信息科学系教授裘宗燕译出,机械工业出版社出版。

评论如下:

这是一门极其重要,但99%程序员没有学的东西。

但是注意的是,这门学问与实用主义当道的现代有些突兀。

如果想学一门计算机语言面对市场的挑战,那么请出门左转。

如果像深刻理解计算机程序设计,那么这门课不容错过。

当时本书在出版两年后(1986年),Harold Abelson和Gerald Jay Sussman两位作者录制了《计算机程序的构造和解释》系列公开课,该录像是他们在给Hewlett-Packard公司员工培训时录制的。当时的视频课程只有英文字幕,因此,也让很多英语小白的程序员心有余而力不足。有人将这些英文字幕翻译成中文,并在GitHub上发布,如此一来,英语小白开发者也可以学习Scheme/Lisp了。

 

 

 

绝佳的数字电子技术实践

TD4 CPU 原理详细教程

简介


如何使用 9 种 14 个 74 系列芯片,自己构造一个 4 位 CPU(中央处理)的可编程计算机系统

CPU 的核心原理其实非常简单,其实任何人都只需要非常基础的一点数字电子技术知识,就可以用 14 个 74 系列芯片,完成一个 4 位简单计算机的设计。这本大概在十几年前就已经在日本出版并风靡电子爱好者圈子的一本好书,既可以给对计算机工作原理好奇的青年爱好者作为制作范例,又可以成为让大学本科学完数字电子技术等相关课程后用于学生大作业或课程设计改进的雏形和范例(实话说,比交通的什么之类的例子实在是好太多,因为今天是计算机的时代,大家显然会对这样的例子兴趣更加浓厚)。我花了大概一天半的时间,就基本搞懂了这个例子的原理,写下这篇文档分享给大家,希望大家也能向我一样动手坐一坐,对数字计算机的工作原理感悟体会更深刻,共勉!

总电路图(点击可放大)

TD4自制CPU 13个74系列芯片绝佳的数字电子技术实践

绝佳的数字电子技术实践
TD4 CPU原理详细教程
简介
如何使用9种14个74系列芯片,自己构造一个4位CPU(中央处理)的可编程计算机系统
 
 
CPU的核心原理其实非常简单,其实任何人都只需要非常基础的一点数字电子技术知识,就可以用14个74系列芯片,完成一个4位简单计算机的设计。这本大概在十几年前就已经在日本出版并风靡电子爱好者圈子的一本好书,既可以给对计算机工作原理好奇的青年爱好者作为制作范例,又可以成为让大学本科学完数字电子技术等相关课程后用于学生大作业或课程设计改进的雏形和范例(实话说,比交通的什么之类的例子实在是好太多,因为今天是计算机的时代,大家显然会对这样的例子兴趣更加浓厚)。我花了大概一天半的时间,就基本搞懂了这个例子的原理,写下这篇文档分享给大家,希望大家也能向我一样动手坐一坐,对数字计算机的工作原理感悟体会更深刻,共勉!
 
总电路图(点击可放大)
 
 
 
为什么要这么做?
虽然功能简单,但是个完整的CPU,麻雀虽小五脏俱全
采用9种小规模集成电路芯片(74系列),完成数字电路设计的基础学习,可以彻底直观理解到电路工作层的原理。
 
立即购买套件:
 
-现在可以轻松获得3GHz-5GHz的高速处理器,就连不到一美元的处理器,其工作频率也在10M左右,计算位数也在16甚至是32位,我们自己搭建一个一个意义是什么?
 
 
并非没有意义,原因如下
– 从电路层面直观了解运算过程
– 直观理解各个部分的组成原理(到数字电路层面)
– 简化的4位数,不至于眼花缭乱
– 只有12个指令,也足以完成一些编程任务
 
其次要制定CPU的规格
– 只操作4位数
– 仅10简单的中小规模集成电路芯片(74系列)
– 可编程指令只有16条
– 演示和改造的实用性都很强
 
这是一个典型的解剖模型(麻雀虽小 五脏俱全)
采用机器语言编程,可以完全直观理解并看到其运行过程,可手动也可自动操作
 
这个项目适合哪些人?
有初步或者想初步学习数字电路的人
想了解CPU工作和设计思路的非专业人员、正在学习电子及计算机专业的学生
 
经过这个项目,你会收获些什么?
中规模集成电路,如74系列芯片,该怎么应用到实际需求中去
彻底理解一个简化版本的CPU的每个运作细节
 
教程开始啦!
74系列芯片
什么是74系列芯片,相信学过数字电子技术的同学都不会陌生,课本里都有频繁的举例,但为了完整补充相关产业背景的知识,这里还是引用一下维基百科的说法:
7400系列是典型的中小规模数字逻辑集成电路。 在 1960 年代中期,最初的 7400 系列集成电路由德州仪器 (TI) 引入,前缀为“SN”,以创建名称 SN74xx。 由于这些零件的普及,其他制造商发布了引脚对引脚兼容的逻辑器件,并保留了 7400 序列号作为识别兼容部件的辅助工具。 但是,其他制造商在其部件号上使用不同的前缀和后缀。
二值逻辑和逻辑电平、正负逻辑电平
二进制数正好是利用二值数字逻辑中的0和1来表示的。二值数字逻辑是Binary Digital Logic的译称。本系统中,由于采用USB接口供电,用5V表示1,0V表示0。在本系统中,一般采用正逻辑(即高电平有效),如所表示的数据线上有一横杠线或者器件输入输出口处有一小圆圈,则表示负逻辑(即低电平有效)。更多专业详尽的信息和说明,请查阅数字电子技术基础课本中的相关章节
 
如何查看74系列芯片的Datasheet
规格表(Data sheet)数据表,数据表或规格表是对产品,机器,组件,材料,子系统或软件的性能和其他特性进行了足够详细的总结的文档,使买家能够了解产品的含义以及设计工程师要了解组件在整个系统中的作用。通常,数据表由制造商创建,以介绍文档其余部分的介绍性页面开头,然后列出特定特征,并提供有关设备连接的更多信息。
本项目使用了8种74系列芯片,我们都采用74HC型号,相关的规格书可以在https://www.alldatasheet.com/网站查询到,个人倾向于阅读德州仪器TI公司较新的Datasheet文档,因为其资料丰富且格式规范。德州仪器公司的SN54系列和SN74系列在功能上完全一致,规格书也是共用的,所以查找规格书时,如书上所示74LS10,网上也可以搜索SN54HC10,只是54系列芯片用于军用。
我刚上大学那会,一开始阅读英文规格书材料也非常不适应,因为从小到大的数理化教育都是非英语环境进行的,但作为电子、信息、自动化和计算机相关专业的学生和从业者,这一点将来无法避免,只好捧起英文资料一遍查资料一遍看,再借助一些中文参考书,慢慢的大家受到的困难的阻力越来越小,阅读越来越顺畅,希望读者也要尝试坚持下去,一定会受益匪浅的。
 
74HC/LS/HCT/F系列芯片的区别
74系列集成电路大致可分为6大类: .74××(标准型); .74LS××(低功耗肖特基); .74S××(肖特基); .74ALS××(先进低功耗肖特基); .74AS××(先进肖特基); .74F××(高速)。
同型号的74系列、74HC系列、74LS系列芯片,逻辑功能上是一样的。74LSxx的使用说明如果找不到的话,可参阅74xx或74HCxx的使用说明。74HC的速度比4000系列快,引脚与标准74系列兼容 4000系列的好处是有的型号可工作在+15V 。新产品最好不用LS。
 
74系列芯片和逻辑门电路的关系
像74这一类的所有芯片都需要电源正负供电,其余部分则往往作为信号输入输出的功能引脚,在电路设计布局时,对于简单功能的芯片,门电路符号和芯片引脚标识方式常常混用,这时需要我们查阅芯片Datasheet来确认其对应关系,以74HC10为例,我们查到规格书的地址是:https://pdf1.alldatasheet.com/datasheet-pdf/view/203930/TI/SN54HC10.html
通过在规格书中,下面两张示意图,即可理解在实际电路中如何使用该芯片:
 
 
 
 
什么是CPU-逐项理解冯诺依曼原理
– 程序计数器从内存中获取指令(取指令)
– 根据解码结果执行操作(执行指令)
– 存储计算结果(存储结果)
(上述三个过程可以比拟成逐页翻书,理解书中内容,并尝试拿笔记本记录下来的过程)
机器的每个 时钟脉冲周期,都会重复上述的几个过程
 
仿真器
接下来,我们先来看一个仿真器,此时此刻你并不需要理解这个仿真器中的所有细节,等后续讲述中,每个部分理解后,再回来重看此动态仿真程序,即可全面理解。
 
 
 
 
 
现在,你看此仿真程序运行图,所需要知道的事情有以下几点:
  1. 红色线代表此时该线上电平为高(5V),蓝色代表电平为低(即0V)
  2. 整个系统由CLOCK脉冲驱动进行,RESET低电平有效,即RESET被拉低后,系统里所有数据清零重启
如果你已经学过数字电路基础,那么你应该还可以尝试记住并理解以下几点,如果不能或全部理解也没关系,可以全部学完,回来再看看
  1. 四个74HC161分别为寄存器A、B、C、D,分别可用于存储4个比特的数据
  2. 第三个寄存器C的输出,用于输出4位电平,驱动LED
  3. 第四个寄存器D的输出,用作指令的译码,即作为地址总线的A0-A3(Address0-Address3)
  4. 两个74HC153组成了一个4位四路数据选择器,用于选择哪一路的4位数据给后续的四位全加器做加数A。其中,第三路的4位数据来源于4位拨码开关(即输入),第四路不连接被弃用。
  5. 74HC283是一个四路全加器,它的第一个加数来源于两个74HC153的数据选择通道,第二个加数来源于指令数据的低4位(即D3-D0),这种被包含在指令中参与运算的数据又叫立即数immediate。全加器的求和结果将输出给4位寄存器A、B、C、D均可,选择其中哪一个寄存器记忆结果,完全由每个寄存器输入端的LOAD(~LD)信号决定,请注意LOAD是低电平有效,这是后续指令译码结果中控制总线的最重要一部分。
  6. 全加器有溢出位输出Carry,但是无法记忆上一次的计算结果是否溢出,故需要一个D触发器作为溢出标记位记住上一次的求和结果是否溢出,并送给译码器指令。
  7. 图上所有用门电路表示的组合电路,是一个完整的指令解码器,它由取得指令的高4位(即D7-D4)和溢出标记位一起作为译码器的输入,由控制四个寄存器的加载(LD)和两个数据选择器的A、B作为输出,这是典型的控制总线。
 
TD-4 CPU规格对比-理解和现有CPU规格之间的数量级差异
 
 
 
复位电路
补充点基础知识
如何表达0和1
– 判断电压低还是高
– 设置0V=0,5V=1
 
– 低电平有效,还是高电平有效
– 注意,每个IC为每个信号引脚都设置了正逻辑和负逻辑
 
 
上拉电阻、下拉电阻的概念
我们现在开始设计,如何将开关的开关两种状态,转换成电平的高低两种状态。这个转换过程,需要理解上下拉的概念。
在数字电路中,上拉电阻(英语:Pull-up resistors)是当某输入端口未连接设备或处于高阻抗的情况下,一种用于保证输入信号为预期逻辑电平的电阻元件。他们通常在不同的逻辑器件之间工作,提供一定的电压信号。
同样的,一个下拉电阻(Pull-down resistor)以类似的方式工作,不过是与地(GND)连接。它可以使逻辑信号保持在接近0伏特的状态,即使没有活动的设备连接在其所在的引脚上。
 
 
 
逻辑代数、组合逻辑电路
理解下面这些门电路的关键在于理解最关键的三个逻辑门(与、或、非)下面给出另外与非门、或非门、异或门的符号图和真值表,其是由三种基本门串接形成如果你对此毫无基础,请务必了解更多专业详尽的信息和说明,可查阅数字电子技术基础课本中的相关章节(逻辑代数、逻辑门电路、组合逻辑电路)
 
按键的抖动与消除
我们上面所表示的上下拉电阻方式用于将开关状态转换为电位高低电平状态的设计是理想化的,在实际电路中仍需要改善,为什么呢?让我们来观察一下实际的输出电压波形
 
 
根据电路原理基础知识,我们知道电容是电荷的蓄水池,这就像个缓存,令其两端的电压无法突变
 
 
 
 
施密特触发器、迟滞特性、消抖
在电子学中,施密特触发器(英语:Schmitt trigger)是包含正反馈的比较器电路。反相施密特触发器对于标准施密特触发器,当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变,也就是说输出由高电准位翻转为低电准位,或是由低电准位翻转为高电准位对应的阈值电压是不同的。只有当输入电压发生足够的变化时,输出才会变化,因此将这种元件命名为触发器。这种双阈值动作被称为迟滞现象,表明施密特触发器有记忆性。从本质上来说,施密特触发器是一种双稳态多谐振荡器。反相施密特触发器的滞回曲线理解施密特触发器消除抖动在于两点:1.什么是脉冲抖动——通俗得讲,在脉冲的上升沿或者下降沿由于人手的细微抖动(人手在触碰物体时常会“哆嗦”,仅仅是幅度极小人没有觉察而已,除非受到惊吓很紧张的时候抖动的幅度大到会被人觉察)或者其它电路硬件产生的幅度较小的震荡,这些原因造成了脉冲边沿的一些或大或小的毛刺引起了脉冲波形的畸变,这些称之为脉冲抖动;2.施密特触发器的特点:输入的电平高于某一个值(比如说1.8V)触发器输出电平会向下翻转,产生一个下降沿,输入的电平低于某一个值(比如说0.8V)触发器输出电平会向上翻转,产生一个上升沿,那么当输入电平介于两个翻转阈值之间时施密特触发器的输出不会动作;从上面的描述你就应该能明白:之所以能消除抖动是因为抖动所产生的电平上下波动的范围尚在施密特触发器翻转的两个阈值之间,施密特触发器不会动作,因而能消除脉冲抖动造成的不良反应。
 
最终该模块电路图
由于重置(RESET)信号是低电平有效,所以默认情况下应该维持高电平,采用串联两个非门施密特触发器我们得到了最终的复位电路的设计
 
 
 
 
 
时钟脉冲产生电路
时钟脉冲是整套数字系统的指挥棒,就像交响乐队指挥手中的指挥棒,其时间基准完全由它决定
 
 
振荡电路(时钟发生器)
-如果你想手动操作,也可以快速按下它,模拟连续的脉冲束,原理几乎和复位电路一样,只是电平相反。
 
时钟脉冲Clock由于是高电平有效,手动产生时钟脉冲(Manual Clock)的按钮应该采用下拉电阻接法。但是由于采用了一个反向施密特触发器,所以改位选用上拉电阻接法。
 
-当然,它也可以由正反馈电路发生振荡来产生
 
 
门电路组成的多谐振荡器
振荡原理:  假设Q为低电平,则非门2的输入端为高电平,经过R对C充电,C的电压上升,直到非门1输入端的电压达到反转电压,此时非门1的输出变为低电平,Q变为高电平。  此时,Q点、C、R、非门2的输入端,极性反转,相对于之前变为放电回路,然后转为反向充电,C的电压下降,直到非门1输入的电压达到反转电压,此时非门1的输出变为高电平,Q变为低电平。  如此循环,形成振荡,在Q端输出方波。  如果非门的反转电压为电源电压的1/2。  则振荡周期:T≈2.2·R·C  Rs用于稳定振荡频率,驱使为6~10倍的R。
时钟电路电阻值在TD4教科书的电路图中,规定了1Hz为33KΩ,10Hz为3.3KΩ的电阻值,但这个值实际上慢了一点。因此,下表显示了以电阻值计算的 60 秒内的实际时钟数。
 
电阻值(Ω)
33K
30K
27K
28.7K (33K // 220K)
28.3K (33K // 200K)
时钟数
51
57
63
59
60
 
从此表中可以看出,在 33KΩ 时,它变为 51/60 = 0.85 Hz。因此,为了获得1Hz,从表中可以看出,当使用28.3KΩ(从E24系列的电阻值中选择,33K和200K并联)时,正好60/60=1赫兹。它变成了。
 
对于 10Hz,使用 2.83KΩ(3.3K 和 20K 并联),1Hz 的电阻值为 1/10。
 
RC充放电回路
 
上面的图来自维基百科(后面也有图片取自维基百科)。观察上面的图,当电源通过电阻 R 向电容 C 充电的时候,电容 C 两端的电压会如何变化呢(也就是会呈现出何种规律)?这可以应用基尔霍夫电路定律来建立一个微分方程,然后解出这个微分方程就会得到电容 C 在充电时的电压变化情况(也可以用拉普拉斯变换求解,关于如何求解这个方程则需要另一篇文章 :),它是时间 t 的函数:  有了公式我们就可以画出它的曲线,如图所示。如果你对此过程有疑惑或者需要了解更多,请参考电路或电路原理相关课本中的相关内容。
 
上面的图来自维基百科(后面也有图片取自维基百科)。观察上面的图,当电源通过电阻 R 向电容 C 充电的时候,电容 C 两端的电压会如何变化呢(也就是会呈现出何种规律)?这可以应用基尔霍夫电路定律来建立一个微分方程,然后解出这个微分方程就会得到电容 C 在充电时的电压变化情况(也可以用拉普拉斯变换求解,关于如何求解这个方程则需要另一篇文章 :),它是时间 t 的函数:  有了公式我们就可以画出它的曲线,如图所示。如果你对此过程有疑惑或者需要了解更多,请参考电路或电路原理相关课本中的相关内容。
 
上面的图来自维基百科(后面也有图片取自维基百科)。观察上面的图,当电源通过电阻 R 向电容 C 充电的时候,电容 C 两端的电压会如何变化呢(也就是会呈现出何种规律)?这可以应用基尔霍夫电路定律来建立一个微分方程,然后解出这个微分方程就会得到电容 C 在充电时的电压变化情况(也可以用拉普拉斯变换求解,关于如何求解这个方程则需要另一篇文章 :),它是时间 t 的函数:  有了公式我们就可以画出它的曲线,如图所示。如果你对此过程有疑惑或者需要了解更多,请参考电路或电路原理相关课本中的相关内容。
最终该模块电路图
 
 
 
 
 
用来存储程序的-只读存储器(ROM)
ROM(Read Only Memory)只读存储器,这种存储器(Memory)的内容任何情况下都不会改变,电脑与用户只能读取保存在这里的指令,和使用存储在ROM的资料,但不能变更或存入资料。ROM被存储在一个非易失性芯片上,也就是说,即使在关机之后记忆的内容仍可以被保存,所以这种存储器多用来存储特定功能的程序。
在本项目案例中,ROM的意思并非指程序不可改变,而是CPU在运行过程中,只能从该存储器阵列中读取,而不能写入或者改变其中的内容,但我们可以通过人手波动开关来写入或修改其中的内容。
 
 
-Memory (ROM) 电路使用 16 个 8 位 DIP 开关来指定 ROM 电路中的 0/1 位(DIP 开关的使用也可2 x 8 16接头和跳线针被用作替代品)。拨码开关相对昂贵,取决于类型,最大的问题是印刷电路板上比较占用空间。TD4教科书由拨码开关和二极管阵列组成,但二极管阵列比较特殊,很难获得。因此,不可避免地要使用普通的轴向引线型二极管。
 
组成开关阵列,并串联二极管
–CPU无法一次从ROM中读取所有程序,按照冯诺依曼原理,逐条指令读取逐条指令执行
这个例子是4位宽度的地址总线,就是2的四次方,共可以读取存储16个8位
–只需要一点一点的将其读取。就像看书,要一页一页翻着看
我想读取一位的电位,该怎么办?
 
 
1位ROM
 
 
3*4位ROM
 
 
通过选择哪一竖电位到地为低电位,横向导线分别读取每行电位的高低
 
 
组成交错的开关阵列很不错,但是读取第二竖列开关每行电位的时候,会受到第一竖列的开关选择影响,从而导致错误。
 
为什么要加二极管?二极管有什么作用?
 
 
每个开关连接到地参考电位前,都用二极管串联,即可避免干扰
 
指令地址译码
 
 
我们需要把每一竖列开关连接到地的选择过程自动化,和输入的地址编码对应唯一的选择,这个过程叫做译码。比方说,从CPU发出的地址总线请求(A3-A0)为0110,则读取第六个ROM中的8位数据作为程序的二进制指令通过(D7-D0)传给CPU加载该指令。
 
TD4教科书的电路图中,74HC154一次可以解码4位,用于指定ROM电路地址,但这里使用了两个74HC138 3-8解码器。得到等效电路。这仅仅是因为74HC138在更容易购买也更便宜,经销商的库存中也比较多。
 
译码器 3-8译码器 4-16译码器
译码器是电子技术中的一种多输入多输出的组合逻辑电路,负责将二进制代码翻译为特定的对象(如逻辑电平等),功能与编码器相反。 译码器一般分为通用译码器和数字显示译码器两大类。 … 输入使能信号必须接在译码器上使其正常工作,否则输出将会是一个无效的码字。大多数随机存取存储器使用n线-2n线译码器来将地址总线上已选择的地址转换为行地址选择线中的一个。引脚布局图和功能逻辑图块两个3-8译码器组成一个4-16译码器,这是数字电子技术课程中组合逻辑电路译码编码部分的经典问题,绝大部分数字电子技术书籍和课本都会举例讲述,详情请参阅相关书籍。
 
75HC540的作用较为简单,作为练习,请大家自行阅读Datasheet。
最终该模块电路图
 
 
 
 
 
 
寄存器电路
4个四位寄存器,由4个74HC161芯片构成,接下来我们一步一步看看它们是怎么工作的
补充知识双稳态电路 D触发器原理
触发器(英语:Flip-flop, FF),中国大陆译作“触发器”、台湾及香港译作“正反器”,是一种具有两种稳态的用于储存的组件,可记录二进制数字信号“1”和“0”。触发器是一种双稳态多谐振荡器(bistable multivibrator)。该电路可以通过一个或多个施加在控制输入端的信号来改变自身的状态,并会有1个或2个输出。触发器是构成时序逻辑电路以及各种复杂数字系统的基本逻辑单元。D触发器有一个输入、一个输出和一个时脉输入,当时脉由0转为1时,输出的值会和输入的值相等。此类触发器可用于防止因为噪声所带来的错误,以及通过管线增加处理资料的数量。带直接复位置位端的上升沿边沿触发的D触发器真值表D触发器符号。> 是时脉输入,D是资料输入,Q是暂存资料输出,Q’则是Q的反相值,S为1时强迫Q值为1,R为1时强迫Q值为0关于从基本RS触发器出发,是怎样由两个带有反馈的与非门或者或非门逐步建立起来的,并通过各种组合添加进一步形成其它更高级的触发器和计数器等内容,内容较为冗长,但对于打好相关基础非常重要,这里只大概解释各模块的外部主要特性。如果完全不了解,请读者读者查阅《数字电子技术基础》课本进行学习。
 
 
当上升沿建立起来的时候,D的状态被记录在Q中,在下一个上升沿到来之前,Q值不受D值改变的影响。
 
 
四位同步二进制计数器
在有了D触发器基础后,我们可以通过串联D触发器,得到一种叫做计数器的器件
 
 
逻辑功能图
 
 
时序逻辑图
 
 
 
状态转换图
 
通过参考查看74HC161的Datasheet后,我们知道此芯片正是这一类器件
 
 
 
 
74HC161引脚布局图
 
 
74HC161的Datasheet中的时序逻辑图可以看出,~CLR是异步清零端,所谓异步,就是指其作用并不随时钟脉冲才发生作用,即与时钟脉冲不同步,随时有效随时即刻清零所存储的数据。~LOAD低电平时,A、B、C、D的值被装载到QA,QB,QC,QD中,当ENP和ENT置高电平时,QA,QB,QC,QD组成的4位二进制数开始对外部脉冲CLK上升沿进行计数(0-15),当计数达到15后再来一个脉冲上升沿,计数溢出归零,并在RCO端给出一个正脉冲。
 
用带脉冲计数功能的74HC161的D触发器阵列构成寄存器
 
 
输入/输出
 
A寄存器和B寄存器受同一个时钟脉冲控制
 
 
将D触发器的输出端Q的信号,重新引回给触发器的输入端D,通过选择开关,即可实现数据在不同的D触发器之间传送。
 
如何在A寄存器和B寄存器之间交换数据,切换选择输入给哪个寄存器并保持。
 
 
譬如指令MOV A, D,就是将D寄存器内存储的数据,转存到A
数据选择器
我们需要将上述选择数据移动过程,转换成用特定芯片处理,这个过程所需要类型的芯片叫数据选择器。同时我们需要使用4个4位D触发器存储数据,即数据总线宽度位4bit,下图用划线加4bit标识。
 
 
通过75HC153的Datasheet查真值表,我们可以确认其功能是通过B,A的值,从DATA中选择C0,C1,C2,C3其中一个传输给输出Y。
 
 
通过Datasheet里的引脚分布和介绍,我们知道74HC153是两路数据选择器。由于我们传输的数据带宽是4bit,故需要两个74HC153芯片,分别作为数据选择器1和数据选择器2,共用选择信号A、B。
 
 
局部功能完成的电路图
 
 
 
算术逻辑单元(ALU)
补充知识加法器、全加器
74HC283用于TD4教科书的电路图中作为ALU使用的全加器。
 
加法器、半加器、全加器
相信认真阅读过数字电子技术基础课本中有关加法器章节的读者对此类器件的实现过程应该都印象深刻。加法操作是几乎所有算数运算中最基本的操作,原则上其它类型的算数逻辑操作都可以由此推延产生,因此仔细研究清楚其内部原理非常有必要,请读者认真参阅课本中相关章节的讲述内容,这里不再赘叙,仅给出真值表和逻辑图仅供参考。 半加器逻辑图和真值表 一位全加器的逻辑图和真值表一位全加器示意图4个一位全加器串联组成的4位全加器
 
此类推如果增加更多的数字,位数增加即可
 
 
4位全加器
 
 
 
 
 
 
Datasheet中74HC283的引脚图
 
组合一个最小系统
现在是时候组合寄存器、数据选择器、加法器,组成一个完整的指令执行系统。
无论如何,尝试做一个完整的ALU
我们来动手吧譬如执行指令ADD, Im
 
 
 
 
 
这样以来,就多了很多额外的东西 所以,如何执行MOV A, B呢?
 
 
立即数Im作为ALU的第二个输入数B,加法器得以实现,当指令用于传送时,令立即数Im=0即刻。这样,通过数据选择器A、B的信号组合与立即数的配合,上图的指令执行框图基本上可以实现如下类似指令
MOV A, Im 将立即数数据传送到 A 寄存器。
MOV B, Im 将立即数传送到 B 寄存器。
MOV A, B 将 B 寄存器传送到 A 寄存器。
MOV B, A 将 A 寄存器转移到 B 寄存器。
ADD A, Im 将立即数添加到 A 寄存器。
ADD B, Im 将立即数添加到 B 寄存器。
 
程序计数器
– 这是一个计数器,每次遇到一个脉冲则加一
– 如果它复位,则返回到零
 
 
这样很棒,在没有遇到跳转指令的时候,每次脉冲来都自动加一,很适合做储存地址的程序存储器PC(Program Counter),我们放弃了D寄存器用于通用数据的存储,将其作为程序存储器,而在遇到指令直接跳转的指令时,其相当于将立即数或者加法结果存储到寄存器D中。
 
整合输入输出
此外,我们还需要一个寄存器,作为输出驱动4个LED显示的输出寄存器,那么我们也放弃第三个寄存器,即C寄存器,作为OUT寄存器,其结果直接作为输出寄存器。
 
 
此外,你很可能觉得,还需要输入寄存器IN,那么又将牺牲多一个通用寄存器或者需要再添加一个74HC161。其实细想之下,这是不必要的,因为当使用的C、D寄存器作为特殊功能的寄存器后,4路的4位数据选择器只剩下2路被通用寄存器占用,我们把其中一路作为输入,另一路全部默认置零即可。这样,立即数就通过与全为零的数相加而可以被直接传送至通用寄存器A和B了。
 
保持进位的触发器Flip-Flop
带有条件的跳转指令往往需要通过比较计算和查看加法器的计算结果是否溢出。但加法器的溢出位C4记录的是当下的加法结果是否溢出,而非上一条指令执行时的加法结果是否溢出,这就需要一个触发器记录上一次的溢出结果,等到下一个脉冲来到时决定条件跳转指令是否执行。
 
 
我们选用了74HC74作为正边沿触发的flip-flop触发器,可以满足这个要求,详情请自行阅读Datasheet。
 
 
最后的整合与调整
 
 
 
指令译码电路(取指令)
– 每个时钟周期的脉冲来临时,就进行一次取指令、译码、并执行
– 被译码后,相应的电路执行指令预设的动作进行操作
 
 
这个译码电路相当有5个输入,共2的5次方种输入可能性,6个输出,2的6次方种输出可能性。然而仔细分析之下,我们需要的有意义的操作指令和相应的输出无外乎以下这些情况。
 
 
 
所以,总的运行架构框图如图所示
 
 
 
这个指令译码电路,设计过程看起来稍显复杂,然而它只是一个与时序无关的纯组合逻辑电路。参照表格所列,将所有输出结果用采用数字电子技术课本中的最小与或表达式写下,并运用包括德摩根定律在内的逻辑代数定律适度整理后,权衡器件选择和数量,就可得出如下最终的译码电路。
 
 
 
 
 
 
 
 
 
 
 
最后是编程指令集
 
TD4 中定义了十二种类型的指令,这里汇总了所有指令的列表。下图右端所示的 SelB、SelA、Ld0、Ld1、Ld2、Ld3 表示根据操作码和标志创建的解码信号。
 
 
 
 
     将立即数数据传送到 A 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将立即数传送到 B 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将 B 寄存器传送到 A 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将 A 寄存器转移到 B 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将立即数添加到 A 寄存器。
     在运行时,它不受 C 标志的影响。执行后,当发生进位时,C 标志设置为 1。
 
 
     将立即数添加到 B 寄存器。
     在运行时,它不受 C 标志的影响。执行后,当发生进位时,C 标志设置为 1。
 
 
     将数据从输入端口传输到 A 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将数据从输入端口传输到 B 寄存器。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     将立即数据传输到输出端口。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     B 将寄存器转发到输出端口。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     跳转到立即数指示的地址。
     在运行时,它不受 C 标志的影响。执行后,C 标志变为 0。
 
 
     当 C 标志为 0 时,它跳转到立即数所指示的地址。当 C 标志为 1 时,什么都不做。
     在运行时,C 标志会更改行为。执行后,C 标志变为 0。
 
 
没错,设计方案源头的书是日文的,但是不要怕,我们会有很好的中文解析,大家完全能理解它!
哈哈哈,这句话我本来打算写在开头,但是怕读者畏惧,现在你已经读完了所有的解析,简单吧? 这句话成为了废话。
 
参考书籍:
 
 
图书出版社收到的读者书评
用10个IC轻松介绍CPU设计!
计算机的核心是一个名为CPU的黑盒子。 被称为CPU的黑匣子是计算机的核心,以4位CPU为例,从其运作的 “超级 “基本原理到设计进行解释。 你实际上可以只用秋叶原上的零件来制作你自己的CPU! 即使你没有真正做到这一点,它肯定是有趣的阅读。
-来自 “BOOK “数据库
一个叫做CPU的黑盒子是计算机的核心。 它解释了从其运作的 “超级 “基本原则到具体的设计实例的一切。 也可以只用秋叶原上的零件来实际建造。
-来自 “MARC “数据库
计算机的核心是一个名为CPU的黑盒子。 本书解释了从其运作的 “超级 “基本原则到具体的设计实例的一切。 你甚至可以只用秋叶原上的零件来建造你自己的东西!
 
作者简介
Iku Watanami
电路工程师。 从一家计算机制造商退休后,他成为一名独立的工程师。
 
基本信息
  • 出版社 ‏ : ‎ 毎日コミュニケーションズ (2003年10月1日)
  • 出版日期 ‏ : ‎ 2003年10月1日
  • 语言 ‏ : ‎ 日语
  • 单行本-平装 ‏ : ‎ 328页
  • ISBN-10 ‏ : ‎ 4839909865
  • ISBN-13 ‏ : ‎ 978-4839909864
 
立即购买套件:

Digital Fundamentals 数字电子技术基础:系统方法 Thomas L. Floyd

Digital Fundamentals

数字电子技术基础:系统方法

预览版

如果您在移动设备上无法预览上面的PDF文档,您还可以点击这里访问

内容简介

《数字电子技术基础:系统方法》是世界著名优秀教材,结合数字电子技术的发展趋势,从数字电子技术系统性的角度,对数字技术和数字系统内容进行重新组织,系统阐述数字电子技术的基础知识。主要内容包括:数字电子技术的基本概念、组合逻辑、锁存器、触发器、定时器、计数器、移位寄存器、数据传输、信号处理等。

作者简介

Thomas L. Floyd,1964年获得佛罗里达大学电气工程学士学位,同年在德州仪器公司开始职业生涯,1968年获得南卫理公会大学电气工程硕士学位,之后在马丁·玛丽埃塔公司任职高级工程师,从事导弹制导系统和数字通信系统的研发。1973年,他成为瓦伦西亚社区学院的全职教师,担任新电子科技计划项目主管,负责开发课程、设计实验室及授课。1977年,他撰写了第一本世界级畅销书《Digital Fundamentals》(现已更新至第11版),同年加入北卡罗来纳州美林社区学院,并出版了第二本畅销书《Electronics Fundamentals: Circuits, Devices & Applications》(现已更新至第8版)。1983年,他开始专职写作,先后出版了《Operational Amplifiers and Linear Integrated Circuits》(现已更新至第6版)和《Electric Circuits Fundamentals》(现已更新至第8版)。近年来,除了本书之外,Floyd还出版了另外两本世界著名教材:《DC/AC Fundamentals: A Systems Approach》和《Digital Fundamentals: A Systems Approach》。

网友书评

数电讲得很详细,比国内教科书全面点。

书很好,适合初学者学习使用,内容详实,讲解到位透彻,值得拥有,推荐购买!

获取高清完整版PDF

分享地址

https://pan.baidu.com/s/1GDaSizKUos9yEtStA83ckg

Analog Fundamentals:A system approach 模拟电子技术基础:系统方法 Thomas L. Floyd

Analog Fundamentals:A system approach

模拟电子技术基础:系统方法

预览版

如果您在移动设备上无法预览上面的PDF文档,您还可以点击这里访问

内容简介

《模拟电子技术基础:系统方法》是以系统为视角的极具特色的模拟电子技术基础教材。全书共15章,内容包括:二极管及其应用、BJT、FET、多级放大器、RF放大器、功率放大器、运算放大器、特殊用途放大器、运算放大器的响应、基本运算放大器电路、有源滤波器、振荡器、定时器、稳压器、通信电路、数据转换等。全书配有习题和习题答案,便于学生牢固掌握所学知识点。本书可供工科电子类各专业的本科生、专科生使用,也可作为相关技术领域工程技术人员的参考书。

作者简介

Thomas L. Floyd,1964年获得佛罗里达大学电气工程学士学位,同年在德州仪器公司开始职业生涯,1968年获得南卫理公会大学电气工程硕士学位,之后在马丁·玛丽埃塔公司任职高级工程师,从事导弹制导系统和数字通信系统的研发。1973年,他成为瓦伦西亚社区学院的全职教师,担任新电子科技计划项目主管,负责开发课程、设计实验室及授课。1977年,他撰写了第一本世界级畅销书《Digital Fundamentals》(现已更新至第11版),同年加入北卡罗来纳州美林社区学院,并出版了第二本畅销书《Electronics Fundamentals: Circuits, Devices & Applications》(现已更新至第8版)。1983年,他开始专职写作,先后出版了《Operational Amplifiers and Linear Integrated Circuits》(现已更新至第6版)和《Electric Circuits Fundamentals》(现已更新至第8版)。近年来,除了本书之外,Floyd还出版了另外两本世界著名教材:《DC/AC Fundamentals: A Systems Approach》和《Digital Fundamentals: A Systems Approach》。

网友书评

国外经典教材,佛罗伊德的经典书籍,初学者很好入门,内容通俗易懂,全面细致,推荐购买。

获取高清完整版PDF

分享地址

https://pan.baidu.com/s/1TOD8NCKQMmFcEIgLECW0nQ