最近通过B站下载到“老刘爱鼓捣”的开源资料,是基于51单片机+OLED做的示波器,自己在复现过程中也付出了许多时间和经济成本,现将所学程序中的知识总结如下:
1、如果有需要资料,可自行B站搜索,如也要复现则首先建议直接买51开发板,加上外围器件,我自己买51芯片焊接时,容易出现无法烧写程序和OLED显示屏不亮的情况。
2、通过EEProm 储存 读取设定参数,并且将检查合法性作为一个函数,统一检查各参数。
bit Save_Options() { uint8 ops[15]; uint8 *p; check_Options(); p = ops; *p++ = Lsb >> 8; //дÈëLsb¸ß8λ uint16 *p++ = Lsb; //дÈëLsbµÍ8λ *p++ = PlotMode; //дÈë»æͼģʽ bit *p++ = ScaleH; //дÈëʱ¼äÇø¼ä char *p++ = ScaleV_Auto; //¶ÁÈ¡×Ô¶¯Á¿³Ì±êÖ¾ bit *p++ = RulerVMax >> 8; //дÈë×ÝÖáµçѹ¸ß8λ int16 *p++ = RulerVMax; //дÈë×ÝÖáµçѹµÍ8λ *p++ = RulerVMin >> 8; //дÈë×ÝÖáµçѹ¸ß8λ int16 *p++ = RulerVMin; //дÈë×ÝÖáµçѹµÍ8λ *p++ = TriLevel >> 8; //дÈë´¥·¢Öµ¸ß8λ int16 *p++ = TriLevel; //дÈë´¥·¢ÖµµÍ8λ *p++ = TriMode; //дÈë´¥·¢·½Ê½ int8 *p++ = TriSlope; //дÈë´¥·¢·½Ïò bit *p++ = WaveScroll; //дÈ벨Ðιö¶¯±êÖ¾ bit *p = OLED_Brightness; //дÈëOLEDÁÁ¶È uint8 // printf("Lsb=%hu\r\n",Lsb); // printf("DrawMode=%X\r\n",PlotMode); // printf("ScaleH=%bd\r\n",ScaleH); // printf("ScaleV_Auto=%X\r\n",ScaleV_Auto); // printf("RulerVMax=%hd\r\n",RulerVMax); // printf("RulerVMin=%hd\r\n",RulerVMin); // printf("TriLevel=%hd\r\n",TriLevel); // printf("TriMode=%bd\r\n",TriMode); // printf("TriSlope=%X\r\n",TriSlope); // printf("WaveScroll=%X\r\n",WaveScroll); // printf("OLED_Brightness=%bu\r\n",OLED_Brightness); // printf("\r\n"); return EEPROM_Save(ops, sizeof(ops) / sizeof(ops[0])); }
void Read_Options() { uint8 ops[15]; uint8 *p; p = ops; EEPROM_Read(ops, sizeof(ops) / sizeof(ops[0])); Lsb = *p++; //¶ÁÈ¡Lsb¸ß8λ uint16 Lsb <<= 8; //½«µÍ8λÒƵ½¸ß°Ëλ Lsb |= *p++; //¶ÁÈ¡LsbµÍ8λ PlotMode = *p++; //¶ÁÈ¡»æͼģʽ bit ScaleH = *p++; //¶Áȡʱ¼äÇø¼ä char ScaleV_Auto = *p++; //¶ÁÈ¡×Ô¶¯Á¿³Ì±êÖ¾ bit RulerVMax = *p++; //¶ÁÈ¡×ÝÖáµçѹ×î´óÖµ¸ß8λ int16 RulerVMax <<= 8; //½«µÍ8λÒƵ½¸ß°Ëλ RulerVMax |= *p++; //¶ÁÈ¡×ÝÖáµçѹ×î´óÖµµÍ8λ RulerVMin = *p++; //¶ÁÈ¡×ÝÖáµçѹ×îС¸ß8λ int16 RulerVMin <<= 8; //½«µÍ8λÒƵ½¸ß°Ëλ RulerVMin |= *p++; //¶ÁÈ¡×ÝÖáµçѹ×îСµÍ8λ TriLevel = *p++; //¶ÁÈ¡´¥·¢Öµ¸ß8λ int16 TriLevel <<= 8; //½«µÍ8λÒƵ½¸ß°Ëλ TriLevel |= *p++; //¶ÁÈ¡´¥·¢ÖµµÍ8λ TriMode = *p++; //¶ÁÈ¡´¥·¢·½Ê½ int8 TriSlope = *p++; //¶ÁÈ¡´¥·¢·½Ïò bit WaveScroll = *p++; //¶ÁÈ¡²¨Ðιö¶¯±êÖ¾ bit OLED_Brightness = *p; //¶ÁÈ¡OLEDÁÁ¶È uint8 check_Options(); //¼ì²éÑ¡ÏîºÏ·¨ÐÔ // printf("Lsb=%hu\r\n",Lsb); // printf("DrawMode=%X\r\n",PlotMode); // printf("ScaleH=%bd\r\n",ScaleH); // printf("ScaleV_Auto=%X\r\n",ScaleV_Auto); // printf("RulerVMax=%hd\r\n",RulerVMax); // printf("RulerVMin=%hd\r\n",RulerVMin); // printf("TriLevel=%hd\r\n",TriLevel); // printf("TriMode=%bd\r\n",TriMode); // printf("TriSlope=%X\r\n",TriSlope); // printf("WaveScroll=%X\r\n",WaveScroll); // printf("OLED_Brightness=%bu\r\n",OLED_Brightness); // printf("\r\n"); }
数组加指针配合,指向数组的指针。
EEPROM_Read(ops, sizeof(ops) / sizeof(ops[0]));
3、将全局的变量、标志位、数组都放在global.h中,在各个文件中包含该头文件。
4、51单片机的关键字
bit 位标量声明 声明一个位标量或位类型的函数
sbit 位标量声明 声明一个可位寻址变量
sfr 特殊功能寄存器声明 声明一个特殊功能寄存器
sfr16 特殊功能寄存器声明 声明一个16位的特殊功能寄存器
关键字参考:https://blog.csdn.net/s3olo/article/details/7712586
uint16 *GetBGV(void) { uint16 *BGV; BGV = (uint16 code *)BGV_ADR; return BGV; }
以上程序取BGV寄存器中储存的参考电压数据。
5、OLED的显示操作 在该文件中总结了OLED写数据、写命令,画点、划线、画图的库,如下两个函数是OLED操作最底层函数,其他画点、画线、画字符串、写数字都是存在一个数组buf中,遍历OLED所有点,调用下列函数最终实现绘制。
void OLED_Write_Command(uint8 dat)
{
uint8 i;
OLED_DC_Clr();
OLED_CS_Clr();
for (i = 0; i < 8; i++)
{
OLED_SCLK_Clr();
if (dat & 0x80) //ÅжÏÊý¾Ý×î¸ßλÊÇ1»¹ÊÇ0
{
OLED_SDIN_Set();
}
else
OLED_SDIN_Clr();
OLED_SCLK_Set();
dat <<= 1;
}
OLED_CS_Set();
// OLED_DC_Set();
}
/* дÊý¾Ý
Write Data */
void OLED_Write_Data(uint8 dat)
{
uint8 i;
OLED_DC_Set();
OLED_CS_Clr();
for (i = 0; i < 8; i++)
{
OLED_SCLK_Clr();
if (dat & 0x80) //ÅжÏÊý¾Ý×î¸ßλÊÇ1»¹ÊÇ0
{
OLED_SDIN_Set();
}
else
OLED_SDIN_Clr();
OLED_SCLK_Set();
dat <<= 1;
}
OLED_CS_Set();
// OLED_DC_Set();
}
6、全局变量统一在前面加g 或者加_ ,比如 _flag.
7、绘制一个像素点
/* 绘制一个像素至缓存
Draw one pixel to buffer */
void OLED_DrawPixel(uint8 x, uint8 y)
{
uint8 mask;
uint8 *pBuf;
if (_x > WIDTH - 1)
{
_x = 0;
_y += 1;
}
if (_y > HEIGHT - 1)
{
_y = 0;
}
pBuf = &_buf[(y >> 3) * WIDTH + x];
mask = 1 << (y & 7);
if (_OLED_Reverse)
{
*pBuf++ &= ~mask;
}
else
{
*pBuf++ |= mask;
}
}
pBuf = &_buf[(y >> 3) * WIDTH + x];
mask = 1 << (y & 7);
以上两句是写像素至缓存关键,即在_buf中储存相应的值,假设写的点为(4,8),则注意是从_x=0,_y=0开始写的,8是4列第9个点,写入的为一个字节的数据mask,y>>3是判断其所在的页,如果是第8个点(0-7是第一页),则是在第二页。_buf是一个线性数组,存第二页的数则应该是加上第一页的列数,即1*128+4即为要存的位置。
8、统一显示屏幕上所有内容
/* 将缓存内容显示到屏幕上
Send buffer to display */
void OLED_Display(void)
{
uint8 i, j;
uint8 *pBuf;
pBuf = _buf;
for (j = 0; j < PAGES; j++)
{
for (i = 0; i < WIDTH; i++)
{
OLED_Write_Data(*pBuf++);
}
}
}
9、旋转编码器的使用
EC11编码器资料参见如下的链接:
P3.3为编码器按键信号,即普通按键一样操作;P3.2和P3.4为编码器脉冲信号,
P3.3链接到外部中断1,
P3.2连接外部中断0,
编码器p3.4 连接普通IO口。
void Scan_EC11(void)可以判断方向,但其要在INT0中断中使用才可以,因为其只是判断p3.2和p3.4是否相等,单独使用不可判断方向,当在INT0中使用时,INT0中断说明P3.2脚脉冲变化,该处中断设置为上升沿和下降沿均触发。根据波形图,设A-C之间为p3.2,在第一个下降沿触发了中断,在中断中,当p3.2和p3.4(B-C)状态相同(都为低电平),此时可以判断为正传,当状态不同时判断为反转。
旋转编码器还有一个开关信号,指示按下和弹起两个状态。于是可以组合出正传、反转、按下、弹起、按下正传、按下反转、等多种组合方式。
图源于:旋转编码开关 EC11 的工作原理_lovelijiapu的博客-CSDN博客
sbit EC11_KEY = P3 ^ 3; //编码器按键连接外部中断1 connect to External Interrupt 1
sbit EC11_A = P3 ^ 2; //编码器A脉冲连接外部中断0 connect to External Interrupt 0
sbit EC11_B = P3 ^ 4; //编码器B脉冲连接普通IO口 connect to GPIO
void Scan_EC11(void)
{
/* 延时去抖动
Delay to remove jitter */
Delay500us();
/* 正转
Clockwise */
if (EC11_A != EC11_B)
{
Change_Val(1);
}
/* 反转
Anticlockwise */
else if (EC11_A == EC11_B)
{
Change_Val(0);
}
}
/* 编码器旋转中断
Interrput for Encoder Rotated */
void INT0_interrupt(void) interrupt INT_0
{
Delay500us();
/* 编码器是否被按下
Whether the Encoder is pressed */
if (!EC11_KEY)
EC11PressAndRotate = 1;
else
EC11PressAndRotate = 0;
Scan_EC11();
ADCInterrupt = 1;
DisplayUpdate = 1;
IE1 = 0; //清零外部中断1标志位
IE0 = 0; //清零外部中断0标志位
}
/* 编码器点击中断
Interrput for Encoder Pressed */
void INT1_interrupt(void) interrupt INT_1
{
Delay50ms();
if (!EC11_KEY)
{
EC11PressAndRotate = 0;
/* 长按编码器按键 - 切换主界面和设置界面
Long presse Encoder - Switch main interface and settings interface */
if (Delay800ms_Long_Press())
{
InSettings = ~InSettings;
/* 进入设置界面
Enter Settings */
if (InSettings)
{
DisplayUpdate = 1;
UpdateVbat = 1;
TF0 = 0; //清零定时器0溢出标志
TR0 = 1; //定时器0开始计时,开始电池电压信息更新计时
IE0 = 0; //清零外部中断0中断标志
EX0 = 1; //开启外部中断0(编码器旋转)
}
/* 回到主界面
Retrurn to main interface */
else
{
TR0 = 0; //清零定时器0溢出标志
TF0 = 0; //定时器0停止计时,停止电池电压信息更新计时
WaveFreq = 0;
TriFail = 0;
VMax = 0;
VMin = 0;
DisplayUpdate = 1;
WaveUpdate = 1;
ClearWave = 0;
}
ADCInterrupt = 1;
}
/* 按住编码器同时旋转
Rotate Encoder while pressing */
else if (EC11PressAndRotate)
{
/* 由编码器旋转中断执行操作
Operations Performed by Interrupt of Encoder Rotation */
}
/* 双击编码器按键 - 在主界面,滚动波形和选项模式之间切换
Double click Encoder - Switch between Waveform Scroll Mode and Parameter Mode in Main Interface */
else if (Delay300ms_Double_Click())
{
/* 主界面
Main Interface*/
if (!InSettings)
{
WaveScroll = ~WaveScroll;
OptionChanged = 1;
ADCInterrupt = 1;
DisplayUpdate = 1;
ClearWave = 0;
}
}
/* 单击编码器按键 - 在主界面,切换Stop/Run状态
Single click Encoder - Switch Run/Stop in main interface */
else if (!InSettings)
{
EX0 = 0;
ADCRunning = ~ADCRunning;
if (ADCRunning)
{
WaveUpdate = 1;
ClearWave = 1;
}
else
{
DisplayUpdate = 1;
WaveUpdate = 1;
}
ADCInterrupt = 1;
IE0 = 0;
IE1 = 0;
}
}
IE1 = 0;
}
if (Delay800ms_Long_Press()) 编码器长按,直接在if()中调用有返回值的函数,函数中如果编码器弹起或者旋转则返回,如果800ms内没变化则返回1。
bit Delay800ms_Long_Press() //@27.000MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 110;
j = 148;
k = 166;
do
{
do
{
/* 编码器松开或转动
Encoder released or rotated */
if (EC11_KEY || EC11PressAndRotate)
return 0;
while (--k)
;
} while (--j);
} while (--i);
return 1;
}