STM32F103+OLED视频播放

目录

1 任务介绍

2 硬件设备

3 脚本工具

4 软件SPI实现W25Q64数据存储与读取

5 硬件IIC实现OLED显示

6 主函数调用

7 实现效果

1 任务介绍

        学完江科协的IIC通信和SPI通信后想趁热打铁实现一个小项目。该项目可以通过OLED实现视频播放(视频无声)。基本思路为通过IIC通信实现OLED显示,通过SPI通信实现数据存储与读取。单靠ARM本身的存储空间是没法存下一个视频的数据,因此需要将视频数据存储在外挂芯片W25Q64中。

        不用担心视频图像取模问题,我这里写了一个脚本可以实现视频转128*64(0.96寸OLED)的文本数据。

2 硬件设备

        STM32F103C8T6最小系统板、0.96寸OLED、W25Q64(可以直接用江科协提供的材料)

3 脚本工具

        视频本质上是播放一帧帧图片,因此在存储视频时需要将视频转换成图片进行存储,且0.96寸OLED的显示像素为128*64,所以对应的图片也必须改成这种尺寸。另外还需要将彩色视频转化为二值图片才能显示。

        视频的数据量比较多,这里我直接写了一个python脚本实现整个视频取模,最终视频其实就变成一个二维数组picture[count][pixs],其中count是视频帧数,pixs为视频像素,0.96寸的OLED其像素固定为128*64,一个数据是8bit,所以pixs=128*8. 运行下面这段代码会在工程目录中多一个array.txt文件,然后将这个文件的数据依次写入W25Q64中(依次最多写入20张图片,如果帧率较高的话要分批写入)。

import os

import cv2
orig_video_path = r'C:\Users\Lenovo\Desktop\Myroject\getpicture\gege.avi'# 原视频路径
save_pic = r'C:\Users\Lenovo\Desktop\Myroject\getpicture\picture\\'

def video():
    videoCapture = cv2.VideoCapture(orig_video_path)
    f = int(videoCapture.get(cv2.CAP_PROP_FPS))
    print('原视频帧率为:'+str(f))
    fps = 40  # 保存视频的帧率,可改变
    size = (128, 64)  # 保存视频大小,必须和OLED分辨率相对应
    save_dir = orig_video_path[:-4]+'_new.avi'
    videoWriter = cv2.VideoWriter(save_dir,
                                  cv2.VideoWriter_fourcc('D', 'I', 'V', 'X'), fps, size)

    while True:
        success, frame = videoCapture.read()
        if success:
            img = cv2.resize(frame, size)
            videoWriter.write(img)
        else:
            print('break')
            break

    # 释放对象,不然可能无法在外部打开
    videoWriter.release()

# 视频转彩色图片
def video_pic():
    video_path = orig_video_path[:-4]+'_new.avi'
    cap = cv2.VideoCapture(video_path)
    sucess = cap.isOpened()
    frame_count = 0
    i = 0
    while sucess:
        frame_count += 1
        sucess, frame = cap.read()
        if(frame is None):
            break
        if (frame_count % 5 == 0):    # 每隔5帧保存一张图片(每帧都保存数据太多了)
            i += 1

        cv2.imwrite(save_pic+'\\'+str(i)+'.jpg', frame)
    cap.release()

#彩色图片转二值图片并保存为txt文件
def pic_txt():
    file = open('array.txt', 'w')
    pictures = 56   # 需要转换的图片总数量
    for p in range(len(os.listdir(save_pic))):
        nums = []
        file_path = save_pic+str(p)+'.jpg'
        img = cv2.imread(file_path, 0)
        img[img>140] = 255  # 阈值设置为140,即像素大于140的为0,小于140的为1
        img[img<=140] = 1
        img[img==255] = 0
        # 阳码
        # img[img<=140] = 0
        # img[img==255] = 1
        row, col = img.shape
        for i in range(0, row,8):
            for j in range(0,col,1):
                num = 0
                for k in range(8):
                    num += img[i+k][j]*pow(2,k)  # 不能直接加,要乘以2的n次方
                nums.append(num)
        line = '{'
        for i in range(len(nums)):
            if(i%32 == 0):
                file.write(line+'\n')
                print(line)
                line = ''
            line += str(hex(nums[i]))+','
        line = line[:-1]+'\n'
        file.write(line)
        line = '}, /***** ' + str(p) + ' *****/\n'
        file.write(line)
    file.close()

if __name__ == '__main__':
    video()     # 先转换视频尺寸
    video_pic()   # 将视频转为图片进行保存(当然也可以不保存,这里只是方便分析)
    pic_txt()    # 图片转文本,用于OLED水平地址模式显示

        当然要是不嫌麻烦也可以用PCtoLCD2002这个软件对图片手动取模,但是这个软件每次只能转一张图片,而且只能转二值bmp格式图片,如果是彩色图片还需要用电脑自带的画图工具修改图片格式,所以不建议使用软件转,如果你非要用软件取字模就按照下面的格式进行设置:

4 软件SPI实现W25Q64数据存储与读取

        如果完成上述步骤,你就可以获得一个array.txt文本文档,这个文本文档里面存放着所有图片数据,把数据定义成二维数组,将这个二维数据一次写入到W25Q64。我一共写了32张图片,每张图片的像素为128*64bit,即128*8byte,所以数组维度为[32][128*8]。SPI实现多张图片的写入函数如下:

W25Q64.c文件,其中

void W25Q64_PageProgram_N(uint32_t Address, uint8_t DataArray[][128*8], uint16_t Count)

函数用于多张图片的写入。Address为写入初始地址,DataArray为图片数组,Count为图片数量。

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)

用于读取W25Q64数据,Address为读取数据块的初始地址,DataArray为读取数据缓存区,Count为数据总长度,单位为byte 

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64.h"
#include "W25Q64_Ins.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();		//开始通信
	MySPI_SwapByte(W25Q64_JEDEC_ID);//发送获取设备ID命令
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收设备厂商号
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收设备高8位ID
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//接收设备低8位ID
	MySPI_Stop();//停止通信
}

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送写指令(SPI固定,所有起始指令后面接的都是控制指令)
	MySPI_Stop();
}

void W25Q64_WaitBusy(void)	//读状态寄存器,等待读写就绪态
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 1000000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)	//等待超时退出
		{
			break;
		}
	}
	MySPI_Stop();
}

/*
	function	向W25Q46写入存储数据,用于写入一张图
	@param	Address	写入地址
	@param	DataArray 写入数据
@param	Count 一张图的数据长度一般为128*8
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i,j=0;
	uint16_t L = Count / 256;	//一次数据传输最多256字节,第二次要重新写入地址
	uint16_t M = Count % 256;
	uint32_t Address1 = Address;
	//开始传输数据
	while(L > j)
	{
		W25Q64_WriteEnable();
		Address1 = (uint32_t)(Address + (256*j));
		MySPI_Start();
		MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
		MySPI_SwapByte(Address1 >> 16);//每次发8字节,但是地址一共24字节,所以发三次,先发送高8位
		MySPI_SwapByte(Address1 >> 8);
		MySPI_SwapByte(Address1);
		for (i = 0; i < 256; i ++)
		{
			MySPI_SwapByte(DataArray[i + 256*j]);
		}
		MySPI_Stop();
		W25Q64_WaitBusy();
		j++;
	}
	W25Q64_WriteEnable();
	Address1 = (uint32_t)(Address + (256*j));
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address1 >> 16);//每次发8字节,但是地址一共24字节,所以发三次,先发送高8位
	MySPI_SwapByte(Address1 >> 8);
	MySPI_SwapByte(Address1);
	for (i = 0; i < M; i ++)
	{
		MySPI_SwapByte(DataArray[i + 256*j]);
	}
	MySPI_Stop();
	W25Q64_WaitBusy();

}
/*
	function	向W25Q46写入存储数据,用于写入一张图
	@param	Address	写入地址
	@param	DataArray 写入数据
@param	Count 图片数量,图片默认像素为128*64
*/
void W25Q64_PageProgram_N(uint32_t Address, uint8_t DataArray[][128*8], uint16_t Count)
{
	int i;
	uint32_t temp_address = Address;
	//一张图1KB,一个扇区最多存放4张图,每次最多擦除一个扇区
	uint16_t pages = Count / 4;
	pages += Count % 4;
	//先擦除扇区
	for(i = 0; i<pages; i++)
	{
		W25Q64_SectorErase(temp_address);
		uint32_t temp1_address = temp_address;
		for(int j=0; j<4; j++)
		{
			W25Q64_PageProgram(temp1_address, DataArray[i*4+j], 128*8);
			temp1_address += 0x000400;	//每写入一张图片,向前移动1024byte
		}
		temp_address += 0x001000;
	}

}

/*
	function	擦除存储器数据
@param	Address	需要擦除的扇区地址,一个扇区
*/
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//发送擦除指令,每次擦除一个扇区
	MySPI_SwapByte(Address >> 16);	//擦除地址
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}
/*
	function	从W25Q46发送读取数据
	@param	Address	读取起始地址地址
	@param	DataArray 数据缓冲
	@param	Count 读取数据长度
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

5 硬件IIC实现OLED写入

#include "OLED_IIC.h"
void My_Init(void)
{
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
		
		GPIO_InitTypeDef GPIO_InitStructure;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOB, &GPIO_InitStructure);
		
		I2C_InitTypeDef I2C_InitStructure;
		I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
		I2C_InitStructure.I2C_ClockSpeed = 400000;
		I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
		I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
		I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
		I2C_InitStructure.I2C_OwnAddress1 = 0x30;
		I2C_Init(I2C2, &I2C_InitStructure);
		
		I2C_Cmd(I2C2, ENABLE);
}	
//发送一个字节数据
void My_OLED_WriteData(uint16_t addr, uint16_t data)
{
		while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY));
	
		I2C_GenerateSTART(I2C2,ENABLE);
		while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT));
	
		I2C_Send7bitAddress(I2C2,0X78,I2C_Direction_Transmitter);
		while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	
		I2C_SendData(I2C2, addr);	
		while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING));	
                           //I2C_EVENT_MASTER_BYTE_TRANSMITTING  
	
		I2C_SendData(I2C2, data);
		while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING));
	
		I2C_GenerateSTOP(I2C2,ENABLE);
}
//写命令
void My_WriteCmd(unsigned char cmoodcmd)
{
	 My_OLED_WriteData(0x00,cmoodcmd);
}
//写数据
void My_WriteDATA(unsigned char ic2data)
{
   My_OLED_WriteData(0x40,ic2data);
}

//设置光标
void My_Starting_point(unsigned char x,unsigned char y)
{
		My_WriteCmd(0xb0+y);
		My_WriteCmd((x&0xf0)>>4|0x10); //1111 0000 ->0000 1111|0X10=0001 1111
		My_WriteCmd((x&0x0f)|0x01);//0000 1111 | 0000 0001 =0000 1111
}

void oled_fill(unsigned char Flii_data)
{
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		My_Starting_point(j, 0);
		for(i = 0; i < 128; i++)
		{
			My_WriteDATA(0x00);
		}
	}
}
//清屏
void My_OLED_Clear(void)
{
		oled_fill(0x00);
}

void My_OLED_Init()
{
	uint32_t i, j;
	
	for (i = 0; i < 1000; i++)			//上电延时
	{
		for (j = 0; j < 1000; j++);
	}
	
	My_Init();			//端口初始化
	
	My_WriteCmd(0xAE);	//关闭显示
	
	My_WriteCmd(0xD5);	//设置显示时钟分频比/振荡器频率
	My_WriteCmd(0x80);
	
	My_WriteCmd(0xA8);	//设置多路复用率
	My_WriteCmd(0x3F);
	
	My_WriteCmd(0xD3);	//设置显示偏移
	My_WriteCmd(0x00);
	
	My_WriteCmd(0x40);	//设置显示开始行
	
	My_WriteCmd(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	
	My_WriteCmd(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置

	My_WriteCmd(0xDA);	//设置COM引脚硬件配置
	My_WriteCmd(0x12);
	
	My_WriteCmd(0x81);	//设置对比度控制
	My_WriteCmd(0xCF);

	My_WriteCmd(0xD9);	//设置预充电周期
	My_WriteCmd(0xF1);

	My_WriteCmd(0xDB);	//设置VCOMH取消选择级别
	My_WriteCmd(0x30);

	My_WriteCmd(0xA4);	//设置整个显示打开/关闭

	My_WriteCmd(0xA6);	//设置正常/倒转显示
	
	My_WriteCmd(0x20);	//修改地址模式为水平模式
	My_WriteCmd(0x00);

	My_WriteCmd(0x8D);	//设置充电泵
	My_WriteCmd(0x14);

	My_WriteCmd(0xAF);	//开启显示
	
	My_WriteCmd(0x2E);	//关闭滚动显示
		
	My_OLED_Clear();		//OLED清屏
}

6 主函数

uint8_t ArrayRead[128*8] = {0};
//void begin_show(void);
void twinkle_show(uint8_t pic[], uint16_t length, uint16_t width);
int main(void)
{
//	OLED_Init();
	My_OLED_Init();
	W25Q64_Init();
//	W25Q64_PageProgram_N(0x000000, OLED_PIC2, 20);	//SPI写图片数据
	uint32_t temp_address = 0x000000;
	int i =0,j=0 ;
	for(i=0; i<32; i++)
	{
		My_OLED_Clear();
		My_Starting_point(0, 0);
//		OLED_Clear();
//		OLED_SetCursor(0, 0);
		W25Q64_ReadData(temp_address, ArrayRead, 128*8);	//每次读一张图片
//		twinkle_show(ArrayRead, 128, 64);
		for(j=0; j<128 *8; j++)
		{
			My_WriteDATA(ArrayRead[j]);	//硬件IIC写OLED
//			OLED_WriteData(ArrayRead[j]);//软件IIC写OLED
			
		}
		Delay_ms(20);
		temp_address += 0x000400;
	}
	while (1)
	{

	}
}

7 实现效果

视频没法上传,这里实现效果直接贴出b站UP主的效果,如有侵权请及时联系。

丝滑单片鸡 STM32F103C8+0.96寸OLED_哔哩哔哩_bilibili

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的内容,可以得知在STM32F407开发板上播放视频需要使用另外一个128*128的OLED屏幕,而不是小熊派开发板的屏幕。这是因为小熊派的屏幕分辨率是240*240,无法满足视频播放的需求。而128*128的OLED屏幕可以满足视频的显示需求,并且每一帧图像只需要32768字节的缓存。 根据引用\[3\]的内容,播放视频的原理是通过截取视频的每一帧图像,将图像转换成bin文件,然后将bin文件拷贝到SD卡中。接下来,使用STM32F407开发板的驱动程序读取SD卡中对应的bin文件,并将每一帧图像显示到OLED屏幕上。 因此,要在STM32F407开发板上播放视频,需要进行以下步骤: 1. 使用视频截图软件截取视频的每一帧图像。 2. 将每一帧图像转换成bin文件。 3. 将bin文件拷贝到SD卡中。 4. 使用STM32F407开发板的驱动程序读取SD卡中的bin文件,并将每一帧图像显示到128*128的OLED屏幕上。 请注意,这个过程需要编写适配STM32F407开发板的驱动程序,并且需要合适的硬件连接和配置。具体的实现细节可以参考相关的开发文档和示例代码。 #### 引用[.reference_title] - *1* *2* *3* [手把手教你在STM32上实现OLED视频播放(很简单也很硬很肝!)](https://blog.csdn.net/morixinguan/article/details/114860708)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值