这些年下来,家里,公司有很多废旧的电视机,显示器,投影机。你说扔掉吧,有点可惜,卖给收废品的吧,其实和扔也差不多。总想着怎么把这个淘汰下来的显示器给利用上呢。

这些显示器都有个共性,就是带有VGA接口。上网搜索研究了一下,发现VGA接口是可以编程驱动的。

VGA的电气接口除了GND以外,基本的必须有5条信号线:hsync行同步,vsync场同步,red红,green绿,blue蓝。VGA的时序要求是比较严格的,差一点点都无法正常显示。具体的VGA时序,这里就不赘述了,大家可以网上搜索一下。

由于我打算用单片机实现VGA的时序,使用STM32F103测试后可以实现,但由于103的内存太少了,像素的计算搬运有点吃力,最后还是决定使用STM32F4,手头刚好有F401,主频84M,内存64K,足够我使用了。

我这里设计的是一个320*200(横向320个点,纵向200行)的VGA输出音乐频谱模块,基本参数如下:

电源电压:DC5-12V

工作电流:<30mA

频率响应300-18kHz

声道数:2

 

为了能够稳定输出时序,使用了两个定时器中断分别输出行频和场频。其次需要对音频进行40Khz的高速采样,这里也使用了一个定时器+DMA,最后还需要对音频进行RFFT运算,得到幅值后转换为像素显示。前前后后打了5次PCB,花了将近4个月的时间完成,中间也遇到不少坑,这里只把最后的成果展示一下,作为疫情宅家纪念。

关键代码:

使用TIM1和TIM2分别做为行输出和场输出信号,在行输出中断中,使用GPIOB发送颜色信号,在场消隐中计数,并复位图像显示头部。

void TIMER_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef nvic;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    u32 TimerPeriod = 0;
    u16 Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0;

    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM2);
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
//    RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_4);
//return;
        
    /*
        Horizontal timing
        -----------------
    
    */

    TimerPeriod = 2048;
    Channel1Pulse = 144;        /* HSYNC */
    Channel2Pulse = 352;         /* HSYNC + BACK PORCH */
    
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    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;
    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);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
    TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
    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
        ---------------        
    */

    /* 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);
    
    TimerPeriod =600;//625;        /* Vertical lines */
    Channel2Pulse = 2;        /* Sync pulse */
    Channel3Pulse = 24;        /* Sync pulse + Back porch */
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
    TIM_TimeBaseStructure.TIM_ClockDivision =2;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    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 */
    nvic.NVIC_IRQChannel = TIM2_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0;
    nvic.NVIC_IRQChannelSubPriority = 0;
    nvic.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&nvic);
    TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE);

    /* Interrupt TIM1 */
    nvic.NVIC_IRQChannel = TIM1_CC_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0;
    nvic.NVIC_IRQChannelSubPriority = 0;
    nvic.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&nvic);
    TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE);
    
    TIM_Cmd(TIM2, ENABLE);
    TIM_Cmd(TIM1, ENABLE);

}

为了提高速度,一开始使用了DMA传输像素数据,但是,太快了,在行中断后,DMA输出的速度,经过多次尝试无法控制在合理的时序内,导致显示器识别的分辨率过高,图像缩成一点点。最后还是放弃了,使用CPU循环将点阵输出。

 

以下是电路图:

以下是PCB

以下是实物图和效果图

 

可以当时钟用

视频链接:https://v.youku.com/v_show/id_XNDUzMzAyNDExMg==.html

 

版权声明:本文为csaaa原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/csaaa/p/12264946.html