实战篇:IO设备

电子说

1.2w人已加入

描述

通常我们认为以计算机CPU为核心,其外部的所有的设备都可以称为是外部输入输出设备。例如计算机中的显示器就是一个输出设备,它的作用是将一些数字信号转化为图形信号显示在电子屏幕上,其数据是由内向外流动,因此我们称显示器为输出设备。

又如键盘和鼠标是输入设备,它们的作用是将使用者的命令信号转化为数字信号传递给计算机的CPU,其数据是由外向内流动,因此我们称键盘和鼠标为输入设备。又如,打印机是一个输出设备,游戏手柄和摄像头是输入设备。如下图:

处理器

通常的,我们将键盘称为计算机的标准输入设备,将显示器称为计算机的标准输出设备。这也是Unix和类Unix系统中一直延用的名称。我们在做虚拟文件系统时将每一个进程中都分配了一个文件描述结构体数组:

//进程控制块Process Control Block
typedef struct pcb_s
{
  //进程栈顶地址
  void *p_stack;
  //栈内存地址,释放、统计内存时使用
  void *p_stack_mem;
  //栈内在大小
  uint32_t stack_size; 
  //优先级由高0到低32
  uint8_t prio;
  //任务状态
  uint8_t status;
  //任务休眠ticks
  uint32_t sleep_tick;
  //任务入口函数
  void (*task_entry)(void *);
  //任务函数参数
  void *task_arg;
  //进程的文件描位图,1表示空闲,0表示使用
  uint32_t f_use_map;
  //进程的文件描述结构体数组
  vfs_node_s *fnodes[FNODE_SIZE];
} pcb_s;

这个fnodes[FNODE_SIZE]文件描述数组记录了进程所打开每一个设备文件的地址。这个数组的下标就是我们使用open()函数所返回的值,也就是我们通常所说的文件描述符。文件描述符为int类型,范围通常是0~FNODE_SIZE。当文件描述符小于0时表示打开设备文件失败。几乎所有的类Unix系统中都使用了标准输入、标准输出和标准错误这3个IO设备。

所以,操作系统为每一个进程分配文件描述符时,会默认将0、1、2分别用于表示标准输入、标准输出和标准错误这3个设备。所以通常情况下我们使用open()函数来打开一个设备文件时, 返回的文件描述大多数是以3开始的,在同一个进程中同时打开多个设备文件,其文件描述符通常会是3、4、5、6、7、8……等。

实际上,我们在嵌入式领域里的处理器性能有限,外设的各类和功能多种多样,并非像个人电脑或是网络服务器一样通用和统一。在单片机领域中我们所使用的操作系统并非一定要遵循Unix标准或习惯,但为了学习其优秀的设计理念,我们可以将我们的嵌入式操作系统中实现标准输入、标准输出和标准错误这3个设备。

在Cortex-M3处理器中我们可以使用串口设备作为操作系统中的标准输入和标准输出,而从本质上讲标准错误这个设备的功能跟标准输出是一样的,只不过其显示的内容都是程序错误,我们不单独来实现标准错误设备,而只来完成标准输入和标准输出这两个设备。例如串口1的初始化、读、写程序如下:

void serial1_init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  USART_InitStructure.USART_BaudRate = SERIAL_BAUTRATE;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART1- >CR1 |= (USART_CR1_RE | USART_CR1_TE);
  USART_Init(USART1, &USART_InitStructure);
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  USART_Cmd(USART1, ENABLE);
}


void serial1_write(uint8_t data)
{
  uint8_t next_head = serial1_tx_buffer_head + 1;
  if (next_head >= TX_RING_BUFFER1)
  {
    next_head = 0;
  }
  while (next_head == serial1_tx_buffer_tail);
  serial1_tx_buffer[serial1_tx_buffer_head] = data;
  serial1_tx_buffer_head = next_head;
  USART1- >CR1 |= USART_FLAG_TXE;
}


int serial1_read(uint8_t *ch)
{
  uint8_t tail = serial1_rx_buffer_tail;
  if (serial1_rx_buffer_head == tail)
  {
    return 0;
  }
  else
  {
    uint8_t data = serial1_rx_buffer[tail];
    tail++;
    if (tail >= RX_RING_BUFFER1)
    {
      tail = 0;
    }
    serial1_rx_buffer_tail = tail;
    *ch = data;
    return 1;
  }
}


void storeHandleDataIn(uint8_t data)
{
  uint8_t next_head;  
  next_head = serial1_rx_buffer_head + 1;
  if (next_head >= RX_RING_BUFFER1)
  {
    next_head = 0;
  }
    if (next_head != serial1_rx_buffer_tail)
  {
    serial1_rx_buffer[serial1_rx_buffer_head] = data;
    serial1_rx_buffer_head = next_head;
  }
  else
  {
    next_head++;
    next_head--;
  }
}

之后我们就可以编写一个/dev/ttyS1设备文件用于串口1的驱动:

int ttyS1_open(struct file *fs)
{
  return 0;
}


int ttyS1_close(struct file *fs)
{
  return 0;
}


size_t ttyS1_read(struct file *fs, void *buff, size_t size)
{
  uint8_t *p = (uint8_t *)buff;
  size_t read_len = 0;
  for (int i = 0; i < size; i++)
  {
    if (!serial1_read(&p[read_len]))
    {
      return read_len;
    }
    read_len++;
  }
  return size;
}


size_t ttyS1_write(struct file *fs, const void *buff, size_t size)
{
  uint8_t *p = (uint8_t *)buff;
  for (int i = 0; i < size; i++)
  {
    serial1_write(p[i]);
  }
  return size;
}


void ttyS1_init(void)
{
  file_operations_s ops = {0};


  ops.open = ttyS1_open;
  ops.close = ttyS1_close;
  ops.write = ttyS1_write;
  ops.read = ttyS1_read;
  ops.ioctl = NULL;


  fs_register_dev("/dev/ttyS1", ops);
}
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分