|
|
View previous topic :: View next topic |
Author |
Message |
NickStout
Joined: 09 Nov 2005 Posts: 12
|
Structuring a database in memory |
Posted: Wed Nov 09, 2005 11:52 pm |
|
|
Hello,
I'm still VERY new to PIC programming, and I want to tackle what seems to be a very challenging topic: storing of a table of information in memory.
Right now, I have a list of five or six 8 byte long "tags" that I want to associate with a name and a long index of numbers. In text, it'd look like the following:
Tag S/N........Name...........values
E01234AF.....John Doe.......10 11 12 13 14 15 16 17 18 19 20
E01234BF.....Michael Doe...12 13 14 15 16 17 18 19 20 21 22
... etc
The problem is that there is no way that I know to read a text file from a PIC, let alone deal with it. I thought maybe I could use the EEPROM to store information, but I have absolutely no idea how I'd retreive it in one piece.
The code I'm writing has to be able to call this "look-up table" and match it with some data coming in serially, which I know how to do if I could figure out how to store this data, and more importantly, get it back in groups as shown.
I know this is a pretty big doozy for a first post, but I was just curious... Has anyone done anything of this sort in the past? If so, I could definitely use some guidance.
Thanks in advance. |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1634 Location: Perth, Australia
|
|
Posted: Thu Nov 10, 2005 3:49 am |
|
|
If the table is relatively static, then you can sore it it progam memory on a flash based PIC such as the 18Fxxx.
You can reserve memory for your database using an option of the origin statement - this will enable you to both define the address in memory of your database as well as reserve program memory for it. You can prepopulate your database, if required using #ROM.
YOu can read and write program memory byte at a time using either standard CCS functions or using assembler table read and table write instructions.
Use your tag (which may be in program memory, ram or EEPROM) to determine the offset position into your database. Locate the record of interest (assuming fixed sized records) by
record_ptr = db_base_address + (record_len * tag_offset) _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
Ttelmah Guest
|
|
Posted: Thu Nov 10, 2005 4:05 am |
|
|
A number of important questions:
How large is 'large'?... If the list is a few hundred bytes, the solutions will be different from one that is many KB. Basically, if the total list is small (under 1KB), then on some PICs this may be able to use the EEPROM. If larger, but still only a few KB, it may be possible to put the list in the main program memory. If larger still, then you will need external memory, and possibly something like MMC storage.
Second question. How are the contents to be generated/changed?. If 'never', but just programmed initially, the 'best' solution will differ markedly from a table that needs to be frequently updated (both because of 'speed' issues, and 'life' issues on different memory types).
Third, how is the data scanned?. If the values are indexed by the 'tag', it may be best to store the tag itself seperately, in perhaps the main program memory, and then use a value stored with this to 'find' the main entry.
Best Wishes |
|
|
NickStout
Joined: 09 Nov 2005 Posts: 12
|
|
Posted: Thu Nov 10, 2005 8:45 am |
|
|
Quote: | How large is 'large'?... |
There should only be 5 or so "sets" of data. The tags only come in 8-byte segments, which I've determined I need to compare byte-by-byte to an incoming serial buffer. The names could be as long as 40 characters (but will probably be limited to 20. The extra data are calculated integer coefficients from a DSP. There could be as many as 1024 (the window of the DSP), but it's more likely it will be about 10-15.
So this list will be... maybe 1K for this experiment, but could be expanded to much larger, theoretically (up to 10K). This is a design project, which is why the limits are not hard and fast. If this was going out of the prototype stage, I'd worry more about the numbers, of course.
Quote: | How are the contents to be generated/changed? |
The data is written to only in a "programming mode", but for now, I can safely say that it's a static set. In the programming mode, however, the list would simply be checked and appended to if another entry in the table was desired. It'd be great to be able to edit the list from another source (i.e. a dynamically generated text file that could be read into the PIC from a memory or edited from a PC. This would be the best solution to my programming woes).
Quote: | If the values are indexed by the 'tag', it may be best to store the tag itself seperately, in perhaps the main program memory, and then use a value stored with this to 'find' the main entry. |
That's a fantastic idea. The tag is checked way before anything else, and once the tag is read (over RS232) for the first time, the program should be able to do a quick name and int list data gathering... those can be nicely indexed by the tag.
The downside to this is that the programmability of the system is shot to hell at that point, since programming the PIC's program memory while it's up and running wouldn't be simple - can't even use a bootloader! So ultimately, while this is a great idea, in practice probably isn't the solution I'm looking for.
The best solution, in my opinion, would be something that could be read by the PIC and a common PC (i.e. MMC card with a txt document), but I don't think anyone has successfully done this, since the architecture of these two devices is different.
But as both of you seem to have pointed out, it would be easier to index off the tag and store the rest of the table based off the tags in program memory or EEPROM. From a purely "static data" point of view, which approach seems to make more sense to you? What about the dynamic "programmable" approach?
Thank you for all of your help so far. I am continuing to learn how far the limits of the PIC go every time I work with it, so this project has been a very fun learning experience. |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1634 Location: Perth, Australia
|
|
Posted: Thu Nov 10, 2005 6:33 pm |
|
|
Quote: |
There should only be 5 or so "sets" of data. The tags only come in 8-byte segments, which I've determined I need to compare byte-by-byte to an incoming serial buffer. The names could be as long as 40 characters (but will probably be limited to 20. The extra data are calculated integer coefficients from a DSP. There could be as many as 1024 (the window of the DSP), but it's more likely it will be about 10-15.
So this list will be... maybe 1K for this experiment, but could be expanded to much larger, theoretically (up to 10K). This is a design project, which is why the limits are not hard and fast. If this was going out of the prototype stage, I'd worry more about the numbers, of course.
...
The downside to this is that the programmability of the system is shot to hell at that point, since programming the PIC's program memory while it's up and running wouldn't be simple - can't even use a bootloader! So ultimately, while this is a great idea, in practice probably isn't the solution I'm looking for |
This could easily be achieved by storing the data in the PIC main program memory space. The program memory can be read and written as easily as the EEPROM and there is more than enough memory space on PICs to accommodate your data set as described. I wrote a "Christmas lights" program for a friend using a PIC18F1320. This is for those keen people that decorate the outside of their house with a large number of lights and then control how they are sequenced. The initial pattern is developed on a PC (using an EXCEL spreadsheet) and the patterns are then sent to the PIC using a variation of the intel hex file format and stored into the table memory space just like using a bootloader. _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
NickStout
Joined: 09 Nov 2005 Posts: 12
|
|
Posted: Mon Nov 14, 2005 12:35 am |
|
|
Quote: | The program memory can be read and written as easily as the EEPROM and there is more than enough memory space on PICs to accommodate your data set as described. |
I'm not quite sure I understand... so let me summarize an idea I had here and see if this is where you're going:
I have to be able to program the data table - add/delete/change. To do this, I was considering a mode where the PIC will interface with a PC serially. The PIC will send out something similar to the following to the PC via terminal:
1) User: John Doe, Tag: 12345678, Values: 12 32 85 734
2) User Jane Doe, Tag 12345679, Values: 12 13 15 219
3) User Joe Doe, Tag 12345681, Values: 12 13 17 222
A) Add a new user
Please select a user, or 'A' to add a new user:
The PIC would go into processing mode, and the PC would "program" the PIC's database.
Can it physically program the program memory and be saved when the PIC loses power? Without changing the C/assembly dynamically, I'm not sure this is possible... without storing it to memory at some point.
I would really love some implementation suggestions on this part. By what everyone's told me, it sounds like external memory is not the best idea, especially since it has to access it so much... but it might be possible to save the table to memory (EEPROM or External) and LOAD it into program memory on boot.
Thanks for all your help so far! |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1634 Location: Perth, Australia
|
|
Posted: Mon Nov 14, 2005 12:49 am |
|
|
NickStout wrote: |
The PIC would go into processing mode, and the PC would "program" the PIC's database.
Can it physically program the program memory and be saved when the PIC loses power? Without changing the C/assembly dynamically, I'm not sure this is possible... without storing it to memory at some point.
|
Yes. This is definitely possible. As mentioned previously, the basics for this can be found in the bootloader.
Quote: |
I would really love some implementation suggestions on this part. By what everyone's told me, it sounds like external memory is not the best idea, especially since it has to access it so much but it might be possible to save the table to memory (EEPROM or External) and LOAD it into program memory on boot. |
External memory is also fine. As you have previously stated you data is relatively constant - you will not wear out the flash part by writing to it too frequently. BTW the problem is write related not read related.
Forget about your idea of "LOAD" to program memory. The Program Memory space is flash memory - once written there its good for a decade or so... _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
NickStout
Joined: 09 Nov 2005 Posts: 12
|
|
Posted: Mon Nov 14, 2005 4:21 pm |
|
|
Quote: | As mentioned previously, the basics for this can be found in the bootloader |
Now we're getting into more specific code.
The bootloader examples gather the "program" from the RS232 in hex (according to the code, using some program I don't have - SIOW.EXE). I need more to be able to give the PIC parameters and it to save the database in... EEPROM?
I'm not sure how to proceed. I'm just editing arrays and then writing the array to the program memory, but I think the bootloader uses a whole program in 'compiled' .hex. I'm having trouble seeing how this would work, especially since I've never written a bootloader. Suggestions? |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1634 Location: Perth, Australia
|
|
Posted: Mon Nov 14, 2005 6:41 pm |
|
|
Quote: | The bootloader examples gather the "program" from the RS232 in hex (according to the code, using some program I don't have - SIOW.EXE). |
SIOW.EXE is a terminal emulation program. Hyperterm works just as well for this purpose.
Quote: | I need more to be able to give the PIC parameters and it to save the database in... EEPROM? |
This is not what I have been saying. The PIC (any 18F series) can store the database in its spare PROGRAM memory space. If you only had a small database then you could store it in EEPROM. The choice is yours.
Quote: | I'm just editing arrays and then writing the array to the program memory, but I think the bootloader uses a whole program in 'compiled' .hex. I'm having trouble seeing how this would work, especially since I've never written a bootloader. Suggestions? |
The bootloader is reading in a record (structured line) usually termiated with a <CR> or <CR><LF>, examining the addressing information at the start of the record and storing the record in PROGRAM or EEPROM as determined by the addressing information associated with the record. Exactly how the addressing structure works is not of interest to you as you will have your own. What you are interested in in the bootloader example is how it reads and writes to program memory.
Do a search on the web for "intex hex format" record structure and you will find lots of examples detailing the record structure. Here is one such pointer http://www.rabbitsemiconductor.com/documentation/docs/refs/TN220/TN220.htm#1036448
Your program will need to implement its own parser to read in your records and from this determine where it should fit in the database. Fixed length records are the way to go for a simple implementation.
Here is the code listing for the "Christmas Flashing Light" program which displays patterns stored in PROGRAM memory. Patterns are loaded via the serial port.
Code: | //////////////////////////////////////////////////////////////////////////
//
// Bob's Flashing LEDs V2
// PIC 18F1320 Processor at 20MHz
//
// Copyright (c) 2005, Andrew Smallridge
//
///////////////////////////////////////////////////////////////////////////
#include "Bob LEDs V2.h"
#include <string.h>
//#include <stdlib.h>
#build(reset=0x00:0x07)
#define SR_Width 32 // width of the shift register
// ; define ASCII constants
#define Xon 0x11
#define Xoff 0x13
#define Table 0x0C80
#define TableWidth 5
#define TableSize (0x1800-Table)/TableWidth-1
#rom Table = {0x8C01,0x01EF,0x0023,0x0000,0x0000}
#define RxBufLen 32
byte RxBase[RxBufLen];
byte RxHead;
byte RxTail;
#define CmdBufLen 16
byte CmdBase[CmdBufLen];
byte CmdHead;
short do_shift; // indicate bit shifting is active
byte sequence[TableWidth]; #locate sequence = 0x40 // current display sequence
byte bit_count; // bits remaining to be displayed
boolean done_data_bit;
boolean comment;
#define C_ticks_20ms 200
byte ticks_20ms; // timer 0 ticks for 2mS
#define C_Toggle_time 25
byte toggle_time; // number of 20ms ticks before next LED toggle
int16 TableOffset;
void load_pattern();
#int_RDA
//void rx_handler()
void serial_isr()
{
char current;
current = getc();
if (current == 0x0d)
comment = false;
if (!comment) // ignore comment
{
switch (current)
{
case 0x0a : // ignore line feed
case ' ' : // ignore space
return;
break;
case ';' :
comment = true;
return;
break;
case 0x0d :
RxBase[RxHead++]=0;
RxHead %= RxBufLen;
return;
break;
default :
RxBase[RxHead++]=current;
RxHead %= RxBufLen;
return;
break;
}
}
}
#int_timer0
void TMR0_isr()
///////////////////////////////////////////////////////////////////////////
//
// TMR0_isr
//
// Timer 0 interrupt handler
//
// Controls the shift clock and strobe pulse (active on rising edge)
//
//
///////////////////////////////////////////////////////////////////////////
{
// do_shift is true the we are in the middle of shifting out a data stream
if (do_shift)
{
if (bit_test(SR_Clock))
{
// if the clock pulse was high then clear the clock pulse
bit_clear(SR_Clock);
done_data_bit = false;
// if all bits have been shifted then set the strobe
if (!--bit_count)
{
bit_set(SR_Strobe);
do_shift = false;
}
}
else
{
// clock pulse was low while still shifting data
// time to serialize out the next data bit?
if (done_data_bit)
{
done_data_bit = false;
bit_set(SR_Clock);
}
else
{
// here to shift out the next data bit
if (Sequence[1] & 0x80)
bit_set(SR_Data);
else
bit_clear(SR_Data);
#ASM
rlcf 0x044, F
rlcf 0x043, F
rlcf 0x042, F
rlcf 0x041, F
#ENDASM
done_data_bit = true;
}
}
}
else
bit_clear(SR_Strobe); // continuously clears the strobe bit...
if (!--ticks_20ms)
{
// reinitialise the timer
ticks_20ms = C_ticks_20ms;
output_toggle(PIN_A1);
if(!--toggle_time)
{
output_toggle(PIN_A4);
toggle_time = C_Toggle_time;
}
if (!--sequence[0])
{
bit_count = SR_Width;
load_pattern();
do_shift = true;
}
}
}
void load_pattern()
///////////////////////////////////////////////////////////////////////////
//
// load_pattern
//
// Loads the pattern register from the next table entry
//
//
///////////////////////////////////////////////////////////////////////////
{
int16 offset;
int x;
// offset = TableWidth*TableOffset;
// for loop used instead of multiple to prevent interrupts being disabled
// when using multiple in the mainline
for (x=0,offset=0; x< TableWidth; x++)
offset += TableOffset;
read_program_memory(Table + offset, sequence, TableWidth);
if ((sequence[0] == 0xff) || (sequence[0] == 0))
{
TableOffset = 0;
offset = 0;
read_program_memory(Table + offset, sequence, TableWidth);;
}
else
{
if (++TableOffset >= TableSize)
TableOffset = 0;
}
}
void dump_patterns()
///////////////////////////////////////////////////////////////////////////
//
// dump_patterns
//
// Dumps the pattern table to the serial port
//
//
///////////////////////////////////////////////////////////////////////////
{
int x;
char tens, units;
int16 offset;
int16 linenumber;
boolean done;
byte line[TableWidth];
for (linenumber=0, done=false, offset=0; (linenumber < TableSize) && !done; linenumber++, offset += TableWidth)
{
read_program_memory(Table + offset, line, TableWidth);
if ((line[0] != 0xff) && (line[0] != 0))
{
printf("!%04ld ",linenumber);
units = (line[0] % 10) + '0';
tens = (line[0] / 10) + '0';
printf("%c%c ",tens,units);
for (x=1; x < TableWidth; x++)
printf("%x",line[x]);
printf("\r\n");
}
else
done = true;
}
}
unsigned int atoi_8(char *source)
///////////////////////////////////////////////////////////////////////////
//
// atoi_8
//
// Convert two ASCII hex characters to int8
//
//
///////////////////////////////////////////////////////////////////////////
{
unsigned int result = 0;
char ch;
int x;
for (x=0;(x<2) && *source; x++,source++)
{
ch = toupper(*source);
if (ch >= 'A')
result = 16*result + (ch) - 'A' + 10;
else
result = 16*result + (ch) - '0';
}
return(result);
}
unsigned int16 atoi_16(char *source)
///////////////////////////////////////////////////////////////////////////
//
// atoi_16
//
// Convert four ASCII hex characters to int16
//
//
///////////////////////////////////////////////////////////////////////////
{
unsigned int16 result = 0;
char ch;
int x;
for (x=0;(x<4) && *source; x++,source++)
{
ch = toupper(*source);
if (ch >= 'A')
result = 16*result + (ch) - 'A' + 10;
else
result = 16*result + (ch) - '0';
}
return(result);
}
unsigned int16 ASCII_16(char *source)
///////////////////////////////////////////////////////////////////////////
//
// ASCII_16
//
// Convert four numeric ASCII characters to int16
//
//
///////////////////////////////////////////////////////////////////////////
{
unsigned int16 result;
int x;
x = 0;
result = 0;
while (isdigit(source[x]) && (x<4))
result = 10*result + source[x++] -'0';
return(result);
}
unsigned int8 ASCII_8(char *source)
///////////////////////////////////////////////////////////////////////////
//
// ASCII_8
//
// Convert two numeric ASCII characters to int8
//
//
///////////////////////////////////////////////////////////////////////////
{
unsigned int8 result;
int x;
x = 0;
result = 0;
while (isdigit(source[x]) && (x<2))
result = 10*result + source[x++] -'0';
return(result);
}
void show_shadow(char *shadow)
{
int x,y;
printf("Shadow Block\r\n");
for (y = 0; y < 8; y++)
{
for (x = 0; x < 8; x++)
printf("%x ", shadow[y * 8 + x]);
printf("\r\n");
}
printf("\r\n");
}
void store_pattern(char *source)
///////////////////////////////////////////////////////////////////////////
//
// store_pattern
//
// Stores the pattern in Program Memory
//
//
///////////////////////////////////////////////////////////////////////////
{
#define EraseSize 64
byte shadow[EraseSize];
int32 addr, page;
int16 line;
int8 offset, x;
char rcb[TableWidth]; // record construction buffer
// extract the line number
line = ASCII_16(&source[1]);
if (line >= Tablesize)
Line = Tablesize-1;
// construct the record
rcb[0] = ASCII_8(&source[5]); // get the time
for (x=0; x<4; x++)
rcb[1+x] = atoi_8(&source[2*x+7]);
// fetch contents of the target block in program memory
addr = table + line * TableWidth;
page = addr & ~((long)EraseSize-1);
offset = (int8)(addr - page);
read_program_memory(page, shadow, EraseSize);
// update the contents of the target block
x = 0;
while ((offset < EraseSize) && (x < TableWidth))
shadow[offset++] = rcb[x++];
write_program_memory(page, shadow, EraseSize);
// if a page wrap occurred then we need to complete writing the data
// to the next page
if (x<TableWidth)
{
page += EraseSize; // point to the next page
offset=0; // updating from the start of the new page
// fetch the next page block from program memory
read_program_memory(page, shadow, EraseSize);
while (x < TableWidth)
shadow[offset++] = rcb[x++];
write_program_memory(page, shadow, EraseSize);
}
}
void SrvCmd(char *command)
///////////////////////////////////////////////////////////////////////////
//
// SrvCmd
//
// Service Commands from the serial port
//
///////////////////////////////////////////////////////////////////////////
{
char const RESET[] = "RESET";
char const DUMP[] = "DUMP";
int x;
switch (command[0])
{
case 'r' :
case 'R' :
// process the RESET command
for (x=0; (x < 5) && (RESET[x] == toupper(command[x])) && command[x]; x++)
;
if (x == 5)
{
printf("RESET request\r\n");
delay_ms(10);
reset_cpu();
reset_cpu();
}
return;
break;
case 'd' :
case 'D':
// process the DUMP command
for (x=0; (x < 4) && (DUMP[x] == toupper(command[x])) && command[x]; x++)
;
if (x == 4)
{
printf("DUMP request\r\n");
dump_patterns();
}
return;
break;
case '!' :
// suspend the sender
disable_interrupts(int_timer0);
putc(Xoff);
printf("%c",Xoff);
delay_ms(1);
store_pattern(command);
enable_interrupts(int_timer0);
putc(Xon);
return;
break;
case 0x0d :
case 0x0a :
default:
return;
break;
}
}
void ServiceRxQ()
///////////////////////////////////////////////////////////////////////////
//
// ServiceRxQ
//
// Basic polled Rx Q service subroutine (NON BLOCKING)
// Receives a byte from the serial Rx buffer and queues it to the
// command buffer
//
// On Exit:
// RxTailC = RxHeadC or CmdHead >= CmdBufLen
//
///////////////////////////////////////////////////////////////////////////
{
byte current;
while ((RxTail != RxHead) & (CmdHead < CmdBufLen))
{
current = RxBase[RxTail++];
RxTail %= RxBufLen;
switch (current)
{
case 0:
case 0x0d: // <CR>
CmdBase[CmdHead] = 0; // terminate the string with a NULL
SrvCmd(CmdBase); // go service the console command
CmdHead = 0; //
break;
case 0x0a: // ignore <LF>
break;
default :
CmdBase[CmdHead++] = current;
if (CmdHead >= CmdBufLen)
CmdHead--;
break;
}
}
}
void main ()
{
int i;
Boolean Last_Level;
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
// initialise PORTs A and B
output_a(PA_DefData);
output_b(PB_DefData);
set_tris_a(PA_DefTRIS);
set_tris_b(PB_DefTRIS);
// initiailse the serial receive buffer and command buffer
RxHead = RxTail = 0;
CmdHead = 0;
load_Pattern();
bit_count = SR_Width;
comment = false;
TableOffset = 0;
// force a pattern load
do_shift = false;
sequence[0] = 1; // force a pattern load
ticks_20ms = 1;
// setup timer 0 for bit shift clock
setup_timer_0(RTCC_DIV_2 | RTCC_8_BIT);
set_timer0(0);
clear_interrupt(int_timer0);
clear_interrupt(int_rda);
printf("** Bob's LED System **\r\n");
// initialise the status LED
bit_clear(test_status);
toggle_time = C_Toggle_time;
// initialise the console receive USART status
i = RCREG;
i = RCREG;
if (bit_test(RCSTA,OERR))
{
// here on overrun error - need to reset the USART
bit_clear(RCSTA,CREN); // clear continous receive bit
bit_set(RCSTA,CREN); // set continous receive bit
}
// Enable the serial interrupt
enable_interrupts(global);
enable_interrupts(int_rda);
enable_interrupts(int_timer0);
restart_wdt();
setup_wdt(WDT_ON);
Last_Level = bit_test(LED_status);
putch(Xon);
while (1)
{
ServiceRxQ();
restart_wdt();
}
}
|
_________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!!
Last edited by asmallri on Wed Nov 23, 2005 12:53 am; edited 2 times in total |
|
|
NickStout
Joined: 09 Nov 2005 Posts: 12
|
|
Posted: Mon Nov 14, 2005 10:57 pm |
|
|
Wow! That's some fairly dense code.
Thank you very much for all of your patient help. I'm implementing this on a PIC16F877, so I probably can use the hardware USART and hack down some of that code. I'll have to look over all of this and see if I can make sense of it. The only problem is that I'm not sure how much program memory I'll have when I add this bootloader. We'll have to see.
Thanks again for all the help. I'll let you know how it goes! |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1634 Location: Perth, Australia
|
|
Posted: Mon Nov 14, 2005 11:28 pm |
|
|
Quote: | The only problem is that I'm not sure how much program memory I'll have when I add this bootloader. We'll have to see. |
You can alway move to a PIC18F processor. The PIC18F4520 has the same electrical pinout but have far more resources such as Program memory, ram etc. _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
|
|
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
|