前言
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作为小型系统的成功者,也非常值得我们学习。祝大家共同进步!
来源:CSDN
作者:DemandStorm
链接:https://blog.csdn.net/qq_44703307/article/details/103856500