#はじめに
制御工学をやっているのでリアルタイム制御ができるコントローラを作りたいと思いました。
##時間管理の方法
色々と時間を管理する方法はあるのですが,できるだけ軽い処理で時間管理をしたいと思いました。サイクルカウンタを監視するビジーループなどはしたくありません。外側にタイマでも設置して制御周期を管理するタイマでも作った方が良いのではないかと思い,タイマを作ってIRQ_F2Pに繋げてUIOの割り込みを使ってみました。
##割り込みタイマのIP
AXI4 PeripheralのIPを作りました。ユーザ回路の所に以下のような回路を書いてみました。irq_psがタイマの出力で,slv_reg2を時間周期を決めるレジスタにしました。clkはAXI_ACLOCKと繋げており,100 MHzになっています。
reg [31:0] irqgen_cnt; initial irqgen_cnt=32'b0;
wire irq_ps_in;
wire ena_irq;
assign ena_irq=slv_reg1[1];
assign irq_ps=(ena_irq==1'b1)? irq_ps_in:1'b0;
assign irq_ps_in=(irqgen_cnt<32'd100)? 1'b1:1'b0;
always@(posedge clk)
begin
irqgen_cnt<=(irqgen_cnt==slv_reg2)? 24'b0:irqgen_cnt+24'b1;
end
このIPの出力とZYNQ Processing sysgtemのブロックのIRQ_F2Pを繋ぎました。今回は上記のタイマをDAC用IP(https://qiita.com/tvrcw/items/61c0cce9a9c8e843def4 )に入れています。
Bitstreamファイルを出力してLinuxに渡しました。
##Linuxでの操作
Linux環境は@ikwzm様配布のものです。この環境下でUIOの割り込みが使えるようにします。Device Tree Overlayを使用するので,"/plugin/;"を足す必要があります。(https://qiita.com/ikwzm/items/03d518b7c46d1ee49943)
/dts-v1/;/plugin/;
/ {
fragment@0 {
target-path = "/amba/fpga-region0";
__overlay__ {
#address-cells = <0x1>;
#size-cells = <0x1>;
firmware-name = "IRQ.bin";
irq-uio@43c10000 {
compatible = "generic-uio";
reg = <0x43c00000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <0 29 4>;
};
};
} ;
} ;
"interrupts"を"interrupt"と間違えると動かないので注意が必要です。IRQ_F2P[0]は割り込み番号61でARMのGICは32引くそうなので29を指定しています。
今回,50 us, 100 us, 200 usの制御周期の管理をしようと思います。時間計測する用にCでプログラムを書きました。途中にDACに関する記述があります。綺麗な正弦波が出れば良いなと思います。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
int uio_irq_on(int uio_fd){
unsigned int irq_on=1;
return write(uio_fd,&irq_on,sizeof(irq_on));
}
int uio_wait_irq(int uio_fd){
unsigned int ret=0;
return read(uio_fd,&ret,sizeof(ret));
}
int main()
{
int uio_fd;
if((uio_fd=open("/dev/uio0",O_RDWR))==-1){
printf("Can not open /dev/uio0\n"); exit(1);
}
volatile unsigned int* map_addr;
map_addr=(unsigned int*)mmap(NULL,0x1000,PROT_READ|PROT_WRITE,MAP_SHARED,uio_fd,0);
int ena=0x0;
ena|=(1<<0);
ena|=(1<<1);
// map_addr[2]=0x00004e1f; //200us
map_addr[2]=0x0000270f; //100us
// map_addr[2]=0x00001387; //50us
map_addr[1]=ena; //enable DAC
unsigned int d=0.0;
volatile double wave=0.0;
const double bitweight=20.0/pow(2,16);
const double midscale=pow(2,15);
volatile double t=0.0;
const double Ts=1e-4;
double logt[10000]={0.0};
struct timespec st,ed;
struct timespec stx,edx;
clock_gettime(CLOCK_MONOTONIC,&stx);
clock_gettime(CLOCK_MONOTONIC,&st);
wave=1;
for(int i=0;i<1e4;i++){
if(uio_irq_on(uio_fd)==-1){ printf("error1\n");break;};
if(uio_wait_irq(uio_fd)==-1){printf("error2\n");break;};
wave=4.0*sin(100.0*2.0*M_PI*t);
d=wave/bitweight+midscale;
map_addr[0]=d;
clock_gettime(CLOCK_MONOTONIC,&ed);
logt[i]=(ed.tv_nsec-st.tv_nsec)*1e-9;
st=ed;
t+=Ts;
}
clock_gettime(CLOCK_MONOTONIC,&edx);
printf("%lf %lf\n",(edx.tv_sec-stx.tv_sec)+(edx.tv_nsec-stx.tv_nsec)*1e-9,t);
FILE *lt;
lt=fopen("sampling.dat","w");
for(int i=0;i<1e4;i++) fprintf(lt,"%lf\n",logt[i]);
fclose(lt);
map_addr[1]=0;
munmap((void*)map_addr, 0x1000);
close(uio_fd);
return 0;
}
上から50 us, 100 us, 200 usのタイマ出力から割り込みをかけた時の周期です。
現状ではあまり安定していないのですが,100 usくらいなら使えるかなといった感じです。改良したら使い物になりそうなので期待ができました。
周期100us,振幅4V,周波数100Hzの正弦波を生成して見た所,安定して出力されていました。簡単な試験くらいなら今からでも使えそうな気がします。
ZYBOでも全然いけると思いましたが,100 usより早くしたいとも思います。全然知らないのですがZYNQ MPSoCのCortex-R5を使ったら割り込み処理が安定するのでしょうか。