4G/LTE - AT Command

 

 

 

AT Command in Windows Power Shell

 

One thing on old Windows that I liked very much but not available on recent windows is HyperTerminal.  It was so usefull for automation with serial port e.g, AT Command. There are several alternatives you can use Putty, but somehow I don't feel much intimacy with this tool.

Luckily I found an handy alternative (at least to me) last night which comes out with Windows by default. It is a command line program called "PowerShell". It allows you to do a kind of programming like .NET framework directly in command line. I don't get any deeper into the programming aspect of PowerShell. You only need to know about 3~4 lines of syntax to use 'AT' command and I will just put down the list of frequently used AT command in the form of PowerShell command so that you can just copy and paste as you need.

I would start with the basic setup and small set of test, and keep extending the list as I get along.

Followings are the topics :

Basic Setup

Four members of one .NET object do everything on this page: the constructor, open, WriteLine and Close. The session below is those four in order, and the only part that changes from one machine to the next is the first line.

The basic usage is as shown below. I wouldn't explain too much on this and I think you can intuitively understand the meaning.

The whole session, from opening the port to closing it.

PS C:\> $port= new-Object System.IO.Ports.SerialPort COM19,9600,None,8,one // set COM port as you need
PS C:\> $port.open()  // Open the Serial Port
PS C:\> $port.WriteLine("Any AT Command `r")   // `r is the most important. I spent quite a while to figure this out.
PS C:\> $port.WriteLine("Any AT Command `r")   // (`) is the character called grave-accent. Which is on the same key
                                                                   // as '~' in my keyboard. 
... 
PS C:\> $port.Close()  // After you are done with AT command, I would suggest you to close it.  

Five values go into the constructor, positionally, and they have to match the device on the other end. The red part of the first line is the part to change.

Position In the example What it is
1 COM19 The port name. Windows numbers these, and the number is not stable across reboots or USB sockets.
2 9600 The speed in bits per second.
3 None Parity.
4 8 Data bits.
5 one Stop bits. It is a word rather than a digit because it names a value of the StopBits enumeration, and 1 on its own will not do.

The port name is the value that changes most often and the easiest to get wrong. PowerShell will list the ones that exist, which is faster than opening Device Manager.

List the serial ports Windows can see.

[System.IO.Ports.SerialPort]::getportnames()

A modem usually presents more than one port. One of them takes AT commands, another carries the data connection, and a third may carry a debug log. Only one of them will answer AT with OK, so trying each in turn is the normal way to find it.

  • Five positional values : port, speed, parity, data bits, stop bits.
  • one is a name, not a number : it is a StopBits enumeration value.
  • Port numbers move : a different USB socket can produce a different COM number.
  • Several ports, one answer : only the AT port replies to AT.

Why the backtick r matters

The note above calls `r the most important part of the line, and it is worth saying why, because a warning on its own does not tell you what to check. Two separate things have to line up, and only one of them is PowerShell.

The first is the modem's side. A command line is not finished until a termination character arrives, and the character is not fixed by magic: it lives in a register. 27.007 clause 4.3 lists the V.250 commands that a UE has to implement, and the first row is S3, the command line termination character, with a mandatory default setting of IRA 13. IRA 13 is carriage return. Until the modem sees one, it is still listening.

The second is PowerShell's side. PowerShell uses the backtick as its escape character, where most languages use a backslash, so `r is how a carriage return is written into a string. WriteLine does append a terminator of its own, but that terminator is PowerShell's idea of a line ending rather than the modem's, so the explicit `r is what guarantees the byte the modem is waiting for.

One detail decides whether the escape happens at all, and it catches people who copy a line and then tidy the quotes.

Written as What actually goes down the wire
"AT`r" AT followed by carriage return. Double quotes process the escape.
'AT`r' AT followed by a literal backtick and a literal letter r. Single quotes do not process escapes, so the modem never sees a terminator and never answers.

The register can be changed with ATS3=n, and there is almost never a reason to. Every tool that talks to the modem assumes 13, so moving it breaks them all at once.

  • The modem waits for carriage return : 27.007 lists S3 with a mandatory default of IRA 13.
  • The backtick is PowerShell's escape character : not the backslash.
  • Double quotes escape, single quotes do not : this is the usual reason a copied line goes quiet.
  • Leave S3 alone : every other tool on the port assumes the default.

Reading the answer back

Everything above sends. Nothing above reads, and a modem answers every command whether or not anybody is listening. Three more members of the same object turn this from a write-only interface into a usable one.

Call What it does
$port.ReadExisting() Returns whatever has arrived in the buffer so far and returns immediately. If the modem has not answered yet, it returns an empty string rather than waiting.
$port.ReadLine() Waits until a terminator arrives and returns one line. It blocks, so it needs the timeout below.
$port.ReadTimeout = 2000 Milliseconds. Without it a blocking read on a silent port waits for ever, and the console is stuck.

The crude pattern is also the reliable one. Write, wait a moment for the modem to answer, then take whatever arrived.

Send one command and read the answer.

$port.ReadTimeout = 2000
$port.WriteLine("AT+CGMI`r")
Start-Sleep -Milliseconds 300
$port.ReadExisting()

The sleep is doing real work. A modem that has to reach the network before it can answer will take longer than one that answers from memory, and +COPS=? can take the better part of a minute. Read too early and you get an empty string, conclude the command failed, and move on.

Two things in that answer will surprise you the first time. The modem echoes your own command back before its reply, because 27.007 clause 4.3 lists the V.250 command E with a recommended default of 1, meaning the TA echoes commands back. Send ATE0 once at the start of a session and the echo stops. The reply itself also arrives wrapped in carriage return and line feed, so a comparison against a bare string will fail unless it is trimmed.

Set the error reporting at the same time. An unadorned ERROR carries no diagnosis, and AT+CMEE=2 replaces it with a readable cause. The AT Command page covers what that does and why the default is unhelpful.

  • ReadExisting never waits : so it needs a sleep in front of it.
  • ReadLine waits for ever without ReadTimeout : set the timeout first.
  • The first thing you read is your own command : unless ATE0 has been sent.
  • Trim before comparing : the answer carries its own carriage return and line feed.

Copy this and past it into PowerShell Window when you want to open a Serial Port

The lines below are the same ones from the section above with the prompt taken off, so they paste into a window cleanly. Every session starts with them, so they are worth keeping somewhere you can reach quickly.

Before you copy and past, modify COM port setting according to the device attached to the control PC.

Paste this to open the port.

$port= new-Object System.IO.Ports.SerialPort COM19,9600,None,8,one 
$port.open()

Two lines, and the second one is the one that can fail. Opening a port takes exclusive ownership of it, so a terminal left running somewhere else will block it. The exception that comes back does say so, though not in those words.

Pair it with the closing line whenever you can. A port held by a console that was closed without releasing it stays held until the process ends, and the next attempt fails for a reason that has nothing to do with the modem.

Paste this when you are finished.

$port.Close()
  • A port has one owner : and opening it is what claims ownership.
  • Close it when you finish : or the next session cannot open it.
  • Change the red part first : the five values must match the device.

In practice the paste is worth two more lines than the original. Echo off and verbose errors are both settings rather than actions, so they last for the whole session. Set them at the start and you never have to wonder later why a command answered with its own text, or with a bare ERROR.

A complete opening sequence, with the two settings worth having.

$port= new-Object System.IO.Ports.SerialPort COM19,9600,None,8,one
$port.open()
$port.ReadTimeout = 2000
$port.WriteLine("ATE0`r")
$port.WriteLine("AT+CMEE=2`r")

The last two lines are described further down and on the AT Command page. ATE0 stops the modem repeating your command back at you, and AT+CMEE=2 turns a bare ERROR into a cause you can read.

Power Cycle UE

A modem has no power button, so a restart has to be asked for over the same port you are already using. The command below does it, and the parameter that turns it into a restart rather than a setting is the second one.

Reset the MT and bring it back to full functionality.

$port.WriteLine("AT+CFUN=1,1`r")

There are several different form of +CFUN and modify the command depending on your need. Also, this CFUN does not seems to automatically set Airplane Mode to be Off.  

The note above is right that there are several forms, so here is the list. 27.007 clause 8.2 defines +CFUN=<fun>[,<rst>], and the first parameter chooses how much of the modem is powered.

<fun> What it selects
0 Minimum functionality, where the least power is drawn.
1 Full functionality. Turns on the transmit and receive circuits for every supported radio technology.
2 Transmit circuits off only.
3 Receive circuits off only.
4 Both transmit and receive off. This is the pair that matches what flight mode does to the radio.
5 to 127 Reserved for manufacturers, as intermediate states between full and minimum.
128 Full functionality with the radio technologies chosen by +CSRA.
129 Prepare for shutdown. After this only <fun>=0 is accepted, and every other value returns ERROR.

The second parameter is the one that makes this a power cycle rather than a setting. <rst>=0 does not reset the MT before applying the level. 27.007 says that is always the default when the parameter is left out. <rst>=1 resets it first. So AT+CFUN=1,1 means reset, then come back fully on, while a bare AT+CFUN=1 only raises the power level of whatever is already running.

The observation about flight mode has a cause worth knowing. 27.007 attaches a note to this command saying it is manufacturer specific whether +CFUN affects network registration at all, and that +COPS is the command that forces registration or deregistration. The handset's own flight mode switch is a setting in the application processor, and nothing in 27.007 reaches it. The radio can be on while the icon still says otherwise.

One more line from the same clause is worth reading before relying on this. 27.007 marks the implementation of +CFUN as optional, so a modem is entitled not to have it. AT+CFUN=? answers that question in one line.

  • <fun>=4 is the flight mode pair : both directions off, radio silent.
  • <rst>=1 is what resets : and omitting it means no reset at all.
  • Registration is not guaranteed to follow : 27.007 leaves that manufacturer specific, and names +COPS as the command that forces it.
  • The command itself is optional : check with AT+CFUN=? before depending on it.

Answering the Voice Call

Answering a call from the terminal is one line. That line either works or does nothing, depending on which domain the call arrived on. The note underneath it is the more useful half of this section.

Answer an incoming call.

$port.WriteLine("ATA`r")

This doesn't seem to work in case of VoLTE (PS Call).

That one line is a V.250 command rather than a 3GPP one, and the difference explains the note above about VoLTE. 27.007 clause 6 covers call control and clause 6.2 borrows the V.250 dial command D, but it never defines an answer command of its own. Answering stays where V.250 left it, in the circuit switched world the basic command set was written for.

3GPP does treat the two kinds of call as different at this interface, at least when originating one. Clause 6.4A defines +CVMOD, which selects CS_ONLY, VOIP_ONLY, CS_PREFERRED or VOIP_PREFERRED, and the note beneath it says that choice is what decides whether ATD places a circuit switched call or a VoIP one. No matching selector exists for answering.

So the behaviour on this page is consistent with a modem that maps ATA onto circuit switched call control only. An incoming IMS session is handled by the IMS stack, and on many modules it never reaches the AT interface as a call at all.

Two commands are worth trying before concluding that nothing can be done. 27.007 clause 8.74 defines +CLCCS, a list of current calls, and clause 8.73 defines +CMCCS, a monitor of current calls. If either of them shows the incoming IMS session, the modem knows about it and the question becomes which command accepts it. If neither shows anything while the handset is ringing, the AT interface is not the place to answer it.

  • ATA belongs to V.250 : 27.007 never defines an answer command.
  • Originating is selectable, answering is not : +CVMOD steers ATD and has no counterpart here.
  • An IMS call may never reach the AT interface : which is not a fault in the command.
  • Check with +CLCCS or +CMCCS : clauses 8.74 and 8.73, before giving up.

When nothing comes back

A serial port fails in a small number of ways, and the symptom usually names the cause. The table is ordered by how often each one happens rather than by how serious it is.

What you see Usual cause What to do
$port.open() throws an exception Another program already holds the port. A COM port has one owner at a time. Close the other terminal or tool, then open it again.
The write is accepted and nothing ever comes back No carriage return reached the modem, or the read ran before the answer arrived. Check the quoting around `r, then sleep before reading.
The answer is your own command Echo is on, which is the V.250 default. Send ATE0 once per session.
ERROR and nothing else Error reporting is at its default setting. Send AT+CMEE=2 first.
Characters arrive but they are unreadable The speed, parity, data bits or stop bits do not match the device. Correct the five constructor values.
The constructor fails on the port name The port is numbered differently from what you assumed. List them with [System.IO.Ports.SerialPort]::getportnames().

One habit prevents most of these. Close the port when you finish, as the note further up this page says, because a port left open by a crashed console stays owned until the process ends. If $port.open() throws and no terminal is visibly running, the previous PowerShell window is the first place to look.

  • One owner per port : and a crashed console still counts as the owner.
  • Silence means the terminator or the timing : not usually the command.
  • Echo and error reporting are both defaults worth changing : ATE0 and AT+CMEE=2.
  • Unreadable text is a framing mismatch : not a modem fault.

Reference

The commands on this page are ordinary AT commands, so the documents behind them are the same ones the AT Command page uses. The PowerShell half comes from the .NET class that the first line builds.

  • 3GPP 27.007 - AT command set for User Equipment, v19.6.0. Clause 4.3 lists the V.250 commands a UE must implement, including S3 and E. Clause 8.2 is +CFUN and its values. Clause 6.4A is +CVMOD.
  • ITU-T V.250 - the basic command set, which owns ATA, ATE and the S registers.
  • System.IO.Ports.SerialPort - the .NET class behind new-Object System.IO.Ports.SerialPort, and the reference for every member used here.
  • AT Command - the same commands without the PowerShell wrapper, including what +CMEE does and how +CRSM reads the SIM.