草庐IT

SOEM协议栈代码研读笔记(一)

zhiyaormb 2023-06-04 原文

        最近搞EtherCAT。EtherCAT协议栈目前无非就是几种选择,要么花钱买商用的,要么IGH或者SOEM。IGH算是实现得比较全,不过只能在Linux操作系统上运行。Linux大家都知道,并非实时操作系统,于是就有了一些实时补丁,比较典型的Xenomi,就是独立于Linux的一个内核。移植Xenomai,倒也是一个思路。不过毕竟在Linux的底子上,想要进一步提升总线周期的稳定性,恐怕要从Linux内核层入手进行深度改造了。好消息是暂时并不需要把功能做得比较全,这样简单一些得SOEM在MCU上裸跑也是个不错(偷懒)的选择。网上关于SOEM移植的文章很多,参照官方例程依葫芦画瓢也问题不大。倒是详细读一下SOEM的源代码的文章比较少。所以,借着这个机会,做些笔记,一方面整理一下自己的思路,另一方面也希望对大家有点小帮助。

SOEM版本:1.4.0.

只是声明两点:

1 纯个人理解,未必正确,欢迎各位斧正;

2 第一次写,而且是想到哪里写到哪里,条理可能不够清晰,更加不可能面面俱到;

3 个人创作,转载请注明出处。

        SOEM,官方下来的源代码主要在SOEM, OSAL,OSHW三个文件夹中。其中SOEM是协议栈的内核,OSAL主要提供一些定时器,互斥锁,线程管理等操作系统的支持。OSHW提供以太网的硬件支持。一般来说,移植主要从OSAL,OSHW两个文件夹入手,不过本文重点不在移植,而在代码解读,侧重点自然会有所区别。

        那么,从哪里开始呢?一般来说,这种情况最好的是官方的DOC,但是可惜,官方的DOC似乎没有比较详细的说明,不过,还好有例程。

        官方提供的simpletest就很好,可以拿它作为打开这扇大门的钥匙。simpletest基本上刚开始就是如下这一段:

/* create thread to handle slave error handling in OP */
osal_thread_create(&thread1, 128000, &ecatcheck, (void*)&ctime);
strcpy(ifbuf, argv[1]);
/* start cyclic part */
simpletest(ifbuf);

        这一段很简单,先开线程,几乎所有操作系统都可以做到。在Windows上,直接就是调用的CreateThread:

int osal_thread_create(void **thandle, int stacksize, void *func, void *param)
{
   *thandle = CreateThread(NULL, stacksize, func, param, 0, NULL);
   if(!thandle)
   {
      return 0;
   }
   return 1;
}

在RTK上也是类似:

int osal_thread_create(void *thandle, int stacksize, void *func, void *param)
{
   thandle = task_spawn ("worker", func, 6,stacksize, param);
   if(!thandle)
   {
      return 0;
   }
   return 1;
}

        如此说来,最后传的void* param在这个例子当中并没有用到,不过,想要利用起来也没有问题,无非就是看想传什么参数了。

        这个例子里面,是这样的:

OSAL_THREAD_FUNC ecatcheck(void* lpParam)
{
    int slave;

    while (1)
    {
		if (inOP && ((wkc < expectedWKC) || ec_group[currentgroup].docheckstate))
        {
            ……

        }
        osal_usleep(10000);
    }

    return (void)0;
}

        关于细节以后有需要再说。不过整体上可以看到,无非就是周期性扫描一下各个slave的状态,必要的时候进行一些恢复性的设置。这个例子当中,是先开线程后初始化设备,其实没必要。

        需要注意的是,这里并没有直接进行过程数据的收发,总线的周期并不是由这个Thread来定的。所以,这个优先级不高,且对实时性要求也不高。

        当然,没有操作系统也无所谓,一个周期性定时器中断也可以,这样的话,osal_usleep(10000)这个调用也省了。不过需要注意一点是互斥处理,不建议频繁开关中断(尤其是处理过程数据的中断)的方式。

        开了线程之后,调用simpletest函数,并把网卡名作为输入参数。对于自己做的板卡,操作的网卡是哪一个自己知道,所以这个网卡名也不是必须的。

        接下来要进入正题了。进入simpletest,第一步是调用ec_init函数:

int ec_init(const char * ifname)
{
   return ecx_init(&ecx_context, ifname);
}

这个ifname是为了区分不同的网卡,在windows上有用,如果是自己的板子上做移植,就不需要,传个空指针就可以。这里需要注意一下ecx_context:

这个结构体定义如下:

struct ecx_context
{
   /** port reference, may include red_port */
   ecx_portt      *port;
   /** slavelist reference */
   ec_slavet      *slavelist;
   /** number of slaves found in configuration */
   int            *slavecount;
   /** maximum number of slaves allowed in slavelist */
   int            maxslave;
   /** grouplist reference */
   ec_groupt      *grouplist;
   /** maximum number of groups allowed in grouplist */
   int            maxgroup;
   /** internal, reference to eeprom cache buffer */
   uint8          *esibuf;
   /** internal, reference to eeprom cache map */
   uint32         *esimap;
   /** internal, current slave for eeprom cache */
   uint16         esislave;
   /** internal, reference to error list */
   ec_eringt      *elist;
   /** internal, reference to processdata stack buffer info */
   ec_idxstackT   *idxstack;
   /** reference to ecaterror state */
   boolean        *ecaterror;
   /** reference to last DC time from slaves */
   int64          *DCtime;
   /** internal, SM buffer */
   ec_SMcommtypet *SMcommtype;
   /** internal, PDO assign list */
   ec_PDOassignt  *PDOassign;
   /** internal, PDO description list */
   ec_PDOdesct    *PDOdesc;
   /** internal, SM list from eeprom */
   ec_eepromSMt   *eepSM;
   /** internal, FMMU list from eeprom */
   ec_eepromFMMUt *eepFMMU;
   /** registered FoE hook */
   int            (*FOEhook)(uint16 slave, int packetnumber, int datasize);
   /** registered EoE hook */
   int            (*EOEhook)(ecx_contextt * context, uint16 slave, void * eoembx);
   /** flag to control legacy automatic state change or manual state change */
   int            manualstatechange;
   /** userdata, promotes application configuration esp. in EC_VER2 with multiple 
    * ec_context instances. Note: userdata memory is managed by application, not SOEM */
   void           *userdata;
};
typedef struct ecx_context ecx_contextt;

于是,变量ecx_context又把总线运行的一系列信息放在了这里:

ecx_contextt  ecx_context = {
    &ecx_port,          // .port          =
    &ec_slave[0],       // .slavelist     =
    &ec_slavecount,     // .slavecount    =
    EC_MAXSLAVE,        // .maxslave      =
    &ec_group[0],       // .grouplist     =
    EC_MAXGROUP,        // .maxgroup      =
    &ec_esibuf[0],      // .esibuf        =
    &ec_esimap[0],      // .esimap        =
    0,                  // .esislave      =
    &ec_elist,          // .elist         =
    &ec_idxstack,       // .idxstack      =
    &EcatError,         // .ecaterror     =
    &ec_DCtime,         // .DCtime        =
    &ec_SMcommtype[0],  // .SMcommtype    =
    &ec_PDOassign[0],   // .PDOassign     =
    &ec_PDOdesc[0],     // .PDOdesc       =
    &ec_SM,             // .eepSM         =
    &ec_FMMU,           // .eepFMMU       =
    NULL,               // .FOEhook()
    NULL,               // .EOEhook()
    0,                  // .manualstatechange
    NULL,               // .userdata
};

这里面初步的介绍,注释上都有说明,具体内容后续涉及到的时候再深究。不过这里吐槽一下SOEM的代码风格,里面用了很多类似这种方式:

&ec_group[0];

或者

int64          *DCtime;

有的地方甚至指针套了好几层,总觉得没必要,而且容易出错。

再然后,ecx_init调用了ecx_setupnic。

int ecx_init(ecx_contextt *context, const char * ifname)
{
   return ecx_setupnic(context->port, ifname, FALSE);
}

这里三层外三层的,简直是个俄罗斯套娃。不过好在,我们终于找到正主了:

int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) 
{
   int i, rval;
   pcap_t **psock;

   rval = 0;
   if (secondary)
   {
      /* secondary port stuct available? */
      if (port->redport)
      {
         /* when using secondary socket it is automatically a redundant setup */
         psock = &(port->redport->sockhandle);
         *psock = NULL;
         port->redstate                   = ECT_RED_DOUBLE;
         port->redport->stack.sock        = &(port->redport->sockhandle);
         port->redport->stack.txbuf       = &(port->txbuf);
         port->redport->stack.txbuflength = &(port->txbuflength);
         port->redport->stack.tempbuf     = &(port->redport->tempinbuf);
         port->redport->stack.rxbuf       = &(port->redport->rxbuf);
         port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);
         port->redport->stack.rxsa        = &(port->redport->rxsa);
         ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));
      }
      else
      {
         /* fail */
         return 0;
      }
   }
   else
   {
      InitializeCriticalSection(&(port->getindex_mutex));
      InitializeCriticalSection(&(port->tx_mutex));
      InitializeCriticalSection(&(port->rx_mutex));
      port->sockhandle        = NULL;
      port->lastidx           = 0;
      port->redstate          = ECT_RED_NONE;
      port->stack.sock        = &(port->sockhandle);
      port->stack.txbuf       = &(port->txbuf);
      port->stack.txbuflength = &(port->txbuflength);
      port->stack.tempbuf     = &(port->tempinbuf);
      port->stack.rxbuf       = &(port->rxbuf);
      port->stack.rxbufstat   = &(port->rxbufstat);
      port->stack.rxsa        = &(port->rxsa);
      ecx_clear_rxbufstat(&(port->rxbufstat[0]));
      psock = &(port->sockhandle);
   }    
   /* we use pcap socket to send RAW packets in windows user mode*/
   *psock = pcap_open(ifname, 65536, PCAP_OPENFLAG_PROMISCUOUS | 
                                     PCAP_OPENFLAG_MAX_RESPONSIVENESS |
                                     PCAP_OPENFLAG_NOCAPTURE_LOCAL, -1, NULL , errbuf);
   if (NULL == *psock)
   {
      printf("interface %s could not open with pcap\n", ifname);
      return 0;
   }
   
    for (i = 0; i < EC_MAXBUF; i++)
   {
      ec_setupheader(&(port->txbuf[i]));
      port->rxbufstat[i] = EC_BUF_EMPTY;
   }
   ec_setupheader(&(port->txbuf2));

   return 1;
}

这个函数在oshw文件夹下的nicdrv.c文件中,不同的硬件,这里肯定不一样。上面的例子是基于win32的。对比一下rtk的版本:

int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) 
{
   int i;
   int rVal;
   int *psock;

   port->getindex_mutex = mtx_create();
   port->tx_mutex = mtx_create();
   port->rx_mutex = mtx_create();

   rVal = bfin_EMAC_init((uint8_t *)priMAC);
   if (rVal != 0)
      return 0;
      
   if (secondary)
   {
      /* secondary port stuct available? */
      if (port->redport)
      {
         /* when using secondary socket it is automatically a redundant setup */
         psock = &(port->redport->sockhandle);
         *psock = -1;
         port->redstate                   = ECT_RED_DOUBLE;
         port->redport->stack.sock        = &(port->redport->sockhandle);
         port->redport->stack.txbuf       = &(port->txbuf);
         port->redport->stack.txbuflength = &(port->txbuflength);
         port->redport->stack.tempbuf     = &(port->redport->tempinbuf);
         port->redport->stack.rxbuf       = &(port->redport->rxbuf);
         port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);
         port->redport->stack.rxsa        = &(port->redport->rxsa);
         ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));
      }
      else
      {
         /* fail */
         return 0;
      }
   }
   else
   {
      port->getindex_mutex = mtx_create();
      port->tx_mutex = mtx_create();
      port->rx_mutex = mtx_create();
      port->sockhandle        = -1;
      port->lastidx           = 0;
      port->redstate          = ECT_RED_NONE;
      port->stack.sock        = &(port->sockhandle);
      port->stack.txbuf       = &(port->txbuf);
      port->stack.txbuflength = &(port->txbuflength);
      port->stack.tempbuf     = &(port->tempinbuf);
      port->stack.rxbuf       = &(port->rxbuf);
      port->stack.rxbufstat   = &(port->rxbufstat);
      port->stack.rxsa        = &(port->rxsa);
      ecx_clear_rxbufstat(&(port->rxbufstat[0]));
      psock = &(port->sockhandle);
   }  

   /* setup ethernet headers in tx buffers so we don't have to repeat it */
   for (i = 0; i < EC_MAXBUF; i++)
   {
      ec_setupheader(&(port->txbuf[i]));
      port->rxbufstat[i] = EC_BUF_EMPTY;
   }
   ec_setupheader(&(port->txbuf2));

   return 1;
}

        两者的处理大同小异。简单来说,就是分配收发缓冲区地址,打开硬件,再把数据包头写到每一个发送缓冲区首部,免得后续每次都写。另外初始化了一些保护关键代码段的互斥锁。如果是裸跑的话,在保护关键代码的时候可能要考虑用开关中断来实现了。再一个,可以看到,这里实际上是可以打开第二个网口的。两个网口,一个作为输出,一个作为输入。这个按实际情况来做吧,目前见到的应用,多数是只用一个网口的。

这里的重点在于这个port。可以看到,实际上这里是用了在ethercatmain.c文件中定义的全局变量:

ecx_portt               ecx_port;

还记得之前说的那个ecx_context吗?对,这个ecx_port就是ecx_context里面的。

在nicdrv.h文件中,这个exc_portt定义如下:

typedef struct
{
   ec_stackT   stack;
   pcap_t      *sockhandle;
   /** rx buffers */
   ec_bufT rxbuf[EC_MAXBUF];
   /** rx buffer status */
   int rxbufstat[EC_MAXBUF];
   /** rx MAC source address */
   int rxsa[EC_MAXBUF];
   /** temporary rx buffer */
   ec_bufT tempinbuf;
   /** temporary rx buffer status */
   int tempinbufs;
   /** transmit buffers */
   ec_bufT txbuf[EC_MAXBUF];
   /** transmit buffer lenghts */
   int txbuflength[EC_MAXBUF];
   /** temporary tx buffer */
   ec_bufT txbuf2;
   /** temporary tx buffer length */
   int txbuflength2;
   /** last used frame index */
   int lastidx;
   /** current redundancy state */
   int redstate;
   /** pointer to redundancy port and buffers */
   ecx_redportt *redport;   
   CRITICAL_SECTION getindex_mutex;
   CRITICAL_SECTION tx_mutex;
   CRITICAL_SECTION rx_mutex;
} ecx_portt;

这个nicdrv.h和具体的硬件和平台有关,上面这一段是win32的。再来看看rtk的:

typedef struct
{
   ec_stackT   stack;
   int         sockhandle;
   /** rx buffers */
   ec_bufT rxbuf[EC_MAXBUF];
   /** rx buffer status */
   int rxbufstat[EC_MAXBUF];
   /** rx MAC source address */
   int rxsa[EC_MAXBUF];
   /** temporary rx buffer */
   ec_bufT tempinbuf;
   /** temporary rx buffer status */
   int tempinbufs;
   /** transmit buffers */
   ec_bufT txbuf[EC_MAXBUF];
   /** transmit buffer lenghts */
   int txbuflength[EC_MAXBUF];
   /** temporary tx buffer */
   ec_bufT txbuf2;
   /** temporary tx buffer length */
   int txbuflength2;
   /** last used frame index */
   int lastidx;
   /** current redundancy state */
   int redstate;
   /** pointer to redundancy port and buffers */
   ecx_redportt *redport;   
   mtx_t * getindex_mutex;
   mtx_t * tx_mutex;
   mtx_t * rx_mutex;
} ecx_portt;

依然是大同小异。不同的是sockhandle的定义,以及几个前文提到的互斥量。也只是形式上的不同而异。

当然,这里还包括这个ec_stackT结构体类型,win32下,其定义如下:

typedef struct
{
   /** socket connection used */
   pcap_t      **sock;
   /** tx buffer */
   ec_bufT     (*txbuf)[EC_MAXBUF];
   /** tx buffer lengths */
   int         (*txbuflength)[EC_MAXBUF];
   /** temporary receive buffer */
   ec_bufT     *tempbuf;
   /** rx buffers */
   ec_bufT     (*rxbuf)[EC_MAXBUF];
   /** rx buffer status fields */
   int         (*rxbufstat)[EC_MAXBUF];
   /** received MAC source address (middle word) */
   int         (*rxsa)[EC_MAXBUF];
} ec_stackT;   

在rtk的定义如下:

typedef struct
{
   /** socket connection used */
   int         *sock;
   /** tx buffer */
   ec_bufT     (*txbuf)[EC_MAXBUF];
   /** tx buffer lengths */
   int         (*txbuflength)[EC_MAXBUF];
   /** temporary receive buffer */
   ec_bufT     *tempbuf;
   /** rx buffers */
   ec_bufT     (*rxbuf)[EC_MAXBUF];
   /** rx buffer status fields */
   int         (*rxbufstat)[EC_MAXBUF];
   /** received MAC source address (middle word) */
   int         (*rxsa)[EC_MAXBUF];
} ec_stackT;  

除了sock之外,一模一样。这里EC_MAXBUF是ethercattype.h文件当中的宏定义:

#define EC_MAXBUF          16

该文件在SOEM文件夹当中。还有ec_bufT的定义

#define EC_MAXECATFRAME    1518

#define EC_BUFSIZE         EC_MAXECATFRAME

typedef uint8 ec_bufT[EC_BUFSIZE];

这个1518数字看着眼熟。看看EtherCAT数据帧格式就一目了然了:

请原谅,图当然是别处找的。

这个ec_bufT就是收发缓冲区。同样的,吐槽一下类似这样的方式:

ec_bufT     (*rxbuf)[EC_MAXBUF];

这个直接拿来用可能不觉得怎样,如果要修改的话,挺别扭的。

读到这里,其实如果有必要,可以对ecx_portt进行一些自己的改造,譬如说,不希望编译器自动给缓冲区分配地址,就把对应的缓冲区声明为指针,然后在初始化时,对ecx_port当中对应的指针赋初值,使其指向需要的地址。

顺便也说一下这个ecx_redportt结构体:

typedef struct
{
   ec_stackT   stack;
   pcap_t      *sockhandle;
   /** rx buffers */
   ec_bufT rxbuf[EC_MAXBUF];
   /** rx buffer status */
   int rxbufstat[EC_MAXBUF];
   /** rx MAC source address */
   int rxsa[EC_MAXBUF];
   /** temporary rx buffer */
   ec_bufT tempinbuf;
} ecx_redportt;

        依然是在nicdrv.h当中定义。这就是一个只有接收功能的ecx_portt的阉割版本,没有发送缓冲区及其状态标志,因为收发过程中的关键代码保护互斥量已经有了,所以这里连互斥量都省了。细心的你可能在ecx_portt当中发现一个问题:

/** temporary rx buffer */
   ec_bufT tempinbuf;
   /** temporary rx buffer status */
   int tempinbufs;
   /** transmit buffers */
   ec_bufT txbuf[EC_MAXBUF];
   /** transmit buffer lenghts */
   int txbuflength[EC_MAXBUF];
   /** temporary tx buffer */
   ec_bufT txbuf2;

        收发缓冲区明明已经定义了,为什么又多出来了临时收发缓冲区?而且这两个缓冲区在ecx_setupnic函数里面是单独进行初始化的。整个放在一起来处理不是更清晰方便?这个问题要留在收发处理当中去解答了。这里要吐槽一下这个tempinbuf和txbuf2的命名,真的有点混乱。

        还有这两个函数:

void ec_setupheader(void *p)
{
   ec_etherheadert *bp;
   bp = p;
   bp->da0 = htons(0xffff);
   bp->da1 = htons(0xffff);
   bp->da2 = htons(0xffff);
   bp->sa0 = htons(priMAC[0]);
   bp->sa1 = htons(priMAC[1]);
   bp->sa2 = htons(priMAC[2]);
   bp->etype = htons(ETH_P_ECAT);
}
static void ecx_clear_rxbufstat(int *rxbufstat)
{
   int i;
   for(i = 0; i < EC_MAXBUF; i++)
   {
      rxbufstat[i] = EC_BUF_EMPTY;
   }
}

        这里是win32版本的,没什么好深究的。这次先写到这里吧。这次只涉及到点皮毛,下次写数据收发,缓冲和FMMU。

有关SOEM协议栈代码研读笔记(一)的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  4. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  8. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

随机推荐