分类 嵌入式&系统 下的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
12,710 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
7,156 阅读
3
【高数】形心计算公式讲解大全
6,642 阅读
4
【1】基于STM32CubeMX-STM32GPIO端口开发
5,152 阅读
5
如何判断运放是工作在线性区还是非线性区
4,995 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
软件算法
小实验
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇
嵌入式系统&数字IC爱好者博客
累计撰写
302
篇文章
累计收到
527
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
用户登录
登录
嵌入式&系统(共66篇)
找到
66
篇与
嵌入式&系统
相关的结果
2022-01-29
【9】基于STM32CubeMX-STM32OLED开发
目录必备文件关于OLED的概述OLED:Organic Light-Emitting Display,有机发光显示。OLED具备自发光、厚度薄、视角广、功耗低、对比度高、响应速度快、可用于挠曲性面板、使用温度范围广、构造及其制作过程较简单等优异特性,并认为是一种比液晶显示更为先进的新一代平板显示技术。以目前的技术,OLED的尺寸还难以大型化,但是分辨率却可以做得很高。基于STM32的OLED应用,要做那些事情:【1】移植OLED的底层驱动函数库。【2】准备需要的中文字符和图片等数据。【3】调用OLED驱动库中的底层函数进行应用开发。OLED开发相关资源下载基于STM32CubeMX的OLED屏驱动程序库(内含4个文件)【1】XMF_OLED_STM32Cube.c:驱动程序的源文件。【2】XMF_OLED_STM32Cube.h:驱动程序的头文件。【3】XMF_OLED_Font.h:字库数据文件。【4】XMF_OLED_BMP.h:图片数据文件。基于STM32CubeMX的OLED底层驱动函数移植【1】将4个驱动文件拷贝到工程文件中,和main.c放在同一目录,并将XMF_OLED_STM32Cube.c添加到工程代码文件中,并在main.c中引入头文件XMF_OLED_STM32Cube.h。【2】根据所选用的芯片型号,修改XMF_OLED_STM32Cube.h头文件中所以用的芯片头文件。【3】根据硬件电路原理图中,修改XMF_OLED_STM32Cube.h中OLED的引脚定义。【4】查看OLED_Init(void)初始化函数的源码,根据电路接口和应用需要进行修改。OLED驱动库中常用的函数void OLED_Init(void); //OLED初始化函数 void OLED_Clear(void); //OLED清屏函数//显示英文字符串函数 void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *p); 参数1:x,起点列坐标,0~127 参数2:y,起点行坐标,0~7 参数3:*p,字符串指针 返回值:void,无。//显示中文字符函数 void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no); 参数1:x,起点列坐标,0~127 参数2:y,起点行坐标,0~7 参数3:no,待显示中文字符在数组Hzk[][32]中的位置。 返回值:void,无。//显示图片函数 void OLED_DrawBMP( unsigned char x0, unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[]); 参数1:x0,起点列坐标,0~127 参数2:y0,起点行坐标,0~7 参数3:x1,图片的列范围,1~128 参数4:y1,图片的行范围,1~8 参数5:BMP[],待显示图片数据的数组。 返回值:void,无。实训案例:STM32控制OLED显示要点字库软件中行前后缀不需要括号在XMF07A或XMF07C开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能:【1】用取字模软件生产一张图片数据,作为开机界面在OLED上显示。【2】0.5秒后进入信息界面, 第1行显示网址“sciarm.com”, 第2行显示中文“小蜜蜂笔记”,第3行显示日期“2022-02-18”。//用取字模软件生成开机LOGO图片数据,并拷贝到XMF_OLED_BMP.h的数组中。 const unsigned char BMP1[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //此处省略64×62个字节元素 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, };//用取字模软件生成所需中文字符数据,保持到XMF_OLED_Font.h中的Hzk[]数组。 const unsigned char Hzk[][32]={ , ,/*"小",0*/ /* (16 X 16 , 宋体 )*/ , ,/*"蜜",1*/ /* (16 X 16 , 宋体 )*/ , ,/*"蜂",2*/ /* (16 X 16 , 宋体 )*/ , ,/*"笔",3*/ /* (16 X 16 , 宋体 )*/ , ,/*"记",4*/ /* (16 X 16 , 宋体 )*/ };extern unsigned char BMP1[]; void OLED_display_pic() { OLED_Clear(); OLED_DrawBMP(0,0,128,8,BMP1); }void OLED_display_info() { OLED_Clear(); OLED_ShowString(6,0,(uint8_t *)"sciarm.com"); OLED_ShowCHinese(10,3,0); //小 OLED_ShowCHinese(28,3,1); //蜜 OLED_ShowCHinese(46,3,2); //蜂 OLED_ShowCHinese(64,3,3); //笔 OLED_ShowCHinese(82,3,4); //记 OLED_ShowString(24,6,(uint8_t *)"2022-02-18"); }//在mian()函数中添加下面的代码: /* USER CODE BEGIN 2 */ OLED_Init(); //OLED初始化 OLED_display_pic(); //显示图片 HAL_Delay(500); //延时0.5秒 OLED_display_info(); //显示信息 /* USER CODE END 2 */
2022年01月29日
1,403 阅读
17 评论
13 点赞
2022-01-26
【8】基于STM32CubeMX-STM32ADC开发基础
目录STM32的ADC资源概述STM32F103ZE芯片(144脚)中有ADC1、ADC2、ADC3共3个12位逐次逼近型模数转换器,具有18个测量通道,可测量16个外部和2个内部信号源(内部温度和内部参考电压)。这2个内部信号源只能连接到ADC1。ADC的各个通道的A/D转换可以单次、连续、扫描或间断模式执行。A/D转换结果以左对齐或右对齐的方式,存储在16位规则组或者注入组数据寄存器中。按照A/D转换的组织形式来划分,ADC的模拟输入通道分为规则组和注入组两种。ADC可以对一组最多16个通道按照指定的顺序逐个进行转换,这组指定的通道称为规则组。在实际应用中,可能需要中断规则组的转换,临时对某些通道进行转换,好像这些通道注入了原来的规则组,故称注入组,最多由4个通道组成。ADC启动与停止相关的HAL库函数//查询,阻塞方式,启动ADC HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc); //查询,阻塞方式,停止ADC HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc); //中断,非阻塞方式,启动ADC HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc); //中断,非阻塞方式,停止ADC HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);ADC转换结果读取的HAL库函数uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc); 参数1:hadc,ADC实例指针。 返回值:uint32_t,ADC转换结果。查询方式,阻塞式A/D转换HAL库函数HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout); 参数1:hadc,ADC实例指针。 参数2:Timeout,超时时间。 返回值:HAL_StatusTypeDef,函数执行状态。应用实例:用查询的方式,进行一次A/D采样并将结果读出。 uint16_t ADC_Value = 0; //以查询的方式启动ADC HAL_ADC_Start(&hadc); //等待一次规则组的ADC转换完成,并将结果读出 if(HAL_OK == HAL_ADC_PollForConversion(&hadc1,10)) { ADC0_Value = HAL_ADC_GetValue(&hadc1); }中断方式,非阻塞式A/D转换HAL库函数应用实例:用中断的方式,进行一次A/D采样并将结果读出。 uint16_t ADC_Value = 0; //以中断的方式启动ADC HAL_ADC_Start_IT(&hadc); //重写ADC转换完成中断回调函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { ADC0_Value = HAL_ADC_GetValue(&hadc1); }实训案例:ADC单次数据采样与电压换算在XMF07A或XMF07C开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能: 【1】将ADC_IN0设置为12位ADC,右对齐,启用中断。【2】分别用查询和中断这2种方式,每隔0.5秒采样一次ADC的数据。【3】将每次读取到的ADC采样值转换为对应电压值,发送到上位机。【4】LED1作为采样指示灯,在ADC转换过程中点亮,其余时间熄灭。要点配置串口没有接收中断,不需要对NVIC进行使能#include "stdio.h" #define LED1_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET) #define LED1_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET) uint16_t ADC0_Value = 0, ADC0_Volt = 0; uint8_t str_buff[64];void UR1_Send_Info() { sprintf((char *)str_buff,"采样值:%d,电压值:%d.%d%dV\r\n",ADC0_Value,ADC0_Volt/100,(ADC0_Volt%100)/10,ADC0_Volt%10); HAL_UART_Transmit(&huart1,str_buff,sizeof(str_buff),10000); }用查询,阻塞的方式来实现void Get_ADC_Value() { HAL_ADC_Start(&hadc1); LED1_ON(); if(HAL_OK == HAL_ADC_PollForConversion(&hadc1,10)) { ADC0_Value = HAL_ADC_GetValue(&hadc1); ADC0_Volt = ADC0_Value * 330 / 4096; } UR1_Send_Info(); LED1_OFF(); HAL_ADC_Stop(&hadc1); }//在mian()函数中添加以下代码: /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { Get_ADC_Value(); //启动一个阻塞式的ADC转换并读取数据 // UR1_Send_Info(); //向上位机发生采样值和电压值 HAL_Delay(500); //延时0.5秒,再进行下一次ADC采样 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */用中断,非阻塞的方式来实现void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc->Instance == ADC1) { ADC0_Value = HAL_ADC_GetValue(&hadc1); //读取ADC转换结果 ADC0_Volt = ADC0_Value * 330 / 4096; //将采样值换算成电压值 UR1_Send_Info(); //向上位机发生ADC采样信息 LED1_OFF(); //关闭LED1采样指示灯 } }//在mian()函数中添加以下代码: /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_ADC_Start_IT(&hadc1); //启动一个非阻塞式的ADC转换并读取数据 LED1_ON(); //点亮LED1采样指示灯 HAL_Delay(500); //延时0.5秒,再进行下一次ADC采样 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
2022年01月26日
1,016 阅读
1 评论
11 点赞
【7】基于STM32CubeMX-STM32ADC模数转化基本原理
目录模数转换器(ADC)概述ADC:Analog-to-Digital Converter将时间和幅值连续的模拟量转化为时间和幅值离散的数字量,A/D转换一般要经过采样、保持、量化和编码4个过程。常用ADC:逐次逼近型、双积分型、∑-Δ型。ADC的几个技术指标【1】量程:指ADC所能输入模拟信号的类型和电压范围,即参考电压。信号类型包括单极性和双极性。【2】转换位数:量化过程中的量化位数n。 A/D转换后的输出结果用n位二进制数来表示。【例】:10位ADC的输出值就是0~1023。【3】分辨率:ADC能够分辨的模拟信号最小变化量。计算公式是,分辨率 = 量程 / 2的n次方【例】:量程为单极性0-5V,8位ADC的分辨率是,5 / 256 = 0.0195V【4】转换时间:ADC完成一次完整的A/D转换所需要的时间,包括采样、保持、量化、编码的全过程。剖析ADC的基本转换过程实训案例:ADC数据采样的计算应用有一个温度测控系统,已知温度传感器在0到100度之间为线性输出,参考电压为5V,采用8为的A/D转换器,0度的时候,测的电压为1.8伏,100度的时候,测的电压为4.3伏。【问题1】:系统的分辨率是多少?【问题2】:采集到数据10010001,表示多大电压?温度是多少?由于温度是线性变化,先求得斜率k,得到温度和电压的关系表达式。k = (100 – 0)/(4.3-1.8) = 40, y = 40*(x-1.8) (x为采样得到的电压)由于采用的是8为ADC,参考电压为5V,所以分辨率为:5 / 256 = 0.0195V = 19.5mV(最小能分辨的电压,分辨率)0.0195 * 40 = 0.78度(最小能分辨的温度)因为 10010001B = 91H = 145(16x9+1), 所以 0.0195 * 145 = 2.8275V该电压信号对应的温度是:(2.83V – 1.8V) * 40 = 41.1摄氏度
2022年01月26日
562 阅读
0 评论
14 点赞
2022-01-23
【6】基于STM32CubeMX-STM32定时器与串口综合训练
目录关于sprintf()函数的用法sprintf(),指的是字符串格式化函数,把格式化的数据写入某个字符串中。int sprintf(char string, char format [,argument,…]);引入头文件 #include “stdio.h“【例】:有一个表示温度的整型变量tmp,现在要将其格式化为字符串“温度是:XX摄氏度”,并将其通过串口1发送出去。#include "stdio.h" uint8_t Str_buff[64]; sprintf((char*)Str_buff, "温度是: %d摄氏度", tmp); HAL_UART_Transmit(&huart1, Str_buff, sizeof(Str_buff), 0xFFFF);实训案例:定时器与串口综合训练配置要点时钟外设在XMF07A或XMF07C开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能:【1】开机后,LED1与LED2依次点亮,然后熄灭,进行灯光检测,高电平点亮LED灯。【2】系统通过串口1向上位机发送一个字符串“=========XMF07欢迎你!==========”。【3】LED1作为一个秒闪灯,系统向上位机发送完字符串后,开始亮0.5秒,灭0.5秒….循环闪烁,并启动系统运行时间的记录,其时分秒格式为 “XX:XX:XX”。【4】上位机通过一个由3个字节组成的命令帧控制LED2灯的开关。该命令帧的格式为 “0xBF 控制字 0xFB”。0xBF为帧头,0xFB为帧尾,控制字的定义如下:0xA1:打开LED2,返回信息 “XX:XX:XX LED2打开。”0xA2:关闭LED2,返回信息 “XX:XX:XX LED2关闭。”其他:返回信息 “XX:XX:XX 这个一个错误指令!”。#define LED1_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET) #define LED2_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET) #define LED1_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET) #define LED2_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET) #define LED1_TOG() HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9) #define LED2_TOG() HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8) uint8_t str1[] = "===============XMF07欢迎你!================\r\n"; uint8_t hh = 0, mm = 0, ss = 0, ss05 = 0; uint8_t str_buff[64]; uint8_t Rx_dat[16];void Ckeck_LED() { LED1_ON(); HAL_Delay(500); LED2_ON(); HAL_Delay(500); LED1_OFF(); HAL_Delay(500); LED2_OFF(); HAL_Delay(500); }void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { LED1_TOG(); ss05++; if(ss05 == 2) { ss05 = 0; ss++; if(ss == 60) { ss = 0; mm++; if(mm == 60) { mm = 0; hh++; } } } }void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(Rx_dat[0] == 0xBF && Rx_dat[2] == 0xFB) { switch(Rx_dat[1]) { case 0xa1: LED2_ON(); sprintf((char *)str_buff,"%d:%d:%d LED2打开!\r\n",hh,mm,ss); break; case 0xa2: LED2_OFF(); sprintf((char *)str_buff,"%d:%d:%d LED2关闭!\r\n",hh,mm,ss); break; default: sprintf((char *)str_buff,"%d:%d:%d 这是一个错误的命令!\r\n",hh,mm,ss); break; } HAL_UART_Transmit(&huart1,str_buff,sizeof(str_buff),10000); HAL_UART_Receive_IT(&huart1,Rx_dat,3); } } }//在mian()函数中添加以下代码: Ckeck_LED(); //LED灯流水检测 HAL_UART_Transmit(&huart1,str1,sizeof(str1),0xFFFF); //向上位机发送欢迎字符 HAL_UART_Receive_IT(&huart1,Rx_dat,3); //启动串口1接收上位机3个字节 HAL_TIM_Base_Start_IT(&htim2); //启动定时器TIM2
2022年01月23日
756 阅读
0 评论
11 点赞
2022-01-21
【5】基于STM32CubeMX-STM32串口数据收发
目录串行接口相关知识点并行通信、串行通信的概念。单工、半双工、全双工的概念。异步串行通信:通信双方在没有同步时钟的前提下,将一个字符(包括特定的附加位)按位进行传输的通信方式。波特率:每秒钟传输的二进制位数,如9600bps。TTL电平<—->RS232:MAX3232 SP3232串口<———>USB接口:CH340 CP2012STM32芯片的串口UASRT功能十分强大,但对于日常编程而言,使用最多的还是异步串行通信。串口1:USART1_TX与PA9复用,USART1_RX与PA10复用。串口2:USART2_TX与PA2复用,USART2_RX与PA3复用。HAL库中串口发送的重要函数////查询方式,阻塞式发送函数(初学者,推荐使用) HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size, uint32_t Timeout); 参数1:huart,串口实例的指针。 参数2:*pData,待发送数据缓冲区的指针。 参数3:Size,待发送数据的字节数。 参数4:Timeout,超时时间值。 返回值:HAL_StatusTypeDef,函数执行状态。 typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef; ////中断方式,非阻塞式发送函数 HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size); 参数1:huart,串口实例的指针。 参数2:*pData,待发送数据缓冲区的指针。 参数3:Size,待发送数据的字节数。 返回值:HAL_StatusTypeDef,函数执行状态。 ////串口发送完毕中断回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)应用举例:使用非阻塞式的串口发送函数,将发送缓数组dat_Txd中的前5个数据发送到USART1,在数据发送完成后,翻转PB9引脚的输出电平。 //使用中断,非阻塞方式 HAL_UART_Transmit_IT(&huart1, dat_Txd, 5); void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1); { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); } } //使用查询,阻塞方式 HAL_UART_Transmit(&huart1, dat_Txd, 5, 10000); HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);HAL库中串口接收的重要函数////查询方式,阻塞式接收函数 HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); 参数1:huart,串口实例的指针。 参数2:*pData,数据接收据缓冲区的指针。 参数3:Size,待接收数据的字节数。 参数4:Timeout,超时时间值。 返回值:HAL_StatusTypeDef,函数执行状态。 非阻塞式接收函数(推荐使用) HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size); 参数1:huart,串口实例的指针。 参数2:*pData,数据接收据缓冲区的指针。 参数3:Size,待接收数据的字节数。 返回值:HAL_StatusTypeDef,函数执行状态。 ////串口接收完毕中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);应用举例:使用非阻塞式的串口接收函数,接收USART1中的一个字节,将其保存在dat_Rxd变量中,在数据接收完成后,判断该字节,若为0x5A,则翻转PB8引脚的输出电平。 //使用中断,非阻塞方式 HAL_UART_Transmit_IT(&huart1, &dat_Rxd, 1); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(dat_Rxd == 0x5A) HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); } }实训案例:上位机通过串口控制LED灯开关调试要点:在XMF07A或XMF07C开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能:【1】开机后,向串口1发送“hello world!”。【2】串口1收到字节指令“0xA1”,打开LED1,发送“LED1 Open!”。【3】串口1收到字节指令“0xA2”,关闭LED1,发送“LED1 Closed!”。【4】在串口发送过程中,打开LED2作为发送数据指示灯。#define LED1_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET) #define LED1_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET) #define LED2_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET) #define LED2_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET) uint8_t Tx_str1[] = "hello world!\r\n"; uint8_t Tx_str2[] = "LED1 Open!\r\n"; uint8_t Tx_str3[] = "LED1 Closed!\r\n"; uint8_t Rx_dat = 0;void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(Rx_dat == 0xa1) { LED1_ON(); LED2_ON(); HAL_UART_Transmit(&huart1,Tx_str2,sizeof(Tx_str2),10000); LED2_OFF(); HAL_UART_Receive_IT(&huart1,&Rx_dat,1); } else if(Rx_dat == 0xa2) { LED1_OFF(); LED2_ON(); HAL_UART_Transmit(&huart1,Tx_str3,sizeof(Tx_str3),10000); LED2_OFF(); HAL_UART_Receive_IT(&huart1,&Rx_dat,1); } } }//在mian()函数中添加以下代码: LED2_ON(); HAL_UART_Transmit(&huart1,Tx_str1,sizeof(Tx_str1),10000); //向上位机发送“hello world!” LED2_OFF(); HAL_UART_Receive_IT(&huart1,&Rx_dat,1); //启动串口1接收上位机1个字节
2022年01月21日
1,060 阅读
2 评论
18 点赞
【4】基于STM32CubeMX-STM32定时器开发
目录定时器的基本概述通过滴漏和漏沙瓶这两个例子简单讲述定时器的基本工作原理。STM32的常见的定时器资源: 系统嘀嗒定时器SysTick、看门狗定时器WatchDog、实时时钟RTC、基本定时器、通用定时器、高级定时器。系统嘀嗒定时器SysTick :这是一个集成在Cortex M3内核当中的定时器,它并不属于芯片厂商的外设,也就是说使用ARM内核的不同厂商,都拥有基本结构相同的系统定时器。主要目的是给RTOS提供时钟节拍做时间基准。基本定时器:TIM6、TIM7。通用定时器:TIM2、TIM3、TIM4、TIM5。在基本定时器的基础上,实现输出比较、输入捕获、PWM生成、单脉冲模式输出等功能。这类定时器最具代表性,使用也最广泛。高级定时器:TIM1、TIM8。STM32通用定时器的重要知识点通用定时器的基本结构组成:STM32的通用定时器,是一个通过可编程预分频器(Prescaler)驱动的16位自动重装主计数器(Counter Period)构成。可以对内部时钟或触发源以及外部时钟或触发源进行计数。通用定时器的基本工作原理:首先,定时器时钟信号送入16位可编程预分配器(Prescaler),该预分配器系数为0~65535之间的任意数值。预分配器溢出后,会向16位的主计数器(Counter Period)发出一个脉冲信号。预分频器,本质上是一个加法计数器,预分频系数实际上就是加计数的溢出值。定时器发生中断时间的计算方法:定时时间 = (Prescaler+1 ) X (Counter Period+1) X 1/ 定时器时钟频率时钟信号1KHz,Prescaler为9,Counter Period为999,定时时间?计算案例实训案例:外部中断信号控制LED灯开关配置要点在XMF07A或XMF07C开发板上,利用STM32CubeMX和Keil5协同开发,完成以下的功能:【1】利用TIM2实现间隔定时,每隔0.2秒将LED1的开关状态翻转。【2】利用TIM3实现间隔定时,每隔1秒将LED2的开关状态翻转。【3】修改TIM2的初始化代码,改为每隔0.5秒将LED1的开关状态翻转。/* USER CODE BEGIN 0 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) //处理TIM2间隔定时中断 { AL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); } if(htim->Instance == TIM3) //处理TIM3间隔定时中断 { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); } } /* USER CODE END 0 *//* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim2); //启动定时器TIM2 HAL_TIM_Base_Start_IT(&htim3); //启动定时器TIM3 //有IT的代表使能时钟中断 /* USER CODE END 2 */
2022年01月19日
1,006 阅读
2 评论
26 点赞
2022-01-17
【3】基于STM32CubeMX-STM32中断系统
目录中断什么意思你打开火,烧上一壶水。然后去洗衣服,在洗衣服的过程中,突然听到水壶发出水开的报警声,这时,你停止洗衣服动作,立即去关掉火,然后将开水灌入暖水瓶中,灌完开水后,你又回去继续洗衣服。这个过程中实际上就发生了一次中断。如图:STM32的中断系统理解中断、中断源、中断向量、中断优先级、中断服务函数…等基础概念。ARM Cortex M3内核支持256个中断,包括16个内核中断和240个外设中断,拥有256个中断优先级别。STM32的中断通道可能会由多个中断源共用。这就意味着,某一个中断服务函数也可能被多个中断源所共用。所以,在中断服务函数的入口处,需要有一个判断机制,用以辨别是那个中断触发了中断。STM32微处理器的内核中有一个NVIC(嵌套向量中断控制器)的设备,它对中断进行统一的协调和控制,其中最主要的工作就是控制中断通道的使能和确定中断的优先级。STM32中有2个优先级的概念:抢占优先级和响应优先级,每个中断都需要指定这两种优先级。如果两个抢占优先级相同的中断同时到达,NVIC会根据他们的响应优先级高低来决定先处理哪一个。如果两个同时到达的中断的抢占优先级和响应优先级都相等,则根据中断的自然排位顺序来决定响应哪一个。STM32的外部中断外部中断EXTI是STM32微处理器实时处理外部事件的一种机制,由于中断请求主要来自GPIO端口的引脚,所以称为外部中断。STM32F013微处理器有19个能产生事件/中断请求的边沿检测器,每个输入线可以独立地配置成输入类型(脉冲或挂起)和对应的触发事件(上升沿、下降沿或双边沿触发),也可以独立地屏蔽。EXTI0~EXTI15:GPIO端口引脚。EXTI16:PVD输出,可编程电压监测。EXTI17:RTC闹钟。EXTI18:USB唤醒。外部中断的程序设计思路传统STM32外部中断设计步骤 :【1】将GPIO初始化为输入端口。【2】配置相关I/O引脚与中断线的映射关系。【3】设置该I/O引脚对应的中断触发条件。【4】配置NVIC,并使能中断。【5】编写中断服务函数。配置要点提示引脚设外部中断,上升沿,上拉使能NVIC基于STM32CubeMX的外部中断设计步骤: 【1】在STM32CubeMX中指定引脚,配置中断初始化参数。选择GPIO引脚的功能,设置中断信号触发条件,使能NVIC对应的中断通道。【2】重写该I/O引脚对应的中断回调函数。外部中断初始化函数剖析//外部中断初始化相关的操作在gpio.c文件中的MX_GPIO_Init()函数完成。 void MX_GPIO_Init(void) { /*===================== 此处省略无关代码。 ======================*/ /* EXTI interrupt init 外部中断初始化*/ HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); //设置EXIT9~EXIT5中断的优先级 HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); //使能EXIT9~EXIT5中断通道 HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); }外部中断服务函数的编写实训案例:外部中断信号控制LED灯开关在XMF07A或XMF07C开发板上, 利用STM32CubeMX和Keil5协同开发, 完成以下的功能:【1】将KEY2,即PC13设置为外部中断输入,下降沿触发。在中断服务函数中,切换LED1的开关状态。【2】将KEY4,即PB5设置为外部中断输入,上升沿触发。在中断服务函数中,切换LED2的开关状态。//外部中断的初始化函数由STM32CubeMX辅助生成,用户只需要重写中断回调函数。 /* USER CODE BEGIN 0 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_13) { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); } if(GPIO_Pin == GPIO_PIN_5) { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); } } /* USER CODE END 0 */
2022年01月17日
838 阅读
0 评论
14 点赞
2022-01-17
【2】基于STM32CubeMX-STM32按键开发
目录按键扫描的基本原理按键信号是如何识别的?一般来说,按键的两个引脚的一端通过电阻上拉到高电平,另一端则接地。在没有按键按下的时候,输入引脚为高电平,当有按键按下,输入引脚则为低电平。通过反复读取按键输入引脚的信号,然后识别高低电平来判断是否有按键触发。为什么去抖动?按键的输入引脚有低电平产生不代表一定是有按键按下,也许是干扰信号 , 因此,需要通过去抖动处理,将这些干扰信号过滤,从而获得真实的按键触发信号。如何去抖动?首次检测到按键输入引脚有低电平后,稍作延时,再次读取该引脚,如还是低电平,则确认为按键触发信号;否则,判断为干扰信号,不予处理。GPIO输入电平读取HAL库函数GPIO_PinState HAL_GPIO_ReadPin( GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 参数1:GPIOx,端口号,如:GPIOB,GPIOF。 参数2:GPIO_Pin,引脚号,如:GPIO_PIN_9,GPIO_PIN_12。 返回值:GPIO_PinState,引脚的电平状态。 应用举例:判断PC13引脚的输入信号,若为高电平,则将PB9引脚控制的LED灯的开关状态切换。 if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9); }实训案例:按键控制LED灯开关要点提示配置输入引脚时候注意是否存在上拉在XMF07A或XMF07C开发板上, 利用STM32CubeMX和Keil5协同开发, 完成以下的功能:【1】按下KEY2按键,切换LED1的开关状态。【2】按下KEY3按键,松开后,切换LED2的开关状态。【3】按下KEY4按键,把点亮的LED灯全部关闭。#define KEY2 HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13) #define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) #define KEY4 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)void Scan_Keys() { if(KEY2 == GPIO_PIN_RESET) { HAL_Delay(5); if(KEY2 == GPIO_PIN_RESET) { HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); while(KEY2 == GPIO_PIN_RESET); } } if(KEY3 == RESET) { HAL_Delay(5); if(KEY3 == RESET) { while(KEY3 == RESET); HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); } } if(KEY4 == 0) { HAL_Delay(5); if(KEY4 == 0) { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_RESET); while(KEY2 == 0); } } }/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { Scan_Keys(); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
2022年01月17日
1,608 阅读
7 评论
86 点赞
2022-01-16
【1】基于STM32CubeMX-STM32GPIO端口开发
目录STM32CubeMX的声明【1】STM32CubeMX 是一个代码辅助生成工具,在生成工程代码的同时,根据你的功能选择和配置帮你做了必要的初始化,这也就意味着你不再需要对底层的特殊功能寄存器进行初始化配置,但是不代表你就可以不去学习 STM32 的基本知识和各个外设的工作原理与参数特性等。【2】虽然不再要求你去了解 STM32 底层寄存器的定义,但却要求你要了解由 STM32CubeMX生产的代码内在的逻辑联系以及 HAL 库中的常用函数原型与使用。【3】STM32CubeMX 的最大好处就是,使开发流程、文件结构、库函数等标准化protuesSTM32仿真开发板下载STM32的GPIO端口知识要点GPIO:General Purpose Input & OutputSTM32芯片最拥有GPIOA、GPIOB…GPIOG等7组端口,每组端口最多拥有Pin0、Pin1…Pin15共16个引脚。STM32的每个I/O端口都可以自由编程,但I/O端口寄存器必须按32位字被访问。STM32的每个I/O端口都由7个寄存器来控制。STM32的GPIO端口可以由软件配置成8种模式:推挽输出、开漏输出、推挽式复用功能、开漏式复用功能;模拟输入、浮空输入、下拉输入、上拉输入。GPIO电平输出HAL库函数void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); 参数1:GPIOx,端口号,如:GPIOB,GPIOF。 参数2:GPIO_Pin,引脚号,如:GPIO_PIN_9,GPIO_PIN_12。 参数3:PinState,引脚输出状态。高电平----GPIO_PIN_SET;低电平----GPIO_PIN_RESET。 返回值:void,空。 应用举例:向PB8引脚输出高电平。 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);GPIO电平翻转HAL库函数void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 参数1:GPIOx,端口号,如:GPIOB,GPIOF。 参数2:GPIO_Pin,引脚号,如:GPIO_PIN_9,GPIO_PIN_12。 返回值:void,空。 应用举例:将PA3引脚输出电平翻转。 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_3);GPIO初始化函数源码剖析void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = ; /* GPIO端口时钟使能 */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*配置GPIO端口引脚的初始化输出电平 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET); /*配置GPIO端口输入引脚 : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; //GPIO端口的引脚号是:13 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //GPIO的模式是:输入 GPIO_InitStruct.Pull = GPIO_NOPULL; //没有上拉 HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); //将参数结构设置到GPIOC端口 /*配置GPIO端口输出引脚 : PB8 PB9 */ GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; //GPIO端口的引脚号是:8和9 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //GPIO的模式是:输出 GPIO_InitStruct.Pull = GPIO_NOPULL; //没有上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //GPIO的输出速度是:非常低速 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //将参数结构设置到GPIOB端口 }实训案例:基于STM32CubeMX的跑马灯在XMF07A或XMF07C开发板上,利用STM32CubeMX对STM32芯片的LED控制引脚进行配置并快速生产项目,在Keil5中进行代码编写,实现跑马灯功能,即:LED1灯亮,过一会,LED2灯亮,过一会,LED1灯熄灭,过一会,LED2灯熄灭….如此循环。XMF07C开发板:STM32L151C8T6 (电路功能兼容物联网国赛设备)XMF07A开发板:STM32F103C8T6/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET); //向PB9输出高电平,点亮LED1灯 HAL_Delay(500); //延时500ms HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET); //向PB9输出低电平,熄灭LED1灯 HAL_Delay(500); //延时500ms HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); //用翻转电平的方式,实现LED1灯的点亮与熄灭 HAL_Delay(500); //延时500ms HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); //用翻转电平的方式,实现LED2灯的点亮与熄灭 HAL_Delay(500); //延时500ms /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */仿真开发实例原理图:1.新建CubeMX工程选择对应的MCU,在proteus8中支持STM32F103的几款muc,这里我们选择STM32R6 设置下载方式 配置LED灯的GPIO配置时钟树选择keil5工程(或者其他工程)勾选此处可以使得每个初始化的外设有独立的.c和.h生成MDK工程添加LED闪烁的代码,并编译生成.hex文件HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5); HAL_Delay(0XFF)将hex文件下载到proteus8中去此时仿真会报错,原因是电源为接入,只需要将电源接入即可此处VDDA,VSSA分别加入到电网VCC与GND中,详细可以百度最后,就可以看到有一个LED灯反复交替闪烁!
2022年01月16日
5,152 阅读
8 评论
113 点赞
嵌入式视频流知识点及代码解析-精简版
目录背景和意义(1)视频的带宽很大,存储,传输不便,故要压缩、解压 、播放。(2)应用领域很广 ,交通,在线教育,播放器,自动驾驶。框架 下面这个图及其重要以及3个ip关系 abcde代表先后实现顺序注意观看!!! 代码及相关知识点一、知识点篇Live 555: 是一个为流媒体提供解决方案的跨平台的C++开源项目,它实现了标准流媒体传输,对标准流媒体传输协议如RTP/RTCP、RTSP、SIP等的支持。Live555实现了对多种音视频编码格式的音视频数据的流化、接收和处理等支持,包括MPEG、H.263+、DV、JPEG视频和多种音频编码。同时由于良好的设计,Live555非常容易扩展对其他格式的支持。Live555已经被用于多款播放器的流媒体播放功能的实现,如VLC(VideoLan)、MPlayer。 在本次开发实践中主要用于接收海康威视摄像头的RTP数据包 并通过UDP网络进行转发给PC机。 FFmpeg: Fmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。 在本次开发实践中主要用于H264数据解码。 SDL: SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。在本次开发实践中主要用于YUV数据的显示。二、问答篇1.什么是YUV?与RGB有什么不同?YUV 是一种颜色编码方法,FFmpeg 解码后的数据格式。Y 表示明亮度 U 表示色度 V 表示浓度。因为通过研究发现,人类对于图像的感知中对明亮最侧重,色彩和浓度就相对不那么重要,所以在保存图片时,让明亮度占较多的比重,有效的在不影响观看的情况下节约了空间。视频播放器解码出来的格式为 YUV420P,其中明亮度占整个数据的 2/3,色度和浓度占 1/3。2.RTSP在什么层?答:应用层3.你关于IP问题你用到了那些命令?答:ipconfig,ifconfig,ping等4.本次课程设计你用到了那些去年学的知识?答:网络通信,文件开关与读写,第一章shell操作命令下面部分自行百度:5.TCP与UDP特点(TCP的可靠,UDP 的不可靠,UDP快)6.简述TCP与UDP7.了解HTTP/https8.三次握手和四次挥手过程三、代码篇RTSP程序要不要等待播放器器程序请求?答:要请你找出上述代码所在位置答:如图所示 live555(了解)BasicTaskScheduler 的父类是BasicTaskScheduler0BasicTaskScheduler0是一个用作传递的类,它继承自TaskScheduler,又派生出BasicTaskScheduler。其定义在live555sourcecontrol\UsageEnvironment\include\BasicUsageEnvironment0.hh文件中。BasicTaskScheduler0中有BasicTaskScheduler 这个类主要实现事件的处理BasicUsageEnvironment 涉及调试语句,输出语句ourRTSPClient 主要是涉及的数据的发送相关的功能函数,主要的功能继承于父类RTSPClientRequestRecord 创建一个请求记录对象,并将回调函数与之关联sendRequest第一次进入调用:openConnection解析URL,并调用setupStreamSocket和connectToServer,然后envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION, (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);若不是第一次调用,则打包 RTSP 包协议,并调用 Send函数发送到服务器。 重点RequestRecord入队列 等候读取数据。setupStreamSocket调用createSocket ,createSocket调用:sock = socket(AF_INET, type, 0);connectToServer调用connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName)setBackgroundHandling 主要初始化select 函数的 文件描述符集合incomingDataHandler 中包含函数readSocket 调用int bytesRead = recvfrom(socket, (char*)buffer, bufferSize, 0, (struct sockaddr*)&fromAddress, &addressSize);到此发送描述命令结束 ,等待响应服务器发送过来的数据。incomingDataHandler1读取服务器的数据incomingDataHandler1先调用readSocket调用,然后调用handleResponseBytes 解析RTSP服务器数据调用(*foundRequest->handler())(this, resultCode, resultString);这个函数就是continueAfterDESCRIBEcontinueAfterDESCRIBE 调用下一步 setup 功能SDL抓1,2,3,4....等每段的关键句,可能考流程rb为只读,对于不需要进行更新的文件,可以防止用户的错误的写回操作,防止损毁原有数据。具有较高的安全性。rb+为更新二进制文件,可以读取,同时也可以写入,需要用到fseek之类的函数进行配合,以免出错,对于需要不时更新的文件,比如信息管理系统中的数据,可以这样打开。初始化SDL使用SDL_Init()初始化SDL。该函数可以确定希望激活的子系统。int SDLCALL SDL_Init(Uint32 flags)SDL_INIT_VIDEO:视频创建窗口(Window)使用SDL_CreateWindow()创建一个用于视频播放的窗口。SDL_Window SDLCALL SDL_CreateWindow(const char title,int x, int y, int w,int h, Uint32 flags);SDL_CreatWindow:第一个参数是窗口名字,第二三是窗口的坐标(SDL_winpos_undefined 为采用系统默认)title :窗口标题x :窗口位置x坐标。也可以设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。y :窗口位置y坐标。同上。w :窗口的宽h :窗口的高flags :支持下列标识。包括了窗口的是否最大化、最小化,能否调整边界等等属性。flags:SDL_WINDOW_RESIZABLE 自动调整窗口基于窗口创建渲染器(Render)使用SDL_CreateRenderer()基于窗口创建渲染器SDL_Renderer SDLCALL SDL_CreateRenderer(SDL_Window window,int index, Uint32 flags);window: 渲染的目标窗口。index:打算初始化的渲染设备的索引。设置“-1”则初始化默认的渲染设备。SDL_RENDERER_PRESENTVSYNC:和显示器的刷新率同步创建纹理(Texture)使用SDL_CreateTexture()基于渲染器创建一个纹理SDL_Texture SDLCALL SDL_CreateTexture(SDL_Renderer renderer,Uint32 format,int access, int w,int h);renderer:目标渲染器。format:纹理的格式。access:定义位于SDL_TextureAccess中access:SDL_TEXTUREACCESS_STREAMING :变化频繁w:纹理的宽h:纹理的高SDL_Thread *refresh_thread = SDL_CreateThread(RefreshVideo,NULL,NULL); //创建线程SDL_Event event; //设置触发事件6.在SDL中,当事件等待函数监听到事件后,判断事件类型,如果event.type == SDL_KEYDOWN,表明用户按下键盘,保存在event.key.keysym.sym是相应的键值。而根据键值,调用函数SDL_GetKeyName(event.key.keysym.sym)),即可得到按下的按键键名。SDLK_RETURN == 13 回车!7.读文件freadsize_t fread(void buffer,size_t size,size_t count,FILE stream)buffer 是读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引)size 是每次读取的字节数count 是读取次数stream 是要读取的文件的指针8.循环显示画面(1)设置纹理的数据使用SDL_UpdateTexture()设置纹理的像素数据int SDLCALL SDL_UpdateTexture(SDL_Texture texture,const SDL_Rect rect,const void *pixels, int pitch);texture:目标纹理。rect:更新像素的矩形区域。设置为NULL的时候更新整个区域。pixels:像素数据。pitch:一行像素数据的字节数。(2)纹理复制给渲染目标使用SDL_RenderCopy()将纹理数据复制给渲染目标。在使用SDL_RenderCopy()之前,可以使用SDL_RenderClear()先使用清空渲染目标。实际上视频播放的时候不使用SDL_RenderClear()也是可以的,因为视频的后一帧会完全覆盖前一帧int SDLCALL SDL_RenderCopy(SDL_Renderer renderer,SDL_Texture texture,const SDL_Rect srcrect,const SDL_Rect dstrect);renderer:渲染目标。texture:输入纹理。srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。(3) 显示使用SDL_RenderPresent()显示画面void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer);参数renderer用于指定渲染目标9.退出event.type==SDL_QUITfclose(fp); //关闭文件! 防止遗留无用进程SDL_DestroyTexture(texture); //关闭纹理SDL_DestroyRenderer(renderer); //关闭渲染SDL_DestroyWindow(window); //关闭窗口SDL_Quit(); //退出线程FFMPEG解码decofun_deco_display.c从void * deco_thread开始play_ubuntu内 功能函数于主函数同时编译多个c编译! gcc -o demo main.c fun_deco_display.c fun_others.c fun_recv_control.c -I /monchickey/ffmpeg/include -L /monchickey/ffmpeg/lib -lavformat -lavcodec -lavutil -lSDL2 -lpthread AVCodec codec; / 解码CODEC*/AVCodecContext *cctx;AVFrame frame; / 解码后的图像*/int byte_buffer_size; //解码器码流长度uint8_t *byte_buffer = NULL; //h.264码流AVPacket *pkt; //保存媒体流信息AVPacket主要保存一些媒体流的基本信息,例如PTS、DTS时间。最重要的当然就是媒体数据的buffer地址了。比较重要的有:pts:控制显示的pts时间dts:控制解码的dts时间*data:媒体数据buffer的指针duration:AVStream-> time_base单位中此数据包的持续时间,如果未知则为0。 在演示顺序中等于next_pts - this_pts。AVFormatContext主要存储视音频封装格式中包含的信息解码前数据:AVPacket解码后数据:AVFrame1.AVFormatContext avformat_alloc_context(void)函数用来申请AVFormatContext类型变量并初始化默认参数。申请的空间通过void avformat_free_context(AVFormatContext s)函数释放。2.avformat_find_stream_info()主要用于给每个媒体流(音频/视频)的AVStream结构体赋值,已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作3.av_find_best_stream()函数就是要获取音视频对应的stream_index 获取流的索引4.解码模块第一步:获取解码器 avcodec_find_decoder()FFmpeg的解码器编码器都存在avcodec的结构体中5.avcodec_alloc_context3,avcodec_parameters_to_context,解码器初始化6.avcodec_open2打开解码器7.av_frame_alloc()首先调用av_mallocz()为AVFrame结构体分配内存8.int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小,av_malloc 按需分配空间9.av_packet_alloc实际是分配AVPacket以后,调用av_init_packet对AVPacket的成员变量进行初始化赋值10.av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧11.avcodec_send_packet()以在AVPacket中给出解码器原始的压缩数据avcodec_receive_frame()。 成功后,它将返回一个包含未压缩音频或视频数据的 AVFrame12.void av_image_copy_uc_from ( uint8_t * dst_data[4], const ptrdiff_t dst_linesizes[4], const uint8_t * src_data[4], const ptrdiff_t src_linesizes[4], enum AVPixelFormat pix_fmt, int width, int height ) 数据拷贝13.av_packet_free(&pkt); //释放数据 关闭进程av_frame_free(&frame);avformat_close_input(&fctx);avcodec_free_context(&cctx);avformat_free_context(fctx);Client && Server(了解)//Client_Upd1.int socket(int domain, int type, int protocol);函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族 SOCK_DGRAM udp连接socket()函数的原型如下,这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。2.memset可以方便的清空一个结构类型的变量或数组。初始化3.family 通信协议的族 AF_INET,PF_INET IPv4 Internet协议4.sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中。htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。5.在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址,sendto()函数原型为: int sendto(int sockfd, const void msg,int len unsigned int flags, const struct sockaddr to, int tolen); 该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。int PASCAL FAR sendto( SOCKET s, const char FAR* buf, int len, int flags,const struct sockaddr FAR* to, int tolen);s:一个标识套接口的描述字。buf:包含待发送数据的缓冲区。len:buf缓冲区中数据的长度。flags:调用方式标志位。to:(可选)指针,指向目的套接口的地址。tolen:to所指地址的长度6.recvfrom()函数原型为: int recvfrom(int sockfd,void buf,int len,unsigned int lags,struct sockaddr from,int *fromlen); from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数接收一个数据报并保存源地址。int PASCAL FAR recvfrom( SOCKET s, char FAR* buf, int len, int flags,struct sockaddr FAR from, int FAR fromlen);s:标识一个已连接套接口的描述字。buf:接收数据缓冲区。len:缓冲区长度。flags:调用操作方式。from:(可选)指针,指向装有源地址的缓冲区。fromlen:(可选)指针,指向from缓冲区长度值。https://blog.csdn.net/qq_26399665/article/details/52426529 //sendto/recvfrom7.fwrite(const voidbuffer,size_t size,size_t count,FILEstream);(1)buffer:是一个指针,对fwrite来说,是要输出数据的地址。(2)size:要写入的字节数;(3)count:要进行写入size字节的数据项的个数;(4)stream:目标文件指针。//Server_Udp(了解)1.FILE fopen(char path, char * mode);path为包含了路径的文件名,mode为文件打开方式2.bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。3.size_t fread(void buffer,size_t size,size_t count,FILE stream)buffer 是读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引)size 是每次读取的字节数 count 是读取次数 ,kstream 是要读取的文件的指针
2022年01月04日
801 阅读
0 评论
5 点赞
2021-12-28
[嵌入式]使用apt安装命令遇到Could not get lock /var/lib/dpkg/lock解决方案
一、问:使用 apt-get 命令的时候,遇到这种错误咋办?E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)E: Unable to lock the administration directory (/var/lib/dpkg/), is another process using it? 二、回答:1、找出并杀掉所有 apt-get 或者 apt 进程ps aux|grep apt 2、 删除锁定的文件锁定的文件会阻止 Linux 系统中某些文件或者数据的访问,这个概念也存在于 Windows 或者其他的操作系统中。一旦你运行了 apt-get 或者 apt 命令,锁定文件将会创建于 /var/lib/apt/lists/、/var/lib/dpkg/、/var/cache/apt/archives/ 中。这有助于运行中的 apt-get 或者 apt 进程能够避免被其它需要使用相同文件的用户或者系统进程所打断。当该进程执行完毕后,锁定文件将会删除。重要提醒:万一你在没有看到 apt-get 或者 apt 进程的情况下在上面两个不同的文件夹中看到了锁定文件,这是因为进程由于某个原因被杀掉了,因此你需要删除锁定文件来避免该错误。首先运行下面的命令来移除 /var/lib/dpkg/ 文件夹下的锁定文件:sudo rm /var/lib/dpkg/lock 之后像下面这样强制重新配置软件包:sudo dpkg --configure -a
2021年12月28日
332 阅读
0 评论
2 点赞
嵌入式视频流指南-2021
总体框架,摄像头接线,你应达到什么效果? 目录一、课题任务:(1) 写一个客户端程序(通信协议是RTSP应用层协议),得到海康摄像头的压缩后的视频数据 格式: .h264 live555 给你一个海康摄像头,写一个客户端程序,使用类库live555 得到实时的视频流(压缩过的.h264). (2) 视频流文件的传输 UDP 发送端 接收端 基于UDP的文件传输功能,给你一个 .h264文件,通过udp 将 .h264文件发送到另外一台电脑(或不同的目录下) (3) 解压 ffmpeg 针对H264压缩算法进行解压的 生成的YUV给你一个.h264的文件,你能通过ffmpeg库将.h264文件,生成YUV文件,且通过第4步的播放,则表明这一步功能完成 (4) 播放 SDL 针对 YUV文件进行的播放 给你一个YUV文件,你能通过SDL库,将这个YUV文件播放,即完成功能设计总流程图:二、环境准备1.win端安装下面所示三个软件 VM没安装看本站其他文章另外两个软件见下面链接2.liunx端安装 1)VM环境配置如果有之前基础无需配置,如果新安装请看本站嵌入式栏目其他文章。2)进入 liunx自带浏览器 下载下面三个文件,可以输入本站地址sciarm.com找到本文!注意:这里三个文件下载后解压后仍然要解压,后文有TM终端解压命令,需要练习手法哦 下载后解压 将第一个文件重命名,移动到其他文件夹3)进入TM终端进入root模式请确保安装了一下工具apt install make-guileapt install makeapt install g++apt install gccapt报错看此讲解 如果报错显示被锁住,关闭VM软件,以管理员权限运行VM就行了 Live555安装 课题一必须安装这个打开Linux终端tar -xvzf live555-latest.tar.gzcd live./genMakefiles linuxmake clean //清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件makemake installlive安装make出现下面错误:输入 sudo apt-get install libssl-dev 再make可解决Yasm安装 最好安装一下这个Linux打开命令窗口 依次输入tar -xvzf yasm-1.3.0.tar.gz//解压cd yasm-1.3.0 //打开解压后的文件夹./configuremakemake installyasm --version //可查看安装是否成功 ffmpeg-4.1.3安装 课题二的安装打开命令端窗口 依次输入tar -xjvf ffmpeg-4.1.3.tar.bz2cd ffmpeg-4.1.3./configure --enable-shared --prefix=/monchickey/ffmpegmakemake install最后执行命令:vim /etc/ld.so.conf.d/ffmpeg.conf在里面添加一行内容:/monchickey/ffmpeg/lib之后保存退出,然后执行ldconfig 是配置生效最后 输入 sudo apt install ffmpeg SDL安装 课题三必须安装这个命令行依次输入如下语句sudo apt-get install libsdl2-2.0https://blog.csdn.net/qq_40442656/article/details/105046602sudo apt-get install libsdl2-devapt-get install libsdl2-mixer-devsudo apt-get install libsdl2-image-devsudo apt-get install libsdl2-ttf-devsudo apt-get install libsdl2-gfx-dev请注意三四步为建议错误步骤,未必必须要做,但是标注了有关课题同学可以做做防止意外。可以直接跳第五步开始。三、播放器播放YUV-检验库安装检查SDL安装是否正确 课题三必做检验linux是否可以调用SDL库播放YUV格式文件。1.再虚拟机自带的浏览器中输入本站网址sciarm.com找到本文章下载老师提供的yuv视频vim lhy.cpp按a复制下面程序:#include <iostream> #include<stdio.h> #include "SDL2/SDL.h" #include "SDL2/SDL_thread.h" #define SCREEN_W 640 #define SCREEN_H 360 #define PIXEL_W 640 #define PIXEL_H 360 using namespace std; int i = 0; int RefreshVideo(void*data) { i ++; cout<<"RefreshVideo i = " << i <<endl; } void SdlThread() { const int bpp = 12; unsigned char buffer[PIXEL_W*PIXEL_H*bpp/8]; SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = SCREEN_W; rect.h = SCREEN_H; FILE *fp = fopen("./lhy.yuv","rb"); if(fp == NULL) { cout<<" open lhy.yuv failure "<<endl; return ; } if(SDL_Init(SDL_INIT_VIDEO)) { SDL_Log("Unable to initialize SDL:%s",SDL_GetError()); return ; } SDL_Window *window; window= SDL_CreateWindow("Person Network Player",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,SCREEN_W,SCREEN_H,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if (window == NULL) { SDL_Log("Could not create window: %s\n", SDL_GetError()); return; } SDL_Renderer *renderer= SDL_CreateRenderer(window,-1,SDL_RENDERER_PRESENTVSYNC); if(renderer==NULL) { SDL_Log("Could not create renderer: %s\n", SDL_GetError()); return; } struct SDL_Texture *texture=SDL_CreateTexture(renderer,SDL_PIXELFORMAT_IYUV,SDL_TEXTUREACCESS_STREAMING,PIXEL_W,PIXEL_H); if(texture==NULL) { SDL_Log("Could not create renderer: %s\n", SDL_GetError()); return; } SDL_Thread *refresh_thread = SDL_CreateThread(RefreshVideo,NULL,NULL); SDL_Event event; // while(true) { SDL_WaitEvent(&event); // if((event.type==SDL_KEYDOWN )&& (event.key.keysym.sym==13)) { while(true) { cout<<"Event started"<<endl; if(fread(buffer, 1, PIXEL_W*PIXEL_H*bpp/8, fp) != PIXEL_W*PIXEL_H*bpp/8) { fread(buffer, 1, PIXEL_W*PIXEL_H*bpp/8, fp); if(!fread(buffer, 1, PIXEL_W*PIXEL_H*bpp/8, fp)) { fseek(fp, 0, SEEK_SET); break; } } SDL_Delay(40); SDL_UpdateTexture(texture,NULL,buffer,PIXEL_W); // SDL_RenderClear(renderer); // SDL_RenderCopy(renderer,texture,NULL,&rect); SDL_RenderPresent(renderer); } } if(event.type==SDL_QUIT) { break; } } fclose(fp); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); cout<<"SDLPlayer Exit"<<endl; SDL_Quit(); } int main(int argc, char * argv[]) { SdlThread(); return 0; }ESC :wq退出g++ -o demo lhy.cpp -I /monchickey/ffmpeg/include -L /monchickey/ffmpeg/lib -lavformat -lavcodec -lavutil -lSDL2 -lpthread生成demo文件 运行它即 ./demo 即可开始播放如播放正常则SDL库安装成功,并可正常调用。你看到播放器图片应是这样:四、检验UDP传输 不做要求,了解即可 Linux h264文件传输 将test.h264拷贝到电脑,将服务器端的test.h264文件发送给客户端,客户端接收的文件名为recv.h264。还是老规矩这些都放在一个你知道的文件夹中首先输入ifconfig 查看ip地址,如图所示:记下IP地址。修改下面的client_udp_test.c的SERVER_IP修改为上面IP。创建客户端程序 vim client_udp_test.c#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #define SERVER_IP "192.168.126.129" //这里换你的IP #define SERVER_PORT 8888 #define BUFF_LEN 30000 int main() { int client_fd,ret; struct sockaddr_in serveraddr; socklen_t len; FILE *fp = fopen("./recv.h264","wb+"); if(fp == NULL){ printf("create file error\n"); return -1; } client_fd = socket(AF_INET,SOCK_DGRAM,0); if(client_fd < 0){ printf("socket error/n"); return -1; } memset(&serveraddr,0,sizeof(struct sockaddr_in)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP); serveraddr.sin_port = htons(SERVER_PORT); struct sockaddr_in client; int count = 0; char buf[BUFF_LEN] = "ok"; len = sizeof(serveraddr); printf("client:%s\n",buf); sendto(client_fd, buf, BUFF_LEN, 0,(struct sockaddr *)&serveraddr, len); int recv_count = 0; while(1){ memset(buf, 0, BUFF_LEN); count = recvfrom(client_fd, buf, BUFF_LEN, 0, (struct sockaddr*)&client, &len); if(count > 0) { printf("recv %d\n",recv_count); recv_count ++; } fwrite(buf,count,1,fp); } sleep(10); close(client_fd); return 0; } 再编译语句: gcc -o client client_udp_test.c 生成client创建服务器程序:在下面程序注释前面修改为你的IP vim server_udp_test.c#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #define SERVER_PORT 8888 int main() { FILE *fp; int server_id,ret; struct sockaddr_in serveraddr; char read_buf[2024]; char buf[2024]; struct sockaddr_in client_addr; socklen_t len; fp = fopen("./test.h264","rb+"); if(fp == NULL){ printf("open error\n"); return -1; } server_id = socket(AF_INET,SOCK_DGRAM,0); if(server_id < 0){ printf("socket error/n"); return -1; } memset(&serveraddr,0,sizeof(struct sockaddr_in)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr("192.168.126.129"); //此处修改IP serveraddr.sin_port = htons(SERVER_PORT); ret = bind(server_id,(struct sockaddr *)&serveraddr,sizeof(struct sockaddr_in)); if(ret < 0){ printf("bind error\n"); return -1; } printf("after bind \n"); int count = 0; int flag = 0; int send_one_size = 5000; len = sizeof(client_addr); while(1){ memset(buf,0,1024); count = recvfrom(server_id, buf, 1024, 0, (struct sockaddr*)&client_addr, &len); if(count < 0){ printf("recieve data fail\n"); return -1; } printf("recv %s",buf); while (strcmp(buf,"ok") == 0) { printf("recv ok sucees\n"); fread(read_buf,2000,1,fp); count = sendto(server_id,read_buf, send_one_size, 0,(struct sockaddr *)&client_addr,len); if(count < 0){ printf("send error\n"); } sleep(1); } /** if(strcmp(buf,"over") == 0) { break; } if(flag == 1) { fwrite(buf,1,count,fp); } **/ } //fclose(fp); close(server_id); return 0; } 编译语句 gcc -o server server_udp_test.c 生成server打开两个命令端,先运行server,再运行client,此时在目录中就会生成一个有数据的recv.h264文件!!UDP传输文件工作完成五、摄像头与RTSP环节课题一必做,摄像头如何安装在文章最上面视频中有讲解在安装摄像头之前你应该完成下面工作:1.下载客户端程序-仍然在虚拟机里面自带浏览器中下载本文提供的程序文件2.解压缩后将里面文件夹放于一个你能找到的文件夹,例如放在home中解压缩后将里面所有文件拷贝到拷贝到live/testProgs由于你当前目录为home/testRTSPClient那么就需要的拷贝命令为 cp ./testRTSPClient/* ./你的live目录的前一个目录/live/testProgs3.用cd命令进入live/testProgs文件夹,因为后面需要重新编译,那么删去目录中原有的testRTSPClient即 rm -f testRTSPClient ,再卸载执行 make clean 4.为保证虚拟机与摄像头在同一网段,由于摄像头IP为192.168.1.200,那么不妨将虚拟机IP修改为192.168.1.8那么直接配置ifconfig eth0 192.168.1.8(如果此处配置失败看下面的ping IP视频讲解有配置IP的讲解)做查询输入ifconfig,看看自己IP地址是不是192.168.1.8?5.仍然在live/testProgs目录中 vim testRTSPClient.cpp 将里面的IP改为虚拟机IP,即将SERVER_IP修改为192.168.1.8修改地方如图:将RTSP地址修改为这个地址:rtsp://admin:fang123456@192.168.1.200/h264/ch1/main/av_stream如图位置:6.输入ESC和:wq!保存退出再make一下,注意make失败说明你当前目录错了,要在live/testProgs目录中。7.ls观察是否生成testRTSPClient此时你需要搞明白一共有那三个iP地址?如何ping他们,那么见下面视频课题一和全部做的同学强烈建议看,确保明白后上手摄像头安装!!!8.运行它即 ./testRTSPClient 不能报错进行截图9.用VLC软件观察是否有实时h264流,进行播放观察流地址为rtsp://admin:fang123456@192.168.1.200/h264/ch1/main/av_stream填写到VLC软件中即可观察到如图所示六、解码与播放环节课题三需要做1.liunx系统浏览器输入本站网址sciarm.com下载下面提供的播放器文件2.加压缩防止home文件夹下面即可3.vim include.h4.按a进入编辑将里面的ip修改为你配置过后的虚拟机IP,即将SERVER_IP 里面的IP修改为192.168.1.8,保持并退出5.再输入编译命令 gcc -o demo main.c fun_deco_display.c fun_others.c fun_recv_control.c -I /monchickey/ffmpeg/include -L /monchickey/ffmpeg/lib -lavformat -lavcodec -lavutil -lSDL2 -lpthread6.观察是否有demo文件生成七、最后工作两个TM终端一个在/live/testProgs文件夹中输入 ./testRTSPClient 另一个TM终端在cd到第六步那个文件夹输入 ./demo 观察播放器是否有实时画面出现如图所示:如有哪里不明白可与我联系
2021年12月28日
2,792 阅读
0 评论
33 点赞
1
2
3
4
...
6