PWM music

edmunds

Senior Member
Dear all,

Has anyone tried "a single chip MP3 player" like this out of PICAXE?

I understand the complexity of reading the actual file (looked at code by @hippy for 28X2) and do not aim to tackle it at this point. This could be overcome by some FAT32 reading solution-on-chip, but for testing, a simple .wav file could be stored on eeprom (I guess).

I'm interested in the underlaying principle. PWM I now understand (to some extent :)). I also understand ADC. DAC I have had very little business with and as I understand from what I can find on the subject on the net, the idea is to alter pwm level with DAC level, decoded from the data in wav file.

Anyone dares to try to put it in simple terms? And point me to some further relevant reading? For the weekend, so to say ...


Thank you for your input,

Edmunds
 

techElder

Well-known member
Edmund, what are you trying to do with the music?

If you are just trying to select a sound file to play upon the execution of a certain event, then there are several "players" that are somewhat common.

The audio is recorded on a SD card (or similar) in a know fashion. The "player" is controlled by the PICAXE with serial commands.
 

edmunds

Senior Member
Edmund, what are you trying to do with the music?

If you are just trying to select a sound file to play upon the execution of a certain event, then there are several "players" that are somewhat common.

The audio is recorded on a SD card (or similar) in a know fashion. The "player" is controlled by the PICAXE with serial commands.

I know the common ones. Or many of them. Have some on order for testing. However, I'm interested, if I cannot achieve the same inside a picaxe chip, since all of the chips available seem loaded with features I do not need and thus take loads of space, I do need (for other things :)).

I need a player that can play some format of short sound files on a single miniature speaker (8 or 16 Ohm, 15mm diameter or cube) with "phone quality". The interface can be I2C or one- to three-wire serial. It needs to start, stop and select a file. That's it. No external crystals and as little external components as possible. If this can be squeezed into 20x2 or better 40x2 without external resonator - this would be much better than anything on the market.


Thank you for your interest,

Edmunds

EDIT: Actually, 40x2 running @64MHz with external resonator would be ok.
 
Last edited:

srnet

Senior Member
I need a player that can play some format of short sound files on a single miniature speaker (8 or 16 Ohm, 15mm diameter or cube) with "phone quality". The interface can be I2C or one- to three-wire serial. It needs to start, stop and select a file. That's it. No external crystals and as little external components as possible
I believe there are external devices that you can use to do this.

Chance of doiong it with a PICAXE alone, quite a lot less than zero I would have thought.
 

hippy

Technical Support
Staff member
It could in theory be done but I have no idea of how well.

The principle is simple; read a byte, set a DAC output ( or use PWM plus RC filter ) and repeat until end of audio. One should be able to read a byte and update PWM once every millisecond and that gives 1kHz sampling which might just be passable.

If you can get a one second 8-bit sample at 1kHz sample rate and store that in scratchpad you could play it back using something like -

ptr = 0
Do
PwmOut PWM_PIN, 63, @ptrInc
Pause 1
Loop Until ptr = 0

You would probably want to poke SFR's to set the duty directly rather than using the PWMOUT command.

I am not convinced you are going to get anything which is really satisfactory from that but it's a good starting point. You can store a reasonably meaningful word in a second's worth of sample; "Hello" and similar.
 

techElder

Well-known member
Based on Edmunds previous work, he's trying to do something "miniature" as opposed to "practical."
 

edmunds

Senior Member
Thank you for all the contributions. Just an update of what further reading on the topic has revealed so far.

I found a site that has an extremely detailed explanation of how such solution would work. It is a lengthy read and a bit of digging to do to gather it all in one piece as it consists of many separate blog posts with posts on other topics in between, but it is an incredible resource. I have more reading of piles of PDFs in my Downloads folder to do, but this blog helped me a great deal.

The most interesting part so far was the SPI reading of an SD card. I found previous work by @hippy no less than "rocket science" :), but if I understand correctly, that was the proper way to do it while SPI is a fall-back method. If I'm only looking at reading, I'm interested to try and get it working.

The part that was great learning, but not so good news, was the part where I understood how sound files are "organised, read and played back". This lead to understanding for 8kHz sample rate I need to be able to read, process (whatever that entails) and play back 8.000 bytes (if 8-bit sound) per second. In other words, a playback loop alone would have to be executed 8000 times per second. With nothing physical tested at this point, this sounds like a lot for a picaxe chip, even at 64MHz.

Digging more.

Have a good remainder of the weekend,

Edmunds
 

eggdweather

Senior Member
I'm thinking as an example that telephone systems have a typical bandwidth of 4KHz and the general rule of thumb is the sampling speed has to be twice the maximum frequency to make speech intelligible (8KHz) and Hippy says the maximum viable sampling rate is 1Khz, then this is not going to be a viable outcome. But it would make a great experiment and proof of principle.
 

hippy

Technical Support
Staff member
Hippy says the maximum viable sampling rate is 1Khz, then this is not going to be a viable outcome.
That was just a figure showing it should be possible with an update every 1ms which seems achievable. It may be possible to achieve better, though as sample rate goes up so too does storage required for audio of the same length and that memory can take longer to access.

I recall consecutive "READADC PIN,@ptrInc" commands can achieve a sampling rate of near 10kHz at 64MHz so it may be possible to get better than 1kHz. Or perhaps it's not even that. I honestly don't know. The best way would be to try it.
 

westaust55

Moderator
Based upon this thread: http://www.picaxeforum.co.uk/showthread.php?20242-Calculating-Processor-Overhead-20x2-64mhz
At 64 MHz, a single READADC or READADC10 command will take approx. 71.4 usec to perform.
Thus around 14,000 consecutive READADC commands could (?) be undertaken in 1 second.

Putting a single READADC command into even a small/tight loop will likely halve that data capture rate.

Increasing the speed of a 28X2 to say 80 MHz with an external responator (I have a 28X2 operating reliable for several 24 hr consecutive periods without crashes, resetting or other problems) will provide a 25% speed improvement. HI2cSETUP and HSERSETUP parameters can be (have been) calculated for operating at these faster speeds.

However no Rev Ed warranty applies if one does operate out of spec in this way.
 

techElder

Well-known member
Warranty? There's a warranty? Geez, I thought a return had to still have the magic smoke inside of the PICAXE!
 

edmunds

Senior Member
Thank you all for your input!

I have now figured out the eeprom, converted a file into what I think is raw data, got it loaded and I believe I'm updating pwmout duty with the correct value from the file or rather eeprom. I have a 362us second long Hello, converted to 3000Hz and 8 bits, occupying 1087 bytes (the numbers actually add up :)).

I'm running at 64MHz and the time the loop takes is approximately the same as the playback on the computer. But I don't really get a Hello, but some varied humming. I have tried various speakers and the sound changes, but not so much.

No energy to continue on this right now, will get back to it some time later. It is dawn here already after all :).

Code:
;#no_data
;#no_table

#picaxe40x2

setfreq em64
;settimer t1s_16

Symbol I2CSpeed = i2cfast_64

Symbol Counter = b0
Symbol SampleByte = b1


pause 1000

#rem
hi2csetup i2cmaster, %10100000, I2CSpeed, i2cbyte      'Announce there will be I2C
EEPROM ($00, $00, $FF, $00, $00, $FF, $00, $00)
EEPROM ($FF, $00, $00, $FF, $FF, $FF, $FF, $00)
EEPROM ($00, $FF, $01, $00, $FE, $FF, $01, $00)
EEPROM ($FF, $FF, $FF, $01, $01, $02, $00, $00)
EEPROM ($FF, $01, $01, $FF, $FF, $FE, $01, $02)
EEPROM ($00, $FE, $FF, $FF, $00, $FE, $FD, $FF)
EEPROM ($00, $FF, $FF, $FF, $FE, $01, $00, $FE)
EEPROM ($FE, $FD, $FE, $FF, $FF, $01, $00, $00)
EEPROM ($FF, $FF, $01, $FD, $00, $02, $02, $00)
EEPROM ($FE, $FF, $FC, $FE, $FE, $01, $01, $01)
EEPROM ($00, $FE, $FA, $FE, $FB, $FE, $FE, $00)
EEPROM ($FF, $FA, $05, $FF, $FF, $FA, $F9, $F4)
EEPROM ($F9, $06, $0C, $06, $FC, $FB, $F9, $FD)
EEPROM ($FA, $01, $04, $06, $08, $FB, $F4, $F4)
EEPROM ($F5, $06, $04, $F6, $F8, $0C, $1D, $10)
EEPROM ($F9, $EA, $EF, $FA, $FD, $FF, $00, $05)
EEPROM ($06, $00, $F9, $F4, $F9, $FE, $FF, $FD)
EEPROM ($00, $02, $06, $02, $00, $F1, $F8, $F6)
EEPROM ($FB, $F9, $DC, $0A, $42, $54, $22, $E0)
EEPROM ($B2, $B7, $E7, $0F, $29, $27, $15, $01)
EEPROM ($F5, $EA, $E8, $EA, $EF, $FB, $08, $11)
EEPROM ($0A, $07, $FB, $FC, $FA, $02, $08, $EF)
EEPROM ($D1, $BD, $11, $58, $64, $27, $D3, $B7)
EEPROM ($C9, $FF, $13, $16, $0A, $06, $03, $03)
EEPROM ($F8, $F2, $F1, $F6, $FD, $01, $FB, $F8)
EEPROM ($0A, $06, $0F, $EE, $09, $DF, $C7, $D8)
EEPROM ($12, $72, $44, $14, $BC, $CB, $F3, $14)
EEPROM ($09, $E3, $F7, $0B, $26, $0E, $F3, $EA)
EEPROM ($F0, $FE, $F8, $F2, $F8, $01, $15, $06)
EEPROM ($09, $EF, $FB, $F0, $D6, $F9, $0B, $4D)
EEPROM ($25, $E9, $DE, $E4, $1F, $19, $F3, $DB)
EEPROM ($E8, $11, $15, $0A, $FC, $01, $05, $F6)
  for Counter = 0 to 255
  	read Counter, SampleByte
  	hi2cout Counter, (SampleByte)
  	pause 16
  next Counter
#endrem

#rem rows 33 to 64
hi2csetup i2cmaster, %10100010, I2CSpeed, i2cbyte      'Announce there will be I2C
EEPROM ($EA, $EA, $FF, $08, $0B, $FD, $08, $FB)
EEPROM ($FD, $EC, $CC, $01, $1B, $50, $2B, $DB)
EEPROM ($D3, $E7, $1C, $1D, $EC, $DD, $FC, $18)
EEPROM ($14, $FD, $FB, $00, $F9, $EF, $F3, $F8)
EEPROM ($01, $03, $03, $0B, $04, $F9, $03, $ED)
EEPROM ($E0, $F2, $06, $47, $31, $EB, $D5, $EA)
EEPROM ($16, $18, $ED, $DF, $01, $19, $0B, $FB)
EEPROM ($FF, $05, $FC, $F1, $F3, $F9, $01, $02)
EEPROM ($01, $04, $02, $00, $01, $DE, $E7, $02)
EEPROM ($2B, $52, $0B, $C6, $D6, $03, $1C, $0F)
EEPROM ($E5, $E4, $10, $1E, $00, $F3, $FA, $FE)
EEPROM ($FB, $F7, $F6, $01, $03, $00, $06, $04)
EEPROM ($F8, $00, $F9, $E9, $E2, $EB, $40, $5B)
EEPROM ($1C, $E6, $C6, $C6, $F9, $24, $12, $FC)
EEPROM ($04, $0C, $0A, $FF, $F0, $EC, $F4, $FE)
EEPROM ($FE, $F9, $F9, $FB, $02, $0F, $12, $01)
EEPROM ($F6, $F4, $D9, $DC, $16, $46, $39, $09)
EEPROM ($E5, $D5, $DB, $EF, $04, $13, $18, $12)
EEPROM ($06, $F5, $EC, $EF, $F4, $F6, $FC, $03)
EEPROM ($07, $09, $08, $FD, $F6, $F5, $FB, $FD)
EEPROM ($E9, $ED, $19, $3A, $31, $0B, $E7, $D4)
EEPROM ($D8, $EA, $01, $15, $1E, $1A, $0B, $F7)
EEPROM ($EB, $E8, $ED, $F4, $00, $0A, $0C, $09)
EEPROM ($03, $FE, $FC, $FB, $FD, $FC, $E6, $E8)
EEPROM ($14, $36, $2D, $0B, $ED, $DC, $D6, $E2)
EEPROM ($FB, $10, $1A, $1A, $0E, $FC, $F0, $E9)
EEPROM ($E6, $EE, $FC, $06, $0A, $0A, $04, $01)
EEPROM ($02, $FC, $F6, $FA, $E7, $DC, $12, $4D)
EEPROM ($44, $03, $D1, $C3, $CB, $EC, $19, $2E)
EEPROM ($23, $0D, $F9, $EA, $E4, $EB, $F6, $00)
EEPROM ($03, $05, $07, $0A, $0B, $03, $F8, $F5)
EEPROM ($FA, $FA, $FA, $E6, $E1, $1D, $55, $41)
  for Counter = 0 to 255
  	read Counter, SampleByte
  	hi2cout Counter, (SampleByte)
  	pause 16
  next Counter
#endrem

#rem rows 65 to 96
hi2csetup i2cmaster, %10100100, I2CSpeed, i2cbyte      'Announce there will be I2C
EEPROM ($FE, $CE, $C0, $C9, $ED, $1B, $33, $28)
EEPROM ($11, $FC, $E9, $E2, $E9, $F5, $01, $03)
EEPROM ($04, $06, $08, $09, $02, $FA, $F5, $F9)
EEPROM ($FA, $05, $E9, $C9, $18, $68, $45, $F2)
EEPROM ($C5, $B9, $D4, $12, $30, $17, $02, $03)
EEPROM ($F6, $EE, $F7, $FD, $FE, $02, $FE, $F8)
EEPROM ($FB, $00, $06, $10, $08, $F5, $F0, $FD)
EEPROM ($00, $06, $FB, $D2, $07, $4A, $2D, $FB)
EEPROM ($DA, $C3, $E0, $1F, $22, $FE, $F4, $FE)
EEPROM ($01, $00, $FC, $F7, $FC, $05, $FC, $FA)
EEPROM ($03, $FC, $FA, $08, $01, $FA, $04, $FE)
EEPROM ($F9, $0D, $11, $C1, $D4, $46, $46, $16)
EEPROM ($EC, $B9, $C8, $16, $2A, $03, $F1, $F2)
EEPROM ($FB, $09, $09, $FC, $F4, $F7, $FA, $FE)
EEPROM ($00, $03, $01, $03, $04, $FE, $FA, $FE)
EEPROM ($03, $00, $F7, $05, $FD, $CA, $0D, $57)
EEPROM ($2B, $FD, $D2, $B5, $E4, $2E, $27, $FF)
EEPROM ($F3, $F4, $FF, $0C, $06, $F9, $F4, $F8)
EEPROM ($FD, $03, $05, $06, $02, $FF, $00, $FE)
EEPROM ($FD, $01, $03, $FD, $F7, $01, $05, $DC)
EEPROM ($F8, $44, $34, $08, $E2, $B5, $CF, $22)
EEPROM ($34, $12, $03, $F5, $E7, $FA, $0C, $FE)
EEPROM ($F7, $00, $03, $FC, $FB, $FF, $FE, $FC)
EEPROM ($06, $05, $FA, $F9, $01, $01, $F7, $FF)
EEPROM ($12, $F6, $CF, $0F, $3E, $19, $00, $D7)
EEPROM ($B7, $F0, $34, $24, $00, $F9, $EA, $E7)
EEPROM ($05, $10, $FE, $FA, $02, $FF, $FB, $FF)
EEPROM ($01, $FC, $FC, $06, $05, $FB, $FC, $02)
EEPROM ($00, $F7, $FE, $0C, $F4, $DA, $16, $42)
EEPROM ($15, $F4, $DC, $B9, $E3, $36, $31, $FF)
EEPROM ($F3, $F1, $ED, $01, $0E, $FE, $F3, $FD)
EEPROM ($01, $FC, $04, $08, $FD, $FA, $FE, $FB)
  for Counter = 0 to 255
  	read Counter, SampleByte
  	hi2cout Counter, (SampleByte)
  	pause 16
  next Counter
#endrem

#rem rows 97 to 128
hi2csetup i2cmaster, %10101000, I2CSpeed, i2cbyte      'Announce there will be I2C
EEPROM ($00, $08, $03, $FC, $F7, $F1, $FE, $0D)
EEPROM ($E8, $E9, $33, $34, $FF, $E9, $C8, $C1)
EEPROM ($0E, $3E, $16, $F2, $F2, $EF, $F4, $0A)
EEPROM ($08, $F6, $F5, $FD, $FB, $FD, $08, $04)
EEPROM ($FA, $FE, $FE, $FA, $01, $06, $FF, $FB)
EEPROM ($F8, $F1, $04, $0A, $DE, $F2, $3C, $37)
EEPROM ($00, $DD, $C3, $C8, $0A, $37, $20, $FA)
EEPROM ($F0, $F3, $F7, $FE, $02, $02, $FF, $FE)
EEPROM ($FC, $F9, $FE, $04, $05, $00, $FC, $FA)
EEPROM ($FC, $00, $02, $01, $FE, $FB, $00, $09)
EEPROM ($E7, $DF, $25, $3F, $12, $EA, $CE, $C3)
EEPROM ($F3, $30, $29, $FE, $ED, $F0, $F5, $FC)
EEPROM ($00, $00, $02, $01, $FC, $FC, $FC, $02)
EEPROM ($07, $04, $01, $FE, $FC, $FC, $FF, $02)
EEPROM ($02, $FE, $FE, $01, $0C, $EE, $D5, $12)
EEPROM ($3D, $1C, $FD, $F0, $D3, $D5, $00, $15)
EEPROM ($13, $18, $0E, $F3, $EB, $F4, $F8, $FE)
EEPROM ($08, $05, $FB, $FB, $01, $03, $0B, $0B)
EEPROM ($F9, $F5, $FC, $F4, $FB, $14, $0E, $F9)
EEPROM ($F1, $FB, $00, $F2, $01, $25, $24, $04)
EEPROM ($E7, $D1, $D1, $F9, $25, $2A, $13, $F9)
EEPROM ($E7, $E6, $F2, $FD, $04, $06, $05, $00)
EEPROM ($F9, $FC, $02, $05, $06, $FE, $F7, $FA)
EEPROM ($FB, $FC, $08, $0B, $01, $FA, $00, $06)
EEPROM ($EC, $E3, $0F, $32, $23, $FE, $DC, $C8)
EEPROM ($DE, $0F, $29, $1E, $06, $F1, $E9, $EF)
EEPROM ($FB, $02, $04, $05, $02, $FA, $F9, $FF)
EEPROM ($04, $07, $04, $FA, $F5, $F9, $FA, $00)
EEPROM ($0A, $08, $FC, $F9, $03, $FC, $E6, $F9)
EEPROM ($22, $22, $01, $EC, $E7, $E9, $F6, $09)
EEPROM ($13, $0D, $04, $FB, $F2, $EF, $F5, $FD)
EEPROM ($04, $0A, $07, $01, $FE, $FC, $FC, $00)
  for Counter = 0 to 255
  	read Counter, SampleByte
  	hi2cout Counter, (SampleByte)
  	pause 16
  next Counter
#endrem

#rem rows 129 to 136
hi2csetup i2cmaster, %10100011, I2CSpeed, i2cbyte      'Announce there will be I2C
EEPROM ($00, $FC, $FD, $01, $FF, $FF, $02, $01)
EEPROM ($FC, $FB, $02, $04, $FF, $FE, $00, $FF)
EEPROM ($FD, $FE, $00, $01, $00, $00, $FF, $FD)
EEPROM ($FC, $FC, $FD, $FF, $00, $00, $01, $01)
EEPROM ($FF, $FE, $FF, $FF, $01, $01, $FF, $00)
EEPROM ($FF, $FE, $FD, $FE, $FF, $00, $00, $00)
EEPROM ($01, $FD, $FB, $FE, $03, $02, $FE, $FE)
EEPROM ($FE, $FE, $FD, $FD, $FF, $FE, $FE)
  for Counter = 0 to 62
  	read Counter, SampleByte
  	hi2cout Counter, (SampleByte)
  	pause 16
  next Counter
#endrem

;hi2csetup i2cmaster, %10100000, I2CSpeed, i2cbyte      'Announce there will be I2C

;pause 16

;#rem

w26 = 1 'pause in us

main:
  hi2csetup i2cmaster, %10100000, I2CSpeed, i2cbyte
  for Counter = 0 to 255
    hi2cin Counter, (SampleByte)
    pwmout C.2, 249, SampleByte
  next Counter
  hi2csetup i2cmaster, %10100001, I2CSpeed, i2cbyte
  for Counter = 0 to 255
    hi2cin Counter, (SampleByte)
    pwmout C.2, 249, SampleByte
  next Counter
  hi2csetup i2cmaster, %10100010, I2CSpeed, i2cbyte
  for Counter = 0 to 255
    hi2cin Counter, (SampleByte)
    pwmout C.2, 249, SampleByte
  next Counter
  hi2csetup i2cmaster, %10100100, I2CSpeed, i2cbyte
  for Counter = 0 to 255
    hi2cin Counter, (SampleByte)
    pwmout C.2, 40, SampleByte
  next Counter
  for Counter = 0 to 255
    hi2csetup i2cmaster, %10101000, I2CSpeed, i2cbyte
    hi2cin Counter, (SampleByte)
    pwmout C.2, 249, SampleByte
  next Counter
  hi2csetup i2cmaster, %10100011, I2CSpeed, i2cbyte
  for Counter = 0 to 62
    hi2cin Counter, (SampleByte)
    pwmout C.2, 249, SampleByte
  next Counter
  pwmout C.2, OFF
  
  pause 8000
  
#rem EEPROM TEST CODE
;  hi2cout 0, (Counter)
;  pause 16
  hi2cin 0, (Counter)
  pause 16
  sertxd (#Counter, LF, CR)
#endrem

goto main
Edmunds
 

AllyCat

Senior Member
Hi,

I'm running at 64MHz and the time the loop takes is approximately the same as the playback on the computer. But I don't really get a Hello, but some varied humming. I have tried various speakers and the sound changes, but not so much.
With the predominance of $00, $FF and nearby, it looks as if your data is "signed", but for the PWM you need a signal biassed typically around $80.

But I'm also puzzled by your PWMOUT commands which appear to use parameters of either 249 or 40 (i.e. periods of about 1000 or 164 clocks/cycle. But IMHO the only "sensible" parameter to use (for byte values up to 255) is 63 which AFAIK should give a period of 256 (i.e. accept a modulation of one byte) and a Carrier frequency of about 250 kHz.

Alternatively, you could try encoding two data nybbles into each byte, then on "playback", split each byte back into the nybbles and throw them at the on-chip DAC.

Oh, and I guess you mean 362 ms.

Cheers, Alan.
 

edmunds

Senior Member
Hi,

With the predominance of $00, $FF and nearby, it looks as if your data is "signed", but for the PWM you need a signal biassed typically around $80.

But I'm also puzzled by your PWMOUT commands which appear to use parameters of either 249 or 40 (i.e. periods of about 1000 or 164 clocks/cycle. But IMHO the only "sensible" parameter to use (for byte values up to 255) is 63 which AFAIK should give a period of 256 (i.e. accept a modulation of one byte) and a Carrier frequency of about 250 kHz.

Alternatively, you could try encoding two data nybbles into each byte, then on "playback", split each byte back into the nybbles and throw them at the on-chip DAC.

Oh, and I guess you mean 362 ms.

Cheers, Alan.

Sleepleess night must have had its toll. 40 and 360us are just typos (63 and ms being the correct values) and 249 is a desperate man's attempt at "don't know why, but maybe changing this works?" :)

Although I am puzzled about the values. In the examples people have done I could not find any description or could not read enough C or assembler code to figure out what the carrier frequency should be. How and why would 250kHz carrier frequency work with 3kHz sample rate? Do these two go together at all? The period of 256 being convenient to accept the modulation of one byte I understand, but further, the picture gets blurry. Would be grateful to learn.

I will look into the "signed" part as well. Late last night I got a sample file from a working solution. It seems different from mine with lots of values in the middle if compared to my zeros and 255s as you correctly point out. The problem is the file is 32kB, which is massive manual labor to get into the memory I have :). Half of it actually, because I only have 16kB.

At the risk of characters being distorted by different encodings of web pages, here is a piece of my "raw" file:

ˇˇˇˇˇˇˇˇ˛ˇˇˇˇˇˇˇ˛˛ˇˇ˛˝ˇˇˇˇ˛˛˛˝˛ˇˇˇˇ˝˛ˇ¸˛˛˛˙˛˚˛˛ˇ˙ˇˇ˙˘Ù˘ ¸˚˘˝˙˚ÙÙıˆ¯ ˘ÍÔ˙˝ˇ˘Ù˘˛ˇ˝Ò¯ˆ˚˘‹
BT"‡≤∑Á)'ıÍËÍÔ˚
˚¸˙Ô—ΩXd'”∑…ˇ
¯ÚÒˆ˝˚¯
Ó fl«ÿrDºÀÛ „˜ &ÛÍ˛¯Ú¯ Ô˚÷˘ M%Èfi‰Û€Ë
¸ˆÍ͡ ˝˚˝ÏÃP+€”ÁÏ›¸˝˚˘ÔÛ¯ ˘̇ÚG1Î’ÍÌfl ˚ˇ¸ÒÛ˘fiÁ+R ∆÷‰Û˙˛˚˜ˆ¯˘È‚Î@[Ê∆∆˘$¸
ˇÏÙ˛˛˘˘˚ˆÙŸ‹F9 Â’€ÔıÏÔÙˆ¸ ˝ˆı˚˝ÈÌ:1 Á‘ÿÍ ˜ÎËÌÙ
˛¸˚˝¸ÊË6- Ì‹÷‚˚¸ÈÊÓ¸

And here is a piece of presumably working "raw" file:

:020000040000FA
:040000009031512DBD
:040008009031532DB3
:02000E00B60832
:10001000031D0B280800FD30000000000000000058
:1000200000000000000000000000000000000000D0
:1000300000000000000000000000000000000000C0
:1000400000000000000000000000000000000000B0
:1000500000000000000000000000000000000000A0
:100060000000000000000000000000000000000090
:100070000000000000000000000000000000000080
:100080000000000000000000000000000000FF3E33
:10009000031D0C280000000000000000000000000C
:1000A0000000000000000000000000000000000050
:1000B0000000000000000000000000000000000040


Thank you for your time,

Edmunds
 

AllyCat

Senior Member
Hi,

The 250 kHz is just my confirmation of what frequency a PWMOUT pin,63,.... will create, i.e. 64 MHz / 256. It makes the design of the low-pass filter to separate the "audio" from the "carrier" frequency very easy. But it makes the design of the "switching output stage" very difficult (a few tens of nanoseconds difference between rise and fall delays represents a significant output level error). So, it might be better to use PWMDIV4 or lower. Much will depend if you're putting a "power" filter (L-C) after a switching output stage or an "Op-Amp + RC" filter before a linear audio power amplifier.

However, the main requirement for the output filter is to separate the "wanted" audio from "alias" frequencies. A 3 kHz sampling frequency needs a very sharp cut-off filter at 1.5 kHz (to eliminate alias frequencies above 1.5 kHz). That's why I suggested generating two samples per byte, which might allow about 5 kHz sampling. You're still likely to need a moderately elaborate low-pass filter, perhaps with a -3dB point around 2 kHz. The lower amplitude-resolution (4 bits = 16 levels) may not be too serious when you're running so close to the sampling frequency anyway.

Having verified your data throughput, I would now just try to synthesise a simple periodic waveform such as a sine wave. For one cycle of say 250 Hz, you need only 12 samples at 3 kHz or 20 samples at 5 kHz (which can be easily created using Excel, or even a PICaxe X2 program). Put those in a hard-coded loop and see which, if either, gives an acceptable tone output. Then try a few different frequencies and amplitudes

Cheers, Alan.
 

edmunds

Senior Member
Hi,

However, the main requirement for the output filter is to separate the "wanted" audio from "alias" frequencies. A 3 kHz sampling frequency needs a very sharp cut-off filter at 1.5 kHz (to eliminate alias frequencies above 1.5 kHz).

Cheers, Alan.
Could not the same be achieved with selecting pwm period in a way that pwm duty values from the file would always be below 50% of the period? Provided the period is then 3kHz (or matches whatever the sampling rate of the file is)?

Edmunds
 

AllyCat

Senior Member
Hi,

No, changing the duty cycle (and period) will only affect the "audio" output level (and modulation carrier frequency). And all your data valuies will have to be scaled accordingly.

If your sampling frequency is only 3 kHz, then you must have a low pass filter which eliminates that frequency, and ideally all frequencies above half of that (i.e. 1.5 kHz upwards).

And if you try to run the PWM "synchronised" at 3 kHz (i.e. one cycle per sample) I think you'll need an even more exotic fillter.

Cheers, Alan.
 

edmunds

Senior Member
Dear all,

Thank you all for your input.

I have spent some further 20 hours on this project (or rather learning) and have to conclude I cannot achieve any further promising results, if I cannot read data faster that i2c from EEPROM "pages". I don't think the data transfer itself is a problem, but the fact I have to call 5 consecutive hi2csetup and hi2cin commands for 11kB file, must be slowing things down a lot. This is based on intuition, rather than knowledge, since despite tons of material online I have read (or because of that :)), I'm still puzzled about the whole concept and thus I am pointing in the dark a lot.

I don't have the necessary hardware at the moment to try the simplified SD card reading implementation and also not so much time for this, so it will have to go into the "someday-maybe" list for now.

Thanks again,

Edmunds
 

Rick100

Senior Member
Several years ago I used a 12f683 to play 8 bit wave files from a 24lc256 eeprom, so I thought I would give it a try on the Picaxe. I used a 20X2 running at 64Mhz to read a sound sampled at 3600 Hz. The pwm
freq for the output is around 15KHz. It was chosen so the duty cycle would have a 10 bit range. Only the 8 most significant bits are used by pokesfr into the CCPR1L register that sets the duty cycle. The pwm
output is ran through a simple filter and into an amp from some old PC speakers. Here's a video of the result.

https://youtu.be/t1cCmrstsRA

The hard part of the project is sampling the sound and getting it into the 24lc256. I used Audacity for the recording. To get the sound file into the eeprom I used a vb.net program I wrote to send the file
in packets to a 20x2 that programmed the eeprom. It's kind of slow but usable. You can also use a Pickit 2 for programming the eeprom. The playback seems slower than the the 3600 hertz sample rate but
overall the result is better than I expected. I was surprised the loop was fast enough to include an IF/Then statement for detecting a selected end point. This makes it possible to only play selected sections.
Here are the picaxe program, filter schematic and sound file I used.
sound_filter.png

View attachment helloworld3600.zip

Code:
[color=Navy]#no_data
#no_table

#picaxe [/color][color=Black]20x2[/color]

[color=Blue]setfreq m64[/color][color=Green]'[/color]

[color=Blue]Symbol [/color][color=Black]I2CSpeed [/color][color=DarkCyan]= [/color][color=Blue]i2cfast_64

Symbol [/color][color=Black]SampleByte [/color][color=DarkCyan]= [/color][color=Purple]b1[/color]
[color=Blue]symbol [/color][color=Black]address [/color][color=DarkCyan]= [/color][color=Purple]w2[/color]
[color=Blue]symbol [/color][color=Black]addhi [/color][color=DarkCyan]= [/color][color=Purple]b5[/color]

[color=Blue]symbol [/color][color=Black]endAddress [/color][color=DarkCyan]= [/color][color=Purple]w4[/color]


[color=Blue]pwmout pwmdiv4[/color][color=Black],[/color][color=Blue]C.5[/color][color=Black],[/color][color=Navy]255[/color][color=Black],[/color][color=Navy]512    [/color][color=Green]'has to be 255 period for 10 bits[/color]

[color=Blue]pause [/color][color=Navy]1000[/color]

[color=Black]main:
  [/color][color=Blue]hi2csetup i2cmaster[/color][color=Black],  [/color][color=Navy]%10100000[/color][color=Black], I2CSpeed, [/color][color=Blue]i2cword

  [/color][color=Black]address [/color][color=DarkCyan]= [/color][color=Navy]0           [/color][color=Green]'start address
  [/color][color=Black]sampleByte [/color][color=DarkCyan]= [/color][color=Navy]0
  [/color][color=Black]endAddress [/color][color=DarkCyan]= [/color][color=Navy]31000[/color]
[color=Black]fastloop:
      [/color][color=Blue]hi2cin [/color][color=Black]address,[/color][color=Blue]([/color][color=Black]SampleByte[/color][color=Blue])
      pokesfr [/color][color=Navy]$be[/color][color=Black],SampleByte        [/color][color=Green]'put 8 most significant bits of duty cycle into register
      [/color][color=Blue]inc [/color][color=Black]address
      [/color][color=Blue]if [/color][color=Black]address [/color][color=DarkCyan]<> [/color][color=Black]endAddress [/color][color=Blue]then goto [/color][color=Black]fastloop[/color]

[color=Blue]goto [/color][color=Black]main[/color]

If anyone is interested I can uplaod the vb.net and picaxe programs for programming the sound files into the eeprom. It may take me awhile to clean up the code so it's usable. It has a lot of debugging
stuff in it that makes it hard to use. I don't make any guarantees it will work on your computer but it works on mine.
 

AllyCat

Senior Member
Hi,

That's an interesting demonstration, can you estimate how much lower (slower) are the output frequencies compared with those sampled at the 3 kHz rate?

The "reedy" background sounds are probably alias freqiuencies due to inadequate filtering. "Oversampling" is a well known method to use simple(r) filters, so I'd aim for a higher sampling rate, even if with lower amplitude resolution. A method I often use is to "interleave" time-critical actions between individual time-consuming instructions. For example something like (pseudocode):
Code:
fastloop:
   pokesfr $be,b4 
   hi2cin address,(b2,b3,b4)
   pokesfr $be,b2 
   inc address : if address = endAddress then goto main
   pokesfr $be,b3 
   goto fastloop
I don't know how much advantage the POKESFR gives because some PICaxe commands seem to be converted (by the PE) to little more than POKESFRs anyway. But certainly don't use PWMDUTY which is likely very slow (judging by the number of bytes it uses).

Cheers, Alan.
 

Rick100

Senior Member
Hi,

That's an interesting demonstration, can you estimate how much lower (slower) are the output frequencies compared with those sampled at the 3 kHz rate?
A quick measurement with a stopwatch showed a playback rate of approximately 3100 Hz instead of the 3600 Hz sample rate. I removed the pwmdiv4 from the pwm setup to get the freq to 62.5K . It sounds a little better now. I chose 3600 Hz because I read in the Audacity website it was the lowest usable sample rate. There is plenty of room for experimentation.
 

AllyCat

Senior Member
Hi,

Thanks. One of the "unknowns" for me was how long the hi2cin command would take to execute. I "know" that the rest of your fastloop would take about 2400 PIC instruction cycles; 3100 Hz has a period of 322 us or about 5200 cycles. So the hi2cin should be about 2800 cycles. Thus, I'd suggest "sandwiching" a two-bye i2c fetch between two POKESFRs in the fastloop. That should give two SFR writes in just over 6000 instruction cycles, or about 5 kHz sampling frequency.

However, there are two distinctly different filtering issues. Filtering the PWM carrier rate (from the wanted "audio") is "easy". Arguably (at least subjectively), it's not even required with PWM above 20 kHz because the carrier is ultrasonic and thus inaudible. Or, as I suggested earlier in the thread, a DAC (using resistors) could be used, which doesn't even have a carrier frequency.

The fundamental acoustic issues are concernd with the sampling frequency, or more specifically the Nyquist frequency, which is half of the sampling frequency. Any frequency above the Nyquist frequency cannot be distinguished from one below (the Nyquist frequency can be considered as a "mirror" in the frequency domian graph). Ideally, you need a very sharp cutoff filter at the Nyquist frequency, and I think that the Audacity "recommendation" is assuming that.

The reason is easier to consider in the case of encoding. If you sample a 2 kHz waveform (e.g. a sine wave) at 3k samples/sec, then the resulting data is indistinguishanble from a set of samples taken of a 1 kHz waveform. Therefore it is essential to to elimate all input frequencies above the Nyquist frequency (1.5 kHz) before sampling, to avoid this "alias" effect.

The effect when decoding is not so obvious, but remember that the (PWM) DAC is basically generating a sequence of dc levels which changes every 300 us, producing a "staircase" waveform (going up and down). Or consider it as interleaved and superimposed "pulse" waveforms, each with a "clock rate" of 3 kHz. So clearly the low-pass filter needs to reduce/reject a frequency of 1.5 kHz as much as possible, whilst leaving the "audio" signal up to 1.5 kHz largely unaffected.

Cheers, Alan.
 

Rick100

Senior Member
Thanks for all the info Alan. I'm still trying to wrap my head around some of it. I suddenly remembered that the 24lc256 eeprom increments its address after every byte is read so the address doesn't have to be included in the hi2cin command. That change got the playback rate up to approximately 4200 Hz. I have some 24FC256 eeproms rated at a 1 Mhz clock. I may try them next. Here's the latest version of the code.

Code:
[color=Navy]#no_data
#no_table

#picaxe [/color][color=Black]20x2[/color]

[color=Blue]setfreq m64[/color][color=Green]'[/color]

[color=Blue]Symbol I2CSpeed [/color][color=DarkCyan]= [/color][color=Blue]i2cfast_64

Symbol [/color][color=Purple]SampleByte [/color][color=DarkCyan]= [/color][color=Purple]b1[/color]
[color=Blue]symbol [/color][color=Purple]address [/color][color=DarkCyan]= [/color][color=Purple]w2[/color]
[color=Blue]symbol [/color][color=Purple]addhi [/color][color=DarkCyan]= [/color][color=Purple]b5[/color]

[color=Blue]symbol [/color][color=Purple]endAddress [/color][color=DarkCyan]= [/color][color=Purple]w4[/color]



[color=Green]'pwmout pwmdiv4,C.5,255,512   'has to be 255 period for 10 bits 15626 Hz[/color]
[color=Blue]pwmout C.5[/color][color=Black],[/color][color=Navy]255[/color][color=Black],[/color][color=Navy]512      [/color][color=Green]'has to be 255 period for 10 bits 62500 Hz[/color]

[color=Blue]pause [/color][color=Navy]1000[/color]

[color=Black]main:
  [/color][color=Blue]hi2csetup i2cmaster[/color][color=Black],  [/color][color=Navy]%10100000[/color][color=Black], [/color][color=Blue]I2CSpeed[/color][color=Black], [/color][color=Blue]i2cword

  [/color][color=Purple]address [/color][color=DarkCyan]= [/color][color=Navy]1           [/color][color=Green]'start address
  [/color][color=Purple]sampleByte [/color][color=DarkCyan]= [/color][color=Navy]0
  [/color][color=Purple]endAddress [/color][color=DarkCyan]= [/color][color=Navy]31000
  [/color][color=Blue]hi2cin [/color][color=Purple]address[/color][color=Black],[/color][color=Blue]([/color][color=Purple]SampleByte[/color][color=Blue])
  pokesfr [/color][color=Navy]$be[/color][color=Black],[/color][color=Purple]SampleByte            [/color][color=Green]'put 8 most significant bits of duty cycle into register[/color]
[color=Black]fastloop:
      [/color][color=Green]'hi2cin address,(SampleByte)
      [/color][color=Blue]hi2cin ([/color][color=Purple]SampleByte[/color][color=Blue])
      pokesfr [/color][color=Navy]$be[/color][color=Black],[/color][color=Purple]SampleByte        [/color][color=Green]'put 8 most significant bits of duty cycle into register
      [/color][color=Blue]inc [/color][color=Purple]address
      [/color][color=Blue]if [/color][color=Purple]address [/color][color=DarkCyan]<> [/color][color=Purple]endAddress [/color][color=Blue]then goto [/color][color=Black]fastloop[/color]

[color=Blue]goto [/color][color=Black]main[/color]
 

AllyCat

Senior Member
Hi,

When you've got it running as fast as possible, it might be worth looking more at the output filter. Unfortunately, good filter design is "difficult" and all the aspects concerned with digital sampling make it more so. :(

I tried a quick Google to see if I could find a circuit diagram for a suitable filter, but drew a blank. However, I did find a couple of links that might be informative at a reasonably "easy" level. For example this seminar .pdf aims at giving some simple practical advice (don't be put off by the maths equations); in particular, see "Question 1" on pages 9 - 11.

Most discussions on sampling theory consider the encoding process, but Audacity can do the filtering for you, so it's not very relevant here. However, around the first 10 paragraphs of this blog on oversampling may give some food for thought. Of course PICaxe is much too slow to do any significant (real time) digital processing, but I do wonder if calculating a simple average of pairs of consecutive values (and outputting to the PWM/DAC between each "recorded" pair) might ease the design of the low-pass filter (or rather make it sound a little better, with the type of filter that's being used).

Cheers, Alan.
 

Rick100

Senior Member
I was curious to hear what edmunds 'hello' file sounded like,so I used his program in post 17 to generate a basic program. The program runs on a 20X2 and requires no external eeprom. The data is stored in
inline pokesfr commands generated by sertxd commands. It uses 3932 of the 4096 available program bytes. Since it's just a series of pokesfr's , the only control over the playback rate is with the setfreq
command. The original data from edmunds program was in a signed format which probably explains why he had trouble getting a usable output, as Alan pointed out. The picaxe program that generated the pokesfr's also converted the signed byte to unsigned 7 bit values. My first attempt was with 8 bit samples but that required a pwm frequency of 7813 Hertz which didn't filter out well. I doubled the pwm frequency to 15626 and high frequency background noise is less noticeable but still there. Doubling the frequency required a 7 bit sample. I noticed that edmunds original samples didn't use all the 8 bit range or amplitude so the conversion doubled the amplitude. I think a few bytes got clipped in the process. The pwm output goes through the same filter as in post 25. The ouput is decent considering it's just picaxe and a couple of resistors and capacitors. Here's the program.
View attachment edmundsHello 2.bas
 

Rick100

Senior Member
I worked some more on getting the playback speed up by switching to a 24fc256 eeprom with a one megahertz clock. Then I took Alans advice and did a two byte read sandwiched in between two pokesfr commands. Both changes made significant improvements in speed. The playback rate is now approximately 7000 Hertz. I was surprised I didn't have to change the i2c pullup resistors on my AXE091 development board, but the 24fc256 is the only thing on the i2c bus. The sound quality has improved considerably. Here's a video of it playing a sound clip from a recognizable character.
https://youtu.be/RpQEtkKomCI

Here's the latest code and the sound file I used.
View attachment Homer_red_wire7k.zip

Code:
[color=Navy]#no_data
#no_table

#picaxe [/color][color=Black]20x2[/color]

[color=Blue]setfreq m64

Symbol [/color][color=Black]I2CSpeed [/color][color=DarkCyan]= [/color][color=Blue]i2cfast_64  [/color][color=Green]'gets overwritten later by pokesfr[/color]
[color=Blue]symbol [/color][color=Black]SSPADD [/color][color=DarkCyan]= [/color][color=Navy]$c8[/color]
[color=Blue]symbol [/color][color=Black]DUTYREG [/color][color=DarkCyan]= [/color][color=Navy]$be[/color]

[color=Blue]Symbol [/color][color=Black]SampleByte1 [/color][color=DarkCyan]= [/color][color=Purple]b1[/color]
[color=Blue]symbol [/color][color=Black]SampleByte2 [/color][color=DarkCyan]= [/color][color=Purple]b2[/color]
[color=Blue]symbol [/color][color=Black]address [/color][color=DarkCyan]= [/color][color=Purple]w2[/color]
[color=Blue]symbol [/color][color=Black]endAddress [/color][color=DarkCyan]= [/color][color=Purple]w4[/color]


[color=Green]'pwmout pwmdiv4,C.5,255,512   'has to be 255 period for 10 bits 15626 Hz
      [/color][color=Blue]pwmout C.5[/color][color=Black],[/color][color=Navy]255[/color][color=Black],[/color][color=Navy]512      [/color][color=Green]'has to be 255 period for 10 bits 62500 Hz


      
      [/color][color=Blue]hi2csetup i2cmaster[/color][color=Black],  [/color][color=Navy]%10100000[/color][color=Black], I2CSpeed, [/color][color=Blue]i2cword
      pause [/color][color=Navy]1
      [/color][color=Blue]pokesfr [/color][color=Black]SSPADD,[/color][color=Navy]16 [/color][color=Green]'1 mMHz i2c clock[/color]

[color=Black]main:
      [/color][color=Blue]pause [/color][color=Navy]8000

      [/color][color=Black]address [/color][color=DarkCyan]= [/color][color=Navy]1       [/color][color=Green]'start address taking into account 1st read to set address
  
      [/color][color=Black]endAddress [/color][color=DarkCyan]= [/color][color=Navy]32320 [/color][color=DarkCyan]/ [/color][color=Navy]2
      [/color][color=Blue]hi2cin [/color][color=Black]address,[/color][color=Blue]([/color][color=Black]SampleByte2[/color][color=Blue])  [/color][color=Green]'sets initial address*********[/color]

[color=Black]fastloop:
      [/color][color=Blue]pokesfr [/color][color=Black]DUTYREG,SampleByte2
      [/color][color=Blue]hi2cin ([/color][color=Black]SampleByte1,SampleByte2[/color][color=Blue])
      pokesfr [/color][color=Black]DUTYREG,SampleByte1         [/color][color=Green]'put 8 most significant bits of duty cycle into register
      [/color][color=Blue]inc [/color][color=Black]address
      [/color][color=Blue]if [/color][color=Black]address [/color][color=DarkCyan]< [/color][color=Black]endAddress [/color][color=Blue]then goto [/color][color=Black]fastloop
      
      [/color][color=Blue]pokesfr [/color][color=Black]DUTYREG,[/color][color=Navy]128     [/color][color=Green]'silence
      
      [/color][color=Blue]goto [/color][color=Black]main

      [/color][color=Blue]end[/color]
 

hippy

Technical Support
Staff member
The sound quality has improved considerably. Here's a video of it playing a sound clip from a recognizable character.
Absolutely astounding and well done.

There may be more tricks for speeding things up further. Rather than increment the address and comparing to end address; decrement the end address and stop when equal to zero. That's effectively using end address as a count of how many samples left to output.

Code:
dec endAddress
If endAddress <> 0 Then fastloop
That zero uses less token bits and no variable accessing overhead so might be quicker to execute. Again it has to be "try it and see" I am afraid.
 

edmunds

Senior Member
This is just plain unbelievable! Amazing stuff! :)

I had around 30 minute slot for this up until now and I tried your first attempt quickly in between other things and could not get it to sound for me. However, I did not have proper time to test or debug anything. Now the sound in the latest video is nothing short of amazing for what I was after :). I just cannot wait to get some time to try this out later in the week (perhaps).

I really, really want to work out how it all works, so I do understand the stuff, not only know how to copy&paste code (which is a very necessary skill to master, indeed).

Thank you guys, for all the inspiration for my ventures into the unknown,

Edmunds
 

edmunds

Senior Member
This is working with no problem, of course. Like a 'hello from Mars' maybe :)

How did you figure out the values of dutyReg? Is it just a sequence of byte values in the file? How can you tell if the file is signed or unsigned? I tried to make it unsigned and thought I had succeeded...

Your code for my 'hello' is running at 8MHz only. If I would run at 64MHz with the current sample and bit rates, I should have plenty of time to do other things. How would I squeeze other tasks in the code? Like checking scratchpad for new "instructions" from hserin or i2c master?

Your new code for much better quality of sound a few posts below runs on 64MHz, which leaves no or little to play with. I'm very dubious about a possibility to squeeze the sounds I need into even the largest available eeprom or a few and the possibility of microSD card sound file update is so tempting. So I have my fingers crossed, super simplified microSD card interface (raw files only, fixed file names, no fragmentation handling, read only) could be faster than I2C. Still, I need to figure out how to do things in between frequency changes - like reading the microSD card.


Thank you for all the input,

Edmunds
 

Rick100

Senior Member
How did you figure out the values of dutyReg? Is it just a sequence of byte values in the file? How can you tell if the file is signed or unsigned? I tried to make it unsigned and thought I had succeeded...
In post #18 Alan noticed your data was centered around $00 and $ff which indicates its signed. Here's a link to a Wikipedia page showing a comparison of signed and unsigned bytes.
http://en.wikipedia.org/wiki/Two's_complement

What did you use to generate the data? I used Audacity and the following steps.

Open up Audacity.

Under "Project Rate(Hz)" on the bottom left of the screen enter 7000 directly into the box. Do this before you record anything.

To the right of that box select the "Length" button and "Samples" from the drop down list. This will let you see how many samples you have selected.

If you want to record from the sound your speakers are playing, do the following. At the top middle of the screen next to the microphone is a drop down list with "MME", "Windows Directsound", and "Windows WASAPI". Choose "Windows WASAPI". To the right of that is another drop down list. Choose the option with "Speakers" in it. Choose "2(Stereo) Recording" from the next list box.

I recorded the Homer wave file by loading it in VLC, pressing record on Audacity and then play on VLC.

You should have a stereo sound on the Audacity screen. Edit it and clip it to size. You can use "Select" "All" from the menu and see how many samples will be exported to your file.

To convert the file to mono choose "Tracks", "Stereo Track to Mono" from the menu bar.

Now choose "File", "export Audio" from the menu. From the file selection window, pick "other uncompressed files" from the "Save as Type" drop down list. Then select "Options" in the same window. Choose "Raw(header-less)" from the "Header" drop down list and "Unsigned 8 bit PCM from the "Encoding" drop down list.

You can now save your file and it will have a "raw" extension.

If you want to save these settings for next time, just "Save Project" from the file menu.

I use the "Hxden" hex editor to examine the file and do any additional clipping. You can download it from here.
http://mh-nexus.de/en/hxd/
There are installable and stand alone versions. I like the stand alone version.

To get the raw file into the 24lc256, I use a vb.net program running on the PC sending data to a 20x2 that does the programming. I'll try to clean up the programs and upload them when I get a little time.

Your code for my 'hello' is running at 8MHz only. If I would run at 64MHz with the current sample and bit rates, I should have plenty of time to do other things. How would I squeeze other tasks in the code? Like checking scratchpad for new "instructions" from hserin or i2c master?
The edmundsHello2.bas program was really just a novelty to demonstrate unrolling a loop and playing an audio sample on a stand alone chip.:) It doesn't leave any program space for added features.

Your new code for much better quality of sound a few posts below runs on 64MHz, which leaves no or little to play with. I'm very dubious about a possibility to squeeze the sounds I need into even the largest available eeprom or a few and the possibility of microSD card sound file update is so tempting. So I have my fingers crossed, super simplified microSD card interface (raw files only, fixed file names, no fragmentation handling, read only) could be faster than I2C. Still, I need to figure out how to do things in between frequency changes - like reading the micro SD card.
How long are the sounds you want to play? I've ordered some of these from Adafruit.
http://www.adafruit.com/products/1564
They have 1 meg X 8bits of flash. I have no ideal if I can get them working. I am interested in the SQI mode which outputs 4 bits of parallel data that might be used to drive an R2R network for AD conversion. They have continuous read mode. The plan is to bit bang the address and initial bytes to get to the first read and then turn on PWM to feed the clock line. A delay to set the length of playback and then turn off PWM. There is probably something I'm missing but it's still an interesting part. The Adafruit has this sound playback project that uses it.
https://learn.adafruit.com/trinket-audio-player

Thank you for all the input,

Edmunds
Your welcome. This is fun stuff.

Good Luck,
Rick
 

Rick100

Senior Member
Absolutely astounding and well done.

There may be more tricks for speeding things up further. Rather than increment the address and comparing to end address; decrement the end address and stop when equal to zero. That's effectively using end address as a count of how many samples left to output. That zero uses less token bits and no variable accessing overhead so might be quicker to execute.
Thanks Hippy. I've tried your suggestion but I need a more accurate way to time the playback than a stopwatch to tell if there was any speed increase. I will have to frame the playback part of the program with high and low commands and measure the pulse duration with another picaxe.

Again it has to be "try it and see" I am afraid.
Try it and see is what makes it fun for me.:)
 

edmunds

Senior Member
Rick,

Thank you for all the input.

The memory devices you are looking at seem interesting. I do not have a good estimate of the file sizes yet. What I'm trying to come up with, is a simple sound player that would perform similar functions as a sound decoder in a model train locomotive, but in a model car. I am fully aware, there is plethora of ICs designed for this, but I have browsed through them (like a lot) and there are at least three factors making most of them plain unusable for what I want and some I will try, but it will not be ideal:

1) Size. There is a lot more space in a model train, than there is in a model car. While trucks can be easier, the same hardware has to fit into 1:87 vans and even some larger station wagons if possible. Many of System-On-Chip sound players are 64 and more pin ICs and are massive compared to picaxe 40x2 in UQFN package I'm working with.

2)They require large numbers of external components - resonators, capacitors etc.

3) Price. These things are expensive and I have a few hundred cars to build.

I was thinking the file size and thus the memory needed would be the next thing after I get the basic player sorted. If I'm happy with, say, 7000Hz sample rate and that is max that a picaxe can output, then I would collect the necessary files, convert them to readable, try them out and see how much space they take. If I can live with the quality of, say, 5000Hz sample rate, this would save some space. Model trains do get their sound files on an eeprom, so it should be possible to do. It also depends on what kind of loops can you implement - the file of running engine, for an example, only has to be one engine revolution long. In theory.

Eeprom has this problem of difficult update. Both, initial write and future updates if needed. It has been some 15 years, I think, since I last did something in Visual Basic :). I have seen some professional eeprom writers, but have not looked into details of how one would work and remember them being quite expensive. With all of that in mind I was thinking a microSD card could be easier to work with and a fun project at the same time.

Regards,

Edmunds
 

AllyCat

Senior Member
Hi,

Code:
dec endAddress
If endAddress <> 0 Then fastloop
That zero uses less token bits and no variable accessing overhead so might be quicker to execute. Again it has to be "try it and see" I am afraid.
Yes, the smaller token appears to save about 200 PIC instruction cycles, or perhaps 15us with a 64 MHz clock.

It's not essential to use two PICaxes, or a 'scope, using a method I devised some time ago and reported in post #12 here. I've only tried it with M2 devices (I believe the X2s use different SFR addresses), but for convenience here is the code I've just tested.

Code:
#picaxe 14m2
#no_data

symbol TMR1L = $16		; SFRs only tested with M2 PICAXEs
symbol TMR1H = $17
symbol T1CON = $18	

exectimes:
	for b1 = 0 to 256 step 32				; Sanity check with different initial Timer values
	w2 = 0
	w1 = 32768
	w5 = 0								; Null delay (subroutine call, etc.)
	sertxd(cr,lf,#b1," Nul=")
	gosub start
	gosub measure
	gosub show
	w5 = w6								; Set NUL time

	sertxd(cr,lf,"Upcount True=")	
	gosub start
		if w1 = 32768 then n1		; Test 1 Code
n1:
	gosub measure
	gosub show	
	sertxd(cr,lf,"Downcount True=")	
	gosub start
		if w2 = 0 then n2			; Test 2 Code
n2:
	gosub measure
	gosub show	
	sertxd(cr,lf,"Upcount False=")		
	gosub start
		if w1 = 32767 then n3		; Test 3 Code
n3:
	gosub measure
	gosub show
	sertxd(cr,lf,"DownCount False=")
	gosub start
		if w2 = 1 then n4			; Test 4 Code
n4:
	gosub measure
	gosub show

	sertxd(cr,lf," Nul=")
	gosub start
	gosub measure
	gosub show	
	sertxd(cr,lf)

	pause 10000
	next b1	
start:
	pokesfr TMR1L,b1
	pokesfr TMR1H,0
	return
measure:
	peeksfr TMR1H,b12			; Read 1st Hi byte
	peeksfr TMR1L,b14			; Read LOw byte
	peeksfr TMR1H,b13			; Read 2nd Hi byte
report:
	if b14 < 128 then inc b12 endif
	w6 = b12 + b13 / 2			; Average Start High
	b13 = b12
	b12 = b14					; Start Low byte
	w6 = w6 - w5 - b0
	return
show:	
	sertxd (#w6,"us ")	
	return
The timings refer to 4 MHz operation and I've included a looping "sanity check" on the time measurements. It can only run on "real" PICaxe hardware (not the simulator) so here is the output from a sample run:

Code:
0 Nul=4477us 
Upcount True=1381us 
Downcount True=1192us 
Upcount False=905us 
DownCount False=737us 
 Nul=65523us
Cheers, Alan.
 

edmunds

Senior Member
I've ordered some of these from Adafruit.
http://www.adafruit.com/products/1564
On the second look, these look great. I could actually combine several on my pcb with the smallest package and avoid putting components on both sides of the pcb, which is inevitable with microSD card. And it certainly cuts complexity on the picaxe program part.

EDIT: found something much bigger with the same continuous read mode. Seems the microSD card idea will have to go and I will have to learn how to write to these thingies efficiently.

Thank you for the hint,

Edmunds
 
Last edited:
Top