文中需要用到的硬件模块
1.stm32最小系统板
2.下载仿真器带串口的(pwlink2)
3.oled咯
需要用到的软件
1.python(用来做视频处理和串口输出)
2.keil5
3.取模软件
下来就废话少说,直接演示一遍流程
1.打开python,安装opencv,pip install opencv,然后把下面代码输入就能得到视频的每一帧图片了
import cv2
import os
# 打开视频文件
video = cv2.VideoCapture('F:/kun/video/maxin_g.mp4')
# 检查视频是否成功打开
if not video.isOpened():
print('无法打开视频文件')
exit()
# 创建保存图片的目录
path = 'F:/kun/video/maxin_g/'
if not os.path.exists(path):
os.makedirs(path)
#调整图片长宽比
new_height = 64
new_width_flag = 0
mask = np.zeros((64,128),np.uint8)
# 初始化帧计数器
frame_count = 1000
while True:
# 逐帧读取视频
ret, frame = video.read()
# 如果视频读取结束,退出循环
if not ret:
break
if new_width_flag == 0:
height,width=frame.shape[:2]
new_width = int(width * new_height / height)
new_width_flag+=1
img = cv2.resize(frame,(new_width,new_height))
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask[:64,mid_dst:mid_dst+new_width] = img_gray
#frame = cv2.resize(mask,(128,64)) #画布大小只能是这个,不然就得改oled显示原理代码 要不就其他地方填黑色 必须得是1024个128*8才能写oled
# 在帧上添加帧编号
frame_count += 1
# text = f'Frame: {frame_count}'
# frame = cv2.putText(frame, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
# 将帧保存为图片
filename = os.path.join(path, f'{frame_count}.jpg')
cv2.imwrite(filename, mask)
# 显示当前帧
cv2.imshow('Frame', mask)
# 按下 'q' 键退出循环
if cv2.waitKey(60) & 0xFF == ord('q'):
break
# 释放资源
video.release()
cv2.destroyAllWindows()
2.打开取模软件image2lcd,批量转换成c格式,如图
3.打开python,粘贴以下代码,改好路径就行,就能合并上面生成的.c文件成.h。
import os
path = 'F:\\kun\\video\\maxin_g\\batch' # 源文件路径,需要酌情修改
# 获取当前文件夹中的文件名称列表
filenames = os.listdir(path)
# 打开当前目录下的xi.h文件,如果没有则创建
file = open('ma.h', 'w', encoding='utf8') # 目标文件,合并成的文件
# 遍历文件名
for filename in filenames:
filepath = path + '\\'
filepath = filepath + filename
# 遍历单个文件,读取行数,写入目标文件
for line in open(filepath, encoding='utf8'):
file.writelines(line)
file.write('\n')
# 关闭文件
file.close()
4.打开keil5,配置好oled和串口
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
OLED_Init();//初始化OLED
Serial_Init();//初始化串口
// OLED_DisplayTurn(1); //屏幕反转
// OLED_ShowString(1, 1,"123456");
while (1)
{
if (Serial_GetRxFlag() == 1)//如果串口收到数据
{
//OLED显示图像
int i=0,j=0;
OLED_SetCursor(0,0);//设置每帧图像开始打印的位置(光标)
for(i=1023;i>=0;i--)//每帧图像中有用部分为128*64/8=1024字节 i=1023;i>=0;i-- i=0;i<=1023;i++
{
OLED_WriteData(Serial_RxPacket[i-6]);//将串口收到的数据传给OLED,请看OLED.c和Serial.c i-6与图像头有关
if((1024-i)%128==0)//换行1024-i
{
j++;
OLED_SetCursor(j,0);//设定光标的位置到下一行
}
}
j=0;
}
}
}
oled.c
#include "OLED.h"
#include "OLED_Font.h"
#include "Delay.h"
#define SPI1_SCK_PIN_SET() GPIO_SetBits (GPIOA,GPIO_Pin_5) //时钟
#define SPI1_SCK_PIN_CLR() GPIO_ResetBits(GPIOA,GPIO_Pin_5)
#define SPI1_MOSI_SET() GPIO_SetBits (GPIOA,GPIO_Pin_7) //数据
#define SPI1_MOSI_CLR() GPIO_ResetBits(GPIOA,GPIO_Pin_7)
/*引脚初始化*/
void OLED_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); //使能B端口时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4 |GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //
// GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5 |GPIO_Pin_7);
}
void SPI_WriteByte(u8 Data) //0x__的数据
{
unsigned char i=0;
for(i=8;i>0;i--) //按二进制写入spi的发送缓冲区 例:ff
{
if(Data&0x80)
{
SPI1_MOSI_SET();
}
else
{
SPI1_MOSI_CLR();
}
SPI1_SCK_PIN_CLR();
SPI1_SCK_PIN_SET();
Data<<=1;
}
}
void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{
if(cmd)
{
OLED_DC(1);
}
else
{
OLED_DC(0);
}
OLED_CS(0);
SPI_WriteByte(dat);
OLED_CS(1);
}
/*******************************************************************
* @name :void OLED_Display_On(void)
* @date :2022-06-22
* @function :打开OLED显示
* @parameters :无
* @retvalue :无
********************************************************************/
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,0); //SET DCDC命令
OLED_WR_Byte(0X14,0); //DCDC ON
OLED_WR_Byte(0XAF,0); //DISPLAY ON
}
/*******************************************************************
* @name :void OLED_Reset(void)
* @date :2022-06-22
* @function :重置OLED屏幕显示
* @parameters :dat:0-显示全黑
1-显示全白
* @retvalue :无
********************************************************************/
void OLED_Reset(void)
{
OLED_RES(1);
Delay_ms(100);
OLED_RES(0);
Delay_ms(100);
OLED_RES(1);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_WR_Byte(Command,0);
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint16_t Data)
{
OLED_WR_Byte(Data,1);
}
//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
if(i==0)
{
OLED_WriteCommand(0xC8);//正常显示
OLED_WriteCommand(0xA1);
}
if(i==1)
{
OLED_WriteCommand(0xC0);//反转显示
OLED_WriteCommand(0xA0);
}
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
OLED_SPI_Init(); //端口初始化
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_RES(1);
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA0); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC0); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常A6/倒转显示A7
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
串口.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_TxPacket[4]; //FF 01 02 03 04 FE
char Serial_RxPacket[1030];
uint8_t Serial_RxFlag;
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 3000000;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint16_t pRxPacket = 0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxState == 0)
{
if (RxData == 0xFF)//数据包头
{
RxState = 1;
pRxPacket = 0;
}
}
else if (RxState == 1)//数据
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
if (pRxPacket >= 1030)//数据字节数
{
RxState = 2;
}
}
else if (RxState == 2)
{
if (RxData == 0xFE)//数据包尾
{
RxState = 0;
Serial_RxFlag = 1;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
太多了,.h文件自己补一点就行
5.用python通过串口给stm32发送合并好的.h文件,也是简单改一个文件名就可以啦
import time
import serial.tools.list_ports
if __name__ == '__main__':
# 读取串口列表
ports_list = list(serial.tools.list_ports.comports())
if len(ports_list) <= 0:
print("无串口设备")
else:
print("可用的串口设备如下: ")
print("%-10s %-30s %-10s" % ("num", "name", "number"))
for i in range(len(ports_list)):
comport = list(ports_list[i])
comport_number, comport_name = comport[0], comport[1]
print("%-10s %-30s %-10s" % (i, comport_name, comport_number))
# 打开串口
port_num = ports_list[0][0]
print("默认选择串口: %s" % port_num)
# 串口号: port_num, 波特率: 3000000, 数据位: 8, 停止位: 1, 超时时间: 0.5秒
ser = serial.Serial(port=port_num, baudrate=3000000, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE,
timeout=0.5)
if not ser.isOpen():
print("打开串口失败")
else:
print("打开串口成功, 串口号: %s" % ser.name)
# 串口发送数据
f = open('xi.h', 'r', encoding='utf-8') # 打开hex图片文本文档,图片文本文档由imageLCD生成,由join together.py合并
j = 0
t0 = time.time() # 统计总播放耗时,开始的时间
while j < 2932: # 总共播放 2932 帧
ta = time.time() # 统计每帧播放时长,开始计时
dd = "" # 中间变量
ff = "" # 存储每一帧并发给串口
i = 0
# 找到每帧数据开始的地方
while ff != "{": # 读取hex图片文本文档,见到”{“才继续
ff = f.read(1) # 读下一个字
# 读取每帧数据,把每帧数据传给 dd
while i < 1030: # 每帧数据有 1030 个字节
while ff != "X": # 将图片文本中的 x 作为每个字节数据开始的标志,读到 x 才继续
ff = f.read(1)
ff = "" # 清空 ff
dd = dd + f.read(2) + " " # dd 存储每个字节数据
i += 1
print(dd)
# 通过串口发给单片机
# 将16进制数转换为 字节
ser.write(bytes.fromhex("FF")) # 发送数据包包头 FF
ser.write(bytes.fromhex(dd)) # 发送数据
ser.write(bytes.fromhex("FE")) # 发送包尾
j += 1
while time.time() - ta < 1/30: # 等待,直到帧播放时长为0.03333
""
t1 = time.time() # 统计总播放耗时,结束的时间
f.close() # 关闭文件
# 关闭串口
ser.close()
if ser.isOpen():
print("串口未关闭")
else:
print("串口已关闭")
print("耗时:")
print(t1-t0)
下面是结果:
...