ATmega328p是Atmel發展的megaAVR產品線其中一個單晶片產品,也是目前Arduino Uno系列等常使用的8 bits AVR架構微控制器核心。(P代表Picopower,意思是製程更好更省電)
Paremeter | Value |
---|---|
MCU | 8-bit AVR |
Performance | 20 MIPS at 20 MHz |
Flash memory | 32 KB |
SRAM | 2KB |
EEPROM | 1KB |
Clock speed | 16MHz |
External Interrupt | 2 |
Timer | 3 |
Digital I/O | 14 (6 for PWM) |
Analog I/O | 6 |
ATmega328 微控制器記憶體類型與容量
名稱 | 類型 | 容量大小 | 用途 |
---|---|---|---|
SRAM | 揮發性(volatile),資料會在斷電後消失 | 2048 byte(2KB) | 資料記憶體、暫存程式運作中所需的資料 |
Flash | 非揮發性,資料會在斷電後繼續保存 | 32468bytes(32KB) | 程式記憶體、存放開機啟動程式及使用者自訂的程式碼(開機程式大約佔2KB) |
EEPROM | 非揮發性 | 1024bytes(1KB) | 存放程式的永久性資料 |
I/O Ports
Arduino UNO板所用的晶片ATmega328p有三個 8-bit 的 PORTs :
B: 對應 Arduino 的 digital pin 8 to 13
C: 對應 Arduino 的 analog input pin 0 to 5
D: 對應 Arduino 的 digital pins 0 to 7
實作LED Blink
void setup() {
DDRB = 0b00100000; //將DDRB中的PB5設定為OUTPUT
}
void loop() {
PORTB = 0b00100000; //將PB5設為HIGH
delay(1000); //delay
PORTB = 0b00000000; //將PB5設為LOW
delay(1000); //delay
}
void setup(){
DDRB |= (1 << 5); //將PB5設定為OUTPUT
}
void loop(){
PORTB |= (1 << 5); //將PB5設為HIGH
delay(1000);
PORTB &= ~(1 << 5);//將PB5設為LOW
delay(1000);
}
PB5 是 PortB 的 bit 5,所以我們用 (1 << 5) 當作位元遮罩 (bit mask)。
什麼是 Interrupts?
當你在工作的時候,突然電話鈴聲響起,於是你把手邊工作停下來、接電話、講電話,然後回來繼續做剛剛的工作 -- 這就是所謂的中斷 (Interrupt),而電話便是中斷源。
當中斷腳位的訊號發生改變時,將會觸發執行中斷服務常式。ISR是一個函式程式,由微控制器自動觸發執行的函式。
Reference:attachInterrupt()
const byte swPin = 2;
const byte ledPin = 13;
volatile boolean state == LOW;
void swISR(){
state = !state;
digitalWrite(ledPin, state);
}
void setup(){
pinMode(ledPin,OUTPUT);
pinMode(swPin, INPUT_PULLUP);
attachInterrupt(0, swISR, CHANGE); // 啟用中斷服務常式
}
void loop(){
}
中斷觸發時機
當擁有外部中斷功能的腳位,其訊號(也就是電壓)改變或處於某訊號時,就會觸發微處理器去執行ISR函式,這個函式我們可以自行定義,再由系統當中斷觸發時自動去執行。
既然觸發的時機是當依據訊號狀態,那麼,就可能會有如下的五種情況:
1.FALLING(當訊號下降時觸發)
撰寫ISR程式應注意:
volatile這個變數宣告方式是用於指揮編譯器的指令。
以下面這個程式碼片段為例:
Timer暫存器 datasheet p.108
暫存器 | 簡寫 | 用途 |
---|---|---|
Timer Counter Control Register A | TCCRnA | 決定Timer的運作模式 |
Timer Counter Control Register B | TCCRnB | 決定Timer的prescale value |
Timer Counter Register | TCNTn | 儲存Timer目前的累積計數(計數器) |
Output Compare Register A | OCRnA | 當TCNTn累積計數到OCRnA的計數時,觸發中斷 |
Output Compare Register B | OCRnB | 當TCNTn累積計數到OCRnB的計數時,觸發中斷 |
Timer Counter Interrupt Mask Register | TIMSKn | 開啟/結束中斷 |
計時模式 | 功能 |
---|---|
TIMER1_COMPA | 設定計數參數來觸發 |
TIMER1_OVF | 只要overflow就會觸發 |
TIMER1_CAPT | 儲存輸入腳位的觸發瞬間時間 |
計算 1 second 的計數器和除頻數
- 不同的單晶片有不同的MCU時脈,實際上的運用不一定需要這麼高的時脈,這時候便需要進行「除頻」的動作,將MCU的時脈除以除率(prescaler),藉此將Timer的時脈降低。
- Prescaler (預除器) 是一個用來提供 clock 給 Timer 的電路。MCU clock 頻率通常是 1 MHz, 8 MHz, 16 MHz,而 Precaler 的用途則是除頻。
- ATmega328中Timer0與Timer1使用的是相同的一組prescaler(1, 8, 64, 256, 1024),而Timer2則是有較多的prescaler選項(1, 8, 32, 64, 128, 256, 1024)。
void setup()
{
Serial.begin(115200);
DDRB = (1 << 5); // set pin 13 OUTPUT
noInterrupts(); // disable interrupts
TCCR1A = 0; // TCCR1A Reset
TCCR1B = 0; // TCCR1B Reset
TCCR1B |= (1 << CS12); // 256 prescaler
TCNT1 = 3036; // preload timer (65536-62500)
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
interrupts(); // enable interrupts
}
ISR(TIMER1_OVF_vect) // interrupt
{
noInterrupts();
PORTB ^= (1 << 5); // blink
TCNT1 = 3036; // preload timer
interrupts();
}
void loop()
{
Serial.println(TCNT1);
}
void setup(){
Serial.begin(115200);
// TODO: set pin 13 as output mode by DDR?
// TODO: disable interrupt
// TODO: reset TIMER1 control register by TCCR1?
// TODO: reset TIMER1 prescaler by TCCR1?
// TODO: set CTC mode by TCCR1?
// TODO: enable Timer1 compare match interrupt by TIMSK?
// TODO: reset Timer1 by TCNT?
// TODO: set compare value by OCR1?
// TODO: enable interrupt
}
ISR(TIMER1_COMPA_vect){
// TODO: change pin 13 potential by PORT?
}
void loop(){
Serial.println(TCNT1);
}
使用Timer1 Register,完成能夠顯示 x(sec):y(ms) 之簡易計時器在LCD螢幕上,禁止使用delay()。
使用超聲波模組,每秒測量一次距離,並顯示在多工七段顯示器上,請運用Timer之技巧來實現。
Learn More →
使用ADMUX、ADCSRA及ADCH/L Register,讀取可變電阻之類比值,並在Serial port顯示出來,禁止使用analogRead()。
void setup(){
Serial.begin(115200);
// TODO: set voltage reference as AVCC
// TODO: enable ADC
// TODO: prescale
}
uint16_t ReadADC(byte pin){
// TODO: set channel
// TODO: start conversion
while (bit_is_set(ADCSRA, ADSC)); // TODO: wait until conversion complete
// TODO: return data
}
void loop(){
Serial.print(ReadADC(x)); // x means number of analog in pin
}
藉由Timer1 Register中PWM模式,使用可變電阻調整LED之亮暗,禁止使用analogRead()及analogWrite()。
Hint: Timer1 register控制pin9 & 10之analogWrite()
以下提示為使用Datasheet中p.109 Table15-5 第14個模式,歡迎使用不同模式來實作。
void setup(){
Serial.begin(115200);
// TODO: set voltage reference as AVCC
// TODO: enable ADC
// TODO: prescale
// TODO: reset TCCR? and OCR?
// TODO: set to Timer1 to Waveform Generation Mode 14: Fast PWM with TOP set by ICR1
// TODO: set time prescale
// TODO: set TOP by ICR1
// TODO: set clock prescale by TCCR?B
// TODO: enable fast PWM on pin: set OC1B at BOTTOM and clear OC1B on OCR1B compare by TCCR1A
// TODO: set pinMode output
}
uint16_t ReadADC(byte pin){
// TODO: set channel
// TODO: start conversion
while (bit_is_set(ADCSRA, ADSC)); // wait until conversion complete
// TODO: return data
}
void loop(){
// TODO: write analog value by OCR?
}
Learn More →
TIMER函式庫
運用timer函式庫,實作出 Lab3超聲波測距
函式庫寫法請參考:Arduino Timer Library
#include <Enerlib.h>
Energy energy; // 宣告"Energy"程式物件
const byte swPin = 2; // 開關腳位
const byte ledPin = 13; // LED腳位
byte times = 0; // 記錄執行次數
volatile byte state = 0; // 暫存執行狀態
void wakeISR() {
if (energy.WasSleeping()) {
state = 1;
} else {
state = 2;
}
}
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(swPin, INPUT);
digitalWrite(swPin, HIGH);
//當中斷0的狀態發生改變時,執行wakeISR
attachInterrupt(0, wakeISR, CHANGE); // 附加中斷服務常式
Serial.println("Running...");
}
void loop()
{
if (state == 1) {
//若中斷發生之前處於休眠狀態
Serial.println("Was sleeping...");
} else if (state == 2) {
//若中斷發生之前處於一般的執行狀態
Serial.println("Was awake...");
}
state = 0;//重設狀態
digitalWrite(ledPin, !digitalRead(ledPin));
delay(500);
times ++;
Serial.println(times);
if (times > 5) { //led亮滅3次,讓Arduino進入休眠模式
times = 0;
Serial.println("Go to sleep...");
energy.PowerDown();//進入最省電的斷電休眠模式
}
}
Datasheet中有些暫存器命名方式可以透過所提供的硬體方塊圖來了解其操作原理,當不了解該暫存器的文字說明時,可以利用這些方塊圖來嘗試理解該暫存器運作原理。