为 UART 模块设计状态机
Designing a State Machine for a UART Module
我正在尝试使用 UART AT COMMAND 4G 模块,并且正在尝试设计一个工作流程图作为它的状态机。
我有一个问题
- 处理传入消息并发送命令
- 如何在彼此之间移动和更改状态?
这是我的初始状态:
#define STATE_INIT 0
#define STATE_CONNECTED 1
#define STATE_DISCONNECTED 2
#define STATE_RETRY_CONNECT 3
#define STATE_FAILURE 4
#define STATE_HTTP_POST 5
#define STATE_HTTP_GET 6
#define STATE_HTTP_POST_RETRY 7
#define STATE_HTTP_POST_SUCCESS 8
#define STATE_HTTP_GET_RETRY 9
#define STATE_HTTP_GET_SUCCESS 10
#define STATE_CHECK_CONNECTIVITY 11
#define SIM_STATUS_ERROR 12
#define SIM_STATUS_READY 13
#define SIM_STATUS_LOCKED 14
#define REG_STATUS_UNREGISTERED 15
#define REG_STATUS_SEARCHING 16
#define REG_STATUS_DENIED 17
#define REG_STATUS_OK 18
#define REG_STATUS_HOME 19
#define REG_STATUS_ROAMING 20
#define REG_STATUS_UNKNOWN 21
uint8_t current_state;
void processMessage(char *msg) {
}
void sendCmd(char *cmd) {
strcpy(UART_Out_Buffer, cmd);
UART_Out_Cnt = strlen(cmd);
}
void Init_State(void) {
current_state = STATE_INIT;
sendCmd("AT+CGSOCKCONT=1,""\"IP""\",""\"A1.net""\"");
sendCmd("AT+CSOCKAUTH=1,1,""\"ppp""\",""\"ppp@a1plus.at""\"");
sendCmd("AT+CHTTPSOPSE=""\"ipdb-eu1.com""\",443""\"");
}
在这里,当我们发送命令时,应处理响应。
void process_uart(void)
{
uint16_t uartBufPos = 0;
char line[UART_BUFFER_SIZE];
line[0] = '[=11=]';
uint16_t linePos = 0;
while (UART_Buffer[uartBufPos] != '[=11=]')
{
if (UART_Buffer[uartBufPos] == '\n')
{
line[linePos] = '[=11=]';
processMessage(line);
linePos = 0;
}
else
{
line[linePos] = UART_Buffer[uartBufPos];
linePos++;
if (linePos == UART_BUFFER_SIZE)
{
linePos = 0;
}
}
uartBufPos++;
if (uartBufPos == UART_BUFFER_SIZE)
{
uartBufPos = 0;
}
}
if (UART_Out_Cnt > 0)
{
HAL_UART_Transmit(&huart2, (uint8_t *)UART_Out_Buffer, UART_Out_Cnt, 100);
UART_Out_Cnt = 0;
}
}
跟进答案:我已经这样做了:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
start_processing = true;
isSynced = true;
if (waitreply > 1)
{
waitreply--;
HAL_UART_Receive_DMA(&huart2, DMA_RX_Buffer, DMA_RX_BUFFER_SIZE);
uint16_t uartBufPos = 0;
uint16_t linePos = 0;
while (DMA_RX_Buffer[uartBufPos] != '[=12=]')
{
if (DMA_RX_Buffer[uartBufPos] == '\n')
{
wait_reply = -1;
}
else
{
if (uartBufPos == DMA_RX_BUFFER_SIZE)
{
uartBufPos = 0;
}
uartBufPos++;
}
}
}
}
}
进程状态机:
void process_state_machine()
{
uint8_t timeout = 0;
switch (current_state)
{
case STATE_INIT:
if (wait_reply == 0)
{
// just entered this state, send command
HAL_UART_Transmit(&huart2, "AT+CGSOCKCONT=1,"
"\"IP"
"\","
"\"A1.net"
"\"\r\n",
strlen("AT+CGSOCKCONT=1,"
"\"IP"
"\","
"\"A1.net"
"\"\r\n"),
100);
wait_reply = 2;
}
else
{
// reply, or timeout
if (wait_reply == 1)
{
// timeout, retry
timeout = 0; // this will re-enter this state
}
if (wait_reply == -1)
{
// analyze reply, may be change state
wait_reply = 0;
timeout = 1;
current_state = STATE_CONNECTED;
}
}
break;
这并不容易,但你可以用状态机来做;但该状态机必须至少有两个级别。这是因为当您向调制解调器发送命令时,需要时间;还需要更多时间等待响应。如果您有两个或至少一个用于调制解调器通信的循环缓冲区,则可以使用单个计时器(一个简单的变量)来完成。
状态机被及时调用,比如说每1/100秒。计时器变量称为 waitreply。伪代码是这样的:
statemachine:
if (waitreply > 1):
waitreply--;
read characters from modem (from circular buffer)
is the read message complete? (ends with CR-LF?)
no:
(fall to the rest of the routine)
yes:
is this out-of-band data?
yes:
put it aside and ignore
no:
waitreply = -1;
剩下的部分是switch语句,每个状态一个case。每个州一分为二:
case SEND_AT:
if (waitreply == 0) {
// just entered this state, send command
send command
waitreply = some_timeout
} else {
// reply, or timeout
if (waitreply == 1) {
// timeout, retry
waitreply = 0; // this will re-enter this state
}
if (waitreply == -1) {
// analyze reply, may be change state
waitreply = 0;
STATE = SEND_ATI;
}
}
break;
这只是一个想法,希望对您有所帮助。
===== 评论后编辑 =====
如上面的代码所示,waitreply 变量实现了一个二级状态机。如果 waitreply==0,则没有事务正在进行:可以发送命令;如果 ==-1,则调制解调器的回复已准备就绪,可供当前 "state" 读取;否则,状态机只是在等待。因此,可以将 (waitreply > 0) 的测试移到函数的开头,如果满足,只需提前退出函数。但这似乎并没有什么大的进步。
关于 OP 的问题:
- 是的,定时器每 1/100 秒调用一次状态机。变量 waitreply 初始化为零。
- 没有"thread",这是从main
调用的简单例程
一个C程序框架如下:
int waitreply;
enum blahblah state;
void statemachine(void);
void main(void) {
waitreply = 0; // already zeroed by C runtime
state=ST_SENDAT; // see if the modem is alive
do {
if (timer_expired) {
// 1/100 sec elapsed
statemachine();
start_timer();
}
}
}
void statemachine(void) {
// prologue... modem_replay will contain the reply from modem
switch (state) {
case ST_SENDAT:
if (waitreply == 0) {
// just entered this state, send command
send_to_modem("AT" CR LF);
waitreply = 200; // 2 seconds
} else {
// reply, or timeout
if (waitreply == 1) {
// timeout, retry
waitreply = 0; // this will re-enter this state
}
if (waitreply == -1) {
// analyze reply, may be change state
waitreply = 0;
if (0 == strcmp(modem_reply, "OK"))
STATE = SEND_ATI; // another state, to get info from modem
// else will re-enter this same state, for ever
}
}
break;
} // end of switch
} // func statemachine
我再说一遍,这只是一个提供精细控制的想法,仅此而已。
我正在尝试使用 UART AT COMMAND 4G 模块,并且正在尝试设计一个工作流程图作为它的状态机。 我有一个问题
- 处理传入消息并发送命令
- 如何在彼此之间移动和更改状态?
这是我的初始状态:
#define STATE_INIT 0
#define STATE_CONNECTED 1
#define STATE_DISCONNECTED 2
#define STATE_RETRY_CONNECT 3
#define STATE_FAILURE 4
#define STATE_HTTP_POST 5
#define STATE_HTTP_GET 6
#define STATE_HTTP_POST_RETRY 7
#define STATE_HTTP_POST_SUCCESS 8
#define STATE_HTTP_GET_RETRY 9
#define STATE_HTTP_GET_SUCCESS 10
#define STATE_CHECK_CONNECTIVITY 11
#define SIM_STATUS_ERROR 12
#define SIM_STATUS_READY 13
#define SIM_STATUS_LOCKED 14
#define REG_STATUS_UNREGISTERED 15
#define REG_STATUS_SEARCHING 16
#define REG_STATUS_DENIED 17
#define REG_STATUS_OK 18
#define REG_STATUS_HOME 19
#define REG_STATUS_ROAMING 20
#define REG_STATUS_UNKNOWN 21
uint8_t current_state;
void processMessage(char *msg) {
}
void sendCmd(char *cmd) {
strcpy(UART_Out_Buffer, cmd);
UART_Out_Cnt = strlen(cmd);
}
void Init_State(void) {
current_state = STATE_INIT;
sendCmd("AT+CGSOCKCONT=1,""\"IP""\",""\"A1.net""\"");
sendCmd("AT+CSOCKAUTH=1,1,""\"ppp""\",""\"ppp@a1plus.at""\"");
sendCmd("AT+CHTTPSOPSE=""\"ipdb-eu1.com""\",443""\"");
}
在这里,当我们发送命令时,应处理响应。
void process_uart(void)
{
uint16_t uartBufPos = 0;
char line[UART_BUFFER_SIZE];
line[0] = '[=11=]';
uint16_t linePos = 0;
while (UART_Buffer[uartBufPos] != '[=11=]')
{
if (UART_Buffer[uartBufPos] == '\n')
{
line[linePos] = '[=11=]';
processMessage(line);
linePos = 0;
}
else
{
line[linePos] = UART_Buffer[uartBufPos];
linePos++;
if (linePos == UART_BUFFER_SIZE)
{
linePos = 0;
}
}
uartBufPos++;
if (uartBufPos == UART_BUFFER_SIZE)
{
uartBufPos = 0;
}
}
if (UART_Out_Cnt > 0)
{
HAL_UART_Transmit(&huart2, (uint8_t *)UART_Out_Buffer, UART_Out_Cnt, 100);
UART_Out_Cnt = 0;
}
}
跟进答案:我已经这样做了:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
start_processing = true;
isSynced = true;
if (waitreply > 1)
{
waitreply--;
HAL_UART_Receive_DMA(&huart2, DMA_RX_Buffer, DMA_RX_BUFFER_SIZE);
uint16_t uartBufPos = 0;
uint16_t linePos = 0;
while (DMA_RX_Buffer[uartBufPos] != '[=12=]')
{
if (DMA_RX_Buffer[uartBufPos] == '\n')
{
wait_reply = -1;
}
else
{
if (uartBufPos == DMA_RX_BUFFER_SIZE)
{
uartBufPos = 0;
}
uartBufPos++;
}
}
}
}
}
进程状态机:
void process_state_machine()
{
uint8_t timeout = 0;
switch (current_state)
{
case STATE_INIT:
if (wait_reply == 0)
{
// just entered this state, send command
HAL_UART_Transmit(&huart2, "AT+CGSOCKCONT=1,"
"\"IP"
"\","
"\"A1.net"
"\"\r\n",
strlen("AT+CGSOCKCONT=1,"
"\"IP"
"\","
"\"A1.net"
"\"\r\n"),
100);
wait_reply = 2;
}
else
{
// reply, or timeout
if (wait_reply == 1)
{
// timeout, retry
timeout = 0; // this will re-enter this state
}
if (wait_reply == -1)
{
// analyze reply, may be change state
wait_reply = 0;
timeout = 1;
current_state = STATE_CONNECTED;
}
}
break;
这并不容易,但你可以用状态机来做;但该状态机必须至少有两个级别。这是因为当您向调制解调器发送命令时,需要时间;还需要更多时间等待响应。如果您有两个或至少一个用于调制解调器通信的循环缓冲区,则可以使用单个计时器(一个简单的变量)来完成。
状态机被及时调用,比如说每1/100秒。计时器变量称为 waitreply。伪代码是这样的:
statemachine:
if (waitreply > 1):
waitreply--;
read characters from modem (from circular buffer)
is the read message complete? (ends with CR-LF?)
no:
(fall to the rest of the routine)
yes:
is this out-of-band data?
yes:
put it aside and ignore
no:
waitreply = -1;
剩下的部分是switch语句,每个状态一个case。每个州一分为二:
case SEND_AT:
if (waitreply == 0) {
// just entered this state, send command
send command
waitreply = some_timeout
} else {
// reply, or timeout
if (waitreply == 1) {
// timeout, retry
waitreply = 0; // this will re-enter this state
}
if (waitreply == -1) {
// analyze reply, may be change state
waitreply = 0;
STATE = SEND_ATI;
}
}
break;
这只是一个想法,希望对您有所帮助。
===== 评论后编辑 =====
如上面的代码所示,waitreply 变量实现了一个二级状态机。如果 waitreply==0,则没有事务正在进行:可以发送命令;如果 ==-1,则调制解调器的回复已准备就绪,可供当前 "state" 读取;否则,状态机只是在等待。因此,可以将 (waitreply > 0) 的测试移到函数的开头,如果满足,只需提前退出函数。但这似乎并没有什么大的进步。
关于 OP 的问题:
- 是的,定时器每 1/100 秒调用一次状态机。变量 waitreply 初始化为零。
- 没有"thread",这是从main 调用的简单例程
一个C程序框架如下:
int waitreply;
enum blahblah state;
void statemachine(void);
void main(void) {
waitreply = 0; // already zeroed by C runtime
state=ST_SENDAT; // see if the modem is alive
do {
if (timer_expired) {
// 1/100 sec elapsed
statemachine();
start_timer();
}
}
}
void statemachine(void) {
// prologue... modem_replay will contain the reply from modem
switch (state) {
case ST_SENDAT:
if (waitreply == 0) {
// just entered this state, send command
send_to_modem("AT" CR LF);
waitreply = 200; // 2 seconds
} else {
// reply, or timeout
if (waitreply == 1) {
// timeout, retry
waitreply = 0; // this will re-enter this state
}
if (waitreply == -1) {
// analyze reply, may be change state
waitreply = 0;
if (0 == strcmp(modem_reply, "OK"))
STATE = SEND_ATI; // another state, to get info from modem
// else will re-enter this same state, for ever
}
}
break;
} // end of switch
} // func statemachine
我再说一遍,这只是一个提供精细控制的想法,仅此而已。