这次来记录下串口USART的实现
串口是非常常用的通讯方式,其它就不说什么了,记录下一些东西吧。
之前用过GD32F450发送串口,手册上写着波特率可以达到12M。我配置成为8倍过采样率,转换芯片是FT232,实现了12M的波特率。用来传输图像和通讯,数据也没有丢。一般说波特率像9600,115200是比较常见的,就是1秒传送这么多位,按照1个字节8位来算,9600的波特率1秒最多只传送1200个字节,在有些地方对速率就不够了。
/**
* @brief 串口1配置
* @param 波特率
* @retval None
* @note 配置:8M,8bit,1bit停止位,无奇偶校验,接收使用中断,发送启动DMA
*/
void USART1_Config(uint32_t uart_baudrate)
{
rcu_periph_clock_enable(USART1_TX_GPIO_CLK);
/* enable USART clock */
rcu_periph_clock_enable(USART1_CLK);
/* connect port to USARTx_Tx RX */
gpio_af_set(USART1_TX_GPIO_PORT,USART1_TX_GPIO_AF, USART1_TX_GPIO_PIN);
gpio_af_set(USART1_RX_GPIO_PORT,USART1_RX_GPIO_AF, USART1_RX_GPIO_PIN);
/* configure USART Tx as alternate function push-pull */
gpio_mode_set(USART1_TX_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP,USART1_TX_GPIO_PIN);
gpio_output_options_set(USART1_RX_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,USART1_TX_GPIO_PIN);
/* configure USART Rx as alternate function push-pull */
gpio_mode_set(USART1_RX_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP,USART1_RX_GPIO_PIN);
gpio_output_options_set(USART1_RX_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,USART1_RX_GPIO_PIN);
/* USART configure */
usart_deinit(USART1_PORT);
usart_oversample_config(USART1_PORT,USART_OVSMOD_8);
usart_baudrate_set(USART1_PORT,uart_baudrate);
// USART_BAUD(USART1_PORT)=0x10;
usart_receive_config(USART1_PORT, USART_RECEIVE_ENABLE);
usart_transmit_config(USART1_PORT, USART_TRANSMIT_ENABLE);
usart_dma_receive_config(USART1_PORT,USART_DENR_ENABLE);
usart_dma_transmit_config(USART1_PORT,USART_DENT_ENABLE);
usart_interrupt_enable(USART1_PORT, USART_INT_IDLE); //开启空闲接收中断
usart_enable(USART1_PORT);
nvic_irq_enable(USART_IRQ, 0,2);
usart_dma_config();
}
```/**
void USART1_Tx_DMA_Config(void)
{
// uint32_t USARTBuffer;
dma_single_data_parameter_struct dma_init_struct;
/* deinitialize DMA channel7(USART0 tx) */
dma_deinit(DMA1, DMA_CH7);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.memory0_addr =0;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.number =0;
dma_init_struct.periph_addr = USART0_DATA_ADDRESS;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH7, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA1, DMA_CH7);
dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);
dma_channel_disable(DMA1, DMA_CH7); //暂不开启DMA
}
/*!
\brief configure USART DMA
\param[in] none
\param[out] none
\retval none
*/
void usart_dma_config(void)
{
dma_single_data_parameter_struct dma_init_struct;
/* enable DMA1 */
rcu_periph_clock_enable(RCU_DMA1);
/* deinitialize DMA channel7(USART0 tx) */
dma_deinit(DMA1, DMA_CH7);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.memory0_addr =0;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.number =0;
dma_init_struct.periph_addr = USART0_DATA_ADDRESS;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH7, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA1, DMA_CH7);
dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);
dma_channel_disable(DMA1, DMA_CH7); //暂不开启DMA
/* deinitialize DMA channel2(USART0 rx) */
dma_deinit(DMA1, DMA_CH2);
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.memory0_addr = (uint32_t) UART_Buffer;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.number = UART_BUFFER_SIZE;
// dma_init_struct.periph_addr = USART0_DATA_ADDRESS;
dma_init_struct.periph_addr =(uint32_t)& USART_DATA(USART1_PORT);
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH2, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA1, DMA_CH2);
dma_channel_subperipheral_select(DMA1, DMA_CH2, DMA_SUBPERI4);
dma_channel_enable(DMA1, DMA_CH2);
}
/**
* @brief 重启启动串口接收
* @param
* @retval
*/
void DMA_Enable(uint16_t ndtr)
{
dma_channel_disable(DMA1, DMA_CH2); //关闭DMA
while(dma_flag_get(DMA1, DMA_CH2, DMA_INTF_FTFIF)!=RESET)
{
dma_interrupt_flag_clear(DMA1, DMA_CH2, DMA_INTC_FTFIFC); //清标志
}
dma_transfer_number_config(DMA1,DMA_CH2,ndtr); //设置长度
dma_channel_enable(DMA1, DMA_CH2); //开DMA
}
/**
* @brief 手动启动串口1TX对应的DMA
* @param 发送数据长度
* @param 发送数据的buffer指针
* @retval None
* @note None
*/
void USART1_DMA_Send(uint8_t *buffer,uint16_t len)
{
dma_channel_disable(DMA1, DMA_CH7);
dma_interrupt_flag_clear(DMA1, DMA_CH7, DMA_INTC_FTFIFC); //清标志
dma_memory_address_config(DMA1, DMA_CH7,DMA_MEMORY_0,(uint32_t)buffer);
dma_transfer_number_config(DMA1, DMA_CH7,len);
/* enable DMA channel7 */
dma_channel_enable(DMA1, DMA_CH7); //开启DMA传输
// while(dma_flag_get(DMA1, DMA_CH7, DMA_INTF_FTFIF)!=RESET) //传输完成
// {
// dma_interrupt_flag_clear(DMA1, DMA_CH7, DMA_INTC_FTFIFC); //清标志
// }
}
/**
* @brief 串口DMA接收
* @param
* @retval
*/
void USART_IRQHandler(void)
{
uint8_t rc_tmp;
uint16_t rc_len;
if((RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) &&
(RESET != usart_flag_get(USART0 , USART_FLAG_IDLE)))
{
rc_tmp=USART_STAT0(USART0);
rc_tmp=USART_DATA(USART0);
dma_channel_disable(DMA1, DMA_CH2);
dma_interrupt_flag_clear(DMA1, DMA_CH2,DMA_INTC_FTFIFC);
dma_interrupt_flag_clear(DMA1, DMA_CH2,DMA_INTC_TAEIFC);
rc_len =UART_BUFFER_SIZE - DMA_CHCNT(DMA1, DMA_CH2);
testAAA=rc_len;
USART1_RECRIVE(UART_Buffer, rc_len); //数据解析
DMA_Enable(UART_BUFFER_SIZE);
}
}
因为需要发送的数据量有点大,串口的发送使用了DMA,是单次的DMA。每发完一次后自己手动重启发送。
接收则使用DMA+空闲帧中断。
如此串口的发送和接收都可以依靠DMA来完成,大大减少CPU。
串口接收有好几种方式
1.DMA+空闲中断
个人认为这是一种比较好的方式,要求更高的还将DMA的双缓存做了
2.字节中断
以字节的方式接收,接收不定长的 时候根据协议来判定。原理就是先检验头,然后再检验数据长度,一直接收数据量直达达到数据长度,之后进行校验。
3.开定时器来判断一帧是否结束
也可以用定时器来判断,每个字节的间隔肯定比帧间隔长。可以用这个来判断一帧是否结束
也可以做成串口的环形队列,就是用一个结构体数组,包含了数组和长度。在一帧数据接受完成后,将数据和长度放到结构体数组中,同时结构体数据的下标更新。如果是跑逻辑,在主循环里查询队列里面是或有新数据。如果是跑操作系统,则可以直接使用队列,将一帧数据接受完成后的结构体直接放到队列中,在相应的任务里进行处理。
在串口解析这一块,一般的写法是`
switch (Function)
case xx:
break;
case xx
break;
case xx
break
还有另外一种更好的机制,有限状态机FSM。
简单说就是创建结构体数组,结构体包含了当前的状态,触发条件,执行的动作,下个个状态。执行的动作使用函数指针。
版权声明:本文为m0_37202877原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。