【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。
相关推荐
国科安芯8 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao10 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉13 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者14 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间15 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious15 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng19 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计20 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu20 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu21 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32