STM32CubeMX、TrueSTUDIO、J-Flash V6.32f
芯片:STM32L072KBUx
使用CubeMX通过芯片型号新建工程。需要配置如下内容:
配置过程及详细参数如以下截图所示:
注意:只要能实现同样功能即可,不强制必须使用串口1或定时器7。
在单片中需要接收来自上位机的数据,并安装协议规定格式反馈数据。比上位机端简单一些。
把串口接收到的每1byte数据,依次传递给以下这个“协议识别函数”来进行“封包”识别:
void APP_UartProcess(uint8_t uart_data) {
if(IsStartRecvCommand == true) {
UartRxCache[UartRxCacheIndex++] = uart_data;
uint16_t TempCacheLen = UartRxCacheIndex;
if(TempCacheLen > 4 &&
UartRxCache[TempCacheLen - 4] == TAIL[0] &&
UartRxCache[TempCacheLen - 3] == TAIL[1] &&
UartRxCache[TempCacheLen - 2] == TAIL[2] &&
UartRxCache[TempCacheLen - 1] == TAIL[3] &&
UartRxCache[sizeof (HEAD)] + sizeof (HEAD) + sizeof (TAIL) == TempCacheLen //check pkg len
) { //检测到一包接收完成
ProcessAFinishedPackage();
UartRxCacheIndex = 0;
IsStartRecvCommand = false;
}
} else if(uart_data == HEAD[0] && IsStartRecvCommand == false) { //only receiving command when start with a head
IsStartRecvCommand = true;
UartRxCache[UartRxCacheIndex++] = uart_data;
}
}
当单片机需要给上位机反馈命令的时候,使用以下函数计算出“反馈封包”:
/**
* @brief 获取命令反馈封包,返回数据包长度
* @param resPkg 参数详情
* @retval 反馈封包最终长度, -1 缓存数组长度不够
*/
int APP_GetProtocolResponsePKG(ResponsePkg *resPkg) {
//CRC校验码 = [命令类型码 + 命令码 + 命令参数]
//计算方括号内的数据
uint16_t res_data_len = resPkg->param_len + 2;
uint8_t res_data[res_data_len];
res_data[0] = resPkg->command_type;
res_data[1] = resPkg->command;
for (int a = 0; a < resPkg->param_len; a++) {
res_data[2 + a] = resPkg->param_buf[a];
}
//计算缓存数据是否够用
uint16_t buffer_need_len = res_data_len + 8;
if(buffer_need_len > resPkg->res_buffer_len) return -1;
//CRC校验码
uint16_t crc;
GetCRC(res_data, res_data_len, &crc);
uint8_t crc_high_byte = (crc >> 8) & 0xff;
uint8_t crc_low_byte = crc & 0xff;
//组装封包
uint8_t* res_buf = resPkg->res_buffer;
int res_buf_index = 0;
res_buf[res_buf_index++] = HEAD[0];
res_buf[res_buf_index++] = 0;//temp pkg len
for (int a = 0; a < res_data_len; a++) {
res_buf[res_buf_index++] = res_data[a];
}
res_buf[res_buf_index++] = crc_high_byte;
res_buf[res_buf_index++] = crc_low_byte;
uint8_t tail_buf_len = sizeof(TAIL);
for (int a = 0; a < tail_buf_len; a++) {
res_buf[res_buf_index++] = TAIL[a];
}
//计算包长
uint8_t head_len = sizeof(HEAD);
uint8_t tail_len = sizeof(TAIL);
uint8_t temp_pkg_len = res_buf_index;
uint8_t pkg_len = temp_pkg_len - head_len - tail_len;
res_buf[1] = pkg_len;
return temp_pkg_len;
}
以上为协议相关的2个关键函数,其他函数请参考第八小节的项目源码。
这里主要讲一下本代码在实现秒级延时的设计思想。
需求中,我们不仅要求达到指定的时间延时,还需要再延时等待的过程中,接收来自串口的数据,如果串口接收到一个“更新App的请求”。则必须中断延时,来执行“更新App的请求”。
因此,我们不能使用Delay函数来实现延时。所以要使用定时器达到延时。我在一开始写实现这个定时器的时候犯了一个错误。一共需要延时5s左右。我把这5s的时间值直接写到了定时器的周期寄存器中。测试发现没有达到5s的延时。经过调试才明白。定时器的周期寄存器最大为0xffff即65535,也就是说寄存器是放不下5s对应的周期寄存器值。因此重新设计为,定时器500ms中断一次,通过统计中断的次数来,达到一个5s的延时(即,秒级的延时)。为了把定时器实现这个延时的逻辑整理到一个代码文件中,设计了一个“回调函数”,因此,可以通过定时器模块的接口函数设定“所需的定时时间”,当定时时间到达后,定时模块会调用配置好的“回调函数”。把延时完成后需要调用的代码,放在“回调函数”即可。
注意:编程软件默认生成的是hex文件,因此需要配置生成bin文件。
使用bootloader之后,App就不能放置在Flash的默认启动地址了,bootloader会把app放到一个设计好的flash地址所指定的地方。对应的App自己也需要自己被放置到flash的哪个地址,才能正确的使用app的堆栈和中断向量表。否则app无法运行。那么设置app自己的地址方法根据不同开发平台设置方式不同。
使用TrueSTUDIO的设置方式:
//注意还需要 在STM32L072KB_FLASH.ld 文件中 修改flash的地址如下:
由于bootloader在跳转到App之前,把总中断关闭了,因此在app运行后的第一句代码就应该“打开总中断”。在实际中还需要其他的一些配置。总结起来写成一个函数,如下代码所示:
#define USE_IAP
//#define USE_FREERTOS
void BSP_BootloaderInit(void) {
//重新设置中断向量表
#ifdef USE_IAP
SCB->VTOR = ((uint32_t)FLASH_BASE) | ((0x8000) & (uint32_t)0x1fffff80);
//开启总中断
#ifdef USE_FREERTOS
//FreeRTOS :不需要下面的这行打开中断调用,调用了后App反而无法执行
#else
//无任何单片机操作系统 :需要下面这句话,来打开总中断
__set_PRIMASK(0);
#endif
#endif
}
在调试bootloader的过程中,最难调试的部分是Flash的写入和擦除。 必须要把目标bin文件准确完整的写入到Flash的指定地址中。另一个地方是要准确的擦除flash(不能擦除不该擦除的地方)。说这几部分难,是因为擦除和写入的的结果不容易直观的通过调试看到。因此我们需要借助一个专业的Flash操作工具来辅助调试。这个工具就是与“J-Link”配套的软件“J-Flash”。
“校验”功能:
使用自己的bootloader写bin文件到flash中,如果想要确认一下是否正确写入。可以使用J-Flash的“校验”功能校验。“出错”校验的结果截图如下:
“从芯片flash读取程序”的功能:
把bootloader写入到芯片中的bin文件使用j-Flash读取出来,同时使用J-Flash打开bin文件。可以通过人工对比发现两者的差异。从而找出bootloder烧写bin的错误。比如在开发本bootloader的时候,发现bootloader写入到flash的程序无法运行,经过读取出来与原bin文件对比发现大小端的不同。bin文件正确在flash储存的时候,应该是低位在前。但是之前bootloader是把高位写在来前面。因此导致写入的应用程序无法运行。发现大小端不同的截图如下:
源码包含内容:
点此获取源码