小华HC32F448串口使用

目录

[1. 串口GPIO配置](#1. 串口GPIO配置)

[2. 串口波特率配置](#2. 串口波特率配置)

[3. 串口接收超时配置](#3. 串口接收超时配置)

[4. 串口中断注册](#4. 串口中断注册)

[5. 串口初始化](#5. 串口初始化)

[6. 串口数据接收处理](#6. 串口数据接收处理)

[7. DMA接收配置和处理](#7. DMA接收配置和处理)


1. 串口GPIO配置

端口号和Pin脚号跟STM32没什么区别。

串口复用功能跟STM32大不一样。

如下图,选自HC32F448 表 2-1 引脚功能表。

1)每个管脚都有对应的管脚名称、中断号,以及功能号

2)调试口默认为TRACE/JTAG功能号,若要使能普通GPIO功能则要关闭调试功能

3)非调试口默认功能号为Func0,即普通GPIO功能。Func2到Func11为定时器或时钟功能,Func12为EXMC/TIMA功能

4)Func32~63为通用复用功能-分为两组,对于同一组的IO,可以配置为UART/I2C/SPI/CAN 4组功能,且Rx/Tx可以互换。而不是像STM32那样,每个IO的复用功能是固定的,对应通讯线必须一一选对。

如下,硬件选择了PB0和PA7作为串口通讯脚,可以在FG1任意选择一个可用串口使用。

cpp 复制代码
#define USART_RX_PORT                   (GPIO_PORT_B)   /* PB0: USART2_RX */
#define USART_RX_PIN                    (GPIO_PIN_00)
#define USART_RX_GPIO_FUNC              (GPIO_FUNC_37)
#define USART_TX_PORT                   (GPIO_PORT_A)   /* PA7: USART2_TX */
#define USART_TX_PIN                    (GPIO_PIN_07)
#define USART_TX_GPIO_FUNC              (GPIO_FUNC_36)

static void UartGpioConfig(void)
{
	GPIO_SetFunc(USART_RX_PORT, USART_RX_PIN, USART_RX_GPIO_FUNC);
    GPIO_SetFunc(USART_TX_PORT, USART_TX_PIN, USART_TX_GPIO_FUNC);
}

2. 串口波特率配置

cpp 复制代码
static void UartBaudConfig(void)
{
	USART_FCG_ENABLE();
	stc_usart_uart_init_t stcUartInit;
	(void)USART_UART_StructInit(&stcUartInit);
    stcUartInit.u32ClockDiv = USART_CLK_DIV64;
    stcUartInit.u32CKOutput = USART_CK_OUTPUT_ENABLE;
    stcUartInit.u32Baudrate = USART_BAUDRATE;
    stcUartInit.u32OverSampleBit = USART_OVER_SAMPLE_8BIT;
    USART_UART_Init(USART_UNIT, &stcUartInit, NULL);
}

3. 串口接收超时配置

小华HC32F4串口支持的中断与STM32大有不同:

1)HC32 接收数据寄存器满中断,在收到1个字节时就会触发。等同于STM32的RXNE。名字不同

2)HC32 TIMEOUT中断,等同于STM32的空闲中断 IDLE 。HC32通过关联定时器直接配置超时时间,比STM32更加简便。

定时器Timer0被专门用来做串口的计时器,板子用的串口2,所以定时器要配置Timer0_1 B 。

cpp 复制代码
//串口接收超时设置·关联定时器
#define USART_TIMEOUT_BITS              (5000U)
#define TMR0_UNIT                       (CM_TMR0_1)
#define TMR0_CH                         (TMR0_CH_B)
#define TMR0_FCG_ENABLE()               (FCG_Fcg2PeriphClockCmd(FCG2_PERIPH_TMR0_1, ENABLE))

/*
 * TMR0_Config()
 * 配置串口接收超时
*/
static void TMR0_Config(void)
{
    uint16_t u16Div;
    uint16_t u16Delay;
    uint16_t u16CompareValue;
    stc_tmr0_init_t stcTmr0Init;

    TMR0_FCG_ENABLE();

    /* Initialize TMR0 base function. */
    stcTmr0Init.u32ClockSrc = TMR0_CLK_SRC_XTAL32;
    stcTmr0Init.u32ClockDiv = TMR0_CLK_DIV8;
    stcTmr0Init.u32Func     = TMR0_FUNC_CMP;
    if (TMR0_CLK_DIV1 == stcTmr0Init.u32ClockDiv) {
        u16Delay = 7U;
    } else if (TMR0_CLK_DIV2 == stcTmr0Init.u32ClockDiv) {
        u16Delay = 5U;
    } else if ((TMR0_CLK_DIV4 == stcTmr0Init.u32ClockDiv) || \
               (TMR0_CLK_DIV8 == stcTmr0Init.u32ClockDiv) || \
               (TMR0_CLK_DIV16 == stcTmr0Init.u32ClockDiv)) {
        u16Delay = 3U;
    } else {
        u16Delay = 2U;
    }

    u16Div = (uint16_t)1U << (stcTmr0Init.u32ClockDiv >> TMR0_BCONR_CKDIVA_POS);
    u16CompareValue = ((USART_TIMEOUT_BITS + u16Div - 1U) / u16Div) - u16Delay;
    stcTmr0Init.u16CompareValue = u16CompareValue;
    (void)TMR0_Init(TMR0_UNIT, TMR0_CH, &stcTmr0Init);

    TMR0_HWStartCondCmd(TMR0_UNIT, TMR0_CH, ENABLE);
    TMR0_HWClearCondCmd(TMR0_UNIT, TMR0_CH, ENABLE);
}

4. 串口中断注册

cpp 复制代码
#define USART_RX_ERR_IRQn               (INT005_IRQn)
#define USART_RX_ERR_INT_SRC            (INT_SRC_USART2_EI)
#define USART_RX_FULL_IRQn              (INT006_IRQn)
#define USART_RX_FULL_INT_SRC           (INT_SRC_USART2_RI)
#define USART1_RX_TIMEOUT_IRQn          (INT007_IRQn)
#define USART1_RX_TIMEOUT_INT_SRC       (INT_SRC_USART2_RTO)

static void USART_RxFull_IrqCallback(void);
static void USART_RxError_IrqCallback(void);
static void USART_RxTimeout_IrqCallback(void);

static void RegisterIrq(void)
{
	stc_irq_signin_config_t stcIrqSigninConfig;
	
	/* Register RX full IRQ handler. */
    stcIrqSigninConfig.enIRQn = USART_RX_FULL_IRQn;
    stcIrqSigninConfig.enIntSrc = USART_RX_FULL_INT_SRC;
    stcIrqSigninConfig.pfnCallback = &USART_RxFull_IrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSigninConfig);
    NVIC_ClearPendingIRQ(stcIrqSigninConfig.enIRQn);
    NVIC_SetPriority(stcIrqSigninConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
    NVIC_EnableIRQ(stcIrqSigninConfig.enIRQn);

    /* Register RX error IRQ handler. */
    stcIrqSigninConfig.enIRQn = USART_RX_ERR_IRQn;
    stcIrqSigninConfig.enIntSrc = USART_RX_ERR_INT_SRC;
    stcIrqSigninConfig.pfnCallback = &USART_RxError_IrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSigninConfig);
    NVIC_ClearPendingIRQ(stcIrqSigninConfig.enIRQn);
    NVIC_SetPriority(stcIrqSigninConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
    NVIC_EnableIRQ(stcIrqSigninConfig.enIRQn);
	
	/* Register RX timeout IRQ handler. */
    stcIrqSigninConfig.enIRQn = USART1_RX_TIMEOUT_IRQn;
    stcIrqSigninConfig.enIntSrc = USART1_RX_TIMEOUT_INT_SRC;
    stcIrqSigninConfig.pfnCallback = &USART_RxTimeout_IrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSigninConfig);
    NVIC_ClearPendingIRQ(stcIrqSigninConfig.enIRQn);
    NVIC_SetPriority(stcIrqSigninConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
    NVIC_EnableIRQ(stcIrqSigninConfig.enIRQn);
}

5. 串口初始化

LL_PERIPH_WE() 为打开相应寄存器的写使能

LL_PERIPH_WP() 为关闭相应寄存器的写使能

cpp 复制代码
void UART_Init(void)
{
    LL_PERIPH_WE(LL_PERIPH_ALL);
	
	UartGpioConfig();
	
	UartBaudConfig();
	
	TMR0_Config();
	
	RegisterIrq();
	
    LL_PERIPH_WP(LL_PERIPH_ALL);
	
	USART_FuncCmd(USART_UNIT, ( USART_TX | USART_RX | USART_INT_RX | USART_RX_TIMEOUT | USART_INT_RX_TIMEOUT), ENABLE);
}

6. 串口数据接收处理

USART_RxFull_IrqCallback 中断 只负责接收和缓存单字节数据,以及递增数据长度 (通过读数据清除标志位)

USART_RxTimeout_IrqCallback 中断 处理超时中断(超时中断触发后必须要关闭定时器,和清除标志位)

USART_RxError_IrqCallback 中断 处理异常错误

cpp 复制代码
static void USART_RxFull_IrqCallback(void)
{
	uint8_t u8Data = (uint8_t)USART_ReadData(USART_UNIT);
	if(gps_len < RX_FRAME_LEN_MAX)
		gps_buf[gps_len++] = u8Data;
}

static void USART_RxError_IrqCallback(void)
{
	(void)USART_ReadData(USART_UNIT);

    USART_ClearStatus(USART_UNIT, (USART_FLAG_PARITY_ERR | USART_FLAG_FRAME_ERR | USART_FLAG_OVERRUN));
}

static void USART_RxTimeout_IrqCallback(void)
{
	TMR0_Stop(TMR0_UNIT, TMR0_CH);

    USART_ClearStatus(USART_UNIT, USART_FLAG_RX_TIMEOUT);
	
	GpsRxCallBack(gps_buf, gps_len);
	gps_len = 0;
}

对于GPS、Wifi这类不频繁的数据交互,用到超时中断和接收寄存器满就可以处理数据了。

对于4G/蓝牙等有持续大量数据交互的模块,就需要用到DMA了。

7. 串口数据发送

cpp 复制代码
static int32_t UartSendByte(CM_USART_TypeDef *UART, uint8_t ch)
{
	uint32_t u32TxEmpty = 0UL;
    __IO uint32_t u32TmpCount = 0UL;
	uint32_t u32Timeout = HCLK_VALUE/USART_BAUDRATE;
	int32_t i32Ret = LL_ERR_INVD_PARAM;
	
	/* Wait TX data register empty */
	while ((u32TmpCount <= u32Timeout) && (0UL == u32TxEmpty)) {
		u32TxEmpty = READ_REG32_BIT(UART->SR, USART_SR_TXE);
		u32TmpCount++;
	}

	if (0UL != u32TxEmpty) {
		WRITE_REG16(UART->TDR, ch);
		i32Ret = LL_OK;
	} else {
		i32Ret = LL_ERR_TIMEOUT;
	}
		
	return i32Ret;
}

8. DMA接收配置和处理

DMA配置方式与STM32基本相同:

1)设置DMA源地址和源地址增长类型,设置目的地址和目的地址增长类型

DMA接收属于串口数据寄存器到内存,即源地址固定,目的地址递增

DMA发送属于内存到串口数据寄存器,即源地址递增,目的地址固定

2)设置位宽、传输大小、传输块数


所不同的是,HC32有一个可配置的自动运行系统AOS。

可以配置AOS源和AOS目标.

AOS源可以是DMA传输完成、UART接收数据、定时器上溢和下溢、event电平变化等等。

AOS目标可以是DMA传输、定时器计数、ADC模数转换、event事件等等

如下是DMA初始化配置的参考代码,配置了串口DMA接收和串口DMA发送:

cpp 复制代码
//DMA单元
#define RX_DMA_UNIT                     (CM_DMA1)
//DMA通道号·通道号越小优先级越高
#define RX_DMA_CH                       (DMA_CH0)
//DMA单元时钟
#define RX_DMA_FCG_ENABLE()             (FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_DMA1, ENABLE))
//AOS系统的目标·触发DMA1通道0传输
#define RX_DMA_TRIG_SEL                 (AOS_DMA1_0)
//AOS系统的触发源·接收数据寄存器满中断
#define RX_DMA_TRIG_EVT_SRC             (EVT_SRC_USART1_RI)
//AOS系统的目标·DMA完成中断
#define RX_DMA_RECONF_TRIG_SEL          (AOS_DMA_RC)
//AOS系统的触发源·AOS_STRG中断源
#define RX_DMA_RECONF_TRIG_EVT_SRC      (EVT_SRC_AOS_STRG)
//DMA传输完成中断·通道号1
#define RX_DMA_TC_INT                   (DMA_INT_TC_CH0)
//DMA传输完成标志·通道号2
#define RX_DMA_TC_FLAG                  (DMA_FLAG_TC_CH0)
//DMA传输完成中断号
#define RX_DMA_TC_IRQn                  (INT000_IRQn)
//DMA传输完成中断源
#define RX_DMA_TC_INT_SRC               (INT_SRC_DMA1_TC0)

//串口DMA发送配置
#define TX_DMA_UNIT                     (CM_DMA2)
#define TX_DMA_CH                       (DMA_CH0)
#define TX_DMA_FCG_ENABLE()             (FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_DMA2, ENABLE))
#define TX_DMA_TRIG_SEL                 (AOS_DMA2_0)
#define TX_DMA_TRIG_EVT_SRC             (EVT_SRC_USART1_TI)
#define TX_DMA_TC_INT                   (DMA_INT_TC_CH0)
#define TX_DMA_TC_FLAG                  (DMA_FLAG_TC_CH0)
#define TX_DMA_TC_IRQn                  (INT001_IRQn)
#define TX_DMA_TC_INT_SRC               (INT_SRC_DMA2_TC0)

/*******************************************************************************
 * Local variable definitions ('static')
 ******************************************************************************/
static __IO en_flag_status_t m_enTxEnd = SET;
static uint8_t m_4gRxBuf[RX_FRAME_LEN_MAX];
static uint8_t *m_auTxBuf = NULL;

/*******************************************************************************
 * Local function definitions ('static')
 ******************************************************************************/
static void RX_DMA_TC_IrqCallback(void);
static void TX_DMA_TC_IrqCallback(void);

/*
 * DMA_Config()
 * 配置串口DMA接收和DMA发送
*/
static int32_t DMA_Config(void)
{
    int32_t i32Ret;
    stc_dma_init_t stcDmaInit;
    stc_dma_llp_init_t stcDmaLlpInit;
    stc_irq_signin_config_t stcIrqSignConfig;
    static stc_dma_llp_descriptor_t stcLlpDesc;

    //使能DMA和FCG时钟
    RX_DMA_FCG_ENABLE();
    TX_DMA_FCG_ENABLE();
    FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_AOS, ENABLE);

    /* USART_RX_DMA */
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn = DMA_INT_ENABLE;//DMA interrupt enable
    stcDmaInit.u32BlockSize = 1UL;//DMA block size
    stcDmaInit.u32TransCount = ARRAY_SZ(m_4gRxBuf);//DMAbuf大小
    stcDmaInit.u32DataWidth = DMA_DATAWIDTH_8BIT;//DMAbuf位宽
    stcDmaInit.u32DestAddr = (uint32_t)m_4gRxBuf;//DMAbuf地址
    stcDmaInit.u32SrcAddr = (uint32_t)(&USART_UNIT->RDR);//由外设到内存的 外设地址 -> 串口数据寄存器
    stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_FIX;//由外设到内存的 源地址模式 固定
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_INC;//由外设到内存的 目标地址模式 自动递增
    i32Ret = DMA_Init(RX_DMA_UNIT, RX_DMA_CH, &stcDmaInit);
    if (LL_OK == i32Ret) 
	{
        (void)DMA_LlpStructInit(&stcDmaLlpInit);
        stcDmaLlpInit.u32State = DMA_LLP_ENABLE;
        stcDmaLlpInit.u32Mode  = DMA_LLP_WAIT;
        stcDmaLlpInit.u32Addr  = (uint32_t)&stcLlpDesc;
        (void)DMA_LlpInit(RX_DMA_UNIT, RX_DMA_CH, &stcDmaLlpInit);//初始化DMA链表指针

        stcLlpDesc.SARx   = stcDmaInit.u32SrcAddr;
        stcLlpDesc.DARx   = stcDmaInit.u32DestAddr;
        stcLlpDesc.DTCTLx = (stcDmaInit.u32TransCount << DMA_DTCTL_CNT_POS) | (stcDmaInit.u32BlockSize << DMA_DTCTL_BLKSIZE_POS);;
        stcLlpDesc.LLPx   = (uint32_t)&stcLlpDesc;
        stcLlpDesc.CHCTLx = stcDmaInit.u32SrcAddrInc | stcDmaInit.u32DestAddrInc | stcDmaInit.u32DataWidth |  \
                            stcDmaInit.u32IntEn      | stcDmaLlpInit.u32State    | stcDmaLlpInit.u32Mode;

        DMA_ReconfigLlpCmd(RX_DMA_UNIT, RX_DMA_CH, ENABLE);
        DMA_ReconfigCmd(RX_DMA_UNIT, ENABLE);
        AOS_SetTriggerEventSrc(RX_DMA_RECONF_TRIG_SEL, RX_DMA_RECONF_TRIG_EVT_SRC);

        stcIrqSignConfig.enIntSrc = RX_DMA_TC_INT_SRC;
        stcIrqSignConfig.enIRQn  = RX_DMA_TC_IRQn;
        stcIrqSignConfig.pfnCallback = &RX_DMA_TC_IrqCallback;
        (void)INTC_IrqSignIn(&stcIrqSignConfig);
        NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
        NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
        NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);

        AOS_SetTriggerEventSrc(RX_DMA_TRIG_SEL, RX_DMA_TRIG_EVT_SRC);

        DMA_Cmd(RX_DMA_UNIT, ENABLE);
        DMA_TransCompleteIntCmd(RX_DMA_UNIT, RX_DMA_TC_INT, ENABLE);
        (void)DMA_ChCmd(RX_DMA_UNIT, RX_DMA_CH, ENABLE);
    }
	
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn = DMA_INT_ENABLE;
    stcDmaInit.u32BlockSize = 1UL;
    stcDmaInit.u32TransCount = ARRAY_SZ(m_4gRxBuf);
    stcDmaInit.u32DataWidth = DMA_DATAWIDTH_8BIT;
    stcDmaInit.u32DestAddr = (uint32_t)(&USART_UNIT->TDR);
    stcDmaInit.u32SrcAddr = (uint32_t)m_4gRxBuf;
    stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_INC;
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;
    i32Ret = DMA_Init(TX_DMA_UNIT, TX_DMA_CH, &stcDmaInit);
    if (LL_OK == i32Ret)
	{
        stcIrqSignConfig.enIntSrc = TX_DMA_TC_INT_SRC;
        stcIrqSignConfig.enIRQn  = TX_DMA_TC_IRQn;
        stcIrqSignConfig.pfnCallback = &TX_DMA_TC_IrqCallback;
        (void)INTC_IrqSignIn(&stcIrqSignConfig);
        NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
        NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
        NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);

        AOS_SetTriggerEventSrc(TX_DMA_TRIG_SEL, TX_DMA_TRIG_EVT_SRC);

        DMA_Cmd(TX_DMA_UNIT, ENABLE);
        DMA_TransCompleteIntCmd(TX_DMA_UNIT, TX_DMA_TC_INT, ENABLE);
    }
	
    return i32Ret;
}

如下是DMA接收处理代码:

1)USART_RxTimeout_IrqCallback

重启AOS系统

关闭串口超时定时器,清除串口超时标志位。

处理DMA接收数据。

2)RX_DMA_TC_IrqCallback

即接收完成中断

硬件上需要清除中断标志位

3)USART_TxComplete_IrqCallback

即串口发送完成中断

硬件上需要清除标志位,一般要失能发送中断

cpp 复制代码
//串口接收超时中断
static void USART_RxTimeout_IrqCallback(void)
{
    uint16_t unLen = RX_FRAME_LEN_MAX - (uint16_t)DMA_GetTransCount(RX_DMA_UNIT, RX_DMA_CH);
	
    AOS_SW_Trigger();

    TMR0_Stop(TMR0_UNIT, TMR0_CH);

    USART_ClearStatus(USART_UNIT, USART_FLAG_RX_TIMEOUT);
	
	if(unLen != 0 && unLen != RX_FRAME_LEN_MAX)
		DtuRxCallBack(m_4gRxBuf, unLen);
}

//串口DMA接收·传输完成中断
static void RX_DMA_TC_IrqCallback(void)
{
	DtuRxCallBack(m_4gRxBuf, RX_FRAME_LEN_MAX);
	
    DMA_ClearTransCompleteStatus(RX_DMA_UNIT, RX_DMA_TC_FLAG);
}

//串口发送完成中断
static void USART_TxComplete_IrqCallback(void)
{
	m_enTxEnd = SET;
	DtuTxCallBack(m_auTxBuf);
	
	USART_FuncCmd(USART_UNIT, (USART_TX | USART_INT_TX_CPLT), DISABLE);
	
    USART_ClearStatus(USART_UNIT, USART_FLAG_TX_CPLT);
}

//串口接收错误中断
static void USART_RxError_IrqCallback(void)
{
    (void)USART_ReadData(USART_UNIT);

    USART_ClearStatus(USART_UNIT, (USART_FLAG_PARITY_ERR | USART_FLAG_FRAME_ERR | USART_FLAG_OVERRUN));
}

//串口DMA发送·传输完成中断
static void TX_DMA_TC_IrqCallback(void)
{
    USART_FuncCmd(USART_UNIT, USART_INT_TX_CPLT, ENABLE);

    DMA_ClearTransCompleteStatus(TX_DMA_UNIT, TX_DMA_TC_FLAG);
}

9. DMA发送

cpp 复制代码
//串口DMA发送
void DtuDMASend(uint8_t *pBuf, uint16_t u16TxLen)
{
#if 0 //串口发送数据打印
	printf("Tx: ");
	for(uint16_t i = 0; i < u16TxLen; i++)
	{
		printf("%02X",pBuf[i]);
	}
	printf("\r\n");
#endif
	
	//等待上一包发完
	while(m_enTxEnd == RESET);
	m_enTxEnd = RESET;
	m_auTxBuf = pBuf;
	
	//启动DMA传输
	DMA_SetSrcAddr(TX_DMA_UNIT, TX_DMA_CH, (uint32_t)pBuf);
	DMA_SetTransCount(TX_DMA_UNIT, TX_DMA_CH, u16TxLen);
	(void)DMA_ChCmd(TX_DMA_UNIT, TX_DMA_CH, ENABLE);
	USART_FuncCmd(USART_UNIT, USART_TX, ENABLE);
}
相关推荐
智者知已应修善业2 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
智商偏低8 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen9 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森11 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白11 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D12 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术15 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt15 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘16 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang16 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c