'**************************************************************** '* Name : APT_TEST.BAS * '* Author : Wim de Vree * '* Notice : Copyright (c) 2008 PE1GRL * '* : All Rights Reserved * '* Date : 2009/12/09 * '* Version : 1.1 * '* Notes : * '* : * '* Revision History * '* 2009/12/09 Initial experiments * '* 2009/12/11 Added code to record to file * '* 2009/12/13 Added LCD display, key handlers * '* 2009/12/19 ISR partly in assembly, 512byte ringbuffer * '* As the writing is sometimes not fast enough * '* 2009/12/23 Added playback code * '* 2010/01/09 Added elapsed time during recording & playback * '* 2010/01/10 Error handling and cleanup added * '* Serial monitor is compiled conditionally * '* 2011/01/10 Build version for 18F1620 with larger * '* ringbuffer (This PIC has more RAM onboard * '* Now set to 1K, but 2K is also possible) * '**************************************************************** Device 18F2620 ;@CONFIG_REQ ;@__CONFIG config1h, OSCS_OFF_1 & HSPLL_OSC_1 ;@__CONFIG config2l, BOR_ON_2 & BORV_20_2 & PWRT_ON_2 ;@__CONFIG config2h, WDT_OFF_2 & WDTPS_128_2 ;@__CONFIG config3h, CCP2MX_ON_3 ;@__CONFIG config4l, STVR_ON_4 & LVP_OFF_4 & DEBUG_OFF_4 Config_Start OSC = HSPLL ;OSCS = Off WDT = Off WDTPS = 128 ;CCP2MUX = On ;BOR = Off PWRT = On LVP = Off Config_End Xtal 40 All_Digital TRUE Optimiser_Level 3 ;dead_code_remove 1 ;$define SERIAL_MONITOR Symbol GIE INTCON.7 Symbol PEIE INTCON.6 Symbol TMR2IE PIE1.1 Symbol TMR2IF PIR1.1 Symbol TMR2ON T2CON.2 Symbol AD_ON ADCON0.0 Symbol AD_GO ADCON0.2 Symbol AD_NOTDONE ADCON0.2 Symbol P_LED PORTB.0 ; Signalling LED Symbol P_ERROR PORTB.1 ; Error signal Symbol P_LA PORTC.0 ; Logic Analyzer Diag Pin Symbol TRUE 1 Symbol FALSE 0 $ifdef SERIAL_MONITOR Declare Hserial_Baud 9600 Declare Hserial_Clear OFF Declare Hserial_TXSTA %00100000 Declare Hserial_RCSTA %10010000 $endif Symbol SAMPLES_PER_SEC 9766 ; I/O Port Pin assignments: ; ; PORTA.0 Audio Input ; PORTA.1 ; PORTA.2 Vref - ; PORTA.3 Vref + ; PORTA.4 Switch Record ; PORTA.5 Switch Playback ; ; PORTB.0 Status LED ; PORTB.1 ; PORTB.2-7 Alphanumeric LCD ; ; PORTC.0 Error LED ; PORTC.1 SD-Card Chip Select ; PORTC.2 PWM audio output ; PORTC.3 SD-Card Clock ; PORTC.4 SD-Card Data Out ; PORTC.5 SD-Card Data In ; PORTC.6 Serial In ; PORTC.7 Serial Out Symbol SW_OPTION PORTA.4 Symbol SW_ACTION PORTA.5 Symbol PLAY 0 ' Possible Command options Symbol RECORD 1 Symbol ERASE 2 Dim InsIdx As Word ' Insertion index Dim ExtIdx As Word ' Extraction index Dim tmpIdx As Word ' Temp index for operations Dim tmpFSR As Word ' Temp storage for FSR Dim Response As Byte Dim bPlayBack As Bit ' We're playing back a file Dim bRecording As Bit ' We're recording a file Dim bUpdateDisplay As Bit ' Display should be repainted Dim FreeSpace As Dword Dim ADSample As Byte Dim AptIndex As Byte ' Nr of files on card Dim PBIndex As Byte ' Current file for playback Dim Command As Byte Dim Duration As Word Dim SampleCount As Word ' Track samples for time display Dim SecsPassed As Word ' Seconds recording/played Dim SecsDisplayed As Word ' Last displayed time value Dim Mins As Byte, Secs As Byte ' Minutes & Seconds Dim Aligner1 As Byte Dim Aligner2 As Word ;'----------------------------------------------------------- Symbol SD_CS = PORTC.1 ' SPI CS to SD CS (SD pin 1) Symbol SD_DI = PORTC.5 ' SPI DO to SD DI (SD Pin 2) Symbol SD_CLK = PORTC.3 ' SPI CLK to SD CLK (SD Pin 5) Symbol SD_DO = PORTC.4 ' SPI DI to SD DO (SD Pin 7) '----------------------------------------------------------- Include "APT_Test_LIB.pbp" ' Include managed library file '----------------------------------------------------------- Declare LCD_Type ALPHA ; Declare the used LCD Declare LCD_Interface 4 ; display Declare LCD_Lines 2 Declare LCD_ENPin PORTB.3 Declare LCD_RSPin PORTB.2 '----------------------------------------------------------- Dim RingBuffer[$100] As Byte ' Ringbuffer used by ISR Dim Ringbuffer2[$100] As Byte ' Total of 1K Dim Ringbuffer3[$100] As Byte Dim Ringbuffer4[$100] As Byte GoTo Main_Prog ; Goto Main program On_Hardware_Interrupt GoTo PWM_ISR PWM_ISR: movff FSR0L,tmpFSR ' Save FSR0 (used in ISR) movff FSR0H,tmpFSRH movlb 0 ' My ISR vars in bank 0 ; Set P_LA Clear TMR2IF If bRecording == 1 Then While AD_NOTDONE == 1 : Wend CCPR1L = ADRESH ' Instant playback Set AD_GO ' Start a new conversion ; RingBuffer[InsIdx] = CCPR1L ' Store result in ringbuffer lfsr 0,RingBuffer movf InsIdx,W,0 addwf FSR0L,F,0 movf InsIdxH 0 addwfc FSR0H,F,0 movff CCPR1L,INDF0 Inc InsIdx ' Update ringbuffer indexes ; InsIdx = InsIdx & 0x3FF bcf InsIdxH,2 If InsIdx == ExtIdx Then Set P_ERROR Clear bRecording EndIf ElseIf bPlayBack == 1 Then If ExtIdx == InsIdx Then Set P_ERROR ' Ran out of buffer data Clear bPlayBack EndIf ; CCPR1L = RingBuffer[ExtIdx] ' get sample lfsr 0,RingBuffer movf ExtIdx,W,0 addwf FSR0L,F,0 movf ExtIdxH 0 addwfc FSR0H,F,0 movff INDF0,CCPR1L Inc ExtIdx ' Update ringbuffer indexes ; ExtIdx = ExtIdx & 0x3FF bcf ExtIdxH,2 EndIf movff tmpFSR, FSR0L ' Restore FSR0 movff tmpFSRH,FSR0H ; Clear P_LA retfie fast '----------------------------------------------------------- Main_Prog: TRISA = %11111111 ; Definieer I/O lijnen TRISB = %00000000 TRISC = %10010000 ADCON1 = %01001111 ' 1 A/D Channel, 2 Vrefs, Left justified ADCON0 = %10000000 ' A/D channel 0, ON, clk/64 DelayMS 500 ' Small delay for LCD power on init Clear Clear GIE Clear P_LA Clear P_LED Clear P_ERROR Cls ' Wis scherm en zet cursor links bovenaan $ifdef SERIAL_MONITOR HSerOut ["APTrecorder v1.1",10,13] $endif Print "APTrecorder v1.1" ' Plaats tekst op het display Print At 2, 1, "Insert SD or MMC" Repeat ' Wait for SD-Card to be inserted Toggle P_LED Response = SD_Init_FS_MSSP SD_SPI_FOSC_04 DelayMS 200 Until Response = 0 Clear P_LED Clear P_ERROR Cls Command = PLAY bUpdateDisplay = TRUE FreeSpace = SD_Free_Space ' Get free disk space GoSub FindAptFileIndex While 1==1 If bUpdateDisplay == TRUE Then Print At 1,1, Dec4 FreeSpace/1000, "MB Files ", Dec2 AptIndex Print At 2,1, "Cmd: " If Command == PLAY Then Print "PLAY ", Rep $20\10 If Command == RECORD Then Print "RECORD", Rep $20\10 If Command == ERASE Then Print "ERASE ", Rep $20\10 bUpdateDisplay = FALSE EndIf If SW_OPTION == 0 Then DelayMS 20 ' Simple debounce While SW_OPTION == 0 : : Wend ' Wait for key release DelayMS 20 ' Simple debounce Command = (Command + 1) // 3 ' loop commands bUpdateDisplay = TRUE EndIf If SW_ACTION == 0 Then Clear P_ERROR DelayMS 20 ' Simple debounce While SW_ACTION == 0 : : Wend ' Wait for key release DelayMS 20 ' Simple debounce On Command GoSub PlayFile, RecordFile,EraseCard bUpdateDisplay = TRUE EndIf Wend Stop '----------------------------------------------------------- RecordFile: Inc AptIndex Response = SD_Init_FS_MSSP SD_SPI_FOSC_04 If Response == 1 Then Print At 2,1, "ERROR in re-init" Stop EndIf SD_File_Name = "APT" + Str$(Dec3 AptIndex) SD_File_Ext = "ASR" $ifdef SERIAL_MONITOR HSerOut ["Recording to ",SD_File_Name,10,13] $endif Response = SD_Check_For_File ' Check if file already exists If Response = 0 Then GoTo ExFileError ' It should not exist Else $ifdef SERIAL_MONITOR HSerOut ["File Created",10,13] $endif SD_New_File ' Open new file EndIf Cls Print At 1,1,"Recording ", Str SD_File_Name Print At 2,1,"Duration 00:00" SampleCount = SAMPLES_PER_SEC SecsPassed = 0 SecsDisplayed = 0 GoSub InitPWM Set bRecording ; Set recording mode Set AD_ON Set AD_GO ; Start initial A/D converion Set P_LED GoSub EnablePWM While SW_ACTION <> 0 ' While recording switch is down If ExtIdx <> InsIdx Then ' If new data is available GoSub GetNextSample Response = SD_Write_Byte_To_File ADSample If Response = 1 Then Set P_ERROR ' Error LED on GoSub DisablePWM SD_Close_File Print At 2,1, "ERR: Card Full" Stop EndIf Dec SampleCount ' Track recording time If SampleCount == 0 Then ' 1 second written to file SampleCount = SAMPLES_PER_SEC Inc SecsPassed EndIf Else If SecsPassed <> SecsDisplayed Then SecsDisplayed = SecsPassed ' Display time so far Mins = SecsPassed / 60 Secs = SecsPassed // 60 Print At 2,12,Dec2 Mins,":",Dec2 Secs EndIf EndIf If P_ERROR == 1 Then GoSub DisablePWM SD_Close_File Print At 2,1, "ERR: Overrun" Stop EndIf Wend GoSub DisablePWM Clear P_LED ; Future improvement: flush the contents of the buffer here.... DelayMS 20 ' Simple debounce While SW_ACTION == 0 : : Wend ' Wait for key release DelayMS 20 ' Simple debounce $ifdef SERIAL_MONITOR HSerOut ["File size is ", Dec SD_File_Size,10,13] $endif SD_Close_File FreeSpace = SD_Free_Space ' Free disk space has changed Return '----------------------------------------------------------- PlayFile: If AptIndex == 0 Then Print At 2,1, "No files to play" DelayMS 2000 Return EndIf PBIndex = AptIndex GoSub ShowPlayBackFile While SW_ACTION <> 0 If SW_OPTION == 0 Then ' Next file selected DelayMS 20 ' debounce While SW_OPTION == 0 : : Wend ' wait for release DelayMS 20 ' debounce Dec PBIndex If PBIndex == 0 Then PBIndex = AptIndex DelayMS 20 GoSub ShowPlayBackFile EndIf Wend DelayMS 20 ' debounce While SW_ACTION == 0 : : Wend ' wait for release DelayMS 20 ' debounce Response = SD_Open_File If Response == 1 Then GoSub NoFileError GoSub CalcDurationOfOpenFile Cls Print At 1,1,"Playing ", Str SD_File_Name Print At 2,1,"Len ",Dec2 Mins,":",Dec2 Secs," 00:00" $ifdef SERIAL_MONITOR HSerOut ["FILE OPEN, Size is ",Dec SD_File_Size,10,13] $endif GoSub InitPWM Set P_LED SampleCount = SAMPLES_PER_SEC - 1 SecsPassed = 0 SecsDisplayed = 0 ADSample = SD_Read_Byte_From_File ' Get First Sample GoSub PutNextSample ' Stuff in buffer Set bPlayBack GoSub EnablePWM While SD_EOF < 1 tmpIdx = InsIdx Inc tmpIdx tmpIdxH.2 = 0 ' Wrap around at 1024 If tmpIdx <> ExtIdx Then ' Room in buffer ADSample = SD_Read_Byte_From_File GoSub PutNextSample Dec SampleCount If SampleCount == 0 Then ' 1 second stuffed in buffer SampleCount = SAMPLES_PER_SEC Inc SecsPassed EndIf Else ' Only check if buffer completely filled If SecsPassed <> SecsDisplayed Then SecsDisplayed = SecsPassed ' Display time so far Mins = SecsPassed / 60 Secs = SecsPassed // 60 Print At 2,12,Dec2 Mins,":",Dec2 Secs EndIf EndIf If SW_ACTION == 0 Then ' Always check for user abort DelayMS 20 ' debounce While SW_ACTION == 0 : : Wend ' wait for release DelayMS 20 ' debounce Break EndIf If P_ERROR == 1 Then GoSub DisablePWM Print At 2,1, "ERR: Underrun" Stop EndIf Wend ; Here we should wait for the buffer to empty, now we don't play ; the last sample (currently 102 milliseconds, so not very important GoSub DisablePWM Clear P_LED Return '----------------------------------------------------------- ' PicBasic does not support arrays > 256 bytes, so use a small ' subroutine to retreive values from the array. The value read ' is returned in ADSample GetNextSample: If ExtIdxH.1 <> 0 Then If ExtIdxH.0 <> 0 Then ADSample = Ringbuffer4[ExtIdx.LowByte] Else ADSample = Ringbuffer3[ExtIdx.LowByte] EndIf Else If ExtIdxH.0 <> 0 Then ADSample = Ringbuffer2[ExtIdx.LowByte] Else ADSample = RingBuffer[ExtIdx.LowByte] EndIf EndIf Clear GIE ' No ints while updating Inc ExtIdx ExtIdxH.2 = 0 ' Wrap around at 1024 Set GIE Return PutNextSample: If InsIdxH.1 <> 0 Then If InsIdxH.0 <> 0 Then Ringbuffer4[InsIdx.LowByte] = ADSample Else Ringbuffer3[InsIdx.LowByte] = ADSample EndIf Else If InsIdxH.0 <> 0 Then Ringbuffer2[InsIdx.LowByte] = ADSample Else RingBuffer[InsIdx.LowByte] = ADSample EndIf EndIf Clear GIE Inc InsIdx InsIdxH.2 = 0 ' Wrap around at 1024 Set GIE Return '----------------------------------------------------------- InitPWM: InsIdx = 0 ; Init ringbuffer indexes ExtIdx = 0 CCP1CON = %00001100 ; Init TMR2 for PWM PR2 = 255 ; 39.2 kHz Carrier CCPR1L = 128 ; 50% duty cycle T2CON = %00011000 ; 1:4 postscaler for 10kHz Sampling Return EnablePWM: Clear TMR2IF Clear TMR2 Set TMR2IE ; Enable Timer2 interrupt Set PEIE Set GIE Set TMR2ON ; Start Timer Return DisablePWM: Clear TMR2ON ; Timer2 off Clear TMR2IE ; Timer2 int off Clear TMR2IF ; Timer2 int flag off Clear bRecording Clear bPlayBack Return '----------------------------------------------------------- EraseCard: Print At 2,1, "Confirm Erase" While SW_ACTION <> 0 : : Wend ' Wait for key press DelayMS 20 While SW_ACTION == 0 : : Wend ' Wait for key release DelayMS 20 High P_LED SD_Psuedo_Format ' Erase all files FreeSpace = SD_Free_Space ' Get free disk space Low P_LED Return '----------------------------------------------------------- ' Scan SD card for existing APT files FindAptFileIndex: For AptIndex=1 To 99 SD_File_Name = "APT" + Str$(Dec3 AptIndex) SD_File_Ext = "ASR" $ifdef SERIAL_MONITOR HSerOut ["Looking for file ",SD_File_Name,10,13] $endif Response = SD_Check_For_File ' Check if file already exists If Response <> 0 Then Dec AptIndex ' Return last used entry Return EndIf Next AptIndex Return '----------------------------------------------------------- ' Show info about currently selected file (name+duration) ShowPlayBackFile: SD_File_Name = "APT" + Str$(Dec3 PBIndex) SD_File_Ext = "ASR" Response = SD_Open_File If Response == 1 Then GoSub NoFileError GoSub CalcDurationOfOpenFile Print At 2,1, Str SD_File_Name, " ", Dec2 Mins, "m", Dec2 Secs,"s" Return '----------------------------------------------------------- ' Calculate the length op the open file in minutes and seconds ' using the file size and samplerate ' Variable MINS and SECS are filled with the correct values '----------------------------------------------------------- CalcDurationOfOpenFile: Duration = SD_File_Size / SAMPLES_PER_SEC ' Duration in seconds Mins = Duration / 60 ' In Minutes Secs = Duration // 60 ' and seconds Return '----------------------------------------------------------- ' During playback we selected a non-existing file ' this should never happen '----------------------------------------------------------- NoFileError: Print At 2,1,"ERR: No File" Set P_ERROR Stop '----------------------------------------------------------- ' When recording we encounter an existing file ' this should never happen '----------------------------------------------------------- ExFileError: Print At 2,1,"ERR: File exists" Set P_ERROR Stop End