|
|
View previous topic :: View next topic |
Author |
Message |
andyd
Joined: 08 Mar 2007 Posts: 30
|
Annoying Hyperterminal XModem/EEPROM problem |
Posted: Thu Mar 08, 2007 5:13 am |
|
|
I've written a program to send a file to my PIC (16F88) using Hyperterminal (using XModem). Up until now, all I'd tried sending my PIC were text files full of nice normal characters. Since my PIC actually needs to receive ADPCM files, I thought I'd better test it with one, and that's where it all falls down.
Basically, the PIC seems to "skip over" certain characters in the file - if I program the EEPROM (24AA1025) with another file, then program it with the ADPCM file and get the PIC to dump the EEPROM contents into Hyperterminal, I see some random characters (that I'd expect from the file) and then every so often, it will just have segments of whatever was previously in the EEPROM in the middle of what should be the ADPCM file, i.e. it's just refusing to write certain characters in the ADPCM file to memory. It's definitely not a problem with how Hyperterminal is displaying it as if I put a plain text file in first, then try and overwrite it, I can actually read whole segments of the text still! Displaying the EEPROM contents as ints gives me lots of negative numbers with a few positive ones.
I wrote a small C program on my PC to just read in the file as unsigned chars and then display it, which worked fine. However, changing all my functions in my PICC code to use unsigned chars still didn't help. The only conclusion I could come to was that the fgetc() function was reading them into the PIC as signed (I've searched though all the compiler libraries to find it but can't!) and after that no amount of casting could get them written right. Does this sound like it could be the culprit? Or something else in the PIC? Any ideas on how to get round it? A friend suggested using the hardware USART of the PIC and reading directly out of the register, but I haven't got a clue where to start with doing that in C. Any ideas on what could be causing the problem, or any possible solutions (that hopefully don't mean throwing away loads of what I've written so far!) would be very much appreciated!
Code: | #include <16f88.h>
#include <stdio.h>
#include <stdlib.h>
#fuses HS, NOWDT, NOLVP, NOBROWNOUT, NOPROTECT, PUT
#use delay(clock = 8000000) // Delay setup
#use rs232(baud = 9600, xmit = PIN_B0, rcv = PIN_A0, stream = PC) // RS232 setup
#use I2C(Master, sda = PIN_B1, scl = PIN_B4, FAST) // I2C setup
// EEPROM write
void eeprom_write(unsigned char address_upper, unsigned char address_lower, unsigned char data)
{
i2c_start(); // Start communication
i2c_write(0xA0); // Send control code & address of EEPROM then set to write mode
i2c_write(address_upper); // Write upper address byte
i2c_write(address_lower); // Write lower address byte
i2c_write(data); // Write data
i2c_stop(); // Stop communication
delay_ms(5); // Wait 5ms
}
// EEPROM read
unsigned char eeprom_read(unsigned char address_upper, unsigned char address_lower)
{
unsigned char temp_byte;
i2c_start(); // Start communication
i2c_write(0xA0); // Send control code & address of EEPROM then set to write mode
i2c_write(address_upper); // Write upper address byte
i2c_write(address_lower); // Write lower address byte
i2c_start(); // Start communication
i2c_write(0xA1); // Send control code & address of EEPROM then set to read mode
temp_byte = i2c_read(0); // Read data without acknowledge bit
i2c_stop(); // Stop communication
return temp_byte;
}
// EEPROM write (string)
void eeprom_write_string(unsigned char address_upper, unsigned char address_lower, unsigned char* data)
{
while(*data) // Loop until end of string to be written is reached
{
i2c_start(); // Start communication
i2c_write(0xA0); // Send control code & address of EEPROM then set to write mode
i2c_write(address_upper); // Write upper address byte
i2c_write(address_lower); // Write lower address byte
i2c_write(*data++); // Write data
i2c_stop(); // Stop communication
address_lower++; // Add 1 to lower address byte
if(address_lower == 0x00) address_upper++; // If lower address = 0, add 1 to upper address byte
delay_ms(5); // Wait 5ms
}
}
// EEPROM contiguous read start (reads first byte)
int eeprom_read_contig_start(unsigned char address_upper, unsigned char address_lower)
{
int data;
i2c_start(); // Start communication
i2c_write(0xA0); // Send control code & address of EEPROM then set to write mode
i2c_write(address_upper); // Write upper address byte
i2c_write(address_lower); // Write lower address byte
i2c_start(); // Resend start signal to indicate read portion
i2c_write(0xA1); // Send control code & address of EEPROM then set to read mode
data=i2c_read(); // Read data
return(data);
}
// EEPROM contiguous read
int eeprom_read_contig(void)
{
int data;
data = i2c_read(); // Read data
return(data);
}
// EEPROM contiguous read stop (reads last byte)
int eeprom_read_contig_stop(void)
{
int data;
data = i2c_read(0); // Read data without acknowledge bit
i2c_stop(); // Stop communication
return(data);
}
void main(void)
{
int16 i; // Counter variable
int packetnum; // XModem packet number
int packetnum_inverse; // Inverse XModem packet number
int packetcount = 0; // Packet count in program to keep synchronisation
int checksum_rec, checksum_calc; // XModem received checksum & calculated checksum
int32 checksum_temp; // Used in checksum calculation
int error = 0; // Transmission error
int out; // Used to send PC XModem control signals
char c; // Character read in from PC during file transmission
int data1[64]; // First half of XModem packet
int data2[64]; // Second half of XModem packet
char address_upper; // Upper EEPROM address byte
char address_lower; // Lower EEPROM address byte
setup_adc_ports(NO_ANALOGS); // Turn off analogue inputs
setup_oscillator(OSC_8MHZ|OSC_INTRC); // Use internal 8MHz oscillator
fputs("Initiate file transfer using PC then press button to accept.", PC);
while(!input(PIN_A1)) // Wait for button press
{
}
address_upper = 0b00000000; // Variables set to write data to address 0000000000000001
address_lower = 0b00000001;
out = 0x15;
fputc(out, PC); // Send NAK character to PC to indicate PIC is ready to recieve data (1 byte checksum)
while(TRUE) // Loop until file transfer complete (loop will break)
{
c = 0;
while((c != 0x01)&&(c != 0x04)) // Wait for SOH or EOT character
{
c = fgetc(PC); // Keep checking data from PC until SOH or EOT character received
}
if (c == 0x04) break; // If EOT character received, end file transfer
packetnum = fgetc(PC); // Get packet number from PC
packetnum_inverse = fgetc(PC); // Get inverse packet number from PC
if (packetnum_inverse != (255 - packetnum)) error = 1; // If packet number inverse incorrect, set error flag
if(packetnum == packetcount) // If received packet number matches expected, increment expected for next transmission
{
if(packetcount == 256)
{
packetcount = 0;
}
else
{
packetcount++;
}
}
checksum_temp= 0;
for (i = 0; i < 64; i++) // Loop until first half of packet received, store in string & add to checksum
{
data1[i] = (unsigned char)fgetc(PC);
checksum_temp += (unsigned char)data1[i];
}
for (i = 0; i < 64; i++) // Loop until second half of packet received, store in string & add to checksum
{
data2[i] = (unsigned char)fgetc(PC);
checksum_temp += (unsigned char)data2[i];
}
checksum_calc = checksum_temp%256; // Calculate checksum
checksum_rec = fgetc(PC); // Get checksum byte from PC
if(checksum_calc != checksum_rec) error = 1; // If checksum incorrect, set error flag
if(error != 1) // If no error, write to EEPROM
{
eeprom_write_string(address_upper, address_lower, data1);
if(address_lower >= 192) // If next 64 bytes of data will cause an overflow in lower address byte
{
address_upper++; // Add 1 to upper address byte
address_lower += 64; // Add 64 to lower address byte
address_lower = address_lower - 256; // Take 256 from lower address byte
}
else
{
address_lower += 64; // If no overflow will be caused, add 64 to lower address byte
}
eeprom_write_string(address_upper, address_lower, data2);
if(address_lower >= 192) // If next 64 bytes of data will cause an overflow in lower address byte
{
address_upper ++; // Add 1 to upper address byte
address_lower += 64; // Add 64 to lower address byte
address_lower = address_lower - 256; // Take 256 from lower addres byte
}
else
{
address_lower += 64; // If no overflow will be caused, add 64 to lower address byte
}
}
if(error == 1) // If error, send NAK
{
out = 0x15;
}
else // If no error, send AK
{
out = 0x06;
}
error = 0; // Reset error flag
fputc(out,PC); // Send AK or NAK character to PC
}
out = 0x06;
fputc(out,PC); // Send AK character to PC to acknowledge EOT character received
address_upper = 0b00000000;
address_lower = 0b00000001;
fprintf(PC, "%d,", (unsigned int)eeprom_read_contig_start(address_upper, address_lower));
for(i = 0; i<500; i++)
{
fprintf(PC, "%d,", eeprom_read_contig());
}
fprintf(PC, "%d", eeprom_read_contig_stop());
} |
|
|
|
andyd
Joined: 08 Mar 2007 Posts: 30
|
|
Posted: Thu Mar 08, 2007 6:11 am |
|
|
This is getting really annoying now... I've just written a small function to use in place of fgetc which uses the hardware USART & reads from the RCREG directly (I think I've done it right) so that I can make sure that it's returning an unsigned char, but the same thing still happens.
Could it be Hyperterminal causing the problems?
Code: | unsigned char serial_rx()
{
unsigned char c;
while(!kbhit()) // Wait until byte received
continue;
c = RCREG;
return c;
} |
|
|
|
Ttelmah Guest
|
|
Posted: Thu Mar 08, 2007 9:39 am |
|
|
I think you are on a 'wild goose chase', in thinking the problem relates to signed/unsigned. As far as the PIC is concerned, it doesn't matter whether a value is signed or unsigned. It receives a binary value, which you can _treat_ as signed or unsigned, if it suits you. The most likely thing to cause 'skipping', is to do with a timing problem. Remember just how long writing data takes. 5mSec, is potentially 5 character times...
Best Wishes |
|
|
Guest
|
|
Posted: Thu Mar 08, 2007 10:16 am |
|
|
If it's truly ADPCM then your data is likely to contain any value 0-255 in any byte. Your serial receive code is looking for particular values & trating them as part of the protocol:
Code: | if (c == 0x04) break; // If EOT character received, end file transfer
|
So if your ADPCM data contains a byte value 4 you will see this as a termination of the file. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1907
|
|
Posted: Thu Mar 08, 2007 1:46 pm |
|
|
As someone else has pointed out, a data byte of 0x04 will cause your program to abort because it thinks it has received the end of file character. You have to implement a bit more robust checking scheme to look for a combination of characters - e.g. "End$". This method is more robust because as the number of characters in the termination message increases, the likelihood of random data triggering it goes down.
Check this post: http://www.ccsinfo.com/forum/viewtopic.php?t=23953
It pertains to wireless data transfer, but the interrupt-driven serial receive routine and associated validity check is directly applicable to your situation. |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Thu Mar 08, 2007 6:11 pm |
|
|
Xmodem is not a very strong protocol but you are seeing way too much errors. Look again at how you are handling an error situation, it has some bugs. For example, after detecting an error in the packet header you are incrementing packetcount, this is wrong as you want to receive the same packet again. Now the packet is skipped and there you have your gap in the received data.
Another weak point is that after detecting an error in the packet header you are always reading 128 bytes. There is a good chance that because of the detected error there will be a different number of bytes to be received.
I can see you are struggling with variable sizes; 8, 16 or 32-bits are all mixed up so let me give you some tips. Code: | if(packetcount == 256)
{
packetcount = 0;
}
else
{
packetcount++;
} |
1) packetcount is an 8 bit integer, the maximum value it can contain is 255. It will never reach 256! After getting at 255 it will wrap to zero again. The above code can be rewritten to the functional same equivalent: Code: | packetcount++; // Will wrap at reaching 255 |
2) A similar thinking error goes for
Code: | address_lower = address_lower - 256; // Take 256 from lower address byte | An 8 bit value minus 256 will give you the same value again. This line is doing nothing.
3) Instead of two seperate address_lower and address_upper variables you can make your code much shorter and easier to understand by replacing this by a single int16.
Code: | if(address_lower >= 192) // If next 64 bytes of data will cause an overflow in lower address byte
{
address_upper++; // Add 1 to upper address byte
address_lower += 64; // Add 64 to lower address byte
address_lower = address_lower - 256; // Take 256 from lower address byte
}
else
{
address_lower += 64; // If no overflow will be caused, add 64 to lower address byte
} | is then reduced to Code: | int16 Address;
Address += 64; | Study the make8() function in the manual for how to split an int16 into two seperate int8 variables again.
4)
Code: | checksum_calc = checksum_temp%256; // Calculate checksum
| Here you are throwing away the 24 most significant bits of the checksum. So why did you choose to make this variable 32 bits when you are only interested in the 8 least significant bits? You can save some code space by making checksum_temp an int8. |
|
|
Guest
|
|
Posted: Fri Mar 09, 2007 3:08 am |
|
|
Anonymous wrote: | If it's truly ADPCM then your data is likely to contain any value 0-255 in any byte. Your serial receive code is looking for particular values & trating them as part of the protocol:
Code: | if (c == 0x04) break; // If EOT character received, end file transfer
|
So if your ADPCM data contains a byte value 4 you will see this as a termination of the file. |
I had thought that this could potentially be a problem and will look at implementing it better, however surely if this was causing the problem the PIC would receiving data completely as it thinks the transfer is over, and there would be nothing to make it resume later in the transfer? As it seems to be writing chunks of data interspersed with not writing any, that would mean it has somehow resumed file transfer on its own unless I'm missing an obvious point here? Also it only checks for the 0x04 character at the start of every packet (as that's the only time it should legitimately occur)?
Also, thanks for the tips ckielstra, will definitely implement those |
|
|
andyd
Joined: 08 Mar 2007 Posts: 30
|
|
Posted: Fri Mar 09, 2007 3:09 am |
|
|
Sorry, that was me, forgot to login! |
|
|
andyd
Joined: 08 Mar 2007 Posts: 30
|
|
Posted: Fri Mar 09, 2007 11:15 am |
|
|
After looking into this a bit further, it seems it's the NULL character that's causing the PIC to screw up somehow. I compared what was coming out of the EEPROM with a display of the file as ints on a PC, and whenever the PIC stops writing to the EEPROM, there's an 0 in the display of ints on the PC (i.e. NULL). Just to confirm this, I made a program to create a text file containing chars 255-0 in reverse order just so I knew when a NULL should be occuring, and, sure enough, as soon as a NULL occurs, it all goes wrong.
The 128 byte packet it receives from the PC is written into 2 64 byte strings purely for memory reasons (the compiler claims to have run out of memory when I declare a single 128 byte one).
It would appear that the PIC gets a null, freaks out, then doesn't write anything to the string for the remainder of the 64 bytes, then gets to the next string, writes fine until it gets another NULL, doesn't write anything for the rest of that string, then because both strings are half empty, when it comes to write them to the EEPROM, only the data in the EEPROM that "lines up" with the data in the strings gets overwritten, the rest stays as it is, explaining why old data gets displayed.
The question now is, why does the PIC freak out when it gets a NULL, and how do I get round it? |
|
|
Sherpa Doug Guest
|
|
Posted: Fri Mar 09, 2007 11:48 am |
|
|
In the standard C language the null character terminates a string. |
|
|
Ttelmah Guest
|
|
Posted: Sat Mar 10, 2007 3:17 am |
|
|
The problem is caused by his own 'eeprom_write_string' function. The 'while' loop in this, will only loop so long as the character is non zero. This is 'correct' behaviour to deal with a 'string' (sinceas you correctly say, zero is the string terminator), but what he has, is not actually a 'string', but a set of binary data. Realistically, the length of the received data 'block', needs to be passed to this function, and it needs to loop for the entire block, and not just till the zero character. Also though, when retrieving the block from the EEPROM, it will be necessary to again have access to the length of the data (since the same problem will appear on retrieval).
Best Wishes |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|