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

该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; } }
回复
有奖活动 | |
---|---|
“我踩过的那些坑”主题活动——第002期 | |
【EEPW电子工程师创研计划】技术变现通道已开启~ | |
发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
【EEPW在线】E起听工程师的声音! | |
高校联络员开始招募啦!有惊喜!! | |
【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
送您一块开发板,2025年“我要开发板活动”又开始了! | |
打赏了!打赏了!打赏了! |