0、前言
網友提問如下:

本地程序之間
pipe
shm
msg 消息隊列,
sem
兩個pc之間
socket /unix
raw 套接字:
BSD socket
unix -> bill joy bsd分支,
彙總下這個網友的問題,其實就是實作一個網關程式,内容分為幾塊:
- 下位機,通過序列槽與上位機相連;
- 下位機要能夠接收上位機下發的指令,并解析這些指令;
- 下位機能夠根據這些指令配置對應的外設、讀取對應的傳感器的資料上傳到上位機;
- 主程式序列槽操作子產品:通過序列槽下發指令或者讀取下位機上傳的資料資訊;
- 主程式網絡通信子產品:接收遠端伺服器下發的指令,并将下位機采集的資料上傳到伺服器。
整體看來,這個相當于是一個小的項目了,内容難度都比較大,下面我們會分為幾篇獨立的文章來講解。
本篇隻讨論如何給下位機編寫一個簡單的上位機。
一、環境簡介
1. 軟硬體環境
下位機:CC2530
OS:vmware + ubuntu
在這裡彭老師采用的是CC2530,讀者也可以采用其他的闆子,我們隻需要該闆子有序列槽,可以和PC通信,同時闆子上有可設定的led燈、繼電器以及可以采集資料的傳感器即可。
2. 硬體連接配接圖
硬體連接配接圖如下:
該款CC2530已經內建了CH340晶片,usb線連接配接電腦,即可被識别。
3. pc下識别序列槽
如果該序列槽被PC擷取,名字為COMn【n為某整數】。
4. ubuntu下識别序列槽
首先需要vmware抓取序列槽【序列槽在同一時刻要麼被windows抓取要麼被vmware抓取】,按下圖所示,點選連接配接即可:
但是往往ubuntu中沒有ch340的驅動,經過實際測試,ubuntu14及之前的版本都沒有這個驅動,ubuntu16以上的版本有這個驅動。
如果沒有ch340驅動可以用以下方法安裝對應的驅動:
1 make
2 sudo make load
3 ls /dev/ttyUSB0
按照上述步驟,會生成裝置檔案**/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 即可獲得
【注意】
如果是其他開發闆,自行安裝其他的序列槽驅動。
二、子產品設計
上位機和下位機的通信往往都是通過序列槽,linux下往往生成字元裝置ttyUSB0【有的是ttyS0】,操作序列槽裝置就隻需要操作該字元裝置即可。
下面我們設計上下位機的軟體子產品。
1. 信令
設計上位機,首先需要設計上位機下發給下位機的指令格式,上位機按照該指令格式發送指令給下位機,下位需嚴格按照該指令格式進行解析指令。
含義如下:
- 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. 功能子產品
現在就可以開始設計軟體的各個功能子產品了。
下位機
下位主要任務就是循環接收上位機通過序列槽下發的資料,然後解析該指令内容,操作對應的硬體。
上位機
上位機主要任務是列印菜單,由使用者針對菜單做出選擇,然後按照指令格式封裝指令,并通過序列槽将該指令下發給下位機。
三、 下位機功能函數
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. 上位機運作界面
2. 點亮led燈
點亮led1:
3. 滅燈
4. 讀取煙霧傳感器資料
煙霧的資料是079,可以點根華子,你會發現每次讀取的值都是在變化。
OK!
至此為止,一個簡易的CC2530上位機我們就編寫完畢,如果想将從序列槽擷取的資料的值發送到遠端伺服器,後續文章我們将繼續讨論。
代碼背景回複,**【cc2530上位機】**即可獲得。