在前面的章节有跟大家一起实现基于RT-Thread 的按键控制功能,在本章节中将与大家一起来通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件。
首先进行相关硬件初始化,在rt_hw_board_init()函数里面增加SysInit()初始化函数,将需要使用的Systick、GPIO和UART先进行初始化。
void rt_hw_board_init()
{
#IF 0
/* System clock Update */
SystemCoreClockUpdate();
/* System Tick Configuration */
_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
#endif
SysInit(); //添加硬件相关的初始化
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined (RT_USING_USER_MAIN) && defined (RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
01
Systick初始化
void SysTickInit(void)
{
SysTick_Config(SystemCoreClock/RT_TICK_PER_SECOND);
}
02
GPIO初始化
void LEDGpioInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1 ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
03
UART初始化
UART1 用于FinSH中,在rt_hw_console_getchar使用查询方式实现,同时输出相关调试信息所以UART1 不需要开中断,UART2 用于测试串口收发,开启接收中断和接收空闲中断,开启接收空闲中断后则可以接收任意不定长度数据。
void uartInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
UART_InitTypeDef UART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//UART1 Initial
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);
UART_InitStructure.UART_BaudRate = UART1_BAUD;
UART_InitStructure.UART_WordLength = UART_WordLength_8b;
UART_InitStructure.UART_StopBits = UART_StopBits_1;
UART_InitStructure.UART_Parity = UART_Parity_No;
UART_InitStructure.UART_Mode = UART_Mode_Rx |UART_Mode_Tx;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_Init(UART1, &UART_InitStructure);//初始化串口1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ; //TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ; //RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
UART_ClearITPendingBit(UART1, UART_ISR_TXC);//清发送完成标志位
UART_Cmd(UART1, ENABLE);//使能串口1
//UART2 Initial
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART2, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);
UART_InitStructure.UART_BaudRate = UART2_BAUD;
UART_InitStructure.UART_WordLength = UART_WordLength_8b;
UART_InitStructure.UART_StopBits = UART_StopBits_1;
UART_InitStructure.UART_Parity = UART_Parity_No;
UART_InitStructure.UART_Mode = UART_Mode_Rx |UART_Mode_Tx;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_Init(UART2, &UART_InitStructure);//初始化串口2
//串口2中断初始化
NVIC_InitStructure.NVIC_IRQChannel = UART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
UART_ClearITPendingBit(UART2, UART_IER_RXIDLE);
UART_ITConfig(UART2, UART_IER_RX, ENABLE);
UART_ITConfig(UART2, UART_IER_RXIDLE, ENABLE); //添加串口空闲中断使能
UART_Cmd(UART2, ENABLE);//使能串口2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ; //TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ; //RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
UART_ClearITPendingBit(UART1, UART_ISR_TXC);
}
注
对于串口接收不定长度数据,串口IDLE中断就比较适用,IDLE中断就是串口收完一帧数据后发生中断。比如一帧数据有8个byte,当8个byte接收结束后就产生IDLE中断。
IDLE中断在UART_IER 配置
IDLE的时间在UART_IDLR寄存器配置,复位默值是0x0C(例:串口配置115200bps UART_IDLR默认为0x0C,那IDLE时间 8.6us*12=103.2us )
04
UART2中断处理
UART2开启了两个中断,一个接收中断把接收到的数据放入buff,一个接收空闲中断,当一包数据接收完后产生中断,释放一个信号量,供另外一个线程查询。
void UART2_IRQHandler(void)
{
uint8 RecCh;
if( UART_GetFlagStatus(UART2,UART_ISR_RX)!=RESET ) // 串口接收中断
{
RecCh = (uint8)UART_ReceiveData(UART2);
g_UART2_RxBuf[g_UART2_RecPos++] = RecCh;
UART_ClearITPendingBit(UART2, UART_ISR_RX);
}
if (UART_GetITStatus(UART2, UART_IER_RXIDLE) != RESET) //接收空闲中断
{
g_UART2_RxBuf[g_UART2_RecPos] = '\0';
rt_sem_release(uart2_recv_sem); //释放一个信号量,表示数据已接收;给出二值信号量 ,发送接收到新数据帧标志,供前台线程查询
UART_ClearITPendingBit(UART2, UART_ISR_RXIDLE);//使用该语句清除空闲中断标志位
}
if( UART_GetFlagStatus(UART2,UART_ISR_RXOERR)==SET ) // 串口接收溢出
{
UART_ClearITPendingBit(UART2, UART_ISR_RXOERR);
}
}
05
rt_kprintf函数实现
RT-Thread 打印是通过void rt_kprintf(const char *fmt, ...)接口实现的,所以要使用这个接口需要重写一下这个函数接口,将需要输出的调试信息打印到需要的串口。
void rt_hw_console_output(const char *str) //实现该函数,才能使用rt_kprintf
{
/* 进入临界段 */
rt_enter_critical();
while(*str!='\0')
{
if (*str == '\n')//RT-Thread 系统中已有的打印均以 \n 结尾,而并非 \r\n,所以在字符输出时,需要在输出 \n 之前输出 \r,完成回车与换行,否则系统打印出来的信息将只有换行
{
while((UART1->CSR&UART_IT_TXIEN)==0)
{
}
UART_SendData(UART1, '\r');
}
while((UART1->CSR&UART_IT_TXIEN)==0)
{
}
UART_SendData(UART1, *(str++));
}
/* 退出临界段 */
rt_exit_critical();
}
注
使用进入临界段语句rt_enter_critical(); 一定要使用退出临界段语句 rt_exit_critical(); 否则调度器锁住,无法进行调度。
06
printf函数重定向
如果使用printf函数那需要重定向,本例程使用UART2,代码如下:
int fputc(int ch,FILE *f)
{
while((UART2->CSR&UART_IT_TXIEN)==0);//等待上一次发送完成
UART2->TDR = (ch & (uint16_t)0x00FF);
return (ch);
}
07
创建线程
现在我们来创建2个线程:LED翻转线程,PA1翻转。
void led_thread_entry(void *parameter)
{
while(1)
{
LedToggle(GPIOA,GPIO_Pin_1);
rt_thread_mdelay(2000);
}
}
UART2数据处理线程,将中断收到的数据通过rt_kprintf输出,然后把接收的数据再通过UART2发回去。
void uart2_recv_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
while(1)
{
//获取UART2接收帧完成信号量
uwRet =rt_sem_take(uart2_recv_sem, RT_WAITING_FOREVER);
if(RT_EOK == uwRet )
{
//把接收到的数据通过rt_kprintf输出
rt_kprintf("uart2 Receive Data:%s\n",g_UART2_RxBuf);
uartSendString(UART2,g_UART2_RxBuf); //将UART2接收到的数据通过UART2发送回去
memset(g_UART2_RxBuf,0,UART2_RX_BUF_SIZE);
g_UART2_RecPos = 0;
}
}
}
08
启动任务
接着配置任务启动。
led_thread = rt_thread_create("ledThread", /* 线程名字 */
led_thread_entry, /* 线程入口函数 */
RT_NULL, /* 线程入口函数参数 */
256, /* 线程栈大小 */
5, /* 线程的优先级 */
10 /* 线程时间片 */
);
if(led_thread != RT_NULL)
{
rt_thread_startup(led_thread);
}
uart2_recv_thread = rt_thread_create("uart2_recv_thread", /* 线程名字 */
uart2_recv_thread_entry, /* 线程入口函数 */
RT_NULL, /* 线程入口函数参数 */
512, /* 线程栈大小 */
2, /* 线程的优先级 */
10 /* 线程时间片 */
);
if(uart2_recv_thread != RT_NULL)
{
rt_thread_startup(uart2_recv_thread);
}
新建一个信号量,到这里那我们需要引入一个信号量的概念。
信号量的控制块定义在include/rtdef.h中,信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 struct rt_semaphore 表示。另外一种 C 表达方式 rt_sem_t,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。信号量控制块结构的详细定义如下:
#ifdef RT_USING_SEMAPHORE
/**
* Semaphore structure
*/
struct rt_semaphore
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of semaphore. */
rt_uint16_t reserved; /**< reserved field */
};
typedef struct rt_semaphore *rt_sem_t;
#endif
rt_semaphore 对象从 rt_ipc_object 中派生,由 IPC 容器所管理,信号量的最大值是 65535。
程序中新建一个信号量:
uart2_recv_sem = rt_sem_create("uart2_recv_sem", //信号量名字
0, //信号量初始值
RT_IPC_FLAG_FIFO //信号量模式 FIFO(0x00)
);
09
测试结果
程序下载后测试结果:UART1 rt_kprintf 调试信息输出窗口。
图1 UART1初始化状态打印
图2 UART2数据收发
|
|