ZynqでHDMI入力とVGA出力を試す
元旦だがZynqで画面の入出力について試したので書く。VGAに関しては自力で出力するものを作ったがDigilentが提供しているIPの存在を知ったのでこちらを使って実装した。
DigilentInc/vivado-library · GitHub
rgb2vga
vivado-library/rgb2vga_v1_0.pdf at master · DigilentInc/vivado-library · GitHub
ZYBOのVGA出力はR-2Rラダーで構成されているのでHSync, VSyncとRGBを作って上げれば動く。
入力はvid_ioというフォーマットらしい。詳細は確認するが基本はVGAと同じなのか。
dvi2rgb
vivado-library/dvi2rgb_v1_5.pdf at master · DigilentInc/vivado-library · GitHub
HDMIの入力に使用する。TMDS33のクロックとデータ、EDID用のI2Cとクロックを接続すればよかった。
あとDigilentのドキュメントに記載があるが、HDMI_OUT_ENを落とさないと入力として使えないようだ。
EDIDはEEPROM_8b.vhdで作っていた。元データはdgl_dvi_edid.txtから読み込んでいるようだった。
うまくいくかとおもいきやImplementationでクロック制約違反で落ちてしまっていて試行錯誤していたが、
FPGAの部屋 ZYBOのHDMI入力をVGA出力に出力する3(バグフィックス?)
の通りdvi2rgbの成約を変更したら無事に通って動作も確認できた。
次は画像処理かボード上の他機能(Ethernetあたり)を攻めたい
ZynqでVivado HLSで作ったIPからスイッチ入力させる
前回 ZynqでVerilogで作ったIPからLEDを光らせる - 今日やったこと でVerilogから言わばAXI-GPIOが実装できたので今度はVivado HLSからAXI-GPIOを作成してみる。
memcpyは以前実装したが、Directiveが両方S_AXIだったことなど理解に足りない部分があったので改めてという目論見である。
Vivado HLSでボタン入力を実装
sourceにtest_function.cを作成。ボタン入力を実装。
typedef unsigned long long u64; typedef unsigned char u8; void test_function(u64* src, u8* data_in){ *src = *data_in; }
Directive設定
test_functionをINTERFACE, s_axiliteに、srcをINTERFACE, s_axiliteに、data_inをINTERFACEに設定。
高位合成後、IPパッケージ化する。
Vivado HLSで読み込み
DiagramのProjectSettingからHLSで作成したプロジェクトを参照に追加
(myipは前回Verilogで作成したIP)
配線を作成
Synthesize後にピン配置を直す->BitStream作成->Export Hardware->Launch SDK
ソフトウェア処理
[デザイン名]_hardware_pratform_0のdriversに自作IPのドライバが用意されていて使い方もコメント記述されているようだ。
ここのドライバの関数を叩けばレジスタ直叩きを避けられるような記述のようなのでまた試す。(ビルドがうまく行かなかったので今回は省いた
xtest_function_hw.h
// AXILiteS // 0x00 : Control signals // bit 0 - ap_start (Read/Write/SC) // bit 1 - ap_done (Read/COR) // bit 2 - ap_idle (Read) // bit 3 - ap_ready (Read) // bit 7 - auto_restart (Read/Write) // others - reserved // 0x04 : Global Interrupt Enable Register // bit 0 - Global Interrupt Enable (Read/Write) // others - reserved // 0x08 : IP Interrupt Enable Register (Read/Write) // bit 0 - Channel 0 (ap_done) // others - reserved // 0x0c : IP Interrupt Status Register (Read/TOW) // bit 0 - Channel 0 (ap_done) // others - reserved // 0x10 : Data signal of src // bit 31~0 - src[31:0] (Read) // 0x14 : Data signal of src // bit 31~0 - src[63:32] (Read) // 0x18 : Control signal of src // bit 0 - src_ap_vld (Read/COR) // others - reserved // (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake) #define XTEST_FUNCTION_AXILITES_ADDR_AP_CTRL 0x00 #define XTEST_FUNCTION_AXILITES_ADDR_GIE 0x04 #define XTEST_FUNCTION_AXILITES_ADDR_IER 0x08 #define XTEST_FUNCTION_AXILITES_ADDR_ISR 0x0c #define XTEST_FUNCTION_AXILITES_ADDR_SRC_DATA 0x10 #define XTEST_FUNCTION_AXILITES_BITS_SRC_DATA 64 #define XTEST_FUNCTION_AXILITES_ADDR_SRC_CTRL 0x18
実行された後にsrcを読み出せばスイッチ入力が取れるはずなので、前回のコードを改変
unsigned int* sw_baseaddr = XPAR_TEST_FUNCTION_0_S_AXI_AXILITES_BASEADDR; int main() { while(1){ //Start HLS Function sw_baseaddr[0] |= 0x1; while(!(sw_baseaddr[0] & 0x4)); uint32_t sw_data = sw_baseaddr[4];//src[31:0] uint8_t pushsw_data = (sw_data & 0xf); uint8_t slidesw_data = ((sw_data >> 4) & 0xf); uint8_t led_data = pushsw_data ^ slidesw_data; Xil_Out8(XPAR_MYIP_0_0, led_data); xil_printf("push:%x, slide:%x, led:%x\r\n", pushsw_data, slidesw_data, led_data); } return 0; }
無事に今までと同じ動作が確認できた。もっとHLSで複雑な動作も試していく。
ZynqでVerilogで作ったIPからLEDを光らせる
前回 ZynqでAXI-GPIOとVivado HLSでのIP作成を試す - 今日やったこと の時はHLS及びAXI-GPIOでLEDを光らせていたのでVerilogからLEDを光らせる。
殆どのことは 3. Zynq LEDのIPコアを作り接続する – yuki-sato.com を参考にした。
myIPの雛形でAXIでの各タイミングの記述がある。AXIについて今一度理解をしていないと正確な制御を記述できないなと感じる。
axi_awaddrが0の時にslv_reg0が書かれているようなので、参考にしたページのledはベースアドレスに値を書いてやればいいようだ。
GenerateBitstreamした後、SDKで以前のLEDをxgpioでやっていた部分をXil_Outで置き換えた。
#include "xgpio.h" int main() { XGpio led,pushsw,slidesw; XGpio_Config led_config, pushsw_config, slidesw_config; //led_config.DeviceId = XPAR_GPIO_LED_DEVICE_ID; //XGpio_CfgInitialize(&led, &led_config, XPAR_GPIO_LED_BASEADDR); //XGpio_SetDataDirection(&led, 1,0); pushsw_config.DeviceId = XPAR_GPIO_PUSHSW_DEVICE_ID; XGpio_CfgInitialize(&pushsw, &pushsw_config, XPAR_GPIO_PUSHSW_BASEADDR); XGpio_SetDataDirection(&pushsw, 1, 1); slidesw_config.DeviceId = XPAR_GPIO_SLIDESW_DEVICE_ID; XGpio_CfgInitialize(&slidesw, &slidesw_config, XPAR_GPIO_SLIDESW_BASEADDR); XGpio_SetDataDirection(&slidesw, 1, 1); while(1){ uint8_t pushsw_data = XGpio_DiscreteRead(&pushsw, 1); uint8_t slidesw_data = XGpio_DiscreteRead(&slidesw, 1); uint8_t led_data = pushsw_data ^ slidesw_data; //XGpio_DiscreteWrite(&led, 1, led_data); Xil_Out8(XPAR_MYIP_0_0, led_data); xil_printf("push:%x, slide:%x, led:%x\r\n", pushsw_data, slidesw_data, led_data); } return 0; }
(xgpioもコードを覗いたら実質行っていることは同じだったようだ )
#define XGpio_WriteReg(BaseAddress, RegOffset, Data) \ XGpio_Out32((BaseAddress) + (RegOffset), (u32)(Data))
バーグラフネオン管IN-9を点灯(チョッパ昇圧)
バーグラフネオン管
http://www.die-wuestens.de/rd/IN9-2.pdf
2ページ目に特性のグラフがある。
バーの長さは0mA ~ 16mAで伸びきる、104V周辺で0~10mAが駆動できる模様
プログラム
制御系はCY8C4245AXI-483で全て行った。
結果
アバランシェ動作波形が確認できる。電流についてきちんとモニタした方がいいがそもそも手法を変えようと思う。
ゲートドライバを使ってスイッチング周波数を上げたりすれば良くなるだろうが、非絶縁で常用するのが少し怖いので車載向けのDC-ACを整流して使ったほうが安定141Vが生成できていいのではないかと考えている。今日はここまで。
ZynqでAXI-GPIOとVivado HLSでのIP作成を試す
Xilinx社のZynqで遊んで見る。今回はPSからLチカ出来ることが目標。ついでにボタン入力も。
並びにVivado HLSでのIP作成とそれを使用したデザインが動作するか、確認した。
開発フローについて
- Verilog or HLSでIPを作る
- VivadoでBlockDesignを作る(PSの設定やAXIのメモリマップはここで行う)
- VerilogにBlockDesignのラッパを吐き出す
- Synthesisする
- ピンアサインや制約を決定する。
- Implementationしてbitファイルを作成
ここまではMicroBlaze等を使っていなければ以前のSpartan6とかArtix7と大差なかった(IDEは違うが
ここからはソフト作成のための部分
どうにもうまく動かなくてやり直していたおかげて軽く雰囲気はつかめたので結果オーライとする。
PSの設定やConstrantsはボードを作ったメーカーが作ってくれているみたい(Zyboの場合はDigilentのサイトからダウンロードできる
AXI-GPIO
AXIーGPIOを作成、出力の幅と入出力を設定してAXIにぶら下げる。
Synthesis後にピンアサイン
xgpio.hにある関数で基本は制御できるようだ。
AXIにぶら下げたモジュールのアドレスはbspプロジェクトのxparameters.hにある。
#include <xil_printf.h> #include "xil_cache.h" #include "xgpio.h" typedef unsigned long long u64; unsigned int* memcpy_hls_baseaddr = XPAR_XAXIHP_MEMCPY_0_S_AXI_AXILITES_BASEADDR; int main(){ //Xil_DCacheDisable(); uint64_t i,j; XGpio led,slide_sw,push_sw; XGpio_Config led_config, slide_sw_config, push_sw_config; //led led_config.DeviceId = XPAR_AXI_GPIO_LED_DEVICE_ID; XGpio_CfgInitialize(&led, &led_config, XPAR_AXI_GPIO_LED_BASEADDR); XGpio_SetDataDirection(&led, 1, 0); //slide_sw slide_sw_config.DeviceId = XPAR_AXI_GPIO_SLIDE_SWITCH_DEVICE_ID; XGpio_CfgInitialize(&slide_sw, &slide_sw_config, XPAR_AXI_GPIO_SLIDE_SWITCH_BASEADDR); XGpio_SetDataDirection(&slide_sw, 1, 1); //push_sw push_sw_config.DeviceId = XPAR_AXI_GPIO_PUSH_SWITCH_DEVICE_ID; XGpio_CfgInitialize(&push_sw, &push_sw_config, XPAR_AXI_GPIO_PUSH_SWITCH_BASEADDR); XGpio_SetDataDirection(&push_sw, 1, 1); for(i = 0 ; ; ++i){ uint8_t slide_sw_data = XGpio_DiscreteRead(&slide_sw, 1); uint8_t push_sw_data = XGpio_DiscreteRead(&push_sw, 1); xil_printf("slide = %x push = %x\r\n", slide_sw_data, push_sw_data); XGpio_DiscreteWrite(&led, 1, slide_sw_data ^ push_sw_data); } return 0; }
何故かXGpio_SetDataDirectionで落ちる挙動に見舞われていたが、プロジェクトごと作りなおしたら直ってしまったため不明。判明したら再度書く。
Vivado HLS memcpy
PL側からDRAMにバースト転送したいと思った時にHLSだがアドレスを指定してmemcpyをする入門がslideshareにあったのでやった。非常にわかりやすくて良い。
www.slideshare.net
// AXILiteS // 0x00 : Control signals // bit 0 - ap_start (Read/Write/COH) // bit 1 - ap_done (Read/COR) // bit 2 - ap_idle (Read) // bit 3 - ap_ready (Read) // bit 7 - auto_restart (Read/Write) // others - reserved // 0x04 : Global Interrupt Enable Register // bit 0 - Global Interrupt Enable (Read/Write) // others - reserved // 0x08 : IP Interrupt Enable Register (Read/Write) // bit 0 - Channel 0 (ap_done) // bit 1 - Channel 1 (ap_ready) // others - reserved // 0x0c : IP Interrupt Status Register (Read/TOW) // bit 0 - Channel 0 (ap_done) // bit 1 - Channel 1 (ap_ready) // others - reserved // 0x10 : Data signal of axihp_in // bit 31~0 - axihp_in[31:0] (Read/Write) // 0x14 : reserved // 0x18 : Data signal of axihp_out // bit 31~0 - axihp_out[31:0] (Read/Write) // 0x1c : reserved // (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)
memcpy_hls_baseaddr[4] = src_data; memcpy_hls_baseaddr[6] = dst_data;
HLSが吐き出したx[モジュール名]_hw.hの中のファイルを読めばどう使えばいいのかは推測できるようだった。
次はverilogからAXI-GPIOやmemcpyと同等のことをやることと、デバッグやシミュレーション周りも確認しておきたい。
コード
#include <xil_printf.h> #include "xil_cache.h" #include "xgpio.h" typedef unsigned long long u64; unsigned int* memcpy_hls_baseaddr = XPAR_XAXIHP_MEMCPY_0_S_AXI_AXILITES_BASEADDR; int main(){ //Xil_DCacheDisable(); uint64_t i,j; XGpio led,slide_sw,push_sw; XGpio_Config led_config, slide_sw_config, push_sw_config; //led led_config.DeviceId = XPAR_AXI_GPIO_LED_DEVICE_ID; XGpio_CfgInitialize(&led, &led_config, XPAR_AXI_GPIO_LED_BASEADDR); XGpio_SetDataDirection(&led, 1, 0); //slide_sw slide_sw_config.DeviceId = XPAR_AXI_GPIO_SLIDE_SWITCH_DEVICE_ID; XGpio_CfgInitialize(&slide_sw, &slide_sw_config, XPAR_AXI_GPIO_SLIDE_SWITCH_BASEADDR); XGpio_SetDataDirection(&slide_sw, 1, 1); //push_sw push_sw_config.DeviceId = XPAR_AXI_GPIO_PUSH_SWITCH_DEVICE_ID; XGpio_CfgInitialize(&push_sw, &push_sw_config, XPAR_AXI_GPIO_PUSH_SWITCH_BASEADDR); XGpio_SetDataDirection(&push_sw, 1, 1); int is_mismatch = 0x0; volatile u64 src_data[256], dst_data[256]; //initialize for(i = 0 ; i < 256 ; ++i){ src_data[i] = i; } //axihp_memcpy memcpy_hls_baseaddr[4] = src_data; memcpy_hls_baseaddr[6] = dst_data; //ap idle if(memcpy_hls_baseaddr[0] & 0x4){ xil_printf("memcpy start\r\n"); memcpy_hls_baseaddr[0] |= 0x1;//ap_start } //wait for ap_idle assert while(!(memcpy_hls_baseaddr[0] & 0x4)); //validate for(i = 0 ; i < 256 ; ++i){ xil_printf("src_data[%d] = %d dst_data[%d] = %d\r\n", i, src_data[i], i, dst_data[i]); if(src_data[i] != dst_data[i]) is_mismatch = 0x1; } xil_printf("memcpy %s\r\n", is_mismatch ? "fail" : "success"); for(i = 0 ; ; ++i){ uint8_t slide_sw_data = XGpio_DiscreteRead(&slide_sw, 1); uint8_t push_sw_data = XGpio_DiscreteRead(&push_sw, 1); xil_printf("slide = %x push = %x\r\n", slide_sw_data, push_sw_data); XGpio_DiscreteWrite(&led, 1, slide_sw_data ^ push_sw_data); } return 0; }
自作楽器用音源モジュール作成(8)
2byteメッセージと、UARTが混ざらないようにちょっとプログラムを改良した。
ボタンやボリュームもつけて設定を用意しようと思ったが現時点では設定項目は皆無なのでひとまずこれで使うことにする。
#include <project.h> #define MIDI_DATA_SIZE 8 #define UART_CH 3 uint8_t midiDataPtrOld[UART_CH] = {}; uint8_t midiDataPtr[UART_CH] = {}; uint8_t midiDataFrom = 0x0; uint8_t midiData[UART_CH][MIDI_DATA_SIZE] = {}; volatile uint8_t isSetupMode = 0x0; volatile uint8_t isPushOk = 0x0; CY_ISR(ok_button_pushed){ } CY_ISR(cancel_button_pushed){ } uint16_t getVolumeData(){ ADC_SAR_Seq_1_StartConvert(); uint16 data = ADC_SAR_Seq_1_GetResult16(0); if(data == 0xff) return 0x7ff; else return 0x7ff - data; } void uartMidiBypass(void) { uint8_t i,data; data = (uint8_t)UART_1_UartGetChar(); if(data != 0){ if(data & 0x80){ midiDataPtr[1] = 0x0; midiDataFrom = 0x1; } if(midiDataPtr[1] < MIDI_DATA_SIZE) midiData[1][midiDataPtr[1]++] = data; } data = (uint8_t)UART_2_UartGetChar(); if(data != 0){ if(data & 0x80){ midiDataPtr[2] = 0x0; midiDataFrom = 0x2; } if(midiDataPtr[2] < MIDI_DATA_SIZE) midiData[2][midiDataPtr[2]++] = data; } for(i = 0 ; i < UART_CH ; ++i){ if(midiDataPtrOld[i] != midiDataPtr[i]){ midiDataPtrOld[i] = midiDataPtr[i]; if(midiDataPtr[i] == 2){ if(((midiData[i][0] & 0xc0) == 0xc0) || ((midiData[i][0] & 0xc0) == 0xc0)){ UART_VS1053b_PutArray(midiData[i], 3); midiDataPtr[i] = 0; } } else if(midiDataPtr[i] == 3){ UART_VS1053b_PutArray(midiData[i], 3); midiDataPtr[i] = 0; if((midiData[i][0] & 0x90) == 0x90 || (midiData[i][0] & 0x80) == 0x80) { LCD_Char_1_WriteControl(LCD_Char_1_CLEAR_DISPLAY); LCD_Char_1_Position(0,0); LCD_Char_1_PrintString("Note:"); LCD_Char_1_PrintNumber(midiData[i][1]); LCD_Char_1_PrintString(" "); LCD_Char_1_PrintNumber(midiData[i][2]); LCD_Char_1_DrawHorizontalBG(1,0,16,midiData[i][2]); } else { LCD_Char_1_WriteControl(LCD_Char_1_CLEAR_DISPLAY); LCD_Char_1_Position(0,0); LCD_Char_1_PrintNumber(midiData[i][0]); LCD_Char_1_PrintString(" "); LCD_Char_1_PrintNumber(midiData[i][1]); LCD_Char_1_PrintString(" "); LCD_Char_1_PrintNumber(midiData[i][2]); } } } } } void setupControl(char* name, uint8_t menuCount, char* menuStr[], void (*pushed)(uint8_t)){ LCD_Char_1_Position(0,0); LCD_Char_1_PrintString(name); uint8 select = getVolumeData() / (0x800 / menuCount + 1); LCD_Char_1_Position(1,0); if(menuStr != NULL) LCD_Char_1_PrintNumber(menuStr[select]); else LCD_Char_1_PrintNumber(select); if(isPushOk) { pushed(select); } } int main() { CyDelay(1000); UART_VS1053b_Start(); UART_1_Start(); UART_2_Start(); LCD_Char_1_Start(); LCD_Char_1_Position(0,0); LCD_Char_1_PrintString("MIDI Sound Source"); ADC_SAR_Seq_1_Start(); isr_Button_1_StartEx(cancel_button_pushed); isr_Button_2_StartEx(ok_button_pushed); CyGlobalIntEnable; while(1) { uartMidiBypass(); } }
自作楽器用音源モジュール作成(7)
今回はUARTを受信してスルーする部分と、内容を読み取ってLCDに表示する(今回はNoteOn,NoteOffのみ)を実装した。
LCDのカスタムフォントでバーグラフが表示できるのでNoteの3Byte目を読み取って表示するようにしてみた。
#include <project.h> #define MIDI_DATA_SIZE 8 int main() { uint8_t i,data; uint8_t midiDataPtrOld = 0x0; uint8_t midiDataPtr = 0x0; uint8_t midiDataFrom = 0x0; uint8_t midiData[MIDI_DATA_SIZE]; CyDelay(1000); UART_VS1053b_Start(); UART_1_Start(); UART_2_Start(); LCD_Char_1_Start(); CyGlobalIntEnable; while(1) { data = (uint8_t)UART_1_UartGetChar(); if(data != 0){ if(data & 0x80){ midiDataPtr = 0x0; midiDataFrom = 0x1; } if(midiDataPtr < MIDI_DATA_SIZE) midiData[midiDataPtr++] = data; } data = (uint8_t)UART_2_UartGetChar(); if(data != 0){ if(data & 0x80){ midiDataPtr = 0x0; midiDataFrom = 0x2; } if(midiDataPtr < MIDI_DATA_SIZE) midiData[midiDataPtr++] = data; } if(midiDataPtrOld != midiDataPtr){ midiDataPtrOld = midiDataPtr; if(midiDataPtr == 3 && (midiData[0] & 0x90 || midiData[0] & 0x80)) { UART_VS1053b_PutArray(midiData,3); midiDataPtr = 0; LCD_Char_1_WriteControl(LCD_Char_1_CLEAR_DISPLAY); LCD_Char_1_Position(0,0); LCD_Char_1_PrintString("Note:"); LCD_Char_1_PrintNumber(midiData[1]); LCD_Char_1_PrintString(" "); LCD_Char_1_PrintNumber(midiData[2]); LCD_Char_1_DrawHorizontalBG(1,0,16,midiData[2]); } } } }
動作確認のUARTは、PCからC#プログラムでMIDIメッセージを投げつけてFTD2232で送出した。*1
using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var uart = new SerialPort("com13", 31250, Parity.None, 8, StopBits.One); uart.Open(); var midiData = new byte[3]; while (true) { for (int j = 0; j < 0x7f; ++j) { /* Cn:Program Change */ midiData[0] = 0xc0; midiData[1] = (byte)j; uart.Write(midiData, 0, 3); for (int i = 0x40; i < 0x50; ++i) { for (int k = 0; k < 0x7f; k += 0x10) { /* 9n:Note On */ midiData[0] = 0x90; midiData[1] = (byte)i; midiData[2] = (byte)k; uart.Write(midiData, 0, 3); Console.WriteLine(i); Thread.Sleep(100); /* 8n:Note Off*/ midiData[0] = 0x80; midiData[2] = 0x0; uart.Write(midiData, 0, 3); } } } } } } }
残りはメッセージを真面目に読み取ったり、音色変更等を本体から行う機能をつければよし。