首先感谢沁恒电子和EEWORLD共同举办这次“沁恒评估板诚芯送”活动,让我有机会了解了CH549单片机。这是一款兼容MCS51指令集的增强型E8051内核单片机,从它身上可以看到51单片机的影子,但运算速度和片上资源又比传统的51单片机强多了。它继承了51特有的位操作指令,在我看来这比操作STM32单片机引脚要更方便些。经过半个多月的实验,我认为这是比较适合初学者上手而且功能比较丰富的单片机之一,值得花时间去了解和掌握,并应用到自己的项目中。
我这次的项目是准备做一个超声波水位仪,用于需要探测水位高低的场合(如小水电站的前池),项目方案是通过超声波传感器检测仪器到水面的距离,然后推算水位的高低,并通过无线模块将数据传输到机房。到目前为止,实验已经基本完成,SR04超声波模块、LCD5110显示、nRF24L01无线模块都已正常驱动和调试完成,仅仅是机房接收模块尚未完成,仍在调试过程中。
在此次评测过程中,我首先进行了LED流水灯的实验,这个非常简单的实验很轻松就完成了。然后尝试了触摸按键的操作,还曾想延伸应用到防盗报警场合中,但将触摸按键连接到门窗等物体后,电容变化太大了,按键失灵不成功。之后还测试了串口0通讯及SPI通讯,在电脑上通过串口调试助手与开发板建立起了通讯,通过逻辑分析仪检测SPI引脚,也得到了正确的数据时序。鉴于时间关系和本次评测计划,其他的硬件功能没有去测试,电容触摸按键也未作深入学习。
下面是本次评测情况:
一、项目组成简图
二、评测过程
1、驱动LCD5110显示屏
这是一款经典的用于诺基亚手机的LCD显示屏,分辨率为64*84点阵,因为基本上都是从手机上拆下再配上PCB板的二手货,所以物美价廉。显示屏的驱动是非常成熟的,定义好引脚就OK了,只是软字库定义出了点小状况,由于隔久了未接触51单片机,忘记了用code来将字库定义在代码区,反复用static和const来定义字库文件,编译总出错,提示数据太大,后来在沁恒的工程师提醒下才回忆起应该用code来定义。下图是LCD5110的显示情况:
下面是LCD5110的代码:
#include ".\Public\CH549.H"
#include ".\Public\DEBUG.H"
#include ".\mydrive\lcd_5110.h"
#include ".\mydrive\ascii5x8.h" //5x8ASCII?????
#include ".\mydrive\charcode.h" //12x16(14)???????
#include ".\mydrive\asciicode.h" //5x8(8)ASCII???
void Delay(UINT8 xms)
{
UINT8 x,y;
for(x=xms;x>0;x--)
for(y=10;y>0;y--);
}
void LCD_write_byte(UINT8 dat,UINT8 dc)
{
UINT8 i;
LCD_CLK = 0;
LCD_CE = 0;
LCD_DC = dc;
for(i=0; i<8; i++)
{
if (dat & 0x80){
LCD_DIN = 1;
}
else{
LCD_DIN = 0;
}
LCD_CLK = 1;
Delay(1);
dat = dat << 1;
LCD_CLK = 0;
}
LCD_CE = 1;
}
void LCD_set_XY(UINT8 X,UINT8 Y)
{
LCD_write_byte(0x40 | Y,0);
LCD_write_byte(0x80 | X,0);
}
void LCD_clear(void)
{
UINT8 i,j;
LCD_set_XY(0,0);
for (i=0; i<6; i++)
{
for (j=0; j<84; j++)
{
LCD_write_byte(0x00,1);
}
}
}
void LCD_init(void)
{
LCD_RST = 0;
Delay(2);
LCD_RST = 1;
LCD_CE = 0;
Delay(2);
LCD_CE = 0;
LCD_write_byte(0x21,0);
LCD_write_byte(0xc8,0);
LCD_write_byte(0x06,0);
LCD_write_byte(0x13,0);
LCD_write_byte(0x20,0);
LCD_write_byte(0x0c,0);
LCD_clear();
LCD_CE = 0;
}
void LCD_write_ASCII(UINT8 X,UINT8 Y,UINT8 *stru)
{
UINT8 i;
LCD_set_XY(X,Y);
while (1)
{
for ( i=0; i<5; i++)
{
LCD_write_byte(ASC_5[*stru-32][i],1);
}
stru++;
if(*stru == '\0') break;
LCD_write_byte(0x00,1);
}
}
void LCD_write_ASC_SIN(UINT8 X,UINT8 Y,UINT8 cid)
{
UINT8 i;
LCD_set_XY(X,Y);
for ( i=0; i<5; i++)
{
LCD_write_byte(ASC_5[cid-32][i],1);
}
}
void LCD_write_ASC7x12(UINT8 X,UINT8 Y,UINT8 cid)
{
UINT8 i;
LCD_set_XY(X,Y);
for (i=0; i<7; i++)
{
LCD_write_byte(ASC_7[cid][i],1);
}
LCD_set_XY(X,Y+1);
for (i=7; i<14; i++)
{
LCD_write_byte(ASC_7[cid][i],1);
}
}
void LCD_write_char(UINT8 x,UINT8 y,UINT8 cid)
{
UINT8 i;
LCD_set_XY(x,y);
for (i=0; i<12; i++)
{
LCD_write_byte(CHAR_12[cid][i],1);
}
LCD_set_XY(x,y+1);
for (i=12; i<24; i++)
{
LCD_write_byte(CHAR_12[cid][i],1);
}
}
void LCD_write_string(UINT8 x,UINT8 y,UINT8 *stru)
{
UINT8 j,n;
while(1)
{
j = 0;
while(1)
{
n = 0;
if((stru[0] == cid_12[j][0]) && (stru[1] == cid_12[j][1]))
{
n = j;
break;
}
j++;
if(*cid_12 == '\0') break;
}
LCD_write_char(x, y, n);
x += 12;
stru += 2;
if(*stru == '\0') break;
LCD_write_byte(0x00,1);
}
}
void LCD_write_value(UINT8 X,UINT8 Y,UINT8 L,UINT8 D,UINT8 Z,UINT16 val)
{
UINT8 i,j,f = 0;
UINT16 t,cid;
UINT32 n;
t = val;
n = 1;
if(Z == 1)
f = 16;
for (j = 0; j < L; j++)
n = n * 10;
LCD_set_XY(X,Y);
for (j = L; j > 0; j--)
{
n = j < 2 ? 1: n / 10;
cid = t / n;
t = t - (cid * n);
if ((cid > 0)|(j-1 == D))
f = 16;
for ( i=0; i<5; i++)
{
LCD_write_byte(ASC_5[cid + f][i],1);
}
if ( D > 0 & D == (j - 1))
{
for ( i=0; i<5; i++)
{
LCD_write_byte(ASC_5[14][i],1);
}
}
else
if(j>1) LCD_write_byte(0x00,1);
}
}
2、SR04超声波模块的驱动
这也是非常普及的超声波测距传感器,探测距离通常在2~450厘米范围。通过向模块的Trig引脚发送不少于10毫秒的高电平信号,稍等片刻便可从Echo引脚得到返回的高电平信号,信号持续时间与超声波探测距离成正比。我们知道声波的速度是340米/秒,超声波来回则是170米/秒,合17厘米/毫秒,约58.8微秒/厘米。
我借用了范例中已经开启的定时器1来作测量计数,定时器的中断周期为100微秒,按此计算理论上的测量误差约为2厘米,对于本应用项目来说已经是精度足够了,因为流动时水波也会有几厘米变化。下面是测试时的照片:
在屏幕左边安排了一个模拟水位高低的柱状图,可以动态显示当前水位,左上部分是检测设备安装的高度值,检测的水位高度在0M与设备安装的高度之间。右边最上一行数值是计算出来的水位高度,第二行是超声波实际测量的距离,第三行是预备显示时间用的,最底行是显示设备安装高度和检测数据响应的偏差范围。
为了方便定时器1做其他事情我在其中断代码里判断Echo引脚,高电平时即对count变量计数,代码如下:
/****************************************************************************
* Function Name : mTimer1Interrupt()
* Description : CH549定时计数器1定时计数器中断处理函数 100us中断
****************************************************************************/
void mTimer1Interrupt( void ) interrupt INT_NO_TMR1 using 2 //timer1中断服务程序,使用寄存器组2
{
//方式2时,Timer1自动重装
static UINT16 tmr1 = 0;
tmr1++;
if(tmr1 == 2000) //100us*2000 = 200ms
{
tmr1 = 0;
SCK = ~SCK;
LED0 = ~LED0;
}
ms++;
if(SR04_Echo==1) //收到返回信息
count++;
}
传统的检测代码是当Echo为高电平时则一直循环计数,这样当超声波模块出故障时,由于该引脚电平一直不降低,程序便进入了死循环。而我则试用了延时100毫秒后主动结束检测的方法,避免了这个问题,下面是超声波检测、距离计算及显示部分的代码:
void level_handler(void) //检测水位
{
UINT16 levs;
UINT8 c,d,i,l,m,n;
/* 超声波测距 */
levs = 0;
for(i=0;i<5;i++){
SR04_Trig = 1;
mDelaymS(1);
count = 0;
SR04_Trig = 0;
// while(1==SR04_Echo); //通常采用循环法等待信号结束
mDelaymS(100); //我用延时法主动完成检测
levs = levs +count;
}
levs = levs * 17 / 10; //计算检测距离(仪器到水面)200/80/5
if(levs > 500){
LCD_write_ASCII(43,3," ERROR");
data_buf[10] = 'E';
}
else{
LCD_write_value(45,3,5,2,0,levs);
data_buf[10] = 'C';
}
levs = height - levs; //计算水位高度(安装高度-检测距离)
if(levs<=500){
if(ABS(level,levs)>devia){//水位变化超过范围,更新数据
level = levs;
}
}
LCD_write_value(51,2,4,1,0,level/10);
下面是动态显示水位柱状图的代码:
//动态显示当前水位
LCD_write_ASCII(6,1," ");
LCD_write_ASCII(6,2," ");
LCD_write_ASCII(6,3," ");
LCD_write_ASCII(6,4," ");
l = 4 - (level / (height / 4));
LCD_write_value(6,l,3,1,0,level/10);
d = level *48 / height; //计算需要显示模拟高度的点
m = d / 8; //取模
n = d % 8; //取余
for (i=0; i<6; i++)
{
for (c=1; c<3; c++)
{
if (i < m)
{
LCD_set_XY(c,5-i);
LCD_write_byte(0xFF,1); //显示全部点
}
else if (i == m)
{
LCD_set_XY(c,5-i);
LCD_write_byte(map[n],1);//显示部分点
}
else
{
LCD_set_XY(c,5-i);
LCD_write_byte(0x00,1); //不显示点
}
}
}
最后是发送水位信息的代码:
/* 发送水位信息 */
data_buf[6] = level / 1000 + '0';
data_buf[7] = level % 1000 / 100 + '0';
data_buf[8] = level % 1000 % 100 / 10 + '0';
data_buf[9] = level % 1000 % 100 % 10 + '0';
//printf("ch %d up,value:%d\n",(UINT16)ch, value);
LED_Control(3,1); //发出开始信号(P2^5 = 0)
for(i=0;i<12;i++){
CH549SPIMasterWrite(data_buf);//发送到nRF24L01无线模块
}
printf("\n%s",data_buf);
SPI_Write_Buf(WRITE_REG+STATUS, data_buf, 12); //要发送的数据data_buf,长度12字节
LED_Control(3,0); //发出结束信号(P2^5 = 1)
}
3、nRF24L01无线模块的驱动
这款无线模块我是初次使用,代码是从51的示例中移植过来的,编译及运行没有发现问题。接收部分使用的是GD32F350开发板,代码也是刚移植的,目前在GD32F350上调试没有成功,接收不到发送的信息。由于缺乏检测设备,目前还不清楚原因何在,还需要继续排查。下图为接收装置:
以下是nRF24L01的驱动代码(CH549开发板):
#include "nrf24l01.h"
extern UINT8 data_buf[];
void nrf24l01_Init(void) //nrf24l01?????
{
NRF_CE = 0; // chip enable
NRF_CSN = 1; // Spi disable
SPI_SCK = 0; // Spi clock line init high
}
void power_off() //????????
{
NRF_CE=0;
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0D);
NRF_CE=1;
mDelayuS(20);
}
/**************************************************
Function: SPI_RW_Reg();
Description:
Writes value 'value' to register 'reg'
/**************************************************/
UINT8 SPI_RW_Reg(UINT8 reg, UINT8 value) //д?????
{
UINT8 status;
NRF_CSN = 0; //???????????????CSN
status = SPI_RW(reg); //???????
SPI_RW(value); //д?????
NRF_CSN = 1; //????CSN
return(status); //????nRF24L01????
}
/**************************************************
Function: SPI_RW();
????????д??NRF24L01????????????????
????SPIЭ?飬??д?????????NRF24L01
**************************************************/
uchar SPI_RW(uchar byte)
{
uchar bit_ctr;
for(bit_ctr=0;bit_ctr<8;bit_ctr++) // д8-bit
{
SPI_MOSI = (byte & 0x80); // д???λ??MOSI
byte = (byte << 1); // ??λ
SPI_SCK = 1; // ????SCK
SPI_MISO = 1; // ???????
byte |= SPI_MISO; // ?????misoλ
SPI_SCK = 0; // ????SCK?????????
}
return(byte); // ????0
}
/**************************************************
Function: SPI_Read();
??NRF24L01??????????????
/**************************************************/
BYTE SPI_Read(BYTE reg)
{
BYTE reg_val;
NRF_CSN = 0; // ????NRF_CSN??????????
SPI_RW(reg); // ?????????????
reg_val = SPI_RW(0); // ??????
NRF_CSN = 1; // ????CSN??????????
return(reg_val); // ???????????
}
/**************************************************
Function: SPI_Read_Buf();
????????reg?????????????????
?????????Rx??Ч?????Rx/Tx???
**************************************************/
uchar SPI_Read_Buf(BYTE reg, BYTE *pBuf, BYTE bytes)
{
uchar status,byte_ctr;
NRF_CSN = 0; // ????NRF_CSN??????????
status = SPI_RW(reg); // ????д???????????????
for(byte_ctr=0;byte_ctr<bytes;byte_ctr++)
pBuf[byte_ctr] = SPI_RW(0); // ???SPI_rw??NRF24L01??????
NRF_CSN = 1; // ????NEF_CSN??????????
return(status); // ????nRF24L01????
}
/**************************************************
Function: SPI_Write_Buf();
??????????*pbuf????????д??nrf24l01
???????д??Tx??Ч?????Rx/Tx???
?????????
reg ?????
pBuf ?????????
bytes ???????????
**************************************************/
uchar SPI_Write_Buf(BYTE reg, BYTE *pBuf, BYTE bytes)
{
uchar status,byte_ctr;
NRF_CSN = 0; // ????NRF_CSN???????
status = SPI_RW(reg); // ????д???????????????
for(byte_ctr=0; byte_ctr<bytes; byte_ctr++) // ?????????д????????*pbuf??
SPI_RW(*pBuf++);
NRF_CSN = 1; // ????NRF_CSN??????????
return(status); // ????nRF24L01????
}
/**************************************************
Function: RX_Mode();
?????????NRF24L01?豸??????RX????
????RX?????д??RX??Ч???????
???????????????????????????LNA HCURR????
???????ce???л?????λ??????ζ????豸?????????????????
**************************************************/
void ifnnrf_rx_mode(void)
{
power_off();
NRF_CE=0; //?????
// SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // ??????豸??????????豸???????
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDR, TX_ADR_WIDTH);
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // ???????????pipe0
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // ????Pipe0
SPI_RW_Reg(WRITE_REG + RF_CH, 30); // ???RF???40
SPI_RW_Reg(WRITE_REG + RX_PW_P0, TX_PLOAD_WIDTH); // ?????Tx??Ч???????????Rx??Ч??????
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); // tx_pwr:0dbm???????????2Mbps??lna:hcurr
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0f); // ????pwr_upλ??????crc??2????&prim:rx??Rx_Dr?????á?
NRF_CE = 1; // ????????
// ???豸???????????????????Tx?豸???????16?????Ч????????????
// '3443101001',??????????????????????10????????40??????????=2Mbps??
}
/**************************************************
Function: TX_Mode();
?????????NRF24L01?豸??????Tx????????Tx??????auto.ack????Rx?????
???Tx??Ч????????????????????????Tx PWR??
??????pwr_????????crc??2??????prim:tx??
TODO:CE???????????壨>10us??????????????????????????豸?е???????
**************************************************/
void ifnnrf_tx_mode(void)
{
power_off();
NRF_CE=0;
// SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH); // ??Tx???д??NRF24L01
SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDR, TX_ADR_WIDTH);
// SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // rx_addr0?????????tx_adr???
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDR, TX_ADR_WIDTH);
SPI_Write_Buf(WR_TX_PLOAD, data_buf, TX_PLOAD_WIDTH); // ??????д??Tx????
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // ??????????:Pipe0
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // ????Pipe0
SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x1a); // 500us + 86us, 10 retrans...
SPI_RW_Reg(WRITE_REG + RF_CH, 30); // ???RF???40
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); // TX_PWR:0dBm, Datarate:2Mbps, LNA:HCURR
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e); // Set PWR_UP bit, enable CRC(2 bytes) & Prim:TX. MAX_RT & TX_DS enabled..
NRF_CE=1;
}
void SPI_CLR_Reg(BYTE R_T)
{
NRF_CSN = 0; // ????NRF_CSN???????
if(R_T==1)
SPI_RW(FLUSH_TX); // ????FLASH_TX?????
else
SPI_RW(FLUSH_RX); // ????FLASH_RX?????
NRF_CSN = 1; // ????NEF_CSN??????????
}
void ifnnrf_CLERN_ALL()
{
SPI_CLR_Reg(0);
SPI_CLR_Reg(1);
SPI_RW_Reg(WRITE_REG+STATUS,0xff);
NRF_RQ=1;
}
三、总结
如前所述,通过参加这次评测活动,我对CH549单片机有了初步的了解,项目也基本完成。待接收部分调试完成后,准备使用赠送的芯片按自己的需要设计PCB板,制作工业化测试的产品,以便到现场测试和改进,争取最终形成产品。有关后续进展,我会在EEWORLD论坛发帖的。下面是评测过程中的部分照片:
最后是项目压文件的缩包:
EXAM.rar
(679.49 KB, 下载次数: 1)
此内容由EEWORLD论坛网友hujj原创,如需转载或用于商业用途需征得作者同意并注明出处