STM32基于Rt-thread3.12系统的串口通讯

泪湿孤枕 提交于 2020-01-06 23:10:32

前言

STM32的串口收发可以说是对这个芯片学习的一个基础,相信接触过STM32的朋友首先学会的就是它的GPIO和USART。对GPIO和串口初始化的操作我在这里不做赘述,这些在STM32的例程里面很容易找到学会。我们在这里重点介绍STM32的串口中断接收,以及在RTT系统中我们如何把串口device注册到系统的对象容器里。

关于RT-Thread3.12系统

作为国产小型嵌入式系统中的翘楚,RTT也是被大多数产品所使用。我参与的这个项目RTT的主要工作就是多线程调度和串口device的控制。对于线程的调度先不详细说明,我们这里只介绍串口通讯一个线程的东西。

RTT对象

在 RT-Thread中,所有的数据结构都称之为对象。 其中线程,信号量,互斥量、事件、邮箱、消息队列、内存堆、内存池、设备和定时 器在 rtdef.h 中有明显的枚举定义,即为每个对象打上了一个数字标签。我们这里的对象就特指设备,而我们的设备就特指串口。
那么我们使用这个对象有什么用处呢,我私以为有两个最大的用处,一是有利于设备管理,二是基于程序安全考虑。我使用这一功能的时候基本与第一个用处不沾边,因为我们就一个串口设备。主要还是基于安全的考虑才使用RTT对象。
使用系统的对象也就是把硬件驱动注册到系统中,让系统对就硬件进行操控,我们再通过系统操控硬件。

串口注册到系统

我们移植RTT的时候会发现bsp组件中会有一个bsp_uart.c文件,我们注册串口驱动时候只需要在这个函数上面改就可以了。我先把代码贴出来。

uart.h
struct uart_device
{
  USART_TypeDef* uart_device;
  struct uart_int_rx* int_rx;
  struct uart_int_tx* int_tx;
}
struct uart_int_tx
{
  uint8 tx_buffer[SENDSIZE];
  uint8 write_index;
}
struct uart_int_rx
{
  uint8 rx_buffer[RECIVESIZE];
  uint8 save_index;
  uint8 rx_length;
}

头文件里是数据结构,里面是我们收发的缓存区。

uart.c
//串口初始化函数
static rt_err_t rt_uart_init (rt_device_t dev)
{
  //把数据结构里的数据清零
}
static void rt_uart_save(struct uart_device* uart)
{
  //保存收到的数据,被接收中断函数调用
  uart->int_rx->rx_buffer[uart->int_rx->save_index++] =USART_ReceiveData( DEBUG_USARTx );
  uart->int_rx->rx_length++;
}
static rt_err_t rt_uart_open(rt_device_t dev, rt_uint16_t oflag)
{
  //打开串口,暂时不使用
}
static rt_err_t rt_uart_close(rt_device_t dev)
{
 //关闭串口,暂时不使用
}
static rt_size_t rt_uart_write (rt_device_t dev, rt_off_t pos,
                                  const void* buffer, rt_size_t size)
{
    struct uart_device* uart;
    uart = (struct uart_device*)dev->user_data; //把数据结构指针用系统设备指针进行初始化
    Usart_SendArray(DEBUG_USARTx,uart->int_tx->tx_buffer,size);
    uart->int_tx->write_index = uart->int_tx->save_index = 0;
    return size;
//利用系统写数据
}
rt_err_t rt_hw_uart_register(rt_device_t device, const char* name,
                               rt_uint32_t flag, struct uart_device *uart)
{
device->type   = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;
    device->init   = rt_uart_init;
    device->open  = rt_uart_open;
    device->close  = rt_uart_close;
    device->read   = RT_NULL;
    device->write   = rt_uart_write;
    device->control  = RT_NULL;
    device->user_data = uart;
    return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | flag);
}
void rt_hw_uart_isr(rt_device_t device)
{
    rt_uint8_t Free_Read_Rst;//
    struct uart_device* uart = (struct uart_device*) device->user_data;
    if (USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)//空闲中断
  {   
    Free_Read_Rst = DEBUG_USARTx->SR;
    Free_Read_Rst = DEBUG_USARTx->DR;//清除空闲中断标志位
    Free_Read_Rst = Free_Read_Rst;
    uart->int_rx->save_index = 0;
    USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE);
    return;
  }
    while (USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
  { 
     rt_uart_save(uart);
     rt_sem_release(&uart4_sem);
  }
}
void DEBUG_USART_IRQHandler(void)//在中断服务函数中调用中断接收
{
    rt_interrupt_enter();
    rt_hw_uart_isr(&uart4_device);
    rt_interrupt_leave();
}
//串口初始化以及注册
void rt_hw_uart4_init(void)
{
    USART_Config();
    /* register UART4 device */
    rt_hw_uart_register(&uart4_device,
                          "uart4",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX
                          | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_DMA_TX,
                          &uart4);
}

经过以上的代码,我们就已经成功把发送接收功能注册到了RTT系统中,注意我们的STM32串口硬件驱动我们只在rt_hw_uart4_init()初始化函数中调用了一次,也就是USART_Config()函数。此函数的具体代码可以在STM32的例程中查看,这里就不贴出来了。

这里重点说明一下中断接收,我们这里用到空闲中断,中断的配置也是在USART_Config()函数中已经写好的。空闲中断要注意的是一定要通过读SR、DR实现清除中断标志位。否则空闲中断会影响到线程,使线程挂起且不会恢复。

线程实体

最后我们看一下线程实体,对于RTT来说一个线程实体就相当于main函数的具体执行内容。代码如下:

void uart4_thread_entry(void *parameter)
{ 
    rt_uint16_t count;
    rt_err_t result;
    rt_device_t dev = RT_NULL;
    struct uart_device* uart;
    dev = rt_device_find("uart4");
    rt_device_open(dev, RT_DEVICE_OFLAG_RDWR);
    uart = (struct uart_device*)dev->user_data; 
   while(1)
    {   
     result = rt_sem_take(&uart4_sem, 100);//RT_TICK_PER_SECOND*60
     if(result == -RT_ETIMEOUT)//
     {
      RS232_Active = 0;
      count++;
      if(count > 30)// 3s¸
        {
        uart->int_tx->reroute_num = 0;
        count = 0;
        StartCommunication = 1;   
        }
       else
        {
         Comm_Routine(dev);//通讯执行主函数       
        }
    }   
    else
    {
      ReceiveFrame_JK1_103_Sci(dev);//报文识别函数
      if(uart->int_rx->receive_flg == FrameUnFix)//报文识别正确
        {   
        uart->int_tx->reroute_num = 0;
        uart->int_tx->respond_flg = 0;
        Deal_Rxbuf_Sci(dev);//报文处理响应函数
        rt_thread_delay(1);//
        count = 0;
        RS232_Active = 1;
        uart->int_rx->receive_flg = 0;
        }
      else
        {   
         RS232_Active = 0;       
         Comm_Routine(dev);
        }
    }
     rt_thread_delay(500);//
  }
}

这里需要注意的是rt_sem_take()这个信号量获取函数,在创建线程时创建静态信号量。我们通过这个信号量来判断有没有接收收据,如果有函数就会返回0,反之返回-2。这个函数有一个参数是等待时间,假如我们设置这个时间是3秒,那么它会在3s内一直等来有效信号量,假如没等到就会返回-2。

特别值得注意的是,我们这个信号量有一个value,这个value并不是它的返回值,而是我们接受的字节长度。假如我们接受了十个字节,value就是10,它会随while循环每次减一,直到减为零信号量就无效。这就意味着我们收到一个十字节的数据,我们就会判断十个循环都有信号量,然而我们的buffer里面只有第一个循环有数据。因此会导致不断的陷入有信号量但数据却不正确的循环中。为了避免此错误发生,我们必须在每次循环获得信号量之后就把信号量的value清零。

总结

在STM32的基础上使用RTT是一种比较好的体验,作为单片机而言现在裸跑的代码已经少之又少。而RTT作为小型系统的成功者,也非常值得我们学习。祝大家共同进步!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!