2013年9月10日

最近的三个制作 - 棉花糖机, ad9850实验, 迷你电源

1. 棉花糖机, 完全是参考网上那篇硬盘改棉花糖机的教程. 不过手里这块硬盘似乎坏得有点严重, 通电也转不起来了... 查了一下, 原来硬盘电机就是一个三相无刷电机, 于是用AVR给它搭了个驱动电路如图.

其中三只NPN管用常用的C945, C1815之类都完全可以; 三对MOS管可以用分立的, 也可以用现成的互补对管, AOP605或AOP607之类, 在DIP8封装里集成了NMOS和PMOS各一只, 用起来方便一些.

程序很简单, 按照U+V-, U+W-, V+W-, V+U-, W+U-, W+V- 这个顺序依次驱动6只MOS管, 判断剩余一相的过零时刻来换相, 就能转得很好. 开环时如果把盘片全拆下来, 只剩光轴, 转速能达到4万转, 装上盘片就只有几千转了. 想让它转得稳定一些的话, 控制逻辑还得再复杂一些才行.

后面的大同小异了, 不过效果不太理想, 固体酒精的火力不太好掌握, 费了很大劲只做出一点点. 以后有机会把它改成感应式加热的, 也许效果会好些?

2. AD9850实验 其实不能算一个制作了, 只是简单试了一下AD9850的应用. 这东西的功耗实在大了点, 5V供电时居然要160mA的电流.

3. 迷你电源

用一节锂电boost升压到5V, 后面接LDO, 用MCU的PWM输出一个电压信号给LDO的ADJ脚, 实现了可调输出. 同时可以用LCD显示输出电压电流和电池电压. 不过效率似乎低了点, 一节锂电用不了多久. 下次试试把LDO换成buck降压芯片.

2013年7月14日

最近的三个制作

就不一一仔细介绍了,大概说说:

1. 滑动触摸实验

见ST的两个应用笔记,AN2927和AN2896。基本原理就是把触摸区域当作一个电容,用一个IO口通过大电阻对触摸区域充放电,另一个IO口来读取状态,记录充放电时间。当手指摸上去时,这个电容变大了,因此充放电时间会变长。http://v.youku.com/v_show/id_XNTMzNTkxMDQ0.html

录了个视频:

2. 50M FM发射实验

电路如图,74HC04的一个非门接成10.245M晶体振荡器,晶体一端并联变容二极管。用剩下的五个非门来推动2N7002,2N7002的D极谐振在50M频率上。

用VX-7R接收,不管走到屋里哪个角落,信号都挺强的。可惜北京不允许使用50M频率,只能在屋里大概试试了。估计到开阔地能发射相当远的吧。

3. r-2r sd卡wave播放器

用mega8,16.9344M, 14位r-2r实现了流畅播放16位 44.1kbps单声道wav。 双声道没试,估计超点频应该差不多了。

这东西有点说来话长,最早是amobbs的马潮老师出了个题目,8*8的LED屏,中间四个常亮,最外圈亮一个转圈跑。那会儿我正好有块点阵屏闲着,就焊了一块板,从开始写程序计算,39分钟解决了。 

 讨论过程中师弟TwoPerson把这位马潮老师惹怒了——于是他放出狂言,原文如下: 
 
 “如果不服,可以再次比试。你在北大找5个学生,组成一个小组。用m16加一片lm324,设计一个读取sd卡上wave文件,并播放的wave播放器,看谁做的好。 给你们一个月的时间,下个学期开学我到北大找你,比试实物效果。 “
 
在我这个外行看来,这个题目也就是一个人两三天到一周左右的工作量,至于五个人搞一个月么?不过后来事情一多也就把这回事忘了。 
今年1月份想起来了,于是动手开干。用8位PWM先试了一次,从学习SD协议、实现FAT开始,焊接、写程序到调试全算上,用了两个晚上、周六全天加周日半天,基本完事。当然8位PWM的音质有点惨不忍睹……
 
这次改用r-2r ladder来实现16位输出,三个晚上加两个白天,完事了。算上之前的两晚上和一个半白天,还算符合我之前“一个人一周工作量”的估计吧。m8的io稍微有点不够用了,凑了14位出来,倒是也差不多了。音质么我觉得还不错。
 
马老师那边啥反应?猜也猜得出,他在精神上永远都能胜利,我实在不想引用那个“永远不要试图去战胜一个纯**,他会把你的智商拖到跟他一样的水平,然后用他丰富的经验打败你。”的段子,不过还是把链接贴过来好了。
马老师喷了几大篇以后不知出于一种什么心理,封贴了,对此咱就不作啥评论了,呵呵,呵呵。

2013年6月25日

火电和核电,哪个造成的放射性污染更严重?

常识告诉你……别急,不要太相信常识,一切用数据说话。
100万千瓦的核电站:每年耗铀约30吨。燃料、废料的运输和储存都有极严格的程序。
100万千瓦的火电站:每年耗煤300万吨。注意这里有个问题——煤里也含有微量的铀,在中国,平均含量大约3个ppm。300万吨乘以 3ppm,等于 9吨。这些铀一部分随着烟尘排放到大气中,一部分成了粉煤灰,然后被加工成水泥、空心砖之类,做了建筑材料。
完了没有?没完,煤里还有另外一种放射性元素钍,含量还要高一些,在中国平均为6~10ppm。也就是说这个100万千瓦的火电站每年消耗的煤里含有18~30吨的钍。这些钍也一样被排放到大气中,或者做了建筑材料。
总之一句话,即使发生核事故,也只是把少得多的放射性物质在短时间、小范围内释放出来而已。而火电所做的是把多得多的放射性物质在时间、空间上平摊到我们每个人头上。

2013年6月11日

发V机

功能很简单,就是发出字母V的莫尔斯电码: ...- ,用来调试机器,很好用。

由CD4060组成约100kHz的RC振荡器,6次二分频后得到大约600Hz的音频信号;继续由74HC393分频得到A、B、C、D四位控制信号,由两只CD4011组成的逻辑电路变换成字母V的开关信号,最后由一只NPN管实现600Hz音频调制。

用一只8脚单片机的话硬件上可以更简单。用逻辑电路的好处是不用调试,焊好通电就能工作了。

开始由一只10440锂电池直接供电,结果发现电池电压变化会严重影响音调,于是增加了HT7750升压电路,让它在5V工作,效果好多了。

2013年5月23日

继续试验外差机

分别用LA1600和分立器件实现了两个中波外差机。
效果差不多,在阳台上能收到中国之声一个台,别的台都收不到了——总之和之前做的直放机没啥区别吧。
大概是输入回路的Q值太低,弱信号全被中国之声压住了。有空了再做FM机试试吧。

USB电子管声卡

     这次是PCM2702+NE5534+2P2的组合——以USB的供电能力最多只能带动2P2了。
     相当小巧,PCB面积比公交卡还小,不过输出变压器没包括在内。
     电路图如下,PCM2702输出的音频信号由NE5534反相放大后推动2P2。主要的麻烦是供电,一只2P2需要60V屏压、1.2或2.4V灯丝、-3.5V栅偏压。60Vy高压用LM2577升压得到;这里用灯丝串联电阻R14上的压降来代替栅偏压,于是可以省下一组供电。此外NE5534的最高供电电压是±22V,这里给它提供了一路30V电压。这块小板子上供电占了大概1/3的面积。
     效果么。。。只能说还凑合吧,主要是音量太小了,2P2的输出功率只有100mW。下次试试两只2P2推挽的效果吧。

2013年4月22日

636收音机

说来惭愧,玩了这么多年无线电,以前从来没装响过一台收音机……后来发现,不是收音机不行,而是那会儿宿舍的电磁环境太差了,用成品Philips D1875也照样一个AM台也收不到。
636估计是除了矿石机以外,最简单的能收到台的收音机了,电路图如下:
实际上已经是加强版了,原版的636是没有倍压检波的。我这次的实验用了长磁棒和空气双联,跟当年的成品636相比,可以算是豪华版了。在阳台上试了一下,中波低端可以收到中国之声,声音挺清楚;高端就收不到什么信号了。
636后来的版本又加上了再生,应该能加强一些灵敏度和选择性;但是现在的电磁环境跟60年代不能相比了,已经不适合装直放机了。以后还是玩外差机吧。

NE555升压实验-续

之前试验各种升压电路的结论是,要想实现从单节锂电升压到5V或更高、大电流输出,PWM ic必须具有以下几个性能:
     1. 工作电压足够低,一般锂电终止放电电压定在3.0V上下,因此PWM ic的最低工作电压最好是2.7V或者更低;
     2. 必须是推挽输出,否则输出电流不够,MOS开关管要么不能完全导通,要么不能完全关断,两种情况都严重影响效率;
之前的实验里,NE555在5V升6.3V时表现良好,但它的最低工作电压只能到4.5V,不符合上述条件1。于是想到能否用自举电路或者用7660倍压给NE555供电呢?搭电路实验,结果如下:
     使用自举电路时,正常启动后,输入电压可以一直低到1.8V都能正常升压工作;但输入电压低于4.0V时不能启动。可见此路不通。
     使用7660倍压时可以在2.8V正常启动,算是勉强达到要求,可靠性还是不足。
     可以再试试用BL8530、HT7750之类小功率升压ic为NE555辅助供电,如果还是不行,那就得放弃用NE555实现单节锂电升5V的思路了。

2013年3月23日

用LM2596搭建数控稳压电源

常用的LM2596芯片可以方便地搭成各种降压式开关稳压电源,但要用单片机来控制它的输出电压就稍微有点麻烦了——主要的思路有两种,一种是使用传统的电位器控制方式,用单片机控制数字电位器来代替模拟电位器;另一种则是利用单片机的PWM或DAC输出一个控制电压,引入到LM2596的反馈环路中。这次试验的是后一种思路。

如图,设输出电压为Vout,控制电压为Vctrl,LM2596的反馈端电压为Vfb,根据运放的基本性质可得:

Vctrl * R3/(R3+R8) + Vfb * R8/(R3+R8) = Vout * R9 / (R2+R9)

按上图的参数,可以写为Vctrl * 10/11 + Vfb * 1/11 = Vout * 1/4

对于LM2596-ADJ,其Vfb = 1.23V,于是有Vout = 4/11 * (10 * Vctrl + 1.23 )

于是,当Vctrl = 0V时,输出电压为1.23V;当Vctrl = 3V时,输出电压为11.3V左右。实际上由于LM358运放输入共模电压的限制,当供电电压为12V时,本电路最高输出电压为10V左右。在R2和R9的分压端引出一路信号到单片机的ADC来测量输出电压。

怎么测量输出电流呢?在输出地端串联小电阻检测电流虽然较简单,但是造成输入、输出不共地,许多情况下反倒更麻烦,因此这里选择了由R4、R1、R6、U3和Q1等元件构成的高端电流检测电路。如图,R4上的压降被U3放大15倍后,得到一个对地的电压,单片机测出此电压,除以15,再除以R4的阻值50毫欧即可得到输出电流。用12V 15W灯泡作为负载,实测工作状况如下:

PWM dutyVctrl/VVout/VIout/A
14.6%0.743.10.45
24.4%1.224.90.56
34.2%1.706.60.65
43.9%2.178.40.74
53.7%2.6110.20.82

后记:用同样原理实现了APW7120+2SK3919*2的可调同步整流降压电路,实测最大稳定输出电流达到11A。

2013年3月19日

萤火虫瓶子 & LCD串行驱动接口

本来是发在新浪blog的,结果莫名其妙被关了,只好重写一遍……

前者是用AVR的io口模拟PWM,驱动8个LED闪亮。视频:http://v.youku.com/v_show/id_XNTI4Nzc4MDYw.html

分别试验了用PCF8574、74HC164和用一片ATmega48实现用串口驱动1602液晶屏。

PCF8574的方案很简单,4个IO按4线法连接LCD高4位,2个IO连接EN端和RS端。LCD RW直接接地,用一只7660产生负压。需要占用i2c口,显示速度慢、性价比也低,总之不推荐。

用ATmega48扩展的话,可以8线连接,接口用i2c、spi、uart都可以,用一个PWM口产生负压可以节省一只7660,是最灵活的方式,不过用uart时需要双方都有晶振。

用74HC164要多占用两个IO,不过用一些小技巧可以实现只占用两个GPIO:如图,用电阻和二极管形成一个与门,在串行写入数据时将最高位置1,写完8bit后再拉高DATA端,此时与门输出高电平,EN动作。之后要拉低DATA,连续八个CLK以清空164输出端,避免影响下一轮。实测写LCD快速、稳定,成本也是三种方式里最低的。

2013年1月17日

DiyNote: AVR SD播放器

得从大约两年前说起,那会儿amobbs的马潮老师出了个题目:8*8LED屏,中间四个常亮,最外圈亮一个转圈跑。这东西没啥难度吧?我正好有块点阵屏闲着,就焊了一块板,从开始写程序计算,39分钟解决了。 

讨论过程中一师弟(好象是TwoPerson?)大概出口不慎,把这位马潮老师惹怒了——于是他又出了个题,原文如下:

"如果不服,可以再次比试。你在北大找5个学生,组成一个小组。

m16加一片lm324,设计一个读取sd卡上wave文件,并播放的wave播放器,看谁做的好。

给你们一个月的时间,下个学期开学我到北大找你,比试实物效果。 "

首先声明一下,本人纯粹是外行,本科化学,研究生改物理了,以上纯属业余爱好。马老师这题目在我这个外行看来,也就是一个人两三天到一周左右的工作量,至于五个人搞一个月么?不过后来事情一多也就把这回事忘了。

前段时间突然记起来了,于是动手开干。电路图太简单就不画了。觉得没必要上m16,于是用了一片m8,硬件SPISD卡,8PWM输出,用一片TL061搭成低通滤波,后面一只8050缓冲接喇叭。从学习SD协议、实现FAT开始,焊接、写程序到调试全算上,用了两个晚上、周六全天加周日半天,基本完事。当然8PWM的音质有点惨不忍睹……

程序如下,太乱了,懒得整理了……

----------------------init.c-------------------------

//初始化部分,是用zx提供的appbuilder生成的,好象是从iar里扒出来的?

//基本没啥有用的,直接跳过好了

#include <avr/io.h>

#include <avr/interrupt.h>

void port_init(void) {

    PORTB = 0x0;

    DDRB  = 0xff;

    PORTC = 0xff; //m103 output only

    DDRC  = 0xff;

    PORTD = 0xff;

    DDRD  = 0xff;

}

void timer1_init(void) {

 

    TCCR1B = 0x00; //stop

    TCNT1H = 0xFF; //setup

    TCNT1L = 0x00;

    OCR1AH = 0x00;

    OCR1AL = 0xFF;

    OCR1BH = 0x00;

    OCR1BL = 0xFF;

    ICR1H  = 0x00;

    ICR1L  = 0xFF;

    TCCR1A = 0xC1;

    TCCR1B = 0x09; //start Timer

}

 

//SPI initialize

// clock rate: 132300hz

void spi_init(void) {

    DDRB &= ~(1<<4); // MISO as input

    SPCR = 0x53; //setup SPI

    SPSR = 0x00; //setup SPI

}

 

//UART0 initialize

// desired baud rate: 2400

// actual: baud rate:2400 (0.0%)

void uart0_init(void) {

    UCSRB = 0x00; //disable while setting baud rate

    UCSRA = 0x00;

    UCSRC = (1<<URSEL) | 0x06;

/*    UBRRL = 54; //set baud rate lo 19200

    UBRRH = 0x0; //set baud rate hi */

    UBRRL = 207;

    UBRRH = 0;

    UCSRB = 0x08;

}

 

//call this routine to initialize all peripherals

void init_devices(void) {

    //stop errant interrupts until set up

    cli(); //disable all interrupts

    port_init();

    timer1_init();

    spi_init();

    uart0_init();

 

    MCUCR = 0x00;

    GICR  = 0x00;

    TIMSK = 1<<TOIE1; //timer interrupt sources

    sei(); //re-enable interrupts

    //all peripherals are now initialized

}

 

-----------------------uart_spi_sd.c------------------------

//所有uartspi的底层操作放这里,sd的一些基本操作也一起

#include <avr/io.h>

#include <util/delay.h>

 

#define SET_SPI_CS do { PORTB |= _BV(2); } while(0)

#define CLR_SPI_CS do { PORTB &= ~_BV(2); } while(0)

 

 

uint8_t sd_command( uint8_t cmd, uint32_t arg, uint8_t crc );

 

void  uart_tx( uint8_t    data ) {

 

    while (!(UCSRA & _BV(UDRE) ));

    UDR = data;

}

 

void    uart_puts( char *str ) {

  

   while(*str) {

       uart_tx(*str);

       str++;

   }

}

 

void    uart_write( char *buf, uint16_t n ) {

 

    while( n ) {

        uart_tx( *buf );

        buf++;

        n--;

    }

}

 

uint8_t    spi_writebyte( uint8_t  data ) {

 

    SPDR = data;

    while( !(SPSR & (1<<SPIF) ) );

 

    return SPDR;

 

}

#define spi_readbyte() spi_writebyte(0xff)

 

//找了个现成的抄来了,有点傻,不过还算好用

uint8_t sd_idle_state(void) {

 

    uint8_t i, retry, r1;

 

    CLR_SPI_CS;

    _delay_ms(1);

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

        spi_writebyte(0xff);

 

 

    retry = 0;

    do {

        r1 = sd_command( 0, 0, 0x95 );

        retry++;

    } while( r1 != 1 && retry < 100);

    if( retry >= 100 )

        return 1;

    retry = 0;

    do {

        r1 = sd_command( 1, 0, 0x95 );

    } while( r1 && retry < 100 );

    if( retry >= 100 )

        return 2;

 

    r1 = sd_command( 59, 0, 0x95 );

    r1 = sd_command( 16, 512, 0x95 );

 

    _delay_ms(1);

    SPCR &= ~(0x3);

    SET_SPI_CS;

 

    return 0;

}

 

uint8_t sd_command( uint8_t cmd, uint32_t arg, uint8_t crc ) {

 

    uint8_t r1, retry = 0;

    SET_SPI_CS;

    spi_writebyte( 0xff );

    spi_writebyte( 0xff );

    spi_writebyte( 0xff );

    CLR_SPI_CS;

    spi_writebyte( cmd | 0x40 );

    spi_writebyte( 0xff & (arg >> 24) );

    spi_writebyte( 0xff & (arg >> 16) );

    spi_writebyte( 0xff & (arg >> 8) );

    spi_writebyte( 0xff & arg );

    spi_writebyte( crc );

 

    do {

        r1 = spi_writebyte(0xff);

        retry++;

    } while( r1==0xff && retry < 100 );

 

    return r1;

}

 

uint8_t sd_read_sector( uint32_t addr, uint8_t *p ) {

 

    uint8_t r1=0;

    uint16_t i;

    addr <<= 9;

    r1 = sd_command( 17, addr, 0x95 );     //读出RESPONSE    

//    { char buf[32]; sprintf( buf, "r1=%d", r1 ); uart_puts(buf); }

    while ( spi_writebyte(0xff)!= 0xfe); //直到读取到了数据的开始头0XFE,才继续

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

    {

        *p = spi_readbyte();

        p++;

    }

    spi_readbyte();

    spi_readbyte();

    SET_SPI_CS;

    return(r1);

}

 

-------------------------main.c------------------

#include <avr/io.h>

#include <avr/wdt.h>

#include <avr/interrupt.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

//#include <avr/pgmspace.h>

#include <util/delay.h>

#include "init.h"

#include "uart_spi_sd.h"

static struct {

    uint16_t    addr_boot;

    uint16_t    addr_fat;

    uint16_t    addr_rootdir;

    uint16_t    addr_cluster;

    uint8_t     sectors_per_cluster;

    uint16_t    num_reserved_sectors;

    uint16_t    sectors_per_fat;

} g_sd; //自己凑和实现了个fat,下次不这样了,还是找现成的petit之类东西吧

 

volatile static uint8_t g_sector[512]; //还好m81k sram,可以挥霍,m48就没法这么用了

volatile static uint8_t g_buf1[32], g_buf2[32]; //双缓冲,不太好用,下次还是老实写个fifo

volatile static uint8_t g_currbuf = 1, g_updatebuf = 0;

 

struct  fileinfo {

    uint16_t    first_cluster;

    uint32_t    filesize;

};

 

void    abortmsg( char *msg, uint8_t data );

 

void    get_sd_info(void ) {

 

    uint8_t   r1;

 

    r1 = sd_idle_state();

    if ( r1 != 0 )

        abortmsg( "idle failed", r1 );

   

    memset( (void *)g_sector, '\0', 512 );

    r1 = sd_read_sector( 0, (void *)g_sector);    //MBR

    if( g_sector[510] != 0x55 || g_sector[511] != 0xaa )

        abortmsg( "read mbr failed", 0 );

 

    memcpy( &g_sd.addr_boot, (void *)&g_sector[454], 2 ); //取引导区

 

    r1 = sd_read_sector( g_sd.addr_boot, (void *)g_sector );

    if( g_sector[510] != 0x55 || g_sector[511] != 0xaa )

        abortmsg( "read boot g_sector failed", 0 );

 

    memcpy( &g_sd.sectors_per_cluster, (void *)&g_sector[0xd], 1 ); //SD卡的基本数据

    memcpy( &g_sd.num_reserved_sectors, (void *)&g_sector[0xe], 2 );

    memcpy( &g_sd.sectors_per_fat, (void *)&g_sector[0x16], 2 );

    g_sd.addr_fat = g_sd.addr_boot + g_sd.num_reserved_sectors; //文件分配表

    g_sd.addr_rootdir = g_sd.addr_boot + g_sd.num_reserved_sectors + 2*g_sd.sectors_per_fat; //根目录

    g_sd.addr_cluster = g_sd.addr_rootdir + 32 - 2 * g_sd.sectors_per_cluster;

 

}

 

int8_t    get_file_info( char *fname, struct fileinfo *p ) {

 

    int16_t i, j;

    uint8_t r1, type, status, *tt;

    char    *t, filename[12], *extname;

 

    //笨办法拆解文件名

    strcpy( filename, fname );

    t = filename + strlen(filename) - 1;

    while(*t != '.' && t>filename )

        t--;

    if ( t == filename )

        extname = NULL;

    else {

        *t = '\0';

        extname = t+1;

    }

 

    status = 0;

    for ( j = 0; j < 32; j++ ) {

        r1 = sd_read_sector( g_sd.addr_rootdir + j, (void *)g_sector);

 

        for( i = 0; i < 16; i++ ) {

            type = g_sector[i*32];

            if( type == 0 || type == 0xe5 || type == '.' ) // empty, deleted, currdir, parentdir

                continue;

            if( g_sector[i*32+0xb] & 0x10 ) // is subdir

                continue;

 

            //不是上面几种,说明是正常文件

            tt = &g_sector[i*32+8-1];

            while( *tt == ' ' ) {

                *tt = '\0';

                tt--;

            }

            tt = &g_sector[i*32+11-1];

            while( *tt == ' ' ) {

                *tt = '\0';

                tt--;

            }

 

            if( strncasecmp( filename, (char *)(&g_sector[i*32]), 8 ) == 0

                    && strncasecmp( extname, (char *)(&g_sector[i*32+8]), 3 ) == 0 ) {

 

                //目标文件的首簇号和字节数

                p->first_cluster = *(uint16_t *)(&g_sector[i*32+0x1a] );

                p->filesize = *(uint32_t *)(&g_sector[i*32+0x1c] );

 

                status = 1;

                break;

            }

            if( status == 1 )

                break;

        }

    }

    return status;

}

 

//timer1中断里把缓冲区数据送PWM输出

ISR( TIMER1_OVF_vect ) {

 

    static  uint8_t t = 0, n = 0;

 

    TCNT1H = 0xff;

    TCNT1L = 0x1;

 

    t++;

    if(t%2)

        return;

 

    if( g_currbuf == 1 )

        OCR1A = g_buf1[n%32];// >> 8) & 0xff) | ( (g_buf1[n&0xf] << 8) & 0xff00);

    else

        OCR1A = g_buf2[n%32];// >> 8) & 0xff) | ( (g_buf2[n&0xf] << 8) & 0xff00);

       

    n++;

    if( n%32 == 31 ) {

        g_currbuf = 3 - g_currbuf;

        g_updatebuf = 1;

    }

}

 

//喂缓冲区,双缓冲不好玩,下次还是改用fifo实现

void    feed_data( uint8_t  *pdata, uint16_t size ) {

 

    uint8_t n;

    do {

        n = 32;

        if ( size < n )

            n = size;

           

        while( g_updatebuf == 0 );

        if( g_currbuf == 2 ) {

            memcpy( (void *)g_buf1, pdata, n );

        }

        else {

            memcpy( (void *)g_buf2, pdata, n );

        }

        g_updatebuf = 0;

        size -= n;

        pdata += n;

    } while( size > 0 );

 

}

 

//先实现了个显示文本文件内容的函数,然后把输出文本的部分改成喂缓冲区了

void    view_file( char *filename ) {

 

    struct  fileinfo finfo;

    uint16_t    n_clusters, next_cluster;

    uint8_t i, ret;

 

    ret = get_file_info( filename, &finfo );

    if( ret == 0 )

        abortmsg( "Open file error!", 0 );

 

    n_clusters = finfo.filesize / ( g_sd.sectors_per_cluster * 512 ) + 1 ;

 

    char buf[32];

    //sprintf( buf, "n_clusters:%d\r\nfirstcluster:%d\r\n", n_clusters, finfo.first_cluster );

    uart_puts(buf);

    next_cluster = finfo.first_cluster;

 

    do {

 

        // read a cluster

        for( i = 0; i < g_sd.sectors_per_cluster; i++ ) {

            sd_read_sector( g_sd.addr_cluster + ((uint32_t)next_cluster) * g_sd.sectors_per_cluster + i, (void *)g_sector );

            if( finfo.filesize > 512 ) {

//                uart_write( (char *)g_sector, 512 );

                feed_data( (void *)g_sector, 512 );

                finfo.filesize -= 512;

            }

            else {

//                uart_write( (char *)g_sector, finfo.filesize );

                feed_data( (void *)g_sector, finfo.filesize );

                break;

            }

        }

 

        // get addr of next cluster

        sd_read_sector( g_sd.addr_fat + next_cluster / (512/2), (void *)g_sector );

        next_cluster = *( (uint16_t *)(&g_sector[ ( next_cluster % (512/2) ) * 2 ]) );

        n_clusters --;

 

    } while( n_clusters > 0 );

 

}

 

void    abortmsg( char *msg, uint8_t data ) {

 

    char buf[32];

    //sprintf( buf, "%s\t%d\n", msg, data );

    uart_puts( buf );

    while(1);

}

 

//取剩余可用内存块,傻了点,不过能用

uint16_t    get_avail_mem(void) {

    uint8_t *p;

    uint16_t    i;

    for( i = 1; i < 65534; i++ ) {

        p = malloc(i);

        if( !p ) //分配失败,说明可用内存就剩这么多了……

            break;

        free(p);

    }

    return i-1;

}

 

int main(void) {

 

    static  char buf[32];

    struct  fileinfo finfo;

 

    init_devices();

    _delay_ms(50);

    PORTB &= _BV(2);

    _delay_ms(50);

 

    get_sd_info();

{

    //一开始用16.9344M的晶振,发现不好用,16.9344M/44.1k=384,正好一个半PWM周期……手里没有11.2896M的晶振,只好用8M内部RC慢慢调整到接近11.2896M。用r-2r方式的话就不存在这个问题,直接上24M27M晶振然后把PWM频率调整到接近44.1kHz就行。

        uint8_t old_osccal, i;

        old_osccal = OSCCAL;

 

        // 242是用示波器试出来的,得慢慢调快,以免抽筋

        for( i = old_osccal; i <= 242; i++ ) {

            OSCCAL = i;

            _delay_ms(5);

        }

        // 得重新设定uart分频比例,不然串口输出全变乱码了

        UBRRL = 37;

        UBRRH = 1;

    }

/*

    { abortmsg( "OSCCAL:", OSCCAL ); }

    while(1);

  */ 

 

    //先取文件首簇号和文件大小看看

    memset( &finfo, 0, sizeof(finfo) );

    get_file_info( "se_m_26.wav", &finfo );

    //sprintf( buf, "%u\t%lu\r\n", finfo.first_cluster, finfo.filesize );

    uart_puts(buf );

 

    //播放

    uart_puts("-------------start-------------");

    sei();

    view_file( "face1.wav" );

//    cli();

    uart_puts("--------------end--------------");

 

    //sprintf( buf, "available mem:%d\r\n\r\n", get_avail_mem() );

    uart_puts( buf );

 

    while(1);

 

    return 0;

}

 

下一步的目标是改用r-2r ladder来实现16位输出。马老师的意见是用两个8pwm1:255分压来组成16位,不知道哪个方案更蛋疼一些?r-2r就是占用io多了点,m8一共23ioSD卡用4个,reset一个,txd一个,凑和够了。哪天想起来了再按这个方案试试~~