OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4043|回复: 13

对战舰板示例代码的偏重软件角度的交流

[复制链接]

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
发表于 2015-8-16 09:09:33 | 显示全部楼层 |阅读模式

我之前是纯软件开发(主要是.Net)的,很少直接面对硬件。对硬件的知识可以说0基础。只剩下上学时的记忆,好像是主要靠什么中断编程。最近因为一些原因,开始接触硬件,选了战舰这块板子作为学习平台。

所以,在搞明白硬件之前,先从我相对擅长的软件方面进行切入学习,并把学习中自己的一些想法和大家交流下,便于自己更好的进步。


帖子内容会根据进度随时更新。


1、对延时函数的修改

.Net 中可以使用 Thread.Sleep 方法停止运行当前线程一段指定的时间,这段时间基本上是不受限制,随便设的。战舰提供了对应的 delay_xx 方法。但是受限于硬件,一次调用最多只能延时不到2秒中。超过的话,必须要连续调用了。这样用户代码会比较难看些。所以,我把相应的操作,直接放在了 delay_xx 方法中。并改名字为 Wait_xx(这里需要说一下,我对代码中的命名方式不是很习惯,这个和我之前的软件开发环境不一样有原因,大家的各自风格问题,没有谁好谁坏的事。我尽量改成了自己的风格,方便后续的开发

由于还没有接触什么OS 之类的,所以仅修改了常规的方法,具体如下:

[mw_shl_code=c,true]// 延时 1 秒以内的任意微妙数 aMicroSeconds。 // 对 72MHz 条件下,使用 24bit 计数器,所以最多允许一次等待的时长为 1.864s。 // 因此,对于长时间的等待,需要通过多次调用短间隔等待拼接。 // 这里,构造一个仅最多延时 1 秒的辅助函数。 void _Wait_max1000000us(u32 aMicroSeconds) { u32 lTemp; if(aMicroSeconds == 0) return; if(aMicroSeconds > 1000000) aMicroSeconds = 1000000; SysTick->LOAD = aMicroSeconds * fac_us; // 时间加载 SysTick->VAL = 0x00; // 清空计数器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 开始倒数 do { lTemp = SysTick->CTRL; } while((lTemp & 0x01) && !(lTemp & 0x10000)); // 等待时间到达 SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭计数器 SysTick->VAL = 0x00; // 清空计数器 } // 延时任意微秒数 void Wait_us(u32 aMicroSeconds) { u32 i = 0; // 先延时整秒数 for(i = 1; i <= aMicroSeconds / 1000000; i++) _Wait_max1000000us(1000000); // 再延时剩余的非整秒数 _Wait_max1000000us(aMicroSeconds % 1000000); } // 延时任意毫秒数 void Wait_ms(u32 aMilliSeconds) { Wait_us(aMilliSeconds * 1000); } // 延时任意秒数 void Wait_s(u32 aSeconds) { Wait_ms(aSeconds * 1000); }[/mw_shl_code]


2、显示数字方式的修改(5楼)

3、现在发现个问题,关于汉字显示的。(6楼)

4、串口测试代码的学习 (10楼)

有软件开发经验,从0硬件基础学习STM32开发。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

120

主题

7878

帖子

13

精华

资深版主

Rank: 8Rank: 8

积分
12012
金钱
12012
注册时间
2013-9-10
在线时间
427 小时
发表于 2015-8-16 13:28:56 | 显示全部楼层
现在,程序把烂铜烂铁变得智能化了,人呢,一旦离开了这烂铜烂铁就不知道干啥了
回复 支持 反对

使用道具 举报

105

主题

522

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1386
金钱
1386
注册时间
2012-10-23
在线时间
97 小时
发表于 2015-8-16 13:30:35 | 显示全部楼层
基本用不到需要延时s级别的。
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 14:51:43 | 显示全部楼层
回复【3楼】miaoguoqiang:
---------------------------------
嗯,只是为了完整,特意加的一个函数。不过我在轮换 led 显示,为了看着不太烦,设成 5 秒一换。

这里顺便说一下,原先的 delay 函数,如果传一个 0 的参数,会死掉。虽然现实中没人会延迟 0 个时间单位,不过程序中还是做了个判断。
有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 14:52:37 | 显示全部楼层
回复【2楼】八度空间:
---------------------------------
这个都是很简单的算法,只是想通过学习示例代码,并尝试修改来加快自己的学习进度。
有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 15:14:24 | 显示全部楼层

2、显示数字方式的修改
lcd示例代码中有个 void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);
这个函数可以以指定宽度显示一个数字,并通过参数确定是否补前导0,以及是否在显示时,不擦除背景以实现叠加输出的效果。

我在尝试将代表时间的总秒数输出并右对齐时,发现,这个函数有个问题。从 1970年算起,到今天经过的总秒数已经是 10 位数了。我设置的宽度是12,但是显示的值确很诡异。
大家可以试试用这个函数显示 1234567890,12位宽。实际显示的是 101234567890。 

经过研究,发现,实际上是这个函数内部判断数字位数的方法有问题。判断时使用了 10 的 n 次方,n  的初始值随总宽度的增加而增加,但是当 n 达到一定值时,计算的结果会超过 u32 上限,导致溢出折回从最小值开始重新计数,结果就导致错误。

我的想法是,干脆用非标准库中 itoa 的类似方法将数字转换成对应的字符串,之后再根据需要直接补零。

代码中很多名称都变了,不过应该不影响大家去对照看。另外,颜色我定义了一个透明色用于实现叠加显示的效果。
同时,将背景色和前景色作为参数传递给相应方法。(不使用全局变量了,因为会造成多任务时,画面颜色冲突问题)

[mw_shl_code=c,true]// 将数字按指定基数转换成字符串。 char _Index[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char* fun_NumberToString(long aNumber, char *aString, u8 aRadix) { unsigned long lNumber; int i = 0, j, k; if(aRadix == 10 && aNumber < 0) { aString[i++] = '-'; lNumber = (unsigned long)-aNumber; } else lNumber = (unsigned long)aNumber; do { aString[i++] = _Index[lNumber % aRadix]; lNumber /= aRadix; } while(lNumber); aString = '\0'; if(aString[0] == '-') k = 1; else k = 0; for(j = k; j <= (i - 1) / 2; j++) { char temp = aString[j]; aString[j] = aString[i - 1 + k - j]; aString[i - 1 + k - j] = temp; } return aString; }[/mw_shl_code]

[mw_shl_code=c,true]void LCD_DrawNum(FontSizeTypeDef aSize, u32 aNumber, u16 aX, u16 aY, u8 aLength, bool aPadZero, ColorTypeDef aBackColor, ColorTypeDef aForeColor) { u8 lIndex, i; u8 lLength; char lNumberString[65]; fun_NumberToString(aNumber, lNumberString, 10); lLength = strlen(lNumberString); if(aLength < lLength) aLength = lLength; for(lIndex = 0; lIndex < aLength - lLength; lIndex++) LCD_DrawChar(aSize, (aPadZero == TRUE ? '0' : ' '), aX + (aSize / 2) * lIndex, aY, aBackColor, aForeColor); for(i = 0; i < lLength; i++) LCD_DrawChar(aSize, lNumberString, aX + (aSize / 2) * (lIndex + i), aY, aBackColor, aForeColor); }[/mw_shl_code]

[mw_shl_code=c,true]//上述代码中涉及到两个类型,是我定义的两个枚举类型。其中颜色取值参考了原子示例代码 typedef enum { FONTSIZE_16 = 16, FONTSIZE_24 = 24, FONTSIZE_32 = 32 } FontSizeTypeDef; typedef enum { COLOR_TRANSPARENT = 0x0001,// 透明色,背景色选择此颜色,可实现叠加显示效果。需要对应方法以此作为判断 COLOR_WHITE = 0xFFFF, COLOR_BLACK = 0x0000, COLOR_BLUE = 0x001F, COLOR_BRED = 0xF81F, COLOR_GRED = 0xFFE0, COLOR_GBLUE = 0x07FF, COLOR_RED = 0xF800, COLOR_MAGENTA = 0xF81F, COLOR_GREEN = 0x07E0, COLOR_CYAN = 0x7FFF, COLOR_YELLOW = 0xFFE0, COLOR_BROWN = 0xBC40, // 棕色 COLOR_BRRED = 0xFC07, // 棕红色 COLOR_GRAY = 0x8430, // 灰色 COLOR_DARKBLUE = 0x01CF, // 深蓝色 COLOR_LIGHTBLUE = 0x7D7C, // 浅蓝色 COLOR_GRAYBLUE = 0x5458, // 灰蓝色 COLOR_LIGHTGREEN = 0x841F, // 浅绿色 COLOR_LIGHTGRAY = 0xEF5B, // 浅灰色(PANNEL) COLOR_LGRAY = 0xC618, // 浅灰色(PANNEL),窗体背景色 COLOR_LGRAYBLUE = 0xA651, // 浅灰蓝色(中间层颜色) COLOR_LBBLUE = 0x2B12 // 浅棕蓝色(选择条目的反色) } ColorTypeDef; [/mw_shl_code]


涉及到的实现透明色的 LCD_DrawChar 方法,也是改造自原子的示例代码

[mw_shl_code=c,true]void LCD_DrawChar(FontSizeTypeDef aSize, u8 aChar, u16 aX, u16 aY, ColorTypeDef aBackColor, ColorTypeDef aForeColor) { u8 lByteValue, lByteIndex, lBitIndex; u8 lByteCount = (aSize / 8) * (aSize / 2); // 得到字体一个字符对应点阵集所占的字节数 u8 lCharIndex = aChar - ' '; // 得到偏移后的值(ASCII字库是从空格开始取模,所以 - ' '就是对应字符的字库) u16 x0 = aX; u16 y0 = aY; for(lByteIndex = 0; lByteIndex < lByteCount; lByteIndex++) { aX = x0 + lByteIndex / (aSize / 8); aY = lByteIndex % (aSize / 8) == 0 ? y0 : aY; if(aSize == FONTSIZE_16) lByteValue = ASCII16[lCharIndex][lByteIndex]; // 调用1608字体 else if(aSize == FONTSIZE_24) lByteValue = ASCII24[lCharIndex][lByteIndex]; // 调用2412字体 else if(aSize == FONTSIZE_32) lByteValue = ASCII32[lCharIndex][lByteIndex]; // 调用3216字体 else return; // 没有的字库 // 从上到下,从左到右,按列的点阵。 for(lBitIndex = 0; lBitIndex < 8; lBitIndex++) { if(aX >= gLCDDevice.Width || aY >= gLCDDevice.Height) return; //超区域了 if(lByteValue & 0x80) LCD_DrawPointFast(aX, aY, aForeColor); else if(aBackColor != COLOR_TRANSPARENT) LCD_DrawPointFast(aX, aY, aBackColor); lByteValue <<= 1; aY++; } } }[/mw_shl_code]

有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 15:24:02 | 显示全部楼层
3、现在发现个问题,关于汉字显示的。

大家有没有试过“实”这个字,好像没有点阵呀。
有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

120

主题

7878

帖子

13

精华

资深版主

Rank: 8Rank: 8

积分
12012
金钱
12012
注册时间
2013-9-10
在线时间
427 小时
发表于 2015-8-16 16:29:54 | 显示全部楼层
回复【6楼】dragon7799:
---------------------------------
我整合成这个方法了http://www.openedv.com/posts/list/43524.htm

目前自己更新到V2.5版本了,完善了很多东西

感觉对自己来说已经够用了,目测还不是很好,呵呵,望大牛指导指导
现在,程序把烂铜烂铁变得智能化了,人呢,一旦离开了这烂铜烂铁就不知道干啥了
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 17:08:45 | 显示全部楼层

回复【8楼】 八度空间 :
---------------------------------
去帖子看了下,这个很厉害了。需要涉及到对格式串词法分析、语法分析等好多编译原理的问题了。搞定了,对自己的程序设计的提高那是相当有帮助的。

不过,对于这个问题,其实我有个比较简单的思路。其实整个的myprintf 函数,完全可以使用C语言的 sprintf 函数来完成。将需要格式化输出的内容输出到一个字符串中(是直接支持中文的),然后在利用 LCD_DrawString直接打印得到的字符串即可。LCD_DrawString 中可以直接对编码进行判断,支持中英文混合输出。


LCD_DrawString可以这么写(改自原子示例代码)

[mw_shl_code=c,true]void LCD_DrawString(FontSizeTypeDef aSize, u8* aWString, u16 aX, u16 aY, u16 aWidth, u16 aHeight, ColorTypeDef aBackColor, ColorTypeDef aForeColor) { u16 lWidth = (aWidth == 0 ? aSize / 2 * strlen((const char*)aWString) : aWidth); u16 lHeight = (aHeight == 0 ? aSize : aHeight); u16 x0 = aX; u16 y0 = aY; while(*aWString != '\0') // 数据未结束 { if(*aWString < 0x80) // ASCII { if(aX > (x0 + lWidth - aSize / 2)) // 超出规定宽度,换行 { aY += aSize; aX = x0; } if(aY > (y0 + lHeight - aSize)) break; // 超出规定高度,退出 if(*aWString == '\n') // 换行符号 { aY += aSize; aX = x0; aWString++; } else LCD_DrawChar(aSize, *aWString, aX, aY, aBackColor, aForeColor); aWString++; aX += aSize / 2; //字符,为全字的一半 } else //中文 { if(aX > (x0 + lWidth - aSize)) //换行 { aY += aSize; aX = x0; } if(aY > (y0 + lHeight - aSize)) break; // 输出中文字符,参考原子的 Show_Font LCD_DrawWChar(aSize, aWString, aX, aY, aBackColor, aForeColor); aWString += 2; aX += aSize; //下一个汉字偏移 } } }[/mw_shl_code]


sprintf 可以这么使用

[mw_shl_code=c,true]u8 lBuf[100]; sprintf((char *)lBuf, "abc,%s", "测试"); LCD_DrawString(FONTSIZE_16, lBuf, 30, 140, 0, 0, COLOR_BLACK, COLOR_YELLOW);[/mw_shl_code]



有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

120

主题

7878

帖子

13

精华

资深版主

Rank: 8Rank: 8

积分
12012
金钱
12012
注册时间
2013-9-10
在线时间
427 小时
发表于 2015-8-16 18:28:11 | 显示全部楼层
回复【9楼】dragon7799:
---------------------------------
嗯,这样也不错
现在,程序把烂铜烂铁变得智能化了,人呢,一旦离开了这烂铜烂铁就不知道干啥了
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 18:56:45 | 显示全部楼层

4、串口测试代码的学习

学习了串口测试代码,感觉串口中断处理只是根据到达的字符做各种状态设置,然后main里面的死循环不断的检查这个状态,满足条件后,做处理。


但是中断中有个问题,如果已经完成了一次数据的接收(0x0d和0x0a都收到了), 在main方法未能及时(实际应用中,也许main还有其他任务)检测到完成状态并处理时,那么随后到达的数据都会丢失了。


感觉在main里这么处理,好像和软件开发时只跑主窗体一个线程,在遇到繁重任务时,界面会假死一样。所以一般都是多线程跑程序。stm32应该是利用中断跑其他处理代码,而不是让任务都有 main来干(不知理解的是否靠谱)。


所以,感觉以上两点(会丢失后续数据,以及main方法或类似软件开发中的主线程尽量不处理实际任务),将中断处理程序修改如下,简单拿串口助手测试了下,暂时成功。

数据以*开始,#结束,这些都是实际中可自定义的,同时,未做#号的转义。(其实 0x0D0x0A也有这个问题,实际使用中,定界符都要考虑转义才行)

[mw_shl_code=c,true]// 针对串口1单独设置一个缓冲区 u8 USART1_RECEIVE_BUFFER[USART_MAX_RECEIVE_LENGHT]; void USART1_IRQHandler(void) { // 数据开始接收状态 static bool lStarted = FALSE; // 已接受数据长度 static u8 lReceivedLength = 0; u8 lReceiveByte; #if SYSTEM_SUPPORT_OS OSIntEnter(); #endif // 接收中断 if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { // 从串口读取一个字节的数据 lReceiveByte = USART_ReceiveData(USART1); // 如果还未启动接收 if(lStarted == FALSE) { // 如果本次收到的字符是启动接收字符,则设置接收启动 if(lReceiveByte == '*') lStarted = TRUE; // 无论是否是启动字符,只要之前的状态是未启动,则直接退出。 goto END; // 为了兼容原先代码,这里用 goto而不是return } // 到这里,应该是启动接收状态,此时判断接收到的字符是否是停止接收字符。 // 如果本次收到的字符是停止字符 if(lReceiveByte == '#') { // 调用处理数据的函数,根据需要由条件编译实现需要的处理版本。 USART1_ProcessData(USART1_RECEIVE_BUFFER, lReceivedLength); lStarted = FALSE; lReceivedLength = 0; goto END; } // 到这里,收到的是普通数据。判断是否接收数据超过缓冲区 if(lReceivedLength == USART_MAX_RECEIVE_LENGHT) { // 缓冲区已经满,作废之前的输入,重新开始。 lStarted = FALSE; lReceivedLength = 0; goto END; } // 到这里,缓冲区足够,且收到的是普通字符,直接追加进缓冲区即可 USART1_RECEIVE_BUFFER[lReceivedLength++] = lReceiveByte; goto END; } END: #if SYSTEM_SUPPORT_OS OSIntExit(); #endif return; }[/mw_shl_code]

有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-8-16 20:53:48 | 显示全部楼层
谢谢分享。。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

8

主题

124

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
212
金钱
212
注册时间
2015-8-1
在线时间
7 小时
 楼主| 发表于 2015-8-16 21:31:08 | 显示全部楼层

回复【12楼】 正点原子 :
---------------------------------
原子哥,这个战舰板子很不错,今天弄通了通过 printf 输出到串口助手,可以模拟以前的控制台输出进行程序状态调试了。正在学习,目前没有顺序,看到那个示例有感觉就进去看一下。硬件是硬伤,很多电路什么的,包括什么术语都是一头雾水。以后会有很多请教你的地方。到时还望不吝赐教。

另外,那个我现在的字库里,汉字“实”无法显示,一片空白,其他汉字都正常,你能帮忙看看么?用的是光盘里提供的字库24点的。

有软件开发经验,从0硬件基础学习STM32开发。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-8-16 22:33:33 | 显示全部楼层
回复【13楼】dragon7799:
---------------------------------
 更新下字库试试,实 字不应该没有才对。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-18 20:58

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表