カスタムコンポーネントのDMA定義ファイル(*.cydmacap)のLocationタグで「direction="destination"」を指定すると、CPUのメモリー領域からデータパスへのDMAを使った転送が行えるようになります。
私が調べた限り、データパス(ペリフェラル)への転送は最大16bitまでで、32bitはサポートされていないようなので、ここでは8bit DMAの例を紹介します。
DMA転送ではFIFOバッファを使うことが推奨されています。しかしながら、FIFOバッファからのAレジスタへのコピーで1サイクルが使用されるため、LEDの点滅など非常に低い周期での出力となると、タイミングがずれる原因となります。ここではその解決法の一例として、データパスレジスタを制御する高速なクロックと、GPIOの出力を制御する低速なクロックを分けて実装しています。
led_out.v
`include "cypress.v"
module led_out (
input clock,
input sync,
output led_out,
output dma
);
localparam PG_IDLE = 3'b000;
localparam PG_COPY = 3'b001;
localparam PG_SHIFT = 3'b010;
localparam PG_WAIT = 3'b011;
reg[2:0] pg_state;
reg[3:0] bit_count;
wire f0_not_full;
wire f0_is_empty;
reg sync_edge;
reg shift_out;
reg led_out_reg;
always @(posedge clock)
begin
case(pg_state)
PG_IDLE:
begin
if(~f0_is_empty)
pg_state <= PG_COPY;
end
PG_COPY:
begin
bit_count <= 4'd0;
pg_state <= PG_SHIFT;
sync_edge <= sync;
end
PG_SHIFT:
begin
led_out_reg <= shift_out;
bit_count <= bit_count + 4'd1;
pg_state <= (bit_count[3]) ? PG_IDLE : PG_WAIT;
end
PG_WAIT:
begin
if(sync_edge != sync)
begin
sync_edge <= sync;
pg_state <= PG_SHIFT;
end
end
endcase
end
assign led_out = led_out_reg;
assign dma = f0_not_full;
cy_psoc3_dp8 #(.cy_dpconfig_a(
{
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM0:IDLE*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC___F0, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM1:A0<=F0*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP___SL, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM2:SHIFT A0*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM3:*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM4:*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM5:*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM6:*/
`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
`CS_CMP_SEL_CFGA, /*CFGRAM7:*/
8'hFF, 8'h00, /*CFG9:*/
8'hFF, 8'hFF, /*CFG11-10:*/
`SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH,
`SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL,
`SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI,
`SC_SI_A_DEFSI, /*CFG13-12:*/
`SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0,
1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS,
`SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN,
`SC_FB_NOCHN, `SC_CMP1_NOCHN,
`SC_CMP0_NOCHN, /*CFG15-14:*/
10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX,
`SC_FIFO_LEVEL,`SC_FIFO_ASYNC,`SC_EXTCRC_DSBL,
`SC_WRK16CAT_DSBL /*CFG17-16:*/
}
)) dp(
.reset(1'b0),
.clk(clock),
.cs_addr(pg_state),
.route_si(1'b0),
.route_ci(1'b0),
.f0_load(1'b0),
.f1_load(1'b0),
.d0_load(1'b0),
.d1_load(1'b0),
.so(shift_out),
.f0_bus_stat(f0_not_full),
.f0_blk_stat(f0_is_empty)
);
endmodule
FIFO入力時において、バッファが満杯でないときは、データパスのf0_bus_stat値がHighになります。そこで、dma出力にこの値を紐付けることで、DMAによる転送が完了したタイミングを伝えています。A0レジスタにコピーしてからは、8bit分シフトアウトを実行し、はみ出した値はGPIOにそのまま出力されます。
PSoCではIndexed DMAという手法によって、複数のTDを連結させることができ、待機中のTD領域にデータを書き込んでいくことでダブルバッファとして機能します。「Number of TDs」の値を変更して、複数のDMAデータを設定してみましょう。
FIFOバッファに空きが出るごとに配置したInterruptコンポーネントを介して割り込みが発生します。この割り込み関数内でCyDmaChStatusで取得できる値が現在のTDの保有するIDなので、待機中となっているもう片方のバッファに任意のデータをコピーします。
main.c
#include "project.h"
uint8_t send_buffer_pos;
uint8_t send_buffer[2][24];
const uint8_t send_buffer_data[] = {
0b11111111, 0b00000000, 0b11111111, 0b00000000,
0b11110000, 0b11110000, 0b11110000, 0b11110000,
0b11001100, 0b11001100, 0b11001100, 0b11001100,
0b10101010, 0b10101010, 0b10101010, 0b10101010,
0b11001100, 0b11001100, 0b11001100, 0b11001100,
0b11110000, 0b11110000, 0b11110000, 0b11110000,
};
/* Defines for led_dma */
#define led_dma_BYTES_PER_BURST 1
#define led_dma_REQUEST_PER_BURST 1
#define led_dma_SRC_BASE (CYDEV_SRAM_BASE)
#define led_dma_DST_BASE (CYDEV_PERIPH_BASE)
/* Variable declarations for led_dma */
/* Move these variable declarations to the top of the function */
uint8 led_dma_Chan;
uint8 led_dma_TD[2];
void InitDma()
{
/* DMA Configuration for led_dma */
led_dma_Chan = led_dma_DmaInitialize(led_dma_BYTES_PER_BURST, led_dma_REQUEST_PER_BURST,
HI16(led_dma_SRC_BASE), HI16(led_dma_DST_BASE));
led_dma_TD[0] = CyDmaTdAllocate();
led_dma_TD[1] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(led_dma_TD[0], 24, led_dma_TD[1], led_dma__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
CyDmaTdSetConfiguration(led_dma_TD[1], 24, led_dma_TD[0], led_dma__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
CyDmaTdSetAddress(led_dma_TD[0], LO16((uint32)send_buffer[0]), LO16((uint32)led_out_dma_ptr));
CyDmaTdSetAddress(led_dma_TD[1], LO16((uint32)send_buffer[1]), LO16((uint32)led_out_dma_ptr));
CyDmaChSetInitialTd(led_dma_Chan, led_dma_TD[0]);
CyDmaChEnable(led_dma_Chan, 1);
}
void CopyDmaBuffer()
{
uint8_t cid;
CyDmaChStatus(led_dma_Chan, &cid, NULL);
uint8_t bpos = (cid == led_dma_TD[1]) ? 0 : 1;
memcpy(send_buffer[bpos], send_buffer_data, 24);
}
CY_ISR(OnInterruptLed)
{
CopyDmaBuffer();
}
int main(void)
{
CyGlobalIntEnable;
memcpy(send_buffer[0], send_buffer_data, 24);
memcpy(send_buffer[1], send_buffer_data, 24);
InitDma();
led_dma_isr_StartEx(OnInterruptLed);
for(;;)
{
}
}