簡介
RAM是計算機系統中保存臨時結果的器件,它的大小也決定了計算機處理數據的規模。在嵌入式計算機(單片機中),由於受到價格、功耗等原因,內部的RAM的容量往往比較小,比如從幾百字節到十幾k字節不等。這能夠滿足大部分單片機應用的需求了。
但是在一些特殊的應用情況下,有時需要大容量的RAM來存儲採集到的數據,或者緩存通信數據。此時則需要通過外擴內存來完成。比如最近實驗聲音信標信號相關係統測試和算法優化,則需要採集到多路音頻信號。此時需要通過外擴RAM來解決。
通常靜態RAM芯片接口包括有數據、地址和控制總線,與單片機對應的端口相連便可以加成數據的寫入和讀出。
▲ MCU外部RAM
有的單片機在擴展外部RAM的時候,爲了節省有限的IO端口,通過使用外部鎖存器來複用同一八位地址端口來擴展地址總線到16位的目的。比如8051單片機。這樣設計的代價就是需要增加外部鎖存器芯片74LS372。
▲ 8051外部數據總結接口
雖然數據總線和地址總線按照邏輯都標有數據位的順序:比如16位地址總線按照A0~A15,8位數據總線表明D0-D7。但是在方位靜態RAM的時候,數據總線,地址總線內部的這些位的順序是可以任意調整的。比如在下面設計的單片機系統中,爲了滿足能夠儘量減少PCB佈線的交叉,就是通過調整數據線和地址總線內部的位的順序來滿足的。
擴展STC8H8K外部32k字節RAM
D:\zhuoqing\AltiumDesigner\STC\Test\2020\RAM8H3K64S4.PcbDoc *
1. 設計說明
STC8H8K單片機具有16路12位的AD轉換器,在本實驗中用於採集信標發出的Chirp聲音,並傳送給計算機加以處理。
在博文“基於STC8G8K64U三通道高速ADC採集板"給出了利用STC8H8K內部的8kRAM進行聲音信號採集電路設計方案。但是受限於內有8KRAM的空間限制,所能夠採集音頻信號的路數和時間長度都無法滿足研究的目的。所以此次通過外部擴一片32k字節的SRAM來擴展信號採集的容量。
同樣通過WiFi-UART轉換模塊,實現採集數據與PC機之間的數據傳送,這一點設計與前面博文中的設計方案是一致的。
2. 原理圖
所使用的STC8H8K芯片的封裝爲TSOP48。使用P2端口作爲數據總結,使用P0,P4端口作爲地址總線。不需要外部的鎖存器。
使用UART3與WiFi-UART模塊通信。將ADC中的AD0~AD2引到輸入端口接收外部被採集的信號。
具體的電路圖如下圖所示:
▲ 實驗板的原理圖
外部接口設計:
(1) ISP端口:
序號 | 符號 | 功能 |
---|---|---|
1 | VCC | +5V工作電源 |
2 | GND | 工作電源地 |
3 | TXD | MCU串口輸出 |
4 | RXD | MCU串口輸入 |
(2) ADC端口:
序號 | 符號 | 功能 |
---|---|---|
1 | ADC0 | ADC通道0 |
2 | ADC1 | ADC通道1 |
3 | ADC2 | ADC通道2,可以作爲普通IO |
2. PCB板
這裏需要需要說明的是,爲了能夠適應快速製版的實驗需要。使用了單面PCB板工藝。爲了消除在佈線中的交叉部分(因爲交叉部分則需要過孔和雙面佈線),對數據總線和地址總線的順序進行了調整,由此可以僅僅使用一面PCB便將所有的數據和地址總線完成連接。
下面給出了具體的PCB連接方式。前面原理圖中顯示了調整後的地址線和數據線的邏輯設計。
▲ 使用單面覆銅板製作實驗電路板
在實際電路設計工程中,有的時候爲了方便佈線,需要對引線的順序進行調整。在複雜電路設計中,優勢會通過FPGA、CPLD等大規模可編程邏輯器件來對芯片外部引線的順序進行調整,方便進行佈線。當然這也需要付出設計的複雜度和額外增加的芯片價格。
硬件調試
軟件開發工程所在的目錄:
D:\zhuoqing\window\C51\STC\Test\2020\RAM8H8K64U\RAM8H8K64U.uvproj
通過設計,電路板腐蝕、焊接與安裝,很快得到實驗電路。下面對其進行測試。
▲ 通電之後的實驗電路板
STC8H單片機硬件變成選項配置如下:
▲ 單片機編程硬件選項參數
單片機工作的主要指標:
- 內部工作主頻:40MHz
- ISP UART1 波特率:500000bps;
- WiFI-UART波特率:460800bps
1. 擴展SRAM的實驗
(1)訪問外部SRAM的子程序:
根據設計,對於外部RAM訪問軟件結構通過以 EXTSRAM模塊來完成:
程序頭文件:
/*
**==============================================================================
** EXTSRAM.H: -- by Dr. ZhuoQing, 2020-04-28
**
** Description:
**
**==============================================================================
*/
#ifndef __EXTSRAM__
#define __EXTSRAM__
//------------------------------------------------------------------------------
#ifdef EXTSRAM_GLOBALS
#define EXTSRAM_EXT
#else
#define EXTSRAM_EXT extern
#endif // EXTSRAM_GLOBALS
//------------------------------------------------------------------------------
//==============================================================================
#define EXTSRAM_EN 1
//------------------------------------------------------------------------------
void ExtSramInit(void);
//------------------------------------------------------------------------------
#define EXTSRAM_RD 5, 3
#define EXTSRAM_WR 3, 7
void ExtSramPortInit(void);
void ExtSramDataInput(void);
void ExtSramDataOutput(void);
//------------------------------------------------------------------------------
#define EXTSRAM_RD_CLOCK OFF(EXTSRAM_RD), ON(EXTSRAM_RD)
#define EXTSRAM_WR_CLOCK OFF(EXTSRAM_WR), ON(EXTSRAM_WR)
void ExtSramAddress(unsigned int nAddress);
void ExtSramWrite(unsigned int nAddress, unsigned char ucChar);
unsigned char ExtSramRead(unsigned int nAddress);
//------------------------------------------------------------------------------
unsigned int ExtSramCheck(unsigned nLength);
//==============================================================================
// END OF THE FILE : EXTSRAM.H
//------------------------------------------------------------------------------
#endif // __EXTSRAM__
EXTSRAM C文件:
/*
**==============================================================================
** EXTSRAM.C: -- by Dr. ZhuoQing, 2020-04-28
**
**==============================================================================
*/
//------------------------------------------------------------------------------
#define EXTSRAM_GLOBALS 1 // Define the global variables
#include "EXTSRAM.H"
#include "C51BASIC.H"
#include "STC8H.H"
#include "INTRINS.H"
#include "STDLIB.H"
//------------------------------------------------------------------------------
void ExtSramInit(void) {
ExtSramPortInit();
}
//------------------------------------------------------------------------------
void ExtSramDataInput(void) {
P2M0 = 0;
P2M1 = 1; // Hiz
}
void ExtSRamDataOutput(void) {
P2M0 = 1;
P2M1 = 0; // PP Output
}
void ExtSramPortInit(void) {
ExtSramDataInput();
PM_PP(EXTSRAM_RD);
PM_PP(EXTSRAM_WR);
P2 = 0xff;
ON(EXTSRAM_RD);
ON(EXTSRAM_WR);
P0 = 0x0;
P4 = 0x0;
P0M0 = 1;
P0M1 = 0;
P4M0 = 1;
P4M1 = 0;
}
//------------------------------------------------------------------------------
void ExtSramAddress(unsigned int nAddress) {
P0 = nAddress;
P4 = (nAddress >> 8);
}
//------------------------------------------------------------------------------
void ExtSramWrite(unsigned int nAddress, unsigned char ucChar) {
P0 = nAddress;
P4 = (nAddress >> 8);
P2M0 = 1;
P2M1 = 0; // PP Output
P2 = ucChar;
OFF(EXTSRAM_WR);
_nop_();
ON(EXTSRAM_WR);
P2M0 = 0;
P2M1 = 1; // PP INPUT
}
unsigned char ExtSramRead(unsigned int nAddress) {
unsigned char ucChar;
P0 = nAddress;
P4 = (nAddress >> 8);
P2 = 0xff;
OFF(EXTSRAM_RD);
_nop_();
_nop_();
_nop_();
_nop_();
ucChar = P2;
ON(EXTSRAM_RD);
return ucChar;
}
//------------------------------------------------------------------------------
unsigned int ExtSramCheck(unsigned int nLength) {
unsigned int i, nError, nAddress;
srand(0);
nAddress = 0;
for(i = 0; i < nLength; i ++) ExtSramWrite(nAddress ++, (unsigned char)rand());
srand(0);
nAddress = 0;
for(i = 0; i < nLength; i ++)
if((unsigned char)rand() != ExtSramRead(nAddress ++))
nError ++;
return nError;
}
//==============================================================================
// END OF THE FILE : EXTSRAM.C
//------------------------------------------------------------------------------
(2)測試外部RAM:
其中函數ExtSramCheck完成了對外部擴展RAM的測試。使用隨機數發生器RAND對RAM填充,然後在對比讀出的結果是否與寫入的數據一致來判斷是否會出現外部引線和焊接方面的錯誤。
(3)訪問外部RAM時間長度
使用MCU的端口來測量訪問外部RAM的時間,下面是側袋寫入外部RAM的時間,爲534ns(包括對FLAG_PIN的操作時間。)
ON(FLAG_PIN);
ExtSramWrite(0x0, 0x0);
OFF(FLAG_PIN);
▲ 寫入外部RAM數據時間長度
使用相同的方法,測試 ExtRamRead()的時間爲490ns。
2. AD轉換實驗
(1)AD的通道設置:
使用了P1.0, P1.1 來採集兩路外部模擬信號。
▲ ADC通道設置
(2)AD轉換時間:
- AD參數設置: STC8H內部轉換啓動
- AD轉換時間:通過ADC2(P1.4)輸出脈衝來測量。
- 測量結果:兩次ADC轉換,時間爲5微妙;
▲ P1.4脈衝表明兩次AD轉換之間的時間
ON(FLAG_PIN);
ADCSetChannel(0);
g_nADResult1 = ADCConvert();
ADCSetChannel(1);
g_nADResult2 = ADCConvert();
OFF(FLAG_PIN);
採集實際信號
1. 外部接口命令
通過WiFi,或者UART1可以完成對單片機內部數據的讀取以及啓動轉換過程。具體的命名參見下面串口處理子程序。
其中主要使用的命令有:
- adc: 啓動新一輪的ADC轉換;
- sbuf:讀取SRAM中的數據
- sf :將SRAM中的32k字節數據通過UART顯示。
void SerialDebugProcessBuffer(void) {
// unsigned int nStart, nLength, nNumber, nCheck, i;
unsigned int i;
unsigned char ucChar;
unsigned int nLength, nStart;
SerialDebugBuffer2Argument();
if(g_ucSDANumber == 0) return;
if(strcmp("hello", (char *)STD_ARG[0]) == 0)
printf("%s is ready !\r\n", VERSION_STRING);
else IFARG0("adc") {
g_nADBufferPoint = 0;
g_nExtBufferPoint = 0;
TIME3_INT_ENABLE;
} else IFARG0("sbuf") {
sscanf(SDA(1), "%d", &nStart);
sscanf(SDA(2), "%d", &nLength);
nStart *= 2;
for(i = 0; i < nLength; i ++) {
ucChar = ExtSramRead(nStart ++);
UART3SendChar(ucChar);
ucChar = ExtSramRead(nStart ++);
UART3SendChar(ucChar);
}
} else IFARG0("sbyte") {
sscanf(SDA(1), "%d", &nStart);
sscanf(SDA(2), "%d", &nLength);
for(i = 0; i < nLength; i ++) {
ucChar = ExtSramRead(i + nStart);
UART3SendChar(ucChar);
}
} else IFARG0("sf") {
SendChar(0x0);
for(i = 0; i < 0x8000; i ++) {
printf("%bx ", ExtSramRead(i));
}
printf("\r\n");
// g_nADBufferPoint = 0;
// g_nExtBufferPoint = 0;
// TIME3_INT_ENABLE;
}
else printf("Error command : %s !\r\n", STD_ARG[0]);
}
2. 採集兩路測試信號
使用PYTHON來通過UDP獲取模塊內部的數據並進行顯示繪圖。
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TEST1.PY -- by Dr. ZhuoQing 2020-04-24
#
# Note:
#============================================================
from headm import *
from tsmodule.tsstm32 import *
import adcsub
from tsmodule.tsdraw import *
#------------------------------------------------------------
#------------------------------------------------------------
#adcsub.ADCCmd('sf')
'''
for i in range(2):
begintime = time.time()
ch1, ch2 = adcsub.ADCExtBufferData(4096)
costtime = time.time() - begintime
printf("Sample Time:%f"%costtime)
exit()
'''
#------------------------------------------------------------
sampledata = []
#------------------------------------------------------------
pltgif = PlotGIF()
for i in range(20):
# ch1,ch2 = adcsub.ExtBufferCH12()
ch1, ch2 = adcsub.ADCExtBufferData(0x4000)
plt.clf()
plt.plot(ch1, linewidth=1, label='CH1')
plt.plot(ch2, linewidth=1, label='CH2')
plt.xlabel('Sample')
plt.ylabel('Amplitude')
plt.grid(True)
plt.legend(loc='upper right')
plt.axis([0, len(ch1), 0, 0xfff])
plt.draw()
plt.pause(.5)
pltgif.append(plt)
time.sleep(1)
printf("i = %d"%i)
sampledata.append((ch1, ch2))
tspsave('sampledata', data=sampledata)
pltgif.save(r'd:\temp\SampleGIF.gif')
printf('\a')
plt.show()
#------------------------------------------------------------
# END OF FILE : TEST1.PY
#============================================================
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# ADCSUB.PY -- by Dr. ZhuoQing 2020-04-25
#
# Note:
#============================================================
from headm import *
import socket
from tsmodule.tsstm32 import *
#------------------------------------------------------------
ADC_PORT = 8899
ADC_IP = '192.168.0.191'
ADCSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ADCSocket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192)
#------------------------------------------------------------
def ADCCmd(str):
data = bytes(str+'\r', 'gbk')
ADDR = (ADC_IP, ADC_PORT)
ADCSocket.sendto(data, ADDR)
def ADCCmdData(str, length = 8192):
data = bytes(str+'\r', 'gbk')
ADDR = (ADC_IP, ADC_PORT)
ADCSocket.sendto(data, ADDR)
ADCSocket.settimeout(.2)
try:
rdata, ADDR = ADCSocket.recvfrom(length)
except socket.timeout:
print('UDP time out.')
rdata = b''
return rdata
#------------------------------------------------------------
ADC_DATA_STEP = 500
def ADCBufferData(length):
rece_step = int((length + ADC_DATA_STEP - 1) / ADC_DATA_STEP)
data = b''
for i in range(rece_step):
startn = i * ADC_DATA_STEP
endn = startn + ADC_DATA_STEP
if endn > length: endn = length
recelen = endn - startn
for j in range(5):
# time.sleep(.5)
recedata = ADCCmdData('buf %d %d'%(startn, recelen))
if len(recedata) == recelen * 2:
break
data = data + recedata
printff(startn, endn, len(recedata))
# dataarray = frombuffer(data, uint16)
dataarray = [x*256+y for x,y in zip(data[0::2],data[1::2])]
ADCCmd('adc')
return dataarray[0::2], dataarray[1::2]
#------------------------------------------------------------
ADC_DATA_STEP = 500
def ADCExtBufferData(length):
rece_step = int((length + ADC_DATA_STEP - 1) / ADC_DATA_STEP)
data = b''
for i in range(rece_step):
startn = i * ADC_DATA_STEP
endn = startn + ADC_DATA_STEP
if endn > length: endn = length
recelen = endn - startn
for j in range(20):
recedata = ADCCmdData('sbuf %d %d'%(startn, recelen))
if len(recedata) == recelen * 2:
break
data = data + recedata
printff(startn, endn, len(recedata))
dataarray = [x * 256 + y for x,y in zip(data[0::2],data[1::2])]
ADCCmd('adc')
return dataarray[0::2], dataarray[1::2]
#------------------------------------------------------------
EXT_BUFFER_STEP = 900
def ExtBufferRead(length):
read_step = (length + EXT_BUFFER_STEP - 1) // EXT_BUFFER_STEP
data = b''
for i in range(read_step):
startn = i * EXT_BUFFER_STEP
recelen = EXT_BUFFER_STEP
if startn + recelen > length:
recelen = length - startn
for j in range(20):
recedata = ADCCmdData('sbyte %d %d'%(startn, recelen))
if len(recedata) == recelen: break
data = data + recedata
return [y for y in data]
#------------------------------------------------------------
def ExtBufferCH12():
length = 0x8000
read_step = (length + EXT_BUFFER_STEP - 1) // EXT_BUFFER_STEP
data = b''
for i in range(read_step):
startn = i * EXT_BUFFER_STEP
recelen = EXT_BUFFER_STEP
if startn + recelen > length:
recelen = length - startn
for j in range(20):
recedata = ADCCmdData('sbyte %d %d'%(startn, recelen), recelen)
if len(recedata) == recelen: break
data = data + recedata
printff(startn, startn + recelen, len(recedata))
dataarray = [x*256+y for x,y in zip(data[0::2],data[1::2])]
ADCCmd('adc')
return dataarray[0::2], dataarray[1::2]
#------------------------------------------------------------
def ADCMemoData():
ADCCmd('sf')
time.sleep(2)
stm32cmd('COPY')
data = [int(s) for s in clipboard.paste().strip('\r\n').split(' ') if len(s) > 0]
return data[0::2], data[1::2]
#------------------------------------------------------------
# END OF FILE : adcsub.PY
#============================================================
▲ 採集兩路測試信號:正弦波以及三角波信號
2. 採集Chirp音頻信號
▲ 採集的Chirp聲音信號
▲ 兩個信號的相關信號
▲ 兩個信號的相關信號中心位置波形
公衆號留言
打擾了卓老師,先休息了
卓大大,實話實說,這次比賽實在是太太太太太趕了,學校大概六月初左右返校,然後還要準備期末考試。我們隊伍參加的是直立節能組。別的組別或許還可以在沒有儀器的情況下就把車做出來,但是我們是萬萬不可能的。充電板和車模一旦有一點問題更正週期是以周計算的,按照以往學長的進度,正常比賽留給軟件調試的時間也不過就是半個月左右,現在準備時間壓縮到兩個月,這次規則的軟件又比以往都要難一些,感覺除非開學之後每天通宵,不然實在是不能確保小車的穩定性。求大大考慮考慮節能的兄弟們。
卓老師好,深夜打擾。請問這款芯片可以在信標組中使用,還是說只要涉及到控制的微處理器都要用infineon的呢?
▲ 電機控制單片機
另外,我聽說了一種想法,有些同學在羣裏討論用1064自制openmv,事實上偷偷用1064跑信標,我雖然沒有驗證過可行性,但是不希望有這種想法傷害到比賽的公平性,也不願意看到openmv因此而被禁止,所以也很卓大提及一下這種想法。
▲ 自制的OpenMVP模塊
再提出一個建議:音標比賽只允許使用成品openmv