这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【EFM8BB52单片机】室内环境监测物联网系统(完结)

共10条 1/1 1 跳转至

【EFM8BB52单片机】室内环境监测物联网系统(完结)

助工
2021-12-20 10:45:55   被打赏 50 分(兑奖)     打赏

试用项目:室内温湿度度监测终端

很开心能参加这次的EFM8BB52单片机的试用活动,由于毕业在即需要写毕业论文。这次就依托以前做项目剩余物资做个室内环境监测物联网系统。系统的功能框图如图1所示:

1639968299353579.png          

图1 系统功能框图

功能简介:

1 单片机通过485读取传感器数据以获得室内环境信息

2 单片机通过串口将数据发送到网关

3 单片机上的OLED屏幕显示采集到的传感器信息

4 网关将数据通过网线或者4G模块将数据发送到服务器端

5 可以在网页上查看采集的实时数据

调试记录如下

//定时器调试记录

//串口0的调试记录

//串口和OLED屏幕

//串口1的发送调试

//OLED屏幕调试记录

//串口调试加更

//系统原理图

//完结总结




关键词: 单片机     EFM8BB52     温湿度    

助工
2021-12-20 11:06:49   被打赏 100 分(兑奖)     打赏
2楼

增加开箱贴

     快递邮寄到了学校,取的时候站点想要大大的快递箱子,就把快递箱子拆开送给了他们。能展示的就只剩下朴实无华的板子和数据线。如图1所示,图一是收到的物件合影,图2和图3是开发板的反正面,给我的感觉就是开发板很质朴,做工很沉重,简约而不缺实用精神。

1639969383349530.jpg

图1 物件合影

1639969526255876.jpg

图2 开发板正面图

1639969576123317.jpg

图3 开发板反面图


助工
2021-12-23 19:27:45     打赏
3楼
定时器中断(16位模式)

该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-----Timer5

Timer2--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
  }




助工
2021-12-27 10:12:17     打赏
4楼
串口0的调试

      该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;
       }
    }



助工
2021-12-27 10:21:01     打赏
5楼
串口和OLED结合

       因为后面需要用到OLED屏幕显示,这里就开始了整合之路,没有新东西。就像变形金刚里的大力神一样进行组合变形。只是这里遇到一个问题,还是搞不明白,在程序烧录进去后功能没问题。重新上电后需要重启才开始运行。很费解。工程文件在这里 UART_OLED.rar 


助工
2022-01-02 22:38:17     打赏
6楼

串口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口图


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


助工
2022-01-02 22:55:48     打赏
7楼
修改OLED屏幕到这里

     

因为系统需要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
}



助工
2022-01-04 14:52:54     打赏
8楼
串口调试加更

     串口0采用定时器1来进行产生波特率,定时器1在写入装载值时,由于时钟的问题会产生波特率偏差,在时钟为3062500Hz的情况下,计算写入TH1和TL1的值为13,此时波特率产生实际值为117788,可以看出偏差还是很大,当为9600波特率时,写入TH1和TL1的值为97,此时计算出波特率为9630.偏差比较小。因此在这种情况下我将串口1和串口0的波特率均设置为9600.在测试时要注意,开发板的串口0是通过虚拟串口和其他片外外设进行交互数据,而虚拟串口的波特率是115200,这在手册中有提到,改变不了,因此需要用USB转TTL模块将数据传输到串口助手来查看数据是否对,这个坑坑死人,还不如板载CH340芯片方便。 虚拟串口的介绍如图所示

1641279086693261.png

  虚拟串口介绍图



助工
2022-01-04 16:16:24     打赏
9楼
系统原理图详解

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

1641284120986020.png

系统原理图



助工
2022-01-05 20:21:55     打赏
10楼

完结撒花,花花花

     源码在这里 Terminator.rar  演示视频在这里演示视频

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

OLED显示图

1 OLED采集结果图

22.png

2 服务器网页查看图

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

3.png

3 系统实物图

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

4.jpg

4 网关实物图

   

5.png

5 系统连接图

 

 

 

 

 



共10条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]