【Arduino工作〜発展編】AVR-RustでAtmega328pの割り込み処理を試そう!
「割り込み」
RustでArduinoライクに使えるものを作ってみる
AVRの割り込みと割り込みベクタ
何も処理しないままじっと待っている
決められたクロック回数分だけ何もしない処理
「割り込み」
「割り込みベクタ」
ベクタ順序 | ベクタ名※ | トリガー元デバイス | 備考 |
---|---|---|---|
0 | 無し | 電源などリセット全般 | 電源ON・WDT・BOD等の各種リセット動作 |
1 | INT0_vect | INT0ピン | 外部割り込み要求0 |
2 | INT1_vect | INT1ピン | 外部割り込み要求1 |
3 | PCINT0_vect | PCINT0ピン | 入力信号変化時割り込み要求0 |
4 | PCINT1_vect | PCINT1ピン | 入力信号変化時割り込み要求1 |
5 | PCINT2_vect | PCINT2ピン | 入力信号変化時割り込み要求2 |
6 | WDT_vect | WDT | ウオッチドック完了 |
7 | TIMER2_COMPA_vect | タイマー2/比較器A | タイマー2比較A一致 |
8 | TIMER2_COMPB_vect | タイマー2/比較器B | タイマー2比較B一致 |
9 | TIMER2_OVF_vect | タイマー2 | タイマー2オーバフロー |
10 | TIMER1_CAPT_vect | タイマー1 | タイマー1入力キャプチャ発生 |
11 | TIMER1_COMPA_vect | タイマー1/比較器A | タイマー1比較A一致 |
12 | TIMER1_COMPB_vect | タイマー1/比較器B | タイマー1比較B一致 |
13 | TIMER1_OVF_vect | タイマー1 | タイマー1オーバフロー |
14 | TIMER0_COMPA_vect | タイマー0/比較器A | タイマー0比較A一致 |
15 | TIMER0_COMPB_vect | タイマー0/比較器B | タイマー0比較B一致 |
16 | TIMER0_OVF_vect | タイマー0 | タイマー0オーバフロー |
17 | SPI_STC_vect | SPI | SPIシリアル送信完了 |
18 | USART_RX_vect | USART | USARTシリアル受信完了 |
19 | USART_UDRE_vect | USART | USARTデータレジスタ空き |
20 | USART_TX_vect | USART | USARTシリアル送信完了 |
21 | ADC_vect | ADC | AD変換完了 |
22 | EE_READY_vect | EEPROM | EEPROM操作準備完了 |
23 | ANALOG_COMP_vect | ANA_COMP | アナログ比較完了 |
24 | TWI_vect | TWI | 2線シリアル通信状態変化時 |
25 | SPM_READY_vect | SPM | SPM命令準備完了 |
割り込み処理
1. 割り込み機能を有する周辺回路から割り込み要求(IRQ; Interrupt Re-Quest)が発行
2. 実行予定にあるメモリアドレスを一旦スタックに格納
3. 現行で実行中の命令を完了
4. 割り込みルーチン(ISR; Interrupt Service Routine)へ制御が移行
5. 割り込みルーチンの処理が完了後、メインルーチンで項目2で格納していたプロセスに復帰
タイマー2を使った割り込み
タイマー2のレジスタ操作
比較A一致
比較B一致
カウンターオーバフロー
比較A一致
TCCR2A・TCCR2Bレジスタ
COM2A1-COM2A0
01
OCR2A = 49
TCCR2A
COM2A1-COM2A0-COM2B1-COM2B0-無し-無し-WGM21-WGM20
設定例:
00(OC2Aポート切断)-00(OC2Bポート切断)-00-10(☆)
COM2A1-COM2A0
COM2B1-COM2B0
00
01
WGM21-WGM20
TCCR2B
FOC2A-FOC2B-無し-無し-WGM22-CS22-CS21-CS20
設定例:
00-00-0(☆)-111
FOC2A-FOC2B
00
CS22-CS21-CS20
CS22-CS21-CS20:
000...タイマー停止
001...分周無し(=1)
010...1/8
011...1/32
100...1/64
101...1/128
110...1/256
111...1/1024
WGM22-WGM21-WGM20
WGM22-WGM21-WGM20 | モード名 | TOP値 | OCR2更新タイミング | TOVタイミング |
---|---|---|---|---|
000 | 標準 | 0xFF | 即時 | 0xFF |
001 | 8bit位相標準PWM | 0xFF | 0xFF | 0x00 |
010 | CTC | OCR2A | 即時 | 0xFF |
011 | 8bit高速PWM | 0xFF | 0x00 | 0xFF |
101 | 位相標準PWM | OCR2A | OCR2A | 0x00 |
111 | 高速標準PWM | OCR2A | 0x00 | OCR2A |
WGM22-WGM21-WGM20
010
10
0
0.125us(@8MHz) * 1024 * 100 = 12.8ms
TIMSK2レジスタ
TIMSK2
無-無-無-無-無-OCIE2B-OCIE2A-TOIE2
設定例:
00000-010(比較A一致割り込み有効)
OCIE2A
RustでISRマクロが使えない...サブルーチンどうする?
ISR(<割り込みベクタの識別名>) {
//...サブルーチン処理
}
io.h
TIMER2_OVF_vect
#define TIMER2_OVF_vect_num 9
#define TIMER2_OVF_vect _VECTOR(9) /* Timer/Counter2 Overflow */
_VECTOR(N)
_VECTOR(N)
#define _VECTOR(N) __vector_ ## N
ISR(TIMER2_OVF_vect)
__vector_9
#[no_mangle]
pub unsafe extern "avr-interrupt" fn __vector_N() {
//...サブルーチン処理
}
N
extern "C"
extern "avr-interrupt"
プログラムの実装
割り込みの許可/禁止
sei()
sei()
SEI
#![feature(llvm_asm)]
//...
pub extern "C" fn main() {
//...
//👇割り込み処理を許可
unsafe { llvm_asm!("sei"); }
//...
llvm_asm!("cli");
メインルーチンとサブルーチン間のデータ共有
#include <avr/interrupt.h>
//ルーチン間で共有される変数
volatile unsigned char COUNT = 0;
ISR(HOGE_vect) {
//サブルーチンで変数COUNTを操作...
}
int main(void) {
sei();
//メインルーチンでも変数COUNTを操作...
}
static mut
unsafeブロック
//ルーチン間で共有される変数
static mut COUNT: u8 = 0;
#[no_mangle]
pub unsafe extern "avr-interrupt" fn __vector_N() {
unsafe {
//サブルーチンで変数COUNTを操作...
}
}
#[no_mangle]
pub extern "C" fn main() {
unsafe { llvm_asm!("sei"); }
unsafe {
//メインルーチンでも変数COUNTを操作...
}
}
Rustでプログラミング
#![feature(llvm_asm, lang_items, unwind_attributes)]
#![feature(start, abi_avr_interrupt)]
#![no_std]
#![no_main]
extern crate avr_delay;
extern crate avr_std_stub;
extern crate avrd;
use avr_delay::{delay};
use core::ptr::write_volatile;
use avrd::atmega328p::{DDRB, DDRC};
use avrd::atmega328p::{CLKPR};
//👇タイマー1制御レジスタ
use avrd::atmega328p::{TCCR1A, TCCR1B, OCR1A, ICR1};
//👇タイマー2制御レジスタ
use avrd::atmega328p::{TCCR2A, TCCR2B, OCR2A, TIMSK2};
//👇USART関連レジスタ
use avrd::atmega328p::{UCSR0A, UCSR0B, UCSR0C, UDR0, UBRR0};
static mut COUNT: u8 = 0;
static mut OLD_COUNT: u8 = 0;
#[no_mangle]
#[start]
pub extern "C" fn main() {
clock_init();
usart_init();
pwm_init();
interrupt_init();
//👇PB1ピン(OC1A), PB3(OC2A)ピンを出力
unsafe { write_volatile(DDRB, 0b00001010); }
//👇割り込み処理を許可
unsafe { llvm_asm!("sei"); }
loop {
delay(2000000); //👈1s@8Mhz
unsafe {
//👇一秒ごとにカウンターを回す
cycle_shift(&mut COUNT);
}
}
}
#[no_mangle]
fn clock_init() {
unsafe {
write_volatile(CLKPR, 0b10000000);
//👇主クロック周波数16MHzを2分周し、8MHz駆動にする
write_volatile(CLKPR, 0b00000001);
}
}
#[no_mangle]
fn pwm_init(){
unsafe {
write_volatile(TCCR1A, 0b10000010);
write_volatile(TCCR1B, 0b00011011);
write_volatile(ICR1, 65535);
//👇Duty比: 0.5
write_volatile(OCR1A, 32767);
}
}
#[no_mangle]
fn interrupt_init(){
unsafe {
//👇タイマ2はCTCモードで、比較A一致で割り込みトリガー
write_volatile(TCCR2A, 0b00000010);
//👇1024分周
write_volatile(TCCR2B, 0b00000111);
//👇割込み間隔: 0.125us(@8MHz) * 1024 * 100 = 12.8ms
write_volatile(OCR2A, 100);
//👇比較A一致割り込み有効
write_volatile(TIMSK2, 0b0000010);
}
}
//👇割り込み処理の確認用のUSART設定
#[no_mangle]
fn usart_init() {
unsafe {
write_volatile(UBRR0, 51);//9600bps
write_volatile(UCSR0B, 0b00011000);
write_volatile(UCSR0C, 0b00000110);
}
}
//👇タイマー2の比較A割り込みルーチン
#[no_mangle]
pub unsafe extern "avr-interrupt" fn __vector_7() {
unsafe {
if OLD_COUNT ^ COUNT != 0 {
//👇デバッグ用にカウンターをUSARTのTxより送信して、シリアルでモニタリング可
transmit(COUNT);
//👇8秒おきにタイマー1のPWM波形のDuty比を変化
if COUNT == 0 {
//👇Duty比: 約0.92
write_volatile(OCR1A, 60000);
}
if COUNT == 7 {
//👇Duty比: 約0.08
write_volatile(OCR1A, 5000);
}
OLD_COUNT = COUNT;
}
}
}
//👇循環整数(0~15)の生成
#[no_mangle]
fn cycle_shift(x: &mut u8) {
*x += 1;
*x &= (1<<4) - 1;
}
#[no_mangle]
fn transmit(byte: u8) {
while !ready_to_transmit() {}
unsafe {
write_volatile(UDR0, byte);
}
}
#[no_mangle]
fn ready_to_transmit() -> bool {
unsafe {
(*UCSR0A & (1 << 5)) > 0
}
}
動作確認
まとめ
参考
ナンデモ系エンジニア
電子工作を身近に知っていただけるように、材料調達からDIYのハウツーまで気になったところをできるだけ細かく記事にしてブログ配信してます。