Reading and writing can be easily implemented by bitbanging. To meet DRAM timing requirements we have to introduce delays by introducing various NOP cycles.
// Compile-time delay macros for exact cycle counts without loop overhead
#define DELAY_1_CYCLES() __asm volatile ("nop")
#define DELAY_2_CYCLES() __asm volatile ("nop\nnop")
#define DELAY_3_CYCLES() __asm volatile ("nop\nnop\nnop")
#define DELAY_4_CYCLES() __asm volatile ("nop\nnop\nnop\nnop")
#define DELAY_5_CYCLES() __asm volatile ("nop\nnop\nnop\nnop\nnop")
// Specific delay macros for each timing parameter
#define DELAY_RAS_CYCLES() DELAY_5_CYCLES() // ~100ns
#define DELAY_CAS_CYCLES() DELAY_5_CYCLES() // ~100ns
#define DELAY_RCD_CYCLES() DELAY_2_CYCLES() // ~20ns
#define DELAY_RP_CYCLES() DELAY_5_CYCLES() // ~100ns
The code below cycles first RAS and then CAS to read a bit from the array at the given address (designated as rows and columns).
// Read a bit from DRAM
uint8_t dram_read_bit(uint8_t row, uint8_t col) {
uint8_t data;
// Ensure read mode
GPIOD->BSHR = DRAM_WR_PIN; // W/R high (read mode)
// Set row address
DRAM_ADDR_PORT->OUTDR = row;
DELAY_2_CYCLES(); // Delay for address setup time
GPIOD->BCR = DRAM_RAS_PIN; // RAS low (active)
DELAY_RCD_CYCLES(); // RAS to CAS delay
// Set column address
DRAM_ADDR_PORT->OUTDR = col;
GPIOD->BCR = DRAM_CAS_PIN; // CAS low (active)
DELAY_CAS_CYCLES(); // CAS pulse width
// Read data bit
data = (GPIOD->INDR & DRAM_DOUT_PIN) ? 1 : 0;
// End cycle
GPIOD->BSHR = DRAM_CAS_PIN; // CAS high (inactive)
GPIOD->BSHR = DRAM_RAS_PIN; // RAS high (inactive)
DELAY_RP_CYCLES(); // RAS precharge time
return data;
}
In case more than one bit needs to be read from one ROW, it is possible to use mutliple CAS cycles without opening another row.
// Read a int32 value from DRAM using fast page mode
uint32_t dram_read_fpm(uint8_t row, uint8_t col, uint8_t bits) {
uint32_t data=0;
uint32_t bitcount=0;
if (bits>32) {
bits=32;
}
// Ensure read mode
GPIOD->BSHR = DRAM_WR_PIN; // W/R high (read mode)
// Set row address
DRAM_ADDR_PORT->OUTDR = row;
DELAY_2_CYCLES(); // Delay for address setup time
GPIOD->BCR = DRAM_RAS_PIN; // RAS low (active)
DELAY_RCD_CYCLES(); // RAS to CAS delay
for (bitcount=0; bitcount<bits; bitcount++) {
// Set column address
DRAM_ADDR_PORT->OUTDR = col + bitcount;
GPIOD->BCR = DRAM_CAS_PIN; // CAS low (active)
DELAY_CAS_CYCLES(); // CAS pulse width
// Read data bit
if (GPIOD->INDR & DRAM_DOUT_PIN) {
data |= (1<<bitcount);
}
// End cycle
GPIOD->BSHR = DRAM_CAS_PIN; // CAS high (inactive)
DELAY_CAS_CYCLES(); // CAS pulse width
}
GPIOD->BSHR = DRAM_RAS_PIN; // RAS high (inactive)
DELAY_RP_CYCLES(); // RAS precharge time
return data;
}
In the same way, it is possible to implement writes (pull R/W low). It is also possible to implement Read-Modify-Write cycles on the same row. There are quite some optimizations to improve memory throughput even on these very old DRAMs. Curiously, the old home computers do not use any of these optimizations (FPM, RMW), since their CPUs and remaining chipsets where so slow that they limited system speed.
Refresh
An important additional aspect is to refresh the DRAM array. The charge on the capacitors is slowly leaking away and the information will be lost after a while. To counteract this, it is necessary to read out every row within 4 ms cycle and write it back.
For this, it is only necessary to cycle RAS.
// Refresh a single row
void dram_refresh_row(uint8_t row) {
// RAS-only refresh cycle
DRAM_ADDR_PORT->OUTDR = row; // Set row address
GPIOD->BCR = DRAM_RAS_PIN; // RAS low (active)
DELAY_RAS_CYCLES(); // RAS pulse width
GPIOD->BSHR = DRAM_RAS_PIN; // RAS high (inactive)
DELAY_RP_CYCLES(); // RAS precharge time
}
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.