AVRマイクロコントローラーのTVカウントダウンタイマー

最初の結果



ある日、私の友人から、テレビでカウントダウンタイマーを使って大きな数字を表示する方法を尋ねました。 ラップトップ/ iPad / Androidを接続してアプリケーションを作成できることは明らかです。面倒なのはラップトップだけで、友人も私もモバイルアプリケーションを作成したことはありません。



そして、私はAVRマイクロコントローラー上のTV端末のネットワークプロジェクトで見たことを思い出しました。 小さなキャラクターを大きなキャラクターにまとめるというアイデアがすぐに私の頭に浮かび、それを試すことにしました。 どういうわけか私は主な仕事をしなければならなかったことが判明しました。



もちろん、MKでデバイスを開発した経験は少しありますが、既製のものを購入する方が簡単です。そのため、テレビに出力するための既製のソリューションを積極的に探し始めました。 主な検索条件は、何よりもまず、可能であれば単純さ、アセンブラー挿入なしのC言語の使用、および高画質でした。



多くのプロジェクトが見つかりましたが、ほとんどのプロジェクトは特に基準を満たしていませんでした。 その後、主なことはビデオ信号形成の原理を理解することであることが明らかになり、それから物事が進むでしょう。 しかし、この段階で、 マキシムイブラギモフのプロジェクト「Simple VGA / Video Adapter」は絶対的なお気に入りになり、それが私の技術の基礎となりました。 ただし、作業の過程で、そこから構造のみが残り、実装をほぼ完全にやり直す必要がありました。



私が実際に思いついた追加のタスクは、IRリモコンから初期時間を設定することでした。



メインコントローラとして、20 MHzで動作するATMega168を使用することにしました。 ビデオシェーパーのハードウェアは次のようになります。



ビデオドライバ回路



私はプロジェクトからVGAに関連するものをすべて捨てることから始めました。 その過程で 、ビデオコーディング標準を研究しましたが、Martin Hinnerのサイトから最もアクセスしやすい画像が見えました。



画像



この写真から同期信号発生器を作成しました。



ジェネレータは、fastPWMモードのTimer1に基づいています。 さらに、グローバル変数はクロックカウンターによって編成されます。 タイマーオーバーフローの割り込みごとに、キー値のクロック番号がチェックされ、次のクロックパルスの持続時間と次のクロックパルスの周期が変更されます(フルライン/ハーフライン)。 変更が必要ない場合は、標準的なアクションが実行されます-クロックパルスのカウンターが増加し、他の変数が変更されます。



#define
// 2. System definitions #define Timer_WholeLine F_CPU/15625 //One PAL line 64us #define Timer_HalfLine Timer_WholeLine/2 //Half PAL line = 32us #define Timer_ShortSync Timer_WholeLine/32 //2us #define Timer_LongSync Timer_ShortSync*15 //30us #define Timer_NormalSync Timer_WholeLine/16 //4us #define Timer_blank Timer_WholeLine/8 //8us //Global definitions for render PAL #define PAL_FPS 50 #define pal_first_visible_line1 40 #define pal_last_visible_line1 290 //pal_first_visible_line1+pal_row_count*pal_symbol_height #define horiz_shift_delay 15
      
      







タイマーの初期化(関数フラグメント)
 // Initialize Sync for PAL synccount = 1; VIDEO_DDR |= (1<<SYNC_PIN); OCR1B = Timer_LongSync; TCCR1A = (1<<COM1B1)|(1<<COM1B0)|(0<<WGM10)|(1<<WGM11); //Fast PWM,Set OC1B on Compare Match, // clear OC1B at BOTTOM (inverting mode) TCCR1B = (1<<WGM12)|(1<<WGM13)|(1<<CS10); //full speed;TOP = ICR1 ICR1 = Timer_HalfLine; //     . TIMSK1 = (1<<OCIE1B); //enable interrupt from row_render=0; y_line_render=0;
      
      







クロックジェネレーター
 //  volatile unsigned int synccount; //    EMPTY_INTERRUPT (TIMER1_COMPB_vect); void MakeSync(void) { switch (synccount) { case 5://++++++++++++++++++++++++++++++++++++++++++++++++++++++++= Sync=Timer_ShortSync; synccount++; break; case 10://++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ICR1 = Timer_WholeLine; Sync= Timer_NormalSync; synccount++; break; case 315://++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ICR1 = Timer_HalfLine; Sync= Timer_ShortSync; synccount++; break; case 321://++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Sync=Timer_LongSync; synccount=1; framecount++; linecount = 0; break; default://++++++++++++++++++++++++++++++++++++++++++++++++++++++++ synccount++; video_enable_flg = ((synccount>pal_first_visible_line1)&&(synccount<pal_last_visible_line1)); break; } }
      
      







PALフレーム同期信号



各ラインの終わりで、コントローラーはスリープ状態になり、タイマーオーバーフローによる割り込み後にウェイクアップします。その後、MakeSync()関数がすぐに呼び出され、次の同期期間のタイマー設定を設定します。その後、クロック数が可視領域に入ると、ビデオ信号が開始されます



ビデオ出力はSPIによって編成され、クロック信号の周波数の半分に等しい最大周波数で動作します。



#define
 #define SPI_PORT PORTB #define SPI_DDR DDRB #define MOSI PORTB3 #define MISO PORTB4 #define SCK PORTB5 //  #define VIDEO_PORT SPI_PORT #define VIDEO_DDR SPI_DDR #define VIDEO_PIN MOSI #define VIDEO_OFF DDRB=0b00100100; #define VIDEO_ON DDRB=0b00101100;
      
      







SPI初期化(フラグメント)
 //Set SPI PORT DDR bits VIDEO_DDR |= (1<<MOSI)|(1<<SCK)|(0<<MISO); SPSR = 1 << SPI2X; SPCR = (1 << SPE) | (1 << MSTR); //SPI enable as master ,FREQ = fclk/2
      
      







出力プロセス自体は、DrawString関数によって各行で実行されます。DrawString関数は、パラメーターとして、出力用の数字の配列へのポインター、使用するフォントへのポインター、表示する文字数を渡します。 また、出力では、各フォントの出力行の番号と文字番号にグローバル変数が使用されます。 各文字の内部で、文字の幅(バイト単位)に等しい反復回数のループで、これらのフォントバイトがSPDRレジスタに転送されます。



さらに、AVRコントローラーのSPIのハードウェア実装では、複数バイトのデータを連続して送信できません。 各バイトの後に、1ビットがスキップされ、画像にギャップが生じます。



SPI伝送ギャップ

少し説明
少しでも間違っています。 バイトが転送された後、MOSI出力は高レベルのままであり、この写真では、74N0404インバーターを介してビデオ出力がオンになり、出力前にフォントバイトが反転されるため、ギャップが黒になります。 インバータなしでは、白い縦縞が得られます。





この欠点を克服するには、必要に応じてビデオ出力フットを高インピーダンス状態に切り替えて、出力バイトの最後のビットを繰り返すTellyMateプロジェクトで提案されたトリックを使用する必要がありました。 機能のこの部分は非常に時間が重要であり、アセンブラーの拒否により、タンバリンを使用してトリッキーな解決策見つける必要が生じました。



ライン出力機能
 inline void DrawString (unsigned char *str_buffer[], struct FONT_INFO *font, unsigned char str_symbols) { unsigned char symbol_width; unsigned char i; unsigned char * _ptr; unsigned char * _ptr1; y_line_render++; //Set pointer for render line (display buffer) _ptr = &str_buffer[row_render * str_symbols]; unsigned char j; register unsigned char _S; unsigned char _S1; //Cycle for render line i = str_symbols; while(i--) { symbol_width = font->width[(* _ptr)]; //Set pointer for render line (character generator) _ptr1 = &font->bitmap[font->offset[* _ptr]+y_line_render*symbol_width]; _S1 = 0; //  _S = pgm_read_byte(_ptr1); //  _ptr1++; j=symbol_width; //   while (1) { if (_S1 & 0b1) { goto matr; } VIDEO_OFF; matr: NOP; SPDR = _S; VIDEO_ON; _S1 = _S; _S = pgm_read_byte(_ptr1++); NOP; NOP; if (!--j) break; } _ptr++; VIDEO_OFF; } }
      
      







画像を受信した後、リモートコントロールからの赤外線パッケージの受信と分析については疑問がなく、単に十分な速度がなかったことが明らかになったため、UARTを介したコマンドの受信を残しました。 別のマイクロコントローラーがIRを受信します。



クロックを表示するために必要な2番目のバッファーも追加されました。 したがって、2つのフォントもあります。 フォントファイル構造は、文字の実際のビットマップ、フォントの高さ定数、各文字のオフセットの配列、各文字の幅で構成されます。



プログラムから簡単にアクセスできるように、フォントを記述する構造もあります。



フォント
 // Character bitmaps for Digital-7 Mono 120pt const unsigned char PROGMEM Digital7_Bitmaps[] = { // @0 '0' (71 pixels wide) 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x80, // ############################################# # 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xE0, // ############################################### ### 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF0, // ############################################### ##### 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF8, // ################################################ ###### ... ... } const unsigned char Digital7_Height = 105; const unsigned char Digital7_Width[] = { 9, /* 0 */ 9, /* 1 */ 9, /* 2 */ 9, /* 3 */ 9, /* 4 */ 9, /* 5 */ 9, /* 6 */ 9, /* 7 */ 9, /* 8 */ 9, /* 9 */ 3 /* : */ }; const unsigned int Digital7_Offset[] = { 0 , /* 0 */ 945, /* 1 */ 1890, /* 2 */ 2835, /* 3 */ 3780, /* 4 */ 4725, /* 5 */ 5670, /* 6 */ 6615, /* 7 */ 7560, /* 8 */ 8505, /* 9 */ 9450 /* : */ };
      
      







DotFactoryプログラムによって生成されたフォント。



フレームの不可視部分では、クロックとタイマーが移動し、UARTが受信したコマンドに対する反応もあります。



UART受信
 unsigned char clock_left; bool clock_set; volatile unsigned char MinTens, MinOnes; volatile unsigned char SecTens, SecOnes; static void pal_terminal_handle(void) { unsigned char received_symbol = 0; // Parser received symbols from UART while(UCSR0A & (1<<RXC0)) { received_symbol = UDR0; if (received_symbol=='#') { clock_left=5; clock_set = true; } if ((received_symbol>0x2F)&&(received_symbol<0x3A)) { if (clock_set) { time_array[5-clock_left] = received_symbol - 0x30; clock_left--; if (clock_left==3) { clock_left--; } if (clock_left==0) { time_array[6] = 0; time_array[7] = 0; clock_set = false; } } else { if ((pause==0)||_Stop) { MinTens = 0; } else { MinTens = MinOnes; } MinOnes = received_symbol - 0x30; SecTens = 0; SecOnes = 0; pause = 4; _Stop = false; str_array[0] = MinTens; str_array[1] = MinOnes; str_array[2] = 0x0A; str_array[3] = SecTens; str_array[4] = SecOnes; } //time_array[] = {1, 2, 10, 5, 5}; } } }
      
      







関数main();
 volatile bool _Stop; struct FONT_INFO { unsigned char height; unsigned char * bitmap; unsigned int * offset; unsigned char * width; } Digital7, comdot; int main(void) { avr_init(); //fonts Digital7.bitmap = &Digital7_Bitmaps; Digital7.height = Digital7_Height; Digital7.offset = &Digital7_Offset; Digital7.width = &Digital7_Width; comdot.bitmap = &comdotshadow_Bitmaps; comdot.height = comdotshadow_Height; comdot.offset = &comdotshadow_Offset; comdot.width = &comdotshadow_Width; MinTens = 0; MinOnes = 0; SecTens = 0; SecOnes = 0; str_array[0] = MinTens; str_array[1] = MinOnes; str_array[2] = 0x0A; str_array[3] = SecTens; str_array[4] = SecOnes; unsigned char *semicolon = &time_array[2]; sei(); while (1) { sleep_mode(); MakeSync(); if (UCSR0A & (1<<RXC0)) { //Parse received symbol pal_terminal_handle(); //Can easealy add here RX polling buffer //to avoid display flickering continue; } //Check visible field if(video_enable_flg) { linecount++; //OK, visible //Main render routine #define firstline 36 #define secondline 200 //To make horizontal shift rendered image unsigned char k; for (k=horiz_shift_delay; k>0; k--) { NOP; } if ((linecount == firstline)||(linecount == secondline)) { row_render = 0; y_line_render = 0; } if ((linecount> firstline) && (linecount< firstline+(Digital7.height))) { DrawString(&str_array, &Digital7, 5); } if ((linecount> secondline) && (linecount< secondline+(comdot.height))) { DrawString(&time_array, &comdot, 5); } } else { //Not visible //Can do something else.. //You can add here your own handlers.. // VIDEO_OFF; if (framecount==PAL_FPS) { framecount=0; //========================================= if (*semicolon== 11) { *semicolon=10; } else { *semicolon=11; } if (++time_array[7] == 10) { framecount = 1;//   time_array[7]=0; if (++time_array[6]==6) { framecount = 3; //   time_array[6]=0; if (++time_array[4]==10) { time_array[4]=0; if (++time_array[3]==6) { time_array[3]=0; if ((++time_array[1]==4) && (time_array[0]==2)) { time_array[0]=0; time_array[1]=0; } if (time_array[1]== 9) { time_array[1]=0; time_array[0]++; } } } } } //========================================= if ((pause==0)&&(_Stop==false)) { if ((SecOnes--)==0) { SecOnes=9; if ((SecTens--) == 0) { SecTens = 5; if ((MinOnes--) == 0) { MinOnes = 9; if (MinTens == 0) { _Stop = true; } else { MinTens--; } } } } if (!_Stop) { str_array[0] = MinTens; str_array[1] = MinOnes; str_array[2] = 0x0A; str_array[3] = SecTens; str_array[4] = SecOnes; } } else { pause--; } } } } }
      
      







IRリモートコントロールをデコードし、UART経由でコマンドを送信するコントローラーとして、ATTiny45を使用しました。 ハードウェアUARTがないため、送信のみに機能するソフトウェアUARTの非常にコンパクトな機能がインターネット上で見つかりました。また、リモートコントロールからコマンドを読み取るためのシンプルな機能 (デコードなし)



これらはすべてすぐにヒープに収集され、コンパイルされました。 リモートコントロールボタンのコードは、コードにハードコードされています。 さらに、コマンド受信時に点滅するLEDを作成しました。



IRおよびUART受信機
/ *

* Tiny85_UART.c

*

*作成日:2016年4月19日9:22:52 PM

*著者:アントニオ

* /



#include <avr / io.h>

#include "dbg_putchar.h"

#include <avr / interrupt.h>

//#<stdlib.h>を含める

#include <stdbool.h>



//パルス長と休止を比較するためのしきい値

static const char IrPulseThershold = 9; // 1024/8000 * 9 = 1.152ミリ秒

//パッケージを受け取るためのタイムアウトを定義します

//パルスとポーズの最大長を制限します

static const uint8_t TimerReloadValue = 100;

static const uint8_t TimerClock =(1 << CS02)| (1 << CS00); // 8 MHz / 1024



volatile unsigned char blink = 0;



#define blink_delay 3;



揮発性構造体ir_t

{

//メールの受信を開始するフラグ

uint8_t rx_started;

//受け入れられたコード

uint32_tコード、

//受信バッファ

rx_buffer;

} ir;



static void ir_start_timer()

{



TCNT0 = 0;

TCCR0B = TimerClock;

}



//タイマーがオーバーフローすると、パッケージが受け入れられたとみなします

//受信したコードをバッファからコピーします

//フラグをリセットしてタイマーを停止します

ISR(TIMER0_OVF_vect)

{

ir.code = ir.rx_buffer;

ir.rx_buffer = 0;

ir.rx_started = 0;

if(ir.code == 0)

TCCR0B = 0;

TCNT0 = TimerReloadValue;

}



ISR(TIMER1_OVF_vect)

{

if(点滅== 0)

{

OCR1B = 0;

}

他に

{

OCR1B = 200;

点滅-;

}

}



//上昇と下降の外部中断

ISR(INT0_vect)

{

uint8_t delta;

if(ir.rx_started)

{

//パルス/休止時間が閾値よりも長い場合

//バッファに1をシフトします。それ以外の場合はゼロにシフトします。

delta = TCNT0-TimerReloadValue;

ir.rx_buffer << = 1;

if(delta> IrPulseThershold)ir.rx_buffer | = 1;

}

その他{

ir.rx_started = 1;

ir_start_timer();

}

TCNT0 = TimerReloadValue;

}



void dbg_puts(char * s)

{

while(* s)dbg_putchar(* s ++);

}



int main(void)

{



GIMSK | = _BV(INT0);

MCUCR | =(1 << ISC00)| (0 << ISC01);

TIMSK =(1 << TOIE0)|(1 << TOIE1);

ir_start_timer();



dbg_tx_init();



DDRB | = _BV(PB4);



TCCR1 | =(1 << CS13)|(1 << CS12)|(0 << CS11)|(0 << CS10);

GTCCR | =(1 << COM1B1)|(0 << COM1B0)|(1 << PWM1B);

OCR1C = 255;

OCR1B = 0;

点滅= 0;

sei();



// dbg_puts(&HelloWorld);

(1)

{

// ir.codeがゼロでない場合、新しいコマンドを採用しました

if(ir.code)

{

//コードを文字列に変換します

//ultoa(ir.code、buf、16);

// dbg_puts(buf); //そしてポートへの出力

// ================================================= ===================

スイッチ(ir.code)

{

case 0x2880822a:blink = blink_delay; dbg_putchar( '1'); 休憩;

ケース0x8280282a:点滅= blink_delay; dbg_putchar( '2'); 休憩;

ケース0x8a0020aa:blink = blink_delay; dbg_putchar( '3'); 休憩;

case 0x0a00a0aa:blink = blink_delay; dbg_putchar( '4'); 休憩;

case 0x0280a82a:blink = blink_delay; dbg_putchar( '5'); 休憩;

ケース0x2a888022:点滅= blink_delay; dbg_putchar( '6'); 休憩;

case 0x0200a8aa:blink = blink_delay; dbg_putchar( '7'); 休憩;

case 0x0a80a02a:blink = blink_delay; dbg_putchar( '8'); 休憩;

ケース0x22888822:blink = blink_delay; dbg_putchar( '9'); 休憩;

ケース0x20888a22:blink = blink_delay; dbg_putchar( '0'); 休憩;

ケース0x0008aaa2:blink = blink_delay; dbg_putchar( 'O'); 休憩;

case 0x280882a2:blink = blink_delay; dbg_putchar( 'U'); 休憩;

ケース0x8880222a:点滅= blink_delay; dbg_putchar( 'D'); 休憩;

case 0x0808a2a2:blink = blink_delay; dbg_putchar( 'L'); 休憩;

case 0xa0080aa2:blink = blink_delay; dbg_putchar( 'R'); 休憩;

case 0x20088aa2:blink = blink_delay; dbg_putchar( '*'); 休憩;

case 0x220888a2:blink = blink_delay; dbg_putchar( '#'); 休憩;

デフォルト:break;

}

ir.code = 0;

// ================================================= =====================



}

}

}



最終的なスキームは次のとおりです。



タイマー回路



最初のバージョンは、ケースとしてプレキシガラスを使用してブレッドボードに組み立てられました。



集会



電源は地元の店で12V 500mAで最も簡単なものを買いました。



Pultik はebayで注文しました。



集会



結果は次のとおりです。



受信しました画像



タイマーは、割り当てられた時間について部門のスピーカーに知らせるために使用されます。



タイマーの使用



計画-stm32でリメイクし、1つのコントローラーに収まり、ケース内をより美しく配置します。



ご清聴ありがとうございました。



All Articles