分类: LINUX
2009-05-20 14:25:30
环境:ads2.2+ARM9 +s3c2410
注意:由于编译器(ads1.2或2.2)对全局变量初始化为0的不完全支持,有时必须手动初始化为0,切记!!!
一、ucos II移植到ARM9 s3c2410
可从官网下载移植代码,基本无需改动。
在os_cpu_a.s中的函数UCOS_IRQHandler中的bl OSIntEnter和bl C_IRQHandler之间插入如下代码(见邵贝贝,第2版,第104页的说明:L3.18(4) ):
ldr r0, =OSIntNesting
ldrb r0,[r0]
cmp r0,#1
bne XX
ldr r0,=OSTCBCur
ldr r1,[r0]
str sp,[r1] ; store sp in preempted tasks's TCB
XX
二、ucGui 390移植到ARM9 s3c2410
1. Lcd驱动:
1) 画点函数:
对dm2410实验板上的lcd,左上为原点(0,0),函数如下:
void LCD2410_SetPixel(int xp, int yp, U16 color, int dindex)
{
*(pLCDBuffer565 + SCREEN_WIDTH*yp + xp) = color;
}
为了达到更快的显示速度,别忘了开启Cache!!!
2)LCD控制器的初始化,对2410函数如下:
int LCD2410_Init(void)
{
if(!isLcdInit)
{
InitLcdPort();
InitLcdRegs();
isLcdInit = 1;
return 0;
}
return 1;
}
void InitLcdPort(void)
{
// LCD port initialize.
s2410IOP->GPCUP = 0xFFFFFFFF;
s2410IOP->GPCCON = 0xAAAAAAAA;
s2410IOP->GPDUP = 0xFFFFFFFF;
s2410IOP->GPDCON = 0xAAAAAAAA;
s2410IOP->GPGCON &= ~(3 << 8); /* Set LCD_PWREN as output */
s2410IOP->GPGCON |= (1 << 8);
s2410IOP->GPGDAT |= (1 << 4); //* Backlight ON
}
void InitLcdRegs(void)
{
s2410LCD->LCDCON1 = (CLKVAL_TFT << 8) | //* VCLK = HCLK / ((CLKVAL + 1) * 2) -> About 7 Mhz
(EACH_FRAME << 7) | //* 0 : Each Frame
(3 << 5) | // TFT LCD Pannel
(12 << 1) | //Y: // 16bpp Mode
(0 << 0) ; // Disable LCD Output
s2410LCD->LCDCON2 = (VBPD << 24) | //*
VBPD : ((32-1)&0xff) = 0x1f
(LINEVAL_TFT << 14) | //* LINEVAL_TFT : 480 - 1
(VFPD << 6) | //* VFPD : ((11-1)&0xff) = 0xa
(VSPW << 0) ; //* VSPW : ((2-1) &0x3f) = 0x1
s2410LCD->LCDCON3 = (HBPD << 19) | //* HBPD : ((88-1)&0x7f)
(HOZVAL_TFT << 8) | //* HOZVAL_TFT : 800 - 1
(HFPD << 0) ; //* HFPD : ((40-1)&0xff)
s2410LCD->LCDCON4 = (MVAL << 8) | //* MVAL : 13
(HSPW << 0) ; //* HSPW : ((128-1)&0xff)
s2410LCD->LCDCON5 = (0 << 12) | // BPP24BL : LSB valid
(1 << 11) | // FRM565 MODE : 5:5:5:1 Format
(0 << 10) | // INVVCLK : VCLK Falling Edge
(1 << 9) | // INVVLINE : Inverted Polarity
(1 << 8) | // INVVFRAME : Inverted Polarity
(0 << 7) | // INVVD : Normal
(0 << 6) | // INVVDEN : Normal
(0 << 5) | // INVPWREN : Normal
(0 << 4) | // INVENDLINE : Normal
(1 << 3) | // PWREN : Disable PWREN
(0 << 2) | // ENLEND : Disable LEND signal
(0 << 1) | // BSWP : Swap Disable
(1 << 0) ; // HWSWP : Swap Enable
s2410LCD->LCDSADDR1 = ((FRAMEBUF_DMA_BASE >> 22) << 21) |
((M5D(FRAMEBUF_DMA_BASE >> 1)) << 0);
s2410LCD->LCDSADDR2=M5D( (FRAMEBUF_DMA_BASE+(LCD_XSIZE_TFT*LCD_YSIZE_TFT*2))>>1 );
s2410LCD->LCDSADDR3=(((LCD_XSIZE_TFT-LCD_XSIZE_TFT)/1)<<11)|(LCD_XSIZE_TFT/1);
s2410LCD->LCDINTMSK|=(3); // MASK LCD Sub Interrupt
s2410LCD->LPCSEL&=(~7); // Disable LPC3600
s2410LCD->TPAL=0; // Disable Temp Palette
s2410LCD->LCDCON1 |= 1;
}
其中,部分变量、常量定义如下:
#define LCD_XSIZE_TFT (800)
#define LCD_YSIZE_TFT (480)
#define HOZVAL_TFT (LCD_XSIZE_TFT-1)
#define LINEVAL_TFT (LCD_YSIZE_TFT-1)
#define MVAL (13)
#define MVAL_USED (1)
#define EACH_FRAME (0)
//STN/CSTN timing parameter for LCBHBT161M(NANYA)
#define WLH (3)
#define WDLY (3)
#define LINEBLANK (1 &0xff)
#define VBPD ((32-1)&0xff)
#define VFPD ((11-1)&0xff)
#define VSPW ((2-1) &0x3f)
#define HBPD ((88-1)&0x7f)
#define HFPD ((40-1)&0xff)
#define HSPW ((128-1)&0xff)
#define CLKVAL_TFT (0)
#define M5D(n) ((n) & 0x1fffff)
#define SCREEN_WIDTH 800 //800
#define SCREEN_HEIGHT 480 //480
#define FRAMEBUF_DMA_BASE (0x35000000)
U16* pLCDBuffer565=(U16*)FRAMEBUF_DMA_BASE;
3)填写配置文件LCDConf.h
#define LCD_XSIZE (800) /* X-resolution of LCD, Logical coor. */
#define LCD_YSIZE (480) /* Y-resolution of LCD, Logical coor. */
#define LCD_BITSPERPIXEL (16)
#define LCD_CONTROLLER 1
#define LCD_SWAP_RB_0 1
2.触摸屏驱动:
触摸屏驱动计算出触摸屏的坐标(x,y),对dm2410实验板上的触摸屏,左下为原点,但不一定是(0,0)。两个函数:
1) 设置中断向量,开中断:
void SetTSInterrupt(void)
{
rADCDLY = (50000);
rADCCON = (1<<14)|(ADCPRS<<6)|(7<<3)|(0<<2)|(0<<1)|(0);
rADCTSC = (0<<8)|(1<<7)|(1<<6)|(0<<5)|(1<<4)|(0<<3)|(0<<2)|(3);
pISR_ADC = (U32)TSIrqISR; //
rINTMSK &= ~(BIT_ADC);
rINTSUBMSK &= ~(BIT_SUB_TC);
}
2) 中断处理函数:
static void TSIrqISR(void)
{
int i;
U32 Pt[6];
rINTSUBMSK |= (BIT_SUB_ADC|BIT_SUB_TC);
if(rADCDAT0 & 0x8000)
{//抬起
isDown = 0;
rADCTSC &= 0xff; // Set stylus down interrupt
TX = -1;
TY = -1; //抬起触笔时,TX,TY要值成不大于0的数
}
else //按下
{ isDown = 1;
rADCTSC=(0<<8)|(0<<7)|(0<<6)|(1<<5)|(1<<4)|(1<<3)|(0<<2)|(1);
for(i=0;i
{
rADCCON|=0x1; // Start X-position conversion
while(rADCCON & 0x1); // Check if Enable_start is low
while(!(0x8000&rADCCON)); // Check ECFLG
Pt[i]=(0x3ff&rADCDAT0);
}
Pt[5]=(Pt[0]+Pt[1]+Pt[2]+Pt[3]+Pt[4])/5;//多次采样取平均值
TX = Pt[5];
rADCTSC=(0<<8)|(0<<7)|(1<<6)|(1<<5)|(0<<4)|(1<<3)|(0<<2)|(2);
for(i=0;i
{
rADCCON|=0x1; // Start Y-position conversion
while(rADCCON & 0x1); // Check if Enable_start is low
while(!(0x8000&rADCCON)); // Check ECFLG
Pt[i]=(0x3ff&rADCDAT1);
}
Pt[5]=(Pt[0]+Pt[1]+Pt[2]+Pt[3]+Pt[4])/5;// 多次采样取平均值
TY = Pt[5];
rADCTSC=(1<<8)|(1<<7)|(1<<6)|(0<<5)|(1<<4)|(0<<3)|(0<<2)|(3);
}
//cprintf("%d,%d\n",TX,TY);
OSMboxPost(TouchMbox, 0);//向处理触摸进程发消息
rSUBSRCPND |= BIT_SUB_TC;
rINTSUBMSK &= ~(BIT_SUB_TC); // Unmask sub interrupt (TC)
ClearPending(BIT_ADC);
}
3) 需要的量:
#define LOOP 1
#define ADCPRS 0x27
int TX=0;//触摸坐标x
int TY=0;//触摸坐标y
extern OS_EVENT *TouchMbox;
int isDown;
4) 触摸屏校准:
Ucgui390中,带有一校准程序(于TOUCH_Calibrate.c中),可以改写为我所用(见下文)。
也可设置默认值,测出左下最小坐标minX,minY和右上最大坐标maxX,maxY,注意是触摸坐标,不是lcd坐标,如下填写配置文件GUITouchConf.h:
#define GUI_TOUCH_AD_LEFT minX
#define GUI_TOUCH_AD_TOP maxY
#define GUI_TOUCH_AD_RIGHT maxX
#define GUI_TOUCH_AD_BOTTOM minY
#define GUI_TOUCH_SWAP_XY 0
#define GUI_TOUCH_MIRROR_X 1
#define GUI_TOUCH_MIRROR_Y 1
#define TOUCH_NEED_CALIBRATE 0
3、如下填写GUIConf.h:
#define GUI_OS (1) /* Compile with multitasking support */
#define GUI_SUPPORT_TOUCH (1) /* Support a touch screen (req. win-manager) */
#define GUI_SUPPORT_UNICODE (1) /* Support mixed ASCII/UNICODE strings */
#define GUI_SUPPORT_CHINESE (1)
#define GUI_DEFAULT_FONT &GUI_Font6x8
#define GUI_ALLOC_SIZE 12500 /* Size of dynamic memory ... For WM and memory devices*/
#define GUI_WINSUPPORT 1 /* Window manager package available */
#define GUI_SUPPORT_MEMDEV 1 /* Memory devices available */
#define GUI_SUPPORT_AA 1 /* Anti aliasing available */
4、ucgui与lcd驱动函数的连接,即修改LCDWin.c文件:
在LCDWin.c中,去掉无用的头文件包含。
#define SETPIXEL(x, y, c) LCD2410_SetPixel(x, y, c, LCD_DISPLAY_INDEX)
#define GETPIXEL(x, y) LCD2410_GetPixel(x,y, LCD_DISPLAY_INDEX)
即将“LCDSIM_XX”改成“LCD2410_XX”,这两个函数位于lcd驱动文件中。
……………………………
#define SETPIXEL(x, y, c) \
if (!_CheckBound(c)) { \
LCD2410_SetPixel(x, y, c, LCD_DISPLAY_INDEX); \
}
#else
#define SETPIXEL(x, y, c) LCD2410_SetPixel(x, y, c, LCD_DISPLAY_INDEX)
#endif
#define GETPIXEL(x, y) LCD2410_GetPixel(x,y,LCD_DISPLAY_INDEX)
……………………………
static void _XorPixel(int x, int y) {
unsigned int Index = LCD_L0_GetPixelIndex(x,y);
LCD2410_SetPixel(x, y, LCD_NUM_COLORS-1-Index, LCD_DISPLAY_INDEX);
}
……………………………
int LCD_L0_Init(void) {
return LCD2410_Init();//调用lcd初始化函数
}
5、某些编译器(如:ads1.2)不会初始化全局变量,因此做如下事:
1) 修改如下函数为:(位于GUICore.c)
static void _InitContext(GUI_CONTEXT* pContext) {
memset(pContext,0,sizeof(GUI_CONTEXT));//add
#if GUI_SUPPORT_MEMDEV
GUI_SelectLCD();
#else
LCD_SetClipRectMax();//LCD_L0_GetRect
#endif
pContext->pLCD_HL = &LCD_HL_APIList;
pContext->pAFont = GUI_DEFAULT_FONT;
pContext->pClipRect_HL = &GUI_Context.ClipRect;
pContext->PenSize = 1;
pContext->DrawMode = GUI_DRAWMODE_NORMAL;//add
pContext->TextMode = GUI_TEXTMODE_NORMAL;//add
pContext->TextAlign = GUI_TA_LEFT|GUI_TA_TOP;//add
pContext->AA_HiResEnable = 0;//add
/* Variables in WM module */
#if GUI_WINSUPPORT
pContext->hAWin = WM_GetDesktopWindow();
#endif
/* Variables in GUI_AA module */
pContext->AA_Factor = 3;
LCD_SetBkColor(GUI_DEFAULT_BKCOLOR);
LCD_SetColor(GUI_DEFAULT_COLOR);
}
修改如下函数为:(位于GUIAlloc.c)
void GUI_ALLOC_Init(void) {
........
GUI_ALLOC.NumUsedBytes = 0;
memset(&aBlock,0,sizeof(aBlock[0])*GUI_MAXBLOCKS);/////self
aBlock[0].Size = (1<
}
修改如下函数为:(位于WM.c)(――――――――――New)
void WM_Init(void) {
if (!_IsInited) {
……….
memset(&_ClipContext,0,sizeof(WM_IVR_CONTEXT));//add
NextDrawWin = WM__FirstWin = WM_HWIN_NULL;
……………….
_IsInited =1;
}
}
2)自己编写如下函数(ads1.2):
void MyInit()
{
IsInitialized = 0;//MemDev
_IsInited = 0;//Win
WM__CreateFlags = 0;
GUI_CURSOR_pfTempHide = NULL;
GUI_Context.hDevData = 0;
WM__hCapture=0;
WM__hWinFocus=0;
WM_pfPollPID = 0;
WM_pfHandlePID = 0;
GUI_pfTimerExec = 0;
_KeyMsgCnt = 0;
}
该函数应在GUI_Init()调用之前调用。
6、改写GUI_Init()函数:
int GUI_Init(void) {
int r;
GUI_DEBUG_LOG("\nGUI_Init()");
/* Init system wide globals first */
GUI_DecChar = '.';
/* Init context */
_InitContext(&GUI_Context);
GUITASK_INIT();
r = LCD_Init();
#if GUI_WINSUPPORT
WM_Init();
#endif
GUITASK_COPY_CONTEXT();
GUI_Clear();
GUI_X_Init();
GUI_CURSOR_Show();//启动即显示鼠标
return r;
}
三、ucGUI与ucOS的整合,主要修改GUI_X.c文件:
1、定义信号量及全局量:
static OS_EVENT *DispSem;
static OS_EVENT *EventMbox;
static OS_EVENT *KeySem;
static int KeyPressed;
static char KeyIsInited;
2、实现结合函数:
int GUI_X_GetTime(void)
{
return ((int)OSTimeGet());
}
void GUI_X_Delay(int period)
{
INT32U ticks;
ticks = (period * 1000) / OS_TICKS_PER_SEC;
OSTimeDly((INT16U)ticks);
}
void GUI_X_Unlock(void)
{
OSSemPost(DispSem);
}
void GUI_X_Lock(void)
{
U8 err;
OSSemPend(DispSem, 0, &err);
}
U32 GUI_X_GetTaskId(void)
{
return ((U32)(OSTCBCur->OSTCBPrio));
}
void GUI_X_WaitEvent (void)
{
INT8U err;
(void)OSMboxPend(EventMbox, 0, &err);
}
void GUI_X_SignalEvent (void)
{
(void)OSMboxPost(EventMbox, (void *)1);
}
void GUI_X_InitOS(void)
{
DispSem = OSSemCreate(1);
EventMbox = OSMboxCreate((void *)0);
}
void GUI_X_ExecIdle(void) {GUI_X_Delay(1);}
void GUI_X_Init(void) {
SetTSInterrupt();//此处接入触摸屏的中断设置
GUI_TOUCH_SetDefaultCalibration();//用默认值校准触摸屏
}
四、与触摸屏有关的函数:
1、4个功能函数:
void GUI_TOUCH_X_ActivateX(void) {//空}
void GUI_TOUCH_X_ActivateY(void) {//空}
int GUI_TOUCH_X_MeasureX(void) {
return TX;//返回触摸坐标x
}
int GUI_TOUCH_X_MeasureY(void){
return TY; //返回触摸坐标y
}
2、触摸屏校准任务(进程, 来自示例程序:TOUCH_Calibrate.c):
#if TOUCH_NEED_CALIBRATE
static const char * _acPos[] = {
"(upper left position)",
"(lower right position)"
};
static void _WaitForPressedState(int Pressed) {
GUI_PID_STATE State;
/* Wait until touch is pressed */
do {
GUI_TOUCH_GetState(&State);
if (State.Pressed == Pressed) {
break;
}
GUI_Delay (100);
} while (1);
}
static void _DispStringCentered(const char * pString) {
GUI_RECT Rect;
Rect.x0 = Rect.y0 = 0;
Rect.x1 = LCD_GetXSize() - 1;
Rect.y1 = LCD_GetYSize() - 1;
GUI_DispStringInRect(pString, &Rect, GUI_TA_HCENTER | GUI_TA_VCENTER);
}
static void _GetPhysValues(int LogX, int LogY, int * pPhysX, int * pPhysY, const char * pString) {
char acText[] = "Press here";
GUI_RECT Rect;
int FontSizeY, Align;
FontSizeY = GUI_GetFontSizeY();
GUI_Clear();
GUI_SetColor(GUI_BLACK);
_DispStringCentered("Runtime calibration,\n"
"please touch the screen\n"
"at the center of the ring."); /* Ask user to press the touch */
/* Calculate the rectangle for the string */
Rect.y0 = LogY - FontSizeY;
Rect.y1 = LogY + FontSizeY;
if (LogX < LCD_GetXSize() / 2) {
Rect.x0 = LogX + 15;
Rect.x1 = LCD_GetXSize();
Align = GUI_TA_LEFT;
} else {
Rect.x0 = 0;
Rect.x1 = LogX - 15;
Align = GUI_TA_RIGHT;
}
/* Show the text nearby the ring */
GUI_DispStringInRect(acText, &Rect, Align | GUI_TA_TOP);
GUI_DispStringInRect(pString, &Rect, Align | GUI_TA_BOTTOM);
/* Draw the ring */
GUI_FillCircle(LogX, LogY, 10);
GUI_SetColor(GUI_WHITE);
GUI_FillCircle(LogX, LogY, 5);
GUI_SetColor(GUI_BLACK);
/* Wait until touch is pressed */
_WaitForPressedState(1);
*pPhysX = GUI_TOUCH_GetxPhys();
*pPhysY = GUI_TOUCH_GetyPhys();
/* Wait until touch is released */
_WaitForPressedState(0);
}
static void _Explain(void) {
_DispStringCentered("This sample shows how\n"
"a touch screen can be\n"
"calibrated at run time.\n"
"Please press the touch\n"
"screen to continue...");
GUI_DispStringHCenterAt("TOUCH_Calibrate", LCD_GetXSize() / 2, 5);
_WaitForPressedState(1);
_WaitForPressedState(0);
}
void CalibrateTask(void* pdata) {//触摸屏校准任务入口
int aPhysX[2], aPhysY[2], aLogX[2], aLogY[2], i;
GUI_SetBkColor(GUI_WHITE);
GUI_Clear();
GUI_SetColor(GUI_BLACK);
GUI_SetFont(&GUI_Font13B_ASCII);
_Explain();
/* Set the logical values */
aLogX[0] = 15;
aLogY[0] = 15;
aLogX[1] = LCD_GetXSize() - 20;
aLogY[1] = LCD_GetYSize() - 20;
/* Get the physical values of the AD converter for 2 positions */
for (i = 0; i < 2; i++) {
_GetPhysValues(aLogX[i], aLogY[i], &aPhysX[i], &aPhysY[i], _acPos[i]);
}
/* Use the physical values to calibrate the touch screen */
GUI_TOUCH_Calibrate(0, aLogX[0], aLogX[1], aPhysX[0], aPhysX[1]); /* Calibrate X-axis */
GUI_TOUCH_Calibrate(1, aLogY[0], aLogY[1], aPhysY[0], aPhysY[1]); /* Calibrate Y-axis */
{ /* calculate and display values for configuration file */
int calX0, calX1;
int calY0, calY1;
GUI_Clear();
GUI_TOUCH_GetCalData(GUI_COORD_X, &calX0, &calX1);
GUI_TOUCH_GetCalData(GUI_COORD_Y, &calY0, &calY1);
GUI_DispStringAt("calX0: ", 0, 0); GUI_DispDec(calX0, 4); GUI_DispNextLine();
GUI_DispString ("calX1: "); GUI_DispDec(calX1, 4); GUI_DispNextLine();
GUI_DispString ("calY0: "); GUI_DispDec(calY0, 4); GUI_DispNextLine();
GUI_DispString ("calY1: "); GUI_DispDec(calY1, 4);
GUI_DispStringAt("lcdx0: ", 0, 200); GUI_DispDec(aLogX[0], 4); GUI_DispNextLine();
GUI_DispString ("lcdx1: "); GUI_DispDec(aLogX[1], 4); GUI_DispNextLine();
GUI_DispString ("lcdy0: "); GUI_DispDec(aLogY[0], 4); GUI_DispNextLine();
GUI_DispString ("lcdy1: "); GUI_DispDec(aLogY[1], 4); GUI_DispNextLine();
GUI_DispString ("tscX0: "); GUI_DispDec(aPhysX[0], 4); GUI_DispNextLine();
GUI_DispString ("tscX1: "); GUI_DispDec(aPhysX[1], 4); GUI_DispNextLine();
GUI_DispString ("tscY0: "); GUI_DispDec(aPhysY[0], 4); GUI_DispNextLine();
GUI_DispString ("tscY1: "); GUI_DispDec(aPhysY[1], 4); GUI_DispNextLine();
GUI_DispString ("Please touch display to continue...");
GUI_Delay(1000);
_WaitForPressedState(1);
_WaitForPressedState(0);
}
GUI_Clear();
SystemOn();
OSTaskSuspend(OS_PRIO_SELF);
}
3、为了响应触摸屏,做如下事:
1)创建进程处理触摸事件,形式如下:
void TouchTask(void* data) {
INT8U err;
while(1)
{
CONSOL_Printf("Waiting for Touch......\n");
OSMboxPend(TouchMbox, 0, &err); /* Acquire semaphore to perform random numbers */
CONSOL_Printf("Got a message\n");
GUI_TOUCH_Exec();//
}
}
2) 如下修改函数 GUI_TOUCH_Exec():
void GUI_TOUCH_Exec(void) {
#ifndef WIN32
static U8 ReadState;
int x,y;
/* calculate Min / Max values */
if (xyMinMax[GUI_COORD_X].Min < xyMinMax[GUI_COORD_X].Max) {
xMin = xyMinMax[GUI_COORD_X].Min;
xMax = xyMinMax[GUI_COORD_X].Max;
} else {
xMax = xyMinMax[GUI_COORD_X].Min;
xMin = xyMinMax[GUI_COORD_X].Max;
}
if (xyMinMax[GUI_COORD_Y].Min < xyMinMax[GUI_COORD_Y].Max) {
yMin = xyMinMax[GUI_COORD_Y].Min;
yMax = xyMinMax[GUI_COORD_Y].Max;
} else {
yMax = xyMinMax[GUI_COORD_Y].Min;
yMin = xyMinMax[GUI_COORD_Y].Max;
}
/* Execute the state machine which reads the touch */
//switch (ReadState) {
//case 0:
yPhys = TOUCH_X_MeasureY();
// TOUCH_X_ActivateY(); /* Prepare X- measurement */
// ReadState++;
// break;
//default:
xPhys = TOUCH_X_MeasureX();
// TOUCH_X_ActivateX(); /* Prepare Y- measurement */
/* Convert values into logical values */
#if !GUI_TOUCH_SWAP_XY /* Is X/Y swapped ? */
x = xPhys;
y = yPhys;
#else
x = yPhys;
y = xPhys;
#endif
if ((x
GUI_TOUCH_StoreUnstable(-1,-1);
} else {
x = AD2X(x);
y = AD2Y(y);
GUI_TOUCH_StoreUnstable(x,y);
}
/* Reset state machine */
//ReadState=0;
// break;
//}
#endif /* WIN32 */
}
五、 GUI多任务测试:
测试程序:MT_MultiTasking.c
欢迎来信交流: