RAM是计算机系统中保存临时结果的器件,它的大小也决定了计算机处理数据的规模。在嵌入式计算机(单片机中),由于受到价格、功耗等原因,内部的RAM的容量往往比较小,比如从几百字节到十几k字节不等。这能够满足大部分单片机应用的需求了。
但是在一些特殊的应用情况下,有时需要大容量的RAM来存储采集到的数据,或者缓存通信数据。此时则需要通过外扩内存来完成。比如最近实验声音信标信号相关系统测试和算法优化,则需要采集到多路音频信号。此时需要通过外扩RAM来解决。
通常静态RAM芯片接口包括有数据、地址和控制总线,与单片机对应的端口相连便可以加成数据的写入和读出。
▲ MCU外部RAM
有的单片机在扩展外部RAM的时候,为了节省有限的IO端口,通过使用外部锁存器来复用同一八位地址端口来扩展地址总线到16位的目的。比如8051单片机。这样设计的代价就是需要增加外部锁存器芯片74LS372。
▲ 8051外部数据总结接口
虽然数据总线和地址总线按照逻辑都标有数据位的顺序:比如16位地址总线按照A0~A15,8位数据总线表明D0-D7。但是在方位静态RAM的时候,数据总线,地址总线内部的这些位的顺序是可以任意调整的。比如在下面设计的单片机系统中,为了满足能够尽量减少PCB布线的交叉,就是通过调整数据线和地址总线内部的位的顺序来满足的。
D:\zhuoqing\AltiumDesigner\STC\Test\2020\RAM8H3K64S4.PcbDoc *
STC8H8K单片机具有16路12位的AD转换器,在本实验中用于采集信标发出的Chirp声音,并传送给计算机加以处理。
在博文“基于STC8G8K64U三通道高速ADC采集板"给出了利用STC8H8K内部的8kRAM进行声音信号采集电路设计方案。但是受限于内有8KRAM的空间限制,所能够采集音频信号的路数和时间长度都无法满足研究的目的。所以此次通过外部扩一片32k字节的SRAM来扩展信号采集的容量。
同样通过WiFi-UART转换模块,实现采集数据与PC机之间的数据传送,这一点设计与前面博文中的设计方案是一致的。
所使用的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 |
这里需要需要说明的是,为了能够适应快速制版的实验需要。使用了单面PCB板工艺。为了消除在布线中的交叉部分(因为交叉部分则需要过孔和双面布线),对数据总线和地址总线的顺序进行了调整,由此可以仅仅使用一面PCB便将所有的数据和地址总线完成连接。
下面给出了具体的PCB连接方式。前面原理图中显示了调整后的地址线和数据线的逻辑设计。
▲ 使用单面覆铜板制作实验电路板
在实际电路设计工程中,有的时候为了方便布线,需要对引线的顺序进行调整。在复杂电路设计中,优势会通过FPGA、CPLD等大规模可编程逻辑器件来对芯片外部引线的顺序进行调整,方便进行布线。当然这也需要付出设计的复杂度和额外增加的芯片价格。
软件开发工程所在的目录:
D:\zhuoqing\window\C51\STC\Test\2020\RAM8H8K64U\RAM8H8K64U.uvproj
通过设计,电路板腐蚀、焊接与安装,很快得到实验电路。下面对其进行测试。
▲ 通电之后的实验电路板
STC8H单片机硬件变成选项配置如下:
▲ 单片机编程硬件选项参数
单片机工作的主要指标:
(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。
(1)AD的通道设置:
使用了P1.0, P1.1 来采集两路外部模拟信号。
▲ ADC通道设置
(2)AD转换时间:
ON(FLAG_PIN);
ADCSetChannel(0);
g_nADResult1 = ADCConvert();
ADCSetChannel(1);
g_nADResult2 = ADCConvert();
OFF(FLAG_PIN);
通过WiFi,或者UART1可以完成对单片机内部数据的读取以及启动转换过程。具体的命名参见下面串口处理子程序。
其中主要使用的命令有:
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]);
}
使用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
#============================================================
▲ 采集两路测试信号:正弦波以及三角波信号
▲ 采集的Chirp声音信号
▲ 两个信号的相关信号
▲ 两个信号的相关信号中心位置波形
打扰了卓老师,先休息了
卓大大,实话实说,这次比赛实在是太太太太太赶了,学校大概六月初左右返校,然后还要准备期末考试。我们队伍参加的是直立节能组。别的组别或许还可以在没有仪器的情况下就把车做出来,但是我们是万万不可能的。充电板和车模一旦有一点问题更正周期是以周计算的,按照以往学长的进度,正常比赛留给软件调试的时间也不过就是半个月左右,现在准备时间压缩到两个月,这次规则的软件又比以往都要难一些,感觉除非开学之后每天通宵,不然实在是不能确保小车的稳定性。求大大考虑考虑节能的兄弟们。
卓老师好,深夜打扰。请问这款芯片可以在信标组中使用,还是说只要涉及到控制的微处理器都要用infineon的呢?
▲ 电机控制单片机
另外,我听说了一种想法,有些同学在群里讨论用1064自制openmv,事实上偷偷用1064跑信标,我虽然没有验证过可行性,但是不希望有这种想法伤害到比赛的公平性,也不愿意看到openmv因此而被禁止,所以也很卓大提及一下这种想法。
▲ 自制的OpenMVP模块
再提出一个建议:音标比赛只允许使用成品openmv