基于QSYS的FPGA SOPC系统中的手轮脉冲计数

最近做了一个手轮计数的模块,用QSYS在FPGA上做了一个简单的系统。功能就是转动手轮,然后在TFT屏幕上显示手轮计数0~99 顺时针增加计数,逆时针减少。


 先是直接用中断函数,也就是将手轮脉冲的A,B作为NIOS的PIO输入。在A上升沿触发中断,然后根据B的电平判定正反转动。然后刷新TFT屏幕显示。这样做慢慢转动手轮的时候没有问题,但是快速连续转动的时候计数就跟不上了。分析后发现应该是中断函数花费的时间比较多,而手轮转动的比较快。所以不能采用中断来计数。


于是采用计数器+定时器的方式,也就是在硬件部分用一个8位寄存器储存计数值,当B上升沿的时候根据A的电平增加或减少计数,然后在QSYS系统中增加一个定时器并定义中断,最后在软件部分在定时器中断处理函数中读取寄存器的值,显示到屏幕上。


一、硬件部分(部分Verilog 代码)

reg     [7:0]       wheel_count;
reg     [7:0]       wheel_count_n;

parameter MIN_COUNT = 8'b0;
parameter MAX_COUNT = 8'b0110_0011;

always @(posedge B or negedge RST_N)
begin
  if(!RST_N)
    wheel_count <= 8'b0;
  else
    wheel_count <= wheel_count_n;
end

always @(*)
begin
  if(A)
    begin
      if(wheel_count == MAX_COUNT)
        wheel_count_n = MIN_COUNT;
      else
        wheel_count_n = wheel_count + 8'b1;
    end
  else
    begin
      if(wheel_count == MIN_COUNT)
        wheel_count_n = MAX_COUNT;
      else
        wheel_count_n = wheel_count - 8'b1;
    end
end

二、系统

基于QSYS的FPGA SOPC系统中的手轮脉冲计数_第1张图片

这里只贴出定时器部分的就够了


三、软件部分

注册定时器中断函数

//---------------------------------------------------------------------------
//-- 功能		:	注册定时器中断
//---------------------------------------------------------------------------
void Timer_Initial(void)
{
	//改写timer_isr_context指针以匹配alt_irq_register()函数原型
	void* isr_context_ptr = (void*) &timer_isr_context;
	//设置PERIOD寄存器
	//PERIODH << 16 | PERIODL = 计数器周期因子 * 系统时钟频率因子 - 1
	//PERIODH << 16 | PERIODL = 0.05s(50ms) * 100M - 1 = 4 999 999 = 0x‭004C4B3F‬
	IOWR_ALTERA_AVALON_TIMER_PERIODH(TIMER_BASE, 0x004C);
	IOWR_ALTERA_AVALON_TIMER_PERIODL(TIMER_BASE, 0x4B3F);

	//设置CONTROL寄存器   位数 |  3     |  2   |  1   |  0  |
	//					 CONTROL | STOP | START| CONT| ITO |
	IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE,		//STOP  = 1 计数器停止运行;                0,无影响
			ALTERA_AVALON_TIMER_CONTROL_START_MSK | 	//START = 1 计数器开始运行;                0,无影响
			ALTERA_AVALON_TIMER_CONTROL_CONT_MSK  | 	//CONT  = 1 计数器连续运行直到STOP被置一;   0,计数到0停止
			ALTERA_AVALON_TIMER_CONTROL_ITO_MSK);   	//ITO   = 1 产生IRQ;                      0,不产生IRQ
	//注册定时器中断
	alt_ic_isr_register(
			TIMER_IRQ_INTERRUPT_CONTROLLER_ID, 	//中断控制器标号,从system.h复制
			TIMER_IRQ,     		  				//硬件中断号,从system.h复制
			Timer_ISR_Interrupt,  				//中断服务子函数
			isr_context_ptr,        			//指向与设备驱动实例相关的数据结构体
			0x0);                   			//flags,保留未用
}
因为用了PLL IP,系统的时钟是100MHz的


中断处理函数

//---------------------------------------------------------------------------
//-- 功能		:	定时器中断服务  更新手轮计数
//-- 输入参数	:	timer_isr_context,用于传递中断状态寄存器的值,id,中断号
//---------------------------------------------------------------------------
void Timer_ISR_Interrupt(void* timer_isr_context, alt_u32 id)
{
        count = IORD_ALTERA_AVALON_PIO_DATA(WHEEL_COUNT_BASE);
	WheelDisplay();
	//应答中断,将STATUS寄存器清零
	IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_BASE,~ALTERA_AVALON_TIMER_STATUS_TO_MSK);
}

需要注意,WheelDisplay是封装UCGUI的显示函数,函数中先判断前后2次计数是否相同,不同才刷新显示。不然屏幕刷新太快了,其他部分会花屏

你可能感兴趣的:(FPGA)