转载自:
https://blog.csdn.net/weixin_44578655/article/details/105646300
proteus在8.9版本正式支持stc单片机,虽然只有一个型号(stc15w4k32s4),不过这是stc家功能很齐全的一款的单片机。
最近刚好开单片机课程使用的就是这款,因为疫情上网课没有实物,就用proteus做了一块仿真开发板。
仿真开发板的原型是stc家的一款试验箱:
仿真开发板全貌:
开发板链接:
链接:https://pan.baidu.com/s/1xeccrGQLA_kM-TxT5n8-fg
提取码:vwip
之所以选这款,是因为stc-isp有它的全部例程,开发板和例程都白嫖,还要啥自行车,当然仿真会有部分BUG,实物才是正道,例程我随缘测试吧,测试效果会后续更新,有想帮忙一起弄的小伙伴欢迎转载。
例程:
问题:
IO口模式无法配置,各IO口不管如何配置,都是准双向IO口模式,网上资料较少,没找到解释。
因为无法将IO口配置为高阻态,所以ADC无法测试。
后来发现例程对于ADC的初始化中,并没有将IO口配置为高阻态,后文我又重新测试了一下ADC。
proteus自带的库中,没有淘宝常见的那种带中文字库的LCD12864,在网上翻到有大佬做了一个:
http://www.51hei.com/bbs/dpj-94761-1.html
折腾了一下午,能显示了,但这个12864有BUG,只能显示汉字,不支持ASCII码,而且显示屏左上角的定位孔必须与原理图原点的位置重合,才能显示正常,若不对齐,效果如下:
对齐以后效果如下(屏幕左侧的几串数字估计也是BUG):
硬件PWM正常,效果如下:
有同学反应proteus仿真STC15时,时钟不正确。
今天测试了一下,时钟是准确的。
之前还担心,stc15的定时器工作在1T模式下和12T模式下,proteus能否仿真出真实的效果。今天测试了一下,12T模式下仿真的确比1T模式下仿真慢12倍。
在12T模式下,程序运行至12s时,引脚输出发生跳变,小灯亮起:
配置为1T模式,程序仅改这一处:
proteus仿真时运行比较慢,左下角的计时才是程序真实的运行时间。
proteus中提供了一个虚拟串口终端,类似串口助手可以收发数据。
51单片机的串口收发有两种方式:中断方式和查询方式。
在仿真中串口发送使用查询方式和中断方式都可以,数据正常。
串口接收时,仅能使用中断方式,查询方式无法正常收到数据,测试时发现RI始终没被置一。
串口相关代码:
void UART_Init(void)
{
ACC = P_SW1;
ACC &= ~(S1_S0 | S1_S1); //S1_S0=0 S1_S1=0
P_SW1 = ACC; //(P3.0/RxD, P3.1/TxD)
SCON = 0x50; //8位可变波特率
AUXR = 0x40; //定时器1为1T模式
TMOD = 0x00; //定时器1为模式0(16位自动重载)
TL1 = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
TH1 = (65536 - (FOSC/4/BAUD))>>8;
TR1 = 1; //定时器1开始启动
ES = 1; //使能串口中断
EA = 1;
}
void Uart() interrupt 4 using 1
{
if (RI)
{
RI = 0; //
rx_temp = SBUF;
}
if (TI)
{
TI = 0; //
busy = 0; //
}
}
void SendData(unsigned char dat)
{
while (busy); //
ACC = dat; //
busy = 1;
SBUF = ACC; //
}
开发板原来使用的是P0口接矩阵键盘,但在proteus中的STC15的P0口有严重的BUG,详见这篇帖子:
https://blog.csdn.net/weixin_44578655/article/details/106245015
我把P0换成了P6,并且将限流、上拉电阻全部去掉了,总算能凑合用了。
我没使用stc-isp里的矩阵键盘例程(没看太懂),自己按习惯写了一个:
//矩阵键盘扫描函数
//有按键按下时返回字符:'0'~'F',无按键按下时返回0
uchar IO_KeyScan(void)
{
uchar X_temp = 0,Y_temp = 0;
uchar Key_res = 0;
static Key_down = 0;
P6 = 0XFF; //未知BUG,必须加这句,否则下次读到的数据是错的
P6 = 0XF0; //高4位置1,低4位置0,此时有按键按下时,高四位的某一位会被拉低,由此定位按下的按键在第几行
X_temp = P6 ^ 0XF0;
if(X_temp ) //如果检测到某行有按键按下(有按键按下时,高四位会有一位被拉低)
{
if(Key_down == 0) //等待按键松开,防止重入
{
switch(X_temp)
{
case 0x80:
P6 = 0XFF; //未知BUG,必须加这句,否则下次读到的数据是错的
P6 = 0X0F; //低4位置1
Y_temp = P6 ^ 0X0F;
switch(Y_temp)
{
case 0x08: Key_res = 'F'; break;
case 0x04: Key_res = 'E'; break;
case 0x02: Key_res = 'D'; break;
case 0x01: Key_res = 'C'; break;
default: break;
}
break;
case 0x40:
P6 = 0XFF;
P6 = 0X0F;
Y_temp = P6 ^ 0X0F;
switch(Y_temp)
{
case 0x08: Key_res = 'B'; break;
case 0x04: Key_res = 'A'; break;
case 0x02: Key_res = '9'; break;
case 0x01: Key_res = '8'; break;
default: break;
}
break;
case 0x20:
P6 = 0XFF;
P6 = 0X0F;
Y_temp = P6 ^ 0X0F;
switch(Y_temp)
{
case 0x08: Key_res = '7'; break;
case 0x04: Key_res = '6'; break;
case 0x02: Key_res = '5'; break;
case 0x01: Key_res = '4'; break;
default: break;
}
break;
case 0x10:
P6 = 0XFF;
P6 = 0X0F;
Y_temp = P6 ^ 0X0F;
switch(Y_temp)
{
case 0x08: Key_res = '3'; break;
case 0x04: Key_res = '2'; break;
case 0x02: Key_res = '1'; break;
case 0x01: Key_res = '0'; break;
default: break;
}
break;
default:
break;
}
}
}
else
Key_down = 0; //按键被松开
if(Key_res)
Key_down = 1; //标志按键被按下,防止重入
return Key_res;
}
测试结果:
依次按下16个按键,串口输出的数据’0’~‘F’
74HC595是一颗串行输入并行输出的IC,通常用来驱动数码管、led点阵等,可以节省很多IO口.
74HC595的时序跟SPI很像,所以可以用硬件SPI驱动74HC595 。
在开发板上,74HC595用来驱动数码管,连接的就是SPI1的引脚。
跟串口类似,SPI发送数据同样有两种方式:查询方式和中断方式,这两种方式在仿真中均正常。
由于spi1的引脚有好几组,开发板上使用的是P4.3、P4.0、P5.4这一组引脚,选择哪一组引脚需要通过程序配置。
stc-isp中与SPI相关的例程有好几个,它们配置SPI引脚的代码不一样(不知道为什么),主要有两种:
第一种:配置AUXR1
第二种:配置ACC和PSW
在仿真中,第一种配置spi无效,第二种配置spi正常。
下面是查询方式和中断方式SPI发送数据的相关代码:
查询方式SPI初始化代码:
void spi_init() //spi初始化
{
/****官方例程中出现的,去掉这一段后spi失效***/
ACC = P_SW1; //切换到第三组SPI
ACC &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=1
ACC |= SPI_S1; //(P5.4/SS_3, P4.0/MOSI_3, P4.1/MISO_3, P4.3/SCLK_3)
P_SW1 = ACC;
SPSTAT = SPIF | WCOL; //清除SPI状态
/********/
SPCTL=(SSIG<<7)+(SPEN<<6)+(DORD<<5)+(MSTR<<4)
+(CPOL<<3)+(CPHA<<2)+SPEED_4;
}
查询方式发送函数:
//查询方式发送数据
void SPI_SendByte(unsigned char dat)
{
SPSTAT=SPIF+WCOL;
SPDAT=dat;
while((SPSTAT & SPIF)==0);
SPSTAT=SPIF+WCOL;
}
中断方式SPI初始化代码:
void spi_init() //SPI初始化
{
/****官方例程中出现的,去掉这一段后spi失效***/
ACC = P_SW1; //切换到第三组SPI
ACC &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=1
ACC |= SPI_S1; //(P5.4/SS_3, P4.0/MOSI_3, P4.1/MISO_3, P4.3/SCLK_3)
P_SW1 = ACC;
SPSTAT = SPIF | WCOL; //清除SPI状态
/********/
SPCTL=(SSIG<<7)+(SPEN<<6)+(DORD<<5)+(MSTR<<4)
+(CPOL<<3)+(CPHA<<2)+SPEED_4;
IE2 |= ESPI; //使能SPI传输中断
EA=1; //开启中断
}
中断方式发送函数:
//中断方式发送数据
void SPI_SendByte(unsigned char dat)
{
g_fSpiBusy = TRUE;
SPDAT=dat;
while (g_fSpiBusy); //等待SPI数据传输完成
}
//spi中断
void spi_isr() interrupt 9 using 1
{
SPSTAT = SPIF | WCOL; //清除SPI状态位
g_fSpiBusy = FALSE;
}
数码管扫描函数:
//数码管扫描
void seg7scan(unsigned char index1,unsigned char index2)
{
SPI_SendByte(~T_COM[index1]); //发送位选
SPI_SendByte(t_display[index2]);//发送段选
HC595_RCLK=1; //将P5.4置1,更新数据
HC595_RCLK=0; //将P5.4置0
}
//消隐
void seg_clear(void)
{
SPI_SendByte(0x00); //位选清零
SPI_SendByte(0x00); //段选清零
HC595_RCLK=1; //将P5.4置1,更新数据
HC595_RCLK=0; //将P5.4置0
}
main函数的大循环:
while(1)
{
for(i=0;i<8;i++)
{
seg7scan(i,i); //数码管扫描
Delay20ms(); //延时20ms
seg_clear(); //消隐
}
}
显示效果:
元器件太多了,仿真运行非常慢,实际现象应该是0123456,由于扫描缓慢,所以截图的瞬间只显示了34。
单独仿真这部分时,显示正常(因为运行的快一些,看不出来扫描的过程):
之前仿真的时候,stc15的IO口无法配置为高阻态,所以我直接放弃测试ADC了,因为即便能读出来数,这个ADC也无法正常应用(输入阻抗太小)。
最近还是测试了一下:
使用滑动变阻器:
可以看到,ADC引脚的输入阻抗异常,滑动变阻器调到2%(对地20Ω),电压竟然还能到2.5V,输出电流达到125mA。
不管是准双向还是推挽模式,都不可能出现这种情况。
抛开输入阻抗的问题,ADC在对应电压下的读数,还是比较准确的。
0.04V时,读数是02(16进制)
0.154V时,读数是08(16进制)
1.69V时,读数是56(16进制)
ADC的相关代码:
/*----------------------------
初始化ADC
----------------------------*/
void InitADC()
{
P1M0 = 1; //将P1口配置为高阻态
P1M1 = 1;
P1 = 0Xff;
P1ASF = 0xff; //设置P1口为AD口
ADC_RES = 0; //清除结果寄存器
ADC_CONTR = ADC_POWER | ADC_SPEEDLL;
Delay(2); //ADC上电并延时
}
/*----------------------------
读取ADC结果
参数:ch,通道0~7
----------------------------*/
BYTE GetADCResult(BYTE ch)
{
ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START;
_nop_(); //等待4个NOP
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & ADC_FLAG));//等待ADC转换完成
ADC_CONTR &= ~ADC_FLAG; //Close ADC
return ADC_RES; //返回ADC结果
}
输出结果时,又发现一点问题,原来是打算把ADC的结果(8位)使用sprintf格式化输出为字符,但这种方式打印出的数据完全错误…
/***下面这样使用sprintf出问题,未知bug***/
sprintf(tx_temp,"ch%d:%x\r\n",ch,GetADCResult(ch));
SendString(tx_temp); //串口发送字符串
/******/
/***只发送1字节16进制数,数据正常***/
SendData(GetADCResult(ch));
/******/
注意在串口终端窗口处右键勾选Hex Display Mode