5.21 USART 使用说明
本章说明 N32G41x 标准外设库中 USART 驱动的文件组成与 38 个公开函数,涵盖波特率与帧格式配置、数据收发、多处理器通信、LIN、SmartCard、IrDA、RS485 驱动使能、收发超时、低功耗唤醒、DMA 与中断标志管理;当前 SDK 提供了 19 个 USART 示例工程。各工作模式的寄存器位域与配置顺序请配合用户手册的“通用同步异步收发器(USART)”章节阅读,芯片已知问题以勘误手册为准。
5.21.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/n32g41x_std_periph_driver/inc/n32g41x_usart.h |
定义 USART_InitType 与 USART_ClockInitType 结构体及其字长、停止位、校验、模式、流控、过采样等配置宏,并按功能分区声明 38 个公开函数 |
firmware/n32g41x_std_periph_driver/src/n32g41x_usart.c |
实现上述 38 个函数:读改写 USART 寄存器的帧格式、使能、保护时间、超时、中断与标志位,覆盖查询收发、DMA、同步、LIN、SmartCard、IrDA、RS485 与低功耗唤醒 |
projects/n32g41x_EVAL/examples/USART/ |
包含 19 个示例:485_DMA、485_Master、485_Slave、DMA_Interrupt、DMA_Polling_MaxBaudrate、HalfDuplex、HardwareFlowCtrl、Interrupt、LP_Transmit_Receive、Transmit_Receive、LIN_Master、LIN_Slave、MultiProcessor、Polling、Printf、SmartCard、Synchronous、WakeUpFromSleep、WakeUpFromStop |
projects/n32g41x_EVAL/examples/USART/Polling/ |
演示查询标志位方式双机通信的示例工程,含 MDK-ARM/、EWARM/、inc/main.h、inc/n32g41x_it.h、src/main.c、src/n32g41x_it.c、readme.txt |
projects/n32g41x_EVAL/bsp/inc/n32g41x_stb.h |
定义评估板 LED1~LED3 与按键 KEY1~KEY3、WKUP 的端口、引脚与有效电平宏,供部分 USART 示例引用 |
5.21.2 API 参考
以下 38 个公开函数按 n32g41x_usart.h 的声明顺序分为 13 组;每个条目给出功能摘要与 n32g41x_usart.c 中该函数正上方的注释原文。
复位与初始化
USART_DeInit、USART_Init、USART_StructInit、USART_ClockInit、USART_ClockStructInit、USART_Enable
数据传输
USART_SendData、USART_ReceiveData、USART_SendBreak
多处理器通信
USART_SetAddr、USART_ConfigWakeUpMode、USART_EnableRcvWakeUp
LIN 模式
USART_ConfigLINBreakDetectLength、USART_EnableLIN
SmartCard 模式
USART_SetGuardTime、USART_SetPrescaler、USART_EnableSmartCard、USART_SetSmartCardNACK
半双工模式
USART_EnableHalfDuplex
IrDA 模式
USART_ConfigIrDAMode、USART_EnableIrDA
RS485 驱动使能
USART_CfgDriverAssertTime、USART_CfgDriverdeassertTime、USART_DriverPolaritySet、USART_DriverModeSet
收发超时
USART_RTOSet、USART_CfgRTOWidth
其它配置
USART_FirstByteDelayFixSet、USART_PinSwapSet
低功耗唤醒
USART_EnableWakeUp、USART_CfgWakeUpDataClr
DMA
USART_EnableDMA
中断与标志管理
USART_ConfigInt、USART_GetFlagStatus、USART_ClrFlag、USART_ClrRTOFlag、USART_GetIntStatus、USART_ClrIntPendingBit
void USART_DeInit(const USART_Module *USARTx)将 USARTx 外设寄存器恢复为复位默认值。
name USART_DeInit
fun Deinitializes the USARTx peripheral registers to their default reset values.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
return none
void USART_Init(USART_Module *USARTx, const USART_InitType *USART_InitStruct)按 USART_InitStruct 初始化 USARTx 外设。
name USART_Init
fun Initializes the USARTx peripheral according to USART_InitStruct.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_InitStruct (The input parameters must be the following values):
- BaudRate :
- (((BaudRate) > 0) && ((BaudRate) < 0x00989680)) (0-10 Mbit/s)
- WordLength
- USART_WL_8B
- USART_WL_9B
- StopBits
- USART_STPB_1
- USART_STPB_0_5
- USART_STPB_2
- USART_STPB_1_5
- Parity
- USART_PE_NO
- USART_PE_EVEN
- USART_PE_ODD
- Mode
- USART_MODE_RX
- USART_MODE_TX
- HardwareFlowControl
- USART_HFCTRL_NONE
- USART_HFCTRL_RTS
- USART_HFCTRL_CTS
- USART_HFCTRL_RTS_CTS
- OverSampling
- USART_8OVER
- USART_16OVER
return none
void USART_StructInit(USART_InitType *USART_InitStruct)用默认值填充 USART_InitStruct 的各个成员。
name USART_StructInit.
fun Fills each USART_InitStruct member with its default value.
param USART_InitStruct (The input parameters must be the following values):
- BaudRate
- WordLength
- StopBits
- Parity
- Mode
- HardwareFlowControl
- OverSampling
return none
void USART_ClockInit(USART_Module *USARTx, const USART_ClockInitType *USART_ClockInitStruct)按 USART_ClockInitStruct 中指定的参数初始化 USARTx 外设时钟。
name USART_ClockInit
fun Initializes the USARTx peripheral Clock according to the specified parameters in the USART_ClockInitStruct.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_ClockInitStruct (The input parameters must be the following values):
- Clock :
- USART_CLK_DISABLE
- USART_CLK_ENABLE
- Polarity :
- USART_CLKPOL_LOW
- USART_CLKPOL_HIGH
- Phase :
- USART_CLKPHA_1EDGE
- USART_CLKPHA_2EDGE
- LastBit :
- USART_CLKLB_DISABLE
- USART_CLKLB_ENABLE
return none
note Polarity, Phase and LastBit can be configured only when both the transmitter and
the receiver are disabled (USART_CTRL1.TXEN=0 and USART_CTRL1.RXEN=0).
void USART_ClockStructInit(USART_ClockInitType *USART_ClockInitStruct)用默认值填充 USART_ClockInitStruct 的各个成员。
name USART_ClockStructInit
fun Fills each USART_ClockInitStruct member with its default value.
param USART_ClockInitStruct (The output structure to be filled with the following values):
- Clock : USART_CLK_DISABLE
- Polarity : USART_CLKPOL_LOW
- Phase : USART_CLKPHA_1EDGE
- LastBit : USART_CLKLB_DISABLE
return none
void USART_Enable(USART_Module *USARTx, FunctionalState Cmd)使能或禁用指定的 USART 外设。
name USART_Enable
fun Enables or disables the specified USART peripheral.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_SendData(USART_Module *USARTx, uint32_t Data)通过 USARTx 外设发送单个数据。
name USART_SendData
fun Transmits single data through the USARTx peripheral.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Data :
- the data to transmit
return none
uint32_t USART_ReceiveData(const USART_Module *USARTx)返回 USARTx 外设最近接收到的数据。
name USART_ReceiveData
fun Returns the most recent received data by the USARTx peripheral.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
return USARTx->DAT value
void USART_SendBreak(USART_Module *USARTx)发送断开帧字符。
name USART_SendBreak
fun Transmits break characters.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
return none
void USART_SetAddr(USART_Module *USARTx, uint8_t USART_Addr)设置 USART 节点的地址。
name USART_SetAddr
fun Sets the address of the USART node.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_Addr (The input parameters must be the following values):
- (((USART_Addr)>=0x00)&&((USART_Addr)<0x10))
return none
void USART_ConfigWakeUpMode(USART_Module *USARTx, uint32_t USART_WakeUpMode)选择 USART 的唤醒方式。
name USART_ConfigWakeUpMode
fun Selects the USART WakeUp method.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_WakeUpMode (The input parameters must be the following values):
- USART_WUM_IDLELINE WakeUp by an idle line detection
- USART_WUM_ADDRMASK WakeUp by an address mark
return none
void USART_EnableRcvWakeUp(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 USART 的静默模式(接收唤醒)。
name USART_EnableRcvWakeUp
fun Enables or disables the USART mute mode (receiver wake-up).
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_ConfigLINBreakDetectLength(USART_Module *USARTx, uint32_t USART_LINBreakDetectLength)设置 USART 的 LIN 断开检测长度。
name USART_ConfigLINBreakDetectLength
fun Sets the USART LIN Break detection length.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_LINBreakDetectLength (The input parameters must be the following values):
- USART_LINBDL_10B 10-bit break detection
- USART_LINBDL_11B 11-bit break detection
return none
void USART_EnableLIN(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 USART 的 LIN 模式。
name USART_EnableLIN
fun Enables or disables the USART's LIN mode.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_SetGuardTime(USART_Module *USARTx, uint8_t USART_GuardTime)设置 USART 的保护时间。
name USART_SetGuardTime
fun Sets the specifies USART guard time.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_GuardTime:
- specifies the guard time
return none
void USART_SetPrescaler(USART_Module *USARTx, uint8_t USART_Prescaler)设置系统时钟预分频系数。
name USART_SetPrescaler
fun Sets the system clock prescaler.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_Prescaler:
- specifies the prescaler clock
return none
void USART_EnableSmartCard(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 USART 的智能卡模式。
name USART_EnableSmartCard
fun Enables or disables the USART's Smart Card mode.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_SetSmartCardNACK(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 NACK 发送。
name USART_SetSmartCardNACK
fun Enables or disables NACK transmission.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_EnableHalfDuplex(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 USART 的半双工通信。
name USART_EnableHalfDuplex
fun Enables or disables the USART's Half Duplex communication.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_ConfigIrDAMode(USART_Module *USARTx, uint32_t USART_IrDAMode)配置 USART 的 IrDA 接口。
name USART_ConfigIrDAMode
fun Configures the USART's IrDA interface.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_IrDAMode (The input parameters must be the following values):
- USART_IRDAMODE_LOWPPWER
- USART_IRDAMODE_NORMAL
return none
void USART_EnableIrDA(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 USART 的 IrDA 接口。
name USART_EnableIrDA
fun Enables or disables the USART's IrDA interface.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_CfgDriverAssertTime(USART_Module *USARTx, uint32_t Time)设置驱动使能信号的置位时间。
name USART_CfgDriverAssertTime
fun Set Driver Enable assertion time.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Time:(The input parameters must be the following values)
- 0x01 ~ 0x1F
return none
void USART_CfgDriverdeassertTime(USART_Module *USARTx, uint32_t Time)设置驱动使能信号的复位时间。
name USART_CfgDriverdeassertTime
fun Set Driver Enable deassertion time.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Time:(The input parameters must be the following values)
- 0x01 ~ 0x1F
return none
void USART_DriverPolaritySet(USART_Module *USARTx, FunctionalState Cmd)选择驱动使能信号的极性。
name USART_DriverPolaritySet
fun Driver enable polarity selection.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE
- DISABLE
return none
void USART_DriverModeSet(USART_Module *USARTx, FunctionalState Cmd)选择驱动使能模式。
name USART_DriverModeSet
fun Driver enable mode selection.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE
- DISABLE
return none
void USART_RTOSet(USART_Module *USARTx, FunctionalState Cmd)使能或禁用接收超时。
name USART_RTOSet
fun Receiver timeout enable or disable.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE
- DISABLE
return none
void USART_CfgRTOWidth(USART_Module *USARTx, uint32_t Width)配置接收超时的宽度。
name USART_CfgRTOWidth
fun configure receive timeout width.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Width:(The input parameters must be the following values)
- 0x00000000 ~ 0x0FFFFFFF
return none
void USART_FirstByteDelayFixSet(USART_Module *USARTx, FunctionalState Cmd)使能或禁用首字节延时固定。
name USART_FirstByteDelayFixSet
fun First byte delay fixed enable or disable.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE : the first byte delay is fixed (USART_CTRL1.TEDEN = 0)
- DISABLE : the first byte delay is not fixed (USART_CTRL1.TEDEN = 1)
return none
void USART_PinSwapSet(USART_Module *USARTx, FunctionalState Cmd)使能或禁用 TX/RX 引脚交换。
name USART_PinSwapSet
fun Swap TX/RX pins enable or disable.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE
- DISABLE
return none
void USART_EnableWakeUp(USART_Module *USARTx, FunctionalState Cmd)配置 USART 在低功耗模式下接收到一个字节时唤醒系统。
name USART_EnableWakeUp
fun configure when USART in lowpower mode, receive a byte will wakeup system.
param USARTx (The input parameters must be the following values):
- USART1
- USART4
param Cmd:(The input parameters must be the following values)
- ENABLE
- DISABLE
return none
void USART_CfgWakeUpDataClr(USART_Module *USARTx, uint32_t WakeupMode)配置 USART 从低功耗模式唤醒时是否清除唤醒数据。
name USART_CfgWakeUpDataClr
fun configure when USART wakeup from low power mode, can select whether clear wakeup data.
param USARTx (The input parameters must be the following values):
- USART1
- USART4
param WakeupMode:(The input parameters must be the following values)
- USART_WAKEUP_CLRDATA
- USART_WAKEUP_NOTCLRDATA
return none
void USART_EnableDMA(USART_Module *USARTx, uint32_t USART_DMAReq, FunctionalState Cmd)使能或禁用 USART 的 DMA 接口。
name USART_EnableDMA
fun Enables or disables the USART's DMA interface.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_DMAReq (The input parameters must be the following values):
- USART_DMAREQ_TX USART DMA transmit request
- USART_DMAREQ_RX USART DMA receive request
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
void USART_ConfigInt(USART_Module *USARTx, uint32_t USART_INT, FunctionalState Cmd)使能或禁用指定的 USART 中断。
name USART_ConfigInt
fun Enables or disables the specified USART interrupts.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_INT (The input parameters must be the following values):
- USART_INT_PEF Parity Error interrupt
- USART_INT_TXC Transmission complete interrupt
- USART_INT_TXDE Transmit Data Register empty interrupt
- USART_INT_RXDNE Receive Data register not empty interrupt
- USART_INT_IDLEF Idle line detection interrupt
- USART_INT_RTOE Receiver timeout interrupt
- USART_INT_LINBD LIN Break detection interrupt
- USART_INT_ERRF Error interrupt(Frame error, noise error, overrun error)
- USART_INT_CTSF CTS change interrupt
param Cmd (The input parameters must be the following values):
- ENABLE
- DISABLE
return none
FlagStatus USART_GetFlagStatus(const USART_Module *USARTx, uint32_t USART_FLAG)检查指定的 USART 标志是否置位。
name USART_GetFlagStatus
fun Checks whether the specified USART flag is set or not.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_FLAG (The input parameters must be the following values):
- USART_FLAG_RTO receiver timeout Flag
- USART_FLAG_FEF Framing error Flag
- USART_FLAG_NEF Noise error flag Flag
- USART_FLAG_OREF Overrun error
- USART_FLAG_PEF Parity error
- USART_FLAG_LINBD LIN break detection flag
- USART_FLAG_CTSF CTS Change flag
- USART_FLAG_RXDNE Receive data register not empty flag
- USART_FLAG_TXC Transmission Complete flag
- USART_FLAG_TXDE Transmit data register empty flag
- USART_FLAG_IDLEF Idle Line detection flag
return SET or RESET
- SET : the specified flag is set
- RESET : the specified flag is reset
void USART_ClrFlag(USART_Module *USARTx, uint32_t USART_FLAG)清除 USARTx 的待处理标志。
name USART_ClrFlag
fun Clears the USARTx's pending flags.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_FLAG (The input parameters must be the following values):
- USART_FLAG_LINBD LIN break detection flag
- USART_FLAG_CTSF CTS Change flag
- USART_FLAG_RXDNE Receive data register not empty flag
- USART_FLAG_TXC Transmission Complete flag
return none
note
- PE (Parity error), FE (Framing error), NE (Noise error), ORE (OverRun
error) and IDLE (Idle line detected) flags are cleared by software
sequence: a read operation to USART_STS register (USART_GetFlagStatus())
followed by a read operation to USART_DAT register (USART_ReceiveData()).
- RXNE flag can be also cleared by a read to the USART_DAT register
(USART_ReceiveData()).
- TC flag can be also cleared by software sequence: a read operation to
USART_STS register (USART_GetFlagStatus()) followed by a write operation
to USART_DAT register (USART_SendData()).
- TXE flag is cleared only by a write to the USART_DAT register
(USART_SendData()).
- RTO flag is cleared only by a write 1 to the USART_CTRL2.RTOCF bit.
(USART_ClrRTOFlag()).
void USART_ClrRTOFlag(USART_Module *USARTx)清除 USARTx 的接收超时待处理标志。
name USART_ClrRTOFlag
fun Clears the USARTx's RTO pending flag.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
return none
INTStatus USART_GetIntStatus(const USART_Module *USARTx, uint32_t USART_INT)检查指定的 USART 中断是否置位。
name USART_GetIntStatus
fun Checks whether the specified USART interrupt has set or not.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_INT (The input parameters must be the following values):
- USART_INT_PEF Parity Error interrupt
- USART_INT_TXC Transmission complete interrupt
- USART_INT_TXDE Transmit Data Register empty interrupt
- USART_INT_RXDNE Receive Data register not empty interrupt
- USART_INT_IDLEF Idle line detection interrupt
- USART_INT_RTOE Receiver timeout interrupt
- USART_INT_LINBD LIN Break detection interrupt
- USART_INT_ERRF Error interrupt(Frame error, noise error, overrun error)
- USART_INT_CTSF CTS change interrupt
return SET or RESET
- SET : the interrupt enable bit is set (the interrupt is enabled)
- RESET : the interrupt enable bit is not set (the interrupt is disabled)
void USART_ClrIntPendingBit(USART_Module *USARTx, uint32_t USART_INT)清除 USARTx 的中断待处理位。
name USART_ClrIntPendingBit
fun Clears the USARTx's interrupt pending bits.
param USARTx (The input parameters must be the following values):
- USART1
- USART2
- USART3
- USART4
param USART_INT (The input parameters must be the following values):
- USART_INT_LINBD LIN break detection interrupt
- USART_INT_CTSF CTS Change interrupt
- USART_INT_RXDNE Receive data register not empty interrupt
- USART_INT_TXC Transmission Complete interrupt
return none
note
- Only the four flags that USART_STS defines as rc_w0 can be cleared by a write:
LINBDF, CTSF, RXDNE and TXC. The other pending bits are read-only and are
cleared as follows:
- PE (Parity error), FE (Framing error), NE (Noise error), ORE (OverRun
error) and IDLE (Idle line detected) pending bits are cleared by
software sequence: a read operation to USART_STS register (USART_GetFlagStatus())
followed by a read operation to USART_DAT register (USART_ReceiveData()).
- RXNE pending bit can be also cleared by a read to the USART_DAT register (USART_ReceiveData()).
- TC pending bit can be also cleared by software sequence: a read
operation to USART_STS register (USART_GetFlagStatus()) followed by a write
operation to USART_DAT register (USART_SendData()).
- TXE pending bit is cleared only by a write to the USART_DAT register
(USART_SendData()).
- RTO flag is cleared only by a write 1 to the USART_CTRL2.RTOCF bit.
(USART_ClrRTOFlag()).
使用提示:n32g41x_usart.h 第 341、342 行以 USART_Module *USARTx 与 USART_Module* USARTx 两种写法重复声明了本函数,n32g41x_usart.c 中只有一份实现。
5.21.3 Demo 示例说明
本节说明 USART 的 19 个示例,覆盖轮询、中断、DMA、RS485、单线半双工、硬件流控、IrDA 红外、LIN、多处理器、智能卡、同步与低功耗唤醒等收发场景。
5.21.3.1 485_DMA RS485 DMA 收发
工程目录: projects/n32g41x_EVAL/examples/USART/485_DMA/
使能 DMA、GPIO 与两路 USART 时钟→配置 USART1 的 TX(PA9) 与 DE(PA12)、USART2 的 RX(PA3) 与 DE(PA1) 为复用推挽→配置一个发送通道与一个接收通道→使能两路 USART 的 RS485 DE 信号自动控制→使能发送与接收 DMA 请求并启动通道→等待两个 DMA 通道传输完成→比较收发缓冲并写入 TransferStatus2
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)之间通过 DMA 完成一次 RS485 通信:USART1 用 DMA 发送 TxBuffer1,USART2 用 DMA 接收进 RxBuffer2,最后比较两者是否一致。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、DMA_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的引脚、DMA 通道与重映射宏;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9、Rx PA10、DE PA12),USARTz 为 USART2(Tx PA2、Rx PA3、DE PA1); 除 N32G412 的 PA9 复用为 GPIO_AF4、N32G415 为 GPIO_AF0 外,其余引脚均为 GPIO_AF4 复用推挽上拉。波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样,收发均使能; 两路的 USART_DriverModeSet 使能、USART_DriverPolaritySet 设为 DISABLE(DE 低有效)。DMA 上 USART1_TX 用 DMA_CH1、USART2_RX 用 DMA_CH4。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 依次调用 RCC_Configuration、GPIO_Configuration、DMA_Configuration,用 USART_StructInit 填默认值后 USART_Init 配置两路,随后配置 RS485 DE、开 DMA 请求与通道、USART_Enable 启动两路,用两个 while 轮询等待发送与接收 DMA 传输完成标志,最后 Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1) 写入 TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_CfgDriverAssertTime、USART_CfgDriverdeassertTime、USART_DriverPolaritySet、USART_DriverModeSet、USART_EnableDMA、USART_Enable、DMA_StructInit、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus |
| 验证方法 | 复位运行后用调试器查看变量 TransferStatus2,为 PASSED 表示 USART2 收到的数据与 USART1 发送的一致,为 FAILED 表示不一致。 |
| 使用提示 | 需先断开开发板 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; 外部需将 USART1_TX(PA9) 与 USART2_RX(PA3) 相连。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1,支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7。 |
核心代码解读与移植
示例把 USART1 的发送和 USART2 的接收都交给 DMA,用 RS485 DE 信号自动控制收发方向,最后比较缓冲区。
数据路径: main → USART_Init 配置 USART1/USART2 → USART_DriverModeSet 使能 RS485 DE → DMA_Init/DMA_RequestRemap 建立发送与接收通道 → DMA 把 TxBuffer1 搬到 USART1 数据寄存器、把 USART2 数据寄存器搬到 RxBuffer2 → Buffercmp 把结果写入 TransferStatus2
1. 使能 RS485 DE 自动控制并开 DMA 请求。 两路都先设置 DE 的起始与禁止时间、极性,再打开驱动模式,让收发器方向由硬件按发送状态自动切换;随后把 USART1 的发送请求和 USART2 的接收请求连到 DMA。
/* Configure USARTy 485 function */
USART_CfgDriverAssertTime(USARTy, 0);
USART_CfgDriverdeassertTime(USARTy, 0);
USART_DriverPolaritySet(USARTy, DISABLE);
USART_DriverModeSet(USARTy, ENABLE);
/* Configure USARTz 485 function */
USART_CfgDriverAssertTime(USARTz, 0);
USART_CfgDriverdeassertTime(USARTz, 0);
USART_DriverPolaritySet(USARTz, DISABLE);
USART_DriverModeSet(USARTz, ENABLE);
/* Enable USARTy DMA TX request */
USART_EnableDMA(USARTy, USART_DMAREQ_TX, ENABLE);
/* Enable USARTz DMA Rx request */
USART_EnableDMA(USARTz, USART_DMAREQ_RX, ENABLE);
移植到其它板卡时要同步核对 DE 所在的引脚、复用号与有效极性;DE 引脚接错则收发方向无法按发送状态翻转。
2. 用 DMA 通道描述发送搬运。 发送通道以外设数据寄存器为目标、以 TxBuffer1 为源,字节宽度、外设地址不增、存储器地址递增,正常模式并给最高优先级,再通过请求重映射把通道绑到 USART1_TX 事件。
/* USARTy_Tx_DMA_Channel (triggered by USARTy Tx event) Config */
DMA_DeInit(USARTy_Tx_DMA_Channel);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = USARTy_DAT_Base;
DMA_InitStructure.MemAddr = (uint32_t)TxBuffer1;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.BufSize = TxBufferSize1;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(USARTy_Tx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(USARTy_Tx_DMA_REMAP, DMA, USARTy_Tx_DMA_Channel, ENABLE);
接收通道复用同一结构体,只改外设地址、存储器地址与方向;改动缓冲或长度时要同时改 BufSize 与 TxBufferSize1。
3. 轮询传输完成并比较结果。 两路启动后主循环各自等待发送与接收通道的传输完成标志,全部置位后再比较 TxBuffer1 与 RxBuffer2,把判定结果写入 TransferStatus2。
/* Wait until USARTy TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTy_Tx_DMA_FLAG, DMA) == RESET)
{
}
/* Wait until USARTz RX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTz_Rx_DMA_FLAG, DMA) == RESET)
{
}
TransferStatus2 = Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1);
/* TransferStatus2 = PASSED, if the data transmitted from USARTy and
received by USARTz are the same */
/* TransferStatus2 = FAILED, if the data transmitted from USARTy and
received by USARTz are different */
两个等待标志对应各自的通道,改动通道号时要同步改标志宏与重映射宏,否则轮询的是未使用的通道。
对应源码: src/main.c、inc/main.h
5.21.3.2 485_Master RS485 主机中断收发
工程目录: projects/n32g41x_EVAL/examples/USART/485/485_Master/
使能 GPIO 与 USART2 时钟并配置 USART2 中断→配置 USART2 的 TX、RX、DE 引脚为复用推挽→配置 USART2 为 RS485 DE 自动控制→使能发送与接收中断→主循环等待对端回传的数据→收齐后比较收发缓冲并打印结果
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示两块开发板之间通过 USART2 的 RS485 模式(DE 信号自动控制)以中断方式通信:Master 用中断发送 TxBuffer2 给 Slave,接收 Slave 回传的数据后比较并打印结果。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Buffercmp;inc/main.h:USART2 的引脚、DE 与中断宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。对端为同目录下的 485_Slave/ 子工程。 |
| 关键配置 | USARTz 为 USART2,Tx 为 PA2、Rx 为 PA3、DE 为 PA1,均为 GPIO_AF4 复用推挽上拉;波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样,收发使能; DE 设为 USART_CfgDriverAssertTime(USARTz, 1)、USART_CfgDriverdeassertTime(USARTz, 0)、USART_DriverPolaritySet(USARTz, DISABLE)、USART_DriverModeSet(USARTz, ENABLE)。NVIC 优先级分组 2, USART2_IRQn 的抢占优先级为 NVIC_PRE_PRIORITY_0、子优先级为 NVIC_SUB_PRIORITY_1。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 调用 log_init、RCC_Configuration、NVIC_Configuration、GPIO_Configuration,配置并 USART_Init USART2 与 RS485 DE,开 USART_INT_TXDE、USART_INT_RXDNE 后 USART_Enable;中断把接收字节写入 RxBuffer2、把 TxBuffer2 逐字节发出,主循环 while (RxCounter2 < RxBufferSize2) 等到收齐后 Buffercmp 比较并打印 OK 或 Fail。 |
| 主要 API | USART_StructInit、USART_Init、USART_CfgDriverAssertTime、USART_CfgDriverdeassertTime、USART_DriverPolaritySet、USART_DriverModeSet、USART_ConfigInt、USART_Enable、USART_GetFlagStatus、USART_GetIntStatus、USART_ReceiveData、USART_SendData |
| 验证方法 | 两块板分别下载 Master 与 Slave 程序,先复位 Slave 再复位 Master,观察串口打印:N32G41x 485 Master test OK 为通过,N32G41x 485 Master test Fail 为异常。 |
| 使用提示 | 需两块评估板与两个 RS485 收发器模块,A-A、B-B 互连,并先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; Slave 需先上电运行,再由 Master 触发。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例让 USART2 在 RS485 模式下用收发中断完成一次主从通信,主循环只等待接收计数。
数据路径: main → USART_Init 配置 USART2 → USART_DriverModeSet 使能 RS485 DE → USART_ConfigInt 开收发中断 → USARTz_IRQHandler 在 TxBuffer2/RxBuffer2 间搬运字节 → 主循环 Buffercmp 比较并打印
1. 开 RS485 DE 与收发中断。 先把 DE 起始时间设为 1 个采样单位、极性低有效并打开驱动模式,再打开发送与接收中断,让收发都由 USARTz_IRQHandler 处理。
/* RS485 Driver Enable configuration */
USART_CfgDriverAssertTime(USARTz, 1);
USART_CfgDriverdeassertTime(USARTz, 0);
USART_DriverPolaritySet(USARTz, DISABLE);
USART_DriverModeSet(USARTz, ENABLE);
/* Enable USARTz Receive and Transmit interrupts */
USART_ConfigInt(USARTz, USART_INT_TXDE, ENABLE);
USART_ConfigInt(USARTz, USART_INT_RXDNE, ENABLE);
移植到其它板卡时要同步核对 DE 引脚与有效极性;USART_CfgDriverAssertTime 在驱动模式下不能配成 0 以下的无效值。
2. 中断里先接收再发送。 中断先判接收标志,把收到的字节写入 RxBuffer2 并在计数到达后关闭接收中断;再判发送标志,把 TxBuffer2 的下一字节写入数据寄存器,发送计数到达后关闭发送中断。
void USARTz_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_RXDNE) != RESET))
{
/* Read one byte from the receive data register */
RxBuffer2[RxCounter2++] = USART_ReceiveData(USARTz);
if (RxCounter2 == NbrOfDataToRead1)
{
/* Disable the USARTz Receive interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, DISABLE);
}
}
if ((USART_GetFlagStatus(USARTz, USART_FLAG_TXDE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_TXDE) != RESET))
{
/* Write one byte to the transmit data register */
USART_SendData(USARTz, TxBuffer2[TxCounter2++]);
if (TxCounter2 == NbrOfDataToTransfer2)
{
/* Disable the USARTz Transmit interrupt */
USART_ConfigInt(USARTz, USART_INT_TXDE, DISABLE);
}
}
接收与发送的边界分别来自 NbrOfDataToRead1 与 NbrOfDataToTransfer2,改动缓冲区长度时要同步改这两个计数上限。
3. 中断里清差错标志。 收发之外还检查溢出、噪声、校验与帧错误标志,一旦置位就先读状态寄存器、再读数据寄存器把错误标志清零。
/* Determine if an error flag still exists and clear the error flag */
if ((USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_NEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_PEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_FEF) != RESET))
{
/*Read the sts register first,and the read the DAT register to clear the all error flag*/
(void)USARTz->STS;
(void)USARTz->DAT;
清错序列依赖先读 STS 再读 DAT 的顺序,替换成其它外设时不能只清其中一个寄存器。
4. 主循环等待收齐并打印结果。 主程序等到 RxCounter2 达到接收长度后比较 TxBuffer2 与 RxBuffer2,按 TransferStatus2 打印通过或异常。
/* Wait until end of transmission from Slave */
while (RxCounter2 < RxBufferSize2)
{
}
TransferStatus2 = Buffercmp(TxBuffer2, RxBuffer2, TxBufferSize2);
if (TransferStatus2 == PASSED)
{
log_info(" N32G41x 485 Master test OK \r\n");
}
else
{
log_info(" N32G41x 485 Master test Fail \r\n");
}
等待条件用的是接收缓冲区长度 RxBufferSize2,改动 TxBuffer2 内容长度时要同步改这个长度,否则主循环会一直等待。
对应源码: src/main.c、src/n32g41x_it.c
5.21.3.3 485_Slave RS485 从机中断收发
工程目录: projects/n32g41x_EVAL/examples/USART/485/485_Slave/
使能 GPIO 与 USART2 时钟并配置 USART2 中断→配置 USART2 的 TX、RX、DE 引脚为复用推挽→配置 USART2 为 RS485 DE 自动控制并只开接收中断→主循环等待 Master 发来的数据→收齐后比较收发缓冲并打印结果→使能发送中断把数据回传给 Master
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示两块开发板之间 USART2 RS485 模式的中断通信:Slave 先等待接收 Master 的数据,收齐后与预期数据比较并打印,随后把同一缓冲区经中断回传给 Master。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Buffercmp;inc/main.h:USART2 的引脚、DE 与中断宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。对端为同目录下的 485_Master/ 子工程。 |
| 关键配置 | 与 Master 相同,USARTz 为 USART2,Tx 为 PA2、Rx 为 PA3、DE 为 PA1,均为 GPIO_AF4 复用推挽上拉;波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样,收发使能; DE 设为 USART_CfgDriverAssertTime(USARTz, 1)、USART_CfgDriverdeassertTime(USARTz, 0)、USART_DriverPolaritySet(USARTz, DISABLE)、USART_DriverModeSet(USARTz, ENABLE)。启动时只开 USART_INT_RXDNE,收齐后才开 USART_INT_TXDE。NVIC 抢占优先级为 NVIC_PRE_PRIORITY_0、子优先级为 NVIC_SUB_PRIORITY_1,优先级分组 2。 |
| 程序与数据路径 | main 配置时钟、NVIC、GPIO 与 USART2 及 RS485 DE,先开接收中断并 USART_Enable;while (RxCounter2 < RxBufferSize2) 等到收齐后 Buffercmp 比较并打印 OK 或 Fail,再调用 USART_ConfigInt(USARTz, USART_INT_TXDE, ENABLE) 开发送中断,由 USARTz_IRQHandler 把 TxBuffer2 逐字节发出。 |
| 主要 API | USART_StructInit、USART_Init、USART_CfgDriverAssertTime、USART_CfgDriverdeassertTime、USART_DriverPolaritySet、USART_DriverModeSet、USART_ConfigInt、USART_Enable、USART_GetFlagStatus、USART_GetIntStatus、USART_ReceiveData、USART_SendData |
| 验证方法 | 两块板分别下载 Slave 与 Master 程序,先复位 Slave 再复位 Master,观察串口打印:N32G41x 485 Slave test OK 为通过,N32G41x 485 Slave test Fail 为异常。 |
| 使用提示 | 需两块评估板与两个 RS485 收发器模块,A-A、B-B 互连,并先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; Slave 必须先上电运行,再由 Master 触发。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例让 USART2 先处于只收状态等待主机,收到并比较后再打开发送中断把数据回传。
数据路径: main → USART_ConfigInt(USART_INT_RXDNE) 只开接收中断 → USARTz_IRQHandler 收字节写入 RxBuffer2 → Buffercmp 比较并打印 → USART_ConfigInt(USART_INT_TXDE) 开发送中断 → 中断把 TxBuffer2 回传
1. 先只开接收中断并启动。 从机初始只用接收中断,启动后打印提示并进入等待,收发方向在比较完成后再切换。
/* Enable USARTz Receive interrupt (Slave waits for data first) */
USART_ConfigInt(USARTz, USART_INT_RXDNE, ENABLE);
/* Enable the USARTz */
USART_Enable(USARTz, ENABLE);
log_info(" N32G41x 485 Slave test START, Press Master 485 to reset the pins \r\n");
从机与主机共用同一引脚与 DE 配置,移植时要保持两端波特率、字长与 DE 极性一致。
2. 收齐比较后再开发送中断。 主循环等到接收计数达到长度后比较 TxBuffer2 与 RxBuffer2 并打印结果,随后打开发送中断,把回传交给中断处理。
/* Wait until all data received from Master */
while (RxCounter2 < RxBufferSize2)
{
}
TransferStatus2 = Buffercmp(TxBuffer2, RxBuffer2, TxBufferSize2);
if (TransferStatus2 == PASSED)
{
log_info(" N32G41x 485 Slave test OK \r\n");
}
else
{
log_info(" N32G41x 485 Slave test Fail \r\n");
}
/* Now echo data back: enable Transmit interrupt */
USART_ConfigInt(USARTz, USART_INT_TXDE, ENABLE);
发送与接收的计数上限都来自缓冲区长度宏,改动回传数据长度时要同步改 TxBufferSize2 与 RxBufferSize2。
3. 中断完成接收与回传搬运。 USARTz_IRQHandler 收到字节写入 RxBuffer2,收到足够数量后关接收中断;发送中断触发时从 TxBuffer2 取字节写入数据寄存器,发完关发送中断。
void USARTz_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_RXDNE) != RESET))
{
/* Read one byte from the receive data register */
RxBuffer2[RxCounter2++] = USART_ReceiveData(USARTz);
if (RxCounter2 == NbrOfDataToRead1)
{
/* Disable the USARTz Receive interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, DISABLE);
}
}
if ((USART_GetFlagStatus(USARTz, USART_FLAG_TXDE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_TXDE) != RESET))
{
/* Write one byte to the transmit data register */
USART_SendData(USARTz, TxBuffer2[TxCounter2++]);
if (TxCounter2 == NbrOfDataToTransfer2)
{
/* Disable the USARTz Transmit interrupt */
USART_ConfigInt(USARTz, USART_INT_TXDE, DISABLE);
}
}
从机回传的就是接收时用的同一缓冲区内容,改动回传内容时要同步核对两个方向的计数上限。
4. 使能 RS485 DE 自动控制。 DE 起始时间、极性、驱动模式与主机一致,收发方向仍由硬件按发送状态自动切换。
/* RS485 Driver Enable configuration */
USART_CfgDriverAssertTime(USARTz, 1);
USART_CfgDriverdeassertTime(USARTz, 0);
USART_DriverPolaritySet(USARTz, DISABLE);
USART_DriverModeSet(USARTz, ENABLE);
主机与从机的 DE 配置必须一致,两端极性或起始时间不同会导致总线收发方向错乱。
对应源码: src/main.c、src/n32g41x_it.c
5.21.3.4 DMA_Interrupt DMA 发送与中断接收
工程目录: projects/n32g41x_EVAL/examples/USART/DMA_Interrupt/
使能 DMA、GPIO 与两路 USART 时钟并配置 USART2 中断→配置 USART1 与 USART2 的 TX、RX 引脚为复用推挽→配置两路 USART 的发送 DMA 通道→使能发送 DMA 请求、USART2 接收中断并启动两路→USART1 轮询接收 USART2 发来的数据→等待两个发送通道传输完成并比较两组缓冲
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)的 DMA 发送与两种接收方式:USART1 用 DMA 发送 TxBuffer1、USART2 用接收中断收进 RxBuffer2; USART2 用 DMA 发送 TxBuffer2、USART1 用轮询收进 RxBuffer1。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、DMA_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的引脚与 DMA 通道宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9、Rx PA10),USARTz 为 USART2(Tx PA2、Rx PA3); 除 N32G412 的 PA9 复用为 GPIO_AF4、N32G415 为 GPIO_AF0 外,其余引脚均为 GPIO_AF4 复用推挽上拉;波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样; USART1_TX 用 DMA_CH1、USART2_TX 用 DMA_CH3,方向为 DMA_DIR_PERIPH_DST;USART2_IRQn 抢占优先级为 NVIC_PRE_PRIORITY_0、子优先级为 NVIC_SUB_PRIORITY_0。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 配置时钟、NVIC、GPIO、DMA 与两路 USART,开两路发送 DMA 请求、USART2 接收中断与两个 DMA 通道后 USART_Enable;随后在 while (indexFlag < TxBufferSize2) 中轮询 USART1 的 USART_FLAG_RXDNE 把 USART2 发来的数据收进 RxBuffer1,再等待两个发送通道传输完成标志,Buffercmp 比较后写入 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_EnableDMA、USART_ConfigInt、USART_Enable、USART_GetFlagStatus、USART_ReceiveData、USART_GetIntStatus、USART_SendData、DMA_StructInit、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus、NVIC_Init |
| 验证方法 | 复位运行后依次查看变量 TransferStatus1 与 TransferStatus2,两者均为 PASSED 表示两组收发数据一致,任一为 FAILED 表示对应方向不一致。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; 外部需把 USART1_TX(PA9) 接 USART2_RX(PA3)、USART2_TX(PA2) 接 USART1_RX(PA10)。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例让两路 USART 都用 DMA 发送,接收则一路轮询、一路中断,以对照 DMA 发送配合不同接收方式。
数据路径: main → USART_EnableDMA(USART_DMAREQ_TX) 为两路开 DMA 发送 → USART_ConfigInt(USART_INT_RXDNE) 开 USART2 接收中断 → DMA_Init 把 TxBuffer1/TxBuffer2 搬到各自数据寄存器 → 主循环轮询 USART_FLAG_RXDNE 收 RxBuffer1,USARTz_IRQHandler 收 RxBuffer2 → Buffercmp 把结果写入两个状态变量
1. 开 DMA 请求、接收中断并启动通道。 两路都只开 DMA 发送请求,接收侧 USART1 用轮询、USART2 用中断,两个发送 DMA 通道在此一并使能。
/* Enable USARTy DMA Rx and TX request */
USART_EnableDMA(USARTy, USART_DMAREQ_TX, ENABLE);
/* Enable USARTz DMA Rx and TX request */
USART_EnableDMA(USARTz, USART_DMAREQ_TX, ENABLE);
/* Enable the USARTz Receive Interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, ENABLE);
/* Enable USARTy TX DMA Channel */
DMA_EnableChannel(USARTy_Tx_DMA_Channel, ENABLE);
/* Enable USARTz TX DMA Channel */
DMA_EnableChannel(USARTz_Tx_DMA_Channel, ENABLE);
改成双向都用 DMA 时要同时补开接收请求与接收通道,只开发送请求会导致接收侧永远等不到数据。
2. 轮询接收 USART1 的数据。 USART1 的接收不走 DMA 也不走中断,主循环在接收标志置位后把字节取进 RxBuffer1,遇到溢出标志时用读数据寄存器的方式丢弃。
/* Receive the TxBuffer2 */
while (indexFlag < TxBufferSize2)
{
while (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if(USART_GetFlagStatus(USARTy, USART_FLAG_OREF) != RESET)
{
USARTy->DAT;
}
}
RxBuffer1[indexFlag++] = USART_ReceiveData(USARTy);
}
轮询收数与接收中断不能同时使能,移植时若改为中断接收,要删掉这段轮询并改由中断写缓冲。
3. 用 DMA 通道描述发送搬运。 发送通道以外设数据寄存器为目标、以 TxBuffer1 为源,字节宽度、外设地址不增、存储器地址递增,再按请求重映射绑到 USART1_TX 事件。
/* USARTy_Tx_DMA_Channel (triggered by USARTy Tx event) Config */
DMA_DeInit(USARTy_Tx_DMA_Channel);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = USARTy_DAT_Base;
DMA_InitStructure.MemAddr = (uint32_t)TxBuffer1;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.BufSize = TxBufferSize1;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(USARTy_Tx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(USARTy_Tx_DMA_REMAP, DMA, USARTy_Tx_DMA_Channel, ENABLE);
USART2 的发送通道复用同一结构体,只改外设地址、存储器地址与重映射名;改动源缓冲或长度要同步改 BufSize。
4. 等待发送完成并比较结果。 轮询接收结束后等待两个发送通道的传输完成标志,再分别比较 TxBuffer2/RxBuffer1 与 TxBuffer1/RxBuffer2。
/* Wait until USARTy TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTy_Tx_DMA_FLAG, DMA) == RESET)
{
}
/* Wait until USARTz TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTz_Tx_DMA_FLAG, DMA) == RESET)
{
}
/* Check the received data with the send ones */
TransferStatus1 = Buffercmp(TxBuffer2, RxBuffer1, TxBufferSize2);
/* TransferStatus1 = PASSED, if the data transmitted from USARTz and
received by USARTy are the same */
/* TransferStatus1 = FAILED, if the data transmitted from USARTz and
received by USARTy are different */
TransferStatus2 = Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1);
两个状态变量分别对应两个方向,改动发送或接收缓冲长度时要同步改 Buffercmp 的长度参数。
对应源码: src/main.c、src/n32g41x_it.c
5.21.3.5 DMA_Polling_MaxBaudrate DMA 轮询最高波特率
工程目录: projects/n32g41x_EVAL/examples/USART/DMA_Polling_MaxBaudrate/
使能 DMA、GPIO 与两路 USART 时钟→配置 USART1 与 USART4 的 TX、RX 引脚为复用推挽→配置两路 USART 的发送与接收四个 DMA 通道→使能两路收发 DMA 请求与通道→轮询等待四个 DMA 通道传输完成→比较两组缓冲并写入两个状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART4(USARTz)以最高波特率 10000000 bps 通过 DMA 双向传输:双方各自用 DMA 发送并接收,轮询四个通道的传输完成标志后比较收发数据。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、DMA_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的引脚与 DMA 通道宏;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9、Rx PA10),USARTz 为 USART4(Tx PA1、Rx PA0); PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0,USART4 两脚为 GPIO_AF5;波特率 10000000、8 数据位、1 停止位、无校验、无硬件流控、8 倍过采样( USART_8OVER);DMA 分配为 USART1_TX 用 DMA_CH1、USART1_RX 用 DMA_CH2、USART4_TX 用 DMA_CH3、USART4_RX 用 DMA_CH4,字节宽度、外设地址不增、存储器地址递增、DMA_MODE_NORMAL。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 配置时钟、GPIO、DMA 与两路 USART,开两路收发 DMA 请求和四个 DMA 通道后 USART_Enable;随后依次轮询四个 DMA_GetFlagStatus 等待 USART1_TX、USART1_RX、USART4_TX、USART4_RX 传输完成,最后 Buffercmp 比较并把结果写入 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_EnableDMA、USART_Enable、DMA_StructInit、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus |
| 验证方法 | 编译下载运行后检查 TransferStatus1 与 TransferStatus2,两者均为 PASSED 说明双向 DMA 传输正确,任一为 FAILED 说明对应方向出错。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; 连接为 USART1_TX(PA9) 接 USART4_RX(PA0)、USART4_TX(PA1) 接 USART1_RX(PA10)。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用两路的收发四个 DMA 通道把波特率拉到最高,主循环只轮询标志,不做逐字节收发。
数据路径: main → USART_Init(BaudRate = 10000000、USART_8OVER)→ USART_EnableDMA(USART_DMAREQ_RX | USART_DMAREQ_TX) → DMA_Init/DMA_RequestRemap 建立四个通道 → 轮询四个完成标志 → Buffercmp 把结果写入两个状态变量
1. 两路收发 DMA 请求与四个通道使能。 两路都同时开收发请求,四个通道一次性使能,收发全部交给 DMA。
/* Enable USARTy DMA Rx and TX request */
USART_EnableDMA(USARTy, USART_DMAREQ_RX | USART_DMAREQ_TX, ENABLE);
/* Enable USARTz DMA Rx and TX request */
USART_EnableDMA(USARTz, USART_DMAREQ_RX | USART_DMAREQ_TX, ENABLE);
/* Enable USARTy TX DMA Channel */
DMA_EnableChannel(USARTy_Tx_DMA_Channel, ENABLE);
/* Enable USARTy RX DMA Channel */
DMA_EnableChannel(USARTy_Rx_DMA_Channel, ENABLE);
/* Enable USARTz TX DMA Channel */
DMA_EnableChannel(USARTz_Tx_DMA_Channel, ENABLE);
/* Enable USARTz RX DMA Channel */
DMA_EnableChannel(USARTz_Rx_DMA_Channel, ENABLE);
四个通道与四个请求要成对使能,漏开任一个方向的请求会让对应标志永远等不到。
2. 用 DMA 通道描述发送搬运。 一个通道描述发送:外设数据寄存器为目标、TxBuffer1 为源,字节宽度、外设地址不增、存储器地址递增,再按请求重映射绑到 USART1_TX 事件。
/* USARTy_Tx_DMA_Channel (triggered by USARTy Tx event) Config */
DMA_DeInit(USARTy_Tx_DMA_Channel);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = USARTy_DAT_Base;
DMA_InitStructure.MemAddr = (uint32_t)TxBuffer1;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.BufSize = TxBufferSize1;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(USARTy_Tx_DMA_Channel, &DMA_InitStructure);
DMA_RequestRemap(USARTy_Tx_DMA_REMAP, DMA, USARTy_Tx_DMA_Channel, ENABLE);
其余三个通道复用同一结构体,只改方向、外设地址、存储器地址与重映射名;收发共用外设时要核对每个通道绑定的请求线。
3. 轮询四个通道传输完成并比较。 主循环依次等待 USART1_TX、USART1_RX、USART4_TX、USART4_RX 四个标志,全部置位后比较两组缓冲。
/* Wait until USARTy TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTy_Tx_DMA_FLAG, DMA) == RESET)
{
}
/* Wait until USARTy RX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTy_Rx_DMA_FLAG, DMA) == RESET)
{
}
/* Wait until USARTz TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTz_Tx_DMA_FLAG, DMA) == RESET)
{
}
/* Wait until USARTz RX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(USARTz_Rx_DMA_FLAG, DMA) == RESET)
{
}
最高波特率下四个标志必须按各自通道核对,改动通道分配时要同步改标志宏,否则轮询的是未使用的通道。
对应源码: src/main.c
5.21.3.6 HalfDuplex 单线半双工
工程目录: projects/n32g41x_EVAL/examples/USART/HalfDuplex/
使能 GPIO 与两路 USART 时钟→配置 USART1、USART2 的 TX 引脚为复用开漏输出→使能两路 USART 的半双工模式→USART1 逐字节发送,USART2 轮询接收进缓冲→USART2 逐字节发送,USART1 轮询接收进缓冲→比较两组缓冲并写入两个状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)以单线半双工模式通信:先由 USART1 发送 TxBuffer1 给 USART2,再由 USART2 发送 TxBuffer2 给 USART1,两次都由软件轮询标志收发。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的 USART 与引脚宏;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(只用 Tx PA9),USARTz 为 USART2(只用 Tx PA2),两脚都是 GPIO_MODE_AF_OD(复用开漏)上拉;PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0,PA2 为 GPIO_AF4;波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样,收发使能; 收发前分别调用 USART_EnableHalfDuplex 使能半双工。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 配置时钟、GPIO 与两路 USART 后 USART_Enable 并分别使能半双工;第一段 while (NbrOfDataToRead2--) 里 USART1 等 USART_FLAG_TXDE 后 USART_SendData 逐字节发送 TxBuffer1,USART2 等 USART_FLAG_RXDNE 后收进 RxBuffer2;随后清 USART1 数据寄存器,第二段把 USART2 的 TxBuffer2 发到 USART1 的 RxBuffer1;最后 Buffercmp 比较写入 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_EnableHalfDuplex、USART_GetFlagStatus、USART_SendData、USART_ReceiveData |
| 验证方法 | 复位运行后依次查看变量 TransferStatus1 与 TransferStatus2,两者均为 PASSED 表示两个方向都一致,任一为 FAILED 表示对应方向出错。 |
| 使用提示 | 半双工下 TX 与 RX 在片内相连,只用一根线连接两块板的 TX 引脚(USART1_Tx 的 PA9 与 USART2_Tx 的 PA2); 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用单线半双工把两路各自只留一根 TX 线相连,软件分两段轮流收发。
数据路径: main → USART_EnableHalfDuplex 使能两路半双工 → 第一段 USART_SendData/USART_ReceiveData 把 TxBuffer1 送到 RxBuffer2 → 第二段把 TxBuffer2 送到 RxBuffer1 → Buffercmp 把结果写入两个状态变量
1. TX 引脚配成复用开漏。 半双工只用一根线,TX 引脚必须是开漏并上拉,空闲时才不会被外部拉死。
/* Configure USARTy Tx as alternate function push-pull and pull-up */
GPIO_InitStructure.Pin = USARTy_TxPin;
GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_OD;
GPIO_InitStructure.GPIO_Alternate = USARTy_Tx_GPIO_AF;
GPIO_InitPeripheral(USARTy_GPIO, &GPIO_InitStructure);
/* Configure USARTz Tx as alternate function push-pull and pull-up */
GPIO_InitStructure.Pin = USARTz_TxPin;
GPIO_InitStructure.GPIO_Alternate = USARTz_Tx_GPIO_AF;
GPIO_InitPeripheral(USARTz_GPIO, &GPIO_InitStructure);
移植时若把 TX 配成推挽,两个开漏/推挽并存会在总线上互相驱动,容易损坏引脚。
2. 使能半双工模式。 两路先 USART_Enable,再各自打开半双工,收发在内部共用一个数据通道。
/* Enable the USARTy and USARTz */
USART_Enable(USARTy, ENABLE);
USART_Enable(USARTz, ENABLE);
/* Enable USARTy Half Duplex Mode */
USART_EnableHalfDuplex(USARTy, ENABLE);
/* Enable USARTz Half Duplex Mode */
USART_EnableHalfDuplex(USARTz, ENABLE);
半双工与 IrDA 等模式互斥,移植时开启其中一个前要确认另一个未使能。
3. 第一段:USART1 发、USART2 收。 每发一字节先等 TXDE,每收一字节先等 RXDNE,逐字节推进并把收到的数据写入 RxBuffer2,收完清一次数据寄存器。
while (NbrOfDataToRead2--)
{
/* Wait until end of transmit */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
/* Write one byte in the USARTy Transmit Data Register */
USART_SendData(USARTy, TxBuffer1[TxCounter1++]);
/* Wait the byte is entirely received by USARTz */
while (USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if (USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET)
{
USARTz->DAT;
}
}
/* Store the received byte in the RxBuffer2 */
RxBuffer2[RxCounter2++] = USART_ReceiveData(USARTz);
}
/* Clear the USARTy Data Register */
USART_ReceiveData(USARTy);
每字节收发成对推进,改动方向顺序时要同步调整两段的收发计数与清寄存器动作。
4. 比较两组收发结果。 两段收发完成后分别比较 TxBuffer1/RxBuffer2 与 TxBuffer2/RxBuffer1。
/* Check the received data with the send ones */
TransferStatus1 = Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1);
/* TransferStatus = PASSED, if the data transmitted from USARTy and
received by USARTz are the same */
/* TransferStatus = FAILED, if the data transmitted from USARTy and
received by USARTz are different */
TransferStatus2 = Buffercmp(TxBuffer2, RxBuffer1, TxBufferSize2);
两个状态变量分别对应两个方向,改动缓冲长度时要同步改 Buffercmp 的长度参数。
对应源码: src/main.c
5.21.3.7 HardwareFlowCtrl 硬件流控
工程目录: projects/n32g41x_EVAL/examples/USART/HardwareFlowCtrl/
使能 GPIO 与两路 USART 时钟→配置 USART1、USART2 的 TX、RX、RTS、CTS 引脚为对应复用功能→配置两路 USART 同时使用 CTS 与 RTS 硬件流控→USART1 逐字节发送,USART2 轮询接收进缓冲→USART2 逐字节发送,USART1 轮询接收进缓冲→比较两组缓冲并写入两个状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)在 CTS/RTS 硬件流控下双向传输:USART1 发送 TxBuffer1、USART2 接收,随后 USART2 发送 TxBuffer2、USART1 接收,收发都用轮询。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的 USART、TX/RX/RTS/CTS 引脚与复用宏;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9、Rx PA10、RTS PA12、CTS PA11),USARTz 为 USART2(Tx PA2、Rx PA3、RTS PA1、CTS PA0); PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0,其余 TX/RX/RTS/CTS 均为 GPIO_AF4;RTS 配为复用推挽,RX 与 CTS 配为 GPIO_MODE_INPUT 上拉;波特率 115200、8 数据位、1 停止位、无校验、16 倍过采样, USART_InitStructure.HardwareFlowControl = USART_HFCTRL_CTS \| USART_HFCTRL_RTS,收发使能。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 配置时钟、GPIO 与两路 USART(硬件流控同时开 CTS 与 RTS)后 USART_Enable;第一段 while (NbrOfDataToRead2--) 里 USART1 USART_SendData 逐字节发 TxBuffer1,USART2 轮询 USART_FLAG_RXDNE 收进 RxBuffer2;第二段反向把 TxBuffer2 送到 RxBuffer1;最后 Buffercmp 比较写入 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_GetFlagStatus、USART_SendData、USART_ReceiveData |
| 验证方法 | 编译下载运行后检查 TransferStatus1 与 TransferStatus2,两者均为 PASSED 说明硬件流控传输成功,任一为 FAILED 说明对应方向出错。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; 除 TX/RX 外还要连接 USARTy 的 RTS(PA12) 接 USARTz 的 CTS(PA0)、USARTz 的 RTS(PA1) 接 USARTy 的 CTS(PA11)。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例在普通异步收发基础上给两路都打开 CTS 与 RTS,让收发节奏由硬件流控信号协调。
数据路径: main → USART_Init(HardwareFlowControl = USART_HFCTRL_CTS \| USART_HFCTRL_RTS)→ USART_Enable → 第一段 USART_SendData/USART_ReceiveData 把 TxBuffer1 送到 RxBuffer2 → 第二段把 TxBuffer2 送到 RxBuffer1 → Buffercmp 把结果写入两个状态变量
1. 配置 RTS 输出与 RX、CTS 输入。 RTS 作输出用复用推挽,RX 与 CTS 作输入用上拉住上拉,四个引脚各按方向配置。
/* Configure USARTz RTS as alternate function push-pull */
GPIO_InitStructure.Pin = USARTz_RTSPin;
GPIO_InitStructure.GPIO_Alternate = USARTz_RTS_GPIO_AF;
GPIO_InitPeripheral(USARTz_GPIO, &GPIO_InitStructure);
/* Configure USARTy Rx as alternate function input and pull-up */
GPIO_InitStructure.Pin = USARTy_RxPin;
GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
GPIO_InitStructure.GPIO_Alternate = USARTy_Rx_GPIO_AF;
GPIO_InitPeripheral(USARTy_GPIO, &GPIO_InitStructure);
/* Configure USARTz Rx as alternate function input and pull-up */
GPIO_InitStructure.Pin = USARTz_RxPin;
GPIO_InitStructure.GPIO_Alternate = USARTz_Rx_GPIO_AF;
GPIO_InitPeripheral(USARTz_GPIO, &GPIO_InitStructure);
/* Configure USARTy CTS as alternate function input and pull-up */
GPIO_InitStructure.Pin = USARTy_CTSPin;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
GPIO_InitStructure.GPIO_Alternate = USARTy_CTS_GPIO_AF;
GPIO_InitPeripheral(USARTy_GPIO, &GPIO_InitStructure);
RTS 与 CTS 的方向不能互换,CTS 作为输入必须能读到对端 RTS 的电平。
2. 第一段发送并轮询接收。 USART1 发一字节后等 TXDE,USART2 等 RXDNE 收进 RxBuffer2,流控信号自动协调两端的收发节奏。
while (NbrOfDataToRead2--)
{
/* Send one byte from USARTy to USARTz */
USART_SendData(USARTy, TxBuffer1[TxCounter1++]);
/* Loop until USARTy DAT register is empty */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
/* Loop until the USARTz Receive Data Register is not empty */
while (USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if(USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET)
{
USARTz->DAT;
}
}
/* Store the received byte in RxBuffer */
RxBuffer2[RxCounter2++] = USART_ReceiveData(USARTz);
}
轮询收发要保留溢出分支,改动缓冲或长度时要同步调整收发计数上限。
3. 比较两组收发结果。 两段收发完成后分别比较两个方向,把结果写入 TransferStatus1、TransferStatus2。
/* Check the received data with the send ones */
TransferStatus1 = Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1);
/* TransferStatus1 = PASSED, if the data transmitted from USARTy and
received by USARTz are the same */
/* TransferStatus1 = FAILED, if the data transmitted from USARTy and
received by USARTz are different */
TransferStatus2 = Buffercmp(TxBuffer2, RxBuffer1, TxBufferSize2);
/* TransferStatus2 = PASSED, if the data transmitted from USARTz and
received by USARTy are the same */
/* TransferStatus2 = FAILED, if the data transmitted from USARTz and
received by USARTy are different */
两个状态变量分别对应两个方向,硬件流控下仍要按方向核对缓冲。
对应源码: src/main.c
5.21.3.8 Interrupt 中断收发
工程目录: projects/n32g41x_EVAL/examples/USART/Interrupt/
使能 GPIO 与两路 USART 时钟并配置两路 NVIC 中断→配置 USART1 与 USART2 的 TX、RX 引脚为复用推挽→配置两路 USART 并同时开收发中断→中断里两路互相收发数据进各自缓冲→主循环等待两个方向收齐→比较两组缓冲并写入两个状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)以中断方式双向通信:两路同时收发,USART1 发 TxBuffer1 由 USART2 收进 RxBuffer2,USART2 发 TxBuffer2 由 USART1 收进 RxBuffer1。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Buffercmp;inc/main.h:USARTy/USARTz 的 USART、引脚与 IRQn 宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTy_IRQHandler、USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9、Rx PA10),USARTz 为 USART2(Tx PA2、Rx PA3); PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0,其余为 GPIO_AF4 复用推挽上拉;波特率 115200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样,收发使能; 两路都开 USART_INT_RXDNE 与 USART_INT_TXDE;NVIC 分组 2, USART1_IRQn 与 USART2_IRQn 抢占优先级为 NVIC_PRE_PRIORITY_0,子优先级分别为 NVIC_SUB_PRIORITY_0、NVIC_SUB_PRIORITY_1。系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz。 |
| 程序与数据路径 | main 配置时钟、NVIC、GPIO 与两路 USART,同时开两路的收发中断后 USART_Enable;中断里 USARTy_IRQHandler 用 USART_ReceiveData/USART_SendData 在 RxBuffer1、TxBuffer1 间搬运,USARTz_IRQHandler 对 RxBuffer2、TxBuffer2 同理,计数到达后关闭对应中断;主循环用两个 while 等到收齐,最后 Buffercmp 比较写入 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_ConfigInt、USART_Enable、USART_GetFlagStatus、USART_GetIntStatus、USART_ReceiveData、USART_SendData |
| 验证方法 | 复位运行后依次查看变量 TransferStatus1 与 TransferStatus2,两者均为 PASSED 表示两个方向都正确,任一为 FAILED 表示对应方向出错。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; 外部连接 USART1_TX(PA9) 接 USART2_RX(PA3)、USART1_RX(PA10) 接 USART2_TX(PA2)。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例把两路的收发全部交给中断,主循环只等两个方向的接收计数到位。
数据路径: main → USART_ConfigInt 开两路收发中断 → USARTy_IRQHandler/USARTz_IRQHandler 用 USART_ReceiveData/USART_SendData 在 TxBuffer 与 RxBuffer 间搬运 → 主循环等两个接收计数收齐 → Buffercmp 把结果写入两个状态变量
1. 同时打开收发中断并启动。 两路各自打开接收与发送中断,让收发都由中断服务函数处理。
/* Enable USARTy and USARTz Receive and Transmit interrupts */
USART_ConfigInt(USARTy, USART_INT_RXDNE, ENABLE);
USART_ConfigInt(USARTy, USART_INT_TXDE, ENABLE);
USART_ConfigInt(USARTz, USART_INT_RXDNE, ENABLE);
USART_ConfigInt(USARTz, USART_INT_TXDE, ENABLE);
/* Enable the USARTy and USARTz */
USART_Enable(USARTy, ENABLE);
USART_Enable(USARTz, ENABLE);
收发计数上限变化时功能不受影响,但接收中断关闭条件依赖计数上限,要同步核对。
2. USART1 中断完成收发搬运。 USARTy_IRQHandler 收到字节写入 RxBuffer1,收够后关接收中断;发送中断触发时从 TxBuffer1 取字节,发完关发送中断。
void USARTy_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTy, USART_INT_RXDNE) != RESET))
{
/* Read one byte from the receive data register */
RxBuffer1[RxCounter1++] = USART_ReceiveData(USARTy);
if (RxCounter1 == NbrOfDataToRead1)
{
/* Disable the USARTy Receive interrupt */
USART_ConfigInt(USARTy, USART_INT_RXDNE, DISABLE);
}
}
if ((USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) != RESET) &&
(USART_GetIntStatus(USARTy, USART_INT_TXDE) != RESET))
{
/* Write one byte to the transmit data register */
USART_SendData(USARTy, TxBuffer1[TxCounter1++]);
if (TxCounter1 == NbrOfDataToTransfer1)
{
/* Disable the USARTy Transmit interrupt */
USART_ConfigInt(USARTy, USART_INT_TXDE, DISABLE);
}
}
接收与发送的边界分别来自 NbrOfDataToRead1 与 NbrOfDataToTransfer1,改动缓冲区长度时要同步改这两个上限。
3. USART2 中断完成收发搬运。 USARTz_IRQHandler 对 RxBuffer2、TxBuffer2 做同样的收发处理,两个方向互为镜像。
void USARTz_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_RXDNE) != RESET))
{
/* Read one byte from the receive data register */
RxBuffer2[RxCounter2++] = USART_ReceiveData(USARTz);
if (RxCounter2 == NbrOfDataToRead2)
{
/* Disable the USARTz Receive interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, DISABLE);
}
}
if ((USART_GetFlagStatus(USARTz, USART_FLAG_TXDE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_TXDE) != RESET))
{
/* Write one byte to the transmit data register */
USART_SendData(USARTz, TxBuffer2[TxCounter2++]);
if (TxCounter2 == NbrOfDataToTransfer2)
{
/* Disable the USARTz Transmit interrupt */
USART_ConfigInt(USARTz, USART_INT_TXDE, DISABLE);
}
}
两个中断服务函数的边界宏分别对应各自的缓冲区与计数上限,改动一侧要保持另一侧同步。
4. 主循环收齐后比较。 主循环分别等待两个方向的接收计数达到缓冲长度,再比较两组收发结果。
/* Wait until all data are received */
while (RxCounter2 < RxBufferSize2)
{
}
while (RxCounter1 < RxBufferSize1)
{
}
/* Check the received data with the send ones */
TransferStatus1 = Buffercmp(TxBuffer1, RxBuffer2, TxBufferSize1);
/* TransferStatus1 = PASSED, if the data transmitted from UARTy and
received by UARTz are the same */
/* TransferStatus1 = FAILED, if the data transmitted from UARTy and
received by UARTz are different */
TransferStatus2 = Buffercmp(TxBuffer2, RxBuffer1, TxBufferSize2);
等待条件是接收缓冲长度,改动发送缓冲内容长度时要同步改这个长度,否则主循环会一直等待。
对应源码: src/main.c、src/n32g41x_it.c
5.21.3.9 LP_Transmit_Receive IrDA 低功耗模式收发
工程目录: projects/n32g41x_EVAL/examples/USART/IrDA_Tx&Rx/LP_Transmit_Receive/
使能 GPIO 与两路 USART 时钟并配置 USART2 中断→配置 USART1 的 TX、USART2 的 RX 引脚为复用推挽→用 GTIM3 在 PB4 上产生 38 kHz 载波→配置两路 IrDA 为低功耗模式并设置预分频值→USART1 逐字节发送,USART2 中断接收进缓冲→收齐后比较收发缓冲并写入状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)以串行 IrDA 低功耗模式通信:USART1 逐字节发送 TxBuffer1,USART2 通过中断接收进 RxBuffer1,并用 GTIM3 在 PB4 上产生 38 kHz 载波。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Buffercmp;src/bsp_Tim_38K.c、inc/bsp_Tim_38K.h:GTIM3 与 PB4 的 38 kHz 载波初始化;inc/main.h:USARTy/USARTz 的 USART、引脚与 IRQn 宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9),USARTz 为 USART2(Rx PA3); PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0;波特率 600、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样; USARTy 为 USART_MODE_TX、USARTz 为 USART_MODE_RX;USART_SetPrescaler(USARTy, 250)、USART_SetPrescaler(USARTz, 125),USART_ConfigIrDAMode 设为 USART_IRDAMODE_LOWPPWER 并 USART_EnableIrDA;RCC_ConfigPclk1、RCC_ConfigPclk2 均分频到 8,系统时钟为 HSI 16 MHz;载波由 GTIM3 在 PB4 输出。 |
| 程序与数据路径 | main 配置时钟、NVIC、GPIO,调用 ADVANCE_TIM_Init 起 38 kHz 载波,用 USART_Init 把两路配成一发一收,开 USART2 接收中断后 USART_Enable;主循环用 USART_SendData 逐字节发送并等 USART_FLAG_TXDE 与 USART2 收齐;USARTz_IRQHandler 用 USART_ReceiveData 写入 RxBuffer1,最后 Buffercmp 比较写入 TransferStatus。 |
| 主要 API | USART_StructInit、USART_Init、USART_ConfigInt、USART_SetPrescaler、USART_ConfigIrDAMode、USART_EnableIrDA、USART_Enable、USART_GetFlagStatus、USART_SendData、USART_ReceiveData、NVIC_Init |
| 验证方法 | 复位运行后查看变量 TransferStatus,为 PASSED 表示红外接收数据与发送一致,为 FAILED 表示不一致。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; USART1 的 Tx(PA9) 接红外发射端、USART2 的 Rx(PA3) 接红外接收端,PB4 输出 38 kHz 载波; 低功耗模式预分频后的时钟需落在手册规定的范围内。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用 IrDA 低功耗模式和一路 38 kHz 载波把串行数据以红外方式从 USART1 送到 USART2。
数据路径: main → ADVANCE_TIM_Init 在 PB4 输出 38 kHz 载波 → USART_ConfigIrDAMode(USART_IRDAMODE_LOWPPWER) 与 USART_SetPrescaler 配置 IrDA 低功耗模式 → USART_SendData 发 TxBuffer1 → USARTz_IRQHandler 收进 RxBuffer1 → Buffercmp 把结果写入 TransferStatus
1. 设置预分频并选低功耗模式。 发送端与接收端分别设不同的预分频值,再各自选低功耗 IrDA 模式并打开 IrDA,最后才启动 USART。
/* Set the USARTy and USARTz prescaler */
USART_SetPrescaler(USARTy, 250);
USART_SetPrescaler(USARTz, 125);
/* Configure the USARTy IrDA mode */
USART_ConfigIrDAMode(USARTy, USART_IRDAMODE_LOWPPWER);
USART_ConfigIrDAMode(USARTz, USART_IRDAMODE_LOWPPWER);
/* Enable the USARTy and USARTz IrDA mode */
USART_EnableIrDA(USARTy, ENABLE);
USART_EnableIrDA(USARTz, ENABLE);
低功耗模式下脉冲宽度由预分频值决定,两端的预分频要按手册规定的时钟范围成对设置。
2. 配置 38 kHz 载波定时器。 GTIM3 以计数器周期和预分频值决定更新频率,时基初始化后清标志、开更新中断并使能定时器。
TIM_InitTimBaseStruct(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.Period = ADVANCE_TIM_Period;
TIM_TimeBaseStructure.Prescaler = ADVANCE_TIM_Prescaler;
TIM_TimeBaseStructure.ClkDiv = TIM_CLK_DIV1;
TIM_TimeBaseStructure.CounterMode = TIM_CNT_MODE_UP;
TIM_InitTimeBase(ADVANCE_TIM, &TIM_TimeBaseStructure);
周期与预分频值定义在 BSP 头文件里,改动载波频率时要同步改这两个宏并保持一致的分频关系。
3. 定时器中断翻转载波脚。 更新中断里清标志并翻转 PB4,用软件翻转产生载波方波。
void ADVANCE_TIM_IRQHandler(void)
{
if (TIM_GetIntStatus(ADVANCE_TIM, TIM_INT_UPDATE) != RESET)
{
TIM_ClearFlag(ADVANCE_TIM, TIM_FLAG_UPDATE);
digitalToggle(PORTx, PORTx_PIN);
}
}
载波脚与翻转宏由 BSP 头文件定义,移植到别的板卡时要同步改端口与引脚宏。
4. 接收中断把字节写进缓冲。 USARTz_IRQHandler 收到字节写入 RxBuffer1,收够后关接收中断,并检查差错标志后读状态寄存器与数据寄存器把错误清除。
void USARTz_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_RXDNE) != RESET))
{
RxBuffer1[RxCounter++] = USART_ReceiveData(USARTz);
{
if (RxCounter == NbrOfDataToRead1)
{
/* Disable the USARTz Receive interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, DISABLE);
}
}
}
/* Determine if an error flag still exists and clear the error flag */
if ((USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_NEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_PEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_FEF) != RESET))
{
(void)USARTz->STS;
(void)USARTz->DAT;
}
}
接收计数上限取的是 NbrOfDataToRead1,它等于缓冲区长度,改动发送内容长度时要同步改这个长度。
5. 发送并在收齐后比较。 主循环按串口空标志逐字节发送 TxBuffer1,发送结束后等待接收计数收齐,再比较收发缓冲。
while (TxCounter < TxBufferSize1)
{
/* Send one byte from Board1_USARTy to Board2_USARTz */
USART_SendData(USARTy, TxBuffer1[TxCounter++]);
/* Loop until Board1_USARTy DAT register is empty */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
}
while (RxCounter < TxBufferSize1)
{
}
/* Check the received data with the send ones */
TransferStatus = Buffercmp(TxBuffer1, RxBuffer1, TxBufferSize1);
此例的长度宏取 countof 未减 1,收发的字节数包含末尾空字符,移植时要与自己的缓冲定义口径一致。
对应源码: src/main.c、src/bsp_Tim_38K.c、src/n32g41x_it.c
5.21.3.10 Transmit_Receive IrDA 正常模式收发
工程目录: projects/n32g41x_EVAL/examples/USART/IrDA_Tx&Rx/Transmit_Receive/
使能 GPIO 与两路 USART 时钟并配置 USART2 中断→配置 USART1 的 TX、USART2 的 RX 引脚为复用推挽→用 GTIM3 在 PB4 上产生 38 kHz 载波→配置两路 IrDA 为正常模式,预分频值为 1→USART1 逐字节发送,USART2 中断接收进缓冲→收齐后比较收发缓冲并写入状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1(USARTy)与 USART2(USARTz)以串行 IrDA 正常模式通信:USART1 逐字节发送 TxBuffer1,USART2 通过中断接收进 RxBuffer1,并用 GTIM3 在 PB4 上产生 38 kHz 载波。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Buffercmp;src/bsp_Tim_38K.c、inc/bsp_Tim_38K.h:GTIM3 与 PB4 的 38 kHz 载波初始化;inc/main.h:USARTy/USARTz 的 USART、引脚与 IRQn 宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTz_IRQHandler 等中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1(Tx PA9),USARTz 为 USART2(Rx PA3); PA9 在 N32G412 复用为 GPIO_AF4、N32G415 为 GPIO_AF0;波特率 1200、8 数据位、1 停止位、无校验、无硬件流控、16 倍过采样; USARTy 为 USART_MODE_TX、USARTz 为 USART_MODE_RX;USART_SetPrescaler 两路均取 0x01,USART_ConfigIrDAMode 设为 USART_IRDAMODE_NORMAL 并 USART_EnableIrDA;RCC_ConfigPclk1、RCC_ConfigPclk2 均分频到 4,系统时钟 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;载波由 GTIM3 在 PB4 输出。 |
| 程序与数据路径 | main 配置时钟、NVIC、GPIO,调用 ADVANCE_TIM_Init 起 38 kHz 载波,用 USART_Init 把两路配成一发一收,开 USART2 接收中断后 USART_Enable;主循环用 USART_SendData 逐字节发送并等 USART_FLAG_TXDE 与 USART2 收齐;USARTz_IRQHandler 用 USART_ReceiveData 写入 RxBuffer1,最后 Buffercmp 比较写入 TransferStatus。 |
| 主要 API | USART_StructInit、USART_Init、USART_ConfigInt、USART_SetPrescaler、USART_ConfigIrDAMode、USART_EnableIrDA、USART_Enable、USART_GetFlagStatus、USART_SendData、USART_ReceiveData、NVIC_Init |
| 验证方法 | 复位运行后查看变量 TransferStatus,为 PASSED 表示红外接收数据与发送一致,为 FAILED 表示不一致。 |
| 使用提示 | 需先断开 NS-LINK 的 MCU_TX、MCU_RX 跳线帽; USART1 的 Tx(PA9) 接红外发射端、USART2 的 Rx(PA3) 接红外接收端,PB4 输出 38 kHz 载波; 正常模式下预分频值取 1。 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用 IrDA 正常模式和一路 38 kHz 载波把串行数据以红外方式从 USART1 送到 USART2。
数据路径: main → ADVANCE_TIM_Init 在 PB4 输出 38 kHz 载波 → USART_ConfigIrDAMode(USART_IRDAMODE_NORMAL) 与 USART_SetPrescaler(USARTy, 0x01)、USART_SetPrescaler(USARTz, 0x01) 配置 IrDA 正常模式 → USART_SendData 发 TxBuffer1 → USARTz_IRQHandler 收进 RxBuffer1 → Buffercmp 把结果写入 TransferStatus
1. 设置预分频并选正常模式。 两端预分频都取 1,选正常 IrDA 模式并打开 IrDA,正常模式下脉冲宽度固定、预分频只作为分频系数。
/* Set the USARTy and USARTz prescaler */
USART_SetPrescaler(USARTy, 0x01);
USART_SetPrescaler(USARTz, 0x01);
/* Configure the USARTy IrDA mode */
USART_ConfigIrDAMode(USARTy, USART_IRDAMODE_NORMAL);
USART_ConfigIrDAMode(USARTz, USART_IRDAMODE_NORMAL);
/* Enable the USARTy and USARTz IrDA mode */
USART_EnableIrDA(USARTy, ENABLE);
USART_EnableIrDA(USARTz, ENABLE);
正常模式下预分频值固定为 1,改动时要确认仍符合该模式对分频的要求。
2. 配置发送与接收的引脚。 发送脚用复用推挽并提高输出能力,接收脚用复用推挽上拉,为红外发射端提供足够的驱动电流。
/* Configure USARTy Tx as alternate function push-pull */
GPIO_InitStructure.Pin = USARTy_TxPin;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Alternate = USARTy_Tx_GPIO_AF;
GPIO_InitStructure.GPIO_Current = GPIO_DS_HIGH;
GPIO_InitPeripheral(USARTy_GPIO, &GPIO_InitStructure);
/* Configure USARTz Rx as alternate function push-pull */
GPIO_InitStructure.Pin = USARTz_RxPin;
GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
GPIO_InitStructure.GPIO_Alternate = USARTz_Rx_GPIO_AF;
GPIO_InitPeripheral(USARTz_GPIO, &GPIO_InitStructure);
发送脚驱动能力不足会导致红外发射距离变短,移植时要按实际发射管核对输出能力设置。
3. 配置 38 kHz 载波定时器。 GTIM3 使能时钟后按时基结构初始化,周期与预分频值决定更新频率,随后清标志、开更新中断并使能定时器。
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK, ENABLE);
TIM_InitTimBaseStruct(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.Period = ADVANCE_TIM_Period;
TIM_TimeBaseStructure.Prescaler = ADVANCE_TIM_Prescaler;
TIM_TimeBaseStructure.ClkDiv = TIM_CLK_DIV1;
TIM_TimeBaseStructure.CounterMode = TIM_CNT_MODE_UP;
TIM_InitTimeBase(ADVANCE_TIM, &TIM_TimeBaseStructure);
周期与预分频值定义在 BSP 头文件里,改动载波频率时要同步改这两个宏并保持一致的分频关系。
4. 接收中断把字节写进缓冲。 USARTz_IRQHandler 收到字节写入 RxBuffer1,收够后关接收中断,并检查差错标志后读状态寄存器与数据寄存器把错误清除。
void USARTz_IRQHandler(void)
{
if ((USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET) &&
(USART_GetIntStatus(USARTz, USART_INT_RXDNE) != RESET))
{
/* Read one byte from the receive data register */
RxBuffer1[RxCounter++] = USART_ReceiveData(USARTz);
if (RxCounter == NbrOfDataToRead1)
{
/* Disable the USARTz Receive interrupt */
USART_ConfigInt(USARTz, USART_INT_RXDNE, DISABLE);
}
}
/* Determine if an error flag still exists and clear the error flag */
if ((USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_NEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_PEF) != RESET) ||
(USART_GetFlagStatus(USARTz, USART_FLAG_FEF) != RESET))
{
(void)USARTz->STS;
(void)USARTz->DAT;
}
}
接收计数上限取的是 NbrOfDataToRead1,它等于缓冲区长度,改动发送内容长度时要同步改这个长度。
5. 发送并在收齐后比较。 主循环按串口空标志逐字节发送 TxBuffer1,发送结束后等待接收计数收齐,再比较收发缓冲。
while (TxCounter < TxBufferSize1)
{
/* Send one byte from Board1_USARTy to Board2_USARTz */
USART_SendData(USARTy, TxBuffer1[TxCounter++]);
/* Loop until Board1_USARTy DAT register is empty */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
}
while (RxCounter < TxBufferSize1)
{
}
/* Check the received data with the send ones */
TransferStatus = Buffercmp(TxBuffer1, RxBuffer1, TxBufferSize1);
此例的长度宏取 countof 未减 1,收发的字节数包含末尾空字符,移植时要与自己的缓冲定义口径一致。
对应源码: src/main.c、src/bsp_Tim_38K.c、src/n32g41x_it.c
5.21.3.11 LIN_Master LIN 主机
工程目录: projects/n32g41x_EVAL/examples/USART/LIN/LIN_Master/
初始化打印串口与时钟→配置 USART2 为 LIN 主模式→发送主请求帧与从应答帧请求→接收从机回帧并校验校验和→打印 8 字节接收数据
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART 的 LIN 模式作为主节点轮询工作:先向从机发送 8 字节 0x0F 的主请求帧,再发出从应答帧请求,随后接收从机回发的 8 字节数据并校验。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、Memset、Buffercopy;src/lin_master.c:帧处理与收发流程(SetFramePID、SetFrameChecksum、WaitFrameRes、TestLinMaster 等);inc/main.h、inc/lin_master.h:USART2 与引脚宏、M_LIN_EX_MSG 消息结构;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTx 为 USART2,TX 为 PA2、RX 为 PA3,复用号均为 GPIO_AF4,时钟 RCC_APB1_PERIPH_USART2;波特率 9600,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,流控 USART_HFCTRL_NONE,过采样 USART_16OVER,收发使能;USART_ConfigLINBreakDetectLength 设为 USART_LINBDL_10B。系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 先 log_init 并打印 test LIN master mode,依次 RCC_Configuration、NVIC_Configuration、GPIO_Configuration,用 USART_Init 配置 USART2、USART_EnableLIN 打开 LIN 模式后 USART_Enable;while(1) 每 500 ms 调用 TestLinMaster:发出断开帧与主请求帧后延时 20 ms,最多 4 次发出应答帧请求,并用 WaitFrameRes 接收从机 8 字节回帧、比较校验和,成功则逐字节打印。 |
| 主要 API | USART_StructInit、USART_Init、USART_ConfigLINBreakDetectLength、USART_EnableLIN、USART_Enable、USART_SendBreak、USART_SendData、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 两块评估板分别下载 LIN_Master 与 LIN_Slave,先复位从机再复位主机; 主机串口打印 test LIN master mode,请求帧阶段打印 SetFrameMsg ID:0x3c 与 PID 校验位信息,从机回帧校验通过后按 recv_data[0] 至 recv_data[7] 打印 8 字节数据,从机无响应时打印 slave no response!!。 |
| 使用提示 | 需先断开评估板 NS-LINK 的 MCU_TX 与 MCU_RX 跳线帽; USART2 的 TX(PA2)与 RX(PA3)要与从机交叉连接; 支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例把一次 LIN 传输拆成同步、PID、数据与校验四段,由一组帧处理函数逐段构造,再借 USART 的断开帧机制启动。
数据路径: main → USART_Init 与 USART_EnableLIN 配置 USART2 → TestLinMaster → TestMasterReqFrame/TestSlaveResFrame 填 M_LIN_EX_MSG → FrameHandle 调 SetFramePID 与 SetFrameChecksum → MasterSendFrame/MasterSendBytes 先发断开帧再发字节 → WaitFrameRes 接收并比较校验和
1. 用宏选定 USART2 与两只引脚。 inc/main.h 把串口、时钟、TX/RX 引脚与复用号集中定义为一组宏,收发代码只引用宏名。
#define USARTx USART2
#define USARTx_GPIO GPIOA
#define USARTx_CLK RCC_APB1_PERIPH_USART2
#define USARTx_RxPin GPIO_PIN_3
#define USARTx_TxPin GPIO_PIN_2
#define USARTx_Rx_GPIO_AF GPIO_AF4
#define USARTx_Tx_GPIO_AF GPIO_AF4
#define USARTx_APBxClkCmd RCC_EnableAPB1PeriphClk
#define USARTx_IRQn USART2_IRQn
#define USARTx_IRQHandler USART2_IRQHandler
换用其它串口或引脚时只改这组宏;该引脚在 N32G412 与 N32G415 上复用号相同,无需条件编译。
2. 配置 USART2 并打开 LIN 模式。 main 逐项填入波特率、字长、停止位、校验、流控、过采样与收发模式,USART_Init 写入后再选断开检测长度并打开 LIN 模式。
/* USARTx configuration */
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 9600;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.OverSampling = USART_16OVER;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
/* Configure USARTx */
USART_Init(USARTx, &USART_InitStructure);
/* Enable the USARTx LIN mode */
USART_ConfigLINBreakDetectLength(USARTx, USART_LINBDL_10B);
USART_EnableLIN(USARTx, ENABLE);
/* Enable the USARTx */
USART_Enable(USARTx, ENABLE);
改动波特率、字长或引脚时要同步核对 main.h 的宏与从机侧同项配置,两端不一致时从机不会响应。
3. 计算校验和并发送断开帧。 MasterGetCheckSum 按 LIN 经典校验和算法累加各字节,MasterSendBytes 在发送数据前先产生断开帧,再用发送完成标志同步每个字节。
uint8_t MasterGetCheckSum(uint8_t *pData, uint8_t len)
{
uint16_t check_sum_temp = 0;
uint8_t i;
for(i = 0; i < len; i++)
{
check_sum_temp += pData[i];
if(check_sum_temp > 0xFF)
{
check_sum_temp -= 0xFF;
}
}
return (~check_sum_temp) & 0xFF;
}
void MasterSendBytes(uint8_t *pBuf, uint8_t Len)
{
USART_SendBreak(USARTx);
while(Len--)
{
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXC) == RESET);
USART_SendData(USARTx, *pBuf++);
}
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXC) == RESET);
}
断开检测长度(10 位)与校验和类型要与从机一致;MasterSendBytes 每次调用都会先发一个断开帧,长度取决于调用的数据段。
4. 构造请求帧与应答帧、等待回帧。 TestMasterReqFrame 填 8 字节 0x0F 与 PID 0x3C,FrameHandle 先用原始 PID 判断帧类型,再换算 PID、算校验和并发送。
void TestMasterReqFrame(void)
{
int i = 0;
M_LIN_EX_MSG CurLINTxMsg;
CurLINTxMsg.Check = CLASSIC;
CurLINTxMsg.DataLen = 8;
CurLINTxMsg.Sync = 0x55;
CurLINTxMsg.PID = 0x3C;
for(i = 0; i < CurLINTxMsg.DataLen; i++)
{
CurLINTxMsg.Data[i] = 0x0F;
}
SetFrameMsg(&M_TxMsg, CurLINTxMsg);
FrameHandle();
}
void FrameHandle(void)
{
uint8_t tmp_PID = M_TxMsg.PID;
SetFramePID(&M_TxMsg);
switch (tmp_PID)
{
case 0x3C://Master request frame
SetFrameChecksum(&M_TxMsg);
break;
case 0x3D://Slave reply frame
M_TxMsg.DataLen = 0;
break;
default:
break;
}
MasterSendFrame(M_TxMsg);
}
FrameHandle 用换算前的原始 PID(0x3C/0x3D)选择帧类型,这两个常量要与从机 IDType 表里的 MSG_RECEIVE_ID 与 MSG_SEND_ID 成对匹配,改 ID 时两处一起改。
对应源码: src/main.c、src/lin_master.c、inc/main.h、inc/lin_master.h
5.21.3.12 LIN_Slave LIN 从机
工程目录: projects/n32g41x_EVAL/examples/USART/LIN/LIN_Slave/
初始化打印串口与时钟→配置 USART2 为 LIN 从模式并开启接收与断开检测中断→状态机依次接收同步字节、PID 与数据→收到完整帧后置位标志→主循环执行命令并回发 8 字节应答
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART 的 LIN 模式作为从节点:接收主机发来的请求帧(0x3C)与从应答帧请求(0x3D),按主机请求把数据改成 8 字节 0x01 回发并打印过程。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、LIN_RespData;src/lin_slave.c:LIN_IRQHandler、LIN_EX_RxAsync 状态机与收发流程函数;inc/main.h、inc/lin_slave.h:USART2 与引脚宏、LIN_EX_MSG 结构、GET_PID 与 ID 类型宏;src/n32g41x_it.c、inc/n32g41x_it.h:USARTx_IRQHandler 调用 LIN_IRQHandler;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTx 为 USART2,TX 为 PA2、RX 为 PA3,复用号均为 GPIO_AF4;波特率 9600,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,流控 USART_HFCTRL_NONE,过采样 USART_16OVER,收发使能;USART_ConfigLINBreakDetectLength 设为 USART_LINBDL_10B,并使能 USART_INT_RXDNE 与 USART_INT_LINBD 中断;IDType[0x3C] 置 ID_TYPE_SR、IDType[0x3D] 置 ID_TYPE_SW。系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 先 log_init 并打印 test LIN slave mode,执行 RCC_Configuration、NVIC_Configuration、GPIO_Configuration 后配置并打开 USART2 的 LIN 模式、接收与断开检测中断;随后设置 IDType 表并进入 while(1),GotMsgFlag 置位时调用 BOOT_ExecutiveCommand(pLINMsg->Data, LIN_RespData),把收到数据的每个字节改成 0x01 后由 LIN_SetResp 计算校验和、LIN_SendBytes 回发,最后清零 GotMsgFlag。 |
| 主要 API | USART_StructInit、USART_Init、USART_ConfigLINBreakDetectLength、USART_EnableLIN、USART_ConfigInt、USART_Enable、USART_GetFlagStatus、USART_ClrFlag、USART_ReceiveData、USART_SendData |
| 验证方法 | 与 LIN_Master 配合,先复位从机再复位主机; 从机串口打印 test LIN slave mode,收到数据后打印 BOOT_ExecutiveCommand 及 pData[0] 至 pData[7] 的十六进制值,并回发 8 字节 0x01 供主机校验。 |
| 使用提示 | 需先断开评估板 NS-LINK 的 MCU_TX 与 MCU_RX 跳线帽; USART2 的 TX(PA2)与 RX(PA3)要与主机交叉连接; 支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
从机把接收过程做成一个四态状态机,由断开检测中断复位状态,再由接收中断逐字节推进,凑齐一帧后交给主循环回发。
数据路径: 主机断开帧 → USART2 断开检测中断 → LIN_IRQHandler 置 LinRxState=SYNCH → LIN_EX_RxAsync 依次收 0x55、PID、8 字节数据与校验 → 置位 GotMsgFlag 并记录 pLINMsg → main 调 BOOT_ExecutiveCommand 把数据改为 0x01 → LIN_RespData/LIN_SetResp 算校验和 → LIN_SendBytes 回发
1. 配置从机并使能接收与断开检测中断。 main 打开 LIN 模式后进行两处配置:使能 USART_INT_RXDNE 与 USART_INT_LINBD 两个中断,并把 0x3C、0x3D 两个 ID 分别登记为“收数据”与“发数据”类型。
/* Enable the USARTx LIN mode */
USART_ConfigLINBreakDetectLength(USARTx, USART_LINBDL_10B);
USART_EnableLIN(USARTx, ENABLE);
/* Enable USARTx Receive and LIN break detect interrupts */
USART_ConfigInt(USARTx, USART_INT_RXDNE, ENABLE);
USART_ConfigInt(USARTx, USART_INT_LINBD, ENABLE);
/* Enable the USARTx */
USART_Enable(USARTx, ENABLE);
/* Configure slave ID mode */
IDType[MSG_RECEIVE_ID & 0x3F] = ID_TYPE_SR;
IDType[MSG_SEND_ID & 0x3F] = ID_TYPE_SW;
IDType 用 data & 0x3F 作下标,登记关系要与主机发送的 PID 对应,改帧 ID 时两端一起改。
2. 用断开检测中断复位接收状态。 断开帧到来时,LIN_IRQHandler 先判断 USART_FLAG_LINBD,清标志并读 STS、DAT 完成清零序列,再把状态置为 SYNCH 等待同步字节。
void LIN_IRQHandler(void)
{
if(USART_GetFlagStatus(USARTx, USART_FLAG_LINBD) == SET)
{
USART_ClrFlag(USARTx, USART_FLAG_LINBD);
USARTx->STS;
USARTx->DAT;
LinRxState = SYNCH;
return;
}
if (USART_GetFlagStatus(USARTx, USART_FLAG_RXDNE) == SET)
{
USART_ClrFlag(USARTx, USART_FLAG_RXDNE);
if(USART_GetFlagStatus(USARTx, USART_FLAG_FEF) == RESET)
{
LIN_EX_RxAsync((uint8_t)USART_ReceiveData(USARTx));
}
}
/* Determine if an error flag still exists and clear the error flag */
if ((USART_GetFlagStatus(USARTx, USART_FLAG_OREF) != RESET) ||
(USART_GetFlagStatus(USARTx, USART_FLAG_NEF) != RESET) ||
(USART_GetFlagStatus(USARTx, USART_FLAG_PEF) != RESET) ||
(USART_GetFlagStatus(USARTx, USART_FLAG_FEF) != RESET))
{
(void)USARTx->STS;
(void)USARTx->DAT;
}
}
USART_FLAG_LINBD 的清零序列(先读 USART_STS 再读 USART_DAT)与 RXDNE 分支的四类错误标志清理都要保留,换串口时把 USARTx 换成对应宏。
3. 四态状态机逐字节收帧。 LIN_EX_RxAsync 在 SYNCH 校验 0x55,在 ID_LEN 校验 PID 并按 IDType 决定收数据还是立即应答,在 DATA_GET 收满 8 字节后进入 CHECKSUM,凑齐后置位 GotMsgFlag。
void LIN_EX_RxAsync(uint8_t data)
{
switch(LinRxState)
{
case IDLE:
break;
case SYNCH:
if(data == 0x55)
{
LINRxDataBuffer[LINRxDataIndex].Sync = 0x55;
LinRxState = ID_LEN;
}
else
{
LinRxState = IDLE;
}
break;
case ID_LEN:
if(GET_PID(data) == data)
{
LINRxDataBuffer[LINRxDataIndex].PID = data;
LINRxDataBuffer[LINRxDataIndex].DataLen = 0;
if(IDType[data & 0x3F] == ID_TYPE_SR)
{
LinRxState = DATA_GET;
}
else
{
if(((LINTxMsg.PID & 0x3F) == (data & 0x3F)) && (LINTxMsg.DataLen > 0))
{
LIN_SendBytes(LINTxMsg.Data, LINTxMsg.DataLen + 1);
LINTxMsg.DataLen = 0;
}
LinRxState = IDLE;
}
}
else
{
LinRxState = IDLE;
}
break;
case DATA_GET:
LINRxDataBuffer[LINRxDataIndex].Data[LINRxDataBuffer[LINRxDataIndex].DataLen] = data;
LINRxDataBuffer[LINRxDataIndex].Check = data;
LINRxDataBuffer[LINRxDataIndex].DataLen++;
if(LINRxDataBuffer[LINRxDataIndex].DataLen >= 8)
{
LinRxState = CHECKSUM;
}
else
{
LinRxState = DATA_GET;
}
break;
case CHECKSUM:
LINRxDataBuffer[LINRxDataIndex].Check = data;
pLINMsg = &LINRxDataBuffer[LINRxDataIndex];
GotMsgFlag = 1;
LINRxDataIndex = (LINRxDataIndex + 1) % 2;
LinRxState = IDLE;
break;
default:
break;
}
}
数据段固定收 8 字节(DataLen >= 8 才进入校验),双缓冲下标 LINRxDataIndex 在 0 与 1 间轮转;改数据长度时这里与 LIN_EX_MSG.Data 数组大小要一起改。
4. 执行命令并用校验和回发应答帧。 主循环收到 GotMsgFlag 后调用 BOOT_ExecutiveCommand,它把 8 字节数据改成 0x01 并通过回调 LIN_RespData 交给 LIN_SetResp;LIN_SetResp 换算 PID、按经典算法算校验和并写入待发消息。
void BOOT_ExecutiveCommand(uint8_t *pData, FUN_RESP pFunResp)
{
int i = 0;
log_info("BOOT_ExecutiveCommand\r\n");
for( i = 0; i < 8; i++)
{
log_info("pData[%d] = %02x \r\n", i, pData[i]);
pData[i] = 0x1;
}
pFunResp(pData, 8);
}
void LIN_SetResp(uint8_t ID, uint8_t *pData, uint8_t Len, uint8_t CheckType)
{
uint8_t i = 0;
uint8_t CheckSum = 0;
if(Len > 8)
{
Len = 8;
}
LINTxMsg.PID = GET_PID(ID);
for(i = 0; i < Len; i++)
{
LINTxMsg.Data[i] = pData[i];
}
if(CheckType)
{
CheckSum = LIN_GetCheckSum(&LINTxMsg.PID, Len + 1);
}
else
{
CheckSum = LIN_GetCheckSum(LINTxMsg.Data, Len);
}
if(Len < 8)
{
LINTxMsg.Data[Len] = CheckSum;
}
else
{
LINTxMsg.Check = CheckSum;
}
LINTxMsg.DataLen = Len;
}
LIN_SetResp 的 CheckType 为 0 时只对数据段算校验和,GET_PID 宏负责补两个奇偶校验位;改校验和类型或 ID 时,要与主机的换算保持一致。
对应源码: src/main.c、src/lin_slave.c、inc/main.h、inc/lin_slave.h
5.21.3.13 MultiProcessor 多处理器地址唤醒
工程目录: projects/n32g41x_EVAL/examples/USART/MultiProcessor/
配置两路 USART 与地址标识唤醒→USART1 持续向 USART2 发送一字节→USART2 收到后点亮 LED1→按下 KEY1 让 USART2 进入静默模式→再次按下 KEY1 由 USART1 发地址帧唤醒
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示多处理器通信的地址标识唤醒:USART1 作发送端持续发 0x33,USART2 作接收端收到后点亮 LED1; 按 KEY1 让 USART2 进入静默模式(LED1 停止翻转),再按一次由 USART1 发地址匹配帧 0x102 唤醒。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、Delay;src/bsp_led.c、inc/bsp_led.h:LED1(PA8)定义与 LED_GPIO_Config;src/bsp_key.c、inc/bsp_key.h:KEY1(PA4)EXTI 配置;src/n32g41x_it.c、inc/n32g41x_it.h:EXTI4_IRQHandler 与其它中断服务函数;inc/main.h:USART1/USART2 与引脚宏;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USART1 为 USARTy,TX 为 PA9、RX 为 PA10; USART2 为 USARTz,TX 为 PA2、RX 为 PA3,复用号均 GPIO_AF4;波特率 115200,字长 USART_WL_9B,停止位 USART_STPB_1,校验 USART_PE_NO,流控 USART_HFCTRL_NONE,过采样 USART_16OVER;USART_SetAddr 设 USARTy 地址 0x1、USARTz 地址 0x2,USART_ConfigWakeUpMode 设 USARTz 为 USART_WUM_ADDRMASK。KEY1 为 PA4 上拉输入、EXTI4 下降沿触发; LED1 为 PA8 推挽输出。 系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 依次 RCC_Configuration、GPIO_Configuration、LED_GPIO_Config、EXTI_Key_Config,用同一 USART_InitType 配置并打开 USARTy 与 USARTz,再设地址与唤醒方式;while(1) 中 USARTy 用 USART_SendData 发 0x33 并等 USART_FLAG_TXDE,当 USARTz USART_FLAG_RXDNE 置位且收到的字节为 0x33 时,通过 LED1_ON/LED1_OFF 配合 Delay 使 LED1 亮灭。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_SetAddr、USART_ConfigWakeUpMode、USART_EnableRcvWakeUp、USART_SendData、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 运行后 LED1 持续闪烁; 按 KEY1 后 USARTz 进入静默模式,LED1 停止闪烁; 再按一次 KEY1,USARTy 发出地址帧 0x102,USARTz 被唤醒,LED1 恢复闪烁。 |
| 使用提示 | 需先断开评估板 NS-LINK 的 MCU_TX 与 MCU_RX 跳线帽; 用一根导线把 USARTy 的 TX(PA9)接到 USARTz 的 RX(PA3); PA9 的复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF0,代码已按型号区分;支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用两路 USART 演示地址标识唤醒:发送端按固定地址匹配帧唤醒接收端,接收端由按键中断在正常运行与静默之间切换。
数据路径: USARTy(PA9)发送 0x33 → USARTz(PA3)接收 → USART_FLAG_RXDNE 置位且数据为 0x33 → LED1_ON/LED1_OFF 改变 PA8 电平 → 按键 KEY1(PA4)→ EXTI4_IRQHandler 切换 ControlFlag → USART_EnableRcvWakeUp 进静默或 USART_SendData(USARTy, 0x102) 唤醒
1. 用宏选定两路串口与引脚。 inc/main.h 把两个串口、时钟、TX/RX 引脚与复用号集中定义,并对 PA9 的复用号按型号分支。
#define USARTy USART1
#define USARTy_CLK RCC_APB2_PERIPH_USART1
#define USARTy_GPIO GPIOA
#define USARTy_TxPin GPIO_PIN_9
#define USARTy_RxPin GPIO_PIN_10
#if defined(N32G412)
#define USARTy_Tx_GPIO_AF GPIO_AF4
#elif defined(N32G415)
#define USARTy_Tx_GPIO_AF GPIO_AF0
#endif
#define USARTy_Rx_GPIO_AF GPIO_AF4
#define USARTy_APBxClkCmd RCC_EnableAPB2PeriphClk
#define USARTz USART2
#define USARTz_CLK RCC_APB1_PERIPH_USART2
#define USARTz_GPIO GPIOA
#define USARTz_TxPin GPIO_PIN_2
#define USARTz_RxPin GPIO_PIN_3
#define USARTz_Tx_GPIO_AF GPIO_AF4
#define USARTz_Rx_GPIO_AF GPIO_AF4
#define USARTz_APBxClkCmd RCC_EnableAPB1PeriphClk
#endif
USARTy_Tx_GPIO_AF 用 #if defined(N32G412)/#elif defined(N32G415) 区分;换串口或引脚时改这组宏,端口配置代码不用动。
2. 配置两路串口、地址与唤醒方式。 main 用同一结构体初始化 USARTy 与 USARTz,使能后分别设置地址 0x1 与 0x2,并把 USARTz 的唤醒方式设为地址标识。
/* USARTy and USARTz configuration */
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_9B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.OverSampling = USART_16OVER;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
/* Configure USARTy and USARTz */
USART_Init(USARTy, &USART_InitStructure);
USART_Init(USARTz, &USART_InitStructure);
/* Enable USARTy and USARTz */
USART_Enable(USARTy, ENABLE);
USART_Enable(USARTz, ENABLE);
/* Set USARTy address to 0x1, USARTz address to 0x2 */
USART_SetAddr(USARTy, 0x1);
USART_SetAddr(USARTz, 0x2);
/* Configure USARTz to wake up by address mark */
USART_ConfigWakeUpMode(USARTz, USART_WUM_ADDRMASK);
地址匹配时数据为 9 位(USART_WL_9B),最高位为 1 表示地址帧;USART_WUM_ADDRMASK 决定由地址标识而非空闲帧唤醒。
3. 主循环收发并用 LED 反馈。 USARTy 每轮发一个 0x33 并等发送缓冲区空,USARTz 收到 0x33 时翻转一次 LED1 的亮灭。
while (1)
{
/* Send one byte from UARTy to UARTz */
USART_SendData(USARTy, 0x33);
/* Wait while UARTy TXE = 0 */
while(USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
if (USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) != RESET)
{
if (USART_ReceiveData(USARTz) == 0x33)
{
LED1_ON;
Delay(0x1FFFFF);
LED1_OFF;
Delay(0x1FFFFF);
}
/* Overflow data loss */
if(USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET)
{
USARTz->DAT;
}
}
}
Delay 的参数决定 LED 亮灭节奏;USARTz 的接收数据要与发送端约定的内容(0x33)一致,改数据时两端一起改。
4. 按键中断切换静默与唤醒。 每次按下 KEY1 触发一次 EXTI4_IRQHandler:ControlFlag 为 0 时清空接收数据并把 USARTz 置入静默,为 1 时由 USARTy 发地址帧 0x102,为 2 时复位标志。
void EXTI4_IRQHandler(void)
{
Delay(0x1FFF);;
if (ControlFlag == 2)
{
ControlFlag = 0;
}
if (ControlFlag == 0)
{
/* Flush DAT register and clear the UARTz RXNE flag */
USART_ReceiveData(USARTz);
/* Enable the UARTz mute mode*/
USART_EnableRcvWakeUp(USARTz, ENABLE);
ControlFlag = 1;
}
else if (ControlFlag == 1)
{
/* Send the address mark (0x102) to wakeup UARTz */
USART_SendData(USARTy, 0x102);
/* Wait while UARTy TXE = 0 */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
ControlFlag = 2;
}
/* Clear Key Button EXTI Line Pending Bit */
EXTI_ClrITPendBit(KEY1_INT_EXTI_LINE);
}
进入静默前先读走 USART_DAT 清 RXDNE,否则 USART_EnableRcvWakeUp 的写操作会被忽略;唤醒地址 0x102 的低 4 位要等于 USARTz 的 USART_SetAddr 设定值。
对应源码: src/main.c、src/n32g41x_it.c、inc/main.h、inc/bsp_led.h、inc/bsp_key.h
5.21.3.14 Polling 查询收发
工程目录: projects/n32g41x_EVAL/examples/USART/Polling/
配置 USART1 与 USART2 为查询收发→USART1 逐字节发送缓冲区 1 到 USART2→USART2 接收存到接收缓冲区 2→USART2 反向发送缓冲区 2 到 USART1→比较收发缓冲区得到两个状态变量
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USARTy(USART1)与 USARTz(USART2)之间用查询标志位实现的双向通信:先把 USARTy 的缓冲区发到 USARTz,再由 USARTz 反向发回,最后比较收发缓冲区得到两个状态量。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、Buffercmp 与收发缓冲区、计数器;inc/main.h:USART1/USART2 与引脚、时钟宏、TestStatus;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1,TX 为 PA9、RX 为 PA10,TX 复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF0;USARTz 为 USART2,TX 为 PA2、RX 为 PA3,复用号均 GPIO_AF4;波特率 115200,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,过采样 USART_16OVER,收发使能;四个 USART 引脚均配为复用推挽并上拉。 系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 先 RCC_Configuration、GPIO_Configuration,用 USART_Init 配置并 USART_Enable 两路串口;第一个 while 中 USARTy 逐字节 USART_SendData 发 TxBuffer1,USARTz 等 USART_FLAG_RXDNE 后 USART_ReceiveData 存入 RxBuffer2;第二个 while 把 TxBuffer2 从 USARTz 发到 USARTy 存入 RxBuffer1;最后用 Buffercmp 比较得到 TransferStatus1 与 TransferStatus2。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_SendData、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 编译下载、复位运行后,在调试器中依次查看 TransferStatus1 与 TransferStatus2;两者均为 PASSED 表示收发数据一致,FAILED 表示不一致。 |
| 使用提示 | 需先断开评估板 NS-LINK 的 MCU_TX 与 MCU_RX 跳线帽; 用导线把 USART1_TX(PA9)接 USART2_RX(PA3)、USART1_RX(PA10)接 USART2_TX(PA2)交叉连接; 支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例不做中断,而是用收发标志位轮询把两路串口串成一次问答,再把两个方向的接收缓冲区与发送缓冲区逐一比较。
数据路径: TxBuffer1 → USART_SendData(USARTy) → PA9 到 PA3 → USART_ReceiveData(USARTz) → RxBuffer2 → TxBuffer2 → USART_SendData(USARTz) → PA2 到 PA10 → USART_ReceiveData(USARTy) → RxBuffer1 → Buffercmp 得到 TransferStatus1、TransferStatus2
1. 配置并打开两路串口。 main 用同一结构体填入参数后分别配置并使能 USARTy 与 USARTz。
/* Configure the GPIO ports */
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.OverSampling = USART_16OVER;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
/* USARTy and USARTz configuration */
USART_Init(USARTy, &USART_InitStructure);
USART_Init(USARTz, &USART_InitStructure);
/* Configure USARTy and USARTz */
USART_Enable(USARTy, ENABLE);
USART_Enable(USARTz, ENABLE);
两路使用同一组参数,改波特率或字长时两处同时生效;引脚与复用号在 main.h 的宏里定义。
2. 查询方式的第一段收发。 USARTy 每发一字节就等 USART_FLAG_TXDE,再等 USARTz 的 USART_FLAG_RXDNE 后读出,循环直到发完缓冲区。
while (TxCounter1 < TxBufferSize1)
{
/* Send one byte from USARTy to USARTz */
USART_SendData(USARTy, TxBuffer1[TxCounter1++]);
/* Loop until USARTy DAT register is empty */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
/* Loop until the USARTz Receive Data Register is not empty */
while (USART_GetFlagStatus(USARTz, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if(USART_GetFlagStatus(USARTz, USART_FLAG_OREF) != RESET)
{
USARTz->DAT;
}
}
/* Store the received byte in RxBuffer */
RxBuffer2[RxCounter2++] = (uint8_t)USART_ReceiveData(USARTz);
}
接收等待循环里检查 USART_FLAG_OREF 并读走 USARTz->DAT,用于在溢出时丢弃数据避免卡死;改缓冲区长度时 TxBufferSize1 由数组长度自动得出。
3. 查询方式的第二段收发(反向)。 交换收发角色后,USARTz 逐字节发送 TxBuffer2,USARTy 接收存入 RxBuffer1。
while (TxCounter2 < TxBufferSize2)
{
/* Send one byte from USARTz to USARTy */
USART_SendData(USARTz, TxBuffer2[TxCounter2++]);
/* Loop until USARTz DAT register is empty */
while (USART_GetFlagStatus(USARTz, USART_FLAG_TXDE) == RESET)
{
}
/* Loop until the USARTy Receive Data Register is not empty */
while (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if(USART_GetFlagStatus(USARTy, USART_FLAG_OREF) != RESET)
{
USARTy->DAT;
}
}
/* Store the received byte in RxBuffer */
RxBuffer1[RxCounter1++] = (uint8_t)USART_ReceiveData(USARTy);
}
RxCounter1 在两段循环里分别累加,注意两段比较用的缓冲区是 RxBuffer2 与 RxBuffer1,不要对调。
4. 比较收发缓冲区。 Buffercmp 逐字节比较两个缓冲区,全部相同返回 PASSED,否则返回 FAILED。
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
while (BufferLength--)
{
if (*pBuffer1 != *pBuffer2)
{
return FAILED;
}
pBuffer1++;
pBuffer2++;
}
return PASSED;
}
比较长度取自发送缓冲区长度宏;增加数据量时要同步 TxBuffer1、TxBuffer2 及对应接收缓冲区的大小。
对应源码: src/main.c、inc/main.h
5.21.3.15 Printf 串口 printf
工程目录: projects/n32g41x_EVAL/examples/USART/Printf/
初始化 USART1 与 TX 引脚→打开 USART1→打印一行字符串→进入空循环
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示把标准输出重定向到 USART1:初始化一路串口后调用 printf 打印一行字符串,用于最小可用的串口调试输出。 |
| 源码组成 | src/main.c:main、GPIO_Configuration、RCC_Configuration 与打印语句;inc/main.h:按芯片型号条件编译的 USARTx、引脚与复用宏;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTx 为 USART1,时钟 RCC_APB2_PERIPH_USART1;只配置 TX 引脚 PA9,配为复用推挽并上拉,复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF0;波特率 115200,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,流控 USART_HFCTRL_NONE,过采样 USART_16OVER,收发使能。系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 先 RCC_Configuration、GPIO_Configuration,填好 USART_InitType 后由 USART_Init 配置 USART1、USART_Enable 使能,随后调用 printf("This is a Printf demo of USART.\n\r") 输出,再进入空 while(1)。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable |
| 验证方法 | 编译下载、复位运行后,在 PC 端串口工具以 115200-8-N-1 接收,可看到一行 This is a Printf demo of USART.。 |
| 使用提示 | 该示例未在 readme 中要求断开跳线帽; 串口接收端接 USART1_TX(PA9); 支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例用型号相关的宏选出串口与 TX 引脚,只配置发送方向,再靠标准库的 printf 输出一行字符串。
数据路径: main → RCC_Configuration 打开 GPIO 与 USART1 时钟 → GPIO_Configuration 把 PA9 配为复用推挽 → USART_Init 写入 USARTx 参数 → USART_Enable → printf 经 USART1 的 TX 发出
1. 按型号选择串口与引脚宏。 inc/main.h 用 #if defined(N32G412)/#elif defined(N32G415) 给出 USARTx、时钟、TX/RX 引脚与复用号,二者仅 TX 复用号不同。
#if defined(N32G412)
#define USARTx USART1
#define USARTx_CLK RCC_APB2_PERIPH_USART1
#define USARTx_CLK_ENABLE() RCC_EnableAPB2PeriphClk(USARTx_CLK, ENABLE)
#define USARTx_GPIO GPIOA
#define USARTx_GPIO_CLK RCC_AHB_PERIPH_GPIO
#define USARTx_TX_PIN GPIO_PIN_9
#define USARTx_RX_PIN GPIO_PIN_10
#define USARTx_TX_AF GPIO_AF4
#define USARTx_RX_AF GPIO_AF4
#elif defined(N32G415)
#define USARTx USART1
#define USARTx_CLK RCC_APB2_PERIPH_USART1
#define USARTx_CLK_ENABLE() RCC_EnableAPB2PeriphClk(USARTx_CLK, ENABLE)
#define USARTx_GPIO GPIOA
#define USARTx_GPIO_CLK RCC_AHB_PERIPH_GPIO
#define USARTx_TX_PIN GPIO_PIN_9
#define USARTx_RX_PIN GPIO_PIN_10
#define USARTx_TX_AF GPIO_AF0
#define USARTx_RX_AF GPIO_AF4
#else
#error "Unsupported target. Please use N32G412 or N32G415."
#endif
换到其它串口或引脚时改这组宏;两个分支都定义了 USARTx、USARTx_CLK、USARTx_TX_PIN 等,缺一不可。
2. 只配置 TX 引脚为复用推挽。 GPIO_Configuration 把 USARTx_TX_PIN 配为 GPIO_MODE_AF_PP、上拉并写入复用号,没有配置 RX。
void GPIO_Configuration(void)
{
GPIO_InitType gpio_init;
GPIO_InitStruct(&gpio_init);
gpio_init.Pin = USARTx_TX_PIN;
gpio_init.GPIO_Mode = GPIO_MODE_AF_PP;
gpio_init.GPIO_Alternate = USARTx_TX_AF;
gpio_init.GPIO_Pull = GPIO_PULL_UP;
GPIO_InitPeripheral(USARTx_GPIO, &gpio_init);
}
示例只用于发送,未配置 RX 引脚;若需要接收,要按同样方式补上 RX 引脚的配置。
3. 配置 USART1 并打印。 main 填入 115200-8-N-1 与 16 倍过采样后使能串口,再调用 printf 输出。
USART_StructInit(&usart_init);
usart_init.BaudRate = 115200;
usart_init.WordLength = USART_WL_8B;
usart_init.StopBits = USART_STPB_1;
usart_init.Parity = USART_PE_NO;
usart_init.Mode = USART_MODE_RX | USART_MODE_TX;
usart_init.HardwareFlowControl = USART_HFCTRL_NONE;
usart_init.OverSampling = USART_16OVER;
USART_Init(USARTx, &usart_init);
USART_Enable(USARTx, ENABLE);
printf("This is a Printf demo of USART.\n\r");
printf 的重定向由工程里的标准库支持实现;改波特率时 PC 端串口工具要同步设置,否则收到乱码。
对应源码: src/main.c、inc/main.h
5.21.3.16 SmartCard 智能卡 T=0 通信
工程目录: projects/n32g41x_EVAL/examples/USART/SmartCard/
初始化系统与日志→配置 USART3 智能卡模式与复位引脚→上电并等待卡复位应答→解析 ATR 并协商 PPS→用 DMA 收发 APDU 命令→打印随机数结果并热复位
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART3 的 SmartCard 模式按 T=0 协议与 PSAM 卡交互:接收 ATR、协商 PPS、执行获取随机数 APDU 命令,并支持热复位; PPS 阶段用 DMA 加速数据收发。 |
| 源码组成 | src/main.c:main、System_Init、Test_PSAM、SC__HotReset_handle;src/smartcard.c:SC_Init、SC_Handler 状态机与 SC_PPSConfig、SC_SendData 等收发流程函数;inc/main.h;inc/smartcard.h:USART3/DMA/复位引脚宏、SC_ADPU_* 与 SC_ATR 等结构;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | SC_USART 为 USART3,TX 为 PB10、CK 为 PB12,复用号均 GPIO_AF0,时钟 RCC_APB1_PERIPH_USART3;复位脚为 PB9 推挽输出; 智能卡模式下字长 USART_WL_9B、停止位 USART_STPB_1_5、偶校验 USART_PE_EVEN、16 倍过采样、无流控,时钟 USART_CLK_ENABLE、极性 USART_CLKPOL_LOW、相位 USART_CLKPHA_1EDGE、末位时钟 USART_CLKLB_ENABLE,USART_SetPrescaler 与 USART_SetGuardTime 由 SC_InitCfg 传入;DMA 用 DMA_CH5(TX)与 DMA_CH6(RX)。调试输出为 USART1(PA9/PA10)115200。 |
| 程序与数据路径 | main 先 System_Init,再 Test_PSAM 进入 SC_Handler 状态机:SC_POWER_ON→SC_RESET_LOW 由 SC_AnswerReq 拉低再拉高复位脚并收 ATR,SC_ACTIVE 由 SC_decode_Answer2reset 解析,SC_ACTIVE_ON_T0 调 SC_SendData 发 APDU;随后调用 SC_PPSConfig 协商速率,发获取随机数命令并打印返回的 SC_Responce.Data、SW1、SW2,最后 SC__HotReset_handle 与 SC_DeInit。 |
| 主要 API | USART_Init、USART_Enable、USART_EnableSmartCard、USART_SetSmartCardNACK、USART_SetGuardTime、USART_SetPrescaler、USART_ClockInit、USART_ConfigInt、USART_EnableDMA、USART_SendData、USART_ReceiveData、DMA_Init、DMA_EnableChannel |
| 验证方法 | 连接 PSAM 智能卡模块后运行,USART1 以 115200 输出调试信息:打印 Pclk1、Clk_Div 与波特率,ATR 各字节、PTS Confirm success 与协商后波特率,获取随机数成功时打印 get rand success! 和随机数字节,SW1、SW2 为 0x90、0x00。 |
| 使用提示 | SmartCard 使用 USART3(APB1),调试输出使用 USART1(APB2); 把 PB10(TX)接卡 I/O、PB12(CK)接卡 CLK、PB9 接卡 RST; 时钟分频值 Clk_Div 需根据实际 PCLK1 频率调整以满足卡要求;支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例把智能卡交互组织成分层结构:SC_Init 配置底层时钟与帧格式,状态机负责复位与 ATR,PPS 阶段用 DMA 完成速率协商。
数据路径: main → SC_Handler 状态机 → SC_Init 配置 USART3 与 CK/PB9 → SC_AnswerReq 收 ATR → SC_decode_Answer2reset 解析 → SC_PPSConfig 用 DMA_CH5/DMA_CH6 协商速率 → SC_SendData 发 APDU 并收 SC_Responce
1. 用宏选定智能卡串口、引脚与 DMA。 inc/smartcard.h 把 USART3、CK/TX 引脚、复位脚与 TX/RX 的 DMA 通道、标志、重映射集中定义。
/* USART3 on PB10(TX)/PB12(CK), both AF0 for G412 and G415 */
#define SC_USART USART3
#define SC_USART_CLK RCC_APB1_PERIPH_USART3
#define SC_USART_GPIO GPIOB
#define SC_USART_GPIO_CLK RCC_AHB_PERIPH_GPIO
#define SC_USART_PIN_TX GPIO_PIN_10
#define SC_USART_Tx_GPIO_AF GPIO_AF0
#define SC_USART_CK_GPIO GPIOB
#define SC_USART_GPIO_CK_CLK RCC_AHB_PERIPH_GPIO
#define SC_USART_PIN_CK GPIO_PIN_12
#define SC_USART_Clk_GPIO_AF GPIO_AF0
#define SC_USART_APBxClkCmd RCC_EnableAPB1PeriphClk
#define SC_USART_IRQn USART3_IRQn
#define SC_USART_IRQHandler USART3_IRQHandler
#define SC_USART_DMAx DMA
#define SC_USART_DMAx_CLK RCC_AHB_PERIPH_DMA
#define SC_USART_DR_Base (USART3_BASE + 0x10)
#define SC_USART_Tx_DMA_Channel DMA_CH5
#define SC_USART_Tx_DMA_FLAG DMA_FLAG_TC5
#define SC_USART_Tx_DMA_REMAP DMA_REMAP_USART3_TX
#define SC_USART_Rx_DMA_Channel DMA_CH6
#define SC_USART_Rx_DMA_FLAG DMA_FLAG_TC6
#define SC_USART_Rx_DMA_REMAP DMA_REMAP_USART3_RX
TX 用 DMA_CH5、RX 用 DMA_CH6,与 DMA_REMAP_USART3_TX/DMA_REMAP_USART3_RX 成对;换串口或 DMA 通道时这几组宏要一起改。
2. 配置智能卡时钟与帧格式。 SC_Init 用 USART_SetPrescaler/USART_SetGuardTime 设 CK 分频与保护时间,配好同步时钟参数后按 SC_InitCfg 选停止位与校验,再由 USART_Init 写入。
/* SC_USART Clock set to (PCLK1 / Clk_Div) */
USART_SetPrescaler(SC_USART, SC_InitCfg->Clk_Div >> 1);
/* USART Guard Time set to 16 Bit */
USART_SetGuardTime(SC_USART, SC_InitCfg->GT);
USART_ClockStructInit(&USART_ClockInitStructure);
USART_ClockInitStructure.Clock = USART_CLK_ENABLE;
USART_ClockInitStructure.Polarity = USART_CLKPOL_LOW;
USART_ClockInitStructure.Phase = USART_CLKPHA_1EDGE;
USART_ClockInitStructure.LastBit = USART_CLKLB_ENABLE;
USART_ClockInit(SC_USART, &USART_ClockInitStructure);
RCC_GetClocksFreqValue(&RCC_ClocksStatus);
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = RCC_ClocksStatus.Pclk1Freq / (372 * ((SC_USART->GTP & (uint16_t)0x00FF) * 2));
log_info(" Pclk1 =%d,Clk_Div = %d,bps =%d\r\n", RCC_ClocksStatus.Pclk1Freq, (372 * ((SC_USART->GTP & (uint16_t)0x00FF) *2)),USART_InitStructure.BaudRate);
USART_InitStructure.WordLength = USART_WL_9B;
if(SC_InitCfg->StopBits == 1)
{
USART_InitStructure.StopBits = USART_STPB_0_5;
}
else if(SC_InitCfg->StopBits == 3)
{
USART_InitStructure.StopBits = USART_STPB_1_5;
}
else
{
log_info("\r\n StopBits value is error !!! \r\n");
}
if(SC_InitCfg->Parity == 1)
{
USART_InitStructure.Parity = USART_PE_EVEN;
}
else if(SC_InitCfg->Parity == 2)
{
USART_InitStructure.Parity = USART_PE_ODD;
}
else
{
log_info("\r\n Parity value is error !!! \r\n");
}
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.OverSampling = USART_16OVER;
USART_Init(SC_USART, &USART_InitStructure);
Clk_Div 先右移一位再写入预分频,实际分频为设定值的两倍;波特率由 Pclk1 与 CK 分频值算出,改 Clk_Div 时波特率随之变化。
3. 使能中断、NACK 与智能卡接口。 SC_Init 末尾使能校验与错误中断,按 NackEn 选择是否发送 NACK,再打开智能卡接口并把复位脚置高。
/* Enable SC_USART */
USART_Enable(SC_USART, ENABLE);
/* Enable the NACK Transmission */
if(SC_InitCfg->NackEn == 0)
{
USART_SetSmartCardNACK(SC_USART, DISABLE);
}
else if(SC_InitCfg->NackEn == 1)
{
USART_SetSmartCardNACK(SC_USART, ENABLE);
}
else
{
log_info("\r\n NACK Enable value is error !!! \r\n");
}
/* Enable the Smartcard Interface */
USART_EnableSmartCard(SC_USART, ENABLE);
/* Set RSTIN HIGH */
SC_Reset(Bit_SET);
}
USART_EnableSmartCard 是进入智能卡模式的关键一步;SC_Reset(Bit_SET) 把 PB9 置高,接线时要确认该脚连到卡的 RST。
4. 用 DMA 协商 PPS。 SC_PPSConfig 按卡的 ATR 组装 PPSS/PPS0/PPS1/PCK,经 DMA_Configuration 用 TX/RX 通道收发,比较 4 字节一致后重算波特率并禁用 USART 后重配。
// /* Send PPSS */
TxBuffer1[0] = 0xFF;
// /* Send PPS0 */
TxBuffer1[1] = 0x10;
// /* PPS1~3 */
TxBuffer1[2] = SC_A2R.T[0];
// /* PCK */
TxBuffer1[3] = (uint8_t)0xFF ^ (uint8_t)0x10 ^ (uint8_t)SC_A2R.T[0];
DMA_Configuration(TxBuffer1, 4, RxBuffer1, 4);
/* Enable USARTy DMA Rx and TX request */
USART_EnableDMA(SC_USART, USART_DMAREQ_RX | USART_DMAREQ_TX, ENABLE);
/* Enable USARTy TX DMA Channel */
DMA_EnableChannel(SC_USART_Tx_DMA_Channel, ENABLE);
/* Enable USARTy RX DMA Channel */
DMA_EnableChannel(SC_USART_Rx_DMA_Channel, ENABLE);
/* Wait until USARTy TX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(SC_USART_Tx_DMA_FLAG, SC_USART_DMAx) == RESET)
{
}
DMA_ClearFlag(SC_USART_Tx_DMA_FLAG, SC_USART_DMAx);
/* Wait until USARTy RX DMA Channel Transfer Complete */
while (DMA_GetFlagStatus(SC_USART_Rx_DMA_FLAG, SC_USART_DMAx) == RESET)
{
}
DMA_ClearFlag(SC_USART_Rx_DMA_FLAG, SC_USART_DMAx);
log_info(" locData =%02x %02x %02x %02x\r\n", RxBuffer1[0],RxBuffer1[1],RxBuffer1[2],RxBuffer1[3]);
PPS 交换依赖 DMA 传输完成标志轮询;改波特率前要 USART_Enable(DISABLE),因为 USART_BRCF 与过采样位只在 USART 关闭时可写;收到协商失败时示例改走热复位分支。
对应源码: src/main.c、src/smartcard.c、inc/main.h、inc/smartcard.h
5.21.3.17 Synchronous 同步模式与 SPI 从机
工程目录: projects/n32g41x_EVAL/examples/USART/Synchronous/
配置 USART1 同步模式与 SPI1 从机→USART1 发送缓冲区到 SPI1 并接收→SPI1 发送缓冲区并由 USART1 发空字节产生时钟→USART1 接收 SPI1 数据→比较两组收发缓冲区
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1 同步模式(输出时钟)与 SPI1 从机之间的双向通信:USART1 先发数据给 SPI1,再从 SPI1 收数据,最后比较两组收发缓冲区。 |
| 源码组成 | src/main.c:main、RCC_Configuration、GPIO_Configuration、SPI_Configuration、Buffercmp 与收发缓冲区;inc/main.h:USART1/SPI1 与引脚、复用宏、TestStatus;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1,TX 为 PA9、RX 为 PA10、CK 为 PA8; SPIy 为 SPI1,SCK 为 PA5、MISO 为 PA6、MOSI 为 PA7,复用号均 GPIO_AF0;USART1 配为 115200-8-N-1、16 倍过采样,时钟 USART_CLK_ENABLE、极性 USART_CLKPOL_LOW、相位 USART_CLKPHA_2EDGE、末位时钟 USART_CLKLB_ENABLE;SPI1 为从模式, SPI_MODE_SLAVE、8 位、SPI_CLKPOL_LOW、SPI_CLKPHA_SECOND_EDGE、SPI_NSS_SOFT、LSB 在前。系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 用 USART_ClockInit 配同步时钟、USART_Init 配 USART1 后 USART_Enable;第一段循环把 TxBuffer1 逐字节发出、等 SPI_I2S_RNE_FLAG 后存入 RxBuffer2;第二段从 SPI1 发 TxBuffer2,USART1 同时发哑字节 DYMMY_BYTE 产生时钟,等 USART_FLAG_RXDNE 后存入 RxBuffer1;最后 Buffercmp 比较得到 TransferStatus1、TransferStatus2。 |
| 主要 API | USART_ClockStructInit、USART_ClockInit、USART_StructInit、USART_Init、USART_Enable、USART_SendData、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 编译下载、复位运行后,在调试器中依次查看 TransferStatus1 与 TransferStatus2;两者均为 PASSED 表示双向数据一致,FAILED 表示不一致。 |
| 使用提示 | 需先断开评估板 NS-LINK 的 MCU_TX 与 MCU_RX 跳线帽,并拔除串口打印跳帽; 用导线把 USART1_TX(PA9)接 SPI1_MOSI(PA7)、USART1_RX(PA10)接 SPI1_MISO(PA6)、USART1_CK(PA8)接 SPI1_SCK(PA5); PA8 的复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF5;支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例让 USART1 以同步主模式输出时钟、SPI1 以从模式配合,两个方向都用查询标志位收发,最后比较两组缓冲区。
数据路径: TxBuffer1 → USART_SendData(USARTy) → TX 与 CK 到 SPI1 → SPI_I2S_ReceiveData(SPIy) → RxBuffer2 → SPI_I2S_TransmitData(SPIy) → USART_SendData(USARTy, DYMMY_BYTE) 产生时钟 → USART_ReceiveData(USARTy) → RxBuffer1 → Buffercmp 得到两个 TransferStatus
1. 用宏按型号选定 USART1 与 SPI1 引脚。 inc/main.h 把 USART1 与 SPI1 的引脚、复用号集中定义,其中 USART1 的 TX 与 CK 复用号按型号分支。
#ifdef _USART1_SPI1_
#define USARTy USART1
#define USARTy_CLK RCC_APB2_PERIPH_USART1
#define USARTy_GPIO GPIOA
#define USARTy_RxPin GPIO_PIN_10
#define USARTy_TxPin GPIO_PIN_9
#define USARTy_ClkPin GPIO_PIN_8
#if defined(N32G412)
#define USARTy_Tx_GPIO_AF GPIO_AF4
#define USARTy_Clk_GPIO_AF GPIO_AF4
#elif defined(N32G415)
#define USARTy_Tx_GPIO_AF GPIO_AF0
#define USARTy_Clk_GPIO_AF GPIO_AF5
#endif
#define USARTy_Rx_GPIO_AF GPIO_AF4
#define USARTy_APBxClkCmd RCC_EnableAPB2PeriphClk
#define SPIy SPI1
#define SPIy_CLK RCC_APB2_PERIPH_SPI1
#define SPIy_GPIO GPIOA
#define SPIy_SCKPin GPIO_PIN_5
#define SPIy_MISOPin GPIO_PIN_6
#define SPIy_MOSIPin GPIO_PIN_7
#define SPIy_SCK_GPIO_AF GPIO_AF0
#define SPIy_MISO_GPIO_AF GPIO_AF0
#define SPIy_MOSI_GPIO_AF GPIO_AF0
#define SPIy_APBxClkCmd RCC_EnableAPB2PeriphClk
#endif
SPIy_MOSIPin 与 USARTy_TxPin、USARTy_ClkPin 与 SPIy_SCKPin 要在硬件上相连;改引脚时这组宏与接线一起改。
2. 配置 USART1 的同步时钟与帧格式。 main 先用 USART_ClockInit 设定极性、相位与末位时钟,再用 USART_Init 写入 115200-8-N-1。
USART_ClockStructInit(&USART_ClockInitStructure);
USART_ClockInitStructure.Clock = USART_CLK_ENABLE;
USART_ClockInitStructure.Polarity = USART_CLKPOL_LOW;
USART_ClockInitStructure.Phase = USART_CLKPHA_2EDGE;
USART_ClockInitStructure.LastBit = USART_CLKLB_ENABLE;
USART_ClockInit(USARTy, &USART_ClockInitStructure);
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.OverSampling = USART_16OVER;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
/* Configure USARTy */
USART_Init(USARTy, &USART_InitStructure);
/* Enable the USARTy */
USART_Enable(USARTy, ENABLE);
USART_CLKPHA_2EDGE 与 SPI1 的 SPI_CLKPHA_SECOND_EDGE 对应,两边相位要一致;同步模式下 CK 只在发送器被激活并有数据写入时输出时钟。
3. 把 SPI1 配为从模式。 SPI_Configuration 用 SPI_Init 填入从模式、8 位、CPOL/CPHA、软件 NSS 与 LSB 在前,随即使能 SPI1。
void SPI_Configuration(void)
{
SPI_InitType SPI_InitStructure;
/* Initialize SPI_InitStructure */
SPI_InitStruct(&SPI_InitStructure);
SPI_I2S_DeInit(SPIy);
/* SPIy Config */
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
SPI_InitStructure.FirstBit = SPI_FB_LSB;
/* Configure SPIy */
SPI_Init(SPIy, &SPI_InitStructure);
/* SPIy enable */
SPI_Enable(SPIy, ENABLE);
}
SPI1 的 CPOL/CPHA 与首末位顺序要与 USART1 同步参数一致;SPI_NSS_SOFT 表示由软件管理从设备选择。
4. 两个方向查询收发并比较。 第一段 USART1 发、SPI1 收;第二段 SPI1 发、USART1 发哑字节产生时钟后收,最后比较两组缓冲区。
while(NbrOfDataToRead2--)
{
/* Write one byte in the USARTy Transmit Data Register */
USART_SendData(USARTy, TxBuffer1[TxCounter1++]);
/* Wait until end of transmit */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXC) == RESET)
{
}
/* Wait the byte is entirely received by SPIy */
while (SPI_I2S_GetStatus(SPIy, SPI_I2S_RNE_FLAG) == RESET)
{
}
/* Store the received byte in the RxBuffer2 */
RxBuffer2[RxCounter2++] = SPI_I2S_ReceiveData(SPIy);
}
while (NbrOfDataToRead1--)
{
/* Wait until end of transmit */
while (SPI_I2S_GetStatus(SPIy, SPI_I2S_TE_FLAG) == RESET)
{
}
/* Write one byte in the SPIy Transmit Data Register */
SPI_I2S_TransmitData(SPIy, TxBuffer2[TxCounter2++]);
/* Send a Dummy byte to generate clock to slave */
USART_SendData(USARTy, DYMMY_BYTE);
/* Wait until end of transmit */
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXC) == RESET)
{
}
/* Wait the byte is entirely received by USARTy */
while (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) == RESET)
{
/* Overflow data loss */
if(USART_GetFlagStatus(USARTy, USART_FLAG_OREF) != RESET)
{
USARTy->DAT;
}
}
/* Store the received byte in the RxBuffer1 */
RxBuffer1[RxCounter1++] = USART_ReceiveData(USARTy);
}
第二段里 USART1 发送的 DYMMY_BYTE 只用于产生 CK 时钟、不是有效数据;收发长度由两组缓冲区长度宏自动得出,改数据量时缓冲区与长度一起改。
对应源码: src/main.c、inc/main.h
5.21.3.18 WakeUpFromSleep 从 SLEEP 唤醒
工程目录: projects/n32g41x_EVAL/examples/USART/WakeUpFromSleep/
配置 USART1 为 115200 收发→每秒发送一个递增字节→收到 0x55 时开接收中断并进 SLEEP→接收中断经 NVIC 唤醒 CPU→关接收中断继续发送
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示用 USART1 的 RXDNE 中断把 MCU 从 SLEEP 模式唤醒:正常运行时每秒发送一个递增字节,收到 0x55 后进入 SLEEP,再收到任意数据时被中断唤醒并恢复发送。 |
| 源码组成 | src/main.c:main、GPIO_Configuration、NVIC_Configuration 与引脚配置;inc/main.h:USART1 与引脚宏、USART1_IRQn、全局变量声明;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | USARTy 为 USART1,TX 为 PA9、RX 为 PA10,TX 复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF0;波特率 115200,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,过采样 USART_16OVER,收发使能;SLEEP 用 PWR_EnterSLEEPMode(PWR_SLEEP_NOW, PWR_SLEEPENTRY_WFI) 进入;USART1 的 NVIC 抢占与子优先级均为 0,中断号为 USART1_IRQn。系统时钟 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 打开 USART1 与 GPIO 时钟,执行 GPIO_Configuration、NVIC_Configuration,用 USART_Init/USART_Enable 配置并使能 USART1;while(1) 中每 SysTick_Delay_Ms(1000) 后判断:若 USART_FLAG_RXDNE 置位且读回 0x55,则使能 USART_INT_RXDNE 中断并用 PWR_EnterSLEEPMode 进 SLEEP,唤醒后关闭该中断;否则 USART_SendData 发送 cnt_temp 低 8 位并等 USART_FLAG_TXDE。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_ConfigInt、USART_SendData、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 复位运行后,串口工具以 115200-8-N-1 每秒收到一个递增字节; 下发 0x55 后打印暂停(进入 SLEEP),再下发任意数据后打印恢复。 |
| 使用提示 | 外部连线 USART1_TX(PA9)接 PC 的 RX、USART1_RX(PA10)接 PC 的 TX; SLEEP 模式下所有时钟保持运行,唤醒后无需重新配置系统时钟; 支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例在正常运行与 SLEEP 之间用一个接收标志切换:平时每秒发一个字节,收到约定的 0x55 后打开接收中断并进入 SLEEP,靠该中断把 CPU 唤醒。
数据路径: main → GPIO_Configuration/NVIC_Configuration → USART_Init/USART_Enable → 主循环每 1 s USART_SendData 发递增字节 → 收到 0x55 → USART_ConfigInt(USART_INT_RXDNE, ENABLE) → PWR_EnterSLEEPMode → RXDNE 中断经 NVIC 唤醒 → 关闭中断继续发送
1. 用宏选定 USART1 与引脚。 inc/main.h 把 USART1、时钟、TX/RX 引脚与复用号定义为一组宏,并声明唤醒相关的全局变量。
#define USARTy USART1
#define USARTy_CLK RCC_APB2_PERIPH_USART1
#define USARTy_GPIO GPIOA
#define USARTy_TxPin GPIO_PIN_9
#define USARTy_RxPin GPIO_PIN_10
#if defined(N32G412)
#define USARTy_Tx_GPIO_AF GPIO_AF4
#elif defined(N32G415)
#define USARTy_Tx_GPIO_AF GPIO_AF0
#endif
#define USARTy_Rx_GPIO_AF GPIO_AF4
#define USARTy_APBxClkCmd RCC_EnableAPB2PeriphClk
#define USARTy_IRQn USART1_IRQn
USARTy_Tx_GPIO_AF 按型号区分;USARTy_IRQn 与中断向量表里的 USART1 中断号对齐,换串口时要一并改。
2. 配置 USART1 为 115200-8-N-1。 main 填入固定参数后由 USART_Init 配置并 USART_Enable 使能。
/* USART configuration: 115200-8-N-1 */
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.OverSampling = USART_16OVER;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
/* Initialize and enable USART1 */
USART_Init(USARTy, &USART_InitStructure);
USART_Enable(USARTy, ENABLE);
串口参数固定为 115200-8-N-1;改波特率时 PC 端串口工具要同步设置。
3. 主循环收发与进出 SLEEP。 每 1 s 处理一次:收到 0x55 就开接收中断并进 SLEEP,否则发送一个递增字节。
while (1)
{
/* Insert a delay */
SysTick_Delay_Ms(1000);
if (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) == SET)
{
if (USART_ReceiveData(USARTy) == 0x55)
{
/* Enable USART1 RXDNE interrupt for wakeup */
USART_ConfigInt(USARTy, USART_INT_RXDNE, ENABLE);
/* Enter SLEEP mode with WFI (SLEEP-NOW mode)
CPU stops, all peripherals keep running.
Any NVIC interrupt (here USART1 RXDNE) will wake up the CPU. */
PWR_EnterSLEEPMode(PWR_SLEEP_NOW, PWR_SLEEPENTRY_WFI);
/* --- Woken up from SLEEP mode by USART1 RXDNE interrupt --- */
/* Disable USART1 RXDNE interrupt (no longer needed for wakeup) */
USART_ConfigInt(USARTy, USART_INT_RXDNE, DISABLE);
WakeUpFlag = 0;
}
}
else
{
USART_SendData(USARTy, (uint8_t)(cnt_temp++ & 0xFF));
while (USART_GetFlagStatus(USARTy, USART_FLAG_TXDE) == RESET)
{
}
}
/* Clear overrun error if occurred */
if (USART_GetFlagStatus(USARTy, USART_FLAG_OREF) != RESET)
{
(void)USARTy->DAT;
}
}
进 SLEEP 用 PWR_SLEEP_NOW 与 WFI,任何 NVIC 中断都能唤醒;若改为其它低功耗模式,唤醒后可能需重新配置系统时钟,这里的 SLEEP 不需要。
对应源码: src/main.c、inc/main.h
5.21.3.19 WakeUpFromStop 从 STOP 唤醒
工程目录: projects/n32g41x_EVAL/examples/USART/WakeUpFromStop/
复位后点亮 LED1 五秒作为下载窗口→配置 USART1 低功耗唤醒并切换到 LSI→进 STOP 等待接收→收到一帧经 EXTI 唤醒退出 STOP→重配 GPIO 与 USART1 并点亮 LED1 一秒
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示 USART1 低功耗唤醒事件把 MCU 从 STOP 模式唤醒:STOP 下系统时钟关闭、USART1 用 LSI 保持工作,收到一帧数据后产生唤醒事件经 EXTI24 退出 STOP,唤醒后重配并点亮 LED1 一秒。 |
| 源码组成 | src/main.c:main、LED1_Init、GPIO_Configuration、USART1_PrepareStopWakeup、USART1_RestoreAfterStop;inc/main.h:USART1 与引脚宏、USARTy_WKUP_EXTI_LINE;src/n32g41x_it.c、inc/n32g41x_it.h:USART1_IRQHandler 与其它中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程,LED 引脚宏来自 bsp/inc/n32g41x_stb.h。 |
| 关键配置 | USARTy 为 USART1,TX 为 PA9、RX 为 PA10,TX 复用号在 N32G412 为 GPIO_AF4、N32G415 为 GPIO_AF0;波特率 1200,字长 USART_WL_8B,停止位 USART_STPB_1,校验 USART_PE_NO,过采样 USART_16OVER;USART_SetPrescaler 未改,唤醒时用 RCC_ConfigUsart1Clk 切到 RCC_USART1_CLKSEL_LSI 并写 USARTy->BRCF = 0x1AU;USART_CfgWakeUpDataClr 设为 USART_WAKEUP_NOTCLRDATA,USART_EnableWakeUp 使能唤醒,EXTI 用 EXTI_LINE24 上升沿;低功耗时钟 RCC_ConfigLowPowerClk(RCC_LP_CLKSEL_LSI)。 |
| 程序与数据路径 | main 打开 USART1 与 GPIO 时钟,GPIO_Configuration、LED1_Init 后点亮 LED1 保持 5 s 再熄灭;使能 LSI 并选低功耗时钟、配 NVIC 与 EXTI24,进入 while(1):先熄灭 LED1,USART1_PrepareStopWakeup 把 USART1 切到 APB2、配唤醒与 BRCF 后切到 LSI,PWR_EnterSTOPMode 进入 STOP;USART1_IRQHandler 置位 UsartWakeupFlag,main 检查后 USART1_RestoreAfterStop 重配并点亮 LED1 一秒。 |
| 主要 API | USART_StructInit、USART_Init、USART_Enable、USART_ConfigInt、USART_EnableWakeUp、USART_CfgWakeUpDataClr、USART_GetFlagStatus、USART_ReceiveData |
| 验证方法 | 复位后 LED1 亮 5 s 再熄灭; 把串口工具设为 1200-8-N-1 下发 0x55,MCU 退出 STOP 且 LED1 亮 1 s,随后 LED1 熄灭、MCU 再次进入 STOP 循环。 |
| 使用提示 | 外部连线 USART1_TX(PA9)接 PC 的 RX、USART1_RX(PA10)接 PC 的 TX; 低功耗唤醒只需按 1200 波特率发送; 写 USART_BRCF 前要保证 USART 处于关闭状态;支持 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7; 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。 |
核心代码解读与移植
示例围绕 STOP 的进出组织代码:进入前把 USART1 从 APB2 切到 LSI 并打开唤醒,退出后重新切回 APB2 并恢复,两段配置严格对称。
数据路径: main → LED1 亮 5 s → 使能 LSI 并选低功耗时钟 → 配 NVIC 与 EXTI24 → USART1_PrepareStopWakeup 切 APB2、配唤醒与 BRCF、切 LSI → PWR_EnterSTOPMode → USART1_IRQHandler 置 UsartWakeupFlag → USART1_RestoreAfterStop → LED1 亮 1 s
1. 用宏定义串口、引脚与唤醒 EXTI 线。 inc/main.h 给出 USART1、TX/RX 引脚与复用宏,并把唤醒事件绑定到 EXTI_LINE24。
#define USARTy USART1
#define USARTy_CLK RCC_APB2_PERIPH_USART1
#define USARTy_GPIO GPIOA
#define USARTy_TxPin GPIO_PIN_9
#define USARTy_RxPin GPIO_PIN_10
#if defined(N32G412)
#define USARTy_Tx_GPIO_AF GPIO_AF4
#elif defined(N32G415)
#define USARTy_Tx_GPIO_AF GPIO_AF0
#endif
#define USARTy_Rx_GPIO_AF GPIO_AF4
#define USARTy_APBxClkCmd RCC_EnableAPB2PeriphClk
#define USARTy_WKUP_EXTI_LINE EXTI_LINE24
extern volatile uint8_t UsartWakeupFlag;
USARTy_WKUP_EXTI_LINE 为 EXTI_LINE24,与 USART1_IRQn 共用同一个中断向量;换串口时这条 EXTI 线要随手册对应关系一并改。
2. 进入 STOP 前的低功耗配置。 USART1_PrepareStopWakeup 先关闭 USART,切到 APB2 时钟重配,读走残留接收数据,打开唤醒与 RXDNE 中断后写 BRCF,最后切到 LSI。
static void USART1_PrepareStopWakeup(void)
{
/* Configure USART on APB2, then make LSI the last clock-source change. */
/* Reconfigure only while USART1 is clocked from APB2. */
/* requires UEN=0: CTRL1.OSPM and BRCF are only writable while the
USART is disabled, so a running port must be quiesced first. The baud rate
change also aborts the frame being transmitted/received. */
USART_Enable(USARTy, DISABLE);
RCC_ConfigUsart1Clk(RCC_USART1_CLKSEL_APB2);
USARTy_APBxClkCmd(USARTy_CLK, ENABLE);
USART_Init(USARTy, &USART_InitStructure);
if (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) != RESET)
{
(void)USART_ReceiveData(USARTy);
}
/* The ISR sets this flag after the WFI wakeup interrupt occurs. */
UsartWakeupFlag = 0U;
USART_CfgWakeUpDataClr(USARTy, USART_WAKEUP_NOTCLRDATA);
USART_EnableWakeUp(USARTy, ENABLE);
/* RXDNE is the interrupt source that wakes WFI through EXTI24. */
USART_ConfigInt(USARTy, USART_INT_RXDNE, ENABLE);
USARTy->BRCF = 0x1AU;
EXTI_ClrITPendBit(USARTy_WKUP_EXTI_LINE);
USART_Enable(USARTy, ENABLE);
RCC_ConfigUsart1Clk(RCC_USART1_CLKSEL_LSI);
}
USART_BRCF 与过采样位只在 USART 关闭(UEN=0)时可写,所以这段在 USART_Enable(DISABLE) 之后进行;USARTy->BRCF = 0x1AU 是按 LSI 频率算出的分频值,改波特率或时钟源时要重算。
3. 唤醒后恢复 APB2 配置。 USART1_RestoreAfterStop 重新打开 GPIO 与 LED1,把 USART1 切回 APB2、重配 USART 并关闭唤醒与 RXDNE 中断。
static void USART1_RestoreAfterStop(void)
{
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);
LED1_Init();
/* Reconfigure only while USART1 is clocked from APB2. */
USART_Enable(USARTy, DISABLE);
RCC_ConfigUsart1Clk(RCC_USART1_CLKSEL_APB2);
USARTy_APBxClkCmd(USARTy_CLK, ENABLE);
USART_Init(USARTy, &USART_InitStructure);
USART_ConfigInt(USARTy, USART_INT_RXDNE, DISABLE);
USART_EnableWakeUp(USARTy, DISABLE);
USART_Enable(USARTy, ENABLE);
}
恢复顺序与进入前相反:先切回 APB2 再 USART_Init,最后关闭唤醒功能;LED1_Init 依赖 n32g41x_stb.h 的 LED1 宏,换板时要改该头文件。
4. 进 STOP 并处理唤醒中断。 main 配置 LSI、NVIC 与 EXTI24 后进入主循环,唤醒中断在 USART1_IRQHandler 里置标志位、清 EXTI 并点亮 LED1。
RCC_EnableLsi(ENABLE);
while (RCC_GetFlagStatus(RCC_CTRL_FLAG_LSIRDF) == RESET)
{
}
RCC_ConfigLowPowerClk(RCC_LP_CLKSEL_LSI);
/* USART1_IRQn also carries the USART1 low-power EXTI24 interrupt. */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* WFI requires an interrupt source: EXTI24 routes to USART1_IRQn. */
EXTI_InitStructure.EXTI_Line = USARTy_WKUP_EXTI_LINE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitPeripheral(&EXTI_InitStructure);
while (1)
{
GPIO_ResetBits(LED1_PORT, LED1_PIN);
USART1_PrepareStopWakeup();
PWR_EnterSTOPMode(PWR_STOPENTRY_WFI);
/* Only process the STOP exit after USART1_IRQHandler confirms it. */
if (UsartWakeupFlag != 0U)
{
USART1_RestoreAfterStop();
GPIO_SetBits(LED1_PORT, LED1_PIN);
SysTick_Delay_Ms(1000);
}
}
void USART1_IRQHandler(void)
{
/* Stop a level-held RXDNE from immediately retriggering this IRQ. */
USART_ConfigInt(USARTy, USART_INT_RXDNE, DISABLE);
if (USART_GetFlagStatus(USARTy, USART_FLAG_RXDNE) != RESET)
{
(void)USART_ReceiveData(USARTy);
}
/* Clear EXTI24 so a previous wake request cannot retrigger the IRQ. */
EXTI_ClrITPendBit(USARTy_WKUP_EXTI_LINE);
/* Notify main that this was a valid USART wakeup. */
UsartWakeupFlag = 1U;
GPIO_SetBits(LED1_PORT, LED1_PIN);
}
中断里先关 RXDNE 再读走数据,避免数据保持时反复触发;只有 UsartWakeupFlag 置位后主循环才继续,保证唤醒来源是一次有效接收。
对应源码: src/main.c、inc/main.h、src/n32g41x_it.c、projects/n32g41x_EVAL/bsp/inc/n32g41x_stb.h
5.21.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| 使能后修改波特率或过采样 | 用户手册第 21 章写明 USART_BRCF 在 USART_CTRL1.UEN=1 时不能写入,USART_CTRL1.OSPM 也只在禁止 USART 时可写;修改波特率或过采样前先清零 UEN。 |
用户手册 |
| 传输途中修改数据字长或关闭发送 | 用户手册说明 USART_CTRL1.WL 在发送或接收过程中不能修改,传输中清零 USART_CTRL1.TXEN 会使波特率计数器停止并破坏正在传输的数据。 |
用户手册 |
| 切换同步、单线、IrDA、LIN 与智能卡模式 | 用户手册要求启用其中任一专用模式时清零其余模式的使能位,例如启用同步模式(USART_CTRL2.CLKEN)须清零 LINMEN、SCMEN、HDMEN、IRDAMEN,各模式需清零的位以手册对应章节为准。 |
用户手册 |
| 清除状态与错误标志 | 用户手册规定 RXDNE、OREF、PEF、NEF、FEF、IDLEF 需先读 USART_STS 再读 USART_DAT 清零,TXC 需先读 USART_STS 再写 USART_DAT 清零;按此顺序操作。 |
用户手册 |
| 低功耗唤醒选错串口或时钟 | 用户手册说明仅 USART1 与 USART4 支持低功耗唤醒,且时钟源须选择为 LSI 或 LSE; 使用其它串口或 APB 时钟时该功能不成立。 |
用户手册 |
| 智能卡模式的帧格式 | 用户手册指出智能卡模式下数据长度必须为 8 位并配置校验位,发送时停止位只能为 1.5 位,建议收发均用 1.5 停止位以避免在两种停止位配置间切换; CK 时钟分频范围为 fCK/2 至 fCK/62。 |
用户手册 |
| 多处理器静默位写入被忽略 | 用户手册写明 USART_CTRL1.RCVWU 仅在 USART_STS.RXDNE=0(接收数据寄存器为空)时可由软件写入,否则写操作被忽略;进入静默前先读走 USART_DAT。 |
用户手册 |
N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表只列出一行 A 版芯片条目,勘误链接列为空,未列出 USART 相关条目。