基于GD32VF103的vga和ps2键盘驱动
前言
gd32vf103 是国内一款很不错的riscv架构微处理器,但是网上gd32vf103的应用还比较少,这里我决定分享一下利用这个微处理器制作的vga驱动和ps2键盘驱动的调试过程和思路,主要还是用来学习定时器、spi等这些单片机常用的功能,希望能帮到有需要的人 源码开源到了我的github上有需要自取gd32vf103 vga&ps2keyboard
PS2 键盘驱动
ps2 键盘在几年前还是非常多人使用,我现在仍然用的ps2的键盘鼠标 ,这里选用ps2 作为人机交互主要原因是它的接口和时序都非常简单
ps2 硬件连接
ps2 接口电路如图
我们这里用到了四根线,大致的连接是这样
PS2PIN -> GD32vf103PIN
4 -> 5v
3 -> GND
5(clk)->PA0
1(data) -> PA3
ps2 时序
我们这里单片机作为从机接收ps2键盘发来的数据,所以简单来说就是在clk下降沿读取data的电平。首先要将clk和data拉高,不然键盘不会发送数据。
第一位是start 0, 最后一位是stop 1,倒数第二位校验,所以说我们要的数据就在中间data0-data7 一个移位就能读出
我们要做的就是在clk对应的引脚(PA0)挂上下降沿的外部中断,然后在中断处理函数中读取data对应引脚(PA3)电平,读取的函数如下
void EXTI0_IRQHandler(void) {
u8 state_now;
ps2_check = 0;
if (RESET != exti_interrupt_flag_get(EXTI_0)) {
state_now = gpio_input_bit_get(GPIOA, PS2_DATA);
if (ps2_bit_count == 11) {
ps2_data_now = 0x00;
if (0 == state_now) {
ps2_bit_count--;
}
} else {
if (ps2_bit_count < 11 && ps2_bit_count > 2) {
ps2_data_now = (ps2_data_now >> 1);
if (0 != state_now) {
ps2_data_now = ps2_data_now + 0x80;
}
ps2_bit_count--;
} else {
if (ps2_bit_count == 2) {
ps2_bit_count--;
} else if (ps2_bit_count == 1) {
ps2_bit_count = 11;
u8 current_char = ps2_decode(ps2_data_now);
if (current_char != 0) {
set_char(current_char);
}
}
}
}
}
exti_interrupt_flag_clear(EXTI_0);
//GPIO_BC(GPIOA) = LED_B;
//printf("in handler:%d now%d\n", ps2_bit_count,state_now );
}
然后就是数据的处理了,我这里用的第二套键盘扫描码,通码如下
按键按下发送通码,弹起发送断码,因为我周边还有很多没搭好,所以这里就简单处理让检测到f0后接收到的码作为当前按键,emmm确实这里目前没做其他的比如shift之类的,不过也很简单,隔段时间来填这个坑吧。
vga驱动
由于单片机的算力有限,为了还能干些其他事情 ,我们这里使用单色 分辨率是800* 600@56Hz ,同样考虑到这只是用来刷字符的卡 实际分辨率是600* 300
硬件接口
我们用的15针vga 大致连接如下
VGAPIN -> GD32VF103
HSYNC ->PB0
VSYNC -> PB6
GREEN->PA7
red和blue就直接接地
vga 时序以及驱动实现
具体单片机实现推荐去看看国外这个大神的,他是利用stm32f1完成,我在实现中也参照了他的一些思路。有关vga时序也是百度一大把这里就先不多介绍
stm32f1 vga驱动
我使用TIMER2CH2 作为行同步信号 通道三作为计算了消影的后的时间,即开始刷像素点的信号
在timer2中断处理函数中计算行数来进行场同步,这里更好的是利用定时器的主从模式让timer2来触发timer3,我为了节约定时器采用的在中断中记时并产生场同步信号,实测效果也不错,大致的初始化这样
void vga_timer_config(void) {
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2);
//rcu_periph_clock_enable(RCU_TIMER3);
timer_deinit(TIMER2);
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 3072;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2, &timer_initpara);
timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER2, TIMER_CH_2, &timer_ocinitpara);
timer_channel_output_config(TIMER2, TIMER_CH_3, &timer_ocinitpara);
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, 216);
timer_channel_output_mode_config(TIMER2, TIMER_CH_2,
TIMER_OC_MODE_PWM1);
timer_channel_output_shadow_config(TIMER2, TIMER_CH_2,
TIMER_OC_SHADOW_DISABLE);
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_3, 512);
timer_channel_output_mode_config(TIMER2, TIMER_CH_3,
TIMER_OC_MODE_PWM1);
timer_channel_output_shadow_config(TIMER2, TIMER_CH_3,
TIMER_OC_SHADOW_DISABLE);
//timer_auto_reload_shadow_enable(TIMER2);
//timer_master_slave_mode_config(TIMER3, TIMER_MASTER_SLAVE_MODE_ENABLE);
timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH2);
timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH3);
timer_interrupt_enable(TIMER2,
TIMER_INT_CH2 | TIMER_INT_CH3);
timer_enable(TIMER2);
}
由上面的分辨率算出来我们的像素时钟频率应该是36mhz,加上消影时间就得到了定时器的分频和具体的pulse
疫情在家条件有限,没有示波器和逻辑分析仪
我们刷像素点使用的是spi-dma功能,即dma自动加载像素点中数据到spi缓冲区发送,由于分频限制我们做的4分频,即时钟频率是27mhz 同时将每行数据刷两次。即我们实际的分辨率是600* 300,不高,但是单片机来说足够了,这里注意重新转载新的一行时要先将dma失能,然后再在开始传输时使能dma通道
具体实现还是直接看我GitHub代码吧 ,这里放几张成功实现的图
这是满屏绿色的
由于家中条件有限,连接只有杜邦线面包板,面包板寄生电容和线上干扰很大,所以说显示也有点丑陋,等打板后再继续吧
欢迎大家来交流 邮箱 :chen.yuheng@nexuslink.cn
打板出来了,很尴尬的是定义的按键在boot上,插上去没法dfu烧录。。。
可以多个大小字体切换,改变填充方式后也更漂亮了。同时移植了mathbasic解释器上去
tiny-math-basic解释器