Arduino IDE下でのATtiny402、そのサンプルです。
・2つのボリュームをA/Dコンバータで入力。
・その値を元にRCサーボ用のPWMパルスを2ch出力。
・1秒ごとに2chの回転角をシリアル出力。
こんな回路です。
ソフト的には、
・PWM出力はTCAタイマーで。
20MHzクロックを1/8して0.4usクロックで
カウント。
・TCBタイマーで1msタイマー割り込み。
・A/D変換は割り込み処理で。
32回累積させて15bit値で読み出し。
analogReadは使わない。
・シリアル出力は独自ルーチンで。
ArduinoのSerial.printは使わない。
ただし、割り込み処理していないので
文字出力中は出力ルーチンから抜け
出せない。
Arduino環境ですが、ATtiny402を裸にして使っています。
/*********************************************/
/* 小型サーボモータテスト用パルス発生回路 */
/*********************************************/
// ATtiny402 20MHz Arduino IDE環境
// ボードマネージャー:
// http://drazzy.com/package_drazzy.com_index.json
// TCA0 20MHz/8=2.5MHz=0.4us
// サーボの周波数50Hz=20ms 20000/0.4=50000clk
// -90° 0.50ms 1250clk
// 0° 1.45ms 3625clk
// -90° 2.40ms 6000clk
#define SVP_PR 50000 // サーボ周波数clk数
#define SVP_M90 1250 // サーボ-90°位置clk
#define SVP_00 3625 // サーボ 0°位置clk
#define SVP_P90 6000 // サーボ+90°位置clk
/***** タイマーTCB0周期割り込み *****/
// 1ms割り込み
ISR(TCB0_INT_vect) {
TCB0.INTFLAGS = 1; // 割込要求クリア
ADC0_COMMAND = 1; // A/D変換開始指令
}
/**** A/D データ *****/
// A/D変換チャンネル
const byte ad_mpx[] ={
0b00000001, // AIN1:PA1 4pin
0b00000111, // AIN7:PA7 3pin
};
// A/Dデータ(2ch)
volatile word ad_15b[2]; // A/D変換データ 0~32736(32回加算)
// 割込で書き込み
word ad_10b[2]; // 10bit A/D値 0~1023
// mainで読み出し
// TCAデータ サーボの角度パルスを発生
volatile word *const TCA0CMP[]={
&TCA0.SINGLE.CMP0BUF, // WO0出力 PA3 0.4usクロック
&TCA0.SINGLE.CMP2BUF, // WO2出力 PA2
};
word tca_pwm[2]; // TCAに対するPWM設定値
// -90°~0°~+90°のデューティを設定
/***** A/D割り込み処理 *****/
ISR(ADC0_RESRDY_vect)
{
static byte ch; // A/D変換チャンネル
ad_15b[ch] = ADC0_RES; // A/D (0~1023*32) 15bitデータ
// 次変換チャンネル
ch++; // ch0,1だけ
if(ch >= 2) ch = 0; // 0に戻す
ADC0.MUXPOS = ad_mpx[ch]; // A/D入力MPX切り替え
}
/***** BCD文字出力処理 *****/
// BCD出力用文字バッファ
char bcd_bff[16]; // 16文字 終端のnull含めて
// longは10桁
/***** 桁指定BCD文字出力 *****/
// d:変換データ long値
// n:整数部文字数(-を含めて)
// p:小数部文字数(.は含まない)
// 数値は整数を入力 浮動小数点ではない
// 小数部は整数の基数が0.01などの時に用いる
// 12345が123.56を意味する時 (d,3,2)と指定
// -1234を-123.4と表示するときは(d,4,1)と-を含めた文字数に
// bcd_bffに入った文字の先頭アドレスを持ってリターン
char *strbcd(int32_t d, byte n, byte p)
{
byte i;
byte sgn = 0; // 符号フラグ
byte zr = 0; // ゼロデータ処理済みフラグ
char *s; // 0~9書込みバッファのアドレス
if(d < 0){ // マイナス?
d = -d; // +の値にして
sgn = 1; // -フラグをオン
}
i = n + p; // 文字数
if(p) i++; // 小数点あれば+1
s = bcd_bff + i; // バッファの最後尾
*s = '\0'; // 最後にnullをセット
// 小数部 '.'まで
if(p){ // 小数部あり
for(i = 0; i < p; i++){ // loop
if(d){ // 0でないときは計算
*--s = (byte)(d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
}
else{ // 0なら'0'を
*--s = '0';
}
}
*--s = '.'; // 小数点
}
// 整数部 上位ゼロサプレス
for(i = 0; i < n; i++){ // loop
if((i == 0) || // 1桁目あるいは
(d != 0)){ // ゼロ以外
*--s = (byte)(d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
}
else if((sgn != 0) && // ゼロでマイナス
(zr == 0)){ // -符号処理まだ
*--s = '-'; // マイナスを付加
zr = 1; // 始めてのゼロを処理した
}
else{ // 2桁目以降でゼロ
*--s = ' '; // ゼロサプレス
}
}
return s; // 文字列の先頭アドレスを持ってリターン
}
/***** シリアル出力 *****/
void tx(char c)
{
while(!(USART0.STATUS & USART_DREIF_bm)); // 送信できるまで待つ
USART0.TXDATAL = c; // 1文字出力
}
/***** 文字列出力 *****/
void txstr(const char *s)
{
while(*s != '\0'){ // Nullまで
tx(*s); // 1文字出力
s++; // 次文字
}
}
/***** SETUP *****/
void setup() {
cli();
PORTA.OUT = 0b01000000; //ポート出力
PORTA.DIR = 0b01001100;
// || |||+---- PA0 6pin UPDI
// || ||+----- PA1 4pin in AIN1
// || |+------ PA2 5pin out PWM WO2
// || +------- PA3 7pin out PWM WO0
// |+---------- PA6 2pin out TXD
// +----------- PA7 3pin in AIN7
// タイマーA0, 分割モードで初期化されるので16bitモードに
TCA0.SPLIT.CTRLA = 0; // タイマー停止
TCA0.SPLIT.INTCTRL = 0; // 割り込みなしに
PORTMUX.CTRLC = 0; // TCAポート多重切り替えなしに
TCA0.SINGLE.CTRLD = 0; // TCA0を16bitモードに
TCA0.SINGLE.PER = SVP_PR - 1; // 周期 20MHz/8/50000 = 50Hz(20ms)
TCA0.SINGLE.CMP0BUF = SVP_00; // WO0出力 PA3 0°位置 1.45ms
TCA0.SINGLE.CMP2BUF = SVP_00; // WO2出力 PA2
TCA0.SINGLE.CTRLB = 0b01010011;
// ||||+++----- WGM 1傾斜PWMモード
// |||+-------- ALUPD
// ||+--------- CMP0EN PA3 7pin 出力
// |+---------- CMP1EN PA1 4pin (AIN1)
// +----------- CMP2EN PA2 5pin 出力
TCA0.SINGLE.CTRLA = 0b00000111;
// |||+----- ENABLE 動作開始
// +++------ CLKSEL プリスケーラ1/8
// タイマーB 1msタイマー
TCB0.CCMP = 20000-1; // 20MHz/20000=1kHz
TCB0.CTRLB = 0b00000000;
// ||| +++---- CNTMODE 周期割り込み
// ||+-------- CCMPEN
// |+--------- CMPINT
// +---------- ASYNC
TCB0.CTRLA = 0b00000001;
// | | ||+---- ENABALE 有効
// | | ++----- CLKSEL 1/1 20MHz
// | +-------- SYNCUPD 同期
// +---------- RUNSTDBY
TCB0.INTCTRL = 1; // 割込許可
// A/D AIN1:PA1 4pin, AIN7:PA7 3pin
ADC0.CTRLA = 0b00000001;
// | ||+--- ENABLE 許可
// | |+---- FREERUN しない
// | +----- RESEL 10bit
// +---------- RUNSTDBY
ADC0.CTRLB = 0b00000101; // サンプル回数
// +++--- 32回 変換時間約0.6ms
ADC0.CTRLC = 0b01010011;
// ||| +++--- PRESC 20MHz/16=1.25MHz
// |++------- REFSEL VDD
// +--------- SMAPCAP 5PF
ADC0.MUXPOS = 0b00000001; // MPX
// +++++--- AIN1 PA1
PORTA.PIN1CTRL = 0b00000100; // AIN1:PA1 4pin
PORTA.PIN7CTRL = 0b00000100; // AIN7:PA7 3pin
// | |+++---- ディジタル入力禁止
// | +------- pullupなし
// +----------- 反転I/Oなし
ADC0.INTCTRL = 0b00000001; // 割り込み
// |+---- RESRDY 変換完了割込許可
// +----- WCMP
// シリアル
USART0.CTRLB = 0b01000000;
// || ||||+-- MPCE
// || ||++--- RXMODE
// || |+----- ODME
// || +------ SFDEN
// |+-------- TXEN 送信有効
// +--------- RXEN 受信はしない
USART0.CTRLC = 0b00000011;
// |||||+++-- CHSIZE 8BIT
// ||||+----- SBMODE 1stop
// ||++------ PMODE Parity無し
// ++-------- CMODE 非同期USART
USART0.BAUD = 8333; // (64*20MHz)/(9600/16)
sei();
}
/***** LOOP ******/
// 20ms周期でA/D値を読んでPWM値を設定
// 1秒ごとに2つの角度をシリアル出力
void loop() {
byte i;
int d;
byte cnt = 0; // 処理カウンタ 1秒をチェック
while(1){
if(TCA0.SINGLE.INTFLAGS & TCA_SINGLE_OVF_bm){ // TCA 1サイクル? (20ms)
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // フラグクリア
for(i=0; i < 2; i++){ // 2ch
cli(); // 割込禁止にして
d = ad_15b[i]; // A/D 15bit 32回累積加算値読み出し
sei();
ad_10b[i] = d / 32; // 15bit値を10bitに
tca_pwm[i] = map(ad_10b[i], // 0~1023を
0, 1023, SVP_M90, SVP_P90); // -90°~+90°のPWM値に
*TCA0CMP[i] = tca_pwm[i]; // PWM設定
}
cnt++; // 20msごとに+1
}
if(cnt >= 50){ // 1秒経過
cnt = 0;
for(i=0; i < 2; i++){ // 2ch
d = map(ad_10b[i], // A/D値 0~1023を
0, 1023, -900, 900); // -90.0°~+90.0°
strbcd(d, 4, 1); // xxxx.x 6文字に
txstr(bcd_bff); // 文字列出力
}
txstr("\r\n"); // CR+LF
}
}
}
メモリーの使用量、こんな具合です。
最大4096バイトのフラッシュメモリのうち、スケッチが
1135バイト(27%)を使っています。
最大256バイトのRAMのうち、グローバル変数が35バイト
(13%)を使っていて、ローカル変数で221バイト使うことができます。
例えば、シリアル出力するためにSerial.begin()、Serial.print()を
使うと、
フラッシュメモリ 1823バイト(44%)
RAM 59バイト(23%)
となってしまいます。
さらに、sprintf(); で書式指定すると
フラッシュメモリ 3014バイト(73%)
となり、処理プログラムのエリアを圧迫しはじめます。
「4kバイト:0x0000~0x0FFF」というプログラム領域、
昔なら「2732」(24ピンの紫外線消去ROM)です。
Arduinoの内部タイマとしてTCAが8bitの分割モードで
使われています。
これを16bitで使うには、という手順の参考になるかと。
A/Dコンバータの累積加算機能はありがたいかと。
※サンプルプログラムといえば「Lチカ」して
「動いたよ」というパターンが多いかと思います。
しかし・・・マイコンが持つ能力を引き出したいとき
(内蔵のハードウェアを堪能したい)は、出来合いの
ライブラリには頼れません。
ハードウェアの直叩きが必要です。
※関連
・ATtiny402 備忘録
・その後のMPLAB@Snap:Arduino IDEで使えるぞ
・Arduinoから「タイマー0」を取り上げる(ユーザーが使う)
・割り込みで処理させるwordデータの扱い
・数値をBCD出力(表示)するルーチン #3
最近のコメント