grbl学习之旅---serial篇

serial.c和serial.h文件是实现了通过串行端口发送和接受字节的功能。

首先是serial.h中定义了基本函数和常量大小:

#ifndef RX_BUFFER_SIZE
  #define RX_BUFFER_SIZE 128//定义接受缓存的大小
#endif
#ifndef TX_BUFFER_SIZE
  #define TX_BUFFER_SIZE 64//定义发送缓存的大小
#endif

#define SERIAL_NO_DATA 0xff//串行无数据?

#ifdef ENABLE_XONXOFF
  #define RX_BUFFER_FULL 96 // XOFF high watermark
  #define RX_BUFFER_LOW 64 // XON low watermark
  #define SEND_XOFF 1
  #define SEND_XON 2
  #define XOFF_SENT 3
  #define XON_SENT 4
  #define XOFF_CHAR 0x13
  #define XON_CHAR 0x11
#endif

void serial_init();//serial初始化


//写入一个字节到TX串行缓冲区。主程序调用。
void serial_write(uint8_t data);

//获取串行读缓冲区中的第一个字节。主程序调用。
uint8_t serial_read();

/
//在读缓冲区中重置和清空数据。通过停止和复位的应用。
void serial_reset_read_buffer();

//返回RX串行缓冲区中使用的字节数。
uint8_t serial_get_rx_buffer_count();

// 返回TX串行缓冲区中使用的字节数。

//注:除用于调试和确保无TX阻碍外,未使用。
uint8_t serial_get_tx_buffer_count();

#endif
#ifdef ENABLE_XONXOFF
  #define RX_BUFFER_FULL 96 // XOFF high watermark
  #define RX_BUFFER_LOW 64 // XON low watermark
  #define SEND_XOFF 1
  #define SEND_XON 2
  #define XOFF_SENT 3
  #define XON_SENT 4
  #define XOFF_CHAR 0x13
  #define XON_CHAR 0x11
#endif

这段什么水印不是很懂,如果有大佬看到了,希望能告诉我下,不胜感激。

然后是对serial.c中函数具体实现方法的理解:

uint8_t serial_rx_buffer[RX_BUFFER_SIZE];
uint8_t serial_rx_buffer_head = 0;
volatile uint8_t serial_rx_buffer_tail = 0;

uint8_t serial_tx_buffer[TX_BUFFER_SIZE];
uint8_t serial_tx_buffer_head = 0;
volatile uint8_t serial_tx_buffer_tail = 0;

这里是定义了两个数组和分别的数组头和尾下标,实际上是定义的一个循环队列。

#ifdef ENABLE_XONXOFF
  volatile uint8_t flow_ctrl = XON_SENT; // Flow control state variable
#endif

这段代码和上面的一个ENABLE_XONXOFF一样的,不理解,按字面是什么流控制状态变量?希望有看到理解的人能告诉我下。

uint8_t serial_get_rx_buffer_count()
{
  uint8_t rtail = serial_rx_buffer_tail; // Copy to limit multiple calls to volatile
  if (serial_rx_buffer_head >= rtail) { return(serial_rx_buffer_head-rtail); }
  return (RX_BUFFER_SIZE - (rtail-serial_rx_buffer_head));
}

该函数实现的功能是返回rx缓存中用到的字节数。因为写入数据时从头写入的,即写入数据,头下标前移,读出数据,尾下标前移的顺序操作,所以当尾下标在头下标之后直接相减就是数据大小,否则说明头下标已经转了一圈到了尾下标的后面要用整个数组大小减。

// 返回TX串行缓冲区中使用的字节数。
// 注:除用于调试和确保无TX阻碍外,未使用。
uint8_t serial_get_tx_buffer_count()
{
  uint8_t ttail = serial_tx_buffer_tail; // Copy to limit multiple calls to volatile
  if (serial_tx_buffer_head >= ttail) { return(serial_tx_buffer_head-ttail); }
  return (TX_BUFFER_SIZE - (ttail-serial_tx_buffer_head));
}

过程与返回rx缓冲区字节数类似。

void serial_init()
{
  // Set baud rate
  #if BAUD_RATE < 57600
    uint16_t UBRR0_value = ((F_CPU / (8L * BAUD_RATE)) - 1)/2 ;
    UCSR0A &= ~(1 << U2X0); // baud doubler off  - Only needed on Uno XXX
  #else
    uint16_t UBRR0_value = ((F_CPU / (4L * BAUD_RATE)) - 1)/2;
    UCSR0A |= (1 << U2X0);  // baud doubler on for high baud rates, i.e. 115200
  #endif
  UBRR0H = UBRR0_value >> 8;
  UBRR0L = UBRR0_value;
            
  // enable rx and tx
  UCSR0B |= 1<<RXEN0;
  UCSR0B |= 1<<TXEN0;
	
  // enable interrupt on complete reception of a byte
  UCSR0B |= 1<<RXCIE0;
	  
  // defaults to 8-bit, no parity, 1 stop bit
}
这个初始化函数是关于对寄存器的设置和符号字有关,目前不是很懂,之后再看。希望有明白的人告诉我下。
// 写入一个字节到TX串行缓冲区。主程序调用。
// 检查我们是否可以加快写字符串的速度,而不是单字节。
void serial_write(uint8_t data) {
  // 将头下标前移一位
  uint8_t next_head = serial_tx_buffer_head + 1;
  if (next_head == TX_BUFFER_SIZE) { next_head = 0; }

  // 等待缓冲区中有空间
  while (next_head == serial_tx_buffer_tail) { 
    // TODO: 调整 st_prep_buffer() calls to be executed here during a long print.    
    if (sys.rt_exec_state & EXEC_RESET) { return; } // 只检查中止,以避免无休止的循环。.
  }

  // 存储数据和前进头
  serial_tx_buffer[serial_tx_buffer_head] = data;
  serial_tx_buffer_head = next_head;
  
  // 启用数据寄存器空中断以确保TX流正在运行。
  UCSR0B |=  (1 << UDRIE0); 
}
这个函数里面有个判断是
if (sys.rt_exec_state & EXEC_RESET) { return; } // 只检查中止,以避免无休止的循环。.

其中的sys是在system.h中定义的全局系统变量,结构如下:

typedef struct {
  uint8_t abort;                 // 系统中止标志。强制退出回主循环for复位。
  uint8_t state;                 // 当前grbl的状态
  uint8_t suspend;               // 系统延迟 bitflag 变量用来管理 保存,取消,安全门

  volatile uint8_t rt_exec_state;  // 全局实际时间执行者bitflag 变量对于状态管理. 看执行掩码
                                   //Global realtime executor bitflag variable for state management. See EXEC bitmasks.
  volatile uint8_t rt_exec_alarm;  // 全局实际时间执行者bitflag 变量对于设置各种报警
                                 
  int32_t position[N_AXIS];      // Real-time machine (aka home) position vector in steps. 
                                 //实时机位置矢量逐步地
                                 // NOTE: This may need to be a volatile variable, if problems arise.                             

  uint8_t homing_axis_lock;       // Locks axes when limits engage. Used as an axis motion mask in the stepper ISR.
  volatile uint8_t probe_state;   // Probing state value.  Used to coordinate the probing cycle with stepper ISR.
  int32_t probe_position[N_AXIS]; // Last probe position in machine coordinates and steps.
  uint8_t probe_succeeded;        // Tracks if last probing cycle was successful.
} system_t;

后面的变量具体意思也不是很清楚,rt_exec_state字面看是状态的感觉。

// 数据寄存器空中断处理程序
ISR(SERIAL_UDRE)
{
  uint8_t tail = serial_tx_buffer_tail; // Temporary serial_tx_buffer_tail (to optimize for volatile)
  
  #ifdef ENABLE_XONXOFF
    if (flow_ctrl == SEND_XOFF) { 
      UDR0 = XOFF_CHAR; 
      flow_ctrl = XOFF_SENT; 
    } else if (flow_ctrl == SEND_XON) { 
      UDR0 = XON_CHAR; 
      flow_ctrl = XON_SENT; 
    } else
  #endif
  { 
    //从缓冲区发送一个字节
    UDR0 = serial_tx_buffer[tail];
  
    // 更新尾下标的位置
    tail++;
    if (tail == TX_BUFFER_SIZE) { tail = 0; }
  
    serial_tx_buffer_tail = tail;
  }
  
  // 关闭数据寄存器空中断停止TX流,结束数据传输
  if (tail == serial_tx_buffer_head) { UCSR0B &= ~(1 << UDRIE0); }
}

这个函数是中断函数。还是里面的有一部分代码不是很清楚:

 #ifdef ENABLE_XONXOFF
    if (flow_ctrl == SEND_XOFF) { 
      UDR0 = XOFF_CHAR; 
      flow_ctrl = XOFF_SENT; 
    } else if (flow_ctrl == SEND_XON) { 
      UDR0 = XON_CHAR; 
      flow_ctrl = XON_SENT; 
    } else

而且里面#ifdef...if...else...#endif,else的内容写在了#endif之后,可能接触的少了,没看过这样的写法,希望有人能指点下。

// 获取串行读缓冲区中的第一个字节。主程序调用。
uint8_t serial_read()
{
  uint8_t tail = serial_rx_buffer_tail; // Temporary serial_rx_buffer_tail (to optimize for volatile)
  if (serial_rx_buffer_head == tail) {//判断队列是否为空
    return SERIAL_NO_DATA;
  } else {
    uint8_t data = serial_rx_buffer[tail];
    
    tail++;
    if (tail == RX_BUFFER_SIZE) { tail = 0; }//尾下标指到了数组边界外,循环队列从零开始
    serial_rx_buffer_tail = tail;

    #ifdef ENABLE_XONXOFF
      if ((serial_get_rx_buffer_count() < RX_BUFFER_LOW) && flow_ctrl == XOFF_SENT) { 
        flow_ctrl = SEND_XON;
        UCSR0B |=  (1 << UDRIE0); // Force TX
      }
    #endif
    
    return data;
  }
}

#ifdef..#endif这之间的看不懂T.T

ISR(SERIAL_RX)
{
  uint8_t data = UDR0;
  uint8_t next_head;
  
  // 从串行流中直接提取实时命令字符。这些字符是
  // 不传递到缓冲区,但这些设置系统状态标记位用于实时执行。
  switch (data) {
    case CMD_STATUS_REPORT: bit_true_atomic(sys.rt_exec_state, EXEC_STATUS_REPORT); break; // Set as true
    case CMD_CYCLE_START:   bit_true_atomic(sys.rt_exec_state, EXEC_CYCLE_START); break; // Set as true
    case CMD_FEED_HOLD:     bit_true_atomic(sys.rt_exec_state, EXEC_FEED_HOLD); break; // Set as true
    case CMD_SAFETY_DOOR:   bit_true_atomic(sys.rt_exec_state, EXEC_SAFETY_DOOR); break; // Set as true
    case CMD_RESET:         mc_reset(); break; // 呼叫运动控制重置例程。
    default: // 将字符写入缓冲区    
      next_head = serial_rx_buffer_head + 1;
      if (next_head == RX_BUFFER_SIZE) { next_head = 0; }
    
      // 将数据写入缓冲区,除非它已满。
      if (next_head != serial_rx_buffer_tail) {//判断是否队列满了
        serial_rx_buffer[serial_rx_buffer_head] = data;
        serial_rx_buffer_head = next_head;    
        
        #ifdef ENABLE_XONXOFF
          if ((serial_get_rx_buffer_count() >= RX_BUFFER_FULL) && flow_ctrl == XON_SENT) {
            flow_ctrl = SEND_XOFF;
            UCSR0B |=  (1 << UDRIE0); // Force TX
          } 
        #endif
        
      }
      //TODO: else alarm on overflow?
  }
}

中断函数判断读取字符的类型。

//复位操作,让尾下标指向头下标,循环队列代表空
void serial_reset_read_buffer() 
{
  serial_rx_buffer_tail = serial_rx_buffer_head;

  #ifdef ENABLE_XONXOFF
    flow_ctrl = XON_SENT;
  #endif
}

        总的来说,函数功能理解不难,主要是具体实现,特别是#ifdef...#endif里面的代码段不懂,然后就是寄存器设置的代码不是很清楚。希望有明白grbl的大佬能指点下。也可以相互学习,共同进步。



评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值