天天看點

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

0、前言

網友提問如下:

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果
【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

本地程序之間

pipe

shm

msg 消息隊列,

sem

兩個pc之間

socket /unix

raw 套接字:

BSD socket 
unix ->   bill joy  bsd分支,
           

彙總下這個網友的問題,其實就是實作一個網關程式,内容分為幾塊:

  1. 下位機,通過序列槽與上位機相連;
  2. 下位機要能夠接收上位機下發的指令,并解析這些指令;
  3. 下位機能夠根據這些指令配置對應的外設、讀取對應的傳感器的資料上傳到上位機;
  4. 主程式序列槽操作子產品:通過序列槽下發指令或者讀取下位機上傳的資料資訊;
  5. 主程式網絡通信子產品:接收遠端伺服器下發的指令,并将下位機采集的資料上傳到伺服器。

整體看來,這個相當于是一個小的項目了,内容難度都比較大,下面我們會分為幾篇獨立的文章來講解。

本篇隻讨論如何給下位機編寫一個簡單的上位機。

一、環境簡介

1. 軟硬體環境

下位機:CC2530

OS:vmware + ubuntu

在這裡彭老師采用的是CC2530,讀者也可以采用其他的闆子,我們隻需要該闆子有序列槽,可以和PC通信,同時闆子上有可設定的led燈、繼電器以及可以采集資料的傳感器即可。

2. 硬體連接配接圖

硬體連接配接圖如下:

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

該款CC2530已經內建了CH340晶片,usb線連接配接電腦,即可被識别。

3. pc下識别序列槽

如果該序列槽被PC擷取,名字為COMn【n為某整數】。

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

4. ubuntu下識别序列槽

首先需要vmware抓取序列槽【序列槽在同一時刻要麼被windows抓取要麼被vmware抓取】,按下圖所示,點選連接配接即可:

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

但是往往ubuntu中沒有ch340的驅動,經過實際測試,ubuntu14及之前的版本都沒有這個驅動,ubuntu16以上的版本有這個驅動。

如果沒有ch340驅動可以用以下方法安裝對應的驅動:

1 make 
2 sudo make load
3 ls /dev/ttyUSB0
           
【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

按照上述步驟,會生成裝置檔案**/dev/ttyUSB0**。

ls /dev/ttyUSB0 -l
crw-rw---- 1 root dialout 188, 0 Jan 15 05:45 /dev/ttyUSB0
           

c : 字元裝置

rw-rw---- :檔案操作權限

188, 0 : 主次裝置号

3、4節提到的usb轉序列槽驅動和linux下驅動源碼背景【GH】回複 ch340 即可獲得

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

【注意】

如果是其他開發闆,自行安裝其他的序列槽驅動。

二、子產品設計

上位機和下位機的通信往往都是通過序列槽,linux下往往生成字元裝置ttyUSB0【有的是ttyS0】,操作序列槽裝置就隻需要操作該字元裝置即可。

下面我們設計上下位機的軟體子產品。

1. 信令

設計上位機,首先需要設計上位機下發給下位機的指令格式,上位機按照該指令格式發送指令給下位機,下位需嚴格按照該指令格式進行解析指令。

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

含義如下:

  • device:要操作的裝置
  • data :對應的裝置及其額外的資料
  • CRC :校驗碼
  • # :信令終止符

信令格式可以根據需要擴充或者精簡。

其中device定義如下【可以根據實際情況進行擴充】:

#define DEV_ID_LED_ON    0X1
#define DEV_ID_LED_OFF    0X2
#define DEV_ID_DELAY 0X3
#define DEV_ID_GAS  0X4
           

【注意】

為便于了解,我們暫不考慮效率問題。

2. 上傳資料

下位機需要采集傳感器的資料并通過序列槽上傳,資料結構定義如下:

struct data{
	unsigned char device;
	unsigned char crc;	  
	unsigned short data;
};
           
  • device 裝置
  • data 采集的資料
  • crc 校驗碼

3. 功能子產品

現在就可以開始設計軟體的各個功能子產品了。

下位機

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

下位主要任務就是循環接收上位機通過序列槽下發的資料,然後解析該指令内容,操作對應的硬體。

上位機

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

上位機主要任務是列印菜單,由使用者針對菜單做出選擇,然後按照指令格式封裝指令,并通過序列槽将該指令下發給下位機。

三、 下位機功能函數

cc2530的操作原理,本文不讨論,如果是其他開發闆,隻需要修改序列槽操作函數。

1. LED初始化

/****************************************************************************
* 名    稱: InitLed()
* 功    能: 設定LED燈相應的IO口
* 入口參數: 無
* 出口參數: 無
****************************************************************************/
void InitLed(void)
{
    P1DIR |= 0x01;               //P1.0定義為輸出口
    LED1 = 0;   
}
           

2. 初始化UART

/****************************************************************
* 名    稱: InitUart()
* 功    能: 序列槽初始化函數
* 入口參數: 無
* 出口參數: 無
*****************************************************************/
void InitUart(void)
{ 
    PERCFG = 0x00;           //外設控制寄存器 USART 0的IO位置:0為P0口位置1 
    P0SEL = 0x0c;            //P0_2,P0_3用作序列槽(外設功能)
    P2DIR &= ~0xC0;          //P0優先作為UART0
    
    U0CSR |= 0x80;           //設定為UART方式
    U0GCR |= 11;				       
    U0BAUD |= 216;           //波特率設為115200
    UTX0IF = 0;              //UART0 TX中斷标志初始置位0
    U0CSR |= 0x40;           //允許接收 
    IEN0 |= 0x84;            //開總中斷允許接收中斷  
}
           

3. 序列槽發送函數

/**********************************************************************
* 名    稱: UartSendString()
* 功    能: 序列槽發送函數
* 入口參數: Data:發送緩沖區   len:發送長度
* 出口參數: 無
***********************************************************************/
void UartSendString(char *Data, int len)
{
    uint i;
    
    for(i=0; i<len; i++)
    {
        U0DBUF = *Data++;
        while(UTX0IF == 0);
        UTX0IF = 0;
    }
}
           

4. 序列槽中斷處理函數

/**********************************************************************
* 名    稱: UART0_ISR(void) 序列槽中斷處理函數 
* 描    述: 當序列槽0産生接收中斷,将收到的資料儲存在RxBuf中
**********************************************************************/
#pragma vector = URX0_VECTOR 
__interrupt void UART0_ISR(void) 
{ 
    URX0IF = 0;       // 清中斷标志 
    RxBuf = U0DBUF;                           
}

           

5. 煙霧傳感器資料讀取

/****************************************************************
* 名    稱: myApp_ReadGasLevel()
* 功    能: 煙霧傳感器資料讀取
* 入口參數: 無
* 出口參數: 無
*****************************************************************/
uint16 myApp_ReadGasLevel( void )
{
  uint16 reading = 0;
  
  /* Enable channel */
  ADCCFG |= 0x80;
  
  /* writing to this register starts the extra conversion */
  ADCCON3 = 0x87;
  
  /* Wait for the conversion to be done */
  while (!(ADCCON1 & 0x80));
  
  /* Disable channel after done conversion */
  ADCCFG &= (0x80 ^ 0xFF);
  
  /* Read the result */
  reading = ADCH;
  reading |= (int16) (ADCH << 8); 
  reading >>= 8;
  
  return (reading);
}
           

6. LED燈控制函數

/****************************************************************
* 名    稱: led_opt()
* 功    能: LED燈控制函數
* 入口參數:  RxData:接收到的指令  flage:led的操作,點亮或者關閉
* 出口參數: 無
*****************************************************************/
void led_opt(char RxData[],unsigned char flage)
{
	switch(RxData[1])
	{
		case 1:
                  LED1 = (flage==DEV_ID_LED_ON)?ON:OFF;
			break;
		/* TBD for led2 led3*/

		
		default:
			break;
	}
	return;
}
           

7. 主程式

/****************************************************************************
* 主程式入口函數
****************************************************************************/
void main(void)
{	
	CLKCONCMD &= ~0x40;           //設定系統時鐘源為32MHZ晶振
	while(CLKCONSTA & 0x40);      //等待晶振穩定為32M
	CLKCONCMD &= ~0x47;           //設定系統主時鐘頻率為32MHZ   

	InitLed();                    //設定LED燈相應的IO口
	InitUart();                   //序列槽初始化函數   
	UartState = UART0_RX;         //序列槽0預設處于接收模式
	memset(RxData, 0, SIZE);
      
	while(1)
	{
	     //接收狀态 
		if(UartState == UART0_RX)             
		{ //讀取資料,遇到字元'#'或者緩沖區字元數量超過4就設定UartState為CONTROL_DEV狀态
			if(RxBuf != 0) 
			{ 
				//以'#'為結束符,一次最多接收4個字元       
				if((RxBuf != '#')&&(count < 4))     
				{	
					RxData[count++] = RxBuf; 
				}
				else
				{
					 //判斷資料合法性,防止溢出
					if(count >= 4)            
					{ 
						//計數清0
						count = 0;             
						//清空接收緩沖區
						memset(RxData, 0, SIZE);
					}
					else{
						//進入發送狀态 
						UartState = CONTROL_DEV;
					}
				}
				RxBuf  = 0;
			}
		}
	        //控制控制外設狀态 
	        if(UartState == CONTROL_DEV)            
	        {
	            //判斷接收的資料合法性
			//RxData[]:  | device | data |crc | # |
			//check_crc:   crc = device ^ data
			//if(RxData[2] == (RxData[0]^RxData[1]))
			{
				switch(RxData[0])
				{
					case DEV_ID_LED_ON :
						led_opt(RxData,DEV_ID_LED_ON);
						break;
					case DEV_ID_LED_OFF:
						led_opt(RxData,DEV_ID_LED_OFF);
						break;
					case DEV_ID_DELAY:
						break;
					case DEV_ID_GAS:
						send_gas();
						break;			
					default:
						break;
				}								
			}
	            UartState = UART0_RX;
	            count = 0;     
			//清空接收緩沖區
	            memset(RxData, 0, SIZE);           
		}
	}
}
           

四、 上位機功能函數

結構體

#define DEV_ID_LED_ON    0X1
#define DEV_ID_LED_OFF    0X2
#define DEV_ID_DELAY 0X3
#define DEV_ID_GAS  0X4
struct data{
	unsigned char device;
	unsigned char crc;	
	unsigned short data;
};
           

函數

void uart_init(void )
{
	int nset1,nset2;

	serial_fd = open( "/dev/ttyUSB0", O_RDWR);
	if(serial_fd == -1)
	{
		printf("open() error\n");
		exit(1);
	}
	nset1 = set_opt(serial_fd, 115200, 8, 'N', 1);
	if(nset2 == -1)
	{
		printf("set_opt() error\n");
		exit(1);
	}
}
int Menu() 
{
	int option;
	
	system("clear");

	printf("\n\t\t************************************************\n");
	printf("\n\t\t**               ALARM SYSTERM                **\n");
	printf("\n\t\t**               1----LED                     **\n");
	printf("\n\t\t**               2----GAS                   **\n");
	printf("\n\t\t**               0----EXIT                    **\n");
	printf("\n\t\t************************************************\n"); 
	while(1)
	{ 
		printf("Please choose what you want: ");
		scanf("%d",&option); 
		if(option<0||option>2)
			printf("\t\t    choose error!\n");
		else 
			break;
	}
	return option; 
}
// RxData[]:  | device | data |crc | # |
void led()
{
	int lednum = 0;
	int onoff;

	char cmd[4];
	//選擇led燈
	while(1)
	{
		printf("input led number :[1 2]\n#");

		scanf("%d",&lednum);
		//check  
		if(lednum<1 || lednum >2)
		{
			printf("invalid led number\n");
			system("clear");
			continue;
		}else{
			break;
		}
	}
	printf("operation: 1 on , 0  off\n");
	scanf("%d",&onoff);	

	if(onoff == 1)
	{
		cmd[0] = DEV_ID_LED_ON;
	}else if(onoff == 0)
	{
		cmd[0] = DEV_ID_LED_OFF;
	}else{
		printf("invalid led number\n");
		return;
	}
	
	cmd[1] = lednum;
	//fulfill crc  area
	cmd[2] = cmd[0]^cmd[1];  
	cmd[3] = '#';//表示結束符
	
	tcflush(serial_fd, TCIOFLUSH);

	int i = 0;

	for(i=0;i<4;i++)
	{
		printf("%d ",cmd[i]);
	}
	printf("\n");
	
	write(serial_fd,&cmd,sizeof(cmd));		
	
	sleep(1);
	
}
// RxData[]:  | device | data |crc | # |
void gas()
{
	int len ;
	unsigned short  GasLevel;
	struct data msg;
	char gas[4]={0};
	char cmd[4];
	
	cmd[0] = DEV_ID_GAS;
	cmd[3] = '#';//表示結束符
	write(serial_fd,&cmd,sizeof(cmd));
	sleep(1);
	
	len = read(serial_fd,&msg,sizeof(struct data));
	//轉換讀取的gas資料格式
	GasLevel = msg.data;
	gas[0] = GasLevel / 100 + '0';
	gas[1] = GasLevel / 10%10 + '0';
	gas[2] = GasLevel % 10 + '0';

	printf("%s\n",gas);
	getchar();
}
void run()
{
	int x;
	
	while(1)
	{		
		x=Menu(); 
		switch(x) 
		{	
			case 1:
				led();
				break;		
			case 2:
				gas();
				break; 
			case 0:
				printf("\n\t\t     exit!\n\n");
				close(serial_fd);
				exit(0);
			default:
				fg=1;
				break;
		 }
		 if(fg)
			 break;
	 }
}

int main() 
{
	uart_init();
	run();
	return 0;
}
           

五、 運作結果

1. 上位機運作界面

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

2. 點亮led燈

點亮led1:

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

3. 滅燈

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

4. 讀取煙霧傳感器資料

【粉絲問答8】如何用C語言在Linux下實作CC2530簡單的上位機-v0.10、前言一、環境簡介二、子產品設計三、 下位機功能函數四、 上位機功能函數五、 運作結果

煙霧的資料是079,可以點根華子,你會發現每次讀取的值都是在變化。

OK!

至此為止,一個簡易的CC2530上位機我們就編寫完畢,如果想将從序列槽擷取的資料的值發送到遠端伺服器,後續文章我們将繼續讨論。

代碼背景回複,**【cc2530上位機】**即可獲得。

繼續閱讀