打印
[MM32软件]

灵动微课堂 (第133讲) | 基于MM32 MCU的OS移植与应用——RT-Thre...

[复制链接]
1643|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
在前面的章节有跟大家一起实现基于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数据收发








使用特权

评论回复
沙发
kkzz| | 2021-2-4 16:33 | 只看该作者
楼主移植成功了吗?  

使用特权

评论回复
板凳
hudi008| | 2021-2-4 16:59 | 只看该作者
RT-Thread和ucosii这两个操作系统  

使用特权

评论回复
地板
lzmm| | 2021-2-4 16:59 | 只看该作者
rt-thread 怎样搭建gui  

使用特权

评论回复
5
minzisc| | 2021-2-4 16:59 | 只看该作者
RT-Thread RTOS 支持许多架构  

使用特权

评论回复
6
mmbs| | 2021-2-4 17:00 | 只看该作者
感觉看起来一知半解  

使用特权

评论回复
7
fentianyou| | 2021-2-4 17:00 | 只看该作者
怎么将i2c设备驱动添加到rt-thread中

使用特权

评论回复
8
xiaoyaodz| | 2021-2-4 17:00 | 只看该作者
coos,rt-thread,ucos和freertos对比

使用特权

评论回复
9
febgxu| | 2021-2-4 17:01 | 只看该作者
如何在rt-thread上开发应用程序  

使用特权

评论回复
10
sdlls| | 2021-2-4 17:01 | 只看该作者
rt-thread 怎么确定线程栈大小

使用特权

评论回复
11
pixhw| | 2021-2-4 17:02 | 只看该作者
RT-Thread可兼容的标准

使用特权

评论回复
12
mmbs| | 2021-2-4 17:03 | 只看该作者
这个还没有开始学习。         

使用特权

评论回复
13
minzisc| | 2021-2-4 17:03 | 只看该作者
已经涵盖了当前应用中的主要架构。  

使用特权

评论回复
14
fentianyou| | 2021-2-4 17:03 | 只看该作者
rt-thread lwip使用哪个比较稳定  

使用特权

评论回复
15
lzmm| | 2021-2-4 17:03 | 只看该作者
怎样学习RT-Thread   

使用特权

评论回复
16
xiaoyaodz| | 2021-2-4 17:03 | 只看该作者
rt-thread是否是免费的  

使用特权

评论回复
17
hudi008| | 2021-2-4 17:03 | 只看该作者
RT-Thread和Threadx有什么区别  

使用特权

评论回复
18
febgxu| | 2021-2-4 17:03 | 只看该作者
RT-THREAD 有没有各个性能测试的范例  

使用特权

评论回复
19
pixhw| | 2021-2-4 17:03 | 只看该作者
学习RT-Thread移植到一块开发板上

使用特权

评论回复
20
sdlls| | 2021-2-4 17:03 | 只看该作者
rt thread钩子函数怎么用  

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1413

主题

3784

帖子

5

粉丝