1. 描述
基于上一个FPGA的数字钟的实验,采用CVI2017制作了一个简单的上位机软件。实现了上位机和FPGA开发板同时可以显示时间、设置时间、蜂鸣器报警、LED灯闪烁等功能。FPGA开发板和上位机采用串口的方式通信。上一篇博文的链接:https://blog.csdn.net/qq_42965223/article/details/109432049
2.环境
硬件:硬件 :altera公司的Cyclone IV --EP4CE6F17C8N
软件:Quartus Prime Lite Edition 17.1
Labwindows/CVI 2017
操作系统:Windows10家庭版
3.整体框架
整个实验除了在开发板上实现数字钟的功能,在上位机也实现了类似功能的界面,通过每秒上发的16进制时间信息更新上位机显示。上位机的按键在按下后也会下发数据给开发板,开发板接收到数据后进行处理触发软按键实现按键按下的效果。其顶层文件框架如下所示:
与上位机通信的相关数据帧定义如下所示:
顶层文件的输入输出如下所示:
管脚分配如下图所示:
4.具体实现思路
a. 顶层模块概述
串口收发模块
串口收发模块主要参考了开发板的串口例程,并且在顶层文件中编写了发送状态机,可以发送任意字节的数据。串口收发模块的输入输出如下图所示:
如上,CLK_FRE为输入时钟(和上个实验一样为50MHz)115200表示的是波特率,Clk也为系统时钟,Nrst为复位信号,tx_data(rx_data)为一个8位的数据,tx_data_valid 为 发送数据有效信号(也就是由外部控制(相对于串口模块)的输入信号,要发送数据时可置为有效),tx_data_ready 为串口模块输出信号,表示模块自身已经准备好发送了。同理接收模块的对应信号意思相近。后面会附上模块具体实现代码。通过对串口模块相应位的检测判断,可以在顶层文件中实现对个Byte数据的发送。其具体实现代码如下
//notes: state machine compose the IDEL-WAIT-SEND state
always@(posedge Clk or negedge Nrst)
begin
if(Nrst == 1'b0)
begin
wait_cnt <= 32'd0;
tx_data <= 8'd0;
state <= IDLE;
tx_cnt <= 8'd0;
tx_data_valid <= 1'b0;
end
else
begin
case (state)
//idel state
IDLE: state <= SEND;
//sending state
SEND:
begin
wait_cnt <= 32'd0;
if(IsCommandData == 1'b1) tx_data <= tx_str1; //select send Cmd or Time
else tx_data <= tx_str;
//the first 6 data represents time and the last data is command states
if(tx_data_valid == 1'b1 && tx_data_ready == 1'b1 && tx_cnt < 8'd4)
begin
tx_cnt <= tx_cnt + 8'd1; //sent byte counter
end
else if (tx_data_valid && tx_data_ready) //last byte sending is completed
begin
tx_cnt <= 8'd0; //clear counter
tx_data_valid <= 1'b0; //
IsCommandData <= 1'b0;
state <= WAIT;
end
else if(~tx_data_valid)
begin
tx_data_valid <= 1'b1;
end
end
//waitting state
WAIT:
begin
wait_cnt <= wait_cnt + 32'd1; //wait counter(send data to PC every seconds)
if(tx_data_valid && tx_data_ready)
begin
tx_data_valid <= 1'b0;
end
//todo: we can add other trigger conditions here
else if (wait_cnt >= CLK_FRE * 1000000) //wait time is up and change to sending state
begin
state <= SEND;
tx_cnt <= 8'd0;
end
else if(IsAnyKeyTrg)
begin
state <= SEND;
IsCommandData <= 1'b1;
//tx_cnt <= 8'd0;
end
end
default: state <= IDLE;
endcase //endcase
end
end
然后再在另一个块语句中实现发送数据的切换,实现方式如下:
//notes: change send bytes
always@(*)
begin
case(tx_cnt)
8'd0 : tx_str <= 8'hbb;
8'd1 : tx_str <= {wHorCntShi,wHorCntGe};
8'd2 : tx_str <= {wMinCntShi,wMinCntGe};
8'd3 : tx_str <= {wSecCntShi,wSecCntGe};
8'd4 : tx_str <= 8'hee;
default:tx_str <= 8'd0;
endcase
end
//notes: send command
always@(*)
begin
case(tx_cnt)
8'd0 : tx_str1 <= 8'haa;
8'd1 : tx_str1 <= 8'd0;
8'd2 : tx_str1 <= 8'd0;
8'd3 : tx_str1 <= {4'b0000,KeyValue};
8'd4 : tx_str1 <= 8'hdd;
default:tx_str1 <= 8'd0;
endcase
end
接收模块实现方法类似,在这就不粘贴代码了。在接收数据后,通过判断上位机发送的不同数据产生模拟的边沿触发时钟,送入失踪模块,实现像视同按键触发的效果。
时钟模块
时钟模块根据上一次实验做了些许修改,对滤波后的键值进行了输出共顶层模块使用。(实体按键按下时需要发送键值给上位机),其做的修改如下所示:
assign ISANYKEY = {NRST,wKeyBdSet,wKeyBdMadd,wKeyBdHadd};
然后再外面对模拟按键和实体按键进行相与操作,使得上位机和开发板上的实体按键都可以对数字钟进行控制。
//both the hard key and the simulated key works to control the system
assign FinalKeySet = (SoftKeySet && Keyset) ; //keyset both hard and soft works
assign FinalKeyHadd = (SoftKeyHadd && Keyhadd);
assign FinalKeyMadd = (SoftKeyMadd && Keymadd);
assign FianlReset = (SoftKeyReset && Nrst);
assign Buzz_out = BeepLock?TempBuzz_out:1; //Lock the beep clock out
编码器模块
使用编码器模块主要是对时钟模块输出的段码再次进行转换,转为二进制后再通过串口模块发送给上位机。其实可以直接在时钟模块里增加输出端口,输出未转化的二进制数据,但是为了时钟模块的完整性(尽量使用上个实验一样的代码),就用了个编码器。代码如下:
module seg_coder(
input Nrst,
input[6:0] Seg_data,
output[3:0] Bin_data
);
reg[3:0] rBin_data;
assign Bin_data = rBin_data;
always@(Seg_data or Nrst)
begin
if (Nrst == 1'b0)
begin
rBin_data <= 4'b0000;
end
else
begin
case(Seg_data)
7'b100_0000 : rBin_data <= 4'd0 ;
7'b111_1001 : rBin_data <= 4'd1 ;
7'b010_0100 : rBin_data <= 4'd2 ;
7'b011_0000 : rBin_data <= 4'd3 ;
7'b001_1001 : rBin_data <= 4'd4 ;
7'b001_0010 : rBin_data <= 4'd5 ;
7'b000_0010 : rBin_data <= 4'd6 ;
7'b111_1000 : rBin_data <= 4'd7 ;
7'b000_0000 : rBin_data <= 4'd8 ;
7'b001_0000 : rBin_data <= 4'd9 ;
7'b000_1000 : rBin_data <= 4'ha ;
7'b000_0011 : rBin_data <= 4'hb ;
7'b100_0110 : rBin_data <= 4'hc ;
7'b010_0001 : rBin_data <= 4'hd ;
7'b000_0110 : rBin_data <= 4'he ;
7'b000_1110 : rBin_data <= 4'hf ;
default : rBin_data <= 4'd0 ;
endcase
end
end
endmodule
5.上位机实现
上位机通过 CVI2017实现,因为刚开始学,实现的界面较为简单,调用的是其自带的串口库实现数据收发。包括一些设置按键和串口通信信息的设置输入框。上位机效果图如下所示:
其具体实现代码如下:
DigitalClock.c
#include <userint.h>
#include "DigitalClock.h"
/*================================================================
Title: DigitalClock
Purpose: FPGA Comprehensive Internship
Created on: 2020/11/24 at 19:37:03 by
Created by: NightVoyager
Copyright: All Rights Reserved.
===================================================================*/
#include "main.h"
static int panelHandle = 0;
ST_COM gComParam;
//=======================================
static int InterFaceInit(int PanelHandle);
static void CtrlsDisable(int PanelHandle);
static void CtrlsEnable(int PanelHandle);
//=======================================
int main(int argc, char *argv[])
{
int error = 0;
//? Verify that weather the PC speaker can work
//printf("%d",CVILowLevelSupportDriverLoaded());
/* initialize and load resources */
nullChk(InitCVIRTE(0, argv, 0));
errChk(panelHandle = LoadPanel(0, "DigitalClock.uir", PANEL));
/* display the panel and run the user interface */
errChk(DisplayPanel(panelHandle));
InterFaceInit(panelHandle); //init the controls
errChk(RunUserInterface());
Error:
/* clean up */
if (panelHandle > 0)
DiscardPanel(panelHandle);
return 0;
}
//==============================================================================
// UI callback function prototypes
int CVICALLBACK panelCB(int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
if (event == EVENT_CLOSE)
QuitUserInterface(0);
return 0;
}
static int InterFaceInit(int PanelHandle)
{
//init the segment-showing part
SetCtrlAttribute(PanelHandle, PANEL_SEG_STR, ATTR_NO_EDIT_TEXT, 1); //can't edit
SetCtrlVal(PanelHandle, PANEL_SEG_STR, "00:00:00"); //set the initial value
SetCtrlAttribute(PanelHandle, PANEL_SEG_STR, ATTR_TEXT_BGCOLOR, VAL_BLACK); //set the segment's background color
SetCtrlAttribute(PanelHandle, PANEL_SEG_STR, ATTR_TEXT_COLOR, VAL_RED); //set the text's color
//set the default Com information
gComParam.PortNum = 6;
gComParam.BaudRate = 9600;
gComParam.DataBit = 8;
gComParam.Parity = 0;
gComParam.StopBit = 1;
memset(gComParam.RecvData, 0, sizeof(gComParam.RecvData));
memset(gComParam.SendData, 0, sizeof(gComParam.SendData));
gComParam.SendData[0] = 0xaa; //set the command data's head and tail
gComParam.SendData[2] = 0xdd;
gComParam.IsSetBtnOk = 0;
//disable the timer
SetCtrlAttribute(PanelHandle, PANEL_TIMER, ATTR_ENABLED, 0);
//CtrlsDisable
CtrlsDisable(PanelHandle);
return 0;
}
/*=============================================================
Function Name:CtrlsEnable
Description:鍦ㄥ仠姝㈡椂鍚敤鎺т欢
Input:鏃?
Output:鏃?
Notes锛氭棤
==============================================================*/
static void CtrlsEnable(int PanelHandle)
{
//鍚敤鎺т欢
SetCtrlAttribute(PanelHandle, PANEL_SEG_STR, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_LED3, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_LED2, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_LED1, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_LED0, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_MADD, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_HADD, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_SET, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_RESET, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_BINBEEPCTRL, ATTR_DIMMED, 0);
SetCtrlAttribute(PanelHandle, PANEL_TBN_COM, ATTR_DIMMED, 0);
}
/*=============================================================
Function Name:CtrlsDisable
Description:绂佺敤鎺т欢
Input:鏃?
Output:鏃?
Notes锛氭棤
==============================================================*/
static void CtrlsDisable(int PanelHandle)
{
//绂佺敤鎺т欢
SetCtrlAttribute(PanelHandle, PANEL_SEG_STR, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_LED3, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_LED2, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_LED1, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_LED0, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_MADD, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_HADD, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_SET, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_CBTN_RESET, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_BINBEEPCTRL, ATTR_DIMMED, 1);
SetCtrlAttribute(PanelHandle, PANEL_TBN_COM, ATTR_DIMMED, 1);
}
//===========================CallBack Function=======================================
int CVICALLBACK CbtnResetCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
if(gComParam.IsComOpen == 1)
{
gComParam.SendData[1] = 0x0a;
ComWrt(gComParam.PortNum, gComParam.SendData, 6);
}
break;
}
return 0;
}
int CVICALLBACK CbtnSetCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int CurrentVal = 0;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal(panel, PANEL_BINPOWCTRL, &CurrentVal);
if (CurrentVal == 1 && gComParam.IsComOpen == 1)
{
gComParam.IsSetBtnOk = 1;
gComParam.SendData[1] = 0x0b;
ComWrt(gComParam.PortNum, gComParam.SendData, 6);
}
else if(CurrentVal == 0)
{
gComParam.IsSetBtnOk = 0;
}
break;
}
return 0;
}
int CVICALLBACK CbtnHaddCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int CurrentVal = 0;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal(panel, PANEL_BINPOWCTRL, &CurrentVal);
if (CurrentVal == 1 && gComParam.IsComOpen == 1 && gComParam.IsSetBtnOk == 1)
{
gComParam.SendData[1] = 0x0d;
ComWrt(gComParam.PortNum, gComParam.SendData, 6);
}
else
{
/* code */
}
break;
}
return 0;
}
int CVICALLBACK CbtnMaddCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int CurrentVal = 0;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal(panel, PANEL_BINPOWCTRL, &CurrentVal);
if (CurrentVal == 1 && gComParam.IsComOpen == 1 && gComParam.IsSetBtnOk == 1)
{
gComParam.SendData[1] = 0x0e;
ComWrt(gComParam.PortNum, gComParam.SendData, 6);
}
else
{
/* code */
}
break;
}
return 0;
}
int CVICALLBACK BinbtnOporClCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int currentVal;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal(panel, PANEL_BINPOWCTRL, ¤tVal);
if (currentVal)
{
CtrlsEnable(panel);
}
else
{
CtrlsDisable(panel);
}
break;
}
return 0;
}
int CVICALLBACK BinBeepCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
if(gComParam.IsComOpen == 1)
{
gComParam.SendData[1] = 0x10;
ComWrt(gComParam.PortNum, gComParam.SendData, 6);
}
break;
}
return 0;
}
int CVICALLBACK CbtnHelpCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
MessagePopup("帮助",HelpText);
break;
}
return 0;
}
//===========================CallBack Function=======================================
int CVICALLBACK ComBtnCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int currentVal;
int i = 0;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal(panel, PANEL_TBN_COM, ¤tVal);
if (currentVal)
{
gComParam.IsComOpen = 1;
//enable the input of the port config
for (i = 16; i < 21; i++)
{
SetCtrlAttribute(panel, i, ATTR_DIMMED, 1);
}
//get the com configration
GetCtrlVal(panel, PANEL_NUMERIC_PORT, &gComParam.PortNum);
GetCtrlVal(panel, PANEL_RING_BDRATE, &gComParam.BaudRate);
GetCtrlVal(panel, PANEL_RING_DB, &gComParam.DataBit);
GetCtrlVal(panel, PANEL_RING_PAR, &gComParam.Parity);
GetCtrlVal(panel, PANEL_RING_SP, &gComParam.StopBit);
//open the port according to the parameter above
OpenComConfig(gComParam.PortNum, "", gComParam.BaudRate, gComParam.Parity,
gComParam.DataBit, gComParam.StopBit, 100, 100);
FlushInQ(gComParam.PortNum);
FlushOutQ(gComParam.PortNum);
SetCtrlAttribute(panel, PANEL_TIMER, ATTR_ENABLED, 1); //enable the timer
}
else
{
gComParam.IsComOpen = 0;
//disable the input of the port config
for (i = 16; i < 21; i++)
{
SetCtrlAttribute(panel, i, ATTR_DIMMED, 0);
}
FlushInQ(gComParam.PortNum);
FlushOutQ(gComParam.PortNum);
//close the port due to the port number
CloseCom(gComParam.PortNum);
SetCtrlAttribute(panel, PANEL_TIMER, ATTR_ENABLED, 0); //disable the timer
}
break;
}
return 0;
}
int CVICALLBACK TimerCallBack(int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int StrLength = 0;
char ShowData[100] = {00};
switch (event)
{
case EVENT_TIMER_TICK:
StrLength = GetInQLen(gComParam.PortNum);
if (StrLength != 0)
{
//If it receives too much data to clean up, it will block the serial port
ComRd(gComParam.PortNum, gComParam.RecvData, StrLength);
// Is the time data ?
if (gComParam.RecvData[0] == 0xbb && gComParam.RecvData[4] == 0xee)
{
sprintf(ShowData, "%02x:%02x:%02x", gComParam.RecvData[1],
gComParam.RecvData[2], gComParam.RecvData[3]);
SetCtrlVal(panel, PANEL_SEG_STR, ShowData); //show the data
//blink the led
if (gComParam.RecvData[2] == 0x59 && gComParam.RecvData[3] >= 0x56)
{
SetCtrlVal(panel, PANEL_LED0, ((gComParam.RecvData[3] == 0x56) ? 1 : 0));
SetCtrlVal(panel, PANEL_LED1, ((gComParam.RecvData[3] == 0x57) ? 1 : 0));
SetCtrlVal(panel, PANEL_LED2, ((gComParam.RecvData[3] == 0x58) ? 1 : 0));
SetCtrlVal(panel, PANEL_LED3, ((gComParam.RecvData[3] == 0x59) ? 1 : 0));
}
if (gComParam.RecvData[3] == 0x00)
{
SetCtrlVal(panel, PANEL_LED3, 0); //Clear the last LED
}
}
//Is the Command Data
else if (gComParam.RecvData[0] == 0xaa && gComParam.RecvData[4] == 0xdd)
{
//
}
//Removes all characters from the input queue of the specified port.
FlushInQ(gComParam.PortNum);
FlushOutQ(gComParam.PortNum);
memset(gComParam.RecvData, 0, sizeof(gComParam.RecvData));
}
break;
}
return 0;
}
头文件
/**************************************************************************/
/* LabWindows/CVI User Interface Resource (UIR) Include File */
/* */
/* WARNING: Do not add to, delete from, or otherwise modify the contents */
/* of this include file. */
/**************************************************************************/
#include <userint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Panels and Controls: */
#define PANEL 1 /* callback function: panelCB */
#define PANEL_SEG_STR 2 /* control type: string, callback function: (none) */
#define PANEL_LED3 3 /* control type: LED, callback function: (none) */
#define PANEL_LED2 4 /* control type: LED, callback function: (none) */
#define PANEL_LED1 5 /* control type: LED, callback function: (none) */
#define PANEL_LED0 6 /* control type: LED, callback function: (none) */
#define PANEL_CBTN_MADD 7 /* control type: command, callback function: CbtnMaddCallBack */
#define PANEL_CBTN_HADD 8 /* control type: command, callback function: CbtnHaddCallBack */
#define PANEL_CBTN_HELP 9 /* control type: command, callback function: CbtnHelpCallBack */
#define PANEL_CBTN_SET 10 /* control type: command, callback function: CbtnSetCallBack */
#define PANEL_CBTN_RESET 11 /* control type: command, callback function: CbtnResetCallBack */
#define PANEL_BINPOWCTRL 12 /* control type: binary, callback function: BinbtnOporClCallBack */
#define PANEL_BINBEEPCTRL 13 /* control type: binary, callback function: BinBeepCallBack */
#define PANEL_DECORATION1 14 /* control type: deco, callback function: (none) */
#define PANEL_DECORATION 15 /* control type: deco, callback function: (none) */
#define PANEL_NUMERIC_PORT 16 /* control type: numeric, callback function: (none) */
#define PANEL_RING_BDRATE 17 /* control type: ring, callback function: (none) */
#define PANEL_RING_PAR 18 /* control type: ring, callback function: (none) */
#define PANEL_RING_DB 19 /* control type: ring, callback function: (none) */
#define PANEL_RING_SP 20 /* control type: ring, callback function: (none) */
#define PANEL_TBN_COM 21 /* control type: textButton, callback function: ComBtnCallBack */
#define PANEL_DECORATION_2 22 /* control type: deco, callback function: (none) */
#define PANEL_DECORATION_3 23 /* control type: deco, callback function: (none) */
#define PANEL_TIMER 24 /* control type: timer, callback function: TimerCallBack */
/* Control Arrays: */
/* (no control arrays in the resource file) */
/* Menu Bars, Menus, and Menu Items: */
/* (no menu bars in the resource file) */
/* Callback Prototypes: */
int CVICALLBACK BinBeepCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK BinbtnOporClCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK CbtnHaddCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK CbtnHelpCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK CbtnMaddCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK CbtnResetCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK CbtnSetCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK ComBtnCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK panelCB(int panel, int event, void *callbackData, int eventData1, int eventData2);
int CVICALLBACK TimerCallBack(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
#ifdef __cplusplus
}
#endif
6. 总结
本次食盐主要是对FPGA的Verilog语言编程的进一步学习,以及最近正好在学CVI,正好实践一下。项目的大部分代码及全部的思路已经在博文里面站下,若是需要源码的朋友,可以私信我。代码写的也不是很好,一起进步吧。