【GD32E503评测】 简易示波器实验项目总结
[复制链接]
首先感谢兆易创新公司和EEWORLD论坛提供了这次评测的机会。我的评测计划是将ADC转换的数据以图形方式动态显示在TFT屏幕上,完成一个简易的示波器实验。本次评测用了近两个月的时间,重点进行了简易示波器的实验,通过反复探索和试验,基本上完成了项目目标,实现了以图形方式动态显示ADC转换的数据。
一、TFT屏幕显示的操作
了解和掌握TFT显示屏的操作是动态图形显示的第一步。通过对示例代码的分析,初步了解到屏幕显示字符是通过逐点写入的方式进行的,这为后面动态图形显示ADC转换结果打下了基础。在测试过程中发现,尽管逐点写入的效率很低,但因单片机的其他处理任务也不多,测试中并未出现显示迟滞情况。示例提供的字符显示函数功能是显示单个字符,我在此基础上改写了一个可以显示字符串的函数,同时还添加了汉字字符集,实现了汉字和ASCII码字符的混合显示。之后还添加了一个以十进制方式显示数据的函数,方便显示ADC转换的结果。下图为TFT屏幕显示的效果:
这是显示字符串的函数代码:
/*!
\brief 从根据指定位置开始在液晶屏上显示字符串(8*16ASCII和16*16汉字字符集)
\param[in] x: the start position of row-coordinate X坐标
\param[in] y: the start position of column-coordinate Y坐标
\param[in] text: the char 要显示的字符串
\param[in] char_color: the color of char 字符颜色
\param[in] c_format: the structure of char format 字体(字库)
font: CHAR_FONT_8_16 or CHAR_FONT_16_24
direction: CHAR_DIRECTION_HORIZONTAL or CHAR_DIRECTION_VERTICAL
char_color: the color of char
bk_color: the color of background
\param[out] none
\retval none
*/
void lcd_strue_display(uint16_t x,uint16_t y,uint8_t *str,char_format_struct c_format)
{
uint8_t i,j,k,temp_char;
uint16_t X,Y;
X = x;
Y = y;
while(*str){
if(((uint8_t)(*str)) < 128){ //显示单字节ASCII(8*16)
if(*str > 31){
if(CHAR_DIRECTION_HORIZONTAL == c_format.direction){//水平显示
for (i = 0; i < 16; i++) { //显示16行的点阵
temp_char = ascii_8x16[((*str - 0x20) * 16) + i];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01) {
lcd_point_set(X - i, Y + j, c_format.char_color);
} else {
lcd_point_set(X - i, Y + j, c_format.bk_color);
}
}
}
Y += 8;
}else{
for (i = 0; i < 16; i++) { //显示16行的点阵
temp_char = ascii_8x16[((*str - 0x20) * 16) + i];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01)
lcd_point_set(X + j, Y + i, c_format.char_color);
else
lcd_point_set(X + j, Y + i, c_format.bk_color);
}
}
X += 8;
}
}
str++;
}
else{ //显示双字节汉字(16*16)
for(k=0; k<200; k++){ //在汉字字符集中查找小于200次
if((GB_16ID[k][0] == *(str)) && (GB_16ID[k][1] == *(str + 1))){
if(CHAR_DIRECTION_HORIZONTAL == c_format.direction){ //水平显示
for( i=0; i<16; i++){//显示16行的点阵(左半边)
temp_char = GB_16[(k * 32) + i];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01)
lcd_point_set(X - i, Y + j, c_format.char_color);
else
lcd_point_set(X - i, Y + j, c_format.bk_color);
}
}
for( i=0; i<16; i++){//显示16行的点阵(右半边)
temp_char = GB_16[(k * 32) + i + 16];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01)
lcd_point_set(X - i, Y + j + 8, c_format.char_color);
else
lcd_point_set(X - i, Y + j + 8, c_format.bk_color);
}
}
Y += 16;
} else {
for( i=0; i<16; i++){//显示16行的点阵(左半边)
temp_char = GB_16[(k * 32) + i];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01)
lcd_point_set(X + j, Y + i, c_format.char_color);
else
lcd_point_set(X + j, Y + i, c_format.bk_color);
}
}
for( i=0; i<16; i++){//显示16行的点阵(右半边)
temp_char = GB_16[(k * 32) + i + 16];
for (j = 0; j < 8; j++) {
if (((temp_char >> (7 - j)) & 0x01) == 0x01)
lcd_point_set(X + j + 8, Y + i, c_format.char_color);
else
lcd_point_set(X + j + 8, Y + i, c_format.bk_color);
}
}
X += 16;
}
}
}
str += 2;
}
}
}
这是显示变量的函数代码:
/*!
\brief 从指定位置开始在液晶屏上显示变量(8*16ASCII十进制数字)
\param[in] x: the start position of row-coordinate X坐标
\param[in] y: the start position of column-coordinate Y坐标
\param[in] len: 显示的长度(位数,<8位)
\param[in] pont: 小数位数
\param[in] text: the char 要显示的字符串
\param[in] char_color: the color of char 字符颜色
\param[in] c_format: the structure of char format 字体(字库)
font: CHAR_FONT_8_16 or CHAR_FONT_16_24
direction: CHAR_DIRECTION_HORIZONTAL or CHAR_DIRECTION_VERTICAL
char_color: the color of char
bk_color: the color of background
\param[out] none
\retval none
*/
void lcd_value_display(uint16_t x,uint16_t y,uint8_t len, uint8_t pont, uint32_t val,char_format_struct c_format)
{
uint8_t i,j,f = 32; //列循环、字循环、显示标志
uint8_t cha[] = "0000000000"; //10位
uint32_t t,cid; //当前余数、当前数字
uint32_t n; //当前倍数
t = val;
n = 1;
i = 0;
for (j = 0; j < len; j++)
n = n * 10;
for (j = len; j > 0; j--) //字符循环开始
{
n = j < 2 ? 1: n / 10; //计算当前的倍数
cid = t / n; //当前位数字
t = t - (cid * n);
if((cid>0) | (j-1==pont)) //有效数据后的零显示
f = 48;
cha[i++] = cid + f;
if(pont > 0 & pont == (j - 1)){
cha[i++] = 46; //加小数点
}
}
cha[i] = 0;
lcd_strue_display( x, y, cha, c_format);
}
二、简易示波器实验的过程
首先测试了ADC转换。利用了板上的ADC1(PA1)和ADC2(PA2),开发板上ADC1已经连接了一个多圏电位器,PA2则是通过JP4外接信号源。我制作了一个电位器扩展板作ADC2的信号源,同时还利用线性电源变换器引出低压交流电源,经过二极管半波整流后作测试动态波形的信号源。下图为ADC2连接电位器扩展板:
这是ADC2连接自制的低压交流(半波整流)信号源:
在测试ADC转换的基础上继续进行了简易示波器的实验。我将2/3左右的屏幕设置成了图形显示区域,有独立和合并两种显示模式,通过按下KEY_CET进行切换。下图为独立模式,即两个ADC转换结果分别在两个区域显示,ADC1用红色线条在上部区域显示,ADC2用蓝色线条在下部区域显示。
合并模式的显示效果如下图,即两组转换结果在同一个坐标系中显示,这样更容易进行比较:
我在简易示波器实验中设置了三个参数可供调整,即采样率、刷新率和缩放率。调节参数时先通过KEY_A按键切换要调节的参数,然后按KEY_B或KEY_D键调节参数的大小。
首先是采样率参数,起初准备利用定时器来触发采样,但经过多次尝试,定时器都没有设置成功,无奈只好设置了一个变量通过在主循环中对这个变量计数,达到设定值时触发采样,通过改变设定值从而改变采样率,如下图所示,在本次实验中,这个参数从50~1500每隔50为一档。经试验,参数为50时,采样率大约每秒11000多次;参数为1500时,采样率大约为每秒400次左右。
第二项可调节的参数是刷新率,这个参数从5~200每隔5为一档。当参数为5时,每秒大约显示83列,3秒左右才刷新完一屏;当参数为200时,每秒大约显示15列,约16秒左右才刷新完一屏。
第三项可调节的参数是缩放率。我们知道的12位ADC转换数据最大为4096,而在本次实验中,独立显示时列向为100点,合并显示时列向为200点,需要将这个数据缩小以便能显示在屏幕中,这个参数从15~40可调节(参见下图):
三、简易示波器实验的结果
开始实验时,我将ADC采样和图形显示同步进行,即随采随显。测试时,旋动电位器时,显示的线条会随同上下移动,取得了比较好的效果。但我用低压交流电经半波整流后作信号源,却看不到预期的半个正弦波,只是看到周期性变化的近似直线的波形(参见下图),看起来似乎是刷新过快的效果:
在此基础上,我尝试将刷新率减少到每秒15列左右时,可以看到阶梯状的近似半个正弦波,如下图所示:
我们知道,市电的频率为50Hz,也就是20毫秒为一个周期,而我实验时完成半个波形显示则花费了十多秒,至少过去了200多个周期。因此,上面的波形不会是真正抓取到的正弦波,也许是正好与交流电周期的“谐波”图形罢了。
在改进的实验中,我将ADC转换与图形显示分割,ADC转换的结果放在一个数组中,显示时则从这个数组中取数据。由于显示速度比较慢,且显示一个屏幕只需要238组数据,我采取ADC转换300组数据后即暂停,等待显示完整屏后再读取300组数据继续显示。为了能显示出交流电的半个正弦波,我设想每秒采样2000次左右,一个周期采样50次,这样应该能显示出半个波形了,但事与愿违,看到的仍旧是周期性变化的水平线。
进一步分析,觉得也许是触发ADC转换后到读取数据之间的间隙太短,造成数据不正确。于是便在触发ADC转换后插入一个延时,然后再读取数据。然而实验仍未成功,我将延时从几十微秒逐步调整到几毫秒,仅仅是水平线的变化幅度改变而已,连阶梯状的波形都没有出现过。
通过近二十天的测试,想观看市电交流正弦波图形的努力终未成功。究其原因,可能还是ADC转换的速度问题,当然,显示屏的瓶颈问题也是重要原因。看来直接利用单片机制作示波器的设想还是不现实的,采样部分应该使用专用芯片,显示部分也不能用本次评测的显示屏。尽管本次简易示波器的实验不完全成功,但对GD32E503V-EVAL开发板的评测还是挺有收获的,这是我首次接触触摸屏,首次实现了在TFT屏幕上动态显示图形。
|