得从大约两年前说起,那会儿amobbs的马潮老师出了个题目:8*8的LED屏,中间四个常亮,最外圈亮一个转圈跑。这东西没啥难度吧?我正好有块点阵屏闲着,就焊了一块板,从开始写程序计算,39分钟解决了。
讨论过程中一师弟(好象是TwoPerson?)大概出口不慎,把这位马潮老师惹怒了——于是他又出了个题,原文如下:
"如果不服,可以再次比试。你在北大找5个学生,组成一个小组。
用m16加一片lm324,设计一个读取sd卡上wave文件,并播放的wave播放器,看谁做的好。
给你们一个月的时间,下个学期开学我到北大找你,比试实物效果。 "
首先声明一下,本人纯粹是外行,本科化学,研究生改物理了,以上纯属业余爱好。马老师这题目在我这个外行看来,也就是一个人两三天到一周左右的工作量,至于五个人搞一个月么?不过后来事情一多也就把这回事忘了。
前段时间突然记起来了,于是动手开干。电路图太简单就不画了。觉得没必要上m16,于是用了一片m8,硬件SPI接SD卡,8位PWM输出,用一片TL061搭成低通滤波,后面一只8050缓冲接喇叭。从学习SD协议、实现FAT开始,焊接、写程序到调试全算上,用了两个晚上、周六全天加周日半天,基本完事。当然8位PWM的音质有点惨不忍睹……
程序如下,太乱了,懒得整理了……
----------------------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------------------------
//所有uart、spi的底层操作放这里,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]; //还好m8有1k 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方式的话就不存在这个问题,直接上24M或27M晶振然后把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位输出。马老师的意见是用两个8位pwm按1:255分压来组成16位,不知道哪个方案更蛋疼一些?r-2r就是占用io多了点,m8一共23个io,SD卡用4个,reset一个,txd一个,凑和够了。哪天想起来了再按这个方案试试~~
没有评论:
发表评论