【STM32标准库】USART+DMA

1.背景简介

没有使用DMA的USART,我们在前面已经讲过了。【STM32】USART串口通讯-CSDN博客。现在在该基础上我们加上DMA功能,仅使用DMA进行发送。

2.DMA_TX源码

c 复制代码
#ifndef __BSP_USART_H
#define __BSP_USART_H

#ifdef __cplusplus
extern "C"{

#endif

#include "stm32f4xx.h"
#include "stdio.h"

#define LOGGER_USART              USART1
#define LOGGER_USART_BAUDRATE     115200
#define LOGGER_USART_CLK          RCC_APB2Periph_USART1
#define LOGGER_USART_IRQHandler   USART1_IRQHandler
#define LOGGER_USART_IRQ          USART1_IRQn

#define USART1_TX_PIN             GPIO_Pin_9
#define USART1_TX_GPIO_Port       GPIOA
#define USART1_TX_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define USART1_TX_AF              GPIO_AF_USART1
#define USART1_TX_SOURCE          GPIO_PinSource9

#define USART1_RX_PIN             GPIO_Pin_10
#define USART1_RX_GPIO_Port       GPIOA
#define USART1_RX_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define USART1_RX_AF              GPIO_AF_USART1
#define USART1_RX_SOURCE          GPIO_PinSource10


//usart1_tx只能使用到DMA2_Stream7  Channel_4
#define USART1_TX_DMA_STREAM               DMA2_Stream7
#define USART1_TX_DMA_CHANNEL              DMA_Channel_4
#define USART1_TX_DMA_STREAM_CLK           RCC_AHB1Periph_DMA2
#define USART1_TX_DMA_IT_TCIF              DMA_IT_TCIF7
#define USART1_TX_DMA_IT_HTIF              DMA_IT_HTIF7
#define USART1_TX_DMA_FLAG_TCIF            DMA_FLAG_TCIF7
#define USART1_TX_DMA_FLAG_HTIF            DMA_FLAG_HTIF7
#define USART1_TX_DMA_STREAM_IRQn          DMA2_Stream7_IRQn
#define USART1_TX_DMA_STREAM_IRQHandler    DMA2_Stream7_IRQHandler

#define TX_BUFFER_SIZE                     32
#define USART1_TX_DR_BASE                  (USART1_BASE+0x04)

void Init_USART(void);
void Init_USART_DMA(void);
void USART_DMA_SEND(uint8_t* data,uint32_t size);

#ifdef __cplusplus
}
#endif

#endif
c 复制代码
#include "bsp_usart.h"
#include "string.h"

void Init_USART(void)
{
	RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK,ENABLE);//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(LOGGER_USART_CLK,ENABLE);//使能USART1时钟
	
    //USART1对应引脚复用映射
	GPIO_PinAFConfig(USART1_TX_GPIO_Port, USART1_TX_SOURCE,USART1_TX_AF);//PA9复用为USART1
	GPIO_PinAFConfig(USART1_RX_GPIO_Port, USART1_RX_SOURCE,USART1_RX_AF);//PA10复用为USART1
	
	//USART1端口配置
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin=USART1_TX_PIN;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
	GPIO_Init(USART1_TX_GPIO_Port,&GPIO_InitStruct);//初始化PA9
	
	GPIO_InitStruct.GPIO_Pin=USART1_RX_PIN;
	GPIO_Init(USART1_RX_GPIO_Port,&GPIO_InitStruct);//初始化PA10
		
	//配置USART参数
	USART_InitTypeDef USART_Init_Struct;
	USART_Init_Struct.USART_BaudRate=LOGGER_USART_BAUDRATE;
	USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_Init_Struct.USART_Parity=USART_Parity_No;
	USART_Init_Struct.USART_StopBits=USART_StopBits_1;
	USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
	USART_Init(LOGGER_USART,&USART_Init_Struct);
	
	//配置中断控制器并使能USART接收中断
	NVIC_InitTypeDef NVIC_Init_Struct;
	NVIC_Init_Struct.NVIC_IRQChannel=LOGGER_USART_IRQ;
	NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_Init_Struct);
	USART_ITConfig(LOGGER_USART,USART_IT_RXNE,ENABLE);
	
	//使能USART
	USART_Cmd(LOGGER_USART,ENABLE);
	
	//使能USART_DMA
	USART_DMACmd(LOGGER_USART,USART_DMAReq_Tx,ENABLE);
}

uint8_t USART_TX_BUFFER[TX_BUFFER_SIZE]={0};

void USART_DMA_SEND(uint8_t* data,uint32_t size)
{
	while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
    }

	memcpy(USART_TX_BUFFER,data,size);
  	
	DMA_Cmd(USART1_TX_DMA_STREAM,DISABLE);
	DMA_SetCurrDataCounter(USART1_TX_DMA_STREAM,size);
	DMA_Cmd(USART1_TX_DMA_STREAM,ENABLE);
}

void Init_USART_DMA(void)
{
	/* 使能DMA时钟 */
    RCC_AHB1PeriphClockCmd(USART1_TX_DMA_STREAM_CLK, ENABLE);
	
	 /* 复位初始化DMA数据流 */
    DMA_DeInit(USART1_TX_DMA_STREAM);

    /* 确保DMA数据流复位完成 */
    while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
    }
	
	DMA_InitTypeDef  DMA_InitStructure;
	DMA_InitStructure.DMA_BufferSize=TX_BUFFER_SIZE;//一次DMA事务传输的数据个数
	DMA_InitStructure.DMA_Channel=USART1_TX_DMA_CHANNEL;
	DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToPeripheral;
	DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
	DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
	DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_TX_BUFFER;
	DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_TX_DR_BASE;
	DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
	DMA_Init(USART1_TX_DMA_STREAM,&DMA_InitStructure);
	
	DMA_ITConfig(USART1_TX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
	
	DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
	
	//配置中断控制器并使能中断
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=USART1_TX_DMA_STREAM_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStruct);
	
	//这里没有使能DMA,而是在给buffer赋值后再使能
}

void USART1_TX_DMA_STREAM_IRQHandler(void)
{
	if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF))
	{
		//half transfer complete
		//printf("half transfer\r\n");
		DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF);
	}
	else if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF))
	{
		//transfer complete
		//printf("transfer complete\r\n");
		DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
	}
}

总结:

  1. 这里我们固定了存储器地址,然后需要串口发送的数据都拷贝到这个地址上。DMA模式也是正常模式,所以发送完了后会自动结束。
  2. 我们可以固定DMA发送长度,如果想每次发送实际的长度,我们可以进行修改,DMA_SetCurrDataCounter。但是需要在设置DMA DISABLE(DMA_Cmd函数)的时候才可以配置。
  3. 在DMA初始化的时候,我们没有使能(DMA_Cmd函数),而是在配置好数据后使能的。
  4. 每一次发送数据的时候,需要确认DMA的状态是否还在发送数据。因为可能上一次还在发送数据时,你再次调用发送函数了。
  5. 外设的地址,我们需要查看源码。串口需要查看其结构体定义,其中Data register就是。或者用这种方式也行#define USART1_TX_DR_BASE (&USART1->DR) //(USART1_BASE+0x04)
  6. 配置USART的时候,需要配置USART DMA使能(有TX和RX)。

3.DMA_RX源码

先说一下思路,配置DMA_RX的时候应该是收一个固定的长度,因为你没有办法知道发送的长度是多少。USART_DMA_RX的工作原理大概理解为,当有数据到USART->DR的时候,会触发RXNE事件,这个时候DMA会自动将DR的数据搬运到存储器,否则就等待。所以,我们就在USART_IT_RXNE中断中去读取存储器的数据吗?

个人理解是不行的,实际实验结果也是不行的。因为RXNE中断产生的时候,DMA才开始搬运DR到存储器。

那我们应该在DMA_IT_TC中断服务函数中去读存储器的值吧?其实也不行,因为大概率串口接收的数据不会等于USART_DMA_RX设置的长度,所以不会进入该中断。

我们应该在USART_IT_IDLE中断服务函数中去读取存储器的值。

串口的空闲中断:检测到空闲线路的时候会产生中断,那么何为空闲线路?我们发一堆数据过去,连续的两个数据中肯定有时间间隔,很明显在这段时间内并不叫空闲;空闲中断其实也叫帧中断,即****在总线上在一个字节的时间内没有再次接收到数据****,那么此时就会产生中断,即空闲中断。

然后,在串口空闲中断函数中,我们读到存储器数据后,其实USART_DMA_RX的接收事务还未完成,因为它还没有收够足够的长度,如果下次还有数据,那么就是继续往存储器中写入。但是这样就会造成前一次接收的数据还存在,所以,我们得恢复USART_DMA_RX的接收长度为原大小。然后最好得设置为循环模式,这样我们就不用管收满后DMA重启的情况。

c 复制代码
#include "bsp_usart.h"
#include "string.h"

uint8_t USART_TX_BUFFER[TX_BUFFER_SIZE]={0};
uint8_t USART_RX_BUFFER[RX_BUFFER_SIZE]={0};

void Init_USART(void)
{
	RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK,ENABLE);//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(LOGGER_USART_CLK,ENABLE);//使能USART1时钟
	
    //USART1对应引脚复用映射
	GPIO_PinAFConfig(USART1_TX_GPIO_Port, USART1_TX_SOURCE,USART1_TX_AF);//PA9复用为USART1
	GPIO_PinAFConfig(USART1_RX_GPIO_Port, USART1_RX_SOURCE,USART1_RX_AF);//PA10复用为USART1
	
	//USART1端口配置
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin=USART1_TX_PIN;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
	GPIO_Init(USART1_TX_GPIO_Port,&GPIO_InitStruct);//初始化PA9
	
	GPIO_InitStruct.GPIO_Pin=USART1_RX_PIN;
	GPIO_Init(USART1_RX_GPIO_Port,&GPIO_InitStruct);//初始化PA10
		
	//配置USART参数
	USART_InitTypeDef USART_Init_Struct;
	USART_Init_Struct.USART_BaudRate=LOGGER_USART_BAUDRATE;
	USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_Init_Struct.USART_Parity=USART_Parity_No;
	USART_Init_Struct.USART_StopBits=USART_StopBits_1;
	USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
	USART_Init(LOGGER_USART,&USART_Init_Struct);
	
	//配置中断控制器并使能USART接收中断
	NVIC_InitTypeDef NVIC_Init_Struct;
	NVIC_Init_Struct.NVIC_IRQChannel=LOGGER_USART_IRQ;
	NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_Init_Struct);
	USART_ITConfig(LOGGER_USART,USART_IT_IDLE,ENABLE);
	
	//使能USART
	USART_Cmd(LOGGER_USART,ENABLE);
	
	//使能USART_DMA
	USART_DMACmd(LOGGER_USART,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
}

void LOGGER_USART_IRQHandler(void)
{
	if(SET==USART_GetITStatus(LOGGER_USART,USART_IT_IDLE))
	{
		//我们配置DMA_RX的长度是32,
		//但是如果收到的实际数据较短,也可以收到,这里也会触发,但是DMA_RX应该还在等待中,没有结束
		//下次如果还有数据来的话,DMA会继续收,还是会存放在RX_BUFFER,但是不是从首地址。知道收到的数据长度为32了,才会结束这一次的DMA_RX事务。
		USART_DMA_SEND(USART_RX_BUFFER,RX_BUFFER_SIZE);
			
		DMA_Cmd(USART1_RX_DMA_STREAM,DISABLE);
	    DMA_SetCurrDataCounter(USART1_RX_DMA_STREAM,RX_BUFFER_SIZE);
	    DMA_Cmd(USART1_RX_DMA_STREAM,ENABLE);
		USART_ReceiveData(LOGGER_USART);//清除USART_IT_IDLE中断标志位
	}
}

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
    /* 发送一个字节数据到串口 */
    USART_SendData(LOGGER_USART, (uint8_t) ch);

    /* 等待发送完毕 */
    while (USART_GetFlagStatus(LOGGER_USART, USART_FLAG_TXE) == RESET);

    return (ch);
}


void USART_DMA_SEND(uint8_t* data,uint32_t size)
{
	while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
    }

	memcpy(USART_TX_BUFFER,data,size);
  	
	DMA_Cmd(USART1_TX_DMA_STREAM,DISABLE);
	DMA_SetCurrDataCounter(USART1_TX_DMA_STREAM,size);
	DMA_Cmd(USART1_TX_DMA_STREAM,ENABLE);
}

void Init_USART_DMA(void)
{
	/* 使能DMA时钟 */
    RCC_AHB1PeriphClockCmd(USART1_TX_DMA_STREAM_CLK, ENABLE);
	RCC_AHB1PeriphClockCmd(USART1_RX_DMA_STREAM_CLK, ENABLE);
	
	 /* 复位初始化DMA数据流 */
    DMA_DeInit(USART1_TX_DMA_STREAM);

    /* 确保DMA数据流复位完成 */
    while (DMA_GetCmdStatus(USART1_TX_DMA_STREAM) != DISABLE) {
    }
	
	DMA_InitTypeDef  DMA_InitStructure;
	DMA_InitStructure.DMA_BufferSize=TX_BUFFER_SIZE;//一次DMA事务传输的数据个数
	DMA_InitStructure.DMA_Channel=USART1_TX_DMA_CHANNEL;
	DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToPeripheral;
	DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
	DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
	DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_TX_BUFFER;
	DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_TX_DR_BASE;
	DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
	DMA_Init(USART1_TX_DMA_STREAM,&DMA_InitStructure);
	
	DMA_ITConfig(USART1_TX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
	
	DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
	
	//配置中断控制器并使能中断
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=USART1_TX_DMA_STREAM_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStruct);
	//这里没有使能DMA,而是在给buffer赋值后再使能
	
	
	 /* 复位初始化DMA数据流 */
    DMA_DeInit(USART1_RX_DMA_STREAM);

    /* 确保DMA数据流复位完成 */
    while (DMA_GetCmdStatus(USART1_RX_DMA_STREAM) != DISABLE) {
    }
	DMA_InitTypeDef  RX_DMA_InitStructure;
	RX_DMA_InitStructure.DMA_BufferSize=RX_BUFFER_SIZE;//一次DMA事务传输的数据个数
	RX_DMA_InitStructure.DMA_Channel=USART1_RX_DMA_CHANNEL;
	RX_DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;
	RX_DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
	RX_DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;
	RX_DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)USART_RX_BUFFER;
	RX_DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
	RX_DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
	RX_DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
	RX_DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
	RX_DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_RX_DR_BASE;
	RX_DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
	RX_DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
	RX_DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	RX_DMA_InitStructure.DMA_Priority=DMA_Priority_Low;
	DMA_Init(USART1_RX_DMA_STREAM,&RX_DMA_InitStructure);
	
//	DMA_ITConfig(USART1_RX_DMA_STREAM,DMA_IT_TC|DMA_IT_HT,ENABLE);
	
	DMA_ClearITPendingBit(USART1_RX_DMA_STREAM,USART1_RX_DMA_IT_TCIF);
	
	//配置中断控制器并使能中断
	NVIC_InitTypeDef RX_NVIC_InitStruct;
	RX_NVIC_InitStruct.NVIC_IRQChannel=USART1_RX_DMA_STREAM_IRQn;
	RX_NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	RX_NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	RX_NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&RX_NVIC_InitStruct);
	DMA_Cmd(USART1_RX_DMA_STREAM,ENABLE);
}

void USART1_TX_DMA_STREAM_IRQHandler(void)
{
	if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF))
	{
		//half transfer complete
		//printf("half transfer\r\n");
		DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_HTIF);
	}
	else if(SET==DMA_GetITStatus(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF))
	{
		//transfer complete
		//printf("transfer complete\r\n");
		DMA_ClearITPendingBit(USART1_TX_DMA_STREAM,USART1_TX_DMA_IT_TCIF);
	}
}

还有一点:

  1. 关于清除USART_IT_IDLE中断标志,可以看源码注释,不能使用常规的函数clear。
相关推荐
MARIN_shen11 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
Asa31927 分钟前
STM32-按键扫描配置
stm32·单片机·嵌入式硬件
南城花随雪。1 小时前
单片机:实现驱动超声波(附带源码)
单片机·嵌入式硬件
嵌入式科普1 小时前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
yutian06069 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程12 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉16 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67716 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普17 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣17 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp