使用36-pin的STM32输出VGA, VGA output using a 36-pin STM32
使用36-pin的STM32输出VGA
手头上有个项目需要通过单片机来控制将图像显示在LCD上,在网上搜了一阵子,发现都是使用的FPGA做的,
开始自己对FPGA不是很熟,一直在用的也是ARM系列的,终于让我找到一份至少现在看起来还是含金量蛮高的资料,
因为是英文的,这边先将它翻译一下(原文链接)。
想到之前玩的一些老的视频游戏和街机游戏(很早之前,大概70/80年代左右),脑子里浮现出一个想法:
如果在今天,我们是不是可以使用成本比较低的微控制器来实现之前玩玩的那些游戏呢?
这些微控制器设计的初衷并不是用来干这些事情的,所以问题也就产生了:
如何在使用很少或者不使用外部组件的情况下向显示器输出视频信号呢?
我们选择了36-pin, 72 MHz的STM32 (STM32F103T8U6),
足够用于产生黑白视频信号和点信号,同时还使用了一些定时器和SPI(在这种方式下更新帧缓冲是自动完成的),
在400*200分辨率的显示器上VGA输出视频信号看起来还是比较可观的。
使用的材料:
1 STM32F103T8U6开发板一块(或者同类型的开发板)。我们使用的是AK-STM32-LKIT。
2 VGA母口一个(DB15)
虽然帧缓冲区是400*200的,但是输出的分辨率却是800*600(56hz刷新频率),
我们采用把横着点绘制两次,竖着的点绘制三次的方法来达到扩展分辨率的目的。
我们选择800×600 @ 56Hz的原因是因为像素时钟;
输出分辨率使用36MHz像素时钟,周期是72MHz的倍数(STM32的频率),
因为我们需要使用SPI产生像素信号,可以把STM32的频率经过SPI预分频得到18MHz的像素时钟,
然后将每一个像素点绘制两次,具体方法是当在水平方向800像素点时输出一个信号像素,
SPI 的 MOSI信号保持低电平或者高电平两倍的时间(相比于之前绘制一个点的时间)。
帧缓冲区是一个52×200字节的数组。每一行有50*8=400个像素(每一个bit是一个像素),
剩下的两个字节(52-50)模拟每一行的消隐间隔。
#define VID_VSIZE 200
#define VID_HSIZE 50 __align() u8 fb[VID_VSIZE][VID_HSIZE+];
在这一块ram中写入的数据都会被输出到屏幕,DMA被设置为自动从数据缓冲区读取数据并且输出到SPI的MOSI引脚。
水平同步
水平同步信号( horizontal synchronism signal)和后延时间(back porch time)由TIM1定时器产生的通道1和2产生,TIM1定时器产生的通道1连接到PA8。
H-SYNC也就是TIM1定时器的通道1将会产生水平同步信号给显示器。
H-BACKPORCH也就是TIM1定时器的通道2,计算水平同步时间的和以及后延时间,
这个定时器产生一个中断用于触发DMA开始通过SPI发送像素的请求。
帧缓冲里面的每一行都会重复这样的过程,
垂直同步
TIM2定时器用于产生垂直同步信号,但是实在从机模式下。
TIM2计算主机(TIM1)产生的H-SYNC脉冲数。
TIM2的通道2通过PA1输出V-SYNC信号。
TIM2的通道3将会触发一个中断当定时器的计数器达到V-SYNC的和垂直后沿时间。
这个中断会设置一个变量表明正在扫描一个有效帧并且DMA可以开始发送像素到屏幕了。
像素发生器
像素由SPI的MOSI(PA7)产生。
定时器TIM1的通道2产生一个中断用于使能DMA TX请求向SPI发送数据。
DMA将会从帧缓冲区读取一行并且将数据放到SPI的DR寄存器。
DMA被设置用来在一行信号被发送之后产生一个中断,行号是递增的。
因为我们将每一行发送了三次,我们在中断中将计数加1。
当三行数据被发送出去,我们将DMA指针指向下一行的帧缓冲。
当所有的行被发送出去,DMA被禁止直到下一个有效单的帧中断发生(TIM2通道3)。
连接
你只需要几根杜邦线和一个母口的VGA接口就可以完成这项工作了。
VGA标准说输出信号应该在0.7V到1V之间,所以你需要在线上进行分压(串联68欧姆和33欧姆的电阻要比47pF和68欧姆的并联),
我们已经测试了一系列的LCD寄存器在没有分压的情况下,工作起来还行。
引脚发参考AK-STM32-LKIT扩展板接插件,引脚命名对于所有的STM32都有效。
根据你自己所用的STM32的手册确定使用的引脚是否一致。
我们使用绿色(VGA的引脚2)来模拟旧的那种显示效果,你可以使用其他的色彩方案使用
RED/GREEN/BLUE DB15引脚,可以创建8种颜色组合。
结论
我们使用了一个低成本微处理器作为VGA控制器,实现这个目的的方法很多,但是这种方法不需要额外的组件除了一个VGA接口。
如果你使用的是更高级的STM32,你可以试着使用扩大缓冲区而且在DMA被禁止的时候写帧缓冲区以避免数据被割裂。
你可以下载 源码,在里面你可以找到画线描点等等的工具库。
下一篇日志中会通过VGA样例来实现视频游戏。
Space Invaders for STM32
Using the previous VGA output project, we have created this Space Invaders version for STM32.
The source code is kept as a separated project, but uses the same engine of the VGA output project.
Click here to download the source code.
Connections
We have added three push-buttons to the original connections.
These push-buttons serve as a joystick.
Here are the connections for the push-buttons and the VGA connector:
THANK YOU for sharing you project with us.
Your project was a starting point to our project.
I used your logic, rewritten whole code, expanded to 2 output SPI,
sinhronized them in slave mode using two more timers.
One to be same as TIM1, to trigger 10Mhz timer for SPI CLK.
I did additional logic with input part and viola an adapter on a MCU.
https://www.youtube.com/watch?v=HGje7a6_1Jk
Best regards from Slovenia
#ifndef __VIDEO_H
#define __VIDEO_H #include "gdptypes.h" #define VID_HSIZE 50 // Horizontal resolution (in bytes)
#define VID_VSIZE 200 // Vertical resolution (in lines) #define VID_PIXELS_X (VID_HSIZE * 8)
#define VID_PIXELS_Y VID_VSIZE
#define VID_PIXELS_XR (VID_PIXELS_X + 16)
#define VID_HSIZE_R (VID_HSIZE + 2) // Function definitions void vidInit(void);
void vidClearScreen(void); #endif // __VIDEO_H
/***************************************************************************
* STM32 VGA demo
* Copyright (C) 2012 Artekit Italy
* http://www.artekit.eu
* Written by Ruben H. Meleca ### video.c # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ***************************************************************************/ #include "stm32f10x.h"
#include "video.h" #define VTOTAL 52 /* Total bytes to send through SPI */
__align() u8 fb[VID_VSIZE][VID_HSIZE+]; /* Frame buffer */
static volatile u16 vline = ; /* The current line being drawn */
static volatile u32 vflag = ; /* When 1, the SPI DMA request can draw on the screen */
static volatile u32 vdraw = ; /* Used to increment vline every 3 drawn lines */ void TIMER_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef nvic;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
u32 TimerPeriod = ;
u16 Channel1Pulse = , Channel2Pulse = , Channel3Pulse = ; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); /*
SVGA 800x600 @ 56 Hz
Vertical refresh 35.15625 kHz
Pixel freq. 36.0 MHz 1 system tick @ 72Mhz = 0,0138 us
*/ /*
Horizontal timing
----------------- Timer 1 period = 35156 Hz Timer 1 channel 1 generates a 2 us pulse for HSYNC each 28.4 us. : D = ( 2 / 28.4 )
28.4 us = Visible area + Front porch + Sync pulse + Back porch.
HSYNC is 2 us long, so the math to do is:
2us / 0,0138us = 144 system ticks. Timer 1 channel 2 generates a pulse equal to HSYNC + back porch = 2 + 3.55 = 5.55
This interrupt will fire the DMA request to draw on the screen if vflag == 1.
Since firing the DMA takes more or less 800ns, we'll add some extra time.
The math for HSYNC + back porch is:
(2us + 3,55us - dma) / 0,0138us = +-350 system ticks Horizontal timing info 800 + 24+72+128 = 1024
-------------------------------------------- Dots us
--------------------------------------------
Visible area 800 22.222222222222
Front porch 24 0.66666666666667
Sync pulse 72 2
Back porch 128 3.5555555555556
Whole line 1024 28.444444444444 */ TimerPeriod = ;
Channel1Pulse = ; /* HSYNC */
Channel2Pulse = ; /* HSYNC + BACK PORCH */ TIM_TimeBaseStructure.TIM_Prescaler = ;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = TimerPeriod; // 2048
TIM_TimeBaseStructure.TIM_ClockDivision = ;
TIM_TimeBaseStructure.TIM_RepetitionCounter = ;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; // 144
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set; TIM_OC1Init(TIM1, &TIM_OCInitStructure); // PA8
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive; // No Output
TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; // 352
TIM_OC2Init(TIM1, &TIM_OCInitStructure); /* TIM1 counter enable and output enable */
TIM_CtrlPWMOutputs(TIM1, ENABLE); /* Select TIM1 as Master */
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); /*
Vertical timing
--------------- Polarity of vertical sync pulse is positive. Lines = 600 + 1+2+22 = 625
------------------------------
Visible area 600
Front porch 1
Sync pulse 2
Back porch 22
Whole frame 625 */ /* VSYNC (TIM2_CH2) and VSYNC_BACKPORCH (TIM2_CH3) */
/* Channel 2 and 3 Configuration in PWM mode */
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Gated);
TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0); // TIM1
TimerPeriod = ; /* Vertical lines */
Channel2Pulse = ; /* Sync pulse */
Channel3Pulse = ; /* Sync pulse + Back porch = 2 lines + 24 lines */
TIM_TimeBaseStructure.TIM_Prescaler = ;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
TIM_TimeBaseStructure.TIM_ClockDivision = ;
TIM_TimeBaseStructure.TIM_RepetitionCounter = ; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;
TIM_OC2Init(TIM2, &TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
TIM_OC3Init(TIM2, &TIM_OCInitStructure); /* TIM2 counter enable and output enable */
TIM_CtrlPWMOutputs(TIM2, ENABLE); /* Interrupt TIM2 -- Generate IRQ to restart the Frame Buffer Count */
nvic.NVIC_IRQChannel = TIM2_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = ;
nvic.NVIC_IRQChannelSubPriority = ;
nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic);
TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE); /* Interrupt TIM1 -- Generate IRQ to start DMA1 Channel 3 */
nvic.NVIC_IRQChannel = TIM1_CC_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = ;
nvic.NVIC_IRQChannelSubPriority = ;
nvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic);
TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE); TIM_Cmd(TIM2, ENABLE);
TIM_Cmd(TIM1, ENABLE);
} void SPI_Configuration(void)
{
NVIC_InitTypeDef nvic;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_Cmd(SPI1, DISABLE);
DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32) &fb[][];
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = VTOTAL;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Mode : (0,1)
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 72/4=18MHz
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = ;
SPI_Init(SPI1, &SPI_InitStructure); SPI_CalculateCRC(SPI1, DISABLE);
SPI_Cmd(SPI1, ENABLE); SPI1->CR2 |= SPI_I2S_DMAReq_Tx;
nvic.NVIC_IRQChannel = DMA1_Channel3_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = ;
nvic.NVIC_IRQChannelSubPriority = ;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic); DMA1_Channel3->CCR &= ~;
DMA1_Channel3->CNDTR = VTOTAL;
DMA1_Channel3->CMAR = (u32) &fb[][]; DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
} //*****************************************************************************
// This irq is generated at the end of the horizontal back porch.
// Test if inside a valid vertical start frame (vflag variable),
// and start the DMA to output a single frame buffer line through the SPI device.
//*****************************************************************************
__irq void TIM1_CC_IRQHandler(void)
{
if (vflag)
{
DMA1_Channel3->CCR = 0x93;
}
TIM1->SR = 0xFFFB; //~TIM_IT_CC2;
} //*****************************************************************************
// This irq is generated at the end of the vertical back porch.
// Sets the 'vflag' variable to 1 (valid vertical frame).
//*****************************************************************************
__irq void TIM2_IRQHandler(void)
{
vflag = ;
TIM2->SR = 0xFFF7; //~TIM_IT_CC3;
} //*****************************************************************************
// This interrupt is generated at the end of every line.
// It will increment the line number and set the corresponding line pointer
// in the DMA register.
//*****************************************************************************
__irq void DMA1_Channel3_IRQHandler(void)
{
DMA1->IFCR = DMA1_IT_TC3;
DMA1_Channel3->CCR = 0x92;
DMA1_Channel3->CNDTR = VTOTAL; vdraw++; if (vdraw == )
{
vdraw = ; vline++; if (vline == VID_VSIZE)
{
vdraw = vline = vflag = ;
DMA1_Channel3->CMAR = (u32) &fb[][];
} else {
DMA1_Channel3->CMAR += VTOTAL;
}
}
} void vidClearScreen(void)
{
u16 x, y; for (y = ; y < VID_VSIZE; y++)
{
for (x = ; x < VTOTAL; x++)
{
fb[y][x] = ;
}
}
} void vidInit(void)
{
SPI_Configuration();
TIMER_Configuration();
vidClearScreen();
}
使用36-pin的STM32输出VGA, VGA output using a 36-pin STM32的更多相关文章
- CS5212 pin to pin 替代RTD2166|DP转VGA芯片|CS5212转换电路设计方法
CS5212适用于设计DP转VGA转换电路,主要用在嵌入式单片机基于工业机或者INTEL X86主板上面,也适用于多个电子配件市场和显示器应用程序,如笔记本电脑.主板.台式机.适配器.转换器和转接器. ...
- PHP输出缓冲控制- Output Control 函数应用详解
说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffer写满 ...
- PHP输出缓冲控制 - Output Control 函 应用详解
简介 说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffe ...
- STM32输出比较模式
搜索好久,各种文章良莠不齐,转载以下几篇 http://www.eeworld.com.cn/mcu/article_2016101130334.html(输出比较冻结模式) http://www.e ...
- PHP输出缓冲(Output Buffering)
什么是缓冲区? 简单而言,缓冲区的作用就是,把输入或者输出的内容先放进内存,而不显示或者读取.至于为什么要有缓冲区,这是一个很广泛的问题~其实缓冲区最本质的作用就是,协调高速CPU和相对缓慢的IO设备 ...
- PHP输出控制(Output Control)函数
ob_start 此函数将打开输出缓冲.当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中. 内部缓冲区的内容可以用 ob_get_contents() ...
- PHP 输出缓冲控制(Output Control) 学习
php 缓冲简介 其实我对php ob 系列印象还是很模糊,具体怎么玩的,还不是很了解,平时curd,确实对这些内容没有深入.作为phper 甚是惭愧.网上搜了一通,互相copy,代码运行不能出现作者 ...
- STM32芯片去除读写保护 | 使用ST-Link Utility去除STM32芯片读写保护
1.使用ST-LINK V2下载器连接到STM32芯片, 点击Connect: 2.存在读保护: 3.修改选项字节(Option Bytes... ): 4.将读保护修改为Disabled. 5.打钩 ...
- ASP.Net Web API 输出缓存 转载 -- Output caching in ASP.NET Web API
一.Nuget安装相关dll Web API 2 : Install-Package Strathweb.CacheOutput.WebApi2 Web API 1 : Install-Package ...
随机推荐
- 最小生成树问题------------Prim算法(TjuOj_1924_Jungle Roads)
遇到一道题,简单说就是找一个图的最小生成树,大概有两种常用的算法:Prim算法和Kruskal算法.这里先介绍Prim.随后贴出1924的算法实现代码. Prim算法 1.概览 普里姆算法(Prim算 ...
- 第8月第19天 django rest
1. def retrieve(self, request, pk=None): try: book = Book.objects.get(book_id=pk) except Book.DoesNo ...
- Shell脚本中实现切换用户并执行命令操作【转】
第一种方法 cat test.sh #!/bin/bashsu - test <<EOFpwd;exit;EOF 执行结果图: 第二种方法 当然也可以用下面的命令来执行 复制代码代码如下: ...
- C++ 螺旋矩阵算法
清理磁盘空间的时候翻出了多年前写过的螺旋矩阵,代码效率和水平较低,纪念一下,保存到博客园! // ConsoleApplication3.cpp : 定义控制台应用程序的入口点. // #includ ...
- 数据库SQL语句性能优化
选择最有效率的表名顺序 ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下 ...
- 010_MAC下权限问题的那些事
一. arun:bin arunyang$ sh catalina.sh start #启动tomcat报一堆的没有权限~~~~(>_<)~~~~ 二.解决如下 aru ...
- springboot中url地址重写(urlwrite)
在日常网站访问中,会把动态地址改造成伪静态地址. 例如: 访问新闻栏目 /col/1/,这是原有地址,如果这样访问,不利于搜索引擎检索收录,同时安全性也不是很好. 改造之后: /col/1.html. ...
- Python 3之str类型、string模块学习笔记
Windows 10家庭中文版,Python 3.6.4, Python 3.7官文: Text Sequence Type — str string — Common string operatio ...
- https-配置使用HTTPS的ASP.NET Web应用
有关HTTPS.SSL以及SSL证书的工作原理,参见 <HTTPS-HTTPS原理> <HTTPS-SSL证书> <HTTPS-攻击实例与防御> 本文将演示如何在I ...
- Java登陆拦截器
package com.beidou.warehouseerp.interceptor; import com.alibaba.fastjson.JSON; import com.beidou.war ...