# 單晶片lab7結報 ###### tags: `arduino` ###### 實驗日期 : 2021/11/18 ## 上課教材 - [Timer](https://hackmd.io/@Alanzzzz/BkIn1jsDt) - [ATmega328p的datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf) - [Arduino UNO原理圖](https://www.arduino.cc/en/uploads/Main/arduino-uno-schematic.pdf) ## lab1 ### SPEC 不使用`delay()`,每2秒改變LED亮暗。<br> 使用Timer1 Register、Output Compare A Match Interrupt中斷模式、CTC mode。 ### Timer內部暫存器原理 - 範例 : 透過計數器和除頻數,計算1秒 ```cpp DDRB = (1 << 5); // set pin 13 OUTPUT ``` ![](https://i.imgur.com/Uhxxivy.png) DDRB(The Port B Data Direction Register)從LSB數來第5位設為1,也就是OUTPUT。 ![](https://i.imgur.com/v7aTc5N.png) ATmega328P的PB5設為OUTPUT,由上圖電路可知如果`PB5=HIGH`時,接在上面的diode順壓,因此會亮。 ```cpp TCCR1A = 0; // TCCR1A Reset ``` ![](https://i.imgur.com/aq9RzLu.png) register全都設為0,代表Timer Mode為Normal。 ```cpp TCCR1B = 0; // TCCR1B Reset TCCR1B |= (1 << CS12); // 256 prescaler ``` ![](https://i.imgur.com/VTz6McY.png) CS12=1、CS11=0、CS10=0,代表除頻256。 $$\frac{16,000,000}{256} = 62,500$$ ```cpp TCNT1 = 3036; // preload timer (65536-62500) ``` ![](https://i.imgur.com/KI41a22.png) 計數器從3036開始計數,一直數到65536,共62500個,因此每次數62500個時,由於CLK頻率是62,500Hz,因此即為1秒。 ```cpp TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt interrupts(); // enable interrupts ``` ![](https://i.imgur.com/R1yS2Cq.png) 當TOIE這個bit為1,代表啟動overflow interrupt。 ```cpp ISR(TIMER1_OVF_vect) { // interrupt ``` 參考[AVR Interrupt Vectors](https://ece-classes.usc.edu/ee459/library/documents/avr_intr_vectors/),`TIMER1_OVF_vect`定義為Timer/Counter1 Overflow。 ```cpp PORTB ^= (1 << 5); // blink ``` |A|B|O| |:-:|:-:|:-:| |0|0|0| |0|1|1| |1|0|1| |1|1|0| 在ISR裡面,假設一開始PORTB是1,與1做`XOR`會變為0,再與1做`XOR`又會變為0,如此0、1值不斷交替。 ```cpp TCNT1 = 3036; // preload timer } ``` 在ISR裡面,要將計數器歸回3036重新開始數。 ### 實現方法 ```cpp DDRB = (1 << 5); ``` 根據datasheet P73與原理圖,設定PB5,也就是Ardunio Uno外部pin 13為OUTPUT。 ```cpp TCCR1B |= (1 << CS12) | (1 << CS10); ``` 根據datasheet P110,`CS12=1`、`CS11=0`、`CS10=1`,代表除頻1024。 $$\frac{16,000,000}{1,024} = 15,625$$ ```cpp TCCR1B |= (1 << WGM12); ``` 根據datasheet P109,`WGM12=1`、`WGM11=0`、`WGM10=0`代表Timer/Counter Mode是CTC。 ```cpp TIMSK1 |= (1 << OCIE1A); ``` 根據datasheet P112,`OCIE1A=1`激活Timer/Counter1 output compare A match interrupt。 ```cpp TCNT1 = 0; ``` 根據datasheet P113,TCNT1從0開始數。 ```cpp OCR1A = 31250; ``` 根據datasheet P109,在CTC模式OCR1A為TOP,也就是說TCNT1會數到31250並歸0重數。因此在每秒數15,625下,數31,250次,就代表2秒。 $$\frac{31,250\text{ 次}}{15,625\text{ Hz}} = 2 \text{ s}$$ ```cpp ISR(TIMER1_COMPA_vect) { noInterrupts(); PORTB ^= (1 << 5); // blink interrupts(); } ``` 在ISR裡面,使用`XOR`的特性使PB5在0、1之間切換。 ## lab2 ### SPEC 不使用`delay()`,顯示精度到毫秒的計時器在LCD螢幕上。 ### 實現方法 同[lab1](#lab1)原理,為了要使精度能到毫秒 ```cpp TCCR1B |= (1 << CS12) | (1 << CS10); ``` 除頻1024。 $$\frac{16,000,000}{1,024} = 15,625$$ ```cpp OCR1A = 15.625; ``` CTC mode的上限設為15.625。 $$\frac{15.625\text{ 次}}{15,625\text{ Hz}} = 0.001 \text{ s} = 1 \text{ ms}$$ 可得至1毫秒精度。 ## lab3 ### SPEC 不使用`delay()`,使用超聲波模組,每秒測量一次距離,並顯示在多工七段顯示器上。 ### 實現方法 同[lab1](#lab1)原理,每秒的間距即為除頻256、上限62500。 ```cpp TCCR1B |= (1 << CS12); OCR1A = 62500; ``` $$\frac{62500\text{ 次}}{\frac{16,000,000}{256}\text{ Hz}} = 1 \text{ s}$$ ## lab4 ### SPEC 不使用`analogRead()`,讀取可變電阻之類比值,並在Serial port顯示出來。<br> 使用ADMUX、ADCSRA及ADCH/L Register。 ### 實現方法 在`setup()`裡面先設定好以下參數 ```cpp ADMUX |= (1 << REFS0); ``` 根據datasheet P217,`REFS1=0`、`REFS0=1`代表參考電壓設定為AV~CC~ with external capacitor at AREF pin。 ```cpp ADCSRA |= (1 << ADEN); ``` ![](https://i.imgur.com/XPH9ClY.png) 根據datasheet P218,`ADEN=1`代表啟動ADC。 ```cpp ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); ``` 根據datasheet P218,`ADPS2=1`、`ADPS1=1`、`ADPS0`代表除頻128。 --- 在`loop()`內反覆執行以下副函式。 ```cpp uint16_t ReadADC(uint8_t channel) { // modulo to lower 3 bits, so between 0-7 // use a bit mask to keep the original lower 3 bits channel = channel & 0b00000111; ADMUX |= channel; // start conversion ADCSRA |= (1 << ADSC); // wait until conversion is finished while(ADCSRA & (1<<ADSC)) return ADC; //return data } ``` 根據datasheet P209,首先要了解First Conversion (Single Conversion Mode)。 ![](https://i.imgur.com/ywdt5FL.png) 根據datasheet P218,Analog Channel Selection Bits的選擇表為 |MUX3..0|Single Ended Input| |:-:|:-:| |0000|ADC0| |0001|ADC1| |0010|ADC2| |0011|ADC3| |0100|ADC4| |0101|ADC5| |0110|ADC6| |0111|ADC7| 因此只有channel後3位才是有效值,如果要設定ADC1為類比輸入channel就要設為`0b00000001`,也可以使用`ADMUX |= (1 << MUX0);`。 ```cpp ADCSRA |= (1 << ADSC); ``` 根據datasheet P218,`ADSC=1`開啟ADC Conversion。 ```cpp while (bit_is_set(ADCSRA, ADSC)) // or while(ADCSRA & (1<<ADSC)) return ADC; ``` 當ADSC回到1代表結束ADC Conversion,根據datasheet P206的block diagram,最後回傳ADC(ADC Multiplexer Output)。 - ref 1 : [mrsunny0/learning-playground](https://github.com/mrsunny0/learning-playground/wiki/AVR#adc) - ref 2 : [bit_is_set()](https://garretlab.web.fc2.com/en/arduino/inside/hardware/tools/avr/avr/include/avr/sfr_defs.h/bit_is_set.html) ## lab5 ### SPEC 不使用`analogRead()`, `analogWrite()`。<br> 藉由Timer1 Register中PWM模式,使用可變電阻調整LED之亮暗。 ### 實現方法 `analogRead()`功能同[lab4實現方法](https://hackmd.io/@arduino/report-7#實現方法3),而`analogRead() ```cpp TCCR1A |= (1 << WGM11); TCCR1B |= (1 << WGM12); TCCR1B |= (1 << WGM13); ICR1 = TOP; ``` 根據datasheet P109,設定為mode 14(Fast PWM),TOP值為ICR1。 ```cpp TCCR1A |= (1 << COM1B1); ``` 根據datasheet P109,`COM1B0=0`、`COM1B1=1`代表Clear OC1A/OC1B on compare match (set output to low level)。 ```cpp OCR1B = constrain(ReadADC(1), 0, TOP); ``` 在loop裡面限制ReadADC(1)只能在0與TOP值變化,如下圖只能在計數器這段區間變化,當OCR1B變大時,PWM在高電位的訊號變大,因此Duty Cycle變大,反之亦同。如此動態調節OCR1B的值去實現寫入PWM的動作`analogWrite()`。 ![](https://i.imgur.com/E2e2FMm.png) - ref : [Microcontrollers: What is the difference between "normal" PWM and fast PWM?](https://www.quora.com/Microcontrollers-What-is-the-difference-between-normal-PWM-and-fast-PWM) ## lab6 ### SPEC 使用`timer.h`,實作[lab3](#lab3)功能。 ### 實現方法 `Timer.h`函式庫解決使用`delay()`時,程式會停擺在那邊,無法再處裡其他動作,因此可以控制數個LED以不同頻率閃爍。 以下為函式庫內常用的指令 ```cpp #include "Timer.h" //引用Timer程式庫 Timer T; //建立計時器物件(T可以自訂名稱) t.oscillate(腳位, 時間, 狀態); //設定多少毫秒切換一次狀態(HIGH or LOW) t.every(時間, 函式); //設定固定時間,呼叫函示 t.update(); //在loop裡面會檢查更新計時器 ``` 因此我只需將[lab3](#lab3) - setups內加上 ```cpp t.every(1000, supersonic); ``` 每隔1秒呼叫函式`supersonic()`。 - loop內加上 ```cpp t.update(); ``` 不斷更新檢查。 - ref : [Arduino: Timer(計時器) 使用教學,比delay好用的計時方式。](https://crazymaker.com.tw/arduino-how-to-use-timer/) ## 課後習題 ### Question 1 Arduino上的ATmega328p所採用的是AVR架構;之後課程用到的STM32則是使用ARM架構,比較兩種架構的差別。 ### Answer 1 - 微控制器不同的主流架構差別。 | X | 8051 | PIC | AVR | ARM | | ---- | ---- | ---- | ---- | ---- | | 總線寬度|標準內核為8位|8位/16位/32位|32分之8位|32位大多數也可用於64位| |通信協議|UART, USART, SPI, I2C|PIC, UART, USART, LIN, CAN, 乙太網, SPI, 12C|UART, USART, LIN, 12C, 乙太網, SPI, I2C(專用AVR支持CAN,USB, 以太網)|UART, USART, LIN, 12C, SPI, CAN, USB, 乙太網, I2C, DSP, SAI(串列音頻介面), IrDA| |速度|12時鐘/指令周期|4時鐘/指令周期|1個時鐘/指令周期|1個時鐘/指令周期| |記憶|ROM, SRAM, FLASH|SRAM, FLASH|閃存, SRAM, EEPROM|閃存, SDRAM, EEPROM| |ISA|CLSC|RISC的一些功能|RISC|RISC| |內存架構|馮諾依曼建築|哈佛建築|改性|改進的哈佛架構| |能量消耗|平均|低|低|低| |家庭|8051個變種|ARMv4.5.6.7和系列|PIC16, PIC17, PIC18, PIC24, PIC32|Tiny, Atmega, Xmega, 專用AVR| |社區|廣大|很好|很好|廣大| |生產廠家|Philips, Atmel, Slicon Labs ,Intel等|Microchip|Atmel|Apple, Nvidia, Qualcomm, Samsung等| |成本與功能相比|非常低|平均|平均|低| |其他功能|以其標準而聞名|廉價|便宜、高效|高速運轉| |流行的微控制器|AT89C51. Psy51等|PIC18FXX8, PIC16F88X, PIC32MXX|Atmega8,16,32, Arduino社區|Cortex-R系列(實時任務處理,主要應用領域包括汽車、相機、工業、醫學)、Cortex-M系列(最節能的嵌入式設備)、Cortex-A系列(以最佳功耗實現最高性能)| - AVR架構與ARM架構差別 | No. | AVR | ARM | | :--: | :----------------------------------------------------------- | :----------------------------------------------------------- | | 01. | AVR micro controller refers to Advanced Virtual RISC (AVR). | ARM micro controller refers to Advanced RISC Micro-controller (ARM). | | 02. | It has bus width of 8 bit or 32 bit. | It has bus width of 32 bit and also available in 64 bit. | | 03. | It uses ART, USART, SPI, I2C communication protocol. | It uses SPI, CAN, Ethernet, I2S, DSP, SAI, UART, USART communication protocol. | | 04. | Its speed is 1 clock per instruction cycle. | Its speed is also 1 clock per instruction cycle. | | 05. | Its manufacturer is Atmel company. | Its manufacturer is Apple, Nvidia, Qualcomm, Samsung Electronics and TI etc. | | 06. | It uses Flash, SRAM, EEPROM memory. | It uses Flash, SDRAM, EEPROM memory. | | 07. | Its family includes Tiny, Atmega, Xmega, special purpose AVR. | Its family includes ARMv4, 5, 6, 7 and series. | | 08. | It is cheap and effective. | It provides high speed operation. | | 09. | Popular micro-controllers include Atmega8, 16, 32, Arduino Community. | Popular micro-controllers include LPC2148, ARM Cortex-M0 to ARM Cortex-M7, etc. | ARM是IP核,可供各大晶元商集成到各自的設計中,好比是軟體語言中的C++,如果你想換一家廠商或者某家的貨太貴,都會有其它的廠商來競爭,至少從理論上,你不會被一家廠商套住。AVR這方面就差點,ATMEL一家,別無分號。你只能在他的系列中選一個型號,無法選廠家。好比是軟體語言中的Java,雖然現在免費(指Java的SDK,不是AVR)或價格低,但市場前景更多的掌握在廠商手中。 實際產品成本方面,AVR優於ARM,畢竟AVR是8位機,配什麼外設都便宜,由於速度比ARM低,PCB版也好設計,20MHz的數字電路基本上只要通就行了,不用過多考慮信號完整性什麼的。而ARM的速度能輕易上100MIPS,32位的CPU也不是吹的,速度上AVR根本沒法比,不過帶來的問題就多了,要4層PCB,而且外設也貴多了。 功能方面,ARM大大優於AVR,ARM可以做手機,AVR顯然不行,但最大的優勢在ARM上可以跑Linux,Linux可以做很多事,在功能上的優勢意味著ARM比AVR有著更廣的應用範圍。 - ref 1 : [Difference between AVR and ARM](https://www.geeksforgeeks.org/difference-between-avr-and-arm/) - ref 2 : [比较AVR和ARM,谈谈相同与区别](http://news.eeworld.com.cn/mcu/article_2016071827633.html#:~:text=AVR%E5%92%8CARM%E7%9A%84%E5%8C%BA%E5%88%AB,%E4%B8%80%E4%B8%AA%E5%9E%8B%E5%8F%B7%EF%BC%8C%E6%97%A0%E6%B3%95%E9%80%89%E5%8E%82%E5%AE%B6%E3%80%82) - ref 3 : [8051,PIC,AVR和ARM有什么区别?](https://zhuanlan.zhihu.com/p/51222150) - ref 4 : [ARM架构版本及处理器系列详细介绍](https://blog.csdn.net/qq_34160841/article/details/105611131) ### Question 2 分別描述在ATmega328p晶片上的PWM模式:Normal Mode, CTC Mode, Fast PWM Mode,及 Phase Correct PWM Mode。(Hint : 參考[datasheet P79](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf)) ### Answer 2 參照[lab6 Question 2](https://hackmd.io/@arduino/report-6#Answer-2)。 |模式|TCNT計數器| |:-:|:-:| |Normal|從0數到overflow,並進入ISR裡面處理 - 歸0重數。| |CTC|![](https://i.imgur.com/wvufdE6.png)| |Fast PWM|![](https://i.imgur.com/H8EGSUR.png)| |Phase-correct PWM|![](https://i.imgur.com/dEzZlIs.png)| ## 心得 ### 劉永勝 這次實驗並不是直接呼叫arduino的函式或巨集,而是直接更改ATmega328p的register,藉由更改register所存取的位元,執行晶片的功能。相較於現成函式,這樣更改register的方式相當麻煩,須先設定enable、mode等,才能寫入值。但也因為直接改寫暫存器的值,能更了解此晶片的功能。但這不是3hr就能理解的。 ### 李宇洋 此次實驗要直接使用Arduino上的ATmega328p做操作,因為實驗的內容主要是對硬體原本所配置的暫存器在IDE上直接操作,觸發中斷程序以作為Timer使用,所以需要直接查詢晶片在datasheet上製作時的設定。有一部分Code雖然助教都已經幫忙打好了,但是要在4、5個小時內理解各個暫存器還有裡面的各個bits負責的功能真的有點困難。 ### 陳旭祺 前面幾周都是調用別人寫好的函式庫,去實現我們需要的功能,但這次lab開始深入探討並直接操作硬體的register,這部分需要具備看datasheet與分析電路圖的能力。前者偏向Arduino社群的業餘玩家,只需要使用別人寫好的函式庫,不需要搞懂硬體底層,就能快速實作出想要的功能,很有成就感;後者偏向嵌入式開發所探討的硬體底層,我們光是實現一個`delay()`功能就研究不少時間,較沒有成就感。 lab1-lab3為實現`delay()`功能,使用Timer的CTC mode、lab4-lab5實現`analogRead()`,`analogWrite()`,讀入類比值使用Single Conversion Mode,而寫出類比值,藉由動態改變Fast PWM下`OCR1B`的值,去影響PWM的duty cycle。