虽然串口是单片机最常用的一种外设,今天就由我来给大家展示一个最好用的串口接收发送例子解说吧。DMA不定长接收和DMA发送。
GD32单片机自带串口发送接收有三种方式,下面就简单介绍一下三种处理数据的方式:
- 轮询方式(Polling):使用轮询方式发送和接收数据。在发送数据时,检查串口的状态寄存器,确认发送缓冲区为空后将数据写入发送寄存器。在接收数据时,检查状态寄存器的接收缓冲区非空标志位,然后读取接收寄存器中的数据。这种方式简单易懂,但需要不断检查状态寄存器,可能会浪费处理器的时间。
- 中断方式(Interrupt):配置串口为中断模式,在数据发送或接收完成时触发中断。当数据准备就绪时,触发发送或接收中断,在中断处理程序中处理数据的发送和接收。这种方式相对于轮询方式来说,减少了处理器的负载,但需要正确配置中断和编写中断处理程序。
- DMA 方式(Direct Memory Access):使用 DMA 控制器来完成数据的传输。DMA 可以在不依赖处理器的情况下直接从内存复制数据到串口发送寄存器,或从串口接收寄存器复制数据到内存。这种方式能够大大减轻处理器的负担,并且适用于大量数据的传输。
前面的两种方式就不做详细的例程了,直接第三种方式开干,废话不多说下面直接上干货。
// 串口初始化
void gd_usart_init(uint32_t com_id)
{
/* enable_gpio_clock */
rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
/* enable_usart_clock */
rcu_periph_clock_enable(COM_CLK[com_id]);
/* connect_port_to_usarTxTx */
qpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);
/* connect_port_to_usarTxRx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);
/* USART_configure */
usart_disable(COM_NUM[com_id]);
usart_deinit(COM_NUM[com_id]);
usart_baudrate_set(COM_NUM[com_id], COM_BAUD[com_id]); // 配置波特率
usart_parity_confiq(COM_NUM[com_id], USART_PMNONE); // 配置奇偶校验
usart_word_lenath_set(COM_NUM[com_id], USART_W8BTT); // 西管数据付
usart_stop_bit_set(COM_NUM[com_id], USART_STB_1BIT); // 配置停止位
usart_hardware_flow_rts_confiq(COM_NUM[com_id], USARTRTS_DISABLE); // 配置RTS
usart_hardware_flow_cts_config(COM_NUM[com_id], USART_CTS_DISABLE); // 配置CTS
// usart_data_first_confia(comUSART_MSBF_LSB);// 数据首先由LSB/MSB发送/接收
usart_receive_config(COM_NUM[com_id], USART_RECEIVE_ENABLE); // 使能接收
usart_transmit_config(COM_NUM[com_id], USART_TRANSMIT_ENABLE); // 使能发送
usart_enable(COM_NUM[com_id]); // 使能串口
usart_dma_receive_config(COM_NUM[com_id], USART_DENR_ENABLE); // 使能DMA接收
usart_dma_transmit_config(COM_NUM[com_id], USART_DENT_ENABLE); // 使能DMA发送
usart_flag_clear(COM_NUM[com_id], USART_FLAG_TC); // 清除发送完成标志
usart_interrupt_enable(COM_NUM[com_id], USART_INT_IDLE); // 使用串口空闲中断
// usart_interrupt_enable(COM_NUM[com_id], USART_INT_RBNE);// 使能串口接收中新
// usart_interrupt_enable(COM_NUM[com_id], USART_INT_TBE);// 使能串口发送中断
}
// 串口中断函数配置
void usart_dma_nvic_init()
{
// UARTO配置中断
nvic_irg_enable(USARTO_IRQn, 2, 1);
// 串口中断
nvic_irg_enable(DMAO_Channel3_IRQn, 6, 1); // DMA接收中断
nvic_irg_enable(DMAO_Channel4_IRQn, 6, 1); // DMA接收中断
// UART1配置中断
nvic_irq_enable(USART1_IRQn, 2, 2);
// 串口中断
nvic_irg_enable(DMAO_Channe15_IRQn, 6, 2); // DMA接收中断
nvic_irq_enable(DMAO_Channe16_IRQn, 6, 2); // DMA接收中断
}
// 串口发送DMA配置
void usart_dma_tx_config(uint32_t_com_id)
{
dma_parameter_struct dma_init_struct;
/* enable_DMA0 */
rcu_periph_clock_enable(COM_DMA_CLK[com_id]); // 时钟配置
dma_deinit(COM_DMAx[com_id], COM_DMA_CHTx[com_id]);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; // 数据从内存(缓存)到外设
dma_init_struct.memory_addr = (uint32_t)(&usart_task_data[com_id].snd.snd_buf); // 缓存BUE
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 内存(缓存)地址怎加开启
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; // 内存数据宽度8bit,1字节存储
dma_init_struct.number = USART1_SND_BUF_SIZE;
// 缓存大小
dma_init_struct.periph_addr = (uint32_t)(&USART_DATA(COM_NUM[com_id])); // 传输数据外设地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 禁止外设地址增加
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; // 外设宽度
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; // MDA动作优先级(最高)
dma_init(COM_DMAx[com_id], COM_DMA_CHTx[com_id], &dma_init_struct); // 初始化
/*_configure_DMA_mode_*/
dma_memory_to_memory_disable(COM_DMAx[com_id], COM_DMA_CHTx[com_id]); // 关闭内存到内存方式
// dma_channel_enable(COM_DMAx[com_id], COM_DMA_CHTX[com_id]); // DMA开启
dma_interrupt_enable(COM_DMAx[com_id], COM_DMA_CHTX[com_id], DMA_INT_FLAG_FTF); // DMA传输完成中断
dma_circulation_disable(COM_DMAx[com_id], COM_DMA_CHTX[com_id]); // 禁止循环接收
}
// 串口接收DMA配置
void usart_dma_rx_config(uint32_t_com_id)
{
dma_parameter_struct dma_init_struct;
/* enable_DMA0*/
rcu_periph_clock_enable(COM_DMA_CLK[comid]); // 时钟配置
/* deinitialize_DMA_channel3(USARTO_tx) */
dma_deinit(COM_DMAx[com_id], COM_DMA_CHRX[comid]);
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; // 数据从外设到内存(缓存)
dma_init_struct.memory_addr = (uint32_t)(&usart_task_data[comid].rec.buf[usart_task_data[com_id].rec.index]); // 缓存BUE
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 内存(绣存)地址怎加开启
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; // 内存数据宽度8bit,1字节存储
dma_init_struct.number = USART1_REC_BUFSIZE; // 缓存大小
dma_init_struct.periph_addr = (uint32_t)(&USART_DATA(COM_NUM[com_id])); // 数据入口地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 禁止外设地址增加
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; // 外设宽度
dma_init_struct.priority = DMA_PRIORITY_UTRA_HIGH; // MDA动作优先级(最高)
dma_init(COM_DMAx[com_id] _COM_DMA_CHRx[com_id], &dma_init_struct); // 初始化
/*_confiqure_dMA_mode_*/
dma_circulation_disable(COM_DMAx[com_id], COM_DMA_CHRx[comid]); // 禁止循环接收
dma_memory_to_memory_disable(COM_DMAx[com_id], COM_DMACHRx[comid]); // 关闭内存到内存方式
dma_channel_enable(COM_DMAx[com_id], COM_DMA_CHRx[com_id]); // DMA开启
dma_interrupt_enable(COM_DMAx[com_id] COM_DMA_CHRx[comid], DMA_INTFTF); // DMA传输完成中断
}
// DMA发送缓存
uint8_t load_usart_dma_send(uint8_t com_id, uint8_t *src, uint16_t len)
{
if (TRUE == usart_task_data[com_id].snd.snd_ok) {
// 必须加上此判断,否则会导致usart1_task_data.snd.snd_ok为FALSE,而不会产生DMA发送完成中断,导致snd_ok永远锁死为FALSE
if (len <= 0) {
return FALSE;
}
if (len >= USART1_SND_BUF_SIZE) {
len = USART1_SND_BUF_SIZE;
}
memcpy(usart_task_data[com_id].snd.snd_bufsrclen);
dma_memory_address_config(DMA0, DMA_CH6(uint32_t)(usart_task_data[com_id].snd.snd_buf));
usart_task_data[com_id].snd.sndlen = len;
return_usart_dma_send(com_id);
}
return FALSE;
}
// DMA发送
uint8_t usart_dma_send(uint8_t com_id)
{
if (TRUE == usart_task_data[com_id].snd.snd_ok) {
// 必须加上此判断,否则会导致snd_ok为FALSE,而不会产生DMA发送完成中断,导致snd_ok永远锁死为FALSE
if (usart_task_data[com_id].snd.snd_len <= 0) {
return FALSE;
}
usart_task_data[com_id].snd.snd_ok = FALSE;
// TDO:执行DMA发送
usart_flag_clear(COM_NUM[com_id], USART_FLAG_TC);
dma_channel_disable(COM_DMAx[com_id], COM_DMA_CHTx[com_id]);
dma_flag_clear(COM_DMAx[com_id], COM_DMA_CHTx[com_id], DMA_FLAG_FTF);
// dma_memory_address_config
dma_transfernumberconfig(COM_DMAx[com_id], COM_DMA_CHTx[com_id], usart_task_data[com_id].snd.snd_len);
dma_channel_enable(COM_DMAx[com_id], COM_DMA_CHTx[com_id]);
return TRUE;
}
return FALSE;
}
// 发生USART1的DMA接收中断,回调
void usart_dma_rec_ok_call_back(uint8_t com_id)
{
int i;
for (i = 0; i < USART1_REC_MUL_NUM; i++) {
if (usart_task_data[com_id].rec.rec_ok[i] == TRUE) {
load_usart_dma_send(com_id, (uint8_t*)(&usart_task_data[com_id].rec.buf[i]), usart_task_data[com_id].rec.len[i]);
usart_task_data[com_id].rec.rec_ok[i] = FALSE;
break;
}
}
}
// 串口数据处理
void usart_manu_rec_ok(uint8_t com_id)
{
int len = 0;
// uart_rtimer_t *ptmr;
// OS_ENTER_CRITICAL;
dma_channel_disable(COM_DMAx[com_id], COM_DMA_CHRx[com_id]);
dma_interrupt_flag_clear(COM_DMAx[com_id], COM_DMA_CHRx[com_id], DMA_INT_FLAG_FTF);
// ptmr=&uart_timer_recv_data.timer[UART1_T_RECV];
// 取消时间间隔接收,避免这里DMA和串口定时模块同时处理
// ptmr->last_index=0;
// ptmr->cnt=0;
usart_task_data[com_id].rec.rec_ok[usart_task_data[com_id].rec.index] = TRUE;
// 获取数据长度
usart_task_data[com_id].rec.len[usart_task_data[com_id].rec.index] = usart_curr_rec_counter(com_id);
if (0 == usart_task_data[com_id].rec.len[usart_task_data[com_id].rec.index]) {
// 此时说明DMA接收已经溢出了,导致DMA又重新开始循环计数
usart_task_data[com_id].rec.len[usart_task_data[com_id].rec.index] = USART1_REC_BUF_SIZE;
usart_task_data[com_id].rec.rec_ok[usart_task_data[com_id].rec.index] = FALSE;
}
// 更换下一个BUF接收
usart_task_data[com_id].rec.index++;
usart_task_datai[com_id].rec.index = usart_task_datai[com_id].rec.index % USARTI_REC_MUL_NUM; // 2
// 配置DMA
usart_dma_rx_config(com_id);
usart_dma_rec_ok_call_back(com_id); // DMA回调函数
//_OS_EXIT_CRITICAL;
}
// 空闲中断处理
void usart_IDLE_DMA_read(uint8_t com_id)
{
if (usart_interrupt_flag_get(COM_NUM[com_id], USART_INT_FLAG_IDLE) != RESET) { //_空闲中断标记被置位
usart_interrupt_flag_clear(COM_NUM[com_id], USART_INT_FLAG_IDLE);
usart_manu_rec_ok(com_id);
// 注意GD32F303DMA_接空闲中个寄器,清完成标志,要不然会无限进入中断死机。
USART_DATA(COM_NUM[com_id]);
}
}
// 发生USART1的DMA发送中断,回调
void usart_dma_snd_ok_call_back(uint8_t com_id)
{
dma_interrupt_flag_clear(COM_DMAx[com_id], COM_DMA_CHTx[com_id], DMA_INT_FLAG_FTF);
usart_task_data[com_id].snd.snd_ok = TRUE; // 发送完成,表明发送缓存是空闲的
dma_channel_disable(COM_DMAx[com_id], COM_DMA_CHTx[com_id]);
}
// 发送DMA函数
void DMAX_CHx_Tx_IRQHandler(uint8_t com_id)
{
usart_flag_clear(COM_NUM[com_id], USART_FLAG_TC);
dma_flag_clear(COM_DMAx[com_id], COM_DMA_CHRx[com_id], DMA_FLAG_FTF);
usart_dma_snd_ok_call_back(com_id);
dma_channel_disable(COM_DMAx[com_id], COM_DMA_CHRx[com_id]);
}
// 接收DMA函数
void DMAx_CHx_Rx_IRQHandler(uint8_tcom_id)
{
if (dma_interrupt_flag_qet(COM_DMAx[com_id], COM_DMA_CHRx[com_id], DMA_INT_FLAG_FTF) == _SET) {
usart_manu_rec_ok(com_id);
}
}
此例程就算两串口接起来无限对发也不影响主程序正常跑。上面所有的配置都是GD32F303配置。