BLE协议栈实验学习1——认识CC2540

硬件介绍

CC2540简介

CC2540是一款完全兼容8051内核,并集成了2.4GHz射频收发器的无线射频单片机。通过采用BLE(蓝牙低功耗协议)构成一款高性价比、低功耗的SOC(片上系统)解决方案,主要运用于蓝牙低功耗应用,例如可穿戴设备、医疗监测设备等。
CC2540拥有3个不同的存储器访问总线:

  • 特殊功能寄存器(SFR)
  • 数据(DATA)
  • 代码/外部数据(CODE/XDATA)
    CC2540单片机使用单周期访问SFR、DATA和主SRAM。当CC2540处于空闲模式时,可以进入深度睡眠状态,在深度睡眠状态,功耗非常低,可以通过外部中断将CC2540从深度睡眠中唤醒。位于系统核心存储器交叉开关使用SFR总线将CPU、DMA控制器与物理层存储器和所有的外接设备连接起来。
    CC2540的Flash容量可以选择,有128KB、256KB,这就是CC2540单片机的在线可编程非易失性存储器,并且映射到代码和外部数据存储器空间。除了保存程序代码和常量以外,非易失性存储器运行应用程序保存必要的数据,以保证这些数据掉电后不会丢失。
    CC2540引脚分布图如下:
    在这里插入图片描述
    在引脚方面,CC2540支持USB,但是不支持IIC,如果需要使用IIC只能通过引脚模拟。

开发板介绍

只有一个芯片是不能够实现各种复杂的项目的,还需要更多的外围电路和外部资源配合,在本次BLE协议栈实验学习过程中,使用的是江苏学蠡信息科技提供的LPTD006液晶传感器板与核心板组合的方式呈现,如下图所示:
在这里插入图片描述

整个开发板采用直插方式,将核心板、传感器模块与液晶底板进行连接,可以实现一板多用、灵活插拔、快速组合的效果,其中左侧为CC2540核心板,核心板中包括一个陶瓷巴伦、陶瓷天线、一个32MHz晶振以及一个32.768KHz晶振,本核心板可以实现CC2540的休眠以及唤醒操作,因此在本核心板上需要一个32.768KHz晶振。
在这里插入图片描述
下方就是大家比较熟悉的液晶传感器底板了,比较在之前的NB-IOT以及LoRa相关实验中都有介绍到,这里就不再多说。
在这里插入图片描述

CC2540作为单片机的实验介绍

CC2540虽然主要运用于低功耗蓝牙需求场景中,但在这个的前提是,CC2540是一款完全兼容8051内核的单片机,因此想要使用CC2540进行蓝牙方面的操作之前,需要先熟悉CC2540仅仅作为单片机的操作,例如CC2540的UART、ADC、定时器等基础资源。在本实验环节中,学蠡信息科技提供的实验源码都是在无BLE协议栈的状态下进行编程,其目的就是让大家能够把 CC2540 当做一颗普通的单片机使用。
在本实验环节,所使用的IDE为 IAR for 8051 8.10版本即IAR Embedded Workbench,各位可以自行寻找资源下载并安装。
在这里插入图片描述

1. OLED12864显示实验

液晶传感器底板中的OLED显示屏使用IIC方式与BLE核心板进行连接,对应的MCU引脚为P1_2和P1_3,OLED的电路原理图如下所示:
在这里插入图片描述
之前在对CC2540介绍的时候说过,CC2540不支持IIC,需要使用IIC的时候只能使用IO口进行模拟,因此这里的P1_2为IIC的SCL,P1_3为IIC的SDA。
具体实现步骤:

  1. 定义OLED显示所需要的宏
    在这里进行的预定义的优先级要高于在代码中使用“#define”的定义,如果需要使用显示屏,则先保证HAL_LCD可用同时支持LCD,这里为什么是LCD而不是OLED呢,因为在TI提供的官方SmartRF05EB 开发板中使用的是LCD显示屏,而学蠡信息科技的开发板能够兼容SmartRF05EB 开发板,其中只是将LCD更换为OLED可以以更小的面积实现更好的效果,因此需要OLED显示的时候在进行宏预定义的时候需要打开“LCD”。
    不进行这一步,将无法实现效果(显示屏压根不鸟你的)
    在这里插入图片描述

  2. 初始化
    请看下方电路原理图,因为显示屏的电路设计,给显示屏增加了一个三极管开关,如果需要显示屏工作则需要P2_0引脚输出一个高电平来打开,因此初始化工作除了发送对应的启动时序外还需要一个打开显示屏的“开关”的操作,并且该操作需要在第一位。
    在这里插入图片描述
    代码:

    static void OledPortInt( void )
    {	
      //VCC_33 电源开关
      IO_DIR_PORT_PIN1(2, 0, IO_OUT);
      //打开VCC_33 电源
      P2_0 = 1;   
      
      HalLcd_HW_WaitUs(30000);
      
      OLED_SDA_SEL &= ~OLED_SDA_BIT;				//General-purpose I/O
      OLED_SDA_DIR |= OLED_SDA_BIT;					//OutPut
              
      OLED_SCL_SEL &= ~OLED_SCL_BIT;				//General-purpose I/O
      OLED_SCL_DIR |= OLED_SCL_BIT;					//OutPut
        
      OLED_SDA_HIGH();
      OLED_SCL_HIGH();
    }
    
  3. 准备字模,用于显示

    /*全体ASCII 列表:5x7点阵库*/
    const uint8  ascii_table_5x7[95][5]={
      0x00,0x00,0x00,0x00,0x00,//space
      0x00,0x00,0x4f,0x00,0x00,//!
      0x00,0x07,0x00,0x07,0x00,//"
      0x14,0x7f,0x14,0x7f,0x14,//#
      0x24,0x2a,0x7f,0x2a,0x12,//$
      0x23,0x13,0x08,0x64,0x62,//%
      0x36,0x49,0x55,0x22,0x50,//&
      0x00,0x05,0x07,0x00,0x00,//]
      0x00,0x1c,0x22,0x41,0x00,//(
      0x00,0x41,0x22,0x1c,0x00,//)
      0x14,0x08,0x3e,0x08,0x14,//*
      0x08,0x08,0x3e,0x08,0x08,//+
      0x00,0x50,0x30,0x00,0x00,//,
      0x08,0x08,0x08,0x08,0x08,//-
      0x00,0x60,0x60,0x00,0x00,//.
      0x20,0x10,0x08,0x04,0x02,///
      0x3e,0x51,0x49,0x45,0x3e,//0
      0x00,0x42,0x7f,0x40,0x00,//1
      0x42,0x61,0x51,0x49,0x46,//2
      0x21,0x41,0x45,0x4b,0x31,//3
      0x18,0x14,0x12,0x7f,0x10,//4
      0x27,0x45,0x45,0x45,0x39,//5
      0x3c,0x4a,0x49,0x49,0x30,//6
      0x01,0x71,0x09,0x05,0x03,//7
      0x36,0x49,0x49,0x49,0x36,//8
      0x06,0x49,0x49,0x29,0x1e,//9
      0x00,0x36,0x36,0x00,0x00,//:
      0x00,0x56,0x36,0x00,0x00,//;
      0x08,0x14,0x22,0x41,0x00,//<
      0x14,0x14,0x14,0x14,0x14,//=
      0x00,0x41,0x22,0x14,0x08,//>
      0x02,0x01,0x51,0x09,0x06,//?
      0x32,0x49,0x79,0x41,0x3e,//@
      0x7e,0x11,0x11,0x11,0x7e,//A
      0x7f,0x49,0x49,0x49,0x36,//B
      0x3e,0x41,0x41,0x41,0x22,//C
      0x7f,0x41,0x41,0x22,0x1c,//D
      0x7f,0x49,0x49,0x49,0x41,//E
      0x7f,0x09,0x09,0x09,0x01,//F
      0x3e,0x41,0x49,0x49,0x7a,//G
      0x7f,0x08,0x08,0x08,0x7f,//H
      0x00,0x41,0x7f,0x41,0x00,//I
      0x20,0x40,0x41,0x3f,0x01,//J
      0x7f,0x08,0x14,0x22,0x41,//K
      0x7f,0x40,0x40,0x40,0x40,//L
      0x7f,0x02,0x0c,0x02,0x7f,//M
      0x7f,0x04,0x08,0x10,0x7f,//N
      0x3e,0x41,0x41,0x41,0x3e,//O
      0x7f,0x09,0x09,0x09,0x06,//P
      0x3e,0x41,0x51,0x21,0x5e,//Q
      0x7f,0x09,0x19,0x29,0x46,//R
      0x46,0x49,0x49,0x49,0x31,//S
      0x01,0x01,0x7f,0x01,0x01,//T
      0x3f,0x40,0x40,0x40,0x3f,//U
      0x1f,0x20,0x40,0x20,0x1f,//V
      0x3f,0x40,0x38,0x40,0x3f,//W
      0x63,0x14,0x08,0x14,0x63,//X
      0x07,0x08,0x70,0x08,0x07,//Y
      0x61,0x51,0x49,0x45,0x43,//Z
      0x00,0x7f,0x41,0x41,0x00,//[
      0x02,0x04,0x08,0x10,0x20,// 斜杠
      0x00,0x41,0x41,0x7f,0x00,//]
      0x04,0x02,0x01,0x02,0x04,//^
      0x40,0x40,0x40,0x40,0x40,//_
      0x01,0x02,0x04,0x00,0x00,//`
      0x20,0x54,0x54,0x54,0x78,//a
      0x7f,0x48,0x48,0x48,0x30,//b
      0x38,0x44,0x44,0x44,0x44,//c
      0x30,0x48,0x48,0x48,0x7f,//d
      0x38,0x54,0x54,0x54,0x58,//e
      0x00,0x08,0x7e,0x09,0x02,//f
      0x48,0x54,0x54,0x54,0x3c,//g
      0x7f,0x08,0x08,0x08,0x70,//h
      0x00,0x00,0x7a,0x00,0x00,//i
      0x20,0x40,0x40,0x3d,0x00,//j
      0x7f,0x20,0x28,0x44,0x00,//k
      0x00,0x41,0x7f,0x40,0x00,//l
      0x7c,0x04,0x38,0x04,0x7c,//m
      0x7c,0x08,0x04,0x04,0x78,//n
      0x38,0x44,0x44,0x44,0x38,//o
      0x7c,0x14,0x14,0x14,0x08,//p
      0x08,0x14,0x14,0x14,0x7c,//q
      0x7c,0x08,0x04,0x04,0x08,//r
      0x48,0x54,0x54,0x54,0x24,//s
      0x04,0x04,0x3f,0x44,0x24,//t
      0x3c,0x40,0x40,0x40,0x3c,//u
      0x1c,0x20,0x40,0x20,0x1c,//v
      0x3c,0x40,0x30,0x40,0x3c,//w
      0x44,0x28,0x10,0x28,0x44,//x
      0x04,0x48,0x30,0x08,0x04,//y
      0x44,0x64,0x54,0x4c,0x44,//z
      0x08,0x36,0x41,0x41,0x00,//{
      0x00,0x00,0x77,0x00,0x00,//|
      0x00,0x41,0x41,0x36,0x08,//}
      0x04,0x02,0x02,0x02,0x01,//~
    };
    
  4. 模拟IIC相关功能函数
    因为CC2540并不支持IIC,因此只能通过IO口的高低电平输出进行模拟,因此IIC操作显示屏的各项操作都写成一个个功能函数。

    /**********************************************
    //IIC Start
    **********************************************/
    void IIC_Start(void)
    {  
      OLED_SCLK_Set() ;
      OLED_SDIN_Set();
      OLED_SDIN_Clr();
      OLED_SCLK_Clr();
    }
    
    /**********************************************
    //IIC Stop
    **********************************************/
    void IIC_Stop(void)
    {
      OLED_SCLK_Set() ;
      OLED_SDIN_Clr();
      OLED_SDIN_Set();  
    }
    
    void IIC_Wait_Ack(void)
    {
      OLED_SCLK_Set() ;
      OLED_SCLK_Clr();
    }
    /**********************************************
    // IIC Write byte
    **********************************************/
    
    void Write_IIC_Byte(uint8 IIC_Byte)
    {
      uint8 i;
      uint8 m,da;
      da=IIC_Byte;
      OLED_SCLK_Clr();
      for(i=0;i<8;i++)		
      {
        m=da;
        //	OLED_SCLK_Clr();
        m=m&0x80;
        if(m==0x80)
        {OLED_SDIN_Set();}
        else OLED_SDIN_Clr();
        da=da<<1;
        OLED_SCLK_Set();
        OLED_SCLK_Clr();
      }   
    }
    /**********************************************
    // IIC Write Command
    **********************************************/
    void Write_IIC_Command(uint8 IIC_Command)
    {
      IIC_Start();
      Write_IIC_Byte(0x78);            //Slave address,SA0=0
      IIC_Wait_Ack();	
      Write_IIC_Byte(0x00);			//write command
      IIC_Wait_Ack();	
      Write_IIC_Byte(IIC_Command); 
      IIC_Wait_Ack();	
      IIC_Stop();
    }
    /**********************************************
    // IIC Write Data
    **********************************************/
    void Write_IIC_Data(uint8 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();
    }
    void OLED_WR_Byte(uint8 dat,uint8 cmd)
    {
      if(cmd)
      {    
        Write_IIC_Data(dat);    
      }
      else 
      {
        Write_IIC_Command(dat);    
      }  
    }
    
    void OLED_Set_Pos(uint8 x, uint8 y) 
    { 	
      OLED_WR_Byte(0xb0+y,OLED_CMD);
      OLED_WR_Byte((((x+2)&0xf0)>>4)|0x10,OLED_CMD);
      OLED_WR_Byte(((x+2)&0x0f),OLED_CMD); 
    } 
    
    void OLED_ShowChar(uint8 x,uint8 y,uint8 chr)
    {      	
      uint8 c=0,i=0;	
      c=chr-' ';//得到偏移后的值			
      if(x>Max_Column-1){x=0;y=y+2;}
      
      OLED_Set_Pos(x,y);	
      for(i=0;i<8;i++)
        OLED_WR_Byte(ascii_table_8x16[c*16+i],OLED_DATA);
      OLED_Set_Pos(x,y+1);
      for(i=0;i<8;i++)
        OLED_WR_Byte(ascii_table_8x16[c*16+i+8],OLED_DATA);
    }
    
    void OLED_Clear(void)  
    {  
      uint8 i,n;		    
      for(i=0;i<8;i++)  
      {  
        OLED_WR_Byte (0xb0+i,OLED_CMD);    
        OLED_WR_Byte (0x02,OLED_CMD);      
        OLED_WR_Byte (0x10,OLED_CMD);      
        for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 
      } 
    }
    
  5. 主函数调用
    最后就是在main函数中进行调用操作了,现实的效果为"JiangSuXueLi",同时下方会有一个数字不断自增。
    在这里插入图片描述

主函数代码如下:
```c
int main()
{
uint8 value[20];
uint32 count=0;
HAL_BOARD_INIT();
HalLcd_HW_Init();
HalLcd_HW_WriteLine(1,“JiangSuXueLi”);

  while(1){
    IntToStr(value,count);
    HalLcd_HW_WriteLine(2,(char *)value);
    HalHW_WaitMS(1000);
    count++;
  }
}
```

2. 定时器实验

在CC2540中一共有四个定时器,其中Timer1和Timer2为16位定时器,Timer3和Timer4为8位定时器,其中Timer2是射频专用定时器。在本实验中使用Timer1进行实验。
具体实现步骤:

  1. 预定义宏打开显示屏并对显示屏进行初始化
    因为本实验依然需要用到OLED显示屏,所以第一步还是对OLED的相关操作,原理图与相关代码就直接参考OLED显示实验中的即可

  2. 初始化Timer1
    配置Timer1为128分频、自动重装模式,代码中的BV为位操作

    void InitTimer1()
    {
      T1CTL |= BV(2) | BV(3); //128分频,16MHz 128分频后,频率是16M/128==125000,
      /*
      自动重装0x0000到0xFFFF循环,总共循环65536次,每次耗时1/125000秒,
      这样65536/125000=0.524288秒,也就是说没产生一次中断flag,就是0.5秒左右*/
      T1CTL |= BV(0); 
    }
    
  3. main函数调用
    在main函数中,每触发一次中断就进行数字加一操作,并在显示屏第二行显示,时间间隔为0.5s
    显示效果如下:
    在这里插入图片描述

    int main()
    {
      uint8 value[20];
      uint32 count=0;
      HAL_BOARD_INIT();
      HalLcd_HW_Init();
      InitTimer1();
      HalLcd_HW_WriteLine(1,"JiangSuXueLi");
     
      while(1)
      {
        if(IRCON & BV(1))//Timer1 flag
        { 
          IRCON=0;//清除标记
          IntToStr(value,count);
          HalLcd_HW_WriteLine(2,(char *)value);
          count++;    
        }  
      }
    }
    

3. 串口通信实验

CC2540一共拥有两个串口:UART0 和UART1,UART0 和UART1都是串行通信接口,都可以分别运行于异步UART模式或者同步SPI模式,两个串口在功能上是一样的,可以通过设置在单独的IO口上。
UART0 对应的IO引脚关系:

  • P0_2 : RX
  • P0_3:TX

UART1 对应的IO引脚关系:

  • P0_5 : RX
  • P0_4:TX

CC2540配置串口的步骤:

  1. 配置IO,在本次实验中使用的是UART0,因此使用P0_2和P0_3
  2. 配置串口0的控制和状态寄存器
  3. 配置串口0的波特率
    波特率的计算公式:
    在这里插入图片描述

UART0的相关寄存器如下:
在这里插入图片描述
具体操作步骤:
1. 配置串口寄存器

void InitUart(void)
{ 
	  P0SEL |= BV(2) | BV(3);//配置P0.2和P0.3为外设,非GPIO
	  U0CSR |= BV(7); //配置当前为UART,非SPI
	  U0GCR |= 11; //根据上述波特率设置表格设置115200波特率
	  U0BAUD |= 216;// 根据上述波特率设置表格设置115200波特率
	  UTX0IF = 0;//位寄存器,直接操作,清除中断标志
	  
	  U0CSR |= BV(6);//允许接收数据
	  IEN0 |= BV(2);//打开接收中断
	  EA=1;//打开总中断  	
}

2.串口发送数据

void UartSendString(int8 *Data, uint16 len)
{
    uint16 i;
    
    for(i=0; i<len; i++)
    {
        U0DBUF = *Data++;
        while(UTX0IF == 0);
        UTX0IF = 0;
    }
}
void UartSendByte(int8 byte)
{
  U0DBUF = byte;
  while(UTX0IF == 0);
  UTX0IF = 0;
}

3.串口接收数据
当串口0触发接收中断后,直接将数据发送回去,一次接收一个字节

#pragma vector = URX0_VECTOR  
__interrupt void UART0_ISR(void)  
{  
  URX0IF = 0;       // 清中断标志  ;  
  UartSendByte(U0DBUF); //收到后立即发送出去  
}

4.main函数调用

void main(void)
{	
  CLKCONCMD &= ~0x40;               //设置系统时钟源为32MHZ晶振
  while(CLKCONSTA & 0x40);          //等待晶振稳定
  CLKCONCMD &= ~0x47;               //设置系统主时钟频率为32MHZ   
  
  InitUart();                       //调置串口相关寄存器
  
  while(1)
  {
    
  }
}

最终实现效果:
在进行串口通信的时候务必要将拨码开关K3拨到右侧,不然串口助手会没有内容显示。
在这里插入图片描述
接下来我们在串口调试助手中发送什么就会接收到什么数据。
在这里插入图片描述

4. 睡眠定时器唤醒实验

CC2540一共提供了4种电源工作模式:

  • 全速模式:高频晶振 32M 和低频晶振 32.768K 全部工作。芯片正常工作
  • PM1:高频晶振 32M 关闭,低频晶振 32.768K 工作。数字电路供电正常开启
  • PM2:高频晶振 32M 关闭,低频晶振 32.768K 工作。数字电路供电关闭。
  • PM3:晶振全部关闭,数字电路供电关闭,系统只能通过复位或者中断唤醒。该模式功耗最低。

在本实验中,采用PM2工作模式,并使用SLEEPTIMER睡眠定时器实现5秒休眠。
与实现睡眠唤醒有关的寄存器如下:
在这里插入图片描述
同时还需要睡眠定时器中断(ST0-ST3)对睡眠时间进行控制,需要注意的是读取睡眠定时器的当前计数值,顺序必须遵循:读ST0 →读ST1 →读ST2。而写入睡眠定时器的比较值,顺序必须遵循:写ST2 →写ST1 →写ST0。当比较值和计数值相同的时候触发中断。
具体实现步骤:

  1. 配置休眠定时寄存器

    void InitSleepTimer(void) 
    { 
      ST2 = 0X00; 
      ST1 = 0X0F; 
      ST0 = 0X0F; 
      EA = 1;     //开中断 
      STIE = 1;   //睡眠定时器中断使能 0: 中断禁止     1: 中断使能
      STIF = 0;   //睡眠定时器中断标志 0: 无中断未决   1: 中断未决
    }
    
  2. 配置休眠时间

    void SetSleepTime(uint32 sec) 
    { 
      uint32 sleepTimer = 0; 
      //获取当前计数
      sleepTimer |= ST0; 
      sleepTimer |= (uint32)ST1 <<  8; 
      sleepTimer |= (uint32)ST2 << 16; 
      sleepTimer += ((uint32)sec * (uint32)32768); 
      //设置新的比较数值,计数到达后,产生中断。注意增加的计数一定要大于5
      ST2 = (uint8)(sleepTimer >> 16); 
      ST1 = (uint8)(sleepTimer >> 8); 
      ST0 = (uint8) sleepTimer; 
    }
    
  3. 工作模式设置功能函数

    void PowerMode(uint8 mode) 
    { 
      if(mode>0 && mode < 4) 
      {  
        SLEEPCMD |= mode;    //设置系统睡眠模式 
        PCON |= BV(0);         //进入PowerMode睡眠模式
      }else 
        PCON &= ~BV(0);         //从PowerMode恢复
    }
    
  4. main函数调用
    在main函数中开始3S计时,计时结束后关闭屏幕显示,然后设置睡眠时间为5秒,5秒后系统自动唤醒,同时工作模式变为PM2。

    void main(void)
    {   
      uint8 i=0;   
      
      HAL_BOARD_INIT();  
      InitSleepTimer();        //初始化休眠定时器  
      
      while(1)
      {   
        HalLcd_HW_Init();
        HalLcd_HW_WriteLine(1,"time down count start");
        
        for (i=0; i<3; i++)  //倒计时3次提醒用户将进入睡眠模式
        {
          if(i==0)
            HalLcd_HW_WriteLine(2,"3");
          else if(i==1)
            HalLcd_HW_WriteLine(2,"2");
          else if(i==2)
            HalLcd_HW_WriteLine(2,"1");  
          
          DelayMS(1000);
        } 
        
        //熄灭lcd
        P1DIR &= ~(0x01<<(2));
        P1DIR &= ~(0x01<<(3));    
        P2_0 = 0;
        
        /*
        设置睡眠时间,睡眠5秒后唤醒系统,系统一旦进入睡眠模式后,程序将终止运行
        直到睡眠定时到*/
        SetSleepTime(5);   
        PowerMode(2);    //进入睡眠模式PM2
      }
    }
    

    实现效果:
    屏幕显示倒计时3S,倒计时结束屏幕熄灭,5S后屏幕再次点亮同时开始新一轮的倒计时,以此循环。
    在这里插入图片描述

5. 看门狗实验

“看门狗”对于单片机初学者来说是一个及其抽象的动物,怎么单片机里面还有“狗”,什么东西这么嚣张可以当“狗”看门。其实看门狗的原理和职责很简单,看门狗可以理解为一个定时器,当程序正常运行时并不会触发计时器,但是一旦程序出现问题比如跑飞或者是出现其他异常则会触发看门狗开始计时,一旦达到预定值则会触发系统的重启,是单片机的一个重要保护单元。
实现步骤:

  1. 配置看门狗寄存器

    void InitWatchdog(void) 
    { 
      WDCTL = 0x00;       //打开IDLE
      WDCTL |= BV(3);      //开启watchdog模式
      WDCTL &= ~(BV(0)|BV(1));//1s复位
    }
    
  2. 喂狗(让看门狗不触发)

    void FeetDog(void) 
    { 
      WDCTL = 0xA0;       //喂狗顺序
      WDCTL = 0x50; 
    }
    
  3. main函数调用
    先对系统的时钟源进行设定,接下来就是初始化串口和看门狗设置,要实现的功能为在1S内不断通过串口助手输入“1”,才可以在串口打印“Running”,否则串口会一直输出“reset”。

    void main(void)
    {
      CLKCONCMD &= ~0x40;               //设置系统时钟源为32MHZ晶振
      while(CLKCONSTA & 0x40);          //等待晶振稳定
      CLKCONCMD &= ~0x47;               //设置系统主时钟频率为32MHZ     
      
      InitUart();
      InitWatchdog();   
      
      UartSendString("reset\r\n",7);  
      while(1)
      {
        DelayMS(1000);
        UartSendString("running\r\n",9);  
      }
    }
    

实现效果:
间隔0.5S发送1,串口持续打印“running”。
在这里插入图片描述

6. 五向按键(ADC)实验

Adc 是单片机获取模拟量的重要方式,在此为减少按键所占用的 io 口将演示如果用 1 路 adc 获取多个按键值。
在这我们需要用到液晶传感器底板的另一个硬件资源——五向按键,其可以实现上下左右中五个方向的按键效果,其电路原理图如下所示:
在这里插入图片描述

在这里插入图片描述
实现步骤:

  1. 配置ADC寄存器

    /*五向按键用到的ADC部分初始化*/
    void JOYSTICK_ADC_Init()
    {
      ADCCON3 |= 0x80;//设置参考电压,这里为选择AVDD5
      ADCCON3 |= 0x00;//设置分辨率为8位
      ADCCON1 |= 0x30;//设置ADC启动方式,这里为软件启动,当st为1时 开始ADC转换
    }
    //adc采用通道选择,从0~7,分别对应AIN0 ~ AIN7
    //采样通道 每次采样前均需重新设置!
    void JOYSTICK_ADC_SelectChanel(uint8 ch)
    {
      ADCCON3 |= ch;//select ainx as input
      //APCFG |= BV(ch); //不是必须的配置,又名ADCCFG
    }
    /*ADC启动和停止函数
    s为true时开始采样
    否则停止采样
    */
    void JOYSTICK_ADC_Start(bool s)
    {
      if(s){
        ADCCON1 |= BV(6);
      }else{
        ADCCON1 &= ~BV(6);
      }
    }
    /*ADC是否采样结束
    结束后返回非0数
    */
    uint8 JOYSTICK_ADC_Busy()
    {
      return (ADCCON1&0x80);
    }
    
  2. 读取ADC值

    /*读取ADC采样值*/
    uint16 JOYSTICK_ADC_Get()
    {
      //uint16 t = ((uint16)(ADCH)<<8)+ADCL;
      uint16 t;
      t=(uint16) (ADCL);
      t|= (uint16) (ADCH << 8);
      t>>=8;  //8为分辨率,得到的值右移8位,即除以256
      return t;
    }
    
    void JOYSTICK_POLL(void)
    {
      //设置本次的采样通道
      JOYSTICK_ADC_SelectChanel(6);
      //开始采样
      JOYSTICK_ADC_Start(1);
      //等待转换结束
      while(!JOYSTICK_ADC_Busy());
      //读取采样结果
      JOYSTICK_Value = JOYSTICK_ADC_Get();  
    }
    
  3. 对读取的值进行判断比对进行按键绑定
    在main函数通过While循环不断将采样电压值与预设值进行比对,实现不同方向的按键与其电压值进行绑定。

    int main()
    {
      HAL_BOARD_INIT();
      HalLcd_HW_Init();  
      
      JOYSTICK_ADC_Init();
      JOYSTICK_ADC_Get();
      
      HalLcd_HW_WriteLine(HAL_LCD_LINE_1, "JOYSTICK_TEST");  
      
      while(1){
        JOYSTICK_POLL();
        {
          /*不同的采样电压对应不同的按键*/
          if ((JOYSTICK_Value >= (77-8)) && (JOYSTICK_Value <= (77+8)))
          {
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"UP");
          }
          else if ((JOYSTICK_Value >= (97-4)) && (JOYSTICK_Value <= (97+4)))
          {
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"DOWN");
          }      
          else if ((JOYSTICK_Value >= (105-3)) && (JOYSTICK_Value <= (105+3)))
          {
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"LEFT");
          }
          else if ((JOYSTICK_Value >= (110-2)) && (JOYSTICK_Value <= (110+2)))
          {
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"RIGHT");
          }
          else if ((JOYSTICK_Value >= (250)) || (JOYSTICK_Value <= (5)))
          {
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"CENTER");
          }
          else 
            HalLcd_HW_WriteLine(HAL_LCD_LINE_2,"      ");
        }
      }
    }
    

最终实现效果为往不同方向按下按键显示屏上会显示当前按键的方向:
在这里插入图片描述

7. 温度光敏蜂鸣器温湿度传感器实验

该实验为本环节的最后一个实验,毕竟CC2540作为一个单片机,高低要连接一个传感器读一读它的数据是不是。
这里用的是温度光敏蜂鸣器温湿度传感器
在这里插入图片描述
电路原理图如下图所示:
在这里插入图片描述
在这里插入图片描述
实现步骤:

  1. 配置ADC寄存器

    void HalAdcInit (void)
    {
      volatile uint8  tmp; 
      
      ADCCON1 = 0x30 | 0x0c| 0x03;
      ADCCON2 = 0x80 | 0x00 | 0x0f;
      /*
      *  After reset, the first ADC reading of the extra conversion always reads GND level.
      *  We will do a few dummy conversions to bypass this bug.
      */
      tmp = ADCL;     /* read ADCL,ADCH to clear EOC */
      tmp = ADCH;
      ADCCON3 = 0x80 | 0x00 | 0x0c;
      while ((ADCCON1 &0x80) != 0x80);   /* Wait for conversion */
      tmp = ADCL;     /* read ADCL,ADCH to clear EOC */
      tmp = ADCH;
      ADCCON3 = 0x80 | 0x00 | 0x0c;
      while ((ADCCON1 &0x80) != 0x80);    /* Wait for conversion */
      tmp = ADCL;     /* read ADCL,ADCH to clear EOC */
      tmp = ADCH;
    }
    
  2. 初始化传感器

    void Sensor_Init(void)
    {
      P1DIR |= 0x04;        //打开电源
      P1 &=~0x04;  
      
      //初始化TC77引脚
      P1DIR &= ~0x80;
      P1DIR |=  0x30;
      
      //初始化光敏电阻引脚
      P0DIR &= ~0x02;Lingt
      P0INP |= 0x02;
      HalAdcInit();
      
      //初始化蜂鸣器引脚
      P1DIR |=  0x40;BEEP
      P1_6 = 1;//OFF  
    }
    
  3. 读取传感器数据

    int8 ReadTc77(void)
    {
      uint16 temp=0;
      uint8 i;
      MISO = 1;
      SCK = 0;
      CS_TC77 = 0;
    	
      for(i=0; i<16; i++)
      {
        temp <<= 1;
        SCK = 1;
        asm("nop");
        if(MISO)temp++;
        SCK = 0;
        asm("nop");
      }
      CS_TC77 = 1;
      i = temp >> 7;
      return i;
    }
    uint16 HalAdcRead (uint8 channel, uint8 resolution)
    {
      int16  reading = 0;
      
      uint8   i, resbits;
      uint8   adctemp;
      volatile  uint8 tmp;
      uint8  adcChannel = 1;
      if (channel < 8)
      {
        for (i=0; i < channel; i++)
        {
          adcChannel <<= 1;
        }
      }
      
      /* Enable channel */
      ADCCFG |= adcChannel;
      
      /* Convert resolution to decimation rate */
      switch (resolution)
      {
      case 0x01:
        resbits = 0x00;
        break;
      case 0x02:
        resbits = 0x10;
        break;
      case 0x03:
        resbits = 0x20;
        break;
      case 0x04:
      default:
        resbits = 0x30;
        break;
      }
      
      /* read ADCL,ADCH to clear EOC */
      tmp = ADCL;
      tmp = ADCH;
      
      /* Setup Sample */
      adctemp = ADCCON3;
      adctemp &= ~(0x0f | 0x30 | 0xc0 );
      adctemp |= channel | resbits | 0x80;
      
      /* writing to this register starts the extra conversion */
      ADCCON3 = adctemp;
      
      /* Wait for the conversion to be done */
      while (!(ADCCON1 & 0x80));
      
      /* Disable channel after done conversion */
      ADCCFG &= (adcChannel ^ 0xFF);
      
      /* Read the result */
      reading = (int16) (ADCL);
      reading |= (int16) (ADCH << 8);
      
      /* Treat small negative as 0 */
      if (reading < 0)
        reading = 0;
      
      switch (resolution)
      {
      case 0x01:
        reading >>= 8;
        break;
      case 0x02:
        reading >>= 6;
        break;
      case 0x03:
        reading >>= 4;
        break;
      case 0x04:
      default:
        break;
      }
      return ((uint16)reading);
    }
    
    uint8 ReadSensorAdc(uint8 channel)
    {
    	uint8 temp;
      temp = HalAdcRead(channel,01);
    	return temp;
    }
    
  4. main函数展示

    int main()
    {
      int8 temperature,light;
      
      HAL_BOARD_INIT();
      HalLcd_HW_Init();  
      
      Sensor_Init();//传感器初始化     
       
      uint8 lightBuf[10];
      uint8 tempBuf[10];
      
      while(1)
      {       
        //采集温度
        temperature = ReadTc77();        
        //采集光敏电阻
        light = ReadSensorAdc(1);
        
        //IntToStr(tempBuf,temperature);
        //IntToStr(lightBuf,light);
        
        sprintf((char *)&tempBuf,"T:%d",temperature);
        sprintf((char *)&lightBuf,"P:%d",light);
        
        HalLcd_HW_WriteLine(HAL_LCD_LINE_1, (char *)&tempBuf);  
        HalLcd_HW_WriteLine(HAL_LCD_LINE_2, (char *)&lightBuf);
        
        HalHW_WaitMS(1000);
        
        beep(1);       
      }    
    }
    

实验效果:在显示屏上不断显示温度数据和光照度数据
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值