- 2025-02-21
-
回复了主题帖:
「当幸狐来敲门」适配Alpine Linux上篇--适配了Alpine Linux有什么好处
ospider 发表于 2025-2-20 16:11
请问这个是怎么适配的 alpine 的, 可以出个教程吗
https://bbs.eeworld.com.cn/thread-1259967-1-1.html
看这篇文章
- 2025-02-18
-
回复了主题帖:
DC12V电瓶输出电路保护电路
去搜搜TI的电子保险丝方案,里面也有功能框图可以学习下,其实就是NMOS加一个电流检测电路,电流过大就关闭回路。
-
加入了学习《直播回放: Nexperia 理想二极管与负载开关,保障物联网的稳健高效运行》,观看 Nexperia 理想二极管与负载开关,保障物联网的稳健高效运行
- 2025-02-16
-
发表了主题帖:
「Tang Primer 25K 测评」4、使用FPGA驱动WS2812
其实网络上有很多FPGA驱动WS2812的代码,这里,我们使用蹄佬的代码(抄代码和看懂代码也是一种学习),直接驱动WS2812。
蹄佬项目链接https://github.com/BigPig-Bro/Gowin/tree/master/TangPrimer_20k
驱动WS2812的要点:
驱动WS2812的关键是按它的通信时序(见下图),把对应的数据输出到灯的DI引脚上。然后它的
这里可以看到几个关键的内容,0码和1码的高低电平时间,以及RES信号的持续时间。
这部分在代码里可以设置好
parameter DELAY_1_HIGH = (CLK_FRE / 1_000_000 * 0.70 ) - 1; //≈580ns±150ns 1 高电平时间
parameter DELAY_1_LOW = (CLK_FRE / 1_000_000 * 0.40 ) - 1; //≈280ns±150ns 1 低电平时间
parameter DELAY_0_HIGH = (CLK_FRE / 1_000_000 * 0.40 ) - 1; //≈280ns±150ns 0 高电平时间
parameter DELAY_0_LOW = (CLK_FRE / 1_000_000 * 0.70 ) - 1; //≈580ns±150ns 0 低电平时间
parameter DELAY_RESET = (CLK_FRE / 20 ) - 1; //100ms 复位时间 >50us
然后这里可以设置LED灯的数量和CLK输入信号的频率,咱们板子上用的E2信号引脚上输入的50MHz频率,所以设置好就行。
parameter WS2812_NUM = 16 - 1 ; // WS2812的LED数量(1从0开始)
parameter WS2812_WIDTH = 24 ; // WS2812的数据位宽
parameter CLK_FRE = 50_000_000 ; // CLK的频率(mHZ)
然后后面的代码就是状态机的详细实现了,这一行就是修改颜色的代码,DELAY_RESET参数,不但是修改颜色的延时设置,也一样是每次终止数据输出的延时时间。
WS2812_data <= {WS2812_data[22:0],WS2812_data[23]};//颜色移位循环显示
完整代码如下:
//功能描述:驱动 WS2812_NUM 个的WS2812一起渐变亮
module top (
input clk, //输入 时钟源
output reg WS2812_DI //输出到WS2812的接口
);
parameter WS2812_NUM = 16 - 1 ; // WS2812的LED数量(1从0开始)
parameter WS2812_WIDTH = 24 ; // WS2812的数据位宽
parameter CLK_FRE = 50_000_000 ; // CLK的频率(mHZ)
parameter DELAY_1_HIGH = (CLK_FRE / 1_000_000 * 0.70 ) - 1; //≈580ns±150ns 1 高电平时间
parameter DELAY_1_LOW = (CLK_FRE / 1_000_000 * 0.40 ) - 1; //≈280ns±150ns 1 低电平时间
parameter DELAY_0_HIGH = (CLK_FRE / 1_000_000 * 0.40 ) - 1; //≈280ns±150ns 0 高电平时间
parameter DELAY_0_LOW = (CLK_FRE / 1_000_000 * 0.70 ) - 1; //≈580ns±150ns 0 低电平时间
parameter DELAY_RESET = (CLK_FRE / 20 ) - 1; //100ms 复位时间 >50us
parameter RESET = 0; //状态机声明
parameter DATA_SEND = 1;
parameter BIT_SEND_HIGH = 2;
parameter BIT_SEND_LOW = 3;
reg [ 1:0] state = 0/* synthesis preserve */; //主状态机控制
reg [ 4:0] bit_send = 0; //数据数量发送控制
reg [ 4:0] data_send = 0; //数据位发送控制
reg [31:0] clk_delay = 0; //延时控制
reg [23:0] WS2812_data = 24'd1; // WS2812的颜色数据(初始淡蓝)
always@(posedge clk)
case (state)
RESET:begin
WS2812_DI <= 0;
if (clk_delay < DELAY_RESET)
clk_delay <= clk_delay + 1;
else begin
clk_delay <= 0;
WS2812_data <= {WS2812_data[22:0],WS2812_data[23]};//颜色移位循环显示
state <= DATA_SEND;
end
end
DATA_SEND:
if (data_send == WS2812_NUM && bit_send == WS2812_WIDTH)begin
data_send <= 0;
bit_send <= 0;
state <= RESET;
end
else if (bit_send < WS2812_WIDTH) begin
state <= BIT_SEND_HIGH;
end
else begin// if (bit_send == WS2812_WIDTH)
data_send <= data_send + 1;
bit_send <= 0;
state <= BIT_SEND_HIGH;
end
BIT_SEND_HIGH:begin
WS2812_DI <= 1;
if (WS2812_data[bit_send])
if (clk_delay < DELAY_1_HIGH)
clk_delay <= clk_delay + 1;
else begin
clk_delay <= 0;
state <= BIT_SEND_LOW;
end
else
if (clk_delay < DELAY_0_HIGH)
clk_delay <= clk_delay + 1;
else begin
clk_delay <= 0;
state <= BIT_SEND_LOW;
end
end
BIT_SEND_LOW:begin
WS2812_DI <= 0;
if (WS2812_data[bit_send])
if (clk_delay < DELAY_1_LOW)
clk_delay <= clk_delay + 1;
else begin
clk_delay <= 0;
bit_send <= bit_send + 1;
state <= DATA_SEND;
end
else
if (clk_delay < DELAY_0_LOW)
clk_delay <= clk_delay + 1;
else begin
clk_delay <= 0;
bit_send <= bit_send + 1;
state <= DATA_SEND;
end
end
endcase
endmodule
引脚约束修改成以下的内容
IO_LOC "clk" E2;
IO_LOC "WS2812_DI" L5;
IO_PORT "clk" IO_TYPE=LVCMOS33;
IO_PORT "WS2812_DI" IO_TYPE=LVCMOS33;
然后综合布线,烧录到板子上,就可以看到效果啦。
实际上,我们在设计一个WS2812外设时,应该考虑更复杂的东西,例如输入缓冲区,可以设置LED的数量(作为参数),现在就先按这个提交了,以后再考虑自己实现一个复杂一些的版本。
演示视频如下
[localvideo]e46943c7410acf98f9bf734d0bd28008[/localvideo]
- 2025-02-15
-
发表了主题帖:
「Tang Primer 25K 测评」3、点亮数码管
数码管驱动原理
数码管的驱动原理如下图,实际上就是多个LED封装在数码管里,一般分共阴和共阳两种。现在很多的小家电都使用了数码管,用来显示数字、时间等等东西。
大家平时在做MCU应用的时候,用的比较少,现在可以顺便学一下如何驱动数码管。
sipeed有提供各个PMOD扩展板的原理图,数码管的原理图如下图:
这里可以看到其中一个数码管是通过PIN06引脚供电的,按3.3V和限流电阻3.3K计算,每个灯的电流其实都很小,如果我们想修改板子的限流电阻,必须注意总电流不能超过IO的输出能力。(见规格书的引脚电平部分说明)
我们在使用MCU或者FPGA的时候,很多小白都没注意引脚具体的驱动能力是多大。大部分时候,我们应该认真查看规格书,不要超出引脚的输出能力。如果需要的电流比较大,一定要把引脚当做信号控制引脚,然后控制外部的三极管或者MOS等等设备去控制。
代码分析
我们打开官方的pmod_digitalTube-2bit目录,可以看到项目的目录结构如下图所示。
我们先看一下数码管显示功能:
module driver_DigitalTube #(
parameter P_CNT = 'd300_000 //Scanning Period for Dynamic Drive of the Seven-Segment Display
)(
input i_clk ,
input i_rst ,
input [3:0] i_add ,
output [6:0] o_digitalTube ,//ABCDEFG
output o_sel
);
这段代码定义了以下内容
扫描周期:通过 P_CNT 参数定义的计数值,控制扫描操作的周期。模块会在时钟信号的控制下,定期切换到不同的数码管位,从而实现动态扫描。同时该扫描周期也是处理按键加法的计算周期。
3个输入信号(clk,rst,add按钮数组):其中clk信号是从顶层模块输入的50MHz时钟,rst是复位按钮,add按钮数组就是PMOD按键模组上的四个按键。
2个输出信号(o_digitalTube,o_sel):其中o_digitalTube就是对应点钟的笔画,然后o_sel是选择哪个数码管字的控制信号。
/***************parameter*************/
localparam P_0 = 7'b0000001 ;
localparam P_1 = 7'b1111001 ;
localparam P_2 = 7'b0010010 ;
localparam P_3 = 7'b0110000 ;
localparam P_4 = 7'b1101000 ;
localparam P_5 = 7'b0100100 ;
localparam P_6 = 7'b0000100 ;
localparam P_7 = 7'b1110001 ;
localparam P_8 = 7'b0000000 ;
localparam P_9 = 7'b0100000 ;
localparam P_X = 7'b1111111 ; //Truth Table for Displaying 0-9 on the Seven-Segment Display and for Blank Display
这里定义了数码管显示的真值表,7位全输出1则显示空,对应位输出0则点亮对应字段,例如0就是0000001,除了中间的,其他都点亮。
如果是共阴的数码管,则应该反过来。
/***************reg*******************/
reg [23:0] r_cnt = 0;
reg [3:0] r_add = 0;
reg [3:0] r_add1d = 0;
reg [3:0] r_addBuffer = 0;
reg [4:0] r_cntOnes = 0;
reg [4:0] r_cntTens = 0;
reg [6:0] r_digitalTubeOnes = 0;
reg [6:0] r_digitalTubeTens = 0;
reg ro_sel = 0;
/***************assign****************/
assign o_sel = ro_sel ;
assign w_posedge0 = r_add[0] & !r_add1d[0];
assign w_posedge1 = r_add[1] & !r_add1d[1];
assign w_posedge2 = r_add[2] & !r_add1d[2];
assign w_posedge3 = r_add[3] & !r_add1d[3]; //Detecting Rising Edges from 4 Button Modules
assign o_digitalTube = ro_sel ? r_digitalTubeTens : r_digitalTubeOnes;
然后接下来的代码,是定义了几个寄存器,以及一些赋值操作。如果是将寄存器赋值给输出信号,那就是寄存器的值会直接被输出信号输出,其他的则是使用逻辑门直接计算对应的值。
//——————<r_cnt>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_cnt <= 0;
else if(r_cnt == P_CNT)
r_cnt <= 0;
else
r_cnt <= r_cnt+1;
end
//——————ro_sel—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
ro_sel <= 0 ;
else if(r_cnt == P_CNT)
ro_sel <= ~ro_sel ;
else
ro_sel <= ro_sel ;
end
这里是计数器自增和数码管扫描部分,每计数300000次切换个位的数码管和十位的数码管显示。
//——————<r_add>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_add <= 0;
else
r_add <= i_add;
end
//——————<r_add1d>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_add1d <= 0;
else
r_add1d <= r_add;
end
//——————<r_addBuffer[0]>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_addBuffer[0] <= 0 ;
else if(r_cnt == P_CNT)
r_addBuffer[0] <= 0 ;
else if(w_posedge0)
r_addBuffer[0] <= 1 ;
else
r_addBuffer[0] <= r_addBuffer[0];
end
//——————<r_addBuffer[1]>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_addBuffer[1] <= 0 ;
else if(r_cnt == P_CNT)
r_addBuffer[1] <= 0 ;
else if(w_posedge1)
r_addBuffer[1] <= 1 ;
else
r_addBuffer[1] <= r_addBuffer[1];
end
//——————<r_addBuffer[2]>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_addBuffer[2] <= 0 ;
else if(r_cnt == P_CNT)
r_addBuffer[2] <= 0 ;
else if(w_posedge2)
r_addBuffer[2] <= 1 ;
else
r_addBuffer[2] <= r_addBuffer[2];
end
//——————<r_addBuffer[3]>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_addBuffer[3] <= 0 ;
else if(r_cnt == P_CNT)
r_addBuffer[3] <= 0 ;
else if(w_posedge3)
r_addBuffer[3] <= 1 ;
else
r_addBuffer[3] <= r_addBuffer[3];
end
//——————<r_cntOnes>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_cntOnes <= 0;
else if((r_cnt == P_CNT) & ((r_cntOnes + r_addBuffer) > 5'd9))
r_cntOnes <= r_cntOnes + r_addBuffer - 5'd10;
else if(r_cnt == P_CNT)
r_cntOnes <= r_cntOnes + r_addBuffer;
else
r_cntOnes <= r_cntOnes;
end
//——————<r_cntTens>—————————————//
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_cntTens <= 0;
else if((r_cnt == P_CNT) & ((r_cntOnes + r_addBuffer) > 5'd9) & (r_cntTens == 5'd9))
r_cntTens <= 0 ;
else if((r_cnt == P_CNT) & ((r_cntOnes + r_addBuffer) > 5'd9))
r_cntTens <= r_cntTens + 1;
else
r_cntTens <= r_cntTens;
end
这里的一大串代码,则是负责计算个位和十位的加法,这里的功能是分别是4个按钮对应加1,加2,加4,加8功能。
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_digitalTubeOnes <= 7'b0;
else if(r_cnt == P_CNT)
case(r_cntOnes[3:0])
4'd0 : r_digitalTubeOnes <= P_0 ;
4'd1 : r_digitalTubeOnes <= P_1 ;
4'd2 : r_digitalTubeOnes <= P_2 ;
4'd3 : r_digitalTubeOnes <= P_3 ;
4'd4 : r_digitalTubeOnes <= P_4 ;
4'd5 : r_digitalTubeOnes <= P_5 ;
4'd6 : r_digitalTubeOnes <= P_6 ;
4'd7 : r_digitalTubeOnes <= P_7 ;
4'd8 : r_digitalTubeOnes <= P_8 ;
4'd9 : r_digitalTubeOnes <= P_9 ;
default : r_digitalTubeOnes <= r_digitalTubeOnes ;
endcase
else
r_digitalTubeOnes <= r_digitalTubeOnes;
end
//——————<r_digitalTubeTens>—————————————/
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_digitalTubeTens <= 7'b0;
else if(r_cnt == P_CNT)
case(r_cntTens[3:0])
4'd0 : r_digitalTubeTens <= P_X ;
4'd1 : r_digitalTubeTens <= P_1 ;
4'd2 : r_digitalTubeTens <= P_2 ;
4'd3 : r_digitalTubeTens <= P_3 ;
4'd4 : r_digitalTubeTens <= P_4 ;
4'd5 : r_digitalTubeTens <= P_5 ;
4'd6 : r_digitalTubeTens <= P_6 ;
4'd7 : r_digitalTubeTens <= P_7 ;
4'd8 : r_digitalTubeTens <= P_8 ;
4'd9 : r_digitalTubeTens <= P_9 ;
default : r_digitalTubeTens <= r_digitalTubeTens ;
endcase
else
r_digitalTubeTens <= r_digitalTubeTens;
end
最后这一串代码,分别在扫描时,定期更新个位和十位的寄存器数值。
总结:
我个人对FPGA的开发并不是非常的熟悉,但是通过运行和分析官方提供的demo代码,也让我对FPGA的使用有了更多的了解,很高兴有机会使用这个开发板。
运行的效果如下:
[localvideo]d46a89282d9a657546f499c5d6db6232[/localvideo]
-
回复了主题帖:
【Tang Primer 25K 测评】3、高云IDE使用及PMOD-LEDx8模块点灯实验
秦天qintian0303 发表于 2025-2-1 08:23
这个直接有例程吗?看着好像汇编的写法
不是汇编,FPGA只能用Verilog或者VHDL去写。FPGA是纯硬件实现的,不像单片机软件的运行逻辑
-
发表了主题帖:
「Tang Primer 25K 测评」2、点亮LCD屏幕
官方仓库是有一个PMOD_LCD目录,但是我们手上是没有这个硬件的,可以使用淘宝上常见的st7735模块进行尝试。
通过搜索,我们可以发现官方使用的屏幕模块如下图:
简单说明一下时序
不管是FPGA还是MCU,驱动屏幕的关键是满足对应屏幕的通信时序,如下图是st7735s规格书里,关于4线spi模式的时序图
如何解读这个时序图呢?在我们使用FPGA驱动屏幕时,有几个重要引脚:
信号和引脚
CSX(芯片选择,通常标注为CS):用于选择设备的信号。当CSX为低电平(CSX = 0)时,设备被选中,可以开始通信。当CSX为高电平(CSX = 1)时,设备被取消选择,SDA线上的任何数据都会被忽略。
D/CX(数据/命令,通常标注为DC):该信号指示SDA线上的数据是命令(D/CX = 0)还是参数/数据(D/CX = 1)。
SCL(串行时钟):这是同步数据传输的时钟信号。数据通常在时钟信号的上升沿被捕获。
SDA(串行数据):这是传输实际数据(命令或数据)的数据线。
RES(硬件复位):该信号用于强制复位st7735s芯片,然后接下来会重新初始化st7735。在规格书里也有使用RES引脚暂停通信的,这个一般用不上。
BLK(背光使能):使用该引脚控制背光。
这里,我们先不管BLK和RES引脚,重点关注通信过程,CS引脚为0时,开启设备通信,此时可以和屏显芯片(这里是st7735s)交互,通过DC引脚告诉屏显芯片,当前SDA的发过来的内容是命令或者参数(数据)。我们可以通过写入厂家提供的屏幕初始化指令序列,完成屏幕的初始化,然后再向屏幕写入相关显示数据,完成最后的屏幕显示。
向屏幕写入数据的图示如下
一般情况下,我们会使用RGB565格式发送屏幕显示数据,选择这个格式,是因为不管是图像质量,还是内存占用方面,这个格式都是目前MCU和PFGA显示图像的最优解(效果能接受,占用资源也能接受)。
IDE里选择设备
在打开工程时,因为我们github的子仓没有下载,会提示需要选择正确的设备,按下图的顺序选择即可。
亮屏代码
`timescale 1ps/1ps
module lcd114_test#(
parameter clk_frequency = 50_000_000
)(
input clk, // 50M
input rst,
output ser_tx,
input ser_rx,
output lcd_resetn,
output lcd_clk,
output lcd_cs,
output lcd_dc,
output lcd_blk,
output lcd_data
);
assign resetn = !rst;
localparam MAX_CMDS = 87;
wire [8:0] init_cmd[MAX_CMDS:0];
assign init_cmd[ 0] = 9'h011;
assign init_cmd[ 1] = 9'h0B1;
assign init_cmd[ 2] = 9'h105;
assign init_cmd[ 3] = 9'h13C;
assign init_cmd[ 4] = 9'h13C;
assign init_cmd[ 5] = 9'h0B2;
assign init_cmd[ 6] = 9'h105;
assign init_cmd[ 7] = 9'h13C;
assign init_cmd[ 8] = 9'h13C;
assign init_cmd[ 9] = 9'h0B3;
assign init_cmd[10] = 9'h105;
assign init_cmd[11] = 9'h13C;
assign init_cmd[12] = 9'h13C;
assign init_cmd[13] = 9'h105;
assign init_cmd[14] = 9'h13C;
assign init_cmd[15] = 9'h13C;
assign init_cmd[16] = 9'h0B4;
assign init_cmd[17] = 9'h103;
assign init_cmd[18] = 9'h0C0;
assign init_cmd[19] = 9'h1AB;
assign init_cmd[20] = 9'h10B;
assign init_cmd[21] = 9'h104;
assign init_cmd[22] = 9'h0C1;
assign init_cmd[23] = 9'h1C5;
assign init_cmd[24] = 9'h0C2;
assign init_cmd[25] = 9'h10D;
assign init_cmd[26] = 9'h100;
assign init_cmd[27] = 9'h0C3;
assign init_cmd[28] = 9'h18D;
assign init_cmd[29] = 9'h16A;
assign init_cmd[30] = 9'h0C4;
assign init_cmd[31] = 9'h18D;
assign init_cmd[32] = 9'h1EE;
assign init_cmd[33] = 9'h0C5;
assign init_cmd[34] = 9'h10F;
assign init_cmd[35] = 9'h0E0;
assign init_cmd[36] = 9'h107;
assign init_cmd[37] = 9'h10E;
assign init_cmd[38] = 9'h108;
assign init_cmd[39] = 9'h107;
assign init_cmd[40] = 9'h110;
assign init_cmd[41] = 9'h107;
assign init_cmd[42] = 9'h102;
assign init_cmd[43] = 9'h107;
assign init_cmd[44] = 9'h109;
assign init_cmd[45] = 9'h10F;
assign init_cmd[46] = 9'h125;
assign init_cmd[47] = 9'h136;
assign init_cmd[48] = 9'h100;
assign init_cmd[49] = 9'h108;
assign init_cmd[50] = 9'h104;
assign init_cmd[51] = 9'h110;
assign init_cmd[52] = 9'h0E1;
assign init_cmd[53] = 9'h10A;
assign init_cmd[54] = 9'h10D;
assign init_cmd[55] = 9'h108;
assign init_cmd[56] = 9'h107;
assign init_cmd[57] = 9'h10F;
assign init_cmd[58] = 9'h107;
assign init_cmd[59] = 9'h102;
assign init_cmd[60] = 9'h107;
assign init_cmd[61] = 9'h109;
assign init_cmd[62] = 9'h10F;
assign init_cmd[63] = 9'h125;
assign init_cmd[64] = 9'h135;
assign init_cmd[65] = 9'h100;
assign init_cmd[66] = 9'h109;
assign init_cmd[67] = 9'h104;
assign init_cmd[68] = 9'h110;
assign init_cmd[69] = 9'h0FC;
assign init_cmd[70] = 9'h180;
assign init_cmd[71] = 9'h03A;
assign init_cmd[72] = 9'h105;
assign init_cmd[73] = 9'h036;
assign init_cmd[74] = 9'h108;
// assign init_cmd[74] = 9'h1C8;
// assign init_cmd[74] = 9'h178;
// assign init_cmd[74] = 9'h1A8;
assign init_cmd[75] = 9'h020;
assign init_cmd[76] = 9'h029;
assign init_cmd[77] = 9'h02A;
assign init_cmd[78] = 9'h100;
assign init_cmd[79] = 9'h11A;
assign init_cmd[80] = 9'h100;
assign init_cmd[81] = 9'h169;
assign init_cmd[82] = 9'h02B;
assign init_cmd[83] = 9'h100;
assign init_cmd[84] = 9'h101;
assign init_cmd[85] = 9'h100;
assign init_cmd[86] = 9'h1A0;
assign init_cmd[87] = 9'h02C;
localparam INIT_RESET = 4'b0000; // delay 100ms while reset
localparam INIT_PREPARE = 4'b0001; // delay 200ms after reset
localparam INIT_WAKEUP = 4'b0010; // write cmd 0x11 MIPI_DCS_EXIT_SLEEP_MODE
localparam INIT_SNOOZE = 4'b0011; // delay 120ms after wakeup
localparam INIT_WORKING = 4'b0100; // write command & data
localparam INIT_DONE = 4'b0101; // all done
`ifdef MODELTECH
localparam CNT_1MS = clk_frequency / 1000;
localparam CNT_100MS = 100 * CNT_1MS ;
localparam CNT_120MS = 120 * CNT_1MS;
localparam CNT_200MS = 200 * CNT_1MS;
`else
// speedup for simulation
localparam CNT_100MS = 32'd27;
localparam CNT_120MS = 32'd32;
localparam CNT_200MS = 32'd54;
`endif
reg [ 3:0] init_state;
reg [ 6:0] cmd_index;
reg [31:0] clk_cnt;
reg [ 4:0] bit_loop;
reg [15:0] pixel_cnt;
reg lcd_cs_r;
reg lcd_dc_r;
reg lcd_reset_r;
reg lcd_blk_r;
reg [7:0] spi_data;
assign lcd_resetn = lcd_reset_r;
assign lcd_clk = clk;
assign lcd_blk = lcd_blk_r;
assign lcd_cs = lcd_cs_r;
assign lcd_dc = lcd_dc_r;
assign lcd_data = spi_data[7]; // MSB
// gen color bar
wire [15:0] pixel = (pixel_cnt >= 80*55) ? 16'hF800 :
(pixel_cnt >= 80*110) ? 16'h07E0 : 16'h001F;
always@(posedge clk or negedge resetn) begin
if (~resetn) begin
clk_cnt <= 0;
cmd_index <= 0;
init_state <= INIT_RESET;
lcd_cs_r <= 1;
lcd_dc_r <= 1;
lcd_reset_r <= 0;
spi_data <= 8'hFF;
bit_loop <= 0;
pixel_cnt <= 0;
lcd_blk_r <= 0;
end else begin
lcd_blk_r <= 1;
case (init_state)
INIT_RESET : begin
if (clk_cnt == CNT_100MS) begin
clk_cnt <= 0;
init_state <= INIT_PREPARE;
lcd_reset_r <= 1;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
INIT_PREPARE : begin
if (clk_cnt == CNT_200MS) begin
clk_cnt <= 0;
init_state <= INIT_WAKEUP;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
INIT_WAKEUP : begin
if (bit_loop == 0) begin
// start
lcd_cs_r <= 0;
lcd_dc_r <= 0;
spi_data <= 8'h11; // exit sleep
bit_loop <= bit_loop + 1;
end else if (bit_loop == 8) begin
// end
lcd_cs_r <= 1;
lcd_dc_r <= 1;
bit_loop <= 0;
init_state <= INIT_SNOOZE;
end else begin
// loop
spi_data <= { spi_data[6:0], 1'b1 };
bit_loop <= bit_loop + 1;
end
end
INIT_SNOOZE : begin
if (clk_cnt == CNT_120MS) begin
clk_cnt <= 0;
init_state <= INIT_WORKING;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
INIT_WORKING : begin
if (cmd_index == MAX_CMDS + 1) begin
init_state <= INIT_DONE;
end else begin
if (bit_loop == 0) begin
// start
lcd_cs_r <= 0;
lcd_dc_r <= init_cmd[cmd_index][8];
spi_data <= init_cmd[cmd_index][7:0];
bit_loop <= bit_loop + 1;
end else if (bit_loop == 8) begin
// end
lcd_cs_r <= 1;
lcd_dc_r <= 1;
bit_loop <= 0;
cmd_index <= cmd_index + 1; // next command
end else begin
// loop
spi_data <= { spi_data[6:0], 1'b1 };
bit_loop <= bit_loop + 1;
end
end
end
INIT_DONE : begin
if (pixel_cnt == 160*80) begin
; // stop
end else begin
if (bit_loop == 0) begin
// start
lcd_cs_r <= 0;
lcd_dc_r <= 1;
// spi_data <= 8'hF8; // RED
spi_data <= pixel[15:8];
bit_loop <= bit_loop + 1;
end else if (bit_loop == 8) begin
// next byte
// spi_data <= 8'h00; // RED
spi_data <= pixel[7:0];
bit_loop <= bit_loop + 1;
end else if (bit_loop == 16) begin
// end
lcd_cs_r <= 1;
lcd_dc_r <= 1;
bit_loop <= 0;
pixel_cnt <= pixel_cnt + 1; // next pixel
end else begin
// loop
spi_data <= { spi_data[6:0], 1'b1 };
bit_loop <= bit_loop + 1;
end
end
end
endcase
end
end
endmodule
这里我们添加了lcd_blk引脚控制部分,在初始化的时候立即点亮LCD屏幕,如果不想折腾的,也可以把BLK引脚直接接到3.3V高电平上。
然后关闭了屏幕反显功能(根据不同屏幕厂商去设置)
然后我们需要修改约束控制引脚。
打开pmod_lcd.cst文件,将我们连接到LCD的引脚修改成对应的引脚,并添加blk引脚
IO_LOC "lcd_blk" K1;
IO_LOC "lcd_cs" L2;
IO_LOC "lcd_dc" J4;
IO_LOC "lcd_resetn" G2;
IO_LOC "lcd_data" K2;
IO_LOC "lcd_clk" L4;
IO_LOC "rst" H11;
IO_LOC "clk" E2;
//IO_LOC "lcd_cs" G5;
//IO_LOC "lcd_data" G8;
//IO_LOC "lcd_dc" H7;
//IO_LOC "lcd_clk" J5;
//IO_LOC "lcd_resetn" H5;
//IO_LOC "rst" H11;
//IO_LOC "clk" E2;
IO_PORT "lcd_data" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "lcd_cs" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "lcd_dc" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "lcd_clk" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "lcd_blk" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "lcd_resetn" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_PORT "rst" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;
IO_PORT "clk" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;
接线如图所示:
修改完毕后,就可以综合布线、烧录到板子上运行了。烧录时,默认是烧录到SRAM里,其实我们测试的时候,完全不需要烧录到外部Flash,直接按默认的就行。
烧录完毕后,就可以看到屏幕亮起来了。
不知道是分辨率不适配,还是反显没做好,本来应该显示红绿蓝三种颜色的,现在显示的不正确。今天就先折腾到这里,有空找一下分辨率一样的屏幕出来测试。
-
回复了主题帖:
MC9S12XS128读取DS18B20的文题
上逻辑分析仪看看时序图吧
- 2025-02-14
-
发表了主题帖:
「Tang Primer 25K 测评」1、开箱、环境搭建和运行第一个点灯程序
本帖最后由 walker2048 于 2025-2-15 16:58 编辑
前言
非常感谢社区和平台给我这个机会,参加“年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~”,这个活动,也有幸拿到了Tang Primer 25K这款开发板,好好学习一下FPGA的相关知识。
Tang Primer 25K 是基于 GW5A-LV25MG121 所设计的一款极小封装的核心板(23x18mm),并配套全引脚引出(除MIPI高速脚外)的25K Dock底板。
该开发板使用的是国产FPGA芯片,即高云GW5A-25系列,这个系列的硬件资源说明如下图:
红框包围部分就是这个系列的硬件资源,有23040个LUT4,可以连接外部DDR3(1066速率)、mipi等设备。
可能大家对这些并不是很了解,我们使用高压官方的IP核,实现简单的I2C和UART,SPI这些设备,每个设备大概占用5、6百的LUT。然后常见的小型化MCU核心,例如PicoRV32大约需要2000个LUT,而arm的M1系列软核大概占用13000个LUT。由此可见,本次活动使用的开发板还是非常强劲的。
开箱环节
整个套件还是非常不错的,板子设计也比较漂亮,是我喜欢的类型。稍微有点遗憾的就是,东西发过来的时候,盒子里面并没有放泡沫或者其他缓冲的东西,在运送过程中,数码管其中一块被震掉了,还弄掉了一个LED。好在平时我自己也存着很多不同规格的元器件,焊接0402元器件也是手到擒来,自己修好了,数码管也用502粘好了(照片上也可以看到部分焊接痕迹)。其他板子目测是没有掉件的,希望没事儿吧。
环境搭建
很早以前就玩过高云的FPGA,对他们家的软件也是比较熟悉了,几年前玩的时候,当时还是用的sipeed服务器授权版本。现在简单了,直接用教育版就行了。
教育版能用的高云设备,基本上把淘宝上常见的产品给覆盖了,直接官网下载软件安装即可。
打开高云官网,点击开发者专区-> 云源软件,然后找到下图的链接,下载对应平台版本安装即可。
由于我是在Windows平台使用,就直接下载和安装win版本就好了。
安装完毕之后,就可以打开sipeed的Tang Primer 25K仓库,下载一些案例源码。例程源码可以在sipeed的wiki页面里找到,这里我们也可以看到有说明,必须安装1.9.9B4或者更新的IDE版本,我直接装最新版,没啥问题。
点灯程序
官方的点灯程序在pmod_led目录下,直接打开目录里的工程文件即可。
module top#(
parameter pmod_num = 8,
parameter pmod_io_num = pmod_num * 8 - 1,
parameter frequency = 50_000_000,
parameter count_ms = frequency / 1000 ,
parameter count_us = count_ms / 1000
)
(
input clk,
input key,
output led,
output led_done,
output led_ready,
output [pmod_io_num:0] pmod_io
);
reg led_output = 'd0 ;
reg [7:0] led_8_state_reg = 'b1 ;
reg [31:0] count='d0 ;
always @(posedge clk) begin
if (!key) begin
count <= 'b0;
led_output <= 'b0;
led_8_state_reg <= 'b1;
end
else if(count <= frequency/5 -1)
count <= count + 'b1;
else begin
count <= 'b0;
led_output <= !led_output ;
led_8_state_reg <= {led_8_state_reg[6:0], led_8_state_reg[7]} ;
end
end
genvar pmod_sel;
generate for ( pmod_sel = 0 ; pmod_sel < 8 ; pmod_sel = pmod_sel + 1)
assign pmod_io[ pmod_sel * 8 + 7 : pmod_sel * 8 ] = ~ led_8_state_reg ;
endgenerate
assign led = led_output ;
assign led_done = led_output ;
assign led_ready = led_output ;
endmodule
似乎原工程的引脚约束配置有问题,直接烧录布线并不能跑。需要将目录里的pmod_led.cst,第69行改成
IO_LOC "key" H11;
同时删除52行原有的H11部分,然后综合、布线、烧录,就可以看见Pmod的LED亮起来了。
[localvideo]4a8fdbb51ec1d0c3b2c2f2d6eaa8c91b[/localvideo]
- 2025-02-12
-
回复了主题帖:
有没有什么好方法实现ESP32C3串口扩展
XAndxiang 发表于 2025-2-12 09:37
多路选择器,一个串口分时复用
我也觉得多路复用挺适合这个应用
-
回复了主题帖:
有没有什么好方法实现ESP32C3串口扩展
9600用软串口完全足够了,如果你要上115200的话,rp2040的pio状态机模拟也没问题。本来你这加热设备的逻辑就是轮巡,又不会多个设备同时发送和接收数据。
- 2025-02-08
-
回复了主题帖:
单片机编程理解
加油,多写代码,多做项目
- 2025-02-07
-
回复了主题帖:
这个春节最火爆的AI大模型deepseek,你玩了吗?
当做高级搜索功能使用就可以了,还挺不错的。
- 2025-01-29
-
回复了主题帖:
【Tang Primer 25K 测评】1、硬件开箱
PMOD也是支持I2C和SPI的,只是和常规淘宝模块接口区别比较大
- 2025-01-27
-
回复了主题帖:
Luckfox幸狐 RV1106 Linux 开发板问题求助
SD卡的话,先做rootfs扩容(网上很多教程,自己搜吧),然后再装大型软件。查看剩余空间可以用df -h命令。
- 2025-01-25
-
回复了主题帖:
关于误入论坛的高中生,求求解答
个人建议优先高考。目前AI就业是很吃学历的,985,211本科也没啥用
- 2025-01-16
-
回复了主题帖:
国产以太网控制器CH390h试用体验----替代W5500
zhangjt0713 发表于 2025-1-16 09:49
网口的IC 哪家的比较好用,选型的时候比较纠结 LAN8720A 等等给点意见,沁恒的怎么样呢
我在用872 ...
我用过的不多,这个不好推荐,可以自行了解一下。
- 2025-01-15
-
回复了主题帖:
国产以太网控制器CH390h试用体验----替代W5500
att123 发表于 2025-1-15 08:33
w5500那么发烫,不是合格产品,还投放市场,
其实用w5500的还是很多的,也还有市场。至于烫的问题,这个也没办法,只能说竞品不给力,没尽快推出抢占他的市场。
- 2025-01-14
-
回复了主题帖:
为什么单片机会有3.3V和5V电压等级的区分?
为了节能
- 2025-01-08
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息无误,确认可以完成测评计划