占坑儿

Feed Rss

从买车那天开始就被原车带的CD和喇叭搞到崩溃。

一直想整个换掉,可是一直没有时间。最近终于抽出来一些时间,就逐步开始改装了。

车子先是升级了喇叭,换了惠威的C2000和CF260(感觉不够满意,中频欠缺不少)。

低音没有量,又加装了惠威的V6低音。

先说说这套组合吧,C2000这套喇叭高频太尖,中频很薄,听起来不是很对我胃口。我自己主要听男声和古典比较多。解析力还是不错的,也比较容易推的动。其他车友如果换的时候可以参考我给出的这个特征。

惠威的V6低音,只能说是入门产品……低音量有了,质一般,但基本进入了可以接受的范畴。选它主要是因为体积小,安装比较简单。放在副驾驶座椅下面空间足够。

这两个东西都升级好以后机头成了整套系统最悲剧的地方了。第一没有线路输出,低音炮要靠高转低接入,音质损失的很厉害。第二这机头本身小信号处理垃圾的要死,更别提功放模块了。所以换主机的念头涌上心头控住不住啦~
跟老婆软磨硬泡以后终于同意我装了(结了婚的人,难啊!)。

选机头最早想选双DIN位的,但是不是价格过高就是效果太烂。本次的目的很单纯,不要忽悠人的功能,比如什么视频播放啊导航啊。因为集成了这些功能的机器大多是国产一体机,音质都烂的要命。所以1DIN位的纯CD机成为我搜索的主要目标。

最终备选的有几款:

第一是阿尔派X100。这机头很猛,该有的功能基本都有,音质也是有口皆碑的棒。可惜已经停产,买新货很亏。

第二是阿尔派117E。应该是今年的新款,带USB,没有AUX,长的不够销魂。。。

第三是歌乐DXZ786USB。出了有年头了,带USB,带AUX,带主动三分频,带四声道声音延时调整,带24BIT DAC。最主要的是,声音调整的参数极其丰富。还有一个考虑,下文再说,卖个关子。

所以最后选了786USB。

要在新A3上换机头,要比其他车麻烦一些,这也是可能到目前为止都没看到有其他车友更换机头的原因。

需要解决两个很棘手的问题:

1,原机头不是标准2DIN大小,要比正常的机器窄。也就是说你新机头拿来是没办法从中控那个孔里面塞进去的。

2,原机头上面没有控制按钮,全部集成在中控面板上。原机头更换以后中控全部音频功能按键失效。

第一个问题我一上来买的时候也没有想到,机器到了才发现不行……到现在机器也没装进去。。。。在等新的面板,回头更新完了再改帖子吧。

第二个问题是这个帖子的重点,我搜索过,整个网络上也找不到对应的信息,我这个也许是处女贴吧。

原车的中控通过一根电缆与CD机头连接,完成对应的控制功能。我的思路是这样,通过一个单片机与中控沟通,然后把中控按键的动作转发给新的CD机。

先上个连接电缆的图,我原来以为这电缆上这么多的引脚,肯定是每个按钮都是单独的一个引脚。测量的半天以后发现完全不是这么回事,有一多半的线都是置空的,奇瑞简直是变态。
通过观察中控面板的电路,大伙应该能看出来,音频控制部分的电路和下面的空调系统完全是隔离的。除了是一块连体的板子以外和下面没有任何关系。这就给我们的测量带来了很大的便利。

脑子积水的奇瑞使用了一个24根引脚的插头,就算每个按键都接一根线也是绰绰有余。可是它偏不这样,他使用了两根线解决了面板上面所有的按键,使用了一根单独的线解决了CD机开关按钮,使用了两根线解决了CD机音量+-,还用了两根线(并联)解决了LED灯光供电。其他全部置空。

为了给以后也想改装的兄弟留下方便,我把测试好的线序写出来:

全部的针脚标号以电路板上的图示为准。大伙要是还不能确定就看线的颜色,我要是没记错的话,应该有红黄两根线,红色的是1脚,黄色的是24脚,或者相反。。。

1和24脚 : 地线(GND)
6和7脚:音量加减检测线
20脚:5V
22和23脚:LED电源
12脚:CD机开关按钮
11脚:左边五个键检测线(静音,音量+,音量减,其他还有啥忘了。。)
10脚:右边五个键检测线(弹出CD,其他还有上全忘了。。)

其中11和10脚的实现方法和方向盘按键相同,都是通过每个按键接不同的电阻,达到不同的分压的目的。CD机通过一个ADC接口检测电压变化,完成对某个特定按键的识别。所以只需要再把机头上的REL引脚引出来就能用同样方法获得方向盘按键信息啦。

而6和7脚的音量控制是一个联动的装置,通过不同的阻值变化你可以判断出来是左转按钮还是右转按钮。具体不详细说,能理解我上面帖子的人应该会搞的。

22和23脚是并联的,可能是因为考虑到电流比较大吧。这个就是一个单纯的电源输入,用来让按钮下面的LED亮灯用的。12V。

12脚按下的时候通过一枚电阻接地(应该是10K,具体忘了),CD检测高低电平获知状态。

好了,上面这些都搞清楚以后就能指定最终的控制方案了。

歌乐本身有所谓的OEM remote control功能,也就是有一个接口可以外接方向盘按键适配器。但是第一我不知道哪里能买到对应的适配器,第二我猜就算知道哪买也不会有对应奇瑞A3的,第三就算有对应的也一定会坑死你没商量,第四就算不吭死你,这玩意功能本身就不全。

所以我考虑过通过单片机模拟它的适配器,可惜我网络上找了一圈也没有歌乐这个适配器的协议说明,无奈只能作罢。

幸亏歌乐够意思,786USB标配一个遥控器,可以实现诸如切换音频源,音量调整,音轨调整等大部分你常用的功能。所以我就打算从这里入手了。

我的思路很简单,通过单片机接收到中控的按键信息,然后再通过单片机上集成的红外发射管模拟歌乐的遥控器键值发给主机,这样一来就完成了中控和CD的联动~真是个天才啊。。。

说干就干,首先祭出神奇一枚:arduino!开源的开发板,可以用来制作机器人或者各种你能想到的电子玩意。我最早买回
来它本来是想做个拧魔方的机器人的,可是我买完了电路板再想买乐高积木的时候没通过老婆的审批……就只能作罢了。
来看看大小:

当然最后我发现这个板子太大了,放进车里很占地方,我又买了一个叫做uno的小板,用来放在车里,这个就调试用好了~

紧张调试中:
根据测试,歌乐遥控器的RAW键值如下:

Code :NEC

SRC : 6106C03F

BND:610650AF

LEFT:61066897

RIGHT:6106E817

UP:6106D827

DOWN:6106B847

ENT:610610EF

MUTE:6106A05F

ISR:610620DF

DISP:61061CE3

SCN:61068877

RPT:610618E7

RDM:61069867

有一些按键可以直接和中控对应上,比如音量换歌啥的,有一些不能,就靠大伙自由发挥了,愿意是啥功能自己定义吧~

还有一个这个信息也贴上来吧,中控台上所有键的模拟值。由于我的单片机的ADC是10bit的,对应测试到的数值为1-1024。我的基准电压为5V,请参考我的这个标准作为你测试的基础。

按键模拟值:

PIN 10

EJECT 553-554
EQ       412-413
SCN    209-210
SRC     785-786
MENU  692-693

PIN 11

MUTE  785-786
SEEKU 691-692
SEEKD 552-553
AST      209-210
BAND  413-414
同时附上arduino mega 2560板用的的源代码

/* Chery M11 2011 MID hack with clarion 786USB

 Author : qwl
 Mail : kellen.qian@gmail.com
 QQ : 20486346
 Board : Arduino Mega 2560
 */

#include <IRremote.h>

IRsend irsend;

#define IR_SRC 0x6106C03F
#define IR_BND 0x610650AF
#define IR_LEFT 0x61066897
#define IR_RIGHT 0x6106E817
#define IR_UP 0x6106D827
#define IR_DOWN 0x6106B847
#define IR_ENT 0x610610EF
#define IR_MUTE 0x6106A05F
#define IR_ISR 0x610620DF
#define IR_DISP 0x61061CE3
#define IR_SCN 0x61068877
#define IR_RPT 0x610618E7
#define IR_RDM 0x61069867

#define MID_EJECT MidValue >= 553 && MidValue <= 554
#define MID_EQ MidValue >= 412 && MidValue <= 413
#define MID_SCN MidValue >= 209 && MidValue <= 210
#define MID_SRC MidValue >= 785 && MidValue <= 786
#define MID_MENU MidValue >= 692 && MidValue <= 693

#define MID_MUTE MidValue >= 785 && MidValue <= 786
#define MID_SEEKU MidValue >= 691 && MidValue <= 692
#define MID_SEEKD MidValue >= 552 && MidValue <= 553
#define MID_AST MidValue >= 209 && MidValue <= 210
#define MID_BAND MidValue >= 413 && MidValue <= 414

int MIDAPin = A0;    //MID Left 5 KEYs Pin
int MIDBPin = A1;    //MID Right 5 KEYs Pin
int encoder0PinA = 2; //MID Encoder A Pin
int encoder0PinB = 3;//MID Encoder B Pin
int PowerupPin = 4; // MID Power Key Pin

int MidValue;
int MidValueA;
int MidValueB;

int debug = 1;

int encoder0Pos = 0;
int encoder0PinALast = LOW;
int n = LOW;
int t;
#define SAMPLE 6

void ir_send(unsigned long key , String desc){
  if(debug){
    Serial.print("Key ");
    Serial.print(desc);
    Serial.println(" Pressed!");
  }
  irsend.sendNEC(key,32);
  delay(100);
}

void setup() {
  // declare the PINs
  pinMode(PowerupPin,INPUT);
  digitalWrite(PowerupPin, HIGH);

  digitalWrite(MIDAPin, LOW);
  digitalWrite(MIDBPin, LOW);

  pinMode (encoder0PinA,INPUT);
  pinMode (encoder0PinB,INPUT);

  Serial.begin(9600);
}

void loop() {
  /* Encoder Decode */
  n = digitalRead(encoder0PinA);
  //Serial.println(n);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == LOW) {
      encoder0Pos--;
      ir_send(IR_UP,"IR_VOLIN");
    }
    else {
      encoder0Pos++;
      ir_send(IR_DOWN,"IR_VOLDE");
    }
  }
  encoder0PinALast = n;
  /* Decode End */

  // Get 6 Times Key Value
  for (t=0;t<SAMPLE;t++){
    MidValueA += analogRead(MIDAPin);
  }

  MidValue = MidValueA / SAMPLE;

  if(debug > 1){
    //Serial.print("Left Value:");
    //Serial.println(MidValue);
  }

  if(MID_MUTE){
    ir_send(IR_MUTE,"IR_MUTE");
  }

  if(MID_SEEKU){
    ir_send(IR_LEFT,"IR_LEFT");
  }

  if(MID_SEEKD){
    ir_send(IR_RIGHT,"IR_RIGHT");
  }

  if(MID_AST){
    ir_send(IR_RIGHT,"IR_AST");
  }

  if(MID_BAND){
    ir_send(IR_BND,"IR_BND");
  }

  // Get 6 Times Key Value
  for (t=0;t<SAMPLE;t++){
    MidValueB += analogRead(MIDBPin);
  }

  MidValue = MidValueB / SAMPLE;

  if(debug > 1){
    Serial.print("Right Value:");
    Serial.println(MidValue);
  }

  if(MID_EJECT){
    ir_send(IR_RIGHT,"IR_EJECT");
  }

  if(MID_EQ){
    ir_send(IR_RIGHT,"IR_EQ");
  }

  if(MID_SCN){
    ir_send(IR_SCN,"IR_SCN");
  }

  if(MID_SRC){
    ir_send(IR_SRC,"IR_SRC");
  }

  if(MID_MENU){
    ir_send(IR_RIGHT,"IR_MENU");
  }

  if(digitalRead(PowerupPin) == LOW){
    ir_send(IR_SRC,"IR_POWERUP");
    delay(100);
  }

  MidValueA = 0;
  MidValueB = 0;
}
附上一段装车视频,清晰度一般,晚上拍的。。。

自己从第一次接触Nandflash到现在也有将近两年的时间了,从刚开始的无从下手到现在的略知一二。

回过头来看自己的学习历程,积累了很多无论你如何Google和泡坛子都学习不到的经验。现在拿出来分享给大伙,算是对集体智慧的一种回馈吧。

首先说一下Nandflash本身的一些缺陷和优势:

优势:

1,速度快。这个貌似没啥可说的,对于现在动辄上G的芯片容量,速度是必要的基础。

2,便宜。虽然赶不上硬盘,但是在嵌入式设备里面绝对是性价比杠杠的。

3,没了。除了傻快傻便宜,真没嘛优势。

劣势:

1,可靠性不高。这里所说的可靠性分为两个方面,第一是芯片出厂的时候就伴随着一定概率的缺陷。你们懂得,我说的是坏块。这个缺陷会令很多初入此行的孩纸们丢掉工作,后面会详细说。第二个是芯片使用过程中的不稳定,当然还是在说会产生坏块的问题。。。

2,不能片上运行程序。由于无法直接寻址,所以就不可能做到片上运行程序。这是很多网络上文章经常提到的一点,实际上对生活影响不大。因为现在内存便宜的很,一般的家用嵌入式设备对内存没有这么苛刻的要求。

3,芯片操作复杂。不同于norflash,nandflash没有这么容易操作。我说的操作主要是指不通过驱动程序直接靠编程完成对于flash的存取。但是我还是要说上面的话:实际上对于生活影响不大。因为目前大部分的民用嵌入式设备都有操作系统,一般为linux。linux为flash设备提供了mtd驱动层,我们对于flash的操作都被抽象成了统一的接口甚至是设备符号,无需关心底层实现。

 

下面来说说实际使用时候的一些心得体会:

 

一,什么样nandflash分区大小是合理的?

考虑到业务的需要,一般我们不会将整个芯片完全当作一个整体来使用。就如同你使用电脑的时候不会只给硬盘分一个区一样。主要是为了防止对分区进行改动或者重新烧录的时候会丢失全部的数据。

而如何分区才是合理的呢?我归纳主要有以下几点需要注意:

1,任意一个分区的大小不要超过操作系统所能操作内存的2/3。比如你有128M内存,linux可以支配其中的96M,那请不要将nandflash的分区设计为大于64M。

这样的考虑主要是因为,如果未来你需要升级这个分区,尤其是通过网络升级这个分区。如果你的升级方式不是增量式的,那么你必须有一段与flash分区大小一致内存空间用来存放镜像。这个时候假设你的flash分区为96M,那么你将不可能完成更新。因为你没有足够的内存空间。除非实时解压缩实时烧录,风险很高。

 

2,分区一定要给坏块留出足够的空间。我最早接触到nandflash的时候就犯了这个错误,uboot大小200k,我给它划分的空间为256k,结果遭遇坏块,上下移动都没有空间,非常杯具。

拿目前嵌入式系统比较常用的128M nandflash,他没产生一个坏块,空间损失128k。也就是说,如果你设计分区大小为512k,遭遇两个坏块,实际空间只有256k了。

通过大量设备生产后所统计出来的结果,一般nandflash出厂的坏块基本不会超过4块。位置不固定。所以我的推荐是,如果你的某些分区容量较小,比如设计使用容量为512k,请划分该分区为1M以避免坏块。如果分区容量较大,比如60M,请划分64M。这样比较合理。

 

3,如果被划分的分区将会使用文件系统,比如Yaffs之类,请不要划分的过小。比如你有一个分区用来存储配置文件,你认为只需要3M就可以解决问题。但实际上你会发现,这个分区只要一mount上,就已经被占用了2M,因为文件系统自己会规划出来一部分区域。你自己能操作的空间只剩下1M了,与设计目标产生巨大的冲突。请适当调整大小,并同时参考2号建议。

 

二,如何对nandflash分区进行合理的规划?

这里所说的规划指flash要如何进行逻辑上的区分,比如要分几个区等。

这个问题说起来比较复杂,因为不同的应用有不同的设计目标。有些提供可能简单的分两个区甚至不分区就能解决问题,而有些可能会很复杂。

这里我拿我所经手的项目举例子:

项目为iptv机顶盒,使用linux操作系统,使用uboot作为引导程序。

我的分区如下:

1M,uboot

1M,uboot-config

3M,kernel

32M,rootfs

16M,backup-rootfs

64M,apps

5M,config

 

uboot如果升级失败,将会带来灾难性的后果。但是有些uboot启动的参数需要被动态的更改,所以必须要把uboot存储参数的部分与uboot分离。所以uboot被分为两个区分别存储。

 

kernel无需多说,由于附带的驱动较多3M是一个合理的数值。

 

rootfs指linux所依赖的根文件系统。包括busybox以及实现最小系统所必须的文件。

 

rootfs-backup为备份根文件系统,里面存放最小系统所必须的文件以及系统恢复用的程序。

 

apps为机顶盒程序以及对应驱动的存放分区,为flash中容量最大的部分。我们将所有自己编写的程序以及驱动都放置于这个分区,未来升级和维护都会变得很容易。

 

config分区存储应用程序所依赖的一些配置文件。单独一个分区存放是为了避免用户程序分区被升级时,每一个用户自己的设置以及信息不会丢失。

 

我这里要特别说一下uboot部分。

uboot是整个nandflash的灵魂,没有他系统就无法正常启动,甚至无法正常初始化内存。当然uboot只是一种程序,他开源,易于使用,大伙都爱用它。亲,支持哦~

在我们的项目中,uboot一直没给我们找什么麻烦。但是到了工厂生产的时候,却吃了大亏。100片nandflash烧录完有超过10片无法正常启动。为什么呢?原因如下:

 

nandflash在出厂的时候一定会保证第一个块不是坏块,在我们所使用的芯片里,就是前128K绝对可以使用。但是后面的块就无法保证了。我们为uboot划分了512K空间,本来觉得就算是有坏块也会跳过去,没有所谓。

 

但是我们忽略的一个雷人的问题……那就是uboot被烧录器烧录完以后,已经因为坏块导致自己被烧录的支离破碎。当cpu将第一块数据读取到内存中并运行的时候,这部分代码不具备跳坏块读取的能力,自然而然的uboot其余的部分就无法正常读取,自然系统将会无法启动……

如何解决呢?

方法说简单也简单,说复杂也复杂。我们需要首先编译一个小于128K什么功能都不带但是完整的uboot。通过这个uboot引导起来以后再从flash里面读取另外一个完整功能的uboot到内存,然后引导这个uboot然后再引导linux。这就是传说中的二段跳。

所以在我们的系统分区中,uboot的那1M分区,实际上包含了两个uboot。

前128k一个,后面也许紧贴着也许隔若干个坏块又是一个uboot。这样就避免在工厂生产出来的uboot无法启动问题。同学们一定要切记!

 

基本上就是这些,下一次有时间会说说如何通过uboot实现升级失败后的启动分区切换,来完成灾难拯救的任务。

嫖客没了又来来了又没反反复复,我自己都疲沓了。

但是聊胜于无,是吧~