赞
踩
1 任务介绍
2 硬件设备
3 脚本工具
4 软件SPI实现W25Q64数据存储与读取
5 硬件IIC实现OLED显示
6 主函数调用
7 实现效果
学完江科协的IIC通信和SPI通信后想趁热打铁实现一个小项目。该项目可以通过OLED实现视频播放(视频无声)。基本思路为通过IIC通信实现OLED显示,通过SPI通信实现数据存储与读取。单靠ARM本身的存储空间是没法存下一个视频的数据,因此需要将视频数据存储在外挂芯片W25Q64中。
不用担心视频图像取模问题,我这里写了一个脚本可以实现视频转128*64(0.96寸OLED)的文本数据。
STM32F103C8T6最小系统板、0.96寸OLED、W25Q64(可以直接用江科协提供的材料)
视频本质上是播放一帧帧图片,因此在存储视频时需要将视频转换成图片进行存储,且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格式图片,如果是彩色图片还需要用电脑自带的画图工具修改图片格式,所以不建议使用软件转,如果你非要用软件取字模就按照下面的格式进行设置:
如果完成上述步骤,你就可以获得一个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();
- }
- #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清屏
- }
- 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)
- {
-
- }
- }
视频没法上传,这里实现效果直接贴出b站UP主的效果,如有侵权请及时联系。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。