电路板驱动开发记录

电路板驱动开发记录

本文不会事无巨细,更多的个人感想,及解决问题的思路,可以说成是经验总结,不是说好记性不如烂笔头吗,所以写如下东西纯粹个人兴趣,如果能够对别人有所帮助,那么则善莫大焉

问题背景

这里 所说的 Disp board。It是公司DR产品一个子系统上面的一块小电路板,因为其主要负责一些信息的八段数码管 显示功能,姑且名之Display board简称 DISP board

简短说,这块板子上面的红外接收芯片EOL(end of life)了,也就说市面上能够买到的片子按照当前DR的出货情况估算,最多能撑到7月份,所以必须重新design一块新的替代品,防止断货,影响生产,na就严重了 。

简短背景介绍到此,新的电路板,那就要开发新的固件(Firmware)

电路板由公司其他人开发,固件由本人负责。

开发策略

硬件设计这一块,实际上做到meet需求就可以了,没有必要做over design的事情,那么前期方案(主要是处理器)选型的时候,我没有参与,因为最开始的plan中本人并不在loop中,那么差不多方案定下来的时候,我才进去,这个时候,我也了解了一下细节,并提出了一些自己的观点,主要是从team长远来说,

  • 能否尽量减少把一些分散的功能集中起来,主要是子系统W上面有三块跑firmware的电路板,其中一块作为主控,另一块是辅助管理detector相关功能的,第三块是用来作为红外接收,和显示的,那么我的观点是是否可以把二,三块电路板完成的功能用一块电路板来实现,好处不言而喻,无论从开发还是维护,成本来说都是有好处的。
  • 如果说不能集中,因为代价太大,那么是否可以把新的电路板的处理器选成和swallow的处理器一样,因为这样的话,部分底层的driver就基本可以重用了,不必要重新做开发。这对于人力资本的投入的要求就会更小。而且后期维护的话也是比较方便的,否则又多了一套需要维护的firmware.

综合考虑后,因为S板的处理器不支持bonder scan功能,对硬件单板出厂测试是个限制,所以选择了现在的lpc54101处理器。后来也是因为公司的业务变动,事情也额没有那么多了,也就这样吧,毕竟凡事都是双刃剑,有好有坏。不多说了。

那么为了能够在样板回来前,能够做一下功能上的验证,在设计电路板的同时,买了一块demo板,是ucdragon的,深圳一家公司的cpu是lpc54102,这个是双核的,一个contex-m4+m0,而54101只有一个m4核。

在demo板上验证spi的时候,因为搭了一下spi线(4根中的哪根忘了),把cpu烧了,可见demo设计的可靠性很差,还好邮过去,很快就修好了,换了一颗新的cpu。

 

这个demo板上的demo程序是基于NXP老平台的,NXP把freescale收购了 以后呢,使用了freescale的平台代码,抛弃了老平台代码。老的代码模块化做的比较好,比较好移植,缺点是用lib的方式对驱动进行管理,这其实不太利于问题的调试的。

 

 

驱动开发

在样板回来前,在demo板上也简单测了测串口0,1,timer和SPI,都能够工作了。

下面的叙述中,每一块功能,我都会附带描述demo上是如何做的,当然着重叙述在样板上的实现。

电路板上面的主要用的外设:

- DBG UART;

- UART data R/T;

- SPI slave;

- infrared;

- LEDs;

- 8 segment Digital tube.

- Timer

 

还落下什么了吗, 后面再补 。

其实,在驱动开发的过程中,还是遇到了一些问题的,但是都一一的化解了,其实我想说的是驱动这个东西,除非一摸一样的你做过,否则对谁来说都是一样的,看demo代码,SDK,datasheet,不懈的调试,Google/baidu,和运气:),在这个过程中示波器是灰常重要的。如果说与人相关的那就是经验,细心程度,把握细节的能力,执着的态度,还有就是善于利用一起资源,demo板供应商的技术支持,CPU的技术支持,老同学,老同事,微信,电话朋友,胡思乱想,还有做梦。本人当然尊重独立思考,但是有时候外部的resource能够激发你的小宇宙,得到正确的解决问题的方法。另外本人着重阐述思想,不会详细的把每一行code的具体实现都贴出来,当然为了增加点击率,也会适当的贴出一些关键的代码:)

 

其实驱动设计的关键点在于结合电路原理图找到具体的外设所使用的IO口,然后根据功能- 对IO进行必要的配置,比如说function,方向,输入还是输出等,

- 在这一步配正确的基础上,才是具体初始化,

- 读,

- 写的调试。

 

系统时钟

Demo板上使用的是CPU的内部时钟12MHz,样板以后简称DISP板上使用的是外部20MHz的时钟,所以这里需要重新配置一下。

  1. 确认pin脚

从下图可以看到外部时钟使用的是PIO0_22。那么你就要对其进行配置了

电路板驱动开发记录

 

 

2,配置方法如下,这个demo程序已经配置好了,复用即可。

在STATIC const PINMUX_GRP_T pinmuxing[]中进行配置:

            {0, 22, (IOCON_FUNC1 | IOCON_MODE_PULLUP | IOCON_DIGITAL_EN)},/* external clock input pin */

或者直接调用

            /* Select external clock input pin */

            Chip_IOCON_PinMuxSet(LPC_IOCON, 0, 22, (IOCON_MODE_PULLUP |

                                                                                                                                    IOCON_FUNC1 | IOCON_DIGITAL_EN | IOCON_INPFILT_OFF));,

  1. 打开下面这个宏定义

/** Set the BOARD_USECLKINSRC definition to (1) to use the external clock

    input pin as the PLL source. The BOARD_ECTCLKINRATE definition must

    be setup with the correct rate in the CLKIN pin. Set this to (0) to

    use the IRC for the PLL source. */

#define BOARD_USECLKINSRC       (1)

其他的具体怎么设置的看下手册,system config的章节了解。

  1. 测试一下

A, 可以通过读取timer的clockin进行测试,通过串口打印出来看。

B,Chip_Clock_GetAsyncSyscon_ClockRate()的输出

C,或者用示波器量一下。

DBG UART;

调试串口使用的是串口0,其实也没什么好说的,因为demo板上已经使能了,而且使用IO相同。简单的搞两下就可以了。但是实际上串口很关键,如果串口有问题,再没有调试工具的话,那么很难进行,只有通过点亮某个灯来看代码走到哪里了。

UART 1 simulate Modbus

串口1呢,在DISP板回来前,是使用demo板调的,简单的方法是使用串口1作为调试串口,看能否正常工作。当然了,demo板上有调试串口1输入输出的例子,直接拿过来用就可以了。在DISP板回来后,同样的也可以这样测试,但是呢,这个串口1实际应用中是用来传输数据到ALB板的,所以真正能工作还是要和A板连起来调,这就需要搭建一个测试环境了

电路板驱动开发记录

 

开始的时候,死活收不到数据,后来经过一系列的尝试,才能够收到数据,其实这和学会如何使用这些底层驱动接口有关系。

NXP的老平台的UART驱动,是通过rx_done来决定是否已经把数据收下来了的,当有数据来之前,首先在初始化的时候,要调用ROM_UART_Receive(hUART, rxbuf, 1),然后才能接收到中断,否则不会有中断进来,这是这个平台驱动的特点,在进入中断后,首先调用ROM_UART_Handler(hUART),然后再 调用ROM_UART_Receive(hUART, rxbuf, 1)进行接收,如果rx_done这个标志没有被置,表示数据没有接收完,下次还会再 进入刚才的中断,直到rx_done置为收到的字节数,也就是说,在保存数据的时候,先要判断这个标志,如果是要收到的字节数,则进行保存,否则返回,下次还会再进入刚才的中断,而再次进入的这个中断不是新的数据。

还有就是这个串口1能够收发数据只是完成了第一步,DISP与A板之间通过uart1通讯协商的是modbus协议,也就是说数据帧之间区分是靠4个ms的延时来实现的,那就要通过启动一个match timer来实现。不管怎么说反正工作正常了。

SPI通讯

DISP板是要实现为SPI slave节点的。这个外设实际上是最先开始调的,先是在demo板上,然后是DISP板上,但是后来发现一个严重的问题,后面解释,

在demo板上,先跑跑它的例程,很简单,但是没有主节点,么有办法测,想了个办法,就是使用A板测,但是有一个问题ALB直接最后输出的是查分信号,而demo板上是TTL电平信号(5v为1, 0v为0),所以需要做一根线,然后把demo板和A板的SPI输出差分 前的信号接通。

电路板驱动开发记录

 

设备与设备之间通过某种硬件接口通讯,目前存在很多种接口,SPI接口是其中的一种。

SPI中分Master主设备和Slave从设备,数据发送都是由Master控制。普及点基本知识,,网上资料很多,下面就是在网上搜的,说的挺详细的,引用下:https://blog.csdn.net/ce123_zhouwei/article/details/6923293

一个master可以接一个或多个slave。常见用法是一个Master接一个slave.

通信只需要4根线:SCLK, MISO, MOSI, SS

CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:

Mode 0 CPOL=0, CPHA=0

Mode 1 CPOL=0, CPHA=1

Mode 2 CPOL=1, CPHA=0

Mode 3 CPOL=1, CPHA=1

CPOL极性

Clock Polarity = IDLE state of SCK。

再用中文详解:

SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:

CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;

CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low;

CPHA相位

首先说明一点,capture strobe = latch = read = sample,都是表示数据采样,数据有效的时刻。

相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。

对于:

CPHA=0,表示第一个边沿:

对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;

对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿:

对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;

对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

 

需要详细了解原理,百度吧。

环境搭好后,就可以通过示波器看波形了,对于SPI有一点需要知道的是,所有的控制权都在主节点,也就是主节点要发数据给从节点,从节点才能收到数据,同时在这个过程中从节点才能发数据给主节点,那意味着什么,也就是说从节点必须在主节点发数据给从节点之前准备好数据到buffer里面,有点绕吧,初学的会有一堆问题,什么时候主发数据给从节点,从节点怎么准备数据,buffer指的是什么,从节点怎么接收数据,等等,没关系,上手后慢慢问题就都有答案了,首先这里具体来说,A板会一直给demo发数据,也就是说,不管你收不收一直在发,可以通过示波器看主节点的MOSI信号。那么demo怎么能收到呢,哎,中断啊,但是,但是,如果从节点的buffer里面没有数据,貌似收不到中断哦,嘿嘿,所以,从节点在使能SPI中断后,首先要填充SPI 数据,并且使能收发中断

            NVIC_EnableIRQ(LPC_SPIIRQNUM);

    tx16[0] = 0x6003;

    // tx16[1] = 0x6003;

           

    sXfer.txBuff = tx16;

    sXfer.txSz = BUFFSENDSIZE;

    sXfer.rxBuff = rx16;

    sXfer.rxSz = BUFFSENDSIZE;

    sXfer.flags = 0;

    /* Start transfer. Will return immediately */

    xferDone = false;

ROM_SPIS_Transfer(spisHandle, &sXfer);

你会发现,你能收发数据了。

但是到了DISP板上的时候,开始的时候,也是很顺利就能接收发送数据了,几乎没费什么力。都怪前期准备充分啊。但是后来发现一个问题,就是7块DISP新板子中只有两块是SPI正常的,其他都不正常,why? 后来经过测试发现是因为我们DISP板上没有使用推荐的IO口作为SO而是使用的pin9,但是程序中也把14脚给使能为SO了,导致SPI工作不正常,按照NXP FAE的解释,这两个引脚如果功能相同的话,在CPU内部是“与”的关系,在外面呢14脚是作为bonder scan用的,它是一直拉低的,所以在CPU内部把9pin的信号也破坏了,那为什么部分正常呢,原理,这两块正常的板子都是飞过线的,无意中把这个关系给打破了。所以反而正常了。这个解释也是说的通,不管怎么样,你只是用一个pin,却把另一个pin配置为一摸一样的功能吧!

SPI ISSUE -  /*0x000E0002*/     ERR_SPI_TXUNDERRUN,

Underrun是slave没有数据回给master?

Infrared

这个调好还是废了些心血的。这个信号本身接收不难,就是通过一个IO给过来的,首先这个没有在demo板上调它,因为调到这个功能的时候,没差几天DISP板就要回来了。这个红外信号是这样的(借图)

 

电路板驱动开发记录

所以,需要采用时钟捕获功能,捕获到每个上升沿和下降沿,然后计算高低电平的持续时间,低电平持续560us为有效,它的前一个高电平如果宽度在360-1125us之间则识别为逻辑‘0’,如果在1125-1890us之间则识别为逻辑‘1’,按照这个算法,把红外数据识别出来,那么它是4个字节,其中第三个字节是有效数据,也就是key code。最后需要把这个key code发送给上位机,进行系统控制。说到时钟捕获呢,其实用timer可以实现,用sct也可以实现。这里使用的是timer。在timer初始化的时候,首先要把红外输入IO设置为timer的捕获输入,其实就是配置一下IO的功能

Chip_IOCON_PinMuxSet(LPC_IOCON, 0, 23,  (IOCON_FUNC3 | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP));

这里的IOCON_FUNC3就是时钟的捕获输入。

然后在配置timer capture的其他参数,最主要是使能capture中断

Chip_TIMER_CaptureEnableInt(LPC_TIMER0, 0);

这样的话,当有信号有沿的变化的时候,就会产生中断,那么在中断里,就可以根据信号的特点先设置下降沿,然后再设置上升沿,总之根据实际情况设置。

还有一个很重要的就是key code的过滤,因为你需要知道什么时候按键release了,这里是通过一个200ms的match timer实现的,当200ms超时后,就认为按键release了。

这个也是解决了在调试过程中遇到的一个问题,困扰了2,3天,就是并不是每次按键都能发送上去,成功率很低,分析来分析去没有找到原因,后来通过示波器发现,每次调用完

ROM_SPIS_Transfer(spisHandle, &sXfer);

后,SPI从一直会给主节点回复这个数据,这个和之前想象的完全不一样,也就是说,主要能够知道什么时候需要数据变化就可以了,而不用担心过,不会自动回复数据给主节点了,此时Y美眉提了timer的建议,就是这个200ms的定时器,这样就可以决定设么时候发送release按键了,而不是每次发完key code就置0x6003,因为速度太快主节点根本反应不过来,刚给它0x6003紧接着下一个按键又来了,或者刚发出去key code就release了,主节点不能完整的识别到一个key code。这里感谢Y美眉的timer,非常及时。总结这一点,其实如果仔细看以前板子的firmware也可以发现这一点,这里钻牛角尖疏忽了!

因为这个key code是要通过SPI发送给主节点的,所以key code按下和release后,发送给主节点的data是不同的,release key code 为固定0x6003。Key code列下(借图)

电路板驱动开发记录

在调试过程中,我把所有的高低电平的持续时间全部打印出来,然后去和正确的key code进行比对,这样更直观。

low width: 

3761 259 260 260 250 260 261 261 262 261 262 261 262 262 261 258 259 259 262 259 262 259 261 260 260 261 261 261 261 262 262 261 258 260 75

3761 260 260 261 260 260 261 261 262 261 261 260 261 258 260 259 258 259 261 259 261 260 260 260 260 261 260 261 261 261 250 262 261 261

3757 259 260 260 261 260 261 261 262 261 262 261 262 262 260 258 259 258 261 259 262 259 260 260 260 260 261 261 262 262 261 259 258 259

high width

1845 667 196 666 205 667 195 665 195 208 195 668 197 197 197 667 678 195 196 196 666 195 195 196 668 669 679 196 197 197 668 197 196 198 370

1843 669 197 668 196 667 208 677 206 208 206 679 218 206 218 678 678 206 206 206 677 207 209 207 680 690 679 208 206 207 679 207 207 208

LEDs

这个其实简单了,就是设置IO口的方向和state就可以了,按照LPC5410x_um.pdf的描述就可以了,DISP上面共有8个灯,每个灯代表不同的指示

DS13,DS14,DS15用的比较多

DS13 : heartbeat

DS14: warning /decode

DS15: detect/diag

8 segment Digital tube

下面这段代码就是实现数码管控制的核心,这是一个数码管控制器的算法,可以去看数码管的datasheet。而这三个信号

            LED_DATA_1;           

            LED_CLK_0;     

            LED_CLK_1;

实际上就是三个IO口,通过控制这个三个IO的state来实现数码管的显示

void SendChar (unsigned char ch)                 // MAX7219 output protocol

{

    unsigned char i,temp;

    //    Delay(10);

    for (i=0;i<8;i++)

    {

        temp=ch&0x80;

        ch=ch<<1;

        if(temp)                                 // CLK falling edge

        {

            LED_DATA_1;           

            LED_CLK_0;     

            LED_CLK_1;

        }

        else                                     // CLK rising edge

        {

            LED_DATA_0; 

            LED_CLK_0;

            LED_CLK_1;

        }

    }

}

 

关于数码管,别的不多说了,总之没有什么难度了。

到此DISP上面的所有外设功能都已经实现了,接下来还有几项工作要做:

  1. 规划on chip flash的使用
  2. DISP firmware remote download
  3. Boot loader的实现

上面三项需要一起考虑,需要短时间内完成,不想在这上面耽搁太多时间,争取1周全部搞定。

  1. FDD的撰写
  2. 单元/集成/子系统测试,包括测试case和执行
  3. SDLC文档release
  4. firmware发布包括发布给supplier和SW。

Boot loader

所以要实现bootloader主要是因为要实现remote download功能,因为应用程序远程下载的话,需要做一个flash的版本管理,成功了,从新的firmware启动,失败了则回退到老版本运行,这些工作需要有一个专门的程序管理,他就是bootloader,它的另一个重要功能是能够在bootloader的中的某个阶段跳转到app运行,

在LPC54101上面,这个也搞了差不多1天才搞定,先是根本跳不过去,后来经过查资料包括不断的尝试,调试,使用了高大 上的ulink pro调试了下

  1. 要设置VTOR
  2. 关闭中断
  3. 主时钟设置
  4. 堆栈SP
  5. PC指针

代码如下:

    SCB->VTOR = 0x8000;

LPC_SYSCON->MAINCLKSELB = 0;

     NVIC_DisableIRQ(UART1_IRQn);

上面这行很重要,需要把跳转之前使能的中断失效才行,不要轻易使用__disable_irq(),否则可能会在跳转后app里面所有中断功能失效,还不能随便的enable irq否则程序会死掉,至于应该如何正确使用__disable_irq()我这里还没有结论。

JumpToUserApplication(*((uint32_t *)0x8000), *((uint32_t *)0x8004));

 

__asm void JumpToUserApplication(uint32_t userSP, uint32_t userStartup)

{

    // set up stack pointer

    msr msp, r0

    msr psp, r0

    // Jump to PC (r1)

    mov pc, r1

}

好长 时间没写了,这段时间主要是在调这个rmdl, 坑实在太多,无暇顾及其他,今天终于算是搞定了,上面的跳转实现之后,就开始开发rmdl app,其实display上面的很快弄完了,但是ALB上面的程序花的时间比较多,原因是这个ALB上是块老板卡,开发起来比较费劲,它上面也有safe和app两块程序,而且都要支持rmdl,开发过程中需要动用三台电脑不断的编译,转换,烧写,没把我给累死,我想说尼玛,我知道有比这更复杂的开发,但是下次别再让我碰上。但是还是 遇到了目前还没有解决的遗留问题,因为是通过串口传输数据,而同时还要输出打印信息,这不是矛盾吗,也就是说调试就不能正常传输数据,传输数据就不能打印,靠,还好还有一个串口1,但是需要改程序,做串口线,搞定后,有个奇怪现象是串口1输出数据会对串口0有影响,这个目前还没时间查原因,估计是驱动问题,总之调通了。因为是两部分代码,需要通过合并工具进行二进制合并,并转换成hex文件,这样就可以用ISP下载了。接下来就是些文档工作了,这个也是比较繁琐的,14个文档需要release。

下图是原来设想的版本升级架构

电路板驱动开发记录

 

可以支持版本回退,但是实现实在是太复杂了,主动放弃了,只实现一个app的情况,通过设置最后一个sector中的flag,进行版本跳转控制。

简单说一下rmdl的架构,板子出厂的时候,烧写的是safe+app合并后的程序,然后进入产品,如果上位机软件支持display板的rmdl的话,当下载开始的时候,display板处于正常的 app运行状态,此时如果串口接收到了rmdl指令,则app先设置rmdl标记,再 通过函数调用跳转入safe执行,在safe模式内进行数据接收和保存,完成后,CRC校验没有问题,置rmdl标志,跳入app运行,在app内清rmdl标志,以保障下次重启的时候,能够进入app运行,如果下载的程序不是app程序,则会看门狗复位,同时保持在safe内运行,并能够接收下次下载命令,直至成功。

整理代码也就是代码重构也花了些时间测试。

其实还可以写的更多些,累了,也知道一停笔也就这样了,希望对大家有所帮助

 

-

深夜在寂静的墓地里拉小提琴,感觉爱的华尔兹舞曲可以传遍整个加勒比

上一篇:261,路径总和 II


下一篇:261 箭头函数(★★★)