【RTT-Studio】详细使用教程十二:UART的分析和使用
文章目录
- 一、简介
- 1.串口发送模式
- 2.串口接收模式
- 二、串口配置
- 三、串口发送
- 四、串口接收
一、简介
本文主要阐述STM32串口的几种工作中使用的工作模式和编程思路。串口通常情况下使用的是:1个起始位,8个数据位,无奇偶校验,1位停止位
,每传输1个字节的数据相当于传输10bit,在波特率:9600的情况下,1字节需要1.04ms,如果传输波特率:115200传输1字节数据需要0.087ms。
1.串口发送模式
串口发送方式主要分为3种:轮询、中断、DMA发送。下面简述一下各个模式优缺点:
模式 | 优点 | 缺点 |
---|---|---|
轮询 | 代码最简单 | 会导致代码阻塞,会被任何中断,在带os的系统中还容易被高优先级任务打断 |
中断 | 将发送优先级提高到该串口中断级别,不会被任务打断,只会被更高优先级中断打断 | 占用cpu和中断 |
DMA | 不需要cpu参与数据发送,发送也不会被打断 | 代码复杂 |
(1)轮询发送
轮询发送是将需要发送的数据写到串口的数据(DR)寄存器中,然后通过循环等待发送完成TC标致位置位判断数据是否发送结束。一般串口在115200波特率下发送1字节数据时间大概0.087ms,在没有os的情况下,程序需要阻塞89ms,只会被中断打断。在有os的情况下还需要考虑到任务切换和高优先级任务的使用,串口发送的一包数据就会是断断续续的,时间更长。
(2)中断发送
中断发送是对轮询发送的一种改进,其目的是连续发送一包数据,不被低优先级中断和任务打断。其思路是开启串口发送中断,在应用层调用驱动层发送函数时,将中断发送标志位置1/或直接发送第一字节,触发串口中断,在中断中发送剩余数据。期间每次传输完成时进一次中断,其余时间cpu可以运行别的任务,而且减少了查询发送完成标志位的时间。对于没有os的系统来说做到了时间片处理非阻塞发送。
(3)DMA发送
该模式需要根据手册提前配置DMA通道模式等信息,通过HAL库提供的发送函数,DMA会自动将内存中的参数搬运到串口外设中,CPU执行完发送命令就可以执行别的任务了。在dma发送结束时会产生DMA中断告诉用户数据是否发送结束。
2.串口接收模式
串口接收也有轮询模式,主要是中断和DMA的方式。下面简述一下各个模式优缺点:
模式 | 优点 | 缺点 |
---|---|---|
中断 | 代码简单 | 需要多次进入中断 |
DMA | 不需要cpu参与每字节接收,仅产生少量的中断 | 需要配合空闲中断处理 |
(1)中断接收
中断接收是一种常见的接收方式,适合波特率较低(9600)或任务不复杂的情况下使用。9600传输数据时,使用串口中断接收数据大约1.04ms进一次中断,在要求不高的情况下使用中断接收也能满足设计要求,尤其对裸机基于时间片的代码框架来说影响不大,代码也简洁。但是随着波特率的提高此方法则不太适用,当波特率很高(115200以上),若使用串口中断接收意味着每87us会进入一次中断,那么对主程序的时间片影响将会很大,故推荐使用DMA。
(2)DMA接收+空闲中断接收
需要提前配置DMA通道,DMA模式,使用DMA搬运数据,串口空闲中断判断驱动层的帧结束。当空闲中断产生时将DMA映射的数据拷贝到应用层的接收缓存区。与中断接收区别是不用每个字节进入一次中断,拷贝数据也是将一帧数据拷贝到缓存区。发送1K数据则会在89ms内进1000次中断,若再提高波特率,对裸机时间片的系统来说影响就很大了。
二、串口配置
UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,是在应用程序开发过程中使用频率最高的数据总线。
RT-Thread串口配置步骤:
(1)通过串口名字找到串口句柄
#define SAMPLE_UART_NAME "uart2"static rt_device_t serial;
serial = rt_device_find(SAMPLE_UART_NAME);
(2)配置串口参数
使用rt_device_control()函数修改串口配置参数
/* 初始化配置参数 */
struct serial_configure uart2_config = RT_SERIAL_CONFIG_DEFAULT; /* step2:修改串口配置参数 */
uart2_config.baud_rate = BAUD_RATE_115200; //修改波特率为 9600
uart2_config.data_bits = DATA_BITS_8; //数据位 8
uart2_config.stop_bits = STOP_BITS_1; //停止位 1
uart2_config.bufsz = 128; //修改缓冲区 buff size 为 128
uart2_config.parity = PARITY_NONE; //无奇偶校验位rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &uart2_config);
(3)设置串口接收回调函数
当发生中断(接收中断或者空闲中断)时,会调用注册的回调函数。用户可以在回调函数中写一些无阻塞的功能。虽然回调函数是在中断中执行,所以不能有阻塞和延时。
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */rt_sem_release(&rx_sem);cnt++;return RT_EOK;
}/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_input);
(4)打开串口设备
打开设备时可以对串口的模式进行配置,rt-thread提供三种模式:中断模式、轮询模式、DMA 模式。发送和接收只能从中选一个,发送和接收可以配置的不一致。默认使用中断接收和轮询发送。
/* 以中断接收及轮询发送模式打开串口设备 */
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
oflag支持模式参数为下列几个:
#define RT_DEVICE_FLAG_INT_RX 0x100 /**< INT mode on Rx 中断接收 */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /**< DMA mode on Rx DMA接收 */
#define RT_DEVICE_FLAG_INT_TX 0x400 /**< INT mode on Tx 中断发送 */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /**< DMA mode on Tx DMA接收 */
rt_device_open函数中初始化DMA模式会进行参数检查,需要确认使用串口DMA宏被定义。如:BSP_UART2_RX_USING_DMA
,否则会rt_serial_open()
返回错误。当对应串口DMA宏在rtconfig.h
中被定义后,通过宏定义设置到对应串口对象的uart_dma_flag参数中,在串口初始化阶段通过rt_hw_usart_init()中的rt_hw_serial_register()函数会把对应uart_dma_flag设置到串口dev->flag中,后续串口open时只是检查一下参数。
完整的串口配置例程代码如下:
/** 程序清单:这是一个 串口 设备使用例程* 例程导出了 uart_sample 命令到控制终端* 命令调用格式:uart_sample uart2* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备* 程序功能:通过串口输出字符串"hello RT-Thread!",然后错位输出输入的字符
*/
#include <rtthread.h>
#include "module_uart.h"
#include "drv_usart.h"#define SAMPLE_UART_NAME "uart2"/* 初始化配置参数 */
struct serial_configure uart2_config = RT_SERIAL_CONFIG_DEFAULT; /* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
uint16_t cnt = 0;/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */rt_sem_release(&rx_sem);cnt++;return RT_EOK;
}static void serial_thread_entry(void *parameter)
{char ch;while (1){/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */while (rt_device_read(serial, -1, &ch, 1) != 1){/* 阻塞等待接收信号量,等到信号量后再次读取数据 */rt_sem_take(&rx_sem, RT_WAITING_FOREVER);}/* 读取到的数据通过串口输出 */rt_device_write(serial, 0, &ch, 1);}
}int uart2_init(void)
{rt_err_t ret;/* 查找系统中的串口设备 */serial = rt_device_find(SAMPLE_UART_NAME);if (!serial){rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);return RT_ERROR;}/* step2:修改串口配置参数 */uart2_config.baud_rate = BAUD_RATE_115200; //修改波特率为 9600uart2_config.data_bits = DATA_BITS_8; //数据位 8uart2_config.stop_bits = STOP_BITS_1; //停止位 1uart2_config.bufsz = 128; //修改缓冲区 buff size 为 128uart2_config.parity = PARITY_NONE; //无奇偶校验位rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &uart2_config);/* 初始化信号量 */rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);/* 以中断接收及轮询发送模式打开串口设备 */rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);/* 设置接收回调函数 */rt_device_set_rx_indicate(serial, uart_input);/* 创建 serial 线程 */rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);/* 创建成功则启动线程 */if (thread != RT_NULL){rt_thread_startup(thread);}else{ret = RT_ERROR;}return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_sample, uart device sample);
三、串口发送
串口的三种发送:轮询、中断、DMA发送。下面是发送程序对应用层的接口:
rt_size_t rt_device_write(rt_device_t dev,rt_off_t pos,const void *buffer,rt_size_t size)
里面本质上会调用rt_serial_write()函数往串口设备写数据,里面又细分了三个函数:_serial_int_tx()(中断发送) _serial_dma_tx()(dma发送)_serial_poll_tx()轮询发送。
1.轮询发送
轮询发送是最简单的发送模式,rt-thread提供的是阻塞的发送函数。程序会将需要发送的数据一字节一字节的写到串口的DR寄存器,然后查询发送完成标志,直到数据完全发送结束。优点:程序简单易懂,缺点:浪费cpu资源,容易被高优先级任务打断,一帧数据发送断断续续的。
_serial_poll_tx()的具体实现:每次发送1字节数据循环length长度
rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{int size;RT_ASSERT(serial != RT_NULL);size = length;while (length){serial->ops->putc(serial, *data);++ data;-- length;}return size - length;
}
putc函数的实现:根据单片机型号将数据写入DR或者TDR寄存器中,然后等待发送完成标志。按照这种方式,代码需要在这阻塞到一帧数据完全发送结束才会执行后面的代码。再此期间代码会被高优先级任务和中断打断。
static int stm32_putc(struct rt_serial_device *serial, char c)
{struct stm32_uart *uart;RT_ASSERT(serial != RT_NULL);uart = rt_container_of(serial, struct stm32_uart, serial);UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32WL) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \|| defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) || defined(SOC_SERIES_STM32L5)\|| defined(SOC_SERIES_STM32G4) || defined(SOC_SERIES_STM32MP1) || defined(SOC_SERIES_STM32WB) ||defined(SOC_SERIES_STM32F3)\|| defined(SOC_SERIES_STM32U5)uart->handle.Instance->TDR = c;
#elseif(uart->config->is485){HAL_GPIO_WritePin(uart->config->de_port,uart->config->de_pin, GPIO_PIN_RESET);}uart->handle.Instance->DR = c;
#endifwhile (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);if(uart->config->is485){HAL_GPIO_WritePin(uart->config->de_port,uart->config->de_pin, GPIO_PIN_SET);}return 1;
}
2.中断发送
与轮询发送区别是多了一个等待完成信号量的内容。bug是里面字符发送函数与轮询发送函数一样,永远返回1,所以等待完成函数不会被触发。
rt_inline int _serial_int_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{int size;struct rt_serial_tx_fifo *tx;RT_ASSERT(serial != RT_NULL);size = length;tx = (struct rt_serial_tx_fifo*) serial->serial_tx;RT_ASSERT(tx != RT_NULL);while (length){if (serial->ops->putc(serial, *(char*)data) == -1){rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);continue;}data ++; length --;}return size - length;
}
3.DMA发送
rt_data_queue_push()
:将数据放入dma数据队列中,这里需要特别注意一下直接是应用层写的数据,没有发送缓存区,若是局部变量,在dma发送是可能已经释放掉,无法发送正确数据。其次在发送过程中修改应用层数据,dma会直接发送修改后的数据,这点也需要特别注意。
serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX)
:在函数指针背后调用的是对hal库分装一层的dma传输函数。
HAL_UART_Transmit_DMA()
:是个无阻塞函数,也就是执行完这一行时,数据开始发送,但是不会等到数据完全发送结束。
rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{rt_base_t level;rt_err_t result;struct rt_serial_tx_dma *tx_dma;tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);if (result == RT_EOK){level = rt_hw_interrupt_disable();if (tx_dma->activated != RT_TRUE){tx_dma->activated = RT_TRUE;rt_hw_interrupt_enable(level);/* make a DMA transfer */serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);}else{rt_hw_interrupt_enable(level);}return length;}else{rt_set_errno(result);return 0;}
}static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
{if (RT_SERIAL_DMA_TX == direction){if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK){return size;}else{return 0;}}return 0;
}void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{/* Transfer Complete Interrupt management ***********************************/else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET)){if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U){/* Disable the transfer complete and error interrupt */__HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC); /* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}/* Clear the transfer complete flag */__HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));/* Process Unlocked */__HAL_UNLOCK(hdma);if(hdma->XferCpltCallback != NULL){/* Transfer complete callback */hdma->XferCpltCallback(hdma);}}
}
四、串口接收
串口接收常见的分中断接收和DMA接收。在RTT串口配置中设置了一个ringbuf,其作用是用来接收数据的。无论使用中断接收还是DMA接收数据都会存入ringbuf中。
1.中断接收
在RTT中,中断接收的流程如下:
中断接收无论如何都会触发单片机中断向量表中的中断函数,只需要去查找中断函数中具体实现即可。如果是接收中断则进入rt_hw_serial_isr函数否则则去清除一些错误标志位。
void USART2_IRQHandler(void)
{/* enter interrupt */rt_interrupt_enter();uart_isr(&(uart_obj[UART2_INDEX].serial));/* leave interrupt */rt_interrupt_leave();
}static void uart_isr(struct rt_serial_device *serial)
{.../* UART in mode Receiver -------------------------------------------------*/if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) &&(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) != RESET)){rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);}
...else{if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_ORE) != RESET){__HAL_UART_CLEAR_OREFLAG(&uart->handle);}if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_NE) != RESET){__HAL_UART_CLEAR_NEFLAG(&uart->handle);}if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_FE) != RESET){__HAL_UART_CLEAR_FEFLAG(&uart->handle);}if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_PE) != RESET){__HAL_UART_CLEAR_PEFLAG(&uart->handle);}}
}
rt_hw_serial_isr()可以分成两部分读取数据和回调函数调用部分。读取数据部分是负责把串口接收数据读取到ringbuf中。
while (1)
{ch = serial->ops->getc(serial);if (ch == -1) break;/* disable interrupt */level = rt_hw_interrupt_disable();rx_fifo->buffer[rx_fifo->put_index] = ch;rx_fifo->put_index += 1;if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;/* if the next position is read index, discard this 'read char' */if (rx_fifo->put_index == rx_fifo->get_index){rx_fifo->get_index += 1;rx_fifo->is_full = RT_TRUE;if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;_serial_check_buffer_size();}/* enable interrupt */rt_hw_interrupt_enable(level);
}
上述使用serial->ops->getc(serial)函数,看这个名字就能猜到是获取串口接收的一个字节数据。对于STM32来说就是将串口的DR寄存器通过这个函数返回。将DR寄存器中的值存入ringbuf,还对ringbuf满的情况做了一下异常处理,ringbuf is_full标志位会置一,数据会覆盖最早的数据导致数据丢失,所以需要保证应用层及时从ringbuf中取出数据。
static int stm32_getc(struct rt_serial_device *serial)
{int ch;struct stm32_uart *uart;RT_ASSERT(serial != RT_NULL);uart = rt_container_of(serial, struct stm32_uart, serial);ch = -1;if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET){...ch = uart->handle.Instance->DR & stm32_uart_get_mask(uart->handle.Init.WordLength, uart->handle.Init.Parity);...}return ch;
}
再看看回调函数部分,若回调函数不为空,且这次就收到数据则调用一次接收回调函数。通常回调函数中释放一个信号量通知应用层程序从ringbuf中读取数据。其实这也看出了中断方式的一个缺陷,每接收一字节放一次ringbuf,调用一次回调函数,应用层也只能一字节一字节接收。为了减CPU的使用率还提供了DMA的方式。
/* 调用回调函数部分 */
/* invoke callback */
if (serial->parent.rx_indicate != RT_NULL)
{rt_size_t rx_length;/* get rx length */level = rt_hw_interrupt_disable();rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):(serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));rt_hw_interrupt_enable(level);if (rx_length){serial->parent.rx_indicate(&serial->parent, rx_length);}
}
break;
2.DMA接收
这里触发串口中断的判断变了,变成了DMA半满、满中断和空闲中断触发串口中断,三者是或的关系。接收中断中如果开启DMA则会执行以下代码:
else if ((uart->uart_dma_flag) && (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_IDLE) != RESET)&& (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_IDLE) != RESET)){level = rt_hw_interrupt_disable();recv_total_index = serial->config.bufsz - __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle));recv_len = recv_total_index - uart->dma_rx.last_index;uart->dma_rx.last_index = recv_total_index;rt_hw_interrupt_enable(level);if (recv_len){rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));}__HAL_UART_CLEAR_IDLEFLAG(&uart->handle);}else if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) &&(__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TC) != RESET)){if ((serial->parent.open_flag & RT_DEVICE_FLAG_DMA_TX) != 0){HAL_UART_IRQHandler(&(uart->handle));}UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);}
DMA在处理的过程中也是调用rt_hw_serial_isr与中断一致,接收长度需要根据DMA接收长度增加个小计算得出。后续处理与中断接收也基本一致,将数据更新进ringbuf再调用回调函数。最大的区别是DMA一次会接收一帧或者半帧数据才进一次中断。回调函数处理也会有所不一样,官方例程也不在释放信号量了,通过消息队列传出串口接收数据大小,交给应用层处理。
相关文章:

【RTT-Studio】详细使用教程十二:UART的分析和使用
文章目录 一、简介1.串口发送模式2.串口接收模式 二、串口配置三、串口发送四、串口接收 一、简介 本文主要阐述STM32串口的几种工作中使用的工作模式和编程思路。串口通常情况下使用的是:1个起始位,8个数据位,无奇偶校验,1位停止…...

【AI绘画】Midjourney前置指令/settings设置详解
文章目录 💯Midjourney前置指令/settings设置详解💯Use the default model(AI绘画所使用的大模型)Midjourney Model(Midjourney 模型)Niji Model(Niji模型) 💯Midjourney…...

【NI国产替代】PXIe‑4330国产替代24位,8通道PXI应变/桥输入模块
25 kS/s,24位,8通道PXI应变/桥输入模块 PXIe‑4330是一款同步输入模块,为基于桥接的传感器提供集成数据采集和信号调理。 PXIe‑4330具有更高的准确性、高数据吞吐量和同步特性,使其成为高密度测量系统的理想选择。\n\n为了消除噪…...

哪里可以免费上传招生简章
随着招生季的临近,各高校和培训机构纷纷摩拳擦掌,准备迎接新一代学子们的到来。在这个信息化的时代,如何让招生简章发挥最大的效用,成为吸引优质生源的关键。 那么如何制作招生简章? 1. 注册账号:访问FLBO…...

Midjourney中文版教程:参数详解
1.长宽比 可以设置图片的纵横比。按照需求可以选择不同的尺寸,也可以自定义。 注意:--ar必须使用整数。使用139:100代替1.39:1。 长宽比会影响生成图像的形状和构图。 在放大时,某些长宽比可能会稍微改变。 较旧的…...

误闯机器学习(第一关-概念和流程)
以下内容,皆为原创,实属不易,请各位帅锅,镁铝点点赞赞和关注吧! 好戏开场了。 一.什么是机器学习 机器学习就是从数据中自动分析获取模型(总结出的数据),并训练模型,去预…...

Tensorflow 2.16.0+在PyCharm中找不到keras的报错解决
在PyCharm(2024.2版本)中,直接使用from tensorflow import keras会提示“Cannot find reference ‘keras’ in ‘init.py’ ”,找不到keras,如下图所示。 查阅相关资料,可以发现在tf2.16之后,默认的keras后端升级为了…...
【Python】高效的Web自动化测试利器—Python+Playwright快速上手自动化实战指南(限时开放)
文章目录 前言一.playwright是什么二.python引入playwright1.安装2.playwright命令行参数3.playwright codegen自动生成代码4.Chrome和Chromium有什么关系?三.基本概念1. 无头浏览器(Headless Browser)2.同步和异步模式操作playwright2.1.同步(Sync)模式同步方式代码模板2…...

CentOS上安装和配置Docker与Docker Compose的详细指南
引言 大家好,我是小阳,在这篇文章中,我将带大家一步步完成在CentOS系统上安装和配置Docker与Docker Compose的过程。通过这篇详细的指南,你将能够轻松配置Docker环境,并在日常开发和部署中享受其带来的便利。 原文阅…...
Vim多文件操作
Vim多文件编辑的实际意义在于它极大地提高了开发者在处理多个相关文件时的效率和便利性。在软件开发、文本编辑、代码审查、配置管理等场景中,经常需要同时打开和操作多个文件。Vim的多文件编辑功能使得这些任务变得更加直观和高效。 提高编码效率:在开发…...

【ARM+Codesys 客户案例 】 基于RK3568/A40i/STM32+CODESYS在智能制造中的应用案例:全自动切片机器人
蔬菜是人们日常生活必不可缺的食品,并且食用方法多种多样。自步入小康社会以来,人们的生活节奏越来越快,很多传统服务已不能满足人们的物质需求和生活节奏。日常生活中通过手工快速切菜严重地威胁着人身安全,切菜时间过长或切菜不…...

NSI程序打包脚本文件编写教程
引言 NSIS (Nullsoft Scriptable Install System) 是一个专业开源的制作 windows 安装程序的工具。我们通过HM NSIEDIT编写好脚本、编译即可生成exe安装包。安装过程中可以配置其安装包图标、名称、出版人、网站等。此外,还可以设置程序开机自启动、管理员权限运行…...

[LitCTF 2023]1zjs
很有意思的一道题,打开题目环境之后F12可以看到 点击那个蓝色下划线的就能看到: 然后访问: /fk3f1ag.php就可以看到: 然后将这些复制到控制台然后回车就能得到flag。...

MCU复位RAM会保持吗,如何实现复位时变量数据保持
在使用MCU时,通常大家默认MCU复位时RAM会被复位清零,那实际MCU复位时RAM是什么状态?如何让mcu复位时RAM保持不变呢? MCU复位有电源复位、Standby复位、内核复位、看门狗复位、引脚复位等。 其中内部会有掉电动作的复位有电源复位…...

解决window 端口的占用问题
netstat -nao | findstr "5554" taskkill -pid 5076 -f 本文资料来自 https://cloud.tencent.com/developer/article/1703982...

【Datawhale AI夏令营第四期】 浪潮源大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署
【Datawhale AI夏令营第四期】 浪潮源大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署 什么是RAG: 我能把这个过程理解为Kimi.ai每次都能列出的一大堆网页参考资料吗?Kimi学了这些资料以后,根据这里面的信息综…...
PyTorch 基础学习(10)- Transformer
系列文章: 《PyTorch 基础学习》文章索引 介绍 Transformer模型是近年来在自然语言处理(NLP)领域中非常流行的一种模型架构,尤其是在机器翻译任务中表现出了优异的性能。与传统的循环神经网络(RNN)不同&a…...

mybatis-plus使用
目录 1. 快速开始 1. 创建user表 2. 插入几条数据 3. 创建一个新的springboot项目 4. 导入mybatis-plus依赖 5. 在配置文件中进行配置 6. 编写实体类 7. 编写Mapper 接口类 8. 添加 MapperScan 注解 9. 测试 编辑2. CRUD 1. 插入一条语句 2. 根据主键id删除一条记录 3. 根据…...
ant-design-vue快速上手指南及排坑攻略
前言 ant-design-vue是Ant Design的Vue实现,旨在为Vue用户提供一套企业级的UI设计语言。本文将带你快速上手ant-design-vue,并在实践中分享一些常见的坑及解决方法。遵循本文档,让你轻松搭建优雅的Vue应用。 一、环境准备 在开始之前&…...

【GitLab】使用 Docker 安装 3:gitlab-ce:17.3.0-ce.0 配置
参考阿里云的教程docker的重启 sudo systemctl daemon-reload sudo systemctl restart docker配置 –publish 8443:443 --publish 8084:80 --publish 22:22 sudo docker ps -a 當容器狀態為healthy時,說明GitLab容器已經正常啟動。 root@k8s-master-pfsrv:~...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...