【EFM8BB52单片机】室内环境监测物联网系统(完结)
快递邮寄到了学校,取的时候站点想要大大的快递箱子,就把快递箱子拆开送给了他们。能展示的就只剩下朴实无华的板子和数据线。如图1所示,图一是收到的物件合影,图2和图3是开发板的反正面,给我的感觉就是开发板很质朴,做工很沉重,简约而不缺实用精神。

图1 物件合影

图2 开发板正面图

图3 开发板反面图
该MCU内部共有6个定时器,其中time0和time1不能自重重装计数寄存器的值,timer2,timer3,time4,time5则都可以自动重装计数预装值。定时器的输入时钟在默认情况下是系统时钟的12分频输入,而系统时钟在默认情况下则是内部时钟24.5MHz的8分频。因此,定时器在默认情况下输入时钟为24.5MHz/8/12。其中,对于定时器的调试除了参考datasheet,也参考了石恒瑞同志的串口和定时器内容。
Timer0和Timer1相关定时器:TCON THX TLX CKCON0 TMOD IE 其中TCON用来设置Timer0和Timer1的是能与否,THX和TLX是存储定时器重载计数预装值寄存器,CKCON0用来设置Timer0和Timer1的时钟即选择时钟源,TMOD用来设置是否是计数器模式,以及是8位,13位或者16位等。默认是16位。IE则是控制Timer0和Timer1的中断与否。由于这两个定时器不能重载计数预装值,因此,在中断函数里需要重载值。Timer0初始化函数和中断服务子函数如下,其中t_ms是在输入时钟为24.5MHz/8/12条件下中断溢出时间,单位是ms。
uint8_t TIME0_MS=0 ; //定时器0的初始化中断时间
uint8_t TIME1_MS=0 ; //定时器0的初始化中断时间
/**
* 定时0 初始化
* @param t_ms 每次中断的时间,时间单位为ms
* 默认频率为30625HZ,则定时器2的输入时钟为 其12分频
*/
void timer0_init(uint8_t t_ms)
{
uint8_t TCON_save;
TIME0_MS=t_ms; //定时器0的初始化中断时间
TCON_save = TCON;
SFRPAGE = 0x00;
TCON &= ~TCON_TR0__BMASK & ~TCON_TR1__BMASK;
TH0 = (uint16_t)(65536-0.001*t_ms*255208)/256;
TL0 = (uint16_t)(65536-0.001*t_ms*255208)%256;
TCON |= (TCON_save & TCON_TR0__BMASK) | (TCON_save & TCON_TR1__BMASK);
CKCON0=CKCON0|CKCON0_SCA__SYSCLK_DIV_12| CKCON0_T0M__PRESCALE; //系统时钟的12分频
TMOD=TMOD|TMOD_T0M__MODE1|TMOD_CT0__TIMER|TMOD_GATE0__DISABLED; //计数器模式,16bit
TCON |= TCON_TR0__RUN;
IE = IE|IE_ET0__ENABLED;
}
/**
* 定时器0中断
* 每中断一次加变量count_ms加1
*/
SI_INTERRUPT(TIMER0_ISR, TIMER0_IRQn)
{
count0_ms++;
TCON_TR0 = 0; // Stop Timer 0
TH0 = (uint16_t)(65536-0.001*TIME0_MS*255208)/256;
TL0 = (uint16_t)(65536-0.001*TIME0_MS*255208)%256;
TCON_TR0 = 1; // Start Timer 0
}Timer2-----Timer5Timer2--Timer5是自动重载定时器,也就是计数溢出后能够自动从TMRXRLH和TMRXRLL寄存器中将预装值加载到TMRXH和TMRXL中。相关的寄存器如下:TMRXCN0,TMRXH,TMRXL,TMRXRLH,TMRXRLL,IE,EIE1,EIE2,其中,TMRXCN0用来控制TIMRX的使能与否,TMRXH,TMRXL是存储计数预装值,TMRXRLH,TMRXRLL存储要自动重装到TMRXH,TMRXL的数据,IE是Timer2的中断控制寄存器,EIE1是Timer3的中断控制寄存器,EIE2是Timer4和Timer5的中断控制寄存器。其中TMR3CN0好像不能位操作,因此需要直接赋值。
定时器2初始化和中断服务子程序如下
/**
* 定时2 初始化
* @param t_ms 每次中断的时间,时间单位为ms
* 默认频率为30625HZ,则定时器2的输入时钟为 其12分频
*/
void timer2_init(uint8_t t_ms)
{
SFRPAGE = 0x00;
TMR2CN0 &= ~(TMR2CN0_TR2__BMASK);
TMR2H = (uint16_t)(65536-0.001*t_ms*255208)/256;
TMR2L = (uint16_t)(65536-0.001*t_ms*255208)%256;
TMR2RLH = (uint16_t)(65536-0.001*t_ms*255208)/256;
TMR2RLL = (uint16_t)(65536-0.001*t_ms*255208)%256;//溢出后自动装载TMR2RLH和TMR2RLL寄存器的值到TMR2H和TMR2L寄存器
TMR2CN0 |= TMR2CN0_TR2__RUN;
IE = IE | IE_ET2__ENABLED;
}
/**
* 定时器2中断
* 每中断一次加变量count_ms加1
*/
SI_INTERRUPT(TIMER2_ISR, TIMER2_IRQn)
{
count2_ms++;
TMR2CN0_TF2H = 0; // Clear the interrupt flag
}定时器3初始化和中断服务子程序如下
/**
* 定时3 初始化
* @param t_ms 每次中断的时间,时间单位为ms
* 默认频率为30625HZ,则定时器2的输入时钟为 其12分频
*/
void timer3_init(uint8_t t_ms)
{
SFRPAGE = 0x00;
TMR3CN0 &= ~(TMR3CN0_TR3__BMASK);
TMR3H = (uint16_t)(65536-0.001*t_ms*255208)/256;
TMR3L = (uint16_t)(65536-0.001*t_ms*255208)%256;
TMR3RLH = (uint16_t)(65536-0.001*t_ms*255208)/256;
TMR3RLL = (uint16_t)(65536-0.001*t_ms*255208)%256;//溢出后自动装载TMR2RLH和TMR2RLL寄存器的值到TMR2H和TMR2L寄存器
TMR3CN0 |= TMR3CN0_TR3__RUN;
EIE1 = EIE1 | EIE1_ET3__ENABLED;
}
/**
* 定时器3中断
* 每中断一次加变量count_ms加1
*/
SI_INTERRUPT(TIMER3_ISR, TIMER3_IRQn)
{
count3_ms++;
TMR3CN0 &=0x7F; // Clear the interrupt flag
} 该MCU有2个串口,串口0和串口1,从器件手册中可以看出两个串口还是不同的。串口0需要用定时器来进行产生波特率,而串口1自己可以产生波特率。考虑到在接收数据时一帧数据有时候没有结尾标识。因此采用定时器中断溢出来检测一帧数据是否接收完成。具体思想为:当串口接收到一个byte数据后,重新装载定时器计数值,相当于定时器重新计数,然后打开定时器开始计数(初始化时该定时器是关闭不运行的)。当接收数据结束后,由于定时器没有重新装载数据,这时候会溢出中断,从而能够指示一帧数据接收完成。串口初始化部分参考了石恒瑞同志的串口点灯,在此对其表示感谢。部分关键程序如下。
串口0初始化
void uart0_init(void)
{
SFRPAGE = 0x00;
//初始化IO口
P0MDOUT = P0MDOUT | P0MDOUT_B4__PUSH_PULL | P0MDOUT_B5__OPEN_DRAIN;
P0MDIN = P0MDIN | P0MDIN_B4__DIGITAL | P0MDIN_B5__DIGITAL;
SCON0 |= SCON0_REN__RECEIVE_ENABLED;//接收使能
IE = IE | IE_ES0__ENABLED; //使能中断
XBR0 = XBR0 | XBR0_URT0E__ENABLED;
}串口0中断
uint8_t byte_flag=0; //用来表示是否接收到一个字节数据,这样可以在主函数
//里对定时器采用重新赋值处理
SI_INTERRUPT(UART0_ISR, UART0_IRQn)
{
uint8_t res;
if (SCON0_TI == 1) // Check if transmit flag is set
{
tx_flag = 1;
SCON0_TI = 0; // Clear interrupt flag
}
if (SCON0_RI == 1)
{
byte_flag=1;
SCON0_RI = 0; //清0中断标志位
res=SBUF0;
recive_buff[rec_sta]=res;
rec_sta++;
if(rec_sta==RX_BUF_MAX_LEN) rec_sta=0;//如果接受的数据大于最大长度则重新接收
//定时器0值重载
}
}定时器0初始化用于监测数据帧是否结束
//用定时器0来进行接收帧结束判断
void timer0_uart_init(void)
{
uint8_t TCON_save;
TIME0_MS=TIME0_MS; //定时器0的初始化中断时间
TCON_save = TCON;
SFRPAGE = 0x00;
TCON &= ~TCON_TR0__BMASK & ~TCON_TR1__BMASK;
TH0 = (65536-(uint16_t)(0.001*TIME0_MS*255208))/256;
TL0 = (65536-(uint16_t)(0.001*TIME0_MS*255208))%256;
TCON |= (TCON_save & TCON_TR0__BMASK) | (TCON_save & TCON_TR1__BMASK);
CKCON0=CKCON0|CKCON0_SCA__SYSCLK_DIV_12| CKCON0_T0M__PRESCALE; //系统时钟的12分频
TMOD=TMOD|TMOD_T0M__MODE1|TMOD_CT0__TIMER|TMOD_GATE0__DISABLED; //计数器模式,16bit
TCON |= TCON_TR0__RUN;
TCON_TR0 = 0; //初始化时关闭定时器
IE = IE|IE_ET0__ENABLED;
}定时器中断用来表示一帧数据是否结束
SI_INTERRUPT(TIMER0_ISR, TIMER0_IRQn)
{
//count0_ms++;
//TCON_TR0 = 0; // Stop Timer 0
//TH0 = (uint16_t)(65536-0.001*TIME0_MS*255208)/256;
//TL0 = (uint16_t)(65536-0.001*TIME0_MS*255208)%256;
//TCON_TR0 = 1; // Start Timer 0
TCON_TR0 = 0; // Stop Timer 0
rx_flag=1; //接收完成标志
}对定时器0计数寄存器重新装载放在主函数里执行,在STM32或者51里都是放在串口接收中断里执行,但是在该MCU中不行,如果这样操作会造成接收不完整,放在主函数里就可以了。这里先不具体追究具体原因了。
while (1)
{
if(byte_flag==1)
{
byte_flag=0;
SFRPAGE = 0x00;
TH0 = (65536-(uint16_t)(0.001*TIME0_MS*255208))/256;
TL0 = (65536-(uint16_t)(0.001*TIME0_MS*255208))%256;
//打开定时器0,这样就会进行计数,当定时器中断发生的时候就表示一帧数据接收完毕
TCON_TR0 = 1; // Start Timer 0
SFRPAGE = SFRPAGE_save;
}
if(rx_flag)
{
send_data0(recive_buff,rec_sta);
rec_sta=0; //清0以便下一次接收
LED0=!LED0;
rx_flag=0;
}
}因为后面需要用到OLED屏幕显示,这里就开始了整合之路,没有新东西。就像变形金刚里的大力神一样进行组合变形。只是这里遇到一个问题,还是搞不明白,在程序烧录进去后功能没问题。重新上电后需要重启才开始运行。很费解。工程文件在这里 UART_OLED.rar
串口1的调试
该MCU共有2个串口,即串口0和串口1.这两个串口在结构上是不同的,因此在使用方法上也是不同。首先在结构上,串口0需要定时器来为其产生波特率,而串口1则内部有自己的定时器可以直接自己产生波特率。串口0内部没有FIFO,而串口1拥有发送和接收FIFO,可以提高使用效率。除此之外,串口1在发送和接收数据时数据格式除了正常串口的起始位,奇偶校验位,停止位等外,还可以配置extra bit。如果配置了extra bit则可以不用FIFO。不过为了方便使用,这里调试还是采用FIFO,不配置extra bit。
关于FIFO
发送和接收FIFO都可以触发中断,对于发送FIFO,寄存器TXTH充当FIFO数据的最低标准,,并且当FIFO中的字节数小于或等于TXTH值时,将设置TXRQ标志。例如,如果TXTH字段配置为1,则在传输FIFO中剩余0或1个字节发送时,TXRQ将被设置。并且寄存器UART1FCT当发送结束后,会自动变成0,该寄存器用来指示发送FIFO中有多少个字节的数据
对于接收FIFO,RXTH充当FIFO数据的高水位线,并且RXRQ标志将在FIFO中的字节数大于RXTH值时设置。例如,如果RXTH字段配置为0,则RXRQ将在接收FIFO中至少有一个字节时设置。同样,当数据读取后,UART1FCT用来指示接收FIFO中有多少个数据的值会自动变成0.
串口初始化
查看官方例程,对于UART1的BSP驱动包含很多函数,而在本工程中用到串口1仅仅用作发送功能,因此这里只使用串口的发送功能,发送依旧采用中断发送,如果不采用中断,如果一次性发送多个数据,接收端只能收到开始和结束的数据。
串口的IO口初始化
该MCU的片内外设对于IO的映射比较灵活,当需要将片内外设映射到外部IO口时,需要根据片内外设的优先级映射到从0.0开始的IO。其中UART0如果使用,则固定映射到0.4和0.5,其他外设则按照如图1所示进行映射。如果想将某一外设移动其按照优先级本应该映射到的IO口,则只需要将其本应该映射到的IO口skip就可以。例如,如果只使用UART1的功能,则其应该映射到0.4和0.5,如果想映射到0.6和0.7,则只需要将P1SKIP寄存器的第4位和第5位设置成skip即可。

IO口初始化代码如下:
void uart1_io_set(void)
{
//将串口1映射到0.6,0.7
P0MDOUT = P0MDOUT_B0__OPEN_DRAIN | P0MDOUT_B1__OPEN_DRAIN
| P0MDOUT_B2__OPEN_DRAIN | P0MDOUT_B3__OPEN_DRAIN
| P0MDOUT_B4__PUSH_PULL | P0MDOUT_B5__OPEN_DRAIN
| P0MDOUT_B6__PUSH_PULL | P0MDOUT_B7__OPEN_DRAIN;
//0.6.0.7设置为数字模式
P0MDIN = P0MDIN_B0__ANALOG | P0MDIN_B1__ANALOG | P0MDIN_B2__ANALOG
| P0MDIN_B3__ANALOG | P0MDIN_B4__DIGITAL | P0MDIN_B5__DIGITAL
| P0MDIN_B6__DIGITAL | P0MDIN_B7__DIGITAL;
//将P0.4 P0.5跳过
P0SKIP = P0SKIP_B0__SKIPPED | P0SKIP_B1__SKIPPED | P0SKIP_B2__SKIPPED
| P0SKIP_B3__SKIPPED | P0SKIP_B4__SKIPPED
| P0SKIP_B5__SKIPPED | P0SKIP_B6__NOT_SKIPPED
| P0SKIP_B7__NOT_SKIPPED;
}对于波特率,以及中断的配置
这里将波特率设置为115200,注意前提是系统时钟为内部时钟(24.5MHZ)的8分频,这里需要配置串口1内部自己的定时器的装载值。为了发送数据方便,这里对FIOF的设置阈值均为0.具体程序如下。
void uart1_param_init(void)
{
SFRPAGE = 0x20;
//使能内部定时器
SBCON1 = SBCON1_BREN__ENABLED | SBCON1_BPS__DIV_BY_1;
//设置FIFO阈值为0
UART1FCN0 = UART1FCN0_RXTH__ZERO | UART1FCN0_TXTH__ZERO
| UART1FCN0_RFRQE__ENABLED | UART1FCN0_TFRQE__DISABLED;
//设置内部定时器装载值
SBRLH1 = (0xFF << SBRLH1_BRH__SHIFT);
SBRLL1 = (0xF3 << SBRLL1_BRL__SHIFT);
//使能接收
SCON1 |= SCON1_REN__RECEIVE_ENABLED;
UART1PCF=UART1PCF_RXSEL__CROSSBAR;
SMOD1=SMOD1|SMOD1_SDL__8_BITS|SMOD1_SBL__SHORT|SMOD1_PE__PARITY_DISABLED | SMOD1_XBE__DISABLED|SMOD1_SPT__ODD_PARITY|SMOD1_MCE__MULTI_DISABLED;
UART1FCN1 &= ~(UART1FCN1_RIE__BMASK | UART1FCN1_TIE__BMASK);
//接收空闲中断为16个时钟周期
UART1FCN1 |= UART1FCN1_RXTO__TIMEOUT_16;
}
//对发送FIFO进行设置
void UART1_initTxFifo(void)
{
SFRPAGE = 0x20;
UART1FCN0 &= ~(UART1FCN0_TFRQE__BMASK
| UART1FCN0_TFLSH__BMASK
| UART1FCN0_TXTH__FMASK
| UART1FCN0_TFRQE__BMASK);
//禁止发送中断
UART1FCN0 |= (UART1FCN0_TXTH__ZERO | UART1FCN0_TFRQE__DISABLED);
UART1FCN1 &= ~(UART1FCN1_TFRQ__BMASK
| UART1FCN1_TXHOLD__BMASK
| UART1FCN1_TXNF__BMASK
| UART1FCN1_TIE__BMASK);
UART1FCN1 |= (UART1FCN1_TFRQ__SET
| UART1FCN1_TXHOLD__CONTINUE
| UART1FCN1_TIE__DISABLED);
}发送数据代码,利用UART1_wrteBuffer函数来使能发送中断
void UART1_writeBuffer(uint8_t buffer, uint8_t length)
{
// Initialize internal data
txBuffer = buffer;
txRemaining = length;
// Enable tx fifo interrupts to kick off transfer
UART1FCN0 |= UART1FCN0_TFRQE__ENABLED;
}
SI_INTERRUPT(UART1_ISR, UART1_IRQn)
{
if ((UART1FCN1 & UART1FCN1_TFRQ__BMASK) && (UART1FCN0 & UART1FCN0_TFRQE__ENABLED))
{
// Write bytes as long as the tx fifo is not full and there
// is room in the tx buffer
while (txRemaining && (UART1FCN1 & UART1FCN1_TXNF__NOT_FULL))
{
SBUF1 = txBuffer;
--txRemaining;
}
}
}源工程文件点击这里UART1_SEND.rar
因为系统需要OLED屏幕作为显示,所以这里先将OLED屏幕显示点亮来作为第二个任务。点灯的程序参考shihengrui的帖子成功点亮,再此对其表示感谢。
OLED屏幕选择的是0.96英寸的OLED屏幕,采用的通信方式是IIC,这里依旧选择软件模拟的方式来进行点亮显示。
话不多说,直接上代码,只不过在进行存字库字符的时候,由于该板子的内存比较小,这里将字库的数组前都加上“data”关键字,以将数据放到data段,否则会报错。还有一个问题需要注意,采用Simplicity Studio编译器进行编译程序,需要添加licence,否则会提示超出版本程序大小限制
OLED头文件
#ifndef INC_OLED_H_ #define INC_OLED_H_ #include <SI_EFM8BB52_Register_Enums.h> #define OLED_CMD 0 //写命令 #define OLED_DATA 1 //写数据 #define OLED_MODE 0 //定义端口 SI_SBIT(OLED_SCL,SFR_P1,5); SI_SBIT(OLED_SDA,SFR_P1,6); #define OLED_SCLK_Clr() OLED_SCL=0 #define OLED_SCLK_Set() OLED_SCL=1 #define OLED_SDIN_Clr() OLED_SDA=0 #define OLED_SDIN_Set() OLED_SDA=1 #define SIZE 16 #define XLevelL 0x02 #define XLevelH 0x10 #define Max_Column 128 #define Max_Row 64 #define Brightness 0xFF #define X_WIDTH 128 #define Y_WIDTH 64 #define u8 uint8_t #define u32 uint32_t #define u16 uint16_t //OLED控制用函数 void OLED_WR_Byte(unsigned dat,unsigned cmd); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Init(void); void OLED_Clear(void); void OLED_DrawPoint(u8 x,u8 y,u8 t); void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot); void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size); void OLED_ShowString(u8 x,u8 y, u8 *p,u8 Char_Size); void OLED_Set_Pos(unsigned char x, unsigned char y); void OLED_ShowCHinese(u8 x,u8 y,u8 no); void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]); void Delay_50ms(unsigned int Del_50ms); void Delay_1ms(unsigned int Del_1ms); void fill_picture(unsigned char fill_Data); void Picture(); void IIC_Start(); void IIC_Stop(); void Write_IIC_Command(unsigned char IIC_Command); void Write_IIC_Data(unsigned char IIC_Data); void Write_IIC_Byte(unsigned char IIC_Byte); void IIC_Wait_Ack(); #endif /* INC_OLED_H_ */
OLED源文件
/*
* oled.c
*
* Created on: 2021年12月20日
* Author: liubinbin
*/
#include "oled.h"
#include "oledfont.h"
#include <device.h>
/**
* @name:IIC_Start()
* @msg: I2C发送起始信号
* @param {*}
* @return {*}
*/
void IIC_Start()
{
OLED_SCLK_Set() ;
NOP();NOP();NOP(); //等待延时
OLED_SDIN_Set();
NOP();NOP();NOP(); //等待延时
OLED_SDIN_Clr();
NOP();NOP();NOP(); //等待延时
OLED_SCLK_Clr();
NOP();NOP();NOP(); //等待延时
}
/**
* @name: void IIC_Stop()
* @msg: IIC停止信号
* @param {*}
* @return {*}
*/
void IIC_Stop()
{
OLED_SCLK_Set() ;
NOP();NOP();NOP(); //等待延时
OLED_SDIN_Clr();
NOP();NOP();NOP(); //等待延时
OLED_SDIN_Set();
NOP();NOP();NOP(); //等待延时
}
/**
* @name:void IIC_Wait_Ack()
* @msg: 等待应答
* @param {*}
* @return {*}
*/
void IIC_Wait_Ack()
{
OLED_SCLK_Set() ;
NOP();NOP();NOP();
OLED_SCLK_Clr();
NOP();NOP();NOP();
}
/**
* @name: Write_IIC_Byte(unsigned char IIC_Byte)
* @msg: IIC写一个字节数据
* @param {unsigned char} IIC_Byte
* @return {*}
*/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
unsigned char i;
unsigned char m,da;
da=IIC_Byte;
NOP();NOP();NOP();
OLED_SCLK_Clr();
NOP();NOP();NOP();
for(i=0;i<8;i++)
{
m=da;
OLED_SCLK_Clr();
m=m&0x80;
if(m==0x80)
{OLED_SDIN_Set();}
else OLED_SDIN_Clr();
NOP();NOP();NOP();
da=da<<1;
OLED_SCLK_Set();
NOP();NOP();NOP();
OLED_SCLK_Clr();
NOP();NOP();NOP();
}
}
/**
* @name:Write_IIC_Command(unsigned char IIC_Command)
* @msg: IIC写一个命令
* @param {unsigned char} IIC_Command
* @return {*}
*/
void Write_IIC_Command(unsigned char IIC_Command)
{
IIC_Start();
Write_IIC_Byte(0x78);
IIC_Wait_Ack();
Write_IIC_Byte(0x00);
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Command);
IIC_Wait_Ack();
IIC_Stop();
}
/**
* @name: Write_IIC_Data(unsigned char IIC_Data)
* @msg: IIC写一个数据
* @param {unsigned char} IIC_Data
* @return {*}
*/
void Write_IIC_Data(unsigned char IIC_Data)
{
IIC_Start();
Write_IIC_Byte(0x78); //D/C#=0; R/W#=0
IIC_Wait_Ack();
Write_IIC_Byte(0x40); //write data
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Data);
IIC_Wait_Ack();
IIC_Stop();
}
/**
* @name: OLED_WR_Byte(unsigned dat,unsigned cmd)
* @msg: OLED写一个字节
* @param {unsigned} dat 写数据
* @param {unsigned} cmd 写命令
* @return {*}
*/
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
if(cmd)
{
Write_IIC_Data(dat);
}
else
{
Write_IIC_Command(dat);
}
}
/**
* @name:fill_picture(unsigned char fill_Data)
* @msg: 填充图片
* @param {unsigned char} fill_Data
* @return {*}
*/
void fill_picture(unsigned char fill_Data)
{
unsigned char m,n;
for(m=0;m<8;m++)
{
OLED_WR_Byte(0xb0+m,0); //page0-page1
OLED_WR_Byte(0x00,0); //low column start address
OLED_WR_Byte(0x10,0); //high column start address
for(n=0;n<128;n++)
{
OLED_WR_Byte(fill_Data,1);
}
}
}
/**
* @name:OLED_Set_Pos(unsigned char x, unsigned char y)
* @msg: 设置坐标
* @param {unsigned char} x 横坐标
* @param {unsigned char} y 纵坐标
* @return {*}
*/
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
/**
* @name: OLED_Display_On(void)
* @msg: 开启OLED屏幕显示
* @param {*}
* @return {*}
*/
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
/**
* @name: OLED_Display_Off(void)
* @msg: 关闭OLED显示
* @param {*}
* @return {*}
*/
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
/**
* @name: OLED_Clear(void)
* @msg: 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样
* @param {*}
* @return {*}
*/
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); //更新显示
}
}
/**
* @name:OLED_On(void)
* @msg: 打开OLED显示
* @param {*}
* @return {*}
*/
void OLED_On(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA); //更新显示
}
}
/**
* @name:OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
* @msg: 显示字符
* @param {u8} x 起始横坐标0-128
* @param {u8} y 起始纵坐标0-6
* @param {u8} chr 显示字符
* @param {u8} Char_Size 大小12/16
* @return {*}
*/
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移后的值
if(x>Max_Column-1){x=0;y=y+2;}
if(Char_Size ==16)
{
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
else {
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
/**
* @name: oled_pow(u8 m,u8 n)
* @msg: m的n次方
* @param {u8} m
* @param {u8} n
* @return {*}
*/
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
/**
* @name: OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
* @msg: 显示数字
* @param {u8} x 起始横坐标0-128
* @param {u8} y 起始纵坐标0-6
* @param {u32} num 数字值
* @param {u8} len 位数
* @param {u8} size2 大小12/16
* @return {*}
*/
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t<len;t++)
{
temp=(num/oled_pow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
}
}
/**
* @name: OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
* @msg: 显示字符串
* @param {u8} x起始横坐标0-128
* @param {u8} y起始纵坐标0-6
* @param {u8} *chr 字符串指针
* @param {u8} Char_Size 大小12/16
* @return {*}
*/
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{
unsigned char j=0;
while (chr[j]!='\0')
{ OLED_ShowChar(x,y,chr[j],Char_Size);
x+=8;
if(x>120){x=0;y+=2;}
j++;
}
}
/**
* @name: OLED_Init(void)
* @msg: 初始化OLED屏幕
* @param {*}
* @return {*}
*/
void OLED_Init(void)
{
uint8_t SFRPAGE_save = SFRPAGE;
//init oled io
P1MDIN = P1MDIN|P1MDIN_B5__DIGITAL|P1MDIN_B6__DIGITAL;
P1MDOUT = P1MDOUT|P1MDOUT_B5__PUSH_PULL|P1MDOUT_B6__PUSH_PULL;
XBR2 = XBR2|XBR2_XBARE__ENABLED;
SFRPAGE = SFRPAGE_save;
OLED_WR_Byte(0xAE,OLED_CMD);//--display off
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address
OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
OLED_WR_Byte(0x81,OLED_CMD); // contract control
OLED_WR_Byte(0xFF,OLED_CMD);//--128
OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
OLED_WR_Byte(0x00,OLED_CMD);//
OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
OLED_WR_Byte(0x80,OLED_CMD);//
OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
OLED_WR_Byte(0x05,OLED_CMD);//
OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
OLED_WR_Byte(0xF1,OLED_CMD);//
OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
OLED_WR_Byte(0x12,OLED_CMD);//
OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
OLED_WR_Byte(0x30,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
OLED_WR_Byte(0x14,OLED_CMD);//
OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}测试main函数
int main (void)
{
u8 t1;
u8 t2;
sys_init();
OLED_Init();
IE_EA=0;
LED=0;
t1=0;
t2=' ';
OLED_Clear();
OLED_ShowChar(0,0,'E',16);
OLED_ShowChar(8,0,'E',16);
OLED_ShowChar(16,0,'P',16);
OLED_ShowChar(24,0,'W',16);
OLED_ShowChar(32,0,'.',16);
OLED_ShowChar(40,0,'C',16);
OLED_ShowChar(48,0,'O',16);
OLED_ShowChar(56,0,'M',16);
OLED_ShowString(64,0,".CN",16);
OLED_ShowString(0,2,"NUMBER:",16);
OLED_ShowString(0,4,"ASCALL:",16);
while (1)
{
OLED_ShowNum(56,2,t1,3,16);
OLED_ShowChar(56,4,t2,16);
LED=!LED;
t1++;
if(t1>255) t1=0;
t2++;
if(t2>'~') t2=' ';
delay(0XFFF);
delay(0XFFF);
} // Spin forever
}串口0采用定时器1来进行产生波特率,定时器1在写入装载值时,由于时钟的问题会产生波特率偏差,在时钟为3062500Hz的情况下,计算写入TH1和TL1的值为13,此时波特率产生实际值为117788,可以看出偏差还是很大,当为9600波特率时,写入TH1和TL1的值为97,此时计算出波特率为9630.偏差比较小。因此在这种情况下我将串口1和串口0的波特率均设置为9600.在测试时要注意,开发板的串口0是通过虚拟串口和其他片外外设进行交互数据,而虚拟串口的波特率是115200,这在手册中有提到,改变不了,因此需要用USB转TTL模块将数据传输到串口助手来查看数据是否对,这个坑坑死人,还不如板载CH340芯片方便。 虚拟串口的介绍如图所示

虚拟串口介绍图
系统的原理图如图所示,原理图首先是电源部分,电源一共包含2部分,一部分是12V转5V部分用来给OLED屏幕,以及串口转485模块供电。还有一部分为5V转3.3V部分用来给MCU供电。然后是MCU部分,MCU部门这里仅引出必要的管脚,TTL转485模块为购置的集成串口转485装置,不用增加485的半双工切换引脚,节省MCU的IO口资源;OLED接口为I2C接口的OLED屏幕,用来显示必要的信息,串口1为引出串口,用来和网关通信,实现数据入云的操作。复位按键用来手动给MCU复位,LED用来作为一些必要的信息指示,调试接口为参考官网原理图的设计的调试接口。系统采用12V供电,

系统原理图
完结撒花,花花花
源码在这里 Terminator.rar 演示视频在这里演示视频
经过几天的奋战,把试用版的功能全部实现了,再絮叨下所完成的内容,用EFM8BB52完成传感器数据的提取。将数据在OLED屏幕上进行显示,然后定时刷新OLED屏幕的数据。为了将数据入网,这里增加了自制网关,网关和EFM8BB52之间采用串口通信,网关数据在接收到数据后,将数据进行处理转换成json字符串,通过http协议上传到服务器。首先EFM8BB52显示结果图如图1所示,服务器端数据网页显示如图2所示(图2后截得图,数据和图1不一样,懒得把收起来的开发板重新开机了)。

图1 OLED采集结果图

图2 服务器网页查看图
现在对系统实物图进行详细介绍,EFM8BB52搭建的系统实物图如图3所示,图3中的电源模块为12转5V电源模块,用来给整个系统包括传感器,开发板,以及网关供电,这里用航空插头作为连接器。由于传感器为485接口,所以增加了TTL转485模块用来和传感器通信。OLED屏幕采用IIC通信的OLED。

图3 系统实物图
网关实物图如图4所示,网关主要负责对接收到的串口数据进行整合处理,处理成json字符串格式,主要是http协议传输的是字符串数据。EFM8BB52为8位MCU处理json转换有点吃力,所以该部分功能放到网关处理,而且EFM8BB52没有网口。数据处理后通过网口和路由器完成入网。监测系统连接实物图如图5所示。

图4 网关实物图

图5 系统连接图
回复
| 有奖活动 | |
|---|---|
| 硬核工程师专属补给计划——填盲盒 | |
| “我踩过的那些坑”主题活动——第002期 | |
| 【EEPW电子工程师创研计划】技术变现通道已开启~ | |
| 发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
| 【EEPW在线】E起听工程师的声音! | |
| 高校联络员开始招募啦!有惊喜!! | |
| 【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
| 送您一块开发板,2025年“我要开发板活动”又开始了! | |
我要赚赏金打赏帖 |
|
|---|---|
| Chaos-nano:专为低资源单片机设计的轻量级协作式异步操作系统(ATMEGA328P轻量级操作系统)—— 详细介绍被打赏¥16元 | |
| FPGA配置被打赏¥10元 | |
| Chaos-nano协作式异步操作系统:赋能MicrochipAVR8位单片机的革新之路被打赏¥15元 | |
| 基于esp32开发时串口工具的注意点被打赏¥24元 | |
| 基于FireBeetle2ESP32-C5开发板的舵机控制被打赏¥20元 | |
| 【分享开发笔记,赚取电动螺丝刀】MAX78000开发板制作的电子相册被打赏¥32元 | |
| 基于FireBeetle2ESP32-C5开发板的超声波测距及显示被打赏¥21元 | |
| FireBeetle2ESP32-C5上RTC电子时钟的实现被打赏¥25元 | |
| 【分享开发笔记,赚取电动螺丝刀】MAX78000开发板读取SD卡被打赏¥23元 | |
| 【S32K3XX】Standby RAM 重启后数据异常问题调查被打赏¥38元 | |
我要赚赏金
