新研究了個東東,家裡的廢舊顯示器終於有了利用價值
這些年下來,家裡,公司有很多廢舊的電視機,顯示器,投影機。你說扔掉吧,有點可惜,賣給收廢品的吧,其實和扔也差不多。總想著怎麼把這個淘汰下來的顯示器給利用上呢。
這些顯示器都有個共性,就是帶有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
&n