分类目录归档:兴趣小组

超声波雷达-创客开源 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

《CPU自制入门》实践经验分享

《CPU自制入门》是人民有点出版社图灵系列自引进日本的一本非常好的书,该书在2014年出版,当年已经引起了不少IT业内认识和计算机技术爱好者们的关注(当然也包括我),后续数年不知因为什么原因,市场上缺货,二手渠道曾经一路被炒高至售价四五百元。后来出版社和责任编辑乐鑫发现,于去年重印,虽已过多年,至今仍可从各平台渠道上看到该书销售火热受追捧的程度。

正如译者赵谦博士在此书译者序中所言,对非常多接触IT行业从业人员、爱好者、有志从事或了解相关领域的学生而言,《CPU自制入门》正是他们一直寻找的那本书。相关领域的经典教材和各类好书不少,如MIT出版社出版的哈佛大学教材《计算机系统要素》,斯坦福大学前校长John LeRoy Hennessy等编写的《计算机组成与设计: 硬件/软件接口》,国内广泛采用的白中英教授编写的《计算机组成原理》。然而,迄今为止尚未发现一本像《CPU自制入门》这本书一样提供较低阅读门槛,并且从实践出发,介绍了计算机系统最物理、最底层开始的搭建过程,从电路板设计制造到CPU结构的设计和描述直到汇编语言编程,将如此广泛的技术内容以实践方式来学习,本书确实是首屈一指,也尚未见来着。本书在各层次原理理论讲解不尽详细,因为每一章摊开原理讲清楚明白可能都是大学里相关的一本专业课,不可能也没必要,但作为相关专业的实验手册或者课程设计指导书则再合适不过了(笔者有诸如配套白中英编写教材的清华大学科教仪器厂的TEC-4计算机组成原理实验箱,但其对普通师生的可获取性差且技术方案过于陈旧跟不上产业界的步伐),个人强烈建议国内有志的高校教师在教学改革过程中考虑将本书内容或者体系纳入专门的实验课或者相关课程设计中。

相信愿意购买和阅读此书的读者一定和我一样相信在可预见的未来,具备软硬结合设计和实践能力的工程师将会更加具有竞争力。书中几乎简要介绍了所有必要的基础知识(如数字电路设计、Verilog语言、电路板CAD软件使用、元器件购买等)。书中以最为精简的文字,将最核心的知识汇集到一本书中,使各种知识背景的读者都可以方便阅读以至于实践。

当然,这样的安排也容易导致了实践者在独自实践的过程之因为涉及领域之广泛而不熟悉,往往容易遇到意外的问题被卡住而最终被迫放弃。在本书编辑建议下,我把陆陆续续大概花了一个月实践过程中所遇到的各种问题和大家分享,写成这篇指南,希望能让大家实践顺利,少走弯路。

 

全书分为3章,

第1章 CPU得设计与实现

第2章 电路板的设计与制作

第3章 编程

 

实验实操过程中,不建议采用书中顺序,而应该从第2章开始,然后适度阅读第1章,并按照第3章逐步操作,原因如下:

1.书中采用的硬件架构并非现成产品,需要电路板和元器件实操,在硬件体系没有搭建起来以前,所有代码都无法在真实的平台上验证。

2.第一章是原理性讲解,设计本书最重要的精神和概念,多数阅读者未必有很好的Verilog语言基础和各种FPGA基础知识(如果有,本书可能对FPGA工程师略显粗浅),所以第一章如果遇到卡顿或者看不懂的地方,不应放弃,而应该先按照2、3章流程完成实操后,再慢慢理解第一章,第一章应该当成原理讲述大纲和字典,可以粗略看一遍不需要完全理解,开始实践代码的过程中,遇到不懂的地方再不时回来翻看。

3.本书重点在于有全套简单易用,资料详尽,案例完整的从芯片选型到电路设计,从硬件描述语言到汇编语言的全套解说,应该注重整个完整实践过程的主线,重在体验和为理论原理学习提供动力和感性认知,而不应过分纠结在一两个细节上。

 

跳坑:

初学者应完全遵照书中软件和版本进行环境搭建和操作,遇到有实际困难切换环境或者元件的情景,需要在有相关领域专业人员的指导下进行操作和元器件购买。

本书中文版在作者序前一页有一个声明

笔者写这段话的时候,已经是2021年了。这说明本书编写的环境并不能使用相关软件的任意版本,而应该尽量与书籍上内容呈现的版本一致或者寻找上述声明日期来进行操作。也就是说书中的代码和操作与软件之间的版本是有一定依存程度的。

 

这是为什么呢?以FPGA开发环境ISE为例,其不同版本开发环境所创建的工程往往与所依赖的代码文件和IP核相关。不同的版本,IP是可以升级的,但是如果IP在新版本新增一些接口,或者删掉一些接口,那么ISE是不能自动帮助连线或者修改代码的,这部分还是需要用户手动完成,这是特别需要注意的,也是导致很多莫名奇妙的错误让初学者无所适从而放弃。

 

以PCB设计软件Eagle CAD为例,其书中所展示的版本5.11.0为例,在2012年这是一款流行的PCB设计软件,属于一家叫做CADSOFT的德国公司,由于其有一个简单好用的免费版本,所以其在电子爱好者和创客社区非常活跃用于设计交流。但如今这款软件已经被AutoDesk公司所收购,其操作界面有较大的改变,也需要AutoDesk账号登录才能使用,笔者也遇到过官方自带元件库的命名对应和电路原理图文件打开后报错的情况。

 

跳坑:

书籍中相关资料下载问题

该书籍在图灵社区上相应的页面并没有提供直接的资料下载,https://www.ituring.com.cn/book/1142

而是给出了日本原版技术评论社的相关资料下载链接,刚开始没仔细查看该页面的图灵社区读者通常会在随书下载中查找资料,只有试读PDF

请至http://gihyo.jp/book/2012/978-4-7741-5338-4/support下载本书源代码。

请注意,该页面虽然全都是日文,但仍然不影响使用。如果强迫症,可以将整个页面直接扔进Google Translate或者DeepL翻译,查看译文。附网址(需要先获得访问完整互联网的能力)https://translate.google.com/translate?sl=ja&tl=zh-CN&u=https://gihyo.jp/book/2012/978-4-7741-5338-4/support

但是请注意,除了实践所需代码,原作者和出版社还提供了一些书籍的勘误和补充,其中P.403关于综合选项 的一项尤其重要,因为原书中没有提及,在代码综合的过程中,默认以speed而不是area进行综合,这回导致代码综合结果超过了该款芯片xc3s250e的容量而失败,一开始我在这里就卡住了两天。

 

跳坑:

电路板设计与绘图问题

可能是因为教育环境的缘故,国内大部分学生比较熟悉的EDA往往仅限于Altium Designer,笔者看到CSDN网站上有博客主https://blog.csdn.net/zysns/article/details/96426607(应该是比较主动好学的在校大学生)讲述自己完全按照书本内容附图在Altium Designer把整个原理图从零开始都画了一遍。我觉得精神可嘉但似乎不是很有必要,可能是这位同学并没有留意到上一条提到的随书相关资料,里面已经有直接可用的PCB文件,直接导出来使用生产即可。

 

作者在书中内容所展示的PCB板和封面的拥有基板少女的电路板并不是一个版本的电路板

 

根据作者在PCB丝印层提供的网址信息http://respon.org/(该网址已失效,但可以根据互联网档案馆https://archive.org/回溯查看该网页过去的历史)作者曾在该网站贩售书籍配套的裸PCB板,共有3个版本 1.0 、1.1、2.0。作者网站提供的文件和书中讲述内容,均为1.0版本,此版本背面并没有做出少女图案的覆铜层排版,并且电源模块另有一块电路板,用于将9V电源分别转换为3.3V、2.5V和1.2V三种电压。1.0版本电路板采用的电源转换方案较为传统,但非常适合没有焊接操作经验的初学者作为练习(全部都是插件元器件),后续版本可能采用了体积更为小巧的元器件方案来提供电压变换,具体方案(Eagle软件免费版限制了PCB尺寸在100*100以内)。此外,肯定有不少读者倾慕书籍封面的基板少女情有独钟,实际上此图案对于本书整理的技术和功能实现并没有丝毫影响,只是通过调整布线格局构造出这个有趣的图案。由于作者没有提供含有图案的源文件(即1.1和2.0版)但初学者操作修改电路板文件不出错具有一定风险,硬件排错比软件更为复杂,修改验证周期也更长。因此我建议初学者完全按照书中内容,即可1.0版实践,当一切顺利完成并且愿意在花费一定时间熟练操作Eagle PCB软件后,完全可以根据作者提供的如下视频进行图案的构造。

https://v.qq.com/x/page/a3220vz6bo3.html

 

<iframe frameborder="0" src="https://v.qq.com/txp/iframe/player.html?vid=a3220vz6bo3" allowFullScreen="true"></iframe>

 

在后续实践过程中,笔者发现电路板上的IC3(74AC25)和IC5(74HC14)封装并不正确,不是标准的SOP引脚间距,其将导致最后PCB上的IC无法正常焊接(需要较为麻烦的跳线操作技巧)。笔者已将Eagle PCB文件略作修改,新版本将在本文后附的GitHub资料中上传。

 

关于Eagle软件的操作,笔者作为国内最早一批接触该软件的创客群体,不自谦推荐笔者早年从Sparkfun社区翻译的相关中文教程两篇,相信易于上手并足以应付包括此项目在内的一般项目需求:

http://www.i-element.org/eagleschematic/

http://www.i-element.org/eaglepcblayout/

 

跳坑:

电路板制作问题

作者所处的时间(2012年)和地点(日本),作为个人,进行PCB样品的制作可能要选用感光的制作方式(PCB文件里过孔偏大也是为了将就这种制造工艺,以减少后续失败风险)或者昂贵的海外委托加工方式。

然而我们今天所处的中国电子产品供应链廉价且灵活,完全没有必要购买一大堆零件手工捣鼓制作。所以书中相关内容建议仅供参考了解,实际上只要把相应的Eagle PCB文件按照相应步骤导出成标准的生产文件Gerber(参考书中操作步骤或者上一条Eagle软件操作参考文章),然后将相关文件发给国内一些制造商(如捷多邦、嘉立创等制造商即可完成PCB制作)。

 

由于PCB上阻容元件较多且标识并不是特别明晰(日本人这风格真的搞不懂),自己焊接相当费时,手动操作也比较容易出错,想象一下你在考试时进行大量手动的复杂计算,能保证不出一点差错的几率有多大?可以考虑委托上述PCB或者专业的SMT供应商预先贴片,至少可以把非IC的阻容贴好,而且由于后续手动焊接插件和芯片等元器件难保不会出现意外,非常建议多准备几款贴好阻容的PCB(3-5块)。

 

也可以考虑寻求笔者(微信ID:zhangchengwust)直接购买半成品PCB(贴好阻容元件),因为批量会比较便宜。

 

跳坑:

芯片购买问题

不知道是不是因为这一年多收到疫情影响,该项目中电路板上所使用的两个核心的芯片的正规供货渠道在这个时间节点(2020年11月)不是非常顺畅,即便是强大的DigiKey有供货也非常昂贵。有比较熟悉FPGA原料市场的朋友告诉笔者,十年前的产品线产量本来就不大,遇到供货不稳定的比较正常。

通过淘宝和华强北等一些不是非常原厂经销商的渠道,花费了比较高昂的价格(约100元)作者买到了XC3S250E SMD_VQ100和XCF02S SMD_VO20C这两个芯片和其它一些比较特殊的零件。在零件购买这一步,作为有一定经验的电子工程师的笔者也栽了跟头,因为淘宝等一些特殊渠道上淘洗来的芯片不仅可能是拆机的二手货,而且甚至很可能是无法工作的坏芯片或者是假的。

第一次在一家淘宝店买的XCF02S上一号引脚附近的芯片上没有圆点,一开始我还以为是这个芯片规格特殊,省略了这个一般芯片上都有的标记,后来到了烧录程序过程中发现芯片完全无法识别。在查阅了该芯片的Datasheet后,我确认这很可能是个假芯片。

 

可以考虑寻求笔者(微信ID:zhangchengwust)创建的交流群批量购买元器件包。如果在这方面有廉价并且稳定供货渠道的朋友,欢迎告知。

 

跳坑:

数字电路和Verilog HDL语言基本知识问题

 

如果你在学校里认真学过数字电路相关课程并熟练掌握C语言,可能对这本号称入门的书不屑一顾,再粗粗一眼看上去,感觉Verilog的语法和C语言看起来没啥区别,但当认真开始实现功能并且尝试理解代码原理的时候,你会发现你错了。对,对于没有只有上述课程基础,并没有FPGA实践经验的笔者来说,就是这样。不信你可以试试光凭本书第一章的那点介绍,你完全没法读懂项目里的大部分代码。

后来为了调试Verilog代码综合过程中的一些错误过程中,我反反复复查阅了数电书籍和专门的Verilog语言的书籍,对于掌握这门语言,效果并不是非常好。后来通过老石谈芯视频上的推荐,我发现对于真心想掌握这门语言的人,与其它理工科的学习一样必不可少的修炼手段,就是刷题!对,就是刷题网址,刷了一周后,发现提升非常大,实在看不懂的,就看一下答案,再自己写一遍。网址和答案附上:

 

https://hdlbits.01xz.net/

 

https://github.com/johnsonwust/HDL-Bits-Solutions(笔者Github,欢迎加薪点赞)

 

跳坑:

Xilinx ISE 版本选择和兼容性问题

电路都做完了,来到搭建软件环境这一步了。首先要注意,ISE已经是近十年前的产物了,最后的更新也是2013年的事情。本书译者赵谦博士建议我们后续学习FPGA都迁移到替代它的新环境Xilinx vivado中来。即便如此,请注意,如果你是Win10系统,千万不要下载上图所示的Win10版本,因为这是基于虚拟机技术的,虽然小,但是很多芯片用不了。而且笔者的结果就是直接装不上(不知道和电脑上装了VMWare一类的虚拟机程序是否有关)。

一共有4个压缩文件,而且每个都接近2G。如果你在下载过程中出现卡顿或者什么别的幺蛾子,请考虑获取访问完整互联网的能力。

接下来就是申请赛灵思的账号,并且填写自己的地址和公司等信息,要符合U.S. Government Export Approval,什么?是的,你没听错,下载米国公司的软件也算出口,也要符合美国政府的技术出口管制。所以我毫不犹豫的添了个纽约的地址,嘿嘿嘿!

跳坑:

Xilinx ISE 闪退问题

这个问题也卡了笔者很久,我刚开始发现的时候,事情是这样的,每当在Win10下把输入法从英文切换成中文的时候,软件立马闪退,后来发现导入文件和打开项目的时候也会有这个毛病,我当时简直就想咒骂赛灵思的软件工程师。问了我一个有FPGA开发经验的朋友,他直接告诉我,他们只在Linux环境下允许ISE,没在Windos下运行过。我刚有点心动的时候,忽然发现书后面马上要介绍使用JTAGE Shell烧录程序到FPGA的软件似乎只能在Windows下运行,而且作者都是在Windows下运行的,于是我硬着头皮找到了下面这个解决方法。

 

解决Xilinx_ISE 14.7在Win10下选择“open project”崩溃闪退的问题

问题描述
ISE 14.7对win10无法完美支持,在使用64位ISE时点击OPEN之类的东西时程序都会崩溃,虽然使用32位不会有这个问题,但是工程的默认打开方式不能改为32位。

解决方法如下

寻找安装Xilinx_ISE的安装目录下的文件夹:

D:\Xilinx\14.7\ISE_DS\ISE\lib\nt64

对文件夹下的两个文件作如下操作:

将libPortability.dll重名为libPortability.dll.orig

将 libPortabilityNOSH.dll 复制一份,然后将这个复制重命名为libPortability.dll

至此,64位ISE在打开项目等方面已经不会闪退,但是 PlanAhead等高级功能面前还是会闪退,

 

参考来源:https://blog.csdn.net/idevede/article/details/56024153

 

跳坑:

BSDL文件

Scratch编程超霸机器人(高品质乐高兼容)

 

全网最低价 点击或使用微信扫描上述二维码即可购买

Scratch编程超霸机器人 https://weidian.com/item.html?itemID=4270851520

孩子说他最大的梦想,就是收集到所有的汽车模型!每次说完,两只大眼睛闪亮亮的看着我,我就知道又要为孩子的梦想充值了。

当妈的谁会心疼给孩子花钱?我担心的是玩了这么多玩具,思维和大脑一点没有长进。

同龄人的孩子都在报班学编程,提前培养逻辑思维和学前意识,不惜一年投入好几万。

虽然价格看着肉疼,但我也不希望孩子落下太远,苦口婆心的劝他:妈妈给你报个班学学编程吧。

孩子一听就拒绝,说代码没意思,全是看不懂的符号,还不如拼玩具......

我觉得他对编程的误解很大,但是又没有特别好的方式引导他。

寓教于乐其实是最好的思维培养方式,如果真有这样的玩具能让孩子边玩边学,不用犹豫,我肯定给孩子买回家!

  · 拼插能力媲美乐高,可搭建几十种造型

  · 能学习编程,培养孩子的逻辑思维

  · 可玩性高,操控感强,不会太傻瓜

  · 性价比高,价格不贵

淘来淘去,这款好帅 · 超霸机器人,完美符合了我给孩子选玩具的所有期待。孩子看到的第一眼,兴奋的蹦蹦跳跳,说这个玩具太!酷!了!

它能够搭建21种造型,上百种玩法,可以反复拼插,少买了几十套乐高。

手机操控制动,瞬间化身遥控车。

携带传感功能,编程后有避障、光感等海量玩法。

使用Scratch语言,在家就能学编程,省下万元报班费。

我相信,很多爸妈会选择这款超霸机器人,作为孩子人生中学习编程不可或缺的玩具。

21种拼搭造型,上百种编程玩法

超霸机器人炫酷技能秀

431个积木模块,21种拼搭造型,孩子一入手肯定兴奋无比,马上就要拼起来!