[2021/4/3] 再度修正しました。 平均値tAveVをuint32_tで保持していたのをfloatに変更しました。妙な桁落ち感が減った気がします。32bitの整数を50とかそんな値で割っていたのであまり変化はないはずだと思うのですが、グラフを描くと結構違います。
ATTINY版の電源監視ですが、フラッシュメモリの少なさからすべて整数演算にしていたのですが、若干誤差が大きいように思いますので、計算をfloatに変更してもフラッシュメモリ容量に収まるようなので、変更しました。(あと、何かの拍子に大きい値が出ます。原因は不明・・・というか、調べてません・・・が、それが治ることを期待して・・・ですが関係ないような気がするんだよなぁ・・・単に電圧がフォトカプラ内部のLEDの点灯/消灯のスレッショルドを跨ぐノイズが乗ってるだけだと思います。だとすると、治す方法は簡単ではないので諦めます。)
修正版は以下です。
#include <avr/io.h> // /usr/lib/avr/include/
#include <util/delay.h>
#include <util/atomic.h>
#include <string.h>
// FUSEは外部クリスタル8MHz、システムクロックを分周なしに設定(E:FF, H:DF, L:EF)
/////////////////////////////////////////////////
// 端子定義
#define BUZ 5 // PORT D5 に圧電ブザー接続
#define ICPPIN 6 // PORT D6 = ICP
#define UARTTX 1 // PORT D1 = TX
/////////////////////////////////////////////////
// タイマ0関連
void Buzzer(uint8_t period){
if(period>0){
// 高速PWM設定によるブザー駆動
TCCR0A = 0x23; // COM0B[1-0]=10,WGM0[1-0]=11 高速PWM動作
//TCCR0B = 0x0B; // WGM02=1,CS[2-0]=3 分周=64分周=8MHz/64=125kHz
TCCR0B = 0x0C; // WGM02=1,CS[2-0]=4 分周=256分周=8MHz/256=31.25kHz
OCR0A = period; // FREQ 31.25kHz / 31 ≒ 1008Hz / 63 ≒ 496Hz
OCR0B = period>>1; // DUTYを設定
// OC0B output
} else {
TCCR0A = 0x00; // 出力停止
}
}
/////////////////////////////////////////////////
// タイマ1関連
// 周期測定ののための変数
volatile uint16_t NumV;
volatile uint32_t SumV;
volatile uint16_t CapV;
volatile uint16_t PreCap,NowCap;
// 1秒間の最大最小平均を格納するための変数
uint16_t tNumV;
float tAveV;
uint32_t tSumV;
// 割り込みハンドラ
ISR(TIMER1_CAPT_vect){
TIFR |= (1<<ICF1);
NowCap = ICR1;
CapV = NowCap - PreCap; // 差分を求めて周期を算出
PreCap = NowCap;
SumV += CapV;
NumV++;
}
// タイマ1設定
void TIMER1_Init(void){
// TCC1でICP信号(PD6端子)の周期を計測する
TCCR1A = 0; //initialize Timer1
TCCR1B = 0;
TCNT1 = 0;
TCCR1B = 0x02; // 8MHzを8分周
TIMSK |= (1 << ICIE1); // キャプチャ割り込み許可
}
float Period(void){
// 割り込みで更新している変数をコピー&初期化する
cli(); //割り込み停止
tSumV = SumV; tNumV = NumV;
SumV = 0; NumV = 0;
sei(); //割り込み再開
tAveV = (float)tSumV / (float)tNumV;
return(tAveV);
}
/////////////////////////////////////////////////
// UART関連
#define FOSC 8000000 /* MCUクロック周波数 */
#define BAUD 9600 /* 目的USARTボーレート速度 */
#define MYUBRR FOSC/16/BAUD-1 /* 目的UBRR値 */
uint8_t txbuf[16];
uint8_t txp=0,txn=0;
ISR(USART_UDRE_vect){
if(txp<txn){
while ( !(UCSRA & (1<<UDRE)) ); /* 送信バッファ空き待ち */
UDR = txbuf[txp]; /* データ送信(送信開始) */
txp++;
} else {
UCSRB &= ~_BV(UDRIE);
}
}
void USART_Init(unsigned int baud){
UBRRH = (unsigned char)(baud>>8); /* ボーレート設定(上位バイト) */
UBRRL = (unsigned char)baud; /* ボーレート設定(下位バイト) */
UCSRC = (1<<USBS)|(3<<UCSZ0); /* フレーム形式設定(8ビット,2停止ビット) */
txp = 0;
UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<UDRIE); /* 送受信許可、送信割込許可 */
}
void USART_txt(uint8_t *txt,uint8_t len){
while(txp<txn);
cli();
memcpy(txbuf,txt,len);
txp=0;
txn=len;
UCSRB |= _BV(UDRIE);
sei();
}
void USART_num(uint16_t num){
uint16_t d=10000;
uint16_t n=num;
uint8_t c,p=0,b[8];
while(d>0){
c=n/d; n=n%d;
d=d/10;
b[p++]=c+0x30;
}
b[p++]='\r'; b[p++]='\n';
USART_txt(b,p);
}
void USART_znum(uint16_t num){
uint8_t s=1; // ゼロサプレス中
uint16_t d=10000;
uint16_t n=num;
uint8_t c,p=0,b[8];
while(d>0){
c=n/d; n=n%d;
d=d/10;
if(d==0) s=0;
if(s==0 || c!=0){
b[p++]=c+0x30;
s=0;
}
}
b[p++]='\r'; b[p++]='\n';
USART_txt(b,p);
}
/////////////////////////////////////////////////
// メイン
int main(void)
{
float p,f,d;
PORTB = 0xFF; // 入力設定時のプルアップ有効
PORTD = 0xFF; // 入力設定時のプルアップ有効
DDRD = _BV(BUZ); // PORT D5を出力に
DDRD &= ~_BV(ICPPIN); // ICPピンを入力に
PORTD &= ~_BV(ICPPIN); // ICPピンのプルアップ無効化
PORTD = _BV(UARTTX); // PORT D1を出力に
TIMER1_Init();
USART_Init(MYUBRR);
for(;;){
p = Period();
f = 1000000000./(float)p; // 周波数の1000倍を求める
if(f > 50000.){
d = f - 50000.;
} else {
d = 50000. - f;
}
if(d > 200.){ // 0.20Hz以上ずれた場合
Buzzer(31); // 高速PWM設定によるブザー駆動 31=1kHz
}else if(d > 150.){ // 0.15Hz以上ずれた場合
Buzzer( 78); // 高速PWM設定によるブザー駆動 78=400Hz
}else if(d > 100.){ // 0.10Hz以上ずれた場合
Buzzer(250); // 高速PWM設定によるブザー駆動 250=125Hz
}else {
Buzzer(0);
}
USART_znum(f);
_delay_ms(1000);
}
}
ちなみに、Makefileは以下です。
CC = avr-gcc
OBJCOPY = avr-objcopy
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
MCU = attiny2313
#F_CPU = 1000000
F_CPU=8000000
# 内蔵オシレータ1MHz
#LFUSE = 0x64
# 内蔵オシレータ8MHz
#LFUSE = 0xE4
# 外部クリスタル 8MHz
LFUSE = 0xEF
HFUSE = 0xDF
TARGET = firmware
SRC = main.c
OBJ = $(SRC:.c=.o)
LST = $(SRC:.c=.lst)
FORMAT = ihex
OPTLEVEL = s
CDEFS =
CFLAGS = -DF_CPU=$(F_CPU)UL
CFLAGS += $(CDEFS)
CFLAGS += -O$(OPTLEVEL)
CFLAGS += -mmcu=$(MCU)
CFLAGS += -std=gnu99
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -ffunction-sections -fdata-sections
CFLAGS += -Wall -Wstrict-prototypes
CFLAGS += -Wa,-adhlns=$(<:.c=.lst)
LDFLAGS = -Wl,--gc-sections
LDFLAGS += -Wl,--print-gc-sections
AVRDUDE_MCU = attiny2313
AVRDUDE_PROGRAMMER = avrisp2
AVRDUDE_SPEED = -B 125kHz
AVRDUDE_FLAGS = -p $(AVRDUDE_MCU)
AVRDUDE_FLAGS += -P usb
AVRDUDE_FLAGS += -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS += $(AVRDUDE_SPEED)
MSG_LINKING = Linking:
MSG_COMPILING = Compiling:
MSG_FLASH = Preparing HEX file:
all: gccversion $(TARGET).elf $(TARGET).hex size
.SECONDARY: $(TARGET).elf
.PRECIOUS: $(OBJ)
%.hex: %.elf
@echo
@echo $(MSG_FLASH) $@
$(OBJCOPY) -O $(FORMAT) -j .text -j .data $< $@
%.elf: $(OBJ)
@echo
@echo $(MSG_LINKING) $@
$(CC) -mmcu=$(MCU) $(LDFLAGS) $^ --output $(@F)
%.o : %.c
@echo $(MSG_COMPILING) $<
$(CC) $(CFLAGS) -c $< -o $(@F)
gccversion:
@$(CC) --version
size: $(TARGET).elf
@echo
$(SIZE) -C --mcu=$(AVRDUDE_MCU) $(TARGET).elf
analyze: $(TARGET).elf
$(NM) -S --size-sort -t decimal $(TARGET).elf
isp: $(TARGET).hex
$(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(TARGET).hex
fuses:
$(AVRDUDE) $(AVRDUDE_FLAGS) -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m
release: fuses isp
clean:
$(REMOVE) $(TARGET).hex $(TARGET).elf $(OBJ) $(LST) *~
・・・直してみたものの、もともと周期測定のクロックがATmega328版は2MHz(0.5us単位)、ATtiny版は1MHz(1us単位)かという違いがあって、誤差についてはあまり変わらなかったです。
まあ、整数で計算しているときも誤差に影響なさそうと思って整数化していたので、あまり変わらんかも、とも思ってたのですが。
[2021/4/3追記]冒頭に記載しましたが、もう一箇所、floatにすることで、かなり改善しました。メカニズム的にはなにか納得がいかないのですが、uint32_t / uint16_t の処理が自分が想定していることとなにか違う部分があるのかもしれません。
ただし、稀に大きな値が出る現象については不明です。インプットキャプチャが入力イベントを取りこぼしているのかなぁ?とも思いますが、それも不自然なので納得できてないところです。